source: sasview/sansview/perspectives/fitting/pagestate.py @ beb374a

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 beb374a was 35b556d, checked in by Gervaise Alina <gervyh@…>, 14 years ago

working on save state

  • Property mode set to 100644
File size: 53.6 KB
Line 
1"""
2This software was developed by the University of Tennessee as part of the
3Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
4project funded by the US National Science Foundation.
5
6See the license text in license.txt
7
8copyright 2009, University of Tennessee
9"""
10import time
11import os
12import sys
13import copy
14import logging
15
16import xml.dom.minidom
17from xml.dom.minidom import parse
18from lxml import etree
19
20import DataLoader
21from DataLoader.readers.cansas_reader import Reader as CansasReader
22from DataLoader.readers.cansas_reader import get_content, write_node
23from DataLoader.data_info import Data2D
24
25#Information to read/write state as xml
26FITTING_NODE_NAME = 'fitting_plug_in'
27CANSAS_NS = "cansas1d/1.0"
28
29list_of_data_attributes = [["is_data", "is_data", "bool"],
30                      ["group_id", "data_group_id", "string"],
31                      ["data_name", "data_name", "string"],
32                      ["data_id", "data_id", "string"],
33                      ["name", "name", "string"],
34                      ["data_name", "data_name", "string"]]
35list_of_state_attributes = [["qmin", "qmin", "float"],
36                      ["qmax", "qmax", "float"],
37                      ["npts", "npts", "float"],
38                      ["shape_rbutton", "shape_rbutton", "bool"],
39                      ["shape_indep_rbutton", "shape_indep_rbutton", "bool"],
40                      ["plugin_rbutton", "plugin_rbutton","bool"],
41                      ["struct_rbutton", "struct_rbutton", "bool"],
42                      ["formfactorcombobox", "formfactorcombobox", "bool"],
43                      ["structurecombobox", "structurecombobox", "bool"],
44                      ["disp_box", "disp_box"],
45                      ["enable_smearer","enable_smearer","bool"],
46                      ["disable_smearer","disable_smearer","bool"],
47                      ["pinhole_smearer","pinhole_smearer","bool"],
48                      ["slit_smearer","slit_smearer","bool"],
49                      ["enable_disp","enable_disp","bool"],
50                      ["disable_disp","disable_disp","bool"],
51                      ["slit_smearer","slit_smearer","bool"],
52                      ["enable2D","enable2D","bool"],
53                      ["cb1","cb1","bool"],
54                      ["tcChi","tcChi","float"],
55                     ["smearer", "smearer", "float"],
56                     ["smear_type","smear_type", "string"],
57                     ["dq_l", "dq_l", "string"],
58                     ["dq_r","dq_r", "string"]]
59
60list_of_model_attributes = [["values", "values"],
61                            ["weights", "weights"]]
62
63list_of_state_parameters = [["parameters", "parameters"] ,                     
64                            ["orientation_parameters", "orientation_params"],
65                            ["dispersity_parameters", "orientation_params_disp"],
66                            ["fixed_param", "fixed_param"],                     
67                            ["fittable_param","fittable_param"]]
68list_of_data_2d_attr = [["xmin", "xmin"],
69                        ["xmax","xmax"],
70                        ["ymin","ymin"],
71                        ["ymax","ymax"],
72                        ["_xaxis","_xaxis"],
73                        ["_xunit", "_xunit"],
74                        ["_yaxis","_yaxis"],
75                        ["_yunit","_yunit"],
76                        ["_zaxis","_zaxis"],
77                        ["_zunit","_zunit"]]
78list_of_data2d_values = [["qx_data","qx_data"],
79                         ["qy_data","qy_data"],
80                         ["dqx_data","dqx_data"],
81                         ["dqy_data","dqy_data"],
82                         ["data","data"],
83                         ["q_data","q_data"],
84                         ["err_data","err_data"],
85                         ["mask","mask"],]
86
87class PageState(object):
88    """
89        Contains information to reconstruct a page of the fitpanel.
90    """
91    def __init__(self, parent=None, model=None, data=None):
92       
93        """
94            Initialize the current state
95            @param model: a selected model within a page
96            @param data:
97        """
98        self.file = None
99        #Time of state creation
100        self.timestamp = time.time()
101        ## Data member to store the dispersion object created
102        self._disp_obj_dict = {}
103        #------------------------
104        #Data used for fitting
105        self.data = data
106        #save additional information on data that dataloader.reader does not read
107        self.is_data = None
108        self.data_name = ""
109       
110        if self.data is not None:
111            self.data_name = self.data.name
112        self.data_id = None
113        if self.data is not None and hasattr(self.data, "id"):
114            self.data_id = self.data.id
115        self.data_group_id = None
116        if self.data is not None and hasattr(self.data, "group_id"):
117            self.data_group_id = self.data.group_id
118        #-------------------------
119        ## reset True change the state of exsiting button
120        self.reset = False
121       
122        #engine type
123        self.engine_type = None
124        # flag to allow data2D plot
125        self.enable2D = False
126        # model on which the fit would be performed
127        self.model = model
128       
129        #fit page manager
130        self.manager = None
131        #Store the parent of this panel parent
132        # For this application fitpanel is the parent
133        self.parent  = parent
134        # Event_owner is the owner of model event
135        self.event_owner = None
136        ##page name
137        self.page_name = ""
138        # Contains link between  model ,all its parameters, and panel organization
139        self.parameters = []
140        # Contains list of parameters that cannot be fitted and reference to
141        #panel objects
142        self.fixed_param = []
143        # Contains list of parameters with dispersity and reference to
144        #panel objects
145        self.fittable_param = []
146        ## orientation parameters
147        self.orientation_params = []
148        ## orientation parmaters for gaussian dispersity
149        self.orientation_params_disp = []
150        ## smearer info
151        self.smearer = None
152        self.smear_type = None
153        self.dq_l = None
154        self.dq_r = None
155
156        #list of dispersion paramaters
157        self.disp_list =[]
158        if self.model is not None:
159            self.disp_list = self.model.getDispParamList()
160        self._disp_obj_dict = {}
161        self.disp_cb_dict = {}
162        self.values = []
163        self.weights = []
164                   
165        #contains link between a model and selected parameters to fit
166        self.param_toFit = []
167        ##dictionary of model type and model class
168        self.model_list_box = None
169        ## save the state of the context menu
170        self.saved_states = {}
171        ## save selection of combobox
172        self.formfactorcombobox = None
173        self.structurecombobox  = None
174        ## radio box to select type of model
175        self.shape_rbutton = False
176        self.shape_indep_rbutton = False
177        self.struct_rbutton = False
178        self.plugin_rbutton = False
179        ## the indice of the current selection
180        self.disp_box = 0
181        ## Qrange
182        ## Q range
183        self.qmin = 0.001
184        self.qmax = 0.1
185        self.npts = None
186        self.name = ""
187        ## enable smearering state
188        self.enable_smearer = False
189        self.disable_smearer = True
190        self.pinhole_smearer = False
191        self.slit_smearer   = False
192        ## disperity selection
193        self.enable_disp = False
194        self.disable_disp = True
195       
196        ## state of selected all check button
197        self.cb1 = False
198        ## store value of chisqr
199        self.tcChi = None
200   
201    def clone(self):
202        """
203            Create a new copy of the current object
204        """
205        model = None
206        if self.model is not None:
207            model = self.model.clone()
208            model.name = self.model.name
209        obj = PageState(self.parent, model=model)
210        obj.file = copy.deepcopy(self.file)
211        obj.data = copy.deepcopy(self.data)
212        if self.data is not None:
213            self.data_name = self.data.name
214        obj.data_name = self.data_name
215        obj.is_data = self.is_data
216        obj.model_list_box = copy.deepcopy(self.model_list_box)
217        obj.engine_type = copy.deepcopy(self.engine_type)
218       
219        obj.formfactorcombobox = self.formfactorcombobox
220        obj.structurecombobox  = self.structurecombobox 
221       
222        obj.shape_rbutton = self.shape_rbutton
223        obj.shape_indep_rbutton = self.shape_indep_rbutton
224        obj.struct_rbutton = self.struct_rbutton
225        obj.plugin_rbutton = self.plugin_rbutton
226       
227        obj.manager = self.manager
228        obj.event_owner = self.event_owner
229        obj.disp_list = copy.deepcopy(self.disp_list)
230       
231        obj.enable2D = copy.deepcopy(self.enable2D)
232        obj.parameters = copy.deepcopy(self.parameters)
233        obj.fixed_param = copy.deepcopy(self.fixed_param)
234        obj.fittable_param = copy.deepcopy(self.fittable_param)
235        obj.orientation_params =  copy.deepcopy(self.orientation_params)
236        obj.orientation_params_disp =  copy.deepcopy(self.orientation_params_disp)
237        obj.enable_disp = copy.deepcopy(self.enable_disp)
238        obj.disable_disp = copy.deepcopy(self.disable_disp)
239        obj.tcChi = self.tcChi
240 
241        if len(self._disp_obj_dict)>0:
242            for k , v in self._disp_obj_dict.iteritems():
243                obj._disp_obj_dict[k]= v
244        if len(self.disp_cb_dict)>0:
245            for k , v in self.disp_cb_dict.iteritems():
246                obj.disp_cb_dict[k]= v
247               
248        obj.values = copy.deepcopy(self.values)
249        obj.weights = copy.deepcopy(self.weights)
250        obj.enable_smearer = copy.deepcopy(self.enable_smearer)
251        obj.disable_smearer = copy.deepcopy(self.disable_smearer)
252        obj.pinhole_smearer = copy.deepcopy(self.pinhole_smearer)
253        obj.slit_smearer = copy.deepcopy(self.slit_smearer)
254        obj.smear_type = copy.deepcopy(self.smear_type)
255        obj.dq_l = copy.deepcopy(self.dq_l)
256        obj.dq_r = copy.deepcopy(self.dq_r)
257
258        obj.disp_box = copy.deepcopy(self.disp_box)
259        obj.qmin = copy.deepcopy(self.qmin)
260        obj.qmax = copy.deepcopy(self.qmax)
261        obj.npts = copy.deepcopy(self.npts )
262        obj.cb1 = copy.deepcopy(self.cb1)
263        obj.smearer = copy.deepcopy(self.smearer)
264       
265        for name, state in self.saved_states.iteritems():
266            copy_name = copy.deepcopy(name)
267            copy_state = state.clone()
268            obj.saved_states[copy_name]= copy_state
269        return obj
270   
271    def _repr_helper(self, list, rep):
272        """
273            Helper method to print a state
274        """
275        for item in list:
276            rep += "parameter name: %s \n"%str(item[1])
277            rep += "value: %s\n"%str(item[2])
278            rep += "selected: %s\n"%str(item[0])
279            rep += "error displayed : %s \n"%str(item[4][0])
280            rep += "error value:%s \n"%str(item[4][1])
281            rep += "minimum displayed : %s \n"%str(item[5][0])
282            rep += "minimum value : %s \n"%str(item[5][1])
283            rep += "maximum displayed : %s \n"%str(item[6][0])
284            rep += "maximum value : %s \n"%str(item[6][1])
285            rep += "parameter unit: %s\n\n"%str(item[7])
286        return rep
287   
288    def __repr__(self):
289        """
290            output string for printing
291        """
292        rep = "\nState name: %s\n"%self.file
293        t = time.localtime(self.timestamp)
294        time_str = time.strftime("%b %d %H:%M", t)
295        rep += "State created on : %s\n"%time_str
296        rep += "State form factor combobox selection: %s\n"%self.formfactorcombobox
297        rep += "State structure factor combobox selection: %s\n"%self.structurecombobox
298        rep += "is data : %s\n"%self.is_data
299        rep += "data's name : %s\n"%self.data_name
300        rep += "data's id : %s\n"%self.data_id
301        rep += "model type (form factor) selected: %s\n"%self.shape_rbutton
302        rep += "model type (shape independent) selected: %s\n"%self.shape_indep_rbutton
303        rep += "model type (structure factor) selected: %s\n"%self.struct_rbutton
304        rep += "model type (plug-in ) selected: %s\n"%self.plugin_rbutton
305        rep += "data : %s\n"% str(self.data)
306        rep += "Plotting Range: min: %s, max: %s, steps: %s\n"%(str(self.qmin),
307                                                str(self.qmax),str(self.npts))
308        """
309        rep += "model  : %s\n\n"% str(self.model)
310        rep += "number parameters(self.parameters): %s\n"%len(self.parameters)
311        rep += self._repr_helper( list=self.parameters, rep=rep)
312        rep += "number orientation parameters"
313        rep += "(self.orientation_params): %s\n"%len(self.orientation_params)
314        rep += self._repr_helper( list=self.orientation_params, rep=rep)
315        rep += "number dispersity parameters"
316        rep += "(self.orientation_params_disp): %s\n"%len(self.orientation_params_disp)
317        rep += self._repr_helper( list=self.orientation_params_disp, rep=rep)
318        """
319        return rep
320   
321    def _toXML_helper(self, list, element, newdoc):
322        """
323            Helper method to create xml file for saving state
324        """
325        for item in list:
326            sub_element = newdoc.createElement('parameter')
327            sub_element.setAttribute('name', str(item[1]))
328            sub_element.setAttribute('value', str(item[2]))
329            sub_element.setAttribute('selected_to_fit', str(item[0]))
330            sub_element.setAttribute('error_displayed', str(item[4][0]))
331            sub_element.setAttribute('error_value', str(item[4][1]))
332            sub_element.setAttribute('minimum_displayed', str(item[5][0]))
333            sub_element.setAttribute('minimum_value', str(item[5][1]))
334            sub_element.setAttribute('maximum_displayed', str(item[6][0]))
335            sub_element.setAttribute('maximum_value', str(item[6][1]))
336            sub_element.setAttribute('unit', str(item[7]))
337            element.appendChild(sub_element)
338       
339    def toXML(self, file="fitting_state.fitv", doc=None, entry_node=None):
340        """
341            Writes the state of the InversionControl panel to file, as XML.
342           
343            Compatible with standalone writing, or appending to an
344            already existing XML document. In that case, the XML document
345            is required. An optional entry node in the XML document may also be given.
346           
347            @param file: file to write to
348            @param doc: XML document object [optional]
349            @param entry_node: XML node within the XML document at which we will append the data [optional]
350        """
351        from xml.dom.minidom import getDOMImplementation
352
353        # Check whether we have to write a standalone XML file
354        if doc is None:
355            impl = getDOMImplementation()
356            doc_type = impl.createDocumentType(FITTING_NODE_NAME, "1.0", "1.0")     
357            newdoc = impl.createDocument(None, FITTING_NODE_NAME, doc_type)
358            top_element = newdoc.documentElement
359        else:
360            # We are appending to an existing document
361            newdoc = doc
362            top_element = newdoc.createElement(FITTING_NODE_NAME)
363            if entry_node is None:
364                newdoc.documentElement.appendChild(top_element)
365            else:
366                entry_node.appendChild(top_element)
367           
368        attr = newdoc.createAttribute("version")
369        attr.nodeValue = '1.0'
370        top_element.setAttributeNode(attr)
371       
372        # File name
373        element = newdoc.createElement("filename")
374        if self.file is not None:
375            element.appendChild(newdoc.createTextNode(str(self.file)))
376        else:
377            element.appendChild(newdoc.createTextNode(str(file)))
378        top_element.appendChild(element)
379       
380        element = newdoc.createElement("timestamp")
381        element.appendChild(newdoc.createTextNode(time.ctime(self.timestamp)))
382        attr = newdoc.createAttribute("epoch")
383        attr.nodeValue = str(self.timestamp)
384        element.setAttributeNode(attr)
385        top_element.appendChild(element)
386        # Inputs
387        inputs = newdoc.createElement("Attributes")
388        top_element.appendChild(inputs)
389       
390        element = newdoc.createElement('data_attributes')
391        if self.data is not None and hasattr(self.data, "group_id"):
392            self.data_group_id = self.data.group_id
393        if self.data is not None and hasattr(self.data, "is_data"):
394            self.is_data = self.data.is_data
395        if self.data is not None:
396            self.data_name = self.data.name
397        if self.data is not None and hasattr(self.data, "id"):
398            self.data_id = self.data.id
399       
400        for item in list_of_data_attributes:
401            exec "element.setAttribute(item[0], str(self.%s))"%(item[1])
402        inputs.appendChild(element)   
403       
404        for item in list_of_state_attributes:
405            element = newdoc.createElement(item[0])
406            exec "element.setAttribute(item[0], str(self.%s))"%(item[1])
407            inputs.appendChild(element)
408           
409        for item in list_of_model_attributes:
410            element = newdoc.createElement(item[0])
411            exec "list = self.%s"%item[1]
412            for value in list:
413                exec "element.appendChild(newdoc.createTextNode(str(%s)))" % value
414            inputs.appendChild(element)
415           
416        for item in list_of_state_parameters:
417            element = newdoc.createElement(item[0])
418            exec "self._toXML_helper(list=self.%s, element=element, newdoc=newdoc)"%item[1]                       
419            inputs.appendChild(element)
420       
421        # Save the file
422        if doc is None:
423            fd = open(file, 'w')
424            fd.write(newdoc.toprettyxml())
425            fd.close()
426            return None
427        else:
428            return newdoc.toprettyxml()
429       
430    def _fromXML_helper(self, node, list):
431        """
432            Helper function to write state to xml
433        """
434        for item in node:
435            name = item.get('name')
436            value = item.get('value')
437            selected_to_fit = item.get('selected_to_fit')
438            error_displayed = item.get('error_displayed')
439            error_value = item.get('error_value')
440            minimum_displayed = item.get('minimum_displayed')
441            minimum_value = item.get('minimum_value')
442            maximum_displayed = item.get('maximum_displayed')
443            maximum_value = item.get('maximum_value')
444            unit = item.get('unit')
445            list.append([selected_to_fit, name, value, "+/-",[error_displayed, error_value],
446                         [minimum_displayed,minimum_value],[maximum_displayed,maximum_value], unit])
447       
448    def fromXML(self, file=None, node=None):
449        """
450            Load fitting state from a file
451           
452            @param file: .fitv file
453            @param node: node of a XML document to read from
454        """
455     
456        if file is not None:
457            msg = "PageState no longer supports non-CanSAS"
458            msg += " format for fitting files"
459            raise RuntimeError, msg
460           
461        if node.get('version')and node.get('version') == '1.0':
462           
463            # Get file name
464            entry = get_content('ns:filename', node)
465            if entry is not None:
466                self.file = entry.text.strip()
467               
468            # Get time stamp
469            entry = get_content('ns:timestamp', node)
470            if entry is not None and entry.get('epoch'):
471                try:
472                    self.timestamp = float(entry.get('epoch'))
473                except:
474                    msg = "PageState.fromXML: Could not"
475                    msg += " read timestamp\n %s" % sys.exc_value
476                    logging.error(msg)
477           
478            # Parse fitting attributes
479            entry = get_content('ns:Attributes', node)
480            if entry is not None:
481                for item in list_of_state_attributes:
482                    field = get_content('ns:%s'%item[0], entry)
483                    if field is not None:
484                        if item[2] == "string":
485                            exec "self.%s= str(field.text)"%item[1]
486                        elif item[2] == "bool":
487                            try:
488                                exec "self.%s= field.get(str(%s))"%(item[1], item[0])
489                            except:
490                                exec "self.%s = None"%item[1]
491                        else:
492                            try:
493                                exec "self.%s = float(field.get(%s))"%(item[1], item[0])
494                            except:
495                                exec "self.%s = None"%item[1]
496                for item in list_of_model_attributes:
497                    node = get_content("ns:%s"%item[0], entry)
498                    list = []
499                    for value in node:
500                        try:
501                            list.append(float(value)) 
502                        except:
503                            list.append(None)
504                       
505                    exec "self.%s = list"%item[1]
506                   
507                for item in list_of_state_parameters:
508                    node = get_content("ns:%s"%item[0], entry)
509                    self._fromXML_helper(node=node, list=self.parameters)
510               
511
512class Reader(CansasReader):
513    """
514        Class to load a .fitv fitting file
515    """
516    ## File type
517    type_name = "Fitting"
518   
519    ## Wildcards
520    type = ["Fitting files (*.fitv)|*.fitv"]
521    ## List of allowed extensions
522    ext=['.fitv', '.FITV'] 
523   
524    def __init__(self, call_back=None, cansas=True):
525        """
526            Initialize the call-back method to be called
527            after we load a file
528            @param call_back: call-back method
529            @param cansas:  True = files will be written/read in CanSAS format
530                            False = write CanSAS format
531           
532        """
533        ## Call back method to be executed after a file is read
534        self.call_back = call_back
535        ## CanSAS format flag
536        self.cansas = cansas
537       
538    def read(self, path):
539        """
540            Load a new P(r) inversion state from file
541           
542            @param path: file path
543            @return: None
544        """
545        if self.cansas == True:
546            return self._read_cansas(path)
547     
548    def _data2d_to_xml_doc(self, datainfo):
549        """
550            Create an XML document to contain the content of a Data2D
551           
552            @param datainfo: Data2D object
553        """
554        if not issubclass(datainfo.__class__, Data2D):
555            raise RuntimeError, "The cansas writer expects a Data2D instance"
556       
557        doc = xml.dom.minidom.Document()
558        main_node = doc.createElement("SASroot")
559        main_node.setAttribute("version", self.version)
560     
561        doc.appendChild(main_node)
562       
563        entry_node = doc.createElement("SASentry")
564        main_node.appendChild(entry_node)
565       
566        write_node(doc, entry_node, "Title", datainfo.title)
567       
568        for item in datainfo.run:
569            runname = {}
570            if datainfo.run_name.has_key(item) and len(str(datainfo.run_name[item]))>1:
571                runname = {'name': datainfo.run_name[item] }
572            write_node(doc, entry_node, "Run", item, runname)
573        # Data info
574        node = doc.createElement("SASdata")
575        entry_node.appendChild(node)
576        for item in list_of_data_2d_attr:
577            element = doc.createElement(item[0])
578            exec "element.setAttribute(item[0], str(datainfo.%s))"%(item[1])
579            entry_node.appendChild(element)
580        for item in list_of_data2d_values:
581            element = doc.createElement(item[0])
582            exec "temp_list = datainfo.%s"%item[1]
583            if temp_list is None or len(temp_list)== 0:
584                exec "element.appendChild(doc.createTextNode(str(%s)))"%temp_list
585            else:
586                for value in temp_list:
587                    exec "element.appendChild(doc.createTextNode(str(%s)))"%value
588            entry_node.appendChild(element)
589     
590        # Sample info
591        sample = doc.createElement("SASsample")
592        if datainfo.sample.name is not None:
593            sample.setAttribute("name", str(datainfo.sample.name))
594        entry_node.appendChild(sample)
595        write_node(doc, sample, "ID", str(datainfo.sample.ID))
596        write_node(doc, sample, "thickness", datainfo.sample.thickness, {"unit":datainfo.sample.thickness_unit})
597        write_node(doc, sample, "transmission", datainfo.sample.transmission)
598        write_node(doc, sample, "temperature", datainfo.sample.temperature, {"unit":datainfo.sample.temperature_unit})
599       
600        for item in datainfo.sample.details:
601            write_node(doc, sample, "details", item)
602       
603        pos = doc.createElement("position")
604        written = write_node(doc, pos, "x", datainfo.sample.position.x, {"unit":datainfo.sample.position_unit})
605        written = written | write_node(doc, pos, "y", datainfo.sample.position.y, {"unit":datainfo.sample.position_unit})
606        written = written | write_node(doc, pos, "z", datainfo.sample.position.z, {"unit":datainfo.sample.position_unit})
607        if written == True:
608            sample.appendChild(pos)
609       
610        ori = doc.createElement("orientation")
611        written = write_node(doc, ori, "roll",  datainfo.sample.orientation.x, {"unit":datainfo.sample.orientation_unit})
612        written = written | write_node(doc, ori, "pitch", datainfo.sample.orientation.y, {"unit":datainfo.sample.orientation_unit})
613        written = written | write_node(doc, ori, "yaw",   datainfo.sample.orientation.z, {"unit":datainfo.sample.orientation_unit})
614        if written == True:
615            sample.appendChild(ori)
616       
617        # Instrument info
618        instr = doc.createElement("SASinstrument")
619        entry_node.appendChild(instr)
620       
621        write_node(doc, instr, "name", datainfo.instrument)
622       
623        #   Source
624        source = doc.createElement("SASsource")
625        if datainfo.source.name is not None:
626            source.setAttribute("name", str(datainfo.source.name))
627        instr.appendChild(source)
628       
629        write_node(doc, source, "radiation", datainfo.source.radiation)
630        write_node(doc, source, "beam_shape", datainfo.source.beam_shape)
631        size = doc.createElement("beam_size")
632        if datainfo.source.beam_size_name is not None:
633            size.setAttribute("name", str(datainfo.source.beam_size_name))
634        written = write_node(doc, size, "x", datainfo.source.beam_size.x, {"unit":datainfo.source.beam_size_unit})
635        written = written | write_node(doc, size, "y", datainfo.source.beam_size.y, {"unit":datainfo.source.beam_size_unit})
636        written = written | write_node(doc, size, "z", datainfo.source.beam_size.z, {"unit":datainfo.source.beam_size_unit})
637        if written == True:
638            source.appendChild(size)
639           
640        write_node(doc, source, "wavelength", datainfo.source.wavelength, {"unit":datainfo.source.wavelength_unit})
641        write_node(doc, source, "wavelength_min", datainfo.source.wavelength_min, {"unit":datainfo.source.wavelength_min_unit})
642        write_node(doc, source, "wavelength_max", datainfo.source.wavelength_max, {"unit":datainfo.source.wavelength_max_unit})
643        write_node(doc, source, "wavelength_spread", datainfo.source.wavelength_spread, {"unit":datainfo.source.wavelength_spread_unit})
644       
645        #   Collimation
646        for item in datainfo.collimation:
647            coll = doc.createElement("SAScollimation")
648            if item.name is not None:
649                coll.setAttribute("name", str(item.name))
650            instr.appendChild(coll)
651           
652            write_node(doc, coll, "length", item.length, {"unit":item.length_unit})
653           
654            for apert in item.aperture:
655                ap = doc.createElement("aperture")
656                if apert.name is not None:
657                    ap.setAttribute("name", str(apert.name))
658                if apert.type is not None:
659                    ap.setAttribute("type", str(apert.type))
660                coll.appendChild(ap)
661               
662                write_node(doc, ap, "distance", apert.distance, {"unit":apert.distance_unit})
663               
664                size = doc.createElement("size")
665                if apert.size_name is not None:
666                    size.setAttribute("name", str(apert.size_name))
667                written = write_node(doc, size, "x", apert.size.x, {"unit":apert.size_unit})
668                written = written | write_node(doc, size, "y", apert.size.y, {"unit":apert.size_unit})
669                written = written | write_node(doc, size, "z", apert.size.z, {"unit":apert.size_unit})
670                if written == True:
671                    ap.appendChild(size)
672
673        #   Detectors
674        for item in datainfo.detector:
675            det = doc.createElement("SASdetector")
676            written = write_node(doc, det, "name", item.name)
677            written = written | write_node(doc, det, "SDD", item.distance, {"unit":item.distance_unit})
678            written = written | write_node(doc, det, "slit_length", item.slit_length, {"unit":item.slit_length_unit})
679            if written == True:
680                instr.appendChild(det)
681           
682            off = doc.createElement("offset")
683            written = write_node(doc, off, "x", item.offset.x, {"unit":item.offset_unit})
684            written = written | write_node(doc, off, "y", item.offset.y, {"unit":item.offset_unit})
685            written = written | write_node(doc, off, "z", item.offset.z, {"unit":item.offset_unit})
686            if written == True:
687                det.appendChild(off)
688           
689            center = doc.createElement("beam_center")
690            written = write_node(doc, center, "x", item.beam_center.x, {"unit":item.beam_center_unit})
691            written = written | write_node(doc, center, "y", item.beam_center.y, {"unit":item.beam_center_unit})
692            written = written | write_node(doc, center, "z", item.beam_center.z, {"unit":item.beam_center_unit})
693            if written == True:
694                det.appendChild(center)
695               
696            pix = doc.createElement("pixel_size")
697            written = write_node(doc, pix, "x", item.pixel_size.x, {"unit":item.pixel_size_unit})
698            written = written | write_node(doc, pix, "y", item.pixel_size.y, {"unit":item.pixel_size_unit})
699            written = written | write_node(doc, pix, "z", item.pixel_size.z, {"unit":item.pixel_size_unit})
700            if written == True:
701                det.appendChild(pix)
702               
703            ori = doc.createElement("orientation")
704            written = write_node(doc, ori, "roll",  item.orientation.x, {"unit":item.orientation_unit})
705            written = written | write_node(doc, ori, "pitch", item.orientation.y, {"unit":item.orientation_unit})
706            written = written | write_node(doc, ori, "yaw",   item.orientation.z, {"unit":item.orientation_unit})
707            if written == True:
708                det.appendChild(ori)
709               
710        # Processes info
711        for item in datainfo.process:
712            node = doc.createElement("SASprocess")
713            entry_node.appendChild(node)
714
715            write_node(doc, node, "name", item.name)
716            write_node(doc, node, "date", item.date)
717            write_node(doc, node, "description", item.description)
718            for term in item.term:
719                value = term['value']
720                del term['value']
721                write_node(doc, node, "term", value, term)
722            for note in item.notes:
723                write_node(doc, node, "SASprocessnote", note)
724       
725        # Return the document, and the SASentry node associated with
726        # the data we just wrote
727        return doc, entry_node
728   
729   
730    def _parse_state(self, entry):
731        """
732            Read a fit result from an XML node
733            @param entry: XML node to read from
734            @return: PageState object
735        """
736        # Create an empty state
737        state = PageState()
738        # Locate the P(r) node
739        try:
740            nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME, namespaces={'ns': CANSAS_NS})
741            state.fromXML(node=nodes[0])
742        except:
743            logging.info("XML document does not contain fitting information.\n %s" % sys.exc_value)
744           
745        return state
746   
747    def _parse_entry_2d(self, dom):
748        """
749            Parse a SASentry
750           
751            @param node: SASentry node
752            @return: Data2D object
753        """
754     
755        data_info = Data2D()
756       
757        # Look up title     
758        self._store_content('ns:Title', dom, 'title', data_info)
759       
760        # Look up run number   
761        nodes = dom.xpath('ns:Run', namespaces={'ns': CANSAS_NS})
762        for item in nodes:   
763            if item.text is not None:
764                value = item.text.strip()
765                if len(value) > 0:
766                    data_info.run.append(value)
767                    if item.get('name') is not None:
768                        data_info.run_name[value] = item.get('name')
769                           
770        # Look up instrument name             
771        self._store_content('ns:SASinstrument/ns:name', dom, 'instrument', data_info)
772
773        # Notes
774        note_list = dom.xpath('ns:SASnote', namespaces={'ns': CANSAS_NS})
775        for note in note_list:
776            try:
777                if note.text is not None:
778                    note_value = note.text.strip()
779                    if len(note_value) > 0:
780                        data_info.notes.append(note_value)
781            except:
782                err_mess = "cansas_reader.read: error processing entry notes\n  %s" % sys.exc_value
783                self.errors.append(err_mess)
784                logging.error(err_mess)
785       
786        # Sample info ###################
787        entry = get_content('ns:SASsample', dom)
788        if entry is not None:
789            data_info.sample.name = entry.get('name')
790           
791        self._store_content('ns:SASsample/ns:ID', 
792                     dom, 'ID', data_info.sample)                   
793        self._store_float('ns:SASsample/ns:thickness', 
794                     dom, 'thickness', data_info.sample)
795        self._store_float('ns:SASsample/ns:transmission', 
796                     dom, 'transmission', data_info.sample)
797        self._store_float('ns:SASsample/ns:temperature', 
798                     dom, 'temperature', data_info.sample)
799       
800        nodes = dom.xpath('ns:SASsample/ns:details', namespaces={'ns': CANSAS_NS})
801        for item in nodes:
802            try:
803                if item.text is not None:
804                    detail_value = item.text.strip()
805                    if len(detail_value) > 0:
806                        data_info.sample.details.append(detail_value)
807            except:
808                err_mess = "cansas_reader.read: error processing sample details\n  %s" % sys.exc_value
809                self.errors.append(err_mess)
810                logging.error(err_mess)
811       
812        # Position (as a vector)
813        self._store_float('ns:SASsample/ns:position/ns:x', 
814                     dom, 'position.x', data_info.sample)         
815        self._store_float('ns:SASsample/ns:position/ns:y', 
816                     dom, 'position.y', data_info.sample)         
817        self._store_float('ns:SASsample/ns:position/ns:z', 
818                     dom, 'position.z', data_info.sample)         
819       
820        # Orientation (as a vector)
821        self._store_float('ns:SASsample/ns:orientation/ns:roll', 
822                     dom, 'orientation.x', data_info.sample)         
823        self._store_float('ns:SASsample/ns:orientation/ns:pitch', 
824                     dom, 'orientation.y', data_info.sample)         
825        self._store_float('ns:SASsample/ns:orientation/ns:yaw', 
826                     dom, 'orientation.z', data_info.sample)         
827       
828        # Source info ###################
829        entry = get_content('ns:SASinstrument/ns:SASsource', dom)
830        if entry is not None:
831            data_info.source.name = entry.get('name')
832       
833        self._store_content('ns:SASinstrument/ns:SASsource/ns:radiation', 
834                     dom, 'radiation', data_info.source)                   
835        self._store_content('ns:SASinstrument/ns:SASsource/ns:beam_shape', 
836                     dom, 'beam_shape', data_info.source)                   
837        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength', 
838                     dom, 'wavelength', data_info.source)         
839        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_min', 
840                     dom, 'wavelength_min', data_info.source)         
841        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_max', 
842                     dom, 'wavelength_max', data_info.source)         
843        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_spread', 
844                     dom, 'wavelength_spread', data_info.source)   
845       
846        # Beam size (as a vector)   
847        entry = get_content('ns:SASinstrument/ns:SASsource/ns:beam_size', dom)
848        if entry is not None:
849            data_info.source.beam_size_name = entry.get('name')
850           
851        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:x', 
852                     dom, 'beam_size.x', data_info.source)   
853        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:y', 
854                     dom, 'beam_size.y', data_info.source)   
855        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:z', 
856                     dom, 'beam_size.z', data_info.source)   
857       
858        # Collimation info ###################
859        nodes = dom.xpath('ns:SASinstrument/ns:SAScollimation', namespaces={'ns': CANSAS_NS})
860        for item in nodes:
861            collim = Collimation()
862            if item.get('name') is not None:
863                collim.name = item.get('name')
864            self._store_float('ns:length', item, 'length', collim) 
865           
866            # Look for apertures
867            apert_list = item.xpath('ns:aperture', namespaces={'ns': CANSAS_NS})
868            for apert in apert_list:
869                aperture =  Aperture()
870               
871                # Get the name and type of the aperture
872                aperture.name = apert.get('name')
873                aperture.type = apert.get('type')
874                   
875                self._store_float('ns:distance', apert, 'distance', aperture)   
876               
877                entry = get_content('ns:size', apert)
878                if entry is not None:
879                    aperture.size_name = entry.get('name')
880               
881                self._store_float('ns:size/ns:x', apert, 'size.x', aperture)   
882                self._store_float('ns:size/ns:y', apert, 'size.y', aperture)   
883                self._store_float('ns:size/ns:z', apert, 'size.z', aperture)
884               
885                collim.aperture.append(aperture)
886               
887            data_info.collimation.append(collim)
888       
889        # Detector info ######################
890        nodes = dom.xpath('ns:SASinstrument/ns:SASdetector', namespaces={'ns': CANSAS_NS})
891        for item in nodes:
892           
893            detector = Detector()
894           
895            self._store_content('ns:name', item, 'name', detector)
896            self._store_float('ns:SDD', item, 'distance', detector)   
897           
898            # Detector offset (as a vector)
899            self._store_float('ns:offset/ns:x', item, 'offset.x', detector)   
900            self._store_float('ns:offset/ns:y', item, 'offset.y', detector)   
901            self._store_float('ns:offset/ns:z', item, 'offset.z', detector)   
902           
903            # Detector orientation (as a vector)
904            self._store_float('ns:orientation/ns:roll',  item, 'orientation.x', detector)   
905            self._store_float('ns:orientation/ns:pitch', item, 'orientation.y', detector)   
906            self._store_float('ns:orientation/ns:yaw',   item, 'orientation.z', detector)   
907           
908            # Beam center (as a vector)
909            self._store_float('ns:beam_center/ns:x', item, 'beam_center.x', detector)   
910            self._store_float('ns:beam_center/ns:y', item, 'beam_center.y', detector)   
911            self._store_float('ns:beam_center/ns:z', item, 'beam_center.z', detector)   
912           
913            # Pixel size (as a vector)
914            self._store_float('ns:pixel_size/ns:x', item, 'pixel_size.x', detector)   
915            self._store_float('ns:pixel_size/ns:y', item, 'pixel_size.y', detector)   
916            self._store_float('ns:pixel_size/ns:z', item, 'pixel_size.z', detector)   
917           
918            self._store_float('ns:slit_length', item, 'slit_length', detector)
919           
920            data_info.detector.append(detector)   
921
922        # Processes info ######################
923        nodes = dom.xpath('ns:SASprocess', namespaces={'ns': CANSAS_NS})
924        for item in nodes:
925            process = Process()
926            self._store_content('ns:name', item, 'name', process)
927            self._store_content('ns:date', item, 'date', process)
928            self._store_content('ns:description', item, 'description', process)
929           
930            term_list = item.xpath('ns:term', namespaces={'ns': CANSAS_NS})
931            for term in term_list:
932                try:
933                    term_attr = {}
934                    for attr in term.keys():
935                        term_attr[attr] = term.get(attr).strip()
936                    if term.text is not None:
937                        term_attr['value'] = term.text.strip()
938                        process.term.append(term_attr)
939                except:
940                    err_mess = "cansas_reader.read: error processing process term\n  %s" % sys.exc_value
941                    self.errors.append(err_mess)
942                    logging.error(err_mess)
943           
944            note_list = item.xpath('ns:SASprocessnote', namespaces={'ns': CANSAS_NS})
945            for note in note_list:
946                if note.text is not None:
947                    process.notes.append(note.text.strip())
948           
949            data_info.process.append(process)
950           
951           
952        # Data info ######################
953        nodes = dom.xpath('ns:SASdata', namespaces={'ns': CANSAS_NS})
954        if len(nodes)>1:
955            raise RuntimeError, "CanSAS reader is not compatible with multiple SASdata entries"
956       
957        nodes = dom.xpath('ns:SASdata/ns:Idata', namespaces={'ns': CANSAS_NS})
958
959        for item in nodes:
960            _x, attr = get_float('ns:Q', item)
961            _dx, attr_d = get_float('ns:Qdev', item)
962            _dxl, attr_l = get_float('ns:dQl', item)
963            _dxw, attr_w = get_float('ns:dQw', item)
964            if _dx == None:
965                _dx = 0.0
966            if _dxl == None:
967                _dxl = 0.0
968            if _dxw == None:
969                _dxw = 0.0
970               
971            if attr.has_key('unit') and attr['unit'].lower() != data_info.x_unit.lower():
972                if has_converter==True:
973                    try:
974                        data_conv_q = Converter(attr['unit'])
975                        _x = data_conv_q(_x, units=data_info.x_unit)
976                    except:
977                        raise ValueError, "CanSAS reader: could not convert Q unit [%s]; expecting [%s]\n  %s" \
978                        % (attr['unit'], data_info.x_unit, sys.exc_value)
979                else:
980                    raise ValueError, "CanSAS reader: unrecognized Q unit [%s]; expecting [%s]" \
981                        % (attr['unit'], data_info.x_unit)
982            # Error in Q
983            if attr_d.has_key('unit') and attr_d['unit'].lower() != data_info.x_unit.lower():
984                if has_converter==True:
985                    try:
986                        data_conv_q = Converter(attr_d['unit'])
987                        _dx = data_conv_q(_dx, units=data_info.x_unit)
988                    except:
989                        raise ValueError, "CanSAS reader: could not convert dQ unit [%s]; expecting [%s]\n  %s" \
990                        % (attr['unit'], data_info.x_unit, sys.exc_value)
991                else:
992                    raise ValueError, "CanSAS reader: unrecognized dQ unit [%s]; expecting [%s]" \
993                        % (attr['unit'], data_info.x_unit)
994            # Slit length
995            if attr_l.has_key('unit') and attr_l['unit'].lower() != data_info.x_unit.lower():
996                if has_converter==True:
997                    try:
998                        data_conv_q = Converter(attr_l['unit'])
999                        _dxl = data_conv_q(_dxl, units=data_info.x_unit)
1000                    except:
1001                        raise ValueError, "CanSAS reader: could not convert dQl unit [%s]; expecting [%s]\n  %s" \
1002                        % (attr['unit'], data_info.x_unit, sys.exc_value)
1003                else:
1004                    raise ValueError, "CanSAS reader: unrecognized dQl unit [%s]; expecting [%s]" \
1005                        % (attr['unit'], data_info.x_unit)
1006            # Slit width
1007            if attr_w.has_key('unit') and attr_w['unit'].lower() != data_info.x_unit.lower():
1008                if has_converter==True:
1009                    try:
1010                        data_conv_q = Converter(attr_w['unit'])
1011                        _dxw = data_conv_q(_dxw, units=data_info.x_unit)
1012                    except:
1013                        raise ValueError, "CanSAS reader: could not convert dQw unit [%s]; expecting [%s]\n  %s" \
1014                        % (attr['unit'], data_info.x_unit, sys.exc_value)
1015                else:
1016                    raise ValueError, "CanSAS reader: unrecognized dQw unit [%s]; expecting [%s]" \
1017                        % (attr['unit'], data_info.x_unit)
1018                   
1019            _y, attr = get_float('ns:I', item)
1020            _dy, attr_d = get_float('ns:Idev', item)
1021            if _dy == None:
1022                _dy = 0.0
1023            if attr.has_key('unit') and attr['unit'].lower() != data_info.y_unit.lower():
1024                if has_converter==True:
1025                    try:
1026                        data_conv_i = Converter(attr['unit'])
1027                        _y = data_conv_i(_y, units=data_info.y_unit)
1028                    except:
1029                        raise ValueError, "CanSAS reader: could not convert I(q) unit [%s]; expecting [%s]\n  %s" \
1030                        % (attr['unit'], data_info.y_unit, sys.exc_value)
1031                else:
1032                    raise ValueError, "CanSAS reader: unrecognized I(q) unit [%s]; expecting [%s]" \
1033                        % (attr['unit'], data_info.y_unit)
1034            if attr_d.has_key('unit') and attr_d['unit'].lower() != data_info.y_unit.lower():
1035                if has_converter==True:
1036                    try:
1037                        data_conv_i = Converter(attr_d['unit'])
1038                        _dy = data_conv_i(_dy, units=data_info.y_unit)
1039                    except:
1040                        raise ValueError, "CanSAS reader: could not convert dI(q) unit [%s]; expecting [%s]\n  %s" \
1041                        % (attr_d['unit'], data_info.y_unit, sys.exc_value)
1042                else:
1043                    raise ValueError, "CanSAS reader: unrecognized dI(q) unit [%s]; expecting [%s]" \
1044                        % (attr_d['unit'], data_info.y_unit)
1045               
1046            if _x is not None and _y is not None:
1047                exec "item = numpy.append(x, _x)"
1048   
1049        for item in list_of_model_attributes:
1050            node = get_content("ns:%s"%item[0], entry)
1051            list = []
1052            for value in node:
1053                try:
1054                    list.append(float(value)) 
1055                except:
1056                    list.append(None)
1057               
1058            exec "self.%s = list"%item[1]
1059        data_conv_q = None
1060        data_conv_i = None
1061       
1062        if has_converter == True and data_info.x_unit != '1/A':
1063            data_conv_q = Converter('1/A')
1064            # Test it
1065            data_conv_q(1.0, output.Q_unit)
1066           
1067        if has_converter == True and data_info.y_unit != '1/cm':
1068            data_conv_i = Converter('1/cm')
1069            # Test it
1070            data_conv_i(1.0, output.I_unit)                   
1071               
1072        if data_conv_q is not None:
1073            data_info.xaxis("\\rm{%s}"%str(_xaxis), data_info.x_unit)
1074        else:
1075            data_info.xaxis("\\rm{%s}"%str(_xaxis), 'A^{-1}')
1076        if data_conv_i is not None:
1077            data_info.yaxis("\\rm{%s}"%str(_yaxis), data_info.y_unit)
1078        else:
1079            data_info.yaxis("\\rm{%s}"%str(_yaxis),"cm^{-1}")
1080       
1081        return data_info
1082
1083    def _read_cansas(self, path):
1084        """
1085            Load data and P(r) information from a CanSAS XML file.
1086           
1087            @param path: file path
1088            @return: Data1D object if a single SASentry was found,
1089                        or a list of Data1D objects if multiple entries were found,
1090                        or None of nothing was found
1091            @raise RuntimeError: when the file can't be opened
1092            @raise ValueError: when the length of the data vectors are inconsistent
1093        """
1094        output = []
1095        try:
1096            if os.path.isfile(path):
1097                basename  = os.path.basename(path)
1098                root, extension = os.path.splitext(basename)
1099                #TODO: eventually remove the check for .xml once
1100                # the P(r) writer/reader is truly complete.
1101                if  extension.lower() in self.ext or \
1102                    extension.lower() == '.xml':
1103                   
1104                    tree = etree.parse(path, parser=etree.ETCompatXMLParser())
1105                    # Check the format version number
1106                    # Specifying the namespace will take care of the file format version
1107                    root = tree.getroot()
1108                    entry_list = root.xpath('/ns:SASroot/ns:SASentry', namespaces={'ns': CANSAS_NS})
1109                    for entry in entry_list:
1110                        try:
1111                            sas_entry = self._parse_entry(entry)
1112                        except:
1113                            sas_entry = self._parse_entry_2d(entry)
1114                        fitstate = self._parse_state(entry)
1115                        sas_entry.meta_data['fitstate'] = fitstate
1116                        sas_entry.filename = fitstate.file
1117                        output.append(sas_entry)
1118                   
1119            else:
1120                raise RuntimeError, "%s is not a file" % path
1121           
1122            # Return output consistent with the loader's api
1123            if len(output)==0:
1124                return None
1125            elif len(output)==1:
1126                # Call back to post the new state
1127                state = output[0].meta_data['fitstate']
1128                t = time.localtime(state.timestamp)
1129                time_str = time.strftime("%b %d %H:%M", t)
1130                # Check that no time stamp is already appended
1131                max_char = state.file.find("[")
1132                if max_char < 0:
1133                    max_char = len(state.file)
1134                state.file = state.file[0:max_char] +' [' + time_str + ']'
1135               
1136                   
1137                if state is not None and state.is_data is not None:
1138                    exec 'output[0].is_data = state.is_data' 
1139                 
1140                output[0].filename = state.file
1141                #output[0].filename = state.data_name
1142                state.data = output[0]
1143                state.data.name = state.data_name
1144                state.data.id = state.data_id
1145                #state.data.group_id = state.data_group_id
1146                state.data.group_id = output[0].filename
1147             
1148                self.call_back(state=state, datainfo=output[0])
1149                return output[0]
1150            else:
1151                return output               
1152        except:
1153            raise
1154           
1155    def write(self, filename, datainfo=None, fitstate=None):
1156        """
1157            Write the content of a Data1D as a CanSAS XML file
1158           
1159            @param filename: name of the file to write
1160            @param datainfo: Data1D object
1161            @param fitstate: PageState object
1162        """
1163        # Sanity check
1164        if self.cansas == True:
1165           
1166            # Add fitting information to the XML document
1167            if fitstate is not None:
1168                if fitstate.data is None:
1169                    data = DataLoader.data_info.Data1D(x=[], y=[])   
1170                elif issubclass(fitstate.data.__class__, DataLoader.data_info.Data1D):
1171                    data = fitstate.data
1172                    doc, sasentry = self._to_xml_doc(data)
1173                else:
1174                    data = fitstate.data
1175                    doc, sasentry = self._data2d_to_xml_doc(data)
1176                fitstate.toXML(doc=doc, file=data.name, entry_node=sasentry)
1177            # Write the XML document
1178            fd = open(filename, 'w')
1179            fd.write(doc.toprettyxml())
1180            fd.close()
1181        else:
1182            fitstate.toXML(file=filename)
1183       
1184       
1185if __name__ == "__main__":
1186    state = PageState(parent=None)
1187    state.toXML()
1188    print "state", state
Note: See TracBrowser for help on using the repository browser.