diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..29bca3fb8 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,5 @@ +repos: + - repo: https://github.com/psf/black + rev: stable + hooks: + - id: black diff --git a/pyproject.toml b/pyproject.toml index 6586ec12f..2a22c41bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,16 +13,22 @@ extend-exclude = ''' source = ["asammdf"] omit = ["*/asammdf/gui/ui/*"] - -[tool.isort] -force_sort_within_sections = true -order_by_type = false -profile = "black" -skip_glob = ["src/asammdf/gui/ui"] - [tool.cibuildwheel] test-requires = "pytest" test-command = "pytest {project}/test" build-frontend = "build" archs = ["auto64"] # only build for 64bit architectures skip = "pp* *_ppc64le *_s390x *-musllinux* cp312-*" # skip pypy and irrelevant architectures + +[tool.ruff] +select = [ + "UP", # pyupgrade + "I", # isort +] +exclude = ["./src/asammdf/gui/ui"] +target-version = "py38" + +[tool.ruff.isort] +known-first-party = ["asammdf"] +order-by-type = false +force-sort-within-sections = true \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 068532c7a..d14e6fd57 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,4 @@ pandas typing_extensions python-dateutil isal; platform_machine == "x86_64" or platform_machine == "AMD64" -lxml<=4.9.2 +lxml>=4.9.3 diff --git a/requirements_exe_build.txt b/requirements_exe_build.txt index d7a39f3ff..9ffb6f0f7 100644 --- a/requirements_exe_build.txt +++ b/requirements_exe_build.txt @@ -5,11 +5,11 @@ numpy>=1.23.0 pandas typing_extensions isal; platform_machine == "x86_64" or platform_machine == "AMD64" -lxml<=4.9.2 +lxml==4.9.3 natsort psutil -PySide6==6.2.2 -pyqtgraph==0.12.4 +PySide6==6.6.0 +pyqtgraph==0.13.3 QtPy==2.3.1 pyqtlet2 pyopengl @@ -21,8 +21,8 @@ cChardet==2.1.5 chardet cryptography keyring -pyinstaller<6.0; sys_platform=="win32" +pyinstaller; sys_platform=="win32" pyinstaller<6.0; sys_platform=="darwin" -pyinstaller==4.10; sys_platform=="linux" +pyinstaller<6.0; sys_platform=="linux" scipy sympy diff --git a/run_black_and_isort.bat b/run_black_and_isort.bat deleted file mode 100644 index 7df16b6be..000000000 --- a/run_black_and_isort.bat +++ /dev/null @@ -1,7 +0,0 @@ -pip install -U black isort && ^ -black --config pyproject.toml . && ^ -black --config pyproject.toml asammdf.spec && ^ -black --config pyproject.toml setup.py && ^ -isort --settings-path pyproject.toml asammdf.spec && ^ -isort --settings-path pyproject.toml setup.py && ^ -isort --settings-path pyproject.toml . diff --git a/run_black_and_ruff.bat b/run_black_and_ruff.bat new file mode 100644 index 000000000..d3ab2d875 --- /dev/null +++ b/run_black_and_ruff.bat @@ -0,0 +1,6 @@ +pip install -U black ruff && ^ +ruff check --fix ./src && ^ +ruff check --fix ./setup.py && ^ +black --config pyproject.toml . && ^ +black --config pyproject.toml asammdf.spec && ^ +black --config pyproject.toml setup.py diff --git a/setup.py b/setup.py index 8ce6227db..b6e95bb03 100644 --- a/setup.py +++ b/setup.py @@ -104,18 +104,19 @@ def _get_ext_modules(): "export": [ "fastparquet", "h5py", - "hdf5storage>=0.1.17", + "hdf5storage>=0.1.19", "python-snappy", ], "export_matlab_v5": "scipy", "gui": [ - "lxml<=4.9.2", + "lxml>=4.9.2", "natsort", "psutil", - "PySide6<=6.3.1", - "pyqtgraph>=0.12.4", - "pyqtlet2>=0.8.0", + "PySide6==6.6.0", + "pyqtgraph==0.13.3", + "pyqtlet2==0.9.3", "packaging", + "QtPy==2.3.1", ], "encryption": ["cryptography", "keyring"], "symbolic_math": "sympy", diff --git a/src/asammdf/__init__.py b/src/asammdf/__init__.py index 35fb2e7fa..a4a0fb437 100644 --- a/src/asammdf/__init__.py +++ b/src/asammdf/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ asammdf is a parser and editor for ASAM MDF files """ import logging diff --git a/src/asammdf/blocks/conversion_utils.py b/src/asammdf/blocks/conversion_utils.py index 58886d981..3123de528 100644 --- a/src/asammdf/blocks/conversion_utils.py +++ b/src/asammdf/blocks/conversion_utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ asammdf utility functions for channel conversions """ @@ -8,11 +7,11 @@ from copy import deepcopy from typing import Any, Union +from ..types import ChannelConversionType from . import v2_v3_blocks as v3b from . import v2_v3_constants as v3c from . import v4_blocks as v4b from . import v4_constants as v4c -from ..types import ChannelConversionType __all__ = ["conversion_transfer", "from_dict"] @@ -379,7 +378,7 @@ def from_dict(conversion: dict[str, Any]) -> v4b.ChannelConversion: return conversion -def to_dict(conversion: ChannelConversionType) -> Union[dict, None]: +def to_dict(conversion: ChannelConversionType) -> dict | None: if not conversion: return None diff --git a/src/asammdf/blocks/mdf_v2.py b/src/asammdf/blocks/mdf_v2.py index 6f4b8d7bd..1a84f529b 100644 --- a/src/asammdf/blocks/mdf_v2.py +++ b/src/asammdf/blocks/mdf_v2.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ASAM MDF version 2 file format module """ from __future__ import annotations diff --git a/src/asammdf/blocks/mdf_v3.py b/src/asammdf/blocks/mdf_v3.py index 062fd571f..0e21e9182 100644 --- a/src/asammdf/blocks/mdf_v3.py +++ b/src/asammdf/blocks/mdf_v3.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ASAM MDF version 3 file format module """ from __future__ import annotations @@ -44,11 +43,11 @@ from pandas import DataFrame from typing_extensions import Literal, TypedDict -from . import v2_v3_constants as v23c from .. import tool from ..signal import Signal from ..types import ChannelsType, CompressionType, RasterType, StrPathType from ..version import __version__ +from . import v2_v3_constants as v23c from .conversion_utils import conversion_transfer from .cutils import get_channel_raw_bytes from .mdf_common import MDF_Common @@ -1799,7 +1798,7 @@ def append( sd_nr = len(component_samples) kargs = {"sd_nr": sd_nr} for i, dim in enumerate(shape[::-1]): - kargs["dim_{}".format(i)] = dim + kargs[f"dim_{i}"] = dim parent_dep = ChannelDependency(**kargs) new_gp_dep.append(parent_dep) diff --git a/src/asammdf/blocks/mdf_v4.py b/src/asammdf/blocks/mdf_v4.py index ba83aa616..9e849874b 100644 --- a/src/asammdf/blocks/mdf_v4.py +++ b/src/asammdf/blocks/mdf_v4.py @@ -75,8 +75,6 @@ from numpy.typing import NDArray from pandas import DataFrame -from . import bus_logging_utils -from . import v4_constants as v4c from .. import tool from ..signal import Signal from ..types import ( @@ -89,6 +87,8 @@ WritableBufferType, ) from ..version import __version__ +from . import bus_logging_utils +from . import v4_constants as v4c from .conversion_utils import conversion_transfer from .mdf_common import MDF_Common from .options import get_global_option diff --git a/src/asammdf/blocks/source_utils.py b/src/asammdf/blocks/source_utils.py index 5bc8bdc14..feabce9c2 100644 --- a/src/asammdf/blocks/source_utils.py +++ b/src/asammdf/blocks/source_utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ asammdf utility functions for source information """ @@ -7,11 +6,11 @@ from functools import lru_cache +from ..types import SourceType from . import v2_v3_blocks as v3b from . import v2_v3_constants as v3c from . import v4_blocks as v4b from . import v4_constants as v4c -from ..types import SourceType class Source: diff --git a/src/asammdf/blocks/utils.py b/src/asammdf/blocks/utils.py index 89fa742b9..046b5cbc4 100644 --- a/src/asammdf/blocks/utils.py +++ b/src/asammdf/blocks/utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ asammdf utility functions and classes """ @@ -94,8 +93,6 @@ def detect(text: bytes) -> DetectDict: from numpy.typing import NDArray from pandas import Series -from . import v2_v3_constants as v3c -from . import v4_constants as v4c from ..types import ( ChannelType, DataGroupType, @@ -104,6 +101,8 @@ def detect(text: bytes) -> DetectDict: ReadableBufferType, StrPathType, ) +from . import v2_v3_constants as v3c +from . import v4_constants as v4c UINT8_u = Struct(" bool: return True -class UniqueDB(object): +class UniqueDB: def __init__(self) -> None: self._db = {} @@ -1279,8 +1278,7 @@ def clear(self) -> None: self.data_blocks_info_generator = None def get_data_blocks(self) -> Iterator[DataBlockInfo]: - for blk in self.data_blocks: - yield blk + yield from self.data_blocks while True: try: @@ -1294,8 +1292,7 @@ def get_signal_data_blocks(self, index: int) -> Iterator[SignalDataBlockInfo]: signal_data = self.signal_data[index] if signal_data is not None: signal_data, signal_generator = signal_data - for blk in signal_data: - yield blk + yield from signal_data while True: try: @@ -2322,7 +2319,7 @@ def load_channel_names_from_file(file_name, lab_section=""): channels = load_dsp(file_name, flat=True) elif extension == ".dspf": - with open(file_name, "r") as infile: + with open(file_name) as infile: info = json.load(infile) channels = [] @@ -2345,16 +2342,16 @@ def load_channel_names_from_file(file_name, lab_section=""): channels = [name.split(";")[0] for name in channels] elif extension == ".cfg": - with open(file_name, "r") as infile: + with open(file_name) as infile: info = json.load(infile) channels = info.get("selected_channels", []) elif extension == ".txt": try: - with open(file_name, "r") as infile: + with open(file_name) as infile: info = json.load(infile) channels = info.get("selected_channels", []) except: - with open(file_name, "r") as infile: + with open(file_name) as infile: channels = [line.strip() for line in infile.readlines()] channels = [name for name in channels if name] @@ -2363,7 +2360,7 @@ def load_channel_names_from_file(file_name, lab_section=""): def load_lab(file): sections = {} - with open(file, "r") as lab: + with open(file) as lab: for line in lab: line = line.strip() if not line: diff --git a/src/asammdf/blocks/v2_v3_blocks.py b/src/asammdf/blocks/v2_v3_blocks.py index 23ef14447..cfea9a7b9 100644 --- a/src/asammdf/blocks/v2_v3_blocks.py +++ b/src/asammdf/blocks/v2_v3_blocks.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ classes that implement the blocks for MDF versions 2 and 3 """ from __future__ import annotations @@ -25,8 +24,8 @@ import numpy as np -from . import v2_v3_constants as v23c from ..version import __version__ +from . import v2_v3_constants as v23c from .utils import ( escape_xml_string, get_fields, @@ -1566,16 +1565,13 @@ def convert(self, values, as_object=False, as_bytes=False, ignore_value2text_con P6 = self.P6 X = values - if (P1, P4, P5, P6) == (0, 0, 0, 1): - if (P2, P3) != (1, 0): - values = values * P2 - if P3: - values += P3 - elif (P3, P4, P5, P6) == (0, 0, 1, 0): - if (P1, P2) != (1, 0): - values = values * P1 - if P2: - values += P2 + if (P1, P3, P4, P5) == (0, 0, 0, 0): + if P2 != P6: + values = values * (P2 / P6) + + elif (P2, P3, P4, P6) == (0, 0, 0, 0): + if P1 != P5: + values = values * (P1 / P5) else: try: values = evaluate(v23c.RAT_CONV_TEXT) @@ -1761,7 +1757,7 @@ def __init__(self, **kwargs) -> None: (self.id, self.block_len, self.dependency_type, self.sd_nr) = unpack("<2s3H", stream.read(8)) links_size = 3 * 4 * self.sd_nr - links = unpack("<{}I".format(3 * self.sd_nr), stream.read(links_size)) + links = unpack(f"<{3 * self.sd_nr}I", stream.read(links_size)) for i in range(self.sd_nr): self[f"dg_{i}"] = links[3 * i] @@ -2727,7 +2723,7 @@ def __init__(self, **kwargs) -> None: user = getuser() except ModuleNotFoundError: user = "" - self.author_field = "{:\0<32}".format(user).encode("latin-1") + self.author_field = f"{user:\0<32}".encode("latin-1") self.department_field = "{:\0<32}".format("").encode("latin-1") self.project_field = "{:\0<32}".format("").encode("latin-1") self.subject_field = "{:\0<32}".format("").encode("latin-1") @@ -3162,12 +3158,12 @@ def __init__(self, **kwargs) -> None: nr = self.trigger_events_nr if nr: - values = unpack("<{}d".format(3 * nr), block[10:]) + values = unpack(f"<{3 * nr}d", block[10:]) for i in range(nr): ( - self["trigger_{}_time".format(i)], - self["trigger_{}_pretime".format(i)], - self["trigger_{}_posttime".format(i)], + self[f"trigger_{i}_time"], + self[f"trigger_{i}_pretime"], + self[f"trigger_{i}_posttime"], ) = (values[i * 3], values[3 * i + 1], values[3 * i + 2]) if self.text_addr: @@ -3182,7 +3178,7 @@ def __init__(self, **kwargs) -> None: except KeyError: self.address = 0 nr = 0 - while "trigger_{}_time".format(nr) in kwargs: + while f"trigger_{nr}_time" in kwargs: nr += 1 self.id = b"TR" @@ -3191,11 +3187,11 @@ def __init__(self, **kwargs) -> None: self.trigger_events_nr = nr for i in range(nr): - key = "trigger_{}_time".format(i) + key = f"trigger_{i}_time" self[key] = kwargs[key] - key = "trigger_{}_pretime".format(i) + key = f"trigger_{i}_pretime" self[key] = kwargs[key] - key = "trigger_{}_posttime".format(i) + key = f"trigger_{i}_posttime" self[key] = kwargs[key] def to_blocks(self, address: int, blocks: list[Any]) -> int: @@ -3223,13 +3219,13 @@ def __setitem__(self, item: str, value: Any) -> None: def __bytes__(self) -> bytes: triggers_nr = self.trigger_events_nr - fmt = "<2sHIH{}d".format(triggers_nr * 3) + fmt = f"<2sHIH{triggers_nr * 3}d" keys = ("id", "block_len", "text_addr", "trigger_events_nr") for i in range(triggers_nr): keys += ( - "trigger_{}_time".format(i), - "trigger_{}_pretime".format(i), - "trigger_{}_posttime".format(i), + f"trigger_{i}_time", + f"trigger_{i}_pretime", + f"trigger_{i}_posttime", ) result = pack(fmt, *[self[key] for key in keys]) return result diff --git a/src/asammdf/blocks/v2_v3_constants.py b/src/asammdf/blocks/v2_v3_constants.py index 0b81bd2cb..a26396bf8 100644 --- a/src/asammdf/blocks/v2_v3_constants.py +++ b/src/asammdf/blocks/v2_v3_constants.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ MDF v2 and v3 constants """ import struct diff --git a/src/asammdf/blocks/v4_blocks.py b/src/asammdf/blocks/v4_blocks.py index 10757bb37..36531ae4a 100644 --- a/src/asammdf/blocks/v4_blocks.py +++ b/src/asammdf/blocks/v4_blocks.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ classes that implement the blocks for MDF version 4 """ @@ -42,8 +41,8 @@ import numpy as np -from . import v4_constants as v4c from ..version import __version__ +from . import v4_constants as v4c from .utils import ( block_fields, escape_xml_string, @@ -1766,7 +1765,7 @@ def __bytes__(self) -> bytes: if self.storage: keys += tuple(f"cycle_count_{i}" for i in range(data_links_nr)) - fmt = "<4sI{}Q2BHIiI{}Q{}d{}Q".format(self.links_nr + 2, dims_nr, sum(dim_sizes), data_links_nr) + fmt = f"<4sI{self.links_nr + 2}Q2BHIiI{dims_nr}Q{sum(dim_sizes)}d{data_links_nr}Q" result = pack(fmt, *[getattr(self, key) for key in keys]) return result @@ -3155,17 +3154,13 @@ def convert(self, values, as_object=False, as_bytes=False, ignore_value2text_con if names: name = names[0] vals = values[name] - if (P1, P4, P5, P6) == (0, 0, 0, 1): - if (P2, P3) != (1, 0): - vals = values[name] * P2 - if P3: - vals = vals + P3 - - elif (P3, P4, P5, P6) == (0, 0, 1, 0): - if (P1, P2) != (1, 0): - vals = values[name] * P1 - if P2: - vals = vals + P2 + if (P1, P3, P4, P5) == (0, 0, 0, 0): + if P2 != P6: + vals = vals * (P2 / P6) + + elif (P2, P3, P4, P6) == (0, 0, 0, 0): + if P1 != P5: + vals = vals * (P1 / P5) else: X = vals @@ -3182,16 +3177,13 @@ def convert(self, values, as_object=False, as_bytes=False, ignore_value2text_con else: X = values - if (P1, P4, P5, P6) == (0, 0, 0, 1): - if (P2, P3) != (1, 0): - values = values * P2 - if P3: - values = values + P3 - elif (P3, P4, P5, P6) == (0, 0, 1, 0): - if (P1, P2) != (1, 0): - values = values * P1 - if P2: - values += P2 + if (P1, P3, P4, P5) == (0, 0, 0, 0): + if P2 != P6: + values = values * (P2 / P6) + + elif (P2, P3, P4, P6) == (0, 0, 0, 0): + if P1 != P5: + values = values * (P1 / P5) else: try: values = evaluate(v4c.CONV_RAT_TEXT) @@ -3851,7 +3843,7 @@ def convert(self, values, as_object=False, as_bytes=False, ignore_value2text_con phys = [ conv if isinstance(conv, bytes) - else ((f"{conv.name}=".encode("utf-8"), conv) if conv.name else (b"", conv)) + else ((f"{conv.name}=".encode(), conv) if conv.name else (b"", conv)) for conv in phys ] @@ -4176,20 +4168,20 @@ def __bytes__(self) -> bytes: v4c.CONVERSION_TYPE_TABI, v4c.CONVERSION_TYPE_TAB, ): - fmt = "<4sI{}Q2B3H{}d".format(self.links_nr + 2, self.val_param_nr + 2) + fmt = f"<4sI{self.links_nr + 2}Q2B3H{self.val_param_nr + 2}d" keys = v4c.KEYS_CONVERSION_NONE for i in range(self.val_param_nr // 2): keys += (f"raw_{i}", f"phys_{i}") result = pack(fmt, *[getattr(self, key) for key in keys]) elif self.conversion_type == v4c.CONVERSION_TYPE_RTAB: - fmt = "<4sI{}Q2B3H{}d".format(self.links_nr + 2, self.val_param_nr + 2) + fmt = f"<4sI{self.links_nr + 2}Q2B3H{self.val_param_nr + 2}d" keys = v4c.KEYS_CONVERSION_NONE for i in range(self.val_param_nr // 3): keys += (f"lower_{i}", f"upper_{i}", f"phys_{i}") keys += ("default",) result = pack(fmt, *[getattr(self, key) for key in keys]) elif self.conversion_type == v4c.CONVERSION_TYPE_TABX: - fmt = "<4sI{}Q2B3H{}d".format(self.links_nr + 2, self.val_param_nr + 2) + fmt = f"<4sI{self.links_nr + 2}Q2B3H{self.val_param_nr + 2}d" keys = ( "id", "reserved0", @@ -4214,7 +4206,7 @@ def __bytes__(self) -> bytes: keys += tuple(f"val_{i}" for i in range(self.val_param_nr)) result = pack(fmt, *[getattr(self, key) for key in keys]) elif self.conversion_type == v4c.CONVERSION_TYPE_RTABX: - fmt = "<4sI{}Q2B3H{}d".format(self.links_nr + 2, self.val_param_nr + 2) + fmt = f"<4sI{self.links_nr + 2}Q2B3H{self.val_param_nr + 2}d" keys = ( "id", "reserved0", @@ -4240,7 +4232,7 @@ def __bytes__(self) -> bytes: keys += (f"lower_{i}", f"upper_{i}") result = pack(fmt, *[getattr(self, key) for key in keys]) elif self.conversion_type == v4c.CONVERSION_TYPE_TTAB: - fmt = "<4sI{}Q2B3H{}d".format(self.links_nr + 2, self.val_param_nr + 2) + fmt = f"<4sI{self.links_nr + 2}Q2B3H{self.val_param_nr + 2}d" keys = ( "id", "reserved0", @@ -4265,7 +4257,7 @@ def __bytes__(self) -> bytes: keys += ("val_default",) result = pack(fmt, *[getattr(self, key) for key in keys]) elif self.conversion_type == v4c.CONVERSION_TYPE_TRANS: - fmt = "<4sI{}Q2B3H{}d".format(self.links_nr + 2, self.val_param_nr + 2) + fmt = f"<4sI{self.links_nr + 2}Q2B3H{self.val_param_nr + 2}d" keys = ( "id", "reserved0", @@ -4293,7 +4285,7 @@ def __bytes__(self) -> bytes: result = pack(fmt, *[getattr(self, key) for key in keys]) elif self.conversion_type == v4c.CONVERSION_TYPE_BITFIELD: - fmt = "<4sI{}Q2B3H2d{}Q".format(self.links_nr + 2, self.val_param_nr) + fmt = f"<4sI{self.links_nr + 2}Q2B3H2d{self.val_param_nr}Q" keys = ( "id", "reserved0", @@ -4400,7 +4392,7 @@ def __init__(self, **kwargs) -> None: if type not in ("DT", "SD", "RD", "DV", "DI"): type = "DT" - self.id = "##{}".format(type).encode("ascii") + self.id = f"##{type}".encode("ascii") self.reserved0 = 0 self.block_len = len(kwargs["data"]) + COMMON_SIZE self.links_nr = 0 @@ -4416,7 +4408,7 @@ def __bytes__(self) -> bytes: return v4c.COMMON_p(self.id, self.reserved0, self.block_len, self.links_nr) + self.data -class DataZippedBlock(object): +class DataZippedBlock: """*DataZippedBlock* has the following attributes, that are also available as dict like key-value pairs @@ -4856,7 +4848,7 @@ def __init__(self, **kwargs) -> None: else: (self.reserved1, self.data_block_nr) = unpack("<3sI", stream.read(7)) offsets = unpack( - "<{}Q".format(self.links_nr - 1), + f"<{self.links_nr - 1}Q", stream.read((self.links_nr - 1) * 8), ) for i, offset in enumerate(offsets): @@ -4885,7 +4877,7 @@ def __init__(self, **kwargs) -> None: else: (self.reserved1, self.data_block_nr) = unpack("<3sI", stream.read(7)) offsets = unpack( - "<{}Q".format(self.links_nr - 1), + f"<{self.links_nr - 1}Q", stream.read((self.links_nr - 1) * 8), ) for i, offset in enumerate(offsets): @@ -5282,8 +5274,8 @@ def __init__(self, **kwargs) -> None: except KeyError: version = kwargs.get("version", "4.00") - self.file_identification = "MDF ".encode("utf-8") - self.version_str = "{} ".format(version).encode("utf-8") + self.file_identification = b"MDF " + self.version_str = f"{version} ".encode() self.program_identification = "amdf{}".format(__version__.replace(".", "")).encode("utf-8") self.reserved0 = b"\0" * 4 self.mdf_version = int(version.replace(".", "")) diff --git a/src/asammdf/blocks/v4_constants.py b/src/asammdf/blocks/v4_constants.py index 06ea21b3c..d86839316 100644 --- a/src/asammdf/blocks/v4_constants.py +++ b/src/asammdf/blocks/v4_constants.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ MDF v4 constants """ import re import struct diff --git a/src/asammdf/gui/__init__.py b/src/asammdf/gui/__init__.py index e69de29bb..8b1378917 100644 --- a/src/asammdf/gui/__init__.py +++ b/src/asammdf/gui/__init__.py @@ -0,0 +1 @@ + diff --git a/src/asammdf/gui/asammdfgui.py b/src/asammdf/gui/asammdfgui.py index fc3a74a75..ab407ef6f 100644 --- a/src/asammdf/gui/asammdfgui.py +++ b/src/asammdf/gui/asammdfgui.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import argparse import os import sys @@ -6,6 +5,7 @@ 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 7718073d3..c44b523b2 100644 --- a/src/asammdf/gui/dialogs/advanced_search.py +++ b/src/asammdf/gui/dialogs/advanced_search.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import os import re from traceback import format_exc diff --git a/src/asammdf/gui/dialogs/bus_database_manager.py b/src/asammdf/gui/dialogs/bus_database_manager.py index 815de77ed..6e2a4580f 100644 --- a/src/asammdf/gui/dialogs/bus_database_manager.py +++ b/src/asammdf/gui/dialogs/bus_database_manager.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from PySide6 import QtCore, QtWidgets from ..widgets.bus_database_manager import BusDatabaseManager diff --git a/src/asammdf/gui/dialogs/channel_group_info.py b/src/asammdf/gui/dialogs/channel_group_info.py index a6261e3e6..2ead2663d 100644 --- a/src/asammdf/gui/dialogs/channel_group_info.py +++ b/src/asammdf/gui/dialogs/channel_group_info.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from PySide6 import QtCore, QtGui, QtWidgets from ..ui import resource_rc diff --git a/src/asammdf/gui/dialogs/channel_info.py b/src/asammdf/gui/dialogs/channel_info.py index 981639f64..3d40a3f73 100644 --- a/src/asammdf/gui/dialogs/channel_info.py +++ b/src/asammdf/gui/dialogs/channel_info.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from PySide6 import QtCore, QtGui, QtWidgets from ..ui import resource_rc diff --git a/src/asammdf/gui/dialogs/conversion_editor.py b/src/asammdf/gui/dialogs/conversion_editor.py index 895422e46..ba91b2d91 100644 --- a/src/asammdf/gui/dialogs/conversion_editor.py +++ b/src/asammdf/gui/dialogs/conversion_editor.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import numpy as np from PySide6 import QtCore, QtGui, QtWidgets diff --git a/src/asammdf/gui/dialogs/define_channel.py b/src/asammdf/gui/dialogs/define_channel.py index 865dabdbf..4296344d4 100644 --- a/src/asammdf/gui/dialogs/define_channel.py +++ b/src/asammdf/gui/dialogs/define_channel.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from functools import partial import inspect import os diff --git a/src/asammdf/gui/dialogs/error_dialog.py b/src/asammdf/gui/dialogs/error_dialog.py index 19852c6c4..a8047453e 100644 --- a/src/asammdf/gui/dialogs/error_dialog.py +++ b/src/asammdf/gui/dialogs/error_dialog.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from threading import Thread from time import sleep diff --git a/src/asammdf/gui/dialogs/functions_manager.py b/src/asammdf/gui/dialogs/functions_manager.py index 0b3f47900..b8414910d 100644 --- a/src/asammdf/gui/dialogs/functions_manager.py +++ b/src/asammdf/gui/dialogs/functions_manager.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from copy import deepcopy import os from traceback import format_exc diff --git a/src/asammdf/gui/dialogs/gps_dialog.py b/src/asammdf/gui/dialogs/gps_dialog.py index d4831356e..b9c108ca6 100644 --- a/src/asammdf/gui/dialogs/gps_dialog.py +++ b/src/asammdf/gui/dialogs/gps_dialog.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import re from natsort import natsorted diff --git a/src/asammdf/gui/dialogs/multi_search.py b/src/asammdf/gui/dialogs/multi_search.py index 4325fe446..361d30bd3 100644 --- a/src/asammdf/gui/dialogs/multi_search.py +++ b/src/asammdf/gui/dialogs/multi_search.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import os import re from textwrap import wrap @@ -100,7 +99,7 @@ def _apply(self, event): self.result = set() for i in range(count): text = self.selection.item(i).text() - file_index, channel_name = [item.strip() for item in text.split(":")] + file_index, channel_name = (item.strip() for item in text.split(":")) file_index = int(file_index) - 1 for entry in self.channels_dbs[file_index][channel_name]: self.result.add((file_index, entry)) diff --git a/src/asammdf/gui/dialogs/range_editor.py b/src/asammdf/gui/dialogs/range_editor.py index 79f4b6c10..ded836574 100644 --- a/src/asammdf/gui/dialogs/range_editor.py +++ b/src/asammdf/gui/dialogs/range_editor.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from PySide6 import QtCore, QtWidgets from ..ui import resource_rc diff --git a/src/asammdf/gui/dialogs/simple_search.py b/src/asammdf/gui/dialogs/simple_search.py index 3552f0dab..e1999bfaf 100644 --- a/src/asammdf/gui/dialogs/simple_search.py +++ b/src/asammdf/gui/dialogs/simple_search.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from functools import partial import os import re diff --git a/src/asammdf/gui/dialogs/window_selection_dialog.py b/src/asammdf/gui/dialogs/window_selection_dialog.py index cd48c5d41..ec596aad5 100644 --- a/src/asammdf/gui/dialogs/window_selection_dialog.py +++ b/src/asammdf/gui/dialogs/window_selection_dialog.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from PySide6 import QtWidgets from ..ui import resource_rc diff --git a/src/asammdf/gui/plot.py b/src/asammdf/gui/plot.py index 300e5fc3d..92070f8e5 100644 --- a/src/asammdf/gui/plot.py +++ b/src/asammdf/gui/plot.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import logging import os import sys diff --git a/src/asammdf/gui/utils.py b/src/asammdf/gui/utils.py index e07b61ac6..564766ebd 100644 --- a/src/asammdf/gui/utils.py +++ b/src/asammdf/gui/utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import ctypes from datetime import datetime from functools import reduce @@ -172,7 +171,7 @@ def excepthook(exc_type, exc_value, tracebackobj): msg = "\n".join(sections) print("".join(traceback.format_tb(tracebackobj))) - print("{0}: {1}".format(exc_type, exc_value)) + print(f"{exc_type}: {exc_value}") ErrorDialog(message=errmsg, trace=msg, title="The following error was triggered").exec_() diff --git a/src/asammdf/gui/widgets/attachment.py b/src/asammdf/gui/widgets/attachment.py index 043be4fd1..416f940ff 100644 --- a/src/asammdf/gui/widgets/attachment.py +++ b/src/asammdf/gui/widgets/attachment.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from pathlib import Path from PySide6 import QtWidgets diff --git a/src/asammdf/gui/widgets/bar.py b/src/asammdf/gui/widgets/bar.py index 1d6e92751..aa1c26231 100644 --- a/src/asammdf/gui/widgets/bar.py +++ b/src/asammdf/gui/widgets/bar.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import os import re diff --git a/src/asammdf/gui/widgets/batch.py b/src/asammdf/gui/widgets/batch.py index 41e86b4f1..3525363c3 100644 --- a/src/asammdf/gui/widgets/batch.py +++ b/src/asammdf/gui/widgets/batch.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from datetime import datetime, timedelta, timezone import json import os diff --git a/src/asammdf/gui/widgets/bus_database_manager.py b/src/asammdf/gui/widgets/bus_database_manager.py index 6cdc5832c..60d58e4e1 100644 --- a/src/asammdf/gui/widgets/bus_database_manager.py +++ b/src/asammdf/gui/widgets/bus_database_manager.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from pathlib import Path from PySide6 import QtCore, QtWidgets diff --git a/src/asammdf/gui/widgets/can_bus_trace.py b/src/asammdf/gui/widgets/can_bus_trace.py index fbeb030e8..7e210866e 100644 --- a/src/asammdf/gui/widgets/can_bus_trace.py +++ b/src/asammdf/gui/widgets/can_bus_trace.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import datetime import logging diff --git a/src/asammdf/gui/widgets/channel_bar_display.py b/src/asammdf/gui/widgets/channel_bar_display.py index 18d99ca7c..09ed07d25 100644 --- a/src/asammdf/gui/widgets/channel_bar_display.py +++ b/src/asammdf/gui/widgets/channel_bar_display.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import json import math @@ -59,8 +58,8 @@ def drawWidget(self, qp): w = size.width() h = size.height() - till = int(((w / self.max) * self.value)) - full = int(((w / self.max) * self.over)) + till = int((w / self.max) * self.value) + full = int((w / self.max) * self.over) if self.value >= self.over: qp.setPen(QtGui.QColor(self.color)) diff --git a/src/asammdf/gui/widgets/channel_group_info.py b/src/asammdf/gui/widgets/channel_group_info.py index d89e3d018..494e45296 100644 --- a/src/asammdf/gui/widgets/channel_group_info.py +++ b/src/asammdf/gui/widgets/channel_group_info.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import numpy as np import pandas as pd from PySide6 import QtCore, QtGui, QtWidgets diff --git a/src/asammdf/gui/widgets/channel_info.py b/src/asammdf/gui/widgets/channel_info.py index df531d5c5..5a1943fd2 100644 --- a/src/asammdf/gui/widgets/channel_info.py +++ b/src/asammdf/gui/widgets/channel_info.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from PySide6 import QtWidgets from ..ui import resource_rc diff --git a/src/asammdf/gui/widgets/channel_stats.py b/src/asammdf/gui/widgets/channel_stats.py index 6561efe78..363cd1a23 100644 --- a/src/asammdf/gui/widgets/channel_stats.py +++ b/src/asammdf/gui/widgets/channel_stats.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from copy import deepcopy import numpy as np diff --git a/src/asammdf/gui/widgets/collapsiblebox.py b/src/asammdf/gui/widgets/collapsiblebox.py index b274e8f2e..2d8ba632d 100644 --- a/src/asammdf/gui/widgets/collapsiblebox.py +++ b/src/asammdf/gui/widgets/collapsiblebox.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- - from PySide6 import QtCore, QtWidgets class CollapsibleBox(QtWidgets.QWidget): def __init__(self, title="", parent=None): - super(CollapsibleBox, self).__init__(parent) + super().__init__(parent) self.toggle_button = QtWidgets.QToolButton(text=title, checkable=True, checked=False) self.toggle_button.setStyleSheet("QToolButton { border: none; }") diff --git a/src/asammdf/gui/widgets/cursor.py b/src/asammdf/gui/widgets/cursor.py index a5b3837b5..2831a7860 100644 --- a/src/asammdf/gui/widgets/cursor.py +++ b/src/asammdf/gui/widgets/cursor.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import pyqtgraph as pg from PySide6 import QtCore, QtGui diff --git a/src/asammdf/gui/widgets/database_item.py b/src/asammdf/gui/widgets/database_item.py index b40d68cd2..40a3bca98 100644 --- a/src/asammdf/gui/widgets/database_item.py +++ b/src/asammdf/gui/widgets/database_item.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from PySide6 import QtWidgets from ..ui.database_item import Ui_DatabaseItemUI diff --git a/src/asammdf/gui/widgets/fft_window.py b/src/asammdf/gui/widgets/fft_window.py index 39d3fb0e4..509addb8e 100644 --- a/src/asammdf/gui/widgets/fft_window.py +++ b/src/asammdf/gui/widgets/fft_window.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import numpy as np from PySide6 import QtCore, QtGui, QtWidgets import scipy.signal as scipy_signal diff --git a/src/asammdf/gui/widgets/file.py b/src/asammdf/gui/widgets/file.py index 95253bbd7..1df73e059 100644 --- a/src/asammdf/gui/widgets/file.py +++ b/src/asammdf/gui/widgets/file.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from datetime import datetime, timezone from functools import partial from hashlib import sha1 @@ -1071,12 +1070,12 @@ def load_channel_list(self, event=None, file_name=None, manually=False): channels = [name.split(";")[0] for name in info[section]] elif extension in (".cfg", ".txt"): - with open(file_name, "r") as infile: + with open(file_name) as infile: info = json.load(infile) channels = info.get("selected_channels", []) elif extension == ".dspf": - with open(file_name, "r") as infile: + with open(file_name) as infile: info = json.load(infile) channels = info.get("selected_channels", []) @@ -3118,17 +3117,12 @@ def embed_display_file(self, event=None): ) fh_block = FileHistory() - fh_block.comment = """ + fh_block.comment = f""" Added new embedded attachment from {file_name} - {tool} - {vendor} - {version} -""".format( - tool=tool.__tool__, - vendor=tool.__vendor__, - version=tool.__version__, - file_name=file_name, - ) + {tool.__tool__} + {tool.__vendor__} + {tool.__version__} +""" at_block["creator_index"] = creator_index diff --git a/src/asammdf/gui/widgets/flexray_bus_trace.py b/src/asammdf/gui/widgets/flexray_bus_trace.py index 1967dc2ed..4ee2e8353 100644 --- a/src/asammdf/gui/widgets/flexray_bus_trace.py +++ b/src/asammdf/gui/widgets/flexray_bus_trace.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import datetime import logging diff --git a/src/asammdf/gui/widgets/formated_axis.py b/src/asammdf/gui/widgets/formated_axis.py index cfbf0237a..5ecca16f0 100644 --- a/src/asammdf/gui/widgets/formated_axis.py +++ b/src/asammdf/gui/widgets/formated_axis.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from datetime import datetime, timedelta, timezone from PySide6 import QtCore, QtGui, QtWidgets @@ -99,7 +97,7 @@ def tickStrings(self, values, scale, spacing): strns.append(val) else: if self.format == "phys": - strns = super(FormatedAxis, self).tickStrings(values, scale, spacing) + strns = super().tickStrings(values, scale, spacing) elif self.format == "hex": for val in values: @@ -174,14 +172,14 @@ def labelString(self): else: units = "(x%g)" % (1.0 / self.autoSIPrefixScale) else: - units = "(%s%s)" % (self.labelUnitPrefix, self.labelUnits) + units = f"({self.labelUnitPrefix}{self.labelUnits})" - s = "%s %s" % (self.labelText, units) + s = f"{self.labelText} {units}" self._label_with_unit = s - style = ";".join(["%s: %s" % (k, self.labelStyle[k]) for k in self.labelStyle]) + style = ";".join([f"{k}: {self.labelStyle[k]}" for k in self.labelStyle]) - lsbl = "%s" % (style, s) + lsbl = f"{s}" return lsbl def mouseDragEvent(self, event): diff --git a/src/asammdf/gui/widgets/functions_manager.py b/src/asammdf/gui/widgets/functions_manager.py index a02b4b6ae..ac41357f0 100644 --- a/src/asammdf/gui/widgets/functions_manager.py +++ b/src/asammdf/gui/widgets/functions_manager.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import inspect import json import math @@ -189,7 +188,7 @@ def import_definitions(self, *args): if file_name and Path(file_name).suffix.lower() == ".def": file_name = Path(file_name) - with open(file_name, "r") as infile: + with open(file_name) as infile: info = json.load(infile) for name, definition in info.items(): diff --git a/src/asammdf/gui/widgets/gps.py b/src/asammdf/gui/widgets/gps.py index 1598840d6..a513bd69c 100644 --- a/src/asammdf/gui/widgets/gps.py +++ b/src/asammdf/gui/widgets/gps.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- import io import sys from traceback import format_exc import numpy as np from PySide6 import QtCore, QtWidgets -from PySide6.QtWebEngineCore import QWebEngineSettings try: from pyqtlet2 import L, leaflet, MapWidget + from PySide6.QtWebEngineCore import QWebEngineSettings + except: print(format_exc()) pass diff --git a/src/asammdf/gui/widgets/lin_bus_trace.py b/src/asammdf/gui/widgets/lin_bus_trace.py index 3498248b1..69df53c07 100644 --- a/src/asammdf/gui/widgets/lin_bus_trace.py +++ b/src/asammdf/gui/widgets/lin_bus_trace.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import datetime import logging diff --git a/src/asammdf/gui/widgets/list.py b/src/asammdf/gui/widgets/list.py index 083b8d51d..cafed2fca 100644 --- a/src/asammdf/gui/widgets/list.py +++ b/src/asammdf/gui/widgets/list.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import json from struct import pack from traceback import format_exc diff --git a/src/asammdf/gui/widgets/list_item.py b/src/asammdf/gui/widgets/list_item.py index 596968b06..af9f50c4c 100644 --- a/src/asammdf/gui/widgets/list_item.py +++ b/src/asammdf/gui/widgets/list_item.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from PySide6 import QtWidgets diff --git a/src/asammdf/gui/widgets/main.py b/src/asammdf/gui/widgets/main.py index 8ee8007d7..ddf4e38d8 100644 --- a/src/asammdf/gui/widgets/main.py +++ b/src/asammdf/gui/widgets/main.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from functools import partial import gc import os @@ -10,6 +9,7 @@ from natsort import natsorted import pyqtgraph as pg +from PySide6 import __version__ as pyside6_version from PySide6 import QtCore, QtGui, QtWidgets from ...version import __version__ as libversion @@ -1490,6 +1490,7 @@ def show_about(self):

