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

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 cbaa2f4 was b35d3d1, checked in by Jae Cho <jhjcho@…>, 14 years ago

save state file: working, but more work to do

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