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

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

Merge branch 'master' into ticket-1094-headless

  • Property mode set to 100644
File size: 60.5 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 re
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
156from xml.dom.minidom import getDOMImplementation
157from lxml import etree
158
159from sasmodels import convert
160import sasmodels.weights
161
162from sas.sasview import __version__ as SASVIEW_VERSION
163
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'
174CANSAS_NS = {"ns": "cansas1d/1.0"}
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
217DISPERSION_LIST = [["disp_obj_dict", "disp_obj_dict", "string"]]
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    """
276    def __init__(self, model=None, data=None):
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
288        self.disp_obj_dict = {}
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
402        self.version = (1, 0, 0)
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
412        obj = PageState(model=model)
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
445        if len(self.disp_obj_dict) > 0:
446            for k, v in self.disp_obj_dict.items():
447                obj.disp_obj_dict[k] = v
448        if len(self.disp_cb_dict) > 0:
449            for k, v in self.disp_cb_dict.items():
450                obj.disp_cb_dict[k] = v
451        if len(self.values) > 0:
452            for k, v in self.values.items():
453                obj.values[k] = v
454        if len(self.weights) > 0:
455            for k, v in self.weights.items():
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
480        for name, state in self.saved_states.items():
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 = []
563        for name, info in params.items():
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)
608            for key, value in str_params.items():
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)
641        time_str = time.strftime("%b %d %Y %H:%M:%S ", t)
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:
690            is_2D = (self.data.__class__.__name__ == "Data2D")
691            if not is_2D:
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
713    def _get_report_string(self):
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__()
728        fixed_parameter = False
729        lines = strings.split('\n')
730        # get all string values from __str__()
731        for line in lines:
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":
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 + ','
751            elif name == "value":
752                param_string += value + ','
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":
757                if fixed_parameter:
758                    param_string += '(fixed),'
759                else:
760                    param_string += value + ','
761            elif name == "parameter unit":
762                param_string += value + ':'
763            elif name == "Value of Chisqr":
764                chi2 = ("Chi2/Npts = " + value)
765                chi2_string = CENTRE % chi2
766            elif name == "Title":
767                if len(value.strip()) == 0:
768                    continue
769                title = value + " [" + repo_time + "]"
770                title_name = HEADER % title
771            elif name == "data":
772                try:
773                    # parsing "data : File:     filename [mmm dd hh:mm]"
774                    name = value.split(':', 1)[1].strip()
775                    file_value = "File name:" + name
776                    #Truncating string so print doesn't complain of being outside margins
777                    if sys.platform != "win32":
778                        MAX_STRING_LENGHT = 50
779                        if len(file_value) > MAX_STRING_LENGHT:
780                            file_value = "File name:.."+file_value[-MAX_STRING_LENGHT+10:]
781                    file_name = CENTRE % file_value
782                    if len(title) == 0:
783                        title = name + " [" + repo_time + "]"
784                        title_name = HEADER % title
785                except Exception:
786                    msg = "While parsing 'data: ...'\n"
787                    logger.error(msg + traceback.format_exc())
788            elif name == "model name":
789                try:
790                    modelname = "Model name:" + value
791                except Exception:
792                    modelname = "Model name:" + " NAN"
793                model_name = CENTRE % modelname
794
795            elif name == "Plotting Range":
796                try:
797                    parts = value.split(':')
798                    q_range = parts[0] + " = " + parts[1] \
799                            + " = " + parts[2].split(",")[0]
800                    q_name = ("Q Range:    " + q_range)
801                    q_range = CENTRE % q_name
802                except Exception:
803                    msg = "While parsing 'Plotting Range: ...'\n"
804                    logger.error(msg + traceback.format_exc())
805
806        paramval = ""
807        for lines in param_string.split(":"):
808            line = lines.split(",")
809            if len(lines) > 0:
810                param = line[0]
811                param += " = " + line[1]
812                if len(line[2].split()) > 0 and not line[2].count("None"):
813                    param += " +- " + line[2]
814                if len(line[3].split()) > 0 and not line[3].count("None"):
815                    param += " " + line[3]
816                if not paramval.count(param):
817                    paramval += param + "\n"
818                    paramval_string += CENTRE % param + "\n"
819
820        text_string = "\n\n\n%s\n\n%s\n%s\n%s\n\n%s" % \
821                      (title, file, q_name, chi2, paramval)
822
823        title_name = self._check_html_format(title_name)
824        file_name = self._check_html_format(file_name)
825        title = self._check_html_format(title)
826
827        html_string = title_name + "\n" + file_name + \
828                                   "\n" + model_name + \
829                                   "\n" + q_range + \
830                                   "\n" + chi2_string + \
831                                   "\n" + ELINE + \
832                                   "\n" + paramval_string + \
833                                   "\n" + ELINE + \
834                                   "\n" + FEET_1 % title
835
836        return html_string, text_string, title
837
838    def _check_html_format(self, name):
839        """
840        Check string '%' for html format
841        """
842        if name.count('%'):
843            name = name.replace('%', '&#37')
844
845        return name
846
847    def report(self, fig_urls):
848        """
849        Invoke report dialog panel
850
851        : param figs: list of pylab figures [list]
852        """
853        # get the strings for report
854        html_str, text_str, title = self._get_report_string()
855        # Allow 2 figures to append
856        #Constraining image width for OSX and linux, so print doesn't complain of being outside margins
857        if sys.platform == "win32":
858            image_links = [FEET_2%fig for fig in fig_urls]
859        else:
860            image_links = [FEET_2_unix%fig for fig in fig_urls]
861        # final report html strings
862        report_str = html_str + ELINE.join(image_links)
863        report_str += FEET_3
864        return report_str, text_str
865
866    def _to_xml_helper(self, thelist, element, newdoc):
867        """
868        Helper method to create xml file for saving state
869        """
870        for item in thelist:
871            sub_element = newdoc.createElement('parameter')
872            sub_element.setAttribute('name', str(item[1]))
873            sub_element.setAttribute('value', str(item[2]))
874            sub_element.setAttribute('selected_to_fit', str(item[0]))
875            sub_element.setAttribute('error_displayed', str(item[4][0]))
876            sub_element.setAttribute('error_value', str(item[4][1]))
877            sub_element.setAttribute('minimum_displayed', str(item[5][0]))
878            sub_element.setAttribute('minimum_value', str(item[5][1]))
879            sub_element.setAttribute('maximum_displayed', str(item[6][0]))
880            sub_element.setAttribute('maximum_value', str(item[6][1]))
881            sub_element.setAttribute('unit', str(item[7]))
882            element.appendChild(sub_element)
883
884    def to_xml(self, file="fitting_state.fitv", doc=None,
885               entry_node=None, batch_fit_state=None):
886        """
887        Writes the state of the fit panel to file, as XML.
888
889        Compatible with standalone writing, or appending to an
890        already existing XML document. In that case, the XML document is
891        required. An optional entry node in the XML document may also be given.
892
893        :param file: file to write to
894        :param doc: XML document object [optional]
895        :param entry_node: XML node within the XML document at which we
896                           will append the data [optional]
897        :param batch_fit_state: simultaneous fit state
898        """
899        # Check whether we have to write a standalone XML file
900        if doc is None:
901            impl = getDOMImplementation()
902            doc_type = impl.createDocumentType(FITTING_NODE_NAME, "1.0", "1.0")
903            newdoc = impl.createDocument(None, FITTING_NODE_NAME, doc_type)
904            top_element = newdoc.documentElement
905        else:
906            # We are appending to an existing document
907            newdoc = doc
908            try:
909                top_element = newdoc.createElement(FITTING_NODE_NAME)
910            except Exception:
911                string = etree.tostring(doc, pretty_print=True)
912                newdoc = parseString(string)
913                top_element = newdoc.createElement(FITTING_NODE_NAME)
914            if entry_node is None:
915                newdoc.documentElement.appendChild(top_element)
916            else:
917                try:
918                    entry_node.appendChild(top_element)
919                except Exception:
920                    node_name = entry_node.tag
921                    node_list = newdoc.getElementsByTagName(node_name)
922                    entry_node = node_list.item(0)
923                    entry_node.appendChild(top_element)
924
925        attr = newdoc.createAttribute("version")
926        attr.nodeValue = SASVIEW_VERSION
927        # attr.nodeValue = '1.0'
928        top_element.setAttributeNode(attr)
929
930        # File name
931        element = newdoc.createElement("filename")
932        if self.file is not None:
933            element.appendChild(newdoc.createTextNode(str(self.file)))
934        else:
935            element.appendChild(newdoc.createTextNode(str(file)))
936        top_element.appendChild(element)
937
938        element = newdoc.createElement("timestamp")
939        element.appendChild(newdoc.createTextNode(time.ctime(self.timestamp)))
940        attr = newdoc.createAttribute("epoch")
941        attr.nodeValue = str(self.timestamp)
942        element.setAttributeNode(attr)
943        top_element.appendChild(element)
944
945        # Inputs
946        inputs = newdoc.createElement("Attributes")
947        top_element.appendChild(inputs)
948
949        if self.data is not None and hasattr(self.data, "group_id"):
950            self.data_group_id = self.data.group_id
951        if self.data is not None and hasattr(self.data, "is_data"):
952            self.is_data = self.data.is_data
953        if self.data is not None:
954            self.data_name = self.data.name
955        if self.data is not None and hasattr(self.data, "id"):
956            self.data_id = self.data.id
957
958        for item in LIST_OF_DATA_ATTRIBUTES:
959            element = newdoc.createElement(item[0])
960            element.setAttribute(item[0], str(getattr(self, item[1])))
961            inputs.appendChild(element)
962
963        for item in LIST_OF_STATE_ATTRIBUTES:
964            element = newdoc.createElement(item[0])
965            element.setAttribute(item[0], str(getattr(self, item[1])))
966            inputs.appendChild(element)
967
968        # For self.values ={ disp_param_name: [vals,...],...}
969        # and for self.weights ={ disp_param_name: [weights,...],...}
970        for item in LIST_OF_MODEL_ATTRIBUTES:
971            element = newdoc.createElement(item[0])
972            value_list = getattr(self, item[1])
973            for key, value in value_list.items():
974                sub_element = newdoc.createElement(key)
975                sub_element.setAttribute('name', str(key))
976                for val in value:
977                    sub_element.appendChild(newdoc.createTextNode(str(val)))
978
979                element.appendChild(sub_element)
980            inputs.appendChild(element)
981
982        # Create doc for the dictionary of self.disp_obj_dic
983        for tagname, varname, tagtype in DISPERSION_LIST:
984            element = newdoc.createElement(tagname)
985            value_list = getattr(self, varname)
986            for key, value in value_list.items():
987                sub_element = newdoc.createElement(key)
988                sub_element.setAttribute('name', str(key))
989                sub_element.setAttribute('value', str(value))
990                element.appendChild(sub_element)
991            inputs.appendChild(element)
992
993        for item in LIST_OF_STATE_PARAMETERS:
994            element = newdoc.createElement(item[0])
995            self._to_xml_helper(thelist=getattr(self, item[1]),
996                                element=element, newdoc=newdoc)
997            inputs.appendChild(element)
998
999        # Combined and Simultaneous Fit Parameters
1000        if batch_fit_state is not None:
1001            batch_combo = newdoc.createElement('simultaneous_fit')
1002            top_element.appendChild(batch_combo)
1003
1004            # Simultaneous Fit Number For Linking Later
1005            element = newdoc.createElement('sim_fit_number')
1006            element.setAttribute('fit_number', str(batch_fit_state.fit_page_no))
1007            batch_combo.appendChild(element)
1008
1009            # Save constraints
1010            constraints = newdoc.createElement('constraints')
1011            batch_combo.appendChild(constraints)
1012            for constraint in batch_fit_state.constraints_list:
1013                if constraint.model_cbox.GetValue() != "":
1014                    # model_cbox, param_cbox, egal_txt, constraint,
1015                    # btRemove, sizer
1016                    doc_cons = newdoc.createElement('constraint')
1017                    doc_cons.setAttribute('model_cbox',
1018                                          str(constraint.model_cbox.GetValue()))
1019                    doc_cons.setAttribute('param_cbox',
1020                                          str(constraint.param_cbox.GetValue()))
1021                    doc_cons.setAttribute('egal_txt',
1022                                          str(constraint.egal_txt.GetLabel()))
1023                    doc_cons.setAttribute('constraint',
1024                                          str(constraint.constraint.GetValue()))
1025                    constraints.appendChild(doc_cons)
1026
1027            # Save all models
1028            models = newdoc.createElement('model_list')
1029            batch_combo.appendChild(models)
1030            for model in batch_fit_state.model_list:
1031                doc_model = newdoc.createElement('model_list_item')
1032                doc_model.setAttribute('checked', str(model[0].GetValue()))
1033                keys = model[1].keys()
1034                doc_model.setAttribute('name', str(keys[0]))
1035                values = model[1].get(keys[0])
1036                doc_model.setAttribute('fit_number', str(model[2]))
1037                doc_model.setAttribute('fit_page_source', str(model[3]))
1038                doc_model.setAttribute('model_name', str(values.model.id))
1039                models.appendChild(doc_model)
1040
1041            # Select All Checkbox
1042            element = newdoc.createElement('select_all')
1043            if batch_fit_state.select_all:
1044                element.setAttribute('checked', 'True')
1045            else:
1046                element.setAttribute('checked', 'False')
1047            batch_combo.appendChild(element)
1048
1049        # Save the file
1050        if doc is None:
1051            fd = open(file, 'w')
1052            fd.write(newdoc.toprettyxml())
1053            fd.close()
1054            return None
1055        else:
1056            return newdoc
1057
1058    def _from_xml_helper(self, node, list):
1059        """
1060        Helper function to write state to xml
1061        """
1062        for item in node:
1063            name = item.get('name')
1064            value = item.get('value')
1065            selected_to_fit = (item.get('selected_to_fit') == "True")
1066            error_displayed = (item.get('error_displayed') == "True")
1067            error_value = item.get('error_value')
1068            minimum_displayed = (item.get('minimum_displayed') == "True")
1069            minimum_value = item.get('minimum_value')
1070            maximum_displayed = (item.get('maximum_displayed') == "True")
1071            maximum_value = item.get('maximum_value')
1072            unit = item.get('unit')
1073            list.append([selected_to_fit, name, value, "+/-",
1074                         [error_displayed, error_value],
1075                         [minimum_displayed, minimum_value],
1076                         [maximum_displayed, maximum_value], unit])
1077
1078    def from_xml(self, file=None, node=None):
1079        """
1080        Load fitting state from a file
1081
1082        :param file: .fitv file
1083        :param node: node of a XML document to read from
1084        """
1085        if file is not None:
1086            msg = "PageState no longer supports non-CanSAS"
1087            msg += " format for fitting files"
1088            raise RuntimeError(msg)
1089
1090        if node.get('version'):
1091            # Get the version for model conversion purposes
1092            x = re.sub('[^\d.]', '', node.get('version'))
1093            self.version = tuple(int(e) for e in str.split(x, "."))
1094            # The tuple must be at least 3 items long
1095            while len(self.version) < 3:
1096                ver_list = list(self.version)
1097                ver_list.append(0)
1098                self.version = tuple(ver_list)
1099
1100            # Get file name
1101            entry = get_content('ns:filename', node)
1102            if entry is not None and entry.text:
1103                self.file = entry.text.strip()
1104            else:
1105                self.file = ''
1106
1107            # Get time stamp
1108            entry = get_content('ns:timestamp', node)
1109            if entry is not None and entry.get('epoch'):
1110                try:
1111                    self.timestamp = float(entry.get('epoch'))
1112                except Exception:
1113                    msg = "PageState.fromXML: Could not"
1114                    msg += " read timestamp\n %s" % sys.exc_value
1115                    logger.error(msg)
1116
1117            if entry is not None:
1118                # Parse fitting attributes
1119                entry = get_content('ns:Attributes', node)
1120                for item in LIST_OF_DATA_ATTRIBUTES:
1121                    node = get_content('ns:%s' % item[0], entry)
1122                    setattr(self, item[0], parse_entry_helper(node, item))
1123
1124                dx_old_node = get_content('ns:%s' % 'dx_min', entry)
1125                for item in LIST_OF_STATE_ATTRIBUTES:
1126                    if item[0] == "dx_percent" and dx_old_node is not None:
1127                        dxmin = ["dx_min", "dx_min", "float"]
1128                        setattr(self, item[0], parse_entry_helper(dx_old_node,
1129                                                                  dxmin))
1130                        self.dx_old = True
1131                    else:
1132                        node = get_content('ns:%s' % item[0], entry)
1133                        setattr(self, item[0], parse_entry_helper(node, item))
1134
1135                for item in LIST_OF_STATE_PARAMETERS:
1136                    node = get_content("ns:%s" % item[0], entry)
1137                    self._from_xml_helper(node=node,
1138                                          list=getattr(self, item[1]))
1139
1140                # Recover disp_obj_dict from xml file
1141                self.disp_obj_dict = {}
1142                for tagname, varname, tagtype in DISPERSION_LIST:
1143                    node = get_content("ns:%s" % tagname, entry)
1144                    for attr in node:
1145                        parameter = str(attr.get('name'))
1146                        value = attr.get('value')
1147                        if value.startswith("<"):
1148                            try:
1149                                # <path.to.NamedDistribution object/instance...>
1150                                cls_name = value[1:].split()[0].split('.')[-1]
1151                                cls = getattr(sasmodels.weights, cls_name)
1152                                value = cls.type
1153                            except Exception:
1154                                base = "unable to load distribution %r for %s"
1155                                logger.error(base, value, parameter)
1156                                continue
1157                        disp_obj_dict = getattr(self, varname)
1158                        disp_obj_dict[parameter] = value
1159
1160                # get self.values and self.weights dic. if exists
1161                for tagname, varname in LIST_OF_MODEL_ATTRIBUTES:
1162                    node = get_content("ns:%s" % tagname, entry)
1163                    dic = {}
1164                    value_list = []
1165                    for par in node:
1166                        name = par.get('name')
1167                        values = par.text.split()
1168                        # Get lines only with numbers
1169                        for line in values:
1170                            try:
1171                                val = float(line)
1172                                value_list.append(val)
1173                            except Exception:
1174                                # pass if line is empty (it happens)
1175                                msg = ("Error reading %r from %s %s\n"
1176                                       % (line, tagname, name))
1177                                logger.error(msg + traceback.format_exc())
1178                        dic[name] = np.array(value_list)
1179                    setattr(self, varname, dic)
1180
1181class SimFitPageState(object):
1182    """
1183    State of the simultaneous fit page for saving purposes
1184    """
1185
1186    def __init__(self):
1187        # Sim Fit Page Number
1188        self.fit_page_no = None
1189        # Select all data
1190        self.select_all = False
1191        # Data sets sent to fit page
1192        self.model_list = []
1193        # Data sets to be fit
1194        self.model_to_fit = []
1195        # Number of constraints
1196        self.no_constraint = 0
1197        # Dictionary of constraints
1198        self.constraint_dict = {}
1199        # List of constraints
1200        self.constraints_list = []
1201
1202    def __repr__(self):
1203        # TODO: should use __str__, not __repr__ (similarly for PageState)
1204        # TODO: could use a nicer representation
1205        repr = """\
1206fit page number : %(fit_page_no)s
1207select all : %(select_all)s
1208model_list : %(model_list)s
1209model to fit : %(model_to_fit)s
1210number of construsts : %(no_constraint)s
1211constraint dict : %(constraint_dict)s
1212constraints list : %(constraints_list)s
1213"""%self.__dict__
1214        return repr
1215
1216class Reader(CansasReader):
1217    """
1218    Class to load a .fitv fitting file
1219    """
1220    # File type
1221    type_name = "Fitting"
1222
1223    # Wildcards
1224    type = ["Fitting files (*.fitv)|*.fitv"
1225            "SASView file (*.svs)|*.svs"]
1226    # List of allowed extensions
1227    ext = ['.fitv', '.FITV', '.svs', 'SVS']
1228
1229    def __init__(self, call_back=None, cansas=True):
1230        CansasReader.__init__(self)
1231        """
1232        Initialize the call-back method to be called
1233        after we load a file
1234
1235        :param call_back: call-back method
1236        :param cansas:  True = files will be written/read in CanSAS format
1237                        False = write CanSAS format
1238
1239        """
1240        # Call back method to be executed after a file is read
1241        self.call_back = call_back
1242        # CanSAS format flag
1243        self.cansas = cansas
1244        self.state = None
1245        # batch fitting params for saving
1246        self.batchfit_params = []
1247
1248    def get_state(self):
1249        return self.state
1250
1251    def read(self, path):
1252        """
1253        Load a new P(r) inversion state from file
1254
1255        :param path: file path
1256
1257        """
1258        if self.cansas:
1259            return self._read_cansas(path)
1260
1261    def _parse_state(self, entry):
1262        """
1263        Read a fit result from an XML node
1264
1265        :param entry: XML node to read from
1266        :return: PageState object
1267        """
1268        # Create an empty state
1269        state = None
1270        # Locate the P(r) node
1271        try:
1272            nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
1273                                namespaces=CANSAS_NS)
1274            if nodes:
1275                # Create an empty state
1276                state = PageState()
1277                state.from_xml(node=nodes[0])
1278
1279        except Exception:
1280            logger.info("XML document does not contain fitting information.\n"
1281                        + traceback.format_exc())
1282
1283        return state
1284
1285    def _parse_simfit_state(self, entry):
1286        """
1287        Parses the saved data for a simultaneous fit
1288        :param entry: XML object to read from
1289        :return: XML object for a simultaneous fit or None
1290        """
1291        nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
1292                            namespaces=CANSAS_NS)
1293        if nodes:
1294            simfitstate = nodes[0].xpath('ns:simultaneous_fit',
1295                                         namespaces=CANSAS_NS)
1296            if simfitstate:
1297                sim_fit_state = SimFitPageState()
1298                simfitstate_0 = simfitstate[0]
1299                all = simfitstate_0.xpath('ns:select_all',
1300                                          namespaces=CANSAS_NS)
1301                atts = all[0].attrib
1302                checked = atts.get('checked')
1303                sim_fit_state.select_all = bool(checked)
1304                model_list = simfitstate_0.xpath('ns:model_list',
1305                                                 namespaces=CANSAS_NS)
1306                model_list_items = model_list[0].xpath('ns:model_list_item',
1307                                                       namespaces=CANSAS_NS)
1308                for model in model_list_items:
1309                    attrs = model.attrib
1310                    sim_fit_state.model_list.append(attrs)
1311
1312                constraints = simfitstate_0.xpath('ns:constraints',
1313                                                  namespaces=CANSAS_NS)
1314                constraint_list = constraints[0].xpath('ns:constraint',
1315                                                       namespaces=CANSAS_NS)
1316                for constraint in constraint_list:
1317                    attrs = constraint.attrib
1318                    sim_fit_state.constraints_list.append(attrs)
1319
1320                return sim_fit_state
1321            else:
1322                return None
1323
1324    def _parse_save_state_entry(self, dom):
1325        """
1326        Parse a SASentry
1327
1328        :param node: SASentry node
1329
1330        :return: Data1D/Data2D object
1331
1332        """
1333        node = dom.xpath('ns:data_class', namespaces=CANSAS_NS)
1334        return_value, _ = self._parse_entry(dom)
1335        return return_value, _
1336
1337    def _read_cansas(self, path):
1338        """
1339        Load data and fitting information from a CanSAS XML file.
1340
1341        :param path: file path
1342        :return: Data1D object if a single SASentry was found,
1343                    or a list of Data1D objects if multiple entries were found,
1344                    or None of nothing was found
1345        :raise RuntimeError: when the file can't be opened
1346        :raise ValueError: when the length of the data vectors are inconsistent
1347        """
1348        output = []
1349        simfitstate = None
1350        basename = os.path.basename(path)
1351        root, extension = os.path.splitext(basename)
1352        ext = extension.lower()
1353        try:
1354            if os.path.isfile(path):
1355                if ext in self.ext or ext == '.xml':
1356                    tree = etree.parse(path, parser=etree.ETCompatXMLParser())
1357                    # Check the format version number
1358                    # Specifying the namespace will take care of the file
1359                    # format version
1360                    root = tree.getroot()
1361                    entry_list = root.xpath('ns:SASentry',
1362                                            namespaces=CANSAS_NS)
1363                    for entry in entry_list:
1364                        fitstate = self._parse_state(entry)
1365                        # state could be None when .svs file is loaded
1366                        # in this case, skip appending to output
1367                        if fitstate is not None:
1368                            try:
1369                                sas_entry, _ = self._parse_save_state_entry(
1370                                    entry)
1371                            except:
1372                                raise
1373                            sas_entry.meta_data['fitstate'] = fitstate
1374                            sas_entry.filename = fitstate.file
1375                            output.append(sas_entry)
1376
1377            else:
1378                self.call_back(format=ext)
1379                raise RuntimeError("%s is not a file" % path)
1380
1381            # Return output consistent with the loader's api
1382            if len(output) == 0:
1383                self.call_back(state=None, datainfo=None, format=ext)
1384                return None
1385            else:
1386                for data in output:
1387                    # Call back to post the new state
1388                    state = data.meta_data['fitstate']
1389                    t = time.localtime(state.timestamp)
1390                    time_str = time.strftime("%b %d %H:%M", t)
1391                    # Check that no time stamp is already appended
1392                    max_char = state.file.find("[")
1393                    if max_char < 0:
1394                        max_char = len(state.file)
1395                    original_fname = state.file[0:max_char]
1396                    state.file = original_fname + ' [' + time_str + ']'
1397
1398                    if state is not None and state.is_data is not None:
1399                        data.is_data = state.is_data
1400
1401                    data.filename = state.file
1402                    state.data = data
1403                    state.data.name = data.filename  # state.data_name
1404                    state.data.id = state.data_id
1405                    if state.is_data is not None:
1406                        state.data.is_data = state.is_data
1407                    if data.run_name is not None and len(data.run_name) != 0:
1408                        if isinstance(data.run_name, dict):
1409                            # Note: key order in dict is not guaranteed, so sort
1410                            name = data.run_name.keys()[0]
1411                        else:
1412                            name = data.run_name
1413                    else:
1414                        name = original_fname
1415                    state.data.group_id = name
1416                    state.version = fitstate.version
1417                    # store state in fitting
1418                    self.call_back(state=state, datainfo=data, format=ext)
1419                    self.state = state
1420                simfitstate = self._parse_simfit_state(entry)
1421                if simfitstate is not None:
1422                    self.call_back(state=simfitstate)
1423
1424                return output
1425        except:
1426            self.call_back(format=ext)
1427            raise
1428
1429    def write(self, filename, datainfo=None, fitstate=None):
1430        """
1431        Write the content of a Data1D as a CanSAS XML file only for standalone
1432
1433        :param filename: name of the file to write
1434        :param datainfo: Data1D object
1435        :param fitstate: PageState object
1436
1437        """
1438        # Sanity check
1439        if self.cansas:
1440            # Add fitting information to the XML document
1441            doc = self.write_toXML(datainfo, fitstate)
1442            # Write the XML document
1443        else:
1444            doc = fitstate.to_xml(file=filename)
1445
1446        # Save the document no matter the type
1447        fd = open(filename, 'w')
1448        fd.write(doc.toprettyxml())
1449        fd.close()
1450
1451    def write_toXML(self, datainfo=None, state=None, batchfit=None):
1452        """
1453        Write toXML, a helper for write(),
1454        could be used by guimanager._on_save()
1455
1456        : return: xml doc
1457        """
1458
1459        self.batchfit_params = batchfit
1460        if state.data is None or not state.data.is_data:
1461            return None
1462        # make sure title and data run are filled.
1463        if state.data.title is None or state.data.title == '':
1464            state.data.title = state.data.name
1465        if state.data.run_name is None or state.data.run_name == {}:
1466            state.data.run = [str(state.data.name)]
1467            state.data.run_name[0] = state.data.name
1468
1469        data = state.data
1470        doc, sasentry = self._to_xml_doc(data)
1471
1472        if state is not None:
1473            doc = state.to_xml(doc=doc, file=data.filename, entry_node=sasentry,
1474                               batch_fit_state=self.batchfit_params)
1475
1476        return doc
1477
1478# Simple html report template
1479HEADER = "<html>\n"
1480HEADER += "<head>\n"
1481HEADER += "<meta http-equiv=Content-Type content='text/html; "
1482HEADER += "charset=windows-1252'> \n"
1483HEADER += "<meta name=Generator >\n"
1484HEADER += "</head>\n"
1485HEADER += "<body lang=EN-US>\n"
1486HEADER += "<div class=WordSection1>\n"
1487HEADER += "<p class=MsoNormal><b><span ><center><font size='4' >"
1488HEADER += "%s</font></center></span></center></b></p>"
1489HEADER += "<p class=MsoNormal>&nbsp;</p>"
1490PARA = "<p class=MsoNormal><font size='4' > %s \n"
1491PARA += "</font></p>"
1492CENTRE = "<p class=MsoNormal><center><font size='4' > %s \n"
1493CENTRE += "</font></center></p>"
1494FEET_1 = \
1495"""
1496<p class=MsoNormal>&nbsp;</p>
1497<br>
1498<p class=MsoNormal><b><span ><center> <font size='4' > Graph
1499</font></span></center></b></p>
1500<p class=MsoNormal>&nbsp;</p>
1501<center>
1502<br><font size='4' >Model Computation</font>
1503<br><font size='4' >Data: "%s"</font><br>
1504"""
1505FEET_2 = \
1506"""<img src="%s"></img>
1507"""
1508FEET_2_unix = \
1509"""<img src="%s" width="540"></img>
1510"""
1511FEET_3 = \
1512"""</center>
1513</div>
1514</body>
1515</html>
1516"""
1517ELINE = """<p class=MsoNormal>&nbsp;</p>
1518"""
Note: See TracBrowser for help on using the repository browser.