diff --git a/src/asammdf/blocks/cutils.c b/src/asammdf/blocks/cutils.c index a0c08f3e0..9e1da4022 100644 --- a/src/asammdf/blocks/cutils.c +++ b/src/asammdf/blocks/cutils.c @@ -6,6 +6,7 @@ #include "numpy/ndarrayobject.h" #include #include +#include #include #define PY_PRINTF(o) \ @@ -272,9 +273,9 @@ static PyObject* extract(PyObject* self, PyObject* args) size = calc_size(&buf[offset]); memcpy(addr2, &buf[offset+4], size); } + Py_XDECREF(offsets_list); } } - Py_XDECREF(offsets_list); } return (PyObject *) vals; @@ -352,8 +353,7 @@ static PyObject* get_vlsd_max_sample_size(PyObject* self, PyObject* args) { int i = 0; Py_ssize_t count = 0; - PyObject* data, * offsets, * result; - npy_intp dim[1]; + PyObject* data, * offsets; unsigned long long max_size = 0; unsigned long vlsd_size = 0; char* inptr=NULL, *data_end=NULL, *current_position=NULL; @@ -367,7 +367,7 @@ static PyObject* get_vlsd_max_sample_size(PyObject* self, PyObject* args) } else { - offsets_array = (unsigned long long*)PyArray_GETPTR1(offsets, 0); + offsets_array = (unsigned long long*)PyArray_GETPTR1((PyArrayObject *)offsets, 0); inptr = PyBytes_AsString(data); data_end = inptr + PyBytes_GET_SIZE(data); @@ -394,15 +394,15 @@ void positions_char(PyObject* samples, PyObject* timestamps, PyObject* plot_samp long* outdata; int pos_min = 0, pos_max = 0; - indata = (char*)PyArray_GETPTR1(samples, 0); - outdata = (long*)PyArray_GETPTR1(result, 0); + indata = (char*)PyArray_GETPTR1((PyArrayObject *)samples, 0); + outdata = (long*)PyArray_GETPTR1((PyArrayObject *)result, 0); char * ps; double tmin, tmax, * ts, *pt; - ps = (char*)PyArray_GETPTR1(plot_samples, 0); - pt = (double*)PyArray_GETPTR1(plot_timestamps, 0); - ts = (double*)PyArray_GETPTR1(timestamps, 0); + ps = (char*)PyArray_GETPTR1((PyArrayObject *)plot_samples, 0); + pt = (double*)PyArray_GETPTR1((PyArrayObject *)plot_timestamps, 0); + ts = (double*)PyArray_GETPTR1((PyArrayObject *)timestamps, 0); int current_pos = 0, stop_index=count-1; for (int i = 0; i < (int)count; i++) { @@ -464,15 +464,15 @@ void positions_short(PyObject* samples, PyObject* timestamps, PyObject* plot_sam long* outdata; int pos_min = 0, pos_max = 0; - indata = (short*)PyArray_GETPTR1(samples, 0); - outdata = (long*)PyArray_GETPTR1(result, 0); + indata = (short*)PyArray_GETPTR1((PyArrayObject *)samples, 0); + outdata = (long*)PyArray_GETPTR1((PyArrayObject *)result, 0); short * ps; double tmin, tmax, * ts, *pt; - ps = (short*)PyArray_GETPTR1(plot_samples, 0); - pt = (double*)PyArray_GETPTR1(plot_timestamps, 0); - ts = (double*)PyArray_GETPTR1(timestamps, 0); + ps = (short*)PyArray_GETPTR1((PyArrayObject *)plot_samples, 0); + pt = (double*)PyArray_GETPTR1((PyArrayObject *)plot_timestamps, 0); + ts = (double*)PyArray_GETPTR1((PyArrayObject *)timestamps, 0); int current_pos = 0, stop_index = count - 1; for (int i = 0; i < (int)count; i++) { @@ -534,15 +534,15 @@ void positions_long(PyObject* samples, PyObject* timestamps, PyObject* plot_samp long* outdata; int pos_min = 0, pos_max = 0; - indata = (long*)PyArray_GETPTR1(samples, 0); - outdata = (long*)PyArray_GETPTR1(result, 0); + indata = (long*)PyArray_GETPTR1((PyArrayObject *)samples, 0); + outdata = (long*)PyArray_GETPTR1((PyArrayObject *)result, 0); long * ps; double tmin, tmax, * ts, *pt; - ps = (long*)PyArray_GETPTR1(plot_samples, 0); - pt = (double*)PyArray_GETPTR1(plot_timestamps, 0); - ts = (double*)PyArray_GETPTR1(timestamps, 0); + ps = (long*)PyArray_GETPTR1((PyArrayObject *)plot_samples, 0); + pt = (double*)PyArray_GETPTR1((PyArrayObject *)plot_timestamps, 0); + ts = (double*)PyArray_GETPTR1((PyArrayObject *)timestamps, 0); int current_pos = 0, stop_index = count - 1; for (int i = 0; i < (int)count; i++) { @@ -604,15 +604,15 @@ void positions_long_long(PyObject* samples, PyObject* timestamps, PyObject* plot long* outdata; int pos_min = 0, pos_max = 0; - indata = (long long*)PyArray_GETPTR1(samples, 0); - outdata = (long*)PyArray_GETPTR1(result, 0); + indata = (long long*)PyArray_GETPTR1((PyArrayObject *)samples, 0); + outdata = (long*)PyArray_GETPTR1((PyArrayObject *)result, 0); long long * ps; double tmin, tmax, * ts, * pt; - ps = (long long*)PyArray_GETPTR1(plot_samples, 0); - pt = (double*)PyArray_GETPTR1(plot_timestamps, 0); - ts = (double*)PyArray_GETPTR1(timestamps, 0); + ps = (long long*)PyArray_GETPTR1((PyArrayObject *)plot_samples, 0); + pt = (double*)PyArray_GETPTR1((PyArrayObject *)plot_timestamps, 0); + ts = (double*)PyArray_GETPTR1((PyArrayObject *)timestamps, 0); int current_pos = 0, stop_index = count - 1; for (int i = 0; i < (int)count; i++) { @@ -674,15 +674,15 @@ void positions_unsigned_char(PyObject* samples, PyObject* timestamps, PyObject* long* outdata; int pos_min = 0, pos_max = 0; - indata = (unsigned char*)PyArray_GETPTR1(samples, 0); - outdata = (long*)PyArray_GETPTR1(result, 0); + indata = (unsigned char*)PyArray_GETPTR1((PyArrayObject *)samples, 0); + outdata = (long*)PyArray_GETPTR1((PyArrayObject *)result, 0); unsigned char* ps; double tmin, tmax, * ts, * pt; - ps = (unsigned char*)PyArray_GETPTR1(plot_samples, 0); - pt = (double*)PyArray_GETPTR1(plot_timestamps, 0); - ts = (double*)PyArray_GETPTR1(timestamps, 0); + ps = (unsigned char*)PyArray_GETPTR1((PyArrayObject *)plot_samples, 0); + pt = (double*)PyArray_GETPTR1((PyArrayObject *)plot_timestamps, 0); + ts = (double*)PyArray_GETPTR1((PyArrayObject *)timestamps, 0); int current_pos = 0, stop_index = count - 1; for (int i = 0; i < (int)count; i++) { @@ -744,15 +744,15 @@ void positions_unsigned_short(PyObject* samples, PyObject* timestamps, PyObject* long* outdata; int pos_min = 0, pos_max = 0; - indata = (unsigned short *)PyArray_GETPTR1(samples, 0); - outdata = (long*)PyArray_GETPTR1(result, 0); + indata = (unsigned short *)PyArray_GETPTR1((PyArrayObject *)samples, 0); + outdata = (long*)PyArray_GETPTR1((PyArrayObject *)result, 0); unsigned short* ps; double tmin, tmax, * ts, * pt; - ps = (unsigned short*)PyArray_GETPTR1(plot_samples, 0); - pt = (double*)PyArray_GETPTR1(plot_timestamps, 0); - ts = (double*)PyArray_GETPTR1(timestamps, 0); + ps = (unsigned short*)PyArray_GETPTR1((PyArrayObject *)plot_samples, 0); + pt = (double*)PyArray_GETPTR1((PyArrayObject *)plot_timestamps, 0); + ts = (double*)PyArray_GETPTR1((PyArrayObject *)timestamps, 0); int current_pos = 0, stop_index = count - 1; for (int i = 0; i < (int)count; i++) { @@ -814,15 +814,15 @@ void positions_unsigned_long(PyObject* samples, PyObject* timestamps, PyObject* long* outdata; int pos_min = 0, pos_max = 0; - indata = (unsigned long*)PyArray_GETPTR1(samples, 0); - outdata = (long*)PyArray_GETPTR1(result, 0); + indata = (unsigned long*)PyArray_GETPTR1((PyArrayObject *)samples, 0); + outdata = (long*)PyArray_GETPTR1((PyArrayObject *)result, 0); unsigned long* ps; double tmin, tmax, * ts, * pt; - ps = (unsigned long*)PyArray_GETPTR1(plot_samples, 0); - pt = (double*)PyArray_GETPTR1(plot_timestamps, 0); - ts = (double*)PyArray_GETPTR1(timestamps, 0); + ps = (unsigned long*)PyArray_GETPTR1((PyArrayObject *)plot_samples, 0); + pt = (double*)PyArray_GETPTR1((PyArrayObject *)plot_timestamps, 0); + ts = (double*)PyArray_GETPTR1((PyArrayObject *)timestamps, 0); int current_pos = 0, stop_index = count - 1; for (int i = 0; i < (int)count; i++) { @@ -884,15 +884,15 @@ void positions_unsigned_long_long(PyObject* samples, PyObject* timestamps, PyObj long* outdata; int pos_min = 0, pos_max = 0; - indata = (unsigned long long *)PyArray_GETPTR1(samples, 0); - outdata = (long*)PyArray_GETPTR1(result, 0); + indata = (unsigned long long *)PyArray_GETPTR1((PyArrayObject *)samples, 0); + outdata = (long*)PyArray_GETPTR1((PyArrayObject *)result, 0); unsigned long long* ps; double tmin, tmax, * ts, * pt; - ps = (unsigned long long*)PyArray_GETPTR1(plot_samples, 0); - pt = (double*)PyArray_GETPTR1(plot_timestamps, 0); - ts = (double*)PyArray_GETPTR1(timestamps, 0); + ps = (unsigned long long*)PyArray_GETPTR1((PyArrayObject *)plot_samples, 0); + pt = (double*)PyArray_GETPTR1((PyArrayObject *)plot_timestamps, 0); + ts = (double*)PyArray_GETPTR1((PyArrayObject *)timestamps, 0); int current_pos = 0, stop_index = count - 1; for (int i = 0; i < (int)count; i++) { @@ -954,15 +954,15 @@ void positions_float(PyObject* samples, PyObject* timestamps, PyObject* plot_sam long* outdata= NULL; int pos_min = 0, pos_max = 0; - indata = (float*)PyArray_GETPTR1(samples, 0); - outdata = (long*)PyArray_GETPTR1(result, 0); + indata = (float*)PyArray_GETPTR1((PyArrayObject *)samples, 0); + outdata = (long*)PyArray_GETPTR1((PyArrayObject *)result, 0); float* ps; double tmin, tmax, * ts, * pt; - ps = (float*)PyArray_GETPTR1(plot_samples, 0); - pt = (double*)PyArray_GETPTR1(plot_timestamps, 0); - ts = (double*)PyArray_GETPTR1(timestamps, 0); + ps = (float*)PyArray_GETPTR1((PyArrayObject *)plot_samples, 0); + pt = (double*)PyArray_GETPTR1((PyArrayObject *)plot_timestamps, 0); + ts = (double*)PyArray_GETPTR1((PyArrayObject *)timestamps, 0); int current_pos = 0, stop_index = count - 1; for (int i = 0; i < (int)count; i++) { @@ -1020,19 +1020,19 @@ void positions_float(PyObject* samples, PyObject* timestamps, PyObject* plot_sam void positions_double(PyObject* samples, PyObject* timestamps, PyObject* plot_samples, PyObject* plot_timestamps, PyObject* result, long step, long count, long last) { - double min, max, val, * indata=NULL; + double min, max, * indata=NULL; long* outdata = NULL; int pos_min = 0, pos_max = 0; - indata = (double*)PyArray_GETPTR1(samples, 0); - outdata = (long*)PyArray_GETPTR1(result, 0); + indata = (double*)PyArray_GETPTR1((PyArrayObject *)samples, 0); + outdata = (long*)PyArray_GETPTR1((PyArrayObject *)result, 0); double* ps = NULL; double tmin, tmax, * ts = NULL, * pt = NULL; - ps = (double*)PyArray_GETPTR1(plot_samples, 0); - pt = (double*)PyArray_GETPTR1(plot_timestamps, 0); - ts = (double*)PyArray_GETPTR1(timestamps, 0); + ps = (double*)PyArray_GETPTR1((PyArrayObject *)plot_samples, 0); + pt = (double*)PyArray_GETPTR1((PyArrayObject *)plot_timestamps, 0); + ts = (double*)PyArray_GETPTR1((PyArrayObject *)timestamps, 0); int current_pos = 0, stop_index = count - 1; for (int i = 0; i < (int)count; i++) { @@ -1251,6 +1251,50 @@ static PyObject* data_block_from_arrays(PyObject* self, PyObject* args) } } +static PyObject* get_idx_with_edges(PyObject* self, PyObject* args) +{ + int i = 0; + PyObject *idx=NULL; + PyArrayObject *result=NULL; + + uint8_t *out_array, * idx_array, previous=1, current=0; + + + if (!PyArg_ParseTuple(args, "O", &idx)) + { + return 0; + } + else + { + npy_intp dims[1], count; + count = PyArray_SIZE((PyArrayObject *)idx); + dims[0] = count; + result = PyArray_ZEROS(1, dims, NPY_BOOL, 0); + + idx_array = (uint8_t *)PyArray_GETPTR1((PyArrayObject *)idx, 0); + out_array = (uint8_t *)PyArray_GETPTR1(result, 0); + + for (i = 0; i < count; i++, idx_array++, out_array++) + { + current = *idx_array; + if (current) { + if (current != previous) { + *(out_array-1) = 1; + } + *out_array = 1; + } + else { + if (current != previous && i) { + *(out_array-1) = 0; + } + } + previous = current; + } + } + + return (PyObject *) result; +} + // Our Module's Function Definition struct // We require this `NULL` to signal the end of our method @@ -1265,6 +1309,8 @@ static PyMethodDef myMethods[] = { "positions", positions, METH_VARARGS, "positions" }, { "get_channel_raw_bytes", get_channel_raw_bytes, METH_VARARGS, "get_channel_raw_bytes" }, { "data_block_from_arrays", data_block_from_arrays, METH_VARARGS, "data_block_from_arrays" }, + { "get_idx_with_edges", get_idx_with_edges, METH_VARARGS, "get_idx_with_edges" }, + { NULL, NULL, 0, NULL } }; diff --git a/src/asammdf/blocks/mdf_v4.py b/src/asammdf/blocks/mdf_v4.py index ba0b3999f..4862c85ab 100644 --- a/src/asammdf/blocks/mdf_v4.py +++ b/src/asammdf/blocks/mdf_v4.py @@ -7989,7 +7989,7 @@ def _yield_selected_signals( if group_index == index: master_index = idx - encodings = {group_index: [None] for groups_index in groups} + encodings = {group_index: [None] for group_index in groups} self._set_temporary_master(None) idx = 0 diff --git a/src/asammdf/blocks/utils.py b/src/asammdf/blocks/utils.py index fdbf8cb54..b4df8d26e 100644 --- a/src/asammdf/blocks/utils.py +++ b/src/asammdf/blocks/utils.py @@ -1387,7 +1387,7 @@ def components( values = channel[name] if values.dtype.byteorder not in target_byte_order: - values = values.byteswap().newbyteorder() + values = values.view(values.dtype.newbyteorder()) if len(values.shape) > 1: values = Series( @@ -1406,7 +1406,7 @@ def components( values = channel[name] if values.dtype.byteorder not in target_byte_order: - values = values.byteswap().newbyteorder() + values = values.view(values.dtype.newbyteorder()) if not only_basenames: axis_name = unique_names.get_unique_name(f"{name_}.{name}") @@ -1442,7 +1442,7 @@ def components( else: if values.dtype.byteorder not in target_byte_order: - values = values.byteswap().newbyteorder() + values = values.view(values.dtype.newbyteorder()) if not only_basenames: name_ = unique_names.get_unique_name( diff --git a/src/asammdf/blocks/v4_blocks.py b/src/asammdf/blocks/v4_blocks.py index b28d3831f..a6ac0a037 100644 --- a/src/asammdf/blocks/v4_blocks.py +++ b/src/asammdf/blocks/v4_blocks.py @@ -3129,7 +3129,7 @@ def convert(self, values, as_object=False, as_bytes=False, ignore_value2text_con vals = vals * a if b: vals += b - values = np.core.records.fromarrays( + values = np.rec.fromarrays( [vals] + [values[name] for name in names[1:]], dtype=[(name, vals.dtype, vals.shape[1:])] + [(name, values[name].dtype, values[name].shape[1:]) for name in names[1:]], @@ -3167,7 +3167,7 @@ def convert(self, values, as_object=False, as_bytes=False, ignore_value2text_con except TypeError: vals = (P1 * X**2 + P2 * X + P3) / (P4 * X**2 + P5 * X + P6) - values = np.core.records.fromarrays( + values = np.rec.fromarrays( [vals] + [values[name] for name in names[1:]], dtype=[(name, vals.dtype, vals.shape[1:])] + [(name, values[name].dtype, values[name].shape[1:]) for name in names[1:]], @@ -3349,7 +3349,7 @@ def convert(self, values, as_object=False, as_bytes=False, ignore_value2text_con ret = ret.astype(bytes) ret = ret.reshape(shape) - values = np.core.records.fromarrays( + values = np.rec.fromarrays( [ret] + [values[name] for name in names[1:]], dtype=[(name, ret.dtype, ret.shape[1:])] + [(name, values[name].dtype, values[name].shape[1:]) for name in names[1:]], @@ -3521,7 +3521,7 @@ def convert(self, values, as_object=False, as_bytes=False, ignore_value2text_con ret = np.array(ret, dtype=bytes) ret = ret.reshape(shape) - values = np.core.records.fromarrays( + values = np.rec.fromarrays( [ret] + [values[name] for name in names[1:]], dtype=[(name, ret.dtype, ret.shape[1:])] + [(name, values[name].dtype, values[name].shape[1:]) for name in names[1:]], diff --git a/src/asammdf/gui/__init__.py b/src/asammdf/gui/__init__.py index 8b1378917..b11fce72e 100644 --- a/src/asammdf/gui/__init__.py +++ b/src/asammdf/gui/__init__.py @@ -1 +1,7 @@ +import os +os.environ["QT_API"] = "pyside6" +os.environ["PYQTGRAPH_QT_LIB"] = "PySide6" + +if "QT_ENABLE_HIGHDPI_SCALING" not in os.environ: + os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1" diff --git a/src/asammdf/gui/asammdfgui.py b/src/asammdf/gui/asammdfgui.py index 651bd4a72..bf21cc5df 100644 --- a/src/asammdf/gui/asammdfgui.py +++ b/src/asammdf/gui/asammdfgui.py @@ -4,7 +4,6 @@ os.environ["QT_API"] = "pyside6" os.environ["PYQTGRAPH_QT_LIB"] = "PySide6" -os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "0" os.environ["PYSIDE6_OPTION_PYTHON_ENUM"] = "2" alternative_sitepacakges = os.environ.get("ASAMMDF_PYTHONPATH", "") diff --git a/src/asammdf/gui/dialogs/advanced_search.py b/src/asammdf/gui/dialogs/advanced_search.py index c07b2628e..b8cfb4c57 100644 --- a/src/asammdf/gui/dialogs/advanced_search.py +++ b/src/asammdf/gui/dialogs/advanced_search.py @@ -37,6 +37,7 @@ def __init__( *args, **kwargs, ): + channels_db = kwargs.pop("channels_db", None) super().__init__(*args, **kwargs) self.setupUi(self) @@ -53,7 +54,8 @@ def __init__( self.channels_db = mdf.channels_db self.mdf = mdf else: - self.mdf = self.channels_db = None + self.mdf = None + self.channels_db = channels_db self.apply_btn.clicked.connect(self._apply) self.add_btn.clicked.connect(self._add) @@ -125,6 +127,9 @@ def __init__( self.pattern.editingFinished.connect(self.update_pattern_matches) self.case_sensitivity_pattern.currentIndexChanged.connect(self.update_pattern_matches) self.pattern_match_type.currentIndexChanged.connect(self.update_pattern_matches) + self.filter_type.currentIndexChanged.connect(self.update_pattern_matches) + self.filter_value.valueChanged.connect(self.update_pattern_matches) + self.raw.stateChanged.connect(self.update_pattern_matches) self.update_pattern_matches() self.showMaximized() @@ -421,35 +426,31 @@ def section_resized(self, index, old_size, new_size): self.matches.setColumnWidth(index, new_size) def update_pattern_matches(self, *args): + from ..widgets.mdi_area import extract_signals_using_pattern + self.pattern_matches.clear() if not self.channels_db: return - pattern = self.pattern.text().strip() - case_sensitive = self.case_sensitivity_pattern.currentText() == "Case sensitive" - match_type = self.pattern_match_type.currentText() - - if match_type == "Wildcard": - wild = f"__{os.urandom(3).hex()}WILDCARD{os.urandom(3).hex()}__" - pattern = pattern.replace("*", wild) - pattern = re.escape(pattern) - pattern = pattern.replace(wild, ".*") - - if case_sensitive: - pattern = re.compile(pattern) - else: - pattern = re.compile(f"(?i){pattern}") - - matches = {} + pattern_info = { + "pattern": self.pattern.text().strip(), + "case_sensitive": self.case_sensitivity_pattern.currentText() == "Case sensitive", + "match_type": self.pattern_match_type.currentText(), + "filter_value": self.filter_value.value(), + "filter_type": self.filter_type.currentText(), + "raw": self.raw.isChecked(), + "integer_format": self.integer_format.currentText(), + } - for name, entries in self.channels_db.items(): - if pattern.fullmatch(name): - for entry in entries: - if entry in matches: - continue - matches[entry] = name + signals = extract_signals_using_pattern( + mdf=self.mdf, + channels_db=self.channels_db, + pattern_info=pattern_info, + ignore_value2text_conversions=True, + as_names=True, + ) - items = [QtWidgets.QTreeWidgetItem([name]) for entry, name in matches.items()] + items = [QtWidgets.QTreeWidgetItem([name]) for name in signals] self.pattern_matches.addTopLevelItems(items) diff --git a/src/asammdf/gui/dialogs/define_channel.py b/src/asammdf/gui/dialogs/define_channel.py index 9684a93b3..a776aa0ff 100644 --- a/src/asammdf/gui/dialogs/define_channel.py +++ b/src/asammdf/gui/dialogs/define_channel.py @@ -164,6 +164,7 @@ def function_changed(self, *args): for widget in widgets: self.arg_layout.removeWidget(widget) widget.setParent(None) + widget.deleteLater() self.arg_layout.removeItem(self.arg_widgets[-1]) diff --git a/src/asammdf/gui/dialogs/error_dialog.py b/src/asammdf/gui/dialogs/error_dialog.py index ff759266a..d7198113f 100644 --- a/src/asammdf/gui/dialogs/error_dialog.py +++ b/src/asammdf/gui/dialogs/error_dialog.py @@ -79,6 +79,7 @@ def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key.Key_F1: self.timer.stop() self.status.clear() + event.accept() else: super().keyPressEvent(event) diff --git a/src/asammdf/gui/dialogs/messagebox.py b/src/asammdf/gui/dialogs/messagebox.py index 34953016d..9743a6315 100644 --- a/src/asammdf/gui/dialogs/messagebox.py +++ b/src/asammdf/gui/dialogs/messagebox.py @@ -72,6 +72,7 @@ def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key.Key_F1: self.timer.stop() self.setText(self.original_text) + event.accept() else: super().keyPressEvent(event) diff --git a/src/asammdf/gui/plot.py b/src/asammdf/gui/plot.py index 0b498da8b..8b2e0a9a8 100644 --- a/src/asammdf/gui/plot.py +++ b/src/asammdf/gui/plot.py @@ -1,13 +1,8 @@ import logging -import os from ..blocks.utils import plausible_timestamps try: - os.environ["QT_API"] = "pyside6" - os.environ["PYQTGRAPH_QT_LIB"] = "PySide6" - os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "0" - from PySide6 import QtWidgets from .widgets.plot_standalone import PlotWindow diff --git a/src/asammdf/gui/ui/search_dialog.py b/src/asammdf/gui/ui/search_dialog.py index e61c25ef1..100fbdb5c 100644 --- a/src/asammdf/gui/ui/search_dialog.py +++ b/src/asammdf/gui/ui/search_dialog.py @@ -427,7 +427,7 @@ def retranslateUi(self, SearchDialog): self.label_3.setText(QCoreApplication.translate("SearchDialog", "Filter type", None)) ___qtreewidgetitem2 = self.pattern_matches.headerItem() ___qtreewidgetitem2.setText( - 0, QCoreApplication.translate("SearchDialog", "Channels matching the name pattern", None) + 0, QCoreApplication.translate("SearchDialog", "Channels matching the pattern conditions", None) ) self.tabs.setTabText( self.tabs.indexOf(self.tab_2), QCoreApplication.translate("SearchDialog", "Pattern definition", None) diff --git a/src/asammdf/gui/ui/search_dialog.ui b/src/asammdf/gui/ui/search_dialog.ui index 640564cac..45fe281f3 100644 --- a/src/asammdf/gui/ui/search_dialog.ui +++ b/src/asammdf/gui/ui/search_dialog.ui @@ -548,7 +548,7 @@ - Channels matching the name pattern + Channels matching the pattern conditions diff --git a/src/asammdf/gui/utils.py b/src/asammdf/gui/utils.py index 6d7d63131..3fb69867f 100644 --- a/src/asammdf/gui/utils.py +++ b/src/asammdf/gui/utils.py @@ -316,6 +316,7 @@ def close(self): def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key.Key_Escape and event.modifiers() == QtCore.Qt.KeyboardModifier.NoModifier: + event.accept() self.close() else: super().keyPressEvent(event) @@ -387,6 +388,16 @@ def compute_signal( all_timebase, functions, ): + required_channels = {} + for key, sig in measured_signals.items(): + signal = sig.physical(copy=False) + if signal.samples.dtype.kind in "fui": + required_channels[key] = signal + else: + required_channels[key] = sig + + measured_signals = required_channels + type_ = description["type"] try: diff --git a/src/asammdf/gui/widgets/batch.py b/src/asammdf/gui/widgets/batch.py index dd9b8ee25..b3c76f982 100644 --- a/src/asammdf/gui/widgets/batch.py +++ b/src/asammdf/gui/widgets/batch.py @@ -845,6 +845,7 @@ def _as_mdf(self, file_name): try: from mfile import BSIG, DL3, ERG, TDMS except ImportError: + print(format_exc()) from cmerg import BSIG, ERG if suffix == ".erg": @@ -857,6 +858,7 @@ def _as_mdf(self, file_name): cls = DL3 mdf = cls(file_name).export_mdf() + mdf.original_name = file_name elif suffix in (".mdf", ".mf4", ".mf4z"): mdf = MDF(file_name) @@ -878,7 +880,11 @@ def _prepare_files(self, files=None, progress=None): mdf_files = [] for i, file_name in enumerate(files): progress.signals.setLabelText.emit(f"Preparing the file {i+1} of {count}\n{file_name}") - mdf = self._as_mdf(file_name) + try: + mdf = self._as_mdf(file_name) + except: + print(format_exc()) + mdf = None mdf_files.append(mdf) progress.signals.setValue.emit(i + 1) @@ -1400,7 +1406,7 @@ def apply_processing_thread(self, progress): output_folder = None try: - root = Path(os.path.commonprefix(source_files)).parent + root = Path(os.path.commonpath(source_files)) except ValueError: root = None @@ -1413,6 +1419,9 @@ def apply_processing_thread(self, progress): files = self._prepare_files(list(source_files), progress) for mdf_index, (mdf_file, source_file) in enumerate(zip(files, source_files)): + if mdf_file is None: + continue + mdf_file.configure( read_fragment_size=split_size, integer_interpolation=self.integer_interpolation, @@ -1557,7 +1566,7 @@ def apply_processing_thread(self, progress): if root is None: file_name = output_folder / Path(mdf_file.original_name).name else: - file_name = output_folder / Path(mdf_file.name).relative_to(root) + file_name = output_folder / Path(mdf_file.original_name).relative_to(root) if not file_name.parent.exists(): os.makedirs(file_name.parent, exist_ok=True) diff --git a/src/asammdf/gui/widgets/cursor.py b/src/asammdf/gui/widgets/cursor.py index 7a804940a..f2ed1247c 100644 --- a/src/asammdf/gui/widgets/cursor.py +++ b/src/asammdf/gui/widgets/cursor.py @@ -138,7 +138,11 @@ def paint(self, paint, *args, plot=None, uuid=None): rect = plot.viewbox.sceneBoundingRect() delta = rect.x() height = rect.height() - width = rect.x() + rect.width() + + px, py = plot.px, plot.py + + plot.px = (plot.x_range[1] - plot.x_range[0]) / rect.width() + plot.py = rect.height() x, y = plot.scale_curve_to_pixmap( position, @@ -191,6 +195,8 @@ def paint(self, paint, *args, plot=None, uuid=None): pix = QtGui.QPixmap(":/erase.png").scaled(16, 16) paint.drawPixmap(QtCore.QPointF(rect.x() + rect.width() - 17, rect.y() + 1), pix) + plot.px, plot.py = px, py + def set_value(self, value): self.setPos(value) @@ -296,6 +302,11 @@ def paint(self, paint, *args, plot=None, uuid=None): height = rect.height() width = rect.x() + rect.width() + px, py = plot.px, plot.py + + plot.px = (plot.x_range[1] - plot.x_range[0]) / rect.width() + plot.py = rect.height() + if not self.show_circle and not self.show_horizontal_line or not uuid: x, y = plot.scale_curve_to_pixmap( position, @@ -356,6 +367,8 @@ def paint(self, paint, *args, plot=None, uuid=None): ) paint.drawLine(QtCore.QPointF(x, 0), QtCore.QPointF(x, height)) + plot.px, plot.py = px, py + def _computeBoundingRect(self): # br = UIGraphicsItem.boundingRect(self) vr = self.viewRect() # bounds of containing ViewBox mapped to local coords. @@ -467,6 +480,11 @@ def paint(self, p, *args, plot=None, uuid=None): delta = rect.x() height = rect.height() + px, py = plot.px, plot.py + + plot.px = (plot.x_range[1] - plot.x_range[0]) / rect.width() + plot.py = rect.height() + x1, y1 = plot.scale_curve_to_pixmap( self.lines[0].value(), 0, @@ -495,3 +513,5 @@ def paint(self, p, *args, plot=None, uuid=None): p.drawRect(rect) for line in self.lines: line.paint(p, *args, plot=plot, uuid=uuid) + + plot.px, plot.py = px, py diff --git a/src/asammdf/gui/widgets/file.py b/src/asammdf/gui/widgets/file.py index c1d326f35..a696f4e10 100644 --- a/src/asammdf/gui/widgets/file.py +++ b/src/asammdf/gui/widgets/file.py @@ -1135,6 +1135,7 @@ def load_channel_list(self, event=None, file_name=None, manually=False): self.mdi_area.removeSubWindow(window) widget.setParent(None) widget.close() + widget.deleteLater() window.close() suffix = original_file_name.suffix.lower() @@ -2135,9 +2136,11 @@ def keyPressEvent(self, event): if key == QtCore.Qt.Key.Key_F and modifier == QtCore.Qt.KeyboardModifier.ControlModifier: self.search() + event.accept() elif key == QtCore.Qt.Key.Key_F11: self.full_screen_toggled.emit() + event.accept() elif ( key in (QtCore.Qt.Key.Key_V, QtCore.Qt.Key.Key_H, QtCore.Qt.Key.Key_C, QtCore.Qt.Key.Key_T) @@ -2161,10 +2164,13 @@ def keyPressEvent(self, event): elif mode == "tile horizontally": self.mdi_area.tile_horizontally() + event.accept() + elif key == QtCore.Qt.Key.Key_F and modifier == ( QtCore.Qt.KeyboardModifier.ShiftModifier | QtCore.Qt.KeyboardModifier.AltModifier ): self.toggle_frames() + event.accept() elif key == QtCore.Qt.Key.Key_L and modifier == QtCore.Qt.KeyboardModifier.ShiftModifier: if self.channel_view.isVisible(): @@ -2189,9 +2195,11 @@ def keyPressEvent(self, event): self.splitter.setSizes(self._splitter_sizes) self.splitter.handle(0).setEnabled(True) self.splitter.handle(1).setEnabled(True) + event.accept() elif key == QtCore.Qt.Key.Key_Period and modifier == QtCore.Qt.KeyboardModifier.NoModifier: self.set_line_style() + event.accept() else: widget = self.get_current_widget() @@ -2865,6 +2873,7 @@ def apply_processing_thread(self, file_name, opts, version, needs_filter, channe self.mdi_area.removeSubWindow(window) widget.setParent(None) widget.close() + widget.deleteLater() window.close() result = mdf.save( @@ -3016,6 +3025,7 @@ def embed_display_file(self, event=None): self.mdi_area.removeSubWindow(window) widget.setParent(None) widget.close() + widget.deleteLater() window.close() suffix = original_file_name.suffix.lower() diff --git a/src/asammdf/gui/widgets/formated_axis.py b/src/asammdf/gui/widgets/formated_axis.py index 38dbe639a..775befce9 100644 --- a/src/asammdf/gui/widgets/formated_axis.py +++ b/src/asammdf/gui/widgets/formated_axis.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta, timezone +from traceback import format_exc from PySide6 import QtCore, QtGui, QtWidgets @@ -492,13 +493,40 @@ def wheelEvent(self, event): event.accept() + def drawPicture(self, p, axisSpec, tickSpecs, textSpecs, ratio=1.0): + p.setRenderHint(p.RenderHint.Antialiasing, False) + p.setRenderHint(p.RenderHint.TextAntialiasing, True) + + ## draw long line along axis + pen, p1, p2 = axisSpec + p.setPen(pen) + p.drawLine(p1, p2) + # p.translate(0.5,0) ## resolves some damn pixel ambiguity + + ## draw ticks + for pen, p1, p2 in tickSpecs: + p.setPen(pen) + p.drawLine(p1, p2) + + # Draw all text + if self.style["tickFont"] is not None: + p.setFont(self.style["tickFont"]) + p.setPen(self.textPen()) + bounding = self.boundingRect().toAlignedRect() + bounding.setSize(bounding.size() * ratio) + bounding.moveTo(bounding.topLeft() * ratio) + p.setClipRect(bounding) + for rect, flags, text in textSpecs: + p.drawText(rect, int(flags), text) + def paint(self, p, opt, widget): rect = self.boundingRect() width = rect.width() + ratio = widget.devicePixelRatio() if widget else 1.0 if self.picture is None: try: - picture = QtGui.QPixmap(width, rect.height()) + picture = QtGui.QPixmap(int(width * ratio), int(rect.height() * ratio)) picture.fill(self.background) painter = QtGui.QPainter() @@ -508,26 +536,31 @@ def paint(self, p, opt, widget): painter.setCompositionMode(QtGui.QPainter.CompositionMode.CompositionMode_SourceOver) if self.style["tickFont"]: painter.setFont(self.style["tickFont"]) - specs = self.generateDrawSpecs(painter) + specs = self.generateDrawSpecs(painter, ratio) if specs is not None: - self.drawPicture(painter, *specs) + self.drawPicture(painter, *specs, ratio) if self.minus is not None: painter.drawPixmap( - QtCore.QPoint(int(rect.x()) + 5, 6), - self.minus.pixmap.scaled(BUTTON_SIZE, BUTTON_SIZE), + QtCore.QPoint(int(rect.x() * ratio) + 5 * ratio, 6 * ratio), + self.minus.pixmap.scaled(BUTTON_SIZE * ratio, BUTTON_SIZE * ratio), ) painter.drawPixmap( - QtCore.QPoint(int(rect.x()) + 5, 27), - self.plus.pixmap.scaled(BUTTON_SIZE, BUTTON_SIZE), + QtCore.QPoint((int(rect.x()) + 5) * ratio, 27 * ratio), + self.plus.pixmap.scaled(BUTTON_SIZE * ratio, BUTTON_SIZE * ratio), ) if self.orientation in ("left", "right"): painter.setPen(self._pen) - label_rect = QtCore.QRectF(1, 1, rect.height() - (28 + BUTTON_SIZE), rect.width()) - painter.translate(rect.bottomLeft()) + label_rect = QtCore.QRectF( + 1 * ratio, + 1 * ratio, + rect.height() * ratio - (28 + BUTTON_SIZE) * ratio, + rect.width() * ratio, + ) + painter.translate(rect.bottomLeft() * ratio) painter.rotate(-90) painter.setRenderHint(painter.RenderHint.TextAntialiasing, True) @@ -539,11 +572,17 @@ def paint(self, p, opt, widget): painter.rotate(90) painter.resetTransform() + except: + print(format_exc()) + finally: painter.end() + + picture.setDevicePixelRatio(widget.devicePixelRatio() if widget else 1.0) + self.picture = picture - def generateDrawSpecs(self, p): + def generateDrawSpecs(self, p, ratio): """ Calls tickValues() and tickStrings() to determine where and how ticks should be drawn, then generates from this a set of drawing commands to be @@ -804,4 +843,23 @@ def generateDrawSpecs(self, p): self.tickSpecs = tickSpecs + axisSpec = ( + axisSpec[0], + axisSpec[1] * ratio, + axisSpec[2] * ratio, + ) + + bounds.setSize(bounds.size() * ratio) + + for spec in textSpecs: + spec[0].setSize(spec[0].size() * ratio) + spec[0].moveTo(spec[0].topLeft() * ratio) + + for i, spec in enumerate(tickSpecs): + tickSpecs[i] = ( + spec[0], + spec[1] * ratio, + spec[2] * ratio, + ) + return (axisSpec, tickSpecs, textSpecs) diff --git a/src/asammdf/gui/widgets/list.py b/src/asammdf/gui/widgets/list.py index 106ba87e6..74fd8aceb 100644 --- a/src/asammdf/gui/widgets/list.py +++ b/src/asammdf/gui/widgets/list.py @@ -64,8 +64,10 @@ def keyPressEvent(self, event): self.takeItem(row) if deleted: self.itemsDeleted.emit(deleted) + event.accept() elif key == QtCore.Qt.Key.Key_Space and modifiers == QtCore.Qt.KeyboardModifier.NoModifier: + event.accept() selected_items = self.selectedItems() if not selected_items: return @@ -81,6 +83,7 @@ def keyPressEvent(self, event): wid.display.setCheckState(state) elif key == QtCore.Qt.Key.Key_Space and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier: + event.accept() selected_items = self.selectedItems() if not selected_items: return @@ -98,7 +101,9 @@ def keyPressEvent(self, event): elif modifiers == QtCore.Qt.KeyboardModifier.ControlModifier and key == QtCore.Qt.Key.Key_C: selected_items = self.selectedItems() if not selected_items: + event.accept() return + self.itemWidget(selected_items[0]).keyPressEvent(event) elif modifiers == ( @@ -109,12 +114,12 @@ def keyPressEvent(self, event): ): selected_items = self.selectedItems() if not selected_items: + event.accept() return self.itemWidget(selected_items[0]).keyPressEvent(event) else: super().keyPressEvent(event) - self.parent().keyPressEvent(event) def startDrag(self, supportedActions): selected_items = self.selectedItems() @@ -457,6 +462,8 @@ def keyPressEvent(self, event): if deleted: self.itemsDeleted.emit(deleted) + event.accept() + elif key == QtCore.Qt.Key.Key_C and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier: text = [] for item in self.selectedItems(): @@ -471,6 +478,8 @@ def keyPressEvent(self, event): text = "" QtWidgets.QApplication.instance().clipboard().setText(text) + event.accept() + elif ( key == QtCore.Qt.Key.Key_V and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier @@ -483,6 +492,7 @@ def keyPressEvent(self, event): self.itemsPasted.emit() except: pass + event.accept() else: super().keyPressEvent(event) diff --git a/src/asammdf/gui/widgets/main.py b/src/asammdf/gui/widgets/main.py index b4f6e6aaa..468a9e191 100644 --- a/src/asammdf/gui/widgets/main.py +++ b/src/asammdf/gui/widgets/main.py @@ -1245,6 +1245,7 @@ def close_file(self, index): if widget: widget.close() widget.setParent(None) + widget.deleteLater() if self.files.count(): self.files.setCurrentIndex(0) @@ -1317,6 +1318,7 @@ def keyPressEvent(self, event): if self.files.count() and self.stackedWidget.currentIndex() == 0: self.files.currentWidget().keyPressEvent(event) elif self.files.count() and self.stackedWidget.currentIndex() == 2: + event.accept() count = self.files.count() channels_dbs = [self.files.widget(i).mdf.channels_db for i in range(count)] measurements = [str(self.files.widget(i).mdf.name) for i in range(count)] @@ -1361,6 +1363,7 @@ def keyPressEvent(self, event): elif key == QtCore.Qt.Key.Key_F11: self.toggle_fullscreen() + event.accept() elif key in (QtCore.Qt.Key.Key_F2, QtCore.Qt.Key.Key_F3, QtCore.Qt.Key.Key_F4): if self.files.count() and self.stackedWidget.currentIndex() == 0: @@ -1371,6 +1374,7 @@ def keyPressEvent(self, event): elif key == QtCore.Qt.Key.Key_F4: window_type = "Tabular" self.files.currentWidget()._create_window(None, window_type) + event.accept() else: super().keyPressEvent(event) diff --git a/src/asammdf/gui/widgets/mdi_area.py b/src/asammdf/gui/widgets/mdi_area.py index a780191bd..e2ea4e356 100644 --- a/src/asammdf/gui/widgets/mdi_area.py +++ b/src/asammdf/gui/widgets/mdi_area.py @@ -182,7 +182,18 @@ def build_mime_from_config( return mime, descriptions, found, not_found, computed -def extract_signals_using_pattern(mdf, pattern_info, ignore_value2text_conversions, uuid): +def extract_signals_using_pattern( + mdf, channels_db, pattern_info, ignore_value2text_conversions, uuid=None, as_names=False +): + if not mdf and not channels_db: + if as_names: + return set() + else: + return {} + + elif not channels_db: + channels_db = mdf.channels_db + pattern = pattern_info["pattern"] match_type = pattern_info["match_type"] case_sensitive = pattern_info.get("case_sensitive", False) @@ -205,7 +216,7 @@ def extract_signals_using_pattern(mdf, pattern_info, ignore_value2text_conversio matches = {} - for name, entries in mdf.channels_db.items(): + for name, entries in channels_db.items(): if pattern.fullmatch(name): for entry in entries: if entry in matches: @@ -217,6 +228,9 @@ def extract_signals_using_pattern(mdf, pattern_info, ignore_value2text_conversio print(format_exc()) signals = [] else: + if (as_names and filter_type == "Unspecified") or not mdf: + return {match[0] for match in matches} + psignals = mdf.select( matches, ignore_value2text_conversions=ignore_value2text_conversions, @@ -273,7 +287,10 @@ def extract_signals_using_pattern(mdf, pattern_info, ignore_value2text_conversio sig.ranges = [] output_signals[uuid] = sig - return output_signals + if as_names: + return {sig.name for sig in signals} + else: + return output_signals def generate_window_title(mdi, window_name="", title=""): @@ -638,10 +655,11 @@ def __init__(self, comparison=False, *args, **kwargs): def add_pattern_group(self, plot, group): signals = extract_signals_using_pattern( - self.mdf, - group.pattern, - self.ignore_value2text_conversions, - self.uuid, + mdf=self.mdf, + channels_db=None, + pattern_info=group.pattern, + ignore_value2text_conversions=self.ignore_value2text_conversions, + uuid=self.uuid, ) signals = { @@ -970,8 +988,6 @@ def add_new_channels(self, names, widget, mime_data=None): required_channels.update(measured_signals) - required_channels = {key: sig.physical(copy=False) for key, sig in required_channels.items()} - if required_channels: all_timebase = np.unique( np.concatenate( @@ -1961,10 +1977,11 @@ def _add_numeric_window(self, names): signals.extend( extract_signals_using_pattern( - file.mdf, - pattern_group["pattern"], - file.ignore_value2text_conversions, - file.uuid, + mdf=file.mdf, + channels_db=None, + pattern_info=pattern_group["pattern"], + ignore_value2text_conversions=file.ignore_value2text_conversions, + uuid=file.uuid, ).values() ) @@ -2569,10 +2586,11 @@ def _add_tabular_window(self, names): [ (sig.name, sig.group_index, sig.channel_index) for sig in extract_signals_using_pattern( - file.mdf, - pattern_group["pattern"], - file.ignore_value2text_conversions, - file.uuid, + mdf=file.mdf, + channels_db=None, + pattern_info=pattern_group["pattern"], + ignore_value2text_conversions=file.ignore_value2text_conversions, + uuid=file.uuid, ).values() ] ) @@ -2657,6 +2675,7 @@ def clear_windows(self): self.mdi_area.removeSubWindow(window) widget.setParent(None) window.close() + widget.deleteLater() widget.close() def delete_functions(self, deleted_functions): @@ -2701,8 +2720,6 @@ def edit_channel(self, channel, item, widget): else: all_timebase = [] - required_channels = {key: sig.physical(copy=False) for key, sig in required_channels.items()} - computation = channel["computation"] signal = compute_signal( @@ -2822,10 +2839,11 @@ def _load_numeric_window(self, window_info): pattern_info = window_info["configuration"].get("pattern", {}) if pattern_info: signals = extract_signals_using_pattern( - self.mdf, - pattern_info, - self.ignore_value2text_conversions, - self.uuid, + mdf=self.mdf, + channels_db=None, + pattern_info=pattern_info, + ignore_value2text_conversions=self.ignore_value2text_conversions, + uuid=self.uuid, ) signals = list(signals.values()) @@ -3041,10 +3059,11 @@ def _load_plot_window(self, window_info): pattern_info = window_info["configuration"].get("pattern", {}) if pattern_info: plot_signals = extract_signals_using_pattern( - self.mdf, - pattern_info, - self.ignore_value2text_conversions, - self.uuid, + mdf=self.mdf, + channels_db=None, + pattern_info=pattern_info, + ignore_value2text_conversions=self.ignore_value2text_conversions, + uuid=self.uuid, ) mime_data = None @@ -3174,8 +3193,6 @@ def _load_plot_window(self, window_info): required_channels.update(measured_signals) - required_channels = {key: sig.physical(copy=False) for key, sig in required_channels.items()} - for sig_uuid, channel in computed.items(): computation = channel["computation"] @@ -3466,10 +3483,11 @@ def _load_tabular_window(self, window_info): found_signals = [] signals_ = extract_signals_using_pattern( - self.mdf, - pattern_info, - self.ignore_value2text_conversions, - self.uuid, + mdf=self.mdf, + channels_db=None, + pattern_info=pattern_info, + ignore_value2text_conversions=self.ignore_value2text_conversions, + uuid=self.uuid, ).values() try: @@ -3567,6 +3585,8 @@ def _load_tabular_window(self, window_info): w.setWindowTitle(generate_window_title(w, window_info["type"], window_info["title"])) + mode = tabular.format_selection.currentText() + filter_count = 0 available_columns = [signals.index.name, *signals.columns] for filter_info in window_info["configuration"]["filters"]: @@ -3579,7 +3599,12 @@ def _load_tabular_window(self, window_info): filter.relation.setCurrentText(filter_info["relation"]) filter.column.setCurrentText(filter_info["column"]) filter.op.setCurrentText(filter_info["op"]) - filter.target.setText(str(filter_info["target"]).strip('"')) + if mode == "phys": + filter.target.setText(str(filter_info["target"]).strip('"')) + elif mode == "hex": + filter.target.setText(hex(filter_info["target"]).strip('"')) + elif mode == "bin": + filter.target.setText(bin(filter_info["target"]).strip('"')) filter.validate_target() filter_count += 1 @@ -3773,11 +3798,20 @@ def set_x_range(self, widget, x_range): if not self.subplots_link: return + if not isinstance(x_range, (tuple, list)): + return + + if not len(x_range) == 2: + return + + if np.any(np.isnan(x_range)) or not np.all(np.isfinite(x_range)): + return + for mdi in self.mdi_area.subWindowList(): wid = mdi.widget() if wid is not widget and isinstance(wid, Plot): wid._inhibit_x_range_changed_signal = True - wid.plot.viewbox.setXRange(*x_range, update=True) + wid.plot.viewbox.setXRange(*x_range, padding=0, update=True) wid._inhibit_x_range_changed_signal = False def set_region(self, widget, region): @@ -4015,6 +4049,7 @@ def verify_bookmarks(self, bookmarks, plot): self.mdi_area.removeSubWindow(window) widget.setParent(None) widget.close() + widget.deleteLater() window.close() suffix = original_file_name.suffix.lower() diff --git a/src/asammdf/gui/widgets/numeric.py b/src/asammdf/gui/widgets/numeric.py index 856491f22..1f6b1b479 100644 --- a/src/asammdf/gui/widgets/numeric.py +++ b/src/asammdf/gui/widgets/numeric.py @@ -648,6 +648,7 @@ def keyPressEvent(self, event): modifiers = event.modifiers() if key == QtCore.Qt.Key.Key_Delete and modifiers == QtCore.Qt.KeyboardModifier.NoModifier: + event.accept() selected_items = {index.row() for index in self.selectedIndexes() if index.isValid()} for row in reversed(list(selected_items)): @@ -657,6 +658,8 @@ def keyPressEvent(self, event): self.backend.update() elif key == QtCore.Qt.Key.Key_R and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier: + event.accept() + selected_items = {index.row() for index in self.selectedIndexes() if index.isValid()} if selected_items: @@ -687,6 +690,8 @@ def keyPressEvent(self, event): modifiers == (QtCore.Qt.KeyboardModifier.ControlModifier | QtCore.Qt.KeyboardModifier.ShiftModifier) and key == QtCore.Qt.Key.Key_C ): + event.accept() + selected_items = {index.row() for index in self.selectedIndexes() if index.isValid()} if not selected_items: @@ -710,6 +715,8 @@ def keyPressEvent(self, event): modifiers == (QtCore.Qt.KeyboardModifier.ControlModifier | QtCore.Qt.KeyboardModifier.ShiftModifier) and key == QtCore.Qt.Key.Key_V ): + event.accept() + info = QtWidgets.QApplication.instance().clipboard().text() selected_items = {index.row() for index in self.selectedIndexes() if index.isValid()} @@ -1574,6 +1581,8 @@ def keyPressEvent(self, event): key in (QtCore.Qt.Key.Key_H, QtCore.Qt.Key.Key_B, QtCore.Qt.Key.Key_P, QtCore.Qt.Key.Key_T) and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier ): + event.accept() + if key == QtCore.Qt.Key.Key_H: self.set_format("Hex") elif key == QtCore.Qt.Key.Key_B: @@ -1582,12 +1591,13 @@ def keyPressEvent(self, event): self.set_format("Ascii") else: self.set_format("Physical") - event.accept() + elif ( key == QtCore.Qt.Key.Key_Right and modifiers == QtCore.Qt.KeyboardModifier.NoModifier and self.mode == "offline" ): + event.accept() self.timestamp_slider.setValue(self.timestamp_slider.value() + 1) elif ( @@ -1595,6 +1605,7 @@ def keyPressEvent(self, event): and modifiers == QtCore.Qt.KeyboardModifier.NoModifier and self.mode == "offline" ): + event.accept() self.timestamp_slider.setValue(self.timestamp_slider.value() - 1) elif ( @@ -1602,6 +1613,7 @@ def keyPressEvent(self, event): and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier and self.mode == "offline" ): + event.accept() file_name, _ = QtWidgets.QFileDialog.getSaveFileName( self, "Save as measurement file", @@ -1632,9 +1644,11 @@ def keyPressEvent(self, event): mdf.save(file_name, overwrite=True) elif key == QtCore.Qt.Key.Key_BracketLeft and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier: + event.accept() self.decrease_font() elif key == QtCore.Qt.Key.Key_BracketRight and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier: + event.accept() self.increase_font() elif ( @@ -1642,6 +1656,8 @@ def keyPressEvent(self, event): and modifiers == QtCore.Qt.KeyboardModifier.ShiftModifier and self.mode == "offline" ): + event.accept() + value, ok = QtWidgets.QInputDialog.getDouble( self, "Go to time stamp", diff --git a/src/asammdf/gui/widgets/plot.py b/src/asammdf/gui/widgets/plot.py index b54a4b841..c49a848e1 100644 --- a/src/asammdf/gui/widgets/plot.py +++ b/src/asammdf/gui/widgets/plot.py @@ -19,16 +19,12 @@ from ... import tool as Tool from ...blocks.conversion_utils import from_dict, to_dict +from ...blocks.cutils import get_idx_with_edges, positions from ...blocks.utils import target_byte_order from ..dialogs.messagebox import MessageBox from ..utils import FONT_SIZE, value_as_str from .viewbox import ViewBoxWithCursor -try: - from ...blocks.cutils import positions -except: - pass - @lru_cache(maxsize=1024) def polygon_and_ndarray(size): @@ -224,10 +220,10 @@ def __init__(self, signal, index=0, trim_info=None, duplication=1, allow_trim=Tr self.timestamps = self.timestamps[~nans] if self.samples.dtype.byteorder not in target_byte_order: - self.samples = self.samples.byteswap().newbyteorder() + self.samples = self.samples.view(self.samples.dtype.newbyteorder()) if self.timestamps.dtype.byteorder not in target_byte_order: - self.timestamps = self.timestamps.byteswap().newbyteorder() + self.timestamps = self.timestamps.view(self.timestamps.dtype.newbyteorder()) if self.timestamps.dtype != float64: self.timestamps = self.timestamps.astype(float64) @@ -2181,12 +2177,21 @@ def channel_item_to_config(self, item): return channel def channel_selection_changed(self, update=False): + def set_focused(item): + if item.type() == item.Channel: + item.signal.enable = True + + elif item.type() == item.Group: + for i in range(item.childCount()): + set_focused(item.child(i)) + if self.focused_mode: for signal in self.plot.signals: signal.enable = False + for item in self.channel_selection.selectedItems(): - if item.type() == item.Channel: - item.signal.enable = True + set_focused(item) + self.plot.update() else: if update: @@ -2306,6 +2311,7 @@ def channel_selection_reduced(self, deleted): if not count: self.info.set_stats(None) self.info_uuid = None + self.info.set_stats(None) self.selected_channel_value.setText("") self.close_request.emit() @@ -2620,6 +2626,7 @@ def keyPressEvent(self, event): modifiers = event.modifiers() if key == QtCore.Qt.Key.Key_M and modifiers == QtCore.Qt.KeyboardModifier.NoModifier: + event.accept() ch_size, plt_size, info_size = self.splitter.sizes() if self.info.isVisible(): @@ -2648,6 +2655,8 @@ def keyPressEvent(self, event): self.focused_mode_btn.setFlat(True) self.channel_selection_changed(update=True) + event.accept() + elif ( key in (QtCore.Qt.Key.Key_B, QtCore.Qt.Key.Key_H, QtCore.Qt.Key.Key_P, QtCore.Qt.Key.Key_T) and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier @@ -2708,6 +2717,8 @@ def keyPressEvent(self, event): self.current_uuid_changed(self.plot.current_uuid) self.plot.update() + event.accept() + elif ( key in (QtCore.Qt.Key.Key_R, QtCore.Qt.Key.Key_S) and modifiers == QtCore.Qt.KeyboardModifier.AltModifier @@ -2787,6 +2798,8 @@ def keyPressEvent(self, event): if self.plot.cursor1: self.plot.cursor_moved.emit(self.plot.cursor1) + event.accept() + elif key == QtCore.Qt.Key.Key_I and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier: if self.plot.cursor1: position = self.plot.cursor1.value() @@ -2819,6 +2832,8 @@ def keyPressEvent(self, event): self.update() + event.accept() + elif key == QtCore.Qt.Key.Key_I and modifiers == QtCore.Qt.KeyboardModifier.AltModifier: self.show_bookmarks = not self.show_bookmarks if self.show_bookmarks: @@ -2830,6 +2845,7 @@ def keyPressEvent(self, event): bookmark.visible = self.show_bookmarks self.plot.update() + event.accept() elif key == QtCore.Qt.Key.Key_G and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier: selected_items = [ @@ -2868,6 +2884,8 @@ def keyPressEvent(self, event): self.plot.update() + event.accept() + elif key == QtCore.Qt.Key.Key_R and modifiers == QtCore.Qt.KeyboardModifier.NoModifier: iterator = QtWidgets.QTreeWidgetItemIterator(self.channel_selection) while item := iterator.value(): @@ -2917,6 +2935,8 @@ def keyPressEvent(self, event): else: self.undo_zoom() + event.accept() + elif key == QtCore.Qt.Key.Key_W and modifiers == QtCore.Qt.KeyboardModifier.ShiftModifier: if self.enable_zoom_history and self.zoom_history: self.zoom_history_index = 0 @@ -2941,8 +2961,9 @@ def keyPressEvent(self, event): self.plot.block_zoom_signal = False + event.accept() else: - super().keyPressEvent(event) + event.ignore() def mousePressEvent(self, event): self.clicked.emit() @@ -4186,6 +4207,7 @@ def dragEnterEvent(self, e): super().dragEnterEvent(e) def draw_grids(self, paint, event_rect): + ratio = self.devicePixelRatio() if self.y_axis.grid or self.x_axis.grid: rect = self.viewbox.sceneBoundingRect() y_delta = rect.y() @@ -4195,7 +4217,7 @@ def draw_grids(self, paint, event_rect): for pen, p1, p2 in self.y_axis.tickSpecs: pen2 = fn.mkPen(pen) pen2.setStyle(QtCore.Qt.PenStyle.DashLine) - y_pos = p1.y() + y_delta + y_pos = p1.y() / ratio + y_delta paint.setPen(pen2) paint.drawLine( QtCore.QPointF(0, y_pos), @@ -4206,7 +4228,7 @@ def draw_grids(self, paint, event_rect): for pen, p1, p2 in self.x_axis.tickSpecs: pen2 = fn.mkPen(pen) pen2.setStyle(QtCore.Qt.PenStyle.DashLine) - x_pos = p1.x() + x_delta + x_pos = p1.x() / ratio + x_delta paint.setPen(pen2) paint.drawLine( QtCore.QPointF(x_pos, 0), @@ -4495,15 +4517,23 @@ def keyPressEvent(self, event): elif key == QtCore.Qt.Key.Key_G: if modifier == QtCore.Qt.KeyboardModifier.NoModifier: - y = self.plotItem.ctrl.yGridCheck.isChecked() - x = self.plotItem.ctrl.xGridCheck.isChecked() + y = self.y_axis.grid + x = self.x_axis.grid + # y = self.plotItem.ctrl.yGridCheck.isChecked() + # x = self.plotItem.ctrl.xGridCheck.isChecked() if x and y: - self.plotItem.showGrid(x=False, y=False) + self.y_axis.grid = False + self.x_axis.grid = False elif x: - self.plotItem.showGrid(x=True, y=True) + self.y_axis.grid = True + self.x_axis.grid = True else: - self.plotItem.showGrid(x=True, y=False) + self.y_axis.grid = False + self.x_axis.grid = True + + self.x_axis.picture = None + self.y_axis.picture = None self.update() @@ -4616,6 +4646,7 @@ def keyPressEvent(self, event): self.region_lock = None self.region.setParent(None) self.region.hide() + self.region.deleteLater() self.region = None self.range_removed.emit() @@ -5084,7 +5115,10 @@ def keyPressEvent(self, event): handled = False if not handled: + event.ignore() self.parent().keyPressEvent(event) + else: + event.accept() def open_scale_editor(self, uuid): uuid = uuid or self.current_uuid @@ -5118,7 +5152,9 @@ def paintEvent(self, ev): super().paintEvent(ev) if self._pixmap is None: - self._pixmap = QtGui.QPixmap(event_rect.width(), event_rect.height()) + ratio = self.devicePixelRatio() + + self._pixmap = QtGui.QPixmap(int(event_rect.width() * ratio), int(event_rect.height() * ratio)) self._pixmap.fill(self.backgroundBrush().color()) paint = QtGui.QPainter() @@ -5129,16 +5165,14 @@ def paintEvent(self, ev): self.x_range, self.y_range = self.viewbox.viewRange() rect = self.viewbox.sceneBoundingRect() + rect.setSize(rect.size() * ratio) + rect.moveTo(rect.topLeft() * ratio) self.px = (self.x_range[1] - self.x_range[0]) / rect.width() self.py = rect.height() with_dots = self.with_dots - self.auto_clip_rect(paint) - - rect = self.viewbox.sceneBoundingRect() - delta = rect.x() x_start = self.x_range[0] @@ -5157,6 +5191,10 @@ def paintEvent(self, ev): else: cap_style = QtCore.Qt.PenCapStyle.RoundCap + curve = self._curve + step_mode = self.line_interconnect + default_connect = curve.opts["connect"] + for i, sig in enumerate(self.signals): if ( not sig.enable @@ -5252,9 +5290,29 @@ def paintEvent(self, ev): if not np.any(idx): continue - y = sig.plot_samples.astype("f8") - y[~idx] = np.inf - x = sig.plot_timestamps + if step_mode == "right": + idx_with_edges = get_idx_with_edges(idx) + _connect = "finite" + y = sig.plot_samples.astype("f8") + y[~idx_with_edges] = np.inf + x = sig.plot_timestamps + + elif step_mode == "": + last = idx[-1] + idx_with_edges = np.roll(idx, -1) + idx_with_edges[-1] = last + _connect = idx_with_edges.view("u1") + y = sig.plot_samples.astype("f8") + x = sig.plot_timestamps + + if step_mode == "left": + idx_with_edges = np.repeat(get_idx_with_edges(idx), 2) + idx_with_edges = np.insert(idx_with_edges, 0, idx_with_edges[0]) + _connect = idx_with_edges[:-1].view("u1") + y = sig.plot_samples.astype("f8") + x = sig.plot_timestamps + + curve.opts["connect"] = _connect x, y = self.scale_curve_to_pixmap(x, y, y_range=sig.y_range, x_start=x_start, delta=delta) @@ -5265,7 +5323,18 @@ def paintEvent(self, ev): paint.setPen(pen) paint.drawPath(self.generatePath(x, y)) + curve.opts["connect"] = default_connect + if with_dots: + y = sig.plot_samples.astype("f8") + y[~idx] = np.inf + + x = sig.plot_timestamps + + x, y = self.scale_curve_to_pixmap( + x, y, y_range=sig.y_range, x_start=x_start, delta=delta + ) + paint.setRenderHints(paint.RenderHint.Antialiasing, True) pen.setWidth(dots_with) pen.setCapStyle(cap_style) @@ -5281,8 +5350,11 @@ def paintEvent(self, ev): arr[:, 1] = y paint.drawPoints(poly) paint.setRenderHints(paint.RenderHint.Antialiasing, False) + paint.end() + self._pixmap.setDevicePixelRatio(self.devicePixelRatio()) + paint = QtGui.QPainter() vp = self.viewport() paint.begin(vp) @@ -5294,30 +5366,55 @@ def paintEvent(self, ev): if self.x_axis.picture is None: self.x_axis.paint(paint, None, None) + ratio = self.devicePixelRatio() + + r = self.y_axis.boundingRect() + r.setSize(self.y_axis.picture.size()) + r.moveTo(r.topLeft() * ratio) paint.drawPixmap( self.y_axis.sceneBoundingRect(), self.y_axis.picture, - self.y_axis.boundingRect(), + r, ) + + r = self.x_axis.boundingRect() + r.setSize(self.x_axis.picture.size()) + r.moveTo(r.topLeft() * ratio) paint.drawPixmap( self.x_axis.sceneBoundingRect(), self.x_axis.picture, - self.x_axis.boundingRect(), + r, ) for ax in self.axes: if isinstance(ax, FormatedAxis) and ax.isVisible(): if ax.picture is None: ax.paint(paint, None, None) + + r = ax.boundingRect() + r.setSize(ax.picture.size()) + r.moveTo(r.topLeft() * ratio) + paint.drawPixmap( ax.sceneBoundingRect(), ax.picture, - ax.boundingRect(), + r, ) - self.auto_clip_rect(paint) + r = self.viewbox.sceneBoundingRect() + r.setLeft(r.left() + 5) + r.setSize(r.size() * ratio) + r.moveTo(r.topLeft() * ratio) + + t = self.viewbox.sceneBoundingRect() + t = self.viewbox.sceneBoundingRect() + t.setLeft(t.left() + 5) - paint.drawPixmap(event_rect, self._pixmap, event_rect) + paint.setClipping(False) + paint.drawPixmap(t.toRect(), self._pixmap, r.toRect()) + paint.setClipping(True) + + self.auto_clip_rect(paint) self.draw_grids(paint, event_rect) diff --git a/src/asammdf/gui/widgets/python_highlighter.py b/src/asammdf/gui/widgets/python_highlighter.py index b5779447a..92ee2ae14 100644 --- a/src/asammdf/gui/widgets/python_highlighter.py +++ b/src/asammdf/gui/widgets/python_highlighter.py @@ -53,45 +53,45 @@ class PythonHighlighter(QtGui.QSyntaxHighlighter): # Python operators operators = [ - "=", + r"=", # Comparison - "==", + r"==", "!=", "<", "<=", ">", ">=", # Arithmetic - "\+", + r"\+", "-", - "\*", + r"\*", "/", "//", - "\%", - "\*\*", + r"\%", + r"\*\*", # In-place - "\+=", + r"\+=", "-=", - "\*=", + r"\*=", "/=", - "\%=", + r"\%=", # Bitwise - "\^", - "\|", - "\&", - "\~", + r"\^", + r"\|", + r"\&", + r"\~", ">>", "<<", ] # Python braces braces = [ - "\{", - "\}", - "\(", - "\)", - "\[", - "\]", + r"\{", + r"\}", + r"\(", + r"\)", + r"\[", + r"\]", ] def __init__(self, parent: QtGui.QTextDocument) -> None: diff --git a/src/asammdf/gui/widgets/signal_scale.py b/src/asammdf/gui/widgets/signal_scale.py index f14d2dcc1..8109252ea 100644 --- a/src/asammdf/gui/widgets/signal_scale.py +++ b/src/asammdf/gui/widgets/signal_scale.py @@ -195,8 +195,12 @@ def draw_plot(self, *args): pen.setWidth(2) pen.setStyle(QtCore.Qt.PenStyle.DashDotDotLine) painter.setPen(pen) - offset = int(PLOT_HEIGTH - self.offset.value() * PLOT_HEIGTH / 100) - painter.drawLine(0, offset, PLOT_HEIGTH + 2 * TEXT_WIDTH, offset) + offset = PLOT_HEIGTH - self.offset.value() * PLOT_HEIGTH / 100 + + p1 = QtCore.QPointF(0.0, offset) + p2 = QtCore.QPointF(float(PLOT_HEIGTH + 2 * TEXT_WIDTH), offset) + + painter.drawLine(p1, p2) painter.end() @@ -213,17 +217,23 @@ def keyPressEvent(self, event): QtCore.Qt.KeyboardModifier.NoModifier, QtCore.Qt.KeyboardModifier.ShiftModifier, ): + event.accept() self.zoom_in() + elif key == QtCore.Qt.Key.Key_O and modifiers in ( QtCore.Qt.KeyboardModifier.NoModifier, QtCore.Qt.KeyboardModifier.ShiftModifier, ): + event.accept() self.zoom_out() + elif key == QtCore.Qt.Key.Key_F and modifiers in ( QtCore.Qt.KeyboardModifier.NoModifier, QtCore.Qt.KeyboardModifier.ShiftModifier, ): + event.accept() self.fit() + else: super().keyPressEvent(event) diff --git a/src/asammdf/gui/widgets/tabular_base.py b/src/asammdf/gui/widgets/tabular_base.py index 7b8aa6e49..1eecc5f3f 100644 --- a/src/asammdf/gui/widgets/tabular_base.py +++ b/src/asammdf/gui/widgets/tabular_base.py @@ -462,6 +462,7 @@ def keyPressEvent(self, event): modifiers = event.modifiers() if key == QtCore.Qt.Key.Key_R and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier: + event.accept() selected_items = {index.column() for index in self.selectedIndexes() if index.isValid()} if selected_items: @@ -1681,6 +1682,7 @@ def keyPressEvent(self, event): key in (QtCore.Qt.Key.Key_H, QtCore.Qt.Key.Key_B, QtCore.Qt.Key.Key_P) and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier ): + event.accept() if key == QtCore.Qt.Key.Key_H: self.format_selection.setCurrentText("hex") elif key == QtCore.Qt.Key.Key_B: @@ -1688,9 +1690,8 @@ def keyPressEvent(self, event): else: self.format_selection.setCurrentText("phys") - event.accept() - elif key == QtCore.Qt.Key.Key_S and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier: + event.accept() file_name, _ = QtWidgets.QFileDialog.getSaveFileName( self, "Save as measurement file", @@ -1704,12 +1705,15 @@ def keyPressEvent(self, event): mdf.save(file_name, overwrite=True) elif key == QtCore.Qt.Key.Key_BracketLeft and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier: + event.accept() self.decrease_font() elif key == QtCore.Qt.Key.Key_BracketRight and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier: + event.accept() self.increase_font() elif key == QtCore.Qt.Key.Key_G and modifiers == QtCore.Qt.KeyboardModifier.ShiftModifier: + event.accept() value, ok = QtWidgets.QInputDialog.getDouble( self, "Go to time stamp", @@ -2037,16 +2041,18 @@ def keyPressEvent(self, event): QtWidgets.QWidget.keyPressEvent(self, event) mods = event.modifiers() - # Ctrl+C if event.key() == Qt.Key.Key_C and (mods & Qt.KeyboardModifier.ControlModifier): + event.accept() self.copy(header=True) - # Ctrl+Shift+C + elif ( event.key() == Qt.Key.Key_C and (mods & Qt.KeyboardModifier.ShiftModifier) and (mods & Qt.KeyboardModifier.ControlModifier) ): + event.accept() self.copy(header=True) + else: self.dataView.keyPressEvent(event) diff --git a/src/asammdf/gui/widgets/tree.py b/src/asammdf/gui/widgets/tree.py index 45d9f0b50..2c00644cd 100644 --- a/src/asammdf/gui/widgets/tree.py +++ b/src/asammdf/gui/widgets/tree.py @@ -4,7 +4,6 @@ import json import os import re -from traceback import format_exc import numpy as np from pyqtgraph import functions as fn @@ -218,6 +217,7 @@ def __init__(self, *args, **kwargs): def keyPressEvent(self, event): key = event.key() if key == QtCore.Qt.Key.Key_Space: + event.accept() selected_items = self.selectedItems() if not selected_items: return @@ -558,6 +558,7 @@ def keyPressEvent(self, event): modifiers = event.modifiers() if key == QtCore.Qt.Key.Key_Delete and self.can_delete_items: + event.accept() selected_items = self.selectedItems() deleted = list(set(get_data(self.plot, selected_items, uuids_only=True))) @@ -577,14 +578,23 @@ def keyPressEvent(self, event): self.update_channel_groups_count() elif key == QtCore.Qt.Key.Key_Insert and modifiers == QtCore.Qt.KeyboardModifier.ControlModifier: + event.accept() + if hasattr(self.plot.owner, "mdf"): + mdf = self.plot.owner.mdf + channels_db = None + else: + mdf = None + channels_db = self.plot.owner.channels_db + dlg = AdvancedSearch( - None, + mdf=mdf, show_add_window=False, show_apply=True, show_search=False, show_pattern=True, window_title="Add pattern based group", parent=self, + channels_db=channels_db, ) dlg.setModal(True) dlg.exec_() @@ -624,6 +634,7 @@ def keyPressEvent(self, event): self.update_channel_groups_count() elif key == QtCore.Qt.Key.Key_Insert and modifiers == QtCore.Qt.KeyboardModifier.ShiftModifier: + event.accept() text, ok = QtWidgets.QInputDialog.getText(self, "Channel group name", "New channel group name:") if ok: group = ChannelsTreeItem(ChannelsTreeItem.Group, name=text) @@ -645,6 +656,7 @@ def keyPressEvent(self, event): self.update_channel_groups_count() elif key == QtCore.Qt.Key.Key_Space: + event.accept() selected_items = self.selectedItems() if not selected_items: return @@ -668,6 +680,7 @@ def keyPressEvent(self, event): item.setHidden(True) elif modifiers == QtCore.Qt.KeyboardModifier.NoModifier and key == QtCore.Qt.Key.Key_C: + event.accept() selected_items = self.selectedItems() if not selected_items: return @@ -686,12 +699,14 @@ def keyPressEvent(self, event): item.color = color elif modifiers == QtCore.Qt.KeyboardModifier.ControlModifier and key == QtCore.Qt.Key.Key_C: + event.accept() selected_items = validate_drag_items(self.invisibleRootItem(), self.selectedItems(), []) data = get_data(self.plot, selected_items, uuids_only=False) data = substitude_mime_uuids(data, None, force=True) QtWidgets.QApplication.instance().clipboard().setText(json.dumps(data)) elif modifiers == QtCore.Qt.KeyboardModifier.ControlModifier and key == QtCore.Qt.Key.Key_V: + event.accept() try: data = QtWidgets.QApplication.instance().clipboard().text() data = json.loads(data) @@ -701,6 +716,7 @@ def keyPressEvent(self, event): pass elif modifiers == QtCore.Qt.KeyboardModifier.ControlModifier and key == QtCore.Qt.Key.Key_N: + event.accept() selected_items = self.selectedItems() if not selected_items: return @@ -710,6 +726,7 @@ def keyPressEvent(self, event): QtWidgets.QApplication.instance().clipboard().setText(text) elif modifiers == QtCore.Qt.KeyboardModifier.ControlModifier and key == QtCore.Qt.Key.Key_R: + event.accept() selected_items = self.selectedItems() if not selected_items: return @@ -755,6 +772,7 @@ def keyPressEvent(self, event): modifiers == (QtCore.Qt.KeyboardModifier.ControlModifier | QtCore.Qt.KeyboardModifier.ShiftModifier) and key == QtCore.Qt.Key.Key_C ): + event.accept() selected_items = [ item for item in self.selectedItems() @@ -769,46 +787,47 @@ def keyPressEvent(self, event): modifiers == (QtCore.Qt.KeyboardModifier.ControlModifier | QtCore.Qt.KeyboardModifier.ShiftModifier) and key == QtCore.Qt.Key.Key_V ): + event.accept() info = QtWidgets.QApplication.instance().clipboard().text() selected_items = self.selectedItems() if not selected_items: return - try: - info = json.loads(info) + info = json.loads(info) + if info["type"] == "channel": info["color"] = fn.mkColor(info["color"]) - except: - print(format_exc()) - else: + for item in selected_items: - try: - item.color = info["color"] - item.precision = info["precision"] - item.format = info["format"] - - item.setCheckState( - self.IndividualAxisColumn, - QtCore.Qt.CheckState.Checked if info["individual_axis"] else QtCore.Qt.CheckState.Unchecked, - ) - item.setCheckState( - self.CommonAxisColumn, - QtCore.Qt.CheckState.Checked if info["ylink"] else QtCore.Qt.CheckState.Unchecked, - ) + item.color = info["color"] + item.precision = info["precision"] + item.format = info["format"] - if item.type() == ChannelsTreeItem.Channel: - plot = item.treeWidget().plot.plot - sig, index = plot.signal_by_uuid(item.uuid) - sig.y_range = info["y_range"] + item.setCheckState( + self.IndividualAxisColumn, + QtCore.Qt.CheckState.Checked if info["individual_axis"] else QtCore.Qt.CheckState.Unchecked, + ) + item.setCheckState( + self.CommonAxisColumn, + QtCore.Qt.CheckState.Checked if info["ylink"] else QtCore.Qt.CheckState.Unchecked, + ) - item.set_ranges(info["ranges"]) + if item.type() == ChannelsTreeItem.Channel: + plot = item.treeWidget().plot.plot + sig, index = plot.signal_by_uuid(item.uuid) + sig.y_range = info["y_range"] + + item.set_ranges(info["ranges"]) - if "conversion" in info: - item.set_conversion(from_dict(info["conversion"])) - elif item.type() == ChannelsTreeItem.Group: - item.set_ranges(info["ranges"]) + if "conversion" in info: + item.set_conversion(from_dict(info["conversion"])) - except: - print(format_exc()) + elif item.type() == ChannelsTreeItem.Group: + item.set_ranges(info["ranges"]) + + elif info["type"] == "group": + for item in selected_items: + if item.type() in (ChannelsTreeItem.Channel, ChannelsTreeItem.Group): + item.set_ranges(info["ranges"]) elif modifiers == QtCore.Qt.KeyboardModifier.ShiftModifier and key in ( QtCore.Qt.Key.Key_Up, @@ -1905,30 +1924,34 @@ def _get_color_using_ranges(self, value, pen=False): def get_display_properties(self): if self.type() == ChannelsTreeItem.Group: - if self.childCount(): - return self.child(0).get_display_properties() - return "" - info = { - "color": self.color.name(), - "precision": self.precision, - "ylink": self.checkState(self.CommonAxisColumn) == QtCore.Qt.CheckState.Checked, - "individual_axis": self.checkState(self.IndividualAxisColumn) == QtCore.Qt.CheckState.Checked, - "format": self.format, - "ranges": copy_ranges(self.ranges), - } - - if self.signal.flags & Signal.Flags.user_defined_conversion: - info["conversion"] = to_dict(self.signal.conversion) + info = { + "type": "group", + "ranges": copy_ranges(self.ranges), + } - for range_info in info["ranges"]: - range_info["background_color"] = range_info["background_color"].name() - range_info["font_color"] = range_info["font_color"].name() + elif self.type() == ChannelsTreeItem.Channel: + info = { + "type": "channel", + "color": self.color.name(), + "precision": self.precision, + "ylink": self.checkState(self.CommonAxisColumn) == QtCore.Qt.CheckState.Checked, + "individual_axis": self.checkState(self.IndividualAxisColumn) == QtCore.Qt.CheckState.Checked, + "format": self.format, + "ranges": copy_ranges(self.ranges), + } + + if self.signal.flags & Signal.Flags.user_defined_conversion: + info["conversion"] = to_dict(self.signal.conversion) - plot = self.treeWidget().plot.plot + plot = self.treeWidget().plot.plot - sig, index = plot.signal_by_uuid(self.uuid) + sig, index = plot.signal_by_uuid(self.uuid) - info["y_range"] = tuple(float(e) for e in sig.y_range) + info["y_range"] = tuple(float(e) for e in sig.y_range) + + for range_info in info["ranges"]: + range_info["background_color"] = range_info["background_color"].name() + range_info["font_color"] = range_info["font_color"].name() return json.dumps(info) @@ -2111,17 +2134,18 @@ def set_disabled(self, disabled, preserve_subgroup_state=True): child.set_disabled(disabled, preserve_subgroup_state=False) def set_fmt(self, fmt): - if self.kind in "SUV": - self.fmt = "{}" - elif self.kind == "f": - self.fmt = f"{{:.{self.precision}f}}" - else: - if fmt == "hex": - self.fmt = "0x{:x}" - elif fmt == "bin": - self.fmt = "0b{:b}" - elif fmt == "phys": + if self.type() == self.Channel: + if self.kind in "SUV": self.fmt = "{}" + elif self.kind == "f": + self.fmt = f"{{:.{self.precision}f}}" + else: + if fmt == "hex": + self.fmt = "0x{:x}" + elif fmt == "bin": + self.fmt = "0b{:b}" + elif fmt == "phys": + self.fmt = "{}" def set_pattern(self, pattern): if pattern: diff --git a/src/asammdf/gui/widgets/viewbox.py b/src/asammdf/gui/widgets/viewbox.py index 7d899fc5d..69dc68654 100644 --- a/src/asammdf/gui/widgets/viewbox.py +++ b/src/asammdf/gui/widgets/viewbox.py @@ -152,6 +152,7 @@ def __init__(self, plot, *args, **kwargs): super().__init__(*args, **kwargs) self.menu.setParent(None) + self.menu.deleteLater() self.menu = None self.menu = ViewBoxMenu(self) diff --git a/src/asammdf/mdf.py b/src/asammdf/mdf.py index 2010a6d86..e7e369b52 100644 --- a/src/asammdf/mdf.py +++ b/src/asammdf/mdf.py @@ -3929,291 +3929,284 @@ def iter_to_dataframe( yield df mdf.close() - return - - # else: channels is None + else: + # channels is None - df = {} - self._set_temporary_master(None) + df = {} + self._set_temporary_master(None) - masters = {index: self.get_master(index) for index in self.virtual_groups} + masters = {index: self.get_master(index) for index in self.virtual_groups} - if raster is not None: - try: - raster = float(raster) - assert raster > 0 - except (TypeError, ValueError): - if isinstance(raster, str): - raster = self.get(raster, raw=True, ignore_invalidation_bits=True).timestamps + if raster is not None: + try: + raster = float(raster) + assert raster > 0 + except (TypeError, ValueError): + if isinstance(raster, str): + raster = self.get(raster, raw=True, ignore_invalidation_bits=True).timestamps + else: + raster = np.array(raster) else: - raster = np.array(raster) - else: - raster = master_using_raster(self, raster) - master = raster - else: - if masters: - master = reduce(np.union1d, masters.values()) + raster = master_using_raster(self, raster) + master = raster else: - master = np.array([], dtype="= "4.00" else "latin-1" - signal.raw = False - signal.conversion = None - if signal.samples.dtype.kind == "S": - signal.encoding = "utf-8" if self.version >= "4.00" else "latin-1" + for s_index, sig in enumerate(signals): + sig = sig.validate(copy=False) - for s_index, sig in enumerate(signals): - sig = sig.validate(copy=False) + if len(sig) == 0: + if empty_channels == "zeros": + sig.samples = np.zeros( + len(master) if virtual_group.cycles_nr == 0 else virtual_group.cycles_nr, + dtype=sig.samples.dtype, + ) + sig.timestamps = master if virtual_group.cycles_nr == 0 else group_master - if len(sig) == 0: - if empty_channels == "zeros": - sig.samples = np.zeros( - len(master) if virtual_group.cycles_nr == 0 else virtual_group.cycles_nr, - dtype=sig.samples.dtype, - ) - sig.timestamps = master if virtual_group.cycles_nr == 0 else group_master + signals[s_index] = sig - signals[s_index] = sig + if use_interpolation: + same_master = np.array_equal(master, group_master) - if use_interpolation: - same_master = np.array_equal(master, group_master) + if not same_master and interpolate_outwards_with_nan: + idx = np.argwhere((master >= group_master[0]) & (master <= group_master[-1])).flatten() - if not same_master and interpolate_outwards_with_nan: - idx = np.argwhere((master >= group_master[0]) & (master <= group_master[-1])).flatten() + cycles = len(group_master) - cycles = len(group_master) + signals = [ + signal.interp( + master, + integer_interpolation_mode=self._integer_interpolation, + float_interpolation_mode=self._float_interpolation, + ) + if not same_master or len(signal) != cycles + else signal + for signal in signals + ] - signals = [ - signal.interp( - master, - integer_interpolation_mode=self._integer_interpolation, - float_interpolation_mode=self._float_interpolation, - ) - if not same_master or len(signal) != cycles - else signal - for signal in signals - ] + if not same_master and interpolate_outwards_with_nan: + for sig in signals: + sig.timestamps = sig.timestamps[idx] + sig.samples = sig.samples[idx] - if not same_master and interpolate_outwards_with_nan: - for sig in signals: - sig.timestamps = sig.timestamps[idx] - sig.samples = sig.samples[idx] + group_master = master - group_master = master + signals = [sig for sig in signals if len(sig)] - signals = [sig for sig in signals if len(sig)] + if signals: + diffs = np.diff(group_master, prepend=-np.inf) > 0 - if signals: - diffs = np.diff(group_master, prepend=-np.inf) > 0 + if group_master.dtype.byteorder not in target_byte_order: + group_master = group_master.view(group_master.dtype.newbyteorder()) - if group_master.dtype.byteorder not in target_byte_order: - group_master = group_master.byteswap().newbyteorder() + if np.all(diffs): + index = pd.Index(group_master, tupleize_cols=False) - if np.all(diffs): - index = pd.Index(group_master, tupleize_cols=False) + else: + idx = np.argwhere(diffs).flatten() + group_master = group_master[idx] - else: - idx = np.argwhere(diffs).flatten() - group_master = group_master[idx] + index = pd.Index(group_master, tupleize_cols=False) - index = pd.Index(group_master, tupleize_cols=False) + for sig in signals: + sig.samples = sig.samples[idx] + sig.timestamps = sig.timestamps[idx] - for sig in signals: - sig.samples = sig.samples[idx] - sig.timestamps = sig.timestamps[idx] + size = len(index) + for k, sig in enumerate(signals): + if sig.timestamps.dtype.byteorder not in target_byte_order: + sig.timestamps = sig.timestamps.view(sig.timestamps.dtype.newbyteorder()) - size = len(index) - for k, sig in enumerate(signals): - if sig.timestamps.dtype.byteorder not in target_byte_order: - sig.timestamps = sig.timestamps.byteswap().newbyteorder() + sig_index = index if len(sig) == size else pd.Index(sig.timestamps, tupleize_cols=False) - sig_index = index if len(sig) == size else pd.Index(sig.timestamps, tupleize_cols=False) + # byte arrays + if len(sig.samples.shape) > 1: + if use_display_names: + channel_name = list(sig.display_names)[0] if sig.display_names else sig.name + else: + channel_name = sig.name - # byte arrays - if len(sig.samples.shape) > 1: - if use_display_names: - channel_name = list(sig.display_names)[0] if sig.display_names else sig.name - else: - channel_name = sig.name + channel_name = used_names.get_unique_name(channel_name) - channel_name = used_names.get_unique_name(channel_name) + if sig.samples.dtype.byteorder not in target_byte_order: + sig.samples = sig.samples.view(sig.samples.dtype.newbyteorder()) - if sig.samples.dtype.byteorder not in target_byte_order: - sig.samples = sig.samples.byteswap().newbyteorder() + df[channel_name] = pd.Series( + list(sig.samples), + index=sig_index, + ) - df[channel_name] = pd.Series( - list(sig.samples), - index=sig_index, - ) + # arrays and structures + elif sig.samples.dtype.names: + for name, series in components( + sig.samples, + sig.name, + used_names, + master=sig_index, + only_basenames=only_basenames, + ): + df[name] = series - # arrays and structures - elif sig.samples.dtype.names: - for name, series in components( - sig.samples, - sig.name, - used_names, - master=sig_index, - only_basenames=only_basenames, - ): - df[name] = series - - # scalars - else: - if use_display_names: - channel_name = list(sig.display_names)[0] if sig.display_names else sig.name + # scalars else: - channel_name = sig.name + if use_display_names: + channel_name = list(sig.display_names)[0] if sig.display_names else sig.name + else: + channel_name = sig.name - channel_name = used_names.get_unique_name(channel_name) + channel_name = used_names.get_unique_name(channel_name) - if reduce_memory_usage and sig.samples.dtype.kind in "SU": - unique = np.unique(sig.samples) + if reduce_memory_usage and sig.samples.dtype.kind in "SU": + unique = np.unique(sig.samples) - if sig.samples.dtype.byteorder not in target_byte_order: - sig.samples = sig.samples.byteswap().newbyteorder() + if sig.samples.dtype.byteorder not in target_byte_order: + sig.samples = sig.samples.view(sig.samples.dtype.newbyteorder()) - if len(sig.samples) / len(unique) >= 2: - df[channel_name] = pd.Series( - sig.samples, - index=sig_index, - dtype="category", - ) + if len(sig.samples) / len(unique) >= 2: + df[channel_name] = pd.Series( + sig.samples, + index=sig_index, + dtype="category", + ) + else: + df[channel_name] = pd.Series( + sig.samples, + index=sig_index, + fastpath=True, + ) else: + if reduce_memory_usage: + sig.samples = downcast(sig.samples) + + if sig.samples.dtype.byteorder not in target_byte_order: + sig.samples = sig.samples.view(sig.samples.dtype.newbyteorder()) + df[channel_name] = pd.Series( sig.samples, index=sig_index, fastpath=True, ) - else: - if reduce_memory_usage: - sig.samples = downcast(sig.samples) - if sig.samples.dtype.byteorder not in target_byte_order: - sig.samples = sig.samples.byteswap().newbyteorder() + if progress is not None: + if callable(progress): + progress(group_index + 1, groups_nr) + else: + progress.signals.setValue.emit(group_index + 1) - df[channel_name] = pd.Series( - sig.samples, - index=sig_index, - fastpath=True, - ) + strings, nonstrings = {}, {} - if progress is not None: - if callable(progress): - progress(group_index + 1, groups_nr) + for col, series in df.items(): + if series.dtype.kind == "S": + strings[col] = series else: - progress.signals.setValue.emit(group_index + 1) + nonstrings[col] = series - if progress.stop: - return TERMINATED - - strings, nonstrings = {}, {} - - for col, series in df.items(): - if series.dtype.kind == "S": - strings[col] = series - else: - nonstrings[col] = series + if numeric_1D_only: + nonstrings = {col: series for col, series in nonstrings.items() if series.dtype.kind in "uif"} + strings = {} - if numeric_1D_only: - nonstrings = {col: series for col, series in nonstrings.items() if series.dtype.kind in "uif"} - strings = {} + df = pd.DataFrame(nonstrings, index=master) - df = pd.DataFrame(nonstrings, index=master) + if strings: + df_strings = pd.DataFrame(strings, index=master) + df = pd.concat([df, df_strings], axis=1) - if strings: - df_strings = pd.DataFrame(strings, index=master) - df = pd.concat([df, df_strings], axis=1) + df.index.name = "timestamps" - df.index.name = "timestamps" - - if time_as_date: - delta = pd.to_timedelta(df.index, unit="s") + if time_as_date: + delta = pd.to_timedelta(df.index, unit="s") - new_index = self.header.start_time + delta - df.set_index(new_index, inplace=True) - elif time_from_zero and len(master): - df.set_index(df.index - df.index[0], inplace=True) + new_index = self.header.start_time + delta + df.set_index(new_index, inplace=True) + elif time_from_zero and len(master): + df.set_index(df.index - df.index[0], inplace=True) - yield df + yield df def to_dataframe( self, @@ -4465,7 +4458,7 @@ def to_dataframe( signals = [sig for sig in signals if len(sig)] if group_master.dtype.byteorder not in target_byte_order: - group_master = group_master.byteswap().newbyteorder() + ggroup_master = group_master.view(group_master.dtype.newbyteorder()) if signals: diffs = np.diff(group_master, prepend=-np.inf) > 0 @@ -4487,7 +4480,7 @@ def to_dataframe( size = len(index) for k, sig in enumerate(signals): if sig.timestamps.dtype.byteorder not in target_byte_order: - sig.timestamps = sig.timestamps.byteswap().newbyteorder() + sig.timestamps = sig.timestamps.view(sig.timestamps.dtype.newbyteorder()) sig_index = index if len(sig) == size else pd.Index(sig.timestamps, tupleize_cols=False) @@ -4501,7 +4494,7 @@ def to_dataframe( channel_name = used_names.get_unique_name(channel_name) if sig.samples.dtype.byteorder not in target_byte_order: - sig.samples = sig.samples.byteswap().newbyteorder() + sig.samples = sig.samples.view(sig.samples.dtype.newbyteorder()) df[channel_name] = pd.Series( list(sig.samples), @@ -4533,7 +4526,7 @@ def to_dataframe( sig.samples = downcast(sig.samples) if sig.samples.dtype.byteorder not in target_byte_order: - sig.samples = sig.samples.byteswap().newbyteorder() + sig.samples = sig.samples.view(sig.samples.dtype.newbyteorder()) df[channel_name] = pd.Series(sig.samples, index=sig_index, fastpath=True) @@ -5166,7 +5159,7 @@ def _extract_lin_logging( bus_data_bytes = data_bytes original_msg_ids = original_ids - unique_ids = np.unique(np.core.records.fromarrays([bus_msg_ids, bus_msg_ids])) + unique_ids = np.unique(np.rec.fromarrays([bus_msg_ids, bus_msg_ids])) total_unique_ids = total_unique_ids | {tuple(int(e) for e in f) for f in unique_ids} diff --git a/src/asammdf/signal.py b/src/asammdf/signal.py index 1b49434fa..a303a1fb9 100644 --- a/src/asammdf/signal.py +++ b/src/asammdf/signal.py @@ -5,7 +5,6 @@ from collections.abc import Iterator import logging from textwrap import fill -from traceback import format_exc from typing import Any import numpy as np @@ -224,7 +223,6 @@ def plot(self, validate: bool = True, index_only: bool = False) -> None: return except: - print(format_exc()) try: import matplotlib.pyplot as plt from matplotlib.widgets import Slider diff --git a/src/asammdf/version.py b/src/asammdf/version.py index fe4adae62..88280dfc5 100644 --- a/src/asammdf/version.py +++ b/src/asammdf/version.py @@ -1,3 +1,3 @@ """ asammdf version module """ -__version__ = "7.4.0.dev9" +__version__ = "7.4.0.dev15" diff --git a/test/asammdf/gui/widgets/test_PlotWidget_ContextMenu.py b/test/asammdf/gui/widgets/test_PlotWidget_ContextMenu.py index 78f526eb2..1af0bfa5c 100644 --- a/test/asammdf/gui/widgets/test_PlotWidget_ContextMenu.py +++ b/test/asammdf/gui/widgets/test_PlotWidget_ContextMenu.py @@ -313,8 +313,13 @@ def test_Action_CopyDisplayProperties_Group(self): position = self.plot.channel_selection.visualItemRect(group_channel_a).center() self.context_menu(action_text="Copy display properties [Ctrl+Shift+C]", position=position) clipboard = QtWidgets.QApplication.instance().clipboard().text() - with self.assertRaises(JSONDecodeError): - json.loads(clipboard) + try: + content = json.loads(clipboard) + except JSONDecodeError: + self.fail("Clipboard Content cannot be decoded as JSON content.") + else: + self.assertIsInstance(content, dict) + self.assertTrue(content["type"] == "group") with self.subTest("PopulatedGroup"): # Add Channels to Group @@ -330,6 +335,7 @@ def test_Action_CopyDisplayProperties_Group(self): self.fail("Clipboard Content cannot be decoded as JSON content.") else: self.assertIsInstance(content, dict) + self.assertTrue(content["type"] == "channel") with self.subTest("EmptyGroup_1"): group_channel_a.removeChild(self.plot_channel_a) @@ -337,8 +343,13 @@ def test_Action_CopyDisplayProperties_Group(self): self.context_menu(action_text="Copy display properties [Ctrl+Shift+C]", position=position) clipboard = QtWidgets.QApplication.instance().clipboard().text() print(clipboard) - with self.assertRaises(JSONDecodeError): - json.loads(clipboard) + try: + content = json.loads(clipboard) + except JSONDecodeError: + self.fail("Clipboard Content cannot be decoded as JSON content.") + else: + self.assertIsInstance(content, dict) + self.assertTrue(content["type"] == "group") with self.subTest("EmptyGroup_2"): # Add Channels to Group @@ -354,8 +365,13 @@ def test_Action_CopyDisplayProperties_Group(self): self.context_menu(action_text="Copy display properties [Ctrl+Shift+C]", position=position) clipboard = QtWidgets.QApplication.instance().clipboard().text() print(clipboard) - with self.assertRaises(JSONDecodeError): - json.loads(clipboard) + try: + content = json.loads(clipboard) + except JSONDecodeError: + self.fail("Clipboard Content cannot be decoded as JSON content.") + else: + self.assertIsInstance(content, dict) + self.assertTrue(content["type"] == "group") @unittest.skipIf(sys.platform != "win32", "Timers cannot be started/stopped from another thread.") def test_Action_PasteDisplayProperties_Group(self): @@ -411,7 +427,6 @@ def test_Action_PasteDisplayProperties_Group(self): channel_a_properties = QtWidgets.QApplication.instance().clipboard().text() channel_a_properties = json.loads(channel_a_properties) - del channel_a_properties["y_range"] # Paste position_dst = self.plot.channel_selection.visualItemRect(group_channel_a).center() @@ -423,17 +438,11 @@ def test_Action_PasteDisplayProperties_Group(self): group_channel_properties = QtWidgets.QApplication.instance().clipboard().text() group_channel_properties = json.loads(group_channel_properties) - del group_channel_properties["y_range"] # Evaluate - self.assertEqual(channel_a_properties, group_channel_properties) + self.assertEqual(channel_a_properties["ranges"], group_channel_properties["ranges"]) with self.subTest("FromGroup_ToChannel"): - position_src = self.plot.channel_selection.visualItemRect(self.plot_channel_c).center() - self.context_menu(action_text=action_copy, position=position_src) - channel_c_properties = QtWidgets.QApplication.instance().clipboard().text() - self.assertNotEqual(channel_c_properties, group_channel_properties) - position_src = self.plot.channel_selection.visualItemRect(group_channel_a).center() self.context_menu(action_text=action_copy, position=position_src) @@ -447,10 +456,9 @@ def test_Action_PasteDisplayProperties_Group(self): channel_c_properties = QtWidgets.QApplication.instance().clipboard().text() channel_c_properties = json.loads(channel_c_properties) - del channel_c_properties["y_range"] # Evaluate - self.assertEqual(channel_c_properties, group_channel_properties) + self.assertEqual(channel_c_properties["ranges"], group_channel_properties["ranges"]) with self.subTest("FromGroup_ToGroup"): self.move_channel_to_group(src=self.plot_channel_a, dst=group_channel_a) @@ -459,7 +467,6 @@ def test_Action_PasteDisplayProperties_Group(self): self.context_menu(action_text=action_copy, position=position_src) group_channel_b_properties = QtWidgets.QApplication.instance().clipboard().text() group_channel_b_properties = json.loads(group_channel_b_properties) - del group_channel_b_properties["y_range"] # Paste position_dst = self.plot.channel_selection.visualItemRect(group_channel_a).center() @@ -471,7 +478,6 @@ def test_Action_PasteDisplayProperties_Group(self): group_channel_a_properties = QtWidgets.QApplication.instance().clipboard().text() group_channel_a_properties = json.loads(group_channel_a_properties) - del group_channel_a_properties["y_range"] # Evaluate self.assertEqual(group_channel_a_properties, group_channel_b_properties)