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

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

Delete all plots when resetting state.

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