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

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 c83a5af was 75fbd17, checked in by Gervaise Alina <gervyh@…>, 14 years ago

working on save state

  • Property mode set to 100644
File size: 19.7 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
19import DataLoader
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 get_state(self):
412        return self.state
413    def _parse_prstate(self, entry):
414        """
415        Read a p(r) inversion result from an XML node
416       
417        :param entry: XML node to read from
418       
419        :return: InversionState object
420       
421        """
422        state = None
423       
424        # Locate the P(r) node
425        try:
426            nodes = entry.xpath('ns:%s' % PRNODE_NAME,
427                                namespaces={'ns': CANSAS_NS})
428            if nodes !=[]:
429                # Create an empty state
430                state =  InversionState()
431                state.fromXML(node=nodes[0])
432        except:
433            msg = "XML document does not contain P(r) "
434            msg += "information.\n %s" % sys.exc_value
435            logging.info(msg)
436           
437        return state
438   
439    def _read_cansas(self, path):
440        """
441        Load data and P(r) information from a CanSAS XML file.
442       
443        :param path: file path
444       
445        :return: Data1D object if a single SASentry was found,
446                    or a list of Data1D objects if multiple entries were found,
447                    or None of nothing was found
448                   
449        :raise RuntimeError: when the file can't be opened
450        :raise ValueError: when the length of the data vectors are inconsistent
451       
452        """
453        output = []
454       
455        if os.path.isfile(path):
456            basename  = os.path.basename(path)
457            root, extension = os.path.splitext(basename)
458            #TODO: eventually remove the check for .xml once
459            # the P(r) writer/reader is truly complete.
460            if  extension.lower() in self.ext or extension.lower() == '.xml':
461               
462                tree = etree.parse(path, parser=etree.ETCompatXMLParser())
463                # Check the format version number
464                # Specifying the namespace will take care of the file
465                #format version
466                root = tree.getroot()
467               
468                entry_list = root.xpath('/ns:SASroot/ns:SASentry',
469                                        namespaces={'ns': CANSAS_NS})
470
471                for entry in entry_list:
472                    sas_entry = self._parse_entry(entry)
473                    prstate = self._parse_prstate(entry)
474                    #prstate could be None when .svs file is loaded
475                    #in this case, skip appending to output
476                    if prstate != None:
477                        sas_entry.meta_data['prstate'] = prstate
478                        sas_entry.filename = prstate.file
479                        output.append(sas_entry)
480        else:
481            raise RuntimeError, "%s is not a file" % path
482       
483        # Return output consistent with the loader's api
484        if len(output) == 0:
485            return None
486        elif len(output) == 1:
487            # Call back to post the new state
488            #self.call_back(output[0].meta_data['prstate'], datainfo = output[0])
489            self.state = output[0].meta_data['prstate']
490            return output[0]
491        else:
492            return output               
493   
494   
495    def write(self, filename, datainfo=None, prstate=None):
496        """
497        Write the content of a Data1D as a CanSAS XML file
498       
499        :param filename: name of the file to write
500        :param datainfo: Data1D object
501        :param prstate: InversionState object
502       
503        """
504        # Sanity check
505        if self.cansas == True:
506            doc =self.write_toXML(datainfo, prstate)       
507            # Write the XML document
508            fd = open(filename, 'w')
509            fd.write(doc.toprettyxml())
510            fd.close()
511        else:
512            prstate.toXML(file=filename)
513       
514    def write_toXML(self, datainfo=None, state=None):
515        """
516        Write toXML, a helper for write()
517       
518        : return: xml doc
519        """
520        if datainfo is None:
521            datainfo = DataLoader.data_info.Data1D(x=[], y=[])   
522        elif not issubclass(datainfo.__class__, DataLoader.data_info.Data1D):
523            msg = "The cansas writer expects a Data1D "
524            msg += "instance: %s" % str(datainfo.__class__.__name__)
525            raise RuntimeError, msg
526   
527        # Create basic XML document
528        doc, sasentry = self._to_xml_doc(datainfo)
529   
530        # Add the invariant information to the XML document
531        if state is not None:
532            state.toXML(doc=doc, entry_node=sasentry)
533           
534        return doc
535   
Note: See TracBrowser for help on using the repository browser.