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

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

Implement saving/loading calculated parameters on project save

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