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

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 d45d590 was d45d590, checked in by lewis, 5 years ago

Save transform type in corfunc state

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