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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 9e0aa69a was 9e0aa69a, checked in by krzywon, 7 years ago

Added versioning to pagestate and fit saves for future use.

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