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

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

prview: extending the module to add flexibility necessary for SansView?. Adding compatibility with CanSAS format when saving a P(r) fit result to file.

  • Property mode set to 100644
File size: 16.5 KB
Line 
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
11import logging
12import DataLoader
13from DataLoader.readers.cansas_reader import Reader as CansasReader
14
15PRNODE_NAME = 'pr_inversion'
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
82   
83    def __str__(self):
84        """
85            Pretty print
86           
87            @return: string representing the state
88        """
89        state  = "File:         %s\n" % self.file
90        state += "Timestamp:    %s\n" % self.timestamp
91        state += "Estimate bck: %s\n" % str(self.estimate_bck)
92        state += "No. terms:    %s\n" % str(self.nfunc)
93        state += "D_max:        %s\n" % str(self.d_max)
94        state += "Alpha:        %s\n" % str(self.alpha)
95       
96        state += "Slit height:  %s\n" % str(self.height)
97        state += "Slit width:   %s\n" % str(self.width)
98       
99        state += "Qmin:         %s\n" % str(self.qmin)
100        state += "Qmax:         %s\n" % str(self.qmax)
101       
102        state += "\nEstimates:\n"
103        state += "  Alpha:      %s\n" % str(self.alpha_estimate)
104        state += "  Nterms:     %s\n" % str(self.nterms_estimate)
105       
106        state += "\nOutputs:\n"
107        state += "  Elapsed:    %s\n" % str(self.elapsed)
108        state += "  Rg:         %s\n" % str(self.rg)
109        state += "  I(q=0):     %s\n" % str(self.iq0)
110        state += "  Bck:        %s\n" % str(self.bck)
111        state += "  Chi^2:      %s\n" % str(self.chi2)
112        state += "  Oscillation:%s\n" % str(self.osc)
113        state += "  Positive:   %s\n" % str(self.pos)
114        state += "  1-sigma pos:%s\n" % str(self.pos_err)
115       
116        return state
117       
118    def toXML(self, file="pr_state.prv", doc=None, entry_node=None):
119        """
120            Writes the state of the InversionControl panel to file, as XML.
121           
122            Compatible with standalone writing, or appending to an
123            already existing XML document. In that case, the XML document
124            is required. An optional entry node in the XML document may also be given.
125           
126            @param file: file to write to
127            @param doc: XML document object [optional]
128            @param entry_node: XML node within the XML document at which we will append the data [optional]
129        """
130        from xml.dom.minidom import getDOMImplementation
131
132        # Check whether we have to write a standalone XML file
133        if doc is None:
134            impl = getDOMImplementation()
135       
136            doc_type = impl.createDocumentType(PRNODE_NAME, "1.0", "1.0")     
137       
138            newdoc = impl.createDocument(None, PRNODE_NAME, doc_type)
139            top_element = newdoc.documentElement
140        else:
141            # We are appending to an existing document
142            newdoc = doc
143            top_element = newdoc.createElement(PRNODE_NAME)
144            if entry_node is None:
145                newdoc.documentElement.appendChild(top_element)
146            else:
147                entry_node.appendChild(top_element)
148           
149        attr = newdoc.createAttribute("version")
150        attr.nodeValue = '1.0'
151        top_element.setAttributeNode(attr)
152       
153        # File name
154        if self.file is not None:
155            element = newdoc.createElement("filename")
156            element.appendChild(newdoc.createTextNode(str(self.file)))
157            top_element.appendChild(element)
158       
159        element = newdoc.createElement("timestamp")
160        element.appendChild(newdoc.createTextNode(time.ctime(self.timestamp)))
161        attr = newdoc.createAttribute("epoch")
162        attr.nodeValue = str(self.timestamp)
163        element.setAttributeNode(attr)
164        top_element.appendChild(element)
165       
166        # Inputs
167        inputs = newdoc.createElement("inputs")
168        top_element.appendChild(inputs)
169       
170        for item in in_list:
171            element = newdoc.createElement(item[0])
172            exec "element.appendChild(newdoc.createTextNode(str(%s)))" % item[1]
173            inputs.appendChild(element)
174             
175        # Outputs
176        outputs = newdoc.createElement("outputs")
177        top_element.appendChild(outputs)
178       
179        for item in out_list:
180            element = newdoc.createElement(item[0])
181            exec "element.appendChild(newdoc.createTextNode(str(%s)))" % item[1]
182            outputs.appendChild(element)
183                   
184        # Save the file
185        if doc is None:
186            fd = open(file, 'w')
187            fd.write(newdoc.toprettyxml())
188            fd.close()
189            return None
190        else:
191            return newdoc.toprettyxml()
192
193    def fromXML(self, file=None, node=None):
194        """
195            Load a P(r) inversion state from a file
196           
197            @param file: .prv file
198            @param node: node of a XML document to read from
199        """
200        if file is not None:
201            # Check whether the file is valid
202            if not os.path.isfile(file):
203                raise  RuntimeError, "P(r) reader: cannot open %s" % file
204       
205            from xml.dom.minidom import parse
206            doc = parse(file)
207            node = doc.documentElement
208           
209        if node.tagName == PRNODE_NAME:
210            if node.hasAttribute('version')\
211                and node.getAttribute('version') == '1.0':
212               
213                if node.hasChildNodes():
214                    for node in node.childNodes:
215                        if node.nodeName == 'filename':
216                            if node.hasChildNodes():
217                                self.file = node.childNodes[0].nodeValue.strip()
218                        elif node.nodeName == 'timestamp':
219                            if node.hasAttribute('epoch'):
220                                try:
221                                    self.timestamp = float(node.getAttribute('epoch'))
222                                except:
223                                    # Could not read timestamp: pass
224                                    pass
225                               
226                        # Parse inversion inputs
227                        elif node.nodeName == 'inputs':
228                            if node.hasChildNodes():
229                                for item in node.childNodes:
230                                    for out in in_list:
231                                        if item.nodeName == out[0]:
232                                            try:
233                                                exec '%s = float(item.childNodes[0].nodeValue.strip())' % out[1]
234                                            except:
235                                                exec '%s = None' % out[1]
236                                        elif item.nodeName == 'estimate_bck':
237                                            try:
238                                                self.estimate_bck = item.childNodes[0].nodeValue.strip()=='True'
239                                            except:
240                                                self.estimate_bck = False
241                           
242                        # Parse inversion outputs
243                        elif node.nodeName == 'outputs':
244                            if node.hasChildNodes():
245                                for item in node.childNodes:
246                                    for out in out_list:
247                                        if item.nodeName == out[0]:
248                                            try:
249                                                exec '%s = float(item.childNodes[0].nodeValue.strip())' % out[1]
250                                            except:
251                                                exec '%s = None' % out[1]
252            else:
253                raise RuntimeError, "Unsupported P(r) file version"
254       
255                   
256class Reader(CansasReader):
257    """
258        Class to load a .prv P(r) inversion file
259    """
260    ## File type
261    type_name = "P(r)"
262   
263    ## Wildcards
264    type = ["P(r) files (*.prv)|*.prv"]
265    ## List of allowed extensions
266    ext=['.prv', '.PRV'] 
267   
268    def __init__(self, call_back, cansas=False):
269        """
270            Initialize the call-back method to be called
271            after we load a file
272            @param call_back: call-back method
273            @param cansas:  True = files will be written/read in CanSAS format
274                            False = standalone mode
275           
276        """
277        ## Call back method to be executed after a file is read
278        self.call_back = call_back
279        ## CanSAS format flag
280        self.cansas = cansas
281       
282    def read(self, path):
283        """
284            Load a new P(r) inversion state from file
285           
286            @param path: file path
287            @return: None
288        """
289        if self.cansas==True:
290            return self._read_cansas(path)
291        else:
292            return self._read_standalone(path)
293       
294    def _read_standalone(self, path):
295        """
296            Load a new P(r) inversion state from file.
297            The P(r) node is assumed to be the top element.
298           
299            @param path: file path
300            @return: None
301        """
302        # Read the new state from file
303        state = InversionState()
304        state.fromXML(file=path)
305       
306        # Call back to post the new state
307        self.call_back(state)
308        return None
309   
310    def _parse_prstate(self, entry):
311        """
312            Read a p(r) inversion result from an XML node
313            @param entry: XML node to read from
314            @return: InversionState object
315        """
316        from xml import xpath
317
318        # Create an empty state
319        state = InversionState()
320       
321        # Locate the P(r) node
322        try:
323            nodes = xpath.Evaluate(PRNODE_NAME, entry)
324            state.fromXML(node=nodes[0])
325        except:
326            #raise RuntimeError, "%s is not a file with P(r) information." % path
327            logging.info("XML document does not contain P(r) information.")
328            import sys
329            print sys.exc_value
330           
331        return state
332   
333    def _read_cansas(self, path):
334        """
335            Load data and P(r) information from a CanSAS XML file.
336           
337            @param path: file path
338            @return: Data1D object if a single SASentry was found,
339                        or a list of Data1D objects if multiple entries were found,
340                        or None of nothing was found
341            @raise RuntimeError: when the file can't be opened
342            @raise ValueError: when the length of the data vectors are inconsistent
343        """
344        from xml.dom.minidom import parse
345        from xml import xpath
346        output = []
347       
348        if os.path.isfile(path):
349            basename  = os.path.basename(path)
350            root, extension = os.path.splitext(basename)
351            if  extension.lower() in self.ext or \
352                extension.lower() == '.xml':
353               
354                dom = parse(path)
355               
356                # Format 1: check whether we have a CanSAS file
357                nodes = xpath.Evaluate('SASroot', dom)
358                # Check the format version number
359                if nodes[0].hasAttributes():
360                    for i in range(nodes[0].attributes.length):
361                        if nodes[0].attributes.item(i).nodeName=='version':
362                            if nodes[0].attributes.item(i).nodeValue != self.version:
363                                raise ValueError, "cansas_reader: unrecognized version number %s" % \
364                                    nodes[0].attributes.item(i).nodeValue
365               
366                entry_list = xpath.Evaluate('SASroot/SASentry', dom)
367                for entry in entry_list:
368                    sas_entry = self._parse_entry(entry)
369                    prstate = self._parse_prstate(entry)
370                    sas_entry.meta_data['prstate'] = prstate
371                    sas_entry.filename = basename
372                    output.append(sas_entry)
373        else:
374            raise RuntimeError, "%s is not a file" % path
375       
376        # Return output consistent with the loader's api
377        if len(output)==0:
378            return None
379        elif len(output)==1:
380            return output[0]
381        else:
382            return output               
383   
384   
385    def write(self, filename, datainfo=None, prstate=None):
386        """
387            Write the content of a Data1D as a CanSAS XML file
388           
389            @param filename: name of the file to write
390            @param datainfo: Data1D object
391            @param prstate: InversionState object
392        """
393
394        # Sanity check
395        if self.cansas == True:
396            if datainfo is None:
397                datainfo = DataLoader.data_info.Data1D(x=[], y=[])   
398            elif not datainfo.__class__ == DataLoader.data_info.Data1D: 
399                raise RuntimeError, "The cansas writer expects a Data1D instance"
400       
401            # Create basic XML document
402            doc, sasentry = self._to_xml_doc(datainfo)
403       
404            # Add the P(r) information to the XML document
405            if prstate is not None:
406                prstate.toXML(doc=doc, entry_node=sasentry)
407       
408            # Write the XML document
409            fd = open(filename, 'w')
410            fd.write(doc.toprettyxml())
411            fd.close()
412        else:
413            prstate.toXML(file=filename)
414       
415       
416if __name__ == "__main__": 
417    #TODO: turn all this into unit tests
418   
419    state = InversionState()
420    #print state.fromXML('../../test/pr_state.prv')     
421    print state.fromXML('../../test/test.prv')     
422    print state   
423    state.toXML('test_copy.prv')
424   
425    from DataLoader.loader import Loader
426    l = Loader()
427    datainfo = l.load("../../test/cansas1d.xml")
428   
429    def call_back(state):
430        print state
431       
432    reader = Reader(call_back)
433    reader.cansas = False
434    reader.write("test_copy_from_reader.prv", prstate=state)
435    reader.cansas = True
436    reader.write("testout.prv", datainfo, state)
437    reader.write("testout_wo_datainfo.prv", prstate=state)
438   
439    # Now try to load things back
440    reader.cansas = False
441    #print reader.read("test_copy_from_reader.prv")
442    reader.cansas = True
443    #data = reader.read("testout.prv")
444    data = reader.read("testout_wo_datainfo.prv")
445    print data
446    print data.meta_data['prstate']
447   
448   
Note: See TracBrowser for help on using the repository browser.