source: sasview/src/sas/sascalc/fit/pagestate.py @ ffe5345

ticket-1009
Last change on this file since ffe5345 was ffe5345, checked in by gonzalezm, 5 years ago

Added number of points and sigmas in PD distribution to report

  • Property mode set to 100644
File size: 55.8 KB
Line 
1"""
2Class 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 re
16import os
17import sys
18import copy
19import logging
20import numpy as np
21import traceback
22
23import xml.dom.minidom
24from xml.dom.minidom import parseString
25from xml.dom.minidom import getDOMImplementation
26from lxml import etree
27
28from sasmodels import convert
29import sasmodels.weights
30
31from sas.sasview import __version__ as SASVIEW_VERSION
32
33import sas.sascalc.dataloader
34from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
35from sas.sascalc.dataloader.readers.cansas_reader import get_content, write_node
36from sas.sascalc.dataloader.data_info import Data2D, Collimation, Detector
37from sas.sascalc.dataloader.data_info import Process, Aperture
38
39logger = logging.getLogger(__name__)
40
41# Information to read/write state as xml
42FITTING_NODE_NAME = 'fitting_plug_in'
43CANSAS_NS = {"ns": "cansas1d/1.0"}
44
45CUSTOM_MODEL = 'Plugin Models'
46CUSTOM_MODEL_OLD = 'Customized Models'
47
48LIST_OF_DATA_ATTRIBUTES = [["is_data", "is_data", "bool"],
49                           ["group_id", "data_group_id", "string"],
50                           ["data_name", "data_name", "string"],
51                           ["data_id", "data_id", "string"],
52                           ["name", "name", "string"],
53                           ["data_name", "data_name", "string"]]
54LIST_OF_STATE_ATTRIBUTES = [["qmin", "qmin", "float"],
55                            ["qmax", "qmax", "float"],
56                            ["npts", "npts", "float"],
57                            ["categorycombobox", "categorycombobox", "string"],
58                            ["formfactorcombobox", "formfactorcombobox",
59                             "string"],
60                            ["structurecombobox", "structurecombobox",
61                             "string"],
62                            ["multi_factor", "multi_factor", "float"],
63                            ["magnetic_on", "magnetic_on", "bool"],
64                            ["enable_smearer", "enable_smearer", "bool"],
65                            ["disable_smearer", "disable_smearer", "bool"],
66                            ["pinhole_smearer", "pinhole_smearer", "bool"],
67                            ["slit_smearer", "slit_smearer", "bool"],
68                            ["enable_disp", "enable_disp", "bool"],
69                            ["disable_disp", "disable_disp", "bool"],
70                            ["dI_noweight", "dI_noweight", "bool"],
71                            ["dI_didata", "dI_didata", "bool"],
72                            ["dI_sqrdata", "dI_sqrdata", "bool"],
73                            ["dI_idata", "dI_idata", "bool"],
74                            ["enable2D", "enable2D", "bool"],
75                            ["cb1", "cb1", "bool"],
76                            ["tcChi", "tcChi", "float"],
77                            ["dq_l", "dq_l", "float"],
78                            ["dq_r", "dq_r", "float"],
79                            ["dx_percent", "dx_percent", "float"],
80                            ["dxl", "dxl", "float"],
81                            ["dxw", "dxw", "float"]]
82
83LIST_OF_MODEL_ATTRIBUTES = [["values", "values"],
84                            ["weights", "weights"]]
85
86DISPERSION_LIST = [["disp_obj_dict", "disp_obj_dict", "string"]]
87
88LIST_OF_STATE_PARAMETERS = [["parameters", "parameters"],
89                            ["str_parameters", "str_parameters"],
90                            ["orientation_parameters", "orientation_params"],
91                            ["dispersity_parameters",
92                             "orientation_params_disp"],
93                            ["fixed_param", "fixed_param"],
94                            ["fittable_param", "fittable_param"]]
95LIST_OF_DATA_2D_ATTR = [["xmin", "xmin", "float"],
96                        ["xmax", "xmax", "float"],
97                        ["ymin", "ymin", "float"],
98                        ["ymax", "ymax", "float"],
99                        ["_xaxis", "_xaxis", "string"],
100                        ["_xunit", "_xunit", "string"],
101                        ["_yaxis", "_yaxis", "string"],
102                        ["_yunit", "_yunit", "string"],
103                        ["_zaxis", "_zaxis", "string"],
104                        ["_zunit", "_zunit", "string"]]
105LIST_OF_DATA_2D_VALUES = [["qx_data", "qx_data", "float"],
106                          ["qy_data", "qy_data", "float"],
107                          ["dqx_data", "dqx_data", "float"],
108                          ["dqy_data", "dqy_data", "float"],
109                          ["data", "data", "float"],
110                          ["q_data", "q_data", "float"],
111                          ["err_data", "err_data", "float"],
112                          ["mask", "mask", "bool"]]
113
114
115def parse_entry_helper(node, item):
116    """
117    Create a numpy list from value extrated from the node
118
119    :param node: node from each the value is stored
120    :param item: list name of three strings.the two first are name of data
121        attribute and the third one is the type of the value of that
122        attribute. type can be string, float, bool, etc.
123
124    : return: numpy array
125    """
126    if node is not None:
127        if item[2] == "string":
128            return str(node.get(item[0]).strip())
129        elif item[2] == "bool":
130            try:
131                return node.get(item[0]).strip() == "True"
132            except Exception:
133                return None
134        else:
135            try:
136                return float(node.get(item[0]))
137            except Exception:
138                return None
139
140
141class PageState(object):
142    """
143    Contains information to reconstruct a page of the fitpanel.
144    """
145    def __init__(self, model=None, data=None):
146        """
147        Initialize the current state
148
149        :param model: a selected model within a page
150        :param data:
151
152        """
153        self.file = None
154        # Time of state creation
155        self.timestamp = time.time()
156        # Data member to store the dispersion object created
157        self.disp_obj_dict = {}
158        # ------------------------
159        # Data used for fitting
160        self.data = data
161        # model data
162        self.theory_data = None
163        # Is 2D
164        self.is_2D = False
165        self.images = None
166
167        # save additional information on data that dataloader.reader
168        # does not read
169        self.is_data = None
170        self.data_name = ""
171
172        if self.data is not None:
173            self.data_name = self.data.name
174        self.data_id = None
175        if self.data is not None and hasattr(self.data, "id"):
176            self.data_id = self.data.id
177        self.data_group_id = None
178        if self.data is not None and hasattr(self.data, "group_id"):
179            self.data_group_id = self.data.group_id
180
181        # reset True change the state of existing button
182        self.reset = False
183
184        # flag to allow data2D plot
185        self.enable2D = False
186        # model on which the fit would be performed
187        self.model = model
188        self.m_name = None
189        # list of process done to model
190        self.process = []
191        # fit page manager
192        self.manager = None
193        # Event_owner is the owner of model event
194        self.event_owner = None
195        # page name
196        self.page_name = ""
197        # Contains link between model, its parameters, and panel organization
198        self.parameters = []
199        # String parameter list that can not be fitted
200        self.str_parameters = []
201        # Contains list of parameters that cannot be fitted and reference to
202        # panel objects
203        self.fixed_param = []
204        # Contains list of parameters with dispersity and reference to
205        # panel objects
206        self.fittable_param = []
207        # orientation parameters
208        self.orientation_params = []
209        # orientation parameters for gaussian dispersity
210        self.orientation_params_disp = []
211        self.dq_l = None
212        self.dq_r = None
213        self.dx_percent = None
214        self.dx_old = False
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        # save the state of the context menu
229        self.saved_states = {}
230        # save selection of combobox
231        self.formfactorcombobox = None
232        self.categorycombobox = None
233        self.structurecombobox = None
234
235        # radio box to select type of model
236        # self.shape_rbutton = False
237        # self.shape_indep_rbutton = False
238        # self.struct_rbutton = False
239        # self.plugin_rbutton = False
240        # the indice of the current selection
241        self.disp_box = 0
242        # Qrange
243        # Q range
244        self.qmin = 0.001
245        self.qmax = 0.1
246        # reset data range
247        self.qmax_x = None
248        self.qmin_x = None
249
250        self.npts = None
251        self.name = ""
252        self.multi_factor = None
253        self.magnetic_on = False
254        # enable smearering state
255        self.enable_smearer = False
256        self.disable_smearer = True
257        self.pinhole_smearer = False
258        self.slit_smearer = False
259        # weighting options
260        self.dI_noweight = False
261        self.dI_didata = True
262        self.dI_sqrdata = False
263        self.dI_idata = False
264        # disperity selection
265        self.enable_disp = False
266        self.disable_disp = True
267
268        # state of selected all check button
269        self.cb1 = False
270        # store value of chisqr
271        self.tcChi = None
272        self.version = (1, 0, 0)
273
274    def clone(self):
275        """
276        Create a new copy of the current object
277        """
278        model = None
279        if self.model is not None:
280            model = self.model.clone()
281            model.name = self.model.name
282        obj = PageState(model=model)
283        obj.file = copy.deepcopy(self.file)
284        obj.data = copy.deepcopy(self.data)
285        if self.data is not None:
286            self.data_name = self.data.name
287        obj.data_name = self.data_name
288        obj.is_data = self.is_data
289
290        obj.categorycombobox = self.categorycombobox
291        obj.formfactorcombobox = self.formfactorcombobox
292        obj.structurecombobox = self.structurecombobox
293
294        # obj.shape_rbutton = self.shape_rbutton
295        # obj.shape_indep_rbutton = self.shape_indep_rbutton
296        # obj.struct_rbutton = self.struct_rbutton
297        # obj.plugin_rbutton = self.plugin_rbutton
298
299        obj.manager = self.manager
300        obj.event_owner = self.event_owner
301        obj.disp_list = copy.deepcopy(self.disp_list)
302
303        obj.enable2D = copy.deepcopy(self.enable2D)
304        obj.parameters = copy.deepcopy(self.parameters)
305        obj.str_parameters = copy.deepcopy(self.str_parameters)
306        obj.fixed_param = copy.deepcopy(self.fixed_param)
307        obj.fittable_param = copy.deepcopy(self.fittable_param)
308        obj.orientation_params = copy.deepcopy(self.orientation_params)
309        obj.orientation_params_disp = \
310            copy.deepcopy(self.orientation_params_disp)
311        obj.enable_disp = copy.deepcopy(self.enable_disp)
312        obj.disable_disp = copy.deepcopy(self.disable_disp)
313        obj.tcChi = self.tcChi
314
315        if len(self.disp_obj_dict) > 0:
316            for k, v in self.disp_obj_dict.items():
317                obj.disp_obj_dict[k] = v
318        if len(self.disp_cb_dict) > 0:
319            for k, v in self.disp_cb_dict.items():
320                obj.disp_cb_dict[k] = v
321        if len(self.values) > 0:
322            for k, v in self.values.items():
323                obj.values[k] = v
324        if len(self.weights) > 0:
325            for k, v in self.weights.items():
326                obj.weights[k] = v
327        obj.enable_smearer = copy.deepcopy(self.enable_smearer)
328        obj.disable_smearer = copy.deepcopy(self.disable_smearer)
329        obj.pinhole_smearer = copy.deepcopy(self.pinhole_smearer)
330        obj.slit_smearer = copy.deepcopy(self.slit_smearer)
331        obj.dI_noweight = copy.deepcopy(self.dI_noweight)
332        obj.dI_didata = copy.deepcopy(self.dI_didata)
333        obj.dI_sqrdata = copy.deepcopy(self.dI_sqrdata)
334        obj.dI_idata = copy.deepcopy(self.dI_idata)
335        obj.dq_l = copy.deepcopy(self.dq_l)
336        obj.dq_r = copy.deepcopy(self.dq_r)
337        obj.dx_percent = copy.deepcopy(self.dx_percent)
338        obj.dx_old = copy.deepcopy(self.dx_old)
339        obj.dxl = copy.deepcopy(self.dxl)
340        obj.dxw = copy.deepcopy(self.dxw)
341        obj.disp_box = copy.deepcopy(self.disp_box)
342        obj.qmin = copy.deepcopy(self.qmin)
343        obj.qmax = copy.deepcopy(self.qmax)
344        obj.multi_factor = self.multi_factor
345        obj.magnetic_on = self.magnetic_on
346        obj.npts = copy.deepcopy(self.npts)
347        obj.cb1 = copy.deepcopy(self.cb1)
348        obj.version = copy.deepcopy(self.version)
349
350        for name, state in self.saved_states.items():
351            copy_name = copy.deepcopy(name)
352            copy_state = state.clone()
353            obj.saved_states[copy_name] = copy_state
354        return obj
355
356    def _old_first_model(self):
357        """
358        Handle save states from 4.0.1 and before where the first item in the
359        selection boxes of category, formfactor and structurefactor were not
360        saved.
361        :return: None
362        """
363        if self.categorycombobox == CUSTOM_MODEL_OLD:
364            self.categorycombobox = CUSTOM_MODEL
365        if self.formfactorcombobox == '':
366            FIRST_FORM = {
367                'Shapes' : 'BCCrystalModel',
368                'Uncategorized' : 'LineModel',
369                'StructureFactor' : 'HardsphereStructure',
370                'Ellipsoid' : 'core_shell_ellipsoid',
371                'Lamellae' : 'lamellar',
372                'Paracrystal' : 'bcc_paracrystal',
373                'Parallelepiped' : 'core_shell_parallelepiped',
374                'Shape Independent' : 'be_polyelectrolyte',
375                'Sphere' : 'adsorbed_layer',
376                'Structure Factor' : 'hardsphere',
377                CUSTOM_MODEL : ''
378            }
379            if self.categorycombobox == '':
380                if len(self.parameters) == 3:
381                    self.categorycombobox = "Shape-Independent"
382                    self.formfactorcombobox = 'PowerLawAbsModel'
383                elif len(self.parameters) == 9:
384                    self.categorycombobox = 'Cylinder'
385                    self.formfactorcombobox = 'barbell'
386                else:
387                    msg = "Save state does not have enough information to load"
388                    msg += " the all of the data."
389                    logger.warning(msg=msg)
390            else:
391                self.formfactorcombobox = FIRST_FORM[self.categorycombobox]
392
393    @staticmethod
394    def param_remap_to_sasmodels_convert(params, is_string=False):
395        """
396        Remaps the parameters for sasmodels conversion
397
398        :param params: list of parameters (likely self.parameters)
399        :return: remapped dictionary of parameters
400        """
401        p = dict()
402        for fittable, name, value, _, uncert, lower, upper, units in params:
403            if not value:
404                value = np.nan
405            if not uncert or uncert[1] == '' or uncert[1] == 'None':
406                uncert[0] = False
407                uncert[1] = np.nan
408            if not upper or upper[1] == '' or upper[1] == 'None':
409                upper[0] = False
410                upper[1] = np.nan
411            if not lower or lower[1] == '' or lower[1] == 'None':
412                lower[0] = False
413                lower[1] = np.nan
414            if is_string:
415                p[name] = str(value)
416            else:
417                p[name] = float(value)
418            p[name + ".fittable"] = bool(fittable)
419            p[name + ".std"] = float(uncert[1])
420            p[name + ".upper"] = float(upper[1])
421            p[name + ".lower"] = float(lower[1])
422            p[name + ".units"] = units
423        return p
424
425    @staticmethod
426    def param_remap_from_sasmodels_convert(params):
427        """
428        Converts {name : value} map back to [] param list
429        :param params: parameter map returned from sasmodels
430        :return: None
431        """
432        p_map = []
433        for name, info in params.items():
434            if ".fittable" in name or ".std" in name or ".upper" in name or \
435                            ".lower" in name or ".units" in name:
436                pass
437            else:
438                fittable = params.get(name + ".fittable", True)
439                std = params.get(name + ".std", '0.0')
440                upper = params.get(name + ".upper", 'inf')
441                lower = params.get(name + ".lower", '-inf')
442                units = params.get(name + ".units")
443                if std is not None and std is not np.nan:
444                    std = [True, str(std)]
445                else:
446                    std = [False, '']
447                if lower is not None and lower is not np.nan:
448                    lower = [True, str(lower)]
449                else:
450                    lower = [True, '-inf']
451                if upper is not None and upper is not np.nan:
452                    upper = [True, str(upper)]
453                else:
454                    upper = [True, 'inf']
455                param_list = [bool(fittable), str(name), str(info),
456                              "+/-", std, lower, upper, str(units)]
457                p_map.append(param_list)
458        return p_map
459
460    def _convert_to_sasmodels(self):
461        """
462        Convert parameters to a form usable by sasmodels converter
463
464        :return: None
465        """
466        # Create conversion dictionary to send to sasmodels
467        self._old_first_model()
468        p = self.param_remap_to_sasmodels_convert(self.parameters)
469        structurefactor, params = convert.convert_model(self.structurecombobox,
470                                                        p, False, self.version)
471        formfactor, params = convert.convert_model(self.formfactorcombobox,
472                                                   params, False, self.version)
473        if len(self.str_parameters) > 0:
474            str_pars = self.param_remap_to_sasmodels_convert(
475                self.str_parameters, True)
476            formfactor, str_params = convert.convert_model(
477                self.formfactorcombobox, str_pars, False, self.version)
478            for key, value in str_params.items():
479                params[key] = value
480
481        if self.formfactorcombobox == 'SphericalSLDModel':
482            self.multi_factor += 1
483        self.formfactorcombobox = formfactor
484        self.structurecombobox = structurefactor
485        self.parameters = []
486        self.parameters = self.param_remap_from_sasmodels_convert(params)
487
488    def _repr_helper(self, list, rep):
489        """
490        Helper method to print a state
491        """
492        for item in list:
493            if str(item[1][-6:]) == '.width':
494                par = str(item[1][:-6])
495                pd_type = self.model.dispersion[par]['type']
496                npts = self.model.dispersion[par]['npts']
497                nsigmas = self.model.dispersion[par]['nsigmas']
498                dist_str = str(item[1]) 
499                dist_str += '(' + str(pd_type) 
500                dist_str += '; points = ' + str(npts) 
501                dist_str += '; sigmas = ' + str(nsigmas) + ')' 
502                rep += "parameter name: %s \n" % dist_str
503            else:
504                rep += "parameter name: %s \n" % str(item[1])
505            rep += "value: %s\n" % str(item[2])
506            rep += "selected: %s\n" % str(item[0])
507            rep += "error displayed : %s \n" % str(item[4][0])
508            rep += "error value:%s \n" % str(item[4][1])
509            rep += "minimum displayed : %s \n" % str(item[5][0])
510            rep += "minimum value : %s \n" % str(item[5][1])
511            rep += "maximum displayed : %s \n" % str(item[6][0])
512            rep += "maximum value : %s \n" % str(item[6][1])
513            rep += "parameter unit: %s\n\n" % str(item[7])
514        return rep
515
516    def __repr__(self):
517        """
518        output string for printing
519        """
520        rep = "\nState name: %s\n" % self.file
521        t = time.localtime(self.timestamp)
522        time_str = time.strftime("%b %d %Y %H:%M:%S ", t)
523
524        rep += "State created: %s\n" % time_str
525        rep += "State form factor combobox selection: %s\n" % \
526               self.formfactorcombobox
527        rep += "State structure factor combobox selection: %s\n" % \
528               self.structurecombobox
529        rep += "is data : %s\n" % self.is_data
530        rep += "data's name : %s\n" % self.data_name
531        rep += "data's id : %s\n" % self.data_id
532        if self.model is not None:
533            m_name = self.model.__class__.__name__
534            if m_name == 'Model':
535                m_name = self.m_name
536            rep += "model name : %s\n" % m_name
537        else:
538            rep += "model name : None\n"
539        rep += "multi_factor : %s\n" % str(self.multi_factor)
540        rep += "magnetic_on : %s\n" % str(self.magnetic_on)
541        rep += "model type (Category) selected: %s\n" % self.categorycombobox
542        rep += "data : %s\n" % str(self.data)
543        rep += "Plotting Range: min: %s, max: %s, steps: %s\n" % \
544               (str(self.qmin), str(self.qmax), str(self.npts))
545        rep += "Dispersion selection : %s\n" % str(self.disp_box)
546        rep += "Smearing enable : %s\n" % str(self.enable_smearer)
547        rep += "Smearing disable : %s\n" % str(self.disable_smearer)
548        rep += "Pinhole smearer enable : %s\n" % str(self.pinhole_smearer)
549        rep += "Slit smearer enable : %s\n" % str(self.slit_smearer)
550        rep += "Dispersity enable : %s\n" % str(self.enable_disp)
551        rep += "Dispersity disable : %s\n" % str(self.disable_disp)
552        rep += "Slit smearer enable: %s\n" % str(self.slit_smearer)
553
554        rep += "dI_noweight : %s\n" % str(self.dI_noweight)
555        rep += "dI_didata : %s\n" % str(self.dI_didata)
556        rep += "dI_sqrdata : %s\n" % str(self.dI_sqrdata)
557        rep += "dI_idata : %s\n" % str(self.dI_idata)
558
559        rep += "2D enable : %s\n" % str(self.enable2D)
560        rep += "All parameters checkbox selected: %s\n" % self.cb1
561        rep += "Value of Chisqr : %s\n" % str(self.tcChi)
562        rep += "dq_l  : %s\n" % self.dq_l
563        rep += "dq_r  : %s\n" % self.dq_r
564        rep += "dx_percent  : %s\n" % str(self.dx_percent)
565        rep += "dxl  : %s\n" % str(self.dxl)
566        rep += "dxw : %s\n" % str(self.dxw)
567        rep += "model  : %s\n\n" % str(self.model)
568        temp_parameters = []
569        temp_fittable_param = []
570        if self.data.__class__.__name__ == "Data2D":
571            self.is_2D = True
572        else:
573            self.is_2D = False
574        if self.data is not None:
575            if not self.is_2D:
576                for item in self.parameters:
577                    if item not in self.orientation_params:
578                        temp_parameters.append(item)
579                for item in self.fittable_param:
580                    if item not in self.orientation_params_disp:
581                        temp_fittable_param.append(item)
582            else:
583                temp_parameters = self.parameters
584                temp_fittable_param = self.fittable_param
585
586            rep += "number parameters(self.parameters): %s\n" % \
587                   len(temp_parameters)
588            rep = self._repr_helper(list=temp_parameters, rep=rep)
589            rep += "number str_parameters(self.str_parameters): %s\n" % \
590                   len(self.str_parameters)
591            rep = self._repr_helper(list=self.str_parameters, rep=rep)
592            rep += "number fittable_param(self.fittable_param): %s\n" % \
593                   len(temp_fittable_param)
594            rep = self._repr_helper(list=temp_fittable_param, rep=rep)
595        return rep
596
597    def _get_report_string(self):
598        """
599        Get the values (strings) from __str__ for report
600        """
601        # Dictionary of the report strings
602        repo_time = ""
603        model_name = ""
604        title = ""
605        title_name = ""
606        file_name = ""
607        param_string = ""
608        paramval_string = ""
609        chi2_string = ""
610        q_range = ""
611        strings = self.__repr__()
612        fixed_parameter = False
613        lines = strings.split('\n')
614        # get all string values from __str__()
615        for line in lines:
616            # Skip lines which are not key: value pairs, which includes
617            # blank lines and freeform notes in SASNotes fields.
618            if not ':' in line:
619                #msg = "Report string expected 'name: value' but got %r" % line
620                #logger.error(msg)
621                continue
622
623            name, value = [s.strip() for s in line.split(":", 1)]
624            if name == "State created":
625                repo_time = value
626            elif name == "parameter name":
627                val_name = value.split(".")
628                if len(val_name) > 1:
629                    if val_name[1].count("width"):
630                        param_string += value + ','
631                    else:
632                        continue
633                else:
634                    param_string += value + ','
635            elif name == "value":
636                param_string += value + ','
637            elif name == "selected":
638                # remember if it is fixed when reporting error value
639                fixed_parameter = (value == u'False')
640            elif name == "error value":
641                if fixed_parameter:
642                    param_string += '(fixed),'
643                else:
644                    param_string += value + ','
645            elif name == "parameter unit":
646                param_string += value + ':'
647            elif name == "Value of Chisqr":
648                chi2 = ("Chi2/Npts = " + value)
649                chi2_string = CENTRE % chi2
650            elif name == "Title":
651                if len(value.strip()) == 0:
652                    continue
653                title = (value + " [" + repo_time + "] [SasView v" +
654                         SASVIEW_VERSION + "]")
655                title_name = HEADER % title
656            elif name == "data":
657                try:
658                    # parsing "data : File:     filename [mmm dd hh:mm]"
659                    name = value.split(':', 1)[1].strip()
660                    file_value = "File name:" + name
661                    #Truncating string so print doesn't complain of being outside margins
662                    if sys.platform != "win32":
663                        MAX_STRING_LENGTH = 50
664                        if len(file_value) > MAX_STRING_LENGTH:
665                            file_value = "File name:.."+file_value[-MAX_STRING_LENGTH+10:]
666                    file_name = CENTRE % file_value
667                    if len(title) == 0:
668                        title = name + " [" + repo_time + "]"
669                        title_name = HEADER % title
670                except Exception:
671                    msg = "While parsing 'data: ...'\n"
672                    logger.error(msg + traceback.format_exc())
673            elif name == "model name":
674                try:
675                    modelname = "Model name:" + value
676                except Exception:
677                    modelname = "Model name:" + " NAN"
678                model_name = CENTRE % modelname
679
680            elif name == "Plotting Range":
681                try:
682                    parts = value.split(':')
683                    q_range = parts[0] + " = " + parts[1] \
684                            + " = " + parts[2].split(",")[0]
685                    q_name = ("Q Range:    " + q_range)
686                    q_range = CENTRE % q_name
687                except Exception:
688                    msg = "While parsing 'Plotting Range: ...'\n"
689                    logger.error(msg + traceback.format_exc())
690
691        paramval = ""
692        for lines in param_string.split(":"):
693            line = lines.split(",")
694            if len(lines) > 0:
695                param = line[0]
696                param += " = " + line[1]
697                if len(line[2].split()) > 0 and not line[2].count("None"):
698                    param += " +- " + line[2]
699                if len(line[3].split()) > 0 and not line[3].count("None"):
700                    param += " " + line[3]
701                if not paramval.count(param):
702                    paramval += param + "\n"
703                    paramval_string += CENTRE % param + "\n"
704
705        text_string = "\n\n\n%s\n\n%s\n%s\n%s\n\n%s" % \
706                      (title, file, q_name, chi2, paramval)
707
708        title_name = self._check_html_format(title_name)
709        file_name = self._check_html_format(file_name)
710        title = self._check_html_format(title)
711
712        html_string = title_name + "\n" + file_name + \
713                                   "\n" + model_name + \
714                                   "\n" + q_range + \
715                                   "\n" + chi2_string + \
716                                   "\n" + ELINE + \
717                                   "\n" + paramval_string + \
718                                   "\n" + ELINE + \
719                                   "\n" + FEET_1 % title
720
721        return html_string, text_string, title
722
723    def _check_html_format(self, name):
724        """
725        Check string '%' for html format
726        """
727        if name.count('%'):
728            name = name.replace('%', '&#37')
729
730        return name
731
732    def report(self, fig_urls):
733        """
734        Invoke report dialog panel
735
736        : param figs: list of pylab figures [list]
737        """
738        # get the strings for report
739        html_str, text_str, title = self._get_report_string()
740        # Allow 2 figures to append
741        #Constraining image width for OSX and linux, so print doesn't complain of being outside margins
742        if sys.platform == "win32":
743            image_links = [FEET_2%fig for fig in fig_urls]
744        else:
745            image_links = [FEET_2_unix%fig for fig in fig_urls]
746        # final report html strings
747        report_str = html_str + ELINE.join(image_links)
748        report_str += FEET_3
749        return report_str, text_str
750
751    def _to_xml_helper(self, thelist, element, newdoc):
752        """
753        Helper method to create xml file for saving state
754        """
755        for item in thelist:
756            sub_element = newdoc.createElement('parameter')
757            sub_element.setAttribute('name', str(item[1]))
758            sub_element.setAttribute('value', str(item[2]))
759            sub_element.setAttribute('selected_to_fit', str(item[0]))
760            sub_element.setAttribute('error_displayed', str(item[4][0]))
761            sub_element.setAttribute('error_value', str(item[4][1]))
762            sub_element.setAttribute('minimum_displayed', str(item[5][0]))
763            sub_element.setAttribute('minimum_value', str(item[5][1]))
764            sub_element.setAttribute('maximum_displayed', str(item[6][0]))
765            sub_element.setAttribute('maximum_value', str(item[6][1]))
766            sub_element.setAttribute('unit', str(item[7]))
767            element.appendChild(sub_element)
768
769    def to_xml(self, file="fitting_state.fitv", doc=None,
770               entry_node=None, batch_fit_state=None):
771        """
772        Writes the state of the fit panel to file, as XML.
773
774        Compatible with standalone writing, or appending to an
775        already existing XML document. In that case, the XML document is
776        required. An optional entry node in the XML document may also be given.
777
778        :param file: file to write to
779        :param doc: XML document object [optional]
780        :param entry_node: XML node within the XML document at which we
781                           will append the data [optional]
782        :param batch_fit_state: simultaneous fit state
783        """
784        # Check whether we have to write a standalone XML file
785        if doc is None:
786            impl = getDOMImplementation()
787            doc_type = impl.createDocumentType(FITTING_NODE_NAME, "1.0", "1.0")
788            newdoc = impl.createDocument(None, FITTING_NODE_NAME, doc_type)
789            top_element = newdoc.documentElement
790        else:
791            # We are appending to an existing document
792            newdoc = doc
793            try:
794                top_element = newdoc.createElement(FITTING_NODE_NAME)
795            except Exception:
796                string = etree.tostring(doc, pretty_print=True)
797                newdoc = parseString(string)
798                top_element = newdoc.createElement(FITTING_NODE_NAME)
799            if entry_node is None:
800                newdoc.documentElement.appendChild(top_element)
801            else:
802                try:
803                    entry_node.appendChild(top_element)
804                except Exception:
805                    node_name = entry_node.tag
806                    node_list = newdoc.getElementsByTagName(node_name)
807                    entry_node = node_list.item(0)
808                    entry_node.appendChild(top_element)
809
810        attr = newdoc.createAttribute("version")
811        attr.nodeValue = SASVIEW_VERSION
812        # attr.nodeValue = '1.0'
813        top_element.setAttributeNode(attr)
814
815        # File name
816        element = newdoc.createElement("filename")
817        if self.file is not None:
818            element.appendChild(newdoc.createTextNode(str(self.file)))
819        else:
820            element.appendChild(newdoc.createTextNode(str(file)))
821        top_element.appendChild(element)
822
823        element = newdoc.createElement("timestamp")
824        element.appendChild(newdoc.createTextNode(time.ctime(self.timestamp)))
825        attr = newdoc.createAttribute("epoch")
826        attr.nodeValue = str(self.timestamp)
827        element.setAttributeNode(attr)
828        top_element.appendChild(element)
829
830        # Inputs
831        inputs = newdoc.createElement("Attributes")
832        top_element.appendChild(inputs)
833
834        if self.data is not None and hasattr(self.data, "group_id"):
835            self.data_group_id = self.data.group_id
836        if self.data is not None and hasattr(self.data, "is_data"):
837            self.is_data = self.data.is_data
838        if self.data is not None:
839            self.data_name = self.data.name
840        if self.data is not None and hasattr(self.data, "id"):
841            self.data_id = self.data.id
842
843        for item in LIST_OF_DATA_ATTRIBUTES:
844            element = newdoc.createElement(item[0])
845            element.setAttribute(item[0], str(getattr(self, item[1])))
846            inputs.appendChild(element)
847
848        for item in LIST_OF_STATE_ATTRIBUTES:
849            element = newdoc.createElement(item[0])
850            element.setAttribute(item[0], str(getattr(self, item[1])))
851            inputs.appendChild(element)
852
853        # For self.values ={ disp_param_name: [vals,...],...}
854        # and for self.weights ={ disp_param_name: [weights,...],...}
855        for item in LIST_OF_MODEL_ATTRIBUTES:
856            element = newdoc.createElement(item[0])
857            value_list = getattr(self, item[1])
858            for key, value in value_list.items():
859                sub_element = newdoc.createElement(key)
860                sub_element.setAttribute('name', str(key))
861                for val in value:
862                    sub_element.appendChild(newdoc.createTextNode(str(val)))
863
864                element.appendChild(sub_element)
865            inputs.appendChild(element)
866
867        # Create doc for the dictionary of self.disp_obj_dic
868        for tagname, varname, tagtype in DISPERSION_LIST:
869            element = newdoc.createElement(tagname)
870            value_list = getattr(self, varname)
871            for key, value in value_list.items():
872                sub_element = newdoc.createElement(key)
873                sub_element.setAttribute('name', str(key))
874                sub_element.setAttribute('value', str(value))
875                element.appendChild(sub_element)
876            inputs.appendChild(element)
877
878        for item in LIST_OF_STATE_PARAMETERS:
879            element = newdoc.createElement(item[0])
880            self._to_xml_helper(thelist=getattr(self, item[1]),
881                                element=element, newdoc=newdoc)
882            inputs.appendChild(element)
883
884        # Combined and Simultaneous Fit Parameters
885        if batch_fit_state is not None:
886            batch_combo = newdoc.createElement('simultaneous_fit')
887            top_element.appendChild(batch_combo)
888
889            # Simultaneous Fit Number For Linking Later
890            element = newdoc.createElement('sim_fit_number')
891            element.setAttribute('fit_number', str(batch_fit_state.fit_page_no))
892            batch_combo.appendChild(element)
893
894            # Save constraints
895            constraints = newdoc.createElement('constraints')
896            batch_combo.appendChild(constraints)
897            for constraint in batch_fit_state.constraints_list:
898                if constraint.model_cbox.GetValue() != "":
899                    # model_cbox, param_cbox, egal_txt, constraint,
900                    # btRemove, sizer
901                    doc_cons = newdoc.createElement('constraint')
902                    doc_cons.setAttribute('model_cbox',
903                                          str(constraint.model_cbox.GetValue()))
904                    doc_cons.setAttribute('param_cbox',
905                                          str(constraint.param_cbox.GetValue()))
906                    doc_cons.setAttribute('egal_txt',
907                                          str(constraint.egal_txt.GetLabel()))
908                    doc_cons.setAttribute('constraint',
909                                          str(constraint.constraint.GetValue()))
910                    constraints.appendChild(doc_cons)
911
912            # Save all models
913            models = newdoc.createElement('model_list')
914            batch_combo.appendChild(models)
915            for model in batch_fit_state.model_list:
916                doc_model = newdoc.createElement('model_list_item')
917                doc_model.setAttribute('checked', str(model[0].GetValue()))
918                keys = list(model[1].keys())
919                doc_model.setAttribute('name', str(keys[0]))
920                values = model[1].get(keys[0])
921                doc_model.setAttribute('fit_number', str(model[2]))
922                doc_model.setAttribute('fit_page_source', str(model[3]))
923                doc_model.setAttribute('model_name', str(values.model.id))
924                models.appendChild(doc_model)
925
926            # Select All Checkbox
927            element = newdoc.createElement('select_all')
928            if batch_fit_state.select_all:
929                element.setAttribute('checked', 'True')
930            else:
931                element.setAttribute('checked', 'False')
932            batch_combo.appendChild(element)
933
934        # Save the file
935        if doc is None:
936            fd = open(file, 'w')
937            fd.write(newdoc.toprettyxml())
938            fd.close()
939            return None
940        else:
941            return newdoc
942
943    def _from_xml_helper(self, node, list):
944        """
945        Helper function to write state to xml
946        """
947        for item in node:
948            name = item.get('name')
949            value = item.get('value')
950            selected_to_fit = (item.get('selected_to_fit') == "True")
951            error_displayed = (item.get('error_displayed') == "True")
952            error_value = item.get('error_value')
953            minimum_displayed = (item.get('minimum_displayed') == "True")
954            minimum_value = item.get('minimum_value')
955            maximum_displayed = (item.get('maximum_displayed') == "True")
956            maximum_value = item.get('maximum_value')
957            unit = item.get('unit')
958            list.append([selected_to_fit, name, value, "+/-",
959                         [error_displayed, error_value],
960                         [minimum_displayed, minimum_value],
961                         [maximum_displayed, maximum_value], unit])
962
963    def from_xml(self, file=None, node=None):
964        """
965        Load fitting state from a file
966
967        :param file: .fitv file
968        :param node: node of a XML document to read from
969        """
970        if file is not None:
971            msg = "PageState no longer supports non-CanSAS"
972            msg += " format for fitting files"
973            raise RuntimeError(msg)
974
975        if node.get('version'):
976            # Get the version for model conversion purposes
977            x = re.sub(r'[^\d.]', '', node.get('version'))
978            self.version = tuple(int(e) for e in str.split(x, "."))
979            # The tuple must be at least 3 items long
980            while len(self.version) < 3:
981                ver_list = list(self.version)
982                ver_list.append(0)
983                self.version = tuple(ver_list)
984
985            # Get file name
986            entry = get_content('ns:filename', node)
987            if entry is not None and entry.text:
988                self.file = entry.text.strip()
989            else:
990                self.file = ''
991
992            # Get time stamp
993            entry = get_content('ns:timestamp', node)
994            if entry is not None and entry.get('epoch'):
995                try:
996                    self.timestamp = float(entry.get('epoch'))
997                except Exception as exc:
998                    msg = "PageState.fromXML: Could not"
999                    msg += " read timestamp\n %s" % exc
1000                    logger.error(msg)
1001
1002            if entry is not None:
1003                # Parse fitting attributes
1004                entry = get_content('ns:Attributes', node)
1005                for item in LIST_OF_DATA_ATTRIBUTES:
1006                    node = get_content('ns:%s' % item[0], entry)
1007                    setattr(self, item[0], parse_entry_helper(node, item))
1008
1009                dx_old_node = get_content('ns:%s' % 'dx_min', entry)
1010                for item in LIST_OF_STATE_ATTRIBUTES:
1011                    if item[0] == "dx_percent" and dx_old_node is not None:
1012                        dxmin = ["dx_min", "dx_min", "float"]
1013                        setattr(self, item[0], parse_entry_helper(dx_old_node,
1014                                                                  dxmin))
1015                        self.dx_old = True
1016                    else:
1017                        node = get_content('ns:%s' % item[0], entry)
1018                        setattr(self, item[0], parse_entry_helper(node, item))
1019
1020                for item in LIST_OF_STATE_PARAMETERS:
1021                    node = get_content("ns:%s" % item[0], entry)
1022                    self._from_xml_helper(node=node,
1023                                          list=getattr(self, item[1]))
1024
1025                # Recover disp_obj_dict from xml file
1026                self.disp_obj_dict = {}
1027                for tagname, varname, tagtype in DISPERSION_LIST:
1028                    node = get_content("ns:%s" % tagname, entry)
1029                    for attr in node:
1030                        parameter = str(attr.get('name'))
1031                        value = attr.get('value')
1032                        if value.startswith("<"):
1033                            try:
1034                                # <path.to.NamedDistribution object/instance...>
1035                                cls_name = value[1:].split()[0].split('.')[-1]
1036                                cls = getattr(sasmodels.weights, cls_name)
1037                                value = cls.type
1038                            except Exception:
1039                                base = "unable to load distribution %r for %s"
1040                                logger.error(base, value, parameter)
1041                                continue
1042                        disp_obj_dict = getattr(self, varname)
1043                        disp_obj_dict[parameter] = value
1044
1045                # get self.values and self.weights dic. if exists
1046                for tagname, varname in LIST_OF_MODEL_ATTRIBUTES:
1047                    node = get_content("ns:%s" % tagname, entry)
1048                    dic = {}
1049                    value_list = []
1050                    for par in node:
1051                        name = par.get('name')
1052                        values = par.text.split()
1053                        # Get lines only with numbers
1054                        for line in values:
1055                            try:
1056                                val = float(line)
1057                                value_list.append(val)
1058                            except Exception:
1059                                # pass if line is empty (it happens)
1060                                msg = ("Error reading %r from %s %s\n"
1061                                       % (line, tagname, name))
1062                                logger.error(msg + traceback.format_exc())
1063                        dic[name] = np.array(value_list)
1064                    setattr(self, varname, dic)
1065
1066class SimFitPageState(object):
1067    """
1068    State of the simultaneous fit page for saving purposes
1069    """
1070
1071    def __init__(self):
1072        # Sim Fit Page Number
1073        self.fit_page_no = None
1074        # Select all data
1075        self.select_all = False
1076        # Data sets sent to fit page
1077        self.model_list = []
1078        # Data sets to be fit
1079        self.model_to_fit = []
1080        # Number of constraints
1081        self.no_constraint = 0
1082        # Dictionary of constraints
1083        self.constraint_dict = {}
1084        # List of constraints
1085        self.constraints_list = []
1086
1087    def __repr__(self):
1088        # TODO: should use __str__, not __repr__ (similarly for PageState)
1089        # TODO: could use a nicer representation
1090        repr = """\
1091fit page number : %(fit_page_no)s
1092select all : %(select_all)s
1093model_list : %(model_list)s
1094model to fit : %(model_to_fit)s
1095number of construsts : %(no_constraint)s
1096constraint dict : %(constraint_dict)s
1097constraints list : %(constraints_list)s
1098"""%self.__dict__
1099        return repr
1100
1101class Reader(CansasReader):
1102    """
1103    Class to load a .fitv fitting file
1104    """
1105    # File type
1106    type_name = "Fitting"
1107
1108    # Wildcards
1109    type = ["Fitting files (*.fitv)|*.fitv"
1110            "SASView file (*.svs)|*.svs"]
1111    # List of allowed extensions
1112    ext = ['.fitv', '.FITV', '.svs', 'SVS']
1113
1114    def __init__(self, call_back=None, cansas=True):
1115        CansasReader.__init__(self)
1116        """
1117        Initialize the call-back method to be called
1118        after we load a file
1119
1120        :param call_back: call-back method
1121        :param cansas:  True = files will be written/read in CanSAS format
1122                        False = write CanSAS format
1123
1124        """
1125        # Call back method to be executed after a file is read
1126        self.call_back = call_back
1127        # CanSAS format flag
1128        self.cansas = cansas
1129        self.state = None
1130        # batch fitting params for saving
1131        self.batchfit_params = []
1132
1133    def get_state(self):
1134        return self.state
1135
1136    def read(self, path):
1137        """
1138        Load a new P(r) inversion state from file
1139
1140        :param path: file path
1141
1142        """
1143        if self.cansas:
1144            return self._read_cansas(path)
1145
1146    def _parse_state(self, entry):
1147        """
1148        Read a fit result from an XML node
1149
1150        :param entry: XML node to read from
1151        :return: PageState object
1152        """
1153        # Create an empty state
1154        state = None
1155        # Locate the P(r) node
1156        try:
1157            nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
1158                                namespaces=CANSAS_NS)
1159            if nodes:
1160                # Create an empty state
1161                state = PageState()
1162                state.from_xml(node=nodes[0])
1163
1164        except Exception:
1165            logger.info("XML document does not contain fitting information.\n"
1166                        + traceback.format_exc())
1167
1168        return state
1169
1170    def _parse_simfit_state(self, entry):
1171        """
1172        Parses the saved data for a simultaneous fit
1173        :param entry: XML object to read from
1174        :return: XML object for a simultaneous fit or None
1175        """
1176        nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
1177                            namespaces=CANSAS_NS)
1178        if nodes:
1179            simfitstate = nodes[0].xpath('ns:simultaneous_fit',
1180                                         namespaces=CANSAS_NS)
1181            if simfitstate:
1182                sim_fit_state = SimFitPageState()
1183                simfitstate_0 = simfitstate[0]
1184                all = simfitstate_0.xpath('ns:select_all',
1185                                          namespaces=CANSAS_NS)
1186                atts = all[0].attrib
1187                checked = atts.get('checked')
1188                sim_fit_state.select_all = bool(checked)
1189                model_list = simfitstate_0.xpath('ns:model_list',
1190                                                 namespaces=CANSAS_NS)
1191                model_list_items = model_list[0].xpath('ns:model_list_item',
1192                                                       namespaces=CANSAS_NS)
1193                for model in model_list_items:
1194                    attrs = model.attrib
1195                    sim_fit_state.model_list.append(attrs)
1196
1197                constraints = simfitstate_0.xpath('ns:constraints',
1198                                                  namespaces=CANSAS_NS)
1199                constraint_list = constraints[0].xpath('ns:constraint',
1200                                                       namespaces=CANSAS_NS)
1201                for constraint in constraint_list:
1202                    attrs = constraint.attrib
1203                    sim_fit_state.constraints_list.append(attrs)
1204
1205                return sim_fit_state
1206            else:
1207                return None
1208
1209    def _parse_save_state_entry(self, dom):
1210        """
1211        Parse a SASentry
1212
1213        :param node: SASentry node
1214
1215        :return: Data1D/Data2D object
1216
1217        """
1218        node = dom.xpath('ns:data_class', namespaces=CANSAS_NS)
1219        return_value, _ = self._parse_entry(dom)
1220        return return_value, _
1221
1222    def _read_cansas(self, path):
1223        """
1224        Load data and fitting information from a CanSAS XML file.
1225
1226        :param path: file path
1227        :return: Data1D object if a single SASentry was found,
1228                    or a list of Data1D objects if multiple entries were found,
1229                    or None of nothing was found
1230        :raise RuntimeError: when the file can't be opened
1231        :raise ValueError: when the length of the data vectors are inconsistent
1232        """
1233        output = []
1234        simfitstate = None
1235        basename = os.path.basename(path)
1236        root, extension = os.path.splitext(basename)
1237        ext = extension.lower()
1238        try:
1239            if os.path.isfile(path):
1240                if ext in self.ext or ext == '.xml':
1241                    tree = etree.parse(path, parser=etree.ETCompatXMLParser())
1242                    # Check the format version number
1243                    # Specifying the namespace will take care of the file
1244                    # format version
1245                    root = tree.getroot()
1246                    entry_list = root.xpath('ns:SASentry',
1247                                            namespaces=CANSAS_NS)
1248                    for entry in entry_list:
1249                        fitstate = self._parse_state(entry)
1250                        # state could be None when .svs file is loaded
1251                        # in this case, skip appending to output
1252                        if fitstate is not None:
1253                            try:
1254                                sas_entry, _ = self._parse_save_state_entry(
1255                                    entry)
1256                            except:
1257                                raise
1258                            sas_entry.meta_data['fitstate'] = fitstate
1259                            sas_entry.filename = fitstate.file
1260                            output.append(sas_entry)
1261
1262            else:
1263                self.call_back(format=ext)
1264                raise RuntimeError("%s is not a file" % path)
1265
1266            # Return output consistent with the loader's api
1267            if len(output) == 0:
1268                self.call_back(state=None, datainfo=None, format=ext)
1269                return None
1270            else:
1271                for data in output:
1272                    # Call back to post the new state
1273                    state = data.meta_data['fitstate']
1274                    t = time.localtime(state.timestamp)
1275                    time_str = time.strftime("%b %d %H:%M", t)
1276                    # Check that no time stamp is already appended
1277                    max_char = state.file.find("[")
1278                    if max_char < 0:
1279                        max_char = len(state.file)
1280                    original_fname = state.file[0:max_char]
1281                    state.file = original_fname + ' [' + time_str + ']'
1282
1283                    if state is not None and state.is_data is not None:
1284                        data.is_data = state.is_data
1285
1286                    data.filename = state.file
1287                    state.data = data
1288                    state.data.name = data.filename  # state.data_name
1289                    state.data.id = state.data_id
1290                    if state.is_data is not None:
1291                        state.data.is_data = state.is_data
1292                    if data.run_name is not None and len(data.run_name) != 0:
1293                        if isinstance(data.run_name, dict):
1294                            # Note: key order in dict is not guaranteed, so sort
1295                            name = list(data.run_name.keys())[0]
1296                        else:
1297                            name = data.run_name
1298                    else:
1299                        name = original_fname
1300                    state.data.group_id = name
1301                    state.version = fitstate.version
1302                    # store state in fitting
1303                    self.call_back(state=state, datainfo=data, format=ext)
1304                    self.state = state
1305                simfitstate = self._parse_simfit_state(entry)
1306                if simfitstate is not None:
1307                    self.call_back(state=simfitstate)
1308
1309                return output
1310        except:
1311            self.call_back(format=ext)
1312            raise
1313
1314    def write(self, filename, datainfo=None, fitstate=None):
1315        """
1316        Write the content of a Data1D as a CanSAS XML file only for standalone
1317
1318        :param filename: name of the file to write
1319        :param datainfo: Data1D object
1320        :param fitstate: PageState object
1321
1322        """
1323        # Sanity check
1324        if self.cansas:
1325            # Add fitting information to the XML document
1326            doc = self.write_toXML(datainfo, fitstate)
1327            # Write the XML document
1328        else:
1329            doc = fitstate.to_xml(file=filename)
1330
1331        # Save the document no matter the type
1332        fd = open(filename, 'w')
1333        fd.write(doc.toprettyxml())
1334        fd.close()
1335
1336    def write_toXML(self, datainfo=None, state=None, batchfit=None):
1337        """
1338        Write toXML, a helper for write(),
1339        could be used by guimanager._on_save()
1340
1341        : return: xml doc
1342        """
1343
1344        self.batchfit_params = batchfit
1345        if state.data is None or not state.data.is_data:
1346            return None
1347        # make sure title and data run are filled.
1348        if state.data.title is None or state.data.title == '':
1349            state.data.title = state.data.name
1350        if state.data.run_name is None or state.data.run_name == {}:
1351            state.data.run = [str(state.data.name)]
1352            state.data.run_name[0] = state.data.name
1353
1354        data = state.data
1355        doc, sasentry = self._to_xml_doc(data)
1356
1357        if state is not None:
1358            doc = state.to_xml(doc=doc, file=data.filename, entry_node=sasentry,
1359                               batch_fit_state=self.batchfit_params)
1360
1361        return doc
1362
1363# Simple html report template
1364HEADER = "<html>\n"
1365HEADER += "<head>\n"
1366HEADER += "<meta http-equiv=Content-Type content='text/html; "
1367HEADER += "charset=windows-1252'> \n"
1368HEADER += "<meta name=Generator >\n"
1369HEADER += "</head>\n"
1370HEADER += "<body lang=EN-US>\n"
1371HEADER += "<div class=WordSection1>\n"
1372HEADER += "<p class=MsoNormal><b><span ><center><font size='4' >"
1373HEADER += "%s</font></center></span></center></b></p>"
1374HEADER += "<p class=MsoNormal>&nbsp;</p>"
1375PARA = "<p class=MsoNormal><font size='4' > %s \n"
1376PARA += "</font></p>"
1377CENTRE = "<p class=MsoNormal><center><font size='4' > %s \n"
1378CENTRE += "</font></center></p>"
1379FEET_1 = \
1380"""
1381<p class=MsoNormal>&nbsp;</p>
1382<br>
1383<p class=MsoNormal><b><span ><center> <font size='4' > Graph
1384</font></span></center></b></p>
1385<p class=MsoNormal>&nbsp;</p>
1386<center>
1387<br><font size='4' >Model Computation</font>
1388<br><font size='4' >Data: "%s"</font><br>
1389"""
1390FEET_2 = \
1391"""<img src="%s"></img>
1392"""
1393FEET_2_unix = \
1394"""<img src="%s" width="540"></img>
1395"""
1396FEET_3 = \
1397"""</center>
1398</div>
1399</body>
1400</html>
1401"""
1402ELINE = """<p class=MsoNormal>&nbsp;</p>
1403"""
Note: See TracBrowser for help on using the repository browser.