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

Last change on this file since a67d674 was 9e6aeaf, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

Merge branch 'ticket-853-fit-gui-to-calc' into py3

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