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

ticket-1094-headless
Last change on this file since e870702 was aca687a, checked in by Paul Kienzle <pkienzle@…>, 7 years ago

document PageState? attributes; remove unused is_2D attribute

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