more interactive edit

This commit is contained in:
Dominik Demuth 2023-10-25 20:04:49 +02:00
parent 6339bdc2cc
commit 7fe564a61e
10 changed files with 921 additions and 241 deletions

View File

@ -14,54 +14,167 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ApodEdit(object):
def setupUi(self, ApodEdit):
ApodEdit.setObjectName("ApodEdit")
ApodEdit.resize(784, 484)
ApodEdit.resize(1144, 655)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(ApodEdit.sizePolicy().hasHeightForWidth())
ApodEdit.setSizePolicy(sizePolicy)
self.gridLayout = QtWidgets.QGridLayout(ApodEdit)
self.gridLayout.setContentsMargins(3, 3, 3, 3)
self.gridLayout.setContentsMargins(9, 9, 9, 9)
self.gridLayout.setSpacing(3)
self.gridLayout.setObjectName("gridLayout")
self.graphicsView = NMRPlotWidget(ApodEdit)
self.time_graph = NMRPlotWidget(ApodEdit)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.graphicsView.sizePolicy().hasHeightForWidth())
self.graphicsView.setSizePolicy(sizePolicy)
self.graphicsView.setObjectName("graphicsView")
self.gridLayout.addWidget(self.graphicsView, 2, 0, 1, 1)
self.graphicsView_2 = NMRPlotWidget(ApodEdit)
self.graphicsView_2.setObjectName("graphicsView_2")
self.gridLayout.addWidget(self.graphicsView_2, 2, 1, 1, 1)
self.apodcombobox = QtWidgets.QComboBox(ApodEdit)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.apodcombobox.sizePolicy().hasHeightForWidth())
self.apodcombobox.setSizePolicy(sizePolicy)
self.apodcombobox.setObjectName("apodcombobox")
self.gridLayout.addWidget(self.apodcombobox, 0, 0, 1, 1)
self.widget_layout = QtWidgets.QHBoxLayout()
self.widget_layout.setContentsMargins(-1, 6, -1, -1)
self.widget_layout.setSpacing(20)
self.widget_layout.setObjectName("widget_layout")
self.gridLayout.addLayout(self.widget_layout, 1, 0, 1, 2)
sizePolicy.setHeightForWidth(self.time_graph.sizePolicy().hasHeightForWidth())
self.time_graph.setSizePolicy(sizePolicy)
self.time_graph.setObjectName("time_graph")
self.gridLayout.addWidget(self.time_graph, 2, 1, 1, 1)
self.freq_graph = NMRPlotWidget(ApodEdit)
self.freq_graph.setObjectName("freq_graph")
self.gridLayout.addWidget(self.freq_graph, 2, 2, 1, 1)
self.buttonBox = QtWidgets.QDialogButtonBox(ApodEdit)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.gridLayout.addWidget(self.buttonBox, 4, 0, 1, 2)
self.eqn_label = QtWidgets.QLabel(ApodEdit)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Preferred)
self.gridLayout.addWidget(self.buttonBox, 4, 1, 1, 2)
self.widget = QtWidgets.QWidget(ApodEdit)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.eqn_label.sizePolicy().hasHeightForWidth())
self.eqn_label.setSizePolicy(sizePolicy)
sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth())
self.widget.setSizePolicy(sizePolicy)
self.widget.setObjectName("widget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.widget)
self.verticalLayout.setObjectName("verticalLayout")
self.baseline_box = QtWidgets.QCheckBox(self.widget)
self.baseline_box.setObjectName("baseline_box")
self.verticalLayout.addWidget(self.baseline_box)
self.shift_box = QtWidgets.QGroupBox(self.widget)
self.shift_box.setFlat(True)
self.shift_box.setCheckable(True)
self.shift_box.setChecked(False)
self.shift_box.setObjectName("shift_box")
self.gridLayout_4 = QtWidgets.QGridLayout(self.shift_box)
self.gridLayout_4.setContentsMargins(3, 3, 3, 3)
self.gridLayout_4.setSpacing(3)
self.gridLayout_4.setObjectName("gridLayout_4")
self.ls_lineedit = QtWidgets.QLineEdit(self.shift_box)
self.ls_lineedit.setObjectName("ls_lineedit")
self.gridLayout_4.addWidget(self.ls_lineedit, 1, 1, 1, 1)
self.ls_spinbox = QtWidgets.QSpinBox(self.shift_box)
self.ls_spinbox.setMaximum(999999)
self.ls_spinbox.setObjectName("ls_spinbox")
self.gridLayout_4.addWidget(self.ls_spinbox, 0, 1, 1, 1)
self.ls_combobox = QtWidgets.QComboBox(self.shift_box)
self.ls_combobox.setObjectName("ls_combobox")
self.ls_combobox.addItem("")
self.ls_combobox.addItem("")
self.gridLayout_4.addWidget(self.ls_combobox, 0, 0, 2, 1)
self.verticalLayout.addWidget(self.shift_box)
self.apod_box = QtWidgets.QGroupBox(self.widget)
self.apod_box.setFlat(True)
self.apod_box.setCheckable(True)
self.apod_box.setChecked(False)
self.apod_box.setObjectName("apod_box")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.apod_box)
self.verticalLayout_2.setContentsMargins(3, 3, 3, 3)
self.verticalLayout_2.setSpacing(3)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.apodcombobox = QtWidgets.QComboBox(self.apod_box)
self.apodcombobox.setObjectName("apodcombobox")
self.verticalLayout_2.addWidget(self.apodcombobox)
self.eqn_label = QtWidgets.QLabel(self.apod_box)
self.eqn_label.setIndent(3)
self.eqn_label.setObjectName("eqn_label")
self.gridLayout.addWidget(self.eqn_label, 0, 1, 1, 1)
self.verticalLayout_2.addWidget(self.eqn_label)
self.widget_layout = QtWidgets.QHBoxLayout()
self.widget_layout.setContentsMargins(-1, 6, -1, -1)
self.widget_layout.setSpacing(20)
self.widget_layout.setObjectName("widget_layout")
self.verticalLayout_2.addLayout(self.widget_layout)
self.verticalLayout.addWidget(self.apod_box)
self.zerofill_box = QtWidgets.QGroupBox(self.widget)
self.zerofill_box.setFlat(True)
self.zerofill_box.setCheckable(True)
self.zerofill_box.setChecked(False)
self.zerofill_box.setObjectName("zerofill_box")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.zerofill_box)
self.horizontalLayout.setContentsMargins(3, 3, 3, 3)
self.horizontalLayout.setSpacing(3)
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(self.zerofill_box)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.zf_spinbox = QtWidgets.QSpinBox(self.zerofill_box)
self.zf_spinbox.setMinimum(1)
self.zf_spinbox.setMaximum(3)
self.zf_spinbox.setObjectName("zf_spinbox")
self.horizontalLayout.addWidget(self.zf_spinbox)
self.verticalLayout.addWidget(self.zerofill_box)
self.phase_box = QtWidgets.QGroupBox(self.widget)
self.phase_box.setFlat(True)
self.phase_box.setCheckable(True)
self.phase_box.setChecked(False)
self.phase_box.setObjectName("phase_box")
self.gridLayout_2 = QtWidgets.QGridLayout(self.phase_box)
self.gridLayout_2.setContentsMargins(3, 3, 3, 3)
self.gridLayout_2.setSpacing(3)
self.gridLayout_2.setObjectName("gridLayout_2")
self.label_3 = QtWidgets.QLabel(self.phase_box)
self.label_3.setObjectName("label_3")
self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1)
self.label_4 = QtWidgets.QLabel(self.phase_box)
self.label_4.setObjectName("label_4")
self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1)
self.ph0_spinbox = QtWidgets.QDoubleSpinBox(self.phase_box)
self.ph0_spinbox.setWrapping(True)
self.ph0_spinbox.setDecimals(1)
self.ph0_spinbox.setMinimum(-180.0)
self.ph0_spinbox.setMaximum(180.0)
self.ph0_spinbox.setSingleStep(0.5)
self.ph0_spinbox.setObjectName("ph0_spinbox")
self.gridLayout_2.addWidget(self.ph0_spinbox, 0, 1, 1, 1)
self.pivot_lineedit = QtWidgets.QLineEdit(self.phase_box)
self.pivot_lineedit.setObjectName("pivot_lineedit")
self.gridLayout_2.addWidget(self.pivot_lineedit, 2, 1, 1, 1)
self.label_2 = QtWidgets.QLabel(self.phase_box)
self.label_2.setObjectName("label_2")
self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 1)
self.ph1_spinbox = QtWidgets.QDoubleSpinBox(self.phase_box)
self.ph1_spinbox.setWrapping(True)
self.ph1_spinbox.setDecimals(2)
self.ph1_spinbox.setMinimum(-720.0)
self.ph1_spinbox.setMaximum(720.0)
self.ph1_spinbox.setSingleStep(0.05)
self.ph1_spinbox.setObjectName("ph1_spinbox")
self.gridLayout_2.addWidget(self.ph1_spinbox, 1, 1, 1, 1)
self.verticalLayout.addWidget(self.phase_box)
self.ft_box = QtWidgets.QGroupBox(self.widget)
self.ft_box.setCheckable(True)
self.ft_box.setChecked(False)
self.ft_box.setObjectName("ft_box")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.ft_box)
self.verticalLayout_3.setContentsMargins(3, 3, 3, 3)
self.verticalLayout_3.setSpacing(3)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.phase_before_button = QtWidgets.QRadioButton(self.ft_box)
self.phase_before_button.setChecked(True)
self.phase_before_button.setObjectName("phase_before_button")
self.buttonGroup = QtWidgets.QButtonGroup(ApodEdit)
self.buttonGroup.setObjectName("buttonGroup")
self.buttonGroup.addButton(self.phase_before_button)
self.verticalLayout_3.addWidget(self.phase_before_button)
self.phase_after_button = QtWidgets.QRadioButton(self.ft_box)
self.phase_after_button.setObjectName("phase_after_button")
self.buttonGroup.addButton(self.phase_after_button)
self.verticalLayout_3.addWidget(self.phase_after_button)
self.verticalLayout.addWidget(self.ft_box)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.gridLayout.addWidget(self.widget, 2, 0, 1, 1)
self.retranslateUi(ApodEdit)
self.buttonBox.accepted.connect(ApodEdit.accept) # type: ignore
@ -71,5 +184,22 @@ class Ui_ApodEdit(object):
def retranslateUi(self, ApodEdit):
_translate = QtCore.QCoreApplication.translate
ApodEdit.setWindowTitle(_translate("ApodEdit", "Apodization"))
self.baseline_box.setText(_translate("ApodEdit", "Baseline"))
self.shift_box.setTitle(_translate("ApodEdit", "Shift"))
self.ls_lineedit.setText(_translate("ApodEdit", "0"))
self.ls_combobox.setItemText(0, _translate("ApodEdit", "Points"))
self.ls_combobox.setItemText(1, _translate("ApodEdit", "Seconds"))
self.apod_box.setTitle(_translate("ApodEdit", "Apodization"))
self.eqn_label.setText(_translate("ApodEdit", "TextLabel"))
self.zerofill_box.setTitle(_translate("ApodEdit", "Zero fill"))
self.label.setText(_translate("ApodEdit", "Double length"))
self.zf_spinbox.setSuffix(_translate("ApodEdit", "x"))
self.phase_box.setTitle(_translate("ApodEdit", "Phase correction"))
self.label_3.setText(_translate("ApodEdit", "Phase 1"))
self.label_4.setText(_translate("ApodEdit", "Pivot"))
self.pivot_lineedit.setText(_translate("ApodEdit", "0"))
self.label_2.setText(_translate("ApodEdit", "Phase 0"))
self.ft_box.setTitle(_translate("ApodEdit", "Fourier transform"))
self.phase_before_button.setText(_translate("ApodEdit", "before phase correction"))
self.phase_after_button.setText(_translate("ApodEdit", "after phase correction"))
from ..lib.graph_items import NMRPlotWidget

