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

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

Fix bug when loading saved project with no output saved

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