From e5ba11c4da6a555d18ff0a8d30dde7fbe5e58a47 Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Tue, 16 Apr 2024 08:32:06 +0200 Subject: [PATCH 01/17] Added windows icons to resources. --- resources/windows_icons/pydidas_help.ico | Bin 0 -> 410598 bytes .../windows_icons/pydidas_windows_icon.ico | Bin 0 -> 72566 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/windows_icons/pydidas_help.ico create mode 100644 resources/windows_icons/pydidas_windows_icon.ico diff --git a/resources/windows_icons/pydidas_help.ico b/resources/windows_icons/pydidas_help.ico new file mode 100644 index 0000000000000000000000000000000000000000..378f281481109665d4578b0a29ae85f900bf064c GIT binary patch literal 410598 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 aML-cy1QY>9KoL*`6ahs*5l{r&Lf}92r_Zwh literal 0 HcmV?d00001 diff --git a/resources/windows_icons/pydidas_windows_icon.ico b/resources/windows_icons/pydidas_windows_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..caa8bdff89f18c70bebe0d73c64ba1872919282e GIT binary patch literal 72566 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 Date: Tue, 16 Apr 2024 08:35:07 +0200 Subject: [PATCH 02/17] Fixed deprecated attribute in dataset_utils. --- pydidas/core/utils/dataset_utils.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pydidas/core/utils/dataset_utils.py b/pydidas/core/utils/dataset_utils.py index 0ba8b6661..d25afaa9c 100644 --- a/pydidas/core/utils/dataset_utils.py +++ b/pydidas/core/utils/dataset_utils.py @@ -1,6 +1,6 @@ # This file is part of pydidas. # -# Copyright 2023, Helmholtz-Zentrum Hereon +# Copyright 2023 - 2024, Helmholtz-Zentrum Hereon # SPDX-License-Identifier: GPL-3.0-only # # pydidas is free software: you can redistribute it and/or modify @@ -20,7 +20,7 @@ """ __author__ = "Malte Storm" -__copyright__ = "Copyright 2023, Helmholtz-Zentrum Hereon" +__copyright__ = "Copyright 2023 - 2024, Helmholtz-Zentrum Hereon" __license__ = "GPL-3.0-only" __maintainer__ = "Malte Storm" __status__ = "Production" @@ -65,14 +65,13 @@ def update_dataset_properties_from_kwargs(obj: Dataset, kwargs: dict) -> Dataset obj : pydidas.core.Dataset The updated Dataset. """ - obj._meta = {} + obj._meta = {"getitem_key": ()} obj.axis_units = kwargs.get("axis_units", dataset_ax_str_default(obj.ndim)) obj.axis_labels = kwargs.get("axis_labels", dataset_ax_str_default(obj.ndim)) obj.axis_ranges = kwargs.get("axis_ranges", dataset_ax_default_ranges(obj.shape)) obj.metadata = kwargs.get("metadata", {}) obj.data_unit = kwargs.get("data_unit", "") obj.data_label = kwargs.get("data_label", "") - obj._getitem_key = () return obj From 7b5f6b9ab1988a3c3607391eadca4a653efcbc4d Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Tue, 16 Apr 2024 08:43:53 +0200 Subject: [PATCH 03/17] Fixed a display issue in the MainWindow logging dockable widget. --- CHANGELOG.rst | 10 +++++++++- pydidas/gui/main_window.py | 6 +++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 913d59753..f7626e7c0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,9 +2,17 @@ .. SPDX-License-Identifier: CC0-1.0 -v24.03.25 +v24.xx.yy ========= +Bugfixes +-------- +- Fixed a display issue in the title of the logging dockable widget. + + + +v24.03.25 +========= Improvements ------------ diff --git a/pydidas/gui/main_window.py b/pydidas/gui/main_window.py index 77cf49718..946bd6d37 100644 --- a/pydidas/gui/main_window.py +++ b/pydidas/gui/main_window.py @@ -1,6 +1,6 @@ # This file is part of pydidas. # -# Copyright 2023, Helmholtz-Zentrum Hereon +# Copyright 2023 - 2024, Helmholtz-Zentrum Hereon # SPDX-License-Identifier: GPL-3.0-only # # pydidas is free software: you can redistribute it and/or modify @@ -21,7 +21,7 @@ """ __author__ = "Malte Storm" -__copyright__ = "Copyright 2023, Helmholtz-Zentrum Hereon" +__copyright__ = "Copyright 2023 - 2024, Helmholtz-Zentrum Hereon" __license__ = "GPL-3.0-only" __maintainer__ = "Malte Storm" __status__ = "Production" @@ -77,7 +77,7 @@ def __create_logging_info_box(self): Create the PydidasStatusWidget for logging and status messages. """ self.__info_widget = PydidasStatusWidget() - _dock_widget = QtWidgets.QDockWidget("Logging && information") + _dock_widget = QtWidgets.QDockWidget("Logging and information") _dock_widget.setWidget(self.__info_widget) _dock_widget.setFeatures( QtWidgets.QDockWidget.DockWidgetMovable From 0d50faab873fe667e6db2412734d532e6481ec8c Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Tue, 16 Apr 2024 08:45:16 +0200 Subject: [PATCH 04/17] Updated copyright on file. --- pydidas/apps/directory_spy_app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pydidas/apps/directory_spy_app.py b/pydidas/apps/directory_spy_app.py index 7a39ffac7..17888e852 100644 --- a/pydidas/apps/directory_spy_app.py +++ b/pydidas/apps/directory_spy_app.py @@ -1,6 +1,6 @@ # This file is part of pydidas. # -# Copyright 2023, Helmholtz-Zentrum Hereon +# Copyright 2023 - 2024, Helmholtz-Zentrum Hereon # SPDX-License-Identifier: GPL-3.0-only # # pydidas is free software: you can redistribute it and/or modify @@ -21,7 +21,7 @@ """ __author__ = "Malte Storm" -__copyright__ = "Copyright 2023, Helmholtz-Zentrum Hereon" +__copyright__ = "Copyright 2023 - 2024, Helmholtz-Zentrum Hereon" __license__ = "GPL-3.0-only" __maintainer__ = "Malte Storm" __status__ = "Production" From 5cfa108689c9111b5f0a38ee8cea6819b0dd072a Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Tue, 16 Apr 2024 09:14:28 +0200 Subject: [PATCH 05/17] Fixed an issue in the pyFAI calibration frame where the supported file formats where not correctly available in the file dialog. --- CHANGELOG.rst | 3 ++- pydidas/widgets/silx_plot/silx_actions.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f7626e7c0..881e8c88b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,7 +8,8 @@ v24.xx.yy Bugfixes -------- - Fixed a display issue in the title of the logging dockable widget. - +- Fixed an issue in the pyFAI calibration frame where the supported file + formats where not correctly available in the file dialog. v24.03.25 diff --git a/pydidas/widgets/silx_plot/silx_actions.py b/pydidas/widgets/silx_plot/silx_actions.py index fe5148a48..60a6ac423 100644 --- a/pydidas/widgets/silx_plot/silx_actions.py +++ b/pydidas/widgets/silx_plot/silx_actions.py @@ -308,7 +308,7 @@ def __execute(self): """ _filename = self._dialog.get_existing_filename( caption=self._dialog_kwargs["caption"], - extensions=IoMaster.get_string_of_formats(), + formats=IoMaster.get_string_of_formats(), qsettings_ref=self._dialog_kwargs["qsettings_ref"], ) if _filename is not None: From 7904386844272a054e16c4b17131423c0faa24b2 Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Tue, 16 Apr 2024 15:40:13 +0200 Subject: [PATCH 06/17] h an offset --- CHANGELOG.rst | 7 ++++++- .../core/generic_params/generic_params_scan.py | 18 +++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 881e8c88b..42287c486 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,12 @@ v24.xx.yy ========= +Improvements +------------ +- Improved the naming and tooltips of scan parameters with respect to the + file numbers and indices. + + Bugfixes -------- - Fixed a display issue in the title of the logging dockable widget. @@ -17,7 +23,6 @@ v24.03.25 Improvements ------------ - - Changed a number of filenames and paths (mainly in the documentation) to reduce the total length of the file names. - Updated files to new black 2024 style. diff --git a/pydidas/core/generic_params/generic_params_scan.py b/pydidas/core/generic_params/generic_params_scan.py index 612a24d5d..3c670cc40 100644 --- a/pydidas/core/generic_params/generic_params_scan.py +++ b/pydidas/core/generic_params/generic_params_scan.py @@ -86,26 +86,30 @@ "scan_start_index": { "type": int, "default": 0, - "name": "Starting index", + "name": "First filename number", "choices": None, "unit": "", "allow_None": False, "tooltip": ( - "The starting index offset for the index used to identify data " - "points in the scan." + "The number of the first file to be used in processing. This number " + "will be applied as offset in the scan naming pattern to identify " + "the respective filename for scan points." ), }, "scan_index_stepping": { "type": int, "default": 1, - "name": "Index stepping", + "name": "Frame index stepping", "choices": None, "unit": "", "allow_None": False, "tooltip": ( - "The stepping of the index. A value of n corresponds to only using " - "every n-th index. For example, an index stepping of 3 with an offset " - "of 5 would process the frames 5, 8, 11, 14 etc." + "The stepping of the index in frames. A value of n corresponds to only " + "using every n-th index. For example, an index stepping of 3 with an " + "offset of 5 would process the frames 5, 8, 11, 14 etc. \n" + "Please note that the index stepping refers to the frames, not the " + "filenames. In the case of container files (e.g. hdf5), the index " + "stepping will skip process every n-th frame, not every n-th file." ), }, "scan_multi_image_handling": { From 07487076366df04f2fd0b2f69d89ed022a8ab804 Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Tue, 16 Apr 2024 15:40:38 +0200 Subject: [PATCH 07/17] Improved the naming and tooltips of scan parameters with respect to the file numbers and indices. --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 42287c486..704fbc2f9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,7 +8,7 @@ v24.xx.yy Improvements ------------ - Improved the naming and tooltips of scan parameters with respect to the - file numbers and indices. + file numbers and indices. Bugfixes From 78341cba46348e3a7c660204be0b8e4d1b66ccad Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Wed, 17 Apr 2024 13:23:33 +0200 Subject: [PATCH 08/17] Fixed an issue with settings the X-ray energy / wavelength in the DiffractionExperimentContext on the command line with wrong data types. --- CHANGELOG.rst | 2 ++ pydidas/contexts/diff_exp/diff_exp.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 704fbc2f9..def2524ea 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,8 @@ Bugfixes - Fixed a display issue in the title of the logging dockable widget. - Fixed an issue in the pyFAI calibration frame where the supported file formats where not correctly available in the file dialog. +- Fixed an issue with settings the X-ray energy / wavelength in the + DiffractionExperimentContext on the command line with wrong data types. v24.03.25 diff --git a/pydidas/contexts/diff_exp/diff_exp.py b/pydidas/contexts/diff_exp/diff_exp.py index 6fad9e1ba..1fd95dfc0 100644 --- a/pydidas/contexts/diff_exp/diff_exp.py +++ b/pydidas/contexts/diff_exp/diff_exp.py @@ -104,10 +104,10 @@ def set_param_value(self, param_key: str, value: object): """ self._check_key(param_key) if param_key == "xray_energy": - self.params["xray_energy"].value = value + self.params.set_value("xray_energy", value) self.params["xray_wavelength"].value = LAMBDA_IN_A_TO_E / value elif param_key == "xray_wavelength": - self.params["xray_wavelength"].value = value + self.params.set_value("xray_wavelength", value) self.params["xray_energy"].value = LAMBDA_IN_A_TO_E / value else: self.params.set_value(param_key, value) From 9645ed7c8171a347fef018d506e0a5d519f875a7 Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Wed, 17 Apr 2024 13:26:22 +0200 Subject: [PATCH 09/17] Fixed an issue with convenience type conversions in the Parameter class. --- CHANGELOG.rst | 1 + pydidas/core/parameter.py | 83 +++++++++++++++++++----------------- tests/core/test_parameter.py | 30 +++++++++++-- 3 files changed, 71 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index def2524ea..458cd3f7f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,7 @@ Bugfixes formats where not correctly available in the file dialog. - Fixed an issue with settings the X-ray energy / wavelength in the DiffractionExperimentContext on the command line with wrong data types. +- Fixed an issue with convenience type conversions in the Parameter class. v24.03.25 diff --git a/pydidas/core/parameter.py b/pydidas/core/parameter.py index 49eab96e3..fecd36118 100644 --- a/pydidas/core/parameter.py +++ b/pydidas/core/parameter.py @@ -1,6 +1,6 @@ # This file is part of pydidas. # -# Copyright 2023, Helmholtz-Zentrum Hereon +# Copyright 2023 - 2024, Helmholtz-Zentrum Hereon # SPDX-License-Identifier: GPL-3.0-only # # pydidas is free software: you can redistribute it and/or modify @@ -21,23 +21,23 @@ """ __author__ = "Malte Storm" -__copyright__ = "Copyright 2023, Helmholtz-Zentrum Hereon" +__copyright__ = "Copyright 2023 - 2024, Helmholtz-Zentrum Hereon" __license__ = "GPL-3.0-only" __maintainer__ = "Malte Storm" __status__ = "Production" __all__ = ["Parameter"] -import numbers import warnings from collections.abc import Iterable +from numbers import Integral, Real from pathlib import Path -from typing import Dict, List, Self, Set, Tuple, Type, Union +from typing import Any, Dict, List, Self, Set, Tuple, Type, Union from .hdf5_key import Hdf5key -def _get_base_class(cls) -> type: +def _get_base_class(cls: Any) -> Union[type, Real, Integral, None]: """ Filter numerical classes and return the corresponding abstract base class. @@ -53,14 +53,14 @@ def _get_base_class(cls) -> type: Returns ------- object - The base class of the input class if the inpu. + The base class of the input class if the input. """ if cls is None: return None - if numbers.Integral.__subclasscheck__(cls): - return numbers.Integral - if numbers.Real.__subclasscheck__(cls): - return numbers.Real + if issubclass(cls, Integral): + return Integral + if issubclass(cls, Real): + return Real return cls @@ -70,7 +70,7 @@ class Parameter: The Parameter has the following properties which can be accessed. Only the value and choices properties can be edited at runtime, all other - properties are fixed at instanciation. + properties are fixed at instantiation. +------------+-----------+-------------------------------------------+ | property | editable | description | @@ -104,7 +104,7 @@ class Parameter: +------------+-----------+-------------------------------------------+ Parameters can be passed either as a complete meta_dict or as individual - keyword arguments. The meta_dict will take precendence. + keyword arguments. The meta_dict will take precedence. Parameters ---------- @@ -120,7 +120,7 @@ class Parameter: The default value. The data type must be of the same type as param_type. None is only accepted if param_type is None as well. meta : Union[dict, None], optional - A dictionary with meta data. Any keys specified as meta will + A dictionary with metadata. Any keys specified as meta will overwrite the kwargs dictionary. This is added merely as convenience to facility copying Parameter instances. If None, this entry will be ignored. The default is None. @@ -171,13 +171,13 @@ def __init__( self.__process_choices_input(kwargs) self.value = kwargs.get("value", self.__meta["default"]) - def __process_default_input(self, default: Type): + def __process_default_input(self, default: object): """ Process the default value. Parameters ---------- - default : Type + default : object The default attribute passed to init. Raises @@ -206,7 +206,7 @@ def __process_choices_input(self, kwargs: Dict): TypeError If choices is not of an accepted type (None, list, tuple) ValueError - If the default has been set and it is not in choices. + If the default has been set, and it is not in choices. """ _choices = kwargs.get("choices", None) if not (isinstance(_choices, (list, tuple, set)) or _choices is None): @@ -224,7 +224,7 @@ def __process_choices_input(self, kwargs: Dict): def __typecheck(self, val: object) -> bool: """ - Check the type of a new input. + Check the type of new input. Parameters ---------- @@ -268,19 +268,22 @@ def __convenience_type_conversion(self, value: object) -> object: Returns ------- value : any - The value with the above mentioned type conversions applied. + The value with the above-mentioned type conversions applied. """ if isinstance(value, str): if self.__type == Path: value = Path(value) elif self.__type == Hdf5key: value = Hdf5key(value) - if self.__type == numbers.Real and not self.__meta["allow_None"]: - return float(value) - if isinstance(value, list) and self.__type == tuple: - return tuple(value) - if isinstance(value, tuple) and self.__type == list: - return list(value) + if self.__type in [Real, Integral] and not self.__meta["allow_None"]: + try: + value = float(value) if self.__type == Real else int(value) + except ValueError: + pass + finally: + return value + if isinstance(value, Iterable) and self.__type in [list, set, tuple]: + return self.__type(value) return value @property @@ -310,7 +313,7 @@ def allow_None(self) -> bool: @property def refkey(self) -> str: """ - Return the paramter reference key. + Return the parameter reference key. Returns ------- @@ -356,9 +359,9 @@ def tooltip(self) -> str: _t = self.__meta["tooltip"] if self.unit: _t += f" (unit: {self.unit})" - if self.dtype == numbers.Integral: + if self.dtype == Integral: _t += " (type: integer)" - elif self.dtype == numbers.Real: + elif self.dtype == Real: _t += " (type: float)" elif self.dtype == str: _t += " (type: str)" @@ -375,13 +378,6 @@ def choices(self) -> Union[None, List]: """ Get or set the allowed choices for the Parameter value. - Parameters - ---------- - choices : Union[list, tuple, set] - A list or tuple of allowed choices. A check will be performed that - all entries correspond to the defined data type and that the - curent parameter value is one of the allowed choices. - Returns ------- Union[list, None] @@ -394,11 +390,18 @@ def choices(self, choices: Union[None, List, Tuple, Set]): """ Update the allowed choices of a Parameter. + Parameters + ---------- + choices : Union[list, tuple, set] + A list or tuple of allowed choices. A check will be performed that + all entries correspond to the defined data type and that the + current parameter value is one of the allowed choices. + Raises ------ TypeError If the supplied choices are not of datatype list or tuple. - ValueErrror + ValueError If any choice in choices does not pass the datatype check. ValueError If the current Parameter value is not included in the list of @@ -505,9 +508,9 @@ def value_for_export(self) -> object: return None if self.__type in (str, Hdf5key, Path): return str(self.value) - if self.__type == numbers.Integral: + if self.__type == Integral: return int(self.value) - if self.__type == numbers.Real: + if self.__type == Real: return float(self.value) if self.__type in (list, tuple, dict): return self.value @@ -542,7 +545,7 @@ def restore_default(self): def copy(self) -> Self: """ - A method to get the a copy of the Parameter object. + A method to get a copy of the Parameter object. Returns ------- @@ -575,7 +578,7 @@ def dump(self) -> Tuple: _default = self.__meta["default"] if self.choices is not None and self.__meta["default"] not in self.choices: _default = self.value - return (self.__refkey, self.__type, _default, _meta) + return self.__refkey, self.__type, _default, _meta def export_refkey_and_value(self) -> Tuple: """ @@ -587,7 +590,7 @@ def export_refkey_and_value(self) -> Tuple: tuple The tuple of (refkey, value as pickleable format) """ - return (self.__refkey, self.value_for_export) + return self.__refkey, self.value_for_export def __str__(self) -> str: """ diff --git a/tests/core/test_parameter.py b/tests/core/test_parameter.py index 754e3bc03..88e9cb494 100644 --- a/tests/core/test_parameter.py +++ b/tests/core/test_parameter.py @@ -105,7 +105,7 @@ def test_creation__choices_wrong_type(self): def test_creation__wrong_datatype(self): with self.assertRaises(TypeError): - Parameter("Test0", int, "12") + Parameter("Test0", int, "12b") def test_creation__with_allow_None(self): param = Parameter("Test0", int, 12, allow_None=True) @@ -245,10 +245,10 @@ def test_set_value__w_allow_None(self): obj.value = None self.assertEqual(obj.value, None) - def test_set_value_wrong_type(self): + def test_set_value__wrong_type(self): obj = Parameter("Test0", int, 12) with self.assertRaises(ValueError): - obj.value = "24" + obj.value = "24b" def test_set_value_wrong_choice(self): obj = Parameter("Test0", int, 12, choices=[0, 12]) @@ -392,6 +392,30 @@ def test_convenience_type_conversion_any(self): _newval = obj._Parameter__convenience_type_conversion(_val) self.assertEqual(_val, _newval) + def test_convenience_type_conversion__float_w_string_number_input(self): + _val = "42" + obj = Parameter("Test0", float, 12.2) + _newval = obj._Parameter__convenience_type_conversion(_val) + self.assertEqual(float(_val), _newval) + + def test_convenience_type_conversion__float_w_string_input(self): + _val = "42a" + obj = Parameter("Test0", float, 12.2) + _newval = obj._Parameter__convenience_type_conversion(_val) + self.assertEqual(_val, _newval) + + def test_convenience_type_conversion__int_w_string_number_input(self): + _val = "42" + obj = Parameter("Test0", int, 12) + _newval = obj._Parameter__convenience_type_conversion(_val) + self.assertEqual(int(_val), _newval) + + def test_convenience_type_conversion__int_w_string_input(self): + _val = "42a" + obj = Parameter("Test0", int, 12) + _newval = obj._Parameter__convenience_type_conversion(_val) + self.assertEqual(_val, _newval) + def test_convenience_type_conversion_path(self): _val = __file__ obj = Parameter("Test0", Path, "") From 682cf643366f25e926f45c74410a36bcac3612c1 Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Wed, 17 Apr 2024 13:27:56 +0200 Subject: [PATCH 10/17] Added windows icons to .reuse/dep5 file. --- .reuse/dep5 | 1 + 1 file changed, 1 insertion(+) diff --git a/.reuse/dep5 b/.reuse/dep5 index 79802b066..0b80f4122 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -19,6 +19,7 @@ Files: pydidas/resources/*.png resources/*.png resources/*.svg + resources/*.ico resources/graphics_for_gui/* resources/documentation/dev_guide/* Copyright: 2023 - 2024, Helmholtz-Zentrum Hereon From b08614357c47034b0a1217676ede845b55ff9aad Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Thu, 18 Apr 2024 13:48:35 +0200 Subject: [PATCH 11/17] Fixed outdated tests for the Dataset. --- tests/core/test_dataset.py | 2 +- tests/core/utils/test_dataset_utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/test_dataset.py b/tests/core/test_dataset.py index 27444adae..004a90613 100644 --- a/tests/core/test_dataset.py +++ b/tests/core/test_dataset.py @@ -278,7 +278,7 @@ def test_array_finalize__multiple_ops(self): obj = self.create_large_dataset() _ = obj[0, 0] _ = obj[0] - self.assertEqual(obj._getitem_key, ()) + self.assertEqual(obj._meta["getitem_key"], ()) def test_array_finalize__multiple_slicing(self): obj = self.create_large_dataset() diff --git a/tests/core/utils/test_dataset_utils.py b/tests/core/utils/test_dataset_utils.py index 3c997f206..5720d3e1c 100644 --- a/tests/core/utils/test_dataset_utils.py +++ b/tests/core/utils/test_dataset_utils.py @@ -49,7 +49,7 @@ def tearDown(self): ... def test_update_dataset_properties_from_kwargs__no_input(self): obj = Dataset(np.random.random((10, 10))) update_dataset_properties_from_kwargs(obj, {}) - self.assertEqual(obj._getitem_key, ()) + self.assertEqual(obj._meta["getitem_key"], ()) def test_update_dataset_properties_from_kwargs__axis_units(self): _units = {0: "a", 1: "b"} From 0ff9e922058f495223ffd840b23cd03568e98fd0 Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Thu, 18 Apr 2024 13:49:31 +0200 Subject: [PATCH 12/17] Fixed an issue with possibly joining queues twice on exit of WorkerController. --- CHANGELOG.rst | 1 + pydidas/multiprocessing/worker_controller.py | 9 ++++----- tests/multiprocessing/test_app_runner.py | 3 ++- tests/multiprocessing/test_worker_controller.py | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 458cd3f7f..6deef1166 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,7 @@ Bugfixes - Fixed an issue with settings the X-ray energy / wavelength in the DiffractionExperimentContext on the command line with wrong data types. - Fixed an issue with convenience type conversions in the Parameter class. +- Fixed an issue with possibly joining queues twice on exit of WorkerController. v24.03.25 diff --git a/pydidas/multiprocessing/worker_controller.py b/pydidas/multiprocessing/worker_controller.py index 2db25b7cd..28be72ee4 100644 --- a/pydidas/multiprocessing/worker_controller.py +++ b/pydidas/multiprocessing/worker_controller.py @@ -1,6 +1,6 @@ # This file is part of pydidas. # -# Copyright 2023, Helmholtz-Zentrum Hereon +# Copyright 2023 - 2024, Helmholtz-Zentrum Hereon # SPDX-License-Identifier: GPL-3.0-only # # pydidas is free software: you can redistribute it and/or modify @@ -21,7 +21,7 @@ """ __author__ = "Malte Storm" -__copyright__ = "Copyright 2023, Helmholtz-Zentrum Hereon" +__copyright__ = "Copyright 2023 - 2024, Helmholtz-Zentrum Hereon" __license__ = "GPL-3.0-only" __maintainer__ = "Malte Storm" __status__ = "Production" @@ -471,6 +471,7 @@ def join_queues(self): break _queue.close() _queue.join_thread() + self._queues = {} logger.debug("WorkerController: Joined all queues.") def _wait_for_worker_finished_signals(self, timeout: float = 10): @@ -520,9 +521,7 @@ def exit(self, code: Union[None, int] = None): The exit code. """ logger.debug("WorkerController: Exiting thread") - for _queue in self._queues.values(): - _queue.close() - _queue.join_thread() + self.join_queues() if code is not None: super().exit(code) else: diff --git a/tests/multiprocessing/test_app_runner.py b/tests/multiprocessing/test_app_runner.py index 4e0b61266..a657a5163 100644 --- a/tests/multiprocessing/test_app_runner.py +++ b/tests/multiprocessing/test_app_runner.py @@ -68,7 +68,7 @@ def wait_for_spy_signal(self, spy, timeout=8): QtTest.QTest.qWait(100) if len(spy) == 1: break - if time.time() > _t0 + 6: + if time.time() > _t0 + timeout: raise TimeoutError("Waiting too long for final app state.") time.sleep(0.1) @@ -152,6 +152,7 @@ def testcycle_post_run(self): def test_check_if_running_false(self): self._runner = AppRunner(self.app) self._runner._AppRunner__check_is_running() + # assert does not raise RuntimeError def test_check_if_running_true(self): self._runner = AppRunner(self.app) diff --git a/tests/multiprocessing/test_worker_controller.py b/tests/multiprocessing/test_worker_controller.py index cafc8b26d..d17972e9b 100644 --- a/tests/multiprocessing/test_worker_controller.py +++ b/tests/multiprocessing/test_worker_controller.py @@ -1,6 +1,6 @@ # This file is part of pydidas. # -# Copyright 2023, Helmholtz-Zentrum Hereon +# Copyright 2023 - 2024, Helmholtz-Zentrum Hereon # SPDX-License-Identifier: GPL-3.0-only # # pydidas is free software: you can redistribute it and/or modify @@ -18,7 +18,7 @@ """Unit tests for pydidas modules.""" __author__ = "Malte Storm" -__copyright__ = "Copyright 2023, Helmholtz-Zentrum Hereon" +__copyright__ = "Copyright 2023 - 2024, Helmholtz-Zentrum Hereon" __license__ = "GPL-3.0-only" __maintainer__ = "Malte Storm" __status__ = "Production" From 69fb630df38e7979be5325cc5b9fb2f6ec085636 Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Fri, 19 Apr 2024 13:47:43 +0200 Subject: [PATCH 13/17] Fixed a redundant f-string decorator. --- formatter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formatter.py b/formatter.py index cdb04bed9..f07bef1ea 100644 --- a/formatter.py +++ b/formatter.py @@ -149,7 +149,7 @@ def check_version_tags(directory: Optional[Path] = None): with open(_directory.joinpath("pydidas", "version.py"), "r") as f: _line = [_line for _line in f.readlines() if _line.startswith("__version__")] _version = _line[0].split("=")[1].strip().strip('"') - _timed_print(f"Starting version tag check.", new_lines=1) + _timed_print("Starting version tag check.", new_lines=1) with open(_directory.joinpath("CHANGELOG.rst"), "r") as f: _changelog_lines = f.readlines() _changelog_okay = f"v{_version}" in [_line.strip() for _line in _changelog_lines] From 82656a846c2df30d8b3dba4bc92e828b4040afc4 Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Fri, 19 Apr 2024 13:48:50 +0200 Subject: [PATCH 14/17] Added the ParameterCollection creation the the ParameterCollectionMixin class initialization. --- CHANGELOG.rst | 2 + .../core/object_with_parameter_collection.py | 7 +- pydidas/core/parameter_collection_mixin.py | 96 +++++++++++++------ .../src/manuals/cmdline_global/cmd_global.rst | 22 +++-- .../unittest_objects/create_dummy_plugins.py | 8 +- pydidas/widgets/framework/base_frame.py | 17 ++-- .../selection/result_selection_widget.py | 16 ++-- .../test_object_with_parameter_collection.py | 53 ++++++++++ tests/plugins/test_base_plugin.py | 1 + 9 files changed, 163 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6deef1166..efdaacc71 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,8 @@ Improvements ------------ - Improved the naming and tooltips of scan parameters with respect to the file numbers and indices. +- Added the ParameterCollection creation the the ParameterCollectionMixin + class initialization. Bugfixes diff --git a/pydidas/core/object_with_parameter_collection.py b/pydidas/core/object_with_parameter_collection.py index 400f28d30..f68b19504 100644 --- a/pydidas/core/object_with_parameter_collection.py +++ b/pydidas/core/object_with_parameter_collection.py @@ -1,6 +1,6 @@ # This file is part of pydidas. # -# Copyright 2023, Helmholtz-Zentrum Hereon +# Copyright 2023 - 2024, Helmholtz-Zentrum Hereon # SPDX-License-Identifier: GPL-3.0-only # # pydidas is free software: you can redistribute it and/or modify @@ -22,7 +22,7 @@ """ __author__ = "Malte Storm" -__copyright__ = "Copyright 2023, Helmholtz-Zentrum Hereon" +__copyright__ = "Copyright 2023 - 2024, Helmholtz-Zentrum Hereon" __license__ = "GPL-3.0-only" __maintainer__ = "Malte Storm" __status__ = "Production" @@ -35,7 +35,6 @@ from qtpy import QtCore -from .parameter_collection import ParameterCollection from .parameter_collection_mixin import ParameterCollectionMixIn from .pydidas_q_settings_mixin import PydidasQsettingsMixin @@ -54,7 +53,7 @@ class ObjectWithParameterCollection( def __init__(self): QtCore.QObject.__init__(self) PydidasQsettingsMixin.__init__(self) - self.params = ParameterCollection() + ParameterCollectionMixIn.__init__(self) self._config = {} def __copy__(self) -> Self: diff --git a/pydidas/core/parameter_collection_mixin.py b/pydidas/core/parameter_collection_mixin.py index 4f4af55d5..e24e3f082 100644 --- a/pydidas/core/parameter_collection_mixin.py +++ b/pydidas/core/parameter_collection_mixin.py @@ -1,6 +1,6 @@ # This file is part of pydidas. # -# Copyright 2023, Helmholtz-Zentrum Hereon +# Copyright 2023 - 2024, Helmholtz-Zentrum Hereon # SPDX-License-Identifier: GPL-3.0-only # # pydidas is free software: you can redistribute it and/or modify @@ -21,7 +21,7 @@ class functionality to make simplified use of the pydidas ParameterCollection. """ __author__ = "Malte Storm" -__copyright__ = "Copyright 2023, Helmholtz-Zentrum Hereon" +__copyright__ = "Copyright 2023 - 2024, Helmholtz-Zentrum Hereon" __license__ = "GPL-3.0-only" __maintainer__ = "Malte Storm" __status__ = "Production" @@ -29,7 +29,7 @@ class functionality to make simplified use of the pydidas ParameterCollection. from numbers import Integral -from typing import Dict, List, Self, Tuple, Type, Union +from typing import Union from numpy import mod @@ -42,14 +42,24 @@ class ParameterCollectionMixIn: """ MixIn class with ParameterCollection convenience methods. - If the mix-in is used, the subclass is responsible for creating a - ParameterCollection instance as self.params attribute. + The ParameterCollectionMixIn class will create the generic .params attribute + to be used. It will not overwrite existing params attributes. """ default_params = ParameterCollection() + def __init__(self): + if not hasattr(self, "params"): + self.params = ParameterCollection() + if not isinstance(self.params, ParameterCollection): + raise TypeError( + "The class has a .params attribute which is not a ParameterCollection. " + "The ParameterCollectionMixIn class requires an instance of " + "ParameterCollection." + ) + @classmethod - def get_default_params_copy(cls) -> Self: + def get_default_params_copy(cls) -> ParameterCollection: """ Get a copy of the default ParameterCollection. @@ -61,7 +71,7 @@ def get_default_params_copy(cls) -> Self: return cls.default_params.copy() @property - def param_values(self) -> Dict: + def param_values(self) -> dict: """ Get the values of all stored Parameters along with their refkeys. @@ -72,6 +82,18 @@ def param_values(self) -> Dict: """ return self.get_param_values_as_dict() + @property + def param_keys(self) -> list[str]: + """ + Get the keys of all stored Parameters. + + Returns + ------- + list[str] + The keys of all stored Parameters. + """ + return list(self.params.keys()) + def add_param(self, param: Parameter): """ Add a parameter to the ParameterCollection. @@ -85,7 +107,7 @@ def add_param(self, param: Parameter): """ self.params.add_param(param) - def update_params_from_init(self, *args: Tuple, **kwargs: Dict): + def update_params_from_init(self, *args: tuple, **kwargs: dict): """ Update the Parameters from the given init args and kwargs. @@ -100,7 +122,7 @@ def update_params_from_init(self, *args: Tuple, **kwargs: Dict): self.set_default_params() self.update_param_values_from_kwargs(**kwargs) - def add_params(self, *params: Tuple[Union[Parameter, dict, ParameterCollection]]): + def add_params(self, *params: tuple[Union[Parameter, dict, ParameterCollection]]): """ Add parameters to the object. @@ -114,9 +136,6 @@ def add_params(self, *params: Tuple[Union[Parameter, dict, ParameterCollection]] ---------- *params : Tuple[Union[Parameter, dict, ParameterCollection]] Any Parameter or ParameterCollection - **kwed_params : dict - A dictionary with Parameters. Note that the dictionary keys will - be ignored and only the Parameter.refkey attributes will be used. """ for _param in params: if isinstance(_param, Parameter): @@ -137,11 +156,11 @@ def set_default_params(self): If there are no entries for the Parameter keys, it will add a Parameter with default value. """ - for _param in self.default_params.values(): - if _param.refkey not in self.params: - self.params.add_param(Parameter(*_param.dump())) + for _key, _param in self.default_params.items(): + if _key not in self.params: + self.add_param(_param.copy()) - def update_param_values_from_kwargs(self, **kwargs: Dict): + def update_param_values_from_kwargs(self, **kwargs: dict): """ Update the Parameter values corresponding to the given keys. @@ -158,7 +177,7 @@ def get_param_value( self, param_key: str, *default: object, - dtype: Type = None, + dtype: Union[type, None] = None, for_export: bool = False, ) -> object: """ @@ -174,9 +193,9 @@ def get_param_value( A datatype to convert the value into. If None, the native datatype is returned. The default is None. for_export : bool, optional - An optional flag to force converting the Parameter value to an export- - compatible format. This flag is not compatible with a specific dtype. - The default is False. + An optional flag to force converting the Parameter value to an + export-compatible format. This flag is not compatible with a specific + dtype. The default is False. Returns ------- @@ -215,7 +234,7 @@ def get_param(self, param_key: str) -> Parameter: self._check_key(param_key) return self.params[param_key] - def get_params(self, *param_keys: Tuple[str, ...]) -> List[Parameter]: + def get_params(self, *param_keys: tuple[str, ...]) -> list[Parameter]: """ Get multiple parameters based on their reference keys. @@ -244,23 +263,44 @@ def set_param_value(self, param_key: str, value: object): ---------- param_key : str The key name of the Parameter. - value : * + value : object The value to be set. This has to be the datatype associated with the Parameter. """ self._check_key(param_key) self.params.set_value(param_key, value) - def get_param_values_as_dict(self, filter_types_for_export: bool = False) -> Dict: + def set_param_values(self, **kwargs: dict): + """ + Set multiple parameter values at once. + + Parameters + ---------- + **kwargs : dict + The reference key and value pairs for all Parameters to be set. + """ + _wrong_keys = [_key for _key in kwargs if _key not in self.params] + if _wrong_keys: + raise KeyError( + "The following keys are not registered with " + f"{self.__class__.__name__}: " + ", ".join(_wrong_keys) + ) + for _key, _val in kwargs.items(): + self.params.set_value(_key, _val) + + def get_param_values_as_dict(self, filter_types_for_export: bool = False) -> dict: """ Get a dictionary with Parameter names and values only. + Parameters + ---------- + filter_types_for_export : bool + Flag to return objects in types suitable for exporting (i.e. pickleable). + Returns ------- name_val_pairs : dict The dictionary with Parameter : pairs. - filter_types_for_export : bool - Flag to return objects in """ name_val_pairs = { _key: (_param.value_for_export if filter_types_for_export else _param.value) @@ -268,7 +308,7 @@ def get_param_values_as_dict(self, filter_types_for_export: bool = False) -> Dic } return name_val_pairs - def set_param_values_from_dict(self, value_dict: Dict): + def set_param_values_from_dict(self, value_dict: dict): """ Set the Parameter values from a dict with name, value paris. @@ -281,7 +321,7 @@ def set_param_values_from_dict(self, value_dict: Dict): if _key in self.params: self.set_param_value(_key, _value) - def get_param_keys(self) -> List[str]: + def get_param_keys(self) -> list[str]: """ Get the keys of all registered Parameters. @@ -290,7 +330,7 @@ def get_param_keys(self) -> List[str]: list The keys of all registered Parameters. """ - return [_p.refkey for _p in self.params.values()] + return list(self.params.keys()) def print_param_values(self): """ diff --git a/pydidas/docs/src/manuals/cmdline_global/cmd_global.rst b/pydidas/docs/src/manuals/cmdline_global/cmd_global.rst index 16eb7bbf5..8e2ecf649 100644 --- a/pydidas/docs/src/manuals/cmdline_global/cmd_global.rst +++ b/pydidas/docs/src/manuals/cmdline_global/cmd_global.rst @@ -112,8 +112,15 @@ The object :py:data:`exp` will be used in all examples below. 'detector_rot2': -0.004845626736941386, 'detector_rot3': 5.799041608456517e-08} + .. tip:: + + You can also use the :py:data:`param_values` property to retrieve + all parameter values in a dictionary. + 4. Set the value of the *xray_energy* :py:class:`Parameter `. This is a float value, + however, the :py:class:`Parameter ` will attempt + to convert other types to float. If successful, for demonstration purposes, let us set it with a string first. This will raise a :py:data:ValueError` and the Parameter will not be updated. @@ -122,14 +129,17 @@ The object :py:data:`exp` will be used in all examples below. >>> exp.get_param_value('xray_energy') 15.0 >>> exp.set_param_value('xray_energy', '12.0') - ValueError: Cannot set Parameter (object ID:2506714567632, - refkey: "xray_energy", name: "X-ray energy") because it is of the - wrong data type. - >>> exp.get_param_value('xray_energy') - 15.0 - >>> exp.set_param_value('xray_energy', 12.0) >>> exp.get_param_value('xray_energy') 12.0 + >>> exp.set_param_value('xray_energy', 13) + >>> exp.get_param_value('xray_energy') + 13.0 + >>> exp.set_param_value('xray_energy', "twelve") + ValueError: Cannot set Parameter (object ID:2129071369296, refkey: + 'xray_energy', name: 'X-ray energy') because it is of the wrong data type. + (expected: , input type: + >>> exp.get_param_value('xray_energy') + 13.0 Global pydidas objects diff --git a/pydidas/unittest_objects/create_dummy_plugins.py b/pydidas/unittest_objects/create_dummy_plugins.py index 715e1fcf9..2869797ea 100644 --- a/pydidas/unittest_objects/create_dummy_plugins.py +++ b/pydidas/unittest_objects/create_dummy_plugins.py @@ -1,6 +1,6 @@ # This file is part of pydidas. # -# Copyright 2023, Helmholtz-Zentrum Hereon +# Copyright 2023 - 2024, Helmholtz-Zentrum Hereon # SPDX-License-Identifier: GPL-3.0-only # # pydidas is free software: you can redistribute it and/or modify @@ -21,7 +21,7 @@ """ __author__ = "Malte Storm" -__copyright__ = "Copyright 2023, Helmholtz-Zentrum Hereon" +__copyright__ = "Copyright 2023 - 2024, Helmholtz-Zentrum Hereon" __license__ = "GPL-3.0-only" __maintainer__ = "Malte Storm" __status__ = "Production" @@ -35,7 +35,6 @@ # because these Plugins will be loaded directly by importlib, absolute imports # are required: -from pydidas.core import ParameterCollection from pydidas.core.constants import BASE_PLUGIN, INPUT_PLUGIN, OUTPUT_PLUGIN, PROC_PLUGIN from pydidas.core.utils import get_random_string from pydidas.plugins import BasePlugin, InputPlugin, OutputPlugin, ProcPlugin @@ -45,7 +44,7 @@ def create_base_class(base: type) -> type: """ Create a single-use base class for a temporary plugin. - This single-use class is required to allow managemant of classs attributes. + This single-use class is required to allow management of class attributes. Parameters ---------- @@ -104,6 +103,5 @@ def create_plugin_class(plugin_type: int, number: int = 0) -> type: _cls.basic_plugin = False _cls.plugin_name = f"Plugin {_name}" _cls.number = number - _cls.params = ParameterCollection() _cls.__doc__ = get_random_string(600) return _cls diff --git a/pydidas/widgets/framework/base_frame.py b/pydidas/widgets/framework/base_frame.py index 8c6e86e97..a960cbac9 100644 --- a/pydidas/widgets/framework/base_frame.py +++ b/pydidas/widgets/framework/base_frame.py @@ -1,6 +1,6 @@ # This file is part of pydidas. # -# Copyright 2023, Helmholtz-Zentrum Hereon +# Copyright 2023 - 2024, Helmholtz-Zentrum Hereon # SPDX-License-Identifier: GPL-3.0-only # # pydidas is free software: you can redistribute it and/or modify @@ -21,12 +21,13 @@ """ __author__ = "Malte Storm" -__copyright__ = "Copyright 2023, Helmholtz-Zentrum Hereon" +__copyright__ = "Copyright 2023 - 2024, Helmholtz-Zentrum Hereon" __license__ = "GPL-3.0-only" __maintainer__ = "Malte Storm" __status__ = "Production" __all__ = ["BaseFrame"] + from qtpy import QtCore, QtWidgets from ...core import ParameterCollection, ParameterCollectionMixIn, PydidasQsettingsMixin @@ -78,7 +79,7 @@ def __init__(self, **kwargs: dict): CreateWidgetsMixIn.__init__(self) PydidasQsettingsMixin.__init__(self) ParameterWidgetsMixIn.__init__(self) - self.params = ParameterCollection() + ParameterCollectionMixIn.__init__(self) init_layout = kwargs.get("init_layout", True) if init_layout: @@ -92,7 +93,7 @@ def __init__(self, **kwargs: dict): self._config = {"built": False} @QtCore.Slot(int) - def frame_activated(self, index): + def frame_activated(self, index: int): """ Received signal that frame has been activated. @@ -135,7 +136,7 @@ def finalize_ui(self): finalize the UI initialization. """ - def set_status(self, text): + def set_status(self, text: str): """ Emit a status message to the main window. @@ -159,9 +160,9 @@ def export_state(self): frame's state. """ _params = self.get_param_values_as_dict(filter_types_for_export=True) - return (self.frame_index, {"params": _params}) + return self.frame_index, {"params": _params} - def restore_state(self, state): + def restore_state(self, state: dict): """ Restore the frame's state from stored information. @@ -186,7 +187,7 @@ def restore_state(self, state): else: self.set_param_value(_key, _val) - def closeEvent(self, event): + def closeEvent(self, event: QtCore.QEvent): """ Reimplement the closeEvent to emit a signal about the closing. diff --git a/pydidas/widgets/selection/result_selection_widget.py b/pydidas/widgets/selection/result_selection_widget.py index d1e13281f..734bad8a6 100644 --- a/pydidas/widgets/selection/result_selection_widget.py +++ b/pydidas/widgets/selection/result_selection_widget.py @@ -30,7 +30,7 @@ from functools import partial -from typing import List, Tuple, Union +from typing import Union import numpy as np from qtpy import QtCore @@ -126,7 +126,7 @@ def __init__( EmptyWidget.__init__(self, **kwargs) ParameterWidgetsMixIn.__init__(self) CreateWidgetsMixIn.__init__(self) - self.params = ParameterCollection() + ParameterCollectionMixIn.__init__(self) self._config = { "widget_visibility": False, "result_ndim": -1, @@ -319,7 +319,7 @@ def __update_slice_param_widgets(self, hide_all: bool = False): _unit if self._config["selection_by_data_values"] else " " ) - def __are_axes_used(self) -> Tuple[bool]: + def __are_axes_used(self) -> tuple[bool, bool]: """ Check whether the axes are in use and return the flags. @@ -453,7 +453,7 @@ def __enable_valid_result_plot_selection(self): self._widgets["radio_plot_type"]._buttons[_id].setEnabled(_2d_enabled) @QtCore.Slot(int) - def __arrange_results_in_timeline_or_scan_shape(self, index): + def __arrange_results_in_timeline_or_scan_shape(self, index: int): """ Organize the scan results in a timeline or using the ScanContext shape. @@ -611,7 +611,7 @@ def __check_and_create_params_for_slice_selection(self): ) self.__update_slice_param_widgets() - def _get_axis_index_labels(self) -> List[str]: + def _get_axis_index_labels(self) -> list[str]: """ Get the indices and axis labels for the selected node ID. @@ -632,13 +632,13 @@ def _get_axis_index_labels(self) -> List[str]: for _index, _dim in enumerate(_active_dims) ] - def _get_axis_labels_and_units(self) -> List[List[str]]: + def _get_axis_labels_and_units(self) -> list[list[str]]: """ Get the axis labels and units. Returns ------- - List[List[str]] + list[list[str]] A list with pair entries for label and unit. """ if self._active_node == -1: @@ -649,7 +649,7 @@ def _get_axis_labels_and_units(self) -> List[List[str]]: for _dim in _active_dims ] - def _get_metadata_and_active_dims(self) -> Tuple[dict, list]: + def _get_metadata_and_active_dims(self) -> tuple[dict, list]: """ Get the metadata and active dimensions of the active node. diff --git a/tests/core/test_object_with_parameter_collection.py b/tests/core/test_object_with_parameter_collection.py index f82de79fe..cc4ac1ee7 100644 --- a/tests/core/test_object_with_parameter_collection.py +++ b/tests/core/test_object_with_parameter_collection.py @@ -37,6 +37,7 @@ ObjectWithParameterCollection, Parameter, ParameterCollection, + ParameterCollectionMixIn, UserConfigError, ) @@ -57,6 +58,36 @@ def test_creation(self): obj = ObjectWithParameterCollection() self.assertIsInstance(obj, ObjectWithParameterCollection) + def test_init__simple(self): + obj = ObjectWithParameterCollection() + self.assertIsInstance(obj.params, ParameterCollection) + + def test_init__mixin_with_existing_params_wrong_type(self): + obj = ObjectWithParameterCollection() + obj.params = "Test" + with self.assertRaises(TypeError): + ParameterCollectionMixIn.__init__(obj) + + def test_init__mixin_with_existing_params(self): + obj = ObjectWithParameterCollection() + ParameterCollectionMixIn.__init__(obj) + self.assertIsInstance(obj.params, ParameterCollection) + + def test_param_values(self): + obj = ObjectWithParameterCollection() + obj.add_params(self._params) + _vals = obj.param_values + self.assertIsInstance(_vals, dict) + for _key, _param in self._params.items(): + self.assertEqual(_vals[_key], _param.value) + + def test_param_keys(self): + obj = ObjectWithParameterCollection() + obj.add_params(self._params) + _keys = obj.param_keys + for _key in self._params: + self.assertIn(_key, _keys) + def test_add_params__with_args(self): obj = ObjectWithParameterCollection() obj.add_params(*self._params.values()) @@ -191,6 +222,28 @@ def test_set_param_value(self): obj.set_param_value("Test2", 12) self.assertEqual(obj.get_param_value("Test2"), 12) + def test_set_param_values__correct(self): + _new_vals = {"Test0": 42, "Test1": "Spam and eggs", "Test2": 13, "Test3": 1.23} + obj = ObjectWithParameterCollection() + obj.add_params(self._params) + obj.set_param_values(**_new_vals) + for _key, _val in _new_vals.items(): + self.assertEqual(_val, obj.get_param_value(_key)) + + def test_set_param_values__single(self): + obj = ObjectWithParameterCollection() + obj.add_params(self._params) + obj.set_param_values(Test0=42) + self.assertEqual(42, obj.get_param_value("Test0")) + + def test_set_param_values__wrong_key(self): + obj = ObjectWithParameterCollection() + obj.add_params(self._params) + _val0 = obj.get_param_value("Test0") + with self.assertRaises(KeyError): + obj.set_param_values(Test0=42, Test5=42) + self.assertEqual(_val0, obj.get_param_value("Test0")) + def test_get_param_keys(self): obj = ObjectWithParameterCollection() obj.add_params(self._params) diff --git a/tests/plugins/test_base_plugin.py b/tests/plugins/test_base_plugin.py index e67ac6764..c354e3ba0 100644 --- a/tests/plugins/test_base_plugin.py +++ b/tests/plugins/test_base_plugin.py @@ -459,6 +459,7 @@ def test_copy(self): cp = copy.copy(obj) self.assertEqual(obj.__class__, cp.__class__) self.assertEqual(obj.get_param_value("label"), cp.get_param_value("label")) + self.assertNotEqual(id(obj.params), id(cp.params)) self.assertEqual(obj.node_id, cp.node_id) From ec58a0c4b4b0b654c20042f8569e9af85def1583 Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Fri, 19 Apr 2024 14:28:11 +0200 Subject: [PATCH 15/17] Updated requirements.txt to fix vulnerabilities. --- requirements.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4a894a656..b76160d8e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -60,7 +60,7 @@ hdf5plugin==4.4.0 # via # fabio # pydidas (pyproject.toml) -idna==3.6 +idna==3.7 # via # pydidas (pyproject.toml) # requests @@ -128,7 +128,7 @@ packaging==24.0 # scikit-image # silx # sphinx -pillow==10.2.0 +pillow==10.3.0 # via # fabio # imageio @@ -186,7 +186,6 @@ requests==2.31.0 # via # pydidas (pyproject.toml) # sphinx -setuptools==69.2.0 scikit-image==0.22.0 # via pydidas (pyproject.toml) scipy==1.12.0 @@ -251,3 +250,6 @@ urllib3==2.2.1 # requests wheel==0.43.0 # via pydidas (pyproject.toml) + +# The following packages are considered to be unsafe in a requirements file: +# setuptools From 19f647cfb386a23e6fb0b98abf4696a83abc3dc0 Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Tue, 23 Apr 2024 12:57:11 +0200 Subject: [PATCH 16/17] Sanitized the method names used by PydidasImageView. --- pydidas/gui/frames/data_browsing_frame.py | 2 +- .../selection/hdf5_dataset_selector.py | 33 +++++++++++++------ .../selection/raw_metadata_selector.py | 27 ++++++++++----- .../widgets/silx_plot/pydidas_imageview.py | 7 ++-- 4 files changed, 47 insertions(+), 22 deletions(-) diff --git a/pydidas/gui/frames/data_browsing_frame.py b/pydidas/gui/frames/data_browsing_frame.py index 296aaef80..70ca7164f 100644 --- a/pydidas/gui/frames/data_browsing_frame.py +++ b/pydidas/gui/frames/data_browsing_frame.py @@ -133,4 +133,4 @@ def __file_selected(self, filename: str): return else: _data = import_data(filename) - self._widgets["viewer"].setData(_data) + self._widgets["viewer"].displayImage(_data) diff --git a/pydidas/widgets/selection/hdf5_dataset_selector.py b/pydidas/widgets/selection/hdf5_dataset_selector.py index b1c80f915..cf525162a 100644 --- a/pydidas/widgets/selection/hdf5_dataset_selector.py +++ b/pydidas/widgets/selection/hdf5_dataset_selector.py @@ -95,7 +95,9 @@ def __init__(self, viewWidget=None, datasetKeyFilters=None, **kwargs): ), } self.flags = {"slotActive": False, "autoUpdate": True} - self._widgets["plot"] = viewWidget + self._show_image_method = None + if viewWidget is not None: + self.register_plot_widget(viewWidget) self._frame = None self.__create_widgets_and_layout() self.__connect_slots() @@ -253,7 +255,7 @@ def __update(self, new_frame: bool = False): if self.flags["slotActive"]: self.new_frame_signal.emit(self._frame) if self.flags["autoUpdate"] and self._widgets["plot"] is not None: - self._widgets["plot"].setData(self._frame) + self._show_image_method(self._frame, legend="pydidas image") def __get_frame(self): """ @@ -279,22 +281,33 @@ def register_plot_widget(self, widget: QtWidgets.QWidget): Register a view widget to be used for full visualization of data. This method registers an external view widget for data visualization. - Note that the widget must accept frames through a ``setData`` method. + Note that the widget must accept frames through a ``addImage`` method. Parameters ---------- widget : QWidget - A widget with a ``setData`` method to pass frames. + A widget with a ``addImage`` method to pass frames. Raises ------ TypeError - If the widget does not have a ``setData`` method. + If the widget is not a QWidget. + AttributeError + If the widget does not have an ``addImage`` or `displayImage` method. """ - if isinstance(widget, QtWidgets.QWidget) and hasattr(widget, "setData"): - self._widgets["plot"] = widget - return - raise TypeError("Error: Object must be a widget with a setData method.") + if not isinstance(widget, QtWidgets.QWidget): + raise TypeError("Error: Object must be a QWidget.") + if not (hasattr(widget, "displayImage") or hasattr(widget, "addImage")): + raise AttributeError( + "Error: The selected widget is not supported, as it does not have an " + "`addImage` or `displayImage` method." + ) + self._widgets["plot"] = widget + if hasattr(widget, "displayImage"): + self._show_image_method = widget.displayImage + elif hasattr(widget, "addImage"): + self._show_image_method = widget.addImage + print("registered hdf5", self._show_image_method) def _toggle_filter_key(self, widget: QtWidgets.QWidget, key: str): """ @@ -388,4 +401,4 @@ def click_view_button(self): self.__get_frame() if not isinstance(self._widgets["plot"], QtWidgets.QWidget): raise PydidasGuiError("The reference is not a widget") - self._widgets["plot"].setData(self._frame) + self._show_image_method(self._frame, legend="pydidas_image") diff --git a/pydidas/widgets/selection/raw_metadata_selector.py b/pydidas/widgets/selection/raw_metadata_selector.py index 3fcd56074..2b31ad509 100644 --- a/pydidas/widgets/selection/raw_metadata_selector.py +++ b/pydidas/widgets/selection/raw_metadata_selector.py @@ -64,7 +64,10 @@ def __init__(self, **kwargs: dict): WidgetWithParameterCollection.__init__(self, **kwargs) self.add_params(self.default_params.copy()) self._config = {"filename": None} + self._show_image_method = None self.__plot = kwargs.get("plot_widget", None) + if self.__plot is not None: + self.register_plot_widget(self.__plot) self.__create_widgets() def __create_widgets(self): @@ -95,22 +98,30 @@ def register_plot_widget(self, widget: QtWidgets.QWidget): Register a view widget to be used for full visualization of data. This method registers an external view widget for data visualization. - Note that the widget must accept frames through a ``setData`` method. + Note that the widget must accept frames through a ``addImage`` method. Parameters ---------- widget : QWidget - A widget with a ``setData`` method to pass frames. + A widget with a ``addImage`` method to pass frames. Raises ------ TypeError - If the widget does not have a ``setData`` method. + If the widget does not have a ``addImage`` method. """ - if isinstance(widget, QtWidgets.QWidget) and hasattr(widget, "setData"): - self.__plot = widget - return - raise TypeError("Error: Object must be a widget with a setData method.") + if not isinstance(widget, QtWidgets.QWidget): + raise TypeError("Error: Object must be a QWidget.") + if not (hasattr(widget, "displayImage") or hasattr(widget, "addImage")): + raise AttributeError( + "Error: The selected widget is not supported, as it does not have an " + "`addImage` or `displayImage` method." + ) + self.__plot = widget + if hasattr(widget, "displayImage"): + self._show_image_method = widget.displayImage + elif hasattr(widget, "addImage"): + self._show_image_method = widget.addImage @QtCore.Slot(str) def new_filename(self, name: str): @@ -151,4 +162,4 @@ def _decode_file(self): _data = import_data( self.__filename, datatype=_datatype, offset=_offset, shape=_shape ) - self.__plot.setData(_data) + self._show_image_method(_data, legend="pydidas image") diff --git a/pydidas/widgets/silx_plot/pydidas_imageview.py b/pydidas/widgets/silx_plot/pydidas_imageview.py index 690ad591f..1b1065a22 100644 --- a/pydidas/widgets/silx_plot/pydidas_imageview.py +++ b/pydidas/widgets/silx_plot/pydidas_imageview.py @@ -1,6 +1,6 @@ # This file is part of pydidas. # -# Copyright 2023, Helmholtz-Zentrum Hereon +# Copyright 2023 - 2024, Helmholtz-Zentrum Hereon # SPDX-License-Identifier: GPL-3.0-only # # pydidas is free software: you can redistribute it and/or modify @@ -20,7 +20,7 @@ """ __author__ = "Malte Storm" -__copyright__ = "Copyright 2023, Helmholtz-Zentrum Hereon" +__copyright__ = "Copyright 2023 - 2024, Helmholtz-Zentrum Hereon" __license__ = "GPL-3.0-only" __maintainer__ = "Malte Storm" __status__ = "Production" @@ -152,7 +152,7 @@ def update_from_diffraction_exp(self): self.cs_transform.set_coordinates("cartesian") self.cs_transform.setEnabled(self._cs_transform_valid) - def setData(self, data: ndarray, **kwargs: dict): + def displayImage(self, data: ndarray, **kwargs: dict): """ Set the image data, handle the coordinate system and forward the data to plotting. @@ -166,6 +166,7 @@ def setData(self, data: ndarray, **kwargs: dict): """ if self.cs_transform is not None: self._check_data_shape(data.shape) + _ = kwargs.pop("legend", None) self._plot_kwargs = kwargs ImageView.setImage(self, data, **kwargs) From 96a51850e244ba637aa42fed9c3243dd098a26d7 Mon Sep 17 00:00:00 2001 From: Malte Storm Date: Tue, 23 Apr 2024 13:00:04 +0200 Subject: [PATCH 17/17] Added a user config signal to the PydidasQApp and the PydidasQSettingsMixin. --- pydidas/core/pydidas_q_settings_mixin.py | 10 +++++++--- pydidas_qtcore/pydidas_qapp.py | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/pydidas/core/pydidas_q_settings_mixin.py b/pydidas/core/pydidas_q_settings_mixin.py index 0059cc54b..e0f7c2595 100644 --- a/pydidas/core/pydidas_q_settings_mixin.py +++ b/pydidas/core/pydidas_q_settings_mixin.py @@ -1,6 +1,6 @@ # This file is part of pydidas. # -# Copyright 2023, Helmholtz-Zentrum Hereon +# Copyright 2023 - 2024, Helmholtz-Zentrum Hereon # SPDX-License-Identifier: GPL-3.0-only # # pydidas is free software: you can redistribute it and/or modify @@ -21,7 +21,7 @@ """ __author__ = "Malte Storm" -__copyright__ = "Copyright 2023, Helmholtz-Zentrum Hereon" +__copyright__ = "Copyright 2023 - 2024, Helmholtz-Zentrum Hereon" __license__ = "GPL-3.0-only" __maintainer__ = "Malte Storm" __status__ = "Production" @@ -31,7 +31,7 @@ from numbers import Integral, Real from typing import Optional, Self, Union -from qtpy import QtCore +from qtpy import QtCore, QtWidgets from ..version import VERSION @@ -131,3 +131,7 @@ def q_settings_set(self, key: str, value: object): The value to be stored. """ self.q_settings.setValue(f"{self.q_settings_version}/{key}", value) + if key.startswith("user/"): + _stripped_key = key.replace("user/", "") + _qtapp = QtWidgets.QApplication.instance() + _qtapp.updated_user_config(_stripped_key, value) diff --git a/pydidas_qtcore/pydidas_qapp.py b/pydidas_qtcore/pydidas_qapp.py index f3436a61c..61c9a350c 100644 --- a/pydidas_qtcore/pydidas_qapp.py +++ b/pydidas_qtcore/pydidas_qapp.py @@ -117,6 +117,7 @@ class PydidasQApplication(QtWidgets.QApplication): sig_mpl_font_change = QtCore.Signal() sig_mpl_font_setting_error = QtCore.Signal(str) sig_status_message = QtCore.Signal(str) + sig_updated_user_config = QtCore.Signal(str, str) def __init__(self, args): QtWidgets.QApplication.__init__(self, args) @@ -328,6 +329,19 @@ def font_metrics(self) -> Tuple[float]: self.__font_config["font_metric_height"], ) + def updated_user_config(self, key: str, value: str): + """ + Handle the updated user config and emit a signal with the change to all plots. + + Parameters + ---------- + key : str + The user configuration key. + value : str + The key's new value. + """ + self.sig_updated_user_config.emit(key, value) + def set_status_message(self, status: str): """ Set a status message.