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

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

#189: Can now open the simultaneous and constrained fit panel through a save state, but the constraint loading is still broken.

  • Property mode set to 100644
File size: 72.8 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        if file is not None:
864            msg = "PageState no longer supports non-CanSAS"
865            msg += " format for fitting files"
866            raise RuntimeError, msg
867
868        if node.get('version') and node.get('version') == '1.0':
869
870            # Get file name
871            entry = get_content('ns:filename', node)
872            if entry is not None:
873                self.file = entry.text.strip()
874
875            # Get time stamp
876            entry = get_content('ns:timestamp', node)
877            if entry is not None and entry.get('epoch'):
878                try:
879                    self.timestamp = float(entry.get('epoch'))
880                except:
881                    msg = "PageState.fromXML: Could not"
882                    msg += " read timestamp\n %s" % sys.exc_value
883                    logging.error(msg)
884
885            # Parse fitting attributes
886            entry = get_content('ns:Attributes', node)
887            for item in LIST_OF_DATA_ATTRIBUTES:
888                node = get_content('ns:%s' % item[0], entry)
889                setattr(self, item[0], parse_entry_helper(node, item))
890
891            if entry is not None:
892                for item in LIST_OF_STATE_ATTRIBUTES:
893                    node = get_content('ns:%s' % item[0], entry)
894                    setattr(self, item[0], parse_entry_helper(node, item))
895
896                for item in LIST_OF_STATE_PARAMETERS:
897                    node = get_content("ns:%s" % item[0], entry)
898                    self._fromXML_helper(node=node, list=getattr(self, item[1]))
899
900                # Recover _disp_obj_dict from xml file
901                self._disp_obj_dict = {}
902                for tagname, varname, tagtype in DISPERSION_LIST:
903                    node = get_content("ns:%s" % tagname, entry)
904                    for attr in node:
905                        parameter = str(attr.get('name'))
906                        value = attr.get('value')
907                        if value.startswith("<"):
908                            try:
909                                # <path.to.NamedDistribution object/instance...>
910                                cls_name = value[1:].split()[0].split('.')[-1]
911                                cls = getattr(sasmodels.weights, cls_name)
912                                value = cls.type
913                            except Exception:
914                                logging.error("unable to load distribution %r for %s"
915                                              % (value, parameter))
916                                continue
917                        _disp_obj_dict = getattr(self, varname)
918                        _disp_obj_dict[parameter] = value
919
920                # get self.values and self.weights dic. if exists
921                for tagname, varname in LIST_OF_MODEL_ATTRIBUTES:
922                    node = get_content("ns:%s" % tagname, entry)
923                    dic = {}
924                    value_list = []
925                    for par in node:
926                        name = par.get('name')
927                        values = par.text.split()
928                        # Get lines only with numbers
929                        for line in values:
930                            try:
931                                val = float(line)
932                                value_list.append(val)
933                            except Exception:
934                                # pass if line is empty (it happens)
935                                msg = ("Error reading %r from %s %s\n"
936                                       % (line, tagname, name))
937                                logging.error(msg + traceback.format_exc())
938                        dic[name] = numpy.array(value_list)
939                    setattr(self, varname, dic)
940
941    def set_plot_state(self, figs, canvases):
942        """
943        Build image state that wx.html understand
944        by plotting, putting it into wx.FileSystem image object
945
946        """
947        images = []
948        # some imports
949        import wx
950
951        # Reset memory
952        self.imgRAM = None
953        wx.MemoryFSHandler()
954
955        # For no figures in the list, prepare empty plot
956        if figs == None or len(figs) == 0:
957            figs = [None]
958
959        # Loop over the list of figures
960        # use wx.MemoryFSHandler
961        self.imgRAM = wx.MemoryFSHandler()
962        for fig in figs:
963            if figs != None:
964                ind = figs.index(fig)
965                canvas = canvases[ind]
966
967            #store the image in wx.FileSystem Object
968            wx.FileSystem.AddHandler(wx.MemoryFSHandler())
969
970            # index of the fig
971            ind = figs.index(fig)
972
973            #AddFile, image can be retrieved with 'memory:filename'
974            self.imgRAM.AddFile('img_fit%s.png' % ind,
975                                canvas.bitmap, wx.BITMAP_TYPE_PNG)
976
977            #append figs
978            images.append(fig)
979
980        return images
981
982
983class Reader(CansasReader):
984    """
985    Class to load a .fitv fitting file
986    """
987    ## File type
988    type_name = "Fitting"
989
990    ## Wildcards
991    type = ["Fitting files (*.fitv)|*.fitv"
992            "SASView file (*.svs)|*.svs"]
993    ## List of allowed extensions
994    ext = ['.fitv', '.FITV', '.svs', 'SVS']
995
996    def __init__(self, call_back=None, cansas=True):
997        CansasReader.__init__(self)
998        """
999        Initialize the call-back method to be called
1000        after we load a file
1001
1002        :param call_back: call-back method
1003        :param cansas:  True = files will be written/read in CanSAS format
1004                        False = write CanSAS format
1005
1006        """
1007        ## Call back method to be executed after a file is read
1008        self.call_back = call_back
1009        ## CanSAS format flag
1010        self.cansas = cansas
1011        self.state = None
1012        # batch fitting params for saving
1013        self.batchfit_params = []
1014
1015    def get_state(self):
1016        return self.state
1017
1018    def read(self, path):
1019        """
1020        Load a new P(r) inversion state from file
1021
1022        :param path: file path
1023
1024        """
1025        if self.cansas == True:
1026            return self._read_cansas(path)
1027
1028    def _data2d_to_xml_doc(self, datainfo):
1029        """
1030        Create an XML document to contain the content of a Data2D
1031
1032        :param datainfo: Data2D object
1033
1034        """
1035        if not issubclass(datainfo.__class__, Data2D):
1036            raise RuntimeError, "The cansas writer expects a Data2D instance"
1037
1038        doc = xml.dom.minidom.Document()
1039        main_node = doc.createElement("SASroot")
1040        main_node.setAttribute("version", self.version)
1041        main_node.setAttribute("xmlns", "cansas1d/%s" % self.version)
1042        main_node.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
1043        main_node.setAttribute("xsi:schemaLocation",
1044                               "cansas1d/%s http://svn.smallangles.net/svn/canSAS/1dwg/trunk/cansas1d.xsd" % self.version)
1045
1046        doc.appendChild(main_node)
1047
1048        entry_node = doc.createElement("SASentry")
1049        main_node.appendChild(entry_node)
1050
1051        write_node(doc, entry_node, "Title", datainfo.title)
1052        if datainfo is not None:
1053            write_node(doc, entry_node, "data_class", datainfo.__class__.__name__)
1054        for item in datainfo.run:
1055            runname = {}
1056            if datainfo.run_name.has_key(item) and len(str(datainfo.run_name[item])) > 1:
1057                runname = {'name': datainfo.run_name[item]}
1058            write_node(doc, entry_node, "Run", item, runname)
1059        # Data info
1060        new_node = doc.createElement("SASdata")
1061        entry_node.appendChild(new_node)
1062        for item in LIST_OF_DATA_2D_ATTR:
1063            element = doc.createElement(item[0])
1064            element.setAttribute(item[0], str(getattr(datainfo, item[1])))
1065            new_node.appendChild(element)
1066
1067        for item in LIST_OF_DATA_2D_VALUES:
1068            root_node = doc.createElement(item[0])
1069            new_node.appendChild(root_node)
1070            temp_list = None
1071            temp_list = getattr(datainfo, item[1])
1072
1073            if temp_list is None or len(temp_list) == 0:
1074                element = doc.createElement(item[0])
1075                element.appendChild(doc.createTextNode(str(temp_list)))
1076                root_node.appendChild(element)
1077            else:
1078                for value in temp_list:
1079                    element = doc.createElement(item[0])
1080                    element.setAttribute(item[0], str(value))
1081                    root_node.appendChild(element)
1082
1083        # Sample info
1084        sample = doc.createElement("SASsample")
1085        if datainfo.sample.name is not None:
1086            sample.setAttribute("name", str(datainfo.sample.name))
1087        entry_node.appendChild(sample)
1088        write_node(doc, sample, "ID", str(datainfo.sample.ID))
1089        write_node(doc, sample, "thickness", datainfo.sample.thickness,
1090                   {"unit": datainfo.sample.thickness_unit})
1091        write_node(doc, sample, "transmission", datainfo.sample.transmission)
1092        write_node(doc, sample, "temperature", datainfo.sample.temperature,
1093                   {"unit": datainfo.sample.temperature_unit})
1094
1095        for item in datainfo.sample.details:
1096            write_node(doc, sample, "details", item)
1097
1098        pos = doc.createElement("position")
1099        written = write_node(doc, pos, "x", datainfo.sample.position.x,
1100                             {"unit": datainfo.sample.position_unit})
1101        written = written | write_node(doc, pos, "y",
1102                                       datainfo.sample.position.y,
1103                                       {"unit": datainfo.sample.position_unit})
1104        written = written | write_node(doc, pos, "z",
1105                                       datainfo.sample.position.z,
1106                                       {"unit": datainfo.sample.position_unit})
1107        if written == True:
1108            sample.appendChild(pos)
1109
1110        ori = doc.createElement("orientation")
1111        written = write_node(doc, ori, "roll", datainfo.sample.orientation.x,
1112                             {"unit": datainfo.sample.orientation_unit})
1113        written = written | write_node(doc, ori, "pitch",
1114                                       datainfo.sample.orientation.y,
1115                                       {"unit": datainfo.sample.orientation_unit})
1116        written = written | write_node(doc, ori, "yaw",
1117                                       datainfo.sample.orientation.z,
1118                                       {"unit": datainfo.sample.orientation_unit})
1119        if written == True:
1120            sample.appendChild(ori)
1121
1122        # Instrument info
1123        instr = doc.createElement("SASinstrument")
1124        entry_node.appendChild(instr)
1125
1126        write_node(doc, instr, "name", datainfo.instrument)
1127
1128        #   Source
1129        source = doc.createElement("SASsource")
1130        if datainfo.source.name is not None:
1131            source.setAttribute("name", str(datainfo.source.name))
1132        instr.appendChild(source)
1133
1134        write_node(doc, source, "radiation", datainfo.source.radiation)
1135        write_node(doc, source, "beam_shape", datainfo.source.beam_shape)
1136        size = doc.createElement("beam_size")
1137        if datainfo.source.beam_size_name is not None:
1138            size.setAttribute("name", str(datainfo.source.beam_size_name))
1139        written = write_node(doc, size, "x", datainfo.source.beam_size.x,
1140                             {"unit": datainfo.source.beam_size_unit})
1141        written = written | write_node(doc, size, "y",
1142                                       datainfo.source.beam_size.y,
1143                                       {"unit": datainfo.source.beam_size_unit})
1144        written = written | write_node(doc, size, "z",
1145                                       datainfo.source.beam_size.z,
1146                                       {"unit": datainfo.source.beam_size_unit})
1147        if written == True:
1148            source.appendChild(size)
1149
1150        write_node(doc, source, "wavelength", datainfo.source.wavelength,
1151                   {"unit": datainfo.source.wavelength_unit})
1152        write_node(doc, source, "wavelength_min",
1153                   datainfo.source.wavelength_min,
1154                   {"unit": datainfo.source.wavelength_min_unit})
1155        write_node(doc, source, "wavelength_max",
1156                   datainfo.source.wavelength_max,
1157                   {"unit": datainfo.source.wavelength_max_unit})
1158        write_node(doc, source, "wavelength_spread",
1159                   datainfo.source.wavelength_spread,
1160                   {"unit": datainfo.source.wavelength_spread_unit})
1161
1162        #   Collimation
1163        for item in datainfo.collimation:
1164            coll = doc.createElement("SAScollimation")
1165            if item.name is not None:
1166                coll.setAttribute("name", str(item.name))
1167            instr.appendChild(coll)
1168
1169            write_node(doc, coll, "length", item.length,
1170                       {"unit": item.length_unit})
1171
1172            for apert in item.aperture:
1173                ap = doc.createElement("aperture")
1174                if apert.name is not None:
1175                    ap.setAttribute("name", str(apert.name))
1176                if apert.type is not None:
1177                    ap.setAttribute("type", str(apert.type))
1178                coll.appendChild(ap)
1179
1180                write_node(doc, ap, "distance", apert.distance,
1181                           {"unit": apert.distance_unit})
1182
1183                size = doc.createElement("size")
1184                if apert.size_name is not None:
1185                    size.setAttribute("name", str(apert.size_name))
1186                written = write_node(doc, size, "x", apert.size.x,
1187                                     {"unit": apert.size_unit})
1188                written = written | write_node(doc, size, "y", apert.size.y,
1189                                               {"unit": apert.size_unit})
1190                written = written | write_node(doc, size, "z", apert.size.z,
1191                                               {"unit": apert.size_unit})
1192                if written == True:
1193                    ap.appendChild(size)
1194
1195        #   Detectors
1196        for item in datainfo.detector:
1197            det = doc.createElement("SASdetector")
1198            written = write_node(doc, det, "name", item.name)
1199            written = written | write_node(doc, det, "SDD", item.distance,
1200                                           {"unit": item.distance_unit})
1201            written = written | write_node(doc, det, "slit_length",
1202                                           item.slit_length,
1203                                           {"unit": item.slit_length_unit})
1204            if written == True:
1205                instr.appendChild(det)
1206
1207            off = doc.createElement("offset")
1208            written = write_node(doc, off, "x", item.offset.x,
1209                                 {"unit": item.offset_unit})
1210            written = written | write_node(doc, off, "y", item.offset.y,
1211                                           {"unit": item.offset_unit})
1212            written = written | write_node(doc, off, "z", item.offset.z,
1213                                           {"unit": item.offset_unit})
1214            if written == True:
1215                det.appendChild(off)
1216
1217            center = doc.createElement("beam_center")
1218            written = write_node(doc, center, "x", item.beam_center.x,
1219                                 {"unit": item.beam_center_unit})
1220            written = written | write_node(doc, center, "y",
1221                                           item.beam_center.y,
1222                                           {"unit": item.beam_center_unit})
1223            written = written | write_node(doc, center, "z",
1224                                           item.beam_center.z,
1225                                           {"unit": item.beam_center_unit})
1226            if written == True:
1227                det.appendChild(center)
1228
1229            pix = doc.createElement("pixel_size")
1230            written = write_node(doc, pix, "x", item.pixel_size.x,
1231                                 {"unit": item.pixel_size_unit})
1232            written = written | write_node(doc, pix, "y", item.pixel_size.y,
1233                                           {"unit": item.pixel_size_unit})
1234            written = written | write_node(doc, pix, "z", item.pixel_size.z,
1235                                           {"unit": item.pixel_size_unit})
1236            if written == True:
1237                det.appendChild(pix)
1238
1239            ori = doc.createElement("orientation")
1240            written = write_node(doc, ori, "roll", item.orientation.x,
1241                                 {"unit": item.orientation_unit})
1242            written = written | write_node(doc, ori, "pitch",
1243                                           item.orientation.y,
1244                                           {"unit": item.orientation_unit})
1245            written = written | write_node(doc, ori, "yaw", item.orientation.z,
1246                                           {"unit": item.orientation_unit})
1247            if written == True:
1248                det.appendChild(ori)
1249
1250        # Processes info
1251        for item in datainfo.process:
1252            node = doc.createElement("SASprocess")
1253            entry_node.appendChild(node)
1254
1255            write_node(doc, node, "name", item.name)
1256            write_node(doc, node, "date", item.date)
1257            write_node(doc, node, "description", item.description)
1258            for term in item.term:
1259                value = term['value']
1260                del term['value']
1261                write_node(doc, node, "term", value, term)
1262            for note in item.notes:
1263                write_node(doc, node, "SASprocessnote", note)
1264        # Return the document, and the SASentry node associated with
1265        # the data we just wrote
1266        return doc, entry_node
1267
1268    def _parse_state(self, entry):
1269        """
1270        Read a fit result from an XML node
1271
1272        :param entry: XML node to read from
1273        :return: PageState object
1274        """
1275        # Create an empty state
1276        state = None
1277        # Locate the P(r) node
1278        try:
1279            nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
1280                                namespaces={'ns': CANSAS_NS})
1281            if nodes:
1282                # Create an empty state
1283                state = PageState()
1284                state.fromXML(node=nodes[0])
1285
1286        except:
1287            logging.info("XML document does not contain fitting information.\n"
1288                         + traceback.format_exc())
1289
1290        return state
1291
1292    def _parse_simfit_state(self, entry):
1293        """
1294        Parses the saved data for a simultaneous fit
1295        :param entry: XML object to read from
1296        :return: XML object for a simultaneous fit or None
1297        """
1298        nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
1299                            namespaces={'ns': CANSAS_NS})
1300        if nodes:
1301            simfitstate = nodes[0].xpath('ns:simultaneous_fit',
1302                                         namespaces={'ns': CANSAS_NS})
1303            if simfitstate:
1304                from simfitpage import SimFitPageState
1305                sim_fit_state = SimFitPageState()
1306                simfitstate_0 = simfitstate[0]
1307                all = simfitstate_0.xpath('ns:select_all',
1308                                        namespaces={'ns': CANSAS_NS})
1309                atts = all[0].attrib
1310                checked = atts.get('checked')
1311                sim_fit_state.select_all = bool(checked)
1312                model_list = simfitstate_0.xpath('ns:model_list',
1313                                               namespaces={'ns': CANSAS_NS})
1314                model_list_items = model_list[0].xpath('ns:model_list_item',
1315                                                       namespaces={'ns': CANSAS_NS})
1316                for model in model_list_items:
1317                    attrs = model.attrib
1318                    sim_fit_state.model_list.append(attrs)
1319                constraints = simfitstate_0.xpath('ns:constraints',
1320                                                namespaces={'ns': CANSAS_NS})
1321                constraint_list = constraints[0].xpath('ns:constraint',
1322                                                       namespaces={'ns': CANSAS_NS})
1323                for constraint in constraint_list:
1324                    attrs = constraint.attrib
1325                    sim_fit_state.constraints_list.append(attrs)
1326
1327                return sim_fit_state
1328            else:
1329                return None
1330
1331    def _parse_save_state_entry(self, dom):
1332        """
1333        Parse a SASentry
1334
1335        :param node: SASentry node
1336
1337        :return: Data1D/Data2D object
1338
1339        """
1340        node = dom.xpath('ns:data_class', namespaces={'ns': CANSAS_NS})
1341        if not node or node[0].text.lstrip().rstrip() != "Data2D":
1342            return_value, _ = self._parse_entry(dom)
1343            numpy.trim_zeros(return_value.x)
1344            numpy.trim_zeros(return_value.y)
1345            numpy.trim_zeros(return_value.dy)
1346            size_dx = return_value.dx.size
1347            size_dxl = return_value.dxl.size
1348            size_dxw = return_value.dxw.size
1349            if size_dxl == 0 and size_dxw == 0:
1350                return_value.dxl = None
1351                return_value.dxw = None
1352                numpy.trim_zeros(return_value.dx)
1353            elif size_dx == 0:
1354                return_value.dx = None
1355                size_dx = size_dxl
1356                numpy.trim_zeros(return_value.dxl)
1357                numpy.trim_zeros(return_value.dxw)
1358
1359            return return_value, _
1360
1361        #Parse 2D
1362        data_info = Data2D()
1363
1364        # Look up title
1365        self._store_content('ns:Title', dom, 'title', data_info)
1366
1367        # Look up run number
1368        nodes = dom.xpath('ns:Run', namespaces={'ns': CANSAS_NS})
1369        for item in nodes:
1370            if item.text is not None:
1371                value = item.text.strip()
1372                if len(value) > 0:
1373                    data_info.run.append(value)
1374                    if item.get('name') is not None:
1375                        data_info.run_name[value] = item.get('name')
1376
1377        # Look up instrument name
1378        self._store_content('ns:SASinstrument/ns:name', dom,
1379                            'instrument', data_info)
1380
1381        # Notes
1382        note_list = dom.xpath('ns:SASnote', namespaces={'ns': CANSAS_NS})
1383        for note in note_list:
1384            try:
1385                if note.text is not None:
1386                    note_value = note.text.strip()
1387                    if len(note_value) > 0:
1388                        data_info.notes.append(note_value)
1389            except Exception:
1390                err_mess = "cansas_reader.read: error processing entry notes\n  %s" % sys.exc_value
1391                self.errors.append(err_mess)
1392                logging.error(err_mess)
1393
1394        # Sample info ###################
1395        entry = get_content('ns:SASsample', dom)
1396        if entry is not None:
1397            data_info.sample.name = entry.get('name')
1398
1399        self._store_content('ns:SASsample/ns:ID', dom, 'ID', data_info.sample)
1400        self._store_float('ns:SASsample/ns:thickness', dom, 'thickness', data_info.sample)
1401        self._store_float('ns:SASsample/ns:transmission', dom, 'transmission', data_info.sample)
1402        self._store_float('ns:SASsample/ns:temperature', dom, 'temperature', data_info.sample)
1403
1404        nodes = dom.xpath('ns:SASsample/ns:details', namespaces={'ns': CANSAS_NS})
1405        for item in nodes:
1406            try:
1407                if item.text is not None:
1408                    detail_value = item.text.strip()
1409                    if len(detail_value) > 0:
1410                        data_info.sample.details.append(detail_value)
1411            except Exception:
1412                err_mess = "cansas_reader.read: error processing sample details\n  %s" % sys.exc_value
1413                self.errors.append(err_mess)
1414                logging.error(err_mess)
1415
1416        # Position (as a vector)
1417        self._store_float('ns:SASsample/ns:position/ns:x', dom, 'position.x', data_info.sample)
1418        self._store_float('ns:SASsample/ns:position/ns:y', dom, 'position.y', data_info.sample)
1419        self._store_float('ns:SASsample/ns:position/ns:z', dom, 'position.z', data_info.sample)
1420
1421        # Orientation (as a vector)
1422        self._store_float('ns:SASsample/ns:orientation/ns:roll',
1423                          dom, 'orientation.x', data_info.sample)
1424        self._store_float('ns:SASsample/ns:orientation/ns:pitch',
1425                          dom, 'orientation.y', data_info.sample)
1426        self._store_float('ns:SASsample/ns:orientation/ns:yaw',
1427                          dom, 'orientation.z', data_info.sample)
1428
1429        # Source info ###################
1430        entry = get_content('ns:SASinstrument/ns:SASsource', dom)
1431        if entry is not None:
1432            data_info.source.name = entry.get('name')
1433
1434        self._store_content('ns:SASinstrument/ns:SASsource/ns:radiation',
1435                            dom, 'radiation', data_info.source)
1436        self._store_content('ns:SASinstrument/ns:SASsource/ns:beam_shape',
1437                            dom, 'beam_shape', data_info.source)
1438        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength',
1439                          dom, 'wavelength', data_info.source)
1440        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_min',
1441                          dom, 'wavelength_min', data_info.source)
1442        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_max',
1443                          dom, 'wavelength_max', data_info.source)
1444        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_spread',
1445                          dom, 'wavelength_spread', data_info.source)
1446
1447        # Beam size (as a vector)
1448        entry = get_content('ns:SASinstrument/ns:SASsource/ns:beam_size', dom)
1449        if entry is not None:
1450            data_info.source.beam_size_name = entry.get('name')
1451
1452        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:x',
1453                          dom, 'beam_size.x', data_info.source)
1454        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:y',
1455                          dom, 'beam_size.y', data_info.source)
1456        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:z',
1457                          dom, 'beam_size.z', data_info.source)
1458
1459        # Collimation info ###################
1460        nodes = dom.xpath('ns:SASinstrument/ns:SAScollimation',
1461                          namespaces={'ns': CANSAS_NS})
1462        for item in nodes:
1463            collim = Collimation()
1464            if item.get('name') is not None:
1465                collim.name = item.get('name')
1466            self._store_float('ns:length', item, 'length', collim)
1467
1468            # Look for apertures
1469            apert_list = item.xpath('ns:aperture',
1470                                    namespaces={'ns': CANSAS_NS})
1471            for apert in apert_list:
1472                aperture = Aperture()
1473
1474                # Get the name and type of the aperture
1475                aperture.name = apert.get('name')
1476                aperture.type = apert.get('type')
1477
1478                self._store_float('ns:distance', apert, 'distance', aperture)
1479
1480                entry = get_content('ns:size', apert)
1481                if entry is not None:
1482                    aperture.size_name = entry.get('name')
1483
1484                self._store_float('ns:size/ns:x', apert, 'size.x', aperture)
1485                self._store_float('ns:size/ns:y', apert, 'size.y', aperture)
1486                self._store_float('ns:size/ns:z', apert, 'size.z', aperture)
1487
1488                collim.aperture.append(aperture)
1489
1490            data_info.collimation.append(collim)
1491
1492        # Detector info ######################
1493        nodes = dom.xpath('ns:SASinstrument/ns:SASdetector',
1494                          namespaces={'ns': CANSAS_NS})
1495        for item in nodes:
1496
1497            detector = Detector()
1498
1499            self._store_content('ns:name', item, 'name', detector)
1500            self._store_float('ns:SDD', item, 'distance', detector)
1501
1502            # Detector offset (as a vector)
1503            self._store_float('ns:offset/ns:x', item, 'offset.x', detector)
1504            self._store_float('ns:offset/ns:y', item, 'offset.y', detector)
1505            self._store_float('ns:offset/ns:z', item, 'offset.z', detector)
1506
1507            # Detector orientation (as a vector)
1508            self._store_float('ns:orientation/ns:roll', item,
1509                              'orientation.x', detector)
1510            self._store_float('ns:orientation/ns:pitch', item,
1511                              'orientation.y', detector)
1512            self._store_float('ns:orientation/ns:yaw', item,
1513                              'orientation.z', detector)
1514
1515            # Beam center (as a vector)
1516            self._store_float('ns:beam_center/ns:x', item,
1517                              'beam_center.x', detector)
1518            self._store_float('ns:beam_center/ns:y', item,
1519                              'beam_center.y', detector)
1520            self._store_float('ns:beam_center/ns:z', item,
1521                              'beam_center.z', detector)
1522
1523            # Pixel size (as a vector)
1524            self._store_float('ns:pixel_size/ns:x', item,
1525                              'pixel_size.x', detector)
1526            self._store_float('ns:pixel_size/ns:y', item,
1527                              'pixel_size.y', detector)
1528            self._store_float('ns:pixel_size/ns:z', item,
1529                              'pixel_size.z', detector)
1530
1531            self._store_float('ns:slit_length', item, 'slit_length', detector)
1532
1533            data_info.detector.append(detector)
1534
1535        # Processes info ######################
1536        nodes = dom.xpath('ns:SASprocess', namespaces={'ns': CANSAS_NS})
1537        for item in nodes:
1538            process = Process()
1539            self._store_content('ns:name', item, 'name', process)
1540            self._store_content('ns:date', item, 'date', process)
1541            self._store_content('ns:description', item, 'description', process)
1542
1543            term_list = item.xpath('ns:term', namespaces={'ns': CANSAS_NS})
1544            for term in term_list:
1545                try:
1546                    term_attr = {}
1547                    for attr in term.keys():
1548                        term_attr[attr] = term.get(attr).strip()
1549                    if term.text is not None:
1550                        term_attr['value'] = term.text.strip()
1551                        process.term.append(term_attr)
1552                except:
1553                    err_mess = "cansas_reader.read: error processing process term\n  %s" % sys.exc_value
1554                    self.errors.append(err_mess)
1555                    logging.error(err_mess)
1556
1557            note_list = item.xpath('ns:SASprocessnote',
1558                                   namespaces={'ns': CANSAS_NS})
1559            for note in note_list:
1560                if note.text is not None:
1561                    process.notes.append(note.text.strip())
1562
1563            data_info.process.append(process)
1564
1565        # Data info ######################
1566        nodes = dom.xpath('ns:SASdata', namespaces={'ns': CANSAS_NS})
1567        if len(nodes) > 1:
1568            raise RuntimeError, "CanSAS reader is not compatible with" + \
1569                                " multiple SASdata entries"
1570
1571        for entry in nodes:
1572            for item in LIST_OF_DATA_2D_ATTR:
1573                #get node
1574                node = get_content('ns:%s' % item[0], entry)
1575                setattr(data_info, item[1], parse_entry_helper(node, item))
1576
1577            for item in LIST_OF_DATA_2D_VALUES:
1578                field = get_content('ns:%s' % item[0], entry)
1579                value_list = []
1580                if field is not None:
1581                    value_list = [parse_entry_helper(node, item) for node in field]
1582                if len(value_list) < 2:
1583                    setattr(data_info, item[0], None)
1584                else:
1585                    setattr(data_info, item[0], numpy.array(value_list))
1586
1587        return data_info
1588
1589    def _read_cansas(self, path):
1590        """
1591        Load data and fitting information from a CanSAS XML file.
1592
1593        :param path: file path
1594        :return: Data1D object if a single SASentry was found,
1595                    or a list of Data1D objects if multiple entries were found,
1596                    or None of nothing was found
1597        :raise RuntimeError: when the file can't be opened
1598        :raise ValueError: when the length of the data vectors are inconsistent
1599        """
1600        output = []
1601        simfitstate = None
1602        basename = os.path.basename(path)
1603        root, extension = os.path.splitext(basename)
1604        ext = extension.lower()
1605        try:
1606            if os.path.isfile(path):
1607                if ext in self.ext or ext == '.xml':
1608                    tree = etree.parse(path, parser=etree.ETCompatXMLParser())
1609                    # Check the format version number
1610                    # Specifying the namespace will take care of the file
1611                    # format version
1612                    root = tree.getroot()
1613                    entry_list = root.xpath('ns:SASentry',
1614                                            namespaces={'ns': CANSAS_NS})
1615                    for entry in entry_list:
1616                        try:
1617                            sas_entry, _ = self._parse_save_state_entry(entry)
1618                        except:
1619                            raise
1620                        fitstate = self._parse_state(entry)
1621                        simfitstate = self._parse_simfit_state(entry)
1622
1623                        # state could be None when .svs file is loaded
1624                        # in this case, skip appending to output
1625                        if fitstate is not None:
1626                            sas_entry.meta_data['fitstate'] = fitstate
1627                            sas_entry.filename = fitstate.file
1628                            output.append(sas_entry)
1629
1630            else:
1631                self.call_back(format=ext)
1632                raise RuntimeError, "%s is not a file" % path
1633
1634            # Return output consistent with the loader's api
1635            if len(output) == 0:
1636                self.call_back(state=None, datainfo=None, format=ext)
1637                return None
1638            else:
1639                for ind in range(len(output)):
1640                    # Call back to post the new state
1641                    state = output[ind].meta_data['fitstate']
1642                    t = time.localtime(state.timestamp)
1643                    time_str = time.strftime("%b %d %H:%M", t)
1644                    # Check that no time stamp is already appended
1645                    max_char = state.file.find("[")
1646                    if max_char < 0:
1647                        max_char = len(state.file)
1648                    original_fname = state.file[0:max_char]
1649                    state.file = original_fname + ' [' + time_str + ']'
1650
1651                    if state is not None and state.is_data is not None:
1652                        output[ind].is_data = state.is_data
1653
1654                    output[ind].filename = state.file
1655                    state.data = output[ind]
1656                    state.data.name = output[ind].filename  # state.data_name
1657                    state.data.id = state.data_id
1658                    if state.is_data is not None:
1659                        state.data.is_data = state.is_data
1660                    if output[ind].run_name is not None\
1661                        and len(output[ind].run_name) != 0:
1662                        if isinstance(output[ind].run_name, dict):
1663                            name = output[ind].run_name.keys()[0]
1664                        else:
1665                            name = output[ind].run_name
1666                    else:
1667                        name = original_fname
1668                    state.data.group_id = name
1669                    # store state in fitting
1670                    self.call_back(state=state,
1671                                   datainfo=output[ind], format=ext)
1672                    self.state = state
1673                if simfitstate is not None:
1674                    self.call_back(state=simfitstate)
1675
1676                return output
1677        except:
1678            self.call_back(format=ext)
1679            raise
1680
1681    def write(self, filename, datainfo=None, fitstate=None):
1682        """
1683        Write the content of a Data1D as a CanSAS XML file only for standalone
1684
1685        :param filename: name of the file to write
1686        :param datainfo: Data1D object
1687        :param fitstate: PageState object
1688
1689        """
1690        # Sanity check
1691        if self.cansas:
1692            # Add fitting information to the XML document
1693            doc = self.write_toXML(datainfo, fitstate)
1694            # Write the XML document
1695        else:
1696            doc = fitstate.toXML(file=filename)
1697
1698        # Save the document no matter the type
1699        fd = open(filename, 'w')
1700        fd.write(doc.toprettyxml())
1701        fd.close()
1702
1703    def write_toXML(self, datainfo=None, state=None, batchfit=None):
1704        """
1705        Write toXML, a helper for write(),
1706        could be used by guimanager._on_save()
1707
1708        : return: xml doc
1709        """
1710
1711        self.batchfit_params = batchfit
1712        if state.data is None or not state.data.is_data:
1713            return None
1714        # make sure title and data run are filled.
1715        if state.data.title is None or state.data.title == '':
1716            state.data.title = state.data.name
1717        if state.data.run_name is None or state.data.run_name == {}:
1718            state.data.run = [str(state.data.name)]
1719            state.data.run_name[0] = state.data.name
1720
1721        if issubclass(state.data.__class__,
1722                      sas.sascalc.dataloader.data_info.Data1D):
1723            data = state.data
1724            doc, sasentry = self._to_xml_doc(data)
1725        else:
1726            data = state.data
1727            doc, sasentry = self._data2d_to_xml_doc(data)
1728
1729        if state is not None:
1730            doc = state.toXML(doc=doc, file=data.filename, entry_node=sasentry,
1731                              batch_fit_state=self.batchfit_params)
1732
1733        return doc
Note: See TracBrowser for help on using the repository browser.