forked from IPKM/nmreval
BUGFIX: VFT;
change to src layout
This commit is contained in:
0
src/gui_qt/data/__init__.py
Normal file
0
src/gui_qt/data/__init__.py
Normal file
682
src/gui_qt/data/container.py
Normal file
682
src/gui_qt/data/container.py
Normal 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
|
152
src/gui_qt/data/conversion.py
Normal file
152
src/gui_qt/data/conversion.py
Normal 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()
|
1
src/gui_qt/data/datawidget/__init__.py
Normal file
1
src/gui_qt/data/datawidget/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .datawidget import *
|
494
src/gui_qt/data/datawidget/datawidget.py
Normal file
494
src/gui_qt/data/datawidget/datawidget.py
Normal 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
|
78
src/gui_qt/data/datawidget/properties.py
Normal file
78
src/gui_qt/data/datawidget/properties.py
Normal 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)
|
213
src/gui_qt/data/integral_widget.py
Normal file
213
src/gui_qt/data/integral_widget.py
Normal 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
|
||||
|
41
src/gui_qt/data/interpolate_dialog.py
Normal file
41
src/gui_qt/data/interpolate_dialog.py
Normal 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()
|
114
src/gui_qt/data/plot_dialog.py
Normal file
114
src/gui_qt/data/plot_dialog.py
Normal 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()
|
196
src/gui_qt/data/point_select.py
Normal file
196
src/gui_qt/data/point_select.py
Normal 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)
|
177
src/gui_qt/data/shift_graphs.py
Normal file
177
src/gui_qt/data/shift_graphs.py
Normal 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)
|
2
src/gui_qt/data/signaledit/__init__.py
Normal file
2
src/gui_qt/data/signaledit/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from .phase_dialog import QApodDialog, QPhasedialog
|
||||
from .baseline_dialog import QBaselineDialog
|
94
src/gui_qt/data/signaledit/baseline_dialog.py
Normal file
94
src/gui_qt/data/signaledit/baseline_dialog.py
Normal 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)
|
92
src/gui_qt/data/signaledit/editsignalwidget.py
Normal file
92
src/gui_qt/data/signaledit/editsignalwidget.py
Normal 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()
|
208
src/gui_qt/data/signaledit/phase_dialog.py
Normal file
208
src/gui_qt/data/signaledit/phase_dialog.py
Normal 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
|
357
src/gui_qt/data/valueeditwidget.py
Normal file
357
src/gui_qt/data/valueeditwidget.py
Normal 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])
|
Reference in New Issue
Block a user