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

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

Added a constant for "[plug-in] " to maintain consistency across multiple classes, modified the documentation on a method to be clearer, and changed the exception handling for calcthread back to its original state.

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