1075 lines
42 KiB
Python
1075 lines
42 KiB
Python
from __future__ import annotations
|
|
|
|
import datetime
|
|
import os
|
|
import pathlib
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from numpy import geomspace, linspace
|
|
from pyqtgraph import ViewBox
|
|
|
|
from nmreval.configs import *
|
|
from nmreval.io.sessionwriter import NMRWriter
|
|
|
|
from .management import UpperManagement
|
|
from ..Qt import QtGui, QtPrintSupport
|
|
from ..data.shift_graphs import QShift
|
|
from ..data.signaledit import QApodDialog, QBaselineDialog, QPhasedialog
|
|
from ..fit.result import FitExtension, QFitResult
|
|
from ..graphs.graphwindow import QGraphWindow
|
|
from ..graphs.movedialog import QMover
|
|
from ..io.fcbatchreader import QFCReader
|
|
from ..io.filedialog import *
|
|
from ..lib import get_icon, make_action_icons
|
|
from ..lib.pg_objects import RegionItem
|
|
from ..lib.starter import make_starter
|
|
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
|
|
|
|
|
|
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)
|
|
|
|
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)'))
|
|
|
|
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('')
|
|
self.statusBar.addWidget(self.status)
|
|
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
|
|
self.drawingswidget.graphs = self.management.graphs
|
|
|
|
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.actionSave.triggered.connect(lambda: self.management.save('/autohome/dominik/nmreval/testdata/test.nmr', ''))
|
|
# self.actionSave.triggered.connect(self.save)
|
|
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'))
|
|
self.action_FitWidget.triggered.connect(lambda: self._show_tab('fit'))
|
|
self.action_draw_object.triggered.connect(lambda: self._show_tab('drawing'))
|
|
|
|
self.action_new_set.triggered.connect(self.management.create_empty)
|
|
|
|
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)
|
|
|
|
self.management.newData.connect(self.show_new_data)
|
|
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())
|
|
|
|
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)
|
|
|
|
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)
|
|
self.valuewidget.split_signal.connect(self.management.split_set)
|
|
|
|
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)')
|
|
|
|
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)
|
|
reader.add_graphs(self.management.graphs.list())
|
|
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)
|
|
|
|
mode = save_dialog.exec()
|
|
if mode == QtWidgets.QDialog.Accepted:
|
|
savefile = save_dialog.save_file()
|
|
selected_filter = save_dialog.selectedNameFilter()
|
|
|
|
if savefile is not None:
|
|
self.path = savefile.parent
|
|
use_underscore = save_dialog.checkBox.isChecked()
|
|
self.management.save(savefile, selected_filter, strip_spaces=use_underscore)
|
|
|
|
param_outfile = re.sub('[_\s-]?<label>[_\s-]?', '', savefile.stem)
|
|
|
|
bad_character = r'/*<>\|:"'
|
|
for c in bad_character:
|
|
param_outfile = param_outfile.replace(c, '')
|
|
|
|
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'))
|
|
|
|
@QtCore.pyqtSlot()
|
|
@QtCore.pyqtSlot(list)
|
|
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)
|
|
|
|
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)
|
|
|
|
@QtCore.pyqtSlot(name='on_actionExportGraphic_triggered')
|
|
def export_graphic(self):
|
|
self.current_graph_widget.export_dialog()
|
|
|
|
@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)
|
|
def show_new_data(self, sets: list, graph: str):
|
|
if len(sets) == 0:
|
|
return
|
|
|
|
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 self.valuewidget.isVisible():
|
|
self.valuewidget(self.management.graphs.tree())
|
|
|
|
@QtCore.pyqtSlot(name='on_actionDelete_window_triggered')
|
|
def delete_windows(self):
|
|
self.management.delete_sets()
|
|
|
|
@QtCore.pyqtSlot(str)
|
|
def remove_graph(self, gid: str):
|
|
self.datawidget.remove_item(gid)
|
|
val_figure = self.valuewidget.connected_figure
|
|
self.valuewidget.remove_graph()
|
|
|
|
w = None
|
|
for w in self.area.subWindowList():
|
|
wdgt = w.widget()
|
|
if wdgt.id == gid:
|
|
wdgt.disconnect()
|
|
wdgt.scene.disconnect()
|
|
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:
|
|
self.tabWidget.setCurrentIndex(0)
|
|
|
|
self.current_graph_widget.enable_picking(False)
|
|
|
|
self.current_graph_widget = None
|
|
self.management.current_graph = ''
|
|
self.current_plotitem = None
|
|
|
|
wdgt.setParent(None)
|
|
try:
|
|
wdgt.deleteLater()
|
|
except AttributeError:
|
|
pass
|
|
|
|
break
|
|
|
|
|
|
if w is not None:
|
|
self.area.removeSubWindow(w)
|
|
w.close()
|
|
try:
|
|
w.deleteLater()
|
|
except AttributeError:
|
|
pass
|
|
|
|
if self.current_graph_widget is None:
|
|
if self.area.subWindowList():
|
|
self.area.activateNextSubWindow()
|
|
|
|
@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.delete_windows)
|
|
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 not None:
|
|
if self.current_graph_widget is not None:
|
|
self.current_graph_widget.closable = True
|
|
|
|
if self.ptsselectwidget.isVisible():
|
|
self._select_ptswidget(False, False, False)
|
|
|
|
if self.fit_dialog.isVisible():
|
|
self._select_fitwidget(False, False)
|
|
|
|
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())
|
|
|
|
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)
|
|
|
|
self._set_pick_block(pick, block)
|
|
|
|
self.datawidget.tree.blockSignals(True)
|
|
self.datawidget.tree.highlight(self.management.current_graph)
|
|
self.datawidget.tree.blockSignals(False)
|
|
|
|
@QtCore.pyqtSlot(name='on_actionCascade_windows_triggered')
|
|
@QtCore.pyqtSlot(name='on_actionTile_triggered')
|
|
def change_window_size(self):
|
|
if self.sender() == self.actionCascade_windows:
|
|
self.area.cascadeSubWindows()
|
|
elif self.sender() == self.actionTile:
|
|
self.area.tileSubWindows()
|
|
|
|
@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
|
|
|
|
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'),
|
|
}[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)
|
|
|
|
@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)
|
|
|
|
self._set_pick_block(pick_required, block_window)
|
|
|
|
def _select_ptswidget(self, onoff: bool, pick_required: bool, block_window: bool) -> tuple[bool, bool]:
|
|
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)
|
|
else:
|
|
if self.valuewidget.connected_figure is not None:
|
|
self.management.graphs[self.valuewidget.connected_figure].remove_external(self.valuewidget.selection_real)
|
|
self.management.graphs[self.valuewidget.connected_figure].remove_external(self.valuewidget.selection_imag)
|
|
|
|
def _select_integralwidget(self, onoff: bool, pick_required: bool, block_window: bool) -> tuple[bool, bool]:
|
|
if self.current_graph_widget is None:
|
|
return pick_required, block_window
|
|
|
|
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
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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:
|
|
if self.drawingswidget.graphs is None:
|
|
self.drawingswidget.graphs = self.management.graphs
|
|
self.drawingswidget.update_tree()
|
|
else:
|
|
self.drawingswidget.clear()
|
|
|
|
@QtCore.pyqtSlot(str)
|
|
def get_data(self, key: str):
|
|
self.sender().set_data(self.management[key])
|
|
|
|
@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())
|
|
|
|
@QtCore.pyqtSlot(name='on_action_coup_calc_triggered')
|
|
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')
|
|
@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()]
|
|
|
|
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(str)
|
|
def delete_data(self, sid):
|
|
if self.valuewidget.shown_set == sid:
|
|
self.tabWidget.setCurrentIndex(0)
|
|
|
|
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):
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
@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
|
|
|
|
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)
|
|
|
|
def _select_fitwidget(self, onoff: bool, block_window: bool):
|
|
if self.current_graph_widget is not None:
|
|
pass
|
|
|
|
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)
|
|
|
|
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))
|
|
self.fit_timer.start(500)
|
|
|
|
@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)
|
|
self.fit_timer.stop()
|
|
self.status.setText('')
|
|
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)
|
|
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)
|
|
|
|
@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())
|
|
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)
|
|
|
|
@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()
|
|
|
|
@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):
|
|
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}')
|
|
|
|
@QtCore.pyqtSlot(name='on_actionSnake_triggered')
|
|
@QtCore.pyqtSlot(name='on_actionTetris_triggered')
|
|
@QtCore.pyqtSlot(name='on_actionLife_triggered')
|
|
@QtCore.pyqtSlot(name='on_actionMine_triggered')
|
|
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()
|
|
|
|
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()
|
|
|
|
def close(self):
|
|
write_state({'History': {'recent path': str(self.path)}})
|
|
|
|
# remove backup file when closing
|
|
self.__backup_path.unlink(missing_ok=True)
|
|
|
|
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):
|
|
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'))
|