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

magnetic_scattrelease-4.2.2ticket-1009ticket-1249
Last change on this file since 5251ec6 was 5251ec6, checked in by Paul Kienzle <pkienzle@…>, 12 months ago

improved support for py37 in sasgui

  • Property mode set to 100644
File size: 12.6 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
15logger = logging.getLogger(__name__)
16
17CORNODE_NAME = 'corfunc'
18CANSAS_NS = 'cansas1d/1.0'
19
20# The default state
21DEFAULT_STATE = {
22    'qmin_tcl': None,
23    'qmax1_tcl': None,
24    'qmax2_tcl': None,
25    'background_tcl': None
26}
27
28# List of output parameters, used by __str__
29output_list = [
30    ['max', "Long Period (A): "],
31    ['Lc', "Average Hard Block Thickness (A): "],
32    ['dtr', "Average Interface Thickness (A): "],
33    ['d0', "Average Core Thickness: "],
34    ['A', "PolyDispersity: "],
35    ['fill', "Filling Fraction: "]
36]
37
38class CorfuncState(object):
39    """
40    Stores information about the state of CorfuncPanel
41    """
42
43    def __init__(self):
44        # Inputs
45        self.file = None
46        self.data = None
47        self.qmin = None
48        self.qmax = [0, 0]
49        self.background = None
50        self.outputs = {}
51        self.is_extrapolated = False
52        self.transform_type = 'fourier'
53        self.is_transformed = False
54
55        self.saved_state = DEFAULT_STATE
56        self.timestamp = time.time()
57
58        # Raw Data
59        self.q = None
60        self.iq = None
61
62    def __str__(self):
63        """
64        Pretty print the state
65
66        :return: A string representing the state
67        """
68        state = "File:         {}\n".format(self.file)
69        state += "Timestamp:    {}\n".format(self.timestamp)
70        state += "Qmin:         {}\n".format(str(self.qmin))
71        state += "Qmax:         {}\n".format(str(self.qmax))
72        state += "Background:   {}\n".format(str(self.background))
73
74        if self.outputs != {} and self.outputs is not None:
75            state += "\nOutputs:\n"
76            for key, value in self.outputs.items():
77                name = output_list[key][1]
78                state += "{}: {}\n".format(name, str(value))
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.crf', 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.items():
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        if self.is_transformed:
175            element = new_doc.createElement("transform_type")
176            top_element.appendChild(element)
177            element.appendChild(new_doc.createTextNode(self.transform_type))
178
179        # Output parameters
180        if self.outputs != {} and self.outputs is not None:
181            output = new_doc.createElement("output")
182            top_element.appendChild(output)
183            for key, value in self.outputs.items():
184                element = new_doc.createElement(key)
185                element.appendChild(new_doc.createTextNode(str(value)))
186                output.appendChild(element)
187
188        # Save the file or return the original document with the state
189        # data appended
190        if doc is None:
191            fd = open(filename, 'w')
192            fd.write(new_doc.toprettyxml())
193            fd.close()
194            return None
195        else:
196            return new_doc
197
198
199    def fromXML(self, node):
200        """
201        Load corfunc states from a file
202
203        :param node: node of an XML document to read from (optional)
204        """
205        if node.get('version') and node.get('version') == '1.0':
206            # Parse filename
207            entry = get_content('ns:filename', node)
208            if entry is not None:
209                self.file = entry.text.strip()
210
211            # Parse timestamp
212            entry = get_content('ns:timestamp', node)
213            if entry is not None and entry.get('epoch'):
214                try:
215                    self.timestamp = (entry.get('epoch'))
216                except Exception as exc:
217                    msg = ("CorfuncState.fromXML: Could not read timestamp",
218                        "\n{}").format(exc)
219                    logger.error(msg)
220
221            # Parse current state
222            entry = get_content('ns:state', node)
223            if entry is not None:
224                for item in DEFAULT_STATE.keys():
225                    input_field = get_content("ns:{}".format(item), entry)
226                    if input_field is not None:
227                        try:
228                            value = float(input_field.text.strip())
229                        except:
230                            value = None
231                        self.set_saved_state(name=item, value=value)
232
233            # Parse is_extrapolated and is_transformed
234            entry = get_content('ns:is_extrapolated', node)
235            if entry is not None:
236                self.is_extrapolated = bool(int(entry.text.strip()))
237            entry = get_content('ns:is_transformed', node)
238            if entry is not None:
239                self.is_transformed = bool(int(entry.text.strip()))
240                entry = get_content('ns:transform_type', node)
241                self.transform_type = entry.text.strip()
242
243            # Parse outputs
244            entry = get_content('ns:output', node)
245            if entry is not None:
246                for item in output_list:
247                    parameter = get_content("ns:{}".format(item[0]), entry)
248                    if parameter is not None:
249                        self.outputs[item[0]] = float(parameter.text.strip())
250
251
252
253class Reader(CansasReader):
254    """
255    Reads a CanSAS file containing the state of a CorfuncPanel
256    """
257
258    type_name = "Corfunc"
259
260    type = ["Corfunc file (*.crf)|*.crf",
261            "SASView file (*.svs)|*.svs"]
262
263    ext = ['.crf', '.CRF', '.svs', '.SVS']
264
265    def __init__(self, callback):
266        self.callback = callback
267        self.state = None
268
269    def read(self, path):
270        """
271        Load data and corfunc information frmo a CanSAS file.
272
273        :param path: The file path to read from
274        :return: Data1D object, a list of Data1D objects, or None
275        :raise IOError: When the file can't be found
276        :raise IOError: When the file is an invalid file type
277        :raise ValueError: When the length of the data vectors are inconsistent
278        """
279        output = []
280        if os.path.isfile(path):
281            # Load file
282            basename = os.path.basename(path)
283            root, ext = os.path.splitext(basename)
284            if not ext.lower() in self.ext:
285                raise IOError("{} is not a supported file type".format(ext))
286            tree = etree.parse(path, parser=etree.ETCompatXMLParser())
287            root = tree.getroot()
288            entry_list = root.xpath('/ns:SASroot/ns:SASentry',
289                namespaces={'ns': CANSAS_NS})
290            for entry in entry_list:
291                corstate = self._parse_state(entry)
292
293                if corstate is not None:
294                    sas_entry, _ = self._parse_entry(entry)
295                    sas_entry.meta_data['corstate'] = corstate
296                    sas_entry.filename = corstate.file
297                    output.append(sas_entry)
298        else:
299            # File not found
300            msg = "{} is not a valid file path or doesn't exist".format(path)
301            raise IOError(msg)
302
303        if len(output) == 0:
304            return None
305        elif len(output) == 1:
306            self.callback(output[0].meta_data['corstate'], datainfo=output[0])
307            return output[0]
308        else:
309            return output
310
311    def write(self, filename, datainfo=None, state=None):
312        """
313        Write the content of a Data1D as a CanSAS file.
314
315        : param filename: Name of the file to write
316        : param datainfo: Data1D object
317        : param state: CorfuncState object
318        """
319        # Prepare datainfo
320        if datainfo is None:
321            datainfo = Data1D(x=[], y=[])
322        elif not (isinstance(datainfo, Data1D) or isinstance(datainfo, LoaderData1D)):
323            msg = ("The CanSAS writer expects a Data1D instance. {} was "
324                "provided").format(datainfo.__class__.__name__)
325            raise RuntimeError(msg)
326        if datainfo.title is None or datainfo.title == '':
327            datainfo.title = datainfo.name
328        if datainfo.run_name is None or datainfo.run_name == '':
329            datainfo.run = [str(datainfo.name)]
330            datainfo.run_name[0] = datainfo.name
331
332        # Create the XMl doc
333        doc, sasentry = self._to_xml_doc(datainfo)
334        if state is not None:
335            doc = state.toXML(doc=doc, entry_node=sasentry)
336
337        # Write the XML doc to a file
338        fd = open(filename, 'w')
339        fd.write(doc.toprettyxml())
340        fd.close()
341
342    def get_state(self):
343        return self.state
344
345
346    def _parse_state(self, entry):
347        """
348        Read state data from an XML node
349
350        :param entry: The XML node to read from
351        :return: CorfuncState object
352        """
353        state = None
354        try:
355            nodes = entry.xpath('ns:{}'.format(CORNODE_NAME),
356                namespaces={'ns': CANSAS_NS})
357            if nodes != []:
358                state = CorfuncState()
359                state.fromXML(nodes[0])
360        except Exception as exc:
361            msg = "XML document does not contain CorfuncState information\n{}"
362            msg.format(exc)
363            logger.info(msg)
364        return state
Note: See TracBrowser for help on using the repository browser.