source: sasview/prview/perspectives/pr/inversion_state.py @ e733619

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 e733619 was 302510b, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

prview: minor change to load saved inversions as a separate (time-stamped) problem and avoid confusion with data set that was previously loaded.

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