nmreval/src/gui_qt/main/mainwindow.py

1089 lines
43 KiB
Python
Raw Normal View History

2022-10-20 15:23:15 +00:00
from __future__ import annotations
import datetime
import os
2022-03-24 16:35:10 +00:00
import re
2022-03-08 09:27:40 +00:00
from pathlib import Path
from numpy import geomspace, linspace
from pyqtgraph import ViewBox
2022-03-08 09:27:40 +00:00
2022-10-20 15:23:15 +00:00
from nmreval.configs import *
from nmreval.io.sessionwriter import NMRWriter
2022-10-20 15:23:15 +00:00
2022-03-08 09:27:40 +00:00
from .management import UpperManagement
from ..Qt import QtGui, QtPrintSupport
2022-03-08 09:27:40 +00:00
from ..data.shift_graphs import QShift
from ..data.signaledit import QApodDialog, QBaselineDialog, QPhasedialog
from ..fit.result import FitExtension, QFitResult
2022-03-08 09:27:40 +00:00
from ..graphs.graphwindow import QGraphWindow
from ..graphs.movedialog import QMover
from ..io.fcbatchreader import QFCReader
from ..io.filedialog import *
2022-03-08 09:27:40 +00:00
from ..lib import get_icon, make_action_icons
from ..lib.pg_objects import RegionItem
from ..lib.starter import make_starter
2022-03-08 09:27:40 +00:00
from ..math.evaluation import QEvalDialog
from ..math.interpol import InterpolDialog
from ..math.mean_dialog import QMeanTimes
from ..math.smooth import QSmooth
from ..nmr.coupling_calc import QCoupCalcDialog
from ..nmr.t1_from_tau import QRelaxCalc
from .._py.basewindow import Ui_BaseWindow
from ..lib.utils import UpdateDialog, Updater
2022-03-08 09:27:40 +00:00
class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
closeSignal = QtCore.pyqtSignal()
openpoints = QtCore.pyqtSignal(dict)
save_ses_sig = QtCore.pyqtSignal(str)
rest_ses_sig = QtCore.pyqtSignal(str)
def __init__(self, parents=None, path=None):
super().__init__(parent=parents)
if path is None:
self.path = Path.home()
else:
self.path = Path(path)
self.read_state()
self.management = UpperManagement(self)
self.fitlimitvalues = [None, None]
self.fitpreview = []
self._fit_plot_id = None
self.savefitdialog = None
self.eval = None
self.editor = None
self.movedialog = QMover(self)
self.current_graph_widget = None
self.current_plotitem = None
self._block_window_change = False
self.fname = None
self.settings = QtCore.QSettings('NMREVal', 'settings')
self._init_gui()
self._init_signals()
if os.getenv('APPIMAGE') is not None:
if Updater.get_update_information(os.getenv('APPIMAGE'))[0]:
self.look_for_update()
self.__timer = QtCore.QTimer()
self.__backup_path = config_paths() / f'{datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S")}.nmr'
self.__timer.start(3*60*1000) # every three minutese
self.__timer.timeout.connect(self._autosave)
2023-04-09 15:21:40 +00:00
self.fit_timer = QtCore.QTimer()
self.fit_timer.setInterval(500)
self.fit_timer.timeout.connect(
lambda: self.status.setText(f'Fit running... ({self.management.fitter.step} evaluations)'))
2022-03-08 09:27:40 +00:00
def _init_gui(self):
self.setupUi(self)
make_action_icons(self)
self.setWindowIcon(get_icon('logo'))
self.norm_toolbutton = QtWidgets.QToolButton(self)
self.norm_toolbutton.setMenu(self.menuNormalize)
self.norm_toolbutton.setPopupMode(self.norm_toolbutton.InstantPopup)
self.norm_toolbutton.setIcon(get_icon('normal'))
self.toolbar_edit.addWidget(self.norm_toolbutton)
self.fitlim_button = QtWidgets.QToolButton(self)
self.fitlim_button.setMenu(self.menuLimits)
self.fitlim_button.setPopupMode(self.fitlim_button.InstantPopup)
self.fitlim_button.setIcon(get_icon('fit_region'))
self.toolBar_fit.addWidget(self.fitlim_button)
self.area.dragEnterEvent = self.dragEnterEvent
while self.tabWidget.count() > 2:
self.tabWidget.removeTab(self.tabWidget.count()-1)
# Prevent closing "data" and "values"
for i in [0, 1]:
self.tabWidget.tabBar().tabButton(i, QtWidgets.QTabBar.ButtonPosition.RightSide).resize(0, 0)
self.setAcceptDrops(True)
self.mousepos = QtWidgets.QLabel('')
self.status = QtWidgets.QLabel('')
# noinspection PyUnresolvedReferences
2022-03-08 09:27:40 +00:00
self.statusBar.addWidget(self.status)
# noinspection PyUnresolvedReferences
2022-03-08 09:27:40 +00:00
self.statusBar.addWidget(self.mousepos)
self.fitregion = RegionItem()
self._fit_plot_id = None
self.setGeometry(QtWidgets.QStyle.alignedRect(QtCore.Qt.LeftToRight, QtCore.Qt.AlignCenter,
self.size(), QtWidgets.qApp.desktop().availableGeometry()))
self.datawidget.management = self.management
self.integralwidget.management = self.management
2023-01-26 18:30:39 +00:00
self.drawingswidget.graphs = self.management.graphs
2022-03-08 09:27:40 +00:00
self.ac_group = QtWidgets.QActionGroup(self)
self.ac_group.addAction(self.action_lm_fit)
self.ac_group.addAction(self.action_nm_fit)
self.ac_group.addAction(self.action_odr_fit)
self.ac_group2 = QtWidgets.QActionGroup(self)
self.ac_group2.addAction(self.action_no_range)
self.ac_group2.addAction(self.action_x_range)
self.ac_group2.addAction(self.action_custom_range)
def _init_signals(self):
self.actionRedo = self.management.undostack.createRedoAction(self)
icon = QtGui.QIcon.fromTheme("edit-redo")
self.actionRedo.setIcon(icon)
self.actionRedo.setShortcuts(QtGui.QKeySequence.Redo)
self.menuData.insertAction(self.action_new_set, self.actionRedo)
self.actionUndo = self.management.undostack.createUndoAction(self)
self.actionUndo.setShortcuts(QtGui.QKeySequence.Undo)
icon = QtGui.QIcon.fromTheme("edit-undo")
self.actionUndo.setIcon(icon)
self.menuData.insertAction(self.actionRedo, self.actionUndo)
self.action_save_fit_parameter.triggered.connect(self.save_fit_parameter)
self.ac_group2.triggered.connect(self.change_fit_limits)
self.t1action.triggered.connect(lambda: self._show_tab('t1_temp'))
self.action_edit.triggered.connect(lambda: self._show_tab('signal'))
self.actionPick_position.triggered.connect(lambda: self._show_tab('pick'))
self.actionIntegration.triggered.connect(lambda: self._show_tab('integrate'))
2022-03-08 09:27:40 +00:00
self.action_FitWidget.triggered.connect(lambda: self._show_tab('fit'))
self.action_draw_object.triggered.connect(lambda: self._show_tab('drawing'))
2022-03-08 09:27:40 +00:00
self.action_new_set.triggered.connect(self.management.create_empty)
self.actionDelete_window.triggered.connect(self.management.delete_sets)
self.actionCascade_windows.triggered.connect(self.area.cascadeSubWindows)
self.actionTile.triggered.connect(self.area.tileSubWindows)
self.actionTileHorizontal.triggered.connect(self.area.tileSubWindowsHorizontally)
self.actionTileVertical.triggered.connect(self.area.tileSubWindowsVertically)
2022-03-08 09:27:40 +00:00
self.datawidget.keyChanged.connect(self.management.change_keys)
self.datawidget.tree.deleteItem.connect(self.management.delete_sets)
self.datawidget.tree.moveItem.connect(self.management.move_sets)
self.datawidget.tree.copyItem.connect(self.management.copy_sets)
self.datawidget.graph_toolButton.clicked.connect(self.new_graph)
self.datawidget.empty_toolButton.clicked.connect(self.management.create_empty)
self.datawidget.func_toolButton.clicked.connect(self.make_data_from_function)
self.datawidget.tree.stateChanged.connect(self.management.change_visibility)
self.datawidget.startShowProperty.connect(self.management.get_properties)
self.datawidget.propertyChanged.connect(self.management.update_property)
self.datawidget.tree.saveFits.connect(self.save_fit_parameter)
self.datawidget.tree.extendFits.connect(self.extend_fit)
2022-03-08 09:27:40 +00:00
self.management.newData[list, str].connect(self.show_new_data)
self.management.newData[list, str, bool].connect(self.show_new_data)
2022-03-08 09:27:40 +00:00
self.management.newGraph.connect(self.new_graph)
self.management.dataChanged.connect(self.update_data)
self.management.deleteData.connect(self.delete_data)
self.management.deleteGraph.connect(self.remove_graph)
self.management.restoreGraph.connect(self.set_graph)
self.management.properties_collected.connect(self.datawidget.set_properties)
self.management.unset_state.connect(lambda x: self.datawidget.uncheck_sets(x))
self.management.fitFinished.connect(self.show_fit_results)
self.fit_dialog._management = self.management
self.fit_dialog.preview_emit.connect(self.show_fit_preview)
self.fit_dialog.fitStartSig.connect(self.start_fit)
self.fit_dialog.abortFit.connect(lambda: self.management.stopFit.emit())
2022-03-08 09:27:40 +00:00
self.movedialog.moveData.connect(self.move_sets)
self.movedialog.copyData.connect(self.management.copy_sets)
self.ptsselectwidget.points_selected.connect(self.management.extract_points)
self.t1tauwidget.newData.connect(self.management.add_new_data)
2022-03-08 09:27:40 +00:00
self.t1tauwidget.newData.connect(self.management.add_new_data)
self.editsignalwidget.do_something.connect(self.management.apply)
self.editsignalwidget.preview_triggered.connect(self.do_preview)
self.action_sort_pts.triggered.connect(lambda: self.management.apply('sort', ()))
self.action_calc_eps_derivative.triggered.connect(self.management.bds_deriv)
self.action_magnitude.triggered.connect(self.management.calc_magn)
self.actionCenterMax.triggered.connect(lambda: self.management.apply('center', ()))
self.valuewidget.requestData.connect(self.show_data_values)
self.valuewidget.itemChanged.connect(self.management.set_values)
self.valuewidget.itemDeleted.connect(self.management.remove_values)
self.valuewidget.itemAdded.connect(self.management.append)
self.valuewidget.maskSignal.connect(self.management.mask_value)
self.valuewidget.values_selected.connect(self.plot_selected_values)
2022-04-03 14:42:44 +00:00
self.valuewidget.split_signal.connect(self.management.split_set)
2022-03-08 09:27:40 +00:00
self.actionMaximize.triggered.connect(lambda: self.current_graph_widget.showMaximized())
self.actionNext_window.triggered.connect(lambda: self.area.activateNextSubWindow())
self.actionPrevious.triggered.connect(lambda: self.area.activatePreviousSubWindow())
self.closeSignal.connect(self.close)
self.action_norm_max.triggered.connect(lambda: self.management.apply('norm', ('max',)))
self.action_norm_max_abs.triggered.connect(lambda: self.management.apply('norm', ('maxabs',)))
self.action_norm_first.triggered.connect(lambda: self.management.apply('norm', ('first',)))
self.action_norm_last.triggered.connect(lambda: self.management.apply('norm', ('last',)))
self.action_norm_area.triggered.connect(lambda: self.management.apply('norm', ('area',)))
self.action_cut.triggered.connect(lambda: self.management.cut())
self.actionConcatenate_sets.triggered.connect(lambda : self.management.cat())
@QtCore.pyqtSlot(name='on_action_open_triggered')
def open(self):
filedialog = OpenFileDialog(directory=self.path, caption='Open files',
filter='All files (*.*);;'
'Program session (*.nmr);;'
'HDF files (*.h5);;'
'Text files (*.txt *.dat);;'
'Novocontrol Alpha (*.EPS);;'
'TecMag files (*.tnt);;'
'Grace files (*.agr)')
2022-03-08 09:27:40 +00:00
filedialog.set_graphs(self.management.graphs.list())
filedialog.exec()
fname = filedialog.selectedFiles()
if fname:
self.path = Path(fname[0]).parent
self.management.load_files(fname, new_plot=filedialog.add_to_graph)
@QtCore.pyqtSlot(name='on_actionOpen_FC_triggered')
def read_fc(self):
reader = QFCReader(path=self.path, parent=self)
2023-04-14 15:58:14 +00:00
reader.add_graphs(self.management.graphs.list())
2022-03-08 09:27:40 +00:00
reader.data_read.connect(self.management.add_new_data)
reader.exec()
del reader
@QtCore.pyqtSlot(name='on_actionPrint_triggered')
def print(self):
QtPrintSupport.QPrintDialog().exec()
@QtCore.pyqtSlot(name='on_actionExportData_triggered')
@QtCore.pyqtSlot(name='on_actionSave_triggered')
def save(self):
save_dialog = SaveDirectoryDialog(directory=str(self.path), parent=self)
2022-03-08 09:27:40 +00:00
mode = save_dialog.exec()
if mode == QtWidgets.QDialog.Accepted:
2022-03-24 16:35:10 +00:00
savefile = save_dialog.save_file()
2022-03-08 09:27:40 +00:00
selected_filter = save_dialog.selectedNameFilter()
2022-03-24 16:35:10 +00:00
if savefile is not None:
self.path = savefile.parent
2022-03-24 16:35:10 +00:00
use_underscore = save_dialog.checkBox.isChecked()
self.management.save(savefile, selected_filter, strip_spaces=use_underscore)
param_outfile = re.sub(r'[_\s-]?<label>[_\s-]?', '', savefile.stem)
2022-03-24 16:35:10 +00:00
2023-01-26 18:30:39 +00:00
bad_character = r'/*<>\|:"'
for c in bad_character:
param_outfile = param_outfile.replace(c, '')
2022-03-24 16:35:10 +00:00
if save_dialog.agr_cb.isChecked():
self.current_graph_widget.export(savefile.with_name(param_outfile + '.agr'))
if save_dialog.fit_cb.isChecked():
self.management.save_fit_parameter(savefile.with_name(param_outfile + '.dat'))
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot()
@QtCore.pyqtSlot(list)
2022-10-20 15:23:15 +00:00
def save_fit_parameter(self, fit_sets: list[str] = None):
save_dialog = FileDialog(parent=self, caption='Save fit parameter', directory=str(self.path),
filter='All files(*, *);;Text files(*.dat *.txt)')
save_dialog.setAcceptMode(FileDialog.AcceptSave)
save_dialog.setOption(QtWidgets.QFileDialog.DontConfirmOverwrite, True)
2022-03-08 09:27:40 +00:00
mode = save_dialog.exec()
if mode == QtWidgets.QDialog.Accepted:
savefile = save_dialog.save_file()
if savefile:
self.management.save_fit_parameter(savefile, fit_sets=fit_sets)
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot(name='on_actionExportGraphic_triggered')
def export_graphic(self):
2022-03-24 16:35:10 +00:00
self.current_graph_widget.export_dialog()
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot(name='on_actionNew_window_triggered')
def new_graph(self):
w = QGraphWindow()
self.management.graphs[w.id] = w
self.set_graph(w.id)
if self.eval is not None:
self.eval.set_graphs(self.management.graphs.list())
return w.id
@QtCore.pyqtSlot(list, str)
@QtCore.pyqtSlot(list, str, bool)
def show_new_data(self, sets: list, graph: str, skip_change: bool = False):
2022-03-08 09:27:40 +00:00
if len(sets) == 0:
return
prev_graph = ''
if skip_change:
prev_graph = self.management.current_graph
2022-03-08 09:27:40 +00:00
if graph == '':
graph = self.new_graph()
self.management.plots_to_graph(sets, graph)
for idd in sets:
new_item = self.management[idd]
self.datawidget.blockSignals(True)
self.datawidget.add_item(new_item.id, new_item.name, graph)
self.datawidget.blockSignals(False)
# if graph == self.fit_dialog.connected_figure:
# self.fit_dialog.load(self.management.graphs.active(graph))
if skip_change:
self.area.setActiveSubWidget(prev_graph)
2022-03-08 09:27:40 +00:00
2022-04-03 14:42:44 +00:00
if self.valuewidget.isVisible():
self.valuewidget(self.management.graphs.tree())
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot(str)
def remove_graph(self, gid: str):
self.datawidget.remove_item([gid])
val_figure = self.valuewidget.connected_figure
self.valuewidget.remove_graph()
2022-03-08 09:27:40 +00:00
w = None
for w in self.area.subWindowList():
wdgt = w.widget()
if wdgt.id == gid:
wdgt.disconnect()
wdgt.scene.disconnect()
2022-03-08 09:27:40 +00:00
if wdgt == self.current_graph_widget:
if self.ptsselectwidget.connected_figure == gid:
self.ptsselectwidget.connected_figure = None
self.tabWidget.removeTab(self.tabWidget.indexOf(self.ptsselectwidget))
if self.t1tauwidget.connected_figure == gid:
self.t1tauwidget.connected_figure = None
self.tabWidget.removeTab(self.tabWidget.indexOf(self.t1tauwidget))
if self.fit_dialog.connected_figure == gid:
self.fit_dialog.connected_figure = None
for item in self.fit_dialog.preview_lines:
self.current_graph_widget.remove_external(item)
if val_figure == gid:
2022-03-08 09:27:40 +00:00
self.tabWidget.setCurrentIndex(0)
self.current_graph_widget.enable_picking(False)
2022-03-08 09:27:40 +00:00
self.current_graph_widget = None
self.management.current_graph = ''
self.current_plotitem = None
wdgt.setParent(None)
try:
wdgt.deleteLater()
except AttributeError:
pass
2022-03-08 09:27:40 +00:00
break
if w is not None:
self.area.removeSubWindow(w)
2022-03-08 09:27:40 +00:00
w.close()
try:
w.deleteLater()
except AttributeError:
pass
2022-03-08 09:27:40 +00:00
if self.current_graph_widget is None:
if self.area.subWindowList():
self.area.activateNextSubWindow()
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot(str)
def set_graph(self, key: str):
w = self.management.graphs[key]
subwindow = self.area.addSubWindow(w)
subwindow.setOption(QtWidgets.QMdiSubWindow.RubberBandMove, True)
subwindow.setOption(QtWidgets.QMdiSubWindow.RubberBandResize, True)
subwindow.setMinimumHeight(400)
subwindow.setMinimumWidth(600)
self.datawidget.blockSignals(True)
self.datawidget.tree.blockSignals(True)
self.datawidget.add_graph(w.id, w.title)
self.datawidget.tree.blockSignals(False)
self.datawidget.blockSignals(False)
w.mousePositionChanged.connect(self.mousemoved)
w.aboutToClose.connect(self.management.delete_sets)
2022-03-08 09:27:40 +00:00
w.positionClicked.connect(self.point_selected)
w.show()
graph_list = self.management.graphs.list()
self.t1tauwidget.set_graphs(graph_list)
self.ptsselectwidget.set_graphs(graph_list)
@QtCore.pyqtSlot(QtWidgets.QMdiSubWindow, name='on_area_subWindowActivated')
def change_window(self, wd):
""" Called every time focus moves from or to a subwindow. Returns None if current focus is not on a subwindow"""
if wd is None:
return
2022-03-08 09:27:40 +00:00
if self.current_graph_widget is not None:
self.current_graph_widget.closable = True
2022-03-08 09:27:40 +00:00
if self.ptsselectwidget.isVisible():
self._select_ptswidget(False, False, False)
2022-03-08 09:27:40 +00:00
if self.fit_dialog.isVisible():
self._select_fitwidget(False, False)
2022-03-08 09:27:40 +00:00
self.current_graph_widget = wd.widget()
self.management.current_graph = wd.widget().id
self.current_plotitem = self.current_graph_widget.graphic
self.change_mouse_mode(self.actionMouse_behaviour.isChecked())
2022-03-08 09:27:40 +00:00
pick = False
block = False
if self.ptsselectwidget.isVisible():
pick, block = self._select_ptswidget(True, pick, block)
if self.fit_dialog.isVisible():
block = self._select_fitwidget(True, block)
2022-03-08 09:27:40 +00:00
self._set_pick_block(pick, block)
2022-03-08 09:27:40 +00:00
self.datawidget.tree.blockSignals(True)
self.datawidget.tree.highlight(self.management.current_graph)
self.datawidget.tree.blockSignals(False)
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot(name='on_actionChange_datatypes_triggered')
def type_change_dialog(self):
from ..data.conversion import ConversionDialog
dialog = ConversionDialog(self)
dialog.set_graphs(self.management.graphs.tree())
dialog.convertSets.connect(self.management.convert_sets)
_ = dialog.exec()
dialog.disconnect()
def _set_pick_block(self, pick: bool, block: bool):
self.current_graph_widget.enable_picking(pick)
self.current_graph_widget.closable = not block
2022-10-30 17:45:43 +00:00
def _show_tab(self, mode: str):
widget, name = {
't1_temp': (self.t1tauwidget, 'T1 mininmon'),
'signal': (self.editsignalwidget, 'Signals'),
'pick': (self.ptsselectwidget, 'Pick points'),
'fit': (self.fit_dialog, 'Fit'),
'drawing': (self.drawingswidget, 'Draw'),
'integrate': (self.integralwidget, 'Integrate'),
2022-10-30 17:45:43 +00:00
}[mode]
for idx in range(self.tabWidget.count()):
if self.tabWidget.widget(idx) == widget:
self.tabWidget.setCurrentIndex(idx)
return
self.tabWidget.addTab(widget, name)
self.tabWidget.setCurrentIndex(self.tabWidget.count()-1)
@QtCore.pyqtSlot(int, name='on_tabWidget_tabCloseRequested')
def close_tab(self, idx: int):
if idx == 0:
pass
else:
self.tabWidget.setCurrentIndex(0)
self.tabWidget.removeTab(idx)
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot(int, name='on_tabWidget_currentChanged')
def toggle_tabs(self, idx: int):
widget = self.tabWidget.widget(idx)
if self.current_graph_widget is None:
if self.tabWidget.currentIndex() > 1:
self.tabWidget.removeTab(self.tabWidget.indexOf(widget))
self.tabWidget.setCurrentIndex(0)
return
if self.current_graph_widget is not None:
self.current_graph_widget.enable_picking(False)
pick_required, block_window = self._select_ptswidget(widget == self.ptsselectwidget, False, False)
self._select_valuewidget(widget == self.valuewidget)
pick_required, block_window = self._select_t1tauwidget(widget == self.t1tauwidget, pick_required, block_window)
block_window = self._select_fitwidget(widget == self.fit_dialog, block_window)
self._select_drawingswidget(widget == self.drawingswidget)
pick_required = self._select_integralwidget(widget == self.integralwidget, pick_required, block_window)
2022-03-08 09:27:40 +00:00
self._set_pick_block(pick_required, block_window)
2022-10-20 15:23:15 +00:00
def _select_ptswidget(self, onoff: bool, pick_required: bool, block_window: bool) -> tuple[bool, bool]:
2022-03-08 09:27:40 +00:00
if self.current_graph_widget is None:
return pick_required, block_window
if onoff: # point selection
for line in self.ptsselectwidget.pts_lines:
self.current_graph_widget.add_external(line)
self.ptsselectwidget.point_removed.connect(self.current_graph_widget.remove_external)
self.ptsselectwidget.connected_figure = self.management.current_graph
pick_required = True
else:
if self.ptsselectwidget.connected_figure:
g = self.management.graphs[self.ptsselectwidget.connected_figure]
for line in self.ptsselectwidget.pts_lines:
g.remove_external(line)
# self.ptsselectwidget.clear()
return pick_required, block_window
def _select_valuewidget(self, onoff: bool):
if onoff: # Values
self.valuewidget(self.management.graphs.tree())
current_graph = self.valuewidget.connected_figure
if current_graph is not None:
self.management.graphs[current_graph].add_external(self.valuewidget.selection_real)
self.management.graphs[current_graph].add_external(self.valuewidget.selection_imag)
2022-03-08 09:27:40 +00:00
else:
if self.valuewidget.connected_figure is not None:
conn_fig = self.valuewidget.connected_figure
self.management.graphs[conn_fig].remove_external(self.valuewidget.selection_real)
self.management.graphs[conn_fig].remove_external(self.valuewidget.selection_imag)
2022-03-08 09:27:40 +00:00
def _select_integralwidget(self, onoff: bool, pick_required: bool, block_window: bool) -> tuple[bool, bool]:
2022-03-08 09:27:40 +00:00
if self.current_graph_widget is None:
return pick_required, block_window
2022-03-08 09:27:40 +00:00
if onoff:
self.integralwidget(self.current_graph_widget.title,
self.management.graphs.current_sets(self.management.current_graph))
self.integralwidget.item_deleted.connect(self.current_graph_widget.remove_external)
self.integralwidget.connected_figure = self.management.current_graph
pick_required = True
block_window = True
2022-03-08 09:27:40 +00:00
else:
if self.integralwidget.connected_figure:
g = self.management.graphs[self.integralwidget.connected_figure]
for line in self.integralwidget.lines:
g.remove_external(line[0])
g.remove_external(line[1])
self.integralwidget.clear()
return pick_required, block_window
2022-03-08 09:27:40 +00:00
def _select_t1tauwidget(self, onoff: bool, pick_required: bool, block_window: bool):
if onoff: # tau from t1
if self.current_graph_widget is None:
return pick_required, block_window
2022-03-08 09:27:40 +00:00
idx = self.tabWidget.indexOf(self.t1tauwidget)
if len(self.current_graph_widget) != 1:
QtWidgets.QMessageBox.information(self, 'Too many datasets',
'Only one T1 curve can be evaluated at once')
self.tabWidget.removeTab(idx)
self.tabWidget.setCurrentIndex(0)
return pick_required, block_window
2022-03-08 09:27:40 +00:00
self.tabWidget.setTabText(idx, 'T1 min (%s)' % {self.current_graph_widget.title})
self.t1tauwidget.set_graphs(self.management.graphs.list())
self.t1tauwidget.set_data(*self.management.get_data(self.current_graph_widget.active[0], xy_only=True),
name=self.management[self.current_graph_widget.active[0]].name)
self.t1tauwidget.connected_figure = self.management.current_graph
self.current_graph_widget.add_external(self.t1tauwidget.min_pos)
self.current_graph_widget.add_external(self.t1tauwidget.parabola)
pick_required = True
block_window = True
else:
if self.t1tauwidget.connected_figure:
g = self.management.graphs[self.t1tauwidget.connected_figure]
g.remove_external(self.t1tauwidget.min_pos)
g.remove_external(self.t1tauwidget.parabola)
return pick_required, block_window
def _select_drawingswidget(self, onoff):
if onoff:
2023-01-26 18:30:39 +00:00
if self.drawingswidget.graphs is None:
self.drawingswidget.graphs = self.management.graphs
self.drawingswidget.update_tree()
else:
self.drawingswidget.clear()
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot(str)
def get_data(self, key: str):
if hasattr(self.sender(), 'set_data'):
self.sender().set_data(self.management[key])
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot(name='on_actionCalculateT1_triggered')
def show_t1calc_dialog(self):
dialog = QRelaxCalc(self)
dialog.set_graphs(self.management.graphs.tree(key_only=True))
dialog.newData.connect(self.management.calc_relaxation)
dialog.show()
@QtCore.pyqtSlot(name='on_actionSkip_points_triggered')
def skip_pts_dialog(self):
from ..math.skipping import QSkipDialog
dial = QSkipDialog(self)
dial.exec()
self.management.skip_points(**dial.get_arguments())
2022-03-22 19:07:59 +00:00
@QtCore.pyqtSlot(name='on_action_coup_calc_triggered')
2022-03-08 09:27:40 +00:00
def coupling_dialog(self):
dialog = QCoupCalcDialog(self)
dialog.show()
@QtCore.pyqtSlot(name='on_action_mean_t1_triggered')
def mean_dialog(self):
gnames = self.management.graphs.tree(key_only=True)
dialog = QMeanTimes(gnames, parent=self)
dialog.newValues.connect(self.management.calc_mean)
dialog.show()
@QtCore.pyqtSlot(name='on_actionRunning_values_triggered')
def smooth_dialog(self):
dialog = QSmooth(parent=self)
dialog.newValues.connect(self.management.smooth_data)
dialog.show()
@QtCore.pyqtSlot(name='on_actionInterpolation_triggered')
def interpol_dialog(self):
if self.current_graph_widget is None:
return
gnames = self.management.graphs.tree()
dialog = InterpolDialog(parent=self)
dialog.set_data(gnames, self.current_graph_widget.id)
dialog.new_data.connect(self.management.interpolate_data)
dialog.show()
@QtCore.pyqtSlot(name='on_action_calc_triggered')
def open_eval_dialog(self):
if self.eval is None:
self.eval = QEvalDialog(parent=self)
self.eval.do_eval.connect(self.management.eval_expression)
self.eval.do_calc.connect(self.management.create_from_function)
self.eval.set_mode('e')
self.eval.set_namespace(self.management.get_namespace())
self.eval.add_data(self.management.active_sets)
self.eval.set_graphs(self.management.graphs.list())
self.eval.exec()
@QtCore.pyqtSlot(name='on_actionAddlines_triggered')
def make_data_from_function(self):
if self.eval is None:
self.eval = QEvalDialog(parent=self)
self.eval.do_eval.connect(self.management.eval_expression)
self.eval.do_calc.connect(self.management.create_from_function)
self.eval.set_mode('c')
self.eval.set_namespace(self.management.get_namespace())
self.eval.set_graphs(self.management.graphs.list())
self.eval.exec()
@QtCore.pyqtSlot(name='on_actionDerivation_triggered')
# @QtCore.pyqtSlot(name='on_actionIntegration_triggered')
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot(name='on_actionFilon_triggered')
def int_diff_ft(self):
sets = self.management.active_sets
mode = {
# self.actionIntegration: 'int',
self.actionDerivation: 'diff',
self.actionFilon: 'logft'}[self.sender()]
2022-03-08 09:27:40 +00:00
if sets:
from ..math.integrate_derive import QDeriveIntegrate
dialog = QDeriveIntegrate(mode)
dialog.add_graphs(self.management.graphs.list())
dialog.set_sets(sets)
res = dialog.exec()
if res:
options = dialog.get_options()
mode = options['mode']
if mode in ['i', 'd']:
self.management.integrate(**options)
elif mode == 'l':
self.management.logft(**options)
else:
raise ValueError('Unknown mode %s, not `i`, `d`, `l`.' % str(mode))
@QtCore.pyqtSlot(str)
def update_data(self, sid: str):
if self.valuewidget.shown_set == sid:
self.show_data_values(sid)
self.datawidget.set_name(sid, self.management[sid].name)
@QtCore.pyqtSlot(list)
def delete_data(self, sid: list[str]):
for key in sid:
if self.valuewidget.shown_set == key:
self.tabWidget.setCurrentIndex(0)
2022-03-08 09:27:40 +00:00
self.datawidget.remove_item(sid)
@QtCore.pyqtSlot(name='on_actionBaseline_triggered')
def baseline_dialog(self):
if not self.current_graph_widget:
return
if len(self.current_graph_widget) != 1:
QtWidgets.QMessageBox.information(self, 'Invalid number of sets',
'Baseline correction can only applied to one set at a time.')
return
for sid in self.current_graph_widget.active:
data_mode = self.management[sid].mode
if data_mode in ['spectrum']:
editor = QBaselineDialog(self)
editor.add_data(*self.management.get_data(sid, xy_only=True))
editor.finished.connect(self.management.apply)
editor.exec()
@QtCore.pyqtSlot(str)
def do_preview(self, mode):
2022-03-08 09:27:40 +00:00
if mode == 'ap':
dialog = QApodDialog(parent=self)
elif mode == 'ph':
dialog = QPhasedialog(parent=self)
else:
raise ValueError('Unknown preview mode %s' % str(mode))
dialog.setRange(*self.current_graph_widget.ranges, self.current_graph_widget.log)
2022-03-08 09:27:40 +00:00
for sid in self.current_graph_widget.active:
data_mode = self.management[sid].mode
tobeadded = False
if (data_mode == 'fid') or (data_mode == 'spectrum' and mode == 'ph'):
tobeadded = True
if tobeadded:
dialog.add_data(*self.management.get_data(sid, xy_only=True))
if dialog.exec() == QtWidgets.QDialog.Accepted:
self.management.apply(mode, dialog.get_value())
@QtCore.pyqtSlot(name='on_actionMove_between_plots_triggered')
def move_sets_dialog(self):
gnames = self.management.graphs.tree()
self.movedialog.setup(gnames)
self.movedialog.exec()
@QtCore.pyqtSlot(list, str, str)
def move_sets(self, sets, dest, src):
self.management.move_sets(sets, dest, src)
for s in sets:
self.datawidget.tree.move_sets(s, dest, src)
@QtCore.pyqtSlot(str)
def show_data_values(self, sid: str):
if sid == '':
return
data, mask = self.management.get_data(sid)
self.valuewidget.set_data(data, mask)
def plot_selected_values(self, old_gid: str, new_gid: str):
for pts in (self.valuewidget.selection_real, self.valuewidget.selection_imag):
if old_gid:
self.management.graphs[old_gid].remove_external(pts)
if new_gid:
self.management.graphs[new_gid].add_external(pts)
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot(object, str)
def item_to_graph(self, item, graph_id):
self.management.graphs[graph_id].add_external(item)
@QtCore.pyqtSlot(object, str)
def item_from_graph(self, item, graph_id):
self.management.graphs[graph_id].remove_external(item)
def closeEvent(self, evt):
# self._write_settings()
self.close()
@QtCore.pyqtSlot(int)
def request_data(self, idx):
idd = self.datawidget.get_indexes(idx=idx-1)
try:
x = self.management[idd].x
y = self.management[idd].y
ret_val = (x, y)
except KeyError:
ret_val = None
# noinspection PyUnresolvedReferences
2022-03-08 09:27:40 +00:00
self.sender().receive_data(ret_val)
return ret_val
@QtCore.pyqtSlot(tuple, bool)
def point_selected(self, pos, double):
w = self.tabWidget.currentWidget()
if w == self.ptsselectwidget:
line = self.ptsselectwidget.add(pos, double)
self.current_graph_widget.add_external(line)
elif w == self.t1tauwidget:
self.t1tauwidget.t1min_picked(pos)
elif w == self.integralwidget:
region, integral_plot = self.integralwidget.add(pos)
self.current_graph_widget.add_external(region)
self.current_graph_widget.add_external(integral_plot)
2022-03-08 09:27:40 +00:00
def _select_fitwidget(self, onoff: bool, block_window: bool):
if self.current_graph_widget is not None:
2022-03-24 16:35:10 +00:00
pass
2022-03-08 09:27:40 +00:00
if onoff:
self.fit_dialog.connected_figure = self.management.current_graph
self.fit_dialog.load(self.management.active_sets)
for item in self.fit_dialog.preview_lines:
self.current_graph_widget.add_external(item)
if self.action_custom_range.isChecked():
self.current_graph_widget.add_external(self.fitregion)
block_window = True
else:
for item in self.fit_dialog.preview_lines:
self.current_graph_widget.remove_external(item)
self.current_graph_widget.remove_external(self.fitregion)
2022-03-08 09:27:40 +00:00
return block_window
@QtCore.pyqtSlot(QtWidgets.QAction)
def change_fit_limits(self, action: QtWidgets.QAction):
if action == self.action_custom_range and self.fit_dialog.isVisible():
self.current_graph_widget.add_external(self.fitregion)
else:
self.current_graph_widget.remove_external(self.fitregion)
def start_fit(self, parameter, links, fit_options):
fit_options['limits'] = {
self.action_no_range: 'none',
self.action_x_range: 'x',
self.action_custom_range: self.fitregion.getRegion()
}[self.ac_group2.checkedAction()]
fit_options['fit_mode'] = {
self.action_lm_fit: 'lsq',
self.action_nm_fit: 'nm',
self.action_odr_fit: 'odr'
}[self.ac_group.checkedAction()]
self.fit_dialog.fit_button.setEnabled(False)
self.management.start_fit(parameter, links, fit_options)
self.status.setText('Fit running...'.format(self.management.fitter.step))
2023-04-09 15:21:40 +00:00
self.fit_timer.start(500)
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot(dict, int, bool)
def show_fit_preview(self, funcs: dict, num: int, show: bool):
if self.fit_dialog.connected_figure is None:
return
g = self.management.graphs[self.fit_dialog.connected_figure]
for item in self.fit_dialog.preview_lines:
g.remove_external(item)
if show:
x_lim, _ = g.ranges
space = geomspace if g.log[0] else linspace
x = space(1.01*x_lim[0], 0.99*x_lim[1], num=num)
self.fit_dialog.make_previews(x, funcs)
for item in self.fit_dialog.preview_lines:
g.add_external(item)
@QtCore.pyqtSlot(list)
def show_fit_results(self, results: list):
self.fit_dialog.fit_button.setEnabled(True)
2023-04-09 15:21:40 +00:00
self.fit_timer.stop()
self.status.setText('')
2022-03-08 09:27:40 +00:00
if results:
res_dialog = QFitResult(results, self.management, parent=self)
res_dialog.add_graphs(self.management.graphs.list())
res_dialog.closed.connect(self.accepts_fit)
2022-03-08 09:27:40 +00:00
res_dialog.redoFit.connect(self.management.redo_fits)
res_dialog.show()
@QtCore.pyqtSlot(dict, list, str, bool, bool, list)
def accepts_fit(self, res: dict, opts: list, param_graph: str,
show_fit: bool, parts: bool, extrapolate: list) -> None:
self.fit_dialog.set_parameter(res)
self.management.make_fits(res, opts, param_graph, show_fit, parts, extrapolate)
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot(name='on_actionFunction_editor_triggered')
def edit_models(self):
if self.editor is None:
from ..lib.usermodeleditor import QUsermodelEditor
self.editor = QUsermodelEditor(config_paths() / 'usermodels.py', parent=self)
self.editor.modelsChanged.connect(lambda: self.fit_dialog.read_and_load_functions())
2022-03-08 09:27:40 +00:00
self.editor.setWindowModality(QtCore.Qt.ApplicationModal)
self.editor.show()
@QtCore.pyqtSlot(list)
def extend_fit(self, sets: list):
w = FitExtension(self)
res = w.exec()
if res:
p = w.values
x = linspace(p[0], p[1], num=p[2])
self.management.extend_fits(sets, x)
2023-01-08 18:30:15 +00:00
@QtCore.pyqtSlot(name='on_action_create_fit_function_triggered')
def open_fitmodel_wizard(self):
from ..fit.function_creation_dialog import QUserFitCreator
helper = QUserFitCreator(config_paths() / 'usermodels.py', parent=self)
helper.classCreated.connect(lambda: self.fit_dialog.read_and_load_functions())
helper.show()
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot(name='on_actionShift_triggered')
def shift_dialog(self):
s = QShift(self)
s.set_graphs(self.management.graphs.list())
for key, name in self.management.active_sets:
data = self.management.data[key]
s.add_item(key, name, data.x, data.y)
s.valuesChanged.connect(self.management.shift_scale)
s.show()
@staticmethod
@QtCore.pyqtSlot(name='on_actionDocumentation_triggered')
def open_doc():
docpath = '/autohome/dominik/auswerteprogramm3/doc/_build/html/index.html'
import webbrowser
webbrowser.open(docpath)
def dropEvent(self, evt):
if evt.mimeData().hasUrls():
files = [str(url.toLocalFile()) for url in evt.mimeData().urls()]
self.management.load_files(files)
def dragEnterEvent(self, evt):
evt.accept()
@QtCore.pyqtSlot(bool, name='on_actionMouse_behaviour_toggled')
def change_mouse_mode(self, is_checked: bool):
2022-03-08 09:27:40 +00:00
if is_checked:
self.current_plotitem.plotItem.vb.setMouseMode(ViewBox.RectMode)
else:
self.current_plotitem.plotItem.vb.setMouseMode(ViewBox.PanMode)
def mousemoved(self, xpos, ypos):
self.mousepos.setText(f'x={xpos:.3g}; y={ypos:.3g}')
2022-03-08 09:27:40 +00:00
@QtCore.pyqtSlot(name='on_actionSnake_triggered')
@QtCore.pyqtSlot(name='on_actionTetris_triggered')
@QtCore.pyqtSlot(name='on_actionLife_triggered')
@QtCore.pyqtSlot(name='on_actionMine_triggered')
2022-03-08 09:27:40 +00:00
def spannung_spiel_und_spass(self):
if self.sender() == self.actionLife:
from ..lib.gol import QGameOfLife
game = QGameOfLife(parent=self)
game.setWindowModality(QtCore.Qt.NonModal)
game.show()
elif self.sender() == self.actionMine:
from ..lib.stuff import QMines
game = QMines(parent=self)
game.setWindowModality(QtCore.Qt.NonModal)
game.show()
2022-03-08 09:27:40 +00:00
else:
from ..lib.stuff import Game
if self.sender() == self.actionSnake:
gtype = 'snake'
else:
gtype = 'tetris'
game = Game(gtype, parent=self)
game.show()
@QtCore.pyqtSlot(name='on_actionConfiguration_triggered')
def open_configuration(self):
from ..lib.configurations import GeneralConfiguration
dialog = GeneralConfiguration(self)
dialog.show()
@QtCore.pyqtSlot(name='on_action_colorcycle_triggered')
def open_color_dialog(self):
from ..lib.color_dialog import ColorDialog
dialog = ColorDialog(self)
dialog.show()
2022-03-08 09:27:40 +00:00
def close(self):
write_state({'History': {'recent path': str(self.path)}})
2022-03-08 09:27:40 +00:00
# remove backup file when closing
self.__backup_path.unlink(missing_ok=True)
2022-03-08 09:27:40 +00:00
super().close()
def read_state(self):
opts = read_state()
self.path = pathlib.Path(opts['History'].get('recent path', Path.home()))
@QtCore.pyqtSlot(name='on_actionBugs_triggered')
def report_bug(self):
2023-02-04 15:15:08 +00:00
import webbrowser
webbrowser.open('https://gitea.pkm.physik.tu-darmstadt.de/IPKM-Public/nmreval/issues/new')
@QtCore.pyqtSlot(name='on_actionUpdate_triggered')
def look_for_update(self):
w = UpdateDialog(parent=self)
w.show()
@QtCore.pyqtSlot(name='on_actionShow_error_log_triggered')
def open_log(self):
from ..lib.logger import QLog
QLog(parent=self).show()
def _autosave(self):
# TODO better separate thread may it takes some time to save
self.status.setText('Autosave...')
NMRWriter(self.management.graphs, self.management.data).export(self.__backup_path)
self.status.setText('')
@QtCore.pyqtSlot(name='on_actionCreate_starter_triggered')
def create_starter(self):
make_starter(os.getenv('APPIMAGE'))
@QtCore.pyqtSlot(name='on_actionAbout_triggered')
def show_version(self):
from nmreval.version import __version__
QtWidgets.QMessageBox.about(self, 'Version', f'Build date of AppImage: {__version__}')