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

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

#795: Orientation parameters returned by sasmodels are now handled properly.

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