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

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 998ca90 was 998ca90, checked in by krzywon, 8 years ago

#189 #12 Saving and loading project now fully saves and loads a simultaneous fits. Loading a project will reset Sasview state to its base state (with a warning). One error on load to fix before calling this finished.

  • Property mode set to 100644
File size: 73.4 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" % \
381               self.formfactorcombobox
382        rep += "State structure factor combobox selection: %s\n" % \
383               self.structurecombobox
384        rep += "is data : %s\n" % self.is_data
385        rep += "data's name : %s\n" % self.data_name
386        rep += "data's id : %s\n" % self.data_id
387        if self.model is not None:
388            m_name = self.model.__class__.__name__
389            if m_name == 'Model':
390                m_name = self.m_name
391            rep += "model name : %s\n" % m_name
392        else:
393            rep += "model name : None\n"
394        rep += "multi_factor : %s\n" % str(self.multi_factor)
395        rep += "magnetic_on : %s\n" % str(self.magnetic_on)
396        rep += "model type (Category) selected: %s\n" % self.categorycombobox
397        rep += "data : %s\n" % str(self.data)
398        rep += "Plotting Range: min: %s, max: %s, steps: %s\n" % \
399               (str(self.qmin),str(self.qmax), str(self.npts))
400        rep += "Dispersion selection : %s\n" % str(self.disp_box)
401        rep += "Smearing enable : %s\n" % str(self.enable_smearer)
402        rep += "Smearing disable : %s\n" % str(self.disable_smearer)
403        rep += "Pinhole smearer enable : %s\n" % str(self.pinhole_smearer)
404        rep += "Slit smearer enable : %s\n" % str(self.slit_smearer)
405        rep += "Dispersity enable : %s\n" % str(self.enable_disp)
406        rep += "Dispersity disable : %s\n" % str(self.disable_disp)
407        rep += "Slit smearer enable: %s\n" % str(self.slit_smearer)
408
409        rep += "dI_noweight : %s\n" % str(self.dI_noweight)
410        rep += "dI_didata : %s\n" % str(self.dI_didata)
411        rep += "dI_sqrdata : %s\n" % str(self.dI_sqrdata)
412        rep += "dI_idata : %s\n" % str(self.dI_idata)
413
414        rep += "2D enable : %s\n" % str(self.enable2D)
415        rep += "All parameters checkbox selected: %s\n" % str(self.cb1)
416        rep += "Value of Chisqr : %s\n" % str(self.tcChi)
417        rep += "Smear object : %s\n" % str(self.smearer)
418        rep += "Smear type : %s\n" % str(self.smear_type)
419        rep += "dq_l  : %s\n" % self.dq_l
420        rep += "dq_r  : %s\n" % self.dq_r
421        rep += "dx_max  : %s\n" % str(self.dx_max)
422        rep += "dx_min : %s\n" % str(self.dx_min)
423        rep += "dxl  : %s\n" % str(self.dxl)
424        rep += "dxw : %s\n" % str(self.dxw)
425        rep += "model  : %s\n\n" % str(self.model)
426        temp_parameters = []
427        temp_fittable_param = []
428        if self.data.__class__.__name__ == "Data2D":
429            self.is_2D = True
430        else:
431            self.is_2D = False
432        if self.data is not None:
433            if not self.is_2D:
434                for item in self.parameters:
435                    if item not in self.orientation_params:
436                        temp_parameters.append(item)
437                for item in self.fittable_param:
438                    if item not in self.orientation_params_disp:
439                        temp_fittable_param.append(item)
440            else:
441                temp_parameters = self.parameters
442                temp_fittable_param = self.fittable_param
443
444            rep += "number parameters(self.parameters): %s\n" % \
445                   len(temp_parameters)
446            rep = self._repr_helper(list=temp_parameters, rep=rep)
447            rep += "number str_parameters(self.str_parameters): %s\n" % \
448                   len(self.str_parameters)
449            rep = self._repr_helper(list=self.str_parameters, rep=rep)
450            rep += "number fittable_param(self.fittable_param): %s\n" % \
451                   len(temp_fittable_param)
452            rep = self._repr_helper(list=temp_fittable_param, rep=rep)
453        return rep
454
455    def set_report_string(self):
456        """
457        Get the values (strings) from __str__ for report
458        """
459        # Dictionary of the report strings
460        repo_time = ""
461        model_name = ""
462        title = ""
463        title_name = ""
464        file_name = ""
465        param_string = ""
466        paramval_string = ""
467        chi2_string = ""
468        q_range = ""
469        strings = self.__repr__()
470        lines = strings.split('\n')
471
472        # get all string values from __str__()
473        for line in lines:
474            value = ""
475            content = line.split(":")
476            name = content[0]
477            try:
478                value = content[1]
479            except Exception:
480                msg = "Report string expected 'name: value' but got %r"%line
481                logging.error(msg)
482            if name.count("State created"):
483                repo_time = "" + value
484            if name.count("parameter name"):
485                val_name = value.split(".")
486                if len(val_name) > 1:
487                    if val_name[1].count("width"):
488                        param_string += value + ','
489                    else:
490                        continue
491                else:
492                    param_string += value + ','
493            if name == "value":
494                param_string += value + ','
495            if name == "selected":
496                if value == u' False':
497                    fixed_parameter = True
498                else:
499                    fixed_parameter = False
500            if name == "error value":
501                if fixed_parameter:
502                    param_string += '(fixed),'
503                else:
504                    param_string += value + ','
505            if name == "parameter unit":
506                param_string += value + ':'
507            if name == "Value of Chisqr ":
508                chi2 = ("Chi2/Npts = " + value)
509                chi2_string = CENTRE % chi2
510            if name == "Title":
511                if len(value.strip()) == 0:
512                    continue
513                title = value + " [" + repo_time + "]"
514                title_name = HEADER % title
515            if name == "data ":
516                try:
517                    file_value = ("File name:" + content[2])
518                    file_name = CENTRE % file_value
519                    if len(title) == 0:
520                        title = content[2] + " [" + repo_time + "]"
521                        title_name = HEADER % title
522                except Exception:
523                    msg = "While parsing 'data: ...'\n"
524                    logging.error(msg + traceback.format_exc())
525            if name == "model name ":
526                try:
527                    modelname = "Model name:" + content[1]
528                except:
529                    modelname = "Model name:" + " NAN"
530                model_name = CENTRE % modelname
531
532            if name == "Plotting Range":
533                try:
534                    q_range = content[1] + " = " + content[2] \
535                            + " = " + content[3].split(",")[0]
536                    q_name = ("Q Range:    " + q_range)
537                    q_range = CENTRE % q_name
538                except Exception:
539                    msg = "While parsing 'Plotting Range: ...'\n"
540                    logging.error(msg + traceback.format_exc())
541        paramval = ""
542        for lines in param_string.split(":"):
543            line = lines.split(",")
544            if len(lines) > 0:
545                param = line[0]
546                param += " = " + line[1]
547                if len(line[2].split()) > 0 and not line[2].count("None"):
548                    param += " +- " + line[2]
549                if len(line[3].split()) > 0 and not line[3].count("None"):
550                    param += " " + line[3]
551                if not paramval.count(param):
552                    paramval += param + "\n"
553                    paramval_string += CENTRE % param + "\n"
554
555        text_string = "\n\n\n%s\n\n%s\n%s\n%s\n\n%s" % \
556                      (title, file, q_name, chi2, paramval)
557
558        title_name = self._check_html_format(title_name)
559        file_name = self._check_html_format(file_name)
560        title = self._check_html_format(title)
561
562        html_string = title_name + "\n" + file_name + \
563                                   "\n" + model_name + \
564                                   "\n" + q_range + \
565                                   "\n" + chi2_string + \
566                                   "\n" + ELINE + \
567                                   "\n" + paramval_string + \
568                                   "\n" + ELINE + \
569                                   "\n" + FEET_1 % title + \
570                                   "\n" + FEET_2
571
572        return html_string, text_string, title
573
574    def _check_html_format(self, name):
575        """
576        Check string '%' for html format
577        """
578        if name.count('%'):
579            name = name.replace('%', '&#37')
580
581        return name
582
583    def report(self, figs=None, canvases=None):
584        """
585        Invoke report dialog panel
586
587        : param figs: list of pylab figures [list]
588        """
589        from sas.sasgui.perspectives.fitting.report_dialog import ReportDialog
590        # get the strings for report
591        html_str, text_str, title = self.set_report_string()
592        # Allow 2 figures to append
593        if len(figs) == 1:
594            add_str = FEET_3
595        elif len(figs) == 2:
596            add_str = ELINE
597            add_str += FEET_2 % ("%s")
598            add_str += ELINE
599            add_str += FEET_3
600        elif len(figs) > 2:
601            add_str = ELINE
602            add_str += FEET_2 % ("%s")
603            add_str += ELINE
604            add_str += FEET_2 % ("%s")
605            add_str += ELINE
606            add_str += FEET_3
607        else:
608            add_str = ""
609
610        # final report html strings
611        report_str = html_str % ("%s") + add_str
612
613        # make plot image
614        images = self.set_plot_state(figs, canvases)
615        report_list = [report_str, text_str, images]
616        dialog = ReportDialog(report_list, None, wx.ID_ANY, "")
617        dialog.Show()
618
619    def _toXML_helper(self, thelist, element, newdoc):
620        """
621        Helper method to create xml file for saving state
622        """
623        for item in thelist:
624            sub_element = newdoc.createElement('parameter')
625            sub_element.setAttribute('name', str(item[1]))
626            sub_element.setAttribute('value', str(item[2]))
627            sub_element.setAttribute('selected_to_fit', str(item[0]))
628            sub_element.setAttribute('error_displayed', str(item[4][0]))
629            sub_element.setAttribute('error_value', str(item[4][1]))
630            sub_element.setAttribute('minimum_displayed', str(item[5][0]))
631            sub_element.setAttribute('minimum_value', str(item[5][1]))
632            sub_element.setAttribute('maximum_displayed', str(item[6][0]))
633            sub_element.setAttribute('maximum_value', str(item[6][1]))
634            sub_element.setAttribute('unit', str(item[7]))
635            element.appendChild(sub_element)
636
637    def toXML(self, file="fitting_state.fitv", doc=None,
638              entry_node=None, batch_fit_state=None):
639        """
640        Writes the state of the fit panel to file, as XML.
641
642        Compatible with standalone writing, or appending to an
643        already existing XML document. In that case, the XML document is
644        required. An optional entry node in the XML document may also be given.
645
646        :param file: file to write to
647        :param doc: XML document object [optional]
648        :param entry_node: XML node within the XML document at which we
649                           will append the data [optional]
650        """
651        from xml.dom.minidom import getDOMImplementation
652
653        # Check whether we have to write a standalone XML file
654        if doc is None:
655            impl = getDOMImplementation()
656            doc_type = impl.createDocumentType(FITTING_NODE_NAME, "1.0", "1.0")
657            newdoc = impl.createDocument(None, FITTING_NODE_NAME, doc_type)
658            top_element = newdoc.documentElement
659        else:
660            # We are appending to an existing document
661            newdoc = doc
662            try:
663                top_element = newdoc.createElement(FITTING_NODE_NAME)
664            except:
665                string = etree.tostring(doc, pretty_print=True)
666                newdoc = parseString(string)
667                top_element = newdoc.createElement(FITTING_NODE_NAME)
668            if entry_node is None:
669                newdoc.documentElement.appendChild(top_element)
670            else:
671                try:
672                    entry_node.appendChild(top_element)
673                except:
674                    node_name = entry_node.tag
675                    node_list = newdoc.getElementsByTagName(node_name)
676                    entry_node = node_list.item(0)
677                    entry_node.appendChild(top_element)
678
679        attr = newdoc.createAttribute("version")
680        attr.nodeValue = '1.0'
681        top_element.setAttributeNode(attr)
682
683        # File name
684        element = newdoc.createElement("filename")
685        if self.file is not None:
686            element.appendChild(newdoc.createTextNode(str(self.file)))
687        else:
688            element.appendChild(newdoc.createTextNode(str(file)))
689        top_element.appendChild(element)
690
691        element = newdoc.createElement("timestamp")
692        element.appendChild(newdoc.createTextNode(time.ctime(self.timestamp)))
693        attr = newdoc.createAttribute("epoch")
694        attr.nodeValue = str(self.timestamp)
695        element.setAttributeNode(attr)
696        top_element.appendChild(element)
697
698        # Inputs
699        inputs = newdoc.createElement("Attributes")
700        top_element.appendChild(inputs)
701
702        if self.data is not None and hasattr(self.data, "group_id"):
703            self.data_group_id = self.data.group_id
704        if self.data is not None and hasattr(self.data, "is_data"):
705            self.is_data = self.data.is_data
706        if self.data is not None:
707            self.data_name = self.data.name
708        if self.data is not None and hasattr(self.data, "id"):
709            self.data_id = self.data.id
710
711        for item in LIST_OF_DATA_ATTRIBUTES:
712            element = newdoc.createElement(item[0])
713            element.setAttribute(item[0], str(getattr(self, item[1])))
714            inputs.appendChild(element)
715
716        for item in LIST_OF_STATE_ATTRIBUTES:
717            element = newdoc.createElement(item[0])
718            element.setAttribute(item[0], str(getattr(self, item[1])))
719            inputs.appendChild(element)
720
721        # For self.values ={ disp_param_name: [vals,...],...}
722        # and for self.weights ={ disp_param_name: [weights,...],...}
723        for item in LIST_OF_MODEL_ATTRIBUTES:
724            element = newdoc.createElement(item[0])
725            value_list = getattr(self, item[1])
726            for key, value in value_list.iteritems():
727                sub_element = newdoc.createElement(key)
728                sub_element.setAttribute('name', str(key))
729                for val in value:
730                    sub_element.appendChild(newdoc.createTextNode(str(val)))
731
732                element.appendChild(sub_element)
733            inputs.appendChild(element)
734
735        # Create doc for the dictionary of self._disp_obj_dic
736        for tagname, varname, tagtype in DISPERSION_LIST:
737            element = newdoc.createElement(tagname)
738            value_list = getattr(self, varname)
739            for key, value in value_list.iteritems():
740                sub_element = newdoc.createElement(key)
741                sub_element.setAttribute('name', str(key))
742                sub_element.setAttribute('value', str(value))
743                element.appendChild(sub_element)
744            inputs.appendChild(element)
745
746        for item in LIST_OF_STATE_PARAMETERS:
747            element = newdoc.createElement(item[0])
748            self._toXML_helper(thelist=getattr(self, item[1]),
749                               element=element, newdoc=newdoc)
750            inputs.appendChild(element)
751
752        # Combined and Simultaneous Fit Parameters
753        if batch_fit_state is not None:
754            batch_combo = newdoc.createElement('simultaneous_fit')
755            top_element.appendChild(batch_combo)
756
757            # Simultaneous Fit Number For Linking Later
758            element = newdoc.createElement('sim_fit_number')
759            element.setAttribute('fit_number', str(batch_fit_state.fit_page_no))
760            batch_combo.appendChild(element)
761
762            # Save constraints
763            constraints = newdoc.createElement('constraints')
764            batch_combo.appendChild(constraints)
765            for constraint in batch_fit_state.constraints_list:
766                if constraint.model_cbox.GetValue() != "":
767                    # model_cbox, param_cbox, egal_txt, constraint, btRemove, sizer
768                    doc_cons = newdoc.createElement('constraint')
769                    doc_cons.setAttribute('model_cbox',
770                                          str(constraint.model_cbox.GetValue()))
771                    doc_cons.setAttribute('param_cbox',
772                                          str(constraint.param_cbox.GetValue()))
773                    doc_cons.setAttribute('egal_txt',
774                                          str(constraint.egal_txt.GetLabel()))
775                    doc_cons.setAttribute('constraint',
776                                          str(constraint.constraint.GetValue()))
777                    constraints.appendChild(doc_cons)
778
779            # Save all models
780            models = newdoc.createElement('model_list')
781            batch_combo.appendChild(models)
782            for model in batch_fit_state.model_list:
783                doc_model = newdoc.createElement('model_list_item')
784                doc_model.setAttribute('checked', str(model[0].GetValue()))
785                keys = model[1].keys()
786                doc_model.setAttribute('name', str(keys[0]))
787                values = model[1].get(keys[0])
788                doc_model.setAttribute('fit_number', str(model[2]))
789                doc_model.setAttribute('fit_page_source', str(model[3]))
790                doc_model.setAttribute('model_name', str(values.model.id))
791                models.appendChild(doc_model)
792
793            # Select All Checkbox
794            element = newdoc.createElement('select_all')
795            if batch_fit_state.select_all:
796                element.setAttribute('checked', 'True')
797            else:
798                element.setAttribute('checked', 'False')
799            batch_combo.appendChild(element)
800
801        # Save the file
802        if doc is None:
803            fd = open(file, 'w')
804            fd.write(newdoc.toprettyxml())
805            fd.close()
806            return None
807        else:
808            return newdoc
809
810    def _fromXML_helper(self, node, list):
811        """
812        Helper function to write state to xml
813        """
814        for item in node:
815            try:
816                name = item.get('name')
817            except:
818                name = None
819            try:
820                value = item.get('value')
821            except:
822                value = None
823            try:
824                selected_to_fit = (item.get('selected_to_fit') == "True")
825            except:
826                selected_to_fit = None
827            try:
828                error_displayed = (item.get('error_displayed') == "True")
829            except:
830                error_displayed = None
831            try:
832                error_value = item.get('error_value')
833            except:
834                error_value = None
835            try:
836                minimum_displayed = (item.get('minimum_displayed') == "True")
837            except:
838                minimum_displayed = None
839            try:
840                minimum_value = item.get('minimum_value')
841            except:
842                minimum_value = None
843            try:
844                maximum_displayed = (item.get('maximum_displayed') == "True")
845            except:
846                maximum_displayed = None
847            try:
848                maximum_value = item.get('maximum_value')
849            except:
850                maximum_value = None
851            try:
852                unit = item.get('unit')
853            except:
854                unit = None
855            list.append([selected_to_fit, name, value, "+/-",
856                         [error_displayed, error_value],
857                         [minimum_displayed, minimum_value],
858                         [maximum_displayed, maximum_value], unit])
859
860    def fromXML(self, file=None, node=None):
861        """
862        Load fitting state from a file
863
864        :param file: .fitv file
865        :param node: node of a XML document to read from
866        """
867        if file is not None:
868            msg = "PageState no longer supports non-CanSAS"
869            msg += " format for fitting files"
870            raise RuntimeError, msg
871
872        if node.get('version') and node.get('version') == '1.0':
873
874            # Get file name
875            entry = get_content('ns:filename', node)
876            if entry is not None:
877                self.file = entry.text.strip()
878
879            # Get time stamp
880            entry = get_content('ns:timestamp', node)
881            if entry is not None and entry.get('epoch'):
882                try:
883                    self.timestamp = float(entry.get('epoch'))
884                except:
885                    msg = "PageState.fromXML: Could not"
886                    msg += " read timestamp\n %s" % sys.exc_value
887                    logging.error(msg)
888
889            # Parse fitting attributes
890            entry = get_content('ns:Attributes', node)
891            for item in LIST_OF_DATA_ATTRIBUTES:
892                node = get_content('ns:%s' % item[0], entry)
893                setattr(self, item[0], parse_entry_helper(node, item))
894
895            if entry is not None:
896                for item in LIST_OF_STATE_ATTRIBUTES:
897                    node = get_content('ns:%s' % item[0], entry)
898                    setattr(self, item[0], parse_entry_helper(node, item))
899
900                for item in LIST_OF_STATE_PARAMETERS:
901                    node = get_content("ns:%s" % item[0], entry)
902                    self._fromXML_helper(node=node, list=getattr(self, item[1]))
903
904                # Recover _disp_obj_dict from xml file
905                self._disp_obj_dict = {}
906                for tagname, varname, tagtype in DISPERSION_LIST:
907                    node = get_content("ns:%s" % tagname, entry)
908                    for attr in node:
909                        parameter = str(attr.get('name'))
910                        value = attr.get('value')
911                        if value.startswith("<"):
912                            try:
913                                # <path.to.NamedDistribution object/instance...>
914                                cls_name = value[1:].split()[0].split('.')[-1]
915                                cls = getattr(sasmodels.weights, cls_name)
916                                value = cls.type
917                            except Exception:
918                                base = "unable to load distribution %r for %s"
919                                logging.error(base % (value, parameter))
920                                continue
921                        _disp_obj_dict = getattr(self, varname)
922                        _disp_obj_dict[parameter] = value
923
924                # get self.values and self.weights dic. if exists
925                for tagname, varname in LIST_OF_MODEL_ATTRIBUTES:
926                    node = get_content("ns:%s" % tagname, entry)
927                    dic = {}
928                    value_list = []
929                    for par in node:
930                        name = par.get('name')
931                        values = par.text.split()
932                        # Get lines only with numbers
933                        for line in values:
934                            try:
935                                val = float(line)
936                                value_list.append(val)
937                            except Exception:
938                                # pass if line is empty (it happens)
939                                msg = ("Error reading %r from %s %s\n"
940                                       % (line, tagname, name))
941                                logging.error(msg + traceback.format_exc())
942                        dic[name] = numpy.array(value_list)
943                    setattr(self, varname, dic)
944
945    def set_plot_state(self, figs, canvases):
946        """
947        Build image state that wx.html understand
948        by plotting, putting it into wx.FileSystem image object
949
950        """
951        images = []
952        # some imports
953        import wx
954
955        # Reset memory
956        self.imgRAM = None
957        wx.MemoryFSHandler()
958
959        # For no figures in the list, prepare empty plot
960        if figs == None or len(figs) == 0:
961            figs = [None]
962
963        # Loop over the list of figures
964        # use wx.MemoryFSHandler
965        self.imgRAM = wx.MemoryFSHandler()
966        for fig in figs:
967            if figs != None:
968                ind = figs.index(fig)
969                canvas = canvases[ind]
970
971            #store the image in wx.FileSystem Object
972            wx.FileSystem.AddHandler(wx.MemoryFSHandler())
973
974            # index of the fig
975            ind = figs.index(fig)
976
977            #AddFile, image can be retrieved with 'memory:filename'
978            self.imgRAM.AddFile('img_fit%s.png' % ind,
979                                canvas.bitmap, wx.BITMAP_TYPE_PNG)
980
981            #append figs
982            images.append(fig)
983
984        return images
985
986
987class Reader(CansasReader):
988    """
989    Class to load a .fitv fitting file
990    """
991    ## File type
992    type_name = "Fitting"
993
994    ## Wildcards
995    type = ["Fitting files (*.fitv)|*.fitv"
996            "SASView file (*.svs)|*.svs"]
997    ## List of allowed extensions
998    ext = ['.fitv', '.FITV', '.svs', 'SVS']
999
1000    def __init__(self, call_back=None, cansas=True):
1001        CansasReader.__init__(self)
1002        """
1003        Initialize the call-back method to be called
1004        after we load a file
1005
1006        :param call_back: call-back method
1007        :param cansas:  True = files will be written/read in CanSAS format
1008                        False = write CanSAS format
1009
1010        """
1011        ## Call back method to be executed after a file is read
1012        self.call_back = call_back
1013        ## CanSAS format flag
1014        self.cansas = cansas
1015        self.state = None
1016        # batch fitting params for saving
1017        self.batchfit_params = []
1018
1019    def get_state(self):
1020        return self.state
1021
1022    def read(self, path):
1023        """
1024        Load a new P(r) inversion state from file
1025
1026        :param path: file path
1027
1028        """
1029        if self.cansas == True:
1030            return self._read_cansas(path)
1031
1032    def _data2d_to_xml_doc(self, datainfo):
1033        """
1034        Create an XML document to contain the content of a Data2D
1035
1036        :param datainfo: Data2D object
1037
1038        """
1039        if not issubclass(datainfo.__class__, Data2D):
1040            raise RuntimeError, "The cansas writer expects a Data2D instance"
1041
1042        title = "cansas1d/%s" % self.version
1043        title += "http://svn.smallangles.net/svn/canSAS/1dwg/trunk/cansas1d.xsd"
1044        doc = xml.dom.minidom.Document()
1045        main_node = doc.createElement("SASroot")
1046        main_node.setAttribute("version", self.version)
1047        main_node.setAttribute("xmlns", "cansas1d/%s" % self.version)
1048        main_node.setAttribute("xmlns:xsi",
1049                               "http://www.w3.org/2001/XMLSchema-instance")
1050        main_node.setAttribute("xsi:schemaLocation", title)
1051
1052        doc.appendChild(main_node)
1053
1054        entry_node = doc.createElement("SASentry")
1055        main_node.appendChild(entry_node)
1056
1057        write_node(doc, entry_node, "Title", datainfo.title)
1058        if datainfo is not None:
1059            write_node(doc, entry_node, "data_class",
1060                       datainfo.__class__.__name__)
1061        for item in datainfo.run:
1062            runname = {}
1063            if datainfo.run_name.has_key(item) and \
1064                            len(str(datainfo.run_name[item])) > 1:
1065                runname = {'name': datainfo.run_name[item]}
1066            write_node(doc, entry_node, "Run", item, runname)
1067        # Data info
1068        new_node = doc.createElement("SASdata")
1069        entry_node.appendChild(new_node)
1070        for item in LIST_OF_DATA_2D_ATTR:
1071            element = doc.createElement(item[0])
1072            element.setAttribute(item[0], str(getattr(datainfo, item[1])))
1073            new_node.appendChild(element)
1074
1075        for item in LIST_OF_DATA_2D_VALUES:
1076            root_node = doc.createElement(item[0])
1077            new_node.appendChild(root_node)
1078            temp_list = None
1079            temp_list = getattr(datainfo, item[1])
1080
1081            if temp_list is None or len(temp_list) == 0:
1082                element = doc.createElement(item[0])
1083                element.appendChild(doc.createTextNode(str(temp_list)))
1084                root_node.appendChild(element)
1085            else:
1086                for value in temp_list:
1087                    element = doc.createElement(item[0])
1088                    element.setAttribute(item[0], str(value))
1089                    root_node.appendChild(element)
1090
1091        # Sample info
1092        sample = doc.createElement("SASsample")
1093        if datainfo.sample.name is not None:
1094            sample.setAttribute("name", str(datainfo.sample.name))
1095        entry_node.appendChild(sample)
1096        write_node(doc, sample, "ID", str(datainfo.sample.ID))
1097        write_node(doc, sample, "thickness", datainfo.sample.thickness,
1098                   {"unit": datainfo.sample.thickness_unit})
1099        write_node(doc, sample, "transmission", datainfo.sample.transmission)
1100        write_node(doc, sample, "temperature", datainfo.sample.temperature,
1101                   {"unit": datainfo.sample.temperature_unit})
1102
1103        for item in datainfo.sample.details:
1104            write_node(doc, sample, "details", item)
1105
1106        pos = doc.createElement("position")
1107        written = write_node(doc, pos, "x", datainfo.sample.position.x,
1108                             {"unit": datainfo.sample.position_unit})
1109        written = written | write_node(doc, pos, "y",
1110                                       datainfo.sample.position.y,
1111                                       {"unit": datainfo.sample.position_unit})
1112        written = written | write_node(doc, pos, "z",
1113                                       datainfo.sample.position.z,
1114                                       {"unit": datainfo.sample.position_unit})
1115        if written == True:
1116            sample.appendChild(pos)
1117
1118        ori = doc.createElement("orientation")
1119        written = write_node(doc, ori, "roll", datainfo.sample.orientation.x,
1120                             {"unit": datainfo.sample.orientation_unit})
1121        written = written | write_node(doc, ori, "pitch",
1122                                       datainfo.sample.orientation.y,
1123                                       {"unit": datainfo.sample.orientation_unit})
1124        written = written | write_node(doc, ori, "yaw",
1125                                       datainfo.sample.orientation.z,
1126                                       {"unit": datainfo.sample.orientation_unit})
1127        if written == True:
1128            sample.appendChild(ori)
1129
1130        # Instrument info
1131        instr = doc.createElement("SASinstrument")
1132        entry_node.appendChild(instr)
1133
1134        write_node(doc, instr, "name", datainfo.instrument)
1135
1136        #   Source
1137        source = doc.createElement("SASsource")
1138        if datainfo.source.name is not None:
1139            source.setAttribute("name", str(datainfo.source.name))
1140        instr.appendChild(source)
1141
1142        write_node(doc, source, "radiation", datainfo.source.radiation)
1143        write_node(doc, source, "beam_shape", datainfo.source.beam_shape)
1144        size = doc.createElement("beam_size")
1145        if datainfo.source.beam_size_name is not None:
1146            size.setAttribute("name", str(datainfo.source.beam_size_name))
1147        written = write_node(doc, size, "x", datainfo.source.beam_size.x,
1148                             {"unit": datainfo.source.beam_size_unit})
1149        written = written | write_node(doc, size, "y",
1150                                       datainfo.source.beam_size.y,
1151                                       {"unit": datainfo.source.beam_size_unit})
1152        written = written | write_node(doc, size, "z",
1153                                       datainfo.source.beam_size.z,
1154                                       {"unit": datainfo.source.beam_size_unit})
1155        if written == True:
1156            source.appendChild(size)
1157
1158        write_node(doc, source, "wavelength", datainfo.source.wavelength,
1159                   {"unit": datainfo.source.wavelength_unit})
1160        write_node(doc, source, "wavelength_min",
1161                   datainfo.source.wavelength_min,
1162                   {"unit": datainfo.source.wavelength_min_unit})
1163        write_node(doc, source, "wavelength_max",
1164                   datainfo.source.wavelength_max,
1165                   {"unit": datainfo.source.wavelength_max_unit})
1166        write_node(doc, source, "wavelength_spread",
1167                   datainfo.source.wavelength_spread,
1168                   {"unit": datainfo.source.wavelength_spread_unit})
1169
1170        #   Collimation
1171        for item in datainfo.collimation:
1172            coll = doc.createElement("SAScollimation")
1173            if item.name is not None:
1174                coll.setAttribute("name", str(item.name))
1175            instr.appendChild(coll)
1176
1177            write_node(doc, coll, "length", item.length,
1178                       {"unit": item.length_unit})
1179
1180            for apert in item.aperture:
1181                ap = doc.createElement("aperture")
1182                if apert.name is not None:
1183                    ap.setAttribute("name", str(apert.name))
1184                if apert.type is not None:
1185                    ap.setAttribute("type", str(apert.type))
1186                coll.appendChild(ap)
1187
1188                write_node(doc, ap, "distance", apert.distance,
1189                           {"unit": apert.distance_unit})
1190
1191                size = doc.createElement("size")
1192                if apert.size_name is not None:
1193                    size.setAttribute("name", str(apert.size_name))
1194                written = write_node(doc, size, "x", apert.size.x,
1195                                     {"unit": apert.size_unit})
1196                written = written | write_node(doc, size, "y", apert.size.y,
1197                                               {"unit": apert.size_unit})
1198                written = written | write_node(doc, size, "z", apert.size.z,
1199                                               {"unit": apert.size_unit})
1200                if written == True:
1201                    ap.appendChild(size)
1202
1203        #   Detectors
1204        for item in datainfo.detector:
1205            det = doc.createElement("SASdetector")
1206            written = write_node(doc, det, "name", item.name)
1207            written = written | write_node(doc, det, "SDD", item.distance,
1208                                           {"unit": item.distance_unit})
1209            written = written | write_node(doc, det, "slit_length",
1210                                           item.slit_length,
1211                                           {"unit": item.slit_length_unit})
1212            if written == True:
1213                instr.appendChild(det)
1214
1215            off = doc.createElement("offset")
1216            written = write_node(doc, off, "x", item.offset.x,
1217                                 {"unit": item.offset_unit})
1218            written = written | write_node(doc, off, "y", item.offset.y,
1219                                           {"unit": item.offset_unit})
1220            written = written | write_node(doc, off, "z", item.offset.z,
1221                                           {"unit": item.offset_unit})
1222            if written == True:
1223                det.appendChild(off)
1224
1225            center = doc.createElement("beam_center")
1226            written = write_node(doc, center, "x", item.beam_center.x,
1227                                 {"unit": item.beam_center_unit})
1228            written = written | write_node(doc, center, "y",
1229                                           item.beam_center.y,
1230                                           {"unit": item.beam_center_unit})
1231            written = written | write_node(doc, center, "z",
1232                                           item.beam_center.z,
1233                                           {"unit": item.beam_center_unit})
1234            if written == True:
1235                det.appendChild(center)
1236
1237            pix = doc.createElement("pixel_size")
1238            written = write_node(doc, pix, "x", item.pixel_size.x,
1239                                 {"unit": item.pixel_size_unit})
1240            written = written | write_node(doc, pix, "y", item.pixel_size.y,
1241                                           {"unit": item.pixel_size_unit})
1242            written = written | write_node(doc, pix, "z", item.pixel_size.z,
1243                                           {"unit": item.pixel_size_unit})
1244            if written == True:
1245                det.appendChild(pix)
1246
1247            ori = doc.createElement("orientation")
1248            written = write_node(doc, ori, "roll", item.orientation.x,
1249                                 {"unit": item.orientation_unit})
1250            written = written | write_node(doc, ori, "pitch",
1251                                           item.orientation.y,
1252                                           {"unit": item.orientation_unit})
1253            written = written | write_node(doc, ori, "yaw", item.orientation.z,
1254                                           {"unit": item.orientation_unit})
1255            if written == True:
1256                det.appendChild(ori)
1257
1258        # Processes info
1259        for item in datainfo.process:
1260            node = doc.createElement("SASprocess")
1261            entry_node.appendChild(node)
1262
1263            write_node(doc, node, "name", item.name)
1264            write_node(doc, node, "date", item.date)
1265            write_node(doc, node, "description", item.description)
1266            for term in item.term:
1267                value = term['value']
1268                del term['value']
1269                write_node(doc, node, "term", value, term)
1270            for note in item.notes:
1271                write_node(doc, node, "SASprocessnote", note)
1272        # Return the document, and the SASentry node associated with
1273        # the data we just wrote
1274        return doc, entry_node
1275
1276    def _parse_state(self, entry):
1277        """
1278        Read a fit result from an XML node
1279
1280        :param entry: XML node to read from
1281        :return: PageState object
1282        """
1283        # Create an empty state
1284        state = None
1285        # Locate the P(r) node
1286        try:
1287            nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
1288                                namespaces={'ns': CANSAS_NS})
1289            if nodes:
1290                # Create an empty state
1291                state = PageState()
1292                state.fromXML(node=nodes[0])
1293
1294        except:
1295            logging.info("XML document does not contain fitting information.\n"
1296                         + traceback.format_exc())
1297
1298        return state
1299
1300    def _parse_simfit_state(self, entry):
1301        """
1302        Parses the saved data for a simultaneous fit
1303        :param entry: XML object to read from
1304        :return: XML object for a simultaneous fit or None
1305        """
1306        nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
1307                            namespaces={'ns': CANSAS_NS})
1308        if nodes:
1309            simfitstate = nodes[0].xpath('ns:simultaneous_fit',
1310                                         namespaces={'ns': CANSAS_NS})
1311            if simfitstate:
1312                from simfitpage import SimFitPageState
1313                sim_fit_state = SimFitPageState()
1314                simfitstate_0 = simfitstate[0]
1315                all = simfitstate_0.xpath('ns:select_all',
1316                                        namespaces={'ns': CANSAS_NS})
1317                atts = all[0].attrib
1318                checked = atts.get('checked')
1319                sim_fit_state.select_all = bool(checked)
1320                model_list = simfitstate_0.xpath('ns:model_list',
1321                                               namespaces={'ns': CANSAS_NS})
1322                model_list_items = model_list[0].xpath('ns:model_list_item',
1323                                                   namespaces={'ns': CANSAS_NS})
1324                for model in model_list_items:
1325                    attrs = model.attrib
1326                    sim_fit_state.model_list.append(attrs)
1327
1328                constraints = simfitstate_0.xpath('ns:constraints',
1329                                                namespaces={'ns': CANSAS_NS})
1330                constraint_list = constraints[0].xpath('ns:constraint',
1331                                               namespaces={'ns': CANSAS_NS})
1332                for constraint in constraint_list:
1333                    attrs = constraint.attrib
1334                    sim_fit_state.constraints_list.append(attrs)
1335
1336                return sim_fit_state
1337            else:
1338                return None
1339
1340    def _parse_save_state_entry(self, dom):
1341        """
1342        Parse a SASentry
1343
1344        :param node: SASentry node
1345
1346        :return: Data1D/Data2D object
1347
1348        """
1349        node = dom.xpath('ns:data_class', namespaces={'ns': CANSAS_NS})
1350        if not node or node[0].text.lstrip().rstrip() != "Data2D":
1351            return_value, _ = self._parse_entry(dom)
1352            numpy.trim_zeros(return_value.x)
1353            numpy.trim_zeros(return_value.y)
1354            numpy.trim_zeros(return_value.dy)
1355            size_dx = return_value.dx.size
1356            size_dxl = return_value.dxl.size
1357            size_dxw = return_value.dxw.size
1358            if size_dxl == 0 and size_dxw == 0:
1359                return_value.dxl = None
1360                return_value.dxw = None
1361                numpy.trim_zeros(return_value.dx)
1362            elif size_dx == 0:
1363                return_value.dx = None
1364                size_dx = size_dxl
1365                numpy.trim_zeros(return_value.dxl)
1366                numpy.trim_zeros(return_value.dxw)
1367
1368            return return_value, _
1369
1370        #Parse 2D
1371        data_info = Data2D()
1372
1373        # Look up title
1374        self._store_content('ns:Title', dom, 'title', data_info)
1375
1376        # Look up run number
1377        nodes = dom.xpath('ns:Run', namespaces={'ns': CANSAS_NS})
1378        for item in nodes:
1379            if item.text is not None:
1380                value = item.text.strip()
1381                if len(value) > 0:
1382                    data_info.run.append(value)
1383                    if item.get('name') is not None:
1384                        data_info.run_name[value] = item.get('name')
1385
1386        # Look up instrument name
1387        self._store_content('ns:SASinstrument/ns:name', dom,
1388                            'instrument', data_info)
1389
1390        # Notes
1391        note_list = dom.xpath('ns:SASnote', namespaces={'ns': CANSAS_NS})
1392        for note in note_list:
1393            try:
1394                if note.text is not None:
1395                    note_value = note.text.strip()
1396                    if len(note_value) > 0:
1397                        data_info.notes.append(note_value)
1398            except Exception:
1399                err_mess = "cansas_reader.read: error processing entry notes\n"
1400                err_mess += %s" % sys.exc_value
1401                self.errors.append(err_mess)
1402                logging.error(err_mess)
1403
1404        # Sample info ###################
1405        entry = get_content('ns:SASsample', dom)
1406        if entry is not None:
1407            data_info.sample.name = entry.get('name')
1408
1409        self._store_content('ns:SASsample/ns:ID', dom, 'ID', data_info.sample)
1410        self._store_float('ns:SASsample/ns:thickness', dom, 'thickness',
1411                          data_info.sample)
1412        self._store_float('ns:SASsample/ns:transmission', dom, 'transmission',
1413                          data_info.sample)
1414        self._store_float('ns:SASsample/ns:temperature', dom, 'temperature',
1415                          data_info.sample)
1416
1417        nodes = dom.xpath('ns:SASsample/ns:details',
1418                          namespaces={'ns': CANSAS_NS})
1419        for item in nodes:
1420            try:
1421                if item.text is not None:
1422                    detail_value = item.text.strip()
1423                    if len(detail_value) > 0:
1424                        data_info.sample.details.append(detail_value)
1425            except Exception:
1426                err_mess = "cansas_reader.read: error processing entry notes\n"
1427                err_mess += %s" % sys.exc_value
1428                self.errors.append(err_mess)
1429                logging.error(err_mess)
1430
1431        # Position (as a vector)
1432        self._store_float('ns:SASsample/ns:position/ns:x', dom, 'position.x',
1433                          data_info.sample)
1434        self._store_float('ns:SASsample/ns:position/ns:y', dom, 'position.y',
1435                          data_info.sample)
1436        self._store_float('ns:SASsample/ns:position/ns:z', dom, 'position.z',
1437                          data_info.sample)
1438
1439        # Orientation (as a vector)
1440        self._store_float('ns:SASsample/ns:orientation/ns:roll',
1441                          dom, 'orientation.x', data_info.sample)
1442        self._store_float('ns:SASsample/ns:orientation/ns:pitch',
1443                          dom, 'orientation.y', data_info.sample)
1444        self._store_float('ns:SASsample/ns:orientation/ns:yaw',
1445                          dom, 'orientation.z', data_info.sample)
1446
1447        # Source info ###################
1448        entry = get_content('ns:SASinstrument/ns:SASsource', dom)
1449        if entry is not None:
1450            data_info.source.name = entry.get('name')
1451
1452        self._store_content('ns:SASinstrument/ns:SASsource/ns:radiation',
1453                            dom, 'radiation', data_info.source)
1454        self._store_content('ns:SASinstrument/ns:SASsource/ns:beam_shape',
1455                            dom, 'beam_shape', data_info.source)
1456        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength',
1457                          dom, 'wavelength', data_info.source)
1458        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_min',
1459                          dom, 'wavelength_min', data_info.source)
1460        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_max',
1461                          dom, 'wavelength_max', data_info.source)
1462        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_spread',
1463                          dom, 'wavelength_spread', data_info.source)
1464
1465        # Beam size (as a vector)
1466        entry = get_content('ns:SASinstrument/ns:SASsource/ns:beam_size', dom)
1467        if entry is not None:
1468            data_info.source.beam_size_name = entry.get('name')
1469
1470        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:x',
1471                          dom, 'beam_size.x', data_info.source)
1472        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:y',
1473                          dom, 'beam_size.y', data_info.source)
1474        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:z',
1475                          dom, 'beam_size.z', data_info.source)
1476
1477        # Collimation info ###################
1478        nodes = dom.xpath('ns:SASinstrument/ns:SAScollimation',
1479                          namespaces={'ns': CANSAS_NS})
1480        for item in nodes:
1481            collim = Collimation()
1482            if item.get('name') is not None:
1483                collim.name = item.get('name')
1484            self._store_float('ns:length', item, 'length', collim)
1485
1486            # Look for apertures
1487            apert_list = item.xpath('ns:aperture',
1488                                    namespaces={'ns': CANSAS_NS})
1489            for apert in apert_list:
1490                aperture = Aperture()
1491
1492                # Get the name and type of the aperture
1493                aperture.name = apert.get('name')
1494                aperture.type = apert.get('type')
1495
1496                self._store_float('ns:distance', apert, 'distance', aperture)
1497
1498                entry = get_content('ns:size', apert)
1499                if entry is not None:
1500                    aperture.size_name = entry.get('name')
1501
1502                self._store_float('ns:size/ns:x', apert, 'size.x', aperture)
1503                self._store_float('ns:size/ns:y', apert, 'size.y', aperture)
1504                self._store_float('ns:size/ns:z', apert, 'size.z', aperture)
1505
1506                collim.aperture.append(aperture)
1507
1508            data_info.collimation.append(collim)
1509
1510        # Detector info ######################
1511        nodes = dom.xpath('ns:SASinstrument/ns:SASdetector',
1512                          namespaces={'ns': CANSAS_NS})
1513        for item in nodes:
1514
1515            detector = Detector()
1516
1517            self._store_content('ns:name', item, 'name', detector)
1518            self._store_float('ns:SDD', item, 'distance', detector)
1519
1520            # Detector offset (as a vector)
1521            self._store_float('ns:offset/ns:x', item, 'offset.x', detector)
1522            self._store_float('ns:offset/ns:y', item, 'offset.y', detector)
1523            self._store_float('ns:offset/ns:z', item, 'offset.z', detector)
1524
1525            # Detector orientation (as a vector)
1526            self._store_float('ns:orientation/ns:roll', item,
1527                              'orientation.x', detector)
1528            self._store_float('ns:orientation/ns:pitch', item,
1529                              'orientation.y', detector)
1530            self._store_float('ns:orientation/ns:yaw', item,
1531                              'orientation.z', detector)
1532
1533            # Beam center (as a vector)
1534            self._store_float('ns:beam_center/ns:x', item,
1535                              'beam_center.x', detector)
1536            self._store_float('ns:beam_center/ns:y', item,
1537                              'beam_center.y', detector)
1538            self._store_float('ns:beam_center/ns:z', item,
1539                              'beam_center.z', detector)
1540
1541            # Pixel size (as a vector)
1542            self._store_float('ns:pixel_size/ns:x', item,
1543                              'pixel_size.x', detector)
1544            self._store_float('ns:pixel_size/ns:y', item,
1545                              'pixel_size.y', detector)
1546            self._store_float('ns:pixel_size/ns:z', item,
1547                              'pixel_size.z', detector)
1548
1549            self._store_float('ns:slit_length', item, 'slit_length', detector)
1550
1551            data_info.detector.append(detector)
1552
1553        # Processes info ######################
1554        nodes = dom.xpath('ns:SASprocess', namespaces={'ns': CANSAS_NS})
1555        for item in nodes:
1556            process = Process()
1557            self._store_content('ns:name', item, 'name', process)
1558            self._store_content('ns:date', item, 'date', process)
1559            self._store_content('ns:description', item, 'description', process)
1560
1561            term_list = item.xpath('ns:term', namespaces={'ns': CANSAS_NS})
1562            for term in term_list:
1563                try:
1564                    term_attr = {}
1565                    for attr in term.keys():
1566                        term_attr[attr] = term.get(attr).strip()
1567                    if term.text is not None:
1568                        term_attr['value'] = term.text.strip()
1569                        process.term.append(term_attr)
1570                except:
1571                    err_mess = "cansas_reader.read: error processing "
1572                    err_mess += "entry notes\n  %s" % sys.exc_value
1573                    self.errors.append(err_mess)
1574                    logging.error(err_mess)
1575
1576            note_list = item.xpath('ns:SASprocessnote',
1577                                   namespaces={'ns': CANSAS_NS})
1578            for note in note_list:
1579                if note.text is not None:
1580                    process.notes.append(note.text.strip())
1581
1582            data_info.process.append(process)
1583
1584        # Data info ######################
1585        nodes = dom.xpath('ns:SASdata', namespaces={'ns': CANSAS_NS})
1586        if len(nodes) > 1:
1587            raise RuntimeError, "CanSAS reader is not compatible with" + \
1588                                " multiple SASdata entries"
1589
1590        for entry in nodes:
1591            for item in LIST_OF_DATA_2D_ATTR:
1592                #get node
1593                node = get_content('ns:%s' % item[0], entry)
1594                setattr(data_info, item[1], parse_entry_helper(node, item))
1595
1596            for item in LIST_OF_DATA_2D_VALUES:
1597                field = get_content('ns:%s' % item[0], entry)
1598                value_list = []
1599                if field is not None:
1600                    value_list = \
1601                        [parse_entry_helper(node, item) for node in field]
1602                if len(value_list) < 2:
1603                    setattr(data_info, item[0], None)
1604                else:
1605                    setattr(data_info, item[0], numpy.array(value_list))
1606
1607        return data_info
1608
1609    def _read_cansas(self, path):
1610        """
1611        Load data and fitting information from a CanSAS XML file.
1612
1613        :param path: file path
1614        :return: Data1D object if a single SASentry was found,
1615                    or a list of Data1D objects if multiple entries were found,
1616                    or None of nothing was found
1617        :raise RuntimeError: when the file can't be opened
1618        :raise ValueError: when the length of the data vectors are inconsistent
1619        """
1620        output = []
1621        simfitstate = None
1622        basename = os.path.basename(path)
1623        root, extension = os.path.splitext(basename)
1624        ext = extension.lower()
1625        try:
1626            if os.path.isfile(path):
1627                if ext in self.ext or ext == '.xml':
1628                    tree = etree.parse(path, parser=etree.ETCompatXMLParser())
1629                    # Check the format version number
1630                    # Specifying the namespace will take care of the file
1631                    # format version
1632                    root = tree.getroot()
1633                    entry_list = root.xpath('ns:SASentry',
1634                                            namespaces={'ns': CANSAS_NS})
1635                    name_map = {}
1636                    for entry in entry_list:
1637                        try:
1638                            sas_entry, _ = self._parse_save_state_entry(entry)
1639                        except:
1640                            raise
1641                        fitstate = self._parse_state(entry)
1642                        simfitstate = self._parse_simfit_state(entry)
1643
1644                        # state could be None when .svs file is loaded
1645                        # in this case, skip appending to output
1646                        if fitstate is not None:
1647                            sas_entry.meta_data['fitstate'] = fitstate
1648                            sas_entry.filename = fitstate.file
1649                            output.append(sas_entry)
1650
1651            else:
1652                self.call_back(format=ext)
1653                raise RuntimeError, "%s is not a file" % path
1654
1655            # Return output consistent with the loader's api
1656            if len(output) == 0:
1657                self.call_back(state=None, datainfo=None, format=ext)
1658                return None
1659            else:
1660                for ind in range(len(output)):
1661                    # Call back to post the new state
1662                    state = output[ind].meta_data['fitstate']
1663                    t = time.localtime(state.timestamp)
1664                    time_str = time.strftime("%b %d %H:%M", t)
1665                    # Check that no time stamp is already appended
1666                    max_char = state.file.find("[")
1667                    if max_char < 0:
1668                        max_char = len(state.file)
1669                    original_fname = state.file[0:max_char]
1670                    state.file = original_fname + ' [' + time_str + ']'
1671
1672                    if state is not None and state.is_data is not None:
1673                        output[ind].is_data = state.is_data
1674
1675                    output[ind].filename = state.file
1676                    state.data = output[ind]
1677                    state.data.name = output[ind].filename  # state.data_name
1678                    state.data.id = state.data_id
1679                    if state.is_data is not None:
1680                        state.data.is_data = state.is_data
1681                    if output[ind].run_name is not None\
1682                        and len(output[ind].run_name) != 0:
1683                        if isinstance(output[ind].run_name, dict):
1684                            name = output[ind].run_name.keys()[0]
1685                        else:
1686                            name = output[ind].run_name
1687                    else:
1688                        name = original_fname
1689                    state.data.group_id = name
1690                    # store state in fitting
1691                    self.call_back(state=state,
1692                                   datainfo=output[ind], format=ext)
1693                    self.state = state
1694                if simfitstate is not None:
1695                    self.call_back(state=simfitstate)
1696
1697                return output
1698        except:
1699            self.call_back(format=ext)
1700            raise
1701
1702    def write(self, filename, datainfo=None, fitstate=None):
1703        """
1704        Write the content of a Data1D as a CanSAS XML file only for standalone
1705
1706        :param filename: name of the file to write
1707        :param datainfo: Data1D object
1708        :param fitstate: PageState object
1709
1710        """
1711        # Sanity check
1712        if self.cansas:
1713            # Add fitting information to the XML document
1714            doc = self.write_toXML(datainfo, fitstate)
1715            # Write the XML document
1716        else:
1717            doc = fitstate.toXML(file=filename)
1718
1719        # Save the document no matter the type
1720        fd = open(filename, 'w')
1721        fd.write(doc.toprettyxml())
1722        fd.close()
1723
1724    def write_toXML(self, datainfo=None, state=None, batchfit=None):
1725        """
1726        Write toXML, a helper for write(),
1727        could be used by guimanager._on_save()
1728
1729        : return: xml doc
1730        """
1731
1732        self.batchfit_params = batchfit
1733        if state.data is None or not state.data.is_data:
1734            return None
1735        # make sure title and data run are filled.
1736        if state.data.title is None or state.data.title == '':
1737            state.data.title = state.data.name
1738        if state.data.run_name is None or state.data.run_name == {}:
1739            state.data.run = [str(state.data.name)]
1740            state.data.run_name[0] = state.data.name
1741
1742        if issubclass(state.data.__class__,
1743                      sas.sascalc.dataloader.data_info.Data1D):
1744            data = state.data
1745            doc, sasentry = self._to_xml_doc(data)
1746        else:
1747            data = state.data
1748            doc, sasentry = self._data2d_to_xml_doc(data)
1749
1750        if state is not None:
1751            doc = state.toXML(doc=doc, file=data.filename, entry_node=sasentry,
1752                              batch_fit_state=self.batchfit_params)
1753
1754        return doc
Note: See TracBrowser for help on using the repository browser.