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

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

Remove erroneous debug statement

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