"""
Class that holds a fit page state
"""
# 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"]]
[docs]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
[docs]class PageState(object):
"""
Contains information to reconstruct a page of the fitpanel.
"""
[docs] 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.is_2D = False
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)
[docs] 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
[docs] 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]
[docs] @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
[docs] @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
[docs] 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)
[docs] def _repr_helper(self, list, rep):
"""
Helper method to print a state
"""
for item in list:
if str(item[1][-6:]) == '.width':
par = str(item[1][:-6])
pd_type = self.model.dispersion[par]['type']
npts = self.model.dispersion[par]['npts']
nsigmas = self.model.dispersion[par]['nsigmas']
dist_str = str(item[1])
dist_str += '(' + str(pd_type)
dist_str += '; points = ' + str(npts)
dist_str += '; sigmas = ' + str(nsigmas) + ')'
rep += "parameter name: %s \n" % dist_str
else:
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
[docs] 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.__class__.__name__ == "Data2D":
self.is_2D = True
else:
self.is_2D = False
if self.data is not None:
if not self.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
[docs] 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
[docs] def _check_html_format(self, name):
"""
Check string '%' for html format
"""
if name.count('%'):
name = name.replace('%', '%')
return name
[docs] 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
[docs] 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)
[docs] 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
[docs] 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])
[docs] 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:
# <path.to.NamedDistribution object/instance...>
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)
[docs]class SimFitPageState(object):
"""
State of the simultaneous fit page for saving purposes
"""
[docs] 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 = []
[docs] 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
[docs]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']
[docs] 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 = []
[docs] def get_state(self):
return self.state
[docs] 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)
[docs] 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
[docs] 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
[docs] 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, _
[docs] 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
[docs] 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()
[docs] 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 = "<html>\n"
HEADER += "<head>\n"
HEADER += "<meta http-equiv=Content-Type content='text/html; "
HEADER += "charset=windows-1252'> \n"
HEADER += "<meta name=Generator >\n"
HEADER += "</head>\n"
HEADER += "<body lang=EN-US>\n"
HEADER += "<div class=WordSection1>\n"
HEADER += "<p class=MsoNormal><b><span ><center><font size='4' >"
HEADER += "%s</font></center></span></center></b></p>"
HEADER += "<p class=MsoNormal> </p>"
PARA = "<p class=MsoNormal><font size='4' > %s \n"
PARA += "</font></p>"
CENTRE = "<p class=MsoNormal><center><font size='4' > %s \n"
CENTRE += "</font></center></p>"
FEET_1 = \
"""
<p class=MsoNormal> </p>
<br>
<p class=MsoNormal><b><span ><center> <font size='4' > Graph
</font></span></center></b></p>
<p class=MsoNormal> </p>
<center>
<br><font size='4' >Model Computation</font>
<br><font size='4' >Data: "%s"</font><br>
"""
FEET_2 = \
"""<img src="%s"></img>
"""
FEET_2_unix = \
"""<img src="%s" width="540"></img>
"""
FEET_3 = \
"""</center>
</div>
</body>
</html>
"""
ELINE = """<p class=MsoNormal> </p>
"""