Build information:


Copyright © 2018-2023 Daniel Hrisca

""", diff --git a/src/asammdf/gui/widgets/mdi_area.py b/src/asammdf/gui/widgets/mdi_area.py index 59c15c534..9f084c710 100644 --- a/src/asammdf/gui/widgets/mdi_area.py +++ b/src/asammdf/gui/widgets/mdi_area.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from copy import deepcopy from functools import partial import inspect @@ -468,7 +467,7 @@ def parse_matrix_component(name): def load_comparison_display_file(file_name, uuids): - with open(file_name, "r") as infile: + with open(file_name) as infile: info = json.load(infile) windows = info.get("windows", []) plot_windows = [] @@ -843,6 +842,8 @@ def add_new_channels(self, names, widget, mime_data=None): raw=True, ) + nd = [] + for sig, sig_ in zip(selected_signals, uuids_signals): sig.group_index = sig_["group_index"] sig.channel_index = sig_["channel_index"] @@ -859,7 +860,72 @@ def add_new_channels(self, names, widget, mime_data=None): sig.tooltip = f"{sig.name}\n@ {file.file_name}" sig.name = f"{file_index+1}: {sig.name}" - signals[sig.uuid] = sig + if sig.samples.dtype.kind not in "SU" and ( + sig.samples.dtype.names or len(sig.samples.shape) > 1 + ): + nd.append(sig) + else: + signals[sig.uuid] = sig + + for sig in nd: + if sig.samples.dtype.names is None: + shape = sig.samples.shape[1:] + + matrix_dims = [list(range(dim)) for dim in shape] + + matrix_name = sig.name + + for indexes in itertools.product(*matrix_dims): + indexes_string = "".join(f"[{_index}]" for _index in indexes) + + samples = sig.samples + for idx in indexes: + samples = samples[:, idx] + sig_name = f"{matrix_name}{indexes_string}" + + new_sig = sig.copy() + new_sig.name = sig_name + new_sig.samples = samples + new_sig.group_index = sig.group_index + new_sig.channel_index = sig.channel_index + new_sig.flags &= ~sig.Flags.computed + new_sig.computation = {} + new_sig.origin_uuid = sig.origin_uuid + new_sig.uuid = os.urandom(6).hex() + new_sig.enable = getattr(sig, "enable", True) + + signals[new_sig.uuid] = new_sig + else: + name = sig.samples.dtype.names[0] + if name == sig.name: + array_samples = sig.samples[name] + + shape = array_samples.shape[1:] + + matrix_dims = [list(range(dim)) for dim in shape] + + matrix_name = sig.name + + for indexes in itertools.product(*matrix_dims): + indexes_string = "".join(f"[{_index}]" for _index in indexes) + + samples = array_samples + for idx in indexes: + samples = samples[:, idx] + sig_name = f"{matrix_name}{indexes_string}" + + new_sig = sig.copy() + new_sig.name = sig_name + new_sig.samples = samples + new_sig.group_index = sig.group_index + new_sig.channel_index = sig.channel_index + new_sig.flags &= ~sig.Flags.computed + new_sig.computation = {} + new_sig.origin_uuid = sig.origin_uuid + new_sig.uuid = os.urandom(6).hex() + new_sig.enable = getattr(sig, "enable", True) + + signals[new_sig.uuid] = new_sig signals = { key: sig @@ -1609,7 +1675,7 @@ def _add_lin_bus_trace_window(self, ranges=None): vals = data["LIN_SyncError.BaudRate"] unique = np.unique(vals).tolist() for val in unique: - sys.intern((f"Baudrate {val}")) + sys.intern(f"Baudrate {val}") vals = [f"Baudrate {val}" for val in vals.tolist()] columns["Details"] = vals @@ -1630,7 +1696,7 @@ def _add_lin_bus_trace_window(self, ranges=None): vals = data["LIN_TransmissionError.BaudRate"] unique = np.unique(vals).tolist() for val in unique: - sys.intern((f"Baudrate {val}")) + sys.intern(f"Baudrate {val}") vals = [f"Baudrate {val}" for val in vals.tolist()] columns["Details"] = vals @@ -1656,7 +1722,7 @@ def _add_lin_bus_trace_window(self, ranges=None): vals = data["LIN_ReceiveError.BaudRate"] unique = np.unique(vals).tolist() for val in unique: - sys.intern((f"Baudrate {val}")) + sys.intern(f"Baudrate {val}") vals = [f"Baudrate {val}" for val in vals.tolist()] columns["Details"] = vals @@ -1684,7 +1750,7 @@ def _add_lin_bus_trace_window(self, ranges=None): vals = data["LIN_ChecksumError.Checksum"] unique = np.unique(vals).tolist() for val in unique: - sys.intern((f"Baudrate {val}")) + sys.intern(f"Baudrate {val}") vals = [f"Checksum 0x{val:02X}" for val in vals.tolist()] columns["Details"] = vals diff --git a/src/asammdf/gui/widgets/numeric.py b/src/asammdf/gui/widgets/numeric.py index 5eed2db0d..0019496aa 100644 --- a/src/asammdf/gui/widgets/numeric.py +++ b/src/asammdf/gui/widgets/numeric.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import bisect import json import os @@ -881,7 +880,7 @@ def __init__(self, parent): self.resize(self.sizeHint()) def showEvent(self, a0: QtGui.QShowEvent) -> None: - super(HeaderView, self).showEvent(a0) + super().showEvent(a0) self.initial_size = self.size() def mouseDoubleClickEvent(self, event): diff --git a/src/asammdf/gui/widgets/plot.py b/src/asammdf/gui/widgets/plot.py index 2381e8355..5ee1035f1 100644 --- a/src/asammdf/gui/widgets/plot.py +++ b/src/asammdf/gui/widgets/plot.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import bisect from collections import defaultdict from datetime import timedelta @@ -14,6 +13,7 @@ import numpy as np import pyqtgraph as pg import pyqtgraph.functions as fn +import pyqtgraph.Qt as Qt from PySide6 import QtCore, QtGui, QtWidgets PLOT_BUFFER_SIZE = 4000 @@ -33,8 +33,16 @@ @lru_cache(maxsize=1024) def polygon_and_ndarray(size): - polygon = fn.create_qpolygonf(size) - ndarray = fn.ndarray_from_qpolygonf(polygon).copy() + polygon = QtGui.QPolygonF() + polygon.resize(size) + + nbytes = 2 * len(polygon) * 8 + ptr = polygon.data() + if ptr is None: + ptr = 0 + buffer = Qt.shiboken.VoidPtr(ptr, nbytes, True) + ndarray = np.frombuffer(buffer, np.double).reshape((-1, 2)) + return polygon, ndarray @@ -59,31 +67,6 @@ def getId(obj): return res - # fixes https://github.com/pyqtgraph/pyqtgraph/issues/2117 - def mouseReleaseEvent(self, ev): - if self.mouseGrabberItem() is None: - if ev.button() in self.dragButtons: - if self.sendDragEvent(ev, final=True): - ev.accept() - self.dragButtons.remove(ev.button()) - else: - cev = [e for e in self.clickEvents if e.button() == ev.button()] - if cev: - if self.sendClickEvent(cev[0]): - ev.accept() - try: - self.clickEvents.remove(cev[0]) - except: - pass - - if not ev.buttons(): - self.dragItem = None - self.dragButtons = [] - self.clickEvents = [] - self.lastDrag = None - QtWidgets.QGraphicsScene.mouseReleaseEvent(self, ev) - self.sendHoverEvents(ev) - mkColor_factory = fn.mkColor mkBrush_factory = fn.mkBrush mkPen_factory = fn.mkPen @@ -123,7 +106,6 @@ def cached_mkPen_factory(*args, **kargs): # speed-up monkey patches pg.graphicsItems.ScatterPlotItem.SymbolAtlas._keys = _keys pg.graphicsItems.ScatterPlotItem._USE_QRECT = False - pg.GraphicsScene.mouseReleaseEvent = mouseReleaseEvent fn.mkBrush = mkBrush fn.mkColor = mkColor @@ -1142,7 +1124,7 @@ def trim_python(self, start=None, stop=None, width=1900, force=False): visible = abs(int((stop_t - start_t) / (stop - start) * width)) if visible: - visible_duplication = abs((stop_ - start_)) // visible + visible_duplication = abs(stop_ - start_) // visible else: visible_duplication = 0 except: diff --git a/src/asammdf/gui/widgets/plot_standalone.py b/src/asammdf/gui/widgets/plot_standalone.py index e17134202..6f79534c5 100644 --- a/src/asammdf/gui/widgets/plot_standalone.py +++ b/src/asammdf/gui/widgets/plot_standalone.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from functools import partial import logging import webbrowser diff --git a/src/asammdf/gui/widgets/range_widget.py b/src/asammdf/gui/widgets/range_widget.py index 4abddeb0e..807c53f1d 100644 --- a/src/asammdf/gui/widgets/range_widget.py +++ b/src/asammdf/gui/widgets/range_widget.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from PySide6 import QtCore, QtGui, QtWidgets from ..ui import resource_rc diff --git a/src/asammdf/gui/widgets/search.py b/src/asammdf/gui/widgets/search.py index e33191c16..a7bf371d4 100644 --- a/src/asammdf/gui/widgets/search.py +++ b/src/asammdf/gui/widgets/search.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from PySide6 import QtCore, QtWidgets from ..ui import resource_rc diff --git a/src/asammdf/gui/widgets/signal_scale.py b/src/asammdf/gui/widgets/signal_scale.py index 0c34887d0..b5bee704e 100644 --- a/src/asammdf/gui/widgets/signal_scale.py +++ b/src/asammdf/gui/widgets/signal_scale.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from functools import partial from natsort import natsorted diff --git a/src/asammdf/gui/widgets/tabular.py b/src/asammdf/gui/widgets/tabular.py index 395e097b2..d6837e4d2 100644 --- a/src/asammdf/gui/widgets/tabular.py +++ b/src/asammdf/gui/widgets/tabular.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import datetime import logging from traceback import format_exc diff --git a/src/asammdf/gui/widgets/tabular_base.py b/src/asammdf/gui/widgets/tabular_base.py index d254c3823..002e12489 100644 --- a/src/asammdf/gui/widgets/tabular_base.py +++ b/src/asammdf/gui/widgets/tabular_base.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # this file contains substantial amount of code from https://github.com/adamerose/PandasGUI which is licensed as MIT: # @@ -616,7 +615,7 @@ def __init__(self, parent, orientation): self.resize(self.sizeHint()) def showEvent(self, a0: QtGui.QShowEvent) -> None: - super(HeaderView, self).showEvent(a0) + super().showEvent(a0) self.initial_size = self.size() def mouseDoubleClickEvent(self, event): diff --git a/src/asammdf/gui/widgets/tabular_filter.py b/src/asammdf/gui/widgets/tabular_filter.py index 8d0a7d71d..5dc218fa6 100644 --- a/src/asammdf/gui/widgets/tabular_filter.py +++ b/src/asammdf/gui/widgets/tabular_filter.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import pandas as pd from PySide6 import QtCore, QtWidgets diff --git a/src/asammdf/gui/widgets/tree.py b/src/asammdf/gui/widgets/tree.py index 02a1447c7..7ebc4a301 100644 --- a/src/asammdf/gui/widgets/tree.py +++ b/src/asammdf/gui/widgets/tree.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from datetime import date, datetime from enum import IntFlag from functools import lru_cache @@ -12,10 +11,10 @@ from pyqtgraph import functions as fn from PySide6 import QtCore, QtGui, QtWidgets -from .. import utils from ...blocks.conversion_utils import from_dict, to_dict from ...blocks.utils import extract_mime_names from ...signal import Signal +from .. import utils from ..dialogs.advanced_search import AdvancedSearch from ..dialogs.conversion_editor import ConversionEditor from ..dialogs.messagebox import MessageBox @@ -1030,7 +1029,7 @@ def open_menu(self): for item in self.selectedItems(): if item.type() == ChannelsTreeItem.Channel: while True: - rgb = random.randbytes(3) + rgb = os.urandom(3) if 100 <= sum(rgb) <= 650: break diff --git a/src/asammdf/gui/widgets/tree_item.py b/src/asammdf/gui/widgets/tree_item.py index 02ba0a3de..5a205532e 100644 --- a/src/asammdf/gui/widgets/tree_item.py +++ b/src/asammdf/gui/widgets/tree_item.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from time import perf_counter from traceback import format_exc diff --git a/src/asammdf/gui/widgets/tree_numeric.py b/src/asammdf/gui/widgets/tree_numeric.py deleted file mode 100644 index b33ac2c11..000000000 --- a/src/asammdf/gui/widgets/tree_numeric.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -import json -from struct import pack - -from PySide6 import QtCore, QtGui, QtWidgets - -from ..dialogs.range_editor import RangeEditor -from ..utils import extract_mime_names - - -class NumericTreeWidget(QtWidgets.QTreeWidget): - add_channels_request = QtCore.Signal(list) - items_rearranged = QtCore.Signal() - items_deleted = QtCore.Signal(list) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - self.setAcceptDrops(True) - self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) - self.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked) - - self.header().sortIndicatorChanged.connect(self.handle_sorting_changed) - self.itemDoubleClicked.connect(self.handle_item_double_click) - - self._handles_double_click = True - self.set_double_clicked_enabled(True) - - def set_double_clicked_enabled(self, state): - self._handles_double_click = bool(state) - - def keyPressEvent(self, event): - key = event.key() - if event.key() == QtCore.Qt.Key_Delete and event.modifiers() == QtCore.Qt.NoModifier: - selected = reversed(self.selectedItems()) - names = [(item.origin_uuid, item.text(0)) for item in selected] - for item in selected: - if item.parent() is None: - index = self.indexFromItem(item).row() - self.takeTopLevelItem(index) - else: - item.parent().removeChild(item) - self.items_deleted.emit(names) - else: - super().keyPressEvent(event) - - def startDrag(self, supportedActions): - selected_items = self.selectedItems() - - mimeData = QtCore.QMimeData() - - data = [] - - for item in selected_items: - entry = item.entry - - if entry == (-1, -1): - info = { - "name": item.name, - "computation": item.computation, - } - else: - info = item.name - - ranges = [dict(e) for e in item.ranges] - - for range_info in ranges: - range_info["color"] = range_info["color"].color().name() - - data.append( - ( - info, - *item.entry, - str(item.origin_uuid), - "channel", - ranges, - ) - ) - - data = json.dumps(data).encode("utf-8") - - mimeData.setData("application/octet-stream-asammdf", QtCore.QByteArray(data)) - - drag = QtGui.QDrag(self) - drag.setMimeData(mimeData) - drag.exec(QtCore.Qt.CopyAction) - - def dragEnterEvent(self, e): - e.accept() - - def dropEvent(self, e): - if e.source() is self: - super().dropEvent(e) - self.items_rearranged.emit() - else: - data = e.mimeData() - if data.hasFormat("application/octet-stream-asammdf"): - names = extract_mime_names(data) - self.add_channels_request.emit(names) - else: - super().dropEvent(e) - - def handle_item_double_click(self, item, column): - if self._handles_double_click: - dlg = RangeEditor( - item.name, - ranges=item.ranges, - parent=self, - brush=True, - ) - dlg.exec_() - if dlg.pressed_button == "apply": - item.ranges = dlg.result - item.check_signal_range() - - def handle_sorting_changed(self, index, order): - iterator = QtWidgets.QTreeWidgetItemIterator(self) - while iterator.value(): - item = iterator.value() - iterator += 1 - - item._sorting_column = index diff --git a/src/asammdf/gui/widgets/vrtt_widget.py b/src/asammdf/gui/widgets/vrtt_widget.py index fcaab935e..73c2c729e 100644 --- a/src/asammdf/gui/widgets/vrtt_widget.py +++ b/src/asammdf/gui/widgets/vrtt_widget.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import numpy as np from PySide6 import QtCore, QtGui, QtWidgets diff --git a/src/asammdf/gui/widgets/vtt_widget.py b/src/asammdf/gui/widgets/vtt_widget.py index 1c9cdee81..231ae97b4 100644 --- a/src/asammdf/gui/widgets/vtt_widget.py +++ b/src/asammdf/gui/widgets/vtt_widget.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import numpy as np from PySide6 import QtCore, QtGui, QtWidgets diff --git a/src/asammdf/mdf.py b/src/asammdf/mdf.py index 7cb4fdb47..83432acc2 100644 --- a/src/asammdf/mdf.py +++ b/src/asammdf/mdf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ common MDF file format module """ from __future__ import annotations @@ -70,9 +69,13 @@ from .blocks.v2_v3_blocks import ChannelExtension from .blocks.v2_v3_blocks import HeaderBlock as HeaderV3 from .blocks.v4_blocks import ChannelConversion as ChannelConversionV4 -from .blocks.v4_blocks import EventBlock, FileHistory, FileIdentificationBlock +from .blocks.v4_blocks import ( + EventBlock, + FileHistory, + FileIdentificationBlock, + SourceInformation, +) from .blocks.v4_blocks import HeaderBlock as HeaderV4 -from .blocks.v4_blocks import SourceInformation from .signal import Signal from .types import ( BusType, @@ -376,7 +379,7 @@ def __enter__(self) -> MDF: def __exit__( self, - exc_type: Type[BaseException] | None, + exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, ) -> bool | None: diff --git a/src/asammdf/signal.py b/src/asammdf/signal.py index 3482a7e5c..32fb1167b 100644 --- a/src/asammdf/signal.py +++ b/src/asammdf/signal.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ asammdf *Signal* class module for time correct signal processing """ from __future__ import annotations @@ -31,7 +30,7 @@ logger = logging.getLogger("asammdf") -class Signal(object): +class Signal: """ The *Signal* represents a channel described by it's samples and timestamps. It can perform arithmetic operations against other *Signal* or numeric types. @@ -1244,8 +1243,7 @@ def __ne__(self, other: Signal | NDArray[Any] | None) -> bool: return self.__apply_func(other, "__ne__") def __iter__(self) -> Iterator[Any]: - for item in (self.samples, self.timestamps, self.unit, self.name): - yield item + yield from (self.samples, self.timestamps, self.unit, self.name) def __reversed__(self) -> Iterator[tuple[int, tuple[Any, Any]]]: return enumerate(zip(reversed(self.samples), reversed(self.timestamps))) diff --git a/src/asammdf/tool.py b/src/asammdf/tool.py index 07c7b5f0f..3ea18c1aa 100644 --- a/src/asammdf/tool.py +++ b/src/asammdf/tool.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ asammdf tool module """ from .version import __version__ diff --git a/src/asammdf/version.py b/src/asammdf/version.py index af8c6f017..149e19ab4 100644 --- a/src/asammdf/version.py +++ b/src/asammdf/version.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ asammdf version module """ -__version__ = "7.3.15.dev17" +__version__ = "7.3.16.dev1" diff --git a/test/asammdf/gui/resources/missingItems.dspf b/test/asammdf/gui/resources/missingItems.dspf new file mode 100644 index 000000000..af669eea2 --- /dev/null +++ b/test/asammdf/gui/resources/missingItems.dspf @@ -0,0 +1,408 @@ +{ + "selected_channels": [], + "windows": [ + { + "title": "Plot 0", + "configuration": { + "channels": [ + { + "type": "channel", + "name": "ASAM.M.SCALAR.SBYTE.LINEAR_MUL_2", + "unit": "m/s", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#1f77b4", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{:.3f}", + "format": "phys", + "mode": "phys", + "y_range": [ + -256.0, + 254.0 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "1stMissingItem", + "unit": "", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#7975eb", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{:.3f}", + "format": "phys", + "mode": "phys", + "y_range": [ + -256.0, + 254.0 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "ASAM.M.SCALAR.SLONG.IDENTICAL", + "unit": "hours", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#ff7f0e", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{}", + "format": "phys", + "mode": "phys", + "y_range": [ + 0.0, + 999.0 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "ASAM.M.SCALAR.SWORD.IDENTICAL", + "unit": "hours", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#2ca02c", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{}", + "format": "phys", + "mode": "phys", + "y_range": [ + 0.0, + 999.0 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "ASAM.M.SCALAR.UBYTE.FORM_X_PLUS_4", + "unit": "rpm", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#d62728", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{:.3f}", + "format": "phys", + "mode": "phys", + "y_range": [ + 4.0, + 259.0 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "ASAM.M.SCALAR.UBYTE.HYPERBOLIC", + "unit": "km/h", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#c94597", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{:.3f}", + "format": "phys", + "mode": "phys", + "y_range": [ + 0.00392156862745098, + 1.0 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "ASAM.M.SCALAR.UBYTE.IDENTICAL", + "unit": "hours", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#8c564b", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{}", + "format": "phys", + "mode": "phys", + "y_range": [ + 0.0, + 255.0 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "ASAM.M.SCALAR.UBYTE.IDENTICAL.STATUS_STRING", + "unit": "m/s", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#e377c2", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{}", + "format": "phys", + "mode": "phys", + "y_range": [ + 0.0, + 255.0 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "ASAM.M.SCALAR.UBYTE.RAT_FUNC.DIV_10", + "unit": "km/h", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#7f7f7f", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{:.3f}", + "format": "phys", + "mode": "phys", + "y_range": [ + 0.0, + 25.5 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "ASAM.M.SCALAR.UBYTE.RAT_FUNC.IDENT.STATUS_STRING", + "unit": "m/s", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#bcbd22", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{}", + "format": "phys", + "mode": "phys", + "y_range": [ + 0.0, + 255.0 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "ASAM.M.SCALAR.UBYTE.TAB_INTP_DEFAULT_VALUE", + "unit": "U/ min", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#17becf", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{:.3f}", + "format": "phys", + "mode": "phys", + "y_range": [ + 100.0, + 111.0 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "ASAM.M.SCALAR.UBYTE.TAB_INTP_NO_DEFAULT_VALUE", + "unit": "U/ min", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#1f77b4", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{:.3f}", + "format": "phys", + "mode": "phys", + "y_range": [ + 100.0, + 111.0 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "ASAM.M.SCALAR.UBYTE.TAB_NOINTP_DEFAULT_VALUE", + "unit": "U/ min", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#ff7f0e", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{:.3f}", + "format": "phys", + "mode": "phys", + "y_range": [ + 100.0, + 111.0 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "ASAM.M.SCALAR.UBYTE.TAB_NOINTP_NO_DEFAULT_VALUE", + "unit": "U/ min", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#2ca02c", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{:.3f}", + "format": "phys", + "mode": "phys", + "y_range": [ + 100.0, + 111.0 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "ASAM.M.SCALAR.UBYTE.TAB_VERB_DEFAULT_VALUE", + "unit": "unknown signal type", + "flags": 0, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#d62728", + "computed": false, + "ranges": [], + "precision": 3, + "fmt": "{}", + "format": "phys", + "mode": "phys", + "y_range": [ + 0.0, + 255.0 + ], + "origin_uuid": "ae683e64a806" + }, + { + "type": "channel", + "name": "FirstVirtualChannel", + "unit": "", + "flags": 32, + "enabled": true, + "individual_axis": false, + "common_axis": false, + "color": "#85cf0c", + "computed": true, + "ranges": [], + "precision": 3, + "fmt": "{:.3f}", + "format": "phys", + "mode": "phys", + "computation": { + "args": {}, + "channel_comment": "", + "channel_name": "FirstVirtualChannel", + "channel_unit": "", + "computation_mode": "sample_by_sample", + "function": "TestVirtualChannel", + "triggering": "triggering_on_channel", + "triggering_value": "MAIN_CLOCK", + "type": "python_function" + }, + "y_range": [ + -1.0, + 0.0 + ], + "origin_uuid": "ae683e64a806" + } + ], + "pattern": {}, + "splitter": [ + 394, + 284, + 0 + ], + "x_range": [ + -0.7252090113273859, + 13.152907274715773 + ], + "y_axis_width": 0.0, + "grid": [ + false, + false + ], + "cursor_precision": 6, + "font_size": 9, + "locked": false, + "common_axis_y_range": [ + 0.00392156862745098, + 1.0 + ], + "channels_header": [ + 394, + [ + 366, + 83, + 122, + 35, + 35 + ] + ], + "hide_axes": true, + "hide_selected_channel_value_panel": false, + "focused_mode": false, + "delta_mode": "delta", + "hide_bookmarks": true + }, + "geometry": [ + 0, + 0, + 688, + 646 + ], + "maximized": true, + "minimized": false, + "type": "Plot" + } + ], + "functions": { + "TestVirtualChannel": "def TestVirtualChannel(t=0):\n return 1" + } +} \ No newline at end of file diff --git a/test/asammdf/gui/widgets/test_PlotWidget_ContextMenu.py b/test/asammdf/gui/widgets/test_PlotWidget_ContextMenu.py index f44edc6ef..0503757e9 100644 --- a/test/asammdf/gui/widgets/test_PlotWidget_ContextMenu.py +++ b/test/asammdf/gui/widgets/test_PlotWidget_ContextMenu.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import json from json import JSONDecodeError +import pathlib import re import sys from test.asammdf.gui.widgets.test_BasePlotWidget import TestPlotWidget @@ -8,7 +9,7 @@ from unittest import mock from unittest.mock import ANY -from PySide6 import QtCore, QtTest, QtWidgets +from PySide6 import QtCore, QtGui, QtTest, QtWidgets class TestContextMenu(TestPlotWidget): @@ -852,6 +853,7 @@ def test_Menu_EnableDisable_Action_EnableSelected(self): self.assertEqual(QtCore.Qt.Checked, group_channel.checkState(self.Column.NAME)) + @unittest.skipIf(sys.platform != "win32", "Timers cannot be started/stopped from another thread.") def test_Menu_EnableDisable_Action_DisableSelected(self): """ Test Scope: @@ -925,3 +927,447 @@ def test_Menu_EnableDisable_Action_DisableAllButThis(self): item = self.plot.channel_selection.topLevelItem(i) if item.type() != item.Info and item != self.plot_channel_b: self.assertEqual(QtCore.Qt.Unchecked, item.checkState(self.Column.NAME)) + + def test_Menu_ShowHide_Action_HideDisabledItems(self): + """ + Test Scope: + - Ensure that item is hidden from channel selection when is disabled. + Events: + - Disable 1 channel by key Space + - Disable 1 channel by mouseClick on item CheckBox + Evaluate: + - Evaluate that items that are unchecked are not present anymore on channel selection + """ + self.context_menu(action_text="Hide disabled items") + + with self.subTest("DisableBySpace"): + # Select one channel + self.mouseClick_WidgetItem(self.plot_channel_a) + # Event + QtTest.QTest.keyClick(self.plot.channel_selection, QtCore.Qt.Key_Space) + # Evaluate + self.assertTrue(self.plot_channel_a.isHidden()) + + with self.subTest("DisableByClick"): + pos = self.plot.channel_selection.visualItemRect(self.plot_channel_b).center() + # Magic Number to detect center of checkbox + pos = QtCore.QPoint(28, pos.y()) + # Event + QtTest.QTest.mouseClick( + self.plot.channel_selection.viewport(), QtCore.Qt.LeftButton, QtCore.Qt.KeyboardModifiers(), pos + ) + # Evaluate + self.assertTrue(self.plot_channel_b.isHidden()) + + def test_Menu_ShowHide_Action_ShowDisabledItems(self): + """ + Test Scope: + - Ensure that item is showed on channel selection when is disabled. + Events: + - Disable 1 channel by key Space + - Disable 1 channel by mouseClick on item CheckBox + Evaluate: + - Evaluate that items that are unchecked are not present anymore on channel selection + """ + + with self.subTest("DisableBySpace"): + self.context_menu(action_text="Hide disabled items") + # Select one channel + self.mouseClick_WidgetItem(self.plot_channel_a) + # Event + QtTest.QTest.keyClick(self.plot.channel_selection, QtCore.Qt.Key_Space) + # Evaluate + self.assertTrue(self.plot_channel_a.isHidden()) + self.context_menu(action_text="Show disabled items") + self.assertFalse(self.plot_channel_a.isHidden()) + + with self.subTest("DisableByClick"): + self.context_menu(action_text="Hide disabled items") + pos = self.plot.channel_selection.visualItemRect(self.plot_channel_b).center() + # Magic Number to detect center of checkbox + pos = QtCore.QPoint(28, pos.y()) + # Event + QtTest.QTest.mouseClick( + self.plot.channel_selection.viewport(), QtCore.Qt.LeftButton, QtCore.Qt.KeyboardModifiers(), pos + ) + # Evaluate + self.assertTrue(self.plot_channel_b.isHidden()) + self.context_menu(action_text="Show disabled items") + self.assertFalse(self.plot_channel_b.isHidden()) + + def test_Menu_ShowHide_Action_HideMissingItems(self): + """ + Test Scope: + - Ensure that missing item is hidden from channel selection. + Events: + - Disable 1 channel by key Space + - Disable 1 channel by mouseClick on item CheckBox + Evaluate: + - Evaluate that missing items are not present anymore on channel selection + """ + dspf_filepath = pathlib.Path(self.resource, "missingItems.dspf") + self.load_display_file(display_file=dspf_filepath) + self.plot = self.widget.mdi_area.subWindowList()[0].widget() + + plot_channel = self.find_channel(channel_tree=self.plot.channel_selection, channel_name="1stMissingItem") + self.processEvents() + + self.assertFalse(plot_channel.isHidden()) + + # Events + self.context_menu(action_text="Hide missing items") + + # Evaluate + self.assertTrue(plot_channel.isHidden()) + self.processEvents(timeout=0.01) + + def test_Menu_ShowHide_Action_ShowMissingItems(self): + """ + Test Scope: + - Ensure that missing item is visible from channel selection. + Events: + - Open Context Menu + - Select action: Hide missing items + - Disable 1 channel by key Space + - Disable 1 channel by mouseClick on item CheckBox + Evaluate: + - Evaluate that missing items are present anymore on channel selection + """ + dspf_filepath = pathlib.Path(self.resource, "missingItems.dspf") + self.load_display_file(display_file=dspf_filepath) + self.plot = self.widget.mdi_area.subWindowList()[0].widget() + + self.context_menu(action_text="Hide missing items") + + plot_channel = self.find_channel(channel_tree=self.plot.channel_selection, channel_name="1stMissingItem") + self.processEvents() + + self.assertTrue(plot_channel.isHidden()) + + # Events + self.context_menu(action_text="Show missing items") + + # Evaluate + self.assertFalse(plot_channel.isHidden()) + self.processEvents(timeout=0.01) + + def test_Menu_ShowHide_Action_FilterOnlyComputedChannels(self): + """ + Test Scope: + - Ensure that all channels are hidden from channel selection except VirtualChannels. + Events: + - Open Context Menu + - Select Filter Only Computed Channels + Evaluate: + - Evaluate that channels items are not present anymore on channel selection except VirtualChannels. + """ + dspf_filepath = pathlib.Path(self.resource, "missingItems.dspf") + self.load_display_file(display_file=dspf_filepath) + self.plot = self.widget.mdi_area.subWindowList()[0].widget() + + iterator = QtWidgets.QTreeWidgetItemIterator(self.plot.channel_selection) + while iterator.value(): + item = iterator.value() + if item: + self.assertFalse(item.isHidden()) + iterator += 1 + + # Events + self.context_menu(action_text="Filter only computed channels") + + # Evaluate + iterator = QtWidgets.QTreeWidgetItemIterator(self.plot.channel_selection) + while iterator.value(): + item = iterator.value() + if item and item.text(0) == "FirstVirtualChannel": + self.assertFalse(item.isHidden()) + else: + self.assertTrue(item.isHidden()) + iterator += 1 + + def test_Menu_ShowHide_Action_UnfilterComputedChannels(self): + """ + Test Scope: + - Ensure that all channels are hidden from channel selection except VirtualChannels. + Events: + - Open Context Menu + - Select Filter Only Computed Channels + Evaluate: + - Evaluate that channels items are not present anymore on channel selection except VirtualChannels. + """ + dspf_filepath = pathlib.Path(self.resource, "missingItems.dspf") + self.load_display_file(display_file=dspf_filepath) + self.plot = self.widget.mdi_area.subWindowList()[0].widget() + + # Events + self.context_menu(action_text="Filter only computed channels") + # Evaluate + iterator = QtWidgets.QTreeWidgetItemIterator(self.plot.channel_selection) + while iterator.value(): + item = iterator.value() + if item and item.text(0) == "FirstVirtualChannel": + self.assertFalse(item.isHidden()) + else: + self.assertTrue(item.isHidden()) + iterator += 1 + + # Events + self.context_menu(action_text="Un-filter computed channels") + + # Evaluate + iterator = QtWidgets.QTreeWidgetItemIterator(self.plot.channel_selection) + while iterator.value(): + item = iterator.value() + if item: + self.assertFalse(item.isHidden()) + iterator += 1 + + def test_Action_EditYAxisScaling(self): + """ + Test Scope: + - Ensure that action forwards the request to plot. + Event: + - Select channel + - Open Context Menu + - Select 'Edit Y axis scaling' + Evaluate: + - Evaluate that event is forwarded to plot + """ + # Setup + position = self.plot.channel_selection.visualItemRect(self.plot_channel_a).center() + with mock.patch.object(self.plot, "keyPressEvent") as mo_keyPressEvent: + self.context_menu(action_text="Edit Y axis scaling [Ctrl+G]", position=position) + mo_keyPressEvent.assert_called() + event = mo_keyPressEvent.call_args.args[0] + self.assertEqual(QtCore.QEvent.KeyPress, event.type()) + self.assertEqual(QtCore.Qt.Key_G, event.key()) + self.assertEqual(QtCore.Qt.ControlModifier, event.modifiers()) + + def test_Action_AddToCommonYAxis(self): + """ + Test Scope: + - Ensure that action will mark as checked checkbox for Y Axis. + Event: + - Select one channel + - Open Context Menu + - Select 'Add to common Y axis' + - Select two channels + - Open Context Menu + - Select 'Add to common Y axis' + Evaluate: + - Evaluate that checkbox on column COMMON_AXIS is checked. + """ + with self.subTest("1Channel"): + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_a.checkState(self.Column.COMMON_AXIS)) + + position = self.plot.channel_selection.visualItemRect(self.plot_channel_a).center() + self.context_menu(action_text="Add to common Y axis", position=position) + + self.assertEqual(QtCore.Qt.Checked, self.plot_channel_a.checkState(self.Column.COMMON_AXIS)) + + self.context_menu(action_text="Add to common Y axis", position=position) + + self.assertEqual(QtCore.Qt.Checked, self.plot_channel_a.checkState(self.Column.COMMON_AXIS)) + + with self.subTest("2Channels"): + self.plot_channel_b.setSelected(True) + self.plot_channel_c.setSelected(True) + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_b.checkState(self.Column.COMMON_AXIS)) + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_c.checkState(self.Column.COMMON_AXIS)) + + position = self.plot.channel_selection.visualItemRect(self.plot_channel_c).center() + self.context_menu(action_text="Add to common Y axis", position=position) + + self.assertEqual(QtCore.Qt.Checked, self.plot_channel_b.checkState(self.Column.COMMON_AXIS)) + self.assertEqual(QtCore.Qt.Checked, self.plot_channel_c.checkState(self.Column.COMMON_AXIS)) + + self.context_menu(action_text="Add to common Y axis", position=position) + + self.assertEqual(QtCore.Qt.Checked, self.plot_channel_b.checkState(self.Column.COMMON_AXIS)) + self.assertEqual(QtCore.Qt.Checked, self.plot_channel_c.checkState(self.Column.COMMON_AXIS)) + + def test_Action_RemoveFromCommonYAxis(self): + """ + Test Scope: + - Ensure that action will mark as unchecked checkbox for Y Axis. + Event: + - Select one channel + - Open Context Menu + - Select 'Remove from common Y axis' + - Select two channels + - Open Context Menu + - Select 'Remove from common Y axis' + Evaluate: + - Evaluate that checkbox on column COMMON_AXIS is unchecked. + """ + with self.subTest("1Channel"): + # Setup + position = self.plot.channel_selection.visualItemRect(self.plot_channel_a).center() + + # Pre-evaluation + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_a.checkState(self.Column.COMMON_AXIS)) + + # Event + self.context_menu(action_text="Remove from common Y axis", position=position) + # Evaluate + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_a.checkState(self.Column.COMMON_AXIS)) + + # Event + self.context_menu(action_text="Add to common Y axis", position=position) + self.assertEqual(QtCore.Qt.Checked, self.plot_channel_a.checkState(self.Column.COMMON_AXIS)) + self.context_menu(action_text="Remove from common Y axis", position=position) + # Evaluate + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_a.checkState(self.Column.COMMON_AXIS)) + + # Event + self.context_menu(action_text="Remove from common Y axis", position=position) + # Evaluate + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_a.checkState(self.Column.COMMON_AXIS)) + + with self.subTest("2Channels"): + # Setup + self.plot_channel_b.setSelected(True) + self.plot_channel_c.setSelected(True) + position_c = self.plot.channel_selection.visualItemRect(self.plot_channel_c).center() + + # Pre-evaluation + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_b.checkState(self.Column.COMMON_AXIS)) + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_c.checkState(self.Column.COMMON_AXIS)) + + # Event + + self.context_menu(action_text="Remove from common Y axis", position=position_c) + # Evaluate + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_b.checkState(self.Column.COMMON_AXIS)) + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_c.checkState(self.Column.COMMON_AXIS)) + + # Event + self.context_menu(action_text="Add to common Y axis", position=position_c) + self.assertEqual(QtCore.Qt.Checked, self.plot_channel_b.checkState(self.Column.COMMON_AXIS)) + self.assertEqual(QtCore.Qt.Checked, self.plot_channel_c.checkState(self.Column.COMMON_AXIS)) + self.context_menu(action_text="Remove from common Y axis", position=position) + # Evaluate + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_b.checkState(self.Column.COMMON_AXIS)) + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_c.checkState(self.Column.COMMON_AXIS)) + + # Event + self.context_menu(action_text="Remove from common Y axis", position=position) + # Evaluate + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_b.checkState(self.Column.COMMON_AXIS)) + self.assertNotEqual(QtCore.Qt.Checked, self.plot_channel_c.checkState(self.Column.COMMON_AXIS)) + + def test_Action_SetColor(self): + """ + Test Scope: + - Ensure that channel color is changed. + Events: + - Open Context Menu + - Select 'Set color [C]' + - Select 1 Channel + - Open Context Menu + - Select 'Set color [C]' + - Select 2 Channels + - Open Context Menu + - Select 'Set color [C]' + Evaluate: + - Evaluate that color dialog is not open if channel is not selected. + - Evaluate that channel color is changed. + """ + action_text = "Set color [C]" + + # Event + with self.subTest("NoChannelSelected"): + with mock.patch("asammdf.gui.widgets.tree.QtWidgets.QColorDialog.getColor") as mo_getColor: + self.context_menu(action_text=action_text) + mo_getColor.assert_not_called() + + with self.subTest("1ChannelSelected"): + position = self.plot.channel_selection.visualItemRect(self.plot_channel_a).center() + with mock.patch("asammdf.gui.widgets.tree.QtWidgets.QColorDialog.getColor") as mo_getColor: + # Setup + previous_color = self.plot_channel_a.color.name() + color = QtGui.QColor("red") + color_name = color.name() + mo_getColor.return_value = color + # Event + self.context_menu(action_text=action_text, position=position) + # Evaluate + current_color = self.plot_channel_a.color.name() + mo_getColor.assert_called() + self.assertNotEqual(previous_color, current_color) + self.assertEqual(color_name, current_color) + + with self.subTest("2ChannelsSelected"): + self.mouseClick_WidgetItem(self.plot_channel_b) + self.plot_channel_b.setSelected(True) + self.plot_channel_c.setSelected(True) + position = self.plot.channel_selection.visualItemRect(self.plot_channel_c).center() + with mock.patch("asammdf.gui.widgets.tree.QtWidgets.QColorDialog.getColor") as mo_getColor: + # Setup + previous_b_color = self.plot_channel_b.color.name() + previous_c_color = self.plot_channel_c.color.name() + color = QtGui.QColor("blue") + color_name = color.name() + mo_getColor.return_value = color + # Event + self.context_menu(action_text=action_text, position=position) + # Evaluate + current_b_color = self.plot_channel_b.color.name() + current_c_color = self.plot_channel_c.color.name() + mo_getColor.assert_called() + self.assertNotEqual(previous_b_color, current_b_color) + self.assertNotEqual(previous_c_color, current_c_color) + self.assertEqual(color_name, current_b_color) + self.assertEqual(color_name, current_c_color) + + def test_Action_SetRandomColor(self): + """ + Test Scope: + - Ensure that channel color is changed. + Events: + - Open Context Menu + - Select 'Set random color' + - Select 1 Channel + - Open Context Menu + - Select 'Set random color' + - Select 2 Channels + - Open Context Menu + - Select 'Set random color' + Evaluate: + - Evaluate that color dialog is not open if channel is not selected. + - Evaluate that channel color is changed. + """ + action_text = "Set random color" + + # Event + with self.subTest("NoChannelSelected"): + with mock.patch("asammdf.gui.widgets.tree.QtWidgets.QColorDialog.getColor") as mo_getColor: + self.context_menu(action_text=action_text) + mo_getColor.assert_not_called() + + with self.subTest("1ChannelSelected"): + position = self.plot.channel_selection.visualItemRect(self.plot_channel_a).center() + # Setup + previous_color = self.plot_channel_a.color.name() + # Event + self.context_menu(action_text=action_text, position=position) + # Evaluate + current_color = self.plot_channel_a.color.name() + self.assertNotEqual(previous_color, current_color) + + with self.subTest("2ChannelsSelected"): + self.mouseClick_WidgetItem(self.plot_channel_b) + self.plot_channel_b.setSelected(True) + self.plot_channel_c.setSelected(True) + position = self.plot.channel_selection.visualItemRect(self.plot_channel_c).center() + # Setup + previous_b_color = self.plot_channel_b.color.name() + previous_c_color = self.plot_channel_c.color.name() + # Event + self.context_menu(action_text=action_text, position=position) + # Evaluate + current_b_color = self.plot_channel_b.color.name() + current_c_color = self.plot_channel_c.color.name() + self.assertNotEqual(previous_b_color, current_b_color) + self.assertNotEqual(previous_c_color, current_c_color) + self.assertNotEqual(current_b_color, current_c_color) diff --git a/test/asammdf/gui/widgets/test_PlotWidget_DragAndDrop.py b/test/asammdf/gui/widgets/test_PlotWidget_DragAndDrop.py index d7031e193..b7194c8ed 100644 --- a/test/asammdf/gui/widgets/test_PlotWidget_DragAndDrop.py +++ b/test/asammdf/gui/widgets/test_PlotWidget_DragAndDrop.py @@ -8,6 +8,7 @@ from PySide6 import QtCore, QtGui, QtTest, QtWidgets +@unittest.skipIf(sys.platform != "win32", "Timers cannot be started/stopped from another thread.") class TestDragAndDrop(TestPlotWidget): # Note: Test Plot Widget through FileWidget. diff --git a/tox.ini b/tox.ini index 2053d3e0f..eeddbef2f 100644 --- a/tox.ini +++ b/tox.ini @@ -23,10 +23,10 @@ commands = [testenv:style] deps = black - isort + ruff commands = black --config pyproject.toml --check . - isort --settings-path pyproject.toml --check . + ruff check ./src [testenv:doc] deps =