1
0
forked from IPKM/nmreval

BUGFIX: VFT;

change to src layout
This commit is contained in:
dominik
2022-10-20 17:23:15 +02:00
parent 89ce4bab9f
commit 8d148b639b
445 changed files with 1387 additions and 1920 deletions

View File

View File

@ -0,0 +1,682 @@
from __future__ import annotations
from collections import OrderedDict
from itertools import cycle
from typing import Any
import numpy as np
from pyqtgraph import mkPen
from nmreval.data.points import Points
from nmreval.data.signals import Signal
from nmreval.utils.text import convert
from nmreval.data.bds import BDS
from nmreval.lib.colors import BaseColor, TUColors
from nmreval.lib.lines import LineStyle
from nmreval.lib.symbols import SymbolStyle, symbolcycle
from nmreval.data.nmr import Spectrum, FID
from ..Qt import QtCore, QtGui
from ..io.exporters import GraceExporter
from ..lib.decorators import plot_update
from ..lib.pg_objects import ErrorBars, PlotItem
class ExperimentContainer(QtCore.QObject):
dataChanged = QtCore.pyqtSignal(str)
labelChanged = QtCore.pyqtSignal(str, str)
groupChanged = QtCore.pyqtSignal(str, str)
colors = cycle(TUColors)
def __init__(self, identifier, data, **kwargs):
super().__init__()
self.id = str(identifier)
self._fits = []
self._data = data
self._manager = kwargs.get('manager')
self.graph = ''
self.mode = 'point'
self.plot_real = None
self.plot_imag = None
self.plot_error = None
self.actions = {}
self._update_actions()
def _init_plot(self):
raise NotImplementedError
def __getitem__(self, item):
try:
return self._data[item]
except KeyError:
raise KeyError('Unknown key %s' % str(item)) from None
def __del__(self):
del self._data
del self.plot_real
del self.plot_imag
del self.plot_error
def __repr__(self):
return 'name:' + self.name
def __len__(self):
return len(self._data)
def copy(self, full: bool = False, keep_color: bool = True):
if full:
pen_dict = {}
if keep_color:
pen_dict = {
'symbol': self.plot_real.symbol,
'symbolcolor': self.plot_real.symbolcolor,
'symbolsize': self.plot_real.symbolsize,
'linestyle': self.plot_real.linestyle,
'linecolor': self.plot_real.linecolor,
'linewidth': self.plot_real.linewidth,
}
new_data = type(self)(str(self.id), self._data.copy(), manager=self._manager, **pen_dict)
new_data.mode = self.mode
if keep_color and self.plot_imag is not None:
new_data.plot_imag.set_symbol(symbol=self.plot_imag.symbol, size=self.plot_imag.symbolsize,
color=self.plot_imag.symbolcolor)
new_data.plot_imag.set_line(style=self.plot_imag.linestyle, width=self.plot_imag.linewidth,
color=self.plot_imag.linecolor)
return new_data
else:
return self._data.copy()
def change_type(self, data):
if isinstance(data, (FID, Spectrum, BDS)):
new_type = SignalContainer
elif isinstance(data, Points):
new_type = PointContainer
else:
raise TypeError('Unknown data type')
# pen_dict = {
# 'symbol': self.plot_real.symbol,
# 'symbolcolor': self.plot_real.symbolcolor,
# 'symbolsize': self.plot_real.symbolsize,
# 'linestyle': self.plot_real.linestyle,
# 'linecolor': self.plot_real.linecolor,
# 'linewidth': self.plot_real.linewidth,
# }
new_data = new_type(str(self.id), data, manager=self._manager)
# if new_data.plot_imag is not None:
# if self.plot_imag is not None:
# new_data.plot_imag.set_symbol(symbol=self.plot_imag.symbol, size=self.plot_imag.symbolsize,
# color=self.plot_imag.symbolcolor)
# new_data.plot_imag.set_line(style=self.plot_imag.linestyle, width=self.plot_imag.linewidth,
# color=self.plot_imag.linecolor)
# else:
# new_data.plot_imag.set_symbol(symbol=self.plot_real.symbol, size=self.plot_real.symbolsize,
# color=self.plot_real.symbolcolor)
# new_data.plot_imag.set_line(style=self.plot_real.linestyle, width=self.plot_real.linewidth,
# color=self.plot_real.linecolor)
return new_data
@property
def x(self):
return self._data.x[self._data.mask]
@x.setter
@plot_update
def x(self, value):
if len(self._data.x) == len(value):
self._data.x = value
elif len(self._data.x[self._data.mask]) == len(value):
self._data.x = value
self._data.y = self._data.y[self._data.mask]
self._data.mask = np.ma.array(np.ones_like(self._data.x, dtype=bool))
else:
raise ValueError('x and y have different dimensions!')
@property
def y(self):
return self._data.y[self._data.mask]
@y.setter
@plot_update
def y(self, value):
if len(self._data.y) == len(value):
self._data.y = value
elif len(self._data.y[self._data.mask]) == len(value):
self._data.y = value
self._data.x = self._data.x[self._data.mask]
self._data.mask = np.ma.array(np.ones_like(self._data.y, dtype=bool))
else:
raise ValueError('x and y have different dimensions!')
@property
def y_err(self):
return self._data.y_err[self._data.mask]
@y_err.setter
@plot_update
def y_err(self, value):
if len(self._data.y_err) == len(value):
self._data.y_err = value
elif len(self._data.y[self._data.mask]) == len(value):
self._data.y_err[self._data.mask] = value
else:
raise ValueError('y_err has not correct length')
@property
def name(self):
return self._data.name
@name.setter
@plot_update
def name(self, value: str):
self._data.name = value
self.plot_real.opts['name'] = value
try:
self.plot_imag.opts['name'] = value
except AttributeError:
pass
try:
num_val = float(value)
self._data.value = num_val
except ValueError:
pass
@property
def value(self):
return self._data.value
@value.setter
def value(self, val):
self._data.value = float(val)
@property
def group(self):
return str(self._data['group'])
@group.setter
def group(self, valium):
self._data['group'] = str(valium)
self.groupChanged.emit(self.id, str(valium))
@property
def data(self):
return self._data
@data.setter
@plot_update
def data(self, new_data):
self._data = new_data
self._update_actions()
@property
def opts(self):
return self._data.meta
@property
def plots(self):
return self.plot_real, self.plot_imag, self.plot_error
def get_state(self):
ret_dic = {
'id': self.id,
'data': self._data.get_state(),
'mode': self.mode,
'fits': self._fits,
'real': ({'symbol': self.plot_real.symbol.value,
'size': self.plot_real.symbolsize,
'color': self.plot_real.symbolcolor.value},
{'style': self.plot_real.linestyle.value,
'width': self.plot_real.linewidth,
'color': self.plot_real.linecolor.value})
}
if self.plot_imag is not None:
ret_dic['imag'] = ({'symbol': self.plot_imag.symbol.value,
'size': self.plot_imag.symbolsize,
'color': self.plot_imag.symbolcolor.value},
{'style': self.plot_imag.linestyle.value,
'width': self.plot_imag.linewidth,
'color': self.plot_imag.linecolor.value})
return ret_dic
def get_fits(self):
return [self._manager[idx] for idx in self._fits]
def has_fits(self):
return len(self._fits) != 0
def set_fits(self, value: str | list, replace: bool = False):
if isinstance(value, str):
value = [value]
if replace:
if isinstance(value, list):
self._fits = value
else:
raise TypeError()
else:
self._fits.extend(value)
def _update_actions(self):
self.actions.update({'sort': self._data.sort,
'cut': self._data.cut,
'norm': self._data.normalize,
'center': self.center})
@plot_update
def update(self, opts: dict):
self._data.update(opts)
def get_properties(self) -> dict:
props = OrderedDict()
props['General'] = OrderedDict([('Name', self.name),
('Value', str(self.value)),
('Group', str(self.group))])
props['Symbol'] = OrderedDict()
props['Line'] = OrderedDict()
props['Symbol']['Symbol'] = self.plot_real.symbol
props['Symbol']['Size'] = self.plot_real.symbolsize
props['Symbol']['Color'] = self.plot_real.symbolcolor
props['Line']['Style'] = self.plot_real.linestyle
props['Line']['Width'] = self.plot_real.linewidth
props['Line']['Color'] = self.plot_real.linecolor
if self.plot_imag is not None:
props['Symbol']['Symbol (imag)'] = self.plot_imag.symbol
props['Symbol']['Size (imag)'] = self.plot_imag.symbolsize
props['Symbol']['Color (imag)'] = self.plot_imag.symbolcolor
props['Line']['Style (imag)'] = self.plot_imag.linestyle
props['Line']['Width (imag)'] = self.plot_imag.linewidth
props['Line']['Color (imag)'] = self.plot_imag.linecolor
return props
def setColor(self, color, symbol=False, line=False, mode='real'):
if mode in ['real', 'all']:
self.plot_real.set_color(color, symbol=symbol, line=line)
if self.plot_error is not None:
err_pen = self.plot_error.opts['pen']
err_pen.setColor(QtGui.QColor(*self.plot_real.symbolcolor.rgb()))
self.plot_error.setData(pen=err_pen)
elif mode == 'imag' and self.plot_imag is not None:
self.plot_imag.set_color(color, symbol=symbol, line=line)
else:
print('Updating color failed for ' + str(self.id))
def setSymbol(self, symbol=None, color=None, size=None, mode='real'):
if mode in ['real', 'all']:
self.plot_real.set_symbol(symbol=symbol, size=size, color=color)
if color is not None and self.plot_error is not None and self.plot_real.symbol != SymbolStyle.No:
err_pen = self.plot_error.opts['pen']
err_pen.setColor(QtGui.QColor(*self.plot_real.symbolcolor.rgb()))
self.plot_error.setData(pen=err_pen)
elif mode in ['imag', 'all'] and self.plot_imag is not None:
self.plot_imag.set_symbol(symbol=symbol, size=size, color=color)
else:
print('Updating symbol failed for ' + str(self.id))
def setLine(self, width=None, style=None, color=None, mode='real'):
if mode in ['real', 'all']:
self.plot_real.set_line(width=width, style=style, color=color)
if color is not None and self.plot_error is not None and self.plot_real.symbol == SymbolStyle.No:
err_pen = self.plot_error.opts['pen']
err_pen.setColor(QtGui.QColor(*self.plot_real.linecolor.rgb()))
self.plot_error.setData(pen=err_pen)
elif mode in ['imag', 'all'] and self.plot_imag is not None:
self.plot_imag.set_line(width=width, style=style, color=color)
else:
print('Updating line failed for ' + str(self.id))
def update_property(self, key1: str, key2: str, value: Any):
keykey = key2.split()
if len(keykey) == 1:
if key1 == 'Symbol':
self.setSymbol(mode='real', **{key2.lower(): value})
elif key1 == 'Line':
self.setLine(mode='real', **{key2.lower(): value})
elif key1 == 'General':
setattr(self, key2.lower(), value)
else:
if key1 == 'Symbol':
self.setSymbol(mode='imag', **{keykey[0].lower(): value})
elif key1 == 'Line':
self.setLine(mode='imag', **{keykey[0].lower(): value})
def points(self, params: dict):
return self._data.points(**params)
@plot_update
def apply(self, func: str, args: tuple):
if func in self.actions:
f = self.actions[func]
f(*args)
return self
@plot_update
def unsort(self, order: np.ndarray):
# this exists only to update plots after an undo action
self._data.x = self._data.x[order]
self._data.y = self._data.y[order]
self._data.y_err = self._data.y_err[order]
self._data.mask = self._data.mask[order]
def save(self, fname):
ext = fname.suffix
if ext == '.agr':
dic = self._manager.graphs[self.graph].get_state()
dic['items'] = [self.plot_real.get_data_opts()]
if self.plot_imag is not None:
dic['items'].append(self.plot_imag.get_data_opts())
GraceExporter(dic).export(fname)
elif ext in ['.dat', '.txt']:
self._data.savetxt(fname, err=True)
else:
raise ValueError('Unknown extension ' + ext)
@plot_update
def setvalues(self, pos, valium):
xy, position = pos
if xy == 0:
self._data.x[position] = valium
elif xy == 1:
self._data.y[position] = valium
else:
self._data.y_err[position] = valium
@property
def mask(self):
return self._data.mask
@mask.setter
@plot_update
def mask(self, m):
self._data.mask = np.asarray(m, dtype=bool)
@plot_update
def add(self, m):
if isinstance(m, (np.ndarray, list, tuple)):
self._data.append(m[0], m[1], y_err=m[2])
elif isinstance(m, (Points, ExperimentContainer)):
self._data.append(m.x, m.y, y_err=m.y_err)
else:
raise TypeError('Unknown type ' + type(m))
@plot_update
def remove(self, m):
self._data.remove(m)
@plot_update
def center(self) -> float:
offset = self.x[np.argmax(self.y.real)]
self._data._x -= offset
return offset
def get_namespace(self, i: int = None, j: int = None) -> dict:
if (i is None) and (j is None):
prefix = ''
else:
prefix = 'g[%i].s[%i].' % (i, j)
namespace = {prefix + 'x': (self.x, 'x values'),
prefix + 'y': [self.y, 'y values'],
prefix + 'y_err': (self.y_err, 'y error values'),
prefix + 'value': (self.value, str(self.value))}
if len(self._fits) == 1:
namespace.update({
"%sfit['%s']" % (prefix, convert(pname, old='tex', new='str')): (pvalue.value, str(pvalue.value))
for (pname, pvalue) in self._manager[self._fits[0]].parameter.items()
})
else:
for k, f in enumerate(self._fits):
namespace.update({
"%sfit['%s_%d']" % (prefix, convert(pname, old='tex', new='str'), k): (pvalue.value, str(pvalue.value))
for (pname, pvalue) in self._manager[f].parameter.items()
})
return namespace
def eval_expression(self, cmds, namespace):
namespace.update({'x': self.x, 'y': self.y, 'y_err': self.y_err, 'value': self.value})
if len(self._fits) == 1:
namespace.update({"fit['%s']" % (convert(pname, old='tex', new='str')): pvalue.value
for (pname, pvalue) in self._manager[self._fits[0]].parameter.items()})
else:
for k, f in enumerate(self._fits):
namespace.update({"fit['%s_%i']" % (convert(pname, old='tex', new='str'), k): pvalue.value
for (pname, pvalue) in self._manager[f].parameter.items()})
new_data = self.copy()
for c in cmds:
if c:
exec(c, globals(), namespace)
new_data.set_data(x=namespace['x'], y=namespace['y'], y_err=namespace['y_err'])
new_data.value = namespace['value']
return new_data
class PointContainer(ExperimentContainer):
symbols = symbolcycle()
def __init__(self, identifier, data, **kwargs):
super().__init__(identifier, data, **kwargs)
self.mode = 'pts'
self._init_plot(**kwargs)
def _init_plot(self, **kwargs):
self.plot_imag = None
color = kwargs.get('color', None)
symcolor = kwargs.get('symbolcolor', color)
linecolor = kwargs.get('linecolor', color)
if symcolor is None and linecolor is None:
color = next(self.colors)
symcolor = color
linecolor = color
elif symcolor is None:
symcolor = linecolor
elif linecolor is None:
linecolor = symcolor
sym_kwargs = {
'symbol': kwargs.get('symbol', None),
'size': kwargs.get('symbolsize', 10),
'color': symcolor
}
line_kwargs = {
'style': kwargs.get('linestyle', None),
'width': kwargs.get('linewidth', 1),
'color': linecolor
}
if sym_kwargs['symbol'] is None and line_kwargs['style'] is None:
if len(self._data) > 500:
line_kwargs['style'] = LineStyle.Solid
sym_kwargs['symbol'] = SymbolStyle.No
else:
line_kwargs['style'] = LineStyle.No
sym_kwargs['symbol'] = next(PointContainer.symbols)
self.plot_real = PlotItem(x=self._data.x, y=self._data.y, name=self.name,
symbol=None, pen=None, connect='finite')
self.setSymbol(mode='real', **sym_kwargs)
self.setLine(mode='real', **line_kwargs)
if sym_kwargs['symbol'] != SymbolStyle.No:
self.plot_error = ErrorBars(x=self._data.x, y=self._data.y, top=self._data.y_err, bottom=self._data.y_err,
pen=mkPen({'color': self.plot_real.symbolcolor.rgb()}))
else:
self.plot_error = ErrorBars(x=self._data.x, y=self._data.y, top=self._data.y_err, bottom=self._data.y_err,
pen=mkPen({'color': self.plot_real.linecolor.rgb()}))
class FitContainer(ExperimentContainer):
def __init__(self, identifier, data, **kwargs):
super().__init__(identifier, data, **kwargs)
self.fitted_key = kwargs.get('src', '')
self.mode = 'fit'
self.parent_set = kwargs.get('src', '')
self._init_plot(**kwargs)
for n in ['statistics', 'nobs', 'nvar', 'parameter', 'model_name']:
setattr(self, n, getattr(data, n))
def _init_plot(self, **kwargs):
color = kwargs.get('color', (0, 0, 0))
if isinstance(color, BaseColor):
color = color.rgb()
self.plot_real = PlotItem(x=self._data.x, y=self._data.y.real, name=self.name,
pen=mkPen({'color': color}),
connect='finite', symbol=None)
if np.iscomplexobj(self._data.y):
self.plot_imag = PlotItem(x=self._data.x, y=self._data.y.imag, name=self.name,
pen=mkPen({'color': color}),
connect='finite', symbol=None)
@property
def fitted_key(self):
return self._data.idx
@fitted_key.setter
def fitted_key(self, val):
self._data.idx = val
def get_namespace(self, i: int = None, j: int = None):
namespace = super().get_namespace(i, j)
namespace.update({
"g[%i].s[%i].fit['%s']" % (i, j, convert(pname, old='latex', new='plain')): (pvalue.value, str(pvalue.value))
for (pname, pvalue) in self._data.parameter.items()
})
return namespace
class SignalContainer(ExperimentContainer):
symbols = symbolcycle()
def __init__(self, identifier, data, symbol=None, **kwargs):
super().__init__(identifier, data, **kwargs)
self.mode = 'signal'
self._init_plot(symbol=symbol, **kwargs)
def _init_plot(self, **kwargs):
self.plot_real = PlotItem(x=self._data.x, y=self._data.y.real, name=self.name,
symbol=None, pen=None, connect='finite')
self.plot_imag = PlotItem(x=self._data.x, y=self._data.y.imag, name=self.name,
symbol=None, pen=None, connect='finite')
color = kwargs.get('color', None)
symcolor = kwargs.get('symbolcolor', color)
linecolor = kwargs.get('linecolor', color)
if symcolor is None and linecolor is None:
color = next(self.colors)
symcolor = color
linecolor = color
elif symcolor is None:
symcolor = linecolor
elif linecolor is None:
linecolor = symcolor
sym_kwargs = {
'symbol': kwargs.get('symbol', None),
'size': kwargs.get('symbolsize', 10),
'color': symcolor
}
line_kwargs = {
'style': kwargs.get('linestyle', None),
'width': kwargs.get('linewidth', 1),
'color': linecolor
}
if isinstance(self._data, BDS):
self.mode = 'bds'
if sym_kwargs['symbol'] is None and line_kwargs['style'] is None:
if len(self._data) <= 91:
sym_kwargs['symbol'] = next(PointContainer.symbols)
line_kwargs['style'] = LineStyle.No
else:
line_kwargs['style'] = LineStyle.Solid
sym_kwargs['symbol'] = SymbolStyle.No
elif isinstance(self._data, Signal):
if line_kwargs['style'] is None and sym_kwargs['symbol'] is None:
line_kwargs['style'] = LineStyle.Solid
sym_kwargs['symbol'] = SymbolStyle.No
if isinstance(self._data, FID):
self.mode = 'fid'
else:
self.mode = 'spectrum'
else:
raise TypeError('Unknown class %s, should be FID, Spectrum, or BDS.' % type(self._data))
for mode in ['real', 'imag']:
if mode == 'imag' and line_kwargs['style'] != LineStyle.No:
line_kwargs['style'] = LineStyle.Dashed
self.setSymbol(mode=mode, **sym_kwargs)
self.setLine(mode=mode, **line_kwargs)
def _update_actions(self):
super()._update_actions()
self.actions.update({'ph': self._data.manual_phase, 'bls': self._data.baseline_spline})
if isinstance(self._data, Spectrum):
self.actions.update({'bl': self._data.baseline, 'ls': self._data.shift,
'divide': self._data.divide, 'ft': self.fourier})
self.mode = 'spectrum'
elif isinstance(self._data, FID):
self.actions.update({'bl': self._data.baseline, 'ls': self._data.shift,
'zf': self._data.zerofill, 'divide': self._data.divide,
'ap': self._data.apod, 'ft': self.fourier})
self.mode = 'fid'
@plot_update
def fourier(self, mode='normal'):
if mode == 'normal':
self._data = self._data.fourier()
elif mode == 'depake':
try:
self._data = self._data.fft_depake()
except AttributeError:
return
self._update_actions()
return self

