BUGFIX: VFT;
change to src layout
This commit is contained in:
0
src/gui_qt/fit/__init__.py
Normal file
0
src/gui_qt/fit/__init__.py
Normal file
460
src/gui_qt/fit/fit_forms.py
Normal file
460
src/gui_qt/fit/fit_forms.py
Normal file
@ -0,0 +1,460 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from nmreval.utils.text import convert
|
||||
|
||||
from ..Qt import QtCore, QtWidgets, QtGui
|
||||
from .._py.fitmodelwidget import Ui_FitParameter
|
||||
from .._py.save_fitmodel_dialog import Ui_SaveDialog
|
||||
from ..lib import get_icon
|
||||
|
||||
|
||||
class FitModelWidget(QtWidgets.QWidget, Ui_FitParameter):
|
||||
value_requested = QtCore.pyqtSignal(object)
|
||||
value_changed = QtCore.pyqtSignal(str)
|
||||
state_changed = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, label: str = 'Fitparameter', parent=None, fixed: bool = False):
|
||||
super().__init__(parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.parametername.setText(label + ' ')
|
||||
|
||||
validator = QtGui.QDoubleValidator()
|
||||
validator.setDecimals(9)
|
||||
self.parameter_line.setValidator(validator)
|
||||
self.parameter_line.setText('1')
|
||||
self.parameter_line.setMaximumWidth(60)
|
||||
self.lineEdit.setMaximumWidth(60)
|
||||
self.lineEdit_2.setMaximumWidth(60)
|
||||
|
||||
self.label_3.setText(f'< {label} <')
|
||||
|
||||
self.checkBox.stateChanged.connect(self.enableBounds)
|
||||
self.global_checkbox.stateChanged.connect(lambda: self.state_changed.emit())
|
||||
self.parameter_line.values_requested.connect(lambda: self.value_requested.emit(self))
|
||||
self.parameter_line.editingFinished.connect(lambda: self.value_changed.emit(self.parameter_line.text()))
|
||||
self.fixed_check.toggled.connect(self.set_fixed)
|
||||
|
||||
if fixed:
|
||||
self.fixed_check.hide()
|
||||
|
||||
self.menu = QtWidgets.QMenu(self)
|
||||
self.add_links()
|
||||
|
||||
self.is_linked = None
|
||||
self.parameter_pos = None
|
||||
self.func_idx = None
|
||||
|
||||
self._linetext = '1'
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return convert(self.parametername.text().strip(), old='html', new='str')
|
||||
|
||||
def set_parameter_string(self, p: str):
|
||||
self.parameter_line.setText(p)
|
||||
self.parameter_line.setToolTip(p)
|
||||
|
||||
def set_bounds(self, lb: float, ub: float, cbox: bool = True):
|
||||
self.checkBox.setCheckState(QtCore.Qt.Checked if cbox else QtCore.Qt.Unchecked)
|
||||
for val, bds_line in [(lb, self.lineEdit), (ub, self.lineEdit_2)]:
|
||||
if val is not None:
|
||||
bds_line.setText(str(val))
|
||||
else:
|
||||
bds_line.setText('')
|
||||
|
||||
def enableBounds(self, value: int):
|
||||
self.lineEdit.setEnabled(value == 2)
|
||||
self.lineEdit_2.setEnabled(value == 2)
|
||||
|
||||
def set_parameter(self, p: float | None, bds: tuple[float, float, bool] = None,
|
||||
fixed: bool = None, glob: bool = None):
|
||||
if p is None:
|
||||
# bad hack: linked parameter return (None, linked parameter)
|
||||
# if p is None -> parameter is linked to argument given by bds
|
||||
self.link_parameter(linkto=bds)
|
||||
else:
|
||||
ptext = f'{p:.4g}'
|
||||
|
||||
self.set_parameter_string(ptext)
|
||||
|
||||
if bds is not None:
|
||||
self.set_bounds(*bds)
|
||||
|
||||
if fixed is not None:
|
||||
self.fixed_check.setCheckState(QtCore.Qt.Unchecked if fixed else QtCore.Qt.Checked)
|
||||
|
||||
if glob is not None:
|
||||
self.global_checkbox.setCheckState(QtCore.Qt.Checked if glob else QtCore.Qt.Unchecked)
|
||||
|
||||
def get_parameter(self):
|
||||
if self.is_linked:
|
||||
try:
|
||||
p = float(self._linetext)
|
||||
except ValueError:
|
||||
p = 1.0
|
||||
else:
|
||||
try:
|
||||
p = float(self.parameter_line.text().replace(',', '.'))
|
||||
except ValueError:
|
||||
_ = QtWidgets.QMessageBox().warning(self, 'Invalid value',
|
||||
f'{self.parametername.text()} contains invalid values',
|
||||
QtWidgets.QMessageBox.Cancel)
|
||||
return None
|
||||
|
||||
if self.checkBox.isChecked():
|
||||
try:
|
||||
lb = float(self.lineEdit.text().replace(',', '.'))
|
||||
except ValueError:
|
||||
lb = None
|
||||
|
||||
try:
|
||||
rb = float(self.lineEdit_2.text().replace(',', '.'))
|
||||
except ValueError:
|
||||
rb = None
|
||||
else:
|
||||
lb = rb = None
|
||||
|
||||
bounds = (lb, rb)
|
||||
|
||||
return p, bounds, not self.fixed_check.isChecked(), self.global_checkbox.isChecked(), self.is_linked
|
||||
|
||||
@QtCore.pyqtSlot(bool)
|
||||
def set_fixed(self, state: bool):
|
||||
# self.global_checkbox.setVisible(not state)
|
||||
self.frame.setVisible(not state)
|
||||
|
||||
def add_links(self, parameter: dict = None):
|
||||
if parameter is None:
|
||||
parameter = {}
|
||||
self.menu.clear()
|
||||
|
||||
ac = QtWidgets.QAction('Link to...', self)
|
||||
ac.triggered.connect(self.link_parameter)
|
||||
self.menu.addAction(ac)
|
||||
|
||||
for model_key, model_funcs in parameter.items():
|
||||
m = QtWidgets.QMenu('Model ' + model_key, self)
|
||||
for func_name, func_params in model_funcs.items():
|
||||
m2 = QtWidgets.QMenu(func_name, m)
|
||||
for p_name, idx in func_params:
|
||||
ac = QtWidgets.QAction(p_name, m2)
|
||||
ac.setData((model_key, *idx))
|
||||
ac.triggered.connect(self.link_parameter)
|
||||
m2.addAction(ac)
|
||||
m.addMenu(m2)
|
||||
self.menu.addMenu(m)
|
||||
|
||||
self.toolButton.setMenu(self.menu)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def link_parameter(self, linkto=None):
|
||||
if linkto is None:
|
||||
action = self.sender()
|
||||
else:
|
||||
action = False
|
||||
for m in self.menu.actions():
|
||||
if m.menu():
|
||||
for a in m.menu().actions():
|
||||
if a.data() == linkto:
|
||||
action = a
|
||||
break
|
||||
if action:
|
||||
break
|
||||
|
||||
if (self.func_idx, self.parameter_pos) == action.data():
|
||||
return
|
||||
|
||||
try:
|
||||
new_text = f'Linked to {action.parentWidget().title()}.{action.text()}'
|
||||
self._linetext = self.parameter_line.text()
|
||||
self.parameter_line.setText(new_text)
|
||||
self.parameter_line.setEnabled(False)
|
||||
self.global_checkbox.hide()
|
||||
self.global_checkbox.blockSignals(True)
|
||||
self.global_checkbox.setCheckState(QtCore.Qt.Checked)
|
||||
self.global_checkbox.blockSignals(False)
|
||||
self.frame.hide()
|
||||
self.is_linked = action.data()
|
||||
|
||||
except AttributeError:
|
||||
self.parameter_line.setText(self._linetext)
|
||||
self.parameter_line.setEnabled(True)
|
||||
if self.fixed_check.isEnabled():
|
||||
self.global_checkbox.show()
|
||||
self.frame.show()
|
||||
self.is_linked = None
|
||||
|
||||
self.state_changed.emit()
|
||||
|
||||
|
||||
class QSaveModelDialog(QtWidgets.QDialog, Ui_SaveDialog):
|
||||
def __init__(self, types=None, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
|
||||
if types is None:
|
||||
types = []
|
||||
|
||||
self.comboBox.blockSignals(True)
|
||||
self.comboBox.addItems(types)
|
||||
self.comboBox.addItem('New group...')
|
||||
self.comboBox.blockSignals(False)
|
||||
|
||||
self.frame.hide()
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_comboBox_currentIndexChanged')
|
||||
def new_group(self, idx: int):
|
||||
if idx == self.comboBox.count() - 1:
|
||||
self.frame.show()
|
||||
else:
|
||||
self.lineEdit_2.clear()
|
||||
self.frame.hide()
|
||||
|
||||
@QtCore.pyqtSlot(name='on_toolButton_clicked')
|
||||
def accept_group(self):
|
||||
self.comboBox.insertItem(self.comboBox.count() - 1, self.lineEdit_2.text())
|
||||
self.comboBox.setCurrentIndex(self.comboBox.count() - 2)
|
||||
|
||||
def accept(self):
|
||||
if self.lineEdit.text():
|
||||
self.close()
|
||||
|
||||
|
||||
class FitModelTree(QtWidgets.QTreeWidget):
|
||||
icons = ['plus', 'mal_icon', 'minus_icon', 'geteilt_icon']
|
||||
|
||||
treeChanged = QtCore.pyqtSignal()
|
||||
itemRemoved = QtCore.pyqtSignal(int)
|
||||
|
||||
counterRole = QtCore.Qt.UserRole + 1
|
||||
operatorRole = QtCore.Qt.UserRole + 2
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self.setHeaderHidden(True)
|
||||
self.setDragEnabled(True)
|
||||
self.setDragDropMode(QtWidgets.QTreeWidget.InternalMove)
|
||||
self.setDefaultDropAction(QtCore.Qt.MoveAction)
|
||||
|
||||
self.itemSelectionChanged.connect(lambda: self.treeChanged.emit())
|
||||
|
||||
def keyPressEvent(self, evt):
|
||||
operators = [QtCore.Qt.Key_Plus, QtCore.Qt.Key_Asterisk,
|
||||
QtCore.Qt.Key_Minus, QtCore.Qt.Key_Slash]
|
||||
|
||||
if evt.key() == QtCore.Qt.Key_Delete:
|
||||
for item in self.selectedItems():
|
||||
self.remove_function(item)
|
||||
|
||||
elif evt.key() == QtCore.Qt.Key_Space:
|
||||
for item in self.treeWidget.selectedItems():
|
||||
item.setCheckState(0, QtCore.Qt.Checked) if item.checkState(
|
||||
0) == QtCore.Qt.Unchecked else item.setCheckState(0, QtCore.Qt.Unchecked)
|
||||
|
||||
elif evt.key() in operators:
|
||||
idx = operators.index(evt.key())
|
||||
for item in self.selectedItems():
|
||||
item.setData(0, self.operatorRole, idx)
|
||||
item.setIcon(0, get_icon(self.icons[idx]))
|
||||
|
||||
else:
|
||||
super().keyPressEvent(evt)
|
||||
|
||||
def dropEvent(self, evt: QtGui.QDropEvent):
|
||||
super().dropEvent(evt)
|
||||
self.treeChanged.emit()
|
||||
|
||||
def remove_function(self, item: QtWidgets.QTreeWidgetItem):
|
||||
"""
|
||||
Remove function and children from tree and dictionary
|
||||
"""
|
||||
while item.childCount():
|
||||
self.remove_function(item.child(0))
|
||||
|
||||
if item.parent():
|
||||
item.parent().removeChild(item)
|
||||
else:
|
||||
self.invisibleRootItem().removeChild(item)
|
||||
|
||||
idx = item.data(0, self.counterRole)
|
||||
self.itemRemoved.emit(idx)
|
||||
|
||||
def add_function(self, idx: int, cnt: int, op: int, name: str, color: QtGui.QColor | str | tuple,
|
||||
parent: QtWidgets.QTreeWidgetItem = None, children: list = None, active: bool = True, **kwargs):
|
||||
"""
|
||||
Add function to tree and dictionary of functions.
|
||||
"""
|
||||
if not isinstance(color, QtGui.QColor):
|
||||
if isinstance(color, tuple):
|
||||
color = QtGui.QColor.fromRgbF(*color)
|
||||
else:
|
||||
color = QtGui.QColor(color)
|
||||
|
||||
it = QtWidgets.QTreeWidgetItem()
|
||||
it.setData(0, QtCore.Qt.UserRole, idx)
|
||||
it.setData(0, self.counterRole, cnt)
|
||||
it.setData(0, self.operatorRole, op)
|
||||
it.setText(0, name)
|
||||
it.setForeground(0, QtGui.QBrush(color))
|
||||
|
||||
it.setIcon(0, get_icon(self.icons[op]))
|
||||
it.setCheckState(0, QtCore.Qt.Checked if active else QtCore.Qt.Unchecked)
|
||||
|
||||
if parent is None:
|
||||
self.addTopLevelItem(it)
|
||||
else:
|
||||
parent.addChild(it)
|
||||
|
||||
if children is not None:
|
||||
for c in children:
|
||||
self.add_function(**c, parent=it)
|
||||
|
||||
self.setCurrentIndex(self.indexFromItem(it, 0))
|
||||
|
||||
def sizeHint(self):
|
||||
w = super().sizeHint().width()
|
||||
return QtCore.QSize(w, 100)
|
||||
|
||||
def get_selected(self):
|
||||
try:
|
||||
it = self.selectedItems()[0]
|
||||
function_nr = it.data(0, QtCore.Qt.UserRole)
|
||||
idx = it.data(0, self.counterRole)
|
||||
|
||||
except IndexError:
|
||||
function_nr = None
|
||||
idx = None
|
||||
|
||||
return function_nr, idx
|
||||
|
||||
def get_functions(self, full: bool = True, pos: int = -1, return_pos: bool = False, parent=None):
|
||||
"""
|
||||
Create nested list of functions in tree. Parameters saved are idx (Index of function in list of all functions),
|
||||
cnt (counter of number to associate with functione values), ops (+, -, *, /), and maybe children.
|
||||
"""
|
||||
if parent is None:
|
||||
parent = self.invisibleRootItem()
|
||||
|
||||
funcs = []
|
||||
for i in range(parent.childCount()):
|
||||
pos += 1
|
||||
it = parent.child(i)
|
||||
|
||||
child = {
|
||||
'idx': it.data(0, QtCore.Qt.UserRole),
|
||||
'op': it.data(0, self.operatorRole),
|
||||
'pos': pos,
|
||||
'active': (it.checkState(0) == QtCore.Qt.Checked),
|
||||
'children': []
|
||||
}
|
||||
|
||||
if full:
|
||||
child['name'] = it.text(0)
|
||||
child['cnt'] = it.data(0, self.counterRole)
|
||||
child['color'] = it.foreground(0).color().getRgbF()
|
||||
|
||||
if it.childCount():
|
||||
child['children'], pos = self.get_functions(full=full, parent=it, pos=pos, return_pos=True)
|
||||
|
||||
funcs.append(child)
|
||||
|
||||
if return_pos:
|
||||
return funcs, pos
|
||||
else:
|
||||
return funcs
|
||||
|
||||
|
||||
class FitTableWidget(QtWidgets.QTableWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.horizontalHeader().hide()
|
||||
self.verticalHeader().hide()
|
||||
self.setColumnCount(2)
|
||||
self.setSelectionBehavior(QtWidgets.QTableWidget.SelectRows)
|
||||
self.horizontalHeader().setStretchLastSection(True)
|
||||
self.hideColumn(1)
|
||||
|
||||
def add_model(self, idx: str):
|
||||
model_count = 0
|
||||
for r in range(self.rowCount()):
|
||||
cb = self.cellWidget(r, 1)
|
||||
cb.addItem('Model ' + str(idx), userData=idx)
|
||||
model_count = cb.count()
|
||||
|
||||
if model_count > 2:
|
||||
if self.isColumnHidden(1):
|
||||
self.showColumn(1)
|
||||
self.resizeColumnToContents(0)
|
||||
self.setColumnWidth(1, self.columnWidth(0) - self.columnWidth(1))
|
||||
|
||||
def remove_model(self, idx: str):
|
||||
model_count = 0
|
||||
for r in range(self.rowCount()):
|
||||
cb = self.cellWidget(r, 1)
|
||||
if cb.currentData() == idx:
|
||||
cb.setCurrentIndex(0)
|
||||
cb.removeItem(cb.findData(idx))
|
||||
model_count = cb.count()
|
||||
|
||||
if model_count == 2:
|
||||
self.hideColumn(1)
|
||||
self.resizeColumnToContents(0)
|
||||
|
||||
def load(self, set_ids: list[str]):
|
||||
self.blockSignals(True)
|
||||
|
||||
while self.rowCount():
|
||||
self.removeRow(0)
|
||||
|
||||
self.setColumnCount(2)
|
||||
self.hideColumn(1)
|
||||
|
||||
for (sid, name) in set_ids:
|
||||
item = QtWidgets.QTableWidgetItem(name)
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
item.setData(QtCore.Qt.UserRole+1, sid)
|
||||
row = self.rowCount()
|
||||
self.setRowCount(row+1)
|
||||
self.setItem(row, 0, item)
|
||||
|
||||
item2 = QtWidgets.QTableWidgetItem('')
|
||||
self.setItem(row, 1, item2)
|
||||
cb = QtWidgets.QComboBox(parent=self)
|
||||
cb.addItem('Default')
|
||||
self.setCellWidget(row, 1, cb)
|
||||
|
||||
self.blockSignals(False)
|
||||
|
||||
def collect_data(self, default: str = None, include_name: bool = False) -> dict:
|
||||
data = {}
|
||||
|
||||
for i in range(self.rowCount()):
|
||||
item = self.item(i, 0)
|
||||
if item.checkState() == QtCore.Qt.Checked:
|
||||
mod = self.cellWidget(i, 1).currentData()
|
||||
if mod is None:
|
||||
mod = default
|
||||
|
||||
if include_name:
|
||||
arg = (item.data(QtCore.Qt.UserRole+1), item.text())
|
||||
else:
|
||||
arg = item.data(QtCore.Qt.UserRole+1)
|
||||
|
||||
if mod not in data:
|
||||
data[mod] = []
|
||||
data[mod].append(arg)
|
||||
|
||||
return data
|
||||
|
||||
def data_list(self, include_name: bool = True) -> list:
|
||||
ret_val = []
|
||||
for i in range(self.rowCount()):
|
||||
item = self.item(i, 0)
|
||||
if include_name:
|
||||
ret_val.append((item.data(QtCore.Qt.UserRole+1), item.text()))
|
||||
else:
|
||||
ret_val.append(item.data(QtCore.Qt.UserRole+1))
|
||||
|
||||
return ret_val
|
327
src/gui_qt/fit/fit_parameter.py
Normal file
327
src/gui_qt/fit/fit_parameter.py
Normal file
@ -0,0 +1,327 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from nmreval.utils.text import convert
|
||||
|
||||
from ..Qt import QtWidgets, QtCore, QtGui
|
||||
from .._py.fitfuncwidget import Ui_FormFit
|
||||
from ..lib.forms import SelectionWidget
|
||||
from .fit_forms import FitModelWidget
|
||||
|
||||
|
||||
class QFitParameterWidget(QtWidgets.QWidget, Ui_FormFit):
|
||||
value_requested = QtCore.pyqtSignal(int)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.func = None
|
||||
self.func_idx = None
|
||||
self.max_width = QtCore.QSize(0, 0)
|
||||
self.global_parameter = []
|
||||
self.data_parameter = []
|
||||
self.glob_values = None
|
||||
self.data_values = {}
|
||||
|
||||
self.scrollwidget.setLayout(QtWidgets.QVBoxLayout())
|
||||
self.scrollwidget2.setLayout(QtWidgets.QVBoxLayout())
|
||||
|
||||
def eventFilter(self, src: QtCore.QObject, evt: QtCore.QEvent):
|
||||
if isinstance(evt, QtGui.QKeyEvent):
|
||||
if (evt.key() == QtCore.Qt.Key_Right) and \
|
||||
(evt.modifiers() == QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier):
|
||||
self.change_single_parameter(src.value, sender=src)
|
||||
self.select_next_preview(1)
|
||||
|
||||
return True
|
||||
|
||||
elif (evt.key() == QtCore.Qt.Key_Left) and \
|
||||
(evt.modifiers() == QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier):
|
||||
self.change_single_parameter(src.value, sender=src)
|
||||
self.select_next_preview(-1)
|
||||
|
||||
return True
|
||||
|
||||
return super().eventFilter(src, evt)
|
||||
|
||||
def load(self, data):
|
||||
self.comboBox.blockSignals(True)
|
||||
while self.comboBox.count():
|
||||
self.comboBox.removeItem(0)
|
||||
|
||||
for sid, name in data:
|
||||
self.comboBox.addItem(name, userData=sid)
|
||||
self._make_parameter(sid)
|
||||
self.comboBox.blockSignals(False)
|
||||
self.change_data(0)
|
||||
|
||||
def set_function(self, func, idx):
|
||||
self.func = func
|
||||
self.func_idx = idx
|
||||
|
||||
self.glob_values = [1] * len(func.params)
|
||||
|
||||
for k, v in enumerate(func.params):
|
||||
name = convert(v)
|
||||
widgt = FitModelWidget(label=name, parent=self.scrollwidget)
|
||||
widgt.parameter_pos = k
|
||||
widgt.func_idx = idx
|
||||
try:
|
||||
widgt.set_bounds(*func.bounds[k], False)
|
||||
except (AttributeError, IndexError):
|
||||
pass
|
||||
|
||||
size = widgt.parametername.sizeHint()
|
||||
if self.max_width.width() < size.width():
|
||||
self.max_width = size
|
||||
|
||||
widgt.state_changed.connect(self.make_global)
|
||||
widgt.value_requested.connect(self.look_for_value)
|
||||
widgt.value_changed.connect(self.change_global_parameter)
|
||||
|
||||
self.global_parameter.append(widgt)
|
||||
self.scrollwidget.layout().addWidget(widgt)
|
||||
|
||||
widgt2 = ParameterSingleWidget(name=name, parent=self.scrollwidget2)
|
||||
widgt2.valueChanged.connect(self.change_single_parameter)
|
||||
widgt2.removeSingleValue.connect(self.change_single_parameter)
|
||||
widgt2.installEventFilter(self)
|
||||
self.scrollwidget2.layout().addWidget(widgt2)
|
||||
self.data_parameter.append(widgt2)
|
||||
|
||||
for w1, w2 in zip(self.global_parameter, self.data_parameter):
|
||||
w1.parametername.setFixedSize(self.max_width)
|
||||
w1.checkBox.setFixedSize(self.max_width)
|
||||
w2.label.setFixedSize(self.max_width)
|
||||
|
||||
if hasattr(func, 'choices') and func.choices is not None:
|
||||
cbox = func.choices
|
||||
for c in cbox:
|
||||
widgt = SelectionWidget(*c)
|
||||
widgt.selectionChanged.connect(self.change_global_choice)
|
||||
self.global_parameter.append(widgt)
|
||||
self.glob_values.append(widgt.value)
|
||||
self.scrollwidget.layout().addWidget(widgt)
|
||||
|
||||
widgt2 = SelectionWidget(*c)
|
||||
widgt2.selectionChanged.connect(self.change_single_choice)
|
||||
self.data_parameter.append(widgt2)
|
||||
self.scrollwidget2.layout().addWidget(widgt2)
|
||||
|
||||
for i in range(self.comboBox.count()):
|
||||
self._make_parameter(self.comboBox.itemData(i))
|
||||
|
||||
self.scrollwidget.layout().addStretch(1)
|
||||
self.scrollwidget2.layout().addStretch(1)
|
||||
|
||||
def set_links(self, parameter):
|
||||
for w in self.global_parameter:
|
||||
if isinstance(w, FitModelWidget):
|
||||
w.add_links(parameter)
|
||||
|
||||
@QtCore.pyqtSlot(str)
|
||||
def change_global_parameter(self, value: str, idx: int = None):
|
||||
if idx is None:
|
||||
idx = self.global_parameter.index(self.sender())
|
||||
|
||||
self.glob_values[idx] = float(value)
|
||||
if self.data_values[self.comboBox.currentData()][idx] is None:
|
||||
self.data_parameter[idx].blockSignals(True)
|
||||
self.data_parameter[idx].value = float(value)
|
||||
self.data_parameter[idx].blockSignals(False)
|
||||
|
||||
@QtCore.pyqtSlot(str, object)
|
||||
def change_global_choice(self, _, value):
|
||||
idx = self.global_parameter.index(self.sender())
|
||||
self.glob_values[idx] = value
|
||||
if self.data_values[self.comboBox.currentData()][idx] is None:
|
||||
self.data_parameter[idx].blockSignals(True)
|
||||
self.data_parameter[idx].value = value
|
||||
self.data_parameter[idx].blockSignals(False)
|
||||
|
||||
def change_single_parameter(self, value: float = None, sender=None):
|
||||
if sender is None:
|
||||
sender = self.sender()
|
||||
idx = self.data_parameter.index(sender)
|
||||
self.data_values[self.comboBox.currentData()][idx] = value
|
||||
# look for global parameter values if value is reset, ie None
|
||||
if value is None:
|
||||
self.change_data(self.comboBox.currentIndex())
|
||||
|
||||
def change_single_choice(self, _, value, sender=None):
|
||||
if sender is None:
|
||||
sender = self.sender()
|
||||
idx = self.data_parameter.index(sender)
|
||||
self.data_values[self.comboBox.currentData()][idx] = value
|
||||
|
||||
@QtCore.pyqtSlot(object)
|
||||
def look_for_value(self, sender):
|
||||
self.value_requested.emit(self.global_parameter.index(sender))
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def make_global(self):
|
||||
# disable single parameter if it is set global, enable if global is unset
|
||||
widget = self.sender()
|
||||
idx = self.global_parameter.index(widget)
|
||||
enable = (widget.global_checkbox.checkState() == QtCore.Qt.Unchecked) and (widget.is_linked is None)
|
||||
self.data_parameter[idx].setEnabled(enable)
|
||||
|
||||
def select_next_preview(self, direction):
|
||||
curr_idx = self.comboBox.currentIndex()
|
||||
next_idx = (curr_idx + direction) % self.comboBox.count()
|
||||
self.comboBox.setCurrentIndex(next_idx)
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_comboBox_currentIndexChanged')
|
||||
def change_data(self, idx: int):
|
||||
# new dataset is selected, look for locally set parameter else use global values
|
||||
sid = self.comboBox.itemData(idx)
|
||||
if sid not in self.data_values:
|
||||
self._make_parameter(sid)
|
||||
|
||||
for i, value in enumerate(self.data_values[sid]):
|
||||
w = self.data_parameter[i]
|
||||
w.blockSignals(True)
|
||||
if value is None:
|
||||
w.value = self.glob_values[i]
|
||||
else:
|
||||
w.value = value
|
||||
w.blockSignals(False)
|
||||
|
||||
def _make_parameter(self, sid):
|
||||
if sid not in self.data_values:
|
||||
self.data_values[sid] = [None] * len(self.data_parameter)
|
||||
|
||||
def get_parameter(self, use_func=None):
|
||||
bds = []
|
||||
is_global = []
|
||||
is_fixed = []
|
||||
globs = []
|
||||
is_linked = []
|
||||
|
||||
for g in self.global_parameter:
|
||||
if isinstance(g, FitModelWidget):
|
||||
p_i, bds_i, fixed_i, global_i, link_i = g.get_parameter()
|
||||
|
||||
globs.append(p_i)
|
||||
bds.append(bds_i)
|
||||
is_fixed.append(fixed_i)
|
||||
is_global.append(global_i)
|
||||
is_linked.append(link_i)
|
||||
|
||||
lb, ub = list(zip(*bds))
|
||||
|
||||
data_parameter = {}
|
||||
if use_func is None:
|
||||
use_func = list(self.data_values.keys())
|
||||
|
||||
global_p = None
|
||||
for sid, parameter in self.data_values.items():
|
||||
if sid not in use_func:
|
||||
continue
|
||||
|
||||
kw_p = {}
|
||||
p = []
|
||||
if global_p is None:
|
||||
global_p = {'p': [], 'idx': [], 'var': [], 'ub': [], 'lb': []}
|
||||
|
||||
for i, (p_i, g) in enumerate(zip(parameter, self.global_parameter)):
|
||||
if isinstance(g, FitModelWidget):
|
||||
if (p_i is None) or is_global[i]:
|
||||
p.append(globs[i])
|
||||
if is_global[i]:
|
||||
if i not in global_p['idx']:
|
||||
global_p['p'].append(globs[i])
|
||||
global_p['idx'].append(i)
|
||||
global_p['var'].append(is_fixed[i])
|
||||
global_p['ub'].append(ub[i])
|
||||
global_p['lb'].append(lb[i])
|
||||
else:
|
||||
p.append(p_i)
|
||||
|
||||
try:
|
||||
if p[i] > ub[i]:
|
||||
raise ValueError(f'Parameter {g.name} is outside bounds ({lb[i]}, {ub[i]})')
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
if p[i] < lb[i]:
|
||||
raise ValueError(f'Parameter {g.name} is outside bounds ({lb[i]}, {ub[i]})')
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
else:
|
||||
if p_i is None:
|
||||
kw_p.update(g.value)
|
||||
elif isinstance(p_i, dict):
|
||||
kw_p.update(p_i)
|
||||
else:
|
||||
kw_p[g.argname] = p_i
|
||||
|
||||
data_parameter[sid] = (p, kw_p)
|
||||
|
||||
return data_parameter, lb, ub, is_fixed, global_p, is_linked
|
||||
|
||||
def set_parameter(self, set_id: str | None, parameter: list[float]) -> int:
|
||||
if set_id is None:
|
||||
for val, g in zip(parameter, self.global_parameter):
|
||||
if isinstance(g, SelectionWidget):
|
||||
continue
|
||||
g.set_parameter(val)
|
||||
|
||||
else:
|
||||
new_param = self.data_values[set_id]
|
||||
min_len = min(len(new_param), len(parameter))
|
||||
for i in range(min_len):
|
||||
new_param[i] = parameter[i]
|
||||
|
||||
self.change_data(self.comboBox.currentIndex())
|
||||
|
||||
return len(self.global_parameter)
|
||||
|
||||
|
||||
class ParameterSingleWidget(QtWidgets.QWidget):
|
||||
valueChanged = QtCore.pyqtSignal(object)
|
||||
removeSingleValue = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, name: str, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self._init_ui()
|
||||
|
||||
self._name = name
|
||||
self.label.setText(convert(name))
|
||||
|
||||
self.value_line.setValidator(QtGui.QDoubleValidator())
|
||||
self.value_line.textChanged.connect(lambda: self.valueChanged.emit(self.value) if self.value is not None else 0)
|
||||
self.reset_button.clicked.connect(lambda x: self.removeSingleValue.emit())
|
||||
|
||||
def _init_ui(self):
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(2, 2, 2, 2)
|
||||
layout.setSpacing(2)
|
||||
|
||||
self.label = QtWidgets.QLabel(self)
|
||||
layout.addWidget(self.label)
|
||||
|
||||
layout.addSpacerItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum))
|
||||
|
||||
self.value_line = QtWidgets.QLineEdit(self)
|
||||
layout.addWidget(self.value_line)
|
||||
|
||||
self.reset_button = QtWidgets.QToolButton(self)
|
||||
self.reset_button.setText('Use global')
|
||||
layout.addWidget(self.reset_button)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
@property
|
||||
def value(self) -> float:
|
||||
try:
|
||||
return float(self.value_line.text().replace(',', '.'))
|
||||
except ValueError:
|
||||
return 0.0
|
||||
|
||||
@value.setter
|
||||
def value(self, val):
|
||||
self.value_line.setText(f'{float(val):.5g}')
|
213
src/gui_qt/fit/fitfunction.py
Normal file
213
src/gui_qt/fit/fitfunction.py
Normal file
@ -0,0 +1,213 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from itertools import cycle, count
|
||||
|
||||
from nmreval.configs import config_paths
|
||||
from nmreval import models
|
||||
from nmreval.lib.importer import find_models
|
||||
from nmreval.lib.colors import BaseColor, Tab10
|
||||
from nmreval.utils.text import convert
|
||||
|
||||
from ..lib import get_icon
|
||||
from .._py.fitfunctionwidget import Ui_Form
|
||||
from ..Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
class QFunctionWidget(QtWidgets.QWidget, Ui_Form):
|
||||
func_cnt = count()
|
||||
func_colors = cycle(Tab10)
|
||||
op_names = ['plus_icon', 'mal_icon', 'minus_icon', 'geteilt_icon']
|
||||
|
||||
newFunction = QtCore.pyqtSignal(int, int)
|
||||
treeChanged = QtCore.pyqtSignal()
|
||||
itemRemoved = QtCore.pyqtSignal(int)
|
||||
showFunction = QtCore.pyqtSignal(int)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.setupUi(self)
|
||||
|
||||
self._types = []
|
||||
self.functions = find_models(models)
|
||||
try:
|
||||
self.functions += find_models(config_paths() / 'usermodels.py')
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
for m in self.functions:
|
||||
try:
|
||||
m.type
|
||||
except AttributeError:
|
||||
m.type = 'Other'
|
||||
|
||||
if m.type not in self._types:
|
||||
self._types.append(m.type)
|
||||
|
||||
self.typecomboBox.addItems(sorted(self._types))
|
||||
|
||||
self.functree.treeChanged.connect(lambda: self.treeChanged.emit())
|
||||
self.functree.itemRemoved.connect(self.remove_function)
|
||||
|
||||
self.iscomplex = False
|
||||
self.complex_widget.hide()
|
||||
|
||||
for i, op_icon in enumerate(self.op_names):
|
||||
self.operator_combobox.setItemIcon(i, get_icon(op_icon))
|
||||
|
||||
def __len__(self) -> int:
|
||||
num = 0
|
||||
iterator = QtWidgets.QTreeWidgetItemIterator(self.functree)
|
||||
while iterator.value():
|
||||
num += 1
|
||||
iterator += 1
|
||||
|
||||
return num
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_typecomboBox_currentIndexChanged')
|
||||
def change_group(self, idx: int):
|
||||
"""
|
||||
Change items in fitcombobox to new entries
|
||||
"""
|
||||
self.fitcomboBox.blockSignals(True)
|
||||
while self.fitcomboBox.count():
|
||||
self.fitcomboBox.removeItem(0)
|
||||
|
||||
selected_type = self.typecomboBox.itemText(idx)
|
||||
for m in self.functions:
|
||||
if m.type == selected_type:
|
||||
self.fitcomboBox.addItem(m.name, userData=self.functions.index(m))
|
||||
self.fitcomboBox.blockSignals(False)
|
||||
self.on_fitcomboBox_currentIndexChanged(0)
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_fitcomboBox_currentIndexChanged')
|
||||
def change_function(self, idx: int):
|
||||
"""
|
||||
Display new equation on changing function
|
||||
"""
|
||||
index = self.fitcomboBox.itemData(idx)
|
||||
if self.functions:
|
||||
fitfunc = self.functions[index]
|
||||
try:
|
||||
self.fitequation.setText(convert(fitfunc.equation))
|
||||
except AttributeError:
|
||||
self.fitequation.setText('')
|
||||
|
||||
@QtCore.pyqtSlot(name='on_use_function_button_clicked')
|
||||
def new_function(self):
|
||||
idx = self.fitcomboBox.itemData(self.fitcomboBox.currentIndex())
|
||||
cnt = next(self.func_cnt)
|
||||
op = self.operator_combobox.currentIndex()
|
||||
name = self.functions[idx].name
|
||||
col = next(self.func_colors)
|
||||
|
||||
self.newFunction.emit(idx, cnt)
|
||||
|
||||
self.add_function(idx, cnt, op, name, col)
|
||||
|
||||
def add_function(self, idx: int, cnt: int, op: int,
|
||||
name: str, color: str | tuple[float, float, float] | BaseColor, **kwargs):
|
||||
"""
|
||||
Add function to tree and dictionary of functions.
|
||||
"""
|
||||
if isinstance(color, BaseColor):
|
||||
qcolor = QtGui.QColor.fromRgbF(*color.rgb(normed=True))
|
||||
elif isinstance(color, tuple):
|
||||
qcolor = QtGui.QColor.fromRgbF(*color)
|
||||
else:
|
||||
qcolor = QtGui.QColor(color)
|
||||
self.functree.add_function(idx, cnt, op, name, qcolor, **kwargs)
|
||||
|
||||
f = self.functions[idx]
|
||||
if hasattr(f, 'iscomplex') and f.iscomplex:
|
||||
self.iscomplex = True
|
||||
self.complex_widget.show()
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def remove_function(self, idx: int):
|
||||
iterator = QtWidgets.QTreeWidgetItemIterator(self.functree)
|
||||
self.iscomplex = False
|
||||
while iterator.value():
|
||||
item = iterator.value()
|
||||
f = self.functions[item.data(0, QtCore.Qt.UserRole)]
|
||||
if hasattr(f, 'iscomplex') and f.iscomplex:
|
||||
self.iscomplex = True
|
||||
break
|
||||
|
||||
iterator += 1
|
||||
self.complex_widget.setVisible(self.iscomplex)
|
||||
|
||||
self.itemRemoved.emit(idx)
|
||||
|
||||
@QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem, int, name='on_functree_itemClicked')
|
||||
def show_parameter(self, item: QtWidgets.QTreeWidgetItem, _: int):
|
||||
self.showFunction.emit(item.data(0, self.functree.counterRole))
|
||||
|
||||
def get_selected(self):
|
||||
function_nr, idx = self.functree.get_selected()
|
||||
|
||||
if function_nr is not None:
|
||||
return self.functions[function_nr], idx
|
||||
else:
|
||||
return None, None
|
||||
|
||||
def get_functions(self, full: bool = True, clsname=False, include_all: bool = True):
|
||||
"""
|
||||
Create nested list of functions in tree. Parameters saved are idx (Index of function in list of all functions),
|
||||
cnt (counter of number to associate with functione values), ops (+, -, *, /), and maybe children.
|
||||
"""
|
||||
|
||||
used_functions = self.functree.get_functions(full=full)
|
||||
self._prepare_function_for_model(used_functions, full=full, clsname=clsname, include_all=include_all)
|
||||
|
||||
return used_functions
|
||||
|
||||
def _prepare_function_for_model(self, func_list: list[dict],
|
||||
full: bool = True, clsname: bool = False, include_all: bool = True):
|
||||
|
||||
for func_args in func_list:
|
||||
is_active = func_args.get('active')
|
||||
if (not is_active) and (not include_all):
|
||||
continue
|
||||
|
||||
if not clsname:
|
||||
func_args['func'] = self.functions[func_args['idx']]
|
||||
else:
|
||||
func_args['func'] = self.functions[func_args['idx']].name
|
||||
|
||||
if not full:
|
||||
func_args.pop('active')
|
||||
func_args.pop('idx')
|
||||
|
||||
if func_args['children']:
|
||||
self._prepare_function_for_model(func_args['children'],
|
||||
full=full, clsname=clsname,
|
||||
include_all=include_all)
|
||||
|
||||
def get_parameter_list(self):
|
||||
all_parameters = {}
|
||||
|
||||
iterator = QtWidgets.QTreeWidgetItemIterator(self.functree)
|
||||
while iterator.value():
|
||||
item = iterator.value()
|
||||
f = self.functions[item.data(0, QtCore.Qt.UserRole)]
|
||||
cnt = item.data(0, self.functree.counterRole)
|
||||
all_parameters[f'{f.name}_{cnt}'] = [(convert(pp, new='str'), (cnt, i)) for i, pp in enumerate(f.params)]
|
||||
|
||||
iterator += 1
|
||||
|
||||
return all_parameters
|
||||
|
||||
def get_complex_state(self):
|
||||
return self.complex_comboBox.currentIndex() if self.iscomplex else None
|
||||
|
||||
def set_complex_state(self, state):
|
||||
if state is not None:
|
||||
self.complex_comboBox.setCurrentIndex(state)
|
||||
|
||||
def clear(self):
|
||||
self.functree.blockSignals(True)
|
||||
self.functree.clear()
|
||||
self.functree.blockSignals(False)
|
||||
|
||||
self.complex_comboBox.setCurrentIndex(0)
|
||||
self.complex_widget.hide()
|
474
src/gui_qt/fit/fitwindow.py
Normal file
474
src/gui_qt/fit/fitwindow.py
Normal file
@ -0,0 +1,474 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import reduce
|
||||
from itertools import count, cycle
|
||||
from operator import add
|
||||
from string import ascii_letters
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
import numpy as np
|
||||
from pyqtgraph import mkPen
|
||||
|
||||
from nmreval.fit._meta import MultiModel, ModelFactory
|
||||
from nmreval.fit.result import FitResult
|
||||
|
||||
from .fit_forms import FitTableWidget
|
||||
from .fit_parameter import QFitParameterWidget
|
||||
from ..lib.pg_objects import PlotItem
|
||||
from ..Qt import QtGui, QtCore, QtWidgets
|
||||
from .._py.fitdialog import Ui_FitDialog
|
||||
|
||||
|
||||
class QFitDialog(QtWidgets.QWidget, Ui_FitDialog):
|
||||
func_cnt = count()
|
||||
model_cnt = cycle(ascii_letters)
|
||||
preview_num = 201
|
||||
|
||||
preview_emit = QtCore.pyqtSignal(dict, int, bool)
|
||||
fitStartSig = QtCore.pyqtSignal(dict, list, dict)
|
||||
abortFit = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, mgmt=None, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.parameters = {}
|
||||
self.preview_lines = []
|
||||
self._current_function = None
|
||||
self.param_widgets = {}
|
||||
self._management = mgmt
|
||||
|
||||
self._current_model = next(QFitDialog.model_cnt)
|
||||
self.show_combobox.setItemData(0, self._current_model, QtCore.Qt.UserRole)
|
||||
self.default_combobox.setItemData(0, self._current_model, QtCore.Qt.UserRole)
|
||||
|
||||
self.data_table = FitTableWidget(self.data_widget)
|
||||
self.data_widget.addWidget(self.data_table)
|
||||
self.data_widget.setText('Data')
|
||||
|
||||
self.models = {}
|
||||
self._func_list = {}
|
||||
self._complex = {}
|
||||
|
||||
self.connected_figure = ''
|
||||
|
||||
self.model_frame.hide()
|
||||
self.preview_button.hide()
|
||||
|
||||
self.abort_button.clicked.connect(lambda: self.abortFit.emit())
|
||||
|
||||
self.functionwidget.newFunction.connect(self.add_function)
|
||||
self.functionwidget.showFunction.connect(self.show_function_parameter)
|
||||
self.functionwidget.itemRemoved.connect(self.remove_function)
|
||||
|
||||
@QtCore.pyqtSlot(int, int)
|
||||
def add_function(self, function_idx: int, function_id: int):
|
||||
self.show_function_parameter(function_id, function_idx)
|
||||
self.newmodel_button.setEnabled(True)
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def remove_function(self, idx: int):
|
||||
"""
|
||||
Remove function and children from tree and dictionary
|
||||
"""
|
||||
w = self.param_widgets[idx]
|
||||
self.stackedWidget.removeWidget(w)
|
||||
w.deleteLater()
|
||||
del self.param_widgets[idx]
|
||||
|
||||
if len(self.functionwidget) == 0:
|
||||
# empty model
|
||||
self.newmodel_button.setEnabled(False)
|
||||
self.deletemodel_button.setEnabled(False)
|
||||
self._current_function = None
|
||||
|
||||
else:
|
||||
f_tree = self.functionwidget.functree
|
||||
func_idx = f_tree.currentItem().data(0, f_tree.counterRole)
|
||||
self._current_function = self.functionwidget.functions[func_idx]
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def show_function_parameter(self, function_id: int, function_idx: int = None):
|
||||
"""
|
||||
Display parameter associated with selected function.
|
||||
"""
|
||||
if function_id in self.param_widgets:
|
||||
dialog = self.param_widgets[function_id]
|
||||
|
||||
else:
|
||||
# create new widget for function
|
||||
if function_idx is not None:
|
||||
function = self.functionwidget.functions[function_idx]
|
||||
else:
|
||||
raise ValueError('No function index given')
|
||||
|
||||
if function is None:
|
||||
return
|
||||
|
||||
dialog = QFitParameterWidget()
|
||||
data_names = self.data_table.data_list(include_name=True)
|
||||
|
||||
dialog.set_function(function, function_idx)
|
||||
dialog.load(data_names)
|
||||
dialog.value_requested.connect(self.look_value)
|
||||
|
||||
self.stackedWidget.addWidget(dialog)
|
||||
self.param_widgets[function_id] = dialog
|
||||
|
||||
self.stackedWidget.setCurrentWidget(dialog)
|
||||
|
||||
# collect parameter names etc. to allow linkage
|
||||
self._func_list[self._current_model] = self.functionwidget.get_parameter_list()
|
||||
dialog.set_links(self._func_list)
|
||||
|
||||
# show same tab (general parameter/Data parameter)
|
||||
tab_idx = 0
|
||||
if self._current_function is not None:
|
||||
tab_idx = self.param_widgets[self._current_function].tabWidget.currentIndex()
|
||||
dialog.tabWidget.setCurrentIndex(tab_idx)
|
||||
|
||||
self._current_function = function_id
|
||||
|
||||
def look_value(self, idx: int):
|
||||
func_widget = self.param_widgets[self._current_function]
|
||||
set_ids = [func_widget.comboBox.itemData(i) for i in range(func_widget.comboBox.count())]
|
||||
for s in set_ids:
|
||||
func_widget.data_values[s][idx] = self._management[s].value
|
||||
func_widget.change_data(func_widget.comboBox.currentIndex())
|
||||
|
||||
def get_functions(self):
|
||||
""" update functions, parameters"""
|
||||
self.models[self._current_model] = self.functionwidget.get_functions()
|
||||
self._complex[self._current_model] = self.functionwidget.get_complex_state()
|
||||
self._func_list[self._current_model] = self.functionwidget.get_parameter_list()
|
||||
|
||||
def load(self, ids: List[str]):
|
||||
"""
|
||||
Add name and id of dataset to list.
|
||||
"""
|
||||
self.data_table.load(ids)
|
||||
if self.models:
|
||||
for m in self.models.keys():
|
||||
self.data_table.add_model(m)
|
||||
else:
|
||||
self.data_table.add_model(self._current_model)
|
||||
|
||||
for dialog in self.param_widgets.values():
|
||||
dialog.load(ids)
|
||||
|
||||
@QtCore.pyqtSlot(name='on_newmodel_button_clicked')
|
||||
def make_new_model(self):
|
||||
"""
|
||||
Save model with all its functions in dictionary and adjust gui.
|
||||
"""
|
||||
self.deletemodel_button.setEnabled(True)
|
||||
self.model_frame.show()
|
||||
idx = next(QFitDialog.model_cnt)
|
||||
|
||||
self.data_table.add_model(idx)
|
||||
|
||||
self.default_combobox.addItem('Model '+idx, userData=idx)
|
||||
self.show_combobox.addItem('Model '+idx, userData=idx)
|
||||
self.show_combobox.setItemData(self.show_combobox.count()-1, idx, QtCore.Qt.UserRole)
|
||||
self.show_combobox.setCurrentIndex(self.show_combobox.count()-1)
|
||||
|
||||
self._current_model = idx
|
||||
self.stackedWidget.setCurrentIndex(0)
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_show_combobox_currentIndexChanged')
|
||||
def change_model(self, idx: int):
|
||||
"""
|
||||
Save old model and display new model.
|
||||
"""
|
||||
self.get_functions()
|
||||
self.functionwidget.clear()
|
||||
|
||||
self._current_model = self.show_combobox.itemData(idx, QtCore.Qt.UserRole)
|
||||
if self._current_model in self.models and len(self.models[self._current_model]):
|
||||
for el in self.models[self._current_model]:
|
||||
self.functionwidget.add_function(**el)
|
||||
self.functionwidget.set_complex_state(self._complex[self._current_model])
|
||||
else:
|
||||
self.stackedWidget.setCurrentIndex(0)
|
||||
|
||||
@QtCore.pyqtSlot(name='on_deletemodel_button_clicked')
|
||||
def remove_model(self):
|
||||
model_id = self._current_model
|
||||
|
||||
self.show_combobox.removeItem(self.show_combobox.findData(model_id))
|
||||
self.default_combobox.removeItem(self.default_combobox.findData(model_id))
|
||||
|
||||
for m in self.models[model_id]:
|
||||
func_id = m['cnt']
|
||||
self.stackedWidget.removeWidget(self.param_widgets[func_id])
|
||||
|
||||
self.param_widgets.pop(func_id)
|
||||
|
||||
self._complex.pop(model_id)
|
||||
self._func_list.pop(model_id)
|
||||
self.models.pop(model_id)
|
||||
|
||||
self.data_table.remove_model(model_id)
|
||||
|
||||
if len(self.models) == 1:
|
||||
self.model_frame.hide()
|
||||
|
||||
def _prepare(self, model: list, function_use: list = None,
|
||||
parameter: dict = None, add_idx: bool = False, cnt: int = 0) -> Tuple[dict, int]:
|
||||
if parameter is None:
|
||||
parameter = {'parameter': {}, 'lb': (), 'ub': (), 'var': [],
|
||||
'glob': {'idx': [], 'p': [], 'var': [], 'lb': [], 'ub': []},
|
||||
'links': [], 'color': []}
|
||||
|
||||
for i, f in enumerate(model):
|
||||
if not f['active']:
|
||||
continue
|
||||
|
||||
try:
|
||||
p, lb, ub, var, glob, links = self.param_widgets[f['cnt']].get_parameter(function_use)
|
||||
except ValueError as e:
|
||||
_ = QtWidgets.QMessageBox().warning(self, 'Invalid value', str(e),
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
return {}, -1
|
||||
|
||||
p_len = len(parameter['lb'])
|
||||
|
||||
parameter['lb'] += lb
|
||||
parameter['ub'] += ub
|
||||
parameter['var'] += var
|
||||
parameter['links'] += links
|
||||
parameter['color'] += [f['color']]
|
||||
|
||||
for p_k, v_k in p.items():
|
||||
if add_idx:
|
||||
kw_k = {f'{k}_{cnt}': v for k, v in v_k[1].items()}
|
||||
else:
|
||||
kw_k = v_k[1]
|
||||
|
||||
if p_k in parameter['parameter']:
|
||||
params, kw = parameter['parameter'][p_k]
|
||||
params += v_k[0]
|
||||
kw.update(kw_k)
|
||||
else:
|
||||
parameter['parameter'][p_k] = (v_k[0], kw_k)
|
||||
|
||||
for g_k, g_v in glob.items():
|
||||
if g_k != 'idx':
|
||||
parameter['glob'][g_k] += g_v
|
||||
else:
|
||||
parameter['glob']['idx'] += [idx_i + p_len for idx_i in g_v]
|
||||
|
||||
if add_idx:
|
||||
cnt += 1
|
||||
|
||||
if f['children']:
|
||||
# recurse for children
|
||||
child_parameter, cnt = self._prepare(f['children'], parameter=parameter, add_idx=add_idx, cnt=cnt)
|
||||
|
||||
return parameter, cnt
|
||||
|
||||
@QtCore.pyqtSlot(name='on_fit_button_clicked')
|
||||
def start_fit(self):
|
||||
self.get_functions()
|
||||
|
||||
data = self.data_table.collect_data(default=self.default_combobox.currentData())
|
||||
|
||||
func_dict = {}
|
||||
for k, mod in self.models.items():
|
||||
func, order, param_len = ModelFactory.create_from_list(mod)
|
||||
|
||||
if func is None:
|
||||
continue
|
||||
|
||||
if k in data:
|
||||
parameter, _ = self._prepare(mod, function_use=data[k], add_idx=isinstance(func, MultiModel))
|
||||
if parameter is None:
|
||||
return
|
||||
|
||||
parameter['func'] = func
|
||||
parameter['order'] = order
|
||||
parameter['len'] = param_len
|
||||
parameter['complex'] = self._complex[k]
|
||||
if self._complex[k] is not None:
|
||||
for p_k, p_v in parameter['parameter'].items():
|
||||
p_v[1].update({'complex_mode': self._complex[k]})
|
||||
parameter['parameter'][p_k] = p_v[0], p_v[1]
|
||||
|
||||
func_dict[k] = parameter
|
||||
|
||||
replaceable = []
|
||||
for k, v in func_dict.items():
|
||||
for i, link_i in enumerate(v['links']):
|
||||
if link_i is None:
|
||||
continue
|
||||
|
||||
rep_model, rep_func, rep_pos = link_i
|
||||
try:
|
||||
f = func_dict[rep_model]
|
||||
except KeyError:
|
||||
QtWidgets.QMessageBox().warning(self, 'Invalid value',
|
||||
'Parameter cannot be linked: Model is unused',
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
return
|
||||
|
||||
try:
|
||||
f_idx = f['order'].index(rep_func)
|
||||
except ValueError:
|
||||
QtWidgets.QMessageBox().warning(self, 'Invalid value',
|
||||
'Parameter cannot be linked: '
|
||||
'Function is probably not checked or deleted',
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
return
|
||||
|
||||
repl_idx = sum(f['len'][:f_idx])+rep_pos
|
||||
if repl_idx not in f['glob']['idx']:
|
||||
_ = QtWidgets.QMessageBox().warning(self, 'Invalid value',
|
||||
'Parameter cannot be linked: '
|
||||
'Destination is not a global parameter.',
|
||||
QtWidgets.QMessageBox.Ok)
|
||||
return
|
||||
|
||||
replaceable.append((k, i, rep_model, repl_idx))
|
||||
|
||||
replace_value = None
|
||||
for p_k in f['parameter'].values():
|
||||
replace_value = p_k[0][repl_idx]
|
||||
break
|
||||
|
||||
if replace_value is not None:
|
||||
for p_k in v['parameter'].values():
|
||||
p_k[0][i] = replace_value
|
||||
|
||||
weight = ['None', 'y', 'y2', 'Deltay'][self.weight_combobox.currentIndex()]
|
||||
|
||||
fit_args = {'we': weight}
|
||||
|
||||
if func_dict:
|
||||
self.fitStartSig.emit(func_dict, replaceable, fit_args)
|
||||
|
||||
return func_dict
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_preview_checkbox_stateChanged')
|
||||
def show_preview(self, state: int):
|
||||
if state:
|
||||
self.preview_button.show()
|
||||
self.preview_checkbox.setText('')
|
||||
|
||||
self._prepare_preview()
|
||||
|
||||
else:
|
||||
self.preview_emit.emit({}, -1, False)
|
||||
self.preview_lines = []
|
||||
self.preview_button.hide()
|
||||
self.preview_checkbox.setText('Preview')
|
||||
|
||||
@QtCore.pyqtSlot(name='on_preview_button_clicked')
|
||||
def _prepare_preview(self):
|
||||
self.get_functions()
|
||||
|
||||
default_model = self.default_combobox.currentData()
|
||||
data = self.data_table.collect_data(default=default_model)
|
||||
|
||||
func_dict = {}
|
||||
for k, mod in self.models.items():
|
||||
func, order, param_len = ModelFactory.create_from_list(mod)
|
||||
multiple_funcs = isinstance(func, MultiModel)
|
||||
|
||||
if k in data:
|
||||
parameter, _ = self._prepare(mod, function_use=data[k], add_idx=multiple_funcs)
|
||||
parameter['func'] = func
|
||||
parameter['order'] = order
|
||||
parameter['len'] = param_len
|
||||
|
||||
func_dict[k] = parameter
|
||||
|
||||
for v in func_dict.values():
|
||||
for i, link_i in enumerate(v['links']):
|
||||
if link_i is None:
|
||||
continue
|
||||
|
||||
rep_model, rep_func, rep_pos = link_i
|
||||
f = func_dict[rep_model]
|
||||
f_idx = f['order'].index(rep_func)
|
||||
repl_idx = sum(f['len'][:f_idx]) + rep_pos
|
||||
|
||||
replace_value = None
|
||||
for p_k in f['parameter'].values():
|
||||
replace_value = p_k[0][repl_idx]
|
||||
break
|
||||
|
||||
if replace_value is not None:
|
||||
for p_k in v['parameter'].values():
|
||||
p_k[0][i] = replace_value
|
||||
|
||||
self.preview_emit.emit(func_dict, QFitDialog.preview_num, True)
|
||||
|
||||
def make_previews(self, x, models_parameters: dict):
|
||||
self.preview_lines = []
|
||||
|
||||
for k, model in models_parameters.items():
|
||||
f = model['func']
|
||||
is_complex = self._complex[k]
|
||||
|
||||
parameters = model['parameter']
|
||||
color = model['color']
|
||||
|
||||
for p, kwargs in parameters.values():
|
||||
if is_complex is not None:
|
||||
y = f.func(x, *p, complex_mode=is_complex, **kwargs)
|
||||
if np.iscomplexobj(y):
|
||||
self.preview_lines.append(PlotItem(x=x, y=y.real, pen=mkPen(width=3)))
|
||||
self.preview_lines.append(PlotItem(x=x, y=y.imag, pen=mkPen(width=3)))
|
||||
else:
|
||||
self.preview_lines.append(PlotItem(x=x, y=y, pen=mkPen(width=3)))
|
||||
else:
|
||||
y = f.func(x, *p, **kwargs)
|
||||
self.preview_lines.append(PlotItem(x=x, y=y, pen=mkPen(width=3)))
|
||||
|
||||
if isinstance(f, MultiModel):
|
||||
sub_kwargs = kwargs.copy()
|
||||
if is_complex is not None:
|
||||
sub_kwargs.update({'complex_mode': is_complex})
|
||||
|
||||
for i, s in enumerate(f.subs(x, *p, **sub_kwargs)):
|
||||
pen_i = mkPen(QtGui.QColor.fromRgbF(*color[i]))
|
||||
if np.iscomplexobj(s):
|
||||
self.preview_lines.append(PlotItem(x=x, y=s.real, pen=pen_i))
|
||||
self.preview_lines.append(PlotItem(x=x, y=s.imag, pen=pen_i))
|
||||
else:
|
||||
self.preview_lines.append(PlotItem(x=x, y=s, pen=pen_i))
|
||||
|
||||
return self.preview_lines
|
||||
|
||||
def set_parameter(self, parameter: Dict[str, FitResult]):
|
||||
# which data uses which model
|
||||
data = self.data_table.collect_data(default=self.default_combobox.currentData())
|
||||
|
||||
glob_fit_parameter = []
|
||||
|
||||
for fitted_model, fitted_data in data.items():
|
||||
for fit_id, fit_curve in parameter.items():
|
||||
if fit_id in fitted_data:
|
||||
fit_parameter = list(fit_curve.parameter.values())
|
||||
glob_fit_parameter.append(fit_parameter)
|
||||
|
||||
self.set_parameter_iter(fit_id, [p.value for p in fit_parameter], self.models[fitted_model])
|
||||
|
||||
mean_parameter = [reduce(add, p, 0)/len(p) for p in zip(*glob_fit_parameter)]
|
||||
|
||||
self.set_parameter_iter(None, mean_parameter, self.models[fitted_model])
|
||||
|
||||
def set_parameter_iter(self, fit_id: str | None, param: List[float], functions: List, cnt: int = 0):
|
||||
for model_p in functions:
|
||||
if model_p['active']:
|
||||
cnt += self.param_widgets[model_p['cnt']].set_parameter(fit_id, param[cnt:])
|
||||
if model_p['children']:
|
||||
cnt += self.set_parameter_iter(fit_id, param, model_p['children'], cnt=cnt)
|
||||
|
||||
return cnt
|
||||
|
||||
def closeEvent(self, evt: QtGui.QCloseEvent):
|
||||
self.preview_emit.emit({}, -1, False)
|
||||
self.preview_lines = []
|
||||
|
||||
super().closeEvent(evt)
|
449
src/gui_qt/fit/function_creation_dialog.py
Normal file
449
src/gui_qt/fit/function_creation_dialog.py
Normal file
@ -0,0 +1,449 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import numbers
|
||||
import textwrap
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
from gui_qt.Qt import QtCore, QtWidgets, QtGui
|
||||
from gui_qt._py.fitcreationdialog import Ui_Dialog
|
||||
from gui_qt.lib.namespace import QNamespaceWidget
|
||||
|
||||
__all__ = ['QUserFitCreator']
|
||||
|
||||
validator = QtGui.QRegExpValidator(QtCore.QRegExp('[A-Za-z]\S*'))
|
||||
|
||||
|
||||
class QUserFitCreator(QtWidgets.QDialog, Ui_Dialog):
|
||||
classCreated = QtCore.pyqtSignal(object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self.description_widget = DescWidget(self)
|
||||
self.args_widget = ArgWidget(self)
|
||||
self.kwargs_widget = KwargsWidget(self)
|
||||
self.kwargs_widget.Changed.connect(self.update_function)
|
||||
self.namespace_widget = QNamespaceWidget(self)
|
||||
self.namespace_widget.make_namespace()
|
||||
self.namespace_widget.sendKey.connect(self.namespace_made)
|
||||
|
||||
for b, w in [(self.description_box, self.description_widget), (self.args_box, self.args_widget),
|
||||
(self.kwargs_box, self.kwargs_widget), (self.namespace_box, self.namespace_widget)]:
|
||||
b.layout().addWidget(w)
|
||||
try:
|
||||
w.Changed.connect(self.update_function)
|
||||
except AttributeError:
|
||||
pass
|
||||
b.layout().addStretch()
|
||||
|
||||
self._imports = set()
|
||||
|
||||
self.update_function()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self
|
||||
|
||||
def update_function(self):
|
||||
try:
|
||||
var = self.args_widget.get_parameter()
|
||||
var += self.kwargs_widget.get_parameter()
|
||||
|
||||
k = ''
|
||||
for imps in self._imports:
|
||||
if len(imps) == 2:
|
||||
k += f'from {imps[0]} import {imps[1]}\n'
|
||||
elif imps[0] == 'numpy':
|
||||
k += 'import numpy as np\n'
|
||||
|
||||
if len(self._imports):
|
||||
k += '\n\n'
|
||||
|
||||
k += self.description_widget.get_strings()
|
||||
k += self.args_widget.get_strings()
|
||||
k += self.kwargs_widget.get_strings()
|
||||
|
||||
k += '\n @staticmethod\n'
|
||||
k += f" def func(x, {', '.join(var)}):\n"
|
||||
|
||||
self.plainTextEdit.setPlainText(k)
|
||||
except Exception as e:
|
||||
QtWidgets.QMessageBox.warning(self, 'Failure', f'Error found: {e.args[0]}')
|
||||
|
||||
def change_visibility(self):
|
||||
sender = self.sender()
|
||||
|
||||
for box in (self.description_box, self.args_box, self.kwargs_box, self.namespace_box):
|
||||
box.blockSignals(True)
|
||||
box.setExpansion(sender == box)
|
||||
box.blockSignals(False)
|
||||
|
||||
def namespace_made(self, invalue: str):
|
||||
ns = self.namespace_widget.namespace.namespace
|
||||
func_value = ns[invalue][0]
|
||||
ret_func = ''
|
||||
|
||||
if func_value is None:
|
||||
ret_func = invalue
|
||||
|
||||
elif isinstance(func_value, numbers.Number):
|
||||
ret_func = func_value
|
||||
|
||||
elif isinstance(func_value, np.ufunc):
|
||||
self._imports.add(('numpy',))
|
||||
ret_func = 'np.'+func_value.__name__ + '(x)'
|
||||
|
||||
else:
|
||||
f_string = ns[invalue][-1]
|
||||
args = f_string[f_string.find('('):]
|
||||
if inspect.ismethod(func_value):
|
||||
ret_func = func_value.__self__.__name__ + '.func'+args
|
||||
elif hasattr(func_value, '__qualname__'):
|
||||
ret_func = func_value.__qualname__.split('.')[0]
|
||||
self._imports.add((inspect.getmodule(func_value).__name__, ret_func))
|
||||
|
||||
self.plainTextEdit.insertPlainText(ret_func)
|
||||
self.update_function()
|
||||
|
||||
|
||||
class KwargsWidget(QtWidgets.QWidget):
|
||||
Changed = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self._num_kwargs = 0
|
||||
|
||||
self._setup_ui()
|
||||
|
||||
def _setup_ui(self):
|
||||
layout = QtWidgets.QGridLayout()
|
||||
layout.setContentsMargins(3, 3, 3, 3)
|
||||
layout.setHorizontalSpacing(3)
|
||||
|
||||
self.use_nuclei = QtWidgets.QCheckBox('Add gyromagnetic ratio', self)
|
||||
self.use_nuclei.stateChanged.connect(lambda x: self.Changed.emit())
|
||||
layout.addWidget(self.use_nuclei, 0, 0, 1, 3)
|
||||
|
||||
self.choices = QtWidgets.QTabWidget(self)
|
||||
layout.addWidget(self.choices, 1, 0, 1, 3)
|
||||
|
||||
self.add_choice_button = QtWidgets.QPushButton('Add choice', self)
|
||||
self.add_choice_button.clicked.connect(self.add_choice)
|
||||
layout.addWidget(self.add_choice_button, 2, 0, 1, 1)
|
||||
|
||||
self.rem_choice_button = QtWidgets.QPushButton('Remove choice', self)
|
||||
self.rem_choice_button.clicked.connect(self.remove_choice)
|
||||
layout.addWidget(self.rem_choice_button, 2, 1, 1, 1)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def add_choice(self):
|
||||
cnt = self._num_kwargs
|
||||
c = ChoiceWidget(cnt, self)
|
||||
c.Changed.connect(self.update_choice)
|
||||
self.choices.addTab(c, c.name_line.text())
|
||||
|
||||
self._num_kwargs += 1
|
||||
|
||||
self.choices.setCurrentIndex(cnt)
|
||||
self.Changed.emit()
|
||||
|
||||
def remove_choice(self):
|
||||
cnt = self.choices.currentIndex()
|
||||
self.choices.removeTab(cnt)
|
||||
self.Changed.emit()
|
||||
|
||||
def update_choice(self):
|
||||
idx = self.choices.currentIndex()
|
||||
self.choices.setTabText(idx, self.sender().name_line.text())
|
||||
self.Changed.emit()
|
||||
|
||||
def get_parameter(self):
|
||||
if self.use_nuclei.isChecked():
|
||||
var = ['nucleus=2.67522128e7']
|
||||
else:
|
||||
var = []
|
||||
var += [self.choices.widget(idx).get_parameter() for idx in range(self.choices.count())]
|
||||
|
||||
return var
|
||||
|
||||
def get_strings(self) -> str:
|
||||
kwargs = []
|
||||
if self.use_nuclei.isChecked():
|
||||
kwargs.append("(r'\gamma', 'nucleus', gamma)")
|
||||
|
||||
for i in range(self.choices.count()):
|
||||
kwargs.append(self.choices.widget(i).get_strings())
|
||||
if kwargs:
|
||||
return f" choices = {', '.join(kwargs)}\n"
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
class ChoiceWidget(QtWidgets.QWidget):
|
||||
Changed = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, idx: int, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self._setup_ui()
|
||||
|
||||
self.name_line.setText('choice' + str(idx))
|
||||
self.add_option()
|
||||
|
||||
def _setup_ui(self):
|
||||
layout = QtWidgets.QGridLayout()
|
||||
layout.setContentsMargins(3, 3, 3, 3)
|
||||
layout.setHorizontalSpacing(3)
|
||||
|
||||
self.name_label = QtWidgets.QLabel('Name', self)
|
||||
layout.addWidget(self.name_label, 0, 0, 1, 1)
|
||||
self.name_line = QtWidgets.QLineEdit(self)
|
||||
self.name_line.textChanged.connect(lambda x: self.Changed.emit())
|
||||
layout.addWidget(self.name_line, 0, 1, 1, 1)
|
||||
|
||||
self.disp_label = QtWidgets.QLabel('Disp. name', self)
|
||||
layout.addWidget(self.disp_label, 1, 0, 1, 1)
|
||||
self.display_line = QtWidgets.QLineEdit(self)
|
||||
self.display_line.textChanged.connect(lambda x: self.Changed.emit())
|
||||
layout.addWidget(self.display_line, 1, 1, 1, 1)
|
||||
|
||||
self.add_button = QtWidgets.QPushButton('Add option', self)
|
||||
self.add_button.clicked.connect(self.add_option)
|
||||
layout.addWidget(self.add_button, 2, 0, 1, 2)
|
||||
|
||||
self.remove_button = QtWidgets.QPushButton('Remove option', self)
|
||||
self.remove_button.clicked.connect(self.remove_option)
|
||||
layout.addWidget(self.remove_button, 3, 0, 1, 2)
|
||||
|
||||
self.table = QtWidgets.QTableWidget(self)
|
||||
self.table.setColumnCount(3)
|
||||
self.table.setHorizontalHeaderLabels(['Name', 'Value', 'Type'])
|
||||
self.table.itemChanged.connect(lambda x: self.Changed.emit())
|
||||
layout.addWidget(self.table, 0, 2, 4, 1)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def add_option(self):
|
||||
self.table.blockSignals(True)
|
||||
row = self.table.rowCount()
|
||||
self.table.setRowCount(row+1)
|
||||
|
||||
self.table.setItem(row, 0, QtWidgets.QTableWidgetItem('opt' + str(row)))
|
||||
lineedit = QtWidgets.QLineEdit()
|
||||
lineedit.setValidator(validator)
|
||||
lineedit.setFrame(False)
|
||||
lineedit.setText('opt'+str(row))
|
||||
lineedit.textChanged.connect(lambda x: self.Changed.emit())
|
||||
self.table.setCellWidget(row, 0, lineedit)
|
||||
self.table.setItem(row, 1, QtWidgets.QTableWidgetItem('None'))
|
||||
|
||||
self.table.setItem(row, 2, QtWidgets.QTableWidgetItem(''))
|
||||
cb = QtWidgets.QComboBox()
|
||||
cb.addItems(['None', 'str', 'float', 'int', 'bool'])
|
||||
cb.currentIndexChanged.connect(lambda x: self.Changed.emit())
|
||||
self.table.setCellWidget(row, 2, cb)
|
||||
|
||||
self.table.blockSignals(False)
|
||||
|
||||
self.Changed.emit()
|
||||
|
||||
def remove_option(self):
|
||||
if self.table.rowCount() > 1:
|
||||
self.table.blockSignals(True)
|
||||
self.table.removeRow(self.table.currentRow())
|
||||
self.table.blockSignals(False)
|
||||
self.Changed.emit()
|
||||
|
||||
def get_parameter(self) -> str:
|
||||
return f'{self.name_line.text()}={self._make_value(0)!r}'
|
||||
|
||||
def get_strings(self) -> str:
|
||||
opts = []
|
||||
for i in range(self.table.rowCount()):
|
||||
name = self.table.item(i, 0).text()
|
||||
val = self._make_value(i)
|
||||
opts.append(f'{name!r}: {val!r}')
|
||||
|
||||
opts = f"{{{', '.join(opts)}}}"
|
||||
|
||||
disp = self.display_line.text()
|
||||
name = self.name_line.text()
|
||||
if disp == '':
|
||||
ret_val = '(' + ', '.join([repr(name), repr(name), opts]) + ')'
|
||||
else:
|
||||
ret_val = '(' + ', '.join([repr(name), repr(disp), opts]) + ')'
|
||||
|
||||
return ret_val
|
||||
|
||||
def _make_value(self, i) -> Any:
|
||||
dtype = self.table.cellWidget(i, 2).currentIndex()
|
||||
val = self.table.item(i, 1).text()
|
||||
cast = [None, str, float, int, bool]
|
||||
if dtype == 0:
|
||||
val = None
|
||||
else:
|
||||
try:
|
||||
val = cast[dtype](val)
|
||||
except:
|
||||
raise ValueError(f'Invalid argument for {self.table.cellWidget(i, 0).text()}')
|
||||
|
||||
return val
|
||||
|
||||
|
||||
class ArgWidget(QtWidgets.QWidget):
|
||||
Changed = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self._setup_ui()
|
||||
|
||||
def _setup_ui(self):
|
||||
layout = QtWidgets.QGridLayout()
|
||||
layout.setContentsMargins(3, 3, 3, 3)
|
||||
layout.setHorizontalSpacing(3)
|
||||
|
||||
self.table = QtWidgets.QTableWidget(self)
|
||||
self.table.setColumnCount(4)
|
||||
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.table.setRowCount(0)
|
||||
self.table.setHorizontalHeaderLabels(['Variable', 'Disp. name', 'Lower bound', 'Upper bound'])
|
||||
self.table.itemChanged.connect(lambda x: self.Changed.emit())
|
||||
layout.addWidget(self.table, 0, 0, 1, 3)
|
||||
|
||||
self.add_button = QtWidgets.QPushButton('Add parameter', self)
|
||||
self.add_button.clicked.connect(self.add_variable)
|
||||
layout.addWidget(self.add_button, 1, 0, 1, 1)
|
||||
|
||||
self.rem_button = QtWidgets.QPushButton('Remove parameter', self)
|
||||
self.rem_button.clicked.connect(self.remove_variable)
|
||||
layout.addWidget(self.rem_button, 1, 1, 1, 1)
|
||||
|
||||
spacer = QtWidgets.QSpacerItem(0, 0)
|
||||
layout.addItem(spacer, 1, 2, 1, 1)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def add_variable(self):
|
||||
self.table.blockSignals(True)
|
||||
row = self.table.rowCount()
|
||||
self.table.setRowCount(row + 1)
|
||||
|
||||
self.table.setItem(row, 0, QtWidgets.QTableWidgetItem('p' + str(row)))
|
||||
# arguments cannot start with a number or have spaces
|
||||
lineedit = QtWidgets.QLineEdit()
|
||||
lineedit.setValidator(validator)
|
||||
lineedit.setFrame(False)
|
||||
lineedit.setText('p'+str(row))
|
||||
lineedit.textChanged.connect(lambda x: self.Changed.emit())
|
||||
self.table.setCellWidget(row, 0, lineedit)
|
||||
|
||||
self.table.setItem(row, 1, QtWidgets.QTableWidgetItem('p_{' + str(row) + '}'))
|
||||
self.table.setItem(row, 2, QtWidgets.QTableWidgetItem('--'))
|
||||
self.table.setItem(row, 3, QtWidgets.QTableWidgetItem('--'))
|
||||
self.table.blockSignals(False)
|
||||
|
||||
self.Changed.emit()
|
||||
|
||||
def remove_variable(self):
|
||||
self.table.blockSignals(True)
|
||||
self.table.removeRow(self.table.currentRow())
|
||||
self.table.blockSignals(False)
|
||||
|
||||
self.Changed.emit()
|
||||
|
||||
def get_parameter(self) -> list[str]:
|
||||
var = []
|
||||
for row in range(self.table.rowCount()):
|
||||
var.append(self.table.cellWidget(row, 0).text())
|
||||
|
||||
return var
|
||||
|
||||
def get_strings(self):
|
||||
args = []
|
||||
bnds = []
|
||||
for row in range(self.table.rowCount()):
|
||||
args.append(self.table.item(row, 1).text())
|
||||
lb = self.table.item(row, 2).text()
|
||||
lb = None if lb in ['--', 'None'] else float(lb)
|
||||
|
||||
ub = self.table.item(row, 3).text()
|
||||
ub = None if ub in ['--', 'None'] else float(ub)
|
||||
|
||||
if ub is not None and lb is not None:
|
||||
if not (lb < ub):
|
||||
raise ValueError('Some bounds are invalid')
|
||||
|
||||
bnds.append(f'({lb}, {ub})')
|
||||
|
||||
stringi = f' params = {args}\n'
|
||||
stringi += f" bounds = [{', '.join(bnds)}]\n"
|
||||
|
||||
return stringi
|
||||
|
||||
|
||||
class DescWidget(QtWidgets.QWidget):
|
||||
Changed = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self._setup_ui()
|
||||
|
||||
def _setup_ui(self):
|
||||
layout = QtWidgets.QGridLayout()
|
||||
layout.setContentsMargins(3, 3, 3, 3)
|
||||
layout.setSpacing(3)
|
||||
|
||||
self.klass_label = QtWidgets.QLabel('Class', self)
|
||||
layout.addWidget(self.klass_label, 0, 0, 1, 1)
|
||||
self.klass_lineedit = QtWidgets.QLineEdit(self)
|
||||
self.klass_lineedit.setValidator(validator)
|
||||
self.klass_lineedit.setText('UserClass')
|
||||
self.klass_lineedit.textChanged.connect(lambda x: self.Changed.emit())
|
||||
layout.addWidget(self.klass_lineedit, 0, 1, 1, 1)
|
||||
|
||||
self.name_label = QtWidgets.QLabel('Name', self)
|
||||
layout.addWidget(self.name_label, 1, 0, 1, 1)
|
||||
self.name_lineedit = QtWidgets.QLineEdit(self)
|
||||
self.name_lineedit.setText('Name of function')
|
||||
self.name_lineedit.textChanged.connect(lambda x: self.Changed.emit())
|
||||
layout.addWidget(self.name_lineedit, 1, 1, 1, 1)
|
||||
|
||||
self.group_label = QtWidgets.QLabel('Group', self)
|
||||
layout.addWidget(self.group_label, 2, 0, 1, 1)
|
||||
self.group_lineedit = QtWidgets.QLineEdit(self)
|
||||
self.group_lineedit.setText('User-defined')
|
||||
self.group_lineedit.textChanged.connect(lambda x: self.Changed.emit())
|
||||
layout.addWidget(self.group_lineedit, 2, 1, 1, 1)
|
||||
|
||||
self.eq_label = QtWidgets.QLabel('Disp. equation', self)
|
||||
layout.addWidget(self.eq_label, 3, 0, 1, 1)
|
||||
self.eq_lineedit = QtWidgets.QLineEdit(self)
|
||||
self.eq_lineedit.textChanged.connect(lambda x: self.Changed.emit())
|
||||
layout.addWidget(self.eq_lineedit, 3, 1, 1, 1)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def get_strings(self) -> str:
|
||||
if self.klass_lineedit.text() == '':
|
||||
raise ValueError('Class name is empty')
|
||||
stringi = f'class {self.klass_lineedit.text()}:\n' \
|
||||
f' name = {self.name_lineedit.text()!r}\n' \
|
||||
f' group = {self.group_lineedit.text()!r}\n' \
|
||||
f' equation = {self.eq_lineedit.text()!r}\n'
|
||||
|
||||
return stringi
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
app = QtWidgets.QApplication([])
|
||||
win = QUserFitCreator()
|
||||
win.show()
|
||||
|
||||
sys.exit(app.exec())
|
284
src/gui_qt/fit/result.py
Normal file
284
src/gui_qt/fit/result.py
Normal file
@ -0,0 +1,284 @@
|
||||
from math import isnan
|
||||
|
||||
from pyqtgraph import mkBrush
|
||||
|
||||
from nmreval.utils.text import convert
|
||||
|
||||
from ..lib.utils import RdBuCMap
|
||||
from ..Qt import QtWidgets, QtGui, QtCore
|
||||
from .._py.fitresult import Ui_Dialog
|
||||
from ..lib.pg_objects import PlotItem
|
||||
|
||||
|
||||
class QFitResult(QtWidgets.QDialog, Ui_Dialog):
|
||||
closed = QtCore.pyqtSignal(dict, list, str, bool, dict)
|
||||
redoFit = QtCore.pyqtSignal(dict)
|
||||
|
||||
def __init__(self, results: list, management, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
|
||||
self._management = management
|
||||
|
||||
self._prevs = {}
|
||||
self._models = {}
|
||||
|
||||
for (res, parts) in results:
|
||||
idx = res.idx
|
||||
data_k = management.data[idx]
|
||||
|
||||
if res.name not in self._models:
|
||||
self._models[res.name] = []
|
||||
|
||||
self._models[res.name].append(idx)
|
||||
|
||||
self._prevs[idx] = []
|
||||
for fit in data_k.get_fits():
|
||||
self._prevs[idx].append((fit.name, fit.statistics, fit.nobs-fit.nvar))
|
||||
|
||||
self._results = {res.idx: res for (res, _) in results}
|
||||
self._parts = {res.idx: parts for (res, parts) in results}
|
||||
self._opts = [(False, False) for _ in range(len(self._results))]
|
||||
|
||||
self.residplot = self.graphicsView.addPlot(row=0, col=0)
|
||||
self.resid_graph = PlotItem(x=[], y=[],
|
||||
symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(31, 119, 180)),
|
||||
pen=None)
|
||||
self.resid_graph_imag = PlotItem(x=[], y=[],
|
||||
symbol='s', symbolPen=None, symbolBrush=mkBrush(color=(255, 127, 14)),
|
||||
pen=None)
|
||||
self.residplot.addItem(self.resid_graph)
|
||||
self.residplot.addItem(self.resid_graph_imag)
|
||||
self.residplot.setLabel('left', 'Residual')
|
||||
|
||||
self.fitplot = self.graphicsView.addPlot(row=1, col=0)
|
||||
self.data_graph = PlotItem(x=[], y=[],
|
||||
symbol='o', symbolPen=None, symbolBrush=mkBrush(color=(31, 119, 180)),
|
||||
pen=None)
|
||||
self.data_graph_imag = PlotItem(x=[], y=[],
|
||||
symbol='s', symbolPen=None, symbolBrush=mkBrush(color=(255, 127, 14)),
|
||||
pen=None)
|
||||
self.fitplot.addItem(self.data_graph)
|
||||
self.fitplot.addItem(self.data_graph_imag)
|
||||
self.fitplot.setLabel('left', 'Function')
|
||||
|
||||
self.fit_graph = PlotItem(x=[], y=[])
|
||||
self.fit_graph_imag = PlotItem(x=[], y=[])
|
||||
self.fitplot.addItem(self.fit_graph)
|
||||
self.fitplot.addItem(self.fit_graph_imag)
|
||||
|
||||
self.cmap = RdBuCMap(vmin=-1, vmax=1)
|
||||
|
||||
self.sets_comboBox.blockSignals(True)
|
||||
for n in self._models.keys():
|
||||
self.sets_comboBox.addItem(n)
|
||||
self.sets_comboBox.blockSignals(False)
|
||||
|
||||
self.set_parameter(0)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
|
||||
self.param_tableWidget.itemClicked.connect(self.show_results)
|
||||
self.param_tableWidget.horizontalHeader().sectionClicked.connect(lambda i: self.show_results(None, idx=i))
|
||||
|
||||
self.graph_checkBox.stateChanged.connect(lambda x: self.graph_comboBox.setEnabled(x == QtCore.Qt.Unchecked))
|
||||
|
||||
self.logy_box.stateChanged.connect(lambda x: self.fitplot.setLogMode(y=bool(x)))
|
||||
self.logx_box.stateChanged.connect(lambda x: self.fitplot.setLogMode(x=bool(x)))
|
||||
self.residplot.setXLink(self.fitplot)
|
||||
|
||||
def add_graphs(self, graphs: list):
|
||||
self.graph_comboBox.clear()
|
||||
for (graph_id, graph_name) in graphs:
|
||||
self.graph_comboBox.addItem(graph_name, userData=graph_id)
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_sets_comboBox_currentIndexChanged')
|
||||
def set_parameter(self, idx: int):
|
||||
model_name = self.sets_comboBox.itemText(idx)
|
||||
sets = self._models[model_name]
|
||||
self.param_tableWidget.setColumnCount(len(sets))
|
||||
|
||||
r = self._results[sets[0]]
|
||||
self.param_tableWidget.setRowCount(len(r.parameter))
|
||||
|
||||
for i, pval in enumerate(r.parameter.values()):
|
||||
name = pval.full_name
|
||||
p_header = QtWidgets.QTableWidgetItem(convert(name, 'tex', 'html', brackets=False))
|
||||
self.param_tableWidget.setVerticalHeaderItem(i, p_header)
|
||||
|
||||
for i, set_id in enumerate(sets):
|
||||
data_i = self._management[set_id]
|
||||
header_item = QtWidgets.QTableWidgetItem(data_i.name)
|
||||
header_item.setData(QtCore.Qt.UserRole, set_id)
|
||||
self.param_tableWidget.setHorizontalHeaderItem(i, header_item)
|
||||
|
||||
res = self._results[set_id]
|
||||
for j, pvalue in enumerate(res.parameter.values()):
|
||||
item_text = f'{pvalue.value:.4g}'
|
||||
if pvalue.error is not None:
|
||||
item_text += f' \u00b1 {pvalue.error:.4g}'
|
||||
self.param_tableWidget.setItem(2*j+1, i, QtWidgets.QTableWidgetItem('-'))
|
||||
else:
|
||||
self.param_tableWidget.setItem(2*j+1, i, QtWidgets.QTableWidgetItem())
|
||||
item = QtWidgets.QTableWidgetItem(item_text)
|
||||
self.param_tableWidget.setItem(j, i, item)
|
||||
|
||||
self.param_tableWidget.resizeColumnsToContents()
|
||||
self.param_tableWidget.selectColumn(0)
|
||||
self.show_results(None, idx=0)
|
||||
|
||||
@QtCore.pyqtSlot(int, name='on_reject_fit_checkBox_stateChanged')
|
||||
@QtCore.pyqtSlot(int, name='on_del_prev_checkBox_stateChanged')
|
||||
def change_opts(self, _):
|
||||
idx = self.param_tableWidget.currentIndex().column()
|
||||
|
||||
self._opts[idx] = (self.reject_fit_checkBox.checkState() == QtCore.Qt.Checked,
|
||||
self.del_prev_checkBox.checkState() == QtCore.Qt.Checked)
|
||||
|
||||
def show_results(self, item, idx=None):
|
||||
if item is not None:
|
||||
idx = self.param_tableWidget.indexFromItem(item).column()
|
||||
|
||||
set_id = self.param_tableWidget.horizontalHeaderItem(idx).data(QtCore.Qt.UserRole)
|
||||
self.set_plot(set_id)
|
||||
self.set_correlation(set_id)
|
||||
self.set_statistics(set_id)
|
||||
|
||||
self.reject_fit_checkBox.blockSignals(True)
|
||||
self.reject_fit_checkBox.setChecked(self._opts[idx][0])
|
||||
self.reject_fit_checkBox.blockSignals(False)
|
||||
self.del_prev_checkBox.blockSignals(True)
|
||||
self.del_prev_checkBox.setChecked(self._opts[idx][1])
|
||||
self.del_prev_checkBox.blockSignals(False)
|
||||
|
||||
def set_plot(self, idx: str):
|
||||
res = self._results[idx]
|
||||
iscomplex = res.iscomplex
|
||||
|
||||
if iscomplex:
|
||||
self.data_graph.setData(x=res.x_data, y=res.y_data.real)
|
||||
self.data_graph_imag.setData(x=res.x_data, y=res.y_data.imag)
|
||||
self.fit_graph.setData(x=res.x, y=res.y.real)
|
||||
self.fit_graph_imag.setData(x=res.x, y=res.y.imag)
|
||||
self.resid_graph.setData(x=res.x_data, y=res.residual.real)
|
||||
self.resid_graph_imag.setData(x=res.x_data, y=res.residual.imag)
|
||||
else:
|
||||
self.resid_graph.setData(x=res.x_data, y=res.residual)
|
||||
self.resid_graph_imag.setData(x=[], y=[])
|
||||
self.data_graph.setData(x=res.x_data, y=res.y_data)
|
||||
self.data_graph_imag.setData(x=[], y=[])
|
||||
self.fit_graph.setData(x=res.x, y=res.y)
|
||||
self.fit_graph_imag.setData(x=[], y=[])
|
||||
|
||||
self.fitplot.setLogMode(x=res.islog)
|
||||
self.residplot.setLogMode(x=res.islog)
|
||||
|
||||
def set_correlation(self, idx: str):
|
||||
while self.corr_tableWidget.rowCount():
|
||||
self.corr_tableWidget.removeRow(0)
|
||||
|
||||
res = self._results[idx]
|
||||
c = res.correlation_list()
|
||||
for pi, pj, corr, pcorr in c:
|
||||
cnt = self.corr_tableWidget.rowCount()
|
||||
self.corr_tableWidget.insertRow(cnt)
|
||||
self.corr_tableWidget.setItem(cnt, 0, QtWidgets.QTableWidgetItem(convert(pi, old='tex', new='str')))
|
||||
self.corr_tableWidget.setItem(cnt, 1, QtWidgets.QTableWidgetItem(convert(pj, old='tex', new='str')))
|
||||
|
||||
for i, val in enumerate([corr, pcorr]):
|
||||
if isnan(val):
|
||||
val = 1000.
|
||||
val_item = QtWidgets.QTableWidgetItem(f'{val:.4g}')
|
||||
val_item.setBackground(self.cmap.color(val))
|
||||
if abs(val) > 0.75:
|
||||
val_item.setForeground(QtGui.QColor('white'))
|
||||
self.corr_tableWidget.setItem(cnt, i+2, val_item)
|
||||
|
||||
self.corr_tableWidget.resizeColumnsToContents()
|
||||
|
||||
def set_statistics(self, idx: str):
|
||||
while self.stats_tableWidget.rowCount():
|
||||
self.stats_tableWidget.removeRow(0)
|
||||
|
||||
res = self._results[idx]
|
||||
|
||||
self.stats_tableWidget.setColumnCount(1 + len(self._prevs[idx]))
|
||||
self.stats_tableWidget.setRowCount(len(res.statistics)+3)
|
||||
|
||||
it = QtWidgets.QTableWidgetItem(f'{res.dof}')
|
||||
it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable)
|
||||
self.stats_tableWidget.setVerticalHeaderItem(0, QtWidgets.QTableWidgetItem('DoF'))
|
||||
self.stats_tableWidget.setItem(0, 0, it)
|
||||
|
||||
for col, (name, _, dof) in enumerate(self._prevs[idx], start=1):
|
||||
self.stats_tableWidget.setHorizontalHeaderItem(0, QtWidgets.QTableWidgetItem(name))
|
||||
it = QtWidgets.QTableWidgetItem(f'{dof}')
|
||||
it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable)
|
||||
self.stats_tableWidget.setItem(0, col, it)
|
||||
|
||||
for row, (k, v) in enumerate(res.statistics.items(), start=1):
|
||||
self.stats_tableWidget.setVerticalHeaderItem(row, QtWidgets.QTableWidgetItem(k))
|
||||
it = QtWidgets.QTableWidgetItem(f'{v:.4f}')
|
||||
it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable)
|
||||
self.stats_tableWidget.setItem(row, 0, it)
|
||||
|
||||
best_idx = -1
|
||||
best_val = v
|
||||
for col, (_, stats, _) in enumerate(self._prevs[idx], start=1):
|
||||
if k in ['adj. R^2', 'R^2']:
|
||||
best_idx = col if best_val < stats[k] else max(0, best_idx)
|
||||
else:
|
||||
best_idx = col if best_val > stats[k] else max(0, best_idx)
|
||||
it = QtWidgets.QTableWidgetItem(f'{stats[k]:.4f}')
|
||||
it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable)
|
||||
self.stats_tableWidget.setItem(row, col, it)
|
||||
|
||||
if best_idx > -1:
|
||||
self.stats_tableWidget.item(row, best_idx).setBackground(QtGui.QColor('green'))
|
||||
self.stats_tableWidget.item(row, best_idx).setForeground(QtGui.QColor('white'))
|
||||
|
||||
row = self.stats_tableWidget.rowCount() - 2
|
||||
self.stats_tableWidget.setVerticalHeaderItem(row, QtWidgets.QTableWidgetItem('F'))
|
||||
self.stats_tableWidget.setItem(row, 0, QtWidgets.QTableWidgetItem('-'))
|
||||
|
||||
self.stats_tableWidget.setVerticalHeaderItem(row+1, QtWidgets.QTableWidgetItem('Pr(>F)'))
|
||||
self.stats_tableWidget.setItem(row+1, 0, QtWidgets.QTableWidgetItem('-'))
|
||||
|
||||
for col, (_, stats, dof) in enumerate(self._prevs[idx], start=1):
|
||||
f_value, prob_f = res.f_test(stats['chi^2'], dof)
|
||||
it = QtWidgets.QTableWidgetItem(f'{f_value:.4g}')
|
||||
it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable)
|
||||
self.corr_tableWidget.setItem(row, col, it)
|
||||
|
||||
it = QtWidgets.QTableWidgetItem(f'{prob_f:.4g}')
|
||||
it.setFlags(it.flags() ^ QtCore.Qt.ItemIsEditable)
|
||||
if prob_f < 0.05:
|
||||
it.setBackground(QtGui.QColor('green'))
|
||||
it.setForeground(QtGui.QColor('white'))
|
||||
self.stats_tableWidget.setItem(row+1, col, it)
|
||||
|
||||
@QtCore.pyqtSlot(QtWidgets.QAbstractButton)
|
||||
def on_buttonBox_clicked(self, button: QtWidgets.QAbstractButton):
|
||||
button_type = self.buttonBox.standardButton(button)
|
||||
|
||||
if button_type == self.buttonBox.Retry:
|
||||
self.redoFit.emit(self._results)
|
||||
|
||||
elif button_type == self.buttonBox.Ok:
|
||||
graph = '-1'
|
||||
if self.parameter_checkbox.isChecked():
|
||||
if self.graph_checkBox.checkState() == QtCore.Qt.Checked:
|
||||
graph = ''
|
||||
else:
|
||||
graph = self.graph_comboBox.currentData()
|
||||
|
||||
plot_fits = self.curve_checkbox.isChecked()
|
||||
|
||||
if self.partial_checkBox.checkState() == QtCore.Qt.Checked:
|
||||
self.closed.emit(self._results, self._opts, graph, plot_fits, self._parts)
|
||||
else:
|
||||
self.closed.emit(self._results, self._opts, graph, plot_fits, {})
|
||||
|
||||
self.accept()
|
||||
|
||||
else:
|
||||
self.reject()
|
Reference in New Issue
Block a user