""" Class that holds a fit page state Pagestate fields reflect the names of the gui controls from the sasview 3.x fit page, so they are somewhat difficult to interpret. Pagestate attributes are as follows: # =>name: desc indicates the attribute is derived # name(xml): desc indicates the attribute name differs from the xml tag # SasView version which saved the file version: (4, 1, 2) from # Session information group_id: unique id for fit page in running system (int) => data_group_id: unique id for data item in running system (None) # Data file data: contents of (Data1D or Data2D) data_name: filename + [num] (latex_smeared.xml [1]) data_id: filename + [num] + timestamp (latex_smeared.xml [1]1523303027.73) file: filename + [date time] (latex_smeared.xml [Apr 09 15:45]) name: ?? (None) npts: number of points (float) enable2D: True if data is 2D (or if model is 2D and no data) is_data: True (pagestate will not save if there is no data attached) # Data weighting dI_didata: True if dy = data.dy dI_idata: True if dy = data.y dI_noweight: True if dy = 1 dI_sqrdata: True if dy = sqrt(data.y) # Data selection qmax: maximum q (float) qmin: minimum q (float) => qmax_x: ?? (None) => qmin_x: ?? (None) # Resolution smearing enable_smearer: True if use data.dx disable_smearer: True if no smearing pinhole_smearer: True if custom pinhole smear slit_smearer: True if custom slit smear dq_l: 2D resolution dq_r: 2D resolution dx_old: True for 3.x version of custom pinhole, which used dx_min rather than dx_percent, with dx_percent interpreted as 100 * dx_percent/q[0] dx_percent: custom pinhole resolution percentage dxl: slit height for custom slit resolution dxw: slit width for custom slit resolution smearer: repr() for active smearer (None on load) smear_type: None (None on load) # Model selection categorycombobox: model category formfactorcombobox: model name (could be "[plug-in] name") structurecombobox: structure factor model name (string or None or "None") multi_factor: multiplicity (integer or None) magnetic_on: True if model is magnetic (only for 2D data for now) => model: active model object (None on load) # Model parameters # Parameter is a tuple with the following structure. The parentheses # indicate xml attribute for the tag: # fitted(selected_to_fit): True if parameter is fitted # name(name): display name for the parameter (string) # value(value): displayed parameter value (string) # => plusminus: '+/-' (constant string) # => uncertainty: tuple # (uncertainty_displayed): True if there is an uncertainty # (uncertainty_value): displayed uncertainty (string) # => lower: tuple # (minimum_displayed): True if there is a lower bound # (minimum_value): displayed lower bound (string) # => upper: tuple # (maximum_displayed): True if there is a upper bound # (maximum_value): displayed upper bound (string) # units(unit): displayed units parameters: list of normal parameters fixed_param: list of non-fitting parameters (nsigma, npts in dispersity) fittable_param: list of fittable dispersity parameters (distribution width) str_parameters: list of selection parameters (e.g, shell forms in spherical_sld) orientation_params(orientation_parameters): list of orientation and magnetic parameters (already included in parameters, so safe to ignore) orientation_params_disp(dispersity_parameters): list of orientation disperisty parameters (already included in fixed_param and fittable_param so safe to ignore) # Dispersity controls enable_disp: True if dispersity parameters disable_disp: True if no dispersity parameters disp_obj_dict(disp_obj): {'parameter.width': 'dispersity distribution'} values: {'parameter.width': [array distribution parameter values] } weights: {'parameter.width': [array distribution parameter weights] } => disp_box 0 => disp_cb_dict {} => disp_list [] # Simultaneous fitting => images: None (initialized but unused?) => reset: False (initialized but unused?) => event_owner None => m_name None => manager None => page_name => param_toFit: [] => process: list of process done on object [] (maybe managed by guiframe?) => saved_states {} => cb1: False (simfit cb1 is now stored in select_all) tcChi 1.3463 theory_data None timestamp 1523303103.74 Constraint attributes are as follows: constraint_dict {} constraints_list {'model_cbox': 'M2', 'param_cbox': 'scale', 'egal_txt': ' = ', 'constraint': 'M1.scale'} {'model_cbox': 'M2', 'param_cbox': 'radius', 'egal_txt': ' = ', 'constraint': 'M1.radius'} {'model_cbox': 'M2', 'param_cbox': 'radius.width', 'egal_txt': ' = ', 'constraint': 'M1.radius.width'} fit_page_no None model_list {'fit_number': '393', 'checked': 'True', 'fit_page_source': 'M2', 'name': 'latex_smeared.xml [1]1523535051.03', 'model_name': 'sphere'} {'fit_number': '335', 'checked': 'True', 'fit_page_source': 'M1', 'name': 'latex_smeared.xml 1523535050.03', 'model_name': 'sphere'} model_to_fit no_constraint 0 select_all True """ # TODO: Refactor code so we don't need to use getattr/setattr ################################################################################ # This software was developed by the University of Tennessee as part of the # Distributed Data Analysis of Neutron Scattering Experiments (DANSE) # project funded by the US National Science Foundation. # # See the license text in license.txt # # copyright 2009, University of Tennessee ################################################################################ import time import re import os import sys import copy import logging import numpy as np import traceback import xml.dom.minidom from xml.dom.minidom import parseString from xml.dom.minidom import getDOMImplementation from lxml import etree from sasmodels import convert import sasmodels.weights from sas.sasview import __version__ as SASVIEW_VERSION import sas.sascalc.dataloader from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader from sas.sascalc.dataloader.readers.cansas_reader import get_content, write_node from sas.sascalc.dataloader.data_info import Data2D, Collimation, Detector from sas.sascalc.dataloader.data_info import Process, Aperture logger = logging.getLogger(__name__) # Information to read/write state as xml FITTING_NODE_NAME = 'fitting_plug_in' CANSAS_NS = {"ns": "cansas1d/1.0"} CUSTOM_MODEL = 'Plugin Models' CUSTOM_MODEL_OLD = 'Customized Models' LIST_OF_DATA_ATTRIBUTES = [["is_data", "is_data", "bool"], ["group_id", "data_group_id", "string"], ["data_name", "data_name", "string"], ["data_id", "data_id", "string"], ["name", "name", "string"], ["data_name", "data_name", "string"]] LIST_OF_STATE_ATTRIBUTES = [["qmin", "qmin", "float"], ["qmax", "qmax", "float"], ["npts", "npts", "float"], ["categorycombobox", "categorycombobox", "string"], ["formfactorcombobox", "formfactorcombobox", "string"], ["structurecombobox", "structurecombobox", "string"], ["multi_factor", "multi_factor", "float"], ["magnetic_on", "magnetic_on", "bool"], ["enable_smearer", "enable_smearer", "bool"], ["disable_smearer", "disable_smearer", "bool"], ["pinhole_smearer", "pinhole_smearer", "bool"], ["slit_smearer", "slit_smearer", "bool"], ["enable_disp", "enable_disp", "bool"], ["disable_disp", "disable_disp", "bool"], ["dI_noweight", "dI_noweight", "bool"], ["dI_didata", "dI_didata", "bool"], ["dI_sqrdata", "dI_sqrdata", "bool"], ["dI_idata", "dI_idata", "bool"], ["enable2D", "enable2D", "bool"], ["cb1", "cb1", "bool"], ["tcChi", "tcChi", "float"], ["dq_l", "dq_l", "float"], ["dq_r", "dq_r", "float"], ["dx_percent", "dx_percent", "float"], ["dxl", "dxl", "float"], ["dxw", "dxw", "float"]] LIST_OF_MODEL_ATTRIBUTES = [["values", "values"], ["weights", "weights"]] DISPERSION_LIST = [["disp_obj_dict", "disp_obj_dict", "string"]] LIST_OF_STATE_PARAMETERS = [["parameters", "parameters"], ["str_parameters", "str_parameters"], ["orientation_parameters", "orientation_params"], ["dispersity_parameters", "orientation_params_disp"], ["fixed_param", "fixed_param"], ["fittable_param", "fittable_param"]] LIST_OF_DATA_2D_ATTR = [["xmin", "xmin", "float"], ["xmax", "xmax", "float"], ["ymin", "ymin", "float"], ["ymax", "ymax", "float"], ["_xaxis", "_xaxis", "string"], ["_xunit", "_xunit", "string"], ["_yaxis", "_yaxis", "string"], ["_yunit", "_yunit", "string"], ["_zaxis", "_zaxis", "string"], ["_zunit", "_zunit", "string"]] LIST_OF_DATA_2D_VALUES = [["qx_data", "qx_data", "float"], ["qy_data", "qy_data", "float"], ["dqx_data", "dqx_data", "float"], ["dqy_data", "dqy_data", "float"], ["data", "data", "float"], ["q_data", "q_data", "float"], ["err_data", "err_data", "float"], ["mask", "mask", "bool"]] def parse_entry_helper(node, item): """ Create a numpy list from value extrated from the node :param node: node from each the value is stored :param item: list name of three strings.the two first are name of data attribute and the third one is the type of the value of that attribute. type can be string, float, bool, etc. : return: numpy array """ if node is not None: if item[2] == "string": return str(node.get(item[0]).strip()) elif item[2] == "bool": try: return node.get(item[0]).strip() == "True" except Exception: return None else: try: return float(node.get(item[0])) except Exception: return None class PageState(object): """ Contains information to reconstruct a page of the fitpanel. """ def __init__(self, model=None, data=None): """ Initialize the current state :param model: a selected model within a page :param data: """ self.file = None # Time of state creation self.timestamp = time.time() # Data member to store the dispersion object created self.disp_obj_dict = {} # ------------------------ # Data used for fitting self.data = data # model data self.theory_data = None # Is 2D self.images = None # save additional information on data that dataloader.reader # does not read self.is_data = None self.data_name = "" if self.data is not None: self.data_name = self.data.name self.data_id = None if self.data is not None and hasattr(self.data, "id"): self.data_id = self.data.id self.data_group_id = None if self.data is not None and hasattr(self.data, "group_id"): self.data_group_id = self.data.group_id # reset True change the state of existing button self.reset = False # flag to allow data2D plot self.enable2D = False # model on which the fit would be performed self.model = model self.m_name = None # list of process done to model self.process = [] # fit page manager self.manager = None # Event_owner is the owner of model event self.event_owner = None # page name self.page_name = "" # Contains link between model, its parameters, and panel organization self.parameters = [] # String parameter list that can not be fitted self.str_parameters = [] # Contains list of parameters that cannot be fitted and reference to # panel objects self.fixed_param = [] # Contains list of parameters with dispersity and reference to # panel objects self.fittable_param = [] # orientation parameters self.orientation_params = [] # orientation parameters for gaussian dispersity self.orientation_params_disp = [] self.dq_l = None self.dq_r = None self.dx_percent = None self.dx_old = False self.dxl = None self.dxw = None # list of dispersion parameters self.disp_list = [] if self.model is not None: self.disp_list = self.model.getDispParamList() self.disp_cb_dict = {} self.values = {} self.weights = {} # contains link between a model and selected parameters to fit self.param_toFit = [] # save the state of the context menu self.saved_states = {} # save selection of combobox self.formfactorcombobox = None self.categorycombobox = None self.structurecombobox = None # radio box to select type of model # self.shape_rbutton = False # self.shape_indep_rbutton = False # self.struct_rbutton = False # self.plugin_rbutton = False # the indice of the current selection self.disp_box = 0 # Qrange # Q range self.qmin = 0.001 self.qmax = 0.1 # reset data range self.qmax_x = None self.qmin_x = None self.npts = None self.name = "" self.multi_factor = None self.magnetic_on = False # enable smearering state self.enable_smearer = False self.disable_smearer = True self.pinhole_smearer = False self.slit_smearer = False # weighting options self.dI_noweight = False self.dI_didata = True self.dI_sqrdata = False self.dI_idata = False # disperity selection self.enable_disp = False self.disable_disp = True # state of selected all check button self.cb1 = False # store value of chisqr self.tcChi = None self.version = (1, 0, 0) def clone(self): """ Create a new copy of the current object """ model = None if self.model is not None: model = self.model.clone() model.name = self.model.name obj = PageState(model=model) obj.file = copy.deepcopy(self.file) obj.data = copy.deepcopy(self.data) if self.data is not None: self.data_name = self.data.name obj.data_name = self.data_name obj.is_data = self.is_data obj.categorycombobox = self.categorycombobox obj.formfactorcombobox = self.formfactorcombobox obj.structurecombobox = self.structurecombobox # obj.shape_rbutton = self.shape_rbutton # obj.shape_indep_rbutton = self.shape_indep_rbutton # obj.struct_rbutton = self.struct_rbutton # obj.plugin_rbutton = self.plugin_rbutton obj.manager = self.manager obj.event_owner = self.event_owner obj.disp_list = copy.deepcopy(self.disp_list) obj.enable2D = copy.deepcopy(self.enable2D) obj.parameters = copy.deepcopy(self.parameters) obj.str_parameters = copy.deepcopy(self.str_parameters) obj.fixed_param = copy.deepcopy(self.fixed_param) obj.fittable_param = copy.deepcopy(self.fittable_param) obj.orientation_params = copy.deepcopy(self.orientation_params) obj.orientation_params_disp = \ copy.deepcopy(self.orientation_params_disp) obj.enable_disp = copy.deepcopy(self.enable_disp) obj.disable_disp = copy.deepcopy(self.disable_disp) obj.tcChi = self.tcChi if len(self.disp_obj_dict) > 0: for k, v in self.disp_obj_dict.items(): obj.disp_obj_dict[k] = v if len(self.disp_cb_dict) > 0: for k, v in self.disp_cb_dict.items(): obj.disp_cb_dict[k] = v if len(self.values) > 0: for k, v in self.values.items(): obj.values[k] = v if len(self.weights) > 0: for k, v in self.weights.items(): obj.weights[k] = v obj.enable_smearer = copy.deepcopy(self.enable_smearer) obj.disable_smearer = copy.deepcopy(self.disable_smearer) obj.pinhole_smearer = copy.deepcopy(self.pinhole_smearer) obj.slit_smearer = copy.deepcopy(self.slit_smearer) obj.dI_noweight = copy.deepcopy(self.dI_noweight) obj.dI_didata = copy.deepcopy(self.dI_didata) obj.dI_sqrdata = copy.deepcopy(self.dI_sqrdata) obj.dI_idata = copy.deepcopy(self.dI_idata) obj.dq_l = copy.deepcopy(self.dq_l) obj.dq_r = copy.deepcopy(self.dq_r) obj.dx_percent = copy.deepcopy(self.dx_percent) obj.dx_old = copy.deepcopy(self.dx_old) obj.dxl = copy.deepcopy(self.dxl) obj.dxw = copy.deepcopy(self.dxw) obj.disp_box = copy.deepcopy(self.disp_box) obj.qmin = copy.deepcopy(self.qmin) obj.qmax = copy.deepcopy(self.qmax) obj.multi_factor = self.multi_factor obj.magnetic_on = self.magnetic_on obj.npts = copy.deepcopy(self.npts) obj.cb1 = copy.deepcopy(self.cb1) obj.version = copy.deepcopy(self.version) for name, state in self.saved_states.items(): copy_name = copy.deepcopy(name) copy_state = state.clone() obj.saved_states[copy_name] = copy_state return obj def _old_first_model(self): """ Handle save states from 4.0.1 and before where the first item in the selection boxes of category, formfactor and structurefactor were not saved. :return: None """ if self.categorycombobox == CUSTOM_MODEL_OLD: self.categorycombobox = CUSTOM_MODEL if self.formfactorcombobox == '': FIRST_FORM = { 'Shapes' : 'BCCrystalModel', 'Uncategorized' : 'LineModel', 'StructureFactor' : 'HardsphereStructure', 'Ellipsoid' : 'core_shell_ellipsoid', 'Lamellae' : 'lamellar', 'Paracrystal' : 'bcc_paracrystal', 'Parallelepiped' : 'core_shell_parallelepiped', 'Shape Independent' : 'be_polyelectrolyte', 'Sphere' : 'adsorbed_layer', 'Structure Factor' : 'hardsphere', CUSTOM_MODEL : '' } if self.categorycombobox == '': if len(self.parameters) == 3: self.categorycombobox = "Shape-Independent" self.formfactorcombobox = 'PowerLawAbsModel' elif len(self.parameters) == 9: self.categorycombobox = 'Cylinder' self.formfactorcombobox = 'barbell' else: msg = "Save state does not have enough information to load" msg += " the all of the data." logger.warning(msg=msg) else: self.formfactorcombobox = FIRST_FORM[self.categorycombobox] @staticmethod def param_remap_to_sasmodels_convert(params, is_string=False): """ Remaps the parameters for sasmodels conversion :param params: list of parameters (likely self.parameters) :return: remapped dictionary of parameters """ p = dict() for fittable, name, value, _, uncert, lower, upper, units in params: if not value: value = np.nan if not uncert or uncert[1] == '' or uncert[1] == 'None': uncert[0] = False uncert[1] = np.nan if not upper or upper[1] == '' or upper[1] == 'None': upper[0] = False upper[1] = np.nan if not lower or lower[1] == '' or lower[1] == 'None': lower[0] = False lower[1] = np.nan if is_string: p[name] = str(value) else: p[name] = float(value) p[name + ".fittable"] = bool(fittable) p[name + ".std"] = float(uncert[1]) p[name + ".upper"] = float(upper[1]) p[name + ".lower"] = float(lower[1]) p[name + ".units"] = units return p @staticmethod def param_remap_from_sasmodels_convert(params): """ Converts {name : value} map back to [] param list :param params: parameter map returned from sasmodels :return: None """ p_map = [] for name, info in params.items(): if ".fittable" in name or ".std" in name or ".upper" in name or \ ".lower" in name or ".units" in name: pass else: fittable = params.get(name + ".fittable", True) std = params.get(name + ".std", '0.0') upper = params.get(name + ".upper", 'inf') lower = params.get(name + ".lower", '-inf') units = params.get(name + ".units") if std is not None and std is not np.nan: std = [True, str(std)] else: std = [False, ''] if lower is not None and lower is not np.nan: lower = [True, str(lower)] else: lower = [True, '-inf'] if upper is not None and upper is not np.nan: upper = [True, str(upper)] else: upper = [True, 'inf'] param_list = [bool(fittable), str(name), str(info), "+/-", std, lower, upper, str(units)] p_map.append(param_list) return p_map def _convert_to_sasmodels(self): """ Convert parameters to a form usable by sasmodels converter :return: None """ # Create conversion dictionary to send to sasmodels self._old_first_model() p = self.param_remap_to_sasmodels_convert(self.parameters) structurefactor, params = convert.convert_model(self.structurecombobox, p, False, self.version) formfactor, params = convert.convert_model(self.formfactorcombobox, params, False, self.version) if len(self.str_parameters) > 0: str_pars = self.param_remap_to_sasmodels_convert( self.str_parameters, True) formfactor, str_params = convert.convert_model( self.formfactorcombobox, str_pars, False, self.version) for key, value in str_params.items(): params[key] = value if self.formfactorcombobox == 'SphericalSLDModel': self.multi_factor += 1 self.formfactorcombobox = formfactor self.structurecombobox = structurefactor self.parameters = [] self.parameters = self.param_remap_from_sasmodels_convert(params) def _repr_helper(self, list, rep): """ Helper method to print a state """ for item in list: rep += "parameter name: %s \n" % str(item[1]) rep += "value: %s\n" % str(item[2]) rep += "selected: %s\n" % str(item[0]) rep += "error displayed : %s \n" % str(item[4][0]) rep += "error value:%s \n" % str(item[4][1]) rep += "minimum displayed : %s \n" % str(item[5][0]) rep += "minimum value : %s \n" % str(item[5][1]) rep += "maximum displayed : %s \n" % str(item[6][0]) rep += "maximum value : %s \n" % str(item[6][1]) rep += "parameter unit: %s\n\n" % str(item[7]) return rep def __repr__(self): """ output string for printing """ rep = "\nState name: %s\n" % self.file t = time.localtime(self.timestamp) time_str = time.strftime("%b %d %Y %H:%M:%S ", t) rep += "State created: %s\n" % time_str rep += "State form factor combobox selection: %s\n" % \ self.formfactorcombobox rep += "State structure factor combobox selection: %s\n" % \ self.structurecombobox rep += "is data : %s\n" % self.is_data rep += "data's name : %s\n" % self.data_name rep += "data's id : %s\n" % self.data_id if self.model is not None: m_name = self.model.__class__.__name__ if m_name == 'Model': m_name = self.m_name rep += "model name : %s\n" % m_name else: rep += "model name : None\n" rep += "multi_factor : %s\n" % str(self.multi_factor) rep += "magnetic_on : %s\n" % str(self.magnetic_on) rep += "model type (Category) selected: %s\n" % self.categorycombobox rep += "data : %s\n" % str(self.data) rep += "Plotting Range: min: %s, max: %s, steps: %s\n" % \ (str(self.qmin), str(self.qmax), str(self.npts)) rep += "Dispersion selection : %s\n" % str(self.disp_box) rep += "Smearing enable : %s\n" % str(self.enable_smearer) rep += "Smearing disable : %s\n" % str(self.disable_smearer) rep += "Pinhole smearer enable : %s\n" % str(self.pinhole_smearer) rep += "Slit smearer enable : %s\n" % str(self.slit_smearer) rep += "Dispersity enable : %s\n" % str(self.enable_disp) rep += "Dispersity disable : %s\n" % str(self.disable_disp) rep += "Slit smearer enable: %s\n" % str(self.slit_smearer) rep += "dI_noweight : %s\n" % str(self.dI_noweight) rep += "dI_didata : %s\n" % str(self.dI_didata) rep += "dI_sqrdata : %s\n" % str(self.dI_sqrdata) rep += "dI_idata : %s\n" % str(self.dI_idata) rep += "2D enable : %s\n" % str(self.enable2D) rep += "All parameters checkbox selected: %s\n" % self.cb1 rep += "Value of Chisqr : %s\n" % str(self.tcChi) rep += "dq_l : %s\n" % self.dq_l rep += "dq_r : %s\n" % self.dq_r rep += "dx_percent : %s\n" % str(self.dx_percent) rep += "dxl : %s\n" % str(self.dxl) rep += "dxw : %s\n" % str(self.dxw) rep += "model : %s\n\n" % str(self.model) temp_parameters = [] temp_fittable_param = [] if self.data is not None: is_2D = (self.data.__class__.__name__ == "Data2D") if not is_2D: for item in self.parameters: if item not in self.orientation_params: temp_parameters.append(item) for item in self.fittable_param: if item not in self.orientation_params_disp: temp_fittable_param.append(item) else: temp_parameters = self.parameters temp_fittable_param = self.fittable_param rep += "number parameters(self.parameters): %s\n" % \ len(temp_parameters) rep = self._repr_helper(list=temp_parameters, rep=rep) rep += "number str_parameters(self.str_parameters): %s\n" % \ len(self.str_parameters) rep = self._repr_helper(list=self.str_parameters, rep=rep) rep += "number fittable_param(self.fittable_param): %s\n" % \ len(temp_fittable_param) rep = self._repr_helper(list=temp_fittable_param, rep=rep) return rep def _get_report_string(self): """ Get the values (strings) from __str__ for report """ # Dictionary of the report strings repo_time = "" model_name = "" title = "" title_name = "" file_name = "" param_string = "" paramval_string = "" chi2_string = "" q_range = "" strings = self.__repr__() fixed_parameter = False lines = strings.split('\n') # get all string values from __str__() for line in lines: # Skip lines which are not key: value pairs, which includes # blank lines and freeform notes in SASNotes fields. if not ':' in line: #msg = "Report string expected 'name: value' but got %r" % line #logger.error(msg) continue name, value = [s.strip() for s in line.split(":", 1)] if name == "State created": repo_time = value elif name == "parameter name": val_name = value.split(".") if len(val_name) > 1: if val_name[1].count("width"): param_string += value + ',' else: continue else: param_string += value + ',' elif name == "value": param_string += value + ',' elif name == "selected": # remember if it is fixed when reporting error value fixed_parameter = (value == u'False') elif name == "error value": if fixed_parameter: param_string += '(fixed),' else: param_string += value + ',' elif name == "parameter unit": param_string += value + ':' elif name == "Value of Chisqr": chi2 = ("Chi2/Npts = " + value) chi2_string = CENTRE % chi2 elif name == "Title": if len(value.strip()) == 0: continue title = (value + " [" + repo_time + "] [SasView v" + SASVIEW_VERSION + "]") title_name = HEADER % title elif name == "data": try: # parsing "data : File: filename [mmm dd hh:mm]" name = value.split(':', 1)[1].strip() file_value = "File name:" + name #Truncating string so print doesn't complain of being outside margins if sys.platform != "win32": MAX_STRING_LENGTH = 50 if len(file_value) > MAX_STRING_LENGTH: file_value = "File name:.."+file_value[-MAX_STRING_LENGTH+10:] file_name = CENTRE % file_value if len(title) == 0: title = name + " [" + repo_time + "]" title_name = HEADER % title except Exception: msg = "While parsing 'data: ...'\n" logger.error(msg + traceback.format_exc()) elif name == "model name": try: modelname = "Model name:" + value except Exception: modelname = "Model name:" + " NAN" model_name = CENTRE % modelname elif name == "Plotting Range": try: parts = value.split(':') q_range = parts[0] + " = " + parts[1] \ + " = " + parts[2].split(",")[0] q_name = ("Q Range: " + q_range) q_range = CENTRE % q_name except Exception: msg = "While parsing 'Plotting Range: ...'\n" logger.error(msg + traceback.format_exc()) paramval = "" for lines in param_string.split(":"): line = lines.split(",") if len(lines) > 0: param = line[0] param += " = " + line[1] if len(line[2].split()) > 0 and not line[2].count("None"): param += " +- " + line[2] if len(line[3].split()) > 0 and not line[3].count("None"): param += " " + line[3] if not paramval.count(param): paramval += param + "\n" paramval_string += CENTRE % param + "\n" text_string = "\n\n\n%s\n\n%s\n%s\n%s\n\n%s" % \ (title, file, q_name, chi2, paramval) title_name = self._check_html_format(title_name) file_name = self._check_html_format(file_name) title = self._check_html_format(title) html_string = title_name + "\n" + file_name + \ "\n" + model_name + \ "\n" + q_range + \ "\n" + chi2_string + \ "\n" + ELINE + \ "\n" + paramval_string + \ "\n" + ELINE + \ "\n" + FEET_1 % title return html_string, text_string, title def _check_html_format(self, name): """ Check string '%' for html format """ if name.count('%'): name = name.replace('%', '%') return name def report(self, fig_urls): """ Invoke report dialog panel : param figs: list of pylab figures [list] """ # get the strings for report html_str, text_str, title = self._get_report_string() # Allow 2 figures to append #Constraining image width for OSX and linux, so print doesn't complain of being outside margins if sys.platform == "win32": image_links = [FEET_2%fig for fig in fig_urls] else: image_links = [FEET_2_unix%fig for fig in fig_urls] # final report html strings report_str = html_str + ELINE.join(image_links) report_str += FEET_3 return report_str, text_str def _to_xml_helper(self, thelist, element, newdoc): """ Helper method to create xml file for saving state """ for item in thelist: sub_element = newdoc.createElement('parameter') sub_element.setAttribute('name', str(item[1])) sub_element.setAttribute('value', str(item[2])) sub_element.setAttribute('selected_to_fit', str(item[0])) sub_element.setAttribute('error_displayed', str(item[4][0])) sub_element.setAttribute('error_value', str(item[4][1])) sub_element.setAttribute('minimum_displayed', str(item[5][0])) sub_element.setAttribute('minimum_value', str(item[5][1])) sub_element.setAttribute('maximum_displayed', str(item[6][0])) sub_element.setAttribute('maximum_value', str(item[6][1])) sub_element.setAttribute('unit', str(item[7])) element.appendChild(sub_element) def to_xml(self, file="fitting_state.fitv", doc=None, entry_node=None, batch_fit_state=None): """ Writes the state of the fit panel to file, as XML. Compatible with standalone writing, or appending to an already existing XML document. In that case, the XML document is required. An optional entry node in the XML document may also be given. :param file: file to write to :param doc: XML document object [optional] :param entry_node: XML node within the XML document at which we will append the data [optional] :param batch_fit_state: simultaneous fit state """ # Check whether we have to write a standalone XML file if doc is None: impl = getDOMImplementation() doc_type = impl.createDocumentType(FITTING_NODE_NAME, "1.0", "1.0") newdoc = impl.createDocument(None, FITTING_NODE_NAME, doc_type) top_element = newdoc.documentElement else: # We are appending to an existing document newdoc = doc try: top_element = newdoc.createElement(FITTING_NODE_NAME) except Exception: string = etree.tostring(doc, pretty_print=True) newdoc = parseString(string) top_element = newdoc.createElement(FITTING_NODE_NAME) if entry_node is None: newdoc.documentElement.appendChild(top_element) else: try: entry_node.appendChild(top_element) except Exception: node_name = entry_node.tag node_list = newdoc.getElementsByTagName(node_name) entry_node = node_list.item(0) entry_node.appendChild(top_element) attr = newdoc.createAttribute("version") attr.nodeValue = SASVIEW_VERSION # attr.nodeValue = '1.0' top_element.setAttributeNode(attr) # File name element = newdoc.createElement("filename") if self.file is not None: element.appendChild(newdoc.createTextNode(str(self.file))) else: element.appendChild(newdoc.createTextNode(str(file))) top_element.appendChild(element) element = newdoc.createElement("timestamp") element.appendChild(newdoc.createTextNode(time.ctime(self.timestamp))) attr = newdoc.createAttribute("epoch") attr.nodeValue = str(self.timestamp) element.setAttributeNode(attr) top_element.appendChild(element) # Inputs inputs = newdoc.createElement("Attributes") top_element.appendChild(inputs) if self.data is not None and hasattr(self.data, "group_id"): self.data_group_id = self.data.group_id if self.data is not None and hasattr(self.data, "is_data"): self.is_data = self.data.is_data if self.data is not None: self.data_name = self.data.name if self.data is not None and hasattr(self.data, "id"): self.data_id = self.data.id for item in LIST_OF_DATA_ATTRIBUTES: element = newdoc.createElement(item[0]) element.setAttribute(item[0], str(getattr(self, item[1]))) inputs.appendChild(element) for item in LIST_OF_STATE_ATTRIBUTES: element = newdoc.createElement(item[0]) element.setAttribute(item[0], str(getattr(self, item[1]))) inputs.appendChild(element) # For self.values ={ disp_param_name: [vals,...],...} # and for self.weights ={ disp_param_name: [weights,...],...} for item in LIST_OF_MODEL_ATTRIBUTES: element = newdoc.createElement(item[0]) value_list = getattr(self, item[1]) for key, value in value_list.items(): sub_element = newdoc.createElement(key) sub_element.setAttribute('name', str(key)) for val in value: sub_element.appendChild(newdoc.createTextNode(str(val))) element.appendChild(sub_element) inputs.appendChild(element) # Create doc for the dictionary of self.disp_obj_dic for tagname, varname, tagtype in DISPERSION_LIST: element = newdoc.createElement(tagname) value_list = getattr(self, varname) for key, value in value_list.items(): sub_element = newdoc.createElement(key) sub_element.setAttribute('name', str(key)) sub_element.setAttribute('value', str(value)) element.appendChild(sub_element) inputs.appendChild(element) for item in LIST_OF_STATE_PARAMETERS: element = newdoc.createElement(item[0]) self._to_xml_helper(thelist=getattr(self, item[1]), element=element, newdoc=newdoc) inputs.appendChild(element) # Combined and Simultaneous Fit Parameters if batch_fit_state is not None: batch_combo = newdoc.createElement('simultaneous_fit') top_element.appendChild(batch_combo) # Simultaneous Fit Number For Linking Later element = newdoc.createElement('sim_fit_number') element.setAttribute('fit_number', str(batch_fit_state.fit_page_no)) batch_combo.appendChild(element) # Save constraints constraints = newdoc.createElement('constraints') batch_combo.appendChild(constraints) for constraint in batch_fit_state.constraints_list: if constraint.model_cbox.GetValue() != "": # model_cbox, param_cbox, egal_txt, constraint, # btRemove, sizer doc_cons = newdoc.createElement('constraint') doc_cons.setAttribute('model_cbox', str(constraint.model_cbox.GetValue())) doc_cons.setAttribute('param_cbox', str(constraint.param_cbox.GetValue())) doc_cons.setAttribute('egal_txt', str(constraint.egal_txt.GetLabel())) doc_cons.setAttribute('constraint', str(constraint.constraint.GetValue())) constraints.appendChild(doc_cons) # Save all models models = newdoc.createElement('model_list') batch_combo.appendChild(models) for model in batch_fit_state.model_list: doc_model = newdoc.createElement('model_list_item') doc_model.setAttribute('checked', str(model[0].GetValue())) keys = list(model[1].keys()) doc_model.setAttribute('name', str(keys[0])) values = model[1].get(keys[0]) doc_model.setAttribute('fit_number', str(model[2])) doc_model.setAttribute('fit_page_source', str(model[3])) doc_model.setAttribute('model_name', str(values.model.id)) models.appendChild(doc_model) # Select All Checkbox element = newdoc.createElement('select_all') if batch_fit_state.select_all: element.setAttribute('checked', 'True') else: element.setAttribute('checked', 'False') batch_combo.appendChild(element) # Save the file if doc is None: fd = open(file, 'w') fd.write(newdoc.toprettyxml()) fd.close() return None else: return newdoc def _from_xml_helper(self, node, list): """ Helper function to write state to xml """ for item in node: name = item.get('name') value = item.get('value') selected_to_fit = (item.get('selected_to_fit') == "True") error_displayed = (item.get('error_displayed') == "True") error_value = item.get('error_value') minimum_displayed = (item.get('minimum_displayed') == "True") minimum_value = item.get('minimum_value') maximum_displayed = (item.get('maximum_displayed') == "True") maximum_value = item.get('maximum_value') unit = item.get('unit') list.append([selected_to_fit, name, value, "+/-", [error_displayed, error_value], [minimum_displayed, minimum_value], [maximum_displayed, maximum_value], unit]) def from_xml(self, file=None, node=None): """ Load fitting state from a file :param file: .fitv file :param node: node of a XML document to read from """ if file is not None: msg = "PageState no longer supports non-CanSAS" msg += " format for fitting files" raise RuntimeError(msg) if node.get('version'): # Get the version for model conversion purposes x = re.sub(r'[^\d.]', '', node.get('version')) self.version = tuple(int(e) for e in str.split(x, ".")) # The tuple must be at least 3 items long while len(self.version) < 3: ver_list = list(self.version) ver_list.append(0) self.version = tuple(ver_list) # Get file name entry = get_content('ns:filename', node) if entry is not None and entry.text: self.file = entry.text.strip() else: self.file = '' # Get time stamp entry = get_content('ns:timestamp', node) if entry is not None and entry.get('epoch'): try: self.timestamp = float(entry.get('epoch')) except Exception as exc: msg = "PageState.fromXML: Could not" msg += " read timestamp\n %s" % exc logger.error(msg) if entry is not None: # Parse fitting attributes entry = get_content('ns:Attributes', node) for item in LIST_OF_DATA_ATTRIBUTES: node = get_content('ns:%s' % item[0], entry) setattr(self, item[0], parse_entry_helper(node, item)) dx_old_node = get_content('ns:%s' % 'dx_min', entry) for item in LIST_OF_STATE_ATTRIBUTES: if item[0] == "dx_percent" and dx_old_node is not None: dxmin = ["dx_min", "dx_min", "float"] setattr(self, item[0], parse_entry_helper(dx_old_node, dxmin)) self.dx_old = True else: node = get_content('ns:%s' % item[0], entry) setattr(self, item[0], parse_entry_helper(node, item)) for item in LIST_OF_STATE_PARAMETERS: node = get_content("ns:%s" % item[0], entry) self._from_xml_helper(node=node, list=getattr(self, item[1])) # Recover disp_obj_dict from xml file self.disp_obj_dict = {} for tagname, varname, tagtype in DISPERSION_LIST: node = get_content("ns:%s" % tagname, entry) for attr in node: parameter = str(attr.get('name')) value = attr.get('value') if value.startswith("<"): try: # cls_name = value[1:].split()[0].split('.')[-1] cls = getattr(sasmodels.weights, cls_name) value = cls.type except Exception: base = "unable to load distribution %r for %s" logger.error(base, value, parameter) continue disp_obj_dict = getattr(self, varname) disp_obj_dict[parameter] = value # get self.values and self.weights dic. if exists for tagname, varname in LIST_OF_MODEL_ATTRIBUTES: node = get_content("ns:%s" % tagname, entry) dic = {} value_list = [] for par in node: name = par.get('name') values = par.text.split() # Get lines only with numbers for line in values: try: val = float(line) value_list.append(val) except Exception: # pass if line is empty (it happens) msg = ("Error reading %r from %s %s\n" % (line, tagname, name)) logger.error(msg + traceback.format_exc()) dic[name] = np.array(value_list) setattr(self, varname, dic) class SimFitPageState(object): """ State of the simultaneous fit page for saving purposes """ def __init__(self): # Sim Fit Page Number self.fit_page_no = None # Select all data self.select_all = False # Data sets sent to fit page self.model_list = [] # Data sets to be fit self.model_to_fit = [] # Number of constraints self.no_constraint = 0 # Dictionary of constraints self.constraint_dict = {} # List of constraints self.constraints_list = [] def __repr__(self): # TODO: should use __str__, not __repr__ (similarly for PageState) # TODO: could use a nicer representation repr = """\ fit page number : %(fit_page_no)s select all : %(select_all)s model_list : %(model_list)s model to fit : %(model_to_fit)s number of construsts : %(no_constraint)s constraint dict : %(constraint_dict)s constraints list : %(constraints_list)s """%self.__dict__ return repr class Reader(CansasReader): """ Class to load a .fitv fitting file """ # File type type_name = "Fitting" # Wildcards type = ["Fitting files (*.fitv)|*.fitv" "SASView file (*.svs)|*.svs"] # List of allowed extensions ext = ['.fitv', '.FITV', '.svs', 'SVS'] def __init__(self, call_back=None, cansas=True): CansasReader.__init__(self) """ Initialize the call-back method to be called after we load a file :param call_back: call-back method :param cansas: True = files will be written/read in CanSAS format False = write CanSAS format """ # Call back method to be executed after a file is read self.call_back = call_back # CanSAS format flag self.cansas = cansas self.state = None # batch fitting params for saving self.batchfit_params = [] def get_state(self): return self.state def read(self, path): """ Load a new P(r) inversion state from file :param path: file path """ if self.cansas: return self._read_cansas(path) def _parse_state(self, entry): """ Read a fit result from an XML node :param entry: XML node to read from :return: PageState object """ # Create an empty state state = None # Locate the P(r) node try: nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME, namespaces=CANSAS_NS) if nodes: # Create an empty state state = PageState() state.from_xml(node=nodes[0]) except Exception: logger.info("XML document does not contain fitting information.\n" + traceback.format_exc()) return state def _parse_simfit_state(self, entry): """ Parses the saved data for a simultaneous fit :param entry: XML object to read from :return: XML object for a simultaneous fit or None """ nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME, namespaces=CANSAS_NS) if nodes: simfitstate = nodes[0].xpath('ns:simultaneous_fit', namespaces=CANSAS_NS) if simfitstate: sim_fit_state = SimFitPageState() simfitstate_0 = simfitstate[0] all = simfitstate_0.xpath('ns:select_all', namespaces=CANSAS_NS) atts = all[0].attrib checked = atts.get('checked') sim_fit_state.select_all = bool(checked) model_list = simfitstate_0.xpath('ns:model_list', namespaces=CANSAS_NS) model_list_items = model_list[0].xpath('ns:model_list_item', namespaces=CANSAS_NS) for model in model_list_items: attrs = model.attrib sim_fit_state.model_list.append(attrs) constraints = simfitstate_0.xpath('ns:constraints', namespaces=CANSAS_NS) constraint_list = constraints[0].xpath('ns:constraint', namespaces=CANSAS_NS) for constraint in constraint_list: attrs = constraint.attrib sim_fit_state.constraints_list.append(attrs) return sim_fit_state else: return None def _parse_save_state_entry(self, dom): """ Parse a SASentry :param node: SASentry node :return: Data1D/Data2D object """ node = dom.xpath('ns:data_class', namespaces=CANSAS_NS) return_value, _ = self._parse_entry(dom) return return_value, _ def _read_cansas(self, path): """ Load data and fitting information from a CanSAS XML file. :param path: file path :return: Data1D object if a single SASentry was found, or a list of Data1D objects if multiple entries were found, or None of nothing was found :raise RuntimeError: when the file can't be opened :raise ValueError: when the length of the data vectors are inconsistent """ output = [] simfitstate = None basename = os.path.basename(path) root, extension = os.path.splitext(basename) ext = extension.lower() try: if os.path.isfile(path): if ext in self.ext or ext == '.xml': tree = etree.parse(path, parser=etree.ETCompatXMLParser()) # Check the format version number # Specifying the namespace will take care of the file # format version root = tree.getroot() entry_list = root.xpath('ns:SASentry', namespaces=CANSAS_NS) for entry in entry_list: fitstate = self._parse_state(entry) # state could be None when .svs file is loaded # in this case, skip appending to output if fitstate is not None: try: sas_entry, _ = self._parse_save_state_entry( entry) except: raise sas_entry.meta_data['fitstate'] = fitstate sas_entry.filename = fitstate.file output.append(sas_entry) else: self.call_back(format=ext) raise RuntimeError("%s is not a file" % path) # Return output consistent with the loader's api if len(output) == 0: self.call_back(state=None, datainfo=None, format=ext) return None else: for data in output: # Call back to post the new state state = data.meta_data['fitstate'] t = time.localtime(state.timestamp) time_str = time.strftime("%b %d %H:%M", t) # Check that no time stamp is already appended max_char = state.file.find("[") if max_char < 0: max_char = len(state.file) original_fname = state.file[0:max_char] state.file = original_fname + ' [' + time_str + ']' if state is not None and state.is_data is not None: data.is_data = state.is_data data.filename = state.file state.data = data state.data.name = data.filename # state.data_name state.data.id = state.data_id if state.is_data is not None: state.data.is_data = state.is_data if data.run_name is not None and len(data.run_name) != 0: if isinstance(data.run_name, dict): # Note: key order in dict is not guaranteed, so sort name = list(data.run_name.keys())[0] else: name = data.run_name else: name = original_fname state.data.group_id = name state.version = fitstate.version # store state in fitting self.call_back(state=state, datainfo=data, format=ext) self.state = state simfitstate = self._parse_simfit_state(entry) if simfitstate is not None: self.call_back(state=simfitstate) return output except: self.call_back(format=ext) raise def write(self, filename, datainfo=None, fitstate=None): """ Write the content of a Data1D as a CanSAS XML file only for standalone :param filename: name of the file to write :param datainfo: Data1D object :param fitstate: PageState object """ # Sanity check if self.cansas: # Add fitting information to the XML document doc = self.write_toXML(datainfo, fitstate) # Write the XML document else: doc = fitstate.to_xml(file=filename) # Save the document no matter the type fd = open(filename, 'w') fd.write(doc.toprettyxml()) fd.close() def write_toXML(self, datainfo=None, state=None, batchfit=None): """ Write toXML, a helper for write(), could be used by guimanager._on_save() : return: xml doc """ self.batchfit_params = batchfit if state.data is None or not state.data.is_data: return None # make sure title and data run are filled. if state.data.title is None or state.data.title == '': state.data.title = state.data.name if state.data.run_name is None or state.data.run_name == {}: state.data.run = [str(state.data.name)] state.data.run_name[0] = state.data.name data = state.data doc, sasentry = self._to_xml_doc(data) if state is not None: doc = state.to_xml(doc=doc, file=data.filename, entry_node=sasentry, batch_fit_state=self.batchfit_params) return doc # Simple html report template HEADER = "\n" HEADER += "\n" HEADER += " \n" HEADER += "\n" HEADER += "\n" HEADER += "\n" HEADER += "
\n" HEADER += "

" HEADER += "%s

" HEADER += "

 

" PARA = "

%s \n" PARA += "

" CENTRE = "

%s \n" CENTRE += "

" FEET_1 = \ """

 


Graph

 


Model Computation
Data: "%s"
""" FEET_2 = \ """ """ FEET_2_unix = \ """ """ FEET_3 = \ """
""" ELINE = """

 

"""