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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 3968752 was 5875d3d, checked in by Piotr Rozyczko <rozyczko@…>, 8 years ago

Load invariant state correctly (fixes #615)

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