View File

@ -0,0 +1,152 @@
from ..Qt import QtWidgets, QtCore, QtGui
from .._py.typeconversion import Ui_Dialog
class ConversionDialog(QtWidgets.QDialog, Ui_Dialog):
convertSets = QtCore.pyqtSignal(list)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self.simple_table.installEventFilter(self)
self.merge_table.installEventFilter(self)
self._simple_table_dropevent = self.simple_table.dropEvent
self.simple_table.dropEvent = self._drop_on_simple_table
self._merge_table_dropevent = self.merge_table.dropEvent
self.merge_table.dropEvent = self._drop_on_simple_table
self.buttonGroup.buttonClicked.connect(self.change_table)
def set_graphs(self, graphs: dict):
self.set_list.clear()
self.simple_table.clear()
self.simple_table.setHorizontalHeaderLabels(['Simple', 'Type'])
self.merge_table.clear()
self.merge_table.setHorizontalHeaderLabels(['Real', 'Imag', 'Type'])
for graph, datasets in graphs.items():
for set_id, set_name in datasets:
item_name = set_name + ' (' + graph[1] + ')'
item1 = QtWidgets.QListWidgetItem(item_name)
item1.setData(QtCore.Qt.UserRole, set_id)
item1.setData(QtCore.Qt.UserRole+1, graph[0])
self.set_list.addItem(item1)
def eventFilter(self, src: QtCore.QObject, evt: QtCore.QEvent):
if evt.type() in [QtCore.QEvent.DragEnter, QtCore.QEvent.DragMove]:
evt.accept()
return True
if evt.type() == QtCore.QEvent.KeyPress and evt.key() == QtCore.Qt.Key_Delete:
if src == self.simple_table:
type_idx = 1
else:
type_idx = 2
if len(src.selectedIndexes()) > 0:
idx = src.selectedIndexes()[0]
row, col = idx.row(), idx.column()
if col != type_idx:
src.takeItem(row, col)
is_empty = all(src.item(row, i) is None for i in range(type_idx))
if is_empty:
src.removeRow(row)
return True
return super().eventFilter(src, evt)
def _drop_on_simple_table(self, evt: QtGui.QDropEvent):
"""
event filter does not receive dropevents?
"""
if self.stackedWidget.currentIndex() == 0:
table = self.simple_table
type_column = 1
default_drop = self._simple_table_dropevent
type_name = ['Points', 'FID', 'Spectrum', 'BDS']
else:
table = self.merge_table
type_column = 2
default_drop = self._merge_table_dropevent
type_name = ['FID', 'Spectrum', 'BDS']
pos = evt.pos()
drop_col = table.columnAt(pos.x())
if drop_col == type_column:
evt.ignore()
else:
drop_row = table.rowAt(pos.y())
default_drop(evt)
if drop_row == -1:
w = QtWidgets.QComboBox()
w.addItems(type_name)
item = QtWidgets.QTableWidgetItem('')
drop_row = table.rowAt(pos.y())
table.setItem(drop_row, type_column, item)
table.setCellWidget(drop_row, type_column, w)
item = table.item(drop_row, drop_col)
idx = table.indexFromItem(item)
if idx.row() == -1 and idx.column() == -1:
item = table.takeItem(drop_row, 0)
table.setItem(drop_row, drop_col, item)
if item is not None:
item.setToolTip(item.text())
table.resizeColumnsToContents()
@QtCore.pyqtSlot(QtWidgets.QAbstractButton)
def change_table(self, button: QtWidgets.QAbstractButton):
idx = [self.simple_button, self.merge_button].index(button)
self.stackedWidget.setCurrentIndex(idx)
def collect_args(self) -> list:
src_sets = []
for row in range(self.simple_table.rowCount()):
item = self.simple_table.item(row, 0)
set_id = item.data(QtCore.Qt.UserRole)
graph_id = item.data(QtCore.Qt.UserRole+1)
type_idx = self.simple_table.cellWidget(row, 1).currentIndex()
src_sets.append((set_id, graph_id, type_idx))
for row in range(self.merge_table.rowCount()):
item = self.merge_table.item(row, 0)
graph_id = ''
if item is not None:
set_id_real = item.data(QtCore.Qt.UserRole)
graph_id = item.data(QtCore.Qt.UserRole+1)
else:
set_id_real = ''
item = self.merge_table.item(row, 1)
if item is not None:
set_id_imag = item.data(QtCore.Qt.UserRole)
graph_id = item.data(QtCore.Qt.UserRole+1) if graph_id == '' else graph_id
else:
set_id_imag = ''
type_idx = self.merge_table.cellWidget(row, 2).currentIndex() + 1
src_sets.append((set_id_real, set_id_imag, graph_id, type_idx))
self.convertSets.emit(src_sets)
return src_sets
@QtCore.pyqtSlot(QtWidgets.QAbstractButton, name='on_buttonBox_clicked')
def button_clicked(self, button):
role = self.buttonBox.buttonRole(button)
if role == self.buttonBox.RejectRole:
self.close()
else:
self.collect_args()
self.accept()

