source: sasview/src/sas/sasgui/perspectives/invariant/invariant_state.py @ e9920cd

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249unittest-saveload
Last change on this file since e9920cd was e9920cd, checked in by krzywon, 13 months ago

Add SasView? version to report files. refs #1179

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