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

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

Simplify calling image handler and add documentation.

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