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

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

#795: A few more models have been properly converted and tested to work. Still to do: 4 models not loading, custom models, and fix an error thrown when negative values are loaded.

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