source: sasview/src/sas/sasgui/perspectives/fitting/pagestate.py @ a95ae9a

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.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since a95ae9a was a95ae9a, checked in by krzywon, 8 years ago

#189: Modified save project to include information from simultaneous fit panel. Next step is to load in the parameters and repopulate the simultaneous and constrained fit panel.

  • Property mode set to 100644
File size: 70.7 KB
Line 
1"""
2    Class that holds a fit page state
3"""
4# TODO: Refactor code so we don't need to use getattr/setattr
5################################################################################
6# This software was developed by the University of Tennessee as part of the
7# Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
8# project funded by the US National Science Foundation.
9#
10# See the license text in license.txt
11#
12# copyright 2009, University of Tennessee
13################################################################################
14import time
15import os
16import sys
17import wx
18import copy
19import logging
20import numpy
21import traceback
22
23import xml.dom.minidom
24from xml.dom.minidom import parseString
25from lxml import etree
26
27import sasmodels.weights
28
29import sas.sascalc.dataloader
30from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
31from sas.sascalc.dataloader.readers.cansas_reader import get_content, write_node
32from sas.sascalc.dataloader.data_info import Data2D, Collimation, Detector
33from sas.sascalc.dataloader.data_info import Process, Aperture
34# Information to read/write state as xml
35FITTING_NODE_NAME = 'fitting_plug_in'
36CANSAS_NS = "cansas1d/1.0"
37
38LIST_OF_DATA_ATTRIBUTES = [["is_data", "is_data", "bool"],
39                           ["group_id", "data_group_id", "string"],
40                           ["data_name", "data_name", "string"],
41                           ["data_id", "data_id", "string"],
42                           ["name", "name", "string"],
43                           ["data_name", "data_name", "string"]]
44LIST_OF_STATE_ATTRIBUTES = [["qmin", "qmin", "float"],
45                            ["qmax", "qmax", "float"],
46                            ["npts", "npts", "float"],
47                            ["categorycombobox", "categorycombobox", "string"],
48                            ["formfactorcombobox", "formfactorcombobox", "string"],
49                            ["structurecombobox", "structurecombobox", "string"],
50                            ["multi_factor", "multi_factor", "float"],
51                            ["magnetic_on", "magnetic_on", "bool"],
52                            ["enable_smearer", "enable_smearer", "bool"],
53                            ["disable_smearer", "disable_smearer", "bool"],
54                            ["pinhole_smearer", "pinhole_smearer", "bool"],
55                            ["slit_smearer", "slit_smearer", "bool"],
56                            ["enable_disp", "enable_disp", "bool"],
57                            ["disable_disp", "disable_disp", "bool"],
58                            ["dI_noweight", "dI_noweight", "bool"],
59                            ["dI_didata", "dI_didata", "bool"],
60                            ["dI_sqrdata", "dI_sqrdata", "bool"],
61                            ["dI_idata", "dI_idata", "bool"],
62                            ["enable2D", "enable2D", "bool"],
63                            ["cb1", "cb1", "bool"],
64                            ["tcChi", "tcChi", "float"],
65                            ["smearer", "smearer", "float"],
66                            ["smear_type", "smear_type", "string"],
67                            ["dq_l", "dq_l", "string"],
68                            ["dq_r", "dq_r", "string"],
69                            ["dx_max", "dx_max", "float"],
70                            ["dx_min", "dx_min", "float"],
71                            ["dxl", "dxl", "float"],
72                            ["dxw", "dxw", "float"]]
73
74LIST_OF_MODEL_ATTRIBUTES = [["values", "values"],
75                            ["weights", "weights"]]
76
77DISPERSION_LIST = [["disp_obj_dict", "_disp_obj_dict", "string"]]
78
79LIST_OF_STATE_PARAMETERS = [["parameters", "parameters"],
80                            ["str_parameters", "str_parameters"],
81                            ["orientation_parameters", "orientation_params"],
82                            ["dispersity_parameters", "orientation_params_disp"],
83                            ["fixed_param", "fixed_param"],
84                            ["fittable_param", "fittable_param"]]
85LIST_OF_DATA_2D_ATTR = [["xmin", "xmin", "float"],
86                        ["xmax", "xmax", "float"],
87                        ["ymin", "ymin", "float"],
88                        ["ymax", "ymax", "float"],
89                        ["_xaxis", "_xaxis", "string"],
90                        ["_xunit", "_xunit", "string"],
91                        ["_yaxis", "_yaxis", "string"],
92                        ["_yunit", "_yunit", "string"],
93                        ["_zaxis", "_zaxis", "string"],
94                        ["_zunit", "_zunit", "string"]]
95LIST_OF_DATA_2D_VALUES = [["qx_data", "qx_data", "float"],
96                          ["qy_data", "qy_data", "float"],
97                          ["dqx_data", "dqx_data", "float"],
98                          ["dqy_data", "dqy_data", "float"],
99                          ["data", "data", "float"],
100                          ["q_data", "q_data", "float"],
101                          ["err_data", "err_data", "float"],
102                          ["mask", "mask", "bool"]]
103
104
105def parse_entry_helper(node, item):
106    """
107    Create a numpy list from value extrated from the node
108
109    :param node: node from each the value is stored
110    :param item: list name of three strings.the two first are name of data
111        attribute and the third one is the type of the value of that
112        attribute. type can be string, float, bool, etc.
113
114    : return: numpy array
115    """
116    if node is not None:
117        if item[2] == "string":
118            return str(node.get(item[0]).strip())
119        elif item[2] == "bool":
120            try:
121                return node.get(item[0]).strip() == "True"
122            except:
123                return None
124        else:
125            try:
126                return float(node.get(item[0]))
127            except:
128                return None
129
130
131class PageState(object):
132    """
133    Contains information to reconstruct a page of the fitpanel.
134    """
135    def __init__(self, parent=None, model=None, data=None):
136        """
137        Initialize the current state
138
139        :param model: a selected model within a page
140        :param data:
141
142        """
143        self.file = None
144        # Time of state creation
145        self.timestamp = time.time()
146        # Data member to store the dispersion object created
147        self._disp_obj_dict = {}
148        # ------------------------
149        # Data used for fitting
150        self.data = data
151        # model data
152        self.theory_data = None
153        # Is 2D
154        self.is_2D = False
155        self.images = None
156
157        # save additional information on data that dataloader.reader
158        # does not read
159        self.is_data = None
160        self.data_name = ""
161
162        if self.data is not None:
163            self.data_name = self.data.name
164        self.data_id = None
165        if self.data is not None and hasattr(self.data, "id"):
166            self.data_id = self.data.id
167        self.data_group_id = None
168        if self.data is not None and hasattr(self.data, "group_id"):
169            self.data_group_id = self.data.group_id
170
171        # reset True change the state of existing button
172        self.reset = False
173
174        # flag to allow data2D plot
175        self.enable2D = False
176        # model on which the fit would be performed
177        self.model = model
178        self.m_name = None
179        # list of process done to model
180        self.process = []
181        # fit page manager
182        self.manager = None
183        # Store the parent of this panel parent
184        # For this application fitpanel is the parent
185        self.parent = parent
186        # Event_owner is the owner of model event
187        self.event_owner = None
188        # page name
189        self.page_name = ""
190        # Contains link between model, all its parameters, and panel organization
191        self.parameters = []
192        # String parameter list that can not be fitted
193        self.str_parameters = []
194        # Contains list of parameters that cannot be fitted and reference to
195        # panel objects
196        self.fixed_param = []
197        # Contains list of parameters with dispersity and reference to
198        # panel objects
199        self.fittable_param = []
200        # orientation parameters
201        self.orientation_params = []
202        # orientation parameters for gaussian dispersity
203        self.orientation_params_disp = []
204        # smearer info
205        self.smearer = None
206        self.smear_type = None
207        self.dq_l = None
208        self.dq_r = None
209        self.dx_max = None
210        self.dx_min = None
211        self.dxl = None
212        self.dxw = None
213        # list of dispersion parameters
214        self.disp_list = []
215        if self.model is not None:
216            self.disp_list = self.model.getDispParamList()
217
218        self.disp_cb_dict = {}
219        self.values = {}
220        self.weights = {}
221
222        # contains link between a model and selected parameters to fit
223        self.param_toFit = []
224        # dictionary of model type and model class
225        self.model_list_box = None
226        # save the state of the context menu
227        self.saved_states = {}
228        # save selection of combobox
229        self.formfactorcombobox = None
230        self.categorycombobox = None
231        self.structurecombobox = None
232
233        # radio box to select type of model
234        # self.shape_rbutton = False
235        # self.shape_indep_rbutton = False
236        # self.struct_rbutton = False
237        # self.plugin_rbutton = False
238        # the indice of the current selection
239        self.disp_box = 0
240        # Qrange
241        # Q range
242        self.qmin = 0.001
243        self.qmax = 0.1
244        # reset data range
245        self.qmax_x = None
246        self.qmin_x = None
247
248        self.npts = None
249        self.name = ""
250        self.multi_factor = None
251        self.magnetic_on = False
252        # enable smearering state
253        self.enable_smearer = False
254        self.disable_smearer = True
255        self.pinhole_smearer = False
256        self.slit_smearer = False
257        # weighting options
258        self.dI_noweight = False
259        self.dI_didata = True
260        self.dI_sqrdata = False
261        self.dI_idata = False
262        # disperity selection
263        self.enable_disp = False
264        self.disable_disp = True
265
266        # state of selected all check button
267        self.cb1 = False
268        # store value of chisqr
269        self.tcChi = None
270
271    def clone(self):
272        """
273        Create a new copy of the current object
274        """
275        model = None
276        if self.model is not None:
277            model = self.model.clone()
278            model.name = self.model.name
279        obj = PageState(self.parent, model=model)
280        obj.file = copy.deepcopy(self.file)
281        obj.data = copy.deepcopy(self.data)
282        if self.data is not None:
283            self.data_name = self.data.name
284        obj.data_name = self.data_name
285        obj.is_data = self.is_data
286        obj.model_list_box = copy.deepcopy(self.model_list_box)
287
288        obj.categorycombobox = self.categorycombobox
289        obj.formfactorcombobox = self.formfactorcombobox
290        obj.structurecombobox = self.structurecombobox
291
292        # obj.shape_rbutton = self.shape_rbutton
293        # obj.shape_indep_rbutton = self.shape_indep_rbutton
294        # obj.struct_rbutton = self.struct_rbutton
295        # obj.plugin_rbutton = self.plugin_rbutton
296
297        obj.manager = self.manager
298        obj.event_owner = self.event_owner
299        obj.disp_list = copy.deepcopy(self.disp_list)
300
301        obj.enable2D = copy.deepcopy(self.enable2D)
302        obj.parameters = copy.deepcopy(self.parameters)
303        obj.str_parameters = copy.deepcopy(self.str_parameters)
304        obj.fixed_param = copy.deepcopy(self.fixed_param)
305        obj.fittable_param = copy.deepcopy(self.fittable_param)
306        obj.orientation_params = copy.deepcopy(self.orientation_params)
307        obj.orientation_params_disp = copy.deepcopy(self.orientation_params_disp)
308        obj.enable_disp = copy.deepcopy(self.enable_disp)
309        obj.disable_disp = copy.deepcopy(self.disable_disp)
310        obj.tcChi = self.tcChi
311
312        if len(self._disp_obj_dict) > 0:
313            for k, v in self._disp_obj_dict.iteritems():
314                obj._disp_obj_dict[k] = v
315        if len(self.disp_cb_dict) > 0:
316            for k, v in self.disp_cb_dict.iteritems():
317                obj.disp_cb_dict[k] = v
318        if len(self.values) > 0:
319            for k, v in self.values.iteritems():
320                obj.values[k] = v
321        if len(self.weights) > 0:
322            for k, v in self.weights.iteritems():
323                obj.weights[k] = v
324        obj.enable_smearer = copy.deepcopy(self.enable_smearer)
325        obj.disable_smearer = copy.deepcopy(self.disable_smearer)
326        obj.pinhole_smearer = copy.deepcopy(self.pinhole_smearer)
327        obj.slit_smearer = copy.deepcopy(self.slit_smearer)
328        obj.smear_type = copy.deepcopy(self.smear_type)
329        obj.dI_noweight = copy.deepcopy(self.dI_noweight)
330        obj.dI_didata = copy.deepcopy(self.dI_didata)
331        obj.dI_sqrdata = copy.deepcopy(self.dI_sqrdata)
332        obj.dI_idata = copy.deepcopy(self.dI_idata)
333        obj.dq_l = copy.deepcopy(self.dq_l)
334        obj.dq_r = copy.deepcopy(self.dq_r)
335        obj.dx_max = copy.deepcopy(self.dx_max)
336        obj.dx_min = copy.deepcopy(self.dx_min)
337        obj.dxl = copy.deepcopy(self.dxl)
338        obj.dxw = copy.deepcopy(self.dxw)
339        obj.disp_box = copy.deepcopy(self.disp_box)
340        obj.qmin = copy.deepcopy(self.qmin)
341        obj.qmax = copy.deepcopy(self.qmax)
342        obj.multi_factor = self.multi_factor
343        obj.magnetic_on = self.magnetic_on
344        obj.npts = copy.deepcopy(self.npts)
345        obj.cb1 = copy.deepcopy(self.cb1)
346        obj.smearer = copy.deepcopy(self.smearer)
347
348        for name, state in self.saved_states.iteritems():
349            copy_name = copy.deepcopy(name)
350            copy_state = state.clone()
351            obj.saved_states[copy_name] = copy_state
352        return obj
353
354    def _repr_helper(self, list, rep):
355        """
356        Helper method to print a state
357        """
358        for item in list:
359            rep += "parameter name: %s \n" % str(item[1])
360            rep += "value: %s\n" % str(item[2])
361            rep += "selected: %s\n" % str(item[0])
362            rep += "error displayed : %s \n" % str(item[4][0])
363            rep += "error value:%s \n" % str(item[4][1])
364            rep += "minimum displayed : %s \n" % str(item[5][0])
365            rep += "minimum value : %s \n" % str(item[5][1])
366            rep += "maximum displayed : %s \n" % str(item[6][0])
367            rep += "maximum value : %s \n" % str(item[6][1])
368            rep += "parameter unit: %s\n\n" % str(item[7])
369        return rep
370
371    def __repr__(self):
372        """
373        output string for printing
374        """
375        rep = "\nState name: %s\n" % self.file
376        t = time.localtime(self.timestamp)
377        time_str = time.strftime("%b %d %Y %H;%M;%S ", t)
378
379        rep += "State created: %s\n" % time_str
380        rep += "State form factor combobox selection: %s\n" % self.formfactorcombobox
381        rep += "State structure factor combobox selection: %s\n" % self.structurecombobox
382        rep += "is data : %s\n" % self.is_data
383        rep += "data's name : %s\n" % self.data_name
384        rep += "data's id : %s\n" % self.data_id
385        if self.model is not None:
386            m_name = self.model.__class__.__name__
387            if m_name == 'Model':
388                m_name = self.m_name
389            rep += "model name : %s\n" % m_name
390        else:
391            rep += "model name : None\n"
392        rep += "multi_factor : %s\n" % str(self.multi_factor)
393        rep += "magnetic_on : %s\n" % str(self.magnetic_on)
394        rep += "model type (Category) selected: %s\n" % self.categorycombobox
395        rep += "data : %s\n" % str(self.data)
396        rep += "Plotting Range: min: %s, max: %s, steps: %s\n" % \
397               (str(self.qmin),str(self.qmax), str(self.npts))
398        rep += "Dispersion selection : %s\n" % str(self.disp_box)
399        rep += "Smearing enable : %s\n" % str(self.enable_smearer)
400        rep += "Smearing disable : %s\n" % str(self.disable_smearer)
401        rep += "Pinhole smearer enable : %s\n" % str(self.pinhole_smearer)
402        rep += "Slit smearer enable : %s\n" % str(self.slit_smearer)
403        rep += "Dispersity enable : %s\n" % str(self.enable_disp)
404        rep += "Dispersity disable : %s\n" % str(self.disable_disp)
405        rep += "Slit smearer enable: %s\n" % str(self.slit_smearer)
406
407        rep += "dI_noweight : %s\n" % str(self.dI_noweight)
408        rep += "dI_didata : %s\n" % str(self.dI_didata)
409        rep += "dI_sqrdata : %s\n" % str(self.dI_sqrdata)
410        rep += "dI_idata : %s\n" % str(self.dI_idata)
411
412        rep += "2D enable : %s\n" % str(self.enable2D)
413        rep += "All parameters checkbox selected: %s\n" % str(self.cb1)
414        rep += "Value of Chisqr : %s\n" % str(self.tcChi)
415        rep += "Smear object : %s\n" % str(self.smearer)
416        rep += "Smear type : %s\n" % str(self.smear_type)
417        rep += "dq_l  : %s\n" % self.dq_l
418        rep += "dq_r  : %s\n" % self.dq_r
419        rep += "dx_max  : %s\n" % str(self.dx_max)
420        rep += "dx_min : %s\n" % str(self.dx_min)
421        rep += "dxl  : %s\n" % str(self.dxl)
422        rep += "dxw : %s\n" % str(self.dxw)
423        rep += "model  : %s\n\n" % str(self.model)
424        temp_parameters = []
425        temp_fittable_param = []
426        if self.data.__class__.__name__ == "Data2D":
427            self.is_2D = True
428        else:
429            self.is_2D = False
430        if self.data is not None:
431            if not self.is_2D:
432                for item in self.parameters:
433                    if item not in self.orientation_params:
434                        temp_parameters.append(item)
435                for item in self.fittable_param:
436                    if item not in self.orientation_params_disp:
437                        temp_fittable_param.append(item)
438            else:
439                temp_parameters = self.parameters
440                temp_fittable_param = self.fittable_param
441
442            rep += "number parameters(self.parameters): %s\n" % \
443                   len(temp_parameters)
444            rep = self._repr_helper(list=temp_parameters, rep=rep)
445            rep += "number str_parameters(self.str_parameters): %s\n" % \
446                   len(self.str_parameters)
447            rep = self._repr_helper(list=self.str_parameters, rep=rep)
448            rep += "number fittable_param(self.fittable_param): %s\n" % \
449                   len(temp_fittable_param)
450            rep = self._repr_helper(list=temp_fittable_param, rep=rep)
451        return rep
452
453    def set_report_string(self):
454        """
455        Get the values (strings) from __str__ for report
456        """
457        # Dictionary of the report strings
458        repo_time = ""
459        model_name = ""
460        title = ""
461        title_name = ""
462        file_name = ""
463        param_string = ""
464        paramval_string = ""
465        chi2_string = ""
466        q_range = ""
467        strings = self.__repr__()
468        lines = strings.split('\n')
469
470        # get all string values from __str__()
471        for line in lines:
472            value = ""
473            content = line.split(":")
474            name = content[0]
475            try:
476                value = content[1]
477            except Exception:
478                msg = "Report string expected 'name: value' but got %r"%line
479                logging.error(msg)
480            if name.count("State created"):
481                repo_time = "" + value
482            if name.count("parameter name"):
483                val_name = value.split(".")
484                if len(val_name) > 1:
485                    if val_name[1].count("width"):
486                        param_string += value + ','
487                    else:
488                        continue
489                else:
490                    param_string += value + ','
491            if name == "value":
492                param_string += value + ','
493            if name == "selected":
494                if value == u' False':
495                    fixed_parameter = True
496                else:
497                    fixed_parameter = False
498            if name == "error value":
499                if fixed_parameter:
500                    param_string += '(fixed),'
501                else:
502                    param_string += value + ','
503            if name == "parameter unit":
504                param_string += value + ':'
505            if name == "Value of Chisqr ":
506                chi2 = ("Chi2/Npts = " + value)
507                chi2_string = CENTRE % chi2
508            if name == "Title":
509                if len(value.strip()) == 0:
510                    continue
511                title = value + " [" + repo_time + "]"
512                title_name = HEADER % title
513            if name == "data ":
514                try:
515                    file_value = ("File name:" + content[2])
516                    file_name = CENTRE % file_value
517                    if len(title) == 0:
518                        title = content[2] + " [" + repo_time + "]"
519                        title_name = HEADER % title
520                except Exception:
521                    msg = "While parsing 'data: ...'\n"
522                    logging.error(msg + traceback.format_exc())
523            if name == "model name ":
524                try:
525                    modelname = "Model name:" + content[1]
526                except:
527                    modelname = "Model name:" + " NAN"
528                model_name = CENTRE % modelname
529
530            if name == "Plotting Range":
531                try:
532                    q_range = content[1] + " = " + content[2] \
533                            + " = " + content[3].split(",")[0]
534                    q_name = ("Q Range:    " + q_range)
535                    q_range = CENTRE % q_name
536                except Exception:
537                    msg = "While parsing 'Plotting Range: ...'\n"
538                    logging.error(msg + traceback.format_exc())
539        paramval = ""
540        for lines in param_string.split(":"):
541            line = lines.split(",")
542            if len(lines) > 0:
543                param = line[0]
544                param += " = " + line[1]
545                if len(line[2].split()) > 0 and not line[2].count("None"):
546                    param += " +- " + line[2]
547                if len(line[3].split()) > 0 and not line[3].count("None"):
548                    param += " " + line[3]
549                if not paramval.count(param):
550                    paramval += param + "\n"
551                    paramval_string += CENTRE % param + "\n"
552
553        text_string = "\n\n\n%s\n\n%s\n%s\n%s\n\n%s" % \
554                      (title, file, q_name, chi2, paramval)
555
556        title_name = self._check_html_format(title_name)
557        file_name = self._check_html_format(file_name)
558        title = self._check_html_format(title)
559
560        html_string = title_name + "\n" + file_name + \
561                                   "\n" + model_name + \
562                                   "\n" + q_range + \
563                                   "\n" + chi2_string + \
564                                   "\n" + ELINE + \
565                                   "\n" + paramval_string + \
566                                   "\n" + ELINE + \
567                                   "\n" + FEET_1 % title + \
568                                   "\n" + FEET_2
569
570        return html_string, text_string, title
571
572    def _check_html_format(self, name):
573        """
574        Check string '%' for html format
575        """
576        if name.count('%'):
577            name = name.replace('%', '&#37')
578
579        return name
580
581    def report(self, figs=None, canvases=None):
582        """
583        Invoke report dialog panel
584
585        : param figs: list of pylab figures [list]
586        """
587        from sas.sasgui.perspectives.fitting.report_dialog import ReportDialog
588        # get the strings for report
589        html_str, text_str, title = self.set_report_string()
590        # Allow 2 figures to append
591        if len(figs) == 1:
592            add_str = FEET_3
593        elif len(figs) == 2:
594            add_str = ELINE
595            add_str += FEET_2 % ("%s")
596            add_str += ELINE
597            add_str += FEET_3
598        elif len(figs) > 2:
599            add_str = ELINE
600            add_str += FEET_2 % ("%s")
601            add_str += ELINE
602            add_str += FEET_2 % ("%s")
603            add_str += ELINE
604            add_str += FEET_3
605        else:
606            add_str = ""
607
608        # final report html strings
609        report_str = html_str % ("%s") + add_str
610
611        # make plot image
612        images = self.set_plot_state(figs, canvases)
613        report_list = [report_str, text_str, images]
614        dialog = ReportDialog(report_list, None, wx.ID_ANY, "")
615        dialog.Show()
616
617    def _toXML_helper(self, thelist, element, newdoc):
618        """
619        Helper method to create xml file for saving state
620        """
621        for item in thelist:
622            sub_element = newdoc.createElement('parameter')
623            sub_element.setAttribute('name', str(item[1]))
624            sub_element.setAttribute('value', str(item[2]))
625            sub_element.setAttribute('selected_to_fit', str(item[0]))
626            sub_element.setAttribute('error_displayed', str(item[4][0]))
627            sub_element.setAttribute('error_value', str(item[4][1]))
628            sub_element.setAttribute('minimum_displayed', str(item[5][0]))
629            sub_element.setAttribute('minimum_value', str(item[5][1]))
630            sub_element.setAttribute('maximum_displayed', str(item[6][0]))
631            sub_element.setAttribute('maximum_value', str(item[6][1]))
632            sub_element.setAttribute('unit', str(item[7]))
633            element.appendChild(sub_element)
634
635    def toXML(self, file="fitting_state.fitv", doc=None,
636              entry_node=None, batch_fit_state=None):
637        """
638        Writes the state of the fit panel to file, as XML.
639
640        Compatible with standalone writing, or appending to an
641        already existing XML document. In that case, the XML document
642        is required. An optional entry node in the XML document may also be given.
643
644        :param file: file to write to
645        :param doc: XML document object [optional]
646        :param entry_node: XML node within the XML document at which we will append the data [optional]
647
648        """
649        from xml.dom.minidom import getDOMImplementation
650
651        # Check whether we have to write a standalone XML file
652        if doc is None:
653            impl = getDOMImplementation()
654            doc_type = impl.createDocumentType(FITTING_NODE_NAME, "1.0", "1.0")
655            newdoc = impl.createDocument(None, FITTING_NODE_NAME, doc_type)
656            top_element = newdoc.documentElement
657        else:
658            # We are appending to an existing document
659            newdoc = doc
660            try:
661                top_element = newdoc.createElement(FITTING_NODE_NAME)
662            except:
663                string = etree.tostring(doc, pretty_print=True)
664                newdoc = parseString(string)
665                top_element = newdoc.createElement(FITTING_NODE_NAME)
666            if entry_node is None:
667                newdoc.documentElement.appendChild(top_element)
668            else:
669                try:
670                    entry_node.appendChild(top_element)
671                except:
672                    node_name = entry_node.tag
673                    node_list = newdoc.getElementsByTagName(node_name)
674                    entry_node = node_list.item(0)
675                    entry_node.appendChild(top_element)
676
677        attr = newdoc.createAttribute("version")
678        attr.nodeValue = '1.0'
679        top_element.setAttributeNode(attr)
680
681        # File name
682        element = newdoc.createElement("filename")
683        if self.file is not None:
684            element.appendChild(newdoc.createTextNode(str(self.file)))
685        else:
686            element.appendChild(newdoc.createTextNode(str(file)))
687        top_element.appendChild(element)
688
689        element = newdoc.createElement("timestamp")
690        element.appendChild(newdoc.createTextNode(time.ctime(self.timestamp)))
691        attr = newdoc.createAttribute("epoch")
692        attr.nodeValue = str(self.timestamp)
693        element.setAttributeNode(attr)
694        top_element.appendChild(element)
695
696        # Inputs
697        inputs = newdoc.createElement("Attributes")
698        top_element.appendChild(inputs)
699
700        if self.data is not None and hasattr(self.data, "group_id"):
701            self.data_group_id = self.data.group_id
702        if self.data is not None and hasattr(self.data, "is_data"):
703            self.is_data = self.data.is_data
704        if self.data is not None:
705            self.data_name = self.data.name
706        if self.data is not None and hasattr(self.data, "id"):
707            self.data_id = self.data.id
708
709        for item in LIST_OF_DATA_ATTRIBUTES:
710            element = newdoc.createElement(item[0])
711            element.setAttribute(item[0], str(getattr(self, item[1])))
712            inputs.appendChild(element)
713
714        for item in LIST_OF_STATE_ATTRIBUTES:
715            element = newdoc.createElement(item[0])
716            element.setAttribute(item[0], str(getattr(self, item[1])))
717            inputs.appendChild(element)
718
719        # For self.values ={ disp_param_name: [vals,...],...}
720        # and for self.weights ={ disp_param_name: [weights,...],...}
721        for item in LIST_OF_MODEL_ATTRIBUTES:
722            element = newdoc.createElement(item[0])
723            value_list = getattr(self, item[1])
724            for key, value in value_list.iteritems():
725                sub_element = newdoc.createElement(key)
726                sub_element.setAttribute('name', str(key))
727                for val in value:
728                    sub_element.appendChild(newdoc.createTextNode(str(val)))
729
730                element.appendChild(sub_element)
731            inputs.appendChild(element)
732
733        # Create doc for the dictionary of self._disp_obj_dic
734        for tagname, varname, tagtype in DISPERSION_LIST:
735            element = newdoc.createElement(tagname)
736            value_list = getattr(self, varname)
737            for key, value in value_list.iteritems():
738                sub_element = newdoc.createElement(key)
739                sub_element.setAttribute('name', str(key))
740                sub_element.setAttribute('value', str(value))
741                element.appendChild(sub_element)
742            inputs.appendChild(element)
743
744        for item in LIST_OF_STATE_PARAMETERS:
745            element = newdoc.createElement(item[0])
746            self._toXML_helper(thelist=getattr(self, item[1]), element=element, newdoc=newdoc)
747            inputs.appendChild(element)
748
749        # Combined and Simultaneous Fit Parameters
750        if batch_fit_state is not None:
751            batch_combo = newdoc.createElement('simultaneous_fit')
752            top_element.appendChild(batch_combo)
753
754            # Simultaneous Fit Number For Linking Later
755            element = newdoc.createElement('sim_fit_number')
756            element.setAttribute('fit_number', str(batch_fit_state.fit_page_no))
757            batch_combo.appendChild(element)
758
759            # Save constraints
760            constraints = newdoc.createElement('constraints')
761            batch_combo.appendChild(constraints)
762            for constraint in batch_fit_state.constraints_list:
763                # model_cbox, param_cbox, egal_txt, constraint, btRemove, sizer
764                doc_cons = newdoc.createElement('constraint')
765                doc_cons.setAttribute('model_cbox',
766                                      str(constraint.model_cbox.GetValue()))
767                doc_cons.setAttribute('param_cbox',
768                                      str(constraint.param_cbox.GetValue()))
769                doc_cons.setAttribute('egal_txt',
770                                      str(constraint.egal_txt.GetLabel()))
771                doc_cons.setAttribute('constraint',
772                                      str(constraint.constraint.GetValue()))
773                constraints.appendChild(doc_cons)
774
775            # Save all models
776            models = newdoc.createElement('model_list')
777            batch_combo.appendChild(models)
778            for model in batch_fit_state.model_list:
779                doc_model = newdoc.createElement('model_list_item')
780                doc_model.setAttribute('checked', str(model[0].GetValue()))
781                keys = model[1].keys()
782                doc_model.setAttribute('name', str(keys[0]))
783                values = model[1].get(keys[0])
784                doc_model.setAttribute('fit_number', str(model[2]))
785                doc_model.setAttribute('fit_page_source', str(model[3]))
786                doc_model.setAttribute('model_name', str(values.model.id))
787                models.appendChild(doc_model)
788
789            # Select All Checkbox
790            element = newdoc.createElement('select_all')
791            if batch_fit_state.select_all:
792                element.setAttribute('checked', 'True')
793            else:
794                element.setAttribute('checked', 'False')
795            batch_combo.appendChild(element)
796
797        # Save the file
798        if doc is None:
799            fd = open(file, 'w')
800            fd.write(newdoc.toprettyxml())
801            fd.close()
802            return None
803        else:
804            return newdoc
805
806    def _fromXML_helper(self, node, list):
807        """
808        Helper function to write state to xml
809        """
810        for item in node:
811            try:
812                name = item.get('name')
813            except:
814                name = None
815            try:
816                value = item.get('value')
817            except:
818                value = None
819            try:
820                selected_to_fit = (item.get('selected_to_fit') == "True")
821            except:
822                selected_to_fit = None
823            try:
824                error_displayed = (item.get('error_displayed') == "True")
825            except:
826                error_displayed = None
827            try:
828                error_value = item.get('error_value')
829            except:
830                error_value = None
831            try:
832                minimum_displayed = (item.get('minimum_displayed') == "True")
833            except:
834                minimum_displayed = None
835            try:
836                minimum_value = item.get('minimum_value')
837            except:
838                minimum_value = None
839            try:
840                maximum_displayed = (item.get('maximum_displayed') == "True")
841            except:
842                maximum_displayed = None
843            try:
844                maximum_value = item.get('maximum_value')
845            except:
846                maximum_value = None
847            try:
848                unit = item.get('unit')
849            except:
850                unit = None
851            list.append([selected_to_fit, name, value, "+/-",
852                         [error_displayed, error_value],
853                         [minimum_displayed, minimum_value],
854                         [maximum_displayed, maximum_value], unit])
855
856    def fromXML(self, file=None, node=None):
857        """
858        Load fitting state from a file
859
860        :param file: .fitv file
861        :param node: node of a XML document to read from
862
863        """
864        if file is not None:
865            msg = "PageState no longer supports non-CanSAS"
866            msg += " format for fitting files"
867            raise RuntimeError, msg
868
869        if node.get('version')and node.get('version') == '1.0':
870
871            # Get file name
872            entry = get_content('ns:filename', node)
873            if entry is not None:
874                self.file = entry.text.strip()
875
876            # Get time stamp
877            entry = get_content('ns:timestamp', node)
878            if entry is not None and entry.get('epoch'):
879                try:
880                    self.timestamp = float(entry.get('epoch'))
881                except:
882                    msg = "PageState.fromXML: Could not"
883                    msg += " read timestamp\n %s" % sys.exc_value
884                    logging.error(msg)
885
886            # Parse fitting attributes
887            entry = get_content('ns:Attributes', node)
888            for item in LIST_OF_DATA_ATTRIBUTES:
889                node = get_content('ns:%s' % item[0], entry)
890                setattr(self, item[0], parse_entry_helper(node, item))
891
892            if entry is not None:
893                for item in LIST_OF_STATE_ATTRIBUTES:
894                    node = get_content('ns:%s' % item[0], entry)
895                    setattr(self, item[0], parse_entry_helper(node, item))
896
897                for item in LIST_OF_STATE_PARAMETERS:
898                    node = get_content("ns:%s" % item[0], entry)
899                    self._fromXML_helper(node=node, list=getattr(self, item[1]))
900
901                # Recover _disp_obj_dict from xml file
902                self._disp_obj_dict = {}
903                for tagname, varname, tagtype in DISPERSION_LIST:
904                    node = get_content("ns:%s" % tagname, entry)
905                    for attr in node:
906                        parameter = str(attr.get('name'))
907                        value = attr.get('value')
908                        if value.startswith("<"):
909                            try:
910                                # <path.to.NamedDistribution object/instance...>
911                                cls_name = value[1:].split()[0].split('.')[-1]
912                                cls = getattr(sasmodels.weights, cls_name)
913                                value = cls.type
914                            except Exception:
915                                logging.error("unable to load distribution %r for %s"
916                                              % (value, parameter))
917                                continue
918                        _disp_obj_dict = getattr(self, varname)
919                        _disp_obj_dict[parameter] = value
920
921                # get self.values and self.weights dic. if exists
922                for tagname, varname in LIST_OF_MODEL_ATTRIBUTES:
923                    node = get_content("ns:%s" % tagname, entry)
924                    dic = {}
925                    value_list = []
926                    for par in node:
927                        name = par.get('name')
928                        values = par.text.split()
929                        # Get lines only with numbers
930                        for line in values:
931                            try:
932                                val = float(line)
933                                value_list.append(val)
934                            except Exception:
935                                # pass if line is empty (it happens)
936                                msg = ("Error reading %r from %s %s\n"
937                                       % (line, tagname, name))
938                                logging.error(msg + traceback.format_exc())
939                        dic[name] = numpy.array(value_list)
940                    setattr(self, varname, dic)
941
942    def set_plot_state(self, figs, canvases):
943        """
944        Build image state that wx.html understand
945        by plotting, putting it into wx.FileSystem image object
946
947        """
948        images = []
949        # some imports
950        import wx
951
952        # Reset memory
953        self.imgRAM = None
954        wx.MemoryFSHandler()
955
956        # For no figures in the list, prepare empty plot
957        if figs == None or len(figs) == 0:
958            figs = [None]
959
960        # Loop over the list of figures
961        # use wx.MemoryFSHandler
962        self.imgRAM = wx.MemoryFSHandler()
963        for fig in figs:
964            if figs != None:
965                ind = figs.index(fig)
966                canvas = canvases[ind]
967
968            #store the image in wx.FileSystem Object
969            wx.FileSystem.AddHandler(wx.MemoryFSHandler())
970
971            # index of the fig
972            ind = figs.index(fig)
973
974            #AddFile, image can be retrieved with 'memory:filename'
975            self.imgRAM.AddFile('img_fit%s.png' % ind,
976                                canvas.bitmap, wx.BITMAP_TYPE_PNG)
977
978            #append figs
979            images.append(fig)
980
981        return images
982
983
984class Reader(CansasReader):
985    """
986    Class to load a .fitv fitting file
987    """
988    ## File type
989    type_name = "Fitting"
990
991    ## Wildcards
992    type = ["Fitting files (*.fitv)|*.fitv"
993            "SASView file (*.svs)|*.svs"]
994    ## List of allowed extensions
995    ext = ['.fitv', '.FITV', '.svs', 'SVS']
996
997    def __init__(self, call_back=None, cansas=True):
998        CansasReader.__init__(self)
999        """
1000        Initialize the call-back method to be called
1001        after we load a file
1002
1003        :param call_back: call-back method
1004        :param cansas:  True = files will be written/read in CanSAS format
1005                        False = write CanSAS format
1006
1007        """
1008        ## Call back method to be executed after a file is read
1009        self.call_back = call_back
1010        ## CanSAS format flag
1011        self.cansas = cansas
1012        self.state = None
1013        # batch fitting params for saving
1014        self.batchfit_params = []
1015
1016    def get_state(self):
1017        return self.state
1018
1019    def read(self, path):
1020        """
1021        Load a new P(r) inversion state from file
1022
1023        :param path: file path
1024
1025        """
1026        if self.cansas == True:
1027            return self._read_cansas(path)
1028
1029    def _data2d_to_xml_doc(self, datainfo):
1030        """
1031        Create an XML document to contain the content of a Data2D
1032
1033        :param datainfo: Data2D object
1034
1035        """
1036        if not issubclass(datainfo.__class__, Data2D):
1037            raise RuntimeError, "The cansas writer expects a Data2D instance"
1038
1039        doc = xml.dom.minidom.Document()
1040        main_node = doc.createElement("SASroot")
1041        main_node.setAttribute("version", self.version)
1042        main_node.setAttribute("xmlns", "cansas1d/%s" % self.version)
1043        main_node.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
1044        main_node.setAttribute("xsi:schemaLocation",
1045                               "cansas1d/%s http://svn.smallangles.net/svn/canSAS/1dwg/trunk/cansas1d.xsd" % self.version)
1046
1047        doc.appendChild(main_node)
1048
1049        entry_node = doc.createElement("SASentry")
1050        main_node.appendChild(entry_node)
1051
1052        write_node(doc, entry_node, "Title", datainfo.title)
1053        if datainfo is not None:
1054            write_node(doc, entry_node, "data_class", datainfo.__class__.__name__)
1055        for item in datainfo.run:
1056            runname = {}
1057            if datainfo.run_name.has_key(item) and len(str(datainfo.run_name[item])) > 1:
1058                runname = {'name': datainfo.run_name[item]}
1059            write_node(doc, entry_node, "Run", item, runname)
1060        # Data info
1061        new_node = doc.createElement("SASdata")
1062        entry_node.appendChild(new_node)
1063        for item in LIST_OF_DATA_2D_ATTR:
1064            element = doc.createElement(item[0])
1065            element.setAttribute(item[0], str(getattr(datainfo, item[1])))
1066            new_node.appendChild(element)
1067
1068        for item in LIST_OF_DATA_2D_VALUES:
1069            root_node = doc.createElement(item[0])
1070            new_node.appendChild(root_node)
1071            temp_list = None
1072            temp_list = getattr(datainfo, item[1])
1073
1074            if temp_list is None or len(temp_list) == 0:
1075                element = doc.createElement(item[0])
1076                element.appendChild(doc.createTextNode(str(temp_list)))
1077                root_node.appendChild(element)
1078            else:
1079                for value in temp_list:
1080                    element = doc.createElement(item[0])
1081                    element.setAttribute(item[0], str(value))
1082                    root_node.appendChild(element)
1083
1084        # Sample info
1085        sample = doc.createElement("SASsample")
1086        if datainfo.sample.name is not None:
1087            sample.setAttribute("name", str(datainfo.sample.name))
1088        entry_node.appendChild(sample)
1089        write_node(doc, sample, "ID", str(datainfo.sample.ID))
1090        write_node(doc, sample, "thickness", datainfo.sample.thickness,
1091                   {"unit": datainfo.sample.thickness_unit})
1092        write_node(doc, sample, "transmission", datainfo.sample.transmission)
1093        write_node(doc, sample, "temperature", datainfo.sample.temperature,
1094                   {"unit": datainfo.sample.temperature_unit})
1095
1096        for item in datainfo.sample.details:
1097            write_node(doc, sample, "details", item)
1098
1099        pos = doc.createElement("position")
1100        written = write_node(doc, pos, "x", datainfo.sample.position.x,
1101                             {"unit": datainfo.sample.position_unit})
1102        written = written | write_node(doc, pos, "y",
1103                                       datainfo.sample.position.y,
1104                                       {"unit": datainfo.sample.position_unit})
1105        written = written | write_node(doc, pos, "z",
1106                                       datainfo.sample.position.z,
1107                                       {"unit": datainfo.sample.position_unit})
1108        if written == True:
1109            sample.appendChild(pos)
1110
1111        ori = doc.createElement("orientation")
1112        written = write_node(doc, ori, "roll", datainfo.sample.orientation.x,
1113                             {"unit": datainfo.sample.orientation_unit})
1114        written = written | write_node(doc, ori, "pitch",
1115                                       datainfo.sample.orientation.y,
1116                                       {"unit": datainfo.sample.orientation_unit})
1117        written = written | write_node(doc, ori, "yaw",
1118                                       datainfo.sample.orientation.z,
1119                                       {"unit": datainfo.sample.orientation_unit})
1120        if written == True:
1121            sample.appendChild(ori)
1122
1123        # Instrument info
1124        instr = doc.createElement("SASinstrument")
1125        entry_node.appendChild(instr)
1126
1127        write_node(doc, instr, "name", datainfo.instrument)
1128
1129        #   Source
1130        source = doc.createElement("SASsource")
1131        if datainfo.source.name is not None:
1132            source.setAttribute("name", str(datainfo.source.name))
1133        instr.appendChild(source)
1134
1135        write_node(doc, source, "radiation", datainfo.source.radiation)
1136        write_node(doc, source, "beam_shape", datainfo.source.beam_shape)
1137        size = doc.createElement("beam_size")
1138        if datainfo.source.beam_size_name is not None:
1139            size.setAttribute("name", str(datainfo.source.beam_size_name))
1140        written = write_node(doc, size, "x", datainfo.source.beam_size.x,
1141                             {"unit": datainfo.source.beam_size_unit})
1142        written = written | write_node(doc, size, "y",
1143                                       datainfo.source.beam_size.y,
1144                                       {"unit": datainfo.source.beam_size_unit})
1145        written = written | write_node(doc, size, "z",
1146                                       datainfo.source.beam_size.z,
1147                                       {"unit": datainfo.source.beam_size_unit})
1148        if written == True:
1149            source.appendChild(size)
1150
1151        write_node(doc, source, "wavelength", datainfo.source.wavelength,
1152                   {"unit": datainfo.source.wavelength_unit})
1153        write_node(doc, source, "wavelength_min",
1154                   datainfo.source.wavelength_min,
1155                   {"unit": datainfo.source.wavelength_min_unit})
1156        write_node(doc, source, "wavelength_max",
1157                   datainfo.source.wavelength_max,
1158                   {"unit": datainfo.source.wavelength_max_unit})
1159        write_node(doc, source, "wavelength_spread",
1160                   datainfo.source.wavelength_spread,
1161                   {"unit": datainfo.source.wavelength_spread_unit})
1162
1163        #   Collimation
1164        for item in datainfo.collimation:
1165            coll = doc.createElement("SAScollimation")
1166            if item.name is not None:
1167                coll.setAttribute("name", str(item.name))
1168            instr.appendChild(coll)
1169
1170            write_node(doc, coll, "length", item.length,
1171                       {"unit": item.length_unit})
1172
1173            for apert in item.aperture:
1174                ap = doc.createElement("aperture")
1175                if apert.name is not None:
1176                    ap.setAttribute("name", str(apert.name))
1177                if apert.type is not None:
1178                    ap.setAttribute("type", str(apert.type))
1179                coll.appendChild(ap)
1180
1181                write_node(doc, ap, "distance", apert.distance,
1182                           {"unit": apert.distance_unit})
1183
1184                size = doc.createElement("size")
1185                if apert.size_name is not None:
1186                    size.setAttribute("name", str(apert.size_name))
1187                written = write_node(doc, size, "x", apert.size.x,
1188                                     {"unit": apert.size_unit})
1189                written = written | write_node(doc, size, "y", apert.size.y,
1190                                               {"unit": apert.size_unit})
1191                written = written | write_node(doc, size, "z", apert.size.z,
1192                                               {"unit": apert.size_unit})
1193                if written == True:
1194                    ap.appendChild(size)
1195
1196        #   Detectors
1197        for item in datainfo.detector:
1198            det = doc.createElement("SASdetector")
1199            written = write_node(doc, det, "name", item.name)
1200            written = written | write_node(doc, det, "SDD", item.distance,
1201                                           {"unit": item.distance_unit})
1202            written = written | write_node(doc, det, "slit_length",
1203                                           item.slit_length,
1204                                           {"unit": item.slit_length_unit})
1205            if written == True:
1206                instr.appendChild(det)
1207
1208            off = doc.createElement("offset")
1209            written = write_node(doc, off, "x", item.offset.x,
1210                                 {"unit": item.offset_unit})
1211            written = written | write_node(doc, off, "y", item.offset.y,
1212                                           {"unit": item.offset_unit})
1213            written = written | write_node(doc, off, "z", item.offset.z,
1214                                           {"unit": item.offset_unit})
1215            if written == True:
1216                det.appendChild(off)
1217
1218            center = doc.createElement("beam_center")
1219            written = write_node(doc, center, "x", item.beam_center.x,
1220                                 {"unit": item.beam_center_unit})
1221            written = written | write_node(doc, center, "y",
1222                                           item.beam_center.y,
1223                                           {"unit": item.beam_center_unit})
1224            written = written | write_node(doc, center, "z",
1225                                           item.beam_center.z,
1226                                           {"unit": item.beam_center_unit})
1227            if written == True:
1228                det.appendChild(center)
1229
1230            pix = doc.createElement("pixel_size")
1231            written = write_node(doc, pix, "x", item.pixel_size.x,
1232                                 {"unit": item.pixel_size_unit})
1233            written = written | write_node(doc, pix, "y", item.pixel_size.y,
1234                                           {"unit": item.pixel_size_unit})
1235            written = written | write_node(doc, pix, "z", item.pixel_size.z,
1236                                           {"unit": item.pixel_size_unit})
1237            if written == True:
1238                det.appendChild(pix)
1239
1240            ori = doc.createElement("orientation")
1241            written = write_node(doc, ori, "roll", item.orientation.x,
1242                                 {"unit": item.orientation_unit})
1243            written = written | write_node(doc, ori, "pitch",
1244                                           item.orientation.y,
1245                                           {"unit": item.orientation_unit})
1246            written = written | write_node(doc, ori, "yaw", item.orientation.z,
1247                                           {"unit": item.orientation_unit})
1248            if written == True:
1249                det.appendChild(ori)
1250
1251        # Processes info
1252        for item in datainfo.process:
1253            node = doc.createElement("SASprocess")
1254            entry_node.appendChild(node)
1255
1256            write_node(doc, node, "name", item.name)
1257            write_node(doc, node, "date", item.date)
1258            write_node(doc, node, "description", item.description)
1259            for term in item.term:
1260                value = term['value']
1261                del term['value']
1262                write_node(doc, node, "term", value, term)
1263            for note in item.notes:
1264                write_node(doc, node, "SASprocessnote", note)
1265        # Return the document, and the SASentry node associated with
1266        # the data we just wrote
1267        return doc, entry_node
1268
1269    def _parse_state(self, entry):
1270        """
1271        Read a fit result from an XML node
1272
1273        :param entry: XML node to read from
1274
1275        :return: PageState object
1276        """
1277        # Create an empty state
1278        state = None
1279        # Locate the P(r) node
1280        try:
1281            nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
1282                                namespaces={'ns': CANSAS_NS})
1283            if nodes != []:
1284                # Create an empty state
1285                state = PageState()
1286                state.fromXML(node=nodes[0])
1287
1288        except:
1289            logging.info("XML document does not contain fitting information.\n"
1290                         + traceback.format_exc())
1291
1292        return state
1293
1294    def _parse_save_state_entry(self, dom):
1295        """
1296        Parse a SASentry
1297
1298        :param node: SASentry node
1299
1300        :return: Data1D/Data2D object
1301
1302        """
1303        node = dom.xpath('ns:data_class', namespaces={'ns': CANSAS_NS})
1304        if not node or node[0].text.lstrip().rstrip() != "Data2D":
1305            return_value, _ = self._parse_entry(dom)
1306            numpy.trim_zeros(return_value.x)
1307            numpy.trim_zeros(return_value.y)
1308            numpy.trim_zeros(return_value.dy)
1309            size_dx = return_value.dx.size
1310            size_dxl = return_value.dxl.size
1311            size_dxw = return_value.dxw.size
1312            if size_dxl == 0 and size_dxw == 0:
1313                return_value.dxl = None
1314                return_value.dxw = None
1315                numpy.trim_zeros(return_value.dx)
1316            elif size_dx == 0:
1317                return_value.dx = None
1318                size_dx = size_dxl
1319                numpy.trim_zeros(return_value.dxl)
1320                numpy.trim_zeros(return_value.dxw)
1321
1322            return return_value, _
1323
1324        #Parse 2D
1325        data_info = Data2D()
1326
1327        # Look up title
1328        self._store_content('ns:Title', dom, 'title', data_info)
1329
1330        # Look up run number
1331        nodes = dom.xpath('ns:Run', namespaces={'ns': CANSAS_NS})
1332        for item in nodes:
1333            if item.text is not None:
1334                value = item.text.strip()
1335                if len(value) > 0:
1336                    data_info.run.append(value)
1337                    if item.get('name') is not None:
1338                        data_info.run_name[value] = item.get('name')
1339
1340        # Look up instrument name
1341        self._store_content('ns:SASinstrument/ns:name', dom,
1342                            'instrument', data_info)
1343
1344        # Notes
1345        note_list = dom.xpath('ns:SASnote', namespaces={'ns': CANSAS_NS})
1346        for note in note_list:
1347            try:
1348                if note.text is not None:
1349                    note_value = note.text.strip()
1350                    if len(note_value) > 0:
1351                        data_info.notes.append(note_value)
1352            except Exception:
1353                err_mess = "cansas_reader.read: error processing entry notes\n  %s" % sys.exc_value
1354                self.errors.append(err_mess)
1355                logging.error(err_mess)
1356
1357        # Sample info ###################
1358        entry = get_content('ns:SASsample', dom)
1359        if entry is not None:
1360            data_info.sample.name = entry.get('name')
1361
1362        self._store_content('ns:SASsample/ns:ID', dom, 'ID', data_info.sample)
1363        self._store_float('ns:SASsample/ns:thickness', dom, 'thickness', data_info.sample)
1364        self._store_float('ns:SASsample/ns:transmission', dom, 'transmission', data_info.sample)
1365        self._store_float('ns:SASsample/ns:temperature', dom, 'temperature', data_info.sample)
1366
1367        nodes = dom.xpath('ns:SASsample/ns:details', namespaces={'ns': CANSAS_NS})
1368        for item in nodes:
1369            try:
1370                if item.text is not None:
1371                    detail_value = item.text.strip()
1372                    if len(detail_value) > 0:
1373                        data_info.sample.details.append(detail_value)
1374            except Exception:
1375                err_mess = "cansas_reader.read: error processing sample details\n  %s" % sys.exc_value
1376                self.errors.append(err_mess)
1377                logging.error(err_mess)
1378
1379        # Position (as a vector)
1380        self._store_float('ns:SASsample/ns:position/ns:x', dom, 'position.x', data_info.sample)
1381        self._store_float('ns:SASsample/ns:position/ns:y', dom, 'position.y', data_info.sample)
1382        self._store_float('ns:SASsample/ns:position/ns:z', dom, 'position.z', data_info.sample)
1383
1384        # Orientation (as a vector)
1385        self._store_float('ns:SASsample/ns:orientation/ns:roll',
1386                          dom, 'orientation.x', data_info.sample)
1387        self._store_float('ns:SASsample/ns:orientation/ns:pitch',
1388                          dom, 'orientation.y', data_info.sample)
1389        self._store_float('ns:SASsample/ns:orientation/ns:yaw',
1390                          dom, 'orientation.z', data_info.sample)
1391
1392        # Source info ###################
1393        entry = get_content('ns:SASinstrument/ns:SASsource', dom)
1394        if entry is not None:
1395            data_info.source.name = entry.get('name')
1396
1397        self._store_content('ns:SASinstrument/ns:SASsource/ns:radiation',
1398                            dom, 'radiation', data_info.source)
1399        self._store_content('ns:SASinstrument/ns:SASsource/ns:beam_shape',
1400                            dom, 'beam_shape', data_info.source)
1401        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength',
1402                          dom, 'wavelength', data_info.source)
1403        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_min',
1404                          dom, 'wavelength_min', data_info.source)
1405        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_max',
1406                          dom, 'wavelength_max', data_info.source)
1407        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_spread',
1408                          dom, 'wavelength_spread', data_info.source)
1409
1410        # Beam size (as a vector)
1411        entry = get_content('ns:SASinstrument/ns:SASsource/ns:beam_size', dom)
1412        if entry is not None:
1413            data_info.source.beam_size_name = entry.get('name')
1414
1415        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:x',
1416                          dom, 'beam_size.x', data_info.source)
1417        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:y',
1418                          dom, 'beam_size.y', data_info.source)
1419        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:z',
1420                          dom, 'beam_size.z', data_info.source)
1421
1422        # Collimation info ###################
1423        nodes = dom.xpath('ns:SASinstrument/ns:SAScollimation',
1424                          namespaces={'ns': CANSAS_NS})
1425        for item in nodes:
1426            collim = Collimation()
1427            if item.get('name') is not None:
1428                collim.name = item.get('name')
1429            self._store_float('ns:length', item, 'length', collim)
1430
1431            # Look for apertures
1432            apert_list = item.xpath('ns:aperture',
1433                                    namespaces={'ns': CANSAS_NS})
1434            for apert in apert_list:
1435                aperture = Aperture()
1436
1437                # Get the name and type of the aperture
1438                aperture.name = apert.get('name')
1439                aperture.type = apert.get('type')
1440
1441                self._store_float('ns:distance', apert, 'distance', aperture)
1442
1443                entry = get_content('ns:size', apert)
1444                if entry is not None:
1445                    aperture.size_name = entry.get('name')
1446
1447                self._store_float('ns:size/ns:x', apert, 'size.x', aperture)
1448                self._store_float('ns:size/ns:y', apert, 'size.y', aperture)
1449                self._store_float('ns:size/ns:z', apert, 'size.z', aperture)
1450
1451                collim.aperture.append(aperture)
1452
1453            data_info.collimation.append(collim)
1454
1455        # Detector info ######################
1456        nodes = dom.xpath('ns:SASinstrument/ns:SASdetector',
1457                          namespaces={'ns': CANSAS_NS})
1458        for item in nodes:
1459
1460            detector = Detector()
1461
1462            self._store_content('ns:name', item, 'name', detector)
1463            self._store_float('ns:SDD', item, 'distance', detector)
1464
1465            # Detector offset (as a vector)
1466            self._store_float('ns:offset/ns:x', item, 'offset.x', detector)
1467            self._store_float('ns:offset/ns:y', item, 'offset.y', detector)
1468            self._store_float('ns:offset/ns:z', item, 'offset.z', detector)
1469
1470            # Detector orientation (as a vector)
1471            self._store_float('ns:orientation/ns:roll', item,
1472                              'orientation.x', detector)
1473            self._store_float('ns:orientation/ns:pitch', item,
1474                              'orientation.y', detector)
1475            self._store_float('ns:orientation/ns:yaw', item,
1476                              'orientation.z', detector)
1477
1478            # Beam center (as a vector)
1479            self._store_float('ns:beam_center/ns:x', item,
1480                              'beam_center.x', detector)
1481            self._store_float('ns:beam_center/ns:y', item,
1482                              'beam_center.y', detector)
1483            self._store_float('ns:beam_center/ns:z', item,
1484                              'beam_center.z', detector)
1485
1486            # Pixel size (as a vector)
1487            self._store_float('ns:pixel_size/ns:x', item,
1488                              'pixel_size.x', detector)
1489            self._store_float('ns:pixel_size/ns:y', item,
1490                              'pixel_size.y', detector)
1491            self._store_float('ns:pixel_size/ns:z', item,
1492                              'pixel_size.z', detector)
1493
1494            self._store_float('ns:slit_length', item, 'slit_length', detector)
1495
1496            data_info.detector.append(detector)
1497
1498        # Processes info ######################
1499        nodes = dom.xpath('ns:SASprocess', namespaces={'ns': CANSAS_NS})
1500        for item in nodes:
1501            process = Process()
1502            self._store_content('ns:name', item, 'name', process)
1503            self._store_content('ns:date', item, 'date', process)
1504            self._store_content('ns:description', item, 'description', process)
1505
1506            term_list = item.xpath('ns:term', namespaces={'ns': CANSAS_NS})
1507            for term in term_list:
1508                try:
1509                    term_attr = {}
1510                    for attr in term.keys():
1511                        term_attr[attr] = term.get(attr).strip()
1512                    if term.text is not None:
1513                        term_attr['value'] = term.text.strip()
1514                        process.term.append(term_attr)
1515                except:
1516                    err_mess = "cansas_reader.read: error processing process term\n  %s" % sys.exc_value
1517                    self.errors.append(err_mess)
1518                    logging.error(err_mess)
1519
1520            note_list = item.xpath('ns:SASprocessnote',
1521                                   namespaces={'ns': CANSAS_NS})
1522            for note in note_list:
1523                if note.text is not None:
1524                    process.notes.append(note.text.strip())
1525
1526            data_info.process.append(process)
1527
1528        # Data info ######################
1529        nodes = dom.xpath('ns:SASdata', namespaces={'ns': CANSAS_NS})
1530        if len(nodes) > 1:
1531            raise RuntimeError, "CanSAS reader is not compatible with" + \
1532                                " multiple SASdata entries"
1533
1534        for entry in nodes:
1535            for item in LIST_OF_DATA_2D_ATTR:
1536                #get node
1537                node = get_content('ns:%s' % item[0], entry)
1538                setattr(data_info, item[1], parse_entry_helper(node, item))
1539
1540            for item in LIST_OF_DATA_2D_VALUES:
1541                field = get_content('ns:%s' % item[0], entry)
1542                value_list = []
1543                if field is not None:
1544                    value_list = [parse_entry_helper(node, item) for node in field]
1545                if len(value_list) < 2:
1546                    setattr(data_info, item[0], None)
1547                else:
1548                    setattr(data_info, item[0], numpy.array(value_list))
1549
1550        return data_info
1551
1552    def _read_cansas(self, path):
1553        """
1554        Load data and P(r) information from a CanSAS XML file.
1555
1556        :param path: file path
1557
1558        :return: Data1D object if a single SASentry was found,
1559                    or a list of Data1D objects if multiple entries were found,
1560                    or None of nothing was found
1561
1562        :raise RuntimeError: when the file can't be opened
1563        :raise ValueError: when the length of the data vectors are inconsistent
1564
1565        """
1566        output = []
1567        basename = os.path.basename(path)
1568        root, extension = os.path.splitext(basename)
1569        ext = extension.lower()
1570        try:
1571            if os.path.isfile(path):
1572                if ext in self.ext or ext == '.xml':
1573                    tree = etree.parse(path, parser=etree.ETCompatXMLParser())
1574                    # Check the format version number
1575                    # Specifying the namespace will take care of the file
1576                    # format version
1577                    root = tree.getroot()
1578                    entry_list = root.xpath('ns:SASentry',
1579                                            namespaces={'ns': CANSAS_NS})
1580                    for entry in entry_list:
1581                        try:
1582                            sas_entry, _ = self._parse_save_state_entry(entry)
1583                        except:
1584                            raise
1585                        fitstate = self._parse_state(entry)
1586
1587                        # state could be None when .svs file is loaded
1588                        # in this case, skip appending to output
1589                        if fitstate is not None:
1590                            sas_entry.meta_data['fitstate'] = fitstate
1591                            sas_entry.filename = fitstate.file
1592                            output.append(sas_entry)
1593            else:
1594                self.call_back(format=ext)
1595                raise RuntimeError, "%s is not a file" % path
1596
1597            # Return output consistent with the loader's api
1598            if len(output) == 0:
1599                self.call_back(state=None, datainfo=None, format=ext)
1600                return None
1601            else:
1602                for ind in range(len(output)):
1603                    # Call back to post the new state
1604                    state = output[ind].meta_data['fitstate']
1605                    t = time.localtime(state.timestamp)
1606                    time_str = time.strftime("%b %d %H:%M", t)
1607                    # Check that no time stamp is already appended
1608                    max_char = state.file.find("[")
1609                    if max_char < 0:
1610                        max_char = len(state.file)
1611                    original_fname = state.file[0:max_char]
1612                    state.file = original_fname + ' [' + time_str + ']'
1613
1614                    if state is not None and state.is_data is not None:
1615                        output[ind].is_data = state.is_data
1616
1617                    output[ind].filename = state.file
1618                    state.data = output[ind]
1619                    state.data.name = output[ind].filename  # state.data_name
1620                    state.data.id = state.data_id
1621                    if state.is_data is not None:
1622                        state.data.is_data = state.is_data
1623                    if output[ind].run_name is not None\
1624                        and len(output[ind].run_name) != 0:
1625                        if isinstance(output[ind].run_name, dict):
1626                            name = output[ind].run_name.keys()[0]
1627                        else:
1628                            name = output[ind].run_name
1629                    else:
1630                        name = original_fname
1631                    state.data.group_id = name
1632                    # store state in fitting
1633                    self.call_back(state=state,
1634                                   datainfo=output[ind], format=ext)
1635                    self.state = state
1636                return output
1637        except:
1638            self.call_back(format=ext)
1639            raise
1640
1641    def write(self, filename, datainfo=None, fitstate=None):
1642        """
1643        Write the content of a Data1D as a CanSAS XML file only for standalone
1644
1645        :param filename: name of the file to write
1646        :param datainfo: Data1D object
1647        :param fitstate: PageState object
1648
1649        """
1650        # Sanity check
1651        if self.cansas:
1652            # Add fitting information to the XML document
1653            doc = self.write_toXML(datainfo, fitstate)
1654            # Write the XML document
1655        else:
1656            doc = fitstate.toXML(file=filename)
1657
1658        # Save the document no matter the type
1659        fd = open(filename, 'w')
1660        fd.write(doc.toprettyxml())
1661        fd.close()
1662
1663    def write_toXML(self, datainfo=None, state=None, batchfit=None):
1664        """
1665        Write toXML, a helper for write(),
1666        could be used by guimanager._on_save()
1667
1668        : return: xml doc
1669        """
1670
1671        self.batchfit_params = batchfit
1672        if state.data is None or not state.data.is_data:
1673            return None
1674        # make sure title and data run are filled.
1675        if state.data.title is None or state.data.title == '':
1676            state.data.title = state.data.name
1677        if state.data.run_name is None or state.data.run_name == {}:
1678            state.data.run = [str(state.data.name)]
1679            state.data.run_name[0] = state.data.name
1680
1681        if issubclass(state.data.__class__,
1682                      sas.sascalc.dataloader.data_info.Data1D):
1683            data = state.data
1684            doc, sasentry = self._to_xml_doc(data)
1685        else:
1686            data = state.data
1687            doc, sasentry = self._data2d_to_xml_doc(data)
1688
1689        if state is not None:
1690            doc = state.toXML(doc=doc, file=data.filename, entry_node=sasentry,
1691                              batch_fit_state=self.batchfit_params)
1692
1693        return doc
Note: See TracBrowser for help on using the repository browser.