Index: src/sas/qtgui/MainWindow/DataExplorer.py
===================================================================
--- src/sas/qtgui/MainWindow/DataExplorer.py (revision 339e22b9f5c08262b1d5b0b551ce467bcf83178c)
+++ src/sas/qtgui/MainWindow/DataExplorer.py (revision 339e22b9f5c08262b1d5b0b551ce467bcf83178c)
@@ -560,17 +560,10 @@
# Now query the model item for available plots
plots = GuiUtils.plotsFromFilename(filename, model)
- ids_keys = list(self.active_plots.keys())
- ids_vals = [val.data.id for val in self.active_plots.values()]
new_plots = []
for item, plot in plots.items():
- plot_id = plot.id
- if plot_id in ids_keys:
- self.active_plots[plot_id].replacePlot(plot_id, plot)
- elif plot_id in ids_vals:
- list(self.active_plots.values())[ids_vals.index(plot_id)].replacePlot(plot_id, plot)
- else:
+ if not self.updatePlot(plot):
# Don't plot intermediate results, e.g. P(Q), S(Q)
- match = GuiUtils.theory_plot_ID_pattern.match(plot_id)
+ match = GuiUtils.theory_plot_ID_pattern.match(plot.id)
# 2nd match group contains the identifier for the intermediate result, if present (e.g. "[P(Q)]")
if match and match.groups()[1] != None:
@@ -706,9 +699,13 @@
self.active_plots[plot_set.id] = old_plot
- def updatePlot(self, new_data):
- """
- Modify existing plot for immediate response
- """
- data = new_data[0]
+ def updatePlot(self, data):
+ """
+ Modify existing plot for immediate response and returns True.
+ Returns false, if the plot does not exist already.
+ """
+ try: # there might be a list or a single value being passed
+ data = data[0]
+ except TypeError:
+ pass
assert type(data).__name__ in ['Data1D', 'Data2D']
@@ -719,6 +716,9 @@
if data_id in ids_keys:
self.active_plots[data_id].replacePlot(data_id, data)
+ return True
elif data_id in ids_vals:
list(self.active_plots.values())[ids_vals.index(data_id)].replacePlot(data_id, data)
+ return True
+ return False
def chooseFiles(self):
Index: src/sas/qtgui/Perspectives/Fitting/AssociatedComboBox.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/AssociatedComboBox.py (revision 04f775db13543cbc91b64834af6068a163b77f8b)
+++ src/sas/qtgui/Perspectives/Fitting/AssociatedComboBox.py (revision 04f775db13543cbc91b64834af6068a163b77f8b)
@@ -0,0 +1,38 @@
+from PyQt5 import QtGui, QtWidgets
+
+
+class AssociatedComboBox(QtWidgets.QComboBox):
+ """Just a regular combo box, but associated with a particular QStandardItem so that it can be used to display and
+ select an item's current text and a restricted list of other possible text.
+
+ When the combo box's current text is changed, the change is immediately reflected in the associated item; either
+ the text itself is set as the item's data, or the current index is used; see constructor."""
+ item = None # type: QtGui.QStandardItem
+
+ def __init__(self, item, idx_as_value=False, parent=None):
+ # type: (QtGui.QStandardItem, bool, QtWidgets.QWidget) -> None
+ """
+ Initialize widget. idx_as_value indicates whether to use the combo box's current index (otherwise, current text)
+ as the associated item's new data.
+ """
+ super(AssociatedComboBox, self).__init__(parent)
+ self.item = item
+
+ if idx_as_value:
+ self.currentIndexChanged[int].connect(self._onIndexChanged)
+ else:
+ self.currentTextChanged.connect(self._onTextChanged)
+
+ def _onTextChanged(self, text):
+ # type: (str) -> None
+ """
+ Callback for combo box's currentTextChanged. Set associated item's data to the new text.
+ """
+ self.item.setText(text)
+
+ def _onIndexChanged(self, idx):
+ # type: (int) -> None
+ """
+ Callback for combo box's currentIndexChanged. Set associated item's data to the new index.
+ """
+ self.item.setText(str(idx))
Index: src/sas/qtgui/Perspectives/Fitting/FittingLogic.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/FittingLogic.py (revision b4d05bdc03c6c40fee47932a9a2d20921b1bbe4e)
+++ src/sas/qtgui/Perspectives/Fitting/FittingLogic.py (revision dcabba7be660ff2e140942eb331ca846d1f2a399)
@@ -161,13 +161,7 @@
Create a new 1D data instance based on fitting results
"""
- # Unpack return data from Calc1D
- x, y, page_id, state, weight,\
- fid, toggle_mode_on, \
- elapsed, index, model, \
- data, update_chisqr, source, \
- unsmeared_output, unsmeared_data, unsmeared_error, \
- pq_values, sq_values = return_data
-
- return self._create1DPlot(tab_id, x, y, model, data)
+
+ return self._create1DPlot(tab_id, return_data['x'], return_data['y'],
+ return_data['model'], return_data['data'])
def new2DPlot(self, return_data):
@@ -175,7 +169,7 @@
Create a new 2D data instance based on fitting results
"""
- image, data, page_id, model, state, toggle_mode_on,\
- elapsed, index, fid, qmin, qmax, weight, \
- update_chisqr, source = return_data
+ image = return_data['image']
+ data = return_data['data']
+ model = return_data['model']
np.nan_to_num(image)
@@ -183,6 +177,6 @@
new_plot.name = model.name + '2d'
new_plot.title = "Analytical model 2D "
- new_plot.id = str(page_id) + " " + data.name
- new_plot.group_id = str(page_id) + " Model2D"
+ new_plot.id = str(return_data['page_id']) + " " + data.name
+ new_plot.group_id = str(return_data['page_id']) + " Model2D"
new_plot.detector = data.detector
new_plot.source = data.source
@@ -218,19 +212,16 @@
(pq_plot, sq_plot). If either are unavailable, the corresponding plot is None.
"""
- # Unpack return data from Calc1D
- x, y, page_id, state, weight, \
- fid, toggle_mode_on, \
- elapsed, index, model, \
- data, update_chisqr, source, \
- unsmeared_output, unsmeared_data, unsmeared_error, \
- pq_values, sq_values = return_data
pq_plot = None
sq_plot = None
- if pq_values is not None:
- pq_plot = self._create1DPlot(tab_id, x, pq_values, model, data, component="P(Q)")
- if sq_values is not None:
- sq_plot = self._create1DPlot(tab_id, x, sq_values, model, data, component="S(Q)")
+ if return_data.get('pq_values', None) is not None:
+ pq_plot = self._create1DPlot(tab_id, return_data['x'],
+ return_data['pq_values'], return_data['model'],
+ return_data['data'], component="P(Q)")
+ if return_data.get('sq_values', None) is not None:
+ sq_plot = self._create1DPlot(tab_id, return_data['x'],
+ return_data['sq_values'], return_data['model'],
+ return_data['data'], component="S(Q)")
return pq_plot, sq_plot
Index: src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py (revision b764ae5882f084a8b74c7cf664276c13fb07c29e)
+++ src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py (revision 70f445859abd127c3fa7cd9f6b8e2700e7bcb1a2)
@@ -8,4 +8,6 @@
from sas.qtgui.Plotting.PlotterData import Data1D
from sas.qtgui.Plotting.PlotterData import Data2D
+
+from sas.qtgui.Perspectives.Fitting.AssociatedComboBox import AssociatedComboBox
model_header_captions = ['Parameter', 'Value', 'Min', 'Max', 'Units']
@@ -61,7 +63,37 @@
return (param_name, param_length)
-def addParametersToModel(parameters, kernel_module, is2D):
- """
- Update local ModelModel with sasmodel parameters
+def createFixedChoiceComboBox(param, item_row):
+ """
+ Determines whether param is a fixed-choice parameter, modifies items in item_row appropriately and returns a combo
+ box containing the fixed choices. Returns None if param is not fixed-choice.
+
+ item_row is a list of QStandardItem objects for insertion into the parameter table.
+ """
+
+ # Determine whether this is a fixed-choice parameter. There are lots of conditionals, simply because the
+ # implementation is not yet concrete; there are several possible indicators that the parameter is fixed-choice.
+ # TODO: (when the sasmodels implementation is concrete, clean this up)
+ choices = None
+ if isinstance(param.choices, (list, tuple)) and len(param.choices) > 0:
+ # The choices property is concrete in sasmodels, probably will use this
+ choices = param.choices
+ elif isinstance(param.units, (list, tuple)):
+ choices = [str(x) for x in param.units]
+
+ cbox = None
+ if choices is not None:
+ # Use combo box for input, if it is fixed-choice
+ cbox = AssociatedComboBox(item_row[1], idx_as_value=True)
+ cbox.addItems(choices)
+ item_row[2].setEditable(False)
+ item_row[3].setEditable(False)
+
+ return cbox
+
+def addParametersToModel(parameters, kernel_module, is2D, model=None, view=None):
+ """
+ Update local ModelModel with sasmodel parameters.
+ Actually appends to model, if model and view params are not None.
+ Always returns list of lists of QStandardItems.
"""
multishell_parameters = getIterParams(parameters)
@@ -72,19 +104,20 @@
else:
params = parameters.iq_parameters
- item = []
+
+ rows = []
for param in params:
# don't include shell parameters
if param.name == multishell_param_name:
continue
+
# Modify parameter name from [n] to 1
item_name = param.name
if param in multishell_parameters:
continue
- # item_name = replaceShellName(param.name, 1)
item1 = QtGui.QStandardItem(item_name)
item1.setCheckable(True)
item1.setEditable(False)
- # item_err = QtGui.QStandardItem()
+
# check for polydisp params
if param.polydisperse:
@@ -93,4 +126,5 @@
item1_1 = QtGui.QStandardItem("Distribution")
item1_1.setEditable(False)
+
# Find param in volume_params
for p in parameters.form_volume_parameters:
@@ -99,5 +133,4 @@
width = kernel_module.getParam(p.name+'.width')
ptype = kernel_module.getParam(p.name+'.type')
-
item1_2 = QtGui.QStandardItem(str(width))
item1_2.setEditable(False)
@@ -110,21 +143,36 @@
poly_item.appendRow([item1_1, item1_2, item1_3, item1_4, item1_5])
break
+
# Add the polydisp item as a child
item1.appendRow([poly_item])
+
# Param values
item2 = QtGui.QStandardItem(str(param.default))
- # TODO: the error column.
- # Either add a proxy model or a custom view delegate
- #item_err = QtGui.QStandardItem()
item3 = QtGui.QStandardItem(str(param.limits[0]))
item4 = QtGui.QStandardItem(str(param.limits[1]))
- item5 = QtGui.QStandardItem(param.units)
+ item5 = QtGui.QStandardItem(str(param.units))
item5.setEditable(False)
- item.append([item1, item2, item3, item4, item5])
- return item
-
-def addSimpleParametersToModel(parameters, is2D):
- """
- Update local ModelModel with sasmodel parameters
+
+ # Check if fixed-choice (returns combobox, if so, also makes some items uneditable)
+ row = [item1, item2, item3, item4, item5]
+ cbox = createFixedChoiceComboBox(param, row)
+
+ # Append to the model and use the combobox, if required
+ if None not in (model, view):
+ model.appendRow(row)
+ if cbox:
+ view.setIndexWidget(item2.index(), cbox)
+ rows.append(row)
+
+ return rows
+
+def addSimpleParametersToModel(parameters, is2D, parameters_original=None, model=None, view=None):
+ """
+ Update local ModelModel with sasmodel parameters (non-dispersed, non-magnetic)
+ Actually appends to model, if model and view params are not None.
+ Always returns list of lists of QStandardItems.
+
+ parameters_original: list of parameters before any tagging on their IDs, e.g. for product model (so that those are
+ the display names; see below)
"""
if is2D:
@@ -132,20 +180,46 @@
else:
params = parameters.iq_parameters
- item = []
- for param in params:
+
+ if parameters_original:
+ # 'parameters_original' contains the parameters as they are to be DISPLAYED, while 'parameters'
+ # contains the parameters as they were renamed; this is for handling name collisions in product model.
+ # The 'real name' of the parameter will be stored in the item's user data.
+ if is2D:
+ params_orig = [p for p in parameters_original.kernel_parameters if p.type != 'magnetic']
+ else:
+ params_orig = parameters_original.iq_parameters
+ else:
+ # no difference in names anyway
+ params_orig = params
+
+ rows = []
+ for param, param_orig in zip(params, params_orig):
# Create the top level, checkable item
- item_name = param.name
+ item_name = param_orig.name
item1 = QtGui.QStandardItem(item_name)
+ item1.setData(param.name, QtCore.Qt.UserRole)
item1.setCheckable(True)
item1.setEditable(False)
+
# Param values
# TODO: add delegate for validation of cells
item2 = QtGui.QStandardItem(str(param.default))
- item4 = QtGui.QStandardItem(str(param.limits[0]))
- item5 = QtGui.QStandardItem(str(param.limits[1]))
- item6 = QtGui.QStandardItem(param.units)
- item6.setEditable(False)
- item.append([item1, item2, item4, item5, item6])
- return item
+ item3 = QtGui.QStandardItem(str(param.limits[0]))
+ item4 = QtGui.QStandardItem(str(param.limits[1]))
+ item5 = QtGui.QStandardItem(str(param.units))
+ item5.setEditable(False)
+
+ # Check if fixed-choice (returns combobox, if so, also makes some items uneditable)
+ row = [item1, item2, item3, item4, item5]
+ cbox = createFixedChoiceComboBox(param, row)
+
+ # Append to the model and use the combobox, if required
+ if None not in (model, view):
+ model.appendRow(row)
+ if cbox:
+ view.setIndexWidget(item2.index(), cbox)
+ rows.append(row)
+
+ return rows
def markParameterDisabled(model, row):
@@ -182,4 +256,20 @@
model.appendRow(item_list)
+def addHeadingRowToModel(model, name):
+ """adds a non-interactive top-level row to the model"""
+ header_row = [QtGui.QStandardItem() for i in range(5)]
+ header_row[0].setText(name)
+
+ font = header_row[0].font()
+ font.setBold(True)
+ header_row[0].setFont(font)
+
+ for item in header_row:
+ item.setEditable(False)
+ item.setCheckable(False)
+ item.setSelectable(False)
+
+ model.appendRow(header_row)
+
def addHeadersToModel(model):
"""
@@ -227,10 +317,14 @@
model.header_tooltips = copy.copy(poly_header_error_tooltips)
-def addShellsToModel(parameters, model, index):
- """
- Find out multishell parameters and update the model with the requested number of them
+def addShellsToModel(parameters, model, index, row_num=None, view=None):
+ """
+ Find out multishell parameters and update the model with the requested number of them.
+ Inserts them after the row at row_num, if not None; otherwise, appends to end.
+ If view param is not None, supports fixed-choice params.
+ Returns a list of lists of QStandardItem objects.
"""
multishell_parameters = getIterParams(parameters)
+ rows = []
for i in range(index):
for par in multishell_parameters:
@@ -250,5 +344,5 @@
item1_3 = QtGui.QStandardItem(str(p.limits[0]))
item1_4 = QtGui.QStandardItem(str(p.limits[1]))
- item1_5 = QtGui.QStandardItem(p.units)
+ item1_5 = QtGui.QStandardItem(str(p.units))
poly_item.appendRow([item1_1, item1_2, item1_3, item1_4, item1_5])
break
@@ -258,6 +352,25 @@
item3 = QtGui.QStandardItem(str(par.limits[0]))
item4 = QtGui.QStandardItem(str(par.limits[1]))
- item5 = QtGui.QStandardItem(par.units)
- model.appendRow([item1, item2, item3, item4, item5])
+ item5 = QtGui.QStandardItem(str(par.units))
+ item5.setEditable(False)
+
+ # Check if fixed-choice (returns combobox, if so, also makes some items uneditable)
+ row = [item1, item2, item3, item4, item5]
+ cbox = createFixedChoiceComboBox(par, row)
+
+ # Always add to the model
+ if row_num is None:
+ model.appendRow(row)
+ else:
+ model.insertRow(row_num, row)
+ row_num += 1
+
+ # Apply combobox if required
+ if None not in (view, cbox):
+ view.setIndexWidget(item2.index(), cbox)
+
+ rows.append(row)
+
+ return rows
def calculateChi2(reference_data, current_data):
Index: src/sas/qtgui/Perspectives/Fitting/FittingWidget.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/FittingWidget.py (revision 339e22b9f5c08262b1d5b0b551ce467bcf83178c)
+++ src/sas/qtgui/Perspectives/Fitting/FittingWidget.py (revision 339e22b9f5c08262b1d5b0b551ce467bcf83178c)
@@ -90,6 +90,6 @@
fittingFinishedSignal = QtCore.pyqtSignal(tuple)
batchFittingFinishedSignal = QtCore.pyqtSignal(tuple)
- Calc1DFinishedSignal = QtCore.pyqtSignal(tuple)
- Calc2DFinishedSignal = QtCore.pyqtSignal(tuple)
+ Calc1DFinishedSignal = QtCore.pyqtSignal(dict)
+ Calc2DFinishedSignal = QtCore.pyqtSignal(dict)
def __init__(self, parent=None, data=None, tab_id=1):
@@ -219,6 +219,7 @@
# Utility variable to enable unselectable option in category combobox
self._previous_category_index = 0
- # Utility variable for multishell display
- self._last_model_row = 0
+ # Utility variables for multishell display
+ self._n_shells_row = 0
+ self._num_shell_params = 0
# Dictionary of {model name: model class} for the current category
self.models = {}
@@ -247,4 +248,8 @@
# copy of current kernel model
self.kernel_module_copy = None
+
+ # dictionaries of current params
+ self.poly_params = {}
+ self.magnet_params = {}
# Page id for fitting
@@ -672,5 +677,7 @@
Return list of all parameters for the current model
"""
- return [self._model_model.item(row).text() for row in range(self._model_model.rowCount())]
+ return [self._model_model.item(row).text()
+ for row in range(self._model_model.rowCount())
+ if self.isCheckable(row)]
def modifyViewOnRow(self, row, font=None, brush=None):
@@ -700,4 +707,5 @@
assert isinstance(constraint, Constraint)
assert 0 <= row <= self._model_model.rowCount()
+ assert self.isCheckable(row)
item = QtGui.QStandardItem()
@@ -720,4 +728,5 @@
max_col = self.lstParams.itemDelegate().param_max
for row in self.selectedParameters():
+ assert(self.isCheckable(row))
param = self._model_model.item(row, 0).text()
value = self._model_model.item(row, 1).text()
@@ -762,4 +771,6 @@
max_col = self.lstParams.itemDelegate().param_max
for row in range(self._model_model.rowCount()):
+ if not self.isCheckable(row):
+ continue
if not self.rowHasConstraint(row):
continue
@@ -790,10 +801,12 @@
For the given row, return its constraint, if any
"""
- try:
+ if self.isCheckable(row):
item = self._model_model.item(row, 1)
- return item.child(0).data()
- except AttributeError:
- # return none when no constraints
- return None
+ try:
+ return item.child(0).data()
+ except AttributeError:
+ # return none when no constraints
+ pass
+ return None
def rowHasConstraint(self, row):
@@ -801,9 +814,10 @@
Finds out if row of the main model has a constraint child
"""
- item = self._model_model.item(row, 1)
- if item.hasChildren():
- c = item.child(0).data()
- if isinstance(c, Constraint):
- return True
+ if self.isCheckable(row):
+ item = self._model_model.item(row, 1)
+ if item.hasChildren():
+ c = item.child(0).data()
+ if isinstance(c, Constraint):
+ return True
return False
@@ -812,9 +826,10 @@
Finds out if row of the main model has an active constraint child
"""
- item = self._model_model.item(row, 1)
- if item.hasChildren():
- c = item.child(0).data()
- if isinstance(c, Constraint) and c.active:
- return True
+ if self.isCheckable(row):
+ item = self._model_model.item(row, 1)
+ if item.hasChildren():
+ c = item.child(0).data()
+ if isinstance(c, Constraint) and c.active:
+ return True
return False
@@ -823,9 +838,10 @@
Finds out if row of the main model has an active, nontrivial constraint child
"""
- item = self._model_model.item(row, 1)
- if item.hasChildren():
- c = item.child(0).data()
- if isinstance(c, Constraint) and c.func and c.active:
- return True
+ if self.isCheckable(row):
+ item = self._model_model.item(row, 1)
+ if item.hasChildren():
+ c = item.child(0).data()
+ if isinstance(c, Constraint) and c.func and c.active:
+ return True
return False
@@ -1186,5 +1202,7 @@
# Update the sasmodel
# PD[ratio] -> width, npts -> npts, nsigs -> nsigmas
- self.kernel_module.setParam(parameter_name + '.' + delegate.columnDict()[model_column], value)
+ #self.kernel_module.setParam(parameter_name + '.' + delegate.columnDict()[model_column], value)
+ key = parameter_name + '.' + delegate.columnDict()[model_column]
+ self.poly_params[key] = value
# Update plot
@@ -1195,5 +1213,7 @@
row = self.getRowFromName(parameter_name)
param_item = self._model_model.item(row)
+ self._model_model.blockSignals(True)
param_item.child(0).child(0, model_column).setText(item.text())
+ self._model_model.blockSignals(False)
def onMagnetModelChange(self, item):
@@ -1224,15 +1244,21 @@
# Unparsable field
return
-
- property_index = self._magnet_model.headerData(1, model_column)-1 # Value, min, max, etc.
-
- # Update the parameter value - note: this supports +/-inf as well
- self.kernel_module.params[parameter_name] = value
-
- # min/max to be changed in self.kernel_module.details[parameter_name] = ['Ang', 0.0, inf]
- self.kernel_module.details[parameter_name][property_index] = value
-
- # Force the chart update when actual parameters changed
- if model_column == 1:
+ delegate = self.lstMagnetic.itemDelegate()
+
+ if model_column > 1:
+ if model_column == delegate.mag_min:
+ pos = 1
+ elif model_column == delegate.mag_max:
+ pos = 2
+ elif model_column == delegate.mag_unit:
+ pos = 0
+ else:
+ raise AttributeError("Wrong column in magnetism table.")
+ # min/max to be changed in self.kernel_module.details[parameter_name] = ['Ang', 0.0, inf]
+ self.kernel_module.details[parameter_name][pos] = value
+ else:
+ self.magnet_params[parameter_name] = value
+ #self.kernel_module.setParam(parameter_name) = value
+ # Force the chart update when actual parameters changed
self.recalculatePlotData()
@@ -1481,4 +1507,6 @@
# update charts
self.onPlot()
+ #self.recalculatePlotData()
+
# Read only value - we can get away by just printing it here
@@ -1495,7 +1523,9 @@
# Data going in
data = self.logic.data
- model = self.kernel_module
+ model = copy.deepcopy(self.kernel_module)
qmin = self.q_range_min
qmax = self.q_range_max
+ # add polydisperse/magnet parameters if asked
+ self.updateKernelModelWithExtraParams(model)
params_to_fit = self.main_params_to_fit
@@ -1561,9 +1591,10 @@
# internal so can use closure for param_dict
param_name = str(self._model_model.item(row, 0).text())
- if param_name not in list(param_dict.keys()):
+ if not self.isCheckable(row) or param_name not in list(param_dict.keys()):
return
# modify the param value
param_repr = GuiUtils.formatNumber(param_dict[param_name][0], high=True)
self._model_model.item(row, 1).setText(param_repr)
+ self.kernel_module.setParam(param_name, param_dict[param_name][0])
if self.has_error_column:
error_repr = GuiUtils.formatNumber(param_dict[param_name][1], high=True)
@@ -1573,5 +1604,5 @@
# Utility function for updateof polydispersity part of the main model
param_name = str(self._model_model.item(row, 0).text())+'.width'
- if param_name not in list(param_dict.keys()):
+ if not self.isCheckable(row) or param_name not in list(param_dict.keys()):
return
# modify the param value
@@ -1607,8 +1638,4 @@
poly_item.insertColumn(2, [QtGui.QStandardItem("")])
- # block signals temporarily, so we don't end up
- # updating charts with every single model change on the end of fitting
- self._model_model.blockSignals(True)
-
if not self.has_error_column:
# create top-level error column
@@ -1617,9 +1644,5 @@
self.iterateOverModel(createErrorColumn)
- # we need to enable signals for this, otherwise the final column mysteriously disappears (don't ask, I don't
- # know)
- self._model_model.blockSignals(False)
self._model_model.insertColumn(2, error_column)
- self._model_model.blockSignals(True)
FittingUtilities.addErrorHeadersToModel(self._model_model)
@@ -1630,8 +1653,10 @@
self.has_error_column = True
+ # block signals temporarily, so we don't end up
+ # updating charts with every single model change on the end of fitting
+ self._model_model.itemChanged.disconnect()
self.iterateOverModel(updateFittedValues)
self.iterateOverModel(updatePolyValues)
-
- self._model_model.blockSignals(False)
+ self._model_model.itemChanged.connect(self.onMainParamsChange)
# Adjust the table cells width.
@@ -1668,8 +1693,8 @@
param_repr = GuiUtils.formatNumber(param_dict[param_name][0], high=True)
self._poly_model.item(row_i, 1).setText(param_repr)
+ self.kernel_module.setParam(param_name, param_dict[param_name][0])
if self.has_poly_error_column:
error_repr = GuiUtils.formatNumber(param_dict[param_name][1], high=True)
self._poly_model.item(row_i, 2).setText(error_repr)
-
def createErrorColumn(row_i):
@@ -1692,7 +1717,7 @@
# block signals temporarily, so we don't end up
# updating charts with every single model change on the end of fitting
- self._poly_model.blockSignals(True)
+ self._poly_model.itemChanged.disconnect()
self.iterateOverPolyModel(updateFittedValues)
- self._poly_model.blockSignals(False)
+ self._poly_model.itemChanged.connect(self.onPolyModelChange)
if self.has_poly_error_column:
@@ -1704,7 +1729,5 @@
# switch off reponse to model change
- self._poly_model.blockSignals(True)
self._poly_model.insertColumn(2, error_column)
- self._poly_model.blockSignals(False)
FittingUtilities.addErrorPolyHeadersToModel(self._poly_model)
@@ -1739,4 +1762,5 @@
param_repr = GuiUtils.formatNumber(param_dict[param_name][0], high=True)
self._magnet_model.item(row, 1).setText(param_repr)
+ self.kernel_module.setParam(param_name, param_dict[param_name][0])
if self.has_magnet_error_column:
error_repr = GuiUtils.formatNumber(param_dict[param_name][1], high=True)
@@ -1758,7 +1782,7 @@
# block signals temporarily, so we don't end up
# updating charts with every single model change on the end of fitting
- self._magnet_model.blockSignals(True)
+ self._magnet_model.itemChanged.disconnect()
self.iterateOverMagnetModel(updateFittedValues)
- self._magnet_model.blockSignals(False)
+ self._magnet_model.itemChanged.connect(self.onMagnetModelChange)
if self.has_magnet_error_column:
@@ -1770,7 +1794,5 @@
# switch off reponse to model change
- self._magnet_model.blockSignals(True)
self._magnet_model.insertColumn(2, error_column)
- self._magnet_model.blockSignals(False)
FittingUtilities.addErrorHeadersToModel(self._magnet_model)
@@ -1784,6 +1806,6 @@
self.cmdPlot.setText("Show Plot")
# Force data recalculation so existing charts are updated
+ self.showPlot()
self.recalculatePlotData()
- self.showPlot()
def onSmearingOptionsUpdate(self):
@@ -1950,31 +1972,33 @@
# Crete/overwrite model items
self._model_model.clear()
-
- # First, add parameters from the main model
- if model_name is not None:
+ self._poly_model.clear()
+ self._magnet_model.clear()
+
+ if model_name is None:
+ if structure_factor not in (None, "None"):
+ # S(Q) on its own, treat the same as a form factor
+ self.kernel_module = None
+ self.fromStructureFactorToQModel(structure_factor)
+ else:
+ # No models selected
+ return
+ else:
self.fromModelToQModel(model_name)
-
- # Then, add structure factor derived parameters
- if structure_factor is not None and structure_factor != "None":
- if model_name is None:
- # Instantiate the current sasmodel for SF-only models
- self.kernel_module = self.models[structure_factor]()
- self.fromStructureFactorToQModel(structure_factor)
- else:
+ self.addExtraShells()
+
# Allow the SF combobox visibility for the given sasmodel
self.enableStructureFactorControl(structure_factor)
+
+ # Add S(Q)
if self.cbStructureFactor.isEnabled():
structure_factor = self.cbStructureFactor.currentText()
self.fromStructureFactorToQModel(structure_factor)
- # Then, add multishells
- if model_name is not None:
- # Multishell models need additional treatment
- self.addExtraShells()
-
- # Add polydispersity to the model
- self.setPolyModel()
- # Add magnetic parameters to the model
- self.setMagneticModel()
+ # Add polydispersity to the model
+ self.poly_params = {}
+ self.setPolyModel()
+ # Add magnetic parameters to the model
+ self.magnet_params = {}
+ self.setMagneticModel()
# Adjust the table cells width
@@ -2049,11 +2073,14 @@
self.shell_names = self.shellNamesList()
+ # Add heading row
+ FittingUtilities.addHeadingRowToModel(self._model_model, model_name)
+
# Update the QModel
- new_rows = FittingUtilities.addParametersToModel(self.model_parameters, self.kernel_module, self.is2D)
-
- for row in new_rows:
- self._model_model.appendRow(row)
- # Update the counter used for multishell display
- self._last_model_row = self._model_model.rowCount()
+ FittingUtilities.addParametersToModel(
+ self.model_parameters,
+ self.kernel_module,
+ self.is2D,
+ self._model_model,
+ self.lstParams)
def fromStructureFactorToQModel(self, structure_factor):
@@ -2063,22 +2090,48 @@
if structure_factor is None or structure_factor=="None":
return
- structure_module = generate.load_kernel_module(structure_factor)
- structure_parameters = modelinfo.make_parameter_table(getattr(structure_module, 'parameters', []))
-
- structure_kernel = self.models[structure_factor]()
- form_kernel = self.kernel_module
-
- self.kernel_module = MultiplicationModel(form_kernel, structure_kernel)
-
- new_rows = FittingUtilities.addSimpleParametersToModel(structure_parameters, self.is2D)
- for row in new_rows:
- self._model_model.appendRow(row)
- # disable fitting of parameters not listed in self.kernel_module (probably radius_effective)
- if row[0].text() not in self.kernel_module.params.keys():
- row_num = self._model_model.rowCount() - 1
- FittingUtilities.markParameterDisabled(self._model_model, row_num)
-
- # Update the counter used for multishell display
- self._last_model_row = self._model_model.rowCount()
+
+ if self.kernel_module is None:
+ # Structure factor is the only selected model; build it and show all its params
+ self.kernel_module = self.models[structure_factor]()
+ s_params = self.kernel_module._model_info.parameters
+ s_params_orig = s_params
+
+ else:
+ s_kernel = self.models[structure_factor]()
+ p_kernel = self.kernel_module
+
+ p_pars_len = len(p_kernel._model_info.parameters.kernel_parameters)
+ s_pars_len = len(s_kernel._model_info.parameters.kernel_parameters)
+
+ self.kernel_module = MultiplicationModel(p_kernel, s_kernel)
+ all_params = self.kernel_module._model_info.parameters.kernel_parameters
+ all_param_names = [param.name for param in all_params]
+
+ # S(Q) params from the product model are not necessarily the same as those from the S(Q) model; any
+ # conflicting names with P(Q) params will cause a rename
+
+ if "radius_effective_mode" in all_param_names:
+ # Show all parameters
+ s_params = modelinfo.ParameterTable(all_params[p_pars_len:p_pars_len+s_pars_len])
+ s_params_orig = modelinfo.ParameterTable(s_kernel._model_info.parameters.kernel_parameters)
+ else:
+ # Ensure radius_effective is not displayed
+ s_params_orig = modelinfo.ParameterTable(s_kernel._model_info.parameters.kernel_parameters[1:])
+ if "radius_effective" in all_param_names:
+ s_params = modelinfo.ParameterTable(all_params[p_pars_len+1:p_pars_len+s_pars_len])
+ else:
+ s_params = modelinfo.ParameterTable(all_params[p_pars_len:p_pars_len+s_pars_len-1])
+
+ # Add heading row
+ FittingUtilities.addHeadingRowToModel(self._model_model, structure_factor)
+
+ # Get new rows for QModel
+ # Any renamed parameters are stored as data in the relevant item, for later handling
+ FittingUtilities.addSimpleParametersToModel(
+ s_params,
+ self.is2D,
+ s_params_orig,
+ self._model_model,
+ self.lstParams)
def haveParamsToFit(self):
@@ -2106,4 +2159,5 @@
model_row = item.row()
name_index = self._model_model.index(model_row, 0)
+ name_item = self._model_model.itemFromIndex(name_index)
# Extract changed value.
@@ -2114,5 +2168,9 @@
return
- parameter_name = str(self._model_model.data(name_index)) # sld, background etc.
+ # if the item has user data, this is the actual parameter name (e.g. to handle duplicate names)
+ if name_item.data(QtCore.Qt.UserRole):
+ parameter_name = str(name_item.data(QtCore.Qt.UserRole))
+ else:
+ parameter_name = str(self._model_model.data(name_index))
# Update the parameter value - note: this supports +/-inf as well
@@ -2237,4 +2295,21 @@
return self.completed1D if isinstance(self.data, Data1D) else self.completed2D
+ def updateKernelModelWithExtraParams(self, model=None):
+ """
+ Updates kernel model 'model' with extra parameters from
+ the polydisp and magnetism tab, if the tabs are enabled
+ """
+ if model is None: return
+ if not hasattr(model, 'setParam'): return
+
+ # add polydisperse parameters if asked
+ if self.chkPolydispersity.isChecked():
+ for key, value in self.poly_params.items():
+ model.setParam(key, value)
+ # add magnetic params if asked
+ if self.chkMagnetism.isChecked():
+ for key, value in self.magnet_params.items():
+ model.setParam(key, value)
+
def calculateQGridForModelExt(self, data=None, model=None, completefn=None, use_threads=True):
"""
@@ -2244,5 +2319,7 @@
data = self.data
if model is None:
- model = self.kernel_module
+ model = copy.deepcopy(self.kernel_module)
+ self.updateKernelModelWithExtraParams(model)
+
if completefn is None:
completefn = self.methodCompleteForData()
@@ -2329,15 +2406,21 @@
new_plots.append(sq_data)
+ for plot in new_plots:
+ self.communicate.plotUpdateSignal.emit([plot])
+
+ def complete2D(self, return_data):
+ """
+ Plot the current 2D data
+ """
+ fitted_data = self.logic.new2DPlot(return_data)
+ residuals = self.calculateResiduals(fitted_data)
+ self.model_data = fitted_data
+ new_plots = [fitted_data]
+ if residuals is not None:
+ new_plots.append(residuals)
+
# Update/generate plots
for plot in new_plots:
self.communicate.plotUpdateSignal.emit([plot])
-
- def complete2D(self, return_data):
- """
- Plot the current 2D data
- """
- fitted_data = self.logic.new2DPlot(return_data)
- self.calculateResiduals(fitted_data)
- self.model_data = fitted_data
def calculateResiduals(self, fitted_data):
@@ -2470,4 +2553,9 @@
_, min, max = self.kernel_module.details[param_name]
+ # Update local param dict
+ self.poly_params[param_name + '.width'] = width
+ self.poly_params[param_name + '.npts'] = npts
+ self.poly_params[param_name + '.nsigmas'] = nsigs
+
# Construct a row with polydisp. related variable.
# This will get added to the polydisp. model
@@ -2517,8 +2605,13 @@
def updateFunctionCaption(row):
# Utility function for update of polydispersity function name in the main model
+ if not self.isCheckable(row):
+ return
+ self._model_model.blockSignals(True)
param_name = str(self._model_model.item(row, 0).text())
+ self._model_model.blockSignals(False)
if param_name != param.name:
return
# Modify the param value
+ self._model_model.blockSignals(True)
if self.has_error_column:
# err column changes the indexing
@@ -2526,4 +2619,5 @@
else:
self._model_model.item(row, 0).child(0).child(0,4).setText(combo_string)
+ self._model_model.blockSignals(False)
if combo_string == 'array':
@@ -2644,4 +2738,6 @@
param.units]
+ self.magnet_params[param.name] = param.default
+
FittingUtilities.addCheckedListToModel(model, checked_list)
@@ -2683,5 +2779,5 @@
self.lstParams.setIndexWidget(shell_index, func)
- self._last_model_row = self._model_model.rowCount()
+ self._n_shells_row = shell_row - 1
# Set the index to the state-kept value
@@ -2694,11 +2790,18 @@
"""
# Find row location of the combobox
- last_row = self._last_model_row
- remove_rows = self._model_model.rowCount() - last_row
+ first_row = self._n_shells_row + 1
+ remove_rows = self._num_shell_params
if remove_rows > 1:
- self._model_model.removeRows(last_row, remove_rows)
-
- FittingUtilities.addShellsToModel(self.model_parameters, self._model_model, index)
+ self._model_model.removeRows(first_row, remove_rows)
+
+ new_rows = FittingUtilities.addShellsToModel(
+ self.model_parameters,
+ self._model_model,
+ index,
+ first_row,
+ self.lstParams)
+
+ self._num_shell_params = len(new_rows)
self.current_shell_displayed = index
Index: src/sas/qtgui/Perspectives/Fitting/ModelThread.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/ModelThread.py (revision 2df558ebcc19b99ebc1900efaa872a981b31a67d)
+++ src/sas/qtgui/Perspectives/Fitting/ModelThread.py (revision dcabba7be660ff2e140942eb331ca846d1f2a399)
@@ -101,36 +101,16 @@
elapsed = time.time() - self.starttime
+ res = dict(image = output, data = self.data, page_id = self.page_id,
+ model = self.model, state = self.state,
+ toggle_mode_on = self.toggle_mode_on, elapsed = elapsed,
+ index = index_model, fid = self.fid,
+ qmin = self.qmin, qmax = self.qmax,
+ weight = self.weight, update_chisqr = self.update_chisqr,
+ source = self.source)
+
if LocalConfig.USING_TWISTED:
- return (output,
- self.data,
- self.page_id,
- self.model,
- self.state,
- self.toggle_mode_on,
- elapsed,
- index_model,
- self.fid,
- self.qmin,
- self.qmax,
- self.weight,
- self.update_chisqr,
- self.source)
- else:
- self.completefn((output,
- self.data,
- self.page_id,
- self.model,
- self.state,
- self.toggle_mode_on,
- elapsed,
- index_model,
- self.fid,
- self.qmin,
- self.qmax,
- self.weight,
- #qstep=self.qstep,
- self.update_chisqr,
- self.source))
-
+ return res
+ else:
+ self.completefn(res)
class Calc1D(CalcThread):
@@ -236,30 +216,17 @@
elapsed = time.time() - self.starttime
+ res = dict(x = self.data.x[index], y = output[index],
+ page_id = self.page_id, state = self.state, weight = self.weight,
+ fid = self.fid, toggle_mode_on = self.toggle_mode_on,
+ elapsed = elapsed, index = index, model = self.model,
+ data = self.data, update_chisqr = self.update_chisqr,
+ source = self.source, unsmeared_output = unsmeared_output,
+ unsmeared_data = unsmeared_data, unsmeared_error = unsmeared_error,
+ pq_values = pq_values, sq_values = sq_values)
+
if LocalConfig.USING_TWISTED:
- return (self.data.x[index], output[index],
- self.page_id,
- self.state,
- self.weight,
- self.fid,
- self.toggle_mode_on,
- elapsed, index, self.model,
- self.data,
- self.update_chisqr,
- self.source,
- unsmeared_output, unsmeared_data, unsmeared_error,
- pq_values, sq_values)
- else:
- self.completefn((self.data.x[index], output[index],
- self.page_id,
- self.state,
- self.weight,
- self.fid,
- self.toggle_mode_on,
- elapsed, index, self.model,
- self.data,
- self.update_chisqr,
- self.source,
- unsmeared_output, unsmeared_data, unsmeared_error,
- pq_values, sq_values))
+ return res
+ else:
+ self.completefn(res)
def results(self):
Index: src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py (revision 605d944a69488f22ea74b9d65f595b664f5aae54)
+++ src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py (revision 3fbd77b038f1503431a47e6e7b3a7ad275c0655c)
@@ -256,5 +256,5 @@
self.widget.cbStructureFactor.setCurrentIndex(structure_index)
- # We have 4 more rows now
+ # We have 3 more param rows now (radius_effective is removed), and a new heading
self.assertEqual(self.widget._model_model.rowCount(), rowcount+4)
@@ -276,6 +276,6 @@
last_index = self.widget.cbStructureFactor.count()
self.widget.cbStructureFactor.setCurrentIndex(last_index-1)
- # Do we have all the rows?
- self.assertEqual(self.widget._model_model.rowCount(), 4)
+ # Do we have all the rows (incl. radius_effective & heading row)?
+ self.assertEqual(self.widget._model_model.rowCount(), 5)
# Are the command buttons properly enabled?
@@ -445,21 +445,24 @@
self.assertEqual(self.widget.kernel_module.details['radius_bell'][1], 1.0)
+ #self.widget.show()
+ #QtWidgets.QApplication.exec_()
+
# Change the number of points
- self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 35)
+ self.assertEqual(self.widget.poly_params['radius_bell.npts'], 35)
self.widget._poly_model.item(0,4).setText("22")
- self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 22)
+ self.assertEqual(self.widget.poly_params['radius_bell.npts'], 22)
# try something stupid
self.widget._poly_model.item(0,4).setText("butt")
# see that this didn't annoy the control at all
- self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 22)
+ self.assertEqual(self.widget.poly_params['radius_bell.npts'], 22)
# Change the number of sigmas
- self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 3)
+ self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 3)
self.widget._poly_model.item(0,5).setText("222")
- self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 222)
+ self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 222)
# try something stupid again
self.widget._poly_model.item(0,4).setText("beer")
# no efect
- self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 222)
+ self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 222)
def testOnPolyComboIndexChange(self):
@@ -482,16 +485,16 @@
self.widget.onPolyComboIndexChange('rectangle', 0)
# check values
- self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 35)
- self.assertAlmostEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 1.73205, 5)
+ self.assertEqual(self.widget.poly_params['radius_bell.npts'], 35)
+ self.assertAlmostEqual(self.widget.poly_params['radius_bell.nsigmas'], 1.73205, 5)
# Change the index
self.widget.onPolyComboIndexChange('lognormal', 0)
# check values
- self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 80)
- self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 8)
+ self.assertEqual(self.widget.poly_params['radius_bell.npts'], 80)
+ self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 8)
# Change the index
self.widget.onPolyComboIndexChange('schulz', 0)
# check values
- self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 80)
- self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 8)
+ self.assertEqual(self.widget.poly_params['radius_bell.npts'], 80)
+ self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 8)
# mock up file load
@@ -506,5 +509,9 @@
Test opening of the load file dialog for 'array' polydisp. function
"""
+
+ # open a non-existent file
filename = os.path.join("UnitTesting", "testdata_noexist.txt")
+ with self.assertRaises(OSError, msg="testdata_noexist.txt should be a non-existent file"):
+ os.stat(filename)
QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=(filename,''))
self.widget.show()
@@ -522,5 +529,12 @@
# good file
+ # TODO: this depends on the working directory being src/sas/qtgui,
+ # TODO: which isn't convenient if you want to run this test suite
+ # TODO: individually
filename = os.path.join("UnitTesting", "testdata.txt")
+ try:
+ os.stat(filename)
+ except OSError:
+ self.assertTrue(False, "testdata.txt does not exist")
QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=(filename,''))
@@ -588,7 +602,10 @@
# Assure we have the combobox available
- last_row = self.widget._last_model_row
- func_index = self.widget._model_model.index(last_row-1, 1)
+ cbox_row = self.widget._n_shells_row
+ func_index = self.widget._model_model.index(cbox_row, 1)
self.assertIsInstance(self.widget.lstParams.indexWidget(func_index), QtWidgets.QComboBox)
+
+ # get number of rows before changing shell count
+ last_row = self.widget._model_model.rowCount()
# Change the combo box index
@@ -1024,5 +1041,5 @@
# Check the model
- self.assertEqual(self.widget._model_model.rowCount(), 6)
+ self.assertEqual(self.widget._model_model.rowCount(), 7)
self.assertEqual(self.widget._model_model.columnCount(), 5)
@@ -1140,5 +1157,5 @@
# two rows selected
index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
- index2 = self.widget.lstParams.model().index(2, 0, QtCore.QModelIndex())
+ index2 = self.widget.lstParams.model().index(3, 0, QtCore.QModelIndex())
selection_model = self.widget.lstParams.selectionModel()
selection_model.select(index1, selection_model.Select | selection_model.Rows)
@@ -1176,5 +1193,5 @@
# several random parameters
self.assertEqual(self.widget.getRowFromName('scale'), 0)
- self.assertEqual(self.widget.getRowFromName('length'), 5)
+ self.assertEqual(self.widget.getRowFromName('length'), 6)
def testGetParamNames(self):
@@ -1213,5 +1230,5 @@
# Create a constraint object
const = Constraint(parent=None, value=7.0)
- row = 2
+ row = 3
spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
@@ -1232,5 +1249,5 @@
# assign complex constraint now
const = Constraint(parent=None, param='radius', func='5*sld')
- row = 4
+ row = 5
# call the method tested
self.widget.addConstraintToRow(constraint=const, row=row)
@@ -1291,7 +1308,14 @@
self.widget.cbModel.setCurrentIndex(model_index)
+ row1 = 1
+ row2 = 5
+
+ param1 = "background"
+ param2 = "radius"
+
+ #default_value1 = "0.001"
+ default_value2 = "20"
+
# select two rows
- row1 = 1
- row2 = 4
index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
@@ -1310,10 +1334,10 @@
# delete one of the constraints
- self.widget.deleteConstraintOnParameter(param='background')
+ self.widget.deleteConstraintOnParameter(param=param1)
# see that the other constraint is still present
- cons = self.widget.getConstraintForRow(4) # 4 = radius
- self.assertEqual(cons.param, "radius")
- self.assertEqual(cons.value, "20")
+ cons = self.widget.getConstraintForRow(row2)
+ self.assertEqual(cons.param, param2)
+ self.assertEqual(cons.value, default_value2)
# kill the other constraint
@@ -1321,5 +1345,5 @@
# see that the other constraint is still present
- self.assertEqual(self.widget.getConstraintsForModel(), [('radius', None)])
+ self.assertEqual(self.widget.getConstraintsForModel(), [(param2, None)])
def testGetConstraintForRow(self):
@@ -1341,7 +1365,8 @@
self.widget.cbModel.setCurrentIndex(model_index)
+ row1 = 1
+ row2 = 5
+
# select two rows
- row1 = 1
- row2 = 4
index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
@@ -1353,5 +1378,5 @@
self.widget.addSimpleConstraint()
- con_list = [False, True, False, False, True, False]
+ con_list = [False, True, False, False, False, True, False]
new_list = []
for row in range(self.widget._model_model.rowCount()):
@@ -1371,7 +1396,8 @@
self.widget.cbModel.setCurrentIndex(model_index)
+ row1 = 1
+ row2 = 5
+
# select two rows
- row1 = 1
- row2 = 4
index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
@@ -1387,5 +1413,5 @@
constraint_objects[0].active = False
- con_list = [False, False, False, False, True, False]
+ con_list = [False, False, False, False, False, True, False]
new_list = []
for row in range(self.widget._model_model.rowCount()):
@@ -1408,7 +1434,14 @@
self.assertEqual(self.widget.getConstraintsForModel(),[])
+ row1 = 1
+ row2 = 5
+
+ param1 = "background"
+ param2 = "radius"
+
+ default_value1 = "0.001"
+ default_value2 = "20"
+
# select two rows
- row1 = 1
- row2 = 4
index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
@@ -1422,28 +1455,31 @@
# simple constraints
# self.assertEqual(self.widget.getConstraintsForModel(), [('background', '0.001'), ('radius', '20')])
- cons = self.widget.getConstraintForRow(1) # 1 - background
- self.assertEqual(cons.param, "background")
- self.assertEqual(cons.value, "0.001")
- cons = self.widget.getConstraintForRow(4) # 4 = radius
- self.assertEqual(cons.param, "radius")
- self.assertEqual(cons.value, "20")
+ cons = self.widget.getConstraintForRow(row1)
+ self.assertEqual(cons.param, param1)
+ self.assertEqual(cons.value, default_value1)
+ cons = self.widget.getConstraintForRow(row2)
+ self.assertEqual(cons.param, param2)
+ self.assertEqual(cons.value, default_value2)
objects = self.widget.getConstraintObjectsForModel()
self.assertEqual(len(objects), 2)
- self.assertEqual(objects[1].value, '20')
- self.assertEqual(objects[0].param, 'background')
+ self.assertEqual(objects[1].value, default_value2)
+ self.assertEqual(objects[0].param, param1)
+
+ row = 0
+ param = "scale"
+ func = "5*sld"
# add complex constraint
- const = Constraint(parent=None, param='scale', func='5*sld')
- row = 0
+ const = Constraint(parent=None, param=param, func=func)
self.widget.addConstraintToRow(constraint=const, row=row)
#self.assertEqual(self.widget.getConstraintsForModel(),[('scale', '5*sld'), ('background', '0.001'), ('radius', None)])
- cons = self.widget.getConstraintForRow(4) # 4 = radius
- self.assertEqual(cons.param, "radius")
- self.assertEqual(cons.value, "20")
+ cons = self.widget.getConstraintForRow(row2)
+ self.assertEqual(cons.param, param2)
+ self.assertEqual(cons.value, default_value2)
objects = self.widget.getConstraintObjectsForModel()
self.assertEqual(len(objects), 3)
- self.assertEqual(objects[0].func, '5*sld')
+ self.assertEqual(objects[0].func, func)
def testReplaceConstraintName(self):
Index: src/sas/qtgui/Plotting/Plotter.py
===================================================================
--- src/sas/qtgui/Plotting/Plotter.py (revision b764ae5882f084a8b74c7cf664276c13fb07c29e)
+++ src/sas/qtgui/Plotting/Plotter.py (revision c2f3ca2c72aeba33d19dcf49f5793abf90fa7a3b)
@@ -30,5 +30,7 @@
# Dictionary of {plot_id:Data1d}
self.plot_dict = {}
-
+ # Dictionaty of {plot_id:line}
+
+ self.plot_lines = {}
# Window for text add
self.addText = AddText(self)
@@ -182,4 +184,6 @@
self.plot_dict[self._data.id] = self.data
+ self.plot_lines[self._data.id] = line
+
# Now add the legend with some customizations.
@@ -196,5 +200,5 @@
# refresh canvas
- self.canvas.draw_idle()
+ self.canvas.draw()
# This is an important processEvent.
# This allows charts to be properly updated in order
@@ -416,4 +420,5 @@
This effectlvely refreshes the chart with changes to one of its plots
"""
+ import logging
self.removePlot(id)
self.plot(data=new_plot)
@@ -470,5 +475,5 @@
"""
selected_plot = self.plot_dict[id]
-
+ selected_line = self.plot_lines[id]
# Old style color - single integer for enum color
# New style color - #hhhhhh