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

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 c8e1996 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
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 _repr_helper(self, list, rep):
359        """
360        Helper method to print a state
361        """
362        for item in list:
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])
373        return rep
374
375    def __repr__(self):
376        """
377        output string for printing
378        """
379        rep = "\nState name: %s\n" % self.file
380        t = time.localtime(self.timestamp)
381        time_str = time.strftime("%b %d %Y %H;%M;%S ", t)
382
383        rep += "State created: %s\n" % time_str
384        rep += "State form factor combobox selection: %s\n" % \
385               self.formfactorcombobox
386        rep += "State structure factor combobox selection: %s\n" % \
387               self.structurecombobox
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
391        if self.model is not None:
392            m_name = self.model.__class__.__name__
393            if m_name == 'Model':
394                m_name = self.m_name
395            rep += "model name : %s\n" % m_name
396        else:
397            rep += "model name : None\n"
398        rep += "multi_factor : %s\n" % str(self.multi_factor)
399        rep += "magnetic_on : %s\n" % str(self.magnetic_on)
400        rep += "model type (Category) selected: %s\n" % self.categorycombobox
401        rep += "data : %s\n" % str(self.data)
402        rep += "Plotting Range: min: %s, max: %s, steps: %s\n" % \
403               (str(self.qmin), str(self.qmax), str(self.npts))
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)
412
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)
417
418        rep += "2D enable : %s\n" % str(self.enable2D)
419        rep += "All parameters checkbox selected: %s\n" % self.cb1
420        rep += "Value of Chisqr : %s\n" % str(self.tcChi)
421        rep += "Smear object : %s\n" % self.smearer
422        rep += "Smear type : %s\n" % self.smear_type
423        rep += "dq_l  : %s\n" % self.dq_l
424        rep += "dq_r  : %s\n" % self.dq_r
425        rep += "dx_max  : %s\n" % str(self.dx_max)
426        rep += "dx_min : %s\n" % str(self.dx_min)
427        rep += "dxl  : %s\n" % str(self.dxl)
428        rep += "dxw : %s\n" % str(self.dxw)
429        rep += "model  : %s\n\n" % str(self.model)
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:
439                    if item not in self.orientation_params:
440                        temp_parameters.append(item)
441                for item in self.fittable_param:
442                    if item not in self.orientation_params_disp:
443                        temp_fittable_param.append(item)
444            else:
445                temp_parameters = self.parameters
446                temp_fittable_param = self.fittable_param
447
448            rep += "number parameters(self.parameters): %s\n" % \
449                   len(temp_parameters)
450            rep = self._repr_helper(list=temp_parameters, rep=rep)
451            rep += "number str_parameters(self.str_parameters): %s\n" % \
452                   len(self.str_parameters)
453            rep = self._repr_helper(list=self.str_parameters, rep=rep)
454            rep += "number fittable_param(self.fittable_param): %s\n" % \
455                   len(temp_fittable_param)
456            rep = self._repr_helper(list=temp_fittable_param, rep=rep)
457        return rep
458
459    def set_report_string(self):
460        """
461        Get the values (strings) from __str__ for report
462        """
463        # Dictionary of the report strings
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]
483            except Exception:
484                msg = "Report string expected 'name: value' but got %r" % line
485                logging.error(msg)
486            if name.count("State created"):
487                repo_time = "" + value
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 + ','
499            fixed_parameter = False
500            if name == "selected":
501                if value == u' False':
502                    fixed_parameter = True
503            if name == "error value":
504                if fixed_parameter:
505                    param_string += '(fixed),'
506                else:
507                    param_string += value + ','
508            if name == "parameter unit":
509                param_string += value + ':'
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:
520                    file_value = ("File name:" + content[2])
521                    file_name = CENTRE % file_value
522                    if len(title) == 0:
523                        title = content[2] + " [" + repo_time + "]"
524                        title_name = HEADER % title
525                except Exception:
526                    msg = "While parsing 'data: ...'\n"
527                    logging.error(msg + traceback.format_exc())
528            if name == "model name ":
529                try:
530                    modelname = "Model name:" + content[1]
531                except:
532                    modelname = "Model name:" + " NAN"
533                model_name = CENTRE % modelname
534
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
541                except Exception:
542                    msg = "While parsing 'Plotting Range: ...'\n"
543                    logging.error(msg + traceback.format_exc())
544        paramval = ""
545        for lines in param_string.split(":"):
546            line = lines.split(",")
547            if len(lines) > 0:
548                param = line[0]
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):
555                    paramval += param + "\n"
556                    paramval_string += CENTRE % param + "\n"
557
558        text_string = "\n\n\n%s\n\n%s\n%s\n%s\n\n%s" % \
559                      (title, file, q_name, chi2, paramval)
560
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)
564
565        html_string = title_name + "\n" + file_name + \
566                                   "\n" + model_name + \
567                                   "\n" + q_range + \
568                                   "\n" + chi2_string + \
569                                   "\n" + ELINE + \
570                                   "\n" + paramval_string + \
571                                   "\n" + ELINE + \
572                                   "\n" + FEET_1 % title + \
573                                   "\n" + FEET_2
574
575        return html_string, text_string, title
576
577    def _check_html_format(self, name):
578        """
579        Check string '%' for html format
580        """
581        if name.count('%'):
582            name = name.replace('%', '&#37')
583
584        return name
585
586    def report(self, figs=None, canvases=None):
587        """
588        Invoke report dialog panel
589
590        : param figs: list of pylab figures [list]
591        """
592        from sas.sasgui.perspectives.fitting.report_dialog import ReportDialog
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:
597            add_str = FEET_3
598        elif len(figs) == 2:
599            add_str = ELINE
600            add_str += FEET_2 % ("%s")
601            add_str += ELINE
602            add_str += FEET_3
603        elif len(figs) > 2:
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
610        else:
611            add_str = ""
612
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)
618        report_list = [report_str, text_str, images]
619        dialog = ReportDialog(report_list, None, wx.ID_ANY, "")
620        dialog.Show()
621
622    def _to_xml_helper(self, thelist, element, newdoc):
623        """
624        Helper method to create xml file for saving state
625        """
626        for item in thelist:
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)
639
640    def to_xml(self, file="fitting_state.fitv", doc=None,
641               entry_node=None, batch_fit_state=None):
642        """
643        Writes the state of the fit panel to file, as XML.
644
645        Compatible with standalone writing, or appending to an
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.
648
649        :param file: file to write to
650        :param doc: XML document object [optional]
651        :param entry_node: XML node within the XML document at which we
652                           will append the data [optional]
653        :param batch_fit_state: simultaneous fit state
654        """
655        from xml.dom.minidom import getDOMImplementation
656
657        # Check whether we have to write a standalone XML file
658        if doc is None:
659            impl = getDOMImplementation()
660            doc_type = impl.createDocumentType(FITTING_NODE_NAME, "1.0", "1.0")
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
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)
672            if entry_node is None:
673                newdoc.documentElement.appendChild(top_element)
674            else:
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)
682
683        attr = newdoc.createAttribute("version")
684        attr.nodeValue = '1.0'
685        top_element.setAttributeNode(attr)
686
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)
694
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)
701
702        # Inputs
703        inputs = newdoc.createElement("Attributes")
704        top_element.appendChild(inputs)
705
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
714
715        for item in LIST_OF_DATA_ATTRIBUTES:
716            element = newdoc.createElement(item[0])
717            element.setAttribute(item[0], str(getattr(self, item[1])))
718            inputs.appendChild(element)
719
720        for item in LIST_OF_STATE_ATTRIBUTES:
721            element = newdoc.createElement(item[0])
722            element.setAttribute(item[0], str(getattr(self, item[1])))
723            inputs.appendChild(element)
724
725        # For self.values ={ disp_param_name: [vals,...],...}
726        # and for self.weights ={ disp_param_name: [weights,...],...}
727        for item in LIST_OF_MODEL_ATTRIBUTES:
728            element = newdoc.createElement(item[0])
729            value_list = getattr(self, item[1])
730            for key, value in value_list.iteritems():
731                sub_element = newdoc.createElement(key)
732                sub_element.setAttribute('name', str(key))
733                for val in value:
734                    sub_element.appendChild(newdoc.createTextNode(str(val)))
735
736                element.appendChild(sub_element)
737            inputs.appendChild(element)
738
739        # Create doc for the dictionary of self._disp_obj_dic
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():
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)
749
750        for item in LIST_OF_STATE_PARAMETERS:
751            element = newdoc.createElement(item[0])
752            self._to_xml_helper(thelist=getattr(self, item[1]),
753                                element=element, newdoc=newdoc)
754            inputs.appendChild(element)
755
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:
770                if constraint.model_cbox.GetValue() != "":
771                    # model_cbox, param_cbox, egal_txt, constraint,
772                    # btRemove, sizer
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)
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
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:
813            return newdoc
814
815    def _from_xml_helper(self, node, list):
816        """
817        Helper function to write state to xml
818        """
819        for item in node:
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:
829                selected_to_fit = (item.get('selected_to_fit') == "True")
830            except:
831                selected_to_fit = None
832            try:
833                error_displayed = (item.get('error_displayed') == "True")
834            except:
835                error_displayed = None
836            try:
837                error_value = item.get('error_value')
838            except:
839                error_value = None
840            try:
841                minimum_displayed = (item.get('minimum_displayed') == "True")
842            except:
843                minimum_displayed = None
844            try:
845                minimum_value = item.get('minimum_value')
846            except:
847                minimum_value = None
848            try:
849                maximum_displayed = (item.get('maximum_displayed') == "True")
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
860            list.append([selected_to_fit, name, value, "+/-",
861                         [error_displayed, error_value],
862                         [minimum_displayed, minimum_value],
863                         [maximum_displayed, maximum_value], unit])
864
865    def from_xml(self, file=None, node=None):
866        """
867        Load fitting state from a file
868
869        :param file: .fitv file
870        :param node: node of a XML document to read from
871        """
872        if file is not None:
873            msg = "PageState no longer supports non-CanSAS"
874            msg += " format for fitting files"
875            raise RuntimeError, msg
876
877        if node.get('version') and node.get('version') == '1.0':
878
879            # Get file name
880            entry = get_content('ns:filename', node)
881            if entry is not None:
882                self.file = entry.text.strip()
883
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)
893
894            if entry is not None:
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
901                for item in LIST_OF_STATE_ATTRIBUTES:
902                    node = get_content('ns:%s' % item[0], entry)
903                    setattr(self, item[0], parse_entry_helper(node, item))
904
905                for item in LIST_OF_STATE_PARAMETERS:
906                    node = get_content("ns:%s" % item[0], entry)
907                    self._from_xml_helper(node=node,
908                                          list=getattr(self, item[1]))
909
910                # Recover _disp_obj_dict from xml file
911                self._disp_obj_dict = {}
912                for tagname, varname, tagtype in DISPERSION_LIST:
913                    node = get_content("ns:%s" % tagname, entry)
914                    for attr in node:
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:
924                                base = "unable to load distribution %r for %s"
925                                logging.error(base % (value, parameter))
926                                continue
927                        _disp_obj_dict = getattr(self, varname)
928                        _disp_obj_dict[parameter] = value
929
930                # get self.values and self.weights dic. if exists
931                for tagname, varname in LIST_OF_MODEL_ATTRIBUTES:
932                    node = get_content("ns:%s" % tagname, entry)
933                    dic = {}
934                    value_list = []
935                    for par in node:
936                        name = par.get('name')
937                        values = par.text.split()
938                        # Get lines only with numbers
939                        for line in values:
940                            try:
941                                val = float(line)
942                                value_list.append(val)
943                            except Exception:
944                                # pass if line is empty (it happens)
945                                msg = ("Error reading %r from %s %s\n"
946                                       % (line, tagname, name))
947                                logging.error(msg + traceback.format_exc())
948                        dic[name] = numpy.array(value_list)
949                    setattr(self, varname, dic)
950
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()
962
963        # For no figures in the list, prepare empty plot
964        if figs is None or len(figs) == 0:
965            figs = [None]
966
967        # Loop over the list of figures
968        # use wx.MemoryFSHandler
969        self.imgRAM = wx.MemoryFSHandler()
970        for fig in figs:
971            if fig is not None:
972                ind = figs.index(fig)
973                canvas = canvases[ind]
974
975            # store the image in wx.FileSystem Object
976            wx.FileSystem.AddHandler(wx.MemoryFSHandler())
977
978            # index of the fig
979            ind = figs.index(fig)
980
981            # AddFile, image can be retrieved with 'memory:filename'
982            self.imgRAM.AddFile('img_fit%s.png' % ind,
983                                canvas.bitmap, wx.BITMAP_TYPE_PNG)
984
985            # append figs
986            images.append(fig)
987
988        return images
989
990
991class Reader(CansasReader):
992    """
993    Class to load a .fitv fitting file
994    """
995    # File type
996    type_name = "Fitting"
997
998    # Wildcards
999    type = ["Fitting files (*.fitv)|*.fitv"
1000            "SASView file (*.svs)|*.svs"]
1001    # List of allowed extensions
1002    ext = ['.fitv', '.FITV', '.svs', 'SVS']
1003
1004    def __init__(self, call_back=None, cansas=True):
1005        CansasReader.__init__(self)
1006        """
1007        Initialize the call-back method to be called
1008        after we load a file
1009
1010        :param call_back: call-back method
1011        :param cansas:  True = files will be written/read in CanSAS format
1012                        False = write CanSAS format
1013
1014        """
1015        # Call back method to be executed after a file is read
1016        self.call_back = call_back
1017        # CanSAS format flag
1018        self.cansas = cansas
1019        self.state = None
1020        # batch fitting params for saving
1021        self.batchfit_params = []
1022
1023    def get_state(self):
1024        return self.state
1025
1026    def read(self, path):
1027        """
1028        Load a new P(r) inversion state from file
1029
1030        :param path: file path
1031
1032        """
1033        if self.cansas:
1034            return self._read_cansas(path)
1035
1036    def _data2d_to_xml_doc(self, datainfo):
1037        """
1038        Create an XML document to contain the content of a Data2D
1039
1040        :param datainfo: Data2D object
1041
1042        """
1043        if not issubclass(datainfo.__class__, Data2D):
1044            raise RuntimeError, "The cansas writer expects a Data2D instance"
1045
1046        title = "cansas1d/%s" % self.version
1047        title += "http://svn.smallangles.net/svn/canSAS/1dwg/trunk/cansas1d.xsd"
1048        doc = xml.dom.minidom.Document()
1049        main_node = doc.createElement("SASroot")
1050        main_node.setAttribute("version", self.version)
1051        main_node.setAttribute("xmlns", "cansas1d/%s" % self.version)
1052        main_node.setAttribute("xmlns:xsi",
1053                               "http://www.w3.org/2001/XMLSchema-instance")
1054        main_node.setAttribute("xsi:schemaLocation", title)
1055
1056        doc.appendChild(main_node)
1057
1058        entry_node = doc.createElement("SASentry")
1059        main_node.appendChild(entry_node)
1060
1061        write_node(doc, entry_node, "Title", datainfo.title)
1062        if datainfo is not None:
1063            write_node(doc, entry_node, "data_class",
1064                       datainfo.__class__.__name__)
1065        for item in datainfo.run:
1066            runname = {}
1067            if item in datainfo.run_name and \
1068                            len(str(datainfo.run_name[item])) > 1:
1069                runname = {'name': datainfo.run_name[item]}
1070            write_node(doc, entry_node, "Run", item, runname)
1071        # Data info
1072        new_node = doc.createElement("SASdata")
1073        entry_node.appendChild(new_node)
1074        for item in LIST_OF_DATA_2D_ATTR:
1075            element = doc.createElement(item[0])
1076            element.setAttribute(item[0], str(getattr(datainfo, item[1])))
1077            new_node.appendChild(element)
1078
1079        for item in LIST_OF_DATA_2D_VALUES:
1080            root_node = doc.createElement(item[0])
1081            new_node.appendChild(root_node)
1082            temp_list = getattr(datainfo, item[1])
1083
1084            if temp_list is None or len(temp_list) == 0:
1085                element = doc.createElement(item[0])
1086                element.appendChild(doc.createTextNode(str(temp_list)))
1087                root_node.appendChild(element)
1088            else:
1089                for value in temp_list:
1090                    element = doc.createElement(item[0])
1091                    element.setAttribute(item[0], str(value))
1092                    root_node.appendChild(element)
1093
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))
1100        write_node(doc, sample, "thickness", datainfo.sample.thickness,
1101                   {"unit": datainfo.sample.thickness_unit})
1102        write_node(doc, sample, "transmission", datainfo.sample.transmission)
1103        write_node(doc, sample, "temperature", datainfo.sample.temperature,
1104                   {"unit": datainfo.sample.temperature_unit})
1105
1106        for item in datainfo.sample.details:
1107            write_node(doc, sample, "details", item)
1108
1109        pos = doc.createElement("position")
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})
1118        if written:
1119            sample.appendChild(pos)
1120
1121        ori = doc.createElement("orientation")
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,
1126                                       {"unit":
1127                                            datainfo.sample.orientation_unit})
1128        written = written | write_node(doc, ori, "yaw",
1129                                       datainfo.sample.orientation.z,
1130                                       {"unit":
1131                                            datainfo.sample.orientation_unit})
1132        if written:
1133            sample.appendChild(ori)
1134
1135        # Instrument info
1136        instr = doc.createElement("SASinstrument")
1137        entry_node.appendChild(instr)
1138
1139        write_node(doc, instr, "name", datainfo.instrument)
1140
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)
1146
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))
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})
1160        if written:
1161            source.appendChild(size)
1162
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})
1174
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)
1181
1182            write_node(doc, coll, "length", item.length,
1183                       {"unit": item.length_unit})
1184
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)
1192
1193                write_node(doc, ap, "distance", apert.distance,
1194                           {"unit": apert.distance_unit})
1195
1196                size = doc.createElement("size")
1197                if apert.size_name is not None:
1198                    size.setAttribute("name", str(apert.size_name))
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})
1205                if written:
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)
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})
1217            if written:
1218                instr.appendChild(det)
1219
1220            off = doc.createElement("offset")
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})
1227            if written:
1228                det.appendChild(off)
1229
1230            center = doc.createElement("beam_center")
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})
1239            if written:
1240                det.appendChild(center)
1241
1242            pix = doc.createElement("pixel_size")
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})
1249            if written:
1250                det.appendChild(pix)
1251
1252            ori = doc.createElement("orientation")
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})
1260            if written:
1261                det.appendChild(ori)
1262
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
1280
1281    def _parse_state(self, entry):
1282        """
1283        Read a fit result from an XML node
1284
1285        :param entry: XML node to read from
1286        :return: PageState object
1287        """
1288        # Create an empty state
1289        state = None
1290        # Locate the P(r) node
1291        try:
1292            nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
1293                                namespaces={'ns': CANSAS_NS})
1294            if nodes:
1295                # Create an empty state
1296                state = PageState()
1297                state.from_xml(node=nodes[0])
1298
1299        except:
1300            logging.info("XML document does not contain fitting information.\n"
1301                         + traceback.format_exc())
1302
1303        return state
1304
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',
1321                                          namespaces={'ns': CANSAS_NS})
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',
1326                                                 namespaces={'ns': CANSAS_NS})
1327                model_list_items = model_list[0].xpath('ns:model_list_item',
1328                                                       namespaces={'ns':
1329                                                                    CANSAS_NS})
1330                for model in model_list_items:
1331                    attrs = model.attrib
1332                    sim_fit_state.model_list.append(attrs)
1333
1334                constraints = simfitstate_0.xpath('ns:constraints',
1335                                                namespaces={'ns': CANSAS_NS})
1336                constraint_list = constraints[0].xpath('ns:constraint',
1337                                               namespaces={'ns': CANSAS_NS})
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
1346    def _parse_save_state_entry(self, dom):
1347        """
1348        Parse a SASentry
1349
1350        :param node: SASentry node
1351
1352        :return: Data1D/Data2D object
1353
1354        """
1355        node = dom.xpath('ns:data_class', namespaces={'ns': CANSAS_NS})
1356        if not node or node[0].text.lstrip().rstrip() != "Data2D":
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)
1373
1374            return return_value, _
1375
1376        # Parse 2D
1377        data_info = Data2D()
1378
1379        # Look up title
1380        self._store_content('ns:Title', dom, 'title', data_info)
1381
1382        # Look up run number
1383        nodes = dom.xpath('ns:Run', namespaces={'ns': CANSAS_NS})
1384        for item in nodes:
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')
1391
1392        # Look up instrument name
1393        self._store_content('ns:SASinstrument/ns:name', dom,
1394                            'instrument', data_info)
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)
1404            except Exception:
1405                err_mess = "cansas_reader.read: error processing entry notes\n"
1406                err_mess += %s" % sys.exc_value
1407                self.errors.append(err_mess)
1408                logging.error(err_mess)
1409
1410        # Sample info ###################
1411        entry = get_content('ns:SASsample', dom)
1412        if entry is not None:
1413            data_info.sample.name = entry.get('name')
1414
1415        self._store_content('ns:SASsample/ns:ID', dom, 'ID', data_info.sample)
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})
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)
1431            except Exception:
1432                err_mess = "cansas_reader.read: error processing entry notes\n"
1433                err_mess += %s" % sys.exc_value
1434                self.errors.append(err_mess)
1435                logging.error(err_mess)
1436
1437        # Position (as a vector)
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)
1444
1445        # Orientation (as a vector)
1446        self._store_float('ns:SASsample/ns:orientation/ns:roll',
1447                          dom, 'orientation.x', data_info.sample)
1448        self._store_float('ns:SASsample/ns:orientation/ns:pitch',
1449                          dom, 'orientation.y', data_info.sample)
1450        self._store_float('ns:SASsample/ns:orientation/ns:yaw',
1451                          dom, 'orientation.z', data_info.sample)
1452
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')
1457
1458        self._store_content('ns:SASinstrument/ns:SASsource/ns:radiation',
1459                            dom, 'radiation', data_info.source)
1460        self._store_content('ns:SASinstrument/ns:SASsource/ns:beam_shape',
1461                            dom, 'beam_shape', data_info.source)
1462        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength',
1463                          dom, 'wavelength', data_info.source)
1464        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_min',
1465                          dom, 'wavelength_min', data_info.source)
1466        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_max',
1467                          dom, 'wavelength_max', data_info.source)
1468        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_spread',
1469                          dom, 'wavelength_spread', data_info.source)
1470
1471        # Beam size (as a vector)
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')
1475
1476        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:x',
1477                          dom, 'beam_size.x', data_info.source)
1478        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:y',
1479                          dom, 'beam_size.y', data_info.source)
1480        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:z',
1481                          dom, 'beam_size.z', data_info.source)
1482
1483        # Collimation info ###################
1484        nodes = dom.xpath('ns:SASinstrument/ns:SAScollimation',
1485                          namespaces={'ns': CANSAS_NS})
1486        for item in nodes:
1487            collim = Collimation()
1488            if item.get('name') is not None:
1489                collim.name = item.get('name')
1490            self._store_float('ns:length', item, 'length', collim)
1491
1492            # Look for apertures
1493            apert_list = item.xpath('ns:aperture',
1494                                    namespaces={'ns': CANSAS_NS})
1495            for apert in apert_list:
1496                aperture = Aperture()
1497
1498                # Get the name and type of the aperture
1499                aperture.name = apert.get('name')
1500                aperture.type = apert.get('type')
1501
1502                self._store_float('ns:distance', apert, 'distance', aperture)
1503
1504                entry = get_content('ns:size', apert)
1505                if entry is not None:
1506                    aperture.size_name = entry.get('name')
1507
1508                self._store_float('ns:size/ns:x', apert, 'size.x', aperture)
1509                self._store_float('ns:size/ns:y', apert, 'size.y', aperture)
1510                self._store_float('ns:size/ns:z', apert, 'size.z', aperture)
1511
1512                collim.aperture.append(aperture)
1513
1514            data_info.collimation.append(collim)
1515
1516        # Detector info ######################
1517        nodes = dom.xpath('ns:SASinstrument/ns:SASdetector',
1518                          namespaces={'ns': CANSAS_NS})
1519        for item in nodes:
1520
1521            detector = Detector()
1522
1523            self._store_content('ns:name', item, 'name', detector)
1524            self._store_float('ns:SDD', item, 'distance', detector)
1525
1526            # Detector offset (as a vector)
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)
1530
1531            # Detector orientation (as a vector)
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)
1538
1539            # Beam center (as a vector)
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)
1546
1547            # Pixel size (as a vector)
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)
1554
1555            self._store_float('ns:slit_length', item, 'slit_length', detector)
1556
1557            data_info.detector.append(detector)
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)
1566
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:
1577                    err_mess = "cansas_reader.read: error processing "
1578                    err_mess += "entry notes\n  %s" % sys.exc_value
1579                    self.errors.append(err_mess)
1580                    logging.error(err_mess)
1581
1582            note_list = item.xpath('ns:SASprocessnote',
1583                                   namespaces={'ns': CANSAS_NS})
1584            for note in note_list:
1585                if note.text is not None:
1586                    process.notes.append(note.text.strip())
1587
1588            data_info.process.append(process)
1589
1590        # Data info ######################
1591        nodes = dom.xpath('ns:SASdata', namespaces={'ns': CANSAS_NS})
1592        if len(nodes) > 1:
1593            raise RuntimeError, "CanSAS reader is not compatible with" + \
1594                                " multiple SASdata entries"
1595
1596        for entry in nodes:
1597            for item in LIST_OF_DATA_2D_ATTR:
1598                # get node
1599                node = get_content('ns:%s' % item[0], entry)
1600                setattr(data_info, item[1], parse_entry_helper(node, item))
1601
1602            for item in LIST_OF_DATA_2D_VALUES:
1603                field = get_content('ns:%s' % item[0], entry)
1604                value_list = []
1605                if field is not None:
1606                    value_list = \
1607                        [parse_entry_helper(node, item) for node in field]
1608                if len(value_list) < 2:
1609                    setattr(data_info, item[0], None)
1610                else:
1611                    setattr(data_info, item[0], numpy.array(value_list))
1612
1613        return data_info
1614
1615    def _read_cansas(self, path):
1616        """
1617        Load data and fitting information from a CanSAS XML file.
1618
1619        :param path: file path
1620        :return: Data1D object if a single SASentry was found,
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
1625        """
1626        output = []
1627        simfitstate = None
1628        basename = os.path.basename(path)
1629        root, extension = os.path.splitext(basename)
1630        ext = extension.lower()
1631        try:
1632            if os.path.isfile(path):
1633                if ext in self.ext or ext == '.xml':
1634                    tree = etree.parse(path, parser=etree.ETCompatXMLParser())
1635                    # Check the format version number
1636                    # Specifying the namespace will take care of the file
1637                    # format version
1638                    root = tree.getroot()
1639                    entry_list = root.xpath('ns:SASentry',
1640                                            namespaces={'ns': CANSAS_NS})
1641                    for entry in entry_list:
1642                        try:
1643                            sas_entry, _ = self._parse_save_state_entry(entry)
1644                        except:
1645                            raise
1646                        fitstate = self._parse_state(entry)
1647
1648                        # state could be None when .svs file is loaded
1649                        # in this case, skip appending to output
1650                        if fitstate is not None:
1651                            sas_entry.meta_data['fitstate'] = fitstate
1652                            sas_entry.filename = fitstate.file
1653                            output.append(sas_entry)
1654
1655            else:
1656                self.call_back(format=ext)
1657                raise RuntimeError, "%s is not a file" % path
1658
1659            # Return output consistent with the loader's api
1660            if len(output) == 0:
1661                self.call_back(state=None, datainfo=None, format=ext)
1662                return None
1663            else:
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)
1673                    original_fname = state.file[0:max_char]
1674                    state.file = original_fname + ' [' + time_str + ']'
1675
1676                    if state is not None and state.is_data is not None:
1677                        output[ind].is_data = state.is_data
1678
1679                    output[ind].filename = state.file
1680                    state.data = output[ind]
1681                    state.data.name = output[ind].filename  # state.data_name
1682                    state.data.id = state.data_id
1683                    if state.is_data is not None:
1684                        state.data.is_data = state.is_data
1685                    if output[ind].run_name is not None\
1686                         and len(output[ind].run_name) != 0:
1687                        if isinstance(output[ind].run_name, dict):
1688                            name = output[ind].run_name.keys()[0]
1689                        else:
1690                            name = output[ind].run_name
1691                    else:
1692                        name = original_fname
1693                    state.data.group_id = name
1694                    # store state in fitting
1695                    self.call_back(state=state,
1696                                   datainfo=output[ind], format=ext)
1697                    self.state = state
1698                simfitstate = self._parse_simfit_state(entry)
1699                if simfitstate is not None:
1700                    self.call_back(state=simfitstate)
1701
1702                return output
1703        except:
1704            self.call_back(format=ext)
1705            raise
1706
1707    def write(self, filename, datainfo=None, fitstate=None):
1708        """
1709        Write the content of a Data1D as a CanSAS XML file only for standalone
1710
1711        :param filename: name of the file to write
1712        :param datainfo: Data1D object
1713        :param fitstate: PageState object
1714
1715        """
1716        # Sanity check
1717        if self.cansas:
1718            # Add fitting information to the XML document
1719            doc = self.write_toXML(datainfo, fitstate)
1720            # Write the XML document
1721        else:
1722            doc = fitstate.to_xml(file=filename)
1723
1724        # Save the document no matter the type
1725        fd = open(filename, 'w')
1726        fd.write(doc.toprettyxml())
1727        fd.close()
1728
1729    def write_toXML(self, datainfo=None, state=None, batchfit=None):
1730        """
1731        Write toXML, a helper for write(),
1732        could be used by guimanager._on_save()
1733
1734        : return: xml doc
1735        """
1736
1737        self.batchfit_params = batchfit
1738        if state.data is None or not state.data.is_data:
1739            return None
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)
1751        else:
1752            data = state.data
1753            doc, sasentry = self._data2d_to_xml_doc(data)
1754
1755        if state is not None:
1756            doc = state.to_xml(doc=doc, file=data.filename, entry_node=sasentry,
1757                               batch_fit_state=self.batchfit_params)
1758
1759        return doc
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"""
1800ELINE = "<p class=MsoNormal>&nbsp;</p>"
Note: See TracBrowser for help on using the repository browser.