View File

@ -0,0 +1 @@
from .datawidget import *

View File

@ -0,0 +1,494 @@
from typing import List, Tuple, Union
from nmreval.lib.colors import available_cycles
from .properties import PropWidget
from ...Qt import QtWidgets, QtGui, QtCore
from ..._py.datawidget import Ui_DataWidget
from ...lib import make_action_icons
from ...lib.delegates import HeaderDelegate
class DataTree(QtWidgets.QTreeWidget):
stateChanged = QtCore.pyqtSignal(list, list) # selected, deselected
keyChanged = QtCore.pyqtSignal(str, str) # id, text
positionChanged = QtCore.pyqtSignal(QtWidgets.QTreeWidgetItem)
deleteItem = QtCore.pyqtSignal(list)
moveItem = QtCore.pyqtSignal(list, str, str, int) # items, from, to, new row
copyItem = QtCore.pyqtSignal(list, str)
saveFits = QtCore.pyqtSignal(list)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setColumnCount(1)
self.invisibleRootItem().setFlags(self.invisibleRootItem().flags() ^ QtCore.Qt.ItemIsDropEnabled)
self.itemChanged.connect(self.data_change)
self.itemClicked.connect(self.new_selection)
self.setColumnCount(2)
self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
self.setDragDropMode(QtWidgets.QTreeView.InternalMove)
self.setDefaultDropAction(QtCore.Qt.IgnoreAction)
self.setSelectionMode(QtWidgets.QTreeView.ExtendedSelection)
self.setSelectionBehavior(QtWidgets.QTreeView.SelectRows)
self._checked_graphs = set()
self._checked_sets = set()
self.management = None
header = QtWidgets.QHeaderView(QtCore.Qt.Horizontal, self)
self.setHeader(header)
header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
header.setVisible(False)
header.moveSection(1, 0)
self.setColumnWidth(1, 16)
self.setItemDelegateForColumn(1, HeaderDelegate())
def add_graph(self, idd: str, name: str):
item = QtWidgets.QTreeWidgetItem()
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemIsEditable |
QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable)
item.setText(0, name)
item.setData(0, QtCore.Qt.UserRole, idd)
item.setCheckState(0, QtCore.Qt.Checked)
self.addTopLevelItem(item)
self._checked_graphs.add(idd)
item.setExpanded(True)
def add_item(self, items: Union[tuple, List[tuple]], gid: str):
if isinstance(items, tuple):
items = [items]
for row in range(self.invisibleRootItem().childCount()):
graph = self.invisibleRootItem().child(row)
if graph.data(0, QtCore.Qt.UserRole) == gid:
for (idd, name) in items:
item = QtWidgets.QTreeWidgetItem([name])
item.setData(0, QtCore.Qt.UserRole, idd)
item.setCheckState(0, QtCore.Qt.Checked)
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsEditable |
QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsUserCheckable)
graph.addChild(item)
self._checked_sets.add(idd)
self.resizeColumnToContents(0)
break
@QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem)
def data_change(self, item: QtWidgets.QTreeWidgetItem) -> Tuple[set, set]:
idd = item.data(0, QtCore.Qt.UserRole)
is_selected = item.checkState(0) == QtCore.Qt.Checked
to_be_hidden = set()
to_be_shown = set()
# item is top-level item / graph
if item.parent() is None:
was_selected = idd in self._checked_graphs
# check state changed to selected
if is_selected != was_selected:
# check state changed to checked
if is_selected:
self._checked_graphs.add(idd)
iterator = QtWidgets.QTreeWidgetItemIterator(item)
iterator += 1
self.blockSignals(True)
for i in range(item.childCount()):
child = item.child(i)
child.setCheckState(0, QtCore.Qt.Checked)
to_be_shown.add(child.data(0, QtCore.Qt.UserRole))
self._checked_sets.add(child.data(0, QtCore.Qt.UserRole))
self.blockSignals(False)
# check state change to unchecked
else:
self._checked_graphs.remove(idd)
self.blockSignals(True)
for i in range(item.childCount()):
child = item.child(i)
child.setCheckState(0, QtCore.Qt.Unchecked)
to_be_hidden.add(child.data(0, QtCore.Qt.UserRole))
try:
self._checked_sets.remove(child.data(0, QtCore.Qt.UserRole))
except KeyError:
pass
self.blockSignals(False)
else:
self.keyChanged.emit(idd, item.text(0))
# item is a set
else:
was_selected = idd in self._checked_sets
if is_selected != was_selected:
if is_selected:
to_be_shown.add(idd)
self._checked_sets.add(idd)
else:
to_be_hidden.add(idd)
try:
self._checked_sets.remove(idd)
except KeyError:
pass
else:
self.keyChanged.emit(idd, item.text(0))
if to_be_shown or to_be_hidden:
self.stateChanged.emit(list(to_be_shown), list(to_be_hidden))
return to_be_shown, to_be_hidden
@QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem)
def new_selection(self, item: QtWidgets.QTreeWidgetItem):
if item.parent() is None:
self.management.select_window(item.data(0, QtCore.Qt.UserRole))
def dropEvent(self, evt: QtGui.QDropEvent):
dropped_index = self.indexAt(evt.pos())
if not dropped_index.isValid():
return
to_parent = self.itemFromIndex(dropped_index).parent()
append = False
if not to_parent:
# dropped on graph item -> append items
to_parent = self.itemFromIndex(dropped_index)
append = True
# index may change
persistent_drop = QtCore.QPersistentModelIndex(dropped_index)
tobemoved = []
take_from = []
for it in self.selectedItems():
from_parent = it.parent()
if from_parent is None:
continue
from_parent.removeChild(it)
tobemoved.append(it)
take_from.append(from_parent.data(0, QtCore.Qt.UserRole))
pos = QtCore.QModelIndex(persistent_drop)
if self.dropIndicatorPosition() == QtWidgets.QAbstractItemView.BelowItem:
pos = pos.sibling(pos.row()+1, 0)
row = pos.row()
if (row == -1) or append:
to_parent.addChildren(tobemoved)
else:
to_parent.insertChildren(row, tobemoved)
self.management.move_sets([it.data(0, QtCore.Qt.UserRole) for it in tobemoved],
to_parent.data(0, QtCore.Qt.UserRole), take_from,
pos=-1 if append else row)
def move_sets(self, sid: str, gid_in: str, gid_out: str):
self.blockSignals(True)
to_parent = None
from_parent = None
it = None
iterator = QtWidgets.QTreeWidgetItemIterator(self)
while iterator.value():
item = iterator.value()
if item is not None:
data = item.data(0, QtCore.Qt.UserRole)
if data == gid_out:
from_parent = item
elif data == gid_in:
to_parent = item
elif data == sid:
it = item
iterator += 1
if (from_parent is None) or (to_parent is None) or (it is None):
print('Komisch')
return
from_parent.removeChild(it)
to_parent.addChild(it)
self.blockSignals(False)
def set_name(self, sid, name):
iterator = QtWidgets.QTreeWidgetItemIterator(self)
while iterator.value():
item = iterator.value()
if item is not None:
data = item.data(0, QtCore.Qt.UserRole)
if data == sid:
if name != item.text(0):
item.setText(0, name)
break
iterator += 1
def keyPressEvent(self, evt: QtGui.QKeyEvent):
if evt.key() == QtCore.Qt.Key_Delete:
rm_sets = []
rm_graphs = []
for idx in self.selectedIndexes():
if idx.column() == 1:
continue
item = self.itemFromIndex(idx)
if item.parent() is None:
for c_i in range(item.childCount()):
rm_sets.append(item.child(c_i).data(0, QtCore.Qt.UserRole))
rm_graphs.append(item.data(0, QtCore.Qt.UserRole))
else:
rm_sets.append(item.data(0, QtCore.Qt.UserRole))
# self.deleteItem.emit(rm_sets+rm_graphs)
self.management.delete_sets(rm_sets+rm_graphs)
elif evt.key() == QtCore.Qt.Key_Space:
sets = []
from_parent = []
for idx in self.selectedIndexes():
if idx.column() != 0:
continue
item = self.itemFromIndex(idx)
if item.parent() is None:
is_selected = item.checkState(0)
self.blockSignals(True)
for i in range(item.childCount()):
child = item.child(i)
from_parent.append(child)
self.blockSignals(False)
if is_selected == QtCore.Qt.Checked:
item.setCheckState(0, QtCore.Qt.Unchecked)
else:
item.setCheckState(0, QtCore.Qt.Checked)
else:
sets.append(item)
for it in sets:
if it in from_parent:
continue
it.setCheckState(0, QtCore.Qt.Unchecked if it.checkState(0) == QtCore.Qt.Checked else QtCore.Qt.Checked)
else:
super().keyPressEvent(evt)
def mousePressEvent(self, evt: QtGui.QMouseEvent):
# disable drag-and-drop when column 1 ('header') is clicked
idx = self.indexAt(evt.pos())
self.setDragEnabled(idx.column() == 0)
super().mousePressEvent(evt)
def remove_item(self, ids: list):
iterator = QtWidgets.QTreeWidgetItemIterator(self)
while iterator.value():
item = iterator.value()
_id = item.data(0, QtCore.Qt.UserRole)
if _id in ids:
try:
idx = item.parent().indexOfChild(item)
item.parent().takeChild(idx)
if _id in self._checked_sets:
self._checked_sets.remove(_id)
except AttributeError:
idx = self.invisibleRootItem().indexOfChild(item)
self.invisibleRootItem().takeChild(idx)
self._checked_graphs.remove(_id)
iterator += 1
def contextMenuEvent(self, evt, color_list=None):
menu = QtWidgets.QMenu()
_ = menu.addAction('Hello')
_.setEnabled(False)
menu.addSeparator()
idx = self.selectedIndexes()
if self.invisibleRootItem().childCount() == 0 and len(idx) == 0:
rdn_action = menu.addAction('Randomness')
action = menu.exec(evt.globalPos())
if action == rdn_action:
import webbrowser
webbrowser.open('https://en.wikipedia.org/wiki/Special:Random')
else:
del_action = menu.addAction('Exterminate')
cp_action = menu.addAction('Replicate')
cat_action = menu.addAction('Join us!')
plt_action = None
save_action = None
menu.addSeparator()
col_menu = menu.addMenu('Color cycle')
for c in available_cycles.keys():
col_menu.addAction(c)
idx = {}
has_fits = False
for i in self.selectedIndexes():
if i.column() == 0:
continue
item = self.itemFromIndex(i)
parent = item.parent()
if parent is None:
continue
else:
graph_id = parent.data(0, QtCore.Qt.UserRole)
if graph_id not in idx:
idx[graph_id] = []
# collect sets in their graph
idx[graph_id].append(item.data(0, QtCore.Qt.UserRole))
data = self.management[item.data(0, QtCore.Qt.UserRole)]
if data.mode == 'fit':
has_fits = True
if has_fits:
menu.addSeparator()
plt_action = menu.addAction('Plot fit parameter')
save_action = menu.addAction('Save fit parameter')
action = menu.exec(evt.globalPos())
s = []
for gid, sets in idx.items():
s.extend(sets)
if action == del_action:
self.management.delete_sets(s)
elif action == cp_action:
for gid, sets in idx.items():
self.management.copy_sets(sets, gid)
elif action == cat_action:
self.management.cat(s)
elif action == plt_action:
self.management.make_fit_parameter(s)
elif action == save_action:
self.saveFits.emit(s)
elif action.parent() == col_menu:
self.management.set_cycle(s, action.text())
evt.accept()
def highlight(self, gid: str):
iterator = QtWidgets.QTreeWidgetItemIterator(self)
while iterator.value():
item = iterator.value()
if item is not None:
if item.data(0, QtCore.Qt.UserRole) == gid:
item.setBackground(0, QtGui.QBrush(QtGui.QColor('gray')))
else:
item.setBackground(0, QtGui.QBrush())
iterator += 1
def uncheck_sets(self, sets: List[str]):
self.blockSignals(True)
iterator = QtWidgets.QTreeWidgetItemIterator(self)
while iterator.value():
item = iterator.value()
if item is not None:
if item.data(0, QtCore.Qt.UserRole) in sets:
item.setCheckState(0, QtCore.Qt.Unchecked)
iterator += 1
self.blockSignals(False)
class DataWidget(QtWidgets.QWidget, Ui_DataWidget):
keyChanged = QtCore.pyqtSignal(str, str)
deleteItem = QtCore.pyqtSignal(list)
startShowProperty = QtCore.pyqtSignal(list)
propertyChanged = QtCore.pyqtSignal(list, str, str, object)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self.tree = DataTree(self)
self.verticalLayout.addWidget(self.tree)
self.tree.selectionModel().selectionChanged.connect(lambda x, y: self.show_property(x))
self.tree.keyChanged.connect(lambda x, y: self.keyChanged.emit(x, y))
self.proptable = PropWidget(self)
self.propwidget.addWidget(self.proptable)
self.propwidget.setText('Properties')
self.propwidget.expansionChanged.connect(self.show_property)
self.proptable.propertyChanged.connect(self.change_property)
make_action_icons(self)
def add_graph(self, idd: str, name: str):
self.tree.blockSignals(True)
self.tree.add_graph(idd, name)
self.tree.blockSignals(False)
def add_item(self, idd: str, name: str, gid: str):
self.tree.blockSignals(True)
self.tree.add_item((idd, name), gid)
self.tree.blockSignals(False)
def add_item_list(self, loi: list, gid: str):
self.tree.blockSignals(True)
self.tree.add_item(loi, gid)
self.tree.blockSignals(False)
def remove_item(self, key):
self.tree.remove_item(key)
def show_property(self, _: QtCore.QModelIndex = None):
if not self.propwidget.isExpanded():
return
sid = []
for i in self.tree.selectedIndexes():
if i.column() == 0:
sid.append(i.data(role=QtCore.Qt.UserRole))
self.startShowProperty.emit(sid)
@QtCore.pyqtSlot(dict)
def set_properties(self, props: dict):
self.proptable.populate(props)
def change_property(self, key1, key2, value):
ids = [item.data(0, QtCore.Qt.UserRole) for item in self.tree.selectedItems()]
if key2 == 'Value':
try:
value = float(value)
except ValueError:
QtWidgets.QMessageBox.warning(self, 'Invalid entry',
'Value %r is not a valid number for `value`.' % value)
return
self.propertyChanged.emit(ids, key1, key2, value)
def uncheck_sets(self, sets: List[str]):
self.tree.uncheck_sets(sets)
def set_name(self, sid, value):
self.tree.set_name(sid, value)
@property
def management(self):
return self.tree.management
@management.setter
def management(self, value):
self.tree.management = value

