source: sasview/inversionview/src/sans/perspectives/pr/inversion_state.py @ 29c15be

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 29c15be was 11f5196, checked in by Jae Cho <jhjcho@…>, 13 years ago
  • Property mode set to 100644
File size: 19.6 KB
Line 
1
2################################################################################
3#This software was developed by the University of Tennessee as part of the
4#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
5#project funded by the US National Science Foundation.
6#
7#See the license text in license.txt
8#
9#copyright 2009, University of Tennessee
10################################################################################
11
12
13import time
14import os
15import sys
16import logging
17from xml.dom.minidom import parse
18from lxml import etree
19from sans.guiframe.dataFitting import Data1D
20from DataLoader.readers.cansas_reader import Reader as CansasReader
21from DataLoader.readers.cansas_reader import get_content
22
23PRNODE_NAME = 'pr_inversion'
24CANSAS_NS = "cansas1d/1.0"
25
26# Translation of names between stored and object data
27## List of P(r) inversion inputs
28in_list =  [["nterms",       "nfunc"],
29           ["d_max",        "d_max"],
30           ["alpha",        "alpha"],
31           ["slit_width",   "width"],
32           ["slit_height",  "height"],
33           ["qmin",         "qmin"],
34           ["qmax",         "qmax"]]                     
35
36## List of P(r) inversion outputs
37out_list = [["elapsed", "elapsed"],
38           ["rg",      "rg"],
39           ["iq0",     "iq0"],
40           ["bck",     "bck"],
41           ["chi2",    "chi2"],
42           ["osc",     "osc"],
43           ["pos",     "pos"],
44           ["pos_err", "pos_err"],
45           ["alpha_estimate", "alpha_estimate"],
46           ["nterms_estimate", "nterms_estimate"]]
47
48class InversionState(object):
49    """
50    Class to hold the state information of the InversionControl panel.
51    """
52    def __init__(self):
53        """
54        Default values
55        """
56        # Input
57        self.file  = None
58        self.estimate_bck = False
59        self.timestamp = time.time()
60       
61        # Inversion parameters
62        self.nfunc = None
63        self.d_max = None
64        self.alpha = None
65       
66        # Slit parameters
67        self.height = None
68        self.width  = None
69       
70        # Q range
71        self.qmin  = None
72        self.qmax  = None
73       
74        # Outputs
75        self.elapsed = None
76        self.rg    = None
77        self.iq0   = None
78        self.bck   = None
79        self.chi2  = None
80        self.osc   = None
81        self.pos   = None
82        self.pos_err = None
83       
84        # Estimates
85        self.alpha_estimate = None
86        self.nterms_estimate = None
87       
88        # Data
89        self.q       = None
90        self.iq_obs  = None
91        self.iq_calc = None
92       
93        # Coefficients
94        self.coefficients = None
95        self.covariance = None
96   
97    def __str__(self):
98        """
99        Pretty print
100       
101        :return: string representing the state
102       
103        """
104        state  = "File:         %s\n" % self.file
105        state += "Timestamp:    %s\n" % self.timestamp
106        state += "Estimate bck: %s\n" % str(self.estimate_bck)
107        state += "No. terms:    %s\n" % str(self.nfunc)
108        state += "D_max:        %s\n" % str(self.d_max)
109        state += "Alpha:        %s\n" % str(self.alpha)
110       
111        state += "Slit height:  %s\n" % str(self.height)
112        state += "Slit width:   %s\n" % str(self.width)
113       
114        state += "Qmin:         %s\n" % str(self.qmin)
115        state += "Qmax:         %s\n" % str(self.qmax)
116       
117        state += "\nEstimates:\n"
118        state += "  Alpha:      %s\n" % str(self.alpha_estimate)
119        state += "  Nterms:     %s\n" % str(self.nterms_estimate)
120       
121        state += "\nOutputs:\n"
122        state += "  Elapsed:    %s\n" % str(self.elapsed)
123        state += "  Rg:         %s\n" % str(self.rg)
124        state += "  I(q=0):     %s\n" % str(self.iq0)
125        state += "  Bck:        %s\n" % str(self.bck)
126        state += "  Chi^2:      %s\n" % str(self.chi2)
127        state += "  Oscillation:%s\n" % str(self.osc)
128        state += "  Positive:   %s\n" % str(self.pos)
129        state += "  1-sigma pos:%s\n" % str(self.pos_err)
130       
131        return state
132       
133    def toXML(self, file="pr_state.prv", doc=None, entry_node=None):
134        """
135        Writes the state of the InversionControl panel to file, as XML.
136       
137        Compatible with standalone writing, or appending to an
138        already existing XML document. In that case, the XML document
139        is required. An optional entry node in the XML document
140        may also be given.
141       
142        :param file: file to write to
143        :param doc: XML document object [optional]
144        :param entry_node: XML node within the XML document at which
145            we will append the data [optional]
146       
147        """
148        from xml.dom.minidom import getDOMImplementation
149
150        # Check whether we have to write a standalone XML file
151        if doc is None:
152            impl = getDOMImplementation()
153       
154            doc_type = impl.createDocumentType(PRNODE_NAME, "1.0", "1.0")     
155       
156            newdoc = impl.createDocument(None, PRNODE_NAME, doc_type)
157            top_element = newdoc.documentElement
158        else:
159            # We are appending to an existing document
160            newdoc = doc
161            top_element = newdoc.createElement(PRNODE_NAME)
162            if entry_node is None:
163                newdoc.documentElement.appendChild(top_element)
164            else:
165                entry_node.appendChild(top_element)
166           
167        attr = newdoc.createAttribute("version")
168        attr.nodeValue = '1.0'
169        top_element.setAttributeNode(attr)
170       
171        # File name
172        element = newdoc.createElement("filename")
173        if self.file is not None:
174            element.appendChild(newdoc.createTextNode(str(self.file)))
175        else:
176            element.appendChild(newdoc.createTextNode(str(file)))
177        top_element.appendChild(element)
178       
179        element = newdoc.createElement("timestamp")
180        element.appendChild(newdoc.createTextNode(time.ctime(self.timestamp)))
181        attr = newdoc.createAttribute("epoch")
182        attr.nodeValue = str(self.timestamp)
183        element.setAttributeNode(attr)
184        top_element.appendChild(element)
185       
186        # Inputs
187        inputs = newdoc.createElement("inputs")
188        top_element.appendChild(inputs)
189       
190        for item in in_list:
191            element = newdoc.createElement(item[0])
192            cmd = "element.appendChild(newdoc.createTextNode(str(self.%s)))"
193            exec  cmd % item[1]
194            inputs.appendChild(element)
195             
196        # Outputs
197        outputs = newdoc.createElement("outputs")
198        top_element.appendChild(outputs)
199       
200        for item in out_list:
201            element = newdoc.createElement(item[0])
202            cmd = "element.appendChild(newdoc.createTextNode(str(self.%s)))"
203            exec  cmd % item[1]
204            outputs.appendChild(element)
205                   
206        # Save output coefficients and its covariance matrix
207        element = newdoc.createElement("coefficients")
208        element.appendChild(newdoc.createTextNode(str(self.coefficients)))
209        outputs.appendChild(element)
210        element = newdoc.createElement("covariance")
211        element.appendChild(newdoc.createTextNode(str(self.covariance)))
212        outputs.appendChild(element)
213                   
214        # Save the file
215        if doc is None:
216            fd = open(file, 'w')
217            fd.write(newdoc.toprettyxml())
218            fd.close()
219            return None
220        else:
221            return newdoc.toprettyxml()
222
223    def fromXML(self, file=None, node=None):
224        """
225        Load a P(r) inversion state from a file
226       
227        :param file: .prv file
228        :param node: node of a XML document to read from
229       
230        """
231        if file is not None:
232            msg = "InversionState no longer supports non-CanSAS"
233            msg += " format for P(r) files"
234            raise RuntimeError, msg
235           
236        if node.get('version') and node.get('version') == '1.0':
237           
238            # Get file name
239            entry = get_content('ns:filename', node)
240            if entry is not None:
241                self.file = entry.text.strip()
242           
243            # Get time stamp
244            entry = get_content('ns:timestamp', node)
245            if entry is not None and entry.get('epoch'):
246                try:
247                    self.timestamp = float(entry.get('epoch'))
248                except:
249                    msg = "InversionState.fromXML: Could not read "
250                    msg += "timestamp\n %s" % sys.exc_value
251                    logging.error(msg)
252           
253            # Parse inversion inputs
254            entry = get_content('ns:inputs', node)
255            if entry is not None:
256                for item in in_list:
257                    input_field = get_content('ns:%s' % item[0], entry)
258                    if input_field is not None:
259                        try:
260                            cmd = 'self.%s = float(input_field.text.strip())' 
261                            exec  cmd % item[1]
262                        except:
263                            exec 'self.%s = None' % item[1]
264                input_field = get_content('ns:estimate_bck', entry)
265                if input_field is not None:
266                    try:
267                        self.estimate_bck = input_field.text.strip()=='True'
268                    except:
269                        self.estimate_bck = False
270                   
271            # Parse inversion outputs
272            entry = get_content('ns:outputs', node)
273            if entry is not None:
274                # Output parameters (scalars)
275                for item in out_list:
276                    input_field = get_content('ns:%s' % item[0], entry)
277                    if input_field is not None:
278                        try:
279                            cmd = 'self.%s = float(input_field.text.strip())'
280                            exec  cmd % item[1]
281                        except:
282                            exec 'self.%s = None' % item[1]
283           
284                # Look for coefficients
285                # Format is [value, value, value, value]
286                coeff = get_content('ns:coefficients', entry)
287                if coeff is not None:
288                    # Remove brackets
289                    c_values = coeff.text.strip().replace('[','')
290                    c_values = c_values.replace(']','')
291                    toks = c_values.split()
292                    self.coefficients = []
293                    for c in toks:
294                        try:
295                            self.coefficients.append(float(c))
296                        except:
297                            # Bad data, skip. We will count the number of
298                            # coefficients at the very end and deal with
299                            # inconsistencies then.
300                            pass
301                    # Sanity check
302                    if not len(self.coefficients) == self.nfunc:
303                        # Inconsistent number of coefficients.
304                        # Don't keep the data.
305                        err_msg = "InversionState.fromXML: inconsistant "
306                        err_msg += "number of coefficients: "
307                        err_msg += "%d %d" % (len(self.coefficients),
308                                              self.nfunc)
309                        logging.error(err_msg)
310                        self.coefficients = None
311               
312                # Look for covariance matrix
313                # Format is [ [value, value], [value, value] ]
314                coeff = get_content('ns:covariance', entry)
315                if coeff is not None:
316                    # Parse rows
317                    rows = coeff.text.strip().split('[')
318                    self.covariance = []
319                    for row in rows:
320                        row = row.strip()
321                        if len(row) == 0: continue
322                        # Remove end bracket
323                        row = row.replace(']','')
324                        c_values = row.split()
325                        cov_row = []
326                        for c in c_values:
327                            try:
328                                cov_row.append(float(c))
329                            except:
330                                # Bad data, skip. We will count the number of
331                                # coefficients at the very end and deal with
332                                # inconsistencies then.
333                                pass
334                        # Sanity check: check the number of entries in the row
335                        if len(cov_row) == self.nfunc:
336                            self.covariance.append(cov_row)
337                    # Sanity check: check the number of rows in the covariance
338                    # matrix
339                    if not len(self.covariance) == self.nfunc:
340                        # Inconsistent dimensions of the covariance matrix.
341                        # Don't keep the data.
342                        err_msg = "InversionState.fromXML: "
343                        err_msg += "inconsistant dimensions of the "
344                        err_msg += " covariance matrix: "
345                        err_msg += "%d %d" % (len(self.covariance), self.nfunc)
346                        logging.error(err_msg)
347                        self.covariance = None
348   
349class Reader(CansasReader):
350    """
351    Class to load a .prv P(r) inversion file
352    """
353    ## File type
354    type_name = "P(r)"
355   
356    ## Wildcards
357    type = ["P(r) files (*.prv)|*.prv",
358            "SANSView files (*.svs)|*.svs"]
359    ## List of allowed extensions
360    ext = ['.prv', '.PRV', '.svs', '.SVS'] 
361   
362    def __init__(self, call_back, cansas=True):
363        """
364        Initialize the call-back method to be called
365        after we load a file
366       
367        :param call_back: call-back method
368        :param cansas:  True = files will be written/read in CanSAS format
369                        False = write CanSAS format
370           
371        """
372        ## Call back method to be executed after a file is read
373        self.call_back = call_back
374        ## CanSAS format flag
375        self.cansas = cansas
376        self.state = None
377       
378    def read(self, path):
379        """
380        Load a new P(r) inversion state from file
381       
382        :param path: file path
383       
384        :return: None
385       
386        """
387        if self.cansas==True:
388            return self._read_cansas(path)
389        else:
390            return self._read_standalone(path)
391       
392    def _read_standalone(self, path):
393        """
394        Load a new P(r) inversion state from file.
395        The P(r) node is assumed to be the top element.
396       
397        :param path: file path
398       
399        :return: None
400       
401        """
402        # Read the new state from file
403        state = InversionState()
404        state.fromXML(file=path)
405       
406        # Call back to post the new state
407        self.state = state
408        #self.call_back(state)
409        return None
410   
411    def _parse_prstate(self, entry):
412        """
413        Read a p(r) inversion result from an XML node
414       
415        :param entry: XML node to read from
416       
417        :return: InversionState object
418       
419        """
420        state = None
421       
422        # Locate the P(r) node
423        try:
424            nodes = entry.xpath('ns:%s' % PRNODE_NAME,
425                                namespaces={'ns': CANSAS_NS})
426            if nodes !=[]:
427                # Create an empty state
428                state =  InversionState()
429                state.fromXML(node=nodes[0])
430        except:
431            msg = "XML document does not contain P(r) "
432            msg += "information.\n %s" % sys.exc_value
433            logging.info(msg)
434           
435        return state
436   
437    def _read_cansas(self, path):
438        """
439        Load data and P(r) information from a CanSAS XML file.
440       
441        :param path: file path
442       
443        :return: Data1D object if a single SASentry was found,
444                    or a list of Data1D objects if multiple entries were found,
445                    or None of nothing was found
446                   
447        :raise RuntimeError: when the file can't be opened
448        :raise ValueError: when the length of the data vectors are inconsistent
449       
450        """
451        output = []
452       
453        if os.path.isfile(path):
454            basename  = os.path.basename(path)
455            root, extension = os.path.splitext(basename)
456            #TODO: eventually remove the check for .xml once
457            # the P(r) writer/reader is truly complete.
458            if  extension.lower() in self.ext or extension.lower() == '.xml':
459               
460                tree = etree.parse(path, parser=etree.ETCompatXMLParser())
461                # Check the format version number
462                # Specifying the namespace will take care of the file
463                #format version
464                root = tree.getroot()
465               
466                entry_list = root.xpath('/ns:SASroot/ns:SASentry',
467                                        namespaces={'ns': CANSAS_NS})
468
469                for entry in entry_list:
470                    sas_entry = self._parse_entry(entry)
471                    prstate = self._parse_prstate(entry)
472                    #prstate could be None when .svs file is loaded
473                    #in this case, skip appending to output
474                    if prstate != None:
475                        sas_entry.meta_data['prstate'] = prstate
476                        sas_entry.filename = prstate.file
477                        output.append(sas_entry)
478        else:
479            raise RuntimeError, "%s is not a file" % path
480       
481        # Return output consistent with the loader's api
482        if len(output) == 0:
483            return None
484        elif len(output) == 1:
485            # Call back to post the new state
486            self.call_back(output[0].meta_data['prstate'], datainfo = output[0])
487            #self.state = output[0].meta_data['prstate']
488            return output[0]
489        else:
490            return output               
491   
492   
493    def write(self, filename, datainfo=None, prstate=None):
494        """
495        Write the content of a Data1D as a CanSAS XML file
496       
497        :param filename: name of the file to write
498        :param datainfo: Data1D object
499        :param prstate: InversionState object
500       
501        """
502        # Sanity check
503        if self.cansas == True:
504            doc =self.write_toXML(datainfo, prstate)       
505            # Write the XML document
506            fd = open(filename, 'w')
507            fd.write(doc.toprettyxml())
508            fd.close()
509        else:
510            prstate.toXML(file=filename)
511       
512    def write_toXML(self, datainfo=None, state=None):
513        """
514        Write toXML, a helper for write()
515       
516        : return: xml doc
517        """
518        if datainfo is None:
519            datainfo = Data1D(x=[], y=[])   
520        elif not issubclass(datainfo.__class__, Data1D):
521            msg = "The cansas writer expects a Data1D "
522            msg += "instance: %s" % str(datainfo.__class__.__name__)
523            raise RuntimeError, msg
524   
525        # Create basic XML document
526        doc, sasentry = self._to_xml_doc(datainfo)
527   
528        # Add the invariant information to the XML document
529        if state is not None:
530            state.toXML(doc=doc, entry_node=sasentry)
531           
532        return doc
533   
Note: See TracBrowser for help on using the repository browser.