View File

@ -749,3 +749,41 @@ class SignalContainer(ExperimentContainer):
self._update_actions()
return self
@plot_update
def edit_signal(
self: SignalContainer,
baseline: tuple[None | bool],
leftshift: tuple[None | float, str],
zerofill: tuple[None, int],
apod: tuple[None, list[float], type[object]],
phase: tuple[None | float, float, float],
fourier: tuple[None | bool]
):
"""
Function for EditUndoCommand to call if a timesignal or spectra must be worked on.
This avoids to update the plot for every action we do and makes it slightly faster.
"""
if baseline[0] is not None:
self._data.baseline()
if leftshift[0] is not None:
self._data.shift(*leftshift)
if zerofill[0] is not None:
self._data.zerofill(*zerofill)
if apod[0] is not None:
self._data.apod(*apod)
if fourier[0] is not None:
print(fourier)
if fourier[0]:
if phase[0] is not None:
self._data.manual_phase(*phase)
self.fourier()
else:
self.fourier()
if phase[0] is not None:
self._data.manual_phase(*phase)

View File

@ -1,2 +1,2 @@
from .phase_dialog import QApodDialog, QPhasedialog
from .phase_dialog import QPreviewDialog
from .baseline_dialog import QBaselineDialog

View File

@ -1,119 +1,43 @@
from __future__ import annotations
import numpy as np
from pyqtgraph import mkPen
from numpy import inf, linspace
from numpy.fft import fft, fftfreq, fftshift
import numpy as np
from numpy import pi
from numpy.fft import fft, fftshift, fftfreq
from nmreval.data import FID, Spectrum
from ...lib.pg_objects import PlotItem, LogInfiniteLine
from nmreval.lib.importer import find_models
from nmreval.math import apodization as apodization
from nmreval.utils.text import convert
from ...Qt import QtCore, QtWidgets
from ...Qt import QtCore, QtWidgets, QtGui
from ..._py.apod_dialog import Ui_ApodEdit
from ..._py.phase_corr_dialog import Ui_SignalEdit
from ...lib.forms import FormWidget
class QPreviewDialogs(QtWidgets.QDialog):
class QPreviewDialog(QtWidgets.QDialog, Ui_ApodEdit):
finished = QtCore.pyqtSignal(str, tuple)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.data = []
self.graphs = []
self.mode = ''
def setRange(self, xlim: list, ylim: list, logmode: list[bool]):
self.graphicsView.getPlotItem().setLogMode(x=logmode[0], y=logmode[1])
if logmode[0]:
xlim = [np.log10(x) for x in xlim]
if logmode[1]:
ylim = [np.log10(y) for y in ylim]
self.graphicsView.setRange(xRange=xlim, yRange=ylim, padding=0, disableAutoRange=True)
def add_data(self, x, y):
self.data.append((x, y))
real_plt = PlotItem(x=x, y=y.real, pen=mkPen('b'), )
imag_plt = PlotItem(x=x, y=y.imag, pen=mkPen('r'))
self.graphs.append((real_plt, imag_plt))
self.graphicsView.addItem(real_plt)
self.graphicsView.addItem(imag_plt)
def done(self, val):
self.cleanup()
super().done(val)
def close(self):
self.cleanup()
super().close()
def accept(self):
self.finished.emit(self.mode, self.get_value())
super().accept()
def get_value(self):
raise NotImplementedError
def cleanup(self):
self.blockSignals(True)
for line in self.graphs:
for g in line:
self.graphicsView.removeItem(g)
del g
self.graphicsView.clear()
self.data = []
self.graphs = []
self.blockSignals(False)
class QPhasedialog(QPreviewDialogs, Ui_SignalEdit):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self.mode = 'ph'
self.data = []
self.graphs = []
self._tmp_data_bl = []
self._tmp_data_zf = []
self._tmp_data_ls = []
self._tmp_data_ap = []
self._tmp_data_ph = []
self.pvt_line = LogInfiniteLine(pos=0, movable=True)
self.graphicsView.addItem(self.pvt_line)
self.freq_graph.addItem(self.pvt_line)
self.pvt_line.sigPositionChanged.connect(self.move_line)
@QtCore.pyqtSlot(float, name='on_ph1slider_valueChanged')
@QtCore.pyqtSlot(float, name='on_ph0slider_valueChanged')
def _temp_phase(self, *args):
ph0, ph1, pvt = self.get_value()
self.pvt_line.setValue(pvt)
self.ls_lineedit.hide()
for i, (x, y) in enumerate(self.data):
phasecorr = np.exp(-1j * (ph0 + ph1*(x-pvt)/np.max(x))*np.pi/180.)
_y = y * phasecorr
self.graphs[i][0].setData(x=x, y=_y.real)
self.graphs[i][1].setData(x=x, y=_y.imag)
def get_value(self):
return float(self.ph0slider.text()), float(self.ph1slider.text()), float(self.pivot_lineedit.text())
def move_line(self, evt):
self.pivot_lineedit.setText(f'{evt.value():.5g}')
class QApodDialog(QPreviewDialogs, Ui_ApodEdit):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self._limits = (-inf, inf), -inf
self.apods = []
self.apods = find_models(apodization)
self.apodcombobox.blockSignals(True)
@ -122,72 +46,246 @@ class QApodDialog(QPreviewDialogs, Ui_ApodEdit):
self.apodcombobox.blockSignals(False)
self.apod_graph = PlotItem(x=[], y=[])
self.graphicsView.addItem(self.apod_graph)
self.time_graph.addItem(self.apod_graph)
self.mode = 'ap'
for g in [self.freq_graph, self.time_graph]:
pl = g.getPlotItem()
pl.hideButtons()
pl.setMenuEnabled(False)
self._all_time = None
self._all_freq = None
self.change_apodization(0)
def add_data(self, x, y):
real_plt = PlotItem(x=x, y=y.real, pen=mkPen('b'))
# imag_plt = (x=x, y=y.imag, pen=pg.mkPen('r'))
self.graphicsView.addItem(real_plt)
# self.graphicsView.addItem(imag_plt)
self.shift_box.clicked.connect(self._update_shift)
self.ls_spinbox.valueChanged.connect(self._update_shift)
self.ls_lineedit.setValidator(QtGui.QDoubleValidator())
self.ls_lineedit.textChanged.connect(self._update_shift)
self.zerofill_box.clicked.connect(self._update_zf)
self.zf_spinbox.valueChanged.connect(self._update_zf)
self.apod_box.clicked.connect(self._update_apod)
self.phase_box.clicked.connect(self._update_phase)
self.ph0_spinbox.valueChanged.connect(self._update_phase)
self.ph1_spinbox.valueChanged.connect(self._update_phase)
self.pivot_lineedit.setValidator(QtGui.QDoubleValidator())
self.pivot_lineedit.textChanged.connect(self._update_phase)
self.pivot_lineedit.textEdited.connect(lambda x: self.pvt_line.setValue(float(x)))
def add_data(self: QPreviewDialog, data: FID | Spectrum) -> bool:
if isinstance(data, FID):
if self._all_freq:
msg = QtWidgets.QMessageBox.warning(self, 'Mixed types',
'Timesignals and spectra cannot be edited at the same time.')
return False
else:
self._all_time = True
self._all_freq = False
elif isinstance(data, Spectrum):
if self._all_time:
msg = QtWidgets.QMessageBox.warning(self, 'Mixed types',
'Timesignals and spectra cannot be edited at the same time.')
return False
else:
self._all_time = False
self._all_freq = True
fid = data.copy()
spec = self._temp_fft(fid.x, fid.y)
x_len = data.x.size
self.zf_spinbox.setMaximum(min(2**17//x_len, 3))
real_plt = PlotItem(x=fid.x, y=fid.y.real, pen=mkPen('b'))
imag_plt = PlotItem(x=fid.x, y=fid.y.imag, pen=mkPen('r'))
self.time_graph.addItem(imag_plt)
self.time_graph.addItem(real_plt)
real_plt_fft = PlotItem(x=spec[0], y=spec[1].real, pen=mkPen('b'))
imag_plt_fft = PlotItem(x=spec[0], y=spec[1].imag, pen=mkPen('r'))
self.freq_graph.addItem(imag_plt_fft)
self.freq_graph.addItem(real_plt_fft)
self.data.append(data)
for p in [self._tmp_data_bl, self._tmp_data_ls]:
p.append(data.y.copy())
for p in [self._tmp_data_zf, self._tmp_data_ap]:
p.append((data.x, data.y.copy()))
self._tmp_data_ph.append((data.x, data.y, spec[0], spec[1]))
self.graphs.append((real_plt, imag_plt, real_plt_fft, imag_plt_fft))
return True
@QtCore.pyqtSlot(name='on_baseline_box_clicked')
def _update_bl(self):
if self.baseline_box.isChecked():
for y in self._tmp_data_bl:
y -= y[int(-0.12*y.size):].mean()
else:
for i, d in enumerate(self.data):
self._tmp_data_bl[i] = d.y.copy()
self._update_shift()
def _update_shift(self):
if self.shift_box.isChecked():
if self.ls_combobox.currentIndex() == 0:
num_points = self.ls_spinbox.value()
is_time = False
else:
num_points = float(self.ls_lineedit.text())
is_time = True
for i, y in enumerate(self._tmp_data_bl):
self._tmp_data_ls[i] = self._temp_leftshift(self.data[i].dx, y, num_points, is_time)
else:
for i, y in enumerate(self._tmp_data_bl):
self._tmp_data_ls[i] = y
self._update_zf()
def _update_zf(self):
zf_padding = self.zf_spinbox.value()
if self.zerofill_box.isChecked():
for i, y in enumerate(self._tmp_data_ls):
self._tmp_data_zf[i] = self._temp_zerofill(self.data[i].x, y, zf_padding)
else:
for i, y in enumerate(self._tmp_data_ls):
self._tmp_data_zf[i] = self.data[i].x, y
self._update_apod()
def _update_apod(self):
if self.apod_box.isChecked():
model = self.apods[self.apodcombobox.currentIndex()]
p = self._get_parameter()
x_limit = np.inf, -np.inf
y_limit = -np.inf
for i, (x, y) in enumerate(self._tmp_data_zf):
self._tmp_data_ap[i] = x, y * model.apod(x, *p)
y_limit = max(y.real.max(), y_limit)
x_limit = min(x_limit[0], x.min()), max(x_limit[1], x.max())
_x_apod = np.linspace(*x_limit, num=150)
_y_apod = model.apod(_x_apod, *p)
self.apod_graph.setData(x=_x_apod, y=y_limit * _y_apod)
self.apod_graph.show()
else:
for i, (x, y) in enumerate(self._tmp_data_zf):
self._tmp_data_ap[i] = x, y
self.apod_graph.hide()
self._update_phase()
def _update_phase(self):
if self.phase_box.isChecked():
pvt = float(self.pivot_lineedit.text())
self.pvt_line.show()
ph0 = self.ph0_spinbox.value()
ph1 = self.ph1_spinbox.value()
for i, (x, y) in enumerate(self._tmp_data_ap):
x_fft, y_fft = self._temp_fft(x, y)
if ph0 != 0:
y = self._temp_phase(x, y, ph0, 0, 0)
y_fft = self._temp_phase(x, y_fft, ph0, ph1, pvt)
elif ph1 != 0:
y_fft = self._temp_phase(x, y_fft, ph0, ph1, pvt)
self._tmp_data_ph[i] = x, y, x_fft, y_fft
else:
self.pvt_line.hide()
for i, (x, y) in enumerate(self._tmp_data_ap):
self._tmp_data_ph[i] = x, y, *self._temp_fft(x, y)
self._update_plots()
def _update_plots(self):
for i, (x, y, xf, yf) in enumerate(self._tmp_data_ph):
self.graphs[i][0].setData(x=x, y=y.real)
self.graphs[i][1].setData(x=x, y=y.imag)
self.graphs[i][2].setData(x=xf, y=yf.real)
self.graphs[i][3].setData(x=xf, y=yf.imag)
@staticmethod
def _temp_phase(x: np.ndarray, y: np.ndarray, ph0: float, ph1: float, pvt: float) -> np.ndarray:
phase_correction = np.exp(-1j * (ph0 + ph1 * (x - pvt) / x.max()) * pi / 180.)
_y = y * phase_correction
return _y
@staticmethod
def _temp_zerofill(x: np.ndarray, y: np.ndarray, num_padding: int) -> tuple[np.ndarray, np.ndarray]:
length = x.size
factor = 2**num_padding
_y = np.r_[y, np.zeros((factor-1) * length)]
_temp_x = np.arange(1, (factor-1) * length+1) * (x[1]-x[0]) + np.max(x)
_x = np.r_[x, _temp_x]
return _x, _y
@staticmethod
def _temp_leftshift(dx: np.ndarray, y: np.ndarray, points: float | int, is_time: bool) -> np.ndarray:
if is_time:
points = int(points//dx)
_y = np.roll(y, -points)
_y[-points-1:] = 0
return _y
@staticmethod
def _temp_fft(x: np.ndarray, y: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
y_fft = fftshift(fft(y))
x_fft = fftshift(fftfreq(len(x), d=x[1]-x[0]))
real_plt_fft = PlotItem(x=x_fft, y=y_fft.real, pen=mkPen('b'))
# imag_plt_fft = pg.PlotDataItem(x=x_fft, y=y_fft.imag, pen=pg.mkPen('b'))
self.graphicsView_2.addItem(real_plt_fft)
# self.graphicsView_2.addItem(imag_plt_fft)
self.graphs.append((real_plt, real_plt_fft))
self.data.append((x, y, x_fft))
return x_fft, y_fft
xlimits = (max(x.min(), self._limits[0][0]), min(x.max(), self._limits[0][1]))
ylimit = max(self._limits[1], y.real.max())
self._limits = xlimits, ylimit
def move_line(self, evt):
self.pivot_lineedit.setText(f'{evt.value():.5g}')
@QtCore.pyqtSlot(int, name='on_apodcombobox_currentIndexChanged')
def change_apodization(self, index):
def change_apodization(self, index: int) -> None:
# delete old widgets
self.eqn_label.setText(convert(self.apods[index].equation))
while self.widget_layout.count():
item = self.widget_layout.takeAt(0)
if isinstance(item, FormWidget):
item.disconnect()
try:
item.widget().deleteLater()
except AttributeError:
pass
# set up parameter widgets for new model
for k, v in enumerate(self.apods[index]().params):
widgt = FormWidget(name=v)
widgt.valueChanged.connect(self._temp_apod)
self.widget_layout.addWidget(widgt)
for k, v in enumerate(self.apods[index].params):
widget = FormWidget(name=v)
widget.value = 1
widget.valueChanged.connect(self._update_apod)
self.widget_layout.addWidget(widget)
self.widget_layout.addStretch()
self._temp_apod()
def _temp_apod(self):
apodmodel = self.apods[self.apodcombobox.currentIndex()]
p = self._get_parameter()
if self.data:
for i, (x, y, x_fft) in enumerate(self.data):
y2 = apodmodel.apod(x, *p)
_y = y2 * y
self.graphs[i][0].setData(x=x, y=_y.real)
# self.graphs[i][1].setData(y=_y.imag)
y_fft = fftshift(fft(_y))
self.graphs[i][1].setData(x=x_fft, y=y_fft.real)
# self.graphs[i][3].setData(y=y_fft.imag)
_x_apod = linspace(self._limits[0][0], self._limits[0][1])
try:
_y_apod = apodmodel.apod(_x_apod, *p)
self.apod_graph.setData(x=_x_apod, y=self._limits[1]*_y_apod)
except IndexError:
pass
self._update_apod()
def _get_parameter(self):
p = []
@ -201,8 +299,78 @@ class QApodDialog(QPreviewDialogs, Ui_ApodEdit):
return p
def get_value(self):
apodmodel = self.apods[self.apodcombobox.currentIndex()]
p = self._get_parameter()
@QtCore.pyqtSlot(int, name='on_ls_combobox_currentIndexChanged')
def change_ls(self, idx: int) -> None:
self.ls_lineedit.setVisible(bool(idx))
self.ls_spinbox.setVisible(not bool(idx))
return p, apodmodel
@QtCore.pyqtSlot(bool, name='on_ft_checkbox_stateChanged')
def change_ft(self, state: bool):
self.ph1_spinbox.setEnabled(state)
self.pivot_lineedit.setEnabled(state)
def cleanup(self):
self.blockSignals(True)
for line in self.graphs:
for g in line:
self.time_graph.removeItem(g)
self.freq_graph.removeItem(g)
del g
self.time_graph.clear()
self.freq_graph.clear()
self._tmp_data_ap = []
self._tmp_data_bl = []
self._tmp_data_ls = []
self._tmp_data_ph = []
self._tmp_data_zf = []
self.data = []
self.graphs = []
self.blockSignals(False)
def get_value(self):
edits = [(None,), (None,), (None,), (None,), (None,), (None,)]
if self.baseline_box.isChecked():
edits[0] = (True,)
if self.zerofill_box.isChecked():
edits[2] = (self.zf_spinbox.value(),)
if self.shift_box.isChecked():
if self.ls_combobox.currentIndex() == 0:
edits[1] = (self.ls_spinbox.value(), 'pts')
else:
edits[1] = (float(self.ls_lineedit.text()), 'time')
if self.apod_box.isChecked():
edits[3] = (self._get_parameter(), self.apods[self.apodcombobox.currentIndex()])
if self.phase_box.isChecked():
edits[4] = (self.ph0_spinbox.value(), self.ph1_spinbox.value(), float(self.pivot_lineedit.text()))
if self.ft_box.isChecked():
edits[5] = (self.phase_before_button.isChecked(),)
return edits
def exec(self):
self._prepare_ui()
return super().exec()
def _prepare_ui(self):
"""Stuff we have to do before showing the window but after all the data was added"""
vb = self.freq_graph.getPlotItem().getViewBox()
vb.disableAutoRange(axis=vb.YAxis)
vb = self.time_graph.getPlotItem().getViewBox()
vb.disableAutoRange(axis=vb.YAxis)
self.zerofill_box.setVisible(self._all_time)
self.apod_box.setVisible(self._all_time)
self.shift_box.setVisible(self._all_time)

View File

@ -87,6 +87,21 @@ class ShiftCommand(QtWidgets.QUndoCommand):
self.__data.apply('ls', self.__args)
class EditCommand(QtWidgets.QUndoCommand):
def __init__(self, data, *args):
super().__init__('Edit signal')
self.__data = data
self.__arguments = args
self.__original = copy.deepcopy(self.__data.data)
def undo(self):
self.__data.data = copy.deepcopy(self.__original)
def redo(self):
self.__data.edit_signal(*self.__arguments)
class NormCommand(QtWidgets.QUndoCommand):
def __init__(self, data, mode):
super().__init__('Normalize')

View File

@ -15,7 +15,7 @@ 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 ..data.signaledit import QPreviewDialog, QBaselineDialog
from ..dsc.glass_dialog import TgCalculator
from ..fit.result import FitExtension, QFitResult
from ..graphs.graphwindow import QGraphWindow
@ -164,7 +164,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
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.action_edit.triggered.connect(self.do_preview)
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'))
@ -214,7 +214,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
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.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)
@ -784,29 +784,22 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
editor.finished.connect(self.management.apply)
editor.exec()
@QtCore.pyqtSlot(str)
def do_preview(self, mode):
@QtCore.pyqtSlot()
def do_preview(self):
dialog = QPreviewDialog(self)
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)
success = True
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 data_mode in ('fid', 'spectrum'):
success = dialog.add_data(self.management[sid].data)
if tobeadded:
dialog.add_data(*self.management.get_data(sid, xy_only=True))
if not success:
break
if dialog.exec() == QtWidgets.QDialog.Accepted:
self.management.apply(mode, dialog.get_value())
if success and dialog.exec() == QtWidgets.QDialog.Accepted:
self.management.edit_signals(dialog.get_value())
@QtCore.pyqtSlot(name='on_actionMove_between_plots_triggered')
def move_sets_dialog(self):

View File

@ -418,6 +418,13 @@ class UpperManagement(QtCore.QObject):
self.undostack.push(single_undo)
self.undostack.endMacro()
def edit_signals(self: UpperManagement, args: list[tuple]) -> None:
self.undostack.beginMacro('Edit signals')
for sid in self.graphs[self.current_graph]:
single_undo = EditCommand(self.data[sid], *args)
self.undostack.push(single_undo)
self.undostack.endMacro()
def cut(self):
if self.current_graph:
xlim, _ = self.graphs[self.current_graph].ranges

View File

@ -62,6 +62,10 @@ class FID(Signal):
return self
def manual_phase(self, ph0: float = 0., ph1: float = 0., pvt: float = 0):
"""FID knows only how to phase correct in zeroth order"""
super().manual_phase(ph0=ph0)
def fourier(self) -> 'Spectrum':
ft = np.fft.fftshift(np.fft.fft(self._y)) / self.dx
freq = np.fft.fftshift(np.fft.fftfreq(self.length, self.dx))

View File

@ -319,20 +319,21 @@ class Points:
if pts is None:
pts = []
for x in idx:
if isinstance(x, tuple):
x_idx = np.argmin(np.abs(self._x[self.mask] - (x[0]+x[1])/2))
left_b = np.argmin(np.abs(self._x[self.mask] - x[0]))
right_b = np.argmin(np.abs(self._x[self.mask] - x[1]))
else:
x_idx = np.argmin(np.abs(self._x[self.mask]-x))
left_b = int(max(0, x_idx - avg_range[0]))
right_b = int(min(len(self), x_idx + avg_range[1] + 1))
if idx is not None:
for x in idx:
if isinstance(x, tuple):
x_idx = np.argmin(np.abs(self._x[self.mask] - (x[0]+x[1])/2))
left_b = np.argmin(np.abs(self._x[self.mask] - x[0]))
right_b = np.argmin(np.abs(self._x[self.mask] - x[1]))
else:
x_idx = np.argmin(np.abs(self._x[self.mask]-x))
left_b = int(max(0, x_idx - avg_range[0]))
right_b = int(min(len(self), x_idx + avg_range[1] + 1))
if left_b < right_b:
pts.append([self._x[x_idx], *self._average(avg_mode, x_idx, left_b, right_b)])
else:
pts.append([self._x[x_idx], self._y[x_idx], self._y_err[x_idx]])
if left_b < right_b:
pts.append([self._x[x_idx], *self._average(avg_mode, x_idx, left_b, right_b)])
else:
pts.append([self._x[x_idx], self._y[x_idx], self._y_err[x_idx]])
if special is not None:
if special not in ['max', 'min', 'absmax', 'absmin']:

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>784</width>
<height>484</height>
<width>1144</width>
<height>655</height>
</rect>
</property>
<property name="sizePolicy">
@ -21,22 +21,22 @@
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>3</number>
<number>9</number>
</property>
<property name="topMargin">
<number>3</number>
<number>9</number>
</property>
<property name="rightMargin">
<number>3</number>
<number>9</number>
</property>
<property name="bottomMargin">
<number>3</number>
<number>9</number>
</property>
<property name="spacing">
<number>3</number>
</property>
<item row="2" column="0">
<widget class="NMRPlotWidget" name="graphicsView">
<item row="2" column="1">
<widget class="NMRPlotWidget" name="time_graph">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -45,30 +45,10 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="NMRPlotWidget" name="graphicsView_2"/>
<item row="2" column="2">
<widget class="NMRPlotWidget" name="freq_graph"/>
</item>
<item row="0" column="0">
<widget class="QComboBox" name="apodcombobox">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="widget_layout">
<property name="spacing">
<number>20</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
</layout>
</item>
<item row="4" column="0" colspan="2">
<item row="4" column="1" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -78,20 +58,361 @@
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="eqn_label">
<item row="2" column="0">
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TextLabel</string>
</property>
<property name="indent">
<number>3</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="baseline_box">
<property name="text">
<string>Baseline</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="shift_box">
<property name="title">
<string>Shift</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<property name="spacing">
<number>3</number>
</property>
<item row="1" column="1">
<widget class="QLineEdit" name="ls_lineedit">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="ls_spinbox">
<property name="maximum">
<number>999999</number>
</property>
</widget>
</item>
<item row="0" column="0" rowspan="2">
<widget class="QComboBox" name="ls_combobox">
<item>
<property name="text">
<string>Points</string>
</property>
</item>
<item>
<property name="text">
<string>Seconds</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="apod_box">
<property name="title">
<string>Apodization</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QComboBox" name="apodcombobox"/>
</item>
<item>
<widget class="QLabel" name="eqn_label">
<property name="text">
<string>TextLabel</string>
</property>
<property name="indent">
<number>3</number>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="widget_layout">
<property name="spacing">
<number>20</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="zerofill_box">
<property name="title">
<string>Zero fill</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Double length</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="zf_spinbox">
<property name="suffix">
<string>x</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>3</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="phase_box">
<property name="title">
<string>Phase correction</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<property name="spacing">
<number>3</number>
</property>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Phase 1</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Pivot</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="ph0_spinbox">
<property name="wrapping">
<bool>true</bool>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>-180.000000000000000</double>
</property>
<property name="maximum">
<double>180.000000000000000</double>
</property>
<property name="singleStep">
<double>0.500000000000000</double>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="pivot_lineedit">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Phase 0</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="ph1_spinbox">
<property name="wrapping">
<bool>true</bool>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>-720.000000000000000</double>
</property>
<property name="maximum">
<double>720.000000000000000</double>
</property>
<property name="singleStep">
<double>0.050000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="ft_box">
<property name="title">
<string>Fourier transform</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QRadioButton" name="phase_before_button">
<property name="text">
<string>before phase correction</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="phase_after_button">
<property name="text">
<string>after phase correction</string>
</property>
<attribute name="buttonGroup">
<string notr="true">buttonGroup</string>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
@ -138,4 +459,7 @@
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="buttonGroup"/>
</buttongroups>
</ui>