source: sasview/src/sas/sasgui/perspectives/invariant/invariant_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@…>, 5 years ago

improved support for py37 in sasgui

  • Property mode set to 100644
File size: 30.7 KB
Line 
1"""
2    State class for the invariant UI
3"""
4from __future__ import print_function
5
6# import time
7import os
8import sys
9import logging
10import copy
11import sas.sascalc.dataloader
12# from xml.dom.minidom import parse
13from lxml import etree
14from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
15from sas.sasgui.guiframe.report_image_handler import ReportImageHandler
16from sas.sascalc.dataloader.readers.cansas_reader import get_content
17from sas.sasgui.guiframe.utils import format_number
18from sas.sasgui.guiframe.gui_style import GUIFRAME_ID
19from sas.sasgui.guiframe.dataFitting import Data1D
20
21logger = logging.getLogger(__name__)
22
23INVNODE_NAME = 'invariant'
24CANSAS_NS = "cansas1d/1.0"
25
26# default state
27DEFAULT_STATE = {'file': 'None',
28                 'compute_num':0,
29                 'state_num':0,
30                 'is_time_machine':False,
31                 'background_tcl':0.0,
32                 'scale_tcl':1.0,
33                 'contrast_tcl':1.0,
34                 'porod_constant_tcl':'',
35                 'npts_low_tcl':10,
36                 'npts_high_tcl':10,
37                 'power_high_tcl':4.0,
38                 'power_low_tcl': 4.0,
39                 'enable_high_cbox':False,
40                 'enable_low_cbox':False,
41                 'guinier': True,
42                 'power_law_high': False,
43                 'power_law_low': False,
44                 'fit_enable_high': False,
45                 'fit_enable_low': False,
46                 'fix_enable_high':True,
47                 'fix_enable_low':True,
48                 'volume_tcl':'',
49                 'volume_err_tcl':'',
50                 'surface_tcl':'',
51                 'surface_err_tcl':''}
52# list of states: This list will be filled as panel
53# init and the number of states increases
54state_list = {}
55bookmark_list = {}
56# list of input parameters (will be filled up on panel init) used by __str__
57input_list = {'background_tcl':0,
58              'scale_tcl':0,
59              'contrast_tcl':0,
60              'porod_constant_tcl':'',
61              'npts_low_tcl':0,
62              'npts_high_tcl':0,
63              'power_high_tcl':0,
64              'power_low_tcl': 0}
65# list of output parameters (order sensitive) used by __str__
66output_list = [["qstar_low", "Q* from low Q extrapolation [1/(cm*A)]"],
67               ["qstar_low_err", "dQ* from low Q extrapolation"],
68               ["qstar_low_percent", "Q* percent from low Q extrapolation"],
69               ["qstar", "Q* from data [1/(cm*A)]"],
70               ["qstar_err", "dQ* from data"],
71               ["qstar_percent", "Q* percent from data"],
72               ["qstar_high", "Q* from high Q extrapolation [1/(cm*A)]"],
73               ["qstar_high_err", "dQ* from high Q extrapolation"],
74               ["qstar_high_percent", "Q* percent from low Q extrapolation"],
75               ["qstar_total", "total Q* [1/(cm*A)]"],
76               ["qstar_total_err", "total dQ*"],
77               ["volume", "volume fraction"],
78               ["volume_err", "volume fraction error"],
79               ["surface", "specific surface"],
80               ["surface_err", "specific surface error"]]
81
82
83
84class InvariantState(object):
85    """
86    Class to hold the state information of the InversionControl panel.
87    """
88    def __init__(self):
89        """
90        Default values
91        """
92        # Input
93        self.file = None
94        self.data = Data1D(x=[], y=[], dx=None, dy=None)
95        self.theory_lowQ = Data1D(x=[], y=[], dy=None)
96        self.theory_lowQ.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
97        self.theory_highQ = Data1D(x=[], y=[], dy=None)
98        self.theory_highQ.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
99        # self.is_time_machine = False
100        self.saved_state = DEFAULT_STATE
101        self.state_list = state_list
102        self.bookmark_list = bookmark_list
103        self.input_list = input_list
104        self.output_list = output_list
105
106        self.compute_num = 0
107        self.state_num = 0
108        self.timestamp = ('00:00:00', '00/00/0000')
109        self.container = None
110        # plot image
111        self.wximbmp = None
112        # report_html strings
113        import sas.sasgui.perspectives.invariant as invariant
114        path = invariant.get_data_path(media='media')
115        path_report_html = os.path.join(path, "report_template.html")
116        html_template = open(path_report_html, "r")
117        self.template_str = html_template.read()
118        self.report_str = self.template_str
119        # self.report_str_save = None
120        html_template.close()
121
122    def __str__(self):
123        """
124        Pretty print
125
126        : return: string representing the state
127        """
128        # Input string
129        compute_num = self.saved_state['compute_num']
130        compute_state = self.state_list[str(compute_num)]
131        my_time, date = self.timestamp
132        file_name = self.file
133        from sas.sasview.__init__ import __version__ as sasview_version
134
135        state_num = int(self.saved_state['state_num'])
136        state = "\n[Invariant computation for %s: " % file_name
137        state += "performed at %s on %s] " % (my_time, date)
138        state += "[SasView v%s]\n" % (sasview_version)
139        state += "State No.: %d \n" % state_num
140        state += "\n=== Inputs ===\n"
141
142        # text ctl general inputs ( excluding extrapolation text ctl)
143        for key, value in self.input_list.items():
144            if value == '':
145                continue
146            key_split = key.split('_')
147            max_ind = len(key_split) - 1
148            if key_split[max_ind] == 'tcl':
149                name = ""
150                if key_split[1] == 'low' or key_split[1] == 'high':
151                    continue
152                for ind in range(0, max_ind):
153                    name += " %s" % key_split[ind]
154                state += "%s:   %s\n" % (name.lstrip(" "), value)
155
156        # other input parameters
157        extra_lo = compute_state['enable_low_cbox']
158        if compute_state['enable_low_cbox']:
159            if compute_state['guinier']:
160                extra_lo = 'Guinier'
161            else:
162                extra_lo = 'Power law'
163        extra_hi = compute_state['enable_high_cbox']
164        if compute_state['enable_high_cbox']:
165            extra_hi = 'Power law'
166        state += "\nExtrapolation:  High=%s; Low=%s\n" % (extra_hi, extra_lo)
167        low_off = False
168        high_off = False
169        for key, value in self.input_list.items():
170            key_split = key.split('_')
171            max_ind = len(key_split) - 1
172            if key_split[max_ind] == 'tcl':
173                name = ""
174                # check each buttons whether or not ON or OFF
175                if key_split[1] == 'low' or key_split[1] == 'high':
176                    if not compute_state['enable_low_cbox'] and \
177                        key_split[max_ind - 1] == 'low':
178                        low_off = True
179                        continue
180                    elif not compute_state['enable_high_cbox'] and \
181                        key_split[max_ind - 1] == 'high':
182                        high_off = True
183                        continue
184                    elif extra_lo == 'Guinier' and key_split[0] == 'power' and \
185                        key_split[max_ind - 1] == 'low':
186                        continue
187                    for ind in range(0, max_ind):
188                        name += " %s" % key_split[ind]
189                    name = name.lstrip(" ")
190                    if name == "power low":
191                        if compute_state['fix_enable_low']:
192                            name += ' (Fixed)'
193                        else:
194                            name += ' (Fitted)'
195                    if name == "power high":
196                        if compute_state['fix_enable_high']:
197                            name += ' (Fixed)'
198                        else:
199                            name += ' (Fitted)'
200                    state += "%s:   %s\n" % (name, value)
201        # Outputs
202        state += "\n=== Outputs ==="
203        for item in output_list:
204            item_split = item[0].split('_')
205            # Exclude the extrapolation that turned off
206            if len(item_split) > 1:
207                if low_off and item_split[1] == 'low':
208                    continue
209                if high_off and item_split[1] == 'high':
210                    continue
211            max_ind = len(item_split) - 1
212            value = None
213            if hasattr(self.container, item[0]):
214                # Q* outputs
215                value = getattr(self.container, item[0])
216            else:
217                # other outputs than Q*
218                name = item[0] + "_tcl"
219                if name in self.saved_state:
220                    value = self.saved_state[name]
221
222            # Exclude the outputs w/''
223            if value == '':
224                continue
225            # Error outputs
226            if item_split[max_ind] == 'err':
227                state += "+- %s " % format_number(value)
228            # Percentage outputs
229            elif item_split[max_ind] == 'percent':
230                value = float(value) * 100
231                state += "(%s %s)" % (format_number(value), '%')
232            # Outputs
233            else:
234                state += "\n%s:   %s " % (item[1],
235                                          format_number(value, high=True))
236        # Include warning msg
237        if self.container is not None:
238            state += "\n\nNote:\n" + self.container.warning_msg
239        return state
240
241    def clone_state(self):
242        """
243        deepcopy the state
244        """
245        return copy.deepcopy(self.saved_state)
246
247    def toXML(self, file="inv_state.inv", doc=None, entry_node=None):
248        """
249        Writes the state of the InversionControl panel to file, as XML.
250
251        Compatible with standalone writing, or appending to an
252        already existing XML document. In that case, the XML document
253        is required. An optional entry node in the XML document
254        may also be given.
255
256        : param file: file to write to
257        : param doc: XML document object [optional]
258        : param entry_node: XML node within the document at which we will append the data [optional]
259        """
260        # TODO: Get this to work
261        from xml.dom.minidom import getDOMImplementation
262        import time
263        timestamp = time.time()
264        # Check whether we have to write a standalone XML file
265        if doc is None:
266            impl = getDOMImplementation()
267
268            doc_type = impl.createDocumentType(INVNODE_NAME, "1.0", "1.0")
269
270            newdoc = impl.createDocument(None, INVNODE_NAME, doc_type)
271            top_element = newdoc.documentElement
272        else:
273            # We are appending to an existing document
274            newdoc = doc
275            top_element = newdoc.createElement(INVNODE_NAME)
276            if entry_node is None:
277                newdoc.documentElement.appendChild(top_element)
278            else:
279                entry_node.appendChild(top_element)
280
281        attr = newdoc.createAttribute("version")
282        attr.nodeValue = '1.0'
283        top_element.setAttributeNode(attr)
284
285        # File name
286        element = newdoc.createElement("filename")
287        if self.file is not None and self.file != '':
288            element.appendChild(newdoc.createTextNode(str(self.file)))
289        else:
290            element.appendChild(newdoc.createTextNode(str(file)))
291        top_element.appendChild(element)
292
293        element = newdoc.createElement("timestamp")
294        element.appendChild(newdoc.createTextNode(time.ctime(timestamp)))
295        attr = newdoc.createAttribute("epoch")
296        attr.nodeValue = str(timestamp)
297        element.setAttributeNode(attr)
298        top_element.appendChild(element)
299
300        # Current state
301        state = newdoc.createElement("state")
302        top_element.appendChild(state)
303
304        for name, value in self.saved_state.items():
305            element = newdoc.createElement(str(name))
306            element.appendChild(newdoc.createTextNode(str(value)))
307            state.appendChild(element)
308
309        # State history list
310        history = newdoc.createElement("history")
311        top_element.appendChild(history)
312
313        for name, value in self.state_list.items():
314            history_element = newdoc.createElement('state_' + str(name))
315            for state_name, state_value in value.items():
316                state_element = newdoc.createElement(str(state_name))
317                child = newdoc.createTextNode(str(state_value))
318                state_element.appendChild(child)
319                history_element.appendChild(state_element)
320            # history_element.appendChild(state_list_element)
321            history.appendChild(history_element)
322
323        # Bookmarks  bookmark_list[self.bookmark_num] = [\
324        # my_time,date,state,comp_state]
325        bookmark = newdoc.createElement("bookmark")
326        top_element.appendChild(bookmark)
327        item_list = ['time', 'date', 'state', 'comp_state']
328        for name, value_list in self.bookmark_list.items():
329            element = newdoc.createElement('mark_' + str(name))
330            _, date, state, comp_state = value_list
331            time_element = newdoc.createElement('time')
332            time_element.appendChild(newdoc.createTextNode(str(value_list[0])))
333            date_element = newdoc.createElement('date')
334            date_element.appendChild(newdoc.createTextNode(str(value_list[1])))
335            state_list_element = newdoc.createElement('state')
336            comp_state_list_element = newdoc.createElement('comp_state')
337            for state_name, state_value in value_list[2].items():
338                state_element = newdoc.createElement(str(state_name))
339                child = newdoc.createTextNode(str(state_value))
340                state_element.appendChild(child)
341                state_list_element.appendChild(state_element)
342            for comp_name, comp_value in value_list[3].items():
343                comp_element = newdoc.createElement(str(comp_name))
344                comp_element.appendChild(newdoc.createTextNode(str(comp_value)))
345                comp_state_list_element.appendChild(comp_element)
346            element.appendChild(time_element)
347            element.appendChild(date_element)
348            element.appendChild(state_list_element)
349            element.appendChild(comp_state_list_element)
350            bookmark.appendChild(element)
351
352        # Save the file
353        if doc is None:
354            fd = open('test000', 'w')
355            fd.write(newdoc.toprettyxml())
356            fd.close()
357            return None
358        else:
359            return newdoc
360
361    def fromXML(self, file=None, node=None):
362        """
363        Load invariant states from a file
364
365        : param file: .inv file
366        : param node: node of a XML document to read from
367        """
368        if file is not None:
369            msg = "InvariantSate no longer supports non-CanSAS"
370            msg += " format for invariant files"
371            raise RuntimeError(msg)
372
373        if node.get('version')\
374            and node.get('version') == '1.0':
375
376            # Get file name
377            entry = get_content('ns:filename', node)
378            if entry is not None:
379                file_name = entry.text.strip()
380
381            # Get time stamp
382            entry = get_content('ns:timestamp', node)
383            if entry is not None and entry.get('epoch'):
384                try:
385                    timestamp = (entry.get('epoch'))
386                except Exception as exc:
387                    msg = "InvariantSate.fromXML: Could not read"
388                    msg += " timestamp\n %s" % exc
389                    logger.error(msg)
390
391            # Parse bookmarks
392            entry_bookmark = get_content('ns:bookmark', node)
393
394            for ind in range(1, len(entry_bookmark) + 1):
395                temp_state = {}
396                temp_bookmark = {}
397                entry = get_content('ns:mark_%s' % ind, entry_bookmark)
398
399                if entry is not None:
400                    my_time = get_content('ns:time', entry)
401                    val_time = str(my_time.text.strip())
402                    date = get_content('ns:date', entry)
403                    val_date = str(date.text.strip())
404                    state_entry = get_content('ns:state', entry)
405                    for item in DEFAULT_STATE:
406                        input_field = get_content('ns:%s' % item, state_entry)
407                        val = str(input_field.text.strip())
408                        if input_field is not None:
409                            temp_state[item] = val
410                    comp_entry = get_content('ns:comp_state', entry)
411
412                    for item in DEFAULT_STATE:
413                        input_field = get_content('ns:%s' % item, comp_entry)
414                        val = str(input_field.text.strip())
415                        if input_field is not None:
416                            temp_bookmark[item] = val
417                    try:
418                        self.bookmark_list[ind] = [val_time, val_date, temp_state, temp_bookmark]
419                    except:
420                        raise "missing components of bookmarks..."
421            # Parse histories
422            entry_history = get_content('ns:history', node)
423
424            for ind in range(0, len(entry_history)):
425                temp_state = {}
426                entry = get_content('ns:state_%s' % ind, entry_history)
427
428                if entry is not None:
429                    for item in DEFAULT_STATE:
430                        input_field = get_content('ns:%s' % item, entry)
431                        if input_field.text is not None:
432                            val = str(input_field.text.strip())
433                        else:
434                            val = ''
435                        if input_field is not None:
436                            temp_state[item] = val
437                            self.state_list[str(ind)] = temp_state
438
439            # Parse current state (ie, saved_state)
440            entry = get_content('ns:state', node)
441            if entry is not None:
442                for item in DEFAULT_STATE:
443                    input_field = get_content('ns:%s' % item, entry)
444                    if input_field.text is not None:
445                        val = str(input_field.text.strip())
446                    else:
447                        val = ''
448                    if input_field is not None:
449                        self.set_saved_state(name=item, value=val)
450            self.file = file_name
451
452    def set_report_string(self):
453        """
454        Get the values (strings) from __str__ for report
455        """
456        # default string values
457        s = {num: 'NA' for num in range(1, 19)}
458        # get all string values from __str__()
459        lines = str(self).split('\n')
460        for line_num, line in enumerate(lines):
461            if line_num == 1:
462                s[1] = line
463            elif line_num == 2:
464                s[2] = line
465            else:
466                item = line.split(':')
467                item[0] = item[0].strip()
468                if item[0] == "scale":
469                    s[3] = item[1]
470                elif item[0] == "porod constant":
471                    s[4] = item[1]
472                elif item[0] == "background":
473                    s[5] = item[1]
474                elif item[0] == "contrast":
475                    s[6] = item[1]
476                elif item[0] == "Extrapolation":
477                    extra = item[1].split(";")
478                    bool_0 = extra[0].split("=")
479                    bool_1 = extra[1].split("=")
480                    s[8] = " " + bool_0[0] + "Q region = " + bool_0[1]
481                    s[7] = " " + bool_1[0] + "Q region = " + bool_1[1]
482                elif item[0] == "npts low":
483                    s[9] = item[1]
484                elif item[0] == "npts high":
485                    s[10] = item[1]
486                elif item[0] == "volume fraction":
487                    val = item[1].split("+-")[0].strip()
488                    error = item[1].split("+-")[1].strip()
489                    s[17] = val + " &plusmn; " + error
490                elif item[0] == "specific surface":
491                    val = item[1].split("+-")[0].strip()
492                    error = item[1].split("+-")[1].strip()
493                    s[18] = val + " &plusmn; " + error
494                elif item[0].split("(")[0].strip() == "power low":
495                    s[11] = item[0] + " =" + item[1]
496                elif item[0].split("(")[0].strip() == "power high":
497                    s[12] = item[0] + " =" + item[1]
498                elif item[0].split("[")[0].strip() == "Q* from low Q extrapolation":
499                    # looks messy but this way the symbols +_ and % work on html
500                    val = item[1].split("+-")[0].strip()
501                    error = item[1].split("+-")[1].strip()
502                    err = error.split("%")[0].strip()
503                    percent = error.split("%")[1].strip()
504                    s[13] = val + " &plusmn; " + err + "&#37" + percent
505                elif item[0].split("[")[0].strip() == "Q* from data":
506                    val = item[1].split("+-")[0].strip()
507                    error = item[1].split("+-")[1].strip()
508                    err = error.split("%")[0].strip()
509                    percent = error.split("%")[1].strip()
510                    s[14] = val + " &plusmn; " + err + "&#37" + percent
511                elif item[0].split("[")[0].strip() == "Q* from high Q extrapolation":
512                    val = item[1].split("+-")[0].strip()
513                    error = item[1].split("+-")[1].strip()
514                    err = error.split("%")[0].strip()
515                    percent = error.split("%")[1].strip()
516                    s[15] = val + " &plusmn; " + err + "&#37" + percent
517                elif item[0].split("[")[0].strip() == "total Q*":
518                    val = item[1].split("+-")[0].strip()
519                    error = item[1].split("+-")[1].strip()
520                    s[16] = val + " &plusmn; " + error
521                else:
522                    continue
523
524        s[1] = self._check_html_format(s[1])
525        file_name = self._check_html_format(self.file)
526
527        # make plot image
528        self.set_plot_state(extra_high=bool_0[1], extra_low=bool_1[1])
529        # get ready for report with setting all the html strings
530        self.report_str = str(self.template_str) % (
531            s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11],
532            s[12], s[13], s[14], s[15], s[16], s[17], s[18], file_name, "%s")
533
534    def _check_html_format(self, name):
535        """
536        Check string '%' for html format
537        """
538        if name.count('%'):
539            name = name.replace('%', '&#37')
540
541        return name
542
543    def set_saved_state(self, name, value):
544        """
545        Set the state list
546
547        : param name: name of the state component
548        : param value: value of the state component
549        """
550        rb_list = [['power_law_low', 'guinier'],
551                   ['fit_enable_low', 'fix_enable_low'],
552                   ['fit_enable_high', 'fix_enable_high']]
553
554        self.name = value
555        self.saved_state[name] = value
556        # set the count part of radio button clicked
557        # False for the saved_state
558        for title, content in rb_list:
559            if name == title:
560                name = content
561                value = False
562            elif name == content:
563                name = title
564                value = False
565        self.saved_state[name] = value
566        self.state_num = self.saved_state['state_num']
567
568    def set_plot_state(self, extra_high=False, extra_low=False):
569        """
570        Build image state that wx.html understand
571        by plotting, putting it into wx.FileSystem image object
572
573        : extrap_high,extra_low: low/high extrapolations
574        are possible extra-plots
575        """
576        # some imports
577        import wx
578        import matplotlib.pyplot as plt
579        from matplotlib.backends.backend_agg import FigureCanvasAgg
580
581        # we use simple plot, not plotpanel
582        # make matlab figure
583        fig = plt.figure()
584        fig.set_facecolor('w')
585        graph = fig.add_subplot(111)
586
587        # data plot
588        graph.errorbar(self.data.x, self.data.y, yerr=self.data.dy, fmt='o')
589        # low Q extrapolation fit plot
590        if not extra_low == 'False':
591            graph.plot(self.theory_lowQ.x, self.theory_lowQ.y)
592        # high Q extrapolation fit plot
593        if not extra_high == 'False':
594            graph.plot(self.theory_highQ.x, self.theory_highQ.y)
595        graph.set_xscale("log", nonposx='clip')
596        graph.set_yscale("log", nonposy='clip')
597        graph.set_xlabel('$\\rm{Q}(\\AA^{-1})$', fontsize=12)
598        graph.set_ylabel('$\\rm{Intensity}(cm^{-1})$', fontsize=12)
599        canvas = FigureCanvasAgg(fig)
600        # actually make image
601        canvas.draw()
602
603        # make python.Image object
604        # size
605        w, h = canvas.get_width_height()
606        # convert to wx.Image
607        wximg = wx.EmptyImage(w, h)
608        # wxim.SetData(img.convert('RGB').tostring() )
609        wximg.SetData(canvas.tostring_rgb())
610        # get the dynamic image for the htmlwindow
611        wximgbmp = wx.BitmapFromImage(wximg)
612        # store the image in wx.FileSystem Object
613        imgs, refs = ReportImageHandler.set_figs([fig], [wximgbmp], 'inv')
614
615        self.wximgbmp = refs[0]
616        self.image = imgs[0]
617
618class Reader(CansasReader):
619    """
620    Class to load a .inv invariant file
621    """
622    # # File type
623    type_name = "Invariant"
624
625    # # Wildcards
626    type = ["Invariant file (*.inv)|*.inv",
627            "SASView file (*.svs)|*.svs"]
628    # # List of allowed extensions
629    ext = ['.inv', '.INV', '.svs', 'SVS']
630
631    def __init__(self, call_back, cansas=True):
632        """
633        Initialize the call-back method to be called
634        after we load a file
635
636        : param call_back: call-back method
637        : param cansas:  True = files will be written/read in CanSAS format
638                        False = write CanSAS format
639        """
640        # # Call back method to be executed after a file is read
641        self.call_back = call_back
642        # # CanSAS format flag
643        self.cansas = cansas
644        self.state = None
645
646    def read(self, path):
647        """
648        Load a new invariant state from file
649
650        : param path: file path
651        : return: None
652        """
653        if self.cansas:
654            return self._read_cansas(path)
655        else:
656            return self._read_standalone(path)
657
658    def _read_standalone(self, path):
659        """
660        Load a new invariant state from file.
661        The invariant node is assumed to be the top element.
662
663        : param path: file path
664        : return: None
665        """
666        # Read the new state from file
667        state = InvariantState()
668
669        state.fromXML(file=path)
670
671        # Call back to post the new state
672        self.call_back(state)
673        return None
674
675    def _parse_state(self, entry):
676        """
677        Read an invariant result from an XML node
678
679        : param entry: XML node to read from
680        : return: InvariantState object
681        """
682        state = None
683        # Locate the invariant node
684        try:
685            nodes = entry.xpath('ns:%s' % INVNODE_NAME,
686                                namespaces={'ns': CANSAS_NS})
687            # Create an empty state
688            if nodes != []:
689                state = InvariantState()
690                state.fromXML(node=nodes[0])
691        except Exception as exc:
692            msg = "XML document does not contain invariant"
693            msg += " information.\n %s" % exc
694            logger.info(msg)
695        return state
696
697    def _read_cansas(self, path):
698        """
699        Load data and invariant information from a CanSAS XML file.
700
701        : param path: file path
702        : return: Data1D object if a single SASentry was found,
703                    or a list of Data1D objects if multiple entries were found,
704                    or None of nothing was found
705        : raise RuntimeError: when the file can't be opened
706        : raise ValueError: when the length of the data vectors are inconsistent
707        """
708        output = []
709        if os.path.isfile(path):
710            basename = os.path.basename(path)
711            root, extension = os.path.splitext(basename)
712
713            if  extension.lower() in self.ext or \
714                extension.lower() == '.xml':
715                tree = etree.parse(path, parser=etree.ETCompatXMLParser())
716
717                # Check the format version number
718                # Specifying the namespace will take care of
719                # the file format version
720                root = tree.getroot()
721
722                entry_list = root.xpath('/ns:SASroot/ns:SASentry',
723                                        namespaces={'ns': CANSAS_NS})
724
725                for entry in entry_list:
726                    invstate = self._parse_state(entry)
727                    # invstate could be None when .svs file is loaded
728                    # in this case, skip appending to output
729                    if invstate is not None:
730                        sas_entry, _ = self._parse_entry(entry)
731                        sas_entry.meta_data['invstate'] = invstate
732                        sas_entry.filename = invstate.file
733                        output.append(sas_entry)
734        else:
735            raise RuntimeError("%s is not a file" % path)
736
737        # Return output consistent with the loader's api
738        if len(output) == 0:
739            return None
740        elif len(output) == 1:
741            # Call back to post the new state
742            self.state = output[0].meta_data['invstate']
743            self.call_back(state=output[0].meta_data['invstate'],
744                           datainfo=output[0])
745            return output[0]
746        else:
747            return output
748
749    def get_state(self):
750        return self.state
751
752    def write(self, filename, datainfo=None, invstate=None):
753        """
754        Write the content of a Data1D as a CanSAS XML file
755
756        : param filename: name of the file to write
757        : param datainfo: Data1D object
758        : param invstate: InvariantState object
759        """
760        # Sanity check
761        if self.cansas:
762            doc = self.write_toXML(datainfo, invstate)
763            # Write the XML document
764            fd = open(filename, 'w')
765            fd.write(doc.toprettyxml())
766            fd.close()
767        else:
768            invstate.toXML(file=filename)
769
770    def write_toXML(self, datainfo=None, state=None):
771        """
772        Write toXML, a helper for write()
773
774        : return: xml doc
775        """
776        if datainfo is None:
777            datainfo = sas.sascalc.dataloader.data_info.Data1D(x=[], y=[])
778        elif not issubclass(datainfo.__class__, sas.sascalc.dataloader.data_info.Data1D):
779            msg = "The cansas writer expects a Data1D"
780            msg += " instance: %s" % str(datainfo.__class__.__name__)
781            raise RuntimeError(msg)
782        # make sure title and data run is filled up.
783        if datainfo.title is None or datainfo.title == '':
784            datainfo.title = datainfo.name
785        if datainfo.run_name is None or datainfo.run_name == {}:
786            datainfo.run = [str(datainfo.name)]
787            datainfo.run_name[0] = datainfo.name
788        # Create basic XML document
789        doc, sasentry = self._to_xml_doc(datainfo)
790        # Add the invariant information to the XML document
791        if state is not None:
792            doc = state.toXML(datainfo.name, doc=doc, entry_node=sasentry)
793        return doc
Note: See TracBrowser for help on using the repository browser.