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

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

Plugin models are loaded properly (if they can be found) and fixed a bug in parameter setting routine.

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