From 071109e56b3fe36ab158084ac2f55052afe3ab79 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 9 Oct 2024 14:42:18 +0200 Subject: [PATCH 01/12] chore: Drop python 3.8 --- .github/workflows/tests.yml | 8 +- .pre-commit-config.yaml | 3 - azure-pipelines.yml | 4 +- 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 | 14 +-- .../PartSeg/common_backend/base_argparser.py | 3 +- .../PartSeg/common_backend/base_settings.py | 29 ++--- .../common_backend/partially_const_dict.py | 8 +- 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 | 6 +- .../roi_extraction_algorithms.py | 14 +-- .../PartSeg/plugins/napari_widgets/utils.py | 2 +- .../plugins/old_partseg/old_partseg.py | 2 +- .../PartSegCore/algorithm_describe_base.py | 34 +++--- .../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 | 10 +- package/PartSegCore/analysis/io_utils.py | 4 +- .../PartSegCore/analysis/load_functions.py | 24 ++--- .../PartSegCore/analysis/measurement_base.py | 27 ++--- .../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 | 34 +++--- package/PartSegCore/mask_create.py | 10 +- package/PartSegCore/napari_plugins/loader.py | 6 +- .../napari_plugins/save_tiff_layer.py | 4 +- package/PartSegCore/project_info.py | 14 +-- package/PartSegCore/register.py | 3 +- 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 +- .../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 | 11 +- tox.ini | 11 +- 92 files changed, 484 insertions(+), 487 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8b4e0d381..aeaed10d6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -50,7 +50,7 @@ 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: [ "" ] @@ -90,7 +90,7 @@ 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: @@ -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/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9bb5de629..247301728 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,3 @@ -default_language_version: - python: python3.8 - repos: - repo: https://github.com/psf/black-pre-commit-mirror rev: 24.8.0 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b5e0feb53..8f9da03c3 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" @@ -41,7 +41,7 @@ stages: variables: PRE_COMMIT_HOME: $(Pipeline.Workspace)/pre-commit-cache steps: - - {task: UsePythonVersion@0, inputs: {versionSpec: '3.8', architecture: x64}} + - {task: UsePythonVersion@0, inputs: {versionSpec: '3.9', architecture: x64}} - script: echo "##vso[task.setvariable variable=PY]$(python -VV)" - task: Cache@2 inputs: 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..c061f4d03 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,8 +202,8 @@ 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: """ @@ -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_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..44fcd8014 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 @@ -55,7 +55,7 @@ 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) 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..b5eef441b 100644 --- a/package/PartSegCore/algorithm_describe_base.py +++ b/package/PartSegCore/algorithm_describe_base.py @@ -6,13 +6,13 @@ from abc import ABC, ABCMeta, abstractmethod 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 +156,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 +184,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 +204,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 +225,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 +238,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 +292,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 +399,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 +442,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,9 +535,7 @@ def pretty_print(self, algorithm_dict): ) @classmethod - def _pretty_print( - cls, values: typing.MutableMapping, translate_dict: typing.Dict[str, AlgorithmProperty], indent=0 - ): + def _pretty_print(cls, values: typing.MutableMapping, translate_dict: dict[str, AlgorithmProperty], indent=0): if not isinstance(values, typing.MutableMapping): return textwrap.indent(str(values), " " * indent) res = "" @@ -691,8 +687,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 +718,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..8eb470a96 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 @@ -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..4833b550c 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,7 +125,7 @@ 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 @@ -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,8 +372,8 @@ 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, ): """ 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..dcad95907 100644 --- a/package/PartSegCore/mask/io_functions.py +++ b/package/PartSegCore/mask/io_functions.py @@ -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..4ea040cd9 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: """ @@ -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..2d9c3731b 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 @@ -113,7 +112,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/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..6273f89bc 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,7 +32,7 @@ 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", @@ -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/tox.ini b/tox.ini index d08d7a7b0..05274ec2b 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_{417,418,419,5,repo}, py{39,310}-PySide2-napari_{417,418,419,5,repo} toxworkdir=/tmp/tox [gh-actions] python = - 3.8: py38 3.9: py39 3.10: py310 3.11: py311 @@ -73,7 +72,7 @@ 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_{417,418,419,5,repo}] deps = {[testenv]deps} napari_417: napari==0.4.17 @@ -87,13 +86,13 @@ 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 @@ -108,7 +107,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} From f8ebccffeb21ee61e24bb8ff5d4ff6178f3886f1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 9 Oct 2024 14:43:41 +0200 Subject: [PATCH 02/12] bump black --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 247301728..9e3fbcb98 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.8.0 + rev: 24.10.0 hooks: - id: black pass_filenames: true From b68789dd81f208d3dd9007ba0c99c377e5b6885b Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 15 Oct 2024 23:39:10 +0200 Subject: [PATCH 03/12] bump requirements --- pyproject.toml | 28 ++++++++++++++-------------- tox.ini | 2 -- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6273f89bc..11107ebf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,39 +39,39 @@ dependencies = [ "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", ] diff --git a/tox.ini b/tox.ini index 05274ec2b..62d2a3784 100644 --- a/tox.ini +++ b/tox.ini @@ -94,8 +94,6 @@ commands = [testenv:py39-PyQt5-minimal] min_req = 1 -min_req_constraints= - npe2==0.1.1 setenv = MINIMAL_REQUIREMENTS=1 PIP_CONSTRAINT= From 3aacae84245129b37afaad8574e93e26c7c531c5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 16 Oct 2024 00:17:58 +0200 Subject: [PATCH 04/12] pin typing-extensions --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 62d2a3784..c550067f0 100644 --- a/tox.ini +++ b/tox.ini @@ -94,6 +94,8 @@ commands = [testenv:py39-PyQt5-minimal] min_req = 1 +min_req_constraints= + typing-extensions==4.5.0 setenv = MINIMAL_REQUIREMENTS=1 PIP_CONSTRAINT= From 86d423e46912da347e55106a57eaaa0997d4f9ec Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 17 Oct 2024 11:47:25 +0200 Subject: [PATCH 05/12] improve docstrings --- package/PartSeg/_roi_mask/stack_settings.py | 4 ++-- package/PartSegCore/algorithm_describe_base.py | 9 +++++---- package/PartSegCore/analysis/calculation_plan.py | 2 +- package/PartSegCore/analysis/measurement_base.py | 5 +---- package/PartSegCore/mask/io_functions.py | 6 +++--- package/PartSegCore/mask_create.py | 2 +- 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/package/PartSeg/_roi_mask/stack_settings.py b/package/PartSeg/_roi_mask/stack_settings.py index c061f4d03..8b5ac4787 100644 --- a/package/PartSeg/_roi_mask/stack_settings.py +++ b/package/PartSeg/_roi_mask/stack_settings.py @@ -210,9 +210,9 @@ def transform_state( :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 """ diff --git a/package/PartSegCore/algorithm_describe_base.py b/package/PartSegCore/algorithm_describe_base.py index b5eef441b..5340729ce 100644 --- a/package/PartSegCore/algorithm_describe_base.py +++ b/package/PartSegCore/algorithm_describe_base.py @@ -4,6 +4,7 @@ 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 @@ -535,13 +536,13 @@ def pretty_print(self, algorithm_dict): ) @classmethod - def _pretty_print(cls, values: typing.MutableMapping, translate_dict: 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" @@ -561,7 +562,7 @@ def _pretty_print(cls, values: typing.MutableMapping, translate_dict: dict[str, 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) diff --git a/package/PartSegCore/analysis/calculation_plan.py b/package/PartSegCore/analysis/calculation_plan.py index 8eb470a96..8801a785b 100644 --- a/package/PartSegCore/analysis/calculation_plan.py +++ b/package/PartSegCore/analysis/calculation_plan.py @@ -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__( diff --git a/package/PartSegCore/analysis/measurement_base.py b/package/PartSegCore/analysis/measurement_base.py index 4833b550c..01955dc05 100644 --- a/package/PartSegCore/analysis/measurement_base.py +++ b/package/PartSegCore/analysis/measurement_base.py @@ -129,7 +129,7 @@ def get_channel_num(self, measurement_dict: dict[str, "MeasurementMethodBase"]) """ 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() @@ -380,9 +380,6 @@ def calculate_property( 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/mask/io_functions.py b/package/PartSegCore/mask/io_functions.py index dcad95907..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 diff --git a/package/PartSegCore/mask_create.py b/package/PartSegCore/mask_create.py index 4ea040cd9..b7156599a 100644 --- a/package/PartSegCore/mask_create.py +++ b/package/PartSegCore/mask_create.py @@ -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 From b4eb02e3d30e5a860294fad1e7ca589e91e4a8e6 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 17 Oct 2024 11:53:17 +0200 Subject: [PATCH 06/12] fix workflows --- .github/workflows/test_napari_widgets.yml | 2 +- .github/workflows/tests.yml | 24 +- .github/workflows/upgrade-dependencies.yml | 2 +- requirements/constraints_py3.8.txt | 531 ------------------ requirements/constraints_py3.8_pydantic_1.txt | 526 ----------------- tox.ini | 12 +- 6 files changed, 17 insertions(+), 1080 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..20e962cb8 100644 --- a/.github/workflows/test_napari_widgets.yml +++ b/.github/workflows/test_napari_widgets.yml @@ -36,7 +36,7 @@ jobs: strategy: fail-fast: false matrix: - napari: ["napari417", "napari418"] + napari: ["napari419", "napari54"] qt_backend: ["PyQt5"] include: - napari: "napari417" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index aeaed10d6..85a446e84 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -55,19 +55,19 @@ jobs: 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.11" os: "ubuntu-20.04" qt_backend: "PySide2" - - python_version: "3.9" + - python_version: "3.11" 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" @@ -94,16 +94,16 @@ jobs: 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.11" 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: @@ -193,7 +193,7 @@ jobs: use-mamba: true channels: conda-forge channel-priority: strict - python-version: "3.11" + python-version: "3.12" - uses: tlambert03/setup-qt-libs@v1 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/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 c550067f0..78247769e 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py{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] @@ -18,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_5 repo: napari_repo BACKEND = pyqt: PyQt5 @@ -75,12 +73,8 @@ deps= [testenv:py{39,310,311,312}-{PyQt5,PySide2,PyQt6,PySide6}-napari_{417,418,419,5,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} From d4f81f922ae5a34daa8134054e2afac723111b63 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 17 Oct 2024 12:03:32 +0200 Subject: [PATCH 07/12] fix specifing napari version in test --- .github/workflows/test_napari_widgets.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_napari_widgets.yml b/.github/workflows/test_napari_widgets.yml index 20e962cb8..1c01383f1 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 From 07918b8be51c82bfacac8886e9664318b7c806c7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 17 Oct 2024 12:20:33 +0200 Subject: [PATCH 08/12] fix tox napari 5_4 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 78247769e..0e3a7bc6a 100644 --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,7 @@ fail_on_no_env = True NAPARI = latest: all napari419: napari_419 - napari54: napari_5 + napari54: napari_54 repo: napari_repo BACKEND = pyqt: PyQt5 From 44c8b76e944b217d326f2c6baefc07ac61e5343b Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 17 Oct 2024 12:25:09 +0200 Subject: [PATCH 09/12] try fix warning --- .github/workflows/tests.yml | 6 +++--- package/tests/conftest.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 85a446e84..8b5c123e1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -61,10 +61,10 @@ jobs: - python_version: "3.11" os: "windows-2019" qt_backend: "PyQt5" - - python_version: "3.11" + - python_version: "3.10" os: "ubuntu-20.04" qt_backend: "PySide2" - - python_version: "3.11" + - python_version: "3.10" os: "ubuntu-22.04" qt_backend: "PySide6" - python_version: "3.12" @@ -97,7 +97,7 @@ jobs: - python_version: "3.12" qt_backend: "PyQt5" os: "ubuntu-22.04" - - python_version: "3.11" + - python_version: "3.10" os: "ubuntu-22.04" qt_backend: "PySide6" - python_version: "3.11" 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 From 96a20d211725ecc506aaef78c66e304239018365 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 17 Oct 2024 12:29:29 +0200 Subject: [PATCH 10/12] fix tox --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0e3a7bc6a..efe3a7529 100644 --- a/tox.ini +++ b/tox.ini @@ -70,7 +70,7 @@ deps= pytest-json-report lxml_html_clean -[testenv:py{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_419: napari==0.4.19.post1 From 4e7e31fb4378aedef382cfc3b49643663289b983 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 17 Oct 2024 13:15:15 +0200 Subject: [PATCH 11/12] use setFontFamilies --- package/PartSeg/common_backend/python_syntax_highlight.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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']}")) From c06e15d0190e31a797fef0bc8f02dc5e2684f56c Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 17 Oct 2024 14:23:01 +0200 Subject: [PATCH 12/12] fix problems pointed by last review --- .github/workflows/test_napari_widgets.yml | 2 +- .github/workflows/tests.yml | 2 +- package/PartSeg/plugins/napari_widgets/lables_control.py | 4 ++-- package/PartSegCore/register.py | 2 -- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_napari_widgets.yml b/.github/workflows/test_napari_widgets.yml index 1c01383f1..037975650 100644 --- a/.github/workflows/test_napari_widgets.yml +++ b/.github/workflows/test_napari_widgets.yml @@ -39,7 +39,7 @@ jobs: 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 8b5c123e1..ef82c0e20 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -193,7 +193,7 @@ jobs: use-mamba: true channels: conda-forge channel-priority: strict - python-version: "3.12" + python-version: "3.11" - uses: tlambert03/setup-qt-libs@v1 diff --git a/package/PartSeg/plugins/napari_widgets/lables_control.py b/package/PartSeg/plugins/napari_widgets/lables_control.py index 44fcd8014..8852a4b15 100644 --- a/package/PartSeg/plugins/napari_widgets/lables_control.py +++ b/package/PartSeg/plugins/napari_widgets/lables_control.py @@ -50,7 +50,7 @@ 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 @@ -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/PartSegCore/register.py b/package/PartSegCore/register.py index 2d9c3731b..64c698dd6 100644 --- a/package/PartSegCore/register.py +++ b/package/PartSegCore/register.py @@ -30,8 +30,6 @@ watershed, ) -# from .mask.io_functions import - qss_list = []