source: sasview/prview/perspectives/pr/inversion_state.py @ 3eac6d06

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 3eac6d06 was 3e41f43, checked in by Gervaise Alina <gervyh@…>, 14 years ago

make pr plugin inheriting from pluginbase in guiframe

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