View File

@ -0,0 +1,78 @@
from ...Qt import QtWidgets, QtCore, QtGui
from ...lib.delegates import PropertyDelegate
class PropWidget(QtWidgets.QWidget):
propertyChanged = QtCore.pyqtSignal(str, str, object)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(2, 2, 2, 2)
self.tab = QtWidgets.QTabWidget(self)
self.layout.addWidget(self.tab)
self.pages = []
self.tab.currentChanged.connect(self.tab_change)
self._tab_idx = 0
def populate(self, props: dict):
self.pages = []
self.tab.blockSignals(True)
while self.tab.count():
self.tab.removeTab(0)
for k, v in props.items():
table = PropTable(self)
table.populate(v)
table.itemChanged.connect(self.property_change)
self.tab.addTab(table, k)
self.pages.append(table)
self.tab.blockSignals(False)
self.tab.setCurrentIndex(self._tab_idx)
@QtCore.pyqtSlot(QtWidgets.QTableWidgetItem)
def property_change(self, item: QtWidgets.QTableWidgetItem):
tab_idx = self.tab.currentIndex()
table = self.pages[tab_idx]
idx = table.indexFromItem(item)
self.propertyChanged.emit(self.tab.tabText(tab_idx),
table.item(idx.row(), idx.column()-1).text(),
item.data(QtCore.Qt.DisplayRole))
@QtCore.pyqtSlot(int)
def tab_change(self, idx: int):
self._tab_idx = idx
class PropTable(QtWidgets.QTableWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setColumnCount(2)
self.setItemDelegateForColumn(1, PropertyDelegate())
self.horizontalHeader().setStretchLastSection(True)
self.verticalHeader().setVisible(False)
self.horizontalHeader().setVisible(False)
self.setFrameShape(QtWidgets.QFrame.NoFrame)
self.setFrameShadow(QtWidgets.QFrame.Plain)
def populate(self, prop: dict):
self.clear()
self.setRowCount(0)
self.blockSignals(True)
for k, v in prop.items():
value_item = QtWidgets.QTableWidgetItem('')
value_item.setData(QtCore.Qt.DisplayRole, v)
key_item = QtWidgets.QTableWidgetItem(k)
key_item.setFlags(QtCore.Qt.NoItemFlags)
key_item.setForeground(QtGui.QBrush(QtGui.QColor(0, 0, 0)))
self.setRowCount(self.rowCount()+1)
self.setItem(self.rowCount()-1, 0, key_item)
self.setItem(self.rowCount()-1, 1, value_item)
self.blockSignals(False)

View File

@ -0,0 +1,213 @@
from itertools import cycle
import pyqtgraph as pg
from numpy import nanmax, nanmin, inf, argsort, where
try:
# numpy > 1.19 renamed some integration functions
from scipy.integrate import cumulative_trapezoid
except ImportError:
from scipy.integrate import cumtrapz as cumulative_trapezoid
from ..Qt import QtWidgets, QtCore, QtGui
from .._py.integral_widget import Ui_Form
class IntegralWidget(QtWidgets.QWidget, Ui_Form):
colors = cycle(['red', 'green', 'blue', 'cyan', 'magenta',
'darkRed', 'darkGreen', 'darkBlue', 'darkCyan', 'darkMagenta'])
requestData = QtCore.pyqtSignal(str)
item_deleted = QtCore.pyqtSignal(pg.GraphicsObject)
newData = QtCore.pyqtSignal(str, list)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self.connected_figure = ''
self.graph_shown = None
self.shown_set = None
self._data = None
self.ranges = []
self.lines = []
self.max_area = 0
self.max_y = inf
self.min_y = -inf
def __call__(self, graph_name, items):
self.label_2.setText(f'Connected to <b>{graph_name}</b>')
self.clear()
self.set_combobox.blockSignals(True)
self.set_combobox.clear()
for sid, name in items:
self.set_combobox.addItem(name, userData=sid)
self.set_combobox.blockSignals(False)
self.set_combobox.setCurrentIndex(0)
self.set_combobox.currentIndexChanged.emit(0)
return self
def keyPressEvent(self, e):
if e.key() == QtCore.Qt.Key_Delete:
self.remove_integral()
else:
super().keyPressEvent(e)
@QtCore.pyqtSlot(int, name='on_set_combobox_currentIndexChanged')
def change_set(self, idx: int):
key = self.set_combobox.itemData(idx)
self.requestData.emit(key)
def set_data(self, ptr):
self._data = ptr
self.max_y = nanmax(self._data.y.real)
self.min_y = nanmin(self._data.y.real)
for idx, rnge in enumerate(self.ranges):
self._update_values(idx, rnge)
def add(self, pos):
x = pos[0]
self.ranges.append((x, x * 1.1))
c = next(IntegralWidget.colors)
qc = QtGui.QColor(c)
qc.setAlpha(40)
region = pg.LinearRegionItem(values=[x, x*1.1], brush=QtGui.QBrush(qc), pen=pg.mkPen(QtGui.QColor(c)))
integral_plot = pg.PlotDataItem(x=[], y=[])
region.sigRegionChanged.connect(self._update_integral)
self.lines.append((region, integral_plot))
self.areas.append(0)
self._make_entry(c)
return region, integral_plot
def _make_entry(self, c):
item = QtWidgets.QTreeWidgetItem()
item.setText(0, f'Integral {len(self.ranges)}')
item.setForeground(0, QtGui.QBrush(QtGui.QColor(c)))
pts_i = self.ranges[-1]
item_list = []
for text, val in [('Start', pts_i[0]), ('Stop', pts_i[1]), ('Areas', 0), ('Ratio', 1.)]:
child = QtWidgets.QTreeWidgetItem()
child.setFlags(QtCore.Qt.NoItemFlags)
child.setText(0, f'{text}: {val:.5g}')
child.setForeground(0, QtGui.QBrush(QtGui.QColor('black')))
item_list.append(child)
item.addChildren(item_list)
self.treeWidget.addTopLevelItem(item)
self.treeWidget.expandToDepth(1)
self._update_values(len(self.ranges) - 1, pts_i)
def _update_integral(self):
idx = None
sender = self.sender()
for i, (reg, _) in enumerate(self.lines):
if sender == reg:
idx = i
break
if idx is None:
return
self._update_values(idx, sender.getRegion())
def _update_values(self, idx, new_range):
self.ranges[idx] = new_range
area = self.calc_integral(idx, *new_range)
item = self.treeWidget.topLevelItem(idx)
item.child(0).setText(0, f'Start: {new_range[0]:.5g}')
item.child(1).setText(0, f'Stop: {new_range[1]:.5g}')
if area is not None:
self.areas[idx] = area
item.child(2).setText(0, f'Area: {area:.5g}')
if self.max_area > 0:
self._set_ratios(idx, self.max_area)
curr_max = max(self.areas)
if curr_max != self.max_area:
if curr_max > 0:
root = self.treeWidget.invisibleRootItem()
for i in range(root.childCount()):
self._set_ratios(i, curr_max)
self.max_area = curr_max
def _set_ratios(self, idx, max_value):
item = self.treeWidget.invisibleRootItem().child(idx)
area_i = self.areas[idx]
item.child(3).setText(0, f'Ratio: {area_i / max_value:.3g}')
integral_line = self.lines[idx][1]
x_i, y_i = integral_line.getData()
scale = (self.max_y - self.min_y) / y_i[-1] * (area_i / max_value)
integral_line.setData(x=x_i, y=y_i * scale)
def calc_integral(self, idx, x_min, x_max):
int_range = where((self._data.x >= x_min) & (self._data.x <= x_max))[0]
if len(int_range) > 1:
x_int = self._data.x[int_range]
y_int = self._data.y[int_range].real
order = argsort(x_int)
integral = cumulative_trapezoid(y=y_int[order], x=x_int[order], initial=0)
scale = (self.max_y-self.min_y) / integral[-1]
self.lines[idx][1].setData(x=x_int[order], y=integral*scale + self.min_y)
return integral[-1]
else:
self.lines[idx][1].setData(x=[], y=[])
return None
def remove_integral(self):
root = self.treeWidget.invisibleRootItem()
for item in self.treeWidget.selectedItems():
idx = root.indexOfChild(item)
self.ranges.pop(idx)
self.item_deleted.emit(self.lines[idx][0])
self.item_deleted.emit(self.lines[idx][1])
self.lines.pop(idx)
self.areas.pop(idx)
self.treeWidget.takeTopLevelItem(idx)
@QtCore.pyqtSlot(name='on_pushButton_clicked')
def convert_to_datasets(self):
set_id = self.set_combobox.currentData()
values = []
for i in range(len(self.ranges)):
x_i, y_i = self.lines[i][1].getData()
start_i, stop_i = self.ranges[i]
area_i = self.areas[i]
values.append((x_i, y_i, start_i, stop_i, area_i))
self.newData.emit(set_id, values)
def clear(self):
self.connected_figure = ''
self.graph_shown = None
self.shown_set = None
self._data = None
self.ranges = []
self.lines = []
self.max_area = 0
self.max_y = inf
self.min_y = -inf

View File

@ -0,0 +1,41 @@
from numpy import linspace, logspace, log10
from ..Qt import QtWidgets, QtCore
from .._py.interpol_dialog import Ui_Dialog
class QInterpol(QtWidgets.QDialog, Ui_Dialog):
ready = QtCore.pyqtSignal(dict)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self.on_comboBox_2_currentIndexChanged(0)
@QtCore.pyqtSlot(int)
def on_comboBox_2_currentIndexChanged(self, idx: int):
if idx == 0:
self.frame.hide()
self.frame_2.show()
else:
self.frame.show()
self.frame_2.hide()
def accept(self):
ret_dic = {'kind': self.comboBox.currentIndex(), 'ylog': self.checkBox_2.isChecked()}
if self.comboBox_2.currentIndex() == 0:
ret_dic['xaxis'] = int(self.lineEdit.text())
else:
if self.checkBox.isChecked():
ret_dic['xaxis'] = logspace(log10(float(self.lineEdit_2.text())),
log10(float(self.lineEdit_3.text())),
num=int(self.lineEdit_4.text()))
else:
ret_dic['xaxis'] = linspace(float(self.lineEdit_2.text()),
float(self.lineEdit_3.text()),
num=int(self.lineEdit_4.text()))
ret_dic['idx'] = [i.row() for i in self.listWidget.selectedIndexes()]
self.ready.emit(ret_dic)
self.close()

View File

@ -0,0 +1,114 @@
from random import randint
import numpy as np
from nmreval import models
from nmreval.lib.importer import find_models
from nmreval.lib.utils import valid_function
from ..Qt import QtGui, QtCore, QtWidgets
from .._py.setbyfunction_dialog import Ui_NewCurveDialog
class QPlotDialog(QtWidgets.QDialog, Ui_NewCurveDialog):
line_created = QtCore.pyqtSignal(object)
def __init__(self):
super().__init__()
self.setupUi(self)
self._function = find_models(models)
self.lineEdit_3.setValidator(QtGui.QDoubleValidator())
self.lineEdit_4.setValidator(QtGui.QDoubleValidator())
self.lineEdit_5.setValidator(QtGui.QIntValidator().setBottom(0))
self.buttonBox.accepted.connect(self.make_line)
for cb in [self.comboBox, self.comboBox_2, self.comboBox_4, self.comboBox_5]:
cb.setCurrentIndex(randint(0, cb.count()))
for cb in [self.comboBox_6, self.comboBox_7]:
self.load_models(cb)
def load_models(self, cb: QtWidgets.QComboBox):
for f in self._function:
cb.addItem(f'{f.name} ({f.type})', userData=f)
@QtCore.pyqtSlot(name='on_pushButton_clicked')
def check_input(self):
err = []
try:
start = float(self.lineEdit_3.text())
except ValueError:
err.append(0)
start = 1
try:
stop = float(self.lineEdit_4.text())
except ValueError:
err.append(1)
stop = 10
try:
nums = int(self.lineEdit_5.text())
except ValueError:
err.append(2)
nums = 10
if self.checkBox.isChecked():
if start <= 0 or stop <= 0:
err.append(3)
start, stop = abs(start)+1e-12, abs(stop)
grid = np.geomspace(start, stop, num=nums)
else:
grid = np.linspace(start, stop, num=nums)
x_func = self.lineEdit.text()
x, isok = valid_function(x_func, extra_namespace={'i': grid})
if not isok:
err.append(4)
x = grid
y_func = self.lineEdit_2.text()
y, isok = valid_function(y_func, extra_namespace={'i': grid, 'x':x})
if not isok:
err.append(5)
msg_err = {0: 'Invalid value for grid start',
1: 'Invalid value for grid end',
2: 'Invalid number of grid steps',
3: 'Negative numbers in logarithmic grid',
4: 'Invalid expression for x',
5: 'Invalid expression for y'
}
if err:
m = '\n'.join([msg_err[e] for e in err])
QtWidgets.QMessageBox().information(self, 'Error detected', m)
return False
return True
def make_line(self):
if not self.check_input():
return
start = float(self.lineEdit_3.text())
stop = float(self.lineEdit_4.text())
nums = int(self.lineEdit_5.text())
if self.checkBox.isChecked():
x = np.geomspace(start, stop, num=nums)
else:
x = np.linspace(start, stop, num=nums)
x_func = self.lineEdit.text()
y_func = self.lineEdit_2.text()
sym = self.comboBox.currentText()
lin = self.comboBox_2.currentText()
name = self.lineEdit_6.text()
if not name:
name = 'self done'
lw = self.doubleSpinBox.value()
sw = self.spinBox.value()

View File

@ -0,0 +1,196 @@
import re
from ..Qt import QtCore, QtWidgets
from .._py.ptstab import Ui_Form
from ..lib.pg_objects import LogInfiniteLine, RegionItem
__all__ = ['PointSelectWidget']
REGION_RE = re.compile(r'(?P<first>[+-]*\d+(?:\.\d*)*(?:[eE][+-]*\d+)*)'
r'(?: ?- ?(?P<second>[+-]*\d+(?:\.\d*)*(?:[eE][+-]*\d+)*))*')
class PointSelectWidget(QtWidgets.QWidget, Ui_Form):
widget_closed = QtCore.pyqtSignal()
points_selected = QtCore.pyqtSignal(dict, str)
point_removed = QtCore.pyqtSignal(LogInfiniteLine)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self.pts = []
self.pts_lines = []
self.nop = 0
self._prev_pos = ''
self._last_item = None
self.connected_figure = ''
self.okButton.clicked.connect(self.apply)
self.deleteButton.clicked.connect(self.remove_points)
self.peaktable.itemChanged.connect(self.editing_finished)
self.peaktable.itemDoubleClicked.connect(self.editing_started)
def keyPressEvent(self, e):
if e.key() == QtCore.Qt.Key_Delete:
self.remove_points()
elif e.key() == QtCore.Qt.Key_F2:
self.editing_started()
else:
super().keyPressEvent(e)
def clear(self):
self.pts = []
self.nop = 0
self.peaktable.clear()
self.pts_lines = []
@QtCore.pyqtSlot(tuple, bool)
def add(self, pos: tuple, double: bool):
x = pos[0]
if double:
self.removepoint(-1)
self.pts.append((x, x*1.1))
item = RegionItem(values=[x, x*1.1], mode='mid')
item.sigRegionChanged.connect(self._update_region)
else:
self.pts.append(x)
item = LogInfiniteLine(pos=x, movable=True)
item.sigPositionChanged.connect(self._update_line)
self.pts_lines.append(item)
self.nop += 1
self._makerow()
return item
def remove_points(self):
for i in sorted(self.peaktable.selectedIndexes(), key=lambda x: x.row(), reverse=True):
self.removepoint(pos=i.row())
def removepoint(self, pos=0):
if pos == -1:
pos = len(self.pts) - 1
try:
self.pts.pop(pos)
self.nop -= 1
item = self.peaktable.takeItem(pos)
del item
del_line = self.pts_lines.pop(pos)
self.point_removed.emit(del_line)
del del_line
except IndexError:
pass
def _makerow(self):
if isinstance(self.pts[-1], tuple):
item = QtWidgets.QListWidgetItem(f'{self.pts[-1][0]:.5g} - {self.pts[-1][1]:.5g}')
else:
item = QtWidgets.QListWidgetItem(f'{self.pts[-1]:.5g}')
item.setFlags(item.flags() ^ QtCore.Qt.ItemIsEditable)
self.peaktable.blockSignals(True)
self.peaktable.addItem(item)
self.peaktable.blockSignals(False)
def closeEvent(self, evt):
self.widget_closed.emit()
super().closeEvent(evt)
@QtCore.pyqtSlot()
def apply(self) -> dict:
ret_dic = {'avg_range': [self.left_pt.value(), self.right_pt.value()],
'avg_mode': {0: 'mean', 1: 'sum', 2: 'integral'}[self.average_combobox.currentIndex()],
'special': None, 'idx': None,
'xy': (self.xbutton.isChecked(), self.ybutton.isChecked())}
if self.groupBox_2.isChecked():
ret_dic['special'] = {0: 'max', 1: 'absmax', 2: 'min', 3: 'absmin'}[self.special_comboBox.currentIndex()]
if len(self.pts) != 0:
ret_dic['idx'] = self.pts
if self.graph_checkbox.isChecked():
gid = ''
else:
gid = self.graph_combobox.currentData()
self.points_selected.emit(ret_dic, gid)
return ret_dic
def _update_region(self):
try:
idx = self.pts_lines.index(self.sender())
except ValueError:
return
self.pts[idx] = self.sender().getRegion()
self.peaktable.blockSignals(True)
self.peaktable.item(idx).setText('{:.5g} - {:.5g}'.format(*self.pts[idx]))
self.peaktable.blockSignals(False)
def _update_line(self):
try:
idx = self.pts_lines.index(self.sender())
except ValueError:
return
self.pts[idx] = self.sender().value()
self.peaktable.blockSignals(True)
self.peaktable.item(idx).setText(f'{self.pts[idx]:.5g}')
self.peaktable.blockSignals(False)
@QtCore.pyqtSlot(QtWidgets.QListWidgetItem)
def editing_started(self, item=None):
if item is None:
item = self.peaktable.selectedItems()[0]
self._prev_pos = item.text()
self.peaktable.editItem(item)
@QtCore.pyqtSlot(QtWidgets.QListWidgetItem)
def editing_finished(self, it: QtWidgets.QListWidgetItem):
m = re.match(REGION_RE, it.text()).groupdict()
undo = True
if m:
start, stop = m['first'], m['second']
row = self.peaktable.row(it)
it_pts = self.pts_lines[row]
if ((stop is None) and isinstance(it_pts, RegionItem)) or \
((stop is not None) and isinstance(it_pts, LogInfiniteLine)):
QtWidgets.QMessageBox().information(self, 'Invalid type',
'Conversion between point and region is not possible.')
else:
if stop is None:
it_pts.blockSignals(True)
it_pts.setValue(float(start))
it_pts.blockSignals(False)
self.pts[row] = float(start)
else:
start, stop = float(start), float(stop)
pos = (min(start, stop), max(start, stop))
self.pts[row] = pos
self.peaktable.blockSignals(True)
it.setText(f'{pos[0]:.5g} - {pos[1]:.5g}')
self.peaktable.blockSignals(False)
it_pts.blockSignals(True)
it_pts.setRegion(pos)
it_pts.blockSignals(False)
undo = False
if undo:
self.peaktable.blockSignals(True)
it.setText(self._prev_pos)
self.peaktable.blockSignals(False)
def set_graphs(self, graphs: list):
self.graph_combobox.clear()
for g in graphs:
self.graph_combobox.addItem(g[1], userData=g[0])
@QtCore.pyqtSlot(int, name='on_graph_checkbox_stateChanged')
def changed_state(self, checked):
self.graph_combobox.setEnabled(checked!=QtCore.Qt.Checked)

View File

@ -0,0 +1,177 @@
import numpy as np
from itertools import cycle
from pyqtgraph import mkColor, mkPen
from nmreval.lib.colors import Tab10
from ..Qt import QtGui, QtCore, QtWidgets
from .._py.shift_scale_dialog import Ui_shift_dialog
from ..lib.pg_objects import PlotItem
from ..lib.spinboxes import SciSpinBox
class QShift(QtWidgets.QDialog, Ui_shift_dialog):
valuesChanged = QtCore.pyqtSignal(dict, tuple)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self.graphicsView.setMenuEnabled(False)
self.splitter.setSizes([int(self.width()/4), int(self.width()*2/3)])
self.movements = {}
self.data = {}
self._colors = cycle(Tab10)
delegate = SpinBoxDelegate()
delegate.valueChanged.connect(self.shift)
self.shift_table.setItemDelegate(delegate)
self.x_shift_spinbox.valueChanged.connect(lambda: self.glob_shift('h'))
self.y_shift_spinbox.valueChanged.connect(lambda: self.glob_shift('v'))
delegate = SpinBoxDelegate()
delegate.valueChanged.connect(self.scale)
self.scale_table.setItemDelegate(delegate)
self.x_scale_spinbox.valueChanged.connect(lambda: self.glob_scale('h'))
self.y_scale_spinbox.valueChanged.connect(lambda: self.glob_scale('v'))
def add_item(self, idx, name, x, y):
color = mkColor(next(self._colors).rgb())
if np.iscomplexobj(y):
pl = [PlotItem(x, y.real, name=name, pen=mkPen(color=color)),
PlotItem(x, y.imag, name=name, pen=mkPen(color=color))]
else:
pl = [PlotItem(x, y, name=name, pen=mkPen(color=color))]
self.data[idx] = (pl, x, y)
# [[horizontal shift, vertical shift], [horizontal scale, vertical scale]]
self.movements[idx] = [[0, 0], [1, 1]]
for i, tw in enumerate([self.shift_table, self.scale_table]):
tw.blockSignals(True)
row = tw.rowCount()
tw.insertRow(row)
item = QtWidgets.QTableWidgetItem(name)
item.setForeground(QtGui.QBrush(color))
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
item.setCheckState(QtCore.Qt.Checked)
item.setData(QtCore.Qt.UserRole, idx)
tw.setItem(row, 0, item)
tw.setItem(row, 1, QtWidgets.QTableWidgetItem(str(i)))
tw.setItem(row, 2, QtWidgets.QTableWidgetItem(str(i)))
tw.blockSignals(False)
for i in pl:
self.graphicsView.addItem(i)
def set_graphs(self, graphs: list):
for key, name in graphs:
self.data_combobox.addItem(name, userData=key)
self.values_combobox.addItem(name, userData=key)
def glob_shift_scale(self, widget: QtWidgets.QTableWidget, mode: int, col: int, value: float):
for row in range(widget.rowCount()):
if widget.item(row, 0).checkState() == QtCore.Qt.Checked:
item = widget.item(row, col)
item.setText(str(value))
self.shift_scale(widget, mode, row, col-1, value)
def glob_shift(self, direction: str):
if direction == 'h':
val = self.x_shift_spinbox.value()
self.glob_shift_scale(self.shift_table, 0, 1, val)
else:
val = self.y_shift_spinbox.value()
self.glob_shift_scale(self.shift_table, 0, 2, val)
def glob_scale(self, direction: str):
if direction == 'h':
val = self.x_scale_spinbox.value()
self.glob_shift_scale(self.scale_table, 1, 1, val)
else:
val = self.y_scale_spinbox.value()
self.glob_shift_scale(self.scale_table, 1, 2, val)
def shift_scale(self, widget: QtWidgets.QTableWidget, mode: int,
row: int, col: int, value: float):
item = widget.item(row, 0)
key = item.data(QtCore.Qt.UserRole)
self.movements[key][mode][col] = value
(x_off, y_off), (x_scale, y_scale) = self.movements[key]
pl, x, y = self.data[key]
y_part = [np.real, np.imag]
for i, item in enumerate(pl):
item.setData(x=x*x_scale+x_off, y=y_part[i](y) * y_scale + y_off)
@QtCore.pyqtSlot(int, int, float)
def shift(self, row: int, column: int, value: float):
self.shift_scale(self.shift_table, 0, row, column-1, value)
@QtCore.pyqtSlot(int, int, float)
def scale(self, row: int, column: int, value: float):
self.shift_scale(self.scale_table, 1, row, column-1, value)
@QtCore.pyqtSlot(int, name='on_xlog_checkbox_stateChanged')
@QtCore.pyqtSlot(int, name='on_ylog_checkbox_stateChanged')
def set_log(self, state: int):
if self.sender() == self.xlog_checkbox:
log_state = self.graphicsView.plotItem.ctrl.logXCheck
else:
log_state = self.graphicsView.plotItem.ctrl.logYCheck
log_state.setCheckState(state)
self.graphicsView.plotItem.updateLogMode()
def on_overwrite_checkbox_stateChanged(self, state: int):
self.data_newgraph.setVisible(state != QtCore.Qt.Checked)
self.data_combobox.setVisible(state != QtCore.Qt.Checked)
def on_value_checkbox_stateChanged(self, state: int):
self.values_newgraph.setVisible(state == QtCore.Qt.Checked)
self.values_combobox.setVisible(state == QtCore.Qt.Checked)
def on_data_newgraph_stateChanged(self, state: int):
self.data_combobox.setEnabled(state != QtCore.Qt.Checked)
def on_values_newgraph_stateChanged(self, state: int):
self.values_combobox.setEnabled(state != QtCore.Qt.Checked)
def accept(self):
data_saving = None
if not self.overwrite_checkbox.isChecked():
if self.data_newgraph.isChecked():
data_saving = ''
else:
data_saving = self.data_combobox.currentData()
value_saving = None
if self.value_checkbox.isChecked():
if self.values_newgraph.isChecked():
value_saving = ''
else:
value_saving = self.values_combobox.currentData()
self.valuesChanged.emit(self.movements, (data_saving, value_saving))
self.close()
class SpinBoxDelegate(QtWidgets.QStyledItemDelegate):
valueChanged = QtCore.pyqtSignal(int, int, float)
def createEditor(self, parent: QtWidgets.QWidget, option: QtWidgets.QStyleOptionViewItem,
idx: QtCore.QModelIndex) -> QtWidgets.QWidget:
editor = SciSpinBox(parent)
editor.valueChanged.connect(self.new_value)
return editor
def new_value(self, val):
# geht bestimmt besser...
table = self.sender().parent().parent()
self.valueChanged.emit(table.currentRow(), table.currentColumn(), val)

View File

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

View File

@ -0,0 +1,94 @@
import numpy as np
import pyqtgraph as pg
from scipy.interpolate import splrep, splev
from ...Qt import QtCore, QtWidgets
from ..._py.baseline_dialog import Ui_SignalEdit
class QBaselineDialog(QtWidgets.QDialog, Ui_SignalEdit):
finished = QtCore.pyqtSignal(str, tuple)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self.data = None
self.graph = pg.PlotDataItem(x=[], y=[], pen=pg.mkPen({'color': 'b'}), name='Original')
self.graph_corr = pg.PlotDataItem(x=[], y=[], pen=pg.mkPen({'color': 'r'}), name='Corrected')
self.baseline = pg.PlotDataItem(x=[], y=[], name='Baseline')
self.anchors = []
self.anchor_lines = []
self.spline = None
self.legend = self.graphicsView.addLegend()
self.graphicsView.scene().sigMouseClicked.connect(self.add_node)
self.graphicsView.addItem(self.graph_corr)
self.graphicsView.addItem(self.graph)
self.graphicsView.addItem(self.baseline)
def add_data(self, x, y):
if self.data is not None:
QtWidgets.QMessageBox().information(self, 'Invalid number of datasets',
'Baseline correction is only working on one set at a time.')
self.close()
self.anchors.extend([np.min(x), np.max(x)])
self.data = (x, y)
self.graph.setData(x=x, y=y.real)
self.graph_corr.setData(x=x, y=y.real)
def accept(self):
self.finished.emit('bls', (splev(self.data[0], self.spline),))
self.close()
def add_node(self, evt):
vb = self.graphicsView.plotItem.vb
if self.graphicsView.plotItem.sceneBoundingRect().contains(evt.scenePos()) and evt.button() == 1:
pos = vb.mapSceneToView(evt.scenePos())
x = pos.x()
self.anchors.append(x)
self.anchors.sort()
row = self.anchors.index(x)
self.listWidget.insertItem(row-1, QtWidgets.QListWidgetItem(str(x)))
inf_line = pg.InfiniteLine(pos=x)
self.anchor_lines.insert(row-1, inf_line)
self.graphicsView.addItem(inf_line)
self.change_baseline()
def change_baseline(self):
if self.data:
x, y = self.data
def mean(xx):
return np.mean(y[max(0, np.argmin(abs(x-xx))-5):min(len(x), np.argmin(abs(x-xx))+6)].real)
y_node = [mean(x_node) for x_node in self.anchors]
try:
self.spline = splrep(self.anchors, y_node, per=False)
except TypeError:
self.spline = splrep(self.anchors, y_node, per=False, k=1)
bl = splev(x, self.spline)
self.baseline.setData(x=x, y=bl)
self.graph_corr.setData(x=x, y=y.real-bl)
def keyPressEvent(self, evt):
if self.listWidget.hasFocus() and evt.key() == QtCore.Qt.Key_Delete:
r = self.listWidget.currentRow()
self.anchors.pop(r+1)
listitem = self.listWidget.takeItem(r)
del listitem
self.graphicsView.removeItem(self.anchor_lines.pop(r))
self.change_baseline()
else:
super().keyPressEvent(evt)

View File

@ -0,0 +1,92 @@
from src.nmreval.math import apodization
from src.nmreval.lib.importer import find_models
from src.nmreval.utils.text import convert
from ...Qt import QtCore, QtWidgets, QtGui
from ...lib.forms import FormWidget
from ..._py.editsignalwidget import Ui_Form
class EditSignalWidget(QtWidgets.QWidget, Ui_Form):
do_something = QtCore.pyqtSignal(str, tuple)
get_values = QtCore.pyqtSignal()
preview_triggered = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self.apodlist = find_models(apodization)
self.lineEdit.hide()
self.lineEdit.setValidator(QtGui.QDoubleValidator())
for ap in self.apodlist:
self.apodcombobox.addItem(str(ap().name))
self.change_apodization(0)
self.baselinebutton.clicked.connect(lambda: self.apply_changes('bl'))
self.zfbutton.clicked.connect(lambda: self.apply_changes('zf'))
self.phasebutton.clicked.connect(lambda: self.apply_changes('ph'))
self.apodbutton.clicked.connect(lambda: self.apply_changes('ap'))
self.leftshiftbutton.clicked.connect(lambda: self.apply_changes('ls'))
self.fourierutton.clicked.connect(lambda: self.apply_changes('ft'))
self.pushButton.clicked.connect(lambda: self.preview_triggered.emit('ap'))
self.pushButton_2.clicked.connect(lambda: self.preview_triggered.emit('ph'))
@QtCore.pyqtSlot(str)
def apply_changes(self, sender):
if sender in ['bl', 'zf', 'ft']:
self.do_something.emit(sender, tuple())
elif sender == 'ls':
if self.comboBox.currentIndex() == 0:
_nop = int(self.lsspinBox.text())
stype = 'pts'
else:
try:
_nop = float(self.lineEdit.text())
except ValueError:
_nop = 0.0
stype = 'time'
self.do_something.emit(sender, (_nop, stype))
elif sender == 'ap':
apodmodel = self.apodlist[self.apodcombobox.currentIndex()]
p = [float(x.text()) for x in self.groupBox_3.findChildren(QtWidgets.QLineEdit)]
self.do_something.emit(sender, (p, apodmodel))
elif sender == 'ph':
ph0 = float(self.ph0slider.value())
ph1 = float(self.ph1slider.value())
pvt = float(self.pivot_lineedit.text())
self.do_something.emit(sender, (ph0, ph1, pvt))
else:
print('You should never reach this by accident.')
@QtCore.pyqtSlot(int, name='on_apodcombobox_currentIndexChanged')
def change_apodization(self, index):
apod_func = self.apodlist[index]
self.label_2.setText(convert(apod_func.equation))
while self.verticalLayout_8.count():
item = self.verticalLayout_8.takeAt(0)
try:
item.widget().deleteLater()
except AttributeError:
pass
for k, v in enumerate(apod_func.params):
widgt = FormWidget(name=v)
self.verticalLayout_8.addWidget(widgt)
@QtCore.pyqtSlot(int, name='on_comboBox_currentIndexChanged')
def change_ls(self, idx):
if idx:
self.lineEdit.show()
self.lsspinBox.hide()
else:
self.lineEdit.hide()
self.lsspinBox.show()

View File

@ -0,0 +1,208 @@
from __future__ import annotations
import numpy as np
from pyqtgraph import mkPen
from numpy import inf, linspace
from numpy.fft import fft, fftfreq, fftshift
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 ..._py.apod_dialog import Ui_ApodEdit
from ..._py.phase_corr_dialog import Ui_SignalEdit
from ...lib.forms import FormWidget
class QPreviewDialogs(QtWidgets.QDialog):
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.pvt_line = LogInfiniteLine(pos=0, movable=True)
self.graphicsView.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)
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(str(int(evt.value())))
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)
for ap in self.apods:
self.apodcombobox.addItem(ap().name)
self.apodcombobox.blockSignals(False)
self.apod_graph = PlotItem(x=[], y=[])
self.graphicsView.addItem(self.apod_graph)
self.mode = 'ap'
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)
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))
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
@QtCore.pyqtSlot(int, name='on_apodcombobox_currentIndexChanged')
def change_apodization(self, index):
# delete old widgets
self.eqn_label.setText(convert(self.apods[index].equation))
while self.widget_layout.count():
item = self.widget_layout.takeAt(0)
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)
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
def _get_parameter(self):
p = []
for i in range(self.widget_layout.count()):
item = self.widget_layout.itemAt(i)
w = item.widget()
try:
p.append(w.value)
except AttributeError:
continue
return p
def get_value(self):
apodmodel = self.apods[self.apodcombobox.currentIndex()]
p = self._get_parameter()
return p, apodmodel

View File

@ -0,0 +1,357 @@
from __future__ import annotations
from typing import Any
import numpy as np
from ..Qt import QtGui, QtCore, QtWidgets
from .._py.valueeditor import Ui_MaskDialog
class ValueEditWidget(QtWidgets.QWidget, Ui_MaskDialog):
requestData = QtCore.pyqtSignal(str)
maskSignal = QtCore.pyqtSignal(str, list)
itemChanged = QtCore.pyqtSignal(str, tuple, object)
itemDeleted = QtCore.pyqtSignal(str, list)
itemAdded = QtCore.pyqtSignal(str)
values_selected = QtCore.pyqtSignal(str, list, list)
split_signal = QtCore.pyqtSignal(str, int)
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setupUi(self)
self.connected_figure = None
self.shown_set = None
self.items = {}
self.model = ValueModel()
self.model.itemChanged.connect(self.update_items)
self.selection_model = QtCore.QItemSelectionModel(self.model)
self.selection_model.selectionChanged.connect(self.show_position)
self.tableView.setModel(self.model)
self.tableView.setSelectionModel(self.selection_model)
self.tableView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.tableView.customContextMenuRequested.connect(self.ctx)
self.graph_combobox.currentIndexChanged.connect(self._populate_sets)
self.set_combobox.currentIndexChanged.connect(self._populate_table)
def __call__(self, items: dict):
self.items = items
self.graph_combobox.blockSignals(True)
self.graph_combobox.clear()
for k, v in items.items():
self.graph_combobox.addItem(k[1], userData=k[0])
self.graph_combobox.blockSignals(False)
idx = self.graph_combobox.findData(self.connected_figure)
if idx == -1:
idx = 0
self.graph_combobox.setCurrentIndex(idx)
self.graph_combobox.currentIndexChanged.emit(idx)
return self
@QtCore.pyqtSlot(int)
def _populate_sets(self, idx: int):
if idx == -1:
self.graph_combobox.setCurrentIndex(0)
return
self.set_combobox.blockSignals(True)
self.set_combobox.clear()
self.connected_figure = self.graph_combobox.currentData()
if self.items:
for sid, name in self.items[(self.graph_combobox.currentData(), self.graph_combobox.currentText())]:
self.set_combobox.addItem(name, userData=sid)
self.set_combobox.blockSignals(False)
sidx = self.set_combobox.findData(self.shown_set)
if sidx == -1:
sidx = 0
self.set_combobox.setCurrentIndex(sidx)
self.set_combobox.currentIndexChanged.emit(sidx)
@QtCore.pyqtSlot(int)
def _populate_table(self, idx):
self.selection_model.clearSelection()
self.shown_set = self.set_combobox.itemData(idx)
self.requestData.emit(self.set_combobox.itemData(idx))
def set_data(self, data: list, mask: np.ndarray):
self.selection_model.clearSelection()
self.model.loadData(data, mask)
self.spinBox.setMaximum(self.model.rowCount())
def remove_graph(self):
# remove everything
self.connected_figure = None
self.items = {}
self.selection_model.clear()
while self.model.rowCount():
self.model.removeRow(0)
self.graph_combobox.clear()
self.set_combobox.clear()
def ctx(self, pos: QtCore.QPoint):
idx = self.tableView.indexAt(pos)
if not idx.isValid():
return
menu = QtWidgets.QMenu()
menu.addSeparator()
hide_action = menu.addAction('Hide/Show')
del_action = menu.addAction('Delete')
split_action = menu.addAction('Split after selection')
cpc_action = menu.addAction('Copy to clipboard')
action = menu.exec(self.tableView.viewport().mapToGlobal(pos))
if action == hide_action:
self.mask_row()
elif action == del_action:
self.delete_item()
elif action == cpc_action:
self.copy_selection()
elif action == split_action:
self.split()
def keyPressEvent(self, evt):
if evt.matches(QtGui.QKeySequence.Copy):
self.copy_selection()
elif evt.key() == QtCore.Qt.Key_Delete:
self.delete_item()
else:
super().keyPressEvent(evt)
def copy_selection(self) -> str:
table = ''
for r in self.selection_model.selectedRows():
table += self.model.data(r) + '\t'
table += '\t'.join([self.model.data(r.sibling(r.row(), i)) for i in [1, 2]]) + '\n'
QtWidgets.QApplication.clipboard().setText(table)
return table
@QtCore.pyqtSlot(name='on_split_button_clicked')
def split(self):
self.split_signal.emit(self.set_combobox.currentData(),
self.selection_model.selectedRows()[-1].row()+1)
@QtCore.pyqtSlot(name='on_mask_button_clicked')
def mask_row(self):
for r in self.selection_model.selectedRows():
self.model.setData(r, not self.model.data(r, ValueModel.maskRole), ValueModel.maskRole)
self.maskSignal.emit(self.set_combobox.currentData(), self.model.mask)
@QtCore.pyqtSlot(name='on_unmaskbutton_clicked')
def unmask(self):
self.model.unmask()
self.maskSignal.emit(self.set_combobox.currentData(), self.model.mask)
@QtCore.pyqtSlot(name='on_delete_button_clicked')
def delete_item(self):
idx = [r.row() for r in self.selection_model.selectedRows()]
success = False
for i in sorted(idx, reverse=True):
success = self.model.removeRow(i)
if success:
self.itemDeleted.emit(self.set_combobox.currentData(), idx)
self.spinBox.setMaximum(self.spinBox.maximum()-len(idx))
@QtCore.pyqtSlot(name='on_add_button_clicked')
def new_value(self):
success = self.model.addRows()
if success:
self.spinBox.setMaximum(self.spinBox.maximum()+1)
self.itemAdded.emit(self.set_combobox.currentData())
@QtCore.pyqtSlot(int, int, str)
def update_items(self, col, row, val):
sid = self.set_combobox.currentData()
new_value = complex(val)
new_value = new_value.real if new_value.imag == 0 else new_value
self.itemChanged.emit(sid, (col, row), new_value)
@QtCore.pyqtSlot(QtCore.QItemSelection, QtCore.QItemSelection)
def show_position(self, *_):
xvals = []
yvals = []
for idx in self.selection_model.selectedRows():
xvals.append(float(self.model.data(idx)))
try:
yvals.append(float(self.model.data(idx.sibling(idx.row(), 1))))
except ValueError:
yvals.append(complex(self.model.data(idx.sibling(idx.row(), 1))).real)
self.values_selected.emit(self.connected_figure, xvals, yvals)
@QtCore.pyqtSlot(name='on_toolButton_clicked')
def goto(self):
self.tableView.scrollTo(self.model.index(self.spinBox.value()-1, 0))
class ValueModel(QtCore.QAbstractTableModel):
"""
TableModel with lazy loading
"""
itemChanged = QtCore.pyqtSignal(int, int, str)
load_number = 20
maskRole = QtCore.Qt.UserRole+321
def __init__(self, parent=None):
super().__init__(parent=parent)
self._data = None
self.total_rows = 0
self.rows_loaded = 0
self.mask = None
self.headers = ['x', 'y', '\u0394y']
for i, hd in enumerate(self.headers):
self.setHeaderData(i, QtCore.Qt.Horizontal, hd)
def rowCount(self, *args, **kwargs) -> int:
return self.total_rows
def columnCount(self, *args, **kwargs) -> int:
return len(self.headers)
def loadData(self, data: list[np.ndarray], mask: np.ndarray):
self.beginResetModel()
self._data = []
for x, y, y_err in zip(*data):
self._data.append([x, y, y_err])
self.total_rows = len(self._data)
self.mask = mask.tolist()
self.endResetModel()
self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [QtCore.Qt.DisplayRole])
def data(self, idx: QtCore.QModelIndex, role=QtCore.Qt.DisplayRole) -> Any:
if not idx.isValid():
return
row = idx.row()
if role in [QtCore.Qt.DisplayRole, QtCore.Qt.EditRole]:
val = self._data[row][idx.column()]
if isinstance(val, complex):
return f'{val.real:.8g}{val.imag:+.8g}j'
else:
return f'{val:.8g}'
elif role == QtCore.Qt.BackgroundRole:
pal = QtGui.QGuiApplication.palette()
if not self.mask[row]:
return pal.color(QtGui.QPalette.Disabled, QtGui.QPalette.Base)
else:
return pal.color(QtGui.QPalette.Base)
elif role == QtCore.Qt.ForegroundRole:
pal = QtGui.QGuiApplication.palette()
if not self.mask[row]:
return pal.color(QtGui.QPalette.Disabled, QtGui.QPalette.Text)
else:
return pal.color(QtGui.QPalette.Text)
elif role == ValueModel.maskRole:
return self.mask[row]
else:
return
def setData(self, idx: QtCore.QModelIndex, value: str | bool, role=QtCore.Qt.DisplayRole) -> Any:
col, row = idx.column(), idx.row()
if role == ValueModel.maskRole:
self.mask[row] = bool(value)
self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [role])
return True
if value:
if role == QtCore.Qt.EditRole:
try:
value = complex(value)
except ValueError:
# not a number
return False
self._data[row][col] = value.real if value.imag == 0 else value
self.itemChanged.emit(col, row, str(value))
self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [role])
else:
return super().setData(idx, value, role=role)
return True
else:
return False
def headerData(self, section: int, orientation, role=QtCore.Qt.DisplayRole) -> Any:
if role == QtCore.Qt.DisplayRole:
if orientation == QtCore.Qt.Horizontal:
return self.headers[section]
else:
return str(section+1)
return
def canFetchMore(self, idx: QtCore.QModelIndex) -> bool:
if not idx.isValid():
return False
return self.total_rows > self.rows_loaded
def fetchMore(self, idx: QtCore.QModelIndex):
remaining = self.total_rows - self.rows_loaded
to_be_loaded = min(remaining, ValueModel.load_number)
self.beginInsertRows(QtCore.QModelIndex(), self.rows_loaded, self.rows_loaded + to_be_loaded - 1)
self.rows_loaded += to_be_loaded
self.endInsertRows()
def flags(self, idx: QtCore.QModelIndex) -> QtCore.Qt.ItemFlag:
return QtCore.QAbstractTableModel.flags(self, idx) | QtCore.Qt.ItemIsEditable
def removeRows(self, pos: int, rows: int, parent=None, *args, **kwargs) -> bool:
self.beginRemoveRows(parent, pos, pos+rows-1)
for _ in range(rows):
self._data.pop(pos)
self.mask.pop(pos)
self.endRemoveRows()
self.total_rows -= rows
return True
def addRows(self, num=1):
return self.insertRows(self.rowCount(), num)
def insertRows(self, pos: int, rows: int, parent=QtCore.QModelIndex(), *args, **kwargs):
self.beginInsertRows(parent, pos, pos+rows-1)
for _ in range(rows):
self._data.insert(pos, [0.0] * self.columnCount())
self.mask.insert(pos, True)
self.total_rows += rows
self.endInsertRows()
return True
def unmask(self):
self.mask = [True] * self.total_rows
self.dataChanged.emit(self.index(0, 0), self.index(0, 1), [ValueModel.maskRole])