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

Last change on this file since bacc04b was cb93b40, checked in by lewis, 8 years ago

Load invariant state correctly (fixes #615)

  • Property mode set to 100644
File size: 30.6 KB
RevLine 
[51f14603]1"""
[78a205a]2    State class for the invariant UI
[51f14603]3"""
4
[78a205a]5# import time
[51f14603]6import os
7import sys
8import logging
9import copy
[b699768]10import sas.sascalc.dataloader
[78a205a]11# from xml.dom.minidom import parse
[51f14603]12from lxml import etree
[b699768]13from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
14from sas.sascalc.dataloader.readers.cansas_reader import get_content
[d85c194]15from sas.sasgui.guiframe.utils import format_number
16from sas.sasgui.guiframe.gui_style import GUIFRAME_ID
17from sas.sasgui.guiframe.dataFitting import Data1D
[51f14603]18
19INVNODE_NAME = 'invariant'
20CANSAS_NS = "cansas1d/1.0"
21
22# default state
[78a205a]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
[51f14603]49# init and the number of states increases
50state_list = {}
51bookmark_list = {}
[78a205a]52# list of input parameters (will be filled up on panel init) used by __str__
[51f14603]53input_list = {'background_tcl':0,
[78a205a]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"],
[51f14603]70               ["qstar_high_percent", "Q* percent from low Q extrapolation"],
[78a205a]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
[51f14603]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        """
[78a205a]88        # Input
89        self.file = None
[51f14603]90        self.data = Data1D(x=[], y=[], dx=None, dy=None)
[78a205a]91        self.theory_lowQ = Data1D(x=[], y=[], dy=None)
[51f14603]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
[78a205a]95        # self.is_time_machine = False
96        self.saved_state = DEFAULT_STATE
[51f14603]97        self.state_list = state_list
98        self.bookmark_list = bookmark_list
99        self.input_list = input_list
100        self.output_list = output_list
[78a205a]101
[51f14603]102        self.compute_num = 0
103        self.state_num = 0
104        self.timestamp = ('00:00:00', '00/00/0000')
105        self.container = None
[78a205a]106        # plot image
[51f14603]107        self.wximbmp = None
108        # report_html strings
[d85c194]109        import sas.sasgui.perspectives.invariant as invariant
[51f14603]110        path = invariant.get_data_path(media='media')
[78a205a]111        path_report_html = os.path.join(path, "report_template.html")
112        html_template = open(path_report_html, "r")
[51f14603]113        self.template_str = html_template.read()
114        self.report_str = self.template_str
[78a205a]115        # self.report_str_save = None
[51f14603]116        html_template.close()
[78a205a]117
[51f14603]118    def __str__(self):
119        """
120        Pretty print
[78a205a]121
[51f14603]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"
[78a205a]135
[51f14603]136        # text ctl general inputs ( excluding extrapolation text ctl)
[78a205a]137        for key, value in self.input_list.iteritems():
[51f14603]138            if value == '':
139                continue
[78a205a]140            key_split = key.split('_')
141            max_ind = len(key_split) - 1
142            if key_split[max_ind] == 'tcl':
[51f14603]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)
[78a205a]149
150        # other input parameters
[51f14603]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'
[78a205a]160        state += "\nExtrapolation:  High=%s; Low=%s\n" % (extra_hi, extra_lo)
[51f14603]161        low_off = False
162        high_off = False
[78a205a]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 = ""
[51f14603]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 \
[78a205a]171                        key_split[max_ind - 1] == 'low':
[51f14603]172                        low_off = True
[78a205a]173                        continue
[51f14603]174                    elif not compute_state['enable_high_cbox'] and \
[78a205a]175                        key_split[max_ind - 1] == 'high':
[51f14603]176                        high_off = True
177                        continue
178                    elif extra_lo == 'Guinier' and key_split[0] == 'power' and \
[78a205a]179                        key_split[max_ind - 1] == 'low':
[51f14603]180                        continue
181                    for ind in range(0, max_ind):
182                        name += " %s" % key_split[ind]
183                    name = name.lstrip(" ")
[78a205a]184                    if name == "power low":
[51f14603]185                        if compute_state['fix_enable_low']:
186                            name += ' (Fixed)'
187                        else:
188                            name += ' (Fitted)'
[78a205a]189                    if name == "power high":
[51f14603]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:
[78a205a]198            item_split = item[0].split('_')
[51f14603]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
[78a205a]207            if hasattr(self.container, item[0]):
[51f14603]208                # Q* outputs
[78a205a]209                value = getattr(self.container, item[0])
210            else:
[51f14603]211                # other outputs than Q*
212                name = item[0] + "_tcl"
213                if name in self.saved_state.keys():
[78a205a]214                    value = self.saved_state[name]
215
216            # Exclude the outputs w/''
[51f14603]217            if value == '':
[78a205a]218                continue
[51f14603]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':
[78a205a]224                value = float(value) * 100
[51f14603]225                state += "(%s %s)" % (format_number(value), '%')
226            # Outputs
227            else:
[78a205a]228                state += "\n%s:   %s " % (item[1],
229                                          format_number(value, high=True))
[51f14603]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.
[78a205a]244
[51f14603]245        Compatible with standalone writing, or appending to an
246        already existing XML document. In that case, the XML document
[78a205a]247        is required. An optional entry node in the XML document
[51f14603]248        may also be given.
[78a205a]249
[51f14603]250        : param file: file to write to
251        : param doc: XML document object [optional]
[78a205a]252        : param entry_node: XML node within the document at which we will append the data [optional]
[51f14603]253        """
[92a2ecd]254        # TODO: Get this to work
[51f14603]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()
[78a205a]261
262            doc_type = impl.createDocumentType(INVNODE_NAME, "1.0", "1.0")
263
[51f14603]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)
[78a205a]274
[51f14603]275        attr = newdoc.createAttribute("version")
276        attr.nodeValue = '1.0'
277        top_element.setAttributeNode(attr)
[78a205a]278
[51f14603]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)
[78a205a]286
[51f14603]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)
[78a205a]293
[51f14603]294        # Current state
295        state = newdoc.createElement("state")
296        top_element.appendChild(state)
[78a205a]297
298        for name, value in self.saved_state.iteritems():
[51f14603]299            element = newdoc.createElement(str(name))
300            element.appendChild(newdoc.createTextNode(str(value)))
301            state.appendChild(element)
[78a205a]302
[51f14603]303        # State history list
304        history = newdoc.createElement("history")
305        top_element.appendChild(history)
[78a205a]306
[51f14603]307        for name, value in self.state_list.iteritems():
308            history_element = newdoc.createElement('state_' + str(name))
[78a205a]309            for state_name, state_value in value.iteritems():
[51f14603]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)
[78a205a]314            # history_element.appendChild(state_list_element)
[51f14603]315            history.appendChild(history_element)
316
317        # Bookmarks  bookmark_list[self.bookmark_num] = [\
[78a205a]318        # my_time,date,state,comp_state]
[51f14603]319        bookmark = newdoc.createElement("bookmark")
320        top_element.appendChild(bookmark)
[78a205a]321        item_list = ['time', 'date', 'state', 'comp_state']
[51f14603]322        for name, value_list in self.bookmark_list.iteritems():
[78a205a]323            element = newdoc.createElement('mark_' + str(name))
324            _, date, state, comp_state = value_list
[51f14603]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:
[92a2ecd]353            return newdoc
[78a205a]354
[51f14603]355    def fromXML(self, file=None, node=None):
356        """
357        Load invariant states from a file
[78a205a]358
[51f14603]359        : param file: .inv file
[78a205a]360        : param node: node of a XML document to read from
[51f14603]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
[78a205a]366
[51f14603]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)
[78a205a]384
[51f14603]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 = {}
[78a205a]391                entry = get_content('ns:mark_%s' % ind, entry_bookmark)
392
[51f14603]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)
[78a205a]399                    for item in DEFAULT_STATE:
[51f14603]400                        input_field = get_content('ns:%s' % item, state_entry)
401                        val = str(input_field.text.strip())
402                        if input_field is not None:
[78a205a]403                            temp_state[item] = val
[51f14603]404                    comp_entry = get_content('ns:comp_state', entry)
[78a205a]405
406                    for item in DEFAULT_STATE:
[51f14603]407                        input_field = get_content('ns:%s' % item, comp_entry)
408                        val = str(input_field.text.strip())
409                        if input_field is not None:
[78a205a]410                            temp_bookmark[item] = val
[51f14603]411                    try:
[78a205a]412                        self.bookmark_list[ind] = [val_time, val_date, temp_state, temp_bookmark]
[51f14603]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 = {}
[78a205a]420                entry = get_content('ns:state_%s' % ind, entry_history)
[51f14603]421
422                if entry is not None:
[78a205a]423                    for item in DEFAULT_STATE:
424                        input_field = get_content('ns:%s' % item, entry)
[51f14603]425                        val = str(input_field.text.strip())
426                        if input_field is not None:
[78a205a]427                            temp_state[item] = val
[cb93b40]428                            self.state_list[str(ind)] = temp_state
[78a205a]429
[51f14603]430            # Parse current state (ie, saved_state)
[78a205a]431            entry = get_content('ns:state', node)
[51f14603]432            if entry is not None:
[78a205a]433                for item in DEFAULT_STATE:
[51f14603]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
[78a205a]439
[51f14603]440    def set_report_string(self):
441        """
[78a205a]442        Get the values (strings) from __str__ for report
[51f14603]443        """
444        strings = self.__str__()
[78a205a]445
[51f14603]446        # default string values
[78a205a]447        for num in range(1, 19):
[51f14603]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":
[78a205a]478                    val = item[1].split("+-")[0].strip()
[51f14603]479                    error = item[1].split("+-")[1].strip()
480                    s_17 = val + " ± " + 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 + " ± " + error
485                elif item[0].split("(")[0].strip() == "power low":
[78a205a]486                    s_11 = item[0] + " =" + item[1]
[51f14603]487                elif item[0].split("(")[0].strip() == "power high":
[78a205a]488                    s_12 = item[0] + " =" + item[1]
[51f14603]489                elif item[0].split("[")[0].strip() == "Q* from low Q extrapolation":
[78a205a]490                    # looks messy but this way the symbols +_ and % work on html
[51f14603]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 + " ± " + 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 + " ± " + 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 + " ± " + 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 + " ± " + 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
[78a205a]519        self.set_plot_state(extra_high=bool_0[1], extra_low=bool_1[1])
[51f14603]520        # get ready for report with setting all the html strings
521        self.report_str = str(self.template_str) % (s_1, s_2,
[78a205a]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")
[51f14603]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')
[78a205a]532
[51f14603]533        return name
[78a205a]534
[51f14603]535    def set_saved_state(self, name, value):
536        """
[78a205a]537        Set the state list
538
[51f14603]539        : param name: name of the state component
540        : param value: value of the state component
541        """
[78a205a]542        rb_list = [['power_law_low', 'guinier'],
543                   ['fit_enable_low', 'fix_enable_low'],
544                   ['fit_enable_high', 'fix_enable_high']]
[51f14603]545
[78a205a]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']
[51f14603]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
[78a205a]564
[51f14603]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
[78a205a]573        # we use simple plot, not plotpanel
574        # make matlab figure
[51f14603]575        fig = plt.figure()
576        fig.set_facecolor('w')
577        graph = fig.add_subplot(111)
578
[78a205a]579        # data plot
[51f14603]580        graph.errorbar(self.data.x, self.data.y, yerr=self.data.dy, fmt='o')
[78a205a]581        # low Q extrapolation fit plot
[51f14603]582        if not extra_low == 'False':
[78a205a]583            graph.plot(self.theory_lowQ.x, self.theory_lowQ.y)
584        # high Q extrapolation fit plot
[51f14603]585        if not extra_high == 'False':
[78a205a]586            graph.plot(self.theory_highQ.x, self.theory_highQ.y)
[51f14603]587        graph.set_xscale("log", nonposx='clip')
588        graph.set_yscale("log", nonposy='clip')
[78a205a]589        graph.set_xlabel('$\\rm{Q}(\\AA^{-1})$', fontsize=12)
590        graph.set_ylabel('$\\rm{Intensity}(cm^{-1})$', fontsize=12)
[51f14603]591        canvas = FigureCanvasAgg(fig)
[78a205a]592        # actually make image
[51f14603]593        canvas.draw()
[78a205a]594
595        # make python.Image object
596        # size
[51f14603]597        w, h = canvas.get_width_height()
[78a205a]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
[51f14603]603        wximgbmp = wx.BitmapFromImage(wximg)
[78a205a]604        # store the image in wx.FileSystem Object
[51f14603]605        wx.FileSystem.AddHandler(wx.MemoryFSHandler())
606        # use wx.MemoryFSHandler
607        self.imgRAM = wx.MemoryFSHandler()
[78a205a]608        # AddFile, image can be retrieved with 'memory:filename'
[51f14603]609        self.imgRAM.AddFile('img_inv.png', wximgbmp, wx.BITMAP_TYPE_PNG)
[78a205a]610
[51f14603]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    """
[78a205a]618    # # File type
[51f14603]619    type_name = "Invariant"
[78a205a]620
621    # # Wildcards
[51f14603]622    type = ["Invariant file (*.inv)|*.inv",
[b9a5f0e]623            "SASView file (*.svs)|*.svs"]
[78a205a]624    # # List of allowed extensions
625    ext = ['.inv', '.INV', '.svs', 'SVS']
626
[51f14603]627    def __init__(self, call_back, cansas=True):
628        """
629        Initialize the call-back method to be called
630        after we load a file
[78a205a]631
[51f14603]632        : param call_back: call-back method
633        : param cansas:  True = files will be written/read in CanSAS format
[78a205a]634                        False = write CanSAS format
[51f14603]635        """
[78a205a]636        # # Call back method to be executed after a file is read
[51f14603]637        self.call_back = call_back
[78a205a]638        # # CanSAS format flag
[51f14603]639        self.cansas = cansas
640        self.state = None
641
642    def read(self, path):
[78a205a]643        """
[51f14603]644        Load a new invariant state from file
[78a205a]645
[51f14603]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)
[78a205a]653
[51f14603]654    def _read_standalone(self, path):
[78a205a]655        """
[51f14603]656        Load a new invariant state from file.
657        The invariant node is assumed to be the top element.
[78a205a]658
[51f14603]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)
[78a205a]666
[51f14603]667        # Call back to post the new state
668        self.call_back(state)
669        return None
[78a205a]670
[51f14603]671    def _parse_state(self, entry):
672        """
673        Read an invariant result from an XML node
[78a205a]674
675        : param entry: XML node to read from
[51f14603]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
[78a205a]690            logging.info(msg)
[51f14603]691        return state
[78a205a]692
[51f14603]693    def _read_cansas(self, path):
[78a205a]694        """
[51f14603]695        Load data and invariant information from a CanSAS XML file.
[78a205a]696
[51f14603]697        : param path: file path
[78a205a]698        : return: Data1D object if a single SASentry was found,
[51f14603]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):
[78a205a]706            basename = os.path.basename(path)
[51f14603]707            root, extension = os.path.splitext(basename)
[78a205a]708
[51f14603]709            if  extension.lower() in self.ext or \
710                extension.lower() == '.xml':
711                tree = etree.parse(path, parser=etree.ETCompatXMLParser())
[78a205a]712
[51f14603]713                # Check the format version number
[78a205a]714                # Specifying the namespace will take care of
715                # the file format version
[51f14603]716                root = tree.getroot()
[78a205a]717
[51f14603]718                entry_list = root.xpath('/ns:SASroot/ns:SASentry',
719                                        namespaces={'ns': CANSAS_NS})
[78a205a]720
[51f14603]721                for entry in entry_list:
[78a205a]722
[92a2ecd]723                    sas_entry, _ = self._parse_entry(entry)
[51f14603]724                    invstate = self._parse_state(entry)
[78a205a]725
726                    # invstate could be None when .svs file is loaded
727                    # in this case, skip appending to output
[51f14603]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'],
[78a205a]742                           datainfo=output[0])
[51f14603]743            return output[0]
744        else:
[78a205a]745            return output
746
[51f14603]747    def get_state(self):
748        return self.state
[78a205a]749
[51f14603]750    def write(self, filename, datainfo=None, invstate=None):
751        """
752        Write the content of a Data1D as a CanSAS XML file
[78a205a]753
[51f14603]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:
[78a205a]760            doc = self.write_toXML(datainfo, invstate)
[51f14603]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)
[78a205a]767
[51f14603]768    def write_toXML(self, datainfo=None, state=None):
769        """
770        Write toXML, a helper for write()
[78a205a]771
[51f14603]772        : return: xml doc
773        """
774        if datainfo is None:
[c10d9d6c]775            datainfo = sas.sascalc.dataloader.data_info.Data1D(x=[], y=[])
776        elif not issubclass(datainfo.__class__, sas.sascalc.dataloader.data_info.Data1D):
[51f14603]777            msg = "The cansas writer expects a Data1D"
778            msg += " instance: %s" % str(datainfo.__class__.__name__)
779            raise RuntimeError, msg
[78a205a]780        # make sure title and data run is filled up.
[51f14603]781        if datainfo.title == None or datainfo.title == '':
782            datainfo.title = datainfo.name
[78a205a]783        if datainfo.run_name == None or datainfo.run_name == {}:
[51f14603]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:
[78a205a]790            doc = state.toXML(datainfo.name, doc=doc, entry_node=sasentry)
[51f14603]791        return doc
Note: See TracBrowser for help on using the repository browser.