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

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 f56770ef was caeb382, checked in by lewis, 8 years ago

Refactor writing outputs in corfunc_state

  • Property mode set to 100644
File size: 12.3 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.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,
22    'background_tcl': None
23}
24
25# List of output parameters, used by __str__
26output_list = [
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: "]
33]
34
35class CorfuncState(object):
36    """
37    Stores information about the state of CorfuncPanel
38    """
39
40    def __init__(self):
41        # Inputs
42        self.file = None
43        self.data = None
44        self.qmin = None
45        self.qmax = [0, 0]
46        self.background = None
47        self.outputs = {}
48        self.is_extrapolated = False
49        self.is_transformed = False
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))
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        # Output parameters
174        if self.outputs != {} and self.outputs is not None:
175            output = new_doc.createElement("output")
176            top_element.appendChild(output)
177            for key, value in self.outputs.iteritems():
178                element = new_doc.createElement(key)
179                element.appendChild(new_doc.createTextNode(str(value)))
180                output.appendChild(element)
181
182        # Save the file or return the original document with the state
183        # data appended
184        if doc is None:
185            fd = open(filename, 'w')
186            fd.write(new_doc.toprettyxml())
187            fd.close()
188            return None
189        else:
190            return new_doc
191
192
193    def fromXML(self, node):
194        """
195        Load corfunc states from a file
196
197        :param node: node of an XML document to read from (optional)
198        """
199        if node.get('version') and node.get('version') == '1.0':
200            # Parse filename
201            entry = get_content('ns:filename', node)
202            if entry is not None:
203                self.file = entry.text.strip()
204
205            # Parse timestamp
206            entry = get_content('ns:timestamp', node)
207            if entry is not None and entry.get('epoch'):
208                try:
209                    self.timestamp = (entry.get('epoch'))
210                except:
211                    msg = ("CorfuncState.fromXML: Could not read timestamp",
212                        "\n{}").format(sys.exc_value)
213                    logging.error(msg)
214
215            # Parse current state
216            entry = get_content('ns:state', node)
217            if entry is not None:
218                for item in DEFAULT_STATE.iterkeys():
219                    input_field = get_content("ns:{}".format(item), entry)
220                    if input_field is not None:
221                        try:
222                            value = float(input_field.text.strip())
223                        except:
224                            value = None
225                        self.set_saved_state(name=item, value=value)
226
227            # Parse is_extrapolated and is_transformed
228            entry = get_content('ns:is_extrapolated', node)
229            if entry is not None:
230                self.is_extrapolated = bool(int(entry.text.strip()))
231            entry = get_content('ns:is_transformed', node)
232            if entry is not None:
233                self.is_transformed = bool(int(entry.text.strip()))
234
235            # Parse outputs
236            entry = get_content('ns:output', node)
237            if entry is not None:
238                for item in output_list:
239                    parameter = get_content("ns:{}".format(item[0]), entry)
240                    if parameter is not None:
241                        self.outputs[item[0]] = float(parameter.text.strip())
242
243
244
245class Reader(CansasReader):
246    """
247    Reads a CanSAS file containing the state of a CorfuncPanel
248    """
249
250    type_name = "Corfunc"
251
252    type = ["Invariant file (*.inv)|*.inv",
253            "SASView file (*.svs)|*.svs"]
254
255    ext = ['.cor', '.COR', '.svs', '.SVS']
256
257    def __init__(self, callback):
258        self.callback = callback
259        self.state = None
260
261    def read(self, path):
262        """
263        Load data and corfunc information frmo a CanSAS file.
264
265        :param path: The file path to read from
266        :return: Data1D object, a list of Data1D objects, or None
267        :raise IOError: When the file can't be found
268        :raise IOError: When the file is an invalid file type
269        :raise ValueError: When the length of the data vectors are inconsistent
270        """
271        output = []
272        if os.path.isfile(path):
273            # Load file
274            basename = os.path.basename(path)
275            root, ext = os.path.splitext(basename)
276            if not ext.lower() in self.ext:
277                raise IOError, "{} is not a supported file type".format(ext)
278            tree = etree.parse(path, parser=etree.ETCompatXMLParser())
279            root = tree.getroot()
280            entry_list = root.xpath('/ns:SASroot/ns:SASentry',
281                namespaces={'ns': CANSAS_NS})
282            for entry in entry_list:
283                sas_entry, _ = self._parse_entry(entry)
284                corstate = self._parse_state(entry)
285
286                if corstate != None:
287                    sas_entry.meta_data['corstate'] = corstate
288                    sas_entry.filename = corstate.file
289                    output.append(sas_entry)
290        else:
291            # File not found
292            msg = "{} is not a valid file path or doesn't exist".format(path)
293            raise IOError, msg
294
295        if len(output) == 0:
296            return None
297        elif len(output) == 1:
298            self.callback(output[0].meta_data['corstate'], datainfo=output[0])
299            return output[0]
300        else:
301            return output
302
303    def write(self, filename, datainfo=None, state=None):
304        """
305        Write the content of a Data1D as a CanSAS file.
306
307        : param filename: Name of the file to write
308        : param datainfo: Data1D object
309        : param state: CorfuncState object
310        """
311        # Prepare datainfo
312        if datainfo is None:
313            datainfo = Data1D(x=[], y=[])
314        elif not issubclass(datainfo.__class__, Data1D):
315            msg = ("The CanSAS writer expects a Data1D instance. {} was ",
316                "provided").format(datainfo.__class__.__name__)
317            raise RuntimeError, msg
318        if datainfo.title is None or datainfo.title == '':
319            datainfo.title = datainfo.name
320        if datainfo.run_name == None or datainfo.run_name == '':
321            datainfo.run = [str(datainfo.name)]
322            datainfo.run_name[0] = datainfo.name
323
324        # Create the XMl doc
325        doc, sasentry = self._to_xml_doc(datainfo)
326        if state is not None:
327            doc = state.toXML(doc=doc, entry_node=sasentry)
328
329        # Write the XML doc to a file
330        fd = open(filename, 'w')
331        fd.write(doc.toprettyxml())
332        fd.close()
333
334    def get_state(self):
335        return self.state
336
337
338    def _parse_state(self, entry):
339        """
340        Read state data from an XML node
341
342        :param entry: The XML node to read from
343        :return: CorfuncState object
344        """
345        state = None
346        try:
347            nodes = entry.xpath('ns:{}'.format(CORNODE_NAME),
348                namespaces={'ns': CANSAS_NS})
349            if nodes != []:
350                state = CorfuncState()
351                state.fromXML(nodes[0])
352        except:
353            msg = "XML document does not contain CorfuncState information\n{}"
354            msg.format(sys.exc_value)
355            logging.info(msg)
356        return state
Note: See TracBrowser for help on using the repository browser.