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

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 0d72cac was b8080e1, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

cherry picking sascalc changes from master SASVIEW-996
minor unit test fixes

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