From 6d348fc36c3b94e51eedf80ad864bd38dad9920c Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 17 Oct 2024 15:44:49 +0200 Subject: [PATCH] chore: Drop python 3.8 (#1206) Python 3.8 reached EOL so it is time to drop it. ## Summary by Sourcery Drop support for Python 3.8 across the codebase and CI configuration. Update type annotations to use modern Python syntax and refactor code for improved readability and consistency. Enhancements: - Update type annotations to use Python 3.9+ syntax, replacing typing.List and typing.Dict with list and dict. - Refactor test cases to use updated type annotations and improve readability. - Refactor code to use context managers with parentheses for better readability and consistency. CI: - Remove Python 3.8 from the CI configuration as it has reached end-of-life. ## Summary by CodeRabbit - **New Features** - Updated Python version support for testing workflows to include versions 3.9 to 3.12. - Updated napari versions in testing configurations to the latest releases. - **Bug Fixes** - Corrected inconsistencies in method signatures and type annotations throughout the codebase. - **Chores** - Removed deprecated Python 3.8 references from testing workflows and configuration files. - Eliminated constraints files for Python 3.8 to streamline dependency management. --- .github/workflows/test_napari_widgets.yml | 6 +- .github/workflows/tests.yml | 30 +- .github/workflows/upgrade-dependencies.yml | 2 +- azure-pipelines.yml | 2 +- package/PartSeg/_launcher/check_version.py | 5 +- package/PartSeg/_launcher/main_window.py | 4 +- .../PartSeg/_roi_analysis/advanced_window.py | 8 +- package/PartSeg/_roi_analysis/batch_window.py | 4 +- package/PartSeg/_roi_analysis/export_batch.py | 2 +- package/PartSeg/_roi_analysis/main_window.py | 3 +- .../_roi_analysis/measurement_widget.py | 9 +- .../PartSeg/_roi_analysis/partseg_settings.py | 16 +- .../_roi_analysis/prepare_plan_widget.py | 12 +- .../PartSeg/_roi_analysis/profile_export.py | 8 +- package/PartSeg/_roi_mask/batch_proceed.py | 6 +- package/PartSeg/_roi_mask/main_window.py | 3 +- .../_roi_mask/segmentation_info_dialog.py | 4 +- package/PartSeg/_roi_mask/stack_settings.py | 18 +- .../PartSeg/common_backend/base_argparser.py | 3 +- .../PartSeg/common_backend/base_settings.py | 29 +- .../common_backend/partially_const_dict.py | 8 +- .../common_backend/python_syntax_highlight.py | 2 +- package/PartSeg/common_gui/advanced_tabs.py | 5 +- .../common_gui/algorithms_description.py | 30 +- package/PartSeg/common_gui/channel_control.py | 26 +- .../PartSeg/common_gui/colormap_creator.py | 25 +- .../PartSeg/common_gui/custom_load_dialog.py | 14 +- .../PartSeg/common_gui/custom_save_dialog.py | 6 +- .../PartSeg/common_gui/equal_column_layout.py | 3 +- package/PartSeg/common_gui/error_report.py | 4 +- .../PartSeg/common_gui/image_adjustment.py | 4 +- package/PartSeg/common_gui/label_create.py | 13 +- package/PartSeg/common_gui/main_window.py | 10 +- package/PartSeg/common_gui/mask_widget.py | 4 +- .../common_gui/multiple_file_widget.py | 9 +- .../PartSeg/common_gui/napari_image_view.py | 35 +- .../PartSeg/common_gui/napari_viewer_wrap.py | 4 +- package/PartSeg/common_gui/numpy_qimage.py | 2 +- .../common_gui/select_multiple_files.py | 3 +- .../PartSeg/common_gui/stack_image_view.py | 4 +- .../PartSeg/common_gui/universal_gui_part.py | 4 +- .../modeling_save/save_modeling_data.py | 2 +- .../napari_widgets/algorithm_widgets.py | 4 +- .../napari_widgets/colormap_control.py | 5 +- .../plugins/napari_widgets/lables_control.py | 10 +- .../roi_extraction_algorithms.py | 14 +- .../PartSeg/plugins/napari_widgets/utils.py | 2 +- .../plugins/old_partseg/old_partseg.py | 2 +- .../PartSegCore/algorithm_describe_base.py | 41 +- .../PartSegCore/analysis/analysis_utils.py | 3 +- .../batch_processing/batch_backend.py | 8 +- .../batch_processing/parallel_backend.py | 12 +- .../analysis/calculate_pipeline.py | 6 +- .../PartSegCore/analysis/calculation_plan.py | 12 +- package/PartSegCore/analysis/io_utils.py | 4 +- .../PartSegCore/analysis/load_functions.py | 24 +- .../PartSegCore/analysis/measurement_base.py | 32 +- .../analysis/measurement_calculation.py | 49 +- .../PartSegCore/analysis/save_functions.py | 4 +- package/PartSegCore/class_generator.py | 2 +- package/PartSegCore/custom_name_generate.py | 4 +- package/PartSegCore/image_operations.py | 5 +- .../image_transforming/combine_channels.py | 6 +- .../image_transforming/image_projection.py | 4 +- .../image_transforming/interpolate_image.py | 6 +- .../image_transforming/swap_time_stack.py | 4 +- .../image_transforming/transform_base.py | 6 +- package/PartSegCore/io_utils.py | 30 +- package/PartSegCore/mask/io_functions.py | 40 +- package/PartSegCore/mask_create.py | 12 +- package/PartSegCore/napari_plugins/loader.py | 6 +- .../napari_plugins/save_tiff_layer.py | 4 +- package/PartSegCore/project_info.py | 14 +- package/PartSegCore/register.py | 5 +- package/PartSegCore/roi_info.py | 10 +- .../segmentation/algorithm_base.py | 11 +- .../restartable_segmentation_algorithms.py | 6 +- package/PartSegCore/sphinx/auto_parameters.py | 4 +- package/PartSegCore/utils.py | 4 +- package/PartSegImage/channel_class.py | 4 +- package/PartSegImage/image.py | 4 +- package/PartSegImage/image_reader.py | 27 +- package/PartSegImage/image_writer.py | 4 +- package/tests/conftest.py | 4 +- .../test_PartSeg/test_channel_control.py | 100 ++-- .../tests/test_PartSeg/test_common_backend.py | 2 +- package/tests/test_PartSeg/test_common_gui.py | 8 +- .../test_segmentation_algorithm.py | 4 +- .../test_algorithm_describe_base.py | 12 +- .../test_PartSegCore/test_class_generator.py | 12 +- .../test_PartSegCore/test_class_register.py | 4 +- package/tests/test_PartSegCore/test_io.py | 5 +- .../test_PartSegCore/test_segmentation.py | 16 +- pyproject.toml | 39 +- requirements/constraints_py3.8.txt | 531 ------------------ requirements/constraints_py3.8_pydantic_1.txt | 526 ----------------- tox.ini | 23 +- 97 files changed, 532 insertions(+), 1599 deletions(-) delete mode 100644 requirements/constraints_py3.8.txt delete mode 100644 requirements/constraints_py3.8_pydantic_1.txt diff --git a/.github/workflows/test_napari_widgets.yml b/.github/workflows/test_napari_widgets.yml index c17390a5b..037975650 100644 --- a/.github/workflows/test_napari_widgets.yml +++ b/.github/workflows/test_napari_widgets.yml @@ -27,7 +27,7 @@ jobs: with: python_version: "3.10" os: ${{ matrix.os }} - napari: "napari5" + napari: "napari54" qt_backend: ${{ matrix.qt_backend }} timeout: 10 @@ -36,10 +36,10 @@ jobs: strategy: fail-fast: false matrix: - napari: ["napari417", "napari418"] + napari: ["napari419", "napari54"] qt_backend: ["PyQt5"] include: - - napari: "napari417" + - napari: "napari54" qt_backend: "PySide2" if: github.event_name == 'push' uses: ./.github/workflows/base_test_workflow.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8b4e0d381..ef82c0e20 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -50,24 +50,24 @@ jobs: strategy: fail-fast: false matrix: - python_version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python_version: ["3.9", "3.10", "3.11", "3.12"] os: ["ubuntu-20.04"] qt_backend: ["PyQt5"] tox_args: [ "" ] include: - - python_version: "3.9" + - python_version: "3.11" os: "macos-13" qt_backend: "PyQt5" - - python_version: "3.9" + - python_version: "3.11" os: "windows-2019" qt_backend: "PyQt5" - - python_version: "3.9" + - python_version: "3.10" os: "ubuntu-20.04" qt_backend: "PySide2" - - python_version: "3.9" + - python_version: "3.10" os: "ubuntu-22.04" qt_backend: "PySide6" - - python_version: "3.10" + - python_version: "3.12" os: "ubuntu-22.04" qt_backend: "PyQt6" - python_version: "3.10" @@ -90,20 +90,20 @@ jobs: strategy: fail-fast: false matrix: - python_version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python_version: ["3.9", "3.10", "3.11", "3.12"] os: ["ubuntu-20.04", "macos-13", "windows-2019"] qt_backend: ["PySide2", "PyQt5"] include: - - python_version: "3.11" + - python_version: "3.12" qt_backend: "PyQt5" os: "ubuntu-22.04" - - python_version: "3.9" + - python_version: "3.10" os: "ubuntu-22.04" qt_backend: "PySide6" - - python_version: "3.9" + - python_version: "3.11" os: "ubuntu-22.04" qt_backend: "PyQt6" - - python_version: "3.10" + - python_version: "3.11" os: "ubuntu-22.04" qt_backend: "PyQt5" pydantic: "_pydantic_1" @@ -124,8 +124,8 @@ jobs: uses: ./.github/workflows/base_test_workflow.yml with: test_data: True - python_version: "3.10" - tox_args: "-e py310-PyQt5-coverage" + python_version: "3.12" + tox_args: "-e py312-PyQt5-coverage" coverage: true test_minimal: @@ -134,8 +134,8 @@ jobs: uses: ./.github/workflows/base_test_workflow.yml with: test_data: True - python_version: "3.8" - tox_args: "-e py38-PyQt5-minimal" + python_version: "3.9" + tox_args: "-e py39-PyQt5-minimal" coverage: true coverage_prepare: diff --git a/.github/workflows/upgrade-dependencies.yml b/.github/workflows/upgrade-dependencies.yml index 1296648ae..d0e8b08a2 100644 --- a/.github/workflows/upgrade-dependencies.yml +++ b/.github/workflows/upgrade-dependencies.yml @@ -35,7 +35,7 @@ jobs: pip install -U uv flags=(--extra pyqt6 --extra pyside2 --extra pyside6 --extra test --extra pyinstaller_base) - for pyv in 3.8 3.9 3.10 3.11 3.12; do + for pyv in 3.9 3.10 3.11 3.12; do uv pip compile --python-version ${pyv} --upgrade --output-file requirements/constraints_py${pyv}.txt pyproject.toml requirements/version_denylist.txt "${flags[@]}" uv pip compile --python-version ${pyv} --upgrade --output-file requirements/constraints_py${pyv}_pydantic_1.txt pyproject.toml requirements/version_denylist.txt "${flags[@]}" --constraint requirements/pydantic_1.txt done diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b47780dcf..9e3df56b3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,7 +23,7 @@ stages: displayName: "download data" - task: UsePythonVersion@0 inputs: - versionSpec: '3.9' + versionSpec: '3.11' displayName: 'Use Python $(python.version)' - script: python build_utils/cut_changelog.py changelog_cut.md displayName: "Cut changelog" diff --git a/package/PartSeg/_launcher/check_version.py b/package/PartSeg/_launcher/check_version.py index 6eb576ca1..d4a39d4b7 100644 --- a/package/PartSeg/_launcher/check_version.py +++ b/package/PartSeg/_launcher/check_version.py @@ -40,8 +40,9 @@ def run(self): return try: if os.path.exists(os.path.join(state_store.save_folder, IGNORE_FILE)): - with open(os.path.join(state_store.save_folder, IGNORE_FILE), encoding="utf-8") as f_p, suppress( - ValueError + with ( + open(os.path.join(state_store.save_folder, IGNORE_FILE), encoding="utf-8") as f_p, + suppress(ValueError), ): old_date = date.fromisoformat(f_p.read()) if (date.today() - old_date).days < IGNORE_DAYS: diff --git a/package/PartSeg/_launcher/main_window.py b/package/PartSeg/_launcher/main_window.py index 71f6f7c30..11ebf76a1 100644 --- a/package/PartSeg/_launcher/main_window.py +++ b/package/PartSeg/_launcher/main_window.py @@ -2,7 +2,7 @@ import os import warnings from functools import partial -from typing import TYPE_CHECKING, Type +from typing import TYPE_CHECKING from qtpy.QtCore import QSize, Qt, QThread, Signal from qtpy.QtGui import QIcon @@ -34,7 +34,7 @@ def run(self): plugins.register() main_window_module = importlib.import_module(self.module) - main_window: Type[BaseMainWindow] = main_window_module.MainWindow + main_window: type[BaseMainWindow] = main_window_module.MainWindow settings: BaseSettings = main_window.get_setting_class()(main_window_module.CONFIG_FOLDER) self.errors = settings.load() reader = TiffImageReader() diff --git a/package/PartSeg/_roi_analysis/advanced_window.py b/package/PartSeg/_roi_analysis/advanced_window.py index 8acf41e84..4635ee597 100644 --- a/package/PartSeg/_roi_analysis/advanced_window.py +++ b/package/PartSeg/_roi_analysis/advanced_window.py @@ -2,7 +2,7 @@ import os from contextlib import suppress from copy import deepcopy -from typing import List, Optional, Tuple, Type, Union, cast +from typing import Optional, Union, cast from qtpy.QtCore import QEvent, Qt, Slot from qtpy.QtGui import QIcon @@ -49,7 +49,7 @@ from PartSegCore.universal_const import UNIT_SCALE, Units from PartSegData import icons_dir -_DialogType = Union[Type[str], Type[int], Type[float]] +_DialogType = Union[type[str], type[int], type[float]] def h_line(): @@ -366,7 +366,7 @@ class MeasurementSettings(QWidget): def __init__(self, settings: PartSettings, parent=None): # noqa: PLR0915 super().__init__(parent) self.chosen_element: Optional[MeasurementListWidgetItem] = None - self.chosen_element_area: Optional[Tuple[AreaType, float]] = None + self.chosen_element_area: Optional[tuple[AreaType, float]] = None self.settings = settings self.profile_list = QListWidget(self) self.profile_description = QTextEdit(self) @@ -841,7 +841,7 @@ def __init__( self, text: str, help_text: str = "", - objects_list: Optional[List[Union[Tuple[str, _DialogType], Tuple[str, _DialogType, str]]]] = None, + objects_list: Optional[list[Union[tuple[str, _DialogType], tuple[str, _DialogType, str]]]] = None, parent: Optional[QWidget] = None, ): if objects_list is None: # pragma: no cover diff --git a/package/PartSeg/_roi_analysis/batch_window.py b/package/PartSeg/_roi_analysis/batch_window.py index 97e6f92b3..7e14e2edb 100644 --- a/package/PartSeg/_roi_analysis/batch_window.py +++ b/package/PartSeg/_roi_analysis/batch_window.py @@ -74,7 +74,7 @@ def get_name(cls) -> str: return "Excel (*.xlsx)" @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return [] @@ -376,7 +376,7 @@ class CalculationPrepare(QDialog): def __init__( self, - file_list: typing.List[os.PathLike], + file_list: list[os.PathLike], calculation_plan: CalculationPlan, measurement_file_path: os.PathLike, settings: PartSettings, diff --git a/package/PartSeg/_roi_analysis/export_batch.py b/package/PartSeg/_roi_analysis/export_batch.py index fa3bd31be..96308e075 100644 --- a/package/PartSeg/_roi_analysis/export_batch.py +++ b/package/PartSeg/_roi_analysis/export_batch.py @@ -308,7 +308,7 @@ def _excel_path_changed(self): def _extract_information_from_excel_to_export( excel_path: typing.Union[str, Path], base_folder: typing.Union[str, Path] -) -> typing.List[typing.Tuple[str, bool]]: +) -> list[tuple[str, bool]]: """Extract information from Excel file to export""" file_list = [] file_set = set() diff --git a/package/PartSeg/_roi_analysis/main_window.py b/package/PartSeg/_roi_analysis/main_window.py index efc9a1932..9f18c8874 100644 --- a/package/PartSeg/_roi_analysis/main_window.py +++ b/package/PartSeg/_roi_analysis/main_window.py @@ -1,6 +1,5 @@ import os from contextlib import suppress -from typing import Type from qtpy.QtCore import QByteArray, Qt from qtpy.QtGui import QIcon, QKeyEvent, QKeySequence, QResizeEvent @@ -534,7 +533,7 @@ class MainWindow(BaseMainWindow): settings: PartSettings @classmethod - def get_setting_class(cls) -> Type[PartSettings]: + def get_setting_class(cls) -> type[PartSettings]: return PartSettings initial_image_path = PartSegData.segmentation_analysis_default_image diff --git a/package/PartSeg/_roi_analysis/measurement_widget.py b/package/PartSeg/_roi_analysis/measurement_widget.py index e32733301..d0371e61a 100644 --- a/package/PartSeg/_roi_analysis/measurement_widget.py +++ b/package/PartSeg/_roi_analysis/measurement_widget.py @@ -1,7 +1,6 @@ import locale import os from enum import Enum -from typing import List, Tuple from qtpy.QtCore import Qt from qtpy.QtGui import QKeyEvent, QResizeEvent @@ -56,7 +55,7 @@ def clear(self): self.header = [] self.max_rows = 0 self.content = [] - self.measurements: List[MeasurementResult] = [] + self.measurements: list[MeasurementResult] = [] def get_size(self, save_orientation: bool): if save_orientation: @@ -116,13 +115,13 @@ def get_val_as_str(self, x: int, y: int, save_orientation: bool) -> str: val = sublist[y] return locale.str(val) if isinstance(val, float) else str(val) - def get_header(self, save_orientation: bool) -> List[str]: + def get_header(self, save_orientation: bool) -> list[str]: if save_orientation: return [str(i) for i in range(self.max_rows)] return self.header - def get_rows(self, save_orientation: bool) -> List[str]: + def get_rows(self, save_orientation: bool) -> list[str]: return self.get_header(not save_orientation) @@ -315,7 +314,7 @@ def update_measurement_list(self): self.measurement_type.blockSignals(False) @staticmethod - def _move_widgets(widgets_list: List[Tuple[QWidget, int]], layout1: QBoxLayout, layout2: QBoxLayout): + def _move_widgets(widgets_list: list[tuple[QWidget, int]], layout1: QBoxLayout, layout2: QBoxLayout): for el in widgets_list: layout1.removeWidget(el[0]) layout2.addWidget(el[0], el[1]) diff --git a/package/PartSeg/_roi_analysis/partseg_settings.py b/package/PartSeg/_roi_analysis/partseg_settings.py index 59ac2f0a9..e6b8607fd 100644 --- a/package/PartSeg/_roi_analysis/partseg_settings.py +++ b/package/PartSeg/_roi_analysis/partseg_settings.py @@ -31,7 +31,7 @@ class PartSettings(BaseSettings): json_encoder_class = PartSegEncoder load_metadata = staticmethod(load_metadata) last_executed_algorithm: str - save_locations_keys: typing.ClassVar[typing.List[str]] = [ + save_locations_keys: typing.ClassVar[list[str]] = [ "open_directory", "save_directory", "export_directory", @@ -132,7 +132,7 @@ def set_project_info(self, data: typing.Union[ProjectTuple, MaskInfo, PointsInfo ) self.algorithm_changed.emit() - def get_save_list(self) -> typing.List[SaveSettingsDescription]: + def get_save_list(self) -> list[SaveSettingsDescription]: return [ *super().get_save_list(), SaveSettingsDescription("segmentation_pipeline_save.json", self._segmentation_pipelines_dict), @@ -142,27 +142,27 @@ def get_save_list(self) -> typing.List[SaveSettingsDescription]: ] @property - def segmentation_pipelines(self) -> typing.Dict[str, SegmentationPipeline]: + def segmentation_pipelines(self) -> dict[str, SegmentationPipeline]: warnings.warn("segmentation_pipelines is deprecated, use roi_pipelines", DeprecationWarning, stacklevel=2) return self.roi_pipelines @property - def roi_pipelines(self) -> typing.Dict[str, SegmentationPipeline]: + def roi_pipelines(self) -> dict[str, SegmentationPipeline]: return self._segmentation_pipelines_dict.get(self._current_roi_dict, EventedDict()) @property - def segmentation_profiles(self) -> typing.Dict[str, ROIExtractionProfile]: + def segmentation_profiles(self) -> dict[str, ROIExtractionProfile]: warnings.warn("segmentation_profiles is deprecated, use roi_profiles", DeprecationWarning, stacklevel=2) return self.roi_profiles @property - def roi_profiles(self) -> typing.Dict[str, ROIExtractionProfile]: + def roi_profiles(self) -> dict[str, ROIExtractionProfile]: return self._segmentation_profiles_dict.get(self._current_roi_dict, EventedDict()) @property - def batch_plans(self) -> typing.Dict[str, CalculationPlan]: + def batch_plans(self) -> dict[str, CalculationPlan]: return self._batch_plans_dict.get(self._current_roi_dict, EventedDict()) @property - def measurement_profiles(self) -> typing.Dict[str, MeasurementProfile]: + def measurement_profiles(self) -> dict[str, MeasurementProfile]: return self._measurement_profiles_dict.get(self._current_roi_dict, EventedDict()) diff --git a/package/PartSeg/_roi_analysis/prepare_plan_widget.py b/package/PartSeg/_roi_analysis/prepare_plan_widget.py index 5829520f0..d6f587947 100644 --- a/package/PartSeg/_roi_analysis/prepare_plan_widget.py +++ b/package/PartSeg/_roi_analysis/prepare_plan_widget.py @@ -294,9 +294,7 @@ def enable_protect(self): self.protect = previous @classmethod - def refresh_profiles( - cls, list_widget: typing.Union[QListWidget, SearchableListWidget], new_values: typing.List[str] - ): + def refresh_profiles(cls, list_widget: typing.Union[QListWidget, SearchableListWidget], new_values: list[str]): index = cls.get_index(list_widget.currentItem(), new_values) list_widget.clear() list_widget.addItems(new_values) @@ -304,7 +302,7 @@ def refresh_profiles( list_widget.setCurrentRow(index) @staticmethod - def get_index(item: QListWidgetItem, new_values: typing.List[str]) -> int: + def get_index(item: QListWidgetItem, new_values: list[str]) -> int: if item is None: return -1 text = item.text() @@ -319,7 +317,7 @@ class OtherOperations(ProtectedGroupBox): def __init__(self, parent=None): super().__init__("Other operations:", parent) - self.save_translate_dict: typing.Dict[str, SaveBase] = {x.get_short_name(): x for x in save_dict.values()} + self.save_translate_dict: dict[str, SaveBase] = {x.get_short_name(): x for x in save_dict.values()} self.save_constructor = None self.change_root = QEnumComboBox(self, enum_class=RootType) @@ -642,7 +640,7 @@ def __init__(self, settings: PartSettings, parent: typing.Optional[QWidget] = No self.add_mask_btn.setDisabled(True) - def update_mask_set(self, mask_set: typing.Set[str]): + def update_mask_set(self, mask_set: set[str]): self.mask_set = mask_set def set_replace(self, replace: bool): @@ -692,7 +690,7 @@ class CreatePlan(QWidget): def __init__(self, settings: PartSettings): super().__init__() self.settings = settings - self.save_translate_dict: typing.Dict[str, SaveBase] = {x.get_short_name(): x for x in save_dict.values()} + self.save_translate_dict: dict[str, SaveBase] = {x.get_short_name(): x for x in save_dict.values()} self._mask_set = set() self.plan = PlanPreview(self) self.save_plan_btn = QPushButton("Save") diff --git a/package/PartSeg/_roi_analysis/profile_export.py b/package/PartSeg/_roi_analysis/profile_export.py index b8feea93f..529d48fc2 100644 --- a/package/PartSeg/_roi_analysis/profile_export.py +++ b/package/PartSeg/_roi_analysis/profile_export.py @@ -153,10 +153,10 @@ def get_checked(self): class ImportDialog(QDialog): def __init__( self, - import_dict: typing.Dict[str, typing.Any], - local_dict: typing.Dict[str, typing.Any], - viewer: typing.Type[ObjectPreviewProtocol], - expected_type: typing.Optional[typing.Type] = None, + import_dict: dict[str, typing.Any], + local_dict: dict[str, typing.Any], + viewer: type[ObjectPreviewProtocol], + expected_type: typing.Optional[type] = None, parent: typing.Optional[QWidget] = None, ): """ diff --git a/package/PartSeg/_roi_mask/batch_proceed.py b/package/PartSeg/_roi_mask/batch_proceed.py index 26ed5db2e..786304efb 100644 --- a/package/PartSeg/_roi_mask/batch_proceed.py +++ b/package/PartSeg/_roi_mask/batch_proceed.py @@ -3,7 +3,7 @@ from functools import partial from pathlib import Path from queue import Queue -from typing import List, NamedTuple, Optional, Tuple, Union, cast +from typing import NamedTuple, Optional, Union, cast from pydantic import BaseModel from qtpy.QtCore import QThread, Signal @@ -19,7 +19,7 @@ class BatchTask(NamedTuple): data: Union[str, MaskProjectTuple] parameters: ROIExtractionProfile - save_prefix: Optional[Tuple[Union[str, Path], Union[dict, BaseModel]]] + save_prefix: Optional[tuple[Union[str, Path], Union[dict, BaseModel]]] class BatchProceed(QThread): @@ -40,7 +40,7 @@ def __init__(self): self.result_dir = "" self.save_parameters = {} - def add_task(self, task: Union[BatchTask, List[BatchTask]]): + def add_task(self, task: Union[BatchTask, list[BatchTask]]): if isinstance(task, list): for el in task: self.queue.put(el) diff --git a/package/PartSeg/_roi_mask/main_window.py b/package/PartSeg/_roi_mask/main_window.py index ceef5d1f3..389fa259f 100644 --- a/package/PartSeg/_roi_mask/main_window.py +++ b/package/PartSeg/_roi_mask/main_window.py @@ -1,7 +1,6 @@ import os from contextlib import suppress from functools import partial -from typing import Type import numpy as np from qtpy.QtCore import QByteArray, Qt, Signal, Slot @@ -888,7 +887,7 @@ class MainWindow(BaseMainWindow): settings: StackSettings @classmethod - def get_setting_class(cls) -> Type[StackSettings]: + def get_setting_class(cls) -> type[StackSettings]: return StackSettings initial_image_path = PartSegData.segmentation_mask_default_image diff --git a/package/PartSeg/_roi_mask/segmentation_info_dialog.py b/package/PartSeg/_roi_mask/segmentation_info_dialog.py index fb708953a..c70c4e599 100644 --- a/package/PartSeg/_roi_mask/segmentation_info_dialog.py +++ b/package/PartSeg/_roi_mask/segmentation_info_dialog.py @@ -1,4 +1,4 @@ -from typing import Callable, Dict, Optional +from typing import Callable, Optional from qtpy.QtCore import QEvent from qtpy.QtWidgets import QGridLayout, QLabel, QListWidget, QPlainTextEdit, QPushButton, QWidget @@ -45,7 +45,7 @@ def __init__(self, settings: StackSettings, set_parameters: Callable[[str, dict] self.setLayout(layout) self.setWindowTitle("Parameters preview") - def set_parameters_dict(self, val: Optional[Dict[int, ROIExtractionProfile]]): + def set_parameters_dict(self, val: Optional[dict[int, ROIExtractionProfile]]): self.parameters_dict = val def set_additional_text(self, text): diff --git a/package/PartSeg/_roi_mask/stack_settings.py b/package/PartSeg/_roi_mask/stack_settings.py index 0907daf69..8b5ac4787 100644 --- a/package/PartSeg/_roi_mask/stack_settings.py +++ b/package/PartSeg/_roi_mask/stack_settings.py @@ -22,7 +22,7 @@ class StackSettings(BaseSettings): load_metadata = staticmethod(load_metadata) components_change_list = Signal([int, list]) - save_locations_keys: typing.ClassVar[typing.List[str]] = [ + save_locations_keys: typing.ClassVar[list[str]] = [ "save_batch", "save_components_directory", "save_segmentation_directory", @@ -36,7 +36,7 @@ def __init__(self, json_path, profile_name="default"): super().__init__(json_path, profile_name) self.chosen_components_widget = None self.keep_chosen_components = False - self.components_parameters_dict: typing.Dict[int, ROIExtractionProfile] = {} + self.components_parameters_dict: dict[int, ROIExtractionProfile] = {} def set_segmentation_result(self, result: ROIExtractionResult): if ( @@ -86,7 +86,7 @@ def get_file_names_for_save_result(self, dir_path): return res - def chosen_components(self) -> typing.List[int]: + def chosen_components(self) -> list[int]: """ Needs instance of :py:class:`PartSeg.segmentation_mask.main_window.ChosenComponents` on variable Py:attr:`chosen_components_widget` (or something implementing its interface) @@ -202,17 +202,17 @@ def transform_state( cls, state: MaskProjectTuple, new_roi_info: ROIInfo, - new_roi_extraction_parameters: typing.Dict[int, typing.Optional[ROIExtractionProfile]], - list_of_components: typing.List[int], + new_roi_extraction_parameters: dict[int, typing.Optional[ROIExtractionProfile]], + list_of_components: list[int], save_chosen: bool = True, ) -> MaskProjectTuple: """ :param MaskProjectTuple state: state to be transformed :param ROIInfo new_roi_info: roi description - :param typing.Dict[int, typing.Optional[ROIExtractionProfile]] new_roi_extraction_parameters: + :param dict[int, typing.Optional[ROIExtractionProfile]] new_roi_extraction_parameters: Parameters used to extract roi - :param typing.List[int] list_of_components: list of components from new_roi which should be selected + :param list[int] list_of_components: list of components from new_roi which should be selected :param bool save_chosen: if save currently selected components :return: new state """ @@ -280,7 +280,7 @@ def transform_state( roi_extraction_parameters=components_parameters_dict, ) - def compare_history(self, history: typing.List[HistoryElement]): + def compare_history(self, history: list[HistoryElement]): # TODO check dict comparison if len(history) != self.history_size(): return False @@ -317,7 +317,7 @@ def _set_roi_info( def get_mask( - segmentation: typing.Optional[np.ndarray], mask: typing.Optional[np.ndarray], selected: typing.List[int] + segmentation: typing.Optional[np.ndarray], mask: typing.Optional[np.ndarray], selected: list[int] ) -> np.ndarray: """ Calculate mask base on segmentation, current mask and list of chosen components. diff --git a/package/PartSeg/common_backend/base_argparser.py b/package/PartSeg/common_backend/base_argparser.py index 311d1941d..2c903f80a 100644 --- a/package/PartSeg/common_backend/base_argparser.py +++ b/package/PartSeg/common_backend/base_argparser.py @@ -5,9 +5,10 @@ import platform import sys import zlib +from collections.abc import Sequence from contextlib import suppress from importlib.metadata import version as package_version -from typing import Optional, Sequence +from typing import Optional import sentry_sdk import sentry_sdk.serializer diff --git a/package/PartSeg/common_backend/base_settings.py b/package/PartSeg/common_backend/base_settings.py index d0f2a866a..442068198 100644 --- a/package/PartSeg/common_backend/base_settings.py +++ b/package/PartSeg/common_backend/base_settings.py @@ -6,10 +6,11 @@ import sys import warnings from argparse import Namespace +from collections.abc import Sequence from contextlib import suppress from datetime import datetime from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, NamedTuple, Optional, Sequence, Tuple, Union +from typing import TYPE_CHECKING, Any, Callable, ClassVar, NamedTuple, Optional, Union import napari.utils.theme import numpy as np @@ -93,7 +94,7 @@ def noise_remove_image_part(self, val): # pragma: no cover # pylint: disable=no raise AttributeError("noise_remove_image_part not supported") @property - def additional_layers(self) -> Dict[str, AdditionalLayerDescription]: + def additional_layers(self) -> dict[str, AdditionalLayerDescription]: return self._additional_layers @additional_layers.setter @@ -262,7 +263,7 @@ def __init__(self): self.view_settings_dict = ProfileDict() self.colormap_dict = ColormapDict(self.get_from_profile("custom_colormap", {})) self.label_color_dict = LabelColorDict(self.get_from_profile("custom_label_colors", {})) - self.cached_labels: Optional[Tuple[str, np.ndarray]] = None + self.cached_labels: Optional[tuple[str, np.ndarray]] = None @property def theme_name(self) -> str: @@ -453,7 +454,7 @@ class BaseSettings(ViewSettings): load_metadata = staticmethod(load_metadata_base) algorithm_changed = Signal() """:py:class:`~.Signal` emitted when current algorithm should be changed""" - save_locations_keys: ClassVar[List[str]] = [] + save_locations_keys: ClassVar[list[str]] = [] def __init__(self, json_path: Union[Path, str], profile_name: str = "default"): """ @@ -468,7 +469,7 @@ def __init__(self, json_path: Union[Path, str], profile_name: str = "default"): self._last_algorithm_dict = ProfileDict() self.json_folder_path = json_path self.last_executed_algorithm = "" - self.history: List[HistoryElement] = [] + self.history: list[HistoryElement] = [] self.history_index = -1 self.last_executed_algorithm = "" self._points = None @@ -529,7 +530,7 @@ def set_segmentation_result(self, result: ROIExtractionResult): self._roi_info = roi_info self.roi_changed.emit(self._roi_info) - def _load_files_call(self, files_list: List[str]): + def _load_files_call(self, files_list: list[str]): self.request_load_files.emit(files_list) def add_history_element(self, elem: HistoryElement) -> None: @@ -563,11 +564,11 @@ def history_pop(self) -> Optional[HistoryElement]: return self.history[self.history_index + 1] return None - def set_history(self, history: List[HistoryElement]): + def set_history(self, history: list[HistoryElement]): self.history = history self.history_index = len(self.history) - 1 - def get_history(self) -> List[HistoryElement]: + def get_history(self) -> list[HistoryElement]: return self.history[: self.history_index + 1] @staticmethod @@ -586,7 +587,7 @@ def mask(self, value): except ValueError as e: raise ValueError("mask do not fit to image") from e - def get_save_list(self) -> List[SaveSettingsDescription]: + def get_save_list(self) -> list[SaveSettingsDescription]: """List of files in which program save the state.""" return [ SaveSettingsDescription("segmentation_settings.json", self._roi_dict), @@ -594,7 +595,7 @@ def get_save_list(self) -> List[SaveSettingsDescription]: SaveSettingsDescription("algorithm_settings.json", self._last_algorithm_dict), ] - def get_path_history(self) -> List[str]: + def get_path_history(self) -> list[str]: """ return list containing last 10 elements added with :py:meth:`.add_path_history` and last opened in each category form :py:attr:`save_location_keys` @@ -614,7 +615,7 @@ def _add_elem_to_list(data_list: list, value: Any, keep_len=10) -> list: data_list = data_list[: keep_len - 1] return [value, *data_list] - def get_last_files(self) -> List[Tuple[Tuple[Union[str, Path], ...], str]]: + def get_last_files(self) -> list[tuple[tuple[Union[str, Path], ...], str]]: return self.get(FILE_HISTORY, []) def add_load_files_history(self, file_path: Sequence[Union[str, Path]], load_method: str): # pragma: no cover @@ -626,10 +627,10 @@ def add_last_files(self, file_path: Sequence[Union[str, Path]], load_method: str # keep list of files as list because json serialize tuple to list self.add_path_history(os.path.dirname(file_path[0])) - def get_last_files_multiple(self) -> List[Tuple[Tuple[Union[str, Path], ...], str]]: + def get_last_files_multiple(self) -> list[tuple[tuple[Union[str, Path], ...], str]]: return self.get(MULTIPLE_FILES_OPEN_HISTORY, []) - def add_last_files_multiple(self, file_paths: List[Union[str, Path]], load_method: str): + def add_last_files_multiple(self, file_paths: list[Union[str, Path]], load_method: str): self.set( MULTIPLE_FILES_OPEN_HISTORY, self._add_elem_to_list( @@ -731,7 +732,7 @@ def dump(self, folder_path: Union[Path, str, None] = None): logger.error(errors_list) return errors_list - def _load_settings_file(self, file_path: Union[Path, str]) -> Tuple[ProfileDict, Any]: + def _load_settings_file(self, file_path: Union[Path, str]) -> tuple[ProfileDict, Any]: error = None data: ProfileDict = self.load_metadata(file_path) if isinstance(data, dict) and "__error__" in data: diff --git a/package/PartSeg/common_backend/partially_const_dict.py b/package/PartSeg/common_backend/partially_const_dict.py index 714552679..73b5f1f13 100644 --- a/package/PartSeg/common_backend/partially_const_dict.py +++ b/package/PartSeg/common_backend/partially_const_dict.py @@ -1,13 +1,13 @@ import itertools -from collections.abc import MutableMapping -from typing import Any, ClassVar, Dict, Generic, Iterator, Tuple, TypeVar, Union +from collections.abc import Iterator, MutableMapping +from typing import Any, ClassVar, Generic, TypeVar, Union from qtpy.QtCore import QObject, Signal from PartSeg.common_backend.abstract_class import QtMeta T = TypeVar("T") -RemovableInfo = Tuple[T, bool] +RemovableInfo = tuple[T, bool] class PartiallyConstDict(QObject, MutableMapping, Generic[T], metaclass=QtMeta): @@ -19,7 +19,7 @@ class PartiallyConstDict(QObject, MutableMapping, Generic[T], metaclass=QtMeta): """Signal with item added to dict""" item_removed = Signal(object) """Signal with item remove from dict""" - const_item_dict: ClassVar[Dict[str, Any]] = {} + const_item_dict: ClassVar[dict[str, Any]] = {} """Dict with non removable elements""" def __init__(self, editable_items): diff --git a/package/PartSeg/common_backend/python_syntax_highlight.py b/package/PartSeg/common_backend/python_syntax_highlight.py index 81f6744b8..3f14e5217 100644 --- a/package/PartSeg/common_backend/python_syntax_highlight.py +++ b/package/PartSeg/common_backend/python_syntax_highlight.py @@ -17,7 +17,7 @@ def get_text_char_format(style): """ text_char_format = QtGui.QTextCharFormat() - text_char_format.setFontFamily("monospace") + text_char_format.setFontFamilies(["monospace"]) if style.get("color"): text_char_format.setForeground(QtGui.QColor(f"#{style['color']}")) diff --git a/package/PartSeg/common_gui/advanced_tabs.py b/package/PartSeg/common_gui/advanced_tabs.py index 2556cf15e..5304fb032 100644 --- a/package/PartSeg/common_gui/advanced_tabs.py +++ b/package/PartSeg/common_gui/advanced_tabs.py @@ -6,7 +6,6 @@ import importlib import logging from contextlib import suppress -from typing import List from qtpy.QtCore import QByteArray, Qt from qtpy.QtGui import QCloseEvent @@ -201,7 +200,7 @@ class ColorControl(QTabWidget): Class for storage all settings for labels and colormaps. """ - def __init__(self, settings: BaseSettings, image_view_names: List[str]): + def __init__(self, settings: BaseSettings, image_view_names: list[str]): super().__init__() self.appearance = Appearance(settings) self.colormap_editor = PColormapCreator(settings) @@ -237,7 +236,7 @@ class AdvancedWindow(QTabWidget): :param image_view_names: passed as second argument to :py:class:`~.PColormapList` """ - def __init__(self, settings: BaseSettings, image_view_names: List[str], reload_list=None, parent=None): + def __init__(self, settings: BaseSettings, image_view_names: list[str], reload_list=None, parent=None): super().__init__(parent) self.color_control = ColorControl(settings, image_view_names) self.settings = settings diff --git a/package/PartSeg/common_gui/algorithms_description.py b/package/PartSeg/common_gui/algorithms_description.py index d93b3f81d..93ce3ee60 100644 --- a/package/PartSeg/common_gui/algorithms_description.py +++ b/package/PartSeg/common_gui/algorithms_description.py @@ -282,7 +282,7 @@ def get_change_signal(widget: typing.Union[QWidget, Widget]): # noqa: PLR0911 @staticmethod def get_getter_and_setter_function( # noqa: PLR0911 widget: typing.Union[QWidget, Widget], - ) -> typing.Tuple[ + ) -> tuple[ typing.Callable[ [typing.Union[QWidget, Widget]], typing.Any, @@ -320,7 +320,7 @@ def get_getter_and_setter_function( # noqa: PLR0911 class FieldsList(QObject): changed = Signal() - def __init__(self, field_list: typing.List[QtAlgorithmProperty]): + def __init__(self, field_list: list[QtAlgorithmProperty]): super().__init__() self.field_list = field_list for el in field_list: @@ -378,9 +378,7 @@ def _any(): return _any -FieldAllowedTypes = typing.Union[ - typing.List[AlgorithmProperty], typing.Type[BaseModel], typing.Type[AlgorithmDescribeBase] -] +FieldAllowedTypes = typing.Union[list[AlgorithmProperty], type[BaseModel], type[AlgorithmDescribeBase]] class FormWidget(QWidget): @@ -397,8 +395,8 @@ def __init__( super().__init__(parent=parent) if start_values is None: start_values = {} - self.widgets_dict: typing.Dict[str, QtAlgorithmProperty] = {} - self.channels_chose: typing.List[typing.Union[ChannelComboBox, SubAlgorithmWidget]] = [] + self.widgets_dict: dict[str, QtAlgorithmProperty] = {} + self.channels_chose: list[typing.Union[ChannelComboBox, SubAlgorithmWidget]] = [] layout = QFormLayout() layout.setContentsMargins(10, 0, 10, 0) self._model_class = None @@ -510,7 +508,7 @@ def __init__(self, algorithm_property: AlgorithmProperty): ) self.starting_values = {} self.property = algorithm_property - self.widgets_dict: typing.Dict[str, FormWidget] = {} + self.widgets_dict: dict[str, FormWidget] = {} # TODO protect for recursion widget = self._get_form_widget(algorithm_property) widget.value_changed.connect(self.values_changed) @@ -561,7 +559,7 @@ def set_values(self, val: typing.Mapping): def recursive_get_values(self): return {name: el.recursive_get_values() for name, el in self.widgets_dict.items()} - def get_values(self) -> typing.Dict[str, typing.Any]: + def get_values(self) -> dict[str, typing.Any]: name = self.choose.currentText() values = self.widgets_dict[name].get_values() return {"name": name, "values": values} @@ -613,7 +611,7 @@ class BaseAlgorithmSettingsWidget(QScrollArea): values_changed = Signal() algorithm_thread: SegmentationThread - def __init__(self, settings: BaseSettings, algorithm: typing.Type[ROIExtractionAlgorithm], parent=None): + def __init__(self, settings: BaseSettings, algorithm: type[ROIExtractionAlgorithm], parent=None): """ For algorithm which works on one channel """ @@ -719,9 +717,7 @@ def recursive_get_values(self): class InteractiveAlgorithmSettingsWidget(BaseAlgorithmSettingsWidget): algorithm_thread: SegmentationThread - def __init__( - self, settings, algorithm: typing.Type[ROIExtractionAlgorithm], selector: typing.List[QWidget], parent=None - ): + def __init__(self, settings, algorithm: type[ROIExtractionAlgorithm], selector: list[QWidget], parent=None): super().__init__(settings, algorithm, parent=parent) self.selector = selector[:] self.algorithm_thread.finished.connect(self.enable_selector) @@ -755,16 +751,16 @@ class AlgorithmChooseBase(QWidget): progress_signal = Signal(str, int) algorithm_changed = Signal(str) - algorithm_dict: typing.Dict[str, InteractiveAlgorithmSettingsWidget] + algorithm_dict: dict[str, InteractiveAlgorithmSettingsWidget] - def __init__(self, settings: BaseSettings, algorithms: typing.Type[AlgorithmSelection], parent=None): + def __init__(self, settings: BaseSettings, algorithms: type[AlgorithmSelection], parent=None): super().__init__(parent=parent) self.settings = settings self.algorithms = algorithms settings.algorithm_changed.connect(self.updated_algorithm) self.stack_layout = QStackedLayout() self.algorithm_choose = QComboBox() - self.algorithm_dict: typing.Dict[str, BaseAlgorithmSettingsWidget] = {} + self.algorithm_dict: dict[str, BaseAlgorithmSettingsWidget] = {} self.algorithm_choose.currentTextChanged.connect(self.change_algorithm) self.add_widgets_to_algorithm() @@ -856,7 +852,7 @@ def get_info_text(self): class AlgorithmChoose(AlgorithmChooseBase): - def __init__(self, settings: BaseSettings, algorithms: typing.Type[AlgorithmSelection], parent=None): + def __init__(self, settings: BaseSettings, algorithms: type[AlgorithmSelection], parent=None): super().__init__(settings, algorithms, parent) self.settings.image_changed.connect(self.image_changed) diff --git a/package/PartSeg/common_gui/channel_control.py b/package/PartSeg/common_gui/channel_control.py index c45b84829..ea7129b03 100644 --- a/package/PartSeg/common_gui/channel_control.py +++ b/package/PartSeg/common_gui/channel_control.py @@ -30,7 +30,7 @@ image_dict = {} # dict to store QImages generated from colormap -ColorMapDict = typing.MutableMapping[str, typing.Tuple[Colormap, bool]] +ColorMapDict = typing.MutableMapping[str, tuple[Colormap, bool]] try: from qtpy import PYQT6 @@ -108,7 +108,7 @@ class ColorComboBox(QComboBox): def __init__( self, id_num: int, - colors: typing.List[str], + colors: list[str], color_dict: ColorMapDict, colormap: str = "", base_height=50, @@ -154,7 +154,7 @@ def __init__( self.currentTextChanged.connect(partial(self.channel_colormap_changed.emit, self.id)) self._update_image() - def change_colors(self, colors: typing.List[str]): + def change_colors(self, colors: list[str]): """change list of colormaps to choose""" self.colors = colors current_color = self.currentText() @@ -299,7 +299,7 @@ def __init__(self, settings: ViewSettings, start_name: str): self.current_name = start_name self.current_channel = 0 self._settings = settings - self.widget_dict: typing.Dict[str, ColorComboBoxGroup] = {} + self.widget_dict: dict[str, ColorComboBoxGroup] = {} self.minimum_value = CustomSpinBox(self) self.minimum_value.setRange(-(10**6), 10**6) @@ -507,7 +507,7 @@ def update_colors(self): el: ColorComboBox = self.layout().itemAt(i).widget() el.setCurrentText(self.settings.get_channel_colormap_name(self.viewer_name, i)) - def update_color_list(self, colors: typing.Optional[typing.List[str]] = None): + def update_color_list(self, colors: typing.Optional[list[str]] = None): """Update list of available colormaps in each selector""" if colors is None: colors = self.settings.chosen_colormap @@ -524,7 +524,7 @@ def channels_count(self): return self.layout().count() @property - def selected_colormaps(self) -> typing.List[Colormap]: + def selected_colormaps(self) -> list[Colormap]: """For each channel give information about selected colormap by name""" resp = [] for i in range(self.layout().count()): @@ -533,7 +533,7 @@ def selected_colormaps(self) -> typing.List[Colormap]: return resp @property - def channel_visibility(self) -> typing.List[bool]: + def channel_visibility(self) -> list[bool]: resp = [] for i in range(self.layout().count()): el: ColorComboBox = self.layout().itemAt(i).widget() @@ -541,7 +541,7 @@ def channel_visibility(self) -> typing.List[bool]: return resp @property - def current_colors(self) -> typing.List[typing.Optional[str]]: + def current_colors(self) -> list[typing.Optional[str]]: """List of current colors. None if channel is not selected.""" resp = [] for i in range(self.layout().count()): @@ -553,7 +553,7 @@ def current_colors(self) -> typing.List[typing.Optional[str]]: return resp @property - def current_colormaps(self) -> typing.List[typing.Optional[Colormap]]: + def current_colormaps(self) -> list[typing.Optional[Colormap]]: """List of current colormaps. None if channel is not selected""" resp = [] for i in range(self.layout().count()): @@ -615,7 +615,7 @@ def set_active(self, pos: int): self.change_channel.emit(self.viewer_name, pos) self.repaint() - def get_filter(self) -> typing.List[typing.Tuple[NoiseFilterType, float]]: + def get_filter(self) -> list[tuple[NoiseFilterType, float]]: return [ ( self.settings.get_from_profile(f"{self.viewer_name}.use_filter_{i}", NoiseFilterType.No), @@ -624,8 +624,8 @@ def get_filter(self) -> typing.List[typing.Tuple[NoiseFilterType, float]]: for i in range(self.layout().count()) ] - def get_limits(self) -> typing.List[typing.Union[typing.Tuple[int, int], None]]: - resp: typing.List[typing.Union[typing.Tuple[int, int], None]] = [(0, 0)] * self.layout().count() + def get_limits(self) -> list[typing.Union[tuple[int, int], None]]: + resp: list[typing.Union[tuple[int, int], None]] = [(0, 0)] * self.layout().count() for i in range(self.layout().count()): resp[i] = ( self.settings.get_from_profile(f"{self.viewer_name}.range_{i}", (0, 65000)) @@ -634,7 +634,7 @@ def get_limits(self) -> typing.List[typing.Union[typing.Tuple[int, int], None]]: ) return [x if x is None or x[0] < x[1] else None for x in resp] - def get_gamma(self) -> typing.List[float]: + def get_gamma(self) -> list[float]: return [ self.settings.get_from_profile(f"{self.viewer_name}.gamma_value_{i}", 1) for i in range(self.layout().count()) diff --git a/package/PartSeg/common_gui/colormap_creator.py b/package/PartSeg/common_gui/colormap_creator.py index 9a5687f35..ddba66b42 100644 --- a/package/PartSeg/common_gui/colormap_creator.py +++ b/package/PartSeg/common_gui/colormap_creator.py @@ -1,12 +1,13 @@ import bisect import json import typing +from collections.abc import Iterable from contextlib import suppress from functools import partial from io import BytesIO from math import ceil from pathlib import Path -from typing import Dict, Iterable, List, Optional, Set, Tuple +from typing import Optional import local_migrator import numpy as np @@ -71,8 +72,8 @@ class ColormapEdit(QWidget): def __init__(self): super().__init__() - self.color_list: List[Color] = [] - self.position_list: List[float] = [] + self.color_list: list[Color] = [] + self.position_list: list[float] = [] self.move_ind = None self.image = convert_colormap_to_image(Colormap([(0, 0, 0)])) self.setMinimumHeight(60) @@ -487,14 +488,14 @@ class ColormapList(QWidget): """Hide or show colormap""" def __init__( - self, colormap_map: Dict[str, Tuple[Colormap, bool]], selected: Optional[Iterable[str]] = None, parent=None + self, colormap_map: dict[str, tuple[Colormap, bool]], selected: Optional[Iterable[str]] = None, parent=None ): super().__init__(parent=parent) self._selected = set() if selected is None else set(selected) self._blocked = set() self.current_columns = 1 self.colormap_map = colormap_map - self._widget_dict: Dict[str, ChannelPreview] = {} + self._widget_dict: dict[str, ChannelPreview] = {} self.scroll_area = QScrollArea() self.central_widget = QWidget(self) layout2 = QVBoxLayout() @@ -515,7 +516,7 @@ def __init__( def showEvent(self, event: QShowEvent): self.refresh() - def get_selected(self) -> Set[str]: + def get_selected(self) -> set[str]: """Already selected colormaps""" return self._selected @@ -526,7 +527,7 @@ def change_selection(self, name, selected): self._selected.remove(name) self.visibility_colormap_change.emit(name, selected) - def blocked(self) -> Set[str]: + def blocked(self) -> set[str]: """Channels that cannot be turn of and remove""" return self._blocked @@ -540,7 +541,7 @@ def resizeEvent(self, event: QResizeEvent): def refresh(self): layout: QGridLayout = self.grid_layout - cache_dict: Dict[str, ChannelPreview] = {} + cache_dict: dict[str, ChannelPreview] = {} self._widget_dict = {} for _ in range(layout.count()): el: ChannelPreview = layout.takeAt(0).widget() @@ -616,7 +617,7 @@ class PColormapList(ColormapList): for protect used channels from uncheck or remove """ - def __init__(self, settings: ViewSettings, control_names: List[str]): + def __init__(self, settings: ViewSettings, control_names: list[str]): super().__init__(settings.colormap_dict) settings.colormap_dict.colormap_removed.connect(self.refresh) settings.colormap_dict.colormap_added.connect(self.refresh) @@ -624,14 +625,14 @@ def __init__(self, settings: ViewSettings, control_names: List[str]): self.settings = settings self.color_names = control_names - def get_selected(self) -> Set[str]: + def get_selected(self) -> set[str]: return set(self.settings.chosen_colormap) def change_selection(self, name, selected): self.visibility_colormap_change.emit(name, selected) self.settings.chosen_colormap_change(name, selected) - def blocked(self) -> Set[str]: + def blocked(self) -> set[str]: # TODO check only currently presented channels blocked = set() for el in self.color_names: @@ -684,7 +685,7 @@ def get_short_name(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, diff --git a/package/PartSeg/common_gui/custom_load_dialog.py b/package/PartSeg/common_gui/custom_load_dialog.py index c20dbd1f7..58a8eedd9 100644 --- a/package/PartSeg/common_gui/custom_load_dialog.py +++ b/package/PartSeg/common_gui/custom_load_dialog.py @@ -11,12 +11,12 @@ class LoadProperty(typing.NamedTuple): - load_location: typing.List[typing.Union[str, Path]] + load_location: list[typing.Union[str, Path]] selected_filter: str - load_class: typing.Type[LoadBase] + load_class: type[LoadBase] -IORegister = typing.Union[typing.Dict[str, type(LoadBase)], type(LoadBase), str, typing.List[type(LoadBase)]] +IORegister = typing.Union[dict[str, type(LoadBase)], type(LoadBase), str, list[type(LoadBase)]] class IOMethodMock: @@ -83,7 +83,7 @@ def __init__( load_register: IORegister, parent=None, caption="Load file", - history: typing.Optional[typing.List[str]] = None, + history: typing.Optional[list[str]] = None, ): super().__init__(load_register, caption, parent) self.setOption(QFileDialog.Option.DontUseNativeDialog, True) @@ -116,14 +116,14 @@ def accept(self): super().accept() def get_result(self) -> LoadProperty: - chosen_class: typing.Type[LoadBase] = self.io_register[self.selectedNameFilter()] + chosen_class: type[LoadBase] = self.io_register[self.selectedNameFilter()] return LoadProperty(self.files_list, self.selectedNameFilter(), chosen_class) class PLoadDialog(CustomLoadDialog): def __init__( self, - load_register: typing.Union[typing.Dict[str, type(LoadBase)], type(LoadBase)], + load_register: typing.Union[dict[str, type(LoadBase)], type(LoadBase)], *, settings: "BaseSettings", path: str, @@ -162,7 +162,7 @@ class SelectDirectoryDialog(QFileDialog): def __init__( self, settings: "BaseSettings", - settings_path: typing.Union[str, typing.List[str]], + settings_path: typing.Union[str, list[str]], default_directory: typing.Optional[str] = None, parent=None, ) -> None: diff --git a/package/PartSeg/common_gui/custom_save_dialog.py b/package/PartSeg/common_gui/custom_save_dialog.py index ab443d3f4..21a929e8f 100644 --- a/package/PartSeg/common_gui/custom_save_dialog.py +++ b/package/PartSeg/common_gui/custom_save_dialog.py @@ -15,7 +15,7 @@ class SaveProperty(typing.NamedTuple): - save_destination: typing.Union[str, typing.List[str]] + save_destination: typing.Union[str, list[str]] selected_filter: str save_class: SaveBase parameters: dict @@ -23,7 +23,7 @@ class SaveProperty(typing.NamedTuple): class FormDialog(QDialog): @staticmethod - def widget_class() -> typing.Type[FormWidget]: + def widget_class() -> type[FormWidget]: return FormWidget def __init__(self, fields, values=None, image=None, settings=None, parent=None): @@ -58,7 +58,7 @@ def __init__( base_values: typing.Optional[dict] = None, parent=None, caption="Save file", - history: typing.Optional[typing.List[str]] = None, + history: typing.Optional[list[str]] = None, file_mode=QFileDialog.FileMode.AnyFile, ): super().__init__(save_register, caption, parent) diff --git a/package/PartSeg/common_gui/equal_column_layout.py b/package/PartSeg/common_gui/equal_column_layout.py index a0a50022f..aeb82e83b 100644 --- a/package/PartSeg/common_gui/equal_column_layout.py +++ b/package/PartSeg/common_gui/equal_column_layout.py @@ -1,5 +1,4 @@ from enum import Enum -from typing import List from qtpy.QtCore import QRect, QSize from qtpy.QtWidgets import QLayout, QLayoutItem @@ -13,7 +12,7 @@ class LayoutPosition(Enum): class EqualColumnLayout(QLayout): def __init__(self, parent=None): super().__init__(parent) - self._item_list: List[QLayoutItem] = [] + self._item_list: list[QLayoutItem] = [] def addItem(self, item: QLayoutItem): self._item_list.append(item) diff --git a/package/PartSeg/common_gui/error_report.py b/package/PartSeg/common_gui/error_report.py index 0683fba3f..c8c407523 100644 --- a/package/PartSeg/common_gui/error_report.py +++ b/package/PartSeg/common_gui/error_report.py @@ -258,7 +258,7 @@ class ExceptionListItem(QListWidgetItem): # TODO Prevent from reporting disc error def __init__( self, - exception: typing.Union[Exception, typing.Tuple[Exception, typing.List]], + exception: typing.Union[Exception, tuple[Exception, list]], parent: typing.Optional[QListWidget] = None, ): if isinstance(exception, Exception): @@ -299,7 +299,7 @@ def item_double_clicked(el: QListWidgetItem): class DataImportErrorDialog(QDialog): def __init__( self, - errors: typing.Dict[str, typing.Union[Exception, typing.List[typing.Tuple[str, dict]]]], + errors: dict[str, typing.Union[Exception, list[tuple[str, dict]]]], parent: typing.Optional[QWidget] = None, text: str = "During import data part of the entries was filtered out", ): diff --git a/package/PartSeg/common_gui/image_adjustment.py b/package/PartSeg/common_gui/image_adjustment.py index 3c6253808..1798126ff 100644 --- a/package/PartSeg/common_gui/image_adjustment.py +++ b/package/PartSeg/common_gui/image_adjustment.py @@ -1,4 +1,4 @@ -from typing import Dict, NamedTuple, Optional +from typing import NamedTuple, Optional from qtpy.QtWidgets import QComboBox, QDialog, QGridLayout, QPushButton, QStackedWidget @@ -13,7 +13,7 @@ class ImageAdjustTuple(NamedTuple): class ImageAdjustmentDialog(QDialog): - def __init__(self, image: Image, transform_dict: Optional[Dict[str, TransformBase]] = None): + def __init__(self, image: Image, transform_dict: Optional[dict[str, TransformBase]] = None): super().__init__() if transform_dict is None: transform_dict = image_transform_dict diff --git a/package/PartSeg/common_gui/label_create.py b/package/PartSeg/common_gui/label_create.py index cc226eacf..7fb498e4e 100644 --- a/package/PartSeg/common_gui/label_create.py +++ b/package/PartSeg/common_gui/label_create.py @@ -4,10 +4,11 @@ import json import typing +from collections.abc import Sequence from copy import deepcopy from io import BytesIO from pathlib import Path -from typing import Any, Callable, List, Optional, Sequence, Union +from typing import Any, Callable, Optional, Union import numpy as np from fonticon_fa6 import FA6S @@ -75,7 +76,7 @@ class LabelShow(QWidget): edit_labels_with_name = Signal(str, list) selected = Signal(str) - def __init__(self, name: str, label: List[Sequence[float]], removable, parent=None): + def __init__(self, name: str, label: list[Sequence[float]], removable, parent=None): super().__init__(parent) self.label = label self.name = name @@ -180,7 +181,7 @@ def refresh(self): self.layout().addWidget(label) self.layout().addStretch(1) - def _label_show(self, name: str, label: List[Sequence[float]], removable) -> LabelShow: + def _label_show(self, name: str, label: list[Sequence[float]], removable) -> LabelShow: return LabelShow(name, label, removable, self) def showEvent(self, _): @@ -288,7 +289,7 @@ def add_color(self): color = self.color_picker.currentColor() self.color_layout.addWidget(ColorShow([color.red(), color.green(), color.blue()], self)) - def get_colors(self) -> List[List[int]]: + def get_colors(self) -> list[list[int]]: count = self.color_layout.count() return [self.color_layout.itemAt(i).widget().color for i in range(count)] @@ -329,11 +330,11 @@ def get_name(cls) -> str: @classmethod def load( cls, - load_locations: List[Union[str, BytesIO, Path]], + load_locations: list[Union[str, BytesIO, Path]], range_changed: Optional[Callable[[int, int], Any]] = None, step_changed: Optional[Callable[[int], Any]] = None, metadata: Optional[dict] = None, - ) -> List[List[float]]: + ) -> list[list[float]]: with open(load_locations[0]) as f_p: return json.load(f_p) diff --git a/package/PartSeg/common_gui/main_window.py b/package/PartSeg/common_gui/main_window.py index 75bef3744..f416048b7 100644 --- a/package/PartSeg/common_gui/main_window.py +++ b/package/PartSeg/common_gui/main_window.py @@ -2,7 +2,7 @@ import os import sys from pathlib import Path -from typing import List, Optional, Type, Union +from typing import Optional, Union from napari import __version__ from packaging.version import parse as parse_version @@ -130,7 +130,7 @@ class BaseMainWindow(QMainWindow): """Signal emitted when window has shown. Used to hide Launcher.""" @classmethod - def get_setting_class(cls) -> Type[BaseSettings]: + def get_setting_class(cls) -> type[BaseSettings]: """Get constructor for :py:attr:`settings`""" return BaseSettings @@ -161,7 +161,7 @@ def __init__( self.show_signal.connect(signal_fun) self.settings = settings self._load_dict = load_dict - self.viewer_list: List[Viewer] = [] + self.viewer_list: list[Viewer] = [] self.files_num = 1 self.setAcceptDrops(True) self.setWindowTitle(title) @@ -219,7 +219,7 @@ def _load_recent(self): def toggle_multiple_files(self): self.settings.set("multiple_files_widget", not self.settings.get("multiple_files_widget", False)) - def get_colormaps(self) -> List[Optional[colormap.Colormap]]: + def get_colormaps(self) -> list[Optional[colormap.Colormap]]: channel_num = self.settings.image.channels if not self.channel_info: return [None for _ in range(channel_num)] @@ -264,7 +264,7 @@ def dragEnterEvent(self, event: QDragEnterEvent): # pylint: disable=no-self-use if event.mimeData().hasUrls(): event.acceptProposedAction() - def read_drop(self, paths: List[str]): + def read_drop(self, paths: list[str]): """Function to process loading files by drag and drop.""" self._read_drop(paths, self._load_dict) diff --git a/package/PartSeg/common_gui/mask_widget.py b/package/PartSeg/common_gui/mask_widget.py index 3ce04f929..416e7b062 100644 --- a/package/PartSeg/common_gui/mask_widget.py +++ b/package/PartSeg/common_gui/mask_widget.py @@ -1,6 +1,6 @@ from contextlib import suppress from functools import partial -from typing import List, Union +from typing import Union from qtpy.QtCore import Signal from qtpy.QtWidgets import QCheckBox, QDialog, QHBoxLayout, QLabel, QPushButton, QSpinBox, QVBoxLayout, QWidget @@ -109,7 +109,7 @@ def _value_changed_wrap(self, _val=None): # noinspection PyUnresolvedReferences self.values_changed.emit() - def get_dilate_radius(self) -> Union[int, List[int]]: + def get_dilate_radius(self) -> Union[int, list[int]]: radius = calculate_operation_radius( self.dilate_radius.value(), self.settings.image_spacing, self.dilate_dim.currentEnum() ) diff --git a/package/PartSeg/common_gui/multiple_file_widget.py b/package/PartSeg/common_gui/multiple_file_widget.py index 341e045d8..ad7a85508 100644 --- a/package/PartSeg/common_gui/multiple_file_widget.py +++ b/package/PartSeg/common_gui/multiple_file_widget.py @@ -2,7 +2,6 @@ from collections import Counter, defaultdict from functools import partial from pathlib import Path -from typing import Dict, List, Tuple from qtpy.QtCore import Qt, QTimer, Signal, Slot from qtpy.QtGui import QFontMetrics, QMouseEvent, QResizeEvent @@ -104,7 +103,7 @@ def __init__(self, settings: BaseSettings, parent=None): *self.settings.get_from_profile("multiple_files_dialog_size", (self.size().width(), self.size().height())) ) - def get_files(self) -> List[Tuple[List[str], str]]: + def get_files(self) -> list[tuple[list[str], str]]: return [item.data(Qt.ItemDataRole.UserRole) for item in self.file_list.selectedItems()] def accept(self) -> None: @@ -115,10 +114,10 @@ def accept(self) -> None: class MultipleFileWidget(QWidget): _add_state = Signal(object, bool) - def __init__(self, settings: BaseSettings, load_dict: Dict[str, LoadBase], compare_in_context_menu=False): + def __init__(self, settings: BaseSettings, load_dict: dict[str, LoadBase], compare_in_context_menu=False): super().__init__() self.settings = settings - self.state_dict: Dict[str, Dict[str, ProjectInfoBase]] = defaultdict(dict) + self.state_dict: dict[str, dict[str, ProjectInfoBase]] = defaultdict(dict) self.state_dict_count = Counter() self.file_list = [] self.load_register = load_dict @@ -371,7 +370,7 @@ def mouseReleaseEvent(self, event: QMouseEvent): def set_compare_in_context_menu(self, compare: bool): self.file_view.set_show_compare(compare) - def add_states(self, states: List[ProjectInfoBase]): + def add_states(self, states: list[ProjectInfoBase]): """add multiple states to widget""" for el in states: self.save_state_action(el, False) diff --git a/package/PartSeg/common_gui/napari_image_view.py b/package/PartSeg/common_gui/napari_image_view.py index 7f37e463f..a525b9cb2 100644 --- a/package/PartSeg/common_gui/napari_image_view.py +++ b/package/PartSeg/common_gui/napari_image_view.py @@ -1,11 +1,12 @@ import itertools import logging import platform +from collections.abc import MutableMapping from contextlib import suppress from dataclasses import dataclass, field from enum import Enum from functools import partial -from typing import TYPE_CHECKING, Dict, List, MutableMapping, Optional, Tuple, Union +from typing import TYPE_CHECKING, Optional, Union import napari import numpy as np @@ -113,14 +114,14 @@ def __init__(self, viewer): ORDER_DICT = {"xy": [0, 1, 2, 3], "zy": [0, 2, 1, 3], "zx": [0, 3, 1, 2]} NEXT_ORDER = {"xy": "zy", "zy": "zx", "zx": "xy"} -ColorInfo = Dict[Optional[int], Union[str, List[float]]] +ColorInfo = dict[Optional[int], Union[str, list[float]]] @dataclass class ImageInfo: image: Image - layers: List[NapariImage] - filter_info: List[Tuple[NoiseFilterType, float]] = field(default_factory=list) + layers: list[NapariImage] + filter_info: list[tuple[NoiseFilterType, float]] = field(default_factory=list) mask: Optional[Labels] = None mask_array: Optional[np.ndarray] = None roi: Optional[Labels] = None @@ -128,14 +129,14 @@ class ImageInfo: roi_count: int = 0 highlight: Optional[Labels] = None - def coords_in(self, coords: Union[List[int], np.ndarray]) -> bool: + def coords_in(self, coords: Union[list[int], np.ndarray]) -> bool: if not self.layers: return False fst_layer = self.layers[0] moved_coords = self.translated_coords(coords) return np.all(moved_coords >= 0) and np.all(moved_coords < fst_layer.data.shape) - def translated_coords(self, coords: Union[List[int], np.ndarray]) -> np.ndarray: + def translated_coords(self, coords: Union[list[int], np.ndarray]) -> np.ndarray: if not self.layers: return np.array(coords) fst_layer = self.layers[0] @@ -178,7 +179,7 @@ def __init__( self.settings = settings self.channel_property = channel_property self.name = name - self.image_info: Dict[str, ImageInfo] = {} + self.image_info: dict[str, ImageInfo] = {} self.current_image = "" self._current_order = "xy" self.components = None @@ -634,7 +635,7 @@ def has_image(self, image: Image): return image.file_path in self.image_info @staticmethod - def calculate_filter(array: np.ndarray, parameters: Tuple[NoiseFilterType, float]) -> Optional[np.ndarray]: + def calculate_filter(array: np.ndarray, parameters: tuple[NoiseFilterType, float]) -> Optional[np.ndarray]: if parameters[0] == NoiseFilterType.No or parameters[1] == 0: return array if parameters[0] == NoiseFilterType.Gauss: @@ -680,7 +681,7 @@ def _add_or_move_layer(self, layer: Optional[Layer], index): else: self.viewer.layers.move(self.viewer.layers.index(layer), index) - def _add_image(self, image_data: Tuple[ImageInfo, bool]): + def _add_image(self, image_data: tuple[ImageInfo, bool]): image_info, replace = image_data image = image_info.image @@ -768,7 +769,7 @@ def _prepare_layers(self, image, parameters, replace): def _prepare_layers(self, image, parameters, replace): self._add_image(_prepare_layers(image, parameters, replace)) - def images_bounds(self) -> Tuple[List[int], List[int]]: + def images_bounds(self) -> tuple[list[int], list[int]]: ranges = [] for image_info in self.image_info.values(): if not image_info.layers: @@ -984,7 +985,7 @@ def _update_point(self, lower_bound, upper_bound): def _data_to_world(layer: Layer, cords): return layer._transforms[1:3].simplified(cords) # pylint: disable=protected-access - def _bounding_box(self, num) -> Optional[Tuple[np.ndarray, np.ndarray]]: + def _bounding_box(self, num) -> Optional[tuple[np.ndarray, np.ndarray]]: lower_bound_list = [] upper_bound_list = [] for image_info in self.image_info.values(): @@ -1069,15 +1070,15 @@ def closeEvent(self, event): @dataclass class ImageParameters: - limits: List[Tuple[float, float]] - visibility: List[bool] - gamma: List[float] - colormaps: List[Colormap] - scaling: Tuple[Union[float, int]] + limits: list[tuple[float, float]] + visibility: list[bool] + gamma: list[float] + colormaps: list[Colormap] + scaling: tuple[Union[float, int]] layers: int = 0 -def _prepare_layers(image: Image, param: ImageParameters, replace: bool) -> Tuple[ImageInfo, bool]: +def _prepare_layers(image: Image, param: ImageParameters, replace: bool) -> tuple[ImageInfo, bool]: image_layers = [] for i in range(image.channels): lim = list(param.limits[i]) diff --git a/package/PartSeg/common_gui/napari_viewer_wrap.py b/package/PartSeg/common_gui/napari_viewer_wrap.py index 81e71f66a..f55569162 100644 --- a/package/PartSeg/common_gui/napari_viewer_wrap.py +++ b/package/PartSeg/common_gui/napari_viewer_wrap.py @@ -1,5 +1,5 @@ from contextlib import suppress -from typing import List, Optional +from typing import Optional from napari import Viewer as NViewer from napari.utils.colormaps import Colormap @@ -46,7 +46,7 @@ def __init__(self, settings: BaseSettings, viewer: NViewer, partseg_viewer_name, self.settings.additional_layers_changed.connect(self._sync_additional) self.settings.points_changed.connect(self._sync_points) - def get_colormaps(self) -> List[Optional[Colormap]]: + def get_colormaps(self) -> list[Optional[Colormap]]: channel_num = self.settings.image.channels if not self.partseg_viewer_name: return [None for _ in range(channel_num)] diff --git a/package/PartSeg/common_gui/numpy_qimage.py b/package/PartSeg/common_gui/numpy_qimage.py index d90bd2b6d..74f14b486 100644 --- a/package/PartSeg/common_gui/numpy_qimage.py +++ b/package/PartSeg/common_gui/numpy_qimage.py @@ -5,7 +5,7 @@ from napari.utils.colormaps import make_colorbar from qtpy.QtGui import QImage -ColorMapDict = typing.MutableMapping[str, typing.Tuple[Colormap, bool]] +ColorMapDict = typing.MutableMapping[str, tuple[Colormap, bool]] class NumpyQImage(QImage): diff --git a/package/PartSeg/common_gui/select_multiple_files.py b/package/PartSeg/common_gui/select_multiple_files.py index 751a00417..1fbc9b346 100644 --- a/package/PartSeg/common_gui/select_multiple_files.py +++ b/package/PartSeg/common_gui/select_multiple_files.py @@ -2,7 +2,6 @@ from functools import partial from glob import glob from pathlib import Path -from typing import List from qtpy.QtCore import QPoint, Qt, Signal from qtpy.QtGui import QDragEnterEvent, QDropEvent @@ -89,7 +88,7 @@ class AddFiles(QWidget): def __init__(self, settings: BaseSettings, parent=None, btn_layout=QHBoxLayout): """TODO: to be defined1.""" super().__init__(parent) - self.mask_list: List[MaskMapper] = [] + self.mask_list: list[MaskMapper] = [] self.settings = settings self.files_to_proceed = set() self.paths_input = QLineEdit(self) diff --git a/package/PartSeg/common_gui/stack_image_view.py b/package/PartSeg/common_gui/stack_image_view.py index 59f97622b..0ff5c59c7 100644 --- a/package/PartSeg/common_gui/stack_image_view.py +++ b/package/PartSeg/common_gui/stack_image_view.py @@ -1,5 +1,5 @@ from math import log -from typing import List, Union +from typing import Union import numpy as np from napari.utils import Colormap @@ -19,7 +19,7 @@ class ColorBar(QLabel): - def __init__(self, settings: ViewSettings, image_view: Union[List[ImageView], ImageView]): + def __init__(self, settings: ViewSettings, image_view: Union[list[ImageView], ImageView]): super().__init__() self.image_view = image_view self._settings = settings diff --git a/package/PartSeg/common_gui/universal_gui_part.py b/package/PartSeg/common_gui/universal_gui_part.py index 88bafcd70..e4a982f21 100644 --- a/package/PartSeg/common_gui/universal_gui_part.py +++ b/package/PartSeg/common_gui/universal_gui_part.py @@ -120,7 +120,7 @@ def __init__( unit: Units, parent=None, input_type: QAbstractSpinBox = QDoubleSpinBox, - data_range: typing.Tuple[float, float] = (0, 100000), + data_range: tuple[float, float] = (0, 100000), ): """ :param title: title of the widget @@ -281,7 +281,7 @@ class InfoLabel(QWidget): :param parent: passed to :py:class:`QWidget` constructor """ - def __init__(self, text_list: typing.List[str], delay: int = 10000, parent=None): + def __init__(self, text_list: list[str], delay: int = 10000, parent=None): if len(text_list) == 0: raise ValueError("List of text to show should be non empty.") super().__init__(parent) diff --git a/package/PartSeg/plugins/modeling_save/save_modeling_data.py b/package/PartSeg/plugins/modeling_save/save_modeling_data.py index a38646475..f73d7c627 100644 --- a/package/PartSeg/plugins/modeling_save/save_modeling_data.py +++ b/package/PartSeg/plugins/modeling_save/save_modeling_data.py @@ -18,7 +18,7 @@ class SaveModeling(SaveBase): @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return [ AlgorithmProperty("channel", "Channel", 0, value_type=Channel), AlgorithmProperty("clip", "Clip area", False), diff --git a/package/PartSeg/plugins/napari_widgets/algorithm_widgets.py b/package/PartSeg/plugins/napari_widgets/algorithm_widgets.py index 4dfe7ee57..5ab0fe02d 100644 --- a/package/PartSeg/plugins/napari_widgets/algorithm_widgets.py +++ b/package/PartSeg/plugins/napari_widgets/algorithm_widgets.py @@ -1,6 +1,6 @@ import operator from enum import Enum -from typing import Optional, Type +from typing import Optional import numpy as np import SimpleITK as sitk @@ -217,7 +217,7 @@ def run_calculation(self): class AlgorithmWidgetBase(QWidget): - __data_model__: Type[BaseModel] + __data_model__: type[BaseModel] def __init__(self, napari_viewer: Viewer, parent=None): super().__init__(parent) diff --git a/package/PartSeg/plugins/napari_widgets/colormap_control.py b/package/PartSeg/plugins/napari_widgets/colormap_control.py index 637dd1554..152530bde 100644 --- a/package/PartSeg/plugins/napari_widgets/colormap_control.py +++ b/package/PartSeg/plugins/napari_widgets/colormap_control.py @@ -1,4 +1,5 @@ -from typing import Dict, Iterable, Optional, Tuple +from collections.abc import Iterable +from typing import Optional from napari import Viewer from napari.layers import Image @@ -44,7 +45,7 @@ class NapariColormapList(ColormapList): def __init__( self, viewer: Viewer, - colormap_map: Dict[str, Tuple[Colormap, bool]], + colormap_map: dict[str, tuple[Colormap, bool]], selected: Optional[Iterable[str]] = None, parent=None, ): diff --git a/package/PartSeg/plugins/napari_widgets/lables_control.py b/package/PartSeg/plugins/napari_widgets/lables_control.py index 51b74e1d9..8852a4b15 100644 --- a/package/PartSeg/plugins/napari_widgets/lables_control.py +++ b/package/PartSeg/plugins/napari_widgets/lables_control.py @@ -1,5 +1,5 @@ +from collections.abc import Sequence from importlib.metadata import version -from typing import List, Sequence from napari import Viewer from napari.layers import Labels @@ -15,7 +15,7 @@ class NapariLabelShow(LabelShow): - def __init__(self, viewer: Viewer, name: str, label: List[Sequence[float]], removable, parent=None): + def __init__(self, viewer: Viewer, name: str, label: list[Sequence[float]], removable, parent=None): super().__init__(name, label, removable, parent) self.viewer = viewer @@ -50,12 +50,12 @@ def apply_label(self): layer.color = labels -class NaparliLabelChoose(LabelChoose): +class NapariLabelChoose(LabelChoose): def __init__(self, viewer: Viewer, settings, parent=None): super().__init__(settings, parent) self.viewer = viewer - def _label_show(self, name: str, label: List[Sequence[float]], removable) -> LabelShow: + def _label_show(self, name: str, label: list[Sequence[float]], removable) -> LabelShow: return NapariLabelShow(self.viewer, name, label, removable, self) @@ -74,7 +74,7 @@ def __init__(self, viewer: Viewer, parent=None): settings = get_settings() self.settings = settings self.label_editor = NapariLabelEditor(settings) - self.label_view = NaparliLabelChoose(viewer, settings) + self.label_view = NapariLabelChoose(viewer, settings) self.addTab(self.label_view, "Select labels") self.addTab(self.label_editor, "Create labels") diff --git a/package/PartSeg/plugins/napari_widgets/roi_extraction_algorithms.py b/package/PartSeg/plugins/napari_widgets/roi_extraction_algorithms.py index b96d217d3..437cfd50f 100644 --- a/package/PartSeg/plugins/napari_widgets/roi_extraction_algorithms.py +++ b/package/PartSeg/plugins/napari_widgets/roi_extraction_algorithms.py @@ -64,10 +64,10 @@ def _form_widget(self, algorithm, start_values) -> FormWidget: def reset_choices(self, event=None): self.form_widget.reset_choices(event) - def get_layer_list(self) -> typing.List[str]: + def get_layer_list(self) -> list[str]: return [x.name for x in self.get_layers().values() if x.name != "mask"] - def get_layers(self) -> typing.Dict[str, Layer]: + def get_layers(self) -> dict[str, Layer]: values = self.form_widget.get_layers() return {k: v for k, v in values.items() if isinstance(v, Layer)} @@ -83,7 +83,7 @@ def reset_choices(self, event=None): class ROIExtractionAlgorithms(QWidget): @staticmethod - def get_method_dict() -> typing.Type[AlgorithmSelection]: # pragma: no cover + def get_method_dict() -> type[AlgorithmSelection]: # pragma: no cover raise NotImplementedError @staticmethod @@ -154,7 +154,7 @@ def select_profile(self, text): self.profile_combo_box.setCurrentIndex(0) @property - def profile_dict(self) -> typing.Dict[str, ROIExtractionProfile]: + def profile_dict(self) -> dict[str, ROIExtractionProfile]: return self.settings.get_from_profile(f"{self.prefix()}.profiles", {}) def save_action(self): @@ -202,7 +202,7 @@ def update_mask(self): def update_image(self): widget = typing.cast(NapariInteractiveAlgorithmSettingsWidget, self.algorithm_chose.current_widget()) self.settings.last_executed_algorithm = widget.name - layer_names: typing.List[str] = widget.get_layer_list() + layer_names: list[str] = widget.get_layer_list() if layer_names == self.channel_names: return image = generate_image(self.viewer, *layer_names) @@ -297,8 +297,8 @@ def prefix() -> str: class ProfilePreviewDialog(QDialog): def __init__( self, - profile_dict: typing.Dict[str, ROIExtractionProfile], - algorithm_selection: typing.Type[AlgorithmSelection], + profile_dict: dict[str, ROIExtractionProfile], + algorithm_selection: type[AlgorithmSelection], settings: BaseSettings, parent=None, ): diff --git a/package/PartSeg/plugins/napari_widgets/utils.py b/package/PartSeg/plugins/napari_widgets/utils.py index 322cb06f3..939bcd49b 100644 --- a/package/PartSeg/plugins/napari_widgets/utils.py +++ b/package/PartSeg/plugins/napari_widgets/utils.py @@ -50,7 +50,7 @@ def get_values(self): class NapariFormDialog(FormDialog): @staticmethod - def widget_class() -> typing.Type[FormWidget]: + def widget_class() -> type[FormWidget]: return NapariFormWidget def __init__(self, *args, **kwargs): diff --git a/package/PartSeg/plugins/old_partseg/old_partseg.py b/package/PartSeg/plugins/old_partseg/old_partseg.py index f1c2a2f31..04374b2b5 100644 --- a/package/PartSeg/plugins/old_partseg/old_partseg.py +++ b/package/PartSeg/plugins/old_partseg/old_partseg.py @@ -64,7 +64,7 @@ def _load(cls, tar_file: tarfile.TarFile, file_path: str) -> ProjectTuple: @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, diff --git a/package/PartSegCore/algorithm_describe_base.py b/package/PartSegCore/algorithm_describe_base.py index 6bfab71b4..5340729ce 100644 --- a/package/PartSegCore/algorithm_describe_base.py +++ b/package/PartSegCore/algorithm_describe_base.py @@ -4,15 +4,16 @@ import typing import warnings from abc import ABC, ABCMeta, abstractmethod +from collections.abc import MutableMapping from functools import wraps from importlib.metadata import version +from typing import Annotated from local_migrator import REGISTER, class_to_str from packaging.version import parse as parse_version from pydantic import BaseModel as PydanticBaseModel from pydantic import create_model, validator from pydantic.fields import Field, FieldInfo -from typing_extensions import Annotated from PartSegCore.utils import BaseModel from PartSegImage import Channel @@ -156,7 +157,7 @@ class AlgorithmDescribeBase(ABC, metaclass=AlgorithmDescribeBaseMeta): For each group of algorithm base abstract class will add additional methods """ - __argument_class__: typing.Optional[typing.Type[PydanticBaseModel]] = None + __argument_class__: typing.Optional[type[PydanticBaseModel]] = None __new_style__: bool @classmethod @@ -184,7 +185,7 @@ def get_name(cls) -> str: @classmethod @_partial_abstractmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: """ This function return list of parameters needed by algorithm. It is used for generate form in User Interface @@ -204,7 +205,7 @@ def _get_fields(cls): return base_model_to_algorithm_property(cls.__argument_class__) if cls.__new_style__ else cls.get_fields() @classmethod - def get_fields_dict(cls) -> typing.Dict[str, AlgorithmProperty]: + def get_fields_dict(cls) -> dict[str, AlgorithmProperty]: return {v.name: v for v in get_fields_from_algorithm(cls) if isinstance(v, AlgorithmProperty)} @classmethod @@ -225,7 +226,7 @@ def get_default_values(cls): } -def get_fields_from_algorithm(ald_desc: AlgorithmDescribeBase) -> typing.List[typing.Union[AlgorithmProperty, str]]: +def get_fields_from_algorithm(ald_desc: AlgorithmDescribeBase) -> list[typing.Union[AlgorithmProperty, str]]: if ald_desc.__new_style__: return base_model_to_algorithm_property(ald_desc.__argument_class__) return ald_desc.get_fields() @@ -238,10 +239,10 @@ def is_static(fun): return True if len(args) == 0 else args[0] != "self" -AlgorithmType = typing.TypeVar("AlgorithmType", bound=typing.Type[AlgorithmDescribeBase]) +AlgorithmType = typing.TypeVar("AlgorithmType", bound=type[AlgorithmDescribeBase]) -class Register(typing.Dict, typing.Generic[AlgorithmType]): +class Register(dict, typing.Generic[AlgorithmType]): """ Dict used for register :class:`.AlgorithmDescribeBase` classes. All registers from `PartSeg.PartSegCore.register` are this @@ -292,9 +293,7 @@ def __getitem__(self, item) -> AlgorithmType: def __contains__(self, item): return super().__contains__(item) or item in self._old_mapping - def register( - self, value: AlgorithmType, replace: bool = False, old_names: typing.Optional[typing.List[str]] = None - ): + def register(self, value: AlgorithmType, replace: bool = False, old_names: typing.Optional[list[str]] = None): """ Function for registering :class:`.AlgorithmDescribeBase` based algorithms :param value: algorithm to register @@ -401,7 +400,7 @@ class AlgorithmSelection(BaseModel, metaclass=AddRegisterMeta): # pylint: disab """ name: str - values: typing.Union[typing.Dict[str, typing.Any], PydanticBaseModel] = Field(..., union_mode="left_to_right") + values: typing.Union[dict[str, typing.Any], PydanticBaseModel] = Field(..., union_mode="left_to_right") class_path: str = "" if typing.TYPE_CHECKING: __register__: Register @@ -444,7 +443,7 @@ def update_values(cls, v, values): @classmethod def register( - cls, value: AlgorithmType, replace=False, old_names: typing.Optional[typing.List[str]] = None + cls, value: AlgorithmType, replace=False, old_names: typing.Optional[list[str]] = None ) -> AlgorithmType: """ Function for registering :class:`.AlgorithmDescribeBase` based algorithms @@ -537,15 +536,13 @@ def pretty_print(self, algorithm_dict): ) @classmethod - def _pretty_print( - cls, values: typing.MutableMapping, translate_dict: typing.Dict[str, AlgorithmProperty], indent=0 - ): - if not isinstance(values, typing.MutableMapping): + def _pretty_print(cls, values: MutableMapping, translate_dict: dict[str, AlgorithmProperty], indent=0): + if not isinstance(values, MutableMapping): return textwrap.indent(str(values), " " * indent) res = "" for k, v in values.items(): if k not in translate_dict: - if isinstance(v, typing.MutableMapping): + if isinstance(v, MutableMapping): res += " " * indent + f"{k}: {cls._pretty_print(v, {}, indent + 2)}\n" else: res += " " * indent + f"{k}: {v}\n" @@ -565,7 +562,7 @@ def _pretty_print( if values_: res += "\n" res += cls._pretty_print(values_, desc.possible_values[name].get_fields_dict(), indent + 2) - elif isinstance(v, typing.MutableMapping): + elif isinstance(v, MutableMapping): res += cls._pretty_print(v, {}, indent + 2) else: res += str(v) @@ -691,8 +688,8 @@ def _field_to_algorithm_property_pydantic_1(name: str, field: "ModelField"): def base_model_to_algorithm_property_pydantic_1( - obj: typing.Type[BaseModel], -) -> typing.List[typing.Union[str, AlgorithmProperty]]: + obj: type[BaseModel], +) -> list[typing.Union[str, AlgorithmProperty]]: """ Convert pydantic model to list of AlgorithmPropert nad strings. @@ -722,8 +719,8 @@ def base_model_to_algorithm_property_pydantic_1( def base_model_to_algorithm_property_pydantic_2( - obj: typing.Type[BaseModel], -) -> typing.List[typing.Union[str, AlgorithmProperty]]: + obj: type[BaseModel], +) -> list[typing.Union[str, AlgorithmProperty]]: """ Convert pydantic model to list of AlgorithmPropert nad strings. diff --git a/package/PartSegCore/analysis/analysis_utils.py b/package/PartSegCore/analysis/analysis_utils.py index 47fc840af..d23af969c 100644 --- a/package/PartSegCore/analysis/analysis_utils.py +++ b/package/PartSegCore/analysis/analysis_utils.py @@ -1,4 +1,3 @@ -import typing from textwrap import indent from PartSegCore.algorithm_describe_base import ROIExtractionProfile @@ -27,7 +26,7 @@ def __repr__(self): class SegmentationPipeline(BaseModel): name: str segmentation: ROIExtractionProfile - mask_history: typing.List[SegmentationPipelineElement] + mask_history: list[SegmentationPipelineElement] def pretty_print(self, algorithm_dict): return ( diff --git a/package/PartSegCore/analysis/batch_processing/batch_backend.py b/package/PartSegCore/analysis/batch_processing/batch_backend.py index 0d8d9b3a3..2e57856f5 100644 --- a/package/PartSegCore/analysis/batch_processing/batch_backend.py +++ b/package/PartSegCore/analysis/batch_processing/batch_backend.py @@ -35,7 +35,7 @@ from os import path from queue import Queue from traceback import StackSummary -from typing import TYPE_CHECKING, Any, Dict, List, NamedTuple, Tuple, Union +from typing import TYPE_CHECKING, Any, NamedTuple, Union import numpy as np import pandas as pd @@ -93,9 +93,9 @@ class ResponseData(NamedTuple): values: list[MeasurementResult] -CalculationResultList = List[ResponseData] -ErrorInfo = Tuple[Exception, Union[StackSummary, Tuple[Dict, StackSummary]]] -WrappedResult = Tuple[int, List[Union[ErrorInfo, ResponseData]]] +CalculationResultList = list[ResponseData] +ErrorInfo = tuple[Exception, Union[StackSummary, tuple[dict, StackSummary]]] +WrappedResult = tuple[int, list[Union[ErrorInfo, ResponseData]]] def get_data_loader( diff --git a/package/PartSegCore/analysis/batch_processing/parallel_backend.py b/package/PartSegCore/analysis/batch_processing/parallel_backend.py index 37dd1c0ee..68833294b 100644 --- a/package/PartSegCore/analysis/batch_processing/parallel_backend.py +++ b/package/PartSegCore/analysis/batch_processing/parallel_backend.py @@ -25,7 +25,7 @@ from enum import Enum from queue import Empty, Queue from threading import RLock, Timer -from typing import Any, Callable, Dict, List, Tuple +from typing import Any, Callable __author__ = "Grzegorz Bokota" @@ -68,7 +68,7 @@ def __init__(self): self.process_list = [] self.locker = RLock() - def get_result(self) -> List[Tuple[uuid.UUID, Any]]: + def get_result(self) -> list[tuple[uuid.UUID, Any]]: """ Clean result queue and return it as list @@ -84,7 +84,7 @@ def get_result(self) -> List[Tuple[uuid.UUID, Any]]: Timer(0.1, self._change_process_num, args=[-self.number_off_available_process]).start() return res - def add_work(self, individual_parameters_list: List, global_parameters, fun: Callable[[Any, Any], Any]) -> str: + def add_work(self, individual_parameters_list: list, global_parameters, fun: Callable[[Any, Any], Any]) -> str: """ This function add next works to internal structures. Number of works is length of ``individual_parameters_list`` @@ -207,7 +207,7 @@ def __init__( task_queue: Queue, order_queue: Queue, result_queue: Queue, - calculation_dict: Dict[uuid.UUID, Tuple[Any, Callable[[Any, Any], Any]]], + calculation_dict: dict[uuid.UUID, tuple[Any, Callable[[Any, Any], Any]]], ): self.task_queue = task_queue self.order_queue = order_queue @@ -215,7 +215,7 @@ def __init__( self.calculation_dict = calculation_dict self.canceled_tasks = set() - def calculate_task(self, val: Tuple[Any, uuid.UUID]): + def calculate_task(self, val: tuple[Any, uuid.UUID]): """ Calculate single task. ``val`` is tuple with two elements (task_data, uuid). @@ -259,7 +259,7 @@ def run(self): logging.info("Process %s ended", os.getpid()) -def spawn_worker(task_queue: Queue, order_queue: Queue, result_queue: Queue, calculation_dict: Dict[uuid.UUID, Any]): +def spawn_worker(task_queue: Queue, order_queue: Queue, result_queue: Queue, calculation_dict: dict[uuid.UUID, Any]): """ Function for spawning worker. Designed as argument for :py:meth:`multiprocessing.Process`. diff --git a/package/PartSegCore/analysis/calculate_pipeline.py b/package/PartSegCore/analysis/calculate_pipeline.py index 54712e755..d30c68402 100644 --- a/package/PartSegCore/analysis/calculate_pipeline.py +++ b/package/PartSegCore/analysis/calculate_pipeline.py @@ -23,9 +23,9 @@ def _empty_fun(_a1, _a2): @dataclass(frozen=True) class PipelineResult: roi_info: ROIInfo - additional_layers: typing.Dict[str, AdditionalLayerDescription] + additional_layers: dict[str, AdditionalLayerDescription] mask: np.ndarray - history: typing.List[HistoryElement] + history: list[HistoryElement] description: str @@ -54,7 +54,7 @@ def calculate_pipeline(image: Image, mask: typing.Optional[np.ndarray], pipeline def calculate_segmentation_step( profile: ROIExtractionProfile, image: Image, mask: typing.Optional[np.ndarray] -) -> typing.Tuple[ROIExtractionResult, str]: +) -> tuple[ROIExtractionResult, str]: algorithm: RestartableAlgorithm = AnalysisAlgorithmSelection[profile.algorithm]() algorithm.set_image(image) algorithm.set_mask(mask) diff --git a/package/PartSegCore/analysis/calculation_plan.py b/package/PartSegCore/analysis/calculation_plan.py index 3d94d96b6..8801a785b 100644 --- a/package/PartSegCore/analysis/calculation_plan.py +++ b/package/PartSegCore/analysis/calculation_plan.py @@ -298,7 +298,7 @@ class CalculationTree: def __init__( self, operation: typing.Union[PydanticBaseModel, ROIExtractionProfile, MeasurementCalculate, RootType], - children: typing.List["CalculationTree"], + children: list["CalculationTree"], ): if operation == "root": operation = RootType.Image @@ -404,7 +404,7 @@ class Calculation(BaseCalculation): :ivar CalculationPlan ~.calculation_plan: plan of calculation :ivar str uuid: ~.uuid of whole calculation :ivar ~.voxel_size: default voxel size (for files which do not contains this information in metadata - :ivar typing.List[str] ~.file_list: list of files to be proceed + :ivar list[str] ~.file_list: list of files to be proceed """ def __init__( @@ -427,7 +427,7 @@ def __init__( voxel_size, overwrite_voxel_size, ) - self.file_list: typing.List[str] = file_list + self.file_list: list[str] = file_list def get_base_calculation(self) -> BaseCalculation: """Extract py:class:`BaseCalculation` from instance.""" @@ -502,7 +502,7 @@ class CalculationPlan: :type execution_tree: CalculationTree """ - correct_name: typing.ClassVar[typing.Dict[str, typing.Union[BaseModel, Enum]]] = { + correct_name: typing.ClassVar[dict[str, typing.Union[BaseModel, Enum]]] = { MaskCreate.__name__: MaskCreate, MaskUse.__name__: MaskUse, Save.__name__: Save, @@ -549,7 +549,7 @@ def __str__(self): def __repr__(self): return f"CalculationPlan(name={self.name!r}, execution_tree={self.execution_tree!r})" - def get_measurements(self, node: typing.Optional[CalculationTree] = None) -> typing.List[MeasurementCalculate]: + def get_measurements(self, node: typing.Optional[CalculationTree] = None) -> list[MeasurementCalculate]: """ Get all measurement Calculation below given node @@ -588,7 +588,7 @@ def __copy__(self): def __deepcopy__(self, memo): return CalculationPlan(name=self.name, tree=deepcopy(self.execution_tree)) - def get_node(self, search_pos: typing.Optional[typing.List[int]] = None, parent=False) -> CalculationTree: + def get_node(self, search_pos: typing.Optional[list[int]] = None, parent=False) -> CalculationTree: """ :param search_pos: :return: CalculationTree diff --git a/package/PartSegCore/analysis/io_utils.py b/package/PartSegCore/analysis/io_utils.py index 87d682e47..cb5a724f3 100644 --- a/package/PartSegCore/analysis/io_utils.py +++ b/package/PartSegCore/analysis/io_utils.py @@ -25,9 +25,9 @@ class ProjectTuple(ProjectInfoBase): file_path: str image: Image roi_info: ROIInfo = field(default_factory=lambda: ROIInfo(None)) - additional_layers: typing.Dict[str, AdditionalLayerDescription] = field(default_factory=dict) + additional_layers: dict[str, AdditionalLayerDescription] = field(default_factory=dict) mask: typing.Optional[np.ndarray] = None - history: typing.List[HistoryElement] = field(default_factory=list) + history: list[HistoryElement] = field(default_factory=list) algorithm_parameters: dict = field(default_factory=dict) errors: str = "" points: typing.Optional[np.ndarray] = None diff --git a/package/PartSegCore/analysis/load_functions.py b/package/PartSegCore/analysis/load_functions.py index faaf6e7e0..d270e3b10 100644 --- a/package/PartSegCore/analysis/load_functions.py +++ b/package/PartSegCore/analysis/load_functions.py @@ -165,7 +165,7 @@ def get_short_name(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, @@ -185,7 +185,7 @@ def get_short_name(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, @@ -224,7 +224,7 @@ def number_of_files(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, @@ -246,7 +246,7 @@ def load( return ProjectTuple(load_locations[0], image, mask=image.mask) @classmethod - def get_next_file(cls, file_paths: typing.List[str]): + def get_next_file(cls, file_paths: list[str]): base, ext = os.path.splitext(file_paths[0]) return f"{base}_mask{ext}" @@ -263,7 +263,7 @@ def get_short_name(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, @@ -297,7 +297,7 @@ def _mask_data_outside_mask(file_path): def load_mask_project( - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Callable[[int, int], typing.Any], step_changed: typing.Callable[[int], typing.Any], metadata: typing.Optional[dict] = None, @@ -349,11 +349,11 @@ def get_short_name(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, - ) -> typing.List[ProjectTuple]: + ) -> list[ProjectTuple]: if range_changed is None: def range_changed(_x, _y): @@ -375,11 +375,11 @@ def get_short_name(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, - ) -> typing.Tuple[dict, list]: + ) -> tuple[dict, list]: return load_metadata_part(load_locations[0]) @classmethod @@ -427,11 +427,11 @@ def get_short_name(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, - ) -> typing.Union[ProjectTuple, typing.List[ProjectTuple]]: + ) -> typing.Union[ProjectTuple, list[ProjectTuple]]: ext = os.path.splitext(load_locations[0])[1].lower() for loader in load_dict.values(): diff --git a/package/PartSegCore/analysis/measurement_base.py b/package/PartSegCore/analysis/measurement_base.py index ddcb7618f..01955dc05 100644 --- a/package/PartSegCore/analysis/measurement_base.py +++ b/package/PartSegCore/analysis/measurement_base.py @@ -1,7 +1,8 @@ import sys from abc import ABC +from collections.abc import Iterable from enum import Enum -from typing import Any, ClassVar, Dict, ForwardRef, Iterable, List, Optional, Set, Tuple, Union +from typing import Any, ClassVar, ForwardRef, Optional, Union import numpy as np from local_migrator import REGISTER, class_to_str, register_class, rename_key @@ -56,7 +57,7 @@ def __str__(self): return self.name.replace("_", " ") -def has_mask_components(component_and_mask_info: Iterable[Tuple[PerComponent, AreaType]]) -> bool: +def has_mask_components(component_and_mask_info: Iterable[tuple[PerComponent, AreaType]]) -> bool: """Check if any measurement will return value per mask component""" return any( (cmp == PerComponent.Yes and area != AreaType.ROI) or cmp == PerComponent.Per_Mask_component @@ -64,7 +65,7 @@ def has_mask_components(component_and_mask_info: Iterable[Tuple[PerComponent, Ar ) -def has_roi_components(component_and_mask_info: Iterable[Tuple[PerComponent, AreaType]]) -> bool: +def has_roi_components(component_and_mask_info: Iterable[tuple[PerComponent, AreaType]]) -> bool: """Check if any measurement will return value per ROI component""" return any((cmp == PerComponent.Yes and area == AreaType.ROI) for cmp, area in component_and_mask_info) @@ -124,11 +125,11 @@ def _validate_per_component(cls, v, values): # pylint: disable=no-self-use raise ValueError("Per_Mask_component can be used only with ROI area") return v - def get_channel_num(self, measurement_dict: Dict[str, "MeasurementMethodBase"]) -> Set[Channel]: + def get_channel_num(self, measurement_dict: dict[str, "MeasurementMethodBase"]) -> set[Channel]: """ Get set with number of channels needed for calculate this measurement - :param measurement_dict: dict with all measurementh method. + :param measurement_dict: dict with all measurement methods. :return: set of channels num """ resp = set() @@ -153,7 +154,7 @@ def get_channel_num(self, measurement_dict: Dict[str, "MeasurementMethodBase"]) raise AlgorithmDescribeNotFound(self.name) from e return resp - def _parameters_string(self, measurement_dict: Dict[str, "MeasurementMethodBase"]) -> str: + def _parameters_string(self, measurement_dict: dict[str, "MeasurementMethodBase"]) -> str: parameters = dict(self.parameters) if not parameters and self.channel is None: return "" @@ -168,7 +169,7 @@ def _parameters_string(self, measurement_dict: Dict[str, "MeasurementMethodBase" arr.extend(f"{k.replace('_', ' ')}={v}" for k, v in parameters.items()) return "[" + ", ".join(arr) + "]" - def _plugin_info(self, measurement_dict: Dict[str, "MeasurementMethodBase"]) -> str: + def _plugin_info(self, measurement_dict: dict[str, "MeasurementMethodBase"]) -> str: if self.name not in measurement_dict: return "" measurement_method = measurement_dict[self.name] @@ -181,7 +182,7 @@ def _plugin_info(self, measurement_dict: Dict[str, "MeasurementMethodBase"]) -> return f"[{measurement_method.__module__.split('.', 1)[0]}] " return "" - def pretty_print(self, measurement_dict: Dict[str, "MeasurementMethodBase"]) -> str: + def pretty_print(self, measurement_dict: dict[str, "MeasurementMethodBase"]) -> str: """ Pretty print for presentation in user interface. @@ -268,7 +269,7 @@ class Node(BaseModel): ) right: Union[Node, Leaf] - def get_channel_num(self, measurement_dict: Dict[str, "MeasurementMethodBase"]) -> Set[Channel]: + def get_channel_num(self, measurement_dict: dict[str, "MeasurementMethodBase"]) -> set[Channel]: return self.left.get_channel_num(measurement_dict) | self.right.get_channel_num(measurement_dict) def __str__(self): # pragma: no cover @@ -278,7 +279,7 @@ def __str__(self): # pragma: no cover return left_text + self.op + right_text - def pretty_print(self, measurement_dict: Dict[str, "MeasurementMethodBase"]) -> str: # pragma: no cover + def pretty_print(self, measurement_dict: dict[str, "MeasurementMethodBase"]) -> str: # pragma: no cover left_text = ( f"({self.left.pretty_print(measurement_dict)})" if isinstance(self.left, Node) @@ -324,7 +325,7 @@ class MeasurementEntry(BaseModel): def get_unit(self, unit: Units, ndim) -> str: return str(self.calculation_tree.get_unit(ndim)).format(str(unit)) - def get_channel_num(self, measurement_dict: Dict[str, "MeasurementMethodBase"]) -> Set[Channel]: + def get_channel_num(self, measurement_dict: dict[str, "MeasurementMethodBase"]) -> set[Channel]: return self.calculation_tree.get_channel_num(measurement_dict) @@ -339,7 +340,7 @@ class MeasurementMethodBase(AlgorithmDescribeBase, ABC): text_info = "", "" - need_class_method: ClassVar[List[str]] = [ + need_class_method: ClassVar[list[str]] = [ "get_description", "is_component", "calculate_property", @@ -371,17 +372,14 @@ def calculate_property( mask: np.ndarray, voxel_size: Spacing, result_scalar: float, - roi_alternative: Dict[str, np.ndarray], - roi_annotation: Dict[int, Any], + roi_alternative: dict[str, np.ndarray], + roi_annotation: dict[int, Any], **kwargs, ): """ Main function for calculating measurement :param channel: main channel selected for measurement - :param channel_{i}: for channel requested using :py:meth:`get_fields` - ``AlgorithmProperty("channel", "Channel", 0, value_type=Channel)`` - :param area_array: array representing current area returned by :py:meth:`area_type` :param roi: array representing roi :param mask: array representing mask (upper level roi) :param voxel_size: size of single voxel in meters diff --git a/package/PartSegCore/analysis/measurement_calculation.py b/package/PartSegCore/analysis/measurement_calculation.py index 6f148aae9..3a2bcdb53 100644 --- a/package/PartSegCore/analysis/measurement_calculation.py +++ b/package/PartSegCore/analysis/measurement_calculation.py @@ -1,5 +1,6 @@ import warnings from collections import OrderedDict +from collections.abc import Generator, Iterator, MutableMapping, Sequence from contextlib import suppress from enum import Enum from functools import reduce @@ -7,16 +8,8 @@ from typing import ( Any, Callable, - Dict, - Generator, - Iterator, - List, - MutableMapping, NamedTuple, Optional, - Sequence, - Set, - Tuple, Union, ) @@ -88,7 +81,7 @@ class ComponentsInfo(NamedTuple): roi_components: np.ndarray mask_components: np.ndarray - components_translation: Dict[int, List[int]] + components_translation: dict[int, list[int]] def has_components(self): return all(len(x) for x in self.components_translation.values()) @@ -98,9 +91,9 @@ def empty_fun(_a0=None, _a1=None): """This function is being used as dummy reporting function.""" -MeasurementValueType = Union[float, List[float], str] -MeasurementResultType = Tuple[MeasurementValueType, str] -MeasurementResultInputType = Tuple[MeasurementValueType, str, Tuple[PerComponent, AreaType]] +MeasurementValueType = Union[float, list[float], str] +MeasurementResultType = tuple[MeasurementValueType, str] +MeasurementResultInputType = tuple[MeasurementValueType, str, tuple[PerComponent, AreaType]] FILE_NAME_STR = "File name" @@ -114,8 +107,8 @@ class MeasurementResult(MutableMapping[str, MeasurementResultType]): def __init__(self, components_info: ComponentsInfo): self.components_info = components_info self._data_dict = OrderedDict() - self._units_dict: Dict[str, str] = {} - self._type_dict: Dict[str, Tuple[PerComponent, AreaType]] = {} + self._units_dict: dict[str, str] = {} + self._type_dict: dict[str, tuple[PerComponent, AreaType]] = {} self._units_dict["Mask component"] = "" self._units_dict["Segmentation component"] = "" @@ -164,7 +157,7 @@ def set_filename(self, path_fo_file: str): self._units_dict[FILE_NAME_STR] = "" self._data_dict.move_to_end(FILE_NAME_STR, False) - def get_component_info(self, all_components: bool = False) -> Tuple[bool, bool]: + def get_component_info(self, all_components: bool = False) -> tuple[bool, bool]: """ Get information which type of components are in storage. @@ -175,7 +168,7 @@ def get_component_info(self, all_components: bool = False) -> Tuple[bool, bool]: return has_mask_components(self._type_dict.values()), has_roi_components(self._type_dict.values()) - def get_labels(self, expand=True, all_components=False) -> List[str]: + def get_labels(self, expand=True, all_components=False) -> list[str]: """ If expand is false return list of keys of this storage. Otherwise return labels for measurement. Base are keys of this storage. @@ -193,7 +186,7 @@ def get_labels(self, expand=True, all_components=False) -> List[str]: labels.insert(index, "Segmentation component") return labels - def get_units(self, all_components=False) -> List[str]: + def get_units(self, all_components=False) -> list[str]: return [self._units_dict[x] for x in self.get_labels(all_components=all_components)] def get_global_names(self): @@ -239,7 +232,7 @@ def _prepare_res_iterator(self, counts): iterator = iter(self._data_dict.keys()) return res, iterator - def get_separated(self, all_components=False) -> List[List[MeasurementValueType]]: + def get_separated(self, all_components=False) -> list[list[MeasurementValueType]]: """Get measurements separated for each component""" mask_components, roi_components = self.get_component_info(all_components) if not mask_components and not roi_components: @@ -271,7 +264,7 @@ def get_separated(self, all_components=False) -> List[List[MeasurementValueType] class MeasurementProfile(BaseModel): name: str - chosen_fields: List[MeasurementEntry] + chosen_fields: list[MeasurementEntry] name_prefix: str = "" @property @@ -291,7 +284,7 @@ def _need_mask_without_segmentation(self, tree): return tree.area == AreaType.Mask_without_ROI return self._need_mask_without_segmentation(tree.left) or self._need_mask_without_segmentation(tree.right) - def _get_par_component_and_area_type(self, tree: Union[Node, Leaf]) -> Tuple[PerComponent, AreaType]: + def _get_par_component_and_area_type(self, tree: Union[Node, Leaf]) -> tuple[PerComponent, AreaType]: if isinstance(tree, Leaf): method = MEASUREMENT_DICT[tree.name] area_type = method.area_type(tree.area) @@ -316,7 +309,7 @@ def _get_par_component_and_area_type(self, tree: Union[Node, Leaf]) -> Tuple[Per res_area = AreaType.Mask_without_ROI return res_par, res_area - def get_channels_num(self) -> Set[Channel]: + def get_channels_num(self) -> set[Channel]: resp = set() for el in self.chosen_fields: resp.update(el.get_channel_num(MEASUREMENT_DICT)) @@ -430,7 +423,7 @@ def _calculate_leaf_value( def _calculate_leaf( self, node: Leaf, segmentation_mask_map: ComponentsInfo, help_dict: dict, kwargs: dict - ) -> Tuple[Union[float, np.ndarray], symbols, AreaType]: + ) -> tuple[Union[float, np.ndarray], symbols, AreaType]: method: MeasurementMethodBase = MEASUREMENT_DICT[node.name] hash_str = hash_fun_call_name( @@ -452,7 +445,7 @@ def _calculate_leaf( def _calculate_node( self, node: Node, segmentation_mask_map: ComponentsInfo, help_dict: dict, kwargs: dict - ) -> Tuple[Union[float, np.ndarray], symbols, AreaType]: + ) -> tuple[Union[float, np.ndarray], symbols, AreaType]: if node.op != "/": raise ValueError(f"Wrong measurement: {node}") left_res, left_unit, left_area = self.calculate_tree(node.left, segmentation_mask_map, help_dict, kwargs) @@ -485,7 +478,7 @@ def _calculate_node( def calculate_tree( self, node: Union[Node, Leaf], segmentation_mask_map: ComponentsInfo, help_dict: dict, kwargs: dict - ) -> Tuple[Union[float, np.ndarray], symbols, AreaType]: + ) -> tuple[Union[float, np.ndarray], symbols, AreaType]: """ Main function for calculation tree of measurements. It is executed recursively @@ -528,7 +521,7 @@ def get_segmentation_to_mask_component(segmentation: np.ndarray, mask: Optional[ res[num] = res[num][1:] return ComponentsInfo(components, mask_components, res) - def get_component_and_area_info(self) -> List[Tuple[PerComponent, AreaType]]: + def get_component_and_area_info(self) -> list[tuple[PerComponent, AreaType]]: """For each measurement check if is per component and in which types""" res = [] for el in self.chosen_fields: @@ -706,7 +699,7 @@ def get_main_axis_length( index: int, area_array: np.ndarray, channel: np.ndarray, voxel_size, result_scalar, _cache=False, **kwargs ): if _cache and "_area" in kwargs and "_per_component" in kwargs and "channel_num" in kwargs: - help_dict: Dict = kwargs["help_dict"] + help_dict: dict = kwargs["help_dict"] _area: AreaType = kwargs["_area"] _per_component: PerComponent = kwargs["_per_component"] hash_name = hash_fun_call_name( @@ -721,7 +714,7 @@ def get_main_axis_length( def hash_fun_call_name( fun: Union[Callable, MeasurementMethodBase], - arguments: Dict, + arguments: dict, area: AreaType, per_component: PerComponent, channel: Channel, @@ -1565,7 +1558,7 @@ def calculate_property( # pylint: disable=arguments-differ if isinstance(feature, str): feature = HaralickEnum(feature) if _cache := _cache and "_area" in kwargs and "_per_component" in kwargs: - help_dict: Dict = kwargs["help_dict"] + help_dict: dict = kwargs["help_dict"] _area: AreaType = kwargs["_area"] _per_component: PerComponent = kwargs["_per_component"] hash_name = hash_fun_call_name( diff --git a/package/PartSegCore/analysis/save_functions.py b/package/PartSegCore/analysis/save_functions.py index f21f60744..2ae37cec1 100644 --- a/package/PartSegCore/analysis/save_functions.py +++ b/package/PartSegCore/analysis/save_functions.py @@ -35,7 +35,7 @@ def save_project( image: Image, roi_info: ROIInfo, mask: typing.Optional[np.ndarray], - history: typing.List[HistoryElement], + history: list[HistoryElement], algorithm_parameters: dict, ): # TODO add support for binary objects @@ -398,7 +398,7 @@ def get_name(cls) -> str: return "Segment profile (*.json)" @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return [] diff --git a/package/PartSegCore/class_generator.py b/package/PartSegCore/class_generator.py index f219f0676..3fd90157a 100644 --- a/package/PartSegCore/class_generator.py +++ b/package/PartSegCore/class_generator.py @@ -364,7 +364,7 @@ def asdict(self) -> collections.OrderedDict: def replace_(self, **_kwargs): return self - def as_tuple(self) -> typing.Tuple: + def as_tuple(self) -> tuple: """declare interface""" @classmethod diff --git a/package/PartSegCore/custom_name_generate.py b/package/PartSegCore/custom_name_generate.py index a5afcc658..583e0d26c 100644 --- a/package/PartSegCore/custom_name_generate.py +++ b/package/PartSegCore/custom_name_generate.py @@ -1,9 +1,9 @@ import random import string -from typing import Any, Dict, Set +from typing import Any -def custom_name_generate(prohibited_names: Set[str], dict_names: Dict[str, Any]) -> str: +def custom_name_generate(prohibited_names: set[str], dict_names: dict[str, Any]) -> str: letters_set = string.ascii_letters + string.digits for _ in range(1000): rand_name = "custom_" + "".join(random.choice(letters_set) for _ in range(10)) # nosec # noqa: S311 #NOSONAR diff --git a/package/PartSegCore/image_operations.py b/package/PartSegCore/image_operations.py index 3b82e105c..2ba3a663c 100644 --- a/package/PartSegCore/image_operations.py +++ b/package/PartSegCore/image_operations.py @@ -1,5 +1,6 @@ +from collections.abc import Iterable from enum import Enum -from typing import Iterable, List, Union +from typing import Union import numpy as np import SimpleITK as sitk @@ -78,7 +79,7 @@ def bilateral(image: np.ndarray, radius: float, layer=True): return _generic_image_operation(image, radius, sitk.Bilateral, layer) -def median(image: np.ndarray, radius: Union[int, List[int]], layer=True): +def median(image: np.ndarray, radius: Union[int, list[int]], layer=True): """ Median blur of image. diff --git a/package/PartSegCore/image_transforming/combine_channels.py b/package/PartSegCore/image_transforming/combine_channels.py index 452fee91b..9e8d81ed2 100644 --- a/package/PartSegCore/image_transforming/combine_channels.py +++ b/package/PartSegCore/image_transforming/combine_channels.py @@ -1,5 +1,5 @@ from enum import Enum, auto -from typing import Callable, List, Optional, Tuple, Union +from typing import Callable, Optional, Union import numpy as np @@ -20,7 +20,7 @@ def get_fields(cls): return [AlgorithmProperty("combine_mode", "Combine Mode", CombineMode.Sum)] @classmethod - def get_fields_per_dimension(cls, image: Image) -> List[Union[str, AlgorithmProperty]]: + def get_fields_per_dimension(cls, image: Image) -> list[Union[str, AlgorithmProperty]]: return [ AlgorithmProperty("combine_mode", "Combine Mode", CombineMode.Sum), *[AlgorithmProperty(f"channel_{i}", f"Channel {i}", False) for i in range(image.channels)], @@ -37,7 +37,7 @@ def transform( roi_info: Optional[ROIInfo], arguments: dict, callback_function: Optional[Callable[[str, int], None]] = None, - ) -> Tuple[Image, Optional[ROIInfo]]: + ) -> tuple[Image, Optional[ROIInfo]]: channels = [i for i, x in enumerate(x for x in arguments.items() if x[0].startswith("channel")) if x[1]] if not channels: return image, roi_info diff --git a/package/PartSegCore/image_transforming/image_projection.py b/package/PartSegCore/image_transforming/image_projection.py index 69aa95fab..4f9af0456 100644 --- a/package/PartSegCore/image_transforming/image_projection.py +++ b/package/PartSegCore/image_transforming/image_projection.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Callable, Optional, Tuple +from typing import Callable, Optional import numpy as np from pydantic import Field @@ -39,7 +39,7 @@ def transform( roi_info: ROIInfo, arguments: ImageProjectionParams, # type: ignore[override] callback_function: Optional[Callable[[str, int], None]] = None, - ) -> Tuple[Image, Optional[ROIInfo]]: + ) -> tuple[Image, Optional[ROIInfo]]: project_operator = getattr(np, arguments.projection_type.value) axis = image.array_axis_order.index("Z") target_shape = _calc_target_shape(image) diff --git a/package/PartSegCore/image_transforming/interpolate_image.py b/package/PartSegCore/image_transforming/interpolate_image.py index df525faab..3edb70878 100644 --- a/package/PartSegCore/image_transforming/interpolate_image.py +++ b/package/PartSegCore/image_transforming/interpolate_image.py @@ -1,4 +1,4 @@ -from typing import Callable, List, Optional, Tuple, Union +from typing import Callable, Optional, Union from scipy.ndimage import zoom @@ -14,7 +14,7 @@ def get_fields(cls): return ["It can be very slow.", AlgorithmProperty("scale", "Scale", 1.0)] @classmethod - def get_fields_per_dimension(cls, image: Image) -> List[Union[str, AlgorithmProperty]]: + def get_fields_per_dimension(cls, image: Image) -> list[Union[str, AlgorithmProperty]]: component_list = list(image.get_dimension_letters()) return [ "it can be very slow", @@ -32,7 +32,7 @@ def transform( roi_info: Optional[ROIInfo], arguments: dict, callback_function: Optional[Callable[[str, int], None]] = None, - ) -> Tuple[Image, Optional[ROIInfo]]: + ) -> tuple[Image, Optional[ROIInfo]]: keys = [x for x in arguments if x.startswith("scale")] keys_order = Image.axis_order.lower() scale_factor = [1.0] * len(keys_order) diff --git a/package/PartSegCore/image_transforming/swap_time_stack.py b/package/PartSegCore/image_transforming/swap_time_stack.py index 4bafead0f..e12b71950 100644 --- a/package/PartSegCore/image_transforming/swap_time_stack.py +++ b/package/PartSegCore/image_transforming/swap_time_stack.py @@ -14,7 +14,7 @@ def transform( roi_info: ROIInfo, arguments: dict, callback_function: typing.Optional[typing.Callable[[str, int], None]] = None, - ) -> typing.Tuple[Image, typing.Optional[ROIInfo]]: + ) -> tuple[Image, typing.Optional[ROIInfo]]: return image.swap_time_and_stack(), None @classmethod @@ -30,5 +30,5 @@ def get_name(cls) -> str: return "Swap time and Z dim" @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return [] diff --git a/package/PartSegCore/image_transforming/transform_base.py b/package/PartSegCore/image_transforming/transform_base.py index 31564ce79..991fd1a6f 100644 --- a/package/PartSegCore/image_transforming/transform_base.py +++ b/package/PartSegCore/image_transforming/transform_base.py @@ -1,5 +1,5 @@ from abc import ABC -from typing import Callable, List, Optional, Tuple, Union +from typing import Callable, Optional, Union from PartSegCore.algorithm_describe_base import AlgorithmDescribeBase, AlgorithmProperty from PartSegCore.roi_info import ROIInfo @@ -14,11 +14,11 @@ def transform( roi_info: ROIInfo, arguments: dict, callback_function: Optional[Callable[[str, int], None]] = None, - ) -> Tuple[Image, Optional[ROIInfo]]: + ) -> tuple[Image, Optional[ROIInfo]]: raise NotImplementedError @classmethod - def get_fields_per_dimension(cls, image: Image) -> List[Union[str, AlgorithmProperty]]: + def get_fields_per_dimension(cls, image: Image) -> list[Union[str, AlgorithmProperty]]: raise NotImplementedError @classmethod diff --git a/package/PartSegCore/io_utils.py b/package/PartSegCore/io_utils.py index 2d4066df7..93a4505c6 100644 --- a/package/PartSegCore/io_utils.py +++ b/package/PartSegCore/io_utils.py @@ -63,7 +63,7 @@ def get_name_with_suffix(cls): return cls.get_name() @classmethod - def get_extensions(cls) -> typing.List[str]: + def get_extensions(cls) -> list[str]: match = re.match(r".*\((.*)\)", cls.get_name()) if match is None: raise ValueError(f"No extensions found in {cls.get_name()}") @@ -74,7 +74,7 @@ def get_extensions(cls) -> typing.List[str]: class SaveBase(_IOBase, ABC): - need_functions: typing.ClassVar[typing.List[str]] = [ + need_functions: typing.ClassVar[list[str]] = [ "save", "get_short_name", "get_name_with_suffix", @@ -123,7 +123,7 @@ def get_default_extension(cls): class LoadBase(_IOBase, ABC): - need_functions: typing.ClassVar[typing.List[str]] = [ + need_functions: typing.ClassVar[list[str]] = [ "load", "get_short_name", "get_name_with_suffix", @@ -141,11 +141,11 @@ def get_short_name(cls): @abstractmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, - ) -> typing.Union[ProjectInfoBase, typing.List[ProjectInfoBase]]: + ) -> typing.Union[ProjectInfoBase, list[ProjectInfoBase]]: """ Function for load data @@ -167,7 +167,7 @@ def number_of_files(cls): return 1 @classmethod - def get_next_file(cls, file_paths: typing.List[str]): + def get_next_file(cls, file_paths: list[str]): return file_paths[0] @classmethod @@ -194,7 +194,7 @@ def load_metadata_base(data: typing.Union[str, Path, typing.TextIO]): return decoded_data -def load_metadata_part(data: typing.Union[str, Path]) -> typing.Tuple[typing.Any, typing.List[typing.Tuple[str, dict]]]: +def load_metadata_part(data: typing.Union[str, Path]) -> tuple[typing.Any, list[tuple[str, dict]]]: """ Load serialized data. Get valid entries. @@ -218,7 +218,7 @@ def load_metadata_part(data: typing.Union[str, Path]) -> typing.Tuple[typing.Any # backward compatibility -def find_problematic_entries(data: typing.Any) -> typing.List[typing.MutableMapping]: +def find_problematic_entries(data: typing.Any) -> list[typing.MutableMapping]: """ Find top nodes with ``"__error__"`` key. If node found then its children is not checked. @@ -236,7 +236,7 @@ def find_problematic_entries(data: typing.Any) -> typing.List[typing.MutableMapp return res -def find_problematic_leafs(data: typing.Any) -> typing.List[typing.MutableMapping]: +def find_problematic_leafs(data: typing.Any) -> list[typing.MutableMapping]: """ Find bottom nodes with ``"__error__"`` key. If any children has ``"__error__"`` then such node is not returned. @@ -271,7 +271,7 @@ def proxy_callback( def open_tar_file( file_data: typing.Union[str, Path, TarFile, TextIOBase, BufferedIOBase, RawIOBase, IOBase], mode="r" -) -> typing.Tuple[TarFile, str]: +) -> tuple[TarFile, str]: """Create tar file from path or buffer. If passed :py:class:`TarFile` then return it.""" if isinstance(file_data, TarFile): tar_file = file_data @@ -348,7 +348,7 @@ def get_name(cls) -> str: return "Screenshot (*.png *.jpg *.jpeg)" @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return [] @@ -421,7 +421,7 @@ def get_short_name(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, @@ -434,7 +434,7 @@ def get_name(cls) -> str: return "Points (*.csv)" @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return ["text"] @classmethod @@ -450,7 +450,7 @@ def get_short_name(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, @@ -480,7 +480,7 @@ def get_short_name(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, diff --git a/package/PartSegCore/mask/io_functions.py b/package/PartSegCore/mask/io_functions.py index 8c268afa0..2606fb7d4 100644 --- a/package/PartSegCore/mask/io_functions.py +++ b/package/PartSegCore/mask/io_functions.py @@ -69,10 +69,10 @@ class MaskProjectTuple(ProjectInfoBase): :ivar typing.Optional[np.ndarray] ~.mask: Mask limiting segmentation area. :ivar ROIInfo ~.roi_info: ROI information. :ivar SegmentationInfo ~.roi_info: ROI description - :ivar typing.List[int] ~.selected_components: list of selected components - :ivar typing.Dict[int,typing.Optional[SegmentationProfile]] ~.segmentation_parameters: + :ivar list[int] ~.selected_components: list of selected components + :ivar dict[int,typing.Optional[SegmentationProfile]] ~.segmentation_parameters: For each component description set of parameters used for segmentation - :ivar typing.List[HistoryElement] history: list of operations needed to create :py:attr:`mask` + :ivar list[HistoryElement] history: list of operations needed to create :py:attr:`mask` :ivar str ~.errors: information about problems meet during calculation :ivar typing.Optional[typing.List[float]] ~.spacing: information about spacing when image is missed. For napari read plugin @@ -82,14 +82,14 @@ class MaskProjectTuple(ProjectInfoBase): image: typing.Union[Image, str, None] mask: typing.Optional[np.ndarray] = None roi_info: ROIInfo = dataclasses.field(default_factory=lambda: ROIInfo(None)) - additional_layers: typing.Dict[str, AdditionalLayerDescription] = dataclasses.field(default_factory=dict) - selected_components: typing.List[int] = dataclasses.field(default_factory=list) - roi_extraction_parameters: typing.Dict[int, typing.Optional[ROIExtractionProfile]] = dataclasses.field( + additional_layers: dict[str, AdditionalLayerDescription] = dataclasses.field(default_factory=dict) + selected_components: list[int] = dataclasses.field(default_factory=list) + roi_extraction_parameters: dict[int, typing.Optional[ROIExtractionProfile]] = dataclasses.field( default_factory=dict ) - history: typing.List[HistoryElement] = dataclasses.field(default_factory=list) + history: list[HistoryElement] = dataclasses.field(default_factory=list) errors: str = "" - spacing: typing.Optional[typing.List[float]] = None + spacing: typing.Optional[list[float]] = None points: typing.Optional[np.ndarray] = None frame_thickness: int = FRAME_THICKNESS @@ -122,7 +122,7 @@ class SaveROIOptions(BaseModel): " then data outside ROI will be replaced with zeros.", ) frame_thickness: int = Field(2, title="Frame thickness", description="Thickness of frame around ROI") - spacing: typing.List[float] = Field([10**-6, 10**-6, 10**-6], hidden=True) + spacing: list[float] = Field([10**-6, 10**-6, 10**-6], hidden=True) def _save_mask_roi(project: MaskProjectTuple, tar_file: tarfile.TarFile, parameters: SaveROIOptions): @@ -336,7 +336,7 @@ def get_short_name(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, @@ -374,7 +374,7 @@ def get_short_name(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, @@ -423,7 +423,7 @@ def get_short_name(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, @@ -468,7 +468,7 @@ def get_short_name(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, @@ -494,7 +494,7 @@ def get_short_name(cls): return "img_with_mask" @classmethod - def get_next_file(cls, file_paths: typing.List[str]): + def get_next_file(cls, file_paths: list[str]): base, ext = os.path.splitext(file_paths[0]) return f"{base}_mask{ext}" @@ -505,11 +505,11 @@ def number_of_files(cls): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, - ) -> typing.Union[ProjectInfoBase, typing.List[ProjectInfoBase]]: + ) -> typing.Union[ProjectInfoBase, list[ProjectInfoBase]]: if metadata is None: metadata = {"default_spacing": (10**-6, 10**-6, 10**-6)} image = GenericImageReader.read_image( @@ -572,7 +572,7 @@ def save_components( points: typing.Optional[np.ndarray] = None, range_changed=None, step_changed=None, - writer_class: typing.Type[BaseImageWriter] = ImageWriter, + writer_class: type[BaseImageWriter] = ImageWriter, ): if range_changed is None: range_changed = empty_fun @@ -714,7 +714,7 @@ def save( json.dump({"parameters": project_info.roi_extraction_parameters}, ff, cls=PartSegEncoder) @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return [] @classmethod @@ -730,11 +730,11 @@ class LoadROIFromTIFF(LoadBase): @classmethod def load( cls, - load_locations: typing.List[typing.Union[str, BytesIO, Path]], + load_locations: list[typing.Union[str, BytesIO, Path]], range_changed: typing.Optional[typing.Callable[[int, int], typing.Any]] = None, step_changed: typing.Optional[typing.Callable[[int], typing.Any]] = None, metadata: typing.Optional[dict] = None, - ) -> typing.Union[ProjectInfoBase, typing.List[ProjectInfoBase]]: + ) -> typing.Union[ProjectInfoBase, list[ProjectInfoBase]]: image = TiffImageReader.read_image(load_locations[0]) roi = image.get_channel(0) return MaskProjectTuple( diff --git a/package/PartSegCore/mask_create.py b/package/PartSegCore/mask_create.py index 7eebc0e99..b7156599a 100644 --- a/package/PartSegCore/mask_create.py +++ b/package/PartSegCore/mask_create.py @@ -102,7 +102,7 @@ def calculate_mask( roi: np.ndarray, old_mask: typing.Optional[np.ndarray], spacing: typing.Iterable[typing.Union[float, int]], - components: typing.Optional[typing.List[int]] = None, + components: typing.Optional[list[int]] = None, time_axis: typing.Optional[int] = 0, ) -> np.ndarray: """ @@ -115,7 +115,7 @@ def calculate_mask( :param typing.Optional[np.ndarray] old_mask: if in mask_description there is set to crop and old_mask is not None then final mask is clipped to this area :param typing.Iterable[typing.Union[float,int]] spacing: spacing of image. Needed for calculating radius of dilate - :param typing.Optional[typing.List[int]] components: If present inform which components + :param typing.Optional[list[int]] components: If present inform which components should be used when calculation mask, otherwise use all. :param typing.Optional[int] time_axis: which axis of array should be treated as time. IF none then none. :return: new mask @@ -136,7 +136,7 @@ def calculate_mask( mask = np.copy(roi) if mask_description.save_components else np.array(roi > 0) if time_axis is None: return _calculate_mask(mask_description, dilate_radius, mask, old_mask) - slices: typing.List[typing.Union[slice, int]] = [slice(None) for _ in range(mask.ndim)] + slices: list[typing.Union[slice, int]] = [slice(None) for _ in range(mask.ndim)] final_shape = list(mask.shape) final_shape[time_axis] = 1 final_shape = tuple(final_shape) @@ -151,7 +151,7 @@ def calculate_mask( def _calculate_mask( mask_description: MaskProperty, - dilate_radius: typing.List[int], + dilate_radius: list[int], mask: np.ndarray, old_mask: typing.Union[None, np.ndarray], ) -> np.ndarray: @@ -173,7 +173,7 @@ def _calculate_mask( def _cut_components( mask: np.ndarray, image: np.ndarray, borders: int = 0 -) -> typing.Iterator[typing.Tuple[np.ndarray, typing.List[slice], int]]: +) -> typing.Iterator[tuple[np.ndarray, list[slice], int]]: sizes = np.bincount(mask.flat) for i, size in enumerate(sizes[1:], 1): if size > 0: @@ -229,7 +229,7 @@ def fill_holes_in_mask(mask: np.ndarray, volume: int) -> np.ndarray: component_mask = sitk.GetArrayFromImage( sitk.RelabelComponent(sitk.ConnectedComponent(sitk.GetImageFromArray(holes_mask))) ) - border_set: typing.Set[int] = set() + border_set: set[int] = set() for dim_num in range(component_mask.ndim): border_set.update(list(np.unique(np.take(component_mask, [0, -1], axis=dim_num)))) if 0 in border_set: diff --git a/package/PartSegCore/napari_plugins/loader.py b/package/PartSegCore/napari_plugins/loader.py index abd9ee629..35e1961cb 100644 --- a/package/PartSegCore/napari_plugins/loader.py +++ b/package/PartSegCore/napari_plugins/loader.py @@ -15,10 +15,10 @@ def adjust_color(color: str) -> str: ... @typing.overload -def adjust_color(color: typing.List[int]) -> typing.List[float]: ... +def adjust_color(color: list[int]) -> list[float]: ... -def adjust_color(color: typing.Union[str, typing.List[int]]) -> typing.Union[str, typing.List[float]]: +def adjust_color(color: typing.Union[str, list[int]]) -> typing.Union[str, list[float]]: # as napari ignore alpha channel in color, and adding it to # color cause that napari fails to detect that such colormap is already present # in this function I remove alpha channel if it is present @@ -135,7 +135,7 @@ def project_to_layers(project_info: typing.Union[ProjectTuple, MaskProjectTuple] return res_layers -def partseg_loader(loader: typing.Type[LoadBase], path: str): +def partseg_loader(loader: type[LoadBase], path: str): load_locations = [path] load_locations.extend(loader.get_next_file(load_locations) for _ in range(1, loader.number_of_files())) diff --git a/package/PartSegCore/napari_plugins/save_tiff_layer.py b/package/PartSegCore/napari_plugins/save_tiff_layer.py index a77c956a6..c73c5d850 100644 --- a/package/PartSegCore/napari_plugins/save_tiff_layer.py +++ b/package/PartSegCore/napari_plugins/save_tiff_layer.py @@ -1,5 +1,5 @@ import os -from typing import Any, List, Optional +from typing import Any, Optional import numpy as np from napari.types import FullLayerData @@ -25,7 +25,7 @@ def napari_write_labels(path: str, data: Any, meta: dict) -> Optional[str]: return path -def napari_write_images(path: str, layer_data: List[FullLayerData]) -> List[str]: +def napari_write_images(path: str, layer_data: list[FullLayerData]) -> list[str]: ext = os.path.splitext(path)[1] base_shape = layer_data[0][0].shape if not all(isinstance(x[0], np.ndarray) and x[0].shape == base_shape for x in layer_data) or ext.lower() not in { diff --git a/package/PartSegCore/project_info.py b/package/PartSegCore/project_info.py index c54b75509..858db30f4 100644 --- a/package/PartSegCore/project_info.py +++ b/package/PartSegCore/project_info.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from io import BytesIO -from typing import Any, ClassVar, Dict, List, Optional, Protocol, Tuple, Union, runtime_checkable +from typing import Any, ClassVar, Optional, Protocol, Union, runtime_checkable import numpy as np @@ -32,8 +32,8 @@ def __repr__(self): class HistoryElement(BaseModel): - roi_extraction_parameters: Dict[str, Any] - annotations: Optional[Dict[int, Any]] + roi_extraction_parameters: dict[str, Any] + annotations: Optional[dict[int, Any]] mask_property: MaskProperty arrays: BytesIO @@ -65,7 +65,7 @@ def create( annotations=roi_info.annotations, ) - def get_roi_info_and_mask(self) -> Tuple[ROIInfo, Optional[np.ndarray]]: + def get_roi_info_and_mask(self) -> tuple[ROIInfo, Optional[np.ndarray]]: self.arrays.seek(0) seg = np.load(self.arrays) self.arrays.seek(0) @@ -92,9 +92,9 @@ class ProjectInfoBase(Protocol): file_path: str image: Image roi_info: ROIInfo = ROIInfo(None) - additional_layers: ClassVar[Dict[str, AdditionalLayerDescription]] = {} + additional_layers: ClassVar[dict[str, AdditionalLayerDescription]] = {} mask: Optional[np.ndarray] = None - history: ClassVar[List[HistoryElement]] = [] + history: ClassVar[list[HistoryElement]] = [] errors: str = "" points: Optional[np.ndarray] = None @@ -115,7 +115,7 @@ def is_masked(self): def calculate_mask_from_project( - mask_description: MaskProperty, project: ProjectInfoBase, components: Optional[List[int]] = None + mask_description: MaskProperty, project: ProjectInfoBase, components: Optional[list[int]] = None ) -> np.ndarray: """ Function for calculate mask base on MaskProperty. diff --git a/package/PartSegCore/register.py b/package/PartSegCore/register.py index e23d7185b..64c698dd6 100644 --- a/package/PartSegCore/register.py +++ b/package/PartSegCore/register.py @@ -14,7 +14,6 @@ """ from enum import Enum -from typing import Type from PartSegCore import io_utils from PartSegCore.algorithm_describe_base import AlgorithmDescribeBase @@ -31,8 +30,6 @@ watershed, ) -# from .mask.io_functions import - qss_list = [] @@ -113,7 +110,7 @@ class RegisterEnum(Enum): ] -def register(target: Type[AlgorithmDescribeBase], target_type: RegisterEnum, replace=False, old_names=None): +def register(target: type[AlgorithmDescribeBase], target_type: RegisterEnum, replace=False, old_names=None): """ Function for registering new operations in PartSeg inner structures. diff --git a/package/PartSegCore/roi_info.py b/package/PartSegCore/roi_info.py index e1dcd1d3f..777c60b15 100644 --- a/package/PartSegCore/roi_info.py +++ b/package/PartSegCore/roi_info.py @@ -1,5 +1,5 @@ from collections import defaultdict -from typing import Any, Dict, List, NamedTuple, Optional +from typing import Any, NamedTuple, Optional import numpy as np @@ -20,7 +20,7 @@ def box_size(self) -> np.ndarray: """Size of bounding box""" return self.upper - self.lower + 1 - def get_slices(self, margin=0) -> List[slice]: + def get_slices(self, margin=0) -> list[slice]: return [slice(max(x - margin, 0), y + 1 + margin) for x, y in zip(self.lower, self.upper)] def del_dim(self, axis: int): @@ -45,8 +45,8 @@ class ROIInfo: def __init__( self, roi: Optional[np.ndarray], - annotations: Optional[Dict[int, Any]] = None, - alternative: Optional[Dict[str, np.ndarray]] = None, + annotations: Optional[dict[int, Any]] = None, + alternative: Optional[dict[str, np.ndarray]] = None, ): annotations = {} if annotations is None else annotations self.annotations = {int(k): v for k, v in annotations.items()} @@ -77,7 +77,7 @@ def __repr__(self): return f"ROIInfo(roi={numpy_repr(self.roi)}, bound_info={self.bound_info}, sizes={self.sizes!r})" @staticmethod - def calc_bounds(roi: np.ndarray) -> Dict[int, BoundInfo]: + def calc_bounds(roi: np.ndarray) -> dict[int, BoundInfo]: """ Calculate bounding boxes components diff --git a/package/PartSegCore/segmentation/algorithm_base.py b/package/PartSegCore/segmentation/algorithm_base.py index 5e9d61382..08699eb3d 100644 --- a/package/PartSegCore/segmentation/algorithm_base.py +++ b/package/PartSegCore/segmentation/algorithm_base.py @@ -1,8 +1,9 @@ from abc import ABC, abstractmethod +from collections.abc import MutableMapping from copy import deepcopy from dataclasses import dataclass, field from textwrap import indent -from typing import Any, Callable, Dict, MutableMapping, Optional +from typing import Any, Callable, Optional import numpy as np from local_migrator import REGISTER, class_to_str @@ -67,10 +68,10 @@ class ROIExtractionResult: # TODO add alternative representation using dict mapping. roi: np.ndarray parameters: ROIExtractionProfile - additional_layers: Dict[str, AdditionalLayerDescription] = field(default_factory=dict) + additional_layers: dict[str, AdditionalLayerDescription] = field(default_factory=dict) info_text: str = "" - roi_annotation: Dict = field(default_factory=dict) - alternative_representation: Dict[str, np.ndarray] = field(default_factory=dict) + roi_annotation: dict = field(default_factory=dict) + alternative_representation: dict[str, np.ndarray] = field(default_factory=dict) file_path: Optional[str] = None roi_info: Optional[ROIInfo] = None points: Optional[np.ndarray] = None @@ -136,7 +137,7 @@ def __init__(self): self.channel = None self.segmentation = None self._mask: Optional[np.ndarray] = None - self.new_parameters: Dict[str, Any] = {} + self.new_parameters: dict[str, Any] = {} def __repr__(self): # pragma: no cover if self.mask is None: diff --git a/package/PartSegCore/segmentation/restartable_segmentation_algorithms.py b/package/PartSegCore/segmentation/restartable_segmentation_algorithms.py index 5fafd6246..fed6e9fe9 100644 --- a/package/PartSegCore/segmentation/restartable_segmentation_algorithms.py +++ b/package/PartSegCore/segmentation/restartable_segmentation_algorithms.py @@ -55,7 +55,7 @@ class RestartableAlgorithm(ROIExtractionAlgorithm, ABC): def __init__(self, **kwargs): super().__init__() - self.parameters: typing.Dict[str, typing.Optional[typing.Any]] = defaultdict(lambda: None) + self.parameters: dict[str, typing.Optional[typing.Any]] = defaultdict(lambda: None) self.new_parameters = self.__argument_class__() if self.__new_style__ else {} # pylint: disable=not-callable def set_image(self, image): @@ -196,7 +196,7 @@ def __init__(self, **kwargs): def get_additional_layers( self, full_segmentation: typing.Optional[np.ndarray] = None - ) -> typing.Dict[str, AdditionalLayerDescription]: + ) -> dict[str, AdditionalLayerDescription]: """ Create dict with standard additional layers. @@ -333,7 +333,7 @@ def calculation_run( def clean(self): super().clean() - self.parameters: typing.Dict[str, typing.Optional[typing.Any]] = defaultdict(lambda: None) + self.parameters: dict[str, typing.Optional[typing.Any]] = defaultdict(lambda: None) self.cleaned_image = None self.mask = None diff --git a/package/PartSegCore/sphinx/auto_parameters.py b/package/PartSegCore/sphinx/auto_parameters.py index 041b46aea..a25901afd 100644 --- a/package/PartSegCore/sphinx/auto_parameters.py +++ b/package/PartSegCore/sphinx/auto_parameters.py @@ -3,7 +3,7 @@ """ import inspect -from typing import Any, Dict +from typing import Any from sphinx.application import Sphinx from sphinx.ext.autodoc import ModuleLevelDocumenter @@ -74,7 +74,7 @@ def add_content(self, more_content: Any, **kwargs) -> None: self.add_line("", "autogenerated", len(self.object) + k) -def setup(app: Sphinx) -> Dict[str, Any]: +def setup(app: Sphinx) -> dict[str, Any]: app.connect("autodoc-process-docstring", algorithm_parameters_doc) app.add_autodocumenter(RegisterDocumenter) return {"version": "0.9", "env_version": 1, "parallel_read_safe": True} diff --git a/package/PartSegCore/utils.py b/package/PartSegCore/utils.py index 4fe110377..2372fb2fc 100644 --- a/package/PartSegCore/utils.py +++ b/package/PartSegCore/utils.py @@ -268,7 +268,7 @@ class ProfileDict: def __init__(self, klass=None, **kwargs): self._my_dict = EventedDict(klass, **kwargs) - self._callback_dict: typing.Dict[str, typing.List[CallbackBase]] = defaultdict(list) + self._callback_dict: dict[str, list[CallbackBase]] = defaultdict(list) self._my_dict.setted.connect(self._call_callback) self._my_dict.deleted.connect(self._call_callback) @@ -405,7 +405,7 @@ def filter_data(self): # pragma: no cover warnings.warn("Deprecated, use pop errors instead", FutureWarning, stacklevel=2) self.pop_errors() - def pop_errors(self) -> typing.List[typing.Tuple[str, dict]]: + def pop_errors(self) -> list[tuple[str, dict]]: """Remove problematic entries from dict""" error_list = [] for group, up_dkt in list(self.my_dict.items()): diff --git a/package/PartSegImage/channel_class.py b/package/PartSegImage/channel_class.py index 5c20ed6a3..4ec5279f0 100644 --- a/package/PartSegImage/channel_class.py +++ b/package/PartSegImage/channel_class.py @@ -1,5 +1,5 @@ from importlib.metadata import PackageNotFoundError, version -from typing import TYPE_CHECKING, Dict, Union +from typing import TYPE_CHECKING, Union try: PYDANTIC_2 = version("pydantic") >= "2.0.0" @@ -80,6 +80,6 @@ def __modify_schema__(cls, field_schema): @classmethod def __get_pydantic_json_schema__(cls, core_schema: "CoreSchema", handler: "GetJsonSchemaHandler"): - json_schema: Dict[str, Union[str, dict]] = {} + json_schema: dict[str, Union[str, dict]] = {} cls.__modify_schema__(json_schema) return json_schema diff --git a/package/PartSegImage/image.py b/package/PartSegImage/image.py index d4bce8874..73eca3b11 100644 --- a/package/PartSegImage/image.py +++ b/package/PartSegImage/image.py @@ -14,8 +14,8 @@ from PartSegImage.channel_class import Channel -Spacing = typing.Tuple[typing.Union[float, int], ...] -_IMAGE_DATA = typing.Union[typing.List[np.ndarray], np.ndarray] +Spacing = tuple[typing.Union[float, int], ...] +_IMAGE_DATA = typing.Union[list[np.ndarray], np.ndarray] _DEF = object() FRAME_THICKNESS = 2 diff --git a/package/PartSegImage/image_reader.py b/package/PartSegImage/image_reader.py index 6681202fd..f97c8bcb2 100644 --- a/package/PartSegImage/image_reader.py +++ b/package/PartSegImage/image_reader.py @@ -126,15 +126,15 @@ def return_order(cls) -> str: def __init__(self, callback_function: typing.Optional[typing.Callable[[str, int], typing.Any]] = None) -> None: self.default_spacing = 10**-6, 10**-6, 10**-6 self.spacing = self.default_spacing - self.channel_names: typing.List[str] = [] - self.colors: typing.List[typing.Optional[typing.Any]] = [] - self.ranges: typing.List[typing.Tuple[float, float]] = [] + self.channel_names: list[str] = [] + self.colors: list[typing.Optional[typing.Any]] = [] + self.ranges: list[tuple[float, float]] = [] if callback_function is None: self.callback_function = _empty else: self.callback_function = callback_function - def _get_channel_info(self) -> typing.List[ChannelInfo]: + def _get_channel_info(self) -> list[ChannelInfo]: return [ ChannelInfo(name=name, color_map=color, contrast_limits=contrast_limits) for name, color, contrast_limits in zip_longest(self.channel_names, self.colors, self.ranges) @@ -169,7 +169,7 @@ def read_image( image_path: typing.Union[str, Path], mask_path=None, callback_function: typing.Optional[typing.Callable] = None, - default_spacing: typing.Optional[typing.Tuple[float, float, float]] = None, + default_spacing: typing.Optional[tuple[float, float, float]] = None, ) -> Image: """ read image file with optional mask file @@ -188,7 +188,7 @@ def read_image( return instance.read(image_path, mask_path) @staticmethod - def _reduce_obsolete_dummy_axes(array, axes) -> typing.Tuple[np.ndarray, str]: + def _reduce_obsolete_dummy_axes(array, axes) -> tuple[np.ndarray, str]: """ If there are duplicates in axes string then remove dimensions of size one @@ -269,7 +269,7 @@ def read_image( image_path: typing.Union[str, Path, BytesIO], mask_path=None, callback_function: typing.Optional[typing.Callable] = None, - default_spacing: typing.Optional[typing.Tuple[float, float, float]] = None, + default_spacing: typing.Optional[tuple[float, float, float]] = None, ) -> Image: """ read image file with optional mask file @@ -316,9 +316,10 @@ def read(self, image_path: typing.Union[str, Path], mask_path=None, ext=None) -> with OifFile(image_path) as image_file: tiffs = tifffile.natural_sorted(image_file.glob("*.tif")) - with image_file.open_file(tiffs[0]) as tiff_buffer, tifffile.TiffFile( - tiff_buffer, name=tiffs[0] - ) as tif_file: + with ( + image_file.open_file(tiffs[0]) as tiff_buffer, + tifffile.TiffFile(tiff_buffer, name=tiffs[0]) as tif_file, + ): axes = image_file.series[0].axes + tif_file.series[0].axes image_data = image_file.asarray() image_data = self.update_array_shape(image_data, axes) @@ -411,10 +412,10 @@ class ObsepImageReader(BaseImageReader): def _search_for_files( self, directory: Path, - channels: typing.List["Element"], + channels: list["Element"], suffix: str = "", required: bool = False, - ) -> typing.List[Image]: + ) -> list[Image]: possible_extensions = [".tiff", ".tif", ".TIFF", ".TIF"] channel_list = [] for channel in channels: @@ -506,7 +507,7 @@ def report_func(): mask_data = mask_file.asarray() mask_data = self.update_array_shape(mask_data, mask_file.series[0].axes) if "C" in self.return_order(): - pos: typing.List[typing.Union[slice, int]] = [slice(None) for _ in range(mask_data.ndim)] + pos: list[typing.Union[slice, int]] = [slice(None) for _ in range(mask_data.ndim)] pos[self.return_order().index("C")] = 0 mask_data = mask_data[tuple(pos)] diff --git a/package/PartSegImage/image_writer.py b/package/PartSegImage/image_writer.py index 4957b8fac..f13d7caf9 100644 --- a/package/PartSegImage/image_writer.py +++ b/package/PartSegImage/image_writer.py @@ -124,7 +124,7 @@ def save(cls, image: Image, save_path: typing.Union[str, BytesIO, Path], compres """ data = image.get_image_for_save() spacing = image.get_um_spacing() - metadata: typing.Dict[str, typing.Any] = {"mode": "color", "unit": "\\u00B5m"} + metadata: dict[str, typing.Any] = {"mode": "color", "unit": "\\u00B5m"} if len(spacing) == 3: metadata["spacing"] = spacing[0] if image.channel_names is not None: @@ -150,7 +150,7 @@ def save_mask(cls, image: Image, save_path: typing.Union[str, Path], compression return mask_max = np.max(mask) mask = mask.astype(minimal_dtype(mask_max)) - metadata: typing.Dict[str, typing.Union[str, float]] = {"mode": "color", "unit": "\\u00B5m"} + metadata: dict[str, typing.Union[str, float]] = {"mode": "color", "unit": "\\u00B5m"} spacing = image.get_um_spacing() if len(spacing) == 3: metadata["spacing"] = spacing[0] diff --git a/package/tests/conftest.py b/package/tests/conftest.py index 68d484aea..4c16e173c 100644 --- a/package/tests/conftest.py +++ b/package/tests/conftest.py @@ -53,14 +53,14 @@ def image(tmp_path): data[1:-1, 1:5, 1:-1, 1] = 20 data[1:-1, -5:-1, 1:-1, 1] = 20 - return Image(data, (10**-3, 10**-3, 10**-3), axes_order="ZYXC", file_path=str(tmp_path / "test.tiff")) + return Image(data, spacing=(10**-3, 10**-3, 10**-3), axes_order="ZYXC", file_path=str(tmp_path / "test.tiff")) @pytest.fixture def image2(image, tmp_path): data = np.zeros([20, 20, 20, 1], dtype=np.uint8) data[10:-1, 1:-1, 1:-1, 0] = 20 - img = image.merge(Image(data, (10**-3, 10**-3, 10**-3), axes_order="ZYXC"), "C") + img = image.merge(Image(data, spacing=(10**-3, 10**-3, 10**-3), axes_order="ZYXC"), "C") img.file_path = str(tmp_path / "test2.tiff") return img diff --git a/package/tests/test_PartSeg/test_channel_control.py b/package/tests/test_PartSeg/test_channel_control.py index 430f1d994..c7b07e828 100644 --- a/package/tests/test_PartSeg/test_channel_control.py +++ b/package/tests/test_PartSeg/test_channel_control.py @@ -238,46 +238,54 @@ def test_color_combo_box_group_and_color_preview(self, qtbot): def check_parameters(name, index): return name == "test" and index == 1 - with qtbot.waitSignal(box.coloring_update), qtbot.waitSignal( - box.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(box.coloring_update), + qtbot.waitSignal(box.change_channel, check_params_cb=check_parameters), ): ch_property.fixed.setChecked(True) - with qtbot.waitSignal(box.coloring_update), qtbot.waitSignal( - box.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(box.coloring_update), + qtbot.waitSignal(box.change_channel, check_params_cb=check_parameters), ): ch_property.minimum_value.setValue(10) ch_property.maximum_value.setValue(10000) - with qtbot.waitSignal(box.coloring_update), qtbot.waitSignal( - box.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(box.coloring_update), + qtbot.waitSignal(box.change_channel, check_params_cb=check_parameters), ): ch_property.maximum_value.setValue(11000) - with qtbot.waitSignal(box.coloring_update), qtbot.waitSignal( - box.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(box.coloring_update), + qtbot.waitSignal(box.change_channel, check_params_cb=check_parameters), ): ch_property.fixed.setChecked(False) - with qtbot.waitSignal(box.coloring_update), qtbot.waitSignal( - box.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(box.coloring_update), + qtbot.waitSignal(box.change_channel, check_params_cb=check_parameters), ): ch_property.use_filter.setCurrentEnum(NoiseFilterType.Gauss) - with qtbot.waitSignal(box.coloring_update), qtbot.waitSignal( - box.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(box.coloring_update), + qtbot.waitSignal(box.change_channel, check_params_cb=check_parameters), ): ch_property.use_filter.setCurrentEnum(NoiseFilterType.Median) ch_property.filter_radius.setValue(0.5) - with qtbot.waitSignal(box.coloring_update), qtbot.waitSignal( - box.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(box.coloring_update), + qtbot.waitSignal(box.change_channel, check_params_cb=check_parameters), ): ch_property.filter_radius.setValue(2) - with qtbot.waitSignal(box.coloring_update), qtbot.waitSignal( - box.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(box.coloring_update), + qtbot.waitSignal(box.change_channel, check_params_cb=check_parameters), ): ch_property.use_filter.setCurrentEnum(NoiseFilterType.No) @@ -293,12 +301,14 @@ def check_parameters(name, index): return name == "test" and index == 1 if filter_value is NoiseFilterType.No: - with qtbot.waitSignal(image_view.channel_control.coloring_update), qtbot.waitSignal( - image_view.channel_control.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(image_view.channel_control.coloring_update), + qtbot.waitSignal(image_view.channel_control.change_channel, check_params_cb=check_parameters), ): ch_property.use_filter.setCurrentEnum(NoiseFilterType.Gauss) - with qtbot.waitSignal(image_view.channel_control.coloring_update), qtbot.waitSignal( - image_view.channel_control.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(image_view.channel_control.coloring_update), + qtbot.waitSignal(image_view.channel_control.change_channel, check_params_cb=check_parameters), ): ch_property.use_filter.setCurrentEnum(filter_value) image4 = image_view.viewer_widget.screenshot() @@ -323,23 +333,26 @@ def check_parameters(name, index): return name == "test" and index == 1 # Test fixed range - with qtbot.waitSignal(image_view.channel_control.coloring_update), qtbot.waitSignal( - image_view.channel_control.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(image_view.channel_control.coloring_update), + qtbot.waitSignal(image_view.channel_control.change_channel, check_params_cb=check_parameters), ): ch_property.fixed.setChecked(True) image1 = image_view.viewer_widget._render() assert np.any(image1 != 255) - with qtbot.waitSignal(image_view.channel_control.coloring_update), qtbot.waitSignal( - image_view.channel_control.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(image_view.channel_control.coloring_update), + qtbot.waitSignal(image_view.channel_control.change_channel, check_params_cb=check_parameters), ): ch_property.minimum_value.setValue(20) image2 = image_view.viewer_widget._render() assert np.any(image2 != 255) assert np.any(image1 != image2) - with qtbot.waitSignal(image_view.channel_control.coloring_update), qtbot.waitSignal( - image_view.channel_control.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(image_view.channel_control.coloring_update), + qtbot.waitSignal(image_view.channel_control.change_channel, check_params_cb=check_parameters), ): ch_property.maximum_value.setValue(11000) image3 = image_view.viewer_widget.screenshot(flash=False) @@ -347,8 +360,9 @@ def check_parameters(name, index): assert np.any(image2 != image3) assert np.any(image1 != image3) - with qtbot.waitSignal(image_view.channel_control.coloring_update), qtbot.waitSignal( - image_view.channel_control.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(image_view.channel_control.coloring_update), + qtbot.waitSignal(image_view.channel_control.change_channel, check_params_cb=check_parameters), ): ch_property.fixed.setChecked(False) @@ -357,8 +371,9 @@ def check_parameters(name, index): assert np.any(image1 != image2) assert np.any(image1 != image3) # Test gauss - with qtbot.waitSignal(image_view.channel_control.coloring_update), qtbot.waitSignal( - image_view.channel_control.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(image_view.channel_control.coloring_update), + qtbot.waitSignal(image_view.channel_control.change_channel, check_params_cb=check_parameters), ): ch_property.use_filter.setCurrentEnum(NoiseFilterType.Gauss) image4 = image_view.viewer_widget.screenshot(flash=False) @@ -366,8 +381,9 @@ def check_parameters(name, index): assert np.any(image1 != image4) assert np.any(image2 != image4) assert np.any(image3 != image4) - with qtbot.waitSignal(image_view.channel_control.coloring_update), qtbot.waitSignal( - image_view.channel_control.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(image_view.channel_control.coloring_update), + qtbot.waitSignal(image_view.channel_control.change_channel, check_params_cb=check_parameters), ): ch_property.filter_radius.setValue(1) image5 = image_view.viewer_widget.screenshot(flash=False) @@ -387,22 +403,25 @@ def check_parameters(name, index): # Test gauss and fixed range ch_property.minimum_value.setValue(100) ch_property.maximum_value.setValue(10000) - with qtbot.waitSignal(image_view.channel_control.coloring_update), qtbot.waitSignal( - image_view.channel_control.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(image_view.channel_control.coloring_update), + qtbot.waitSignal(image_view.channel_control.change_channel, check_params_cb=check_parameters), ): ch_property.fixed.setChecked(True) image_view.viewer_widget.screenshot(flash=False) image1 = image_view.viewer_widget._render() - with qtbot.waitSignal(image_view.channel_control.coloring_update), qtbot.waitSignal( - image_view.channel_control.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(image_view.channel_control.coloring_update), + qtbot.waitSignal(image_view.channel_control.change_channel, check_params_cb=check_parameters), ): ch_property.minimum_value.setValue(10) image2 = image_view.viewer_widget.screenshot() assert np.any(image2 != 255) assert np.any(image1 != image2) - with qtbot.waitSignal(image_view.channel_control.coloring_update), qtbot.waitSignal( - image_view.channel_control.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(image_view.channel_control.coloring_update), + qtbot.waitSignal(image_view.channel_control.change_channel, check_params_cb=check_parameters), ): ch_property.maximum_value.setValue(11000) image3 = image_view.viewer_widget.screenshot() @@ -410,8 +429,9 @@ def check_parameters(name, index): assert np.any(image2 != image3) assert np.any(image1 != image3) - with qtbot.waitSignal(image_view.channel_control.coloring_update), qtbot.waitSignal( - image_view.channel_control.change_channel, check_params_cb=check_parameters + with ( + qtbot.waitSignal(image_view.channel_control.coloring_update), + qtbot.waitSignal(image_view.channel_control.change_channel, check_params_cb=check_parameters), ): ch_property.fixed.setChecked(False) diff --git a/package/tests/test_PartSeg/test_common_backend.py b/package/tests/test_PartSeg/test_common_backend.py index 1b115c386..cc3f94bdc 100644 --- a/package/tests/test_PartSeg/test_common_backend.py +++ b/package/tests/test_PartSeg/test_common_backend.py @@ -364,7 +364,7 @@ def get_name(cls) -> str: return "test" @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return [AlgorithmProperty("a", "A", 0)] diff --git a/package/tests/test_PartSeg/test_common_gui.py b/package/tests/test_PartSeg/test_common_gui.py index 2a33fa41e..26de8f4a5 100644 --- a/package/tests/test_PartSeg/test_common_gui.py +++ b/package/tests/test_PartSeg/test_common_gui.py @@ -874,7 +874,7 @@ def get_name(cls) -> str: return "1" @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return [AlgorithmProperty("field", "Field", 1)] class SampleClass2(AlgorithmDescribeBase): @@ -883,7 +883,7 @@ def get_name(cls) -> str: return "2" @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return [AlgorithmProperty("field_", "Field", 2)] SampleSelection.register(SampleClass1) @@ -1965,8 +1965,8 @@ def test_labels_meth(cls_): def test_save_colormap_in_settings(part_settings): class DummyColormap(typing.NamedTuple): - colors: typing.List[typing.List[float]] - controls: typing.List[float] + colors: list[list[float]] + controls: list[float] assert "custom_aaa" not in part_settings.colormap_dict cmap = Colormap([[0, 0, 0, 0], [1, 1, 1, 1]], controls=[0, 1]) diff --git a/package/tests/test_PartSegCore/segmentation/test_segmentation_algorithm.py b/package/tests/test_PartSegCore/segmentation/test_segmentation_algorithm.py index 7ef6fd6d3..fe665f5e7 100644 --- a/package/tests/test_PartSegCore/segmentation/test_segmentation_algorithm.py +++ b/package/tests/test_PartSegCore/segmentation/test_segmentation_algorithm.py @@ -1,5 +1,3 @@ -from typing import Type - import numpy as np import pytest @@ -47,7 +45,7 @@ def _param2(self): @pytest.mark.parametrize("algorithm", restartable_list + algorithm_list) @pytest.mark.parametrize("masking", [True, False]) -def test_segmentation_algorithm(image, algorithm: Type[ROIExtractionAlgorithm], masking): +def test_segmentation_algorithm(image, algorithm: type[ROIExtractionAlgorithm], masking): assert algorithm.support_z() is True assert algorithm.support_time() is False assert isinstance(algorithm.get_steps_num(), int) diff --git a/package/tests/test_PartSegCore/test_algorithm_describe_base.py b/package/tests/test_PartSegCore/test_algorithm_describe_base.py index 842eaaa78..3c1f1292d 100644 --- a/package/tests/test_PartSegCore/test_algorithm_describe_base.py +++ b/package/tests/test_PartSegCore/test_algorithm_describe_base.py @@ -73,7 +73,7 @@ def get_name(cls) -> str: return "test1" @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return [] class Class2(AlgorithmDescribeBase): @@ -82,7 +82,7 @@ def get_name(cls) -> str: return "test2" @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return [] TestSelection.register(Class1) @@ -379,7 +379,7 @@ def get_name(cls) -> str: return "1" @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return [] class SampleClass2(AlgorithmDescribeBase): @@ -388,7 +388,7 @@ def get_name(cls) -> str: return "2" @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return [] SampleSelection.register(SampleClass1) @@ -482,7 +482,7 @@ def get_name(cls) -> str: return "sample" @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return ["aaaa", AlgorithmProperty("name", "Name", 1, options_range=(1, 10), help_text="ceeeec")] assert SampleAlgorithm.get_name() == "sample" @@ -528,7 +528,7 @@ def get_name(cls) -> str: return "sample2" @classmethod - def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]: + def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]: return [ *super().get_fields(), AlgorithmProperty("name2", "Name 2", 3.0, options_range=(1, 10), help_text="deeeed"), diff --git a/package/tests/test_PartSegCore/test_class_generator.py b/package/tests/test_PartSegCore/test_class_generator.py index a4281ac8a..e615cf745 100644 --- a/package/tests/test_PartSegCore/test_class_generator.py +++ b/package/tests/test_PartSegCore/test_class_generator.py @@ -142,7 +142,7 @@ class Test(BaseSerializableClass): field1: typing.Union[str, float] field2: typing.Any field3: typing.Optional[int] - field4: typing.List[str] + field4: list[str] val = Test("a", [1, 2, 3], 5, ["a", "b"]) assert val.field1 == "a" @@ -159,7 +159,7 @@ class Test2(BaseSerializableClass): field1: typing.Union[str, float] field2: typing.Any field3: typing.Optional[int] - field4: typing.List[str] + field4: list[str] field5: typing.Optional[MyClass] Test2("aa", 1, None, ["b", "c"], MyClass()) @@ -180,10 +180,10 @@ class Test1(BaseSerializableClass): list2: typing.Union[str, int] class Test2(BaseSerializableClass): - list1: typing.List[int] - list2: typing.List - dict1: typing.Dict[str, int] - dict2: typing.Dict + list1: list[int] + list2: list + dict1: dict[str, int] + dict2: dict empty(Test1, Test2) base_serialize_register.clear() diff --git a/package/tests/test_PartSegCore/test_class_register.py b/package/tests/test_PartSegCore/test_class_register.py index 5c756cf97..494c3af61 100644 --- a/package/tests/test_PartSegCore/test_class_register.py +++ b/package/tests/test_PartSegCore/test_class_register.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any import pytest from local_migrator import REGISTER, class_to_str, register_class, rename_key, update_argument @@ -9,7 +9,7 @@ class SampleClass1: pass -def rename_a_to_c(dkt: Dict[str, Any]) -> Dict[str, Any]: +def rename_a_to_c(dkt: dict[str, Any]) -> dict[str, Any]: dkt = dict(dkt) dkt["c"] = dkt["a"] del dkt["a"] diff --git a/package/tests/test_PartSegCore/test_io.py b/package/tests/test_PartSegCore/test_io.py index 45028287d..d57fd8788 100644 --- a/package/tests/test_PartSegCore/test_io.py +++ b/package/tests/test_PartSegCore/test_io.py @@ -10,7 +10,6 @@ from glob import glob from io import BytesIO from pathlib import Path -from typing import Type import h5py import numpy as np @@ -205,7 +204,7 @@ def test_save_roi_info_project_tuple(self, analysis_segmentation2, tmp_path): def test_save_roi_info_mask_project(self, stack_segmentation2, tmp_path): self.perform_roi_info_test(stack_segmentation2, tmp_path, SaveROI, LoadROI) - def perform_roi_info_test(self, project, save_path, save_method: Type[SaveBase], load_method: Type[LoadBase]): + def perform_roi_info_test(self, project, save_path, save_method: type[SaveBase], load_method: type[LoadBase]): assert save_method.get_short_name().lower() == save_method.get_short_name() assert save_method.get_short_name().isalpha() assert load_method.get_short_name().lower() == load_method.get_short_name() @@ -231,7 +230,7 @@ def test_save_roi_info_history_mask_project(self, stack_segmentation2, mask_prop self.perform_roi_info_history_test(stack_segmentation2, tmp_path, mask_property, SaveROI, LoadROI) def perform_roi_info_history_test( - self, project, save_path, mask_property, save_method: Type[SaveBase], load_method: Type[LoadBase] + self, project, save_path, mask_property, save_method: type[SaveBase], load_method: type[LoadBase] ): alt1 = np.copy(project.roi_info.roi) alt1[alt1 > 0] += 3 diff --git a/package/tests/test_PartSegCore/test_segmentation.py b/package/tests/test_PartSegCore/test_segmentation.py index a5478413d..ff896ad25 100644 --- a/package/tests/test_PartSegCore/test_segmentation.py +++ b/package/tests/test_PartSegCore/test_segmentation.py @@ -3,7 +3,7 @@ import operator from abc import ABC from copy import deepcopy -from typing import List, Type, Union +from typing import Union import numpy as np import pytest @@ -85,7 +85,7 @@ def empty(_s: str, _i: int): def test_base_parameters(algorithm_name): algorithm_class = AnalysisAlgorithmSelection[algorithm_name] assert algorithm_class.get_name() == algorithm_name - algorithm_class: Type[ROIExtractionAlgorithm] + algorithm_class: type[ROIExtractionAlgorithm] obj = algorithm_class() values = algorithm_class.get_default_values() obj.set_parameters(values) @@ -119,7 +119,7 @@ def get_base_object(): def get_side_object(): raise NotImplementedError - def get_algorithm_class(self) -> Type[ROIExtractionAlgorithm]: + def get_algorithm_class(self) -> type[ROIExtractionAlgorithm]: raise NotImplementedError @@ -172,7 +172,7 @@ def get_base_object(): def get_side_object(): return get_two_parts_side() - def get_algorithm_class(self) -> Type[ROIExtractionAlgorithm]: + def get_algorithm_class(self) -> type[ROIExtractionAlgorithm]: return sa.LowerThresholdAlgorithm @@ -194,7 +194,7 @@ def get_base_object(): def get_side_object(): return get_two_parts_side_reversed() - def get_algorithm_class(self) -> Type[ROIExtractionAlgorithm]: + def get_algorithm_class(self) -> type[ROIExtractionAlgorithm]: return sa.UpperThresholdAlgorithm @@ -345,7 +345,7 @@ class TestLowerThresholdFlow(BaseFlowThreshold): get_side_object = staticmethod(get_two_parts_side) get_multiple_part = staticmethod(get_multiple_part) - def get_algorithm_class(self) -> Type[ROIExtractionAlgorithm]: + def get_algorithm_class(self) -> type[ROIExtractionAlgorithm]: return sa.LowerThresholdFlowAlgorithm @@ -369,7 +369,7 @@ class TestUpperThresholdFlow(BaseFlowThreshold): get_side_object = staticmethod(get_two_parts_side_reversed) get_multiple_part = staticmethod(get_multiple_part_reversed) - def get_algorithm_class(self) -> Type[ROIExtractionAlgorithm]: + def get_algorithm_class(self) -> type[ROIExtractionAlgorithm]: return sa.UpperThresholdFlowAlgorithm @@ -514,7 +514,7 @@ def test_dilate(self, radius_type, radius, time): clip_to_mask=False, ) res_array1 = np.zeros((1, 30, 30, 30), dtype=np.uint8) - slices: List[Union[int, slice]] = [slice(None)] * 4 + slices: list[Union[int, slice]] = [slice(None)] * 4 for i in range(1, 4): slices[i] = slice(10 - radius, 20 + radius) if radius_type == RadiusType.R2D: diff --git a/pyproject.toml b/pyproject.toml index 85ac00970..11107ebf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -33,46 +32,46 @@ keywords = [ "bioimaging", "GUI", ] -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = [ "IPython>=7.7.0", "PartSegCore-compiled-backend>=0.13.11,<0.16.0", "PartSegData==0.10.0", "QtAwesome!=1.2.0,>=1.0.3", "QtPy>=1.10.0", - "SimpleITK>=2.0.0", + "SimpleITK>=2.1.0", "appdirs>=1.4.4", "czifile>=2019.5.22", "defusedxml>=0.6.0", "fonticon-fontawesome6>=6.1.1", "h5py>=3.3.0", "imagecodecs>=2020.5.30", - "imageio>=2.5.0", + "imageio>=2.20.0", "ipykernel>=5.2.0", "local-migrator>=0.1.7", "magicgui!=0.5.0,>=0.4.0", - "mahotas>=1.4.10", - "napari>=0.4.14", + "mahotas>=1.4.12", + "napari>=0.4.19", "nme>=0.1.7", - "numpy>=1.18.5 ; python_version >= '3.10'", - "numpy>=1.18.5, <2 ; python_version < '3.10'", + "numpy>=1.22.2 ; python_version >= '3.10'", + "numpy>=1.22.2, <2 ; python_version < '3.10'", "oiffile>=2020.1.18", - "openpyxl>=2.5.7", - "packaging>=20.0", - "pandas>=1.1.0", - "psygnal>=0.3.1", + "openpyxl>=3.0.7", + "packaging>=22.0", + "pandas>=1.3.0", + "psygnal>=0.3.4", "pydantic>=1.9.1,<3", "pygments>=2.12.0", "qtconsole>=4.7.7", "requests>=2.25.0", - "scipy>=1.4.1", + "scipy>=1.5.4", "sentry-sdk>=2.4.0", "six>=1.11.0", - "superqt>=0.3.0", - "sympy>=1.1.1", + "superqt>=0.4.1", + "sympy>=1.10", "tifffile>=2020.9.30", "traceback-with-variables>=2.0.4", - "vispy>=0.9.4", + "vispy>=0.14.1 ", "xlrd>=1.1.0", "xlsxwriter>=2.0.0", ] @@ -128,7 +127,7 @@ pyqt5 = [ ] pyqt6 = [ "PyQt6", - "napari[pyqt6]>=0.5.0; python_version >= '3.9'", + "napari[pyqt6]>=0.5.0", ] pyside = [ "PartSeg[pyside2]", @@ -139,7 +138,7 @@ pyside2 = [ ] pyside6 = [ "PySide6", - "napari[pyside6_experimental]>=0.5.0; python_version >= '3.9'", + "napari[pyside6_experimental]>=0.5.0", ] test = [ "coverage", @@ -268,7 +267,7 @@ exclude_also = [ [tool.black] line-length = 120 -target-version = ['py38'] +target-version = ['py39'] include = '\.pyi?$' exclude = ''' @@ -294,7 +293,7 @@ exclude = ''' [tool.ruff] line-length = 120 exclude = ["examples/call_simple_threshold.py"] -target-version = "py38" +target-version = "py39" fix = true [tool.ruff.lint] diff --git a/requirements/constraints_py3.8.txt b/requirements/constraints_py3.8.txt deleted file mode 100644 index 6871aac26..000000000 --- a/requirements/constraints_py3.8.txt +++ /dev/null @@ -1,531 +0,0 @@ -# This file was autogenerated by uv via the following command: -# uv pip compile --python-version 3.8 --output-file requirements/constraints_py3.8.txt pyproject.toml requirements/version_denylist.txt --extra pyqt6 --extra pyside2 --extra pyside6 --extra test --extra pyinstaller_base -alabaster==0.7.13 - # via sphinx -altgraph==0.17.4 - # via pyinstaller -annotated-types==0.7.0 - # via pydantic -app-model==0.2.8 - # via napari -appdirs==1.4.4 - # via - # partseg (pyproject.toml) - # napari - # npe2 -asttokens==2.4.1 - # via stack-data -attrs==24.2.0 - # via - # jsonschema - # referencing -babel==2.16.0 - # via sphinx -backcall==0.2.0 - # via ipython -build==1.2.2.post1 - # via npe2 -cachey==0.2.1 - # via napari -certifi==2024.8.30 - # via - # napari - # requests - # sentry-sdk -charset-normalizer==3.4.0 - # via requests -click==8.1.7 - # via - # dask - # typer -cloudpickle==3.1.0 - # via dask -comm==0.2.2 - # via ipykernel -coverage==7.6.1 - # via partseg (pyproject.toml) -czifile==2019.7.2 - # via partseg (pyproject.toml) -dask==2023.5.0 - # via napari -debugpy==1.8.7 - # via ipykernel -decorator==5.1.1 - # via ipython -defusedxml==0.7.1 - # via partseg (pyproject.toml) -docstring-parser==0.16 - # via magicgui -docutils==0.20.1 - # via sphinx -et-xmlfile==1.1.0 - # via openpyxl -exceptiongroup==1.2.2 - # via pytest -executing==2.1.0 - # via stack-data -fonticon-fontawesome6==6.4.0 - # via partseg (pyproject.toml) -freetype-py==2.5.1 - # via vispy -fsspec==2024.9.0 - # via dask -h5py==3.11.0 - # via partseg (pyproject.toml) -heapdict==1.0.1 - # via cachey -hsluv==5.0.4 - # via vispy -idna==3.10 - # via requests -imagecodecs==2023.3.16 - # via partseg (pyproject.toml) -imageio==2.35.1 - # via - # -r requirements/version_denylist.txt - # partseg (pyproject.toml) - # napari - # napari-svg - # scikit-image -imagesize==1.4.1 - # via sphinx -importlib-metadata==8.5.0 - # via - # build - # dask - # jupyter-client - # pyinstaller - # pyinstaller-hooks-contrib - # sphinx -importlib-resources==6.4.5 - # via - # jsonschema - # jsonschema-specifications - # vispy -in-n-out==0.2.1 - # via app-model -iniconfig==2.0.0 - # via pytest -ipykernel==6.29.5 - # via - # partseg (pyproject.toml) - # napari-console - # qtconsole -ipython==8.12.3 - # via - # partseg (pyproject.toml) - # ipykernel - # napari-console -jedi==0.19.1 - # via ipython -jinja2==3.1.4 - # via sphinx -jsonschema==4.23.0 - # via napari -jsonschema-specifications==2023.12.1 - # via jsonschema -jupyter-client==8.6.3 - # via - # ipykernel - # qtconsole -jupyter-core==5.7.2 - # via - # ipykernel - # jupyter-client - # qtconsole -kiwisolver==1.4.7 - # via vispy -lazy-loader==0.4 - # via - # napari - # scikit-image -local-migrator==0.1.10 - # via - # partseg (pyproject.toml) - # nme -locket==1.0.0 - # via partd -lxml==5.3.0 - # via - # partseg (pyproject.toml) - # lxml-html-clean -lxml-html-clean==0.3.1 - # via lxml -magicgui==0.9.1 - # via - # partseg (pyproject.toml) - # napari -mahotas==1.4.18 - # via partseg (pyproject.toml) -markdown-it-py==3.0.0 - # via rich -markupsafe==2.1.5 - # via jinja2 -matplotlib-inline==0.1.7 - # via - # ipykernel - # ipython -mdurl==0.1.2 - # via markdown-it-py -mpmath==1.3.0 - # via sympy -napari==0.4.19.post1 - # via partseg (pyproject.toml) -napari-console==0.0.9 - # via napari -napari-plugin-engine==0.2.0 - # via - # napari - # napari-svg -napari-svg==0.1.10 - # via napari -nest-asyncio==1.6.0 - # via ipykernel -networkx==3.1 - # via scikit-image -nme==0.1.8 - # via partseg (pyproject.toml) -npe2==0.7.7 - # via - # -r requirements/version_denylist.txt - # napari -numpy==1.24.4 - # via - # partseg (pyproject.toml) - # czifile - # dask - # h5py - # imagecodecs - # imageio - # mahotas - # napari - # napari-svg - # oiffile - # pandas - # partsegcore-compiled-backend - # pywavelets - # scikit-image - # scipy - # tifffile - # vispy -numpydoc==1.7.0 - # via napari -oiffile==2022.9.29 - # via partseg (pyproject.toml) -openpyxl==3.1.5 - # via partseg (pyproject.toml) -packaging==24.1 - # via - # partseg (pyproject.toml) - # build - # dask - # ipykernel - # lazy-loader - # local-migrator - # pooch - # pyinstaller - # pyinstaller-hooks-contrib - # pytest - # qtconsole - # qtpy - # scikit-image - # sphinx - # vispy -pandas==2.0.3 - # via - # partseg (pyproject.toml) - # napari -parso==0.8.4 - # via jedi -partd==1.4.1 - # via dask -partsegcore-compiled-backend==0.15.1 - # via partseg (pyproject.toml) -partsegdata==0.10.0 - # via partseg (pyproject.toml) -pexpect==4.9.0 - # via ipython -pickleshare==0.7.5 - # via ipython -pillow==10.4.0 - # via - # imageio - # napari - # scikit-image -pint==0.21.1 - # via napari -pkgutil-resolve-name==1.3.10 - # via jsonschema -platformdirs==4.3.6 - # via - # jupyter-core - # pooch -pluggy==1.5.0 - # via - # pytest - # pytest-qt -pooch==1.8.2 - # via scikit-image -prompt-toolkit==3.0.48 - # via ipython -psutil==6.0.0 - # via - # ipykernel - # napari -psygnal==0.11.1 - # via - # partseg (pyproject.toml) - # app-model - # magicgui - # napari - # npe2 -ptyprocess==0.7.0 - # via pexpect -pure-eval==0.2.3 - # via stack-data -pyconify==0.1.6 - # via superqt -pydantic==2.9.2 - # via - # partseg (pyproject.toml) - # app-model - # napari - # npe2 - # pydantic-compat -pydantic-compat==0.1.2 - # via app-model -pydantic-core==2.23.4 - # via pydantic -pygments==2.18.0 - # via - # partseg (pyproject.toml) - # ipython - # napari - # qtconsole - # rich - # sphinx - # superqt -pyinstaller==6.10.0 - # via partseg (pyproject.toml) -pyinstaller-hooks-contrib==2024.8 - # via pyinstaller -pyopengl==3.1.7 - # via napari -pyproject-hooks==1.2.0 - # via build -pyqt6==6.7.1 - # via partseg (pyproject.toml) -pyqt6-qt6==6.7.3 - # via pyqt6 -pyqt6-sip==13.8.0 - # via pyqt6 -pyside2==5.15.2.1 - # via - # -r requirements/version_denylist.txt - # partseg (pyproject.toml) - # napari -pyside6==6.3.1 - # via - # -r requirements/version_denylist.txt - # partseg (pyproject.toml) -pyside6-addons==6.3.1 - # via pyside6 -pyside6-essentials==6.3.1 - # via - # pyside6 - # pyside6-addons -pytest==8.3.3 - # via - # partseg (pyproject.toml) - # pytest-qt - # pytest-timeout -pytest-qt==4.4.0 - # via partseg (pyproject.toml) -pytest-timeout==2.3.1 - # via partseg (pyproject.toml) -python-dateutil==2.9.0.post0 - # via - # jupyter-client - # pandas -pytz==2024.2 - # via - # babel - # pandas -pywavelets==1.4.1 - # via scikit-image -pyyaml==6.0.2 - # via - # dask - # napari - # npe2 -pyzmq==26.2.0 - # via - # ipykernel - # jupyter-client -qtawesome==1.3.1 - # via partseg (pyproject.toml) -qtconsole==5.6.0 - # via - # partseg (pyproject.toml) - # napari-console -qtpy==2.4.1 - # via - # partseg (pyproject.toml) - # magicgui - # napari - # napari-console - # qtawesome - # qtconsole - # superqt -referencing==0.35.1 - # via - # jsonschema - # jsonschema-specifications -requests==2.32.3 - # via - # partseg (pyproject.toml) - # pooch - # pyconify - # sphinx -rich==13.9.2 - # via - # npe2 - # typer -rpds-py==0.20.0 - # via - # jsonschema - # referencing -scikit-image==0.21.0 - # via - # partseg (pyproject.toml) - # napari -scipy==1.10.1 - # via - # partseg (pyproject.toml) - # napari - # scikit-image -sentry-sdk==2.16.0 - # via - # -r requirements/version_denylist.txt - # partseg (pyproject.toml) -setuptools==75.1.0 - # via - # pyinstaller - # pyinstaller-hooks-contrib -shellingham==1.5.4 - # via typer -shiboken2==5.15.2.1 - # via pyside2 -shiboken6==6.3.1 - # via - # pyside6 - # pyside6-addons - # pyside6-essentials -simpleitk==2.4.0 - # via partseg (pyproject.toml) -six==1.16.0 - # via - # partseg (pyproject.toml) - # asttokens - # python-dateutil -snowballstemmer==2.2.0 - # via sphinx -sphinx==7.1.2 - # via numpydoc -sphinxcontrib-applehelp==1.0.4 - # via sphinx -sphinxcontrib-devhelp==1.0.2 - # via sphinx -sphinxcontrib-htmlhelp==2.0.1 - # via sphinx -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-qthelp==1.0.3 - # via sphinx -sphinxcontrib-serializinghtml==1.1.5 - # via sphinx -stack-data==0.6.3 - # via ipython -superqt==0.6.7 - # via - # partseg (pyproject.toml) - # magicgui - # napari -sympy==1.13.3 - # via partseg (pyproject.toml) -tabulate==0.9.0 - # via numpydoc -tifffile==2023.7.10 - # via - # partseg (pyproject.toml) - # czifile - # napari - # oiffile - # scikit-image -tomli==2.0.2 - # via - # build - # npe2 - # numpydoc - # pytest -tomli-w==1.0.0 - # via npe2 -toolz==1.0.0 - # via - # dask - # napari - # partd -tornado==6.4.1 - # via - # ipykernel - # jupyter-client -tqdm==4.66.5 - # via napari -traceback-with-variables==2.0.4 - # via partseg (pyproject.toml) -traitlets==5.14.3 - # via - # comm - # ipykernel - # ipython - # jupyter-client - # jupyter-core - # matplotlib-inline - # qtconsole -typer==0.12.5 - # via npe2 -typing-extensions==4.12.2 - # via - # annotated-types - # app-model - # ipython - # magicgui - # napari - # pydantic - # pydantic-core - # rich - # superqt - # typer -tzdata==2024.2 - # via pandas -urllib3==2.2.3 - # via - # requests - # sentry-sdk -vispy==0.14.2 - # via - # partseg (pyproject.toml) - # napari - # napari-svg -wcwidth==0.2.13 - # via prompt-toolkit -wrapt==1.16.0 - # via napari -xlrd==2.0.1 - # via partseg (pyproject.toml) -xlsxwriter==3.2.0 - # via partseg (pyproject.toml) -zipp==3.20.2 - # via - # importlib-metadata - # importlib-resources diff --git a/requirements/constraints_py3.8_pydantic_1.txt b/requirements/constraints_py3.8_pydantic_1.txt deleted file mode 100644 index d2ec68fd5..000000000 --- a/requirements/constraints_py3.8_pydantic_1.txt +++ /dev/null @@ -1,526 +0,0 @@ -# This file was autogenerated by uv via the following command: -# uv pip compile --python-version 3.8 --output-file requirements/constraints_py3.8_pydantic_1.txt pyproject.toml requirements/version_denylist.txt --extra pyqt6 --extra pyside2 --extra pyside6 --extra test --extra pyinstaller_base --constraint requirements/pydantic_1.txt -alabaster==0.7.13 - # via sphinx -altgraph==0.17.4 - # via pyinstaller -app-model==0.2.8 - # via napari -appdirs==1.4.4 - # via - # partseg (pyproject.toml) - # napari - # npe2 -asttokens==2.4.1 - # via stack-data -attrs==24.2.0 - # via - # jsonschema - # referencing -babel==2.16.0 - # via sphinx -backcall==0.2.0 - # via ipython -build==1.2.2.post1 - # via npe2 -cachey==0.2.1 - # via napari -certifi==2024.8.30 - # via - # napari - # requests - # sentry-sdk -charset-normalizer==3.4.0 - # via requests -click==8.1.7 - # via - # dask - # typer -cloudpickle==3.1.0 - # via dask -comm==0.2.2 - # via ipykernel -coverage==7.6.1 - # via partseg (pyproject.toml) -czifile==2019.7.2 - # via partseg (pyproject.toml) -dask==2023.5.0 - # via napari -debugpy==1.8.7 - # via ipykernel -decorator==5.1.1 - # via ipython -defusedxml==0.7.1 - # via partseg (pyproject.toml) -docstring-parser==0.16 - # via magicgui -docutils==0.20.1 - # via sphinx -et-xmlfile==1.1.0 - # via openpyxl -exceptiongroup==1.2.2 - # via pytest -executing==2.1.0 - # via stack-data -fonticon-fontawesome6==6.4.0 - # via partseg (pyproject.toml) -freetype-py==2.5.1 - # via vispy -fsspec==2024.9.0 - # via dask -h5py==3.11.0 - # via partseg (pyproject.toml) -heapdict==1.0.1 - # via cachey -hsluv==5.0.4 - # via vispy -idna==3.10 - # via requests -imagecodecs==2023.3.16 - # via partseg (pyproject.toml) -imageio==2.35.1 - # via - # -r requirements/version_denylist.txt - # partseg (pyproject.toml) - # napari - # napari-svg - # scikit-image -imagesize==1.4.1 - # via sphinx -importlib-metadata==8.5.0 - # via - # build - # dask - # jupyter-client - # pyinstaller - # pyinstaller-hooks-contrib - # sphinx -importlib-resources==6.4.5 - # via - # jsonschema - # jsonschema-specifications - # vispy -in-n-out==0.2.1 - # via app-model -iniconfig==2.0.0 - # via pytest -ipykernel==6.29.5 - # via - # partseg (pyproject.toml) - # napari-console - # qtconsole -ipython==8.12.3 - # via - # partseg (pyproject.toml) - # ipykernel - # napari-console -jedi==0.19.1 - # via ipython -jinja2==3.1.4 - # via sphinx -jsonschema==4.23.0 - # via napari -jsonschema-specifications==2023.12.1 - # via jsonschema -jupyter-client==8.6.3 - # via - # ipykernel - # qtconsole -jupyter-core==5.7.2 - # via - # ipykernel - # jupyter-client - # qtconsole -kiwisolver==1.4.7 - # via vispy -lazy-loader==0.4 - # via - # napari - # scikit-image -local-migrator==0.1.10 - # via - # partseg (pyproject.toml) - # nme -locket==1.0.0 - # via partd -lxml==5.3.0 - # via - # partseg (pyproject.toml) - # lxml-html-clean -lxml-html-clean==0.3.1 - # via lxml -magicgui==0.9.1 - # via - # partseg (pyproject.toml) - # napari -mahotas==1.4.18 - # via partseg (pyproject.toml) -markdown-it-py==3.0.0 - # via rich -markupsafe==2.1.5 - # via jinja2 -matplotlib-inline==0.1.7 - # via - # ipykernel - # ipython -mdurl==0.1.2 - # via markdown-it-py -mpmath==1.3.0 - # via sympy -napari==0.4.19.post1 - # via partseg (pyproject.toml) -napari-console==0.0.9 - # via napari -napari-plugin-engine==0.2.0 - # via - # napari - # napari-svg -napari-svg==0.1.10 - # via napari -nest-asyncio==1.6.0 - # via ipykernel -networkx==3.1 - # via scikit-image -nme==0.1.8 - # via partseg (pyproject.toml) -npe2==0.7.7 - # via - # -r requirements/version_denylist.txt - # napari -numpy==1.24.4 - # via - # partseg (pyproject.toml) - # czifile - # dask - # h5py - # imagecodecs - # imageio - # mahotas - # napari - # napari-svg - # oiffile - # pandas - # partsegcore-compiled-backend - # pywavelets - # scikit-image - # scipy - # tifffile - # vispy -numpydoc==1.7.0 - # via napari -oiffile==2022.9.29 - # via partseg (pyproject.toml) -openpyxl==3.1.5 - # via partseg (pyproject.toml) -packaging==24.1 - # via - # partseg (pyproject.toml) - # build - # dask - # ipykernel - # lazy-loader - # local-migrator - # pooch - # pyinstaller - # pyinstaller-hooks-contrib - # pytest - # qtconsole - # qtpy - # scikit-image - # sphinx - # vispy -pandas==2.0.3 - # via - # partseg (pyproject.toml) - # napari -parso==0.8.4 - # via jedi -partd==1.4.1 - # via dask -partsegcore-compiled-backend==0.15.1 - # via partseg (pyproject.toml) -partsegdata==0.10.0 - # via partseg (pyproject.toml) -pexpect==4.9.0 - # via ipython -pickleshare==0.7.5 - # via ipython -pillow==10.4.0 - # via - # imageio - # napari - # scikit-image -pint==0.21.1 - # via napari -pkgutil-resolve-name==1.3.10 - # via jsonschema -platformdirs==4.3.6 - # via - # jupyter-core - # pooch -pluggy==1.5.0 - # via - # pytest - # pytest-qt -pooch==1.8.2 - # via scikit-image -prompt-toolkit==3.0.48 - # via ipython -psutil==6.0.0 - # via - # ipykernel - # napari -psygnal==0.11.1 - # via - # partseg (pyproject.toml) - # app-model - # magicgui - # napari - # npe2 -ptyprocess==0.7.0 - # via pexpect -pure-eval==0.2.3 - # via stack-data -pyconify==0.1.6 - # via superqt -pydantic==1.10.18 - # via - # -c requirements/pydantic_1.txt - # partseg (pyproject.toml) - # app-model - # napari - # npe2 - # pydantic-compat -pydantic-compat==0.1.2 - # via app-model -pygments==2.18.0 - # via - # partseg (pyproject.toml) - # ipython - # napari - # qtconsole - # rich - # sphinx - # superqt -pyinstaller==6.10.0 - # via partseg (pyproject.toml) -pyinstaller-hooks-contrib==2024.8 - # via pyinstaller -pyopengl==3.1.7 - # via napari -pyproject-hooks==1.2.0 - # via build -pyqt6==6.7.1 - # via partseg (pyproject.toml) -pyqt6-qt6==6.7.3 - # via pyqt6 -pyqt6-sip==13.8.0 - # via pyqt6 -pyside2==5.15.2.1 - # via - # -r requirements/version_denylist.txt - # partseg (pyproject.toml) - # napari -pyside6==6.3.1 - # via - # -r requirements/version_denylist.txt - # partseg (pyproject.toml) -pyside6-addons==6.3.1 - # via pyside6 -pyside6-essentials==6.3.1 - # via - # pyside6 - # pyside6-addons -pytest==8.3.3 - # via - # partseg (pyproject.toml) - # pytest-qt - # pytest-timeout -pytest-qt==4.4.0 - # via partseg (pyproject.toml) -pytest-timeout==2.3.1 - # via partseg (pyproject.toml) -python-dateutil==2.9.0.post0 - # via - # jupyter-client - # pandas -pytz==2024.2 - # via - # babel - # pandas -pywavelets==1.4.1 - # via scikit-image -pyyaml==6.0.2 - # via - # dask - # napari - # npe2 -pyzmq==26.2.0 - # via - # ipykernel - # jupyter-client -qtawesome==1.3.1 - # via partseg (pyproject.toml) -qtconsole==5.6.0 - # via - # partseg (pyproject.toml) - # napari-console -qtpy==2.4.1 - # via - # partseg (pyproject.toml) - # magicgui - # napari - # napari-console - # qtawesome - # qtconsole - # superqt -referencing==0.35.1 - # via - # jsonschema - # jsonschema-specifications -requests==2.32.3 - # via - # partseg (pyproject.toml) - # pooch - # pyconify - # sphinx -rich==13.9.2 - # via - # npe2 - # typer -rpds-py==0.20.0 - # via - # jsonschema - # referencing -scikit-image==0.21.0 - # via - # partseg (pyproject.toml) - # napari -scipy==1.10.1 - # via - # partseg (pyproject.toml) - # napari - # scikit-image -sentry-sdk==2.16.0 - # via - # -r requirements/version_denylist.txt - # partseg (pyproject.toml) -setuptools==75.1.0 - # via - # pyinstaller - # pyinstaller-hooks-contrib -shellingham==1.5.4 - # via typer -shiboken2==5.15.2.1 - # via pyside2 -shiboken6==6.3.1 - # via - # pyside6 - # pyside6-addons - # pyside6-essentials -simpleitk==2.4.0 - # via partseg (pyproject.toml) -six==1.16.0 - # via - # partseg (pyproject.toml) - # asttokens - # python-dateutil -snowballstemmer==2.2.0 - # via sphinx -sphinx==7.1.2 - # via numpydoc -sphinxcontrib-applehelp==1.0.4 - # via sphinx -sphinxcontrib-devhelp==1.0.2 - # via sphinx -sphinxcontrib-htmlhelp==2.0.1 - # via sphinx -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-qthelp==1.0.3 - # via sphinx -sphinxcontrib-serializinghtml==1.1.5 - # via sphinx -stack-data==0.6.3 - # via ipython -superqt==0.6.7 - # via - # partseg (pyproject.toml) - # magicgui - # napari -sympy==1.13.3 - # via partseg (pyproject.toml) -tabulate==0.9.0 - # via numpydoc -tifffile==2023.7.10 - # via - # partseg (pyproject.toml) - # czifile - # napari - # oiffile - # scikit-image -tomli==2.0.2 - # via - # build - # npe2 - # numpydoc - # pytest -tomli-w==1.0.0 - # via npe2 -toolz==1.0.0 - # via - # dask - # napari - # partd -tornado==6.4.1 - # via - # ipykernel - # jupyter-client -tqdm==4.66.5 - # via napari -traceback-with-variables==2.0.4 - # via partseg (pyproject.toml) -traitlets==5.14.3 - # via - # comm - # ipykernel - # ipython - # jupyter-client - # jupyter-core - # matplotlib-inline - # qtconsole -typer==0.12.5 - # via npe2 -typing-extensions==4.12.2 - # via - # app-model - # ipython - # magicgui - # napari - # pydantic - # rich - # superqt - # typer -tzdata==2024.2 - # via pandas -urllib3==2.2.3 - # via - # requests - # sentry-sdk -vispy==0.14.2 - # via - # partseg (pyproject.toml) - # napari - # napari-svg -wcwidth==0.2.13 - # via prompt-toolkit -wrapt==1.16.0 - # via napari -xlrd==2.0.1 - # via partseg (pyproject.toml) -xlsxwriter==3.2.0 - # via partseg (pyproject.toml) -zipp==3.20.2 - # via - # importlib-metadata - # importlib-resources diff --git a/tox.ini b/tox.ini index d08d7a7b0..efe3a7529 100644 --- a/tox.ini +++ b/tox.ini @@ -4,12 +4,11 @@ # and then run "tox" from this directory. [tox] -envlist = py{38,39,310,311,312}-{PyQt5,PySide2,PyQt6,PySide6}-all, py{39,310,311,312}-{PyQt5,PyQt6}-napari_{417,418,419,5,repo}, py{39,310}-PySide2-napari_{417,418,419,5,repo} +envlist = py{39,310,311,312}-{PyQt5,PySide2,PyQt6,PySide6}-all, py{39,310,311,312}-{PyQt5,PyQt6}-napari_{419,54,repo}, py{39,310}-PySide2-napari_{419,54,repo} toxworkdir=/tmp/tox [gh-actions] python = - 3.8: py38 3.9: py39 3.10: py310 3.11: py311 @@ -19,10 +18,8 @@ fail_on_no_env = True [gh-actions:env] NAPARI = latest: all - napari417: napari_417 - napari418: napari_418 napari419: napari_419 - napari5: napari_5 + napari54: napari_54 repo: napari_repo BACKEND = pyqt: PyQt5 @@ -73,30 +70,26 @@ deps= pytest-json-report lxml_html_clean -[testenv:py{38,39,310,311,312}-{PyQt5,PySide2,PyQt6,PySide6}-napari_{417,418,419,5,repo}] +[testenv:py{39,310,311,312}-{PyQt5,PySide2,PyQt6,PySide6}-napari_{419,54,repo}] deps = {[testenv]deps} - napari_417: napari==0.4.17 - napari_417: pydantic<2 - napari_418: napari==0.4.18 - napari_418: pydantic<2 napari_419: napari==0.4.19.post1 - napari_5: napari==0.5.0 + napari_54: napari==0.5.4 napari_repo: git+https://github.com/napari/napari.git commands = !napari_repo: python -m pytest -v package/tests/test_PartSeg/test_napari_widgets.py --json-report --json-report-file={toxinidir}/report-{envname}-{sys_platform}.json {posargs} napari_repo: python -m pytest package/tests --json-report --json-report-file={toxinidir}/report-{envname}-{sys_platform}.json {posargs} -[testenv:py{38,39,310,311,312}-PyQt5-coverage] +[testenv:py{39,310,311,312}-PyQt5-coverage] deps = {[testenv]deps} commands = coverage run --concurrency=multiprocessing -m pytest --json-report --json-report-file={toxinidir}/report-{envname}-{sys_platform}.json {posargs} -[testenv:py38-PyQt5-minimal] +[testenv:py39-PyQt5-minimal] min_req = 1 min_req_constraints= - npe2==0.1.1 + typing-extensions==4.5.0 setenv = MINIMAL_REQUIREMENTS=1 PIP_CONSTRAINT= @@ -108,7 +101,7 @@ deps = commands = coverage run -m pytest --json-report --json-report-file={toxinidir}/report-{envname}-{sys_platform}.json {posargs} -[testenv:py{38,39,310,311,312}-{PyQt5, PySide2,PyQt6,PySide6}-azure] +[testenv:py{39,310,311,312}-{PyQt5, PySide2,PyQt6,PySide6}-azure] deps = pytest-azurepipelines {[testenv]deps}