source: sasview/invariantview/perspectives/invariant/invariant_state.py @ f5038c06

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since f5038c06 was 4e1c362, checked in by Jae Cho <jhjcho@…>, 14 years ago

Invariant: undo, redo, bookmark, and saving works now: needs plottool fix for making right order on plotting from file: also need some cleaning-up and doc.

  • Property mode set to 100644
File size: 23.9 KB
Line 
1import time, os, sys
2import logging
3import copy
4import DataLoader
5from xml.dom.minidom import parse
6from lxml import etree
7
8from DataLoader.readers.cansas_reader import Reader as CansasReader
9from DataLoader.readers.cansas_reader import get_content
10from sans.guiframe.utils import format_number
11from sans.guiframe.dataFitting import Theory1D, Data1D
12INVNODE_NAME = 'invariant'
13CANSAS_NS = "cansas1d/1.0"
14# default state
15list = {'file': 'None',
16        'compute_num':0,
17        'state_num':0,
18        'is_time_machine':False,
19        'background_tcl':0.0,
20        'scale_tcl':1.0,
21        'contrast_tcl':1.0,
22        'porod_constant_tcl':'',
23        'npts_low_tcl':10,
24        'npts_high_tcl':10,
25        'power_high_tcl':4.0,
26        'power_low_tcl': 4.0,
27        'enable_high_cbox':False,
28        'enable_low_cbox':False,
29        'guinier': True,
30        'power_law_high': False,
31        'power_law_low': False,
32        'fit_enable_high': False,
33        'fit_enable_low': False,
34        'fix_enable_high':True,
35        'fix_enable_low':True,
36        'volume_tcl':'',
37        'volume_err_tcl':'',
38        'surface_tcl':'',
39        'surface_err_tcl':''}
40# list of states: This list will be filled as panel init and the number of states increases
41state_list = {}
42bookmark_list = {}
43# list of input parameters (will be filled up on panel init) used by __str__
44input_list = {}   
45# list of output parameters (order sensitive) used by __str__   
46output_list = [["qstar_low",                  "Q* from low Q extrapolation [1/(cm*A)]"],
47               ["qstar_low_err",             "dQ* from low Q extrapolation"],
48               ["qstar_low_percent",  "Q* percent from low Q extrapolation"],
49               ["qstar",                                     "Q* from data [1/(cm*A)]"],
50               ["qstar_err",                                "dQ* from data"],
51               ["qstar_percent",                     "Q* percent from data"],
52               ["qstar_high",                "Q* from high Q extrapolation [1/(cm*A)]"],
53               ["qstar_high_err",           "dQ* from high Q extrapolation"],
54               ["qstar_high_percent", "Q* percent from low Q extrapolation"],
55               ["qstar_total",                                   "total Q* [1/(cm*A)]"],
56               ["qstar_total_err",                              "total dQ*"],
57               ["volume",                                 "volume fraction"],
58               ["volume_err",                       "volume fraction error"],
59               ["surface",                               "specific surface"],
60               ["surface_err",                     "specific surface error"]]
61
62   
63
64class InvariantState(object):
65    """
66    Class to hold the state information of the InversionControl panel.
67    """
68    def __init__(self):
69        """
70        Default values
71        """
72        # Input
73        self.file  = None
74        self.data = Data1D(x=[], y=[], dx=None, dy=None)
75        self.theory_lowQ =  Theory1D(x=[], y=[], dy=None)
76        self.theory_highQ = Theory1D(x=[], y=[], dy=None)
77        #self.is_time_machine = False
78        self.saved_state = list
79        self.state_list = state_list
80        self.bookmark_list = bookmark_list
81        self.input_list = input_list
82        self.output_list = output_list
83       
84        self.compute_num = 0
85        self.state_num = 0
86        self.timestamp = None
87        self.container = None
88       
89       
90    def __str__(self):
91        """
92        Pretty print
93           
94        : return: string representing the state
95        """
96        # Input string
97        compute_num = self.saved_state['compute_num']
98        compute_state = self.state_list[str(compute_num)]
99        my_time, date = self.timestamp
100        file_name = self.file
101
102        state_num = int(self.saved_state['state_num'])
103        state = "\n[Invariant computation for %s: "% file_name
104        state += "performed at %s on %s] \n"%(my_time, date)
105        state += "State No.: %d \n"% state_num
106        state += "\n=== Inputs ===\n"
107       
108        # text ctl general inputs ( excluding extrapolation text ctl)
109        for key,value in self.input_list.iteritems(): 
110            if value == '':
111                continue
112            key_split = key.split('_') 
113            max_ind = len(key_split)-1
114            if key_split[max_ind] == 'tcl': 
115                name =""
116                if key_split[1]== 'low' or key_split[1]== 'high':
117                    continue
118                for ind in range(0,max_ind):
119                    name +=" %s"%key_split[ind]
120                state += "%s:   %s\n"% (name.lstrip(" "),value)
121               
122        # other input parameters       
123        extra_lo = compute_state['enable_low_cbox']
124        if compute_state['enable_low_cbox']:
125            if compute_state['guinier']:
126                extra_lo = 'Guinier'
127            else:
128                extra_lo = 'Power law'
129        extra_hi = compute_state['enable_high_cbox']
130        if compute_state['enable_high_cbox']:
131            extra_hi = 'Power law'
132        state += "\nExtrapolation:  High=%s; Low=%s\n" %(extra_hi,extra_lo)   
133        low_off = False
134        high_off = False
135        for key,value in self.input_list.iteritems(): 
136            key_split = key.split('_') 
137            max_ind = len(key_split)-1   
138            if key_split[max_ind] == 'tcl': 
139                name ="" 
140                # check each buttons whether or not ON or OFF
141                if key_split[1]== 'low' or key_split[1]== 'high':
142                    if not compute_state['enable_low_cbox'] and key_split[max_ind-1] == 'low':
143                        low_off = True
144                        continue             
145                    elif not compute_state['enable_high_cbox'] and key_split[max_ind-1]== 'high':
146                        high_off = True
147                        continue
148                    elif extra_lo == 'Guinier' and key_split[0]== 'power' and key_split[max_ind-1]== 'low':
149                        continue
150                    for ind in range(0,max_ind):
151                        name +=" %s"%key_split[ind]
152                    name = name.lstrip(" ")
153                    if name == "power low" :
154                        if compute_state['fix_enable_low']:
155                            name += ' (Fixed)'
156                        else:
157                            name += ' (Fitted)'
158                    if name == "power high" :
159                        if compute_state['fix_enable_high']:
160                            name += ' (Fixed)'
161                        else:
162                            name += ' (Fitted)'
163                    state += "%s:   %s\n"% (name,value)
164        # Outputs
165        state += "\n=== Outputs ==="
166        for item in output_list:
167            item_split = item[0].split('_') 
168            # Exclude the extrapolation that turned off
169            if len(item_split)>1:
170                if low_off and item_split[1] == 'low': continue
171                if high_off and item_split[1] == 'high': continue
172            max_ind = len(item_split)-1
173            value = None
174            try:
175                # Q* outputs
176                exec "value = self.container.%s\n"% item[0]
177            except:
178                # other outputs than Q*
179                name = item[0]+"_tcl"
180                exec "value = self.saved_state['%s']"% name
181               
182            # Exclude the outputs w/''   
183            if value == '': continue   
184           
185            # Error outputs
186            if item_split[max_ind] == 'err':
187                state += "+- %s "%format_number(value)
188               
189            # Percentage outputs
190            elif item_split[max_ind] == 'percent':
191                try:
192                    value = float(value)*100
193                except:
194                    pass
195                state += "(%s %s)"%(format_number(value),'%')
196            # Outputs
197            else:
198                state += "\n%s:   %s "%(item[1],format_number(value,high=True))
199        # Include warning msg
200        state += "\n\nNote:\n" + self.container.warning_msg
201
202        return state
203
204   
205    def clone_state(self):
206        """
207        deepcopy the state
208        """
209        return copy.deepcopy(self.saved_state)
210   
211
212    def toXML(self, file="inv_state.inv", doc=None, entry_node=None):
213        """
214        Writes the state of the InversionControl panel to file, as XML.
215       
216        Compatible with standalone writing, or appending to an
217        already existing XML document. In that case, the XML document
218        is required. An optional entry node in the XML document may also be given.
219       
220        : param file: file to write to
221        : param doc: XML document object [optional]
222        : param entry_node: XML node within the XML document at which we will append the data [optional]   
223        """
224        from xml.dom.minidom import getDOMImplementation
225        import time
226        timestamp = time.time()
227        # Check whether we have to write a standalone XML file
228        if doc is None:
229            impl = getDOMImplementation()
230       
231            doc_type = impl.createDocumentType(INVNODE_NAME, "1.0", "1.0")     
232       
233            newdoc = impl.createDocument(None, INVNODE_NAME, doc_type)
234            top_element = newdoc.documentElement
235        else:
236            # We are appending to an existing document
237            newdoc = doc
238            top_element = newdoc.createElement(INVNODE_NAME)
239            if entry_node is None:
240                newdoc.documentElement.appendChild(top_element)
241            else:
242                entry_node.appendChild(top_element)
243           
244        attr = newdoc.createAttribute("version")
245        attr.nodeValue = '1.0'
246        top_element.setAttributeNode(attr)
247       
248        # File name
249        element = newdoc.createElement("filename")
250        if self.file is not None:
251            element.appendChild(newdoc.createTextNode(str(self.file)))
252        else:
253            element.appendChild(newdoc.createTextNode(str(file)))
254        top_element.appendChild(element)
255       
256        element = newdoc.createElement("timestamp")
257        element.appendChild(newdoc.createTextNode(time.ctime(timestamp)))
258        attr = newdoc.createAttribute("epoch")
259        attr.nodeValue = str(timestamp)
260        element.setAttributeNode(attr)
261        top_element.appendChild(element)
262       
263        # Current state
264        state = newdoc.createElement("state")
265        top_element.appendChild(state)
266       
267        for name,value in self.saved_state.iteritems():
268            element = newdoc.createElement(str(name))
269            element.appendChild(newdoc.createTextNode(str(value)))
270            state.appendChild(element)
271             
272        # State history list
273        history = newdoc.createElement("history")
274        top_element.appendChild(history)
275       
276        for name, value in self.state_list.iteritems():
277            history_element = newdoc.createElement('state_'+str(name))
278            for state_name,state_value in value.iteritems():
279                state_element = newdoc.createElement(str(state_name))
280                state_element.appendChild(newdoc.createTextNode(str(state_value)))
281                history_element.appendChild(state_element)
282            #history_element.appendChild(state_list_element)
283            history.appendChild(history_element)
284
285        # Bookmarks  bookmark_list[self.bookmark_num] = [my_time,date,state,comp_state]
286        bookmark = newdoc.createElement("bookmark")
287        top_element.appendChild(bookmark)
288        item_list = ['time','date','state','comp_state']
289        for name, value_list in self.bookmark_list.iteritems():
290            element = newdoc.createElement('mark_'+ str(name))
291            time,date,state,comp_state = value_list
292            time_element = newdoc.createElement('time')
293            time_element.appendChild(newdoc.createTextNode(str(value_list[0])))
294            date_element = newdoc.createElement('date')
295            date_element.appendChild(newdoc.createTextNode(str(value_list[1])))
296            state_list_element = newdoc.createElement('state')
297            comp_state_list_element = newdoc.createElement('comp_state')
298            for state_name,state_value in value_list[2].iteritems():
299                state_element = newdoc.createElement(str(state_name))
300                state_element.appendChild(newdoc.createTextNode(str(state_value)))
301                state_list_element.appendChild(state_element)
302            for comp_name,comp_value in value_list[3].iteritems():
303                comp_element = newdoc.createElement(str(comp_name))
304                comp_element.appendChild(newdoc.createTextNode(str(comp_value)))
305                comp_state_list_element.appendChild(comp_element)
306            element.appendChild(time_element)
307            element.appendChild(date_element)
308            element.appendChild(state_list_element)
309            element.appendChild(comp_state_list_element)
310            bookmark.appendChild(element)
311
312        # Save the file
313        if doc is None:
314            fd = open('test000', 'w')
315            fd.write(newdoc.toprettyxml())
316            fd.close()
317            return None
318        else:
319            return newdoc.toprettyxml()
320       
321    def fromXML(self, file=None, node=None):
322        """
323        Load invariant states from a file
324           
325        : param file: .inv file
326        : param node: node of a XML document to read from       
327        """
328        if file is not None:
329            raise RuntimeError, "InvariantSate no longer supports non-CanSAS format for invariant files"
330       
331        if node.get('version')\
332            and node.get('version') == '1.0':
333
334            # Get file name
335            entry = get_content('ns:filename', node)
336            if entry is not None:
337                self.file = entry.text.strip()
338           
339            # Get time stamp
340            entry = get_content('ns:timestamp', node)
341            if entry is not None and entry.get('epoch'):
342                try:
343                    timestamp = (entry.get('epoch'))
344                except:
345                    logging.error("InvariantSate.fromXML: Could not read timestamp\n %s" % sys.exc_value)
346           
347            # Parse bookmarks
348            entry_bookmark = get_content('ns:bookmark', node)
349
350            for ind in range(1,len(entry_bookmark)+1):
351                temp_state = {}
352                temp_bookmark = {}
353                entry = get_content('ns:mark_%s' % ind, entry_bookmark) 
354                               
355                if entry is not None:
356                    time = get_content('ns:time', entry )
357                    val_time = str(time.text.strip())
358                    date = get_content('ns:date', entry )
359                    val_date = str(date.text.strip())
360                    state_entry = get_content('ns:state', entry )
361                    for item in list:
362
363                        input_field = get_content('ns:%s' % item, state_entry )
364                        val = str(input_field.text.strip())
365
366                        if input_field is not None:
367                            try:
368                                exec "temp_state['%s'] = %s"% (item,val)     
369                            except:
370                                exec "temp_state['%s'] = '%s'"% (item,val)
371
372                           
373                    comp_entry = get_content('ns:comp_state', entry )
374                   
375                    for item in list:
376                        input_field = get_content('ns:%s' % item, comp_entry )
377                        val = str(input_field.text.strip())
378                        if input_field is not None:
379                            try:
380                                exec "temp_bookmark['%s'] = %s"% (item,val)     
381                            except:
382                                exec "temp_bookmark['%s'] = '%s'"% (item,val)
383                    try:
384                        exec "self.bookmark_list[%s] = [val_time,val_date,temp_state,temp_bookmark]"% ind
385
386                    except:
387                        raise "missing components of bookmarks..."
388   
389            # Parse histories
390            entry_history = get_content('ns:history', node)
391
392            for ind in range(0,len(entry_history)):
393                temp_state = {}
394                entry = get_content('ns:state_%s' % ind, entry_history) 
395
396                if entry is not None:
397                    for item in list:
398                        input_field = get_content('ns:%s' % item, entry )
399                        val = str(input_field.text.strip())
400
401                        if input_field is not None:
402                            try:
403                                exec "temp_state['%s'] = %s"% (item,val)         
404                            except:
405                                exec "temp_state['%s'] = '%s'"% (item,val)
406                            finally:
407                                exec "self.state_list['%s'] = temp_state"% ind
408           
409            # Parse current state (ie, saved_state)
410            entry = get_content('ns:state', node)           
411            if entry is not None:
412                for item in list:
413                    input_field = get_content('ns:%s' % item, entry)
414                    val = str(input_field.text.strip())
415                    if input_field is not None:
416                        self.set_saved_state(name=item, value=val)
417
418
419           
420    def set_saved_state(self, name, value):
421        """
422        Set the state list
423                   
424        : param name: name of the state component
425        : param value: value of the state component
426        """
427        rb_list = [['power_law_low','guinier'],['fit_enable_low','fix_enable_low'],['fit_enable_high','fix_enable_high']]
428
429        try:
430            if value == None or value.lstrip().rstrip() =='':
431                exec "self.%s = '%s'" % (name, value)
432                exec "self.saved_state['%s'] = '%s'" %  (name, value)
433            else:
434                exec 'self.%s = %s' % (name, value)
435                exec "self.saved_state['%s'] = %s" %  (name, value)
436
437           
438            # set the count part of radio button clicked False for the saved_state
439            for title,content in rb_list:
440                if name ==  title:
441                    name = content
442                    value = False     
443                elif name == content:
444                    name = title
445                    value = False 
446            exec "self.saved_state['%s'] = '%s'" %  (name, value)     
447            self.state_num = self.saved_state['state_num']
448        except:           
449            pass
450
451
452
453class Reader(CansasReader):
454    """
455    Class to load a .inv invariant file
456    """
457    ## File type
458    type_name = "Invariant"
459   
460    ## Wildcards
461    type = ["Invariant file (*.inv)|*.inv"]
462    ## List of allowed extensions
463    ext=['.inv', '.INV'] 
464   
465    def __init__(self, call_back, cansas=True):
466        """
467        Initialize the call-back method to be called
468        after we load a file
469       
470        : param call_back: call-back method
471        : param cansas:  True = files will be written/read in CanSAS format
472                        False = write CanSAS format 
473        """
474        ## Call back method to be executed after a file is read
475        self.call_back = call_back
476        ## CanSAS format flag
477        self.cansas = cansas
478
479    def read(self, path):
480        """
481        Load a new invariant state from file
482       
483        : param path: file path
484        : return: None
485        """
486        if self.cansas==True:
487            return self._read_cansas(path)
488        else:
489            return self._read_standalone(path)
490       
491    def _read_standalone(self, path):
492        """
493        Load a new invariant state from file.
494        The invariant node is assumed to be the top element.
495       
496        : param path: file path
497        : return: None
498        """
499        # Read the new state from file
500        state = InvariantState()
501
502        state.fromXML(file=path)
503       
504        # Call back to post the new state
505        self.call_back(state)
506        return None
507   
508    def _parse_state(self, entry):
509        """
510        Read an invariant result from an XML node
511       
512        : param entry: XML node to read from
513        : return: InvariantState object
514        """
515        # Create an empty state
516        state = InvariantState()
517
518        # Locate the invariant node
519        try:
520            nodes = entry.xpath('ns:%s' % INVNODE_NAME, namespaces={'ns': CANSAS_NS})
521            state.fromXML(node=nodes[0])
522        except:
523            logging.info("XML document does not contain invariant information.\n %s" % sys.exc_value) 
524        return state
525   
526    def _read_cansas(self, path):
527        """
528        Load data and invariant information from a CanSAS XML file.
529       
530        : param path: file path
531        : return: Data1D object if a single SASentry was found,
532                    or a list of Data1D objects if multiple entries were found,
533                    or None of nothing was found
534        : raise RuntimeError: when the file can't be opened
535        : raise ValueError: when the length of the data vectors are inconsistent
536        """
537        output = []
538       
539        if os.path.isfile(path):
540            basename  = os.path.basename(path)
541            root, extension = os.path.splitext(basename)
542           
543            if  extension.lower() in self.ext or \
544                extension.lower() == '.xml':
545                tree = etree.parse(path, parser=etree.ETCompatXMLParser())
546       
547                # Check the format version number
548                # Specifying the namespace will take care of the file format version
549                root = tree.getroot()
550               
551                entry_list = root.xpath('/ns:SASroot/ns:SASentry', namespaces={'ns': CANSAS_NS})
552               
553                for entry in entry_list:
554                   
555                    sas_entry = self._parse_entry(entry)
556                    invstate = self._parse_state(entry)
557                    sas_entry.meta_data['invstate'] = invstate
558
559                    sas_entry.filename = invstate.file
560                    output.append(sas_entry)
561               
562        else:
563            raise RuntimeError, "%s is not a file" % path
564
565        # Return output consistent with the loader's api
566        if len(output)==0:
567            return None
568        elif len(output)==1:
569            # Call back to post the new state
570
571            self.call_back(state=output[0].meta_data['invstate'], datainfo = output[0])
572            return output[0]
573        else:
574            return output               
575   
576   
577    def write(self, filename, datainfo=None, invstate=None):
578        """
579        Write the content of a Data1D as a CanSAS XML file
580       
581        : param filename: name of the file to write
582        : param datainfo: Data1D object
583        : param invstate: InvariantState object
584        """
585
586        # Sanity check
587        if self.cansas == True:
588            if datainfo is None:
589                datainfo = DataLoader.data_info.Data1D(x=[], y=[])   
590            elif not issubclass(datainfo.__class__, DataLoader.data_info.Data1D):
591                raise RuntimeError, "The cansas writer expects a Data1D instance: %s" % str(datainfo.__class__.__name__)
592       
593            # Create basic XML document
594            doc, sasentry = self._to_xml_doc(datainfo)
595       
596            # Add the invariant information to the XML document
597            if invstate is not None:
598                invstate.toXML(doc=doc, entry_node=sasentry)
599       
600            # Write the XML document
601            fd = open(filename, 'w')
602            fd.write(doc.toprettyxml())
603            fd.close()
604        else:
605            invstate.toXML(file=filename)
606       
607   
608   
Note: See TracBrowser for help on using the repository browser.