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

ticket-1094-headless
Last change on this file since 3f89c0e was cb44d66, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

Merge branch 'master' into ticket-1094-headless

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