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

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 a93f525 was 7116b6e0, checked in by Gervaise Alina <gervyh@…>, 14 years ago

working on documentation

  • Property mode set to 100644
File size: 18.4 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    ## List of allowed extensions
345    ext=['.prv', '.PRV'] 
346   
347    def __init__(self, call_back, cansas=True):
348        """
349        Initialize the call-back method to be called
350        after we load a file
351       
352        :param call_back: call-back method
353        :param cansas:  True = files will be written/read in CanSAS format
354                        False = write CanSAS format
355           
356        """
357        ## Call back method to be executed after a file is read
358        self.call_back = call_back
359        ## CanSAS format flag
360        self.cansas = cansas
361       
362    def read(self, path):
363        """
364        Load a new P(r) inversion state from file
365       
366        :param path: file path
367       
368        :return: None
369       
370        """
371        if self.cansas==True:
372            return self._read_cansas(path)
373        else:
374            return self._read_standalone(path)
375       
376    def _read_standalone(self, path):
377        """
378        Load a new P(r) inversion state from file.
379        The P(r) node is assumed to be the top element.
380       
381        :param path: file path
382       
383        :return: None
384       
385        """
386        # Read the new state from file
387        state = InversionState()
388        state.fromXML(file=path)
389       
390        # Call back to post the new state
391        self.call_back(state)
392        return None
393   
394    def _parse_prstate(self, entry):
395        """
396        Read a p(r) inversion result from an XML node
397       
398        :param entry: XML node to read from
399       
400        :return: InversionState object
401       
402        """
403        # Create an empty state
404        state = InversionState()
405       
406        # Locate the P(r) node
407        try:
408            nodes = entry.xpath('ns:%s' % PRNODE_NAME, namespaces={'ns': CANSAS_NS})
409            state.fromXML(node=nodes[0])
410        except:
411            logging.info("XML document does not contain P(r) information.\n %s" % sys.exc_value)
412           
413        return state
414   
415    def _read_cansas(self, path):
416        """
417        Load data and P(r) information from a CanSAS XML file.
418       
419        :param path: file path
420       
421        :return: Data1D object if a single SASentry was found,
422                    or a list of Data1D objects if multiple entries were found,
423                    or None of nothing was found
424                   
425        :raise RuntimeError: when the file can't be opened
426        :raise ValueError: when the length of the data vectors are inconsistent
427       
428        """
429        output = []
430       
431        if os.path.isfile(path):
432            basename  = os.path.basename(path)
433            root, extension = os.path.splitext(basename)
434            #TODO: eventually remove the check for .xml once
435            # the P(r) writer/reader is truly complete.
436            if  extension.lower() in self.ext or \
437                extension.lower() == '.xml':
438               
439                tree = etree.parse(path, parser=etree.ETCompatXMLParser())
440                # Check the format version number
441                # Specifying the namespace will take care of the file format version
442                root = tree.getroot()
443               
444                entry_list = root.xpath('/ns:SASroot/ns:SASentry', namespaces={'ns': CANSAS_NS})
445
446                for entry in entry_list:
447                    sas_entry = self._parse_entry(entry)
448                    prstate = self._parse_prstate(entry)
449                    sas_entry.meta_data['prstate'] = prstate
450                    sas_entry.filename = prstate.file
451                    output.append(sas_entry)
452        else:
453            raise RuntimeError, "%s is not a file" % path
454       
455        # Return output consistent with the loader's api
456        if len(output)==0:
457            return None
458        elif len(output)==1:
459            # Call back to post the new state
460            self.call_back(output[0].meta_data['prstate'], datainfo = output[0])
461            return output[0]
462        else:
463            return output               
464   
465   
466    def write(self, filename, datainfo=None, prstate=None):
467        """
468        Write the content of a Data1D as a CanSAS XML file
469       
470        :param filename: name of the file to write
471        :param datainfo: Data1D object
472        :param prstate: InversionState object
473       
474        """
475        # Sanity check
476        if self.cansas == True:
477            if datainfo is None:
478                datainfo = DataLoader.data_info.Data1D(x=[], y=[])   
479            elif not issubclass(datainfo.__class__, DataLoader.data_info.Data1D):
480                raise RuntimeError, "The cansas writer expects a Data1D instance: %s" % str(datainfo.__class__.__name__)
481       
482            # Create basic XML document
483            doc, sasentry = self._to_xml_doc(datainfo)
484       
485            # Add the P(r) information to the XML document
486            if prstate is not None:
487                prstate.toXML(doc=doc, entry_node=sasentry)
488       
489            # Write the XML document
490            fd = open(filename, 'w')
491            fd.write(doc.toprettyxml())
492            fd.close()
493        else:
494            prstate.toXML(file=filename)
495       
496   
497   
Note: See TracBrowser for help on using the repository browser.