source: sasview/inversionview/src/sans/perspectives/pr/inversion_state.py @ ef8d42f

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 ef8d42f was 1e62361, checked in by Mathieu Doucet <doucetm@…>, 13 years ago

Fixing code style problems and bugs

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