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

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

Minor refactor and add some comments

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