source: sasview/src/sas/sasgui/perspectives/fitting/pagestate.py @ 377c19a2

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 377c19a2 was 377c19a2, checked in by krzywon, 7 years ago

#795: Form factor and structure factor parameters are handled separately to avoid conflicts in sasmodels convert.py.

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