source: sasview/src/sas/sasgui/perspectives/corfunc/corfunc_state.py @ 2ff9e37

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.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 2ff9e37 was 2ff9e37, checked in by lewis, 8 years ago

On project load, only plot extrapolation/transform if they were plotted before project save

  • Property mode set to 100644
File size: 12.3 KB
RevLine 
[e02d8f6]1import time
2import sys
3import os
[d888abf]4import logging
[e02d8f6]5import sas.sascalc.dataloader
6from lxml import etree
7from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
8from sas.sascalc.dataloader.readers.cansas_reader import get_content
9from sas.sasgui.guiframe.utils import format_number
10from sas.sasgui.guiframe.gui_style import GUIFRAME_ID
11from sas.sasgui.guiframe.dataFitting import Data1D
12from sas.sascalc.dataloader.loader import Loader
13
14CORNODE_NAME = 'corfunc'
15CANSAS_NS = 'cansas1d/1.0'
16
17# The default state
18DEFAULT_STATE = {
19    'qmin_tcl': None,
20    'qmax1_tcl': None,
21    'qmax2_tcl': None,
[b564ea2]22    'background_tcl': None
[e02d8f6]23}
24
25# List of output parameters, used by __str__
26output_list = [
[a684c64]27    ['max', "Long Period (A): "],
28    ['Lc', "Average Hard Block Thickness (A): "],
29    ['dtr', "Average Interface Thickness (A): "],
30    ['d0', "Average Core Thickness: "],
31    ['A', "PolyDispersity: "],
32    ['fill', "Filling Fraction: "]
[e02d8f6]33]
34
35class CorfuncState(object):
[9c90cf3]36    """
37    Stores information about the state of CorfuncPanel
38    """
[e02d8f6]39
40    def __init__(self):
41        # Inputs
42        self.file = None
43        self.data = None
44        self.qmin = None
45        self.qmax = [0, 0]
[b564ea2]46        self.background = None
[a684c64]47        self.outputs = {}
[2ff9e37]48        self.is_extrapolated = False
49        self.is_transformed = False
[e02d8f6]50
51        self.saved_state = DEFAULT_STATE
52        # Will be filled on panel init as number of states increases
53        self.state_list = {}
54        self.timestamp = time.time()
55
56        # Raw Data
57        self.q = None
58        self.iq = None
59        # TODO: Add extrapolated data and transformed data (when implemented)
60
61    def __str__(self):
62        """
63        Pretty print the state
64
65        :return: A string representing the state
66        """
67        state = "File:         {}\n".format(self.file)
68        state += "Timestamp:    {}\n".format(self.timestamp)
69        state += "Qmin:         {}\n".format(str(self.qmin))
70        state += "Qmax:         {}\n".format(str(self.qmax))
[b564ea2]71        state += "Background:   {}\n".format(str(self.background))
[e02d8f6]72        if self.outputs is None:
73            return state
74        state += "\nOutputs:\n"
75        for output in output_list:
76            key = output[0]
77            name = output[1]
78            state += "{}: {}\n".format(name, str(self.outputs[key]))
79
80        return state
81
82    def set_saved_state(self, name, value):
83        """
84        Set a value in the current state.
85
86        :param name: The name of the parameter to set
87        :param value: The value to set the parameter to
88        """
89        self.saved_state[name] = value
90        if name == 'qmin_tcl':
91            self.qmin = value
92        elif name == 'qmax1_tcl':
93            self.qmax[0] = value
94        elif name == 'qmax2_tcl':
95            self.qmax[1] = value
[b564ea2]96        elif name == 'background_tcl':
97            self.background = value
[e02d8f6]98
99    def toXML(self, filename='corfunc_state.cor', doc=None, entry_node=None):
100        """
101        Writes the state of the CorfuncPanel panel to file, as XML.
102
103        Compatible with standalone writing, or appending to an
104        already existing XML document. In that case, the XML document
105        is required. An optional entry node in the XML document
106        may also be given.
107
108        :param file: file to write to
109        :param doc: XML document object [optional]
110        :param entry_node: XML node within the XML document at which
111            we will append the data [optional]
112
[77e9ac6]113        :return: None if no doc is provided, modified XML document if doc!=None
[e02d8f6]114        """
115        from xml.dom.minidom import getDOMImplementation
116
117        top_element = None
118        new_doc = None
119        if doc is None:
120            # Create a new XML document
121            impl = getDOMImplementation()
122            doc_type = impl.createDocumentType(CORNODE_NAME, "1.0", "1.0")
123            new_doc = impl.createDocument(None, CORNODE_NAME, doc_type)
124            top_element = new_doc.documentElement
125        else:
126            # Create a new element in the document provided
127            top_element = doc.createElement(CORNODE_NAME)
128            if entry_node is None:
129                doc.documentElement.appendChild(top_element)
130            else:
131                entry_node.appendChild(top_element)
132            new_doc = doc
133
134        # Version
135        attr = new_doc.createAttribute("version")
136        attr.nodeValue = '1.0'
137        top_element.setAttributeNode(attr)
138
139        # Filename
140        element = new_doc.createElement("filename")
141        if self.file is not None and self.file != '':
142            element.appendChild(new_doc.createTextNode(str(self.file)))
143        else:
144            element.appendChild(new_doc.createTextNode(str(filename)))
145        top_element.appendChild(element)
146
147        # Timestamp
148        element = new_doc.createElement("timestamp")
149        # Pretty printed format
150        element.appendChild(new_doc.createTextNode(time.ctime(self.timestamp)))
151        attr = new_doc.createAttribute("epoch")
152        # Epoch value (used in self.fromXML)
153        attr.nodeValue = str(self.timestamp)
154        element.setAttributeNode(attr)
155        top_element.appendChild(element)
156
157        # Current state
158        state = new_doc.createElement("state")
159        top_element.appendChild(state)
160        for name, value in self.saved_state.iteritems():
161            element = new_doc.createElement(name)
162            element.appendChild(new_doc.createTextNode(str(value)))
163            state.appendChild(element)
164
[2ff9e37]165        # Whether or not the extrapolate & transform buttons have been clicked
166        element = new_doc.createElement("is_extrapolated")
167        top_element.appendChild(element)
168        element.appendChild(new_doc.createTextNode(str(int(self.is_extrapolated))))
169
170        element = new_doc.createElement("is_transformed")
171        top_element.appendChild(element)
172        element.appendChild(new_doc.createTextNode(str(int(self.is_transformed))))
173
[a684c64]174        # Output parameters
[d888abf]175        if self.outputs != {} and self.outputs is not None:
176            output = new_doc.createElement("output")
177            top_element.appendChild(output)
178            for item in output_list:
179                element = new_doc.createElement(item[0])
180                element.appendChild(new_doc.createTextNode(self.outputs[item[0]]))
181                output.appendChild(element)
[e02d8f6]182
183        # Save the file or return the original document with the state
184        # data appended
185        if doc is None:
186            fd = open(filename, 'w')
187            fd.write(new_doc.toprettyxml())
188            fd.close()
189            return None
190        else:
191            return new_doc
192
193
194    def fromXML(self, node):
195        """
196        Load corfunc states from a file
197
198        :param node: node of an XML document to read from (optional)
199        """
[2ff9e37]200        import pdb; pdb.set_trace()
[e02d8f6]201        if node.get('version') and node.get('version') == '1.0':
202            # Parse filename
203            entry = get_content('ns:filename', node)
204            if entry is not None:
205                self.file = entry.text.strip()
206
207            # Parse timestamp
208            entry = get_content('ns:timestamp', node)
209            if entry is not None and entry.get('epoch'):
210                try:
211                    self.timestamp = (entry.get('epoch'))
212                except:
213                    msg = ("CorfuncState.fromXML: Could not read timestamp",
214                        "\n{}").format(sys.exc_value)
215                    logging.error(msg)
216
217            # Parse current state
218            entry = get_content('ns:state', node)
219            if entry is not None:
220                for item in DEFAULT_STATE.iterkeys():
221                    input_field = get_content("ns:{}".format(item), entry)
222                    if input_field is not None:
223                        try:
224                            value = float(input_field.text.strip())
225                        except:
226                            value = None
227                        self.set_saved_state(name=item, value=value)
228
[2ff9e37]229            # Parse is_extrapolated and is_transformed
230            entry = get_content('ns:is_extrapolated', node)
231            if entry is not None:
232                self.is_extrapolated = bool(int(entry.text.strip()))
233            entry = get_content('ns:is_transformed', node)
234            if entry is not None:
235                self.is_transformed = bool(int(entry.text.strip()))
236
[a684c64]237            # Parse outputs
238            entry = get_content('ns:output', node)
239            if entry is not None:
240                for item in output_list:
241                    parameter = get_content("ns:{}".format(item[0]), entry)
242                    if parameter is not None:
243                        self.outputs[item[0]] = parameter.text.strip()
244
[e02d8f6]245
246
247class Reader(CansasReader):
248    """
[9c90cf3]249    Reads a CanSAS file containing the state of a CorfuncPanel
[e02d8f6]250    """
251
252    type_name = "Corfunc"
253
[a684c64]254    type = ["Invariant file (*.inv)|*.inv",
255            "SASView file (*.svs)|*.svs"]
256
257    ext = ['.cor', '.COR', '.svs', '.SVS']
258
[e02d8f6]259    def __init__(self, callback):
260        self.callback = callback
261        self.state = None
262
263    def read(self, path):
264        """
265        Load data and corfunc information frmo a CanSAS file.
266
267        :param path: The file path to read from
268        :return: Data1D object, a list of Data1D objects, or None
269        :raise IOError: When the file can't be found
270        :raise IOError: When the file is an invalid file type
271        :raise ValueError: When the length of the data vectors are inconsistent
272        """
273        output = []
274        if os.path.isfile(path):
275            # Load file
276            basename = os.path.basename(path)
277            root, ext = os.path.splitext(basename)
[a684c64]278            if not ext.lower() in self.ext:
[e02d8f6]279                raise IOError, "{} is not a supported file type".format(ext)
280            tree = etree.parse(path, parser=etree.ETCompatXMLParser())
281            root = tree.getroot()
282            entry_list = root.xpath('/ns:SASroot/ns:SASentry',
283                namespaces={'ns': CANSAS_NS})
284            for entry in entry_list:
285                sas_entry, _ = self._parse_entry(entry)
286                corstate = self._parse_state(entry)
287
288                if corstate != None:
289                    sas_entry.meta_data['corstate'] = corstate
290                    sas_entry.filename = corstate.file
291                    output.append(sas_entry)
292        else:
293            # File not found
294            msg = "{} is not a valid file path or doesn't exist".format(path)
295            raise IOError, msg
296
297        if len(output) == 0:
298            return None
299        elif len(output) == 1:
300            self.callback(output[0].meta_data['corstate'], datainfo=output[0])
301            return output[0]
302        else:
303            return output
304
305    def write(self, filename, datainfo=None, state=None):
306        """
307        Write the content of a Data1D as a CanSAS file.
308
309        : param filename: Name of the file to write
310        : param datainfo: Data1D object
311        : param state: CorfuncState object
312        """
313        # Prepare datainfo
314        if datainfo is None:
315            datainfo = Data1D(x=[], y=[])
316        elif not issubclass(datainfo.__class__, Data1D):
317            msg = ("The CanSAS writer expects a Data1D instance. {} was ",
318                "provided").format(datainfo.__class__.__name__)
319            raise RuntimeError, msg
320        if datainfo.title is None or datainfo.title == '':
321            datainfo.title = datainfo.name
322        if datainfo.run_name == None or datainfo.run_name == '':
323            datainfo.run = [str(datainfo.name)]
324            datainfo.run_name[0] = datainfo.name
325
326        # Create the XMl doc
327        doc, sasentry = self._to_xml_doc(datainfo)
328        if state is not None:
329            doc = state.toXML(doc=doc, entry_node=sasentry)
330
331        # Write the XML doc to a file
332        fd = open(filename, 'w')
333        fd.write(doc.toprettyxml())
334        fd.close()
335
336    def get_state(self):
337        return self.state
338
339
340    def _parse_state(self, entry):
341        """
342        Read state data from an XML node
343
344        :param entry: The XML node to read from
345        :return: CorfuncState object
346        """
347        state = None
348        try:
349            nodes = entry.xpath('ns:{}'.format(CORNODE_NAME),
350                namespaces={'ns': CANSAS_NS})
351            if nodes != []:
352                state = CorfuncState()
353                state.fromXML(nodes[0])
354        except:
355            msg = "XML document does not contain CorfuncState information\n{}"
356            msg.format(sys.exc_value)
357            logging.info(msg)
358        return state
Note: See TracBrowser for help on using the repository browser.