more interactive edit
This commit is contained in:
@ -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)
|
||||
|
Reference in New Issue
Block a user