source: sasview/src/sas/sasgui/perspectives/pr/inversion_state.py @ ec8886e

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.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since ec8886e was 463e7ffc, checked in by Ricardo Ferraz Leal <ricleal@…>, 8 years ago

getLogger with module name

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