source: sasview/src/sas/sasgui/perspectives/fitting/pagestate.py @ 075c460

Last change on this file since 075c460 was c8e1996, checked in by krzywon, 8 years ago

Fixes #738: No errors are thrown on loading projects with fits, plus linting.

  • Property mode set to 100644
File size: 74.7 KB
RevLine 
[f32d144]1"""
2    Class that holds a fit page state
3"""
[a95ae9a]4# TODO: Refactor code so we don't need to use getattr/setattr
[5062bbf]5################################################################################
[a95ae9a]6# This software was developed by the University of Tennessee as part of the
7# Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
8# project funded by the US National Science Foundation.
[5062bbf]9#
[a95ae9a]10# See the license text in license.txt
[5062bbf]11#
[a95ae9a]12# copyright 2009, University of Tennessee
[5062bbf]13################################################################################
[11a7e11]14import time
15import os
16import sys
[abcbe09]17import wx
[6f023e8]18import copy
[11a7e11]19import logging
[df7ed14]20import numpy
[7673ecd]21import traceback
[c77d859]22
[35b556d]23import xml.dom.minidom
[ac5b69d]24from xml.dom.minidom import parseString
[11a7e11]25from lxml import etree
26
[6c382da]27import sasmodels.weights
28
[b699768]29import sas.sascalc.dataloader
30from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
31from sas.sascalc.dataloader.readers.cansas_reader import get_content, write_node
[a95ae9a]32from sas.sascalc.dataloader.data_info import Data2D, Collimation, Detector
33from sas.sascalc.dataloader.data_info import Process, Aperture
34# Information to read/write state as xml
[61cada5]35FITTING_NODE_NAME = 'fitting_plug_in'
36CANSAS_NS = "cansas1d/1.0"
37
[b1e609c]38LIST_OF_DATA_ATTRIBUTES = [["is_data", "is_data", "bool"],
39                           ["group_id", "data_group_id", "string"],
40                           ["data_name", "data_name", "string"],
41                           ["data_id", "data_id", "string"],
42                           ["name", "name", "string"],
43                           ["data_name", "data_name", "string"]]
[acf8e4a5]44LIST_OF_STATE_ATTRIBUTES = [["qmin", "qmin", "float"],
[b1e609c]45                            ["qmax", "qmax", "float"],
46                            ["npts", "npts", "float"],
47                            ["categorycombobox", "categorycombobox", "string"],
[c8e1996]48                            ["formfactorcombobox", "formfactorcombobox",
49                             "string"],
50                            ["structurecombobox", "structurecombobox",
51                             "string"],
[b1e609c]52                            ["multi_factor", "multi_factor", "float"],
53                            ["magnetic_on", "magnetic_on", "bool"],
54                            ["enable_smearer", "enable_smearer", "bool"],
55                            ["disable_smearer", "disable_smearer", "bool"],
56                            ["pinhole_smearer", "pinhole_smearer", "bool"],
57                            ["slit_smearer", "slit_smearer", "bool"],
58                            ["enable_disp", "enable_disp", "bool"],
59                            ["disable_disp", "disable_disp", "bool"],
60                            ["dI_noweight", "dI_noweight", "bool"],
61                            ["dI_didata", "dI_didata", "bool"],
62                            ["dI_sqrdata", "dI_sqrdata", "bool"],
63                            ["dI_idata", "dI_idata", "bool"],
64                            ["enable2D", "enable2D", "bool"],
65                            ["cb1", "cb1", "bool"],
66                            ["tcChi", "tcChi", "float"],
67                            ["smearer", "smearer", "float"],
68                            ["smear_type", "smear_type", "string"],
[c8e1996]69                            ["dq_l", "dq_l", "float"],
70                            ["dq_r", "dq_r", "float"],
[b1e609c]71                            ["dx_max", "dx_max", "float"],
72                            ["dx_min", "dx_min", "float"],
73                            ["dxl", "dxl", "float"],
74                            ["dxw", "dxw", "float"]]
75
76LIST_OF_MODEL_ATTRIBUTES = [["values", "values"],
[11a7e11]77                            ["weights", "weights"]]
78
[b1e609c]79DISPERSION_LIST = [["disp_obj_dict", "_disp_obj_dict", "string"]]
[2296316]80
[b1e609c]81LIST_OF_STATE_PARAMETERS = [["parameters", "parameters"],
[f32d144]82                            ["str_parameters", "str_parameters"],
[61cada5]83                            ["orientation_parameters", "orientation_params"],
[c8e1996]84                            ["dispersity_parameters",
85                             "orientation_params_disp"],
[f32d144]86                            ["fixed_param", "fixed_param"],
87                            ["fittable_param", "fittable_param"]]
[b1e609c]88LIST_OF_DATA_2D_ATTR = [["xmin", "xmin", "float"],
[f32d144]89                        ["xmax", "xmax", "float"],
90                        ["ymin", "ymin", "float"],
91                        ["ymax", "ymax", "float"],
92                        ["_xaxis", "_xaxis", "string"],
[df7ed14]93                        ["_xunit", "_xunit", "string"],
[f32d144]94                        ["_yaxis", "_yaxis", "string"],
95                        ["_yunit", "_yunit", "string"],
96                        ["_zaxis", "_zaxis", "string"],
97                        ["_zunit", "_zunit", "string"]]
[b1e609c]98LIST_OF_DATA_2D_VALUES = [["qx_data", "qx_data", "float"],
99                          ["qy_data", "qy_data", "float"],
100                          ["dqx_data", "dqx_data", "float"],
101                          ["dqy_data", "dqy_data", "float"],
102                          ["data", "data", "float"],
103                          ["q_data", "q_data", "float"],
104                          ["err_data", "err_data", "float"],
105                          ["mask", "mask", "bool"]]
[61cada5]106
[f32d144]107
108def parse_entry_helper(node, item):
[0b12abb5]109    """
110    Create a numpy list from value extrated from the node
[2f4b430]111
[0b12abb5]112    :param node: node from each the value is stored
113    :param item: list name of three strings.the two first are name of data
[f32d144]114        attribute and the third one is the type of the value of that
[0b12abb5]115        attribute. type can be string, float, bool, etc.
[2f4b430]116
[0b12abb5]117    : return: numpy array
118    """
119    if node is not None:
120        if item[2] == "string":
121            return str(node.get(item[0]).strip())
122        elif item[2] == "bool":
123            try:
[e3d1423]124                return node.get(item[0]).strip() == "True"
[c8e1996]125            except Exception:
[0b12abb5]126                return None
127        else:
128            try:
129                return float(node.get(item[0]))
[c8e1996]130            except Exception:
[0b12abb5]131                return None
[2f4b430]132
133
[cfc0913]134class PageState(object):
[c77d859]135    """
[5062bbf]136    Contains information to reconstruct a page of the fitpanel.
[c77d859]137    """
[61cada5]138    def __init__(self, parent=None, model=None, data=None):
[f32d144]139        """
[5062bbf]140        Initialize the current state
[2f4b430]141
[5062bbf]142        :param model: a selected model within a page
[f32d144]143        :param data:
[2f4b430]144
[c77d859]145        """
[61cada5]146        self.file = None
[a95ae9a]147        # Time of state creation
[11a7e11]148        self.timestamp = time.time()
[a95ae9a]149        # Data member to store the dispersion object created
[c77d859]150        self._disp_obj_dict = {}
[a95ae9a]151        # ------------------------
152        # Data used for fitting
[c77d859]153        self.data = data
[2296316]154        # model data
155        self.theory_data = None
[a95ae9a]156        # Is 2D
[2296316]157        self.is_2D = False
158        self.images = None
[2f4b430]159
[a95ae9a]160        # save additional information on data that dataloader.reader
161        # does not read
[61cada5]162        self.is_data = None
163        self.data_name = ""
[c0ff8cc]164
[61cada5]165        if self.data is not None:
166            self.data_name = self.data.name
167        self.data_id = None
168        if self.data is not None and hasattr(self.data, "id"):
169            self.data_id = self.data.id
170        self.data_group_id = None
171        if self.data is not None and hasattr(self.data, "group_id"):
172            self.data_group_id = self.data.group_id
[2f4b430]173
[a95ae9a]174        # reset True change the state of existing button
[61cada5]175        self.reset = False
[2f4b430]176
[c77d859]177        # flag to allow data2D plot
[cfc0913]178        self.enable2D = False
[c77d859]179        # model on which the fit would be performed
180        self.model = model
[93f0a862]181        self.m_name = None
[a95ae9a]182        # list of process done to model
[dce84c0]183        self.process = []
[a95ae9a]184        # fit page manager
[c77d859]185        self.manager = None
[a95ae9a]186        # Store the parent of this panel parent
[c77d859]187        # For this application fitpanel is the parent
[f32d144]188        self.parent = parent
[c77d859]189        # Event_owner is the owner of model event
190        self.event_owner = None
[a95ae9a]191        # page name
[cfc0913]192        self.page_name = ""
[c8e1996]193        # Contains link between model, its parameters, and panel organization
[61cada5]194        self.parameters = []
[fb59ed9]195        # String parameter list that can not be fitted
196        self.str_parameters = []
[f32d144]197        # Contains list of parameters that cannot be fitted and reference to
[a95ae9a]198        # panel objects
[61cada5]199        self.fixed_param = []
[f32d144]200        # Contains list of parameters with dispersity and reference to
[a95ae9a]201        # panel objects
[61cada5]202        self.fittable_param = []
[a95ae9a]203        # orientation parameters
[61cada5]204        self.orientation_params = []
[a95ae9a]205        # orientation parameters for gaussian dispersity
[61cada5]206        self.orientation_params_disp = []
[a95ae9a]207        # smearer info
[61cada5]208        self.smearer = None
[7609f1a]209        self.smear_type = None
210        self.dq_l = None
211        self.dq_r = None
[db8fd5b]212        self.dx_max = None
213        self.dx_min = None
214        self.dxl = None
215        self.dxw = None
[a95ae9a]216        # list of dispersion parameters
[f32d144]217        self.disp_list = []
[61cada5]218        if self.model is not None:
[71f0373]219            self.disp_list = self.model.getDispParamList()
[2296316]220
[61cada5]221        self.disp_cb_dict = {}
[2296316]222        self.values = {}
223        self.weights = {}
[2f4b430]224
[a95ae9a]225        # contains link between a model and selected parameters to fit
[61cada5]226        self.param_toFit = []
[a95ae9a]227        # dictionary of model type and model class
[cfc0913]228        self.model_list_box = None
[a95ae9a]229        # save the state of the context menu
[61cada5]230        self.saved_states = {}
[a95ae9a]231        # save selection of combobox
[3b9e023]232        self.formfactorcombobox = None
[ea5fa58]233        self.categorycombobox = None
[f32d144]234        self.structurecombobox = None
[c0ff8cc]235
[a95ae9a]236        # radio box to select type of model
237        # self.shape_rbutton = False
238        # self.shape_indep_rbutton = False
239        # self.struct_rbutton = False
240        # self.plugin_rbutton = False
241        # the indice of the current selection
[b787e68c]242        self.disp_box = 0
[a95ae9a]243        # Qrange
244        # Q range
[61cada5]245        self.qmin = 0.001
246        self.qmax = 0.1
[a95ae9a]247        # reset data range
[0b12abb5]248        self.qmax_x = None
249        self.qmin_x = None
[2f4b430]250
[cfc0913]251        self.npts = None
[61cada5]252        self.name = ""
[4523b68]253        self.multi_factor = None
[318b5bbb]254        self.magnetic_on = False
[a95ae9a]255        # enable smearering state
[cfc0913]256        self.enable_smearer = False
[fc6ea43]257        self.disable_smearer = True
[7609f1a]258        self.pinhole_smearer = False
[f32d144]259        self.slit_smearer = False
[55bb249c]260        # weighting options
261        self.dI_noweight = False
262        self.dI_didata = True
263        self.dI_sqrdata = False
[f32d144]264        self.dI_idata = False
[a95ae9a]265        # disperity selection
[61cada5]266        self.enable_disp = False
267        self.disable_disp = True
[2f4b430]268
[a95ae9a]269        # state of selected all check button
[cfc0913]270        self.cb1 = False
[a95ae9a]271        # store value of chisqr
[61cada5]272        self.tcChi = None
[2f4b430]273
[6f023e8]274    def clone(self):
[61cada5]275        """
[5062bbf]276        Create a new copy of the current object
[61cada5]277        """
278        model = None
279        if self.model is not None:
[6f023e8]280            model = self.model.clone()
[bb70474]281            model.name = self.model.name
[61cada5]282        obj = PageState(self.parent, model=model)
283        obj.file = copy.deepcopy(self.file)
[6f023e8]284        obj.data = copy.deepcopy(self.data)
[61cada5]285        if self.data is not None:
286            self.data_name = self.data.name
287        obj.data_name = self.data_name
288        obj.is_data = self.is_data
[dcf29d7]289        obj.model_list_box = copy.deepcopy(self.model_list_box)
[2f4b430]290
[ea5fa58]291        obj.categorycombobox = self.categorycombobox
[61cada5]292        obj.formfactorcombobox = self.formfactorcombobox
[f32d144]293        obj.structurecombobox = self.structurecombobox
[2f4b430]294
[a95ae9a]295        # obj.shape_rbutton = self.shape_rbutton
296        # obj.shape_indep_rbutton = self.shape_indep_rbutton
297        # obj.struct_rbutton = self.struct_rbutton
298        # obj.plugin_rbutton = self.plugin_rbutton
[2f4b430]299
[dcf29d7]300        obj.manager = self.manager
301        obj.event_owner = self.event_owner
[71f0373]302        obj.disp_list = copy.deepcopy(self.disp_list)
[2f4b430]303
[c477b31]304        obj.enable2D = copy.deepcopy(self.enable2D)
[cfc0913]305        obj.parameters = copy.deepcopy(self.parameters)
[fb59ed9]306        obj.str_parameters = copy.deepcopy(self.str_parameters)
[cfc0913]307        obj.fixed_param = copy.deepcopy(self.fixed_param)
308        obj.fittable_param = copy.deepcopy(self.fittable_param)
[f32d144]309        obj.orientation_params = copy.deepcopy(self.orientation_params)
[c8e1996]310        obj.orientation_params_disp = \
311            copy.deepcopy(self.orientation_params_disp)
[b787e68c]312        obj.enable_disp = copy.deepcopy(self.enable_disp)
[fc6ea43]313        obj.disable_disp = copy.deepcopy(self.disable_disp)
[0aeabc6]314        obj.tcChi = self.tcChi
[2f4b430]315
[f32d144]316        if len(self._disp_obj_dict) > 0:
317            for k, v in self._disp_obj_dict.iteritems():
318                obj._disp_obj_dict[k] = v
319        if len(self.disp_cb_dict) > 0:
320            for k, v in self.disp_cb_dict.iteritems():
321                obj.disp_cb_dict[k] = v
322        if len(self.values) > 0:
323            for k, v in self.values.iteritems():
[2296316]324                obj.values[k] = v
[f32d144]325        if len(self.weights) > 0:
326            for k, v in self.weights.iteritems():
[2296316]327                obj.weights[k] = v
[b787e68c]328        obj.enable_smearer = copy.deepcopy(self.enable_smearer)
[fc6ea43]329        obj.disable_smearer = copy.deepcopy(self.disable_smearer)
[7609f1a]330        obj.pinhole_smearer = copy.deepcopy(self.pinhole_smearer)
331        obj.slit_smearer = copy.deepcopy(self.slit_smearer)
332        obj.smear_type = copy.deepcopy(self.smear_type)
[55bb249c]333        obj.dI_noweight = copy.deepcopy(self.dI_noweight)
334        obj.dI_didata = copy.deepcopy(self.dI_didata)
335        obj.dI_sqrdata = copy.deepcopy(self.dI_sqrdata)
336        obj.dI_idata = copy.deepcopy(self.dI_idata)
[7609f1a]337        obj.dq_l = copy.deepcopy(self.dq_l)
338        obj.dq_r = copy.deepcopy(self.dq_r)
[db8fd5b]339        obj.dx_max = copy.deepcopy(self.dx_max)
340        obj.dx_min = copy.deepcopy(self.dx_min)
341        obj.dxl = copy.deepcopy(self.dxl)
[2f4b430]342        obj.dxw = copy.deepcopy(self.dxw)
[b787e68c]343        obj.disp_box = copy.deepcopy(self.disp_box)
344        obj.qmin = copy.deepcopy(self.qmin)
345        obj.qmax = copy.deepcopy(self.qmax)
[c0ff8cc]346        obj.multi_factor = self.multi_factor
[318b5bbb]347        obj.magnetic_on = self.magnetic_on
[f32d144]348        obj.npts = copy.deepcopy(self.npts)
[b787e68c]349        obj.cb1 = copy.deepcopy(self.cb1)
[3370922]350        obj.smearer = copy.deepcopy(self.smearer)
[2f4b430]351
[a074145]352        for name, state in self.saved_states.iteritems():
353            copy_name = copy.deepcopy(name)
354            copy_state = state.clone()
[f32d144]355            obj.saved_states[copy_name] = copy_state
[6f023e8]356        return obj
[2f4b430]357
[61cada5]358    def _repr_helper(self, list, rep):
359        """
[5062bbf]360        Helper method to print a state
[61cada5]361        """
362        for item in list:
[f32d144]363            rep += "parameter name: %s \n" % str(item[1])
364            rep += "value: %s\n" % str(item[2])
365            rep += "selected: %s\n" % str(item[0])
366            rep += "error displayed : %s \n" % str(item[4][0])
367            rep += "error value:%s \n" % str(item[4][1])
368            rep += "minimum displayed : %s \n" % str(item[5][0])
369            rep += "minimum value : %s \n" % str(item[5][1])
370            rep += "maximum displayed : %s \n" % str(item[6][0])
371            rep += "maximum value : %s \n" % str(item[6][1])
372            rep += "parameter unit: %s\n\n" % str(item[7])
[240b9966]373        return rep
[2f4b430]374
[61cada5]375    def __repr__(self):
[f32d144]376        """
[5062bbf]377        output string for printing
[11a7e11]378        """
[f32d144]379        rep = "\nState name: %s\n" % self.file
[11a7e11]380        t = time.localtime(self.timestamp)
[deff488]381        time_str = time.strftime("%b %d %Y %H;%M;%S ", t)
[c0ff8cc]382
[f32d144]383        rep += "State created: %s\n" % time_str
[998ca90]384        rep += "State form factor combobox selection: %s\n" % \
385               self.formfactorcombobox
386        rep += "State structure factor combobox selection: %s\n" % \
387               self.structurecombobox
[f32d144]388        rep += "is data : %s\n" % self.is_data
389        rep += "data's name : %s\n" % self.data_name
390        rep += "data's id : %s\n" % self.data_id
[a95ae9a]391        if self.model is not None:
[93f0a862]392            m_name = self.model.__class__.__name__
393            if m_name == 'Model':
394                m_name = self.m_name
[f32d144]395            rep += "model name : %s\n" % m_name
[74dc0a4]396        else:
397            rep += "model name : None\n"
[f32d144]398        rep += "multi_factor : %s\n" % str(self.multi_factor)
[2f4b430]399        rep += "magnetic_on : %s\n" % str(self.magnetic_on)
[ea5fa58]400        rep += "model type (Category) selected: %s\n" % self.categorycombobox
[f32d144]401        rep += "data : %s\n" % str(self.data)
[a95ae9a]402        rep += "Plotting Range: min: %s, max: %s, steps: %s\n" % \
[c8e1996]403               (str(self.qmin), str(self.qmax), str(self.npts))
[f32d144]404        rep += "Dispersion selection : %s\n" % str(self.disp_box)
405        rep += "Smearing enable : %s\n" % str(self.enable_smearer)
406        rep += "Smearing disable : %s\n" % str(self.disable_smearer)
407        rep += "Pinhole smearer enable : %s\n" % str(self.pinhole_smearer)
408        rep += "Slit smearer enable : %s\n" % str(self.slit_smearer)
409        rep += "Dispersity enable : %s\n" % str(self.enable_disp)
410        rep += "Dispersity disable : %s\n" % str(self.disable_disp)
411        rep += "Slit smearer enable: %s\n" % str(self.slit_smearer)
[2f4b430]412
[f32d144]413        rep += "dI_noweight : %s\n" % str(self.dI_noweight)
414        rep += "dI_didata : %s\n" % str(self.dI_didata)
415        rep += "dI_sqrdata : %s\n" % str(self.dI_sqrdata)
416        rep += "dI_idata : %s\n" % str(self.dI_idata)
[2f4b430]417
[f32d144]418        rep += "2D enable : %s\n" % str(self.enable2D)
[c8e1996]419        rep += "All parameters checkbox selected: %s\n" % self.cb1
[f32d144]420        rep += "Value of Chisqr : %s\n" % str(self.tcChi)
[c8e1996]421        rep += "Smear object : %s\n" % self.smearer
422        rep += "Smear type : %s\n" % self.smear_type
[f32d144]423        rep += "dq_l  : %s\n" % self.dq_l
424        rep += "dq_r  : %s\n" % self.dq_r
[db8fd5b]425        rep += "dx_max  : %s\n" % str(self.dx_max)
[2f4b430]426        rep += "dx_min : %s\n" % str(self.dx_min)
[db8fd5b]427        rep += "dxl  : %s\n" % str(self.dxl)
[2f4b430]428        rep += "dxw : %s\n" % str(self.dxw)
[f32d144]429        rep += "model  : %s\n\n" % str(self.model)
[2296316]430        temp_parameters = []
431        temp_fittable_param = []
432        if self.data.__class__.__name__ == "Data2D":
433            self.is_2D = True
434        else:
435            self.is_2D = False
436        if self.data is not None:
437            if not self.is_2D:
438                for item in self.parameters:
[a95ae9a]439                    if item not in self.orientation_params:
[2296316]440                        temp_parameters.append(item)
441                for item in self.fittable_param:
[a95ae9a]442                    if item not in self.orientation_params_disp:
[2296316]443                        temp_fittable_param.append(item)
444            else:
445                temp_parameters = self.parameters
446                temp_fittable_param = self.fittable_param
[2f4b430]447
[a95ae9a]448            rep += "number parameters(self.parameters): %s\n" % \
449                   len(temp_parameters)
[f32d144]450            rep = self._repr_helper(list=temp_parameters, rep=rep)
[a95ae9a]451            rep += "number str_parameters(self.str_parameters): %s\n" % \
452                   len(self.str_parameters)
[f32d144]453            rep = self._repr_helper(list=self.str_parameters, rep=rep)
[a95ae9a]454            rep += "number fittable_param(self.fittable_param): %s\n" % \
455                   len(temp_fittable_param)
[f32d144]456            rep = self._repr_helper(list=temp_fittable_param, rep=rep)
[61cada5]457        return rep
[2296316]458
459    def set_report_string(self):
460        """
[f32d144]461        Get the values (strings) from __str__ for report
[2296316]462        """
[d06ae30]463        # Dictionary of the report strings
[2296316]464        repo_time = ""
465        model_name = ""
466        title = ""
467        title_name = ""
468        file_name = ""
469        param_string = ""
470        paramval_string = ""
471        chi2_string = ""
472        q_range = ""
473        strings = self.__repr__()
474        lines = strings.split('\n')
475
476        # get all string values from __str__()
477        for line in lines:
478            value = ""
479            content = line.split(":")
480            name = content[0]
481            try:
482                value = content[1]
[7673ecd]483            except Exception:
[c8e1996]484                msg = "Report string expected 'name: value' but got %r" % line
[6c382da]485                logging.error(msg)
[b7c6a4a]486            if name.count("State created"):
487                repo_time = "" + value
[2296316]488            if name.count("parameter name"):
489                val_name = value.split(".")
490                if len(val_name) > 1:
491                    if val_name[1].count("width"):
492                        param_string += value + ','
493                    else:
494                        continue
495                else:
496                    param_string += value + ','
497            if name == "value":
498                param_string += value + ','
[c8e1996]499            fixed_parameter = False
[b1e609c]500            if name == "selected":
501                if value == u' False':
502                    fixed_parameter = True
[2296316]503            if name == "error value":
[b1e609c]504                if fixed_parameter:
505                    param_string += '(fixed),'
506                else:
[d06ae30]507                    param_string += value + ','
[2296316]508            if name == "parameter unit":
[f32d144]509                param_string += value + ':'
[2296316]510            if name == "Value of Chisqr ":
511                chi2 = ("Chi2/Npts = " + value)
512                chi2_string = CENTRE % chi2
513            if name == "Title":
514                if len(value.strip()) == 0:
515                    continue
516                title = value + " [" + repo_time + "]"
517                title_name = HEADER % title
518            if name == "data ":
519                try:
[b1e609c]520                    file_value = ("File name:" + content[2])
521                    file_name = CENTRE % file_value
[2296316]522                    if len(title) == 0:
523                        title = content[2] + " [" + repo_time + "]"
524                        title_name = HEADER % title
[7673ecd]525                except Exception:
[6c382da]526                    msg = "While parsing 'data: ...'\n"
527                    logging.error(msg + traceback.format_exc())
[74dc0a4]528            if name == "model name ":
529                try:
530                    modelname = "Model name:" + content[1]
531                except:
532                    modelname = "Model name:" + " NAN"
[f32d144]533                model_name = CENTRE % modelname
[2f4b430]534
[2296316]535            if name == "Plotting Range":
536                try:
537                    q_range = content[1] + " = " + content[2] \
538                            + " = " + content[3].split(",")[0]
539                    q_name = ("Q Range:    " + q_range)
540                    q_range = CENTRE % q_name
[7673ecd]541                except Exception:
[6c382da]542                    msg = "While parsing 'Plotting Range: ...'\n"
543                    logging.error(msg + traceback.format_exc())
[2296316]544        paramval = ""
[f32d144]545        for lines in param_string.split(":"):
[2296316]546            line = lines.split(",")
[f32d144]547            if len(lines) > 0:
548                param = line[0]
[2296316]549                param += " = " + line[1]
550                if len(line[2].split()) > 0 and not line[2].count("None"):
551                    param += " +- " + line[2]
552                if len(line[3].split()) > 0 and not line[3].count("None"):
553                    param += " " + line[3]
554                if not paramval.count(param):
[f32d144]555                    paramval += param + "\n"
[2296316]556                    paramval_string += CENTRE % param + "\n"
[2f4b430]557
[a95ae9a]558        text_string = "\n\n\n%s\n\n%s\n%s\n%s\n\n%s" % \
559                      (title, file, q_name, chi2, paramval)
[2f4b430]560
[f5bdb4a]561        title_name = self._check_html_format(title_name)
562        file_name = self._check_html_format(file_name)
563        title = self._check_html_format(title)
[2f4b430]564
[2296316]565        html_string = title_name + "\n" + file_name + \
[f32d144]566                                   "\n" + model_name + \
[2296316]567                                   "\n" + q_range + \
568                                   "\n" + chi2_string + \
569                                   "\n" + ELINE + \
570                                   "\n" + paramval_string + \
571                                   "\n" + ELINE + \
572                                   "\n" + FEET_1 % title + \
[f32d144]573                                   "\n" + FEET_2
[2f4b430]574
[2296316]575        return html_string, text_string, title
[2f4b430]576
[f5bdb4a]577    def _check_html_format(self, name):
578        """
579        Check string '%' for html format
580        """
581        if name.count('%'):
582            name = name.replace('%', '&#37')
[2f4b430]583
[f5bdb4a]584        return name
[2f4b430]585
[2296316]586    def report(self, figs=None, canvases=None):
587        """
588        Invoke report dialog panel
[2f4b430]589
[2296316]590        : param figs: list of pylab figures [list]
591        """
[d85c194]592        from sas.sasgui.perspectives.fitting.report_dialog import ReportDialog
[2296316]593        # get the strings for report
594        html_str, text_str, title = self.set_report_string()
595        # Allow 2 figures to append
596        if len(figs) == 1:
[f32d144]597            add_str = FEET_3
[2296316]598        elif len(figs) == 2:
[f32d144]599            add_str = ELINE
600            add_str += FEET_2 % ("%s")
601            add_str += ELINE
602            add_str += FEET_3
[2296316]603        elif len(figs) > 2:
[f32d144]604            add_str = ELINE
605            add_str += FEET_2 % ("%s")
606            add_str += ELINE
607            add_str += FEET_2 % ("%s")
608            add_str += ELINE
609            add_str += FEET_3
[2296316]610        else:
611            add_str = ""
[c0ff8cc]612
[2296316]613        # final report html strings
614        report_str = html_str % ("%s") + add_str
615
616        # make plot image
617        images = self.set_plot_state(figs, canvases)
[f32d144]618        report_list = [report_str, text_str, images]
[6f16e25]619        dialog = ReportDialog(report_list, None, wx.ID_ANY, "")
[d838715]620        dialog.Show()
[2f4b430]621
[c8e1996]622    def _to_xml_helper(self, thelist, element, newdoc):
[61cada5]623        """
[5062bbf]624        Helper method to create xml file for saving state
[61cada5]625        """
[eddb6ec]626        for item in thelist:
[61cada5]627            sub_element = newdoc.createElement('parameter')
628            sub_element.setAttribute('name', str(item[1]))
629            sub_element.setAttribute('value', str(item[2]))
630            sub_element.setAttribute('selected_to_fit', str(item[0]))
631            sub_element.setAttribute('error_displayed', str(item[4][0]))
632            sub_element.setAttribute('error_value', str(item[4][1]))
633            sub_element.setAttribute('minimum_displayed', str(item[5][0]))
634            sub_element.setAttribute('minimum_value', str(item[5][1]))
635            sub_element.setAttribute('maximum_displayed', str(item[6][0]))
636            sub_element.setAttribute('maximum_value', str(item[6][1]))
637            sub_element.setAttribute('unit', str(item[7]))
638            element.appendChild(sub_element)
[2f4b430]639
[c8e1996]640    def to_xml(self, file="fitting_state.fitv", doc=None,
641               entry_node=None, batch_fit_state=None):
[61cada5]642        """
[a95ae9a]643        Writes the state of the fit panel to file, as XML.
[2f4b430]644
[5062bbf]645        Compatible with standalone writing, or appending to an
[998ca90]646        already existing XML document. In that case, the XML document is
647        required. An optional entry node in the XML document may also be given.
[2f4b430]648
[5062bbf]649        :param file: file to write to
650        :param doc: XML document object [optional]
[998ca90]651        :param entry_node: XML node within the XML document at which we
652                           will append the data [optional]
[c8e1996]653        :param batch_fit_state: simultaneous fit state
[61cada5]654        """
655        from xml.dom.minidom import getDOMImplementation
[71f0373]656
[61cada5]657        # Check whether we have to write a standalone XML file
658        if doc is None:
659            impl = getDOMImplementation()
[f32d144]660            doc_type = impl.createDocumentType(FITTING_NODE_NAME, "1.0", "1.0")
[61cada5]661            newdoc = impl.createDocument(None, FITTING_NODE_NAME, doc_type)
662            top_element = newdoc.documentElement
663        else:
664            # We are appending to an existing document
665            newdoc = doc
[ac5b69d]666            try:
667                top_element = newdoc.createElement(FITTING_NODE_NAME)
668            except:
669                string = etree.tostring(doc, pretty_print=True)
670                newdoc = parseString(string)
671                top_element = newdoc.createElement(FITTING_NODE_NAME)
[61cada5]672            if entry_node is None:
673                newdoc.documentElement.appendChild(top_element)
674            else:
[ac5b69d]675                try:
676                    entry_node.appendChild(top_element)
677                except:
678                    node_name = entry_node.tag
679                    node_list = newdoc.getElementsByTagName(node_name)
680                    entry_node = node_list.item(0)
681                    entry_node.appendChild(top_element)
[2f4b430]682
[61cada5]683        attr = newdoc.createAttribute("version")
684        attr.nodeValue = '1.0'
685        top_element.setAttributeNode(attr)
[2f4b430]686
[61cada5]687        # File name
688        element = newdoc.createElement("filename")
689        if self.file is not None:
690            element.appendChild(newdoc.createTextNode(str(self.file)))
691        else:
692            element.appendChild(newdoc.createTextNode(str(file)))
693        top_element.appendChild(element)
[2f4b430]694
[11a7e11]695        element = newdoc.createElement("timestamp")
696        element.appendChild(newdoc.createTextNode(time.ctime(self.timestamp)))
697        attr = newdoc.createAttribute("epoch")
698        attr.nodeValue = str(self.timestamp)
699        element.setAttributeNode(attr)
700        top_element.appendChild(element)
[a95ae9a]701
[61cada5]702        # Inputs
703        inputs = newdoc.createElement("Attributes")
704        top_element.appendChild(inputs)
[2f4b430]705
[61cada5]706        if self.data is not None and hasattr(self.data, "group_id"):
707            self.data_group_id = self.data.group_id
708        if self.data is not None and hasattr(self.data, "is_data"):
709            self.is_data = self.data.is_data
710        if self.data is not None:
711            self.data_name = self.data.name
712        if self.data is not None and hasattr(self.data, "id"):
713            self.data_id = self.data.id
[2f4b430]714
[b1e609c]715        for item in LIST_OF_DATA_ATTRIBUTES:
[0b12abb5]716            element = newdoc.createElement(item[0])
[b1e609c]717            element.setAttribute(item[0], str(getattr(self, item[1])))
[f32d144]718            inputs.appendChild(element)
[2f4b430]719
[b1e609c]720        for item in LIST_OF_STATE_ATTRIBUTES:
[26f3dd5]721            element = newdoc.createElement(item[0])
[b1e609c]722            element.setAttribute(item[0], str(getattr(self, item[1])))
[26f3dd5]723            inputs.appendChild(element)
[2f4b430]724
[f32d144]725        # For self.values ={ disp_param_name: [vals,...],...}
726        # and for self.weights ={ disp_param_name: [weights,...],...}
[b1e609c]727        for item in LIST_OF_MODEL_ATTRIBUTES:
[11a7e11]728            element = newdoc.createElement(item[0])
[b1e609c]729            value_list = getattr(self, item[1])
730            for key, value in value_list.iteritems():
[2296316]731                sub_element = newdoc.createElement(key)
732                sub_element.setAttribute('name', str(key))
733                for val in value:
[b1e609c]734                    sub_element.appendChild(newdoc.createTextNode(str(val)))
[2f4b430]735
[f32d144]736                element.appendChild(sub_element)
[11a7e11]737            inputs.appendChild(element)
[2f4b430]738
[2296316]739        # Create doc for the dictionary of self._disp_obj_dic
[6c382da]740        for tagname, varname, tagtype in DISPERSION_LIST:
741            element = newdoc.createElement(tagname)
742            value_list = getattr(self, varname)
743            for key, value in value_list.iteritems():
[f32d144]744                sub_element = newdoc.createElement(key)
745                sub_element.setAttribute('name', str(key))
746                sub_element.setAttribute('value', str(value))
747                element.appendChild(sub_element)
748            inputs.appendChild(element)
[2f4b430]749
[b1e609c]750        for item in LIST_OF_STATE_PARAMETERS:
[61cada5]751            element = newdoc.createElement(item[0])
[c8e1996]752            self._to_xml_helper(thelist=getattr(self, item[1]),
753                                element=element, newdoc=newdoc)
[61cada5]754            inputs.appendChild(element)
[2f4b430]755
[a95ae9a]756        # Combined and Simultaneous Fit Parameters
757        if batch_fit_state is not None:
758            batch_combo = newdoc.createElement('simultaneous_fit')
759            top_element.appendChild(batch_combo)
760
761            # Simultaneous Fit Number For Linking Later
762            element = newdoc.createElement('sim_fit_number')
763            element.setAttribute('fit_number', str(batch_fit_state.fit_page_no))
764            batch_combo.appendChild(element)
765
766            # Save constraints
767            constraints = newdoc.createElement('constraints')
768            batch_combo.appendChild(constraints)
769            for constraint in batch_fit_state.constraints_list:
[998ca90]770                if constraint.model_cbox.GetValue() != "":
[c8e1996]771                    # model_cbox, param_cbox, egal_txt, constraint,
772                    # btRemove, sizer
[998ca90]773                    doc_cons = newdoc.createElement('constraint')
774                    doc_cons.setAttribute('model_cbox',
775                                          str(constraint.model_cbox.GetValue()))
776                    doc_cons.setAttribute('param_cbox',
777                                          str(constraint.param_cbox.GetValue()))
778                    doc_cons.setAttribute('egal_txt',
779                                          str(constraint.egal_txt.GetLabel()))
780                    doc_cons.setAttribute('constraint',
781                                          str(constraint.constraint.GetValue()))
782                    constraints.appendChild(doc_cons)
[a95ae9a]783
784            # Save all models
785            models = newdoc.createElement('model_list')
786            batch_combo.appendChild(models)
787            for model in batch_fit_state.model_list:
788                doc_model = newdoc.createElement('model_list_item')
789                doc_model.setAttribute('checked', str(model[0].GetValue()))
790                keys = model[1].keys()
791                doc_model.setAttribute('name', str(keys[0]))
792                values = model[1].get(keys[0])
793                doc_model.setAttribute('fit_number', str(model[2]))
794                doc_model.setAttribute('fit_page_source', str(model[3]))
795                doc_model.setAttribute('model_name', str(values.model.id))
796                models.appendChild(doc_model)
797
798            # Select All Checkbox
799            element = newdoc.createElement('select_all')
800            if batch_fit_state.select_all:
801                element.setAttribute('checked', 'True')
802            else:
803                element.setAttribute('checked', 'False')
804            batch_combo.appendChild(element)
805
[61cada5]806        # Save the file
807        if doc is None:
808            fd = open(file, 'w')
809            fd.write(newdoc.toprettyxml())
810            fd.close()
811            return None
812        else:
[ac5b69d]813            return newdoc
[2f4b430]814
[c8e1996]815    def _from_xml_helper(self, node, list):
[61cada5]816        """
[5062bbf]817        Helper function to write state to xml
[61cada5]818        """
819        for item in node:
[0b12abb5]820            try:
821                name = item.get('name')
822            except:
823                name = None
824            try:
825                value = item.get('value')
826            except:
827                value = None
828            try:
[3ad91de]829                selected_to_fit = (item.get('selected_to_fit') == "True")
[0b12abb5]830            except:
831                selected_to_fit = None
832            try:
[3ad91de]833                error_displayed = (item.get('error_displayed') == "True")
[0b12abb5]834            except:
835                error_displayed = None
[f32d144]836            try:
[0b12abb5]837                error_value = item.get('error_value')
838            except:
839                error_value = None
840            try:
[f32d144]841                minimum_displayed = (item.get('minimum_displayed') == "True")
[0b12abb5]842            except:
843                minimum_displayed = None
844            try:
845                minimum_value = item.get('minimum_value')
846            except:
847                minimum_value = None
848            try:
[3ad91de]849                maximum_displayed = (item.get('maximum_displayed') == "True")
[0b12abb5]850            except:
851                maximum_displayed = None
852            try:
853                maximum_value = item.get('maximum_value')
854            except:
855                maximum_value = None
856            try:
857                unit = item.get('unit')
858            except:
859                unit = None
[2296316]860            list.append([selected_to_fit, name, value, "+/-",
861                         [error_displayed, error_value],
[f32d144]862                         [minimum_displayed, minimum_value],
863                         [maximum_displayed, maximum_value], unit])
[2f4b430]864
[c8e1996]865    def from_xml(self, file=None, node=None):
[61cada5]866        """
[5062bbf]867        Load fitting state from a file
[2f4b430]868
[5062bbf]869        :param file: .fitv file
870        :param node: node of a XML document to read from
[61cada5]871        """
872        if file is not None:
[11a7e11]873            msg = "PageState no longer supports non-CanSAS"
874            msg += " format for fitting files"
875            raise RuntimeError, msg
[2f4b430]876
[e89aed5]877        if node.get('version') and node.get('version') == '1.0':
[2f4b430]878
[61cada5]879            # Get file name
880            entry = get_content('ns:filename', node)
881            if entry is not None:
882                self.file = entry.text.strip()
[2f4b430]883
[11a7e11]884            # Get time stamp
885            entry = get_content('ns:timestamp', node)
886            if entry is not None and entry.get('epoch'):
887                try:
888                    self.timestamp = float(entry.get('epoch'))
889                except:
890                    msg = "PageState.fromXML: Could not"
891                    msg += " read timestamp\n %s" % sys.exc_value
892                    logging.error(msg)
[2f4b430]893
[61cada5]894            if entry is not None:
[c8e1996]895                # Parse fitting attributes
896                entry = get_content('ns:Attributes', node)
897                for item in LIST_OF_DATA_ATTRIBUTES:
898                    node = get_content('ns:%s' % item[0], entry)
899                    setattr(self, item[0], parse_entry_helper(node, item))
900
[b1e609c]901                for item in LIST_OF_STATE_ATTRIBUTES:
[2296316]902                    node = get_content('ns:%s' % item[0], entry)
[b1e609c]903                    setattr(self, item[0], parse_entry_helper(node, item))
[2f4b430]904
[b1e609c]905                for item in LIST_OF_STATE_PARAMETERS:
[2296316]906                    node = get_content("ns:%s" % item[0], entry)
[c8e1996]907                    self._from_xml_helper(node=node,
908                                          list=getattr(self, item[1]))
[2f4b430]909
[f32d144]910                # Recover _disp_obj_dict from xml file
911                self._disp_obj_dict = {}
[6c382da]912                for tagname, varname, tagtype in DISPERSION_LIST:
913                    node = get_content("ns:%s" % tagname, entry)
[2296316]914                    for attr in node:
[6c382da]915                        parameter = str(attr.get('name'))
916                        value = attr.get('value')
917                        if value.startswith("<"):
918                            try:
919                                # <path.to.NamedDistribution object/instance...>
920                                cls_name = value[1:].split()[0].split('.')[-1]
921                                cls = getattr(sasmodels.weights, cls_name)
922                                value = cls.type
923                            except Exception:
[998ca90]924                                base = "unable to load distribution %r for %s"
925                                logging.error(base % (value, parameter))
[6c382da]926                                continue
927                        _disp_obj_dict = getattr(self, varname)
928                        _disp_obj_dict[parameter] = value
[2f4b430]929
[f32d144]930                # get self.values and self.weights dic. if exists
[6c382da]931                for tagname, varname in LIST_OF_MODEL_ATTRIBUTES:
932                    node = get_content("ns:%s" % tagname, entry)
[2296316]933                    dic = {}
[b1e609c]934                    value_list = []
[2296316]935                    for par in node:
936                        name = par.get('name')
[6c382da]937                        values = par.text.split()
[2296316]938                        # Get lines only with numbers
939                        for line in values:
940                            try:
[f32d144]941                                val = float(line)
[b1e609c]942                                value_list.append(val)
[7673ecd]943                            except Exception:
[2296316]944                                # pass if line is empty (it happens)
[6c382da]945                                msg = ("Error reading %r from %s %s\n"
946                                       % (line, tagname, name))
947                                logging.error(msg + traceback.format_exc())
[0e33a8d]948                        dic[name] = numpy.array(value_list)
[6c382da]949                    setattr(self, varname, dic)
[2f4b430]950
[2296316]951    def set_plot_state(self, figs, canvases):
952        """
953        Build image state that wx.html understand
954        by plotting, putting it into wx.FileSystem image object
955
956        """
957        images = []
958
959        # Reset memory
960        self.imgRAM = None
961        wx.MemoryFSHandler()
[2f4b430]962
[2296316]963        # For no figures in the list, prepare empty plot
[c8e1996]964        if figs is None or len(figs) == 0:
[2296316]965            figs = [None]
[2f4b430]966
[f32d144]967        # Loop over the list of figures
[2296316]968        # use wx.MemoryFSHandler
[f32d144]969        self.imgRAM = wx.MemoryFSHandler()
[2296316]970        for fig in figs:
[c8e1996]971            if fig is not None:
[2296316]972                ind = figs.index(fig)
973                canvas = canvases[ind]
[2f4b430]974
[c8e1996]975            # store the image in wx.FileSystem Object
[2296316]976            wx.FileSystem.AddHandler(wx.MemoryFSHandler())
[2f4b430]977
[2296316]978            # index of the fig
979            ind = figs.index(fig)
[2f4b430]980
[c8e1996]981            # AddFile, image can be retrieved with 'memory:filename'
[f32d144]982            self.imgRAM.AddFile('img_fit%s.png' % ind,
[2296316]983                                canvas.bitmap, wx.BITMAP_TYPE_PNG)
[2f4b430]984
[c8e1996]985            # append figs
[2296316]986            images.append(fig)
[2f4b430]987
[c0ff8cc]988        return images
[61cada5]989
[f32d144]990
[61cada5]991class Reader(CansasReader):
992    """
[5062bbf]993    Class to load a .fitv fitting file
[61cada5]994    """
[c8e1996]995    # File type
[61cada5]996    type_name = "Fitting"
[2f4b430]997
[c8e1996]998    # Wildcards
[b35d3d1]999    type = ["Fitting files (*.fitv)|*.fitv"
[b9a5f0e]1000            "SASView file (*.svs)|*.svs"]
[c8e1996]1001    # List of allowed extensions
[f32d144]1002    ext = ['.fitv', '.FITV', '.svs', 'SVS']
[2f4b430]1003
[61cada5]1004    def __init__(self, call_back=None, cansas=True):
[df7ed14]1005        CansasReader.__init__(self)
[61cada5]1006        """
[5062bbf]1007        Initialize the call-back method to be called
1008        after we load a file
[2f4b430]1009
[5062bbf]1010        :param call_back: call-back method
1011        :param cansas:  True = files will be written/read in CanSAS format
1012                        False = write CanSAS format
[2f4b430]1013
[61cada5]1014        """
[c8e1996]1015        # Call back method to be executed after a file is read
[61cada5]1016        self.call_back = call_back
[c8e1996]1017        # CanSAS format flag
[61cada5]1018        self.cansas = cansas
[75fbd17]1019        self.state = None
[a95ae9a]1020        # batch fitting params for saving
1021        self.batchfit_params = []
[2f4b430]1022
[75fbd17]1023    def get_state(self):
1024        return self.state
[2f4b430]1025
[61cada5]1026    def read(self, path):
[f32d144]1027        """
[5062bbf]1028        Load a new P(r) inversion state from file
[2f4b430]1029
[5062bbf]1030        :param path: file path
[2f4b430]1031
[61cada5]1032        """
[c8e1996]1033        if self.cansas:
[61cada5]1034            return self._read_cansas(path)
[2f4b430]1035
[e9b12eaf]1036    def _data2d_to_xml_doc(self, datainfo):
[61cada5]1037        """
[5062bbf]1038        Create an XML document to contain the content of a Data2D
[2f4b430]1039
[5062bbf]1040        :param datainfo: Data2D object
[2f4b430]1041
[61cada5]1042        """
1043        if not issubclass(datainfo.__class__, Data2D):
1044            raise RuntimeError, "The cansas writer expects a Data2D instance"
[2f4b430]1045
[998ca90]1046        title = "cansas1d/%s" % self.version
1047        title += "http://svn.smallangles.net/svn/canSAS/1dwg/trunk/cansas1d.xsd"
[61cada5]1048        doc = xml.dom.minidom.Document()
1049        main_node = doc.createElement("SASroot")
1050        main_node.setAttribute("version", self.version)
[df7ed14]1051        main_node.setAttribute("xmlns", "cansas1d/%s" % self.version)
[998ca90]1052        main_node.setAttribute("xmlns:xsi",
1053                               "http://www.w3.org/2001/XMLSchema-instance")
1054        main_node.setAttribute("xsi:schemaLocation", title)
[2f4b430]1055
[61cada5]1056        doc.appendChild(main_node)
[2f4b430]1057
[61cada5]1058        entry_node = doc.createElement("SASentry")
1059        main_node.appendChild(entry_node)
[2f4b430]1060
[61cada5]1061        write_node(doc, entry_node, "Title", datainfo.title)
[df7ed14]1062        if datainfo is not None:
[998ca90]1063            write_node(doc, entry_node, "data_class",
1064                       datainfo.__class__.__name__)
[61cada5]1065        for item in datainfo.run:
1066            runname = {}
[c8e1996]1067            if item in datainfo.run_name and \
[998ca90]1068                            len(str(datainfo.run_name[item])) > 1:
[b1e609c]1069                runname = {'name': datainfo.run_name[item]}
[61cada5]1070            write_node(doc, entry_node, "Run", item, runname)
1071        # Data info
[df7ed14]1072        new_node = doc.createElement("SASdata")
1073        entry_node.appendChild(new_node)
[b1e609c]1074        for item in LIST_OF_DATA_2D_ATTR:
[e9b12eaf]1075            element = doc.createElement(item[0])
[b1e609c]1076            element.setAttribute(item[0], str(getattr(datainfo, item[1])))
[df7ed14]1077            new_node.appendChild(element)
[2f4b430]1078
[b1e609c]1079        for item in LIST_OF_DATA_2D_VALUES:
[df7ed14]1080            root_node = doc.createElement(item[0])
1081            new_node.appendChild(root_node)
[b1e609c]1082            temp_list = getattr(datainfo, item[1])
[df7ed14]1083
[2f4b430]1084            if temp_list is None or len(temp_list) == 0:
[df7ed14]1085                element = doc.createElement(item[0])
[b1e609c]1086                element.appendChild(doc.createTextNode(str(temp_list)))
[df7ed14]1087                root_node.appendChild(element)
[35b556d]1088            else:
1089                for value in temp_list:
[df7ed14]1090                    element = doc.createElement(item[0])
[b1e609c]1091                    element.setAttribute(item[0], str(value))
[df7ed14]1092                    root_node.appendChild(element)
[2f4b430]1093
[61cada5]1094        # Sample info
1095        sample = doc.createElement("SASsample")
1096        if datainfo.sample.name is not None:
1097            sample.setAttribute("name", str(datainfo.sample.name))
1098        entry_node.appendChild(sample)
1099        write_node(doc, sample, "ID", str(datainfo.sample.ID))
[f32d144]1100        write_node(doc, sample, "thickness", datainfo.sample.thickness,
1101                   {"unit": datainfo.sample.thickness_unit})
[61cada5]1102        write_node(doc, sample, "transmission", datainfo.sample.transmission)
[f32d144]1103        write_node(doc, sample, "temperature", datainfo.sample.temperature,
1104                   {"unit": datainfo.sample.temperature_unit})
[2f4b430]1105
[61cada5]1106        for item in datainfo.sample.details:
1107            write_node(doc, sample, "details", item)
[2f4b430]1108
[61cada5]1109        pos = doc.createElement("position")
[f32d144]1110        written = write_node(doc, pos, "x", datainfo.sample.position.x,
1111                             {"unit": datainfo.sample.position_unit})
1112        written = written | write_node(doc, pos, "y",
1113                                       datainfo.sample.position.y,
1114                                       {"unit": datainfo.sample.position_unit})
1115        written = written | write_node(doc, pos, "z",
1116                                       datainfo.sample.position.z,
1117                                       {"unit": datainfo.sample.position_unit})
[c8e1996]1118        if written:
[61cada5]1119            sample.appendChild(pos)
[2f4b430]1120
[61cada5]1121        ori = doc.createElement("orientation")
[f32d144]1122        written = write_node(doc, ori, "roll", datainfo.sample.orientation.x,
1123                             {"unit": datainfo.sample.orientation_unit})
1124        written = written | write_node(doc, ori, "pitch",
1125                                       datainfo.sample.orientation.y,
[c8e1996]1126                                       {"unit":
1127                                            datainfo.sample.orientation_unit})
[f32d144]1128        written = written | write_node(doc, ori, "yaw",
1129                                       datainfo.sample.orientation.z,
[c8e1996]1130                                       {"unit":
1131                                            datainfo.sample.orientation_unit})
1132        if written:
[61cada5]1133            sample.appendChild(ori)
[2f4b430]1134
[61cada5]1135        # Instrument info
1136        instr = doc.createElement("SASinstrument")
1137        entry_node.appendChild(instr)
[2f4b430]1138
[61cada5]1139        write_node(doc, instr, "name", datainfo.instrument)
[2f4b430]1140
[61cada5]1141        #   Source
1142        source = doc.createElement("SASsource")
1143        if datainfo.source.name is not None:
1144            source.setAttribute("name", str(datainfo.source.name))
1145        instr.appendChild(source)
[2f4b430]1146
[61cada5]1147        write_node(doc, source, "radiation", datainfo.source.radiation)
1148        write_node(doc, source, "beam_shape", datainfo.source.beam_shape)
1149        size = doc.createElement("beam_size")
1150        if datainfo.source.beam_size_name is not None:
1151            size.setAttribute("name", str(datainfo.source.beam_size_name))
[f32d144]1152        written = write_node(doc, size, "x", datainfo.source.beam_size.x,
1153                             {"unit": datainfo.source.beam_size_unit})
1154        written = written | write_node(doc, size, "y",
1155                                       datainfo.source.beam_size.y,
1156                                       {"unit": datainfo.source.beam_size_unit})
1157        written = written | write_node(doc, size, "z",
1158                                       datainfo.source.beam_size.z,
1159                                       {"unit": datainfo.source.beam_size_unit})
[c8e1996]1160        if written:
[61cada5]1161            source.appendChild(size)
[2f4b430]1162
[f32d144]1163        write_node(doc, source, "wavelength", datainfo.source.wavelength,
1164                   {"unit": datainfo.source.wavelength_unit})
1165        write_node(doc, source, "wavelength_min",
1166                   datainfo.source.wavelength_min,
1167                   {"unit": datainfo.source.wavelength_min_unit})
1168        write_node(doc, source, "wavelength_max",
1169                   datainfo.source.wavelength_max,
1170                   {"unit": datainfo.source.wavelength_max_unit})
1171        write_node(doc, source, "wavelength_spread",
1172                   datainfo.source.wavelength_spread,
1173                   {"unit": datainfo.source.wavelength_spread_unit})
[2f4b430]1174
[61cada5]1175        #   Collimation
1176        for item in datainfo.collimation:
1177            coll = doc.createElement("SAScollimation")
1178            if item.name is not None:
1179                coll.setAttribute("name", str(item.name))
1180            instr.appendChild(coll)
[2f4b430]1181
[f32d144]1182            write_node(doc, coll, "length", item.length,
1183                       {"unit": item.length_unit})
[2f4b430]1184
[61cada5]1185            for apert in item.aperture:
1186                ap = doc.createElement("aperture")
1187                if apert.name is not None:
1188                    ap.setAttribute("name", str(apert.name))
1189                if apert.type is not None:
1190                    ap.setAttribute("type", str(apert.type))
1191                coll.appendChild(ap)
[2f4b430]1192
[f32d144]1193                write_node(doc, ap, "distance", apert.distance,
1194                           {"unit": apert.distance_unit})
[2f4b430]1195
[61cada5]1196                size = doc.createElement("size")
1197                if apert.size_name is not None:
1198                    size.setAttribute("name", str(apert.size_name))
[f32d144]1199                written = write_node(doc, size, "x", apert.size.x,
1200                                     {"unit": apert.size_unit})
1201                written = written | write_node(doc, size, "y", apert.size.y,
1202                                               {"unit": apert.size_unit})
1203                written = written | write_node(doc, size, "z", apert.size.z,
1204                                               {"unit": apert.size_unit})
[c8e1996]1205                if written:
[61cada5]1206                    ap.appendChild(size)
1207
1208        #   Detectors
1209        for item in datainfo.detector:
1210            det = doc.createElement("SASdetector")
1211            written = write_node(doc, det, "name", item.name)
[f32d144]1212            written = written | write_node(doc, det, "SDD", item.distance,
1213                                           {"unit": item.distance_unit})
1214            written = written | write_node(doc, det, "slit_length",
1215                                           item.slit_length,
1216                                           {"unit": item.slit_length_unit})
[c8e1996]1217            if written:
[61cada5]1218                instr.appendChild(det)
[2f4b430]1219
[61cada5]1220            off = doc.createElement("offset")
[f32d144]1221            written = write_node(doc, off, "x", item.offset.x,
1222                                 {"unit": item.offset_unit})
1223            written = written | write_node(doc, off, "y", item.offset.y,
1224                                           {"unit": item.offset_unit})
1225            written = written | write_node(doc, off, "z", item.offset.z,
1226                                           {"unit": item.offset_unit})
[c8e1996]1227            if written:
[61cada5]1228                det.appendChild(off)
[2f4b430]1229
[61cada5]1230            center = doc.createElement("beam_center")
[f32d144]1231            written = write_node(doc, center, "x", item.beam_center.x,
1232                                 {"unit": item.beam_center_unit})
1233            written = written | write_node(doc, center, "y",
1234                                           item.beam_center.y,
1235                                           {"unit": item.beam_center_unit})
1236            written = written | write_node(doc, center, "z",
1237                                           item.beam_center.z,
1238                                           {"unit": item.beam_center_unit})
[c8e1996]1239            if written:
[61cada5]1240                det.appendChild(center)
[2f4b430]1241
[61cada5]1242            pix = doc.createElement("pixel_size")
[f32d144]1243            written = write_node(doc, pix, "x", item.pixel_size.x,
1244                                 {"unit": item.pixel_size_unit})
1245            written = written | write_node(doc, pix, "y", item.pixel_size.y,
1246                                           {"unit": item.pixel_size_unit})
1247            written = written | write_node(doc, pix, "z", item.pixel_size.z,
1248                                           {"unit": item.pixel_size_unit})
[c8e1996]1249            if written:
[61cada5]1250                det.appendChild(pix)
[2f4b430]1251
[61cada5]1252            ori = doc.createElement("orientation")
[f32d144]1253            written = write_node(doc, ori, "roll", item.orientation.x,
1254                                 {"unit": item.orientation_unit})
1255            written = written | write_node(doc, ori, "pitch",
1256                                           item.orientation.y,
1257                                           {"unit": item.orientation_unit})
1258            written = written | write_node(doc, ori, "yaw", item.orientation.z,
1259                                           {"unit": item.orientation_unit})
[c8e1996]1260            if written:
[61cada5]1261                det.appendChild(ori)
[2f4b430]1262
[61cada5]1263        # Processes info
1264        for item in datainfo.process:
1265            node = doc.createElement("SASprocess")
1266            entry_node.appendChild(node)
1267
1268            write_node(doc, node, "name", item.name)
1269            write_node(doc, node, "date", item.date)
1270            write_node(doc, node, "description", item.description)
1271            for term in item.term:
1272                value = term['value']
1273                del term['value']
1274                write_node(doc, node, "term", value, term)
1275            for note in item.notes:
1276                write_node(doc, node, "SASprocessnote", note)
1277        # Return the document, and the SASentry node associated with
1278        # the data we just wrote
1279        return doc, entry_node
[2f4b430]1280
[61cada5]1281    def _parse_state(self, entry):
1282        """
[5062bbf]1283        Read a fit result from an XML node
[2f4b430]1284
[f32d144]1285        :param entry: XML node to read from
[5062bbf]1286        :return: PageState object
[61cada5]1287        """
1288        # Create an empty state
[2f4b430]1289        state = None
[61cada5]1290        # Locate the P(r) node
1291        try:
[f32d144]1292            nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
1293                                namespaces={'ns': CANSAS_NS})
[e89aed5]1294            if nodes:
[b35d3d1]1295                # Create an empty state
[f32d144]1296                state = PageState()
[c8e1996]1297                state.from_xml(node=nodes[0])
[2f4b430]1298
[61cada5]1299        except:
[6c382da]1300            logging.info("XML document does not contain fitting information.\n"
1301                         + traceback.format_exc())
[2f4b430]1302
[61cada5]1303        return state
[2f4b430]1304
[e89aed5]1305    def _parse_simfit_state(self, entry):
1306        """
1307        Parses the saved data for a simultaneous fit
1308        :param entry: XML object to read from
1309        :return: XML object for a simultaneous fit or None
1310        """
1311        nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
1312                            namespaces={'ns': CANSAS_NS})
1313        if nodes:
1314            simfitstate = nodes[0].xpath('ns:simultaneous_fit',
1315                                         namespaces={'ns': CANSAS_NS})
1316            if simfitstate:
1317                from simfitpage import SimFitPageState
1318                sim_fit_state = SimFitPageState()
1319                simfitstate_0 = simfitstate[0]
1320                all = simfitstate_0.xpath('ns:select_all',
[c8e1996]1321                                          namespaces={'ns': CANSAS_NS})
[e89aed5]1322                atts = all[0].attrib
1323                checked = atts.get('checked')
1324                sim_fit_state.select_all = bool(checked)
1325                model_list = simfitstate_0.xpath('ns:model_list',
[c8e1996]1326                                                 namespaces={'ns': CANSAS_NS})
[e89aed5]1327                model_list_items = model_list[0].xpath('ns:model_list_item',
[c8e1996]1328                                                       namespaces={'ns':
1329                                                                    CANSAS_NS})
[e89aed5]1330                for model in model_list_items:
1331                    attrs = model.attrib
1332                    sim_fit_state.model_list.append(attrs)
[998ca90]1333
[e89aed5]1334                constraints = simfitstate_0.xpath('ns:constraints',
1335                                                namespaces={'ns': CANSAS_NS})
1336                constraint_list = constraints[0].xpath('ns:constraint',
[998ca90]1337                                               namespaces={'ns': CANSAS_NS})
[e89aed5]1338                for constraint in constraint_list:
1339                    attrs = constraint.attrib
1340                    sim_fit_state.constraints_list.append(attrs)
1341
1342                return sim_fit_state
1343            else:
1344                return None
1345
[ac5b69d]1346    def _parse_save_state_entry(self, dom):
[e9b12eaf]1347        """
[5062bbf]1348        Parse a SASentry
[2f4b430]1349
[5062bbf]1350        :param node: SASentry node
[2f4b430]1351
[df7ed14]1352        :return: Data1D/Data2D object
[2f4b430]1353
[e9b12eaf]1354        """
[df7ed14]1355        node = dom.xpath('ns:data_class', namespaces={'ns': CANSAS_NS})
1356        if not node or node[0].text.lstrip().rstrip() != "Data2D":
[ac5b69d]1357            return_value, _ = self._parse_entry(dom)
1358            numpy.trim_zeros(return_value.x)
1359            numpy.trim_zeros(return_value.y)
1360            numpy.trim_zeros(return_value.dy)
1361            size_dx = return_value.dx.size
1362            size_dxl = return_value.dxl.size
1363            size_dxw = return_value.dxw.size
1364            if size_dxl == 0 and size_dxw == 0:
1365                return_value.dxl = None
1366                return_value.dxw = None
1367                numpy.trim_zeros(return_value.dx)
1368            elif size_dx == 0:
1369                return_value.dx = None
1370                size_dx = size_dxl
1371                numpy.trim_zeros(return_value.dxl)
1372                numpy.trim_zeros(return_value.dxw)
[2f4b430]1373
[ac5b69d]1374            return return_value, _
[2f4b430]1375
[c8e1996]1376        # Parse 2D
[e9b12eaf]1377        data_info = Data2D()
[2f4b430]1378
[f32d144]1379        # Look up title
[e9b12eaf]1380        self._store_content('ns:Title', dom, 'title', data_info)
[2f4b430]1381
[f32d144]1382        # Look up run number
[e9b12eaf]1383        nodes = dom.xpath('ns:Run', namespaces={'ns': CANSAS_NS})
[f32d144]1384        for item in nodes:
[e9b12eaf]1385            if item.text is not None:
1386                value = item.text.strip()
1387                if len(value) > 0:
1388                    data_info.run.append(value)
1389                    if item.get('name') is not None:
1390                        data_info.run_name[value] = item.get('name')
[2f4b430]1391
[f32d144]1392        # Look up instrument name
1393        self._store_content('ns:SASinstrument/ns:name', dom,
1394                            'instrument', data_info)
[e9b12eaf]1395
1396        # Notes
1397        note_list = dom.xpath('ns:SASnote', namespaces={'ns': CANSAS_NS})
1398        for note in note_list:
1399            try:
1400                if note.text is not None:
1401                    note_value = note.text.strip()
1402                    if len(note_value) > 0:
1403                        data_info.notes.append(note_value)
[7673ecd]1404            except Exception:
[998ca90]1405                err_mess = "cansas_reader.read: error processing entry notes\n"
1406                err_mess += %s" % sys.exc_value
[e9b12eaf]1407                self.errors.append(err_mess)
1408                logging.error(err_mess)
[2f4b430]1409
[e9b12eaf]1410        # Sample info ###################
1411        entry = get_content('ns:SASsample', dom)
1412        if entry is not None:
1413            data_info.sample.name = entry.get('name')
[2f4b430]1414
[b1e609c]1415        self._store_content('ns:SASsample/ns:ID', dom, 'ID', data_info.sample)
[998ca90]1416        self._store_float('ns:SASsample/ns:thickness', dom, 'thickness',
1417                          data_info.sample)
1418        self._store_float('ns:SASsample/ns:transmission', dom, 'transmission',
1419                          data_info.sample)
1420        self._store_float('ns:SASsample/ns:temperature', dom, 'temperature',
1421                          data_info.sample)
1422
1423        nodes = dom.xpath('ns:SASsample/ns:details',
1424                          namespaces={'ns': CANSAS_NS})
[e9b12eaf]1425        for item in nodes:
1426            try:
1427                if item.text is not None:
1428                    detail_value = item.text.strip()
1429                    if len(detail_value) > 0:
1430                        data_info.sample.details.append(detail_value)
[7673ecd]1431            except Exception:
[998ca90]1432                err_mess = "cansas_reader.read: error processing entry notes\n"
1433                err_mess += %s" % sys.exc_value
[e9b12eaf]1434                self.errors.append(err_mess)
1435                logging.error(err_mess)
[2f4b430]1436
[e9b12eaf]1437        # Position (as a vector)
[998ca90]1438        self._store_float('ns:SASsample/ns:position/ns:x', dom, 'position.x',
1439                          data_info.sample)
1440        self._store_float('ns:SASsample/ns:position/ns:y', dom, 'position.y',
1441                          data_info.sample)
1442        self._store_float('ns:SASsample/ns:position/ns:z', dom, 'position.z',
1443                          data_info.sample)
[2f4b430]1444
[e9b12eaf]1445        # Orientation (as a vector)
[f32d144]1446        self._store_float('ns:SASsample/ns:orientation/ns:roll',
[b1e609c]1447                          dom, 'orientation.x', data_info.sample)
[f32d144]1448        self._store_float('ns:SASsample/ns:orientation/ns:pitch',
[b1e609c]1449                          dom, 'orientation.y', data_info.sample)
[f32d144]1450        self._store_float('ns:SASsample/ns:orientation/ns:yaw',
[b1e609c]1451                          dom, 'orientation.z', data_info.sample)
[2f4b430]1452
[e9b12eaf]1453        # Source info ###################
1454        entry = get_content('ns:SASinstrument/ns:SASsource', dom)
1455        if entry is not None:
1456            data_info.source.name = entry.get('name')
[2f4b430]1457
[f32d144]1458        self._store_content('ns:SASinstrument/ns:SASsource/ns:radiation',
[b1e609c]1459                            dom, 'radiation', data_info.source)
[f32d144]1460        self._store_content('ns:SASinstrument/ns:SASsource/ns:beam_shape',
[b1e609c]1461                            dom, 'beam_shape', data_info.source)
[f32d144]1462        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength',
[b1e609c]1463                          dom, 'wavelength', data_info.source)
[f32d144]1464        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_min',
[b1e609c]1465                          dom, 'wavelength_min', data_info.source)
[f32d144]1466        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_max',
[b1e609c]1467                          dom, 'wavelength_max', data_info.source)
[f32d144]1468        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_spread',
[b1e609c]1469                          dom, 'wavelength_spread', data_info.source)
[2f4b430]1470
[f32d144]1471        # Beam size (as a vector)
[e9b12eaf]1472        entry = get_content('ns:SASinstrument/ns:SASsource/ns:beam_size', dom)
1473        if entry is not None:
1474            data_info.source.beam_size_name = entry.get('name')
[2f4b430]1475
[f32d144]1476        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:x',
[b1e609c]1477                          dom, 'beam_size.x', data_info.source)
[f32d144]1478        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:y',
[b1e609c]1479                          dom, 'beam_size.y', data_info.source)
[f32d144]1480        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:z',
[b1e609c]1481                          dom, 'beam_size.z', data_info.source)
[2f4b430]1482
[e9b12eaf]1483        # Collimation info ###################
[f32d144]1484        nodes = dom.xpath('ns:SASinstrument/ns:SAScollimation',
1485                          namespaces={'ns': CANSAS_NS})
[e9b12eaf]1486        for item in nodes:
1487            collim = Collimation()
1488            if item.get('name') is not None:
1489                collim.name = item.get('name')
[f32d144]1490            self._store_float('ns:length', item, 'length', collim)
[2f4b430]1491
[e9b12eaf]1492            # Look for apertures
[f32d144]1493            apert_list = item.xpath('ns:aperture',
1494                                    namespaces={'ns': CANSAS_NS})
[e9b12eaf]1495            for apert in apert_list:
[f32d144]1496                aperture = Aperture()
[2f4b430]1497
[e9b12eaf]1498                # Get the name and type of the aperture
1499                aperture.name = apert.get('name')
1500                aperture.type = apert.get('type')
[2f4b430]1501
[f32d144]1502                self._store_float('ns:distance', apert, 'distance', aperture)
[2f4b430]1503
[e9b12eaf]1504                entry = get_content('ns:size', apert)
1505                if entry is not None:
1506                    aperture.size_name = entry.get('name')
[2f4b430]1507
[f32d144]1508                self._store_float('ns:size/ns:x', apert, 'size.x', aperture)
1509                self._store_float('ns:size/ns:y', apert, 'size.y', aperture)
[e9b12eaf]1510                self._store_float('ns:size/ns:z', apert, 'size.z', aperture)
[2f4b430]1511
[e9b12eaf]1512                collim.aperture.append(aperture)
[2f4b430]1513
[e9b12eaf]1514            data_info.collimation.append(collim)
[2f4b430]1515
[e9b12eaf]1516        # Detector info ######################
[f32d144]1517        nodes = dom.xpath('ns:SASinstrument/ns:SASdetector',
1518                          namespaces={'ns': CANSAS_NS})
[e9b12eaf]1519        for item in nodes:
[2f4b430]1520
[e9b12eaf]1521            detector = Detector()
[2f4b430]1522
[e9b12eaf]1523            self._store_content('ns:name', item, 'name', detector)
[f32d144]1524            self._store_float('ns:SDD', item, 'distance', detector)
[2f4b430]1525
[e9b12eaf]1526            # Detector offset (as a vector)
[f32d144]1527            self._store_float('ns:offset/ns:x', item, 'offset.x', detector)
1528            self._store_float('ns:offset/ns:y', item, 'offset.y', detector)
1529            self._store_float('ns:offset/ns:z', item, 'offset.z', detector)
[2f4b430]1530
[e9b12eaf]1531            # Detector orientation (as a vector)
[f32d144]1532            self._store_float('ns:orientation/ns:roll', item,
1533                              'orientation.x', detector)
1534            self._store_float('ns:orientation/ns:pitch', item,
1535                              'orientation.y', detector)
1536            self._store_float('ns:orientation/ns:yaw', item,
1537                              'orientation.z', detector)
[2f4b430]1538
[e9b12eaf]1539            # Beam center (as a vector)
[f32d144]1540            self._store_float('ns:beam_center/ns:x', item,
1541                              'beam_center.x', detector)
1542            self._store_float('ns:beam_center/ns:y', item,
1543                              'beam_center.y', detector)
1544            self._store_float('ns:beam_center/ns:z', item,
1545                              'beam_center.z', detector)
[2f4b430]1546
[e9b12eaf]1547            # Pixel size (as a vector)
[f32d144]1548            self._store_float('ns:pixel_size/ns:x', item,
1549                              'pixel_size.x', detector)
1550            self._store_float('ns:pixel_size/ns:y', item,
1551                              'pixel_size.y', detector)
1552            self._store_float('ns:pixel_size/ns:z', item,
1553                              'pixel_size.z', detector)
[2f4b430]1554
[e9b12eaf]1555            self._store_float('ns:slit_length', item, 'slit_length', detector)
[2f4b430]1556
[f32d144]1557            data_info.detector.append(detector)
[e9b12eaf]1558
1559        # Processes info ######################
1560        nodes = dom.xpath('ns:SASprocess', namespaces={'ns': CANSAS_NS})
1561        for item in nodes:
1562            process = Process()
1563            self._store_content('ns:name', item, 'name', process)
1564            self._store_content('ns:date', item, 'date', process)
1565            self._store_content('ns:description', item, 'description', process)
[2f4b430]1566
[e9b12eaf]1567            term_list = item.xpath('ns:term', namespaces={'ns': CANSAS_NS})
1568            for term in term_list:
1569                try:
1570                    term_attr = {}
1571                    for attr in term.keys():
1572                        term_attr[attr] = term.get(attr).strip()
1573                    if term.text is not None:
1574                        term_attr['value'] = term.text.strip()
1575                        process.term.append(term_attr)
1576                except:
[998ca90]1577                    err_mess = "cansas_reader.read: error processing "
1578                    err_mess += "entry notes\n  %s" % sys.exc_value
[e9b12eaf]1579                    self.errors.append(err_mess)
1580                    logging.error(err_mess)
[2f4b430]1581
[f32d144]1582            note_list = item.xpath('ns:SASprocessnote',
1583                                   namespaces={'ns': CANSAS_NS})
[e9b12eaf]1584            for note in note_list:
1585                if note.text is not None:
1586                    process.notes.append(note.text.strip())
[2f4b430]1587
[e9b12eaf]1588            data_info.process.append(process)
[2f4b430]1589
[e9b12eaf]1590        # Data info ######################
1591        nodes = dom.xpath('ns:SASdata', namespaces={'ns': CANSAS_NS})
[f32d144]1592        if len(nodes) > 1:
[a95ae9a]1593            raise RuntimeError, "CanSAS reader is not compatible with" + \
1594                                " multiple SASdata entries"
[2f4b430]1595
[df7ed14]1596        for entry in nodes:
[b1e609c]1597            for item in LIST_OF_DATA_2D_ATTR:
[c8e1996]1598                # get node
[f32d144]1599                node = get_content('ns:%s' % item[0], entry)
[b1e609c]1600                setattr(data_info, item[1], parse_entry_helper(node, item))
[2f4b430]1601
[b1e609c]1602            for item in LIST_OF_DATA_2D_VALUES:
[f32d144]1603                field = get_content('ns:%s' % item[0], entry)
[b1e609c]1604                value_list = []
[df7ed14]1605                if field is not None:
[998ca90]1606                    value_list = \
1607                        [parse_entry_helper(node, item) for node in field]
[b1e609c]1608                if len(value_list) < 2:
1609                    setattr(data_info, item[0], None)
[44f7c1b]1610                else:
[b1e609c]1611                    setattr(data_info, item[0], numpy.array(value_list))
[2f4b430]1612
[e9b12eaf]1613        return data_info
1614
[61cada5]1615    def _read_cansas(self, path):
[f32d144]1616        """
[e89aed5]1617        Load data and fitting information from a CanSAS XML file.
[2f4b430]1618
[5062bbf]1619        :param path: file path
[f32d144]1620        :return: Data1D object if a single SASentry was found,
[5062bbf]1621                    or a list of Data1D objects if multiple entries were found,
1622                    or None of nothing was found
1623        :raise RuntimeError: when the file can't be opened
1624        :raise ValueError: when the length of the data vectors are inconsistent
[61cada5]1625        """
1626        output = []
[e89aed5]1627        simfitstate = None
[f32d144]1628        basename = os.path.basename(path)
[b63dc6e]1629        root, extension = os.path.splitext(basename)
1630        ext = extension.lower()
[61cada5]1631        try:
1632            if os.path.isfile(path):
[a95ae9a]1633                if ext in self.ext or ext == '.xml':
[61cada5]1634                    tree = etree.parse(path, parser=etree.ETCompatXMLParser())
1635                    # Check the format version number
[a95ae9a]1636                    # Specifying the namespace will take care of the file
1637                    # format version
[61cada5]1638                    root = tree.getroot()
[f32d144]1639                    entry_list = root.xpath('ns:SASentry',
1640                                            namespaces={'ns': CANSAS_NS})
1641                    for entry in entry_list:
[e9b12eaf]1642                        try:
[ac5b69d]1643                            sas_entry, _ = self._parse_save_state_entry(entry)
[e9b12eaf]1644                        except:
[df7ed14]1645                            raise
[61cada5]1646                        fitstate = self._parse_state(entry)
[2f4b430]1647
[a95ae9a]1648                        # state could be None when .svs file is loaded
1649                        # in this case, skip appending to output
1650                        if fitstate is not None:
[b35d3d1]1651                            sas_entry.meta_data['fitstate'] = fitstate
1652                            sas_entry.filename = fitstate.file
1653                            output.append(sas_entry)
[e89aed5]1654
[61cada5]1655            else:
[b63dc6e]1656                self.call_back(format=ext)
[61cada5]1657                raise RuntimeError, "%s is not a file" % path
[b35d3d1]1658
[61cada5]1659            # Return output consistent with the loader's api
[f32d144]1660            if len(output) == 0:
1661                self.call_back(state=None, datainfo=None, format=ext)
[61cada5]1662                return None
1663            else:
[b35d3d1]1664                for ind in range(len(output)):
1665                    # Call back to post the new state
1666                    state = output[ind].meta_data['fitstate']
1667                    t = time.localtime(state.timestamp)
1668                    time_str = time.strftime("%b %d %H:%M", t)
1669                    # Check that no time stamp is already appended
1670                    max_char = state.file.find("[")
1671                    if max_char < 0:
1672                        max_char = len(state.file)
[ef16f59]1673                    original_fname = state.file[0:max_char]
[f32d144]1674                    state.file = original_fname + ' [' + time_str + ']'
[2f4b430]1675
[b35d3d1]1676                    if state is not None and state.is_data is not None:
[b1e609c]1677                        output[ind].is_data = state.is_data
[2f4b430]1678
[b35d3d1]1679                    output[ind].filename = state.file
1680                    state.data = output[ind]
[f32d144]1681                    state.data.name = output[ind].filename  # state.data_name
[b35d3d1]1682                    state.data.id = state.data_id
1683                    if state.is_data is not None:
1684                        state.data.is_data = state.is_data
[f32d144]1685                    if output[ind].run_name is not None\
[c8e1996]1686                         and len(output[ind].run_name) != 0:
[654e8e0]1687                        if isinstance(output[ind].run_name, dict):
1688                            name = output[ind].run_name.keys()[0]
1689                        else:
1690                            name = output[ind].run_name
[f32d144]1691                    else:
1692                        name = original_fname
[ef16f59]1693                    state.data.group_id = name
[a95ae9a]1694                    # store state in fitting
[f32d144]1695                    self.call_back(state=state,
1696                                   datainfo=output[ind], format=ext)
1697                    self.state = state
[e6de6b8]1698                simfitstate = self._parse_simfit_state(entry)
[e89aed5]1699                if simfitstate is not None:
1700                    self.call_back(state=simfitstate)
1701
[ef16f59]1702                return output
[61cada5]1703        except:
[4bee68d]1704            self.call_back(format=ext)
[61cada5]1705            raise
[2f4b430]1706
[61cada5]1707    def write(self, filename, datainfo=None, fitstate=None):
1708        """
[b35d3d1]1709        Write the content of a Data1D as a CanSAS XML file only for standalone
[2f4b430]1710
[5062bbf]1711        :param filename: name of the file to write
1712        :param datainfo: Data1D object
1713        :param fitstate: PageState object
[2f4b430]1714
[61cada5]1715        """
1716        # Sanity check
[a95ae9a]1717        if self.cansas:
[61cada5]1718            # Add fitting information to the XML document
[ef16f59]1719            doc = self.write_toXML(datainfo, fitstate)
[61cada5]1720            # Write the XML document
1721        else:
[c8e1996]1722            doc = fitstate.to_xml(file=filename)
[2f4b430]1723
[ac5b69d]1724        # Save the document no matter the type
1725        fd = open(filename, 'w')
1726        fd.write(doc.toprettyxml())
1727        fd.close()
[2f4b430]1728
[a95ae9a]1729    def write_toXML(self, datainfo=None, state=None, batchfit=None):
[b35d3d1]1730        """
[f32d144]1731        Write toXML, a helper for write(),
1732        could be used by guimanager._on_save()
[2f4b430]1733
[b35d3d1]1734        : return: xml doc
1735        """
[3c44c66]1736
[a95ae9a]1737        self.batchfit_params = batchfit
1738        if state.data is None or not state.data.is_data:
[3b148c3]1739            return None
[a95ae9a]1740        # make sure title and data run are filled.
1741        if state.data.title is None or state.data.title == '':
1742            state.data.title = state.data.name
1743        if state.data.run_name is None or state.data.run_name == {}:
1744            state.data.run = [str(state.data.name)]
1745            state.data.run_name[0] = state.data.name
1746
1747        if issubclass(state.data.__class__,
1748                      sas.sascalc.dataloader.data_info.Data1D):
1749            data = state.data
1750            doc, sasentry = self._to_xml_doc(data)
[f32d144]1751        else:
[a95ae9a]1752            data = state.data
1753            doc, sasentry = self._data2d_to_xml_doc(data)
[2f4b430]1754
[b35d3d1]1755        if state is not None:
[c8e1996]1756            doc = state.to_xml(doc=doc, file=data.filename, entry_node=sasentry,
1757                               batch_fit_state=self.batchfit_params)
[2f4b430]1758
[f32d144]1759        return doc
[467202f]1760
1761# Simple html report templet
1762HEADER = "<html>\n"
1763HEADER += "<head>\n"
1764HEADER += "<meta http-equiv=Content-Type content='text/html; "
1765HEADER += "charset=windows-1252'> \n"
1766HEADER += "<meta name=Generator >\n"
1767HEADER += "</head>\n"
1768HEADER += "<body lang=EN-US>\n"
1769HEADER += "<div class=WordSection1>\n"
1770HEADER += "<p class=MsoNormal><b><span ><center><font size='4' >"
1771HEADER += "%s</font></center></span></center></b></p>"
1772HEADER += "<p class=MsoNormal>&nbsp;</p>"
1773PARA = "<p class=MsoNormal><font size='4' > %s \n"
1774PARA += "</font></p>"
1775CENTRE = "<p class=MsoNormal><center><font size='4' > %s \n"
1776CENTRE += "</font></center></p>"
1777FEET_1 = \
1778"""
1779<p class=MsoNormal>&nbsp;</p>
1780<br>
1781<p class=MsoNormal><b><span ><center> <font size='4' > Graph
1782</font></span></center></b></p>
1783<p class=MsoNormal>&nbsp;</p>
1784<center>
1785<br><font size='4' >Model Computation</font>
1786<br><font size='4' >Data: "%s"</font><br>
1787"""
1788FEET_2 = \
1789"""
1790<img src="%s" >
1791</img>
1792"""
1793FEET_3 = \
1794"""
1795</center>
1796</div>
1797</body>
1798</html>
1799"""
[c8e1996]1800ELINE = "<p class=MsoNormal>&nbsp;</p>"
Note: See TracBrowser for help on using the repository browser.