source: sasview/src/sas/sascalc/fit/pagestate.py @ 8fbca4f

ticket-1009
Last change on this file since 8fbca4f was 8fbca4f, checked in by gonzalezm, 5 years ago

Changed comparison to find PD parameters to .width instead of width

  • Property mode set to 100644
File size: 55.5 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
[59873e1]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:
[8fbca4f]493            if str(item[1][-6:]) == '.width':
[c74f359]494                par = str(item[1][:-6])
495                pd_type = str(self.model.dispersion[par]['type'])
496                rep += "parameter name: %s (%s) \n" % (str(item[1]), pd_type)
497            else:
498                rep += "parameter name: %s \n" % str(item[1])
[959eb01]499            rep += "value: %s\n" % str(item[2])
500            rep += "selected: %s\n" % str(item[0])
501            rep += "error displayed : %s \n" % str(item[4][0])
502            rep += "error value:%s \n" % str(item[4][1])
503            rep += "minimum displayed : %s \n" % str(item[5][0])
504            rep += "minimum value : %s \n" % str(item[5][1])
505            rep += "maximum displayed : %s \n" % str(item[6][0])
506            rep += "maximum value : %s \n" % str(item[6][1])
507            rep += "parameter unit: %s\n\n" % str(item[7])
508        return rep
509
510    def __repr__(self):
511        """
512        output string for printing
513        """
514        rep = "\nState name: %s\n" % self.file
515        t = time.localtime(self.timestamp)
[78312f7]516        time_str = time.strftime("%b %d %Y %H:%M:%S ", t)
[959eb01]517
518        rep += "State created: %s\n" % time_str
519        rep += "State form factor combobox selection: %s\n" % \
520               self.formfactorcombobox
521        rep += "State structure factor combobox selection: %s\n" % \
522               self.structurecombobox
523        rep += "is data : %s\n" % self.is_data
524        rep += "data's name : %s\n" % self.data_name
525        rep += "data's id : %s\n" % self.data_id
526        if self.model is not None:
527            m_name = self.model.__class__.__name__
528            if m_name == 'Model':
529                m_name = self.m_name
530            rep += "model name : %s\n" % m_name
531        else:
532            rep += "model name : None\n"
533        rep += "multi_factor : %s\n" % str(self.multi_factor)
534        rep += "magnetic_on : %s\n" % str(self.magnetic_on)
535        rep += "model type (Category) selected: %s\n" % self.categorycombobox
536        rep += "data : %s\n" % str(self.data)
537        rep += "Plotting Range: min: %s, max: %s, steps: %s\n" % \
538               (str(self.qmin), str(self.qmax), str(self.npts))
539        rep += "Dispersion selection : %s\n" % str(self.disp_box)
540        rep += "Smearing enable : %s\n" % str(self.enable_smearer)
541        rep += "Smearing disable : %s\n" % str(self.disable_smearer)
542        rep += "Pinhole smearer enable : %s\n" % str(self.pinhole_smearer)
543        rep += "Slit smearer enable : %s\n" % str(self.slit_smearer)
544        rep += "Dispersity enable : %s\n" % str(self.enable_disp)
545        rep += "Dispersity disable : %s\n" % str(self.disable_disp)
546        rep += "Slit smearer enable: %s\n" % str(self.slit_smearer)
547
548        rep += "dI_noweight : %s\n" % str(self.dI_noweight)
549        rep += "dI_didata : %s\n" % str(self.dI_didata)
550        rep += "dI_sqrdata : %s\n" % str(self.dI_sqrdata)
551        rep += "dI_idata : %s\n" % str(self.dI_idata)
552
553        rep += "2D enable : %s\n" % str(self.enable2D)
554        rep += "All parameters checkbox selected: %s\n" % self.cb1
555        rep += "Value of Chisqr : %s\n" % str(self.tcChi)
556        rep += "dq_l  : %s\n" % self.dq_l
557        rep += "dq_r  : %s\n" % self.dq_r
558        rep += "dx_percent  : %s\n" % str(self.dx_percent)
559        rep += "dxl  : %s\n" % str(self.dxl)
560        rep += "dxw : %s\n" % str(self.dxw)
561        rep += "model  : %s\n\n" % str(self.model)
562        temp_parameters = []
563        temp_fittable_param = []
564        if self.data.__class__.__name__ == "Data2D":
565            self.is_2D = True
566        else:
567            self.is_2D = False
568        if self.data is not None:
569            if not self.is_2D:
570                for item in self.parameters:
571                    if item not in self.orientation_params:
572                        temp_parameters.append(item)
573                for item in self.fittable_param:
574                    if item not in self.orientation_params_disp:
575                        temp_fittable_param.append(item)
576            else:
577                temp_parameters = self.parameters
578                temp_fittable_param = self.fittable_param
579
580            rep += "number parameters(self.parameters): %s\n" % \
581                   len(temp_parameters)
582            rep = self._repr_helper(list=temp_parameters, rep=rep)
583            rep += "number str_parameters(self.str_parameters): %s\n" % \
584                   len(self.str_parameters)
585            rep = self._repr_helper(list=self.str_parameters, rep=rep)
586            rep += "number fittable_param(self.fittable_param): %s\n" % \
587                   len(temp_fittable_param)
588            rep = self._repr_helper(list=temp_fittable_param, rep=rep)
589        return rep
590
[78312f7]591    def _get_report_string(self):
[959eb01]592        """
593        Get the values (strings) from __str__ for report
594        """
595        # Dictionary of the report strings
596        repo_time = ""
597        model_name = ""
598        title = ""
599        title_name = ""
600        file_name = ""
601        param_string = ""
602        paramval_string = ""
603        chi2_string = ""
604        q_range = ""
605        strings = self.__repr__()
[78312f7]606        fixed_parameter = False
[959eb01]607        lines = strings.split('\n')
608        # get all string values from __str__()
609        for line in lines:
[78312f7]610            # Skip lines which are not key: value pairs, which includes
611            # blank lines and freeform notes in SASNotes fields.
612            if not ':' in line:
613                #msg = "Report string expected 'name: value' but got %r" % line
614                #logger.error(msg)
615                continue
616
617            name, value = [s.strip() for s in line.split(":", 1)]
618            if name == "State created":
619                repo_time = value
620            elif name == "parameter name":
[959eb01]621                val_name = value.split(".")
622                if len(val_name) > 1:
623                    if val_name[1].count("width"):
624                        param_string += value + ','
625                    else:
626                        continue
627                else:
628                    param_string += value + ','
[78312f7]629            elif name == "value":
[959eb01]630                param_string += value + ','
[78312f7]631            elif name == "selected":
632                # remember if it is fixed when reporting error value
633                fixed_parameter = (value == u'False')
634            elif name == "error value":
[959eb01]635                if fixed_parameter:
636                    param_string += '(fixed),'
637                else:
638                    param_string += value + ','
[78312f7]639            elif name == "parameter unit":
[959eb01]640                param_string += value + ':'
[78312f7]641            elif name == "Value of Chisqr":
[959eb01]642                chi2 = ("Chi2/Npts = " + value)
643                chi2_string = CENTRE % chi2
[78312f7]644            elif name == "Title":
[959eb01]645                if len(value.strip()) == 0:
646                    continue
[e9920cd]647                title = (value + " [" + repo_time + "] [SasView v" +
[863ac2c]648                         SASVIEW_VERSION + "]")
[959eb01]649                title_name = HEADER % title
[78312f7]650            elif name == "data":
[959eb01]651                try:
[78312f7]652                    # parsing "data : File:     filename [mmm dd hh:mm]"
653                    name = value.split(':', 1)[1].strip()
654                    file_value = "File name:" + name
[47c44f0]655                    #Truncating string so print doesn't complain of being outside margins
656                    if sys.platform != "win32":
[e090ba90]657                        MAX_STRING_LENGTH = 50
658                        if len(file_value) > MAX_STRING_LENGTH:
659                            file_value = "File name:.."+file_value[-MAX_STRING_LENGTH+10:]
[959eb01]660                    file_name = CENTRE % file_value
661                    if len(title) == 0:
[78312f7]662                        title = name + " [" + repo_time + "]"
[959eb01]663                        title_name = HEADER % title
664                except Exception:
665                    msg = "While parsing 'data: ...'\n"
666                    logger.error(msg + traceback.format_exc())
[78312f7]667            elif name == "model name":
[959eb01]668                try:
[78312f7]669                    modelname = "Model name:" + value
670                except Exception:
[959eb01]671                    modelname = "Model name:" + " NAN"
672                model_name = CENTRE % modelname
673
[78312f7]674            elif name == "Plotting Range":
[959eb01]675                try:
[78312f7]676                    parts = value.split(':')
677                    q_range = parts[0] + " = " + parts[1] \
678                            + " = " + parts[2].split(",")[0]
[959eb01]679                    q_name = ("Q Range:    " + q_range)
680                    q_range = CENTRE % q_name
681                except Exception:
682                    msg = "While parsing 'Plotting Range: ...'\n"
683                    logger.error(msg + traceback.format_exc())
[78312f7]684
[959eb01]685        paramval = ""
686        for lines in param_string.split(":"):
687            line = lines.split(",")
688            if len(lines) > 0:
689                param = line[0]
690                param += " = " + line[1]
691                if len(line[2].split()) > 0 and not line[2].count("None"):
692                    param += " +- " + line[2]
693                if len(line[3].split()) > 0 and not line[3].count("None"):
694                    param += " " + line[3]
695                if not paramval.count(param):
696                    paramval += param + "\n"
697                    paramval_string += CENTRE % param + "\n"
698
699        text_string = "\n\n\n%s\n\n%s\n%s\n%s\n\n%s" % \
700                      (title, file, q_name, chi2, paramval)
701
702        title_name = self._check_html_format(title_name)
703        file_name = self._check_html_format(file_name)
704        title = self._check_html_format(title)
705
706        html_string = title_name + "\n" + file_name + \
707                                   "\n" + model_name + \
708                                   "\n" + q_range + \
709                                   "\n" + chi2_string + \
710                                   "\n" + ELINE + \
711                                   "\n" + paramval_string + \
712                                   "\n" + ELINE + \
[78312f7]713                                   "\n" + FEET_1 % title
[959eb01]714
715        return html_string, text_string, title
716
717    def _check_html_format(self, name):
718        """
719        Check string '%' for html format
720        """
721        if name.count('%'):
722            name = name.replace('%', '&#37')
723
724        return name
725
[78312f7]726    def report(self, fig_urls):
[959eb01]727        """
728        Invoke report dialog panel
729
730        : param figs: list of pylab figures [list]
731        """
732        # get the strings for report
[78312f7]733        html_str, text_str, title = self._get_report_string()
[959eb01]734        # Allow 2 figures to append
[47c44f0]735        #Constraining image width for OSX and linux, so print doesn't complain of being outside margins
736        if sys.platform == "win32":
737            image_links = [FEET_2%fig for fig in fig_urls]
738        else:
739            image_links = [FEET_2_unix%fig for fig in fig_urls]
[959eb01]740        # final report html strings
[78312f7]741        report_str = html_str + ELINE.join(image_links)
[5818dae]742        report_str += FEET_3
[78312f7]743        return report_str, text_str
[959eb01]744
745    def _to_xml_helper(self, thelist, element, newdoc):
746        """
747        Helper method to create xml file for saving state
748        """
749        for item in thelist:
750            sub_element = newdoc.createElement('parameter')
751            sub_element.setAttribute('name', str(item[1]))
752            sub_element.setAttribute('value', str(item[2]))
753            sub_element.setAttribute('selected_to_fit', str(item[0]))
754            sub_element.setAttribute('error_displayed', str(item[4][0]))
755            sub_element.setAttribute('error_value', str(item[4][1]))
756            sub_element.setAttribute('minimum_displayed', str(item[5][0]))
757            sub_element.setAttribute('minimum_value', str(item[5][1]))
758            sub_element.setAttribute('maximum_displayed', str(item[6][0]))
759            sub_element.setAttribute('maximum_value', str(item[6][1]))
760            sub_element.setAttribute('unit', str(item[7]))
761            element.appendChild(sub_element)
762
763    def to_xml(self, file="fitting_state.fitv", doc=None,
764               entry_node=None, batch_fit_state=None):
765        """
766        Writes the state of the fit panel to file, as XML.
767
768        Compatible with standalone writing, or appending to an
769        already existing XML document. In that case, the XML document is
770        required. An optional entry node in the XML document may also be given.
771
772        :param file: file to write to
773        :param doc: XML document object [optional]
774        :param entry_node: XML node within the XML document at which we
775                           will append the data [optional]
776        :param batch_fit_state: simultaneous fit state
777        """
778        # Check whether we have to write a standalone XML file
779        if doc is None:
780            impl = getDOMImplementation()
781            doc_type = impl.createDocumentType(FITTING_NODE_NAME, "1.0", "1.0")
782            newdoc = impl.createDocument(None, FITTING_NODE_NAME, doc_type)
783            top_element = newdoc.documentElement
784        else:
785            # We are appending to an existing document
786            newdoc = doc
787            try:
788                top_element = newdoc.createElement(FITTING_NODE_NAME)
[78312f7]789            except Exception:
[959eb01]790                string = etree.tostring(doc, pretty_print=True)
791                newdoc = parseString(string)
792                top_element = newdoc.createElement(FITTING_NODE_NAME)
793            if entry_node is None:
794                newdoc.documentElement.appendChild(top_element)
795            else:
796                try:
797                    entry_node.appendChild(top_element)
[78312f7]798                except Exception:
[959eb01]799                    node_name = entry_node.tag
800                    node_list = newdoc.getElementsByTagName(node_name)
801                    entry_node = node_list.item(0)
802                    entry_node.appendChild(top_element)
803
804        attr = newdoc.createAttribute("version")
[00f7ff1]805        attr.nodeValue = SASVIEW_VERSION
[959eb01]806        # attr.nodeValue = '1.0'
807        top_element.setAttributeNode(attr)
808
809        # File name
810        element = newdoc.createElement("filename")
811        if self.file is not None:
812            element.appendChild(newdoc.createTextNode(str(self.file)))
813        else:
814            element.appendChild(newdoc.createTextNode(str(file)))
815        top_element.appendChild(element)
816
817        element = newdoc.createElement("timestamp")
818        element.appendChild(newdoc.createTextNode(time.ctime(self.timestamp)))
819        attr = newdoc.createAttribute("epoch")
820        attr.nodeValue = str(self.timestamp)
821        element.setAttributeNode(attr)
822        top_element.appendChild(element)
823
824        # Inputs
825        inputs = newdoc.createElement("Attributes")
826        top_element.appendChild(inputs)
827
828        if self.data is not None and hasattr(self.data, "group_id"):
829            self.data_group_id = self.data.group_id
830        if self.data is not None and hasattr(self.data, "is_data"):
831            self.is_data = self.data.is_data
832        if self.data is not None:
833            self.data_name = self.data.name
834        if self.data is not None and hasattr(self.data, "id"):
835            self.data_id = self.data.id
836
837        for item in LIST_OF_DATA_ATTRIBUTES:
838            element = newdoc.createElement(item[0])
839            element.setAttribute(item[0], str(getattr(self, item[1])))
840            inputs.appendChild(element)
841
842        for item in LIST_OF_STATE_ATTRIBUTES:
843            element = newdoc.createElement(item[0])
844            element.setAttribute(item[0], str(getattr(self, item[1])))
845            inputs.appendChild(element)
846
847        # For self.values ={ disp_param_name: [vals,...],...}
848        # and for self.weights ={ disp_param_name: [weights,...],...}
849        for item in LIST_OF_MODEL_ATTRIBUTES:
850            element = newdoc.createElement(item[0])
851            value_list = getattr(self, item[1])
[574adc7]852            for key, value in value_list.items():
[959eb01]853                sub_element = newdoc.createElement(key)
854                sub_element.setAttribute('name', str(key))
855                for val in value:
856                    sub_element.appendChild(newdoc.createTextNode(str(val)))
857
858                element.appendChild(sub_element)
859            inputs.appendChild(element)
860
[ba8d326]861        # Create doc for the dictionary of self.disp_obj_dic
[959eb01]862        for tagname, varname, tagtype in DISPERSION_LIST:
863            element = newdoc.createElement(tagname)
864            value_list = getattr(self, varname)
[574adc7]865            for key, value in value_list.items():
[959eb01]866                sub_element = newdoc.createElement(key)
867                sub_element.setAttribute('name', str(key))
868                sub_element.setAttribute('value', str(value))
869                element.appendChild(sub_element)
870            inputs.appendChild(element)
871
872        for item in LIST_OF_STATE_PARAMETERS:
873            element = newdoc.createElement(item[0])
874            self._to_xml_helper(thelist=getattr(self, item[1]),
875                                element=element, newdoc=newdoc)
876            inputs.appendChild(element)
877
878        # Combined and Simultaneous Fit Parameters
879        if batch_fit_state is not None:
880            batch_combo = newdoc.createElement('simultaneous_fit')
881            top_element.appendChild(batch_combo)
882
883            # Simultaneous Fit Number For Linking Later
884            element = newdoc.createElement('sim_fit_number')
885            element.setAttribute('fit_number', str(batch_fit_state.fit_page_no))
886            batch_combo.appendChild(element)
887
888            # Save constraints
889            constraints = newdoc.createElement('constraints')
890            batch_combo.appendChild(constraints)
891            for constraint in batch_fit_state.constraints_list:
892                if constraint.model_cbox.GetValue() != "":
893                    # model_cbox, param_cbox, egal_txt, constraint,
894                    # btRemove, sizer
895                    doc_cons = newdoc.createElement('constraint')
896                    doc_cons.setAttribute('model_cbox',
897                                          str(constraint.model_cbox.GetValue()))
898                    doc_cons.setAttribute('param_cbox',
899                                          str(constraint.param_cbox.GetValue()))
900                    doc_cons.setAttribute('egal_txt',
901                                          str(constraint.egal_txt.GetLabel()))
902                    doc_cons.setAttribute('constraint',
903                                          str(constraint.constraint.GetValue()))
904                    constraints.appendChild(doc_cons)
905
906            # Save all models
907            models = newdoc.createElement('model_list')
908            batch_combo.appendChild(models)
909            for model in batch_fit_state.model_list:
910                doc_model = newdoc.createElement('model_list_item')
911                doc_model.setAttribute('checked', str(model[0].GetValue()))
[e090ba90]912                keys = list(model[1].keys())
[959eb01]913                doc_model.setAttribute('name', str(keys[0]))
914                values = model[1].get(keys[0])
915                doc_model.setAttribute('fit_number', str(model[2]))
916                doc_model.setAttribute('fit_page_source', str(model[3]))
917                doc_model.setAttribute('model_name', str(values.model.id))
918                models.appendChild(doc_model)
919
920            # Select All Checkbox
921            element = newdoc.createElement('select_all')
922            if batch_fit_state.select_all:
923                element.setAttribute('checked', 'True')
924            else:
925                element.setAttribute('checked', 'False')
926            batch_combo.appendChild(element)
927
928        # Save the file
929        if doc is None:
930            fd = open(file, 'w')
931            fd.write(newdoc.toprettyxml())
932            fd.close()
933            return None
934        else:
935            return newdoc
936
937    def _from_xml_helper(self, node, list):
938        """
939        Helper function to write state to xml
940        """
941        for item in node:
[ba8d326]942            name = item.get('name')
943            value = item.get('value')
944            selected_to_fit = (item.get('selected_to_fit') == "True")
945            error_displayed = (item.get('error_displayed') == "True")
946            error_value = item.get('error_value')
947            minimum_displayed = (item.get('minimum_displayed') == "True")
948            minimum_value = item.get('minimum_value')
949            maximum_displayed = (item.get('maximum_displayed') == "True")
950            maximum_value = item.get('maximum_value')
951            unit = item.get('unit')
[959eb01]952            list.append([selected_to_fit, name, value, "+/-",
953                         [error_displayed, error_value],
954                         [minimum_displayed, minimum_value],
955                         [maximum_displayed, maximum_value], unit])
956
957    def from_xml(self, file=None, node=None):
958        """
959        Load fitting state from a file
960
961        :param file: .fitv file
962        :param node: node of a XML document to read from
963        """
964        if file is not None:
965            msg = "PageState no longer supports non-CanSAS"
966            msg += " format for fitting files"
[574adc7]967            raise RuntimeError(msg)
[959eb01]968
969        if node.get('version'):
970            # Get the version for model conversion purposes
[e090ba90]971            x = re.sub(r'[^\d.]', '', node.get('version'))
[59873e1]972            self.version = tuple(int(e) for e in str.split(x, "."))
[959eb01]973            # The tuple must be at least 3 items long
974            while len(self.version) < 3:
975                ver_list = list(self.version)
976                ver_list.append(0)
977                self.version = tuple(ver_list)
978
979            # Get file name
980            entry = get_content('ns:filename', node)
[277257f]981            if entry is not None and entry.text:
[959eb01]982                self.file = entry.text.strip()
[277257f]983            else:
984                self.file = ''
[959eb01]985
986            # Get time stamp
987            entry = get_content('ns:timestamp', node)
988            if entry is not None and entry.get('epoch'):
989                try:
990                    self.timestamp = float(entry.get('epoch'))
[e090ba90]991                except Exception as exc:
[959eb01]992                    msg = "PageState.fromXML: Could not"
[e090ba90]993                    msg += " read timestamp\n %s" % exc
[959eb01]994                    logger.error(msg)
995
996            if entry is not None:
997                # Parse fitting attributes
998                entry = get_content('ns:Attributes', node)
999                for item in LIST_OF_DATA_ATTRIBUTES:
1000                    node = get_content('ns:%s' % item[0], entry)
1001                    setattr(self, item[0], parse_entry_helper(node, item))
1002
1003                dx_old_node = get_content('ns:%s' % 'dx_min', entry)
1004                for item in LIST_OF_STATE_ATTRIBUTES:
1005                    if item[0] == "dx_percent" and dx_old_node is not None:
1006                        dxmin = ["dx_min", "dx_min", "float"]
1007                        setattr(self, item[0], parse_entry_helper(dx_old_node,
1008                                                                  dxmin))
1009                        self.dx_old = True
1010                    else:
1011                        node = get_content('ns:%s' % item[0], entry)
1012                        setattr(self, item[0], parse_entry_helper(node, item))
1013
1014                for item in LIST_OF_STATE_PARAMETERS:
1015                    node = get_content("ns:%s" % item[0], entry)
1016                    self._from_xml_helper(node=node,
1017                                          list=getattr(self, item[1]))
1018
[ba8d326]1019                # Recover disp_obj_dict from xml file
1020                self.disp_obj_dict = {}
[959eb01]1021                for tagname, varname, tagtype in DISPERSION_LIST:
1022                    node = get_content("ns:%s" % tagname, entry)
1023                    for attr in node:
1024                        parameter = str(attr.get('name'))
1025                        value = attr.get('value')
1026                        if value.startswith("<"):
1027                            try:
1028                                # <path.to.NamedDistribution object/instance...>
1029                                cls_name = value[1:].split()[0].split('.')[-1]
1030                                cls = getattr(sasmodels.weights, cls_name)
1031                                value = cls.type
1032                            except Exception:
1033                                base = "unable to load distribution %r for %s"
[ba8d326]1034                                logger.error(base, value, parameter)
[959eb01]1035                                continue
[ba8d326]1036                        disp_obj_dict = getattr(self, varname)
1037                        disp_obj_dict[parameter] = value
[959eb01]1038
1039                # get self.values and self.weights dic. if exists
1040                for tagname, varname in LIST_OF_MODEL_ATTRIBUTES:
1041                    node = get_content("ns:%s" % tagname, entry)
1042                    dic = {}
1043                    value_list = []
1044                    for par in node:
1045                        name = par.get('name')
1046                        values = par.text.split()
1047                        # Get lines only with numbers
1048                        for line in values:
1049                            try:
1050                                val = float(line)
1051                                value_list.append(val)
1052                            except Exception:
1053                                # pass if line is empty (it happens)
1054                                msg = ("Error reading %r from %s %s\n"
1055                                       % (line, tagname, name))
1056                                logger.error(msg + traceback.format_exc())
1057                        dic[name] = np.array(value_list)
1058                    setattr(self, varname, dic)
1059
[ba8d326]1060class SimFitPageState(object):
[00f7ff1]1061    """
1062    State of the simultaneous fit page for saving purposes
1063    """
1064
1065    def __init__(self):
1066        # Sim Fit Page Number
1067        self.fit_page_no = None
1068        # Select all data
1069        self.select_all = False
1070        # Data sets sent to fit page
1071        self.model_list = []
1072        # Data sets to be fit
1073        self.model_to_fit = []
1074        # Number of constraints
1075        self.no_constraint = 0
1076        # Dictionary of constraints
1077        self.constraint_dict = {}
1078        # List of constraints
1079        self.constraints_list = []
[959eb01]1080
[ba8d326]1081    def __repr__(self):
1082        # TODO: should use __str__, not __repr__ (similarly for PageState)
1083        # TODO: could use a nicer representation
1084        repr = """\
1085fit page number : %(fit_page_no)s
1086select all : %(select_all)s
1087model_list : %(model_list)s
1088model to fit : %(model_to_fit)s
1089number of construsts : %(no_constraint)s
1090constraint dict : %(constraint_dict)s
1091constraints list : %(constraints_list)s
1092"""%self.__dict__
1093        return repr
1094
[959eb01]1095class Reader(CansasReader):
1096    """
1097    Class to load a .fitv fitting file
1098    """
1099    # File type
1100    type_name = "Fitting"
1101
1102    # Wildcards
1103    type = ["Fitting files (*.fitv)|*.fitv"
1104            "SASView file (*.svs)|*.svs"]
1105    # List of allowed extensions
1106    ext = ['.fitv', '.FITV', '.svs', 'SVS']
1107
1108    def __init__(self, call_back=None, cansas=True):
1109        CansasReader.__init__(self)
1110        """
1111        Initialize the call-back method to be called
1112        after we load a file
1113
1114        :param call_back: call-back method
1115        :param cansas:  True = files will be written/read in CanSAS format
1116                        False = write CanSAS format
1117
1118        """
1119        # Call back method to be executed after a file is read
1120        self.call_back = call_back
1121        # CanSAS format flag
1122        self.cansas = cansas
1123        self.state = None
1124        # batch fitting params for saving
1125        self.batchfit_params = []
1126
1127    def get_state(self):
1128        return self.state
1129
1130    def read(self, path):
1131        """
1132        Load a new P(r) inversion state from file
1133
1134        :param path: file path
1135
1136        """
1137        if self.cansas:
1138            return self._read_cansas(path)
1139
1140    def _parse_state(self, entry):
1141        """
1142        Read a fit result from an XML node
1143
1144        :param entry: XML node to read from
1145        :return: PageState object
1146        """
1147        # Create an empty state
1148        state = None
1149        # Locate the P(r) node
1150        try:
1151            nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
[ba8d326]1152                                namespaces=CANSAS_NS)
[959eb01]1153            if nodes:
1154                # Create an empty state
1155                state = PageState()
1156                state.from_xml(node=nodes[0])
1157
[ba8d326]1158        except Exception:
[959eb01]1159            logger.info("XML document does not contain fitting information.\n"
[ba8d326]1160                        + traceback.format_exc())
[959eb01]1161
1162        return state
1163
1164    def _parse_simfit_state(self, entry):
1165        """
1166        Parses the saved data for a simultaneous fit
1167        :param entry: XML object to read from
1168        :return: XML object for a simultaneous fit or None
1169        """
1170        nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
[ba8d326]1171                            namespaces=CANSAS_NS)
[959eb01]1172        if nodes:
1173            simfitstate = nodes[0].xpath('ns:simultaneous_fit',
[ba8d326]1174                                         namespaces=CANSAS_NS)
[959eb01]1175            if simfitstate:
1176                sim_fit_state = SimFitPageState()
1177                simfitstate_0 = simfitstate[0]
1178                all = simfitstate_0.xpath('ns:select_all',
[ba8d326]1179                                          namespaces=CANSAS_NS)
[959eb01]1180                atts = all[0].attrib
1181                checked = atts.get('checked')
1182                sim_fit_state.select_all = bool(checked)
1183                model_list = simfitstate_0.xpath('ns:model_list',
[ba8d326]1184                                                 namespaces=CANSAS_NS)
[959eb01]1185                model_list_items = model_list[0].xpath('ns:model_list_item',
[ba8d326]1186                                                       namespaces=CANSAS_NS)
[959eb01]1187                for model in model_list_items:
1188                    attrs = model.attrib
1189                    sim_fit_state.model_list.append(attrs)
1190
1191                constraints = simfitstate_0.xpath('ns:constraints',
[ba8d326]1192                                                  namespaces=CANSAS_NS)
[959eb01]1193                constraint_list = constraints[0].xpath('ns:constraint',
[ba8d326]1194                                                       namespaces=CANSAS_NS)
[959eb01]1195                for constraint in constraint_list:
1196                    attrs = constraint.attrib
1197                    sim_fit_state.constraints_list.append(attrs)
1198
1199                return sim_fit_state
1200            else:
1201                return None
1202
1203    def _parse_save_state_entry(self, dom):
1204        """
1205        Parse a SASentry
1206
1207        :param node: SASentry node
1208
1209        :return: Data1D/Data2D object
1210
1211        """
[ba8d326]1212        node = dom.xpath('ns:data_class', namespaces=CANSAS_NS)
[959eb01]1213        return_value, _ = self._parse_entry(dom)
1214        return return_value, _
1215
1216    def _read_cansas(self, path):
1217        """
1218        Load data and fitting information from a CanSAS XML file.
1219
1220        :param path: file path
1221        :return: Data1D object if a single SASentry was found,
1222                    or a list of Data1D objects if multiple entries were found,
1223                    or None of nothing was found
1224        :raise RuntimeError: when the file can't be opened
1225        :raise ValueError: when the length of the data vectors are inconsistent
1226        """
1227        output = []
1228        simfitstate = None
1229        basename = os.path.basename(path)
1230        root, extension = os.path.splitext(basename)
1231        ext = extension.lower()
1232        try:
1233            if os.path.isfile(path):
1234                if ext in self.ext or ext == '.xml':
1235                    tree = etree.parse(path, parser=etree.ETCompatXMLParser())
1236                    # Check the format version number
1237                    # Specifying the namespace will take care of the file
1238                    # format version
1239                    root = tree.getroot()
1240                    entry_list = root.xpath('ns:SASentry',
[ba8d326]1241                                            namespaces=CANSAS_NS)
[959eb01]1242                    for entry in entry_list:
1243                        fitstate = self._parse_state(entry)
1244                        # state could be None when .svs file is loaded
1245                        # in this case, skip appending to output
1246                        if fitstate is not None:
[1fa4f736]1247                            try:
1248                                sas_entry, _ = self._parse_save_state_entry(
1249                                    entry)
1250                            except:
1251                                raise
[959eb01]1252                            sas_entry.meta_data['fitstate'] = fitstate
1253                            sas_entry.filename = fitstate.file
1254                            output.append(sas_entry)
1255
1256            else:
1257                self.call_back(format=ext)
[574adc7]1258                raise RuntimeError("%s is not a file" % path)
[959eb01]1259
1260            # Return output consistent with the loader's api
1261            if len(output) == 0:
1262                self.call_back(state=None, datainfo=None, format=ext)
1263                return None
1264            else:
[ba8d326]1265                for data in output:
[959eb01]1266                    # Call back to post the new state
[ba8d326]1267                    state = data.meta_data['fitstate']
[959eb01]1268                    t = time.localtime(state.timestamp)
1269                    time_str = time.strftime("%b %d %H:%M", t)
1270                    # Check that no time stamp is already appended
1271                    max_char = state.file.find("[")
1272                    if max_char < 0:
1273                        max_char = len(state.file)
1274                    original_fname = state.file[0:max_char]
1275                    state.file = original_fname + ' [' + time_str + ']'
1276
1277                    if state is not None and state.is_data is not None:
[ba8d326]1278                        data.is_data = state.is_data
[959eb01]1279
[ba8d326]1280                    data.filename = state.file
1281                    state.data = data
1282                    state.data.name = data.filename  # state.data_name
[959eb01]1283                    state.data.id = state.data_id
1284                    if state.is_data is not None:
1285                        state.data.is_data = state.is_data
[ba8d326]1286                    if data.run_name is not None and len(data.run_name) != 0:
1287                        if isinstance(data.run_name, dict):
1288                            # Note: key order in dict is not guaranteed, so sort
[e090ba90]1289                            name = list(data.run_name.keys())[0]
[959eb01]1290                        else:
[ba8d326]1291                            name = data.run_name
[959eb01]1292                    else:
1293                        name = original_fname
1294                    state.data.group_id = name
1295                    state.version = fitstate.version
1296                    # store state in fitting
[ba8d326]1297                    self.call_back(state=state, datainfo=data, format=ext)
[959eb01]1298                    self.state = state
1299                simfitstate = self._parse_simfit_state(entry)
1300                if simfitstate is not None:
1301                    self.call_back(state=simfitstate)
1302
1303                return output
1304        except:
1305            self.call_back(format=ext)
1306            raise
1307
1308    def write(self, filename, datainfo=None, fitstate=None):
1309        """
1310        Write the content of a Data1D as a CanSAS XML file only for standalone
1311
1312        :param filename: name of the file to write
1313        :param datainfo: Data1D object
1314        :param fitstate: PageState object
1315
1316        """
1317        # Sanity check
1318        if self.cansas:
1319            # Add fitting information to the XML document
1320            doc = self.write_toXML(datainfo, fitstate)
1321            # Write the XML document
1322        else:
1323            doc = fitstate.to_xml(file=filename)
1324
1325        # Save the document no matter the type
1326        fd = open(filename, 'w')
1327        fd.write(doc.toprettyxml())
1328        fd.close()
1329
1330    def write_toXML(self, datainfo=None, state=None, batchfit=None):
1331        """
1332        Write toXML, a helper for write(),
1333        could be used by guimanager._on_save()
1334
1335        : return: xml doc
1336        """
1337
1338        self.batchfit_params = batchfit
1339        if state.data is None or not state.data.is_data:
1340            return None
1341        # make sure title and data run are filled.
1342        if state.data.title is None or state.data.title == '':
1343            state.data.title = state.data.name
1344        if state.data.run_name is None or state.data.run_name == {}:
1345            state.data.run = [str(state.data.name)]
1346            state.data.run_name[0] = state.data.name
1347
1348        data = state.data
1349        doc, sasentry = self._to_xml_doc(data)
1350
1351        if state is not None:
1352            doc = state.to_xml(doc=doc, file=data.filename, entry_node=sasentry,
1353                               batch_fit_state=self.batchfit_params)
1354
1355        return doc
1356
[78312f7]1357# Simple html report template
[959eb01]1358HEADER = "<html>\n"
1359HEADER += "<head>\n"
1360HEADER += "<meta http-equiv=Content-Type content='text/html; "
1361HEADER += "charset=windows-1252'> \n"
1362HEADER += "<meta name=Generator >\n"
1363HEADER += "</head>\n"
1364HEADER += "<body lang=EN-US>\n"
1365HEADER += "<div class=WordSection1>\n"
1366HEADER += "<p class=MsoNormal><b><span ><center><font size='4' >"
1367HEADER += "%s</font></center></span></center></b></p>"
1368HEADER += "<p class=MsoNormal>&nbsp;</p>"
1369PARA = "<p class=MsoNormal><font size='4' > %s \n"
1370PARA += "</font></p>"
1371CENTRE = "<p class=MsoNormal><center><font size='4' > %s \n"
1372CENTRE += "</font></center></p>"
1373FEET_1 = \
1374"""
1375<p class=MsoNormal>&nbsp;</p>
1376<br>
1377<p class=MsoNormal><b><span ><center> <font size='4' > Graph
1378</font></span></center></b></p>
1379<p class=MsoNormal>&nbsp;</p>
1380<center>
1381<br><font size='4' >Model Computation</font>
1382<br><font size='4' >Data: "%s"</font><br>
1383"""
1384FEET_2 = \
[3b070a0]1385"""<img src="%s"></img>
[959eb01]1386"""
[47c44f0]1387FEET_2_unix = \
1388"""<img src="%s" width="540"></img>
1389"""
[959eb01]1390FEET_3 = \
[78312f7]1391"""</center>
[959eb01]1392</div>
1393</body>
1394</html>
1395"""
[78312f7]1396ELINE = """<p class=MsoNormal>&nbsp;</p>
1397"""
Note: See TracBrowser for help on using the repository browser.