source: sasview/src/sas/sasgui/perspectives/fitting/pagestate.py @ 12361fd

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

Trying to remove hidden characters in pagestate.

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