diff --git a/CheckLicensesInFiles.py b/CheckLicensesInFiles.py index 5b6af804c90..bb4e66461bc 100644 --- a/CheckLicensesInFiles.py +++ b/CheckLicensesInFiles.py @@ -11,14 +11,13 @@ import json import sys -from typing import List, Dict -def load_copyright_header() -> Dict[str, str]: +def load_copyright_header() -> dict[str, str]: return json.load(open(".licenserc.json")) -def find_files_with_incorrect_license_headers(filepaths: List[str], copyright_text: str) -> List[str]: +def find_files_with_incorrect_license_headers(filepaths: list[str], copyright_text: str) -> list[str]: copyright_lines = copyright_text.split("\n") incorrect_files = [] @@ -35,11 +34,11 @@ def find_files_with_incorrect_license_headers(filepaths: List[str], copyright_te return incorrect_files -def has_shebang_line(file_lines: List[str]) -> bool: +def has_shebang_line(file_lines: list[str]) -> bool: return file_lines[0].startswith("#!") -def has_correct_copyright_lines(file_lines: List[str], copyright_lines: List[str]) -> bool: +def has_correct_copyright_lines(file_lines: list[str], copyright_lines: list[str]) -> bool: if len(file_lines) < len(copyright_lines): return False diff --git a/docs/ext/operations_user_doc.py b/docs/ext/operations_user_doc.py index ce0b7c58ae0..1dc05047123 100644 --- a/docs/ext/operations_user_doc.py +++ b/docs/ext/operations_user_doc.py @@ -2,7 +2,6 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import List import inspect from docutils import nodes @@ -19,11 +18,11 @@ """ -def make_heading(s: str, char: str) -> List[str]: +def make_heading(s: str, char: str) -> list[str]: return [s, len(s) * char, ""] -def split_lines(s: str) -> List[str]: +def split_lines(s: str) -> list[str]: s = s.replace("\n\n", "DOUBLE_NEW_LINE") s = s.replace("\n", " ") s = s.replace("DOUBLE_NEW_LINE", "\n\n") @@ -33,7 +32,7 @@ def split_lines(s: str) -> List[str]: PARAM_SKIP_LIST = ["images", "progress"] -def get_params(s: str) -> List[str]: +def get_params(s: str) -> list[str]: ret = [] for line in s.split("\n"): if line.strip().startswith(":param"): @@ -51,7 +50,7 @@ def get_params(s: str) -> List[str]: class OperationsUserDoc(Directive): - def run(self) -> List[Node]: + def run(self) -> list[Node]: try: from mantidimaging.core.operations.loader import load_filter_packages diff --git a/mantidimaging/core/data/dataset.py b/mantidimaging/core/data/dataset.py index 03884848367..494dcb2ff6c 100644 --- a/mantidimaging/core/data/dataset.py +++ b/mantidimaging/core/data/dataset.py @@ -3,7 +3,6 @@ from __future__ import annotations import uuid from dataclasses import dataclass -from typing import Optional, List, Union import numpy as np @@ -26,7 +25,7 @@ def __init__(self, name: str = ""): self._id: uuid.UUID = uuid.uuid4() self.recons = ReconList() self._name = name - self._sinograms: Optional[ImageStack] = None + self._sinograms: ImageStack | None = None @property def id(self) -> uuid.UUID: @@ -41,11 +40,11 @@ def name(self, arg: str): self._name = arg @property - def sinograms(self) -> Optional[ImageStack]: + def sinograms(self) -> ImageStack | None: return self._sinograms @sinograms.setter - def sinograms(self, sino: Optional[ImageStack]): + def sinograms(self, sino: ImageStack | None): self._sinograms = sino @property @@ -59,7 +58,7 @@ def __contains__(self, images_id: uuid.UUID) -> bool: return any(image.id == images_id for image in self.all) @property - def all_image_ids(self) -> List[uuid.UUID]: + def all_image_ids(self) -> list[uuid.UUID]: return [image_stack.id for image_stack in self.all if image_stack is not None] def add_recon(self, recon: ImageStack): @@ -71,7 +70,7 @@ def delete_recons(self): class MixedDataset(BaseDataset): - def __init__(self, stacks: Optional[List[ImageStack]] = None, name: str = ""): + def __init__(self, stacks: list[ImageStack] | None = None, name: str = ""): super().__init__(name=name) stacks = [] if stacks is None else stacks self._stacks = stacks @@ -80,7 +79,7 @@ def add_stack(self, stack: ImageStack): self._stacks.append(stack) @property - def all(self) -> List[ImageStack]: + def all(self) -> list[ImageStack]: all_images = self._stacks + self.recons.stacks if self.sinograms is None: return all_images @@ -104,17 +103,17 @@ def delete_stack(self, images_id: uuid.UUID): @dataclass class StrictDataset(BaseDataset): sample: ImageStack - flat_before: Optional[ImageStack] = None - flat_after: Optional[ImageStack] = None - dark_before: Optional[ImageStack] = None - dark_after: Optional[ImageStack] = None + flat_before: ImageStack | None = None + flat_after: ImageStack | None = None + dark_before: ImageStack | None = None + dark_after: ImageStack | None = None def __init__(self, sample: ImageStack, - flat_before: Optional[ImageStack] = None, - flat_after: Optional[ImageStack] = None, - dark_before: Optional[ImageStack] = None, - dark_after: Optional[ImageStack] = None, + flat_before: ImageStack | None = None, + flat_after: ImageStack | None = None, + dark_before: ImageStack | None = None, + dark_after: ImageStack | None = None, name: str = ""): super().__init__(name=name) self.sample = sample @@ -127,7 +126,7 @@ def __init__(self, self._name = sample.name @property - def all(self) -> List[ImageStack]: + def all(self) -> list[ImageStack]: image_stacks = [ self.sample, self.proj180deg, self.flat_before, self.flat_after, self.dark_before, self.dark_after, self.sinograms @@ -135,15 +134,15 @@ def all(self) -> List[ImageStack]: return [image_stack for image_stack in image_stacks if image_stack is not None] + self.recons.stacks @property - def _nexus_stack_order(self) -> List[ImageStack]: + def _nexus_stack_order(self) -> list[ImageStack]: return list(filter(None, [self.dark_before, self.flat_before, self.sample, self.flat_after, self.dark_after])) @property - def nexus_arrays(self) -> List[np.ndarray]: + def nexus_arrays(self) -> list[np.ndarray]: return [image_stack.data for image_stack in self._nexus_stack_order] @property - def nexus_rotation_angles(self) -> List[np.ndarray]: + def nexus_rotation_angles(self) -> list[np.ndarray]: proj_angles = [] for image_stack in self._nexus_stack_order: angles = image_stack.real_projection_angles() @@ -151,7 +150,7 @@ def nexus_rotation_angles(self) -> List[np.ndarray]: return proj_angles @property - def image_keys(self) -> List[int]: + def image_keys(self) -> list[int]: image_keys = [] if self.dark_before is not None: image_keys += _image_key_list(2, self.dark_before.data.shape[0]) @@ -220,7 +219,7 @@ def is_processed(self) -> bool: return False -def _get_stack_data_type(stack_id: uuid.UUID, dataset: Union[MixedDataset, StrictDataset]) -> str: +def _get_stack_data_type(stack_id: uuid.UUID, dataset: MixedDataset | StrictDataset) -> str: """ Find the data type as a string of a stack. :param stack_id: The ID of the stack. diff --git a/mantidimaging/core/data/imagestack.py b/mantidimaging/core/data/imagestack.py index 8ad1f73a5bc..3865167faed 100644 --- a/mantidimaging/core/data/imagestack.py +++ b/mantidimaging/core/data/imagestack.py @@ -7,7 +7,7 @@ import os.path import uuid from copy import deepcopy -from typing import List, Optional, Any, Dict, Union, TextIO, TYPE_CHECKING, cast +from typing import Any, TextIO, TYPE_CHECKING, cast import numpy as np @@ -29,11 +29,11 @@ class ImageStack: def __init__(self, data: np.ndarray | pu.SharedArray, - filenames: Optional[List[str]] = None, - indices: List[int] | Indices | None = None, - metadata: Optional[Dict[str, Any]] = None, + filenames: list[str] | None = None, + indices: list[int] | Indices | None = None, + metadata: dict[str, Any] | None = None, sinograms: bool = False, - name: Optional[str] = None): + name: str | None = None): """ :param data: a numpy array or SharedArray object containing the images of the Sample/Projection data :param filenames: All filenames that were matched for loading @@ -53,12 +53,12 @@ def __init__(self, self._filenames = filenames - self.metadata: Dict[str, Any] = deepcopy(metadata) if metadata else {} + self.metadata: dict[str, Any] = deepcopy(metadata) if metadata else {} self._is_sinograms = sinograms - self._proj180deg: Optional[ImageStack] = None + self._proj180deg: ImageStack | None = None self._log_file: InstrumentLog | None = None - self._projection_angles: Optional[ProjectionAngles] = None + self._projection_angles: ProjectionAngles | None = None if name is None: if filenames is not None: @@ -93,11 +93,11 @@ def count(self) -> int: return len(self._filenames) if self._filenames else 0 @property - def filenames(self) -> Optional[List[str]]: + def filenames(self) -> list[str] | None: return self._filenames @filenames.setter - def filenames(self, new_ones: List[str]) -> None: + def filenames(self, new_ones: list[str]) -> None: assert len(new_ones) == self.data.shape[0], "Number of filenames and number of images must match." self._filenames = new_ones @@ -112,7 +112,7 @@ def load_metadata(self, f: TextIO) -> None: self.metadata = json.load(f) | self.metadata self._is_sinograms = self.metadata.get(const.SINOGRAMS, False) - def save_metadata(self, f: TextIO, rescale_params: Optional[Dict[str, Union[str, float]]] = None) -> None: + def save_metadata(self, f: TextIO, rescale_params: dict[str, str | float] | None = None) -> None: self.metadata[const.SINOGRAMS] = self.is_sinograms if rescale_params is not None: @@ -238,7 +238,7 @@ def has_proj180deg(self) -> bool: return self._proj180deg is not None @property - def proj180deg(self) -> Optional['ImageStack']: + def proj180deg(self) -> "ImageStack" | None: return self._proj180deg @proj180deg.setter @@ -306,7 +306,7 @@ def set_projection_angles(self, angles: ProjectionAngles) -> None: self._projection_angles = angles - def real_projection_angles(self) -> Optional[ProjectionAngles]: + def real_projection_angles(self) -> ProjectionAngles | None: """ Return only the projection angles that are from a log file or have been manually loaded. :return: Real projection angles if they were found, None otherwise. @@ -334,7 +334,7 @@ def projection_angles(self, max_angle: float = 360.0) -> ProjectionAngles: else: return ProjectionAngles(np.linspace(0, np.deg2rad(max_angle), self.num_projections)) - def counts(self) -> Optional[Counts]: + def counts(self) -> Counts | None: if self._log_file is not None: return self._log_file.counts() else: @@ -352,7 +352,7 @@ def pixel_size(self, value: float) -> None: def clear_proj180deg(self) -> None: self._proj180deg = None - def make_name_unique(self, existing_names: List[str]) -> None: + def make_name_unique(self, existing_names: list[str]) -> None: name = self.name num = 1 while self.name in existing_names: diff --git a/mantidimaging/core/data/reconlist.py b/mantidimaging/core/data/reconlist.py index 564f8ff0e94..465e225d4b2 100644 --- a/mantidimaging/core/data/reconlist.py +++ b/mantidimaging/core/data/reconlist.py @@ -4,7 +4,7 @@ import uuid from collections import UserList -from typing import List, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING if TYPE_CHECKING: from mantidimaging.core.data import ImageStack @@ -12,7 +12,7 @@ class ReconList(UserList): - def __init__(self, data: Optional[List[ImageStack]] = None): + def __init__(self, data: list[ImageStack] | None = None): data = [] if data is None else data super().__init__(data) self._id: uuid.UUID = uuid.uuid4() @@ -22,9 +22,9 @@ def id(self) -> uuid.UUID: return self._id @property - def ids(self) -> List[uuid.UUID]: + def ids(self) -> list[uuid.UUID]: return [recon.id for recon in self.data] @property - def stacks(self) -> List[ImageStack]: + def stacks(self) -> list[ImageStack]: return self.data diff --git a/mantidimaging/core/io/csv_output.py b/mantidimaging/core/io/csv_output.py index 6a6582cd936..dba3ffae650 100644 --- a/mantidimaging/core/io/csv_output.py +++ b/mantidimaging/core/io/csv_output.py @@ -1,7 +1,7 @@ # Copyright (C) 2024 ISIS Rutherford Appleton Laboratory UKRI # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import IO, Optional +from typing import IO import numpy as np @@ -10,7 +10,7 @@ class CSVOutput: def __init__(self) -> None: self.columns: dict[str, np.ndarray] = {} - self.num_rows: Optional[int] = None + self.num_rows: int | None = None def add_column(self, name: str, values: np.ndarray) -> None: as_column = values.reshape((-1, 1)) diff --git a/mantidimaging/core/io/filenames.py b/mantidimaging/core/io/filenames.py index f5f5884b87c..f473e5d685b 100644 --- a/mantidimaging/core/io/filenames.py +++ b/mantidimaging/core/io/filenames.py @@ -4,7 +4,7 @@ from pathlib import Path import re -from typing import List, Iterator, Optional, Union, Final +from typing import Iterator, Final from logging import getLogger from mantidimaging.core.utility.data_containers import FILE_TYPES @@ -124,15 +124,15 @@ def generate(self, index: int) -> str: class FilenameGroup: - def __init__(self, directory: Path, pattern: FilenamePattern, all_indexes: List[int]): + def __init__(self, directory: Path, pattern: FilenamePattern, all_indexes: list[int]): self.directory = directory self.pattern = pattern self.all_indexes = all_indexes - self.metadata_path: Optional[Path] = None - self.log_path: Optional[Path] = None + self.metadata_path: Path | None = None + self.log_path: Path | None = None @classmethod - def from_file(cls, path: Union[Path, str]) -> "FilenameGroup": + def from_file(cls, path: Path | str) -> "FilenameGroup": path = Path(path) if path.is_dir(): raise ValueError(f"path is a directory: {path}") @@ -149,7 +149,7 @@ def from_file(cls, path: Union[Path, str]) -> "FilenameGroup": return new_filename_group @classmethod - def from_directory(cls, path: Union[Path, str]) -> FilenameGroup | None: + def from_directory(cls, path: Path | str) -> FilenameGroup | None: path = Path(path) if not path.is_dir(): raise ValueError(f"path is a file: {path}") @@ -205,7 +205,7 @@ def find_log_file(self) -> None: shortest = min(log_paths, key=lambda p: len(p.name)) self.log_path = self.directory / shortest - def find_related(self, file_type: FILE_TYPES) -> Optional[FilenameGroup]: + def find_related(self, file_type: FILE_TYPES) -> FilenameGroup | None: if self.directory.name not in ["Tomo", "tomo"]: return None @@ -226,7 +226,7 @@ def find_related(self, file_type: FILE_TYPES) -> Optional[FilenameGroup]: return None - def _find_related_180_proj(self) -> Optional[FilenameGroup]: + def _find_related_180_proj(self) -> FilenameGroup | None: sample_first_name = self.first_file().name test_name = "180deg" diff --git a/mantidimaging/core/io/instrument_log.py b/mantidimaging/core/io/instrument_log.py index d6b5f8a8b23..81140b8ef20 100644 --- a/mantidimaging/core/io/instrument_log.py +++ b/mantidimaging/core/io/instrument_log.py @@ -5,7 +5,7 @@ from abc import ABC, abstractmethod from enum import Enum, auto from pathlib import Path -from typing import ClassVar, Type +from typing import ClassVar import numpy as np @@ -66,9 +66,9 @@ class InstrumentLog: New parsers can be implemented by subclassing InstrumentLogParser """ - parsers: ClassVar[list[Type[InstrumentLogParser]]] = [] + parsers: ClassVar[list[type[InstrumentLogParser]]] = [] - parser: Type[InstrumentLogParser] + parser: type[InstrumentLogParser] data: LogDataType length: int @@ -95,7 +95,7 @@ def parse(self) -> None: self.length = lengths[0] @classmethod - def register_parser(cls, parser: Type[InstrumentLogParser]) -> None: + def register_parser(cls, parser: type[InstrumentLogParser]) -> None: cls.parsers.append(parser) def get_column(self, key: LogColumn) -> list[float]: diff --git a/mantidimaging/core/io/loader/img_loader.py b/mantidimaging/core/io/loader/img_loader.py index 44da547a39c..4875eb1ca68 100644 --- a/mantidimaging/core/io/loader/img_loader.py +++ b/mantidimaging/core/io/loader/img_loader.py @@ -4,7 +4,7 @@ This module handles the loading of FIT, FITS, TIF, TIFF """ from __future__ import annotations -from typing import Tuple, Optional, List, Callable, Union, TYPE_CHECKING +from typing import Callable, TYPE_CHECKING from mantidimaging.core.data import ImageStack from mantidimaging.core.parallel import utility as pu @@ -17,11 +17,11 @@ def execute(load_func: Callable[[str], np.ndarray], - sample_path: List[str], + sample_path: list[str], img_format: str, dtype: 'npt.DTypeLike', - indices: Union[List[int], Indices, None], - progress: Optional[Progress] = None) -> ImageStack: + indices: list[int] | Indices | None, + progress: Progress | None = None) -> ImageStack: """ Reads a stack of images into memory, assuming dark and flat images are in separate directories. @@ -59,10 +59,10 @@ class ImageLoader(object): def __init__(self, load_func: Callable[[str], np.ndarray], img_format: str, - img_shape: Tuple[int, ...], + img_shape: tuple[int, ...], data_dtype: 'npt.DTypeLike', - indices: Union[List[int], Indices, None], - progress: Optional[Progress] = None): + indices: list[int] | Indices | None, + progress: Progress | None = None): self.load_func = load_func self.img_format = img_format self.img_shape = img_shape @@ -70,7 +70,7 @@ def __init__(self, self.indices = indices self.progress = progress - def load_sample_data(self, input_file_names: List[str]) -> pu.SharedArray: + def load_sample_data(self, input_file_names: list[str]) -> pu.SharedArray: # determine what the loaded data was if len(self.img_shape) == 2: # the loaded file was a single image @@ -78,7 +78,7 @@ def load_sample_data(self, input_file_names: List[str]) -> pu.SharedArray: else: raise ValueError(f"Data loaded has invalid shape: {self.img_shape}") - def _do_files_load_seq(self, data: pu.SharedArray, files: List[str]) -> pu.SharedArray: + def _do_files_load_seq(self, data: pu.SharedArray, files: list[str]) -> pu.SharedArray: progress = Progress.ensure_instance(self.progress, num_steps=len(files), task_name='Loading') with progress: @@ -96,7 +96,7 @@ def _do_files_load_seq(self, data: pu.SharedArray, files: List[str]) -> pu.Share return data - def load_files(self, files: List[str]) -> pu.SharedArray: + def load_files(self, files: list[str]) -> pu.SharedArray: # Zeroing here to make sure that we can allocate the memory. # If it's not possible better crash here than later. num_images = len(files) diff --git a/mantidimaging/core/io/loader/loader.py b/mantidimaging/core/io/loader/loader.py index 6434cac7753..025fba12f1a 100644 --- a/mantidimaging/core/io/loader/loader.py +++ b/mantidimaging/core/io/loader/loader.py @@ -5,7 +5,7 @@ from dataclasses import dataclass, field from logging import getLogger from pathlib import Path -from typing import Tuple, List, Optional, Union, TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Callable import numpy as np import astropy.io.fits as fits @@ -35,8 +35,8 @@ class ImageParameters: Dataclass to hold info about an image stack that is to be loaded. Used with LoadingParameters """ file_group: FilenameGroup - log_file: Optional[Path] = None - indices: Optional[Indices] = None + log_file: Path | None = None + indices: Indices | None = None @dataclass @@ -53,7 +53,7 @@ class LoadingParameters: sinograms: bool = DEFAULT_IS_SINOGRAM -def _fitsread(filename: Union[Path, str]) -> np.ndarray: +def _fitsread(filename: Path | str) -> np.ndarray: """ Read one image and return it as a 2d numpy array @@ -68,14 +68,14 @@ def _fitsread(filename: Union[Path, str]) -> np.ndarray: return image[0].data -def _imread(filename: Union[Path, str]) -> np.ndarray: +def _imread(filename: Path | str) -> np.ndarray: try: return tifffile.imread(filename) except tifffile.TiffFileError as e: raise RuntimeError(f"TiffFileError {e.args[0]}: {filename}") from e -def get_loader(in_format: str) -> Callable[[Union[Path, str]], np.ndarray]: +def get_loader(in_format: str) -> Callable[[Path | str], np.ndarray]: if in_format in ['fits', 'fit']: load_func = _fitsread elif in_format in ['tiff', 'tif']: @@ -85,7 +85,7 @@ def get_loader(in_format: str) -> Callable[[Union[Path, str]], np.ndarray]: return load_func -def read_image_dimensions(file_path: Path) -> Tuple[int, int]: +def read_image_dimensions(file_path: Path) -> tuple[int, int]: load_func = get_loader(file_path.suffix.replace(".", "")) img = load_func(file_path) assert len(img.shape) == 2 @@ -97,12 +97,12 @@ def load_log(log_file: Path) -> InstrumentLog: return InstrumentLog(f.readlines(), log_file) -def load_stack_from_group(group: FilenameGroup, progress: Optional[Progress] = None) -> ImageStack: +def load_stack_from_group(group: FilenameGroup, progress: Progress | None = None) -> ImageStack: return load(filename_group=group, progress=progress) def load_stack_from_image_params(image_params: ImageParameters, - progress: Optional[Progress] = None, + progress: Progress | None = None, dtype: npt.DTypeLike = np.float32): return load(filename_group=image_params.file_group, progress=progress, @@ -113,9 +113,9 @@ def load_stack_from_image_params(image_params: ImageParameters, def load(filename_group: FilenameGroup, dtype: 'npt.DTypeLike' = np.float32, - indices: Optional[Union[List[int], Indices]] = None, - progress: Optional[Progress] = None, - log_file: Optional[Path] = None) -> ImageStack: + indices: list[int] | Indices | None = None, + progress: Progress | None = None, + log_file: Path | None = None) -> ImageStack: """ Loads a stack, including sample, white and dark images. @@ -164,7 +164,7 @@ def load(filename_group: FilenameGroup, return image_stack -def create_loading_parameters_for_file_path(file_path: Path) -> Optional[LoadingParameters]: +def create_loading_parameters_for_file_path(file_path: Path) -> LoadingParameters | None: sample_file = find_first_file_that_is_possibly_a_sample(str(file_path)) if sample_file is None: return None diff --git a/mantidimaging/core/io/saver.py b/mantidimaging/core/io/saver.py index 51b280b4e61..de5b9ae15a6 100644 --- a/mantidimaging/core/io/saver.py +++ b/mantidimaging/core/io/saver.py @@ -4,7 +4,7 @@ import datetime import os from logging import getLogger -from typing import List, Union, Optional, Dict, Callable, Tuple, TYPE_CHECKING +from typing import Callable, TYPE_CHECKING import h5py from pathlib import Path @@ -35,17 +35,17 @@ package_version = CheckVersion().get_version() -def write_fits(data: np.ndarray, filename: str, overwrite: bool = False, description: Optional[str] = ""): +def write_fits(data: np.ndarray, filename: str, overwrite: bool = False, description: str | None = ""): hdu = fits.PrimaryHDU(data) hdulist = fits.HDUList([hdu]) hdulist.writeto(filename, overwrite=overwrite) -def write_img(data: np.ndarray, filename: str, overwrite: bool = False, description: Optional[str] = ""): +def write_img(data: np.ndarray, filename: str, overwrite: bool = False, description: str | None = ""): tifffile.imwrite(filename, data, description=description, metadata=None, software="Mantid Imaging") -def write_nxs(data: np.ndarray, filename: str, projection_angles: Optional[np.ndarray] = None, overwrite: bool = False): +def write_nxs(data: np.ndarray, filename: str, projection_angles: np.ndarray | None = None, overwrite: bool = False): import h5py nxs = h5py.File(filename, 'w') @@ -70,12 +70,12 @@ def image_save(images: ImageStack, swap_axes: bool = False, out_format: str = DEFAULT_IO_FILE_FORMAT, overwrite_all: bool = False, - custom_idx: Optional[int] = None, + custom_idx: int | None = None, zfill_len: int = DEFAULT_ZFILL_LENGTH, name_postfix: str = DEFAULT_NAME_POSTFIX, - indices: Union[List[int], Indices, None] = None, - pixel_depth: Optional[str] = None, - progress: Optional[Progress] = None) -> Union[str, List[str]]: + indices: list[int] | Indices | None = None, + pixel_depth: str | None = None, + progress: Progress | None = None) -> str | list[str]: """ Save image volume (3d) into a series of slices along the Z axis. The Z axis in the script is the ndarray.shape[0]. @@ -120,7 +120,7 @@ def image_save(images: ImageStack, # Do rescale if needed. if pixel_depth is None or pixel_depth == "float32": - rescale_params: Optional[Dict[str, Union[str, float]]] = None + rescale_params: dict[str, str | float] | None = None rescale_info = "" elif pixel_depth == "int16": # turn the offset to string otherwise json throws a TypeError when trying to save float32 @@ -146,7 +146,7 @@ def image_save(images: ImageStack, return filename else: if out_format in ['fit', 'fits']: - write_func: Callable[[np.ndarray, str, bool, Optional[str]], None] = write_fits + write_func: Callable[[np.ndarray, str, bool, str | None], None] = write_fits else: # pass all other formats to skimage write_func = write_img @@ -283,7 +283,7 @@ def _save_image_stacks_to_nexus(dataset: StrictDataset, data_group: h5py.Group, index += arr.shape[0] -def _convert_float_to_int(arrays: List[np.ndarray]) -> Tuple[List[np.ndarray], List[int]]: +def _convert_float_to_int(arrays: list[np.ndarray]) -> tuple[list[np.ndarray], list[int]]: """ Scales a float array to convert it to ints. :param arrays: The dataset arrays. @@ -355,7 +355,7 @@ def _save_recon_to_nexus(nexus_file: h5py.File, recon: ImageStack, sample_path: data.create_dataset("z", shape=z_arr.shape, dtype="float16", data=z_arr) -def _create_pixel_size_arrays(recon: ImageStack) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: +def _create_pixel_size_arrays(recon: ImageStack) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """ Create the pixel size arrays for the NXtomproc x/y/z fields. :param recon: The recon data. @@ -391,12 +391,12 @@ def _rescale_recon_data(data: np.ndarray) -> np.ndarray: def generate_names(name_prefix: str, - indices: Union[List[int], Indices, None], + indices: list[int] | Indices | None, num_images: int, - custom_idx: Optional[int] = None, + custom_idx: int | None = None, zfill_len: int = DEFAULT_ZFILL_LENGTH, name_postfix: str = DEFAULT_NAME_POSTFIX, - out_format: str = DEFAULT_IO_FILE_FORMAT) -> List[str]: + out_format: str = DEFAULT_IO_FILE_FORMAT) -> list[str]: start_index = indices[0] if indices else 0 if custom_idx: index = custom_idx @@ -413,7 +413,7 @@ def generate_names(name_prefix: str, return names -def make_dirs_if_needed(dirname: Optional[str] = None, overwrite_all: bool = False) -> None: +def make_dirs_if_needed(dirname: str | None = None, overwrite_all: bool = False) -> None: """ Makes sure that the directory needed (for example to save a file) exists, otherwise creates it. diff --git a/mantidimaging/core/io/utility.py b/mantidimaging/core/io/utility.py index c987bb51f52..10985a92108 100644 --- a/mantidimaging/core/io/utility.py +++ b/mantidimaging/core/io/utility.py @@ -6,7 +6,6 @@ import os import numpy as np from logging import getLogger -from typing import Optional, Tuple log = getLogger(__name__) @@ -16,7 +15,7 @@ THRESHOLD_180 = np.radians(1) -def find_first_file_that_is_possibly_a_sample(file_path: str) -> Optional[str]: +def find_first_file_that_is_possibly_a_sample(file_path: str) -> str | None: # Grab all .tif or .tiff files possible_files = glob.glob(os.path.join(file_path, "**/*.tif*"), recursive=True) @@ -27,7 +26,7 @@ def find_first_file_that_is_possibly_a_sample(file_path: str) -> Optional[str]: return None -def find_projection_closest_to_180(projections: np.ndarray, projection_angles: np.ndarray) -> Tuple[np.ndarray, float]: +def find_projection_closest_to_180(projections: np.ndarray, projection_angles: np.ndarray) -> tuple[np.ndarray, float]: """ Finds the projection closest to 180 and returns it with the difference. :param projections: The array of projection images. diff --git a/mantidimaging/core/net/help_pages.py b/mantidimaging/core/net/help_pages.py index 2f9b385aca0..b003b3e7dbf 100644 --- a/mantidimaging/core/net/help_pages.py +++ b/mantidimaging/core/net/help_pages.py @@ -2,8 +2,6 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import Optional - from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices @@ -17,7 +15,7 @@ def open_user_operation_docs(operation_name: str): open_help_webpage(SECTION_USER_GUIDE, page_url, section) -def open_help_webpage(section_url: str, page_url: str, section: Optional[str] = None): +def open_help_webpage(section_url: str, page_url: str, section: str | None = None): if section is not None: url = f"{section_url}{page_url}.html#{section}" else: diff --git a/mantidimaging/core/operation_history/operations.py b/mantidimaging/core/operation_history/operations.py index d725afe6d58..1438bc2307a 100644 --- a/mantidimaging/core/operation_history/operations.py +++ b/mantidimaging/core/operation_history/operations.py @@ -4,7 +4,7 @@ from functools import partial from logging import getLogger -from typing import Any, Callable, Dict, Iterable, List +from typing import Any, Callable, Iterable import numpy as np @@ -19,12 +19,12 @@ class ImageOperation: A deserialized representation of an item in a stack's operation_history """ - def __init__(self, filter_name: str, filter_kwargs: Dict[str, Any], display_name: str): + def __init__(self, filter_name: str, filter_kwargs: dict[str, Any], display_name: str): self.filter_name = filter_name self.filter_kwargs = filter_kwargs self.display_name = display_name - def to_partial(self, filter_funcs: Dict[str, Callable]) -> partial: + def to_partial(self, filter_funcs: dict[str, Callable]) -> partial: try: fn = filter_funcs[self.filter_name] return partial(fn, **self.filter_kwargs) @@ -34,12 +34,12 @@ def to_partial(self, filter_funcs: Dict[str, Callable]) -> partial: raise KeyError(msg) from exc @staticmethod - def from_serialized(metadata_entry: Dict[str, Any]) -> 'ImageOperation': + def from_serialized(metadata_entry: dict[str, Any]) -> 'ImageOperation': return ImageOperation(filter_name=metadata_entry[const.OPERATION_NAME], filter_kwargs=metadata_entry[const.OPERATION_KEYWORD_ARGS], display_name=metadata_entry[const.OPERATION_DISPLAY_NAME]) - def serialize(self) -> Dict[str, Any]: + def serialize(self) -> dict[str, Any]: return { const.OPERATION_NAME: self.filter_name, const.OPERATION_KEYWORD_ARGS: self.filter_kwargs, @@ -51,13 +51,13 @@ def __str__(self): f"kwargs: {self.filter_kwargs}" -def deserialize_metadata(metadata: Dict[str, Any]) -> List[ImageOperation]: +def deserialize_metadata(metadata: dict[str, Any]) -> list[ImageOperation]: return [ImageOperation.from_serialized(entry) for entry in metadata[const.OPERATION_HISTORY]] \ if const.OPERATION_HISTORY in metadata else [] def ops_to_partials(filter_ops: Iterable[ImageOperation]) -> Iterable[partial]: - filter_funcs: Dict[str, Callable] = {f.__name__: f.filter_func for f in load_filter_packages()} + filter_funcs: dict[str, Callable] = {f.__name__: f.filter_func for f in load_filter_packages()} fixed_funcs = { const.OPERATION_NAME_AXES_SWAP: lambda img, **_: np.swapaxes(img, 0, 1), # const.OPERATION_NAME_TOMOPY_RECON: lambda img, **kwargs: TomopyReconWindowModel.do_recon(img, **kwargs), diff --git a/mantidimaging/core/operations/arithmetic/arithmetic.py b/mantidimaging/core/operations/arithmetic/arithmetic.py index 299e7a2c799..6c9703c6e68 100644 --- a/mantidimaging/core/operations/arithmetic/arithmetic.py +++ b/mantidimaging/core/operations/arithmetic/arithmetic.py @@ -2,7 +2,7 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations from functools import partial -from typing import Callable, Dict, TYPE_CHECKING +from typing import Callable, TYPE_CHECKING from mantidimaging.gui.utility.qt_helpers import add_property_to_form, MAX_SPIN_BOX, Type from mantidimaging.core.operations.base_filter import BaseFilter @@ -52,11 +52,11 @@ def filter_func(images: ImageStack, return images @staticmethod - def compute_function(i: int, array: np.ndarray, params: Dict[str, float]): + def compute_function(i: int, array: np.ndarray, params: dict[str, float]): array[i] = array[i] * (params["mult"] / params["div"]) + (params["add"] - params["sub"]) @staticmethod - def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BaseMainWindowView') -> Dict[str, 'QWidget']: + def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BaseMainWindowView') -> dict[str, 'QWidget']: _, mult_input_widget = add_property_to_form('Multiply', Type.FLOAT, form=form, diff --git a/mantidimaging/core/operations/base_filter.py b/mantidimaging/core/operations/base_filter.py index 397b6705591..0a111e7ff9c 100644 --- a/mantidimaging/core/operations/base_filter.py +++ b/mantidimaging/core/operations/base_filter.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import TYPE_CHECKING, Any, Callable, Dict, Optional +from typing import TYPE_CHECKING, Any, Callable from enum import Enum, auto import numpy as np @@ -62,7 +62,7 @@ def execute_wrapper(args) -> partial: return partial(lambda: None) @staticmethod - def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BaseMainWindowView') -> Dict[str, 'QWidget']: + def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BaseMainWindowView') -> dict[str, 'QWidget']: """ Adds any required input widgets to the given form and returns references to them. @@ -78,7 +78,7 @@ def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BaseMainWindow return {} @staticmethod - def sv_params() -> Dict[str, Any]: + def sv_params() -> dict[str, Any]: """ Any parameters required from the StackVisualizer ie. ROI :return: a map of parameters names @@ -86,7 +86,7 @@ def sv_params() -> Dict[str, Any]: return {} @staticmethod - def validate_execute_kwargs(kwargs: Dict[str, Any]) -> bool: + def validate_execute_kwargs(kwargs: dict[str, Any]) -> bool: return True @staticmethod @@ -94,7 +94,7 @@ def group_name() -> FilterGroup: return FilterGroup.NoGroup @staticmethod - def get_images_from_stack(widget: "DatasetSelectorWidgetView", msg: str) -> Optional[ImageStack]: + def get_images_from_stack(widget: "DatasetSelectorWidgetView", msg: str) -> ImageStack | None: stack_uuid = widget.current() if stack_uuid is None: raise ValueError(f"No stack for {msg}") diff --git a/mantidimaging/core/operations/circular_mask/circular_mask.py b/mantidimaging/core/operations/circular_mask/circular_mask.py index f5b2b902dde..2faa19d44ac 100644 --- a/mantidimaging/core/operations/circular_mask/circular_mask.py +++ b/mantidimaging/core/operations/circular_mask/circular_mask.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import TYPE_CHECKING, List, Dict, Any +from typing import TYPE_CHECKING, Any import numpy as np import tomopy @@ -49,7 +49,7 @@ def filter_func(data: ImageStack, circular_mask_ratio=0.95, circular_mask_value= return data @staticmethod - def compute_function(i: int, arrays: List[np.ndarray], params: Dict[str, Any]): + def compute_function(i: int, arrays: list[np.ndarray], params: dict[str, Any]): array = arrays[i] if array.ndim == 2: array = np.expand_dims(array, axis=0) diff --git a/mantidimaging/core/operations/clip_values/clip_values.py b/mantidimaging/core/operations/clip_values/clip_values.py index 0435db6e7ee..5d66739cace 100644 --- a/mantidimaging/core/operations/clip_values/clip_values.py +++ b/mantidimaging/core/operations/clip_values/clip_values.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import TYPE_CHECKING, Dict, Any +from typing import TYPE_CHECKING, Any import numpy as np @@ -70,7 +70,7 @@ def filter_func(data, return data @staticmethod - def compute_function(i: int, array: np.ndarray, params: Dict[str, Any]): + def compute_function(i: int, array: np.ndarray, params: dict[str, Any]): slice = array[i] clip_min = params['clip_min'] if params['clip_min'] is not None else slice.min() clip_max = params['clip_max'] if params['clip_max'] is not None else slice.max() diff --git a/mantidimaging/core/operations/crop_coords/crop_coords.py b/mantidimaging/core/operations/crop_coords/crop_coords.py index 555b83d4fd0..3c50d3af607 100644 --- a/mantidimaging/core/operations/crop_coords/crop_coords.py +++ b/mantidimaging/core/operations/crop_coords/crop_coords.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import Union, Optional, List, TYPE_CHECKING +from typing import TYPE_CHECKING from mantidimaging import helper as h from mantidimaging.core.operations.base_filter import BaseFilter, FilterGroup @@ -33,7 +33,7 @@ class CropCoordinatesFilter(BaseFilter): @staticmethod def filter_func(images: ImageStack, - region_of_interest: Optional[Union[List[int], List[float], SensibleROI]] = None, + region_of_interest: list[int] | list[float] | SensibleROI | None = None, progress=None) -> ImageStack: """Execute the Crop Coordinates by Region of Interest filter. This does NOT do any checks if the Region of interest is out of bounds! diff --git a/mantidimaging/core/operations/divide/divide.py b/mantidimaging/core/operations/divide/divide.py index 04db2bacc3e..b796449a3e5 100644 --- a/mantidimaging/core/operations/divide/divide.py +++ b/mantidimaging/core/operations/divide/divide.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import Union, Callable, Dict, Any, TYPE_CHECKING +from typing import Callable, Any, TYPE_CHECKING from mantidimaging import helper as h import numpy as np @@ -32,7 +32,7 @@ class DivideFilter(BaseFilter): link_histograms = True @staticmethod - def filter_func(images: ImageStack, value: Union[int, float] = 0, unit="micron", progress=None) -> ImageStack: + def filter_func(images: ImageStack, value: int | float = 0, unit="micron", progress=None) -> ImageStack: """ :param value: The division value. :param unit: The unit of the divisor. @@ -57,7 +57,7 @@ def compute_function(i: int, array: np.ndarray, params: dict): array[i] /= value @staticmethod - def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BasePresenter') -> Dict[str, Any]: + def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BasePresenter') -> dict[str, Any]: from mantidimaging.gui.utility import add_property_to_form _, value_widget = add_property_to_form("Divide by", diff --git a/mantidimaging/core/operations/flat_fielding/flat_fielding.py b/mantidimaging/core/operations/flat_fielding/flat_fielding.py index 751aa6bdff2..0450f4828f6 100644 --- a/mantidimaging/core/operations/flat_fielding/flat_fielding.py +++ b/mantidimaging/core/operations/flat_fielding/flat_fielding.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import Any, Dict, TYPE_CHECKING +from typing import Any, TYPE_CHECKING from PyQt5.QtWidgets import QComboBox, QCheckBox import numpy as np @@ -126,7 +126,7 @@ def filter_func(images: ImageStack, return images @staticmethod - def register_gui(form, on_change, view) -> Dict[str, Any]: + def register_gui(form, on_change, view) -> dict[str, Any]: from mantidimaging.gui.utility import add_property_to_form _, selected_flat_fielding_widget = add_property_to_form("Flat Fielding Method", diff --git a/mantidimaging/core/operations/flat_fielding/test/flat_fielding_test.py b/mantidimaging/core/operations/flat_fielding/test/flat_fielding_test.py index e4577ae5469..2a68c35ed71 100644 --- a/mantidimaging/core/operations/flat_fielding/test/flat_fielding_test.py +++ b/mantidimaging/core/operations/flat_fielding/test/flat_fielding_test.py @@ -4,7 +4,7 @@ from parameterized import parameterized import unittest -from typing import Tuple, TYPE_CHECKING +from typing import TYPE_CHECKING from unittest import mock import numpy as np @@ -25,7 +25,7 @@ class FlatFieldingTest(unittest.TestCase): Tests return value and in-place modified data. """ - def _make_images(self) -> Tuple[ImageStack, ImageStack, ImageStack, ImageStack, ImageStack]: + def _make_images(self) -> tuple[ImageStack, ImageStack, ImageStack, ImageStack, ImageStack]: images = th.generate_images() flat_before = th.generate_images() dark_before = th.generate_images() diff --git a/mantidimaging/core/operations/loader.py b/mantidimaging/core/operations/loader.py index 517f163bfaa..e3396943038 100644 --- a/mantidimaging/core/operations/loader.py +++ b/mantidimaging/core/operations/loader.py @@ -5,17 +5,17 @@ import pkgutil import sys from importlib.util import module_from_spec -from typing import List, TYPE_CHECKING, Type +from typing import TYPE_CHECKING if TYPE_CHECKING: from mantidimaging.core.operations.base_filter import BaseFilter - BaseFilterClass = Type[BaseFilter] + BaseFilterClass = type[BaseFilter] -_OPERATION_MODULES_LIST: List[BaseFilterClass] = [] +_OPERATION_MODULES_LIST: list[BaseFilterClass] = [] -def _find_operation_modules() -> List[BaseFilterClass]: - module_list: List[BaseFilterClass] = [] +def _find_operation_modules() -> list[BaseFilterClass]: + module_list: list[BaseFilterClass] = [] for finder, module_name, ispkg in pkgutil.walk_packages([os.path.dirname(__file__)]): if not ispkg: continue @@ -38,7 +38,7 @@ def _find_operation_modules() -> List[BaseFilterClass]: return module_list -def load_filter_packages() -> List[BaseFilterClass]: +def load_filter_packages() -> list[BaseFilterClass]: """ Imports all subpackages with a FILTER_CLASS attribute, which should be an extension of BaseFilter. diff --git a/mantidimaging/core/operations/median_filter/median_filter.py b/mantidimaging/core/operations/median_filter/median_filter.py index 0d47697d1a7..bcd76863155 100644 --- a/mantidimaging/core/operations/median_filter/median_filter.py +++ b/mantidimaging/core/operations/median_filter/median_filter.py @@ -4,7 +4,7 @@ from functools import partial from logging import getLogger -from typing import Callable, Dict, Any, TYPE_CHECKING, Tuple +from typing import Callable, Any, TYPE_CHECKING import numpy as np from PyQt5.QtGui import QValidator @@ -43,7 +43,7 @@ def __init__(self, on_change: Callable): self.setSizePolicy(QSizePolicy.Policy.Maximum, QSizePolicy.Policy.Fixed) self.valueChanged.connect(lambda: on_change_and_disable(self, on_change)) - def validate(self, input: str, pos: int) -> Tuple[QValidator.State, str, int]: + def validate(self, input: str, pos: int) -> tuple[QValidator.State, str, int]: """ Validate the spin box input. Returns as Intermediate state if the input is empty or contains an even number, otherwise it returns Acceptable. @@ -97,14 +97,14 @@ def filter_func(data: ImageStack, size=None, mode="reflect", progress=None, forc return data @staticmethod - def compute_function(i: int, array: np.ndarray, params: Dict[str, Any]): + def compute_function(i: int, array: np.ndarray, params: dict[str, Any]): mode = params['mode'] size = params['size'] array[i] = _median_filter(array[i], size=size, mode=mode) @staticmethod - def register_gui(form: 'QFormLayout', on_change: Callable, view) -> Dict[str, Any]: + def register_gui(form: 'QFormLayout', on_change: Callable, view) -> dict[str, Any]: # Create a spin box for kernel size without add_property_to_form in order to allow a custom validate method size_field = KernelSpinBox(on_change) diff --git a/mantidimaging/core/operations/monitor_normalisation/monitor_normalisation.py b/mantidimaging/core/operations/monitor_normalisation/monitor_normalisation.py index 66069ee9c78..a86e9cf379b 100644 --- a/mantidimaging/core/operations/monitor_normalisation/monitor_normalisation.py +++ b/mantidimaging/core/operations/monitor_normalisation/monitor_normalisation.py @@ -2,7 +2,7 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations from functools import partial -from typing import Callable, Dict, Any, TYPE_CHECKING +from typing import Callable, Any, TYPE_CHECKING import numpy as np @@ -54,7 +54,7 @@ def filter_func(images: ImageStack, progress=None) -> ImageStack: return images @staticmethod - def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BaseMainWindowView') -> Dict[str, 'QWidget']: + def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BaseMainWindowView') -> dict[str, 'QWidget']: return {} @staticmethod @@ -62,5 +62,5 @@ def execute_wrapper(*args) -> partial: return partial(MonitorNormalisation.filter_func) @staticmethod - def validate_execute_kwargs(kwargs: Dict[str, Any]) -> bool: + def validate_execute_kwargs(kwargs: dict[str, Any]) -> bool: return True diff --git a/mantidimaging/core/operations/nan_removal/nan_removal.py b/mantidimaging/core/operations/nan_removal/nan_removal.py index 04443126588..5db95f80c3a 100644 --- a/mantidimaging/core/operations/nan_removal/nan_removal.py +++ b/mantidimaging/core/operations/nan_removal/nan_removal.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import Dict, TYPE_CHECKING +from typing import TYPE_CHECKING import numpy as np from scipy.ndimage import median_filter @@ -72,7 +72,7 @@ def compute_median_function(i: int, array: np.ndarray, params: dict): array[i] = NaNRemovalFilter._nan_to_median(array[i], size=3, edgemode='reflect') @staticmethod - def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BaseMainWindowView') -> Dict[str, 'QWidget']: + def register_gui(form: 'QFormLayout', on_change: Callable, view: 'BaseMainWindowView') -> dict[str, 'QWidget']: from mantidimaging.gui.utility import add_property_to_form value_range = (-10000000, 10000000) diff --git a/mantidimaging/core/operations/remove_all_stripe/remove_all_stripe.py b/mantidimaging/core/operations/remove_all_stripe/remove_all_stripe.py index d48d6a25c43..63bf4d989a1 100644 --- a/mantidimaging/core/operations/remove_all_stripe/remove_all_stripe.py +++ b/mantidimaging/core/operations/remove_all_stripe/remove_all_stripe.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import Dict, Any, TYPE_CHECKING +from typing import Any, TYPE_CHECKING from algotom.prep.removal import remove_all_stripe @@ -60,11 +60,11 @@ def filter_func(images: ImageStack, snr=3, la_size=61, sm_size=21, dim=1, progre return images @staticmethod - def compute_function_sino(index: int, array: ndarray, params: Dict[str, Any]): + def compute_function_sino(index: int, array: ndarray, params: dict[str, Any]): array[index] = remove_all_stripe(array[index], **params) @staticmethod - def compute_function(index: int, array: ndarray, params: Dict[str, Any]): + def compute_function(index: int, array: ndarray, params: dict[str, Any]): array[:, index, :] = remove_all_stripe(array[:, index, :], **params) @staticmethod diff --git a/mantidimaging/core/operations/remove_dead_stripe/remove_dead_stripe.py b/mantidimaging/core/operations/remove_dead_stripe/remove_dead_stripe.py index 97a5339c922..b36fc4b371d 100644 --- a/mantidimaging/core/operations/remove_dead_stripe/remove_dead_stripe.py +++ b/mantidimaging/core/operations/remove_dead_stripe/remove_dead_stripe.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import Dict, Any, TYPE_CHECKING +from typing import Any, TYPE_CHECKING from algotom.prep.removal import remove_dead_stripe @@ -54,11 +54,11 @@ def filter_func(images: ImageStack, snr=3, size=61, progress=None): return images @staticmethod - def compute_function_sino(index: int, array: ndarray, params: Dict[str, Any]): + def compute_function_sino(index: int, array: ndarray, params: dict[str, Any]): array[index] = remove_dead_stripe(array[index], **params) @staticmethod - def compute_function(index: int, array: ndarray, params: Dict[str, Any]): + def compute_function(index: int, array: ndarray, params: dict[str, Any]): array[:, index, :] = remove_dead_stripe(array[:, index, :], **params) @staticmethod diff --git a/mantidimaging/core/operations/remove_large_stripe/remove_large_stripe.py b/mantidimaging/core/operations/remove_large_stripe/remove_large_stripe.py index 0fc53bda153..96a63799fdf 100644 --- a/mantidimaging/core/operations/remove_large_stripe/remove_large_stripe.py +++ b/mantidimaging/core/operations/remove_large_stripe/remove_large_stripe.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import TYPE_CHECKING, Dict, Any +from typing import TYPE_CHECKING, Any from algotom.prep.removal import remove_large_stripe @@ -52,11 +52,11 @@ def filter_func(images: 'ImageStack', snr=3, la_size=61, progress=None): return images @staticmethod - def compute_function_sino(index: int, array: 'ndarray', params: Dict[str, Any]): + def compute_function_sino(index: int, array: 'ndarray', params: dict[str, Any]): array[index] = remove_large_stripe(array[index], **params) @staticmethod - def compute_function(index: int, array: 'ndarray', params: Dict[str, Any]): + def compute_function(index: int, array: 'ndarray', params: dict[str, Any]): array[:, index, :] = remove_large_stripe(array[:, index, :], **params) @staticmethod diff --git a/mantidimaging/core/operations/remove_stripe_filtering/remove_stripe_filtering.py b/mantidimaging/core/operations/remove_stripe_filtering/remove_stripe_filtering.py index a888ad6f28f..a7a5f1b28d7 100644 --- a/mantidimaging/core/operations/remove_stripe_filtering/remove_stripe_filtering.py +++ b/mantidimaging/core/operations/remove_stripe_filtering/remove_stripe_filtering.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import Dict, Any, TYPE_CHECKING +from typing import Any, TYPE_CHECKING from algotom.prep.removal import remove_stripe_based_filtering, remove_stripe_based_2d_filtering_sorting @@ -69,19 +69,19 @@ def filter_func(images: ImageStack, sigma=3, size=21, window_dim=1, filtering_di return images @staticmethod - def compute_function_sino(index: int, array: ndarray, params: Dict[str, Any]): + def compute_function_sino(index: int, array: ndarray, params: dict[str, Any]): array[index] = remove_stripe_based_filtering(array[index], **params) @staticmethod - def compute_function(index: int, array: ndarray, params: Dict[str, Any]): + def compute_function(index: int, array: ndarray, params: dict[str, Any]): array[:, index, :] = remove_stripe_based_filtering(array[:, index, :], **params) @staticmethod - def compute_function_2d_sino(index: int, array: ndarray, params: Dict[str, Any]): + def compute_function_2d_sino(index: int, array: ndarray, params: dict[str, Any]): array[index] = remove_stripe_based_2d_filtering_sorting(array[index], **params) @staticmethod - def compute_function_2d(index: int, array: ndarray, params: Dict[str, Any]): + def compute_function_2d(index: int, array: ndarray, params: dict[str, Any]): array[:, index, :] = remove_stripe_based_2d_filtering_sorting(array[:, index, :], **params) @staticmethod diff --git a/mantidimaging/core/operations/remove_stripe_sorting_fitting/remove_stripe_sorting_fitting.py b/mantidimaging/core/operations/remove_stripe_sorting_fitting/remove_stripe_sorting_fitting.py index 9fd88c1d925..3627dddffd0 100644 --- a/mantidimaging/core/operations/remove_stripe_sorting_fitting/remove_stripe_sorting_fitting.py +++ b/mantidimaging/core/operations/remove_stripe_sorting_fitting/remove_stripe_sorting_fitting.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import Dict, Any, TYPE_CHECKING +from typing import Any, TYPE_CHECKING from algotom.prep.removal import remove_stripe_based_fitting @@ -57,11 +57,11 @@ def filter_func(images: ImageStack, order=1, sigma=3, progress=None): return images @staticmethod - def compute_function_sino(index: int, array: ndarray, params: Dict[str, Any]): + def compute_function_sino(index: int, array: ndarray, params: dict[str, Any]): array[index] = remove_stripe_based_fitting(array[index], **params) @staticmethod - def compute_function(index: int, array: ndarray, params: Dict[str, Any]): + def compute_function(index: int, array: ndarray, params: dict[str, Any]): array[:, index, :] = remove_stripe_based_fitting(array[:, index, :], **params) @staticmethod diff --git a/mantidimaging/core/operations/rescale/rescale.py b/mantidimaging/core/operations/rescale/rescale.py index 91099c107d6..5d26b68f64c 100644 --- a/mantidimaging/core/operations/rescale/rescale.py +++ b/mantidimaging/core/operations/rescale/rescale.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import Any, Dict, TYPE_CHECKING +from typing import Any, TYPE_CHECKING import numpy as np from mantidimaging.core.parallel import shared as ps @@ -57,7 +57,7 @@ def filter_array(image: np.ndarray, min_input: float, max_input: float, max_outp return image @staticmethod - def register_gui(form, on_change, view: FiltersWindowView) -> Dict[str, Any]: + def register_gui(form, on_change, view: FiltersWindowView) -> dict[str, Any]: from mantidimaging.gui.utility import add_property_to_form _, min_input_widget = add_property_to_form('Min input', Type.FLOAT, @@ -117,5 +117,5 @@ def execute_wrapper( # type: ignore return partial(RescaleFilter.filter_func, min_input=min_input, max_input=max_input, max_output=max_output) @staticmethod - def validate_execute_kwargs(kwargs: Dict[str, Any]) -> bool: + def validate_execute_kwargs(kwargs: dict[str, Any]) -> bool: return True diff --git a/mantidimaging/core/operations/roi_normalisation/roi_normalisation.py b/mantidimaging/core/operations/roi_normalisation/roi_normalisation.py index 161f35a7122..a03f2d20334 100644 --- a/mantidimaging/core/operations/roi_normalisation/roi_normalisation.py +++ b/mantidimaging/core/operations/roi_normalisation/roi_normalisation.py @@ -4,7 +4,7 @@ from functools import partial from logging import getLogger -from typing import List, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING import numpy as np @@ -22,7 +22,7 @@ from mantidimaging.core.data import ImageStack -def modes() -> List[str]: +def modes() -> list[str]: return ['Stack Average', 'Flat Field'] @@ -48,7 +48,7 @@ class RoiNormalisationFilter(BaseFilter): def filter_func(images: ImageStack, region_of_interest: SensibleROI | None = None, normalisation_mode: str = DEFAULT_NORMALISATION_MODE, - flat_field: Optional[ImageStack] = None, + flat_field: ImageStack | None = None, progress=None): """Normalise by beam intensity. @@ -154,7 +154,7 @@ def _divide_by_air(data=None, air_sums=None): def _execute(images: ImageStack, air_region: SensibleROI, normalisation_mode: str, - flat_field: Optional[ImageStack], + flat_field: ImageStack | None, progress=None): log = getLogger(__name__) diff --git a/mantidimaging/core/operations/test/operations_test.py b/mantidimaging/core/operations/test/operations_test.py index 290a9928239..b054710bf2c 100644 --- a/mantidimaging/core/operations/test/operations_test.py +++ b/mantidimaging/core/operations/test/operations_test.py @@ -2,7 +2,7 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import Dict, List, TYPE_CHECKING +from typing import TYPE_CHECKING import unittest from unittest import mock @@ -39,8 +39,8 @@ def get_filter_func_args(): class OperationsTest(unittest.TestCase): - filters: List[BaseFilterClass] - filter_args: Dict + filters: list[BaseFilterClass] + filter_args: dict @classmethod def setUpClass(cls) -> None: diff --git a/mantidimaging/core/parallel/manager.py b/mantidimaging/core/parallel/manager.py index 1694afd13aa..cdb131fd790 100644 --- a/mantidimaging/core/parallel/manager.py +++ b/mantidimaging/core/parallel/manager.py @@ -5,7 +5,7 @@ import os import uuid from logging import getLogger -from typing import List, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING import psutil from psutil import NoSuchProcess, AccessDenied @@ -22,7 +22,7 @@ LOG = getLogger(__name__) cores: int = 1 -pool: Optional['Pool'] = None +pool: 'Pool' | None = None def create_and_start_pool(): @@ -55,7 +55,7 @@ def clear_memory_from_current_process_linux() -> None: free_shared_memory_linux([mem_name]) -def find_memory_from_previous_process_linux() -> List[str]: +def find_memory_from_previous_process_linux() -> list[str]: old_memory = [] for mem_name in _get_shared_mem_names_linux(): if _is_safe_to_remove(mem_name): @@ -63,7 +63,7 @@ def find_memory_from_previous_process_linux() -> List[str]: return old_memory -def free_shared_memory_linux(mem_names: List[str]) -> None: +def free_shared_memory_linux(mem_names: list[str]) -> None: for mem_name in mem_names: os.remove(f'{MEM_DIR_LINUX}/{mem_name}') @@ -83,7 +83,7 @@ def _is_safe_to_remove(mem_name: str) -> bool: return False -def _get_shared_mem_names_linux() -> List[str]: +def _get_shared_mem_names_linux() -> list[str]: return os.listdir(MEM_DIR_LINUX) diff --git a/mantidimaging/core/parallel/shared.py b/mantidimaging/core/parallel/shared.py index cd598d3780c..dd13315a8f3 100644 --- a/mantidimaging/core/parallel/shared.py +++ b/mantidimaging/core/parallel/shared.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import List, Tuple, Union, Callable, Dict, Any, TYPE_CHECKING +from typing import Callable, Any, TYPE_CHECKING from mantidimaging.core.parallel import utility as pu @@ -11,27 +11,27 @@ from numpy import ndarray -def inplace3(func, data: Union[List[pu.SharedArray], List[pu.SharedArrayProxy]], i, **kwargs): +def inplace3(func, data: list[pu.SharedArray] | list[pu.SharedArrayProxy], i, **kwargs): func(data[0].array[i], data[1].array[i], data[2].array, **kwargs) -def inplace2(func, data: Union[List[pu.SharedArray], List[pu.SharedArrayProxy]], i, **kwargs): +def inplace2(func, data: list[pu.SharedArray] | list[pu.SharedArrayProxy], i, **kwargs): func(data[0].array[i], data[1].array[i], **kwargs) -def inplace1(func, data: Union[List[pu.SharedArray], List[pu.SharedArrayProxy]], i, **kwargs): +def inplace1(func, data: list[pu.SharedArray] | list[pu.SharedArrayProxy], i, **kwargs): func(data[0].array[i], **kwargs) -def return_to_self(func, data: Union[List[pu.SharedArray], List[pu.SharedArrayProxy]], i, **kwargs): +def return_to_self(func, data: list[pu.SharedArray] | list[pu.SharedArrayProxy], i, **kwargs): data[0].array[i] = func(data[0].array[i], **kwargs) -def inplace_second_2d(func, data: Union[List[pu.SharedArray], List[pu.SharedArrayProxy]], i, **kwargs): +def inplace_second_2d(func, data: list[pu.SharedArray] | list[pu.SharedArrayProxy], i, **kwargs): func(data[0].array[i], data[1].array, **kwargs) -def return_to_second_at_i(func, data: Union[List[pu.SharedArray], List[pu.SharedArrayProxy]], i, **kwargs): +def return_to_second_at_i(func, data: list[pu.SharedArray] | list[pu.SharedArrayProxy], i, **kwargs): data[1].array[i] = func(data[0].array[i], **kwargs) @@ -51,7 +51,7 @@ def create_partial(func, fwd_function, **kwargs): def execute(partial_func: partial, - arrays: List[pu.SharedArray], + arrays: list[pu.SharedArray], num_operations: int, progress=None, msg: str = '') -> None: @@ -97,8 +97,8 @@ def __call__(self, index: int): def run_compute_func(func: ComputeFuncType, num_operations: int, - arrays: Union[List[pu.SharedArray], pu.SharedArray], - params: Dict[str, Any], + arrays: list[pu.SharedArray] | pu.SharedArray, + params: dict[str, Any], progress=None): if isinstance(arrays, pu.SharedArray): arrays = [arrays] @@ -108,7 +108,7 @@ def run_compute_func(func: ComputeFuncType, def _check_shared_mem_and_get_data( - arrays: List[pu.SharedArray]) -> Tuple[bool, Union[List[pu.SharedArray], List[pu.SharedArrayProxy]]]: + arrays: list[pu.SharedArray]) -> tuple[bool, list[pu.SharedArray] | list[pu.SharedArrayProxy]]: """ Checks if all shared arrays in shared_list are using shared memory and returns this result in the first element of the tuple. The second element of the tuple gives the data to use in the processing. diff --git a/mantidimaging/core/parallel/utility.py b/mantidimaging/core/parallel/utility.py index 7d6b26b62ea..a16e18d09f5 100644 --- a/mantidimaging/core/parallel/utility.py +++ b/mantidimaging/core/parallel/utility.py @@ -5,7 +5,7 @@ import os from logging import getLogger from multiprocessing import shared_memory -from typing import Tuple, TYPE_CHECKING, Optional, Callable +from typing import TYPE_CHECKING, Callable import numpy as np @@ -26,7 +26,7 @@ def enough_memory(shape, dtype): return full_size_KB(shape=shape, dtype=dtype) < system_free_memory().kb() -def create_array(shape: Tuple[int, ...], dtype: 'npt.DTypeLike' = np.float32) -> 'SharedArray': +def create_array(shape: tuple[int, ...], dtype: 'npt.DTypeLike' = np.float32) -> 'SharedArray': """ Create an array in shared memory @@ -41,7 +41,7 @@ def create_array(shape: Tuple[int, ...], dtype: 'npt.DTypeLike' = np.float32) -> return _create_shared_array(shape, dtype) -def _create_shared_array(shape: Tuple[int, ...], dtype: 'npt.DTypeLike' = np.float32) -> 'SharedArray': +def _create_shared_array(shape: tuple[int, ...], dtype: 'npt.DTypeLike' = np.float32) -> 'SharedArray': size = full_size_bytes(shape, dtype) LOG.info(f'Requested shared array with shape={shape}, size={size}, dtype={dtype}') @@ -51,7 +51,7 @@ def _create_shared_array(shape: Tuple[int, ...], dtype: 'npt.DTypeLike' = np.flo return _read_array_from_shared_memory(shape, dtype, mem, True) -def _read_array_from_shared_memory(shape: Tuple[int, ...], dtype: 'npt.DTypeLike', mem: SharedMemory, +def _read_array_from_shared_memory(shape: tuple[int, ...], dtype: 'npt.DTypeLike', mem: SharedMemory, free_mem_on_delete: bool) -> 'SharedArray': array: np.ndarray = np.ndarray(shape, dtype=dtype, buffer=mem.buf) return SharedArray(array, mem, free_mem_on_del=free_mem_on_delete) @@ -145,7 +145,7 @@ def run_compute_func_impl(worker_func: Callable[[int], None], class SharedArray: - def __init__(self, array: np.ndarray, shared_memory: Optional[SharedMemory], free_mem_on_del: bool = True): + def __init__(self, array: np.ndarray, shared_memory: SharedMemory | None, free_mem_on_del: bool = True): self.array = array self._shared_memory = shared_memory self._free_mem_on_del = free_mem_on_del @@ -172,11 +172,11 @@ def array_proxy(self) -> 'SharedArrayProxy': class SharedArrayProxy: - def __init__(self, mem_name: Optional[str], shape: Tuple[int, ...], dtype: 'npt.DTypeLike'): + def __init__(self, mem_name: str | None, shape: tuple[int, ...], dtype: 'npt.DTypeLike'): self._mem_name = mem_name self._shape = shape self._dtype = dtype - self._shared_array: Optional['SharedArray'] = None + self._shared_array: 'SharedArray' | None = None @property def array(self) -> np.ndarray: diff --git a/mantidimaging/core/reconstruct/astra_recon.py b/mantidimaging/core/reconstruct/astra_recon.py index d192af92546..dddd612dc8b 100644 --- a/mantidimaging/core/reconstruct/astra_recon.py +++ b/mantidimaging/core/reconstruct/astra_recon.py @@ -5,7 +5,7 @@ from contextlib import contextmanager from logging import getLogger from threading import Lock -from typing import Union, List, Optional, Tuple, Generator +from typing import Generator import astra import numpy as np @@ -28,7 +28,7 @@ def rotation_matrix2d(theta: float): return np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]) -def vec_geom_init2d(angles_rad: ProjectionAngles, detector_spacing_x: float, center_rot_offset: Union[float]): +def vec_geom_init2d(angles_rad: ProjectionAngles, detector_spacing_x: float, center_rot_offset: float): angles_value = angles_rad.value s0 = [0.0, -1.0] # source u0 = [detector_spacing_x, 0.0] # detector coordinates @@ -42,7 +42,7 @@ def vec_geom_init2d(angles_rad: ProjectionAngles, detector_spacing_x: float, cen @contextmanager -def _managed_recon(sino: np.ndarray, cfg, proj_geom, vol_geom) -> Generator[Tuple[int, int], None, None]: +def _managed_recon(sino: np.ndarray, cfg, proj_geom, vol_geom) -> Generator[tuple[int, int], None, None]: proj_id = None sino_id = None rec_id = None @@ -106,7 +106,7 @@ def single_sino(sino: np.ndarray, cor: ScalarCoR, proj_angles: ProjectionAngles, recon_params: ReconstructionParameters, - progress: Optional[Progress] = None) -> np.ndarray: + progress: Progress | None = None) -> np.ndarray: assert sino.ndim == 2, "Sinogram must be a 2D image" sino = BaseRecon.prepare_sinogram(sino, recon_params) @@ -127,9 +127,9 @@ def single_sino(sino: np.ndarray, @staticmethod def full(images: ImageStack, - cors: List[ScalarCoR], + cors: list[ScalarCoR], recon_params: ReconstructionParameters, - progress: Optional[Progress] = None) -> ImageStack: + progress: Progress | None = None) -> ImageStack: progress = Progress.ensure_instance(progress, num_steps=images.height) output_shape = (images.num_sinograms, images.width, images.width) output_images: ImageStack = ImageStack.create_empty_image_stack(output_shape, images.dtype, images.metadata) @@ -143,7 +143,7 @@ def full(images: ImageStack, return output_images @staticmethod - def allowed_filters() -> List[str]: + def allowed_filters() -> list[str]: # removed from list: 'kaiser' as it hard crashes ASTRA # 'projection', 'sinogram', 'rprojection', 'rsinogram' as they error return [ diff --git a/mantidimaging/core/reconstruct/base_recon.py b/mantidimaging/core/reconstruct/base_recon.py index 0dda5079fc8..2805674f3c2 100644 --- a/mantidimaging/core/reconstruct/base_recon.py +++ b/mantidimaging/core/reconstruct/base_recon.py @@ -2,7 +2,7 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import List, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING import numpy as np from numpy.polynomial import Polynomial @@ -38,7 +38,7 @@ def single_sino(sino: np.ndarray, cor: ScalarCoR, proj_angles: ProjectionAngles, recon_params: ReconstructionParameters, - progress: Optional[Progress] = None) -> np.ndarray: + progress: Progress | None = None) -> np.ndarray: """ Reconstruct a single sinogram @@ -53,9 +53,9 @@ def single_sino(sino: np.ndarray, @staticmethod def full(images: ImageStack, - cors: List[ScalarCoR], + cors: list[ScalarCoR], recon_params: ReconstructionParameters, - progress: Optional[Progress] = None) -> ImageStack: + progress: Progress | None = None) -> ImageStack: """ Performs a volume reconstruction using sample data provided as sinograms. @@ -69,5 +69,5 @@ def full(images: ImageStack, raise NotImplementedError("Base class call") @staticmethod - def allowed_filters() -> List[str]: + def allowed_filters() -> list[str]: return [] diff --git a/mantidimaging/core/reconstruct/cil_recon.py b/mantidimaging/core/reconstruct/cil_recon.py index 1dc931efbdf..1826a5fb350 100644 --- a/mantidimaging/core/reconstruct/cil_recon.py +++ b/mantidimaging/core/reconstruct/cil_recon.py @@ -6,7 +6,7 @@ from logging import getLogger, DEBUG from math import sqrt, ceil from threading import Lock -from typing import List, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING import numpy as np @@ -133,7 +133,7 @@ def single_sino(sino: np.ndarray, cor: ScalarCoR, proj_angles: ProjectionAngles, recon_params: ReconstructionParameters, - progress: Optional[Progress] = None) -> np.ndarray: + progress: Progress | None = None) -> np.ndarray: """ Reconstruct a single slice from a single sinogram. Used for the preview and the single slice button. Should return a numpy array, @@ -214,9 +214,9 @@ def single_sino(sino: np.ndarray, @staticmethod def full(images: ImageStack, - cors: List[ScalarCoR], + cors: list[ScalarCoR], recon_params: ReconstructionParameters, - progress: Optional[Progress] = None) -> ImageStack: + progress: Progress | None = None) -> ImageStack: """ Performs a volume reconstruction using sample data provided as sinograms. diff --git a/mantidimaging/core/reconstruct/tomopy_recon.py b/mantidimaging/core/reconstruct/tomopy_recon.py index 40bcfa34b57..ed2e7f5b787 100644 --- a/mantidimaging/core/reconstruct/tomopy_recon.py +++ b/mantidimaging/core/reconstruct/tomopy_recon.py @@ -3,7 +3,7 @@ from __future__ import annotations from logging import getLogger -from typing import List, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING import numpy as np @@ -36,7 +36,7 @@ def single_sino(sino: np.ndarray, cor: ScalarCoR, proj_angles: ProjectionAngles, recon_params: ReconstructionParameters, - progress: Optional[Progress] = None): + progress: Progress | None = None): sino = BaseRecon.prepare_sinogram(sino, recon_params) volume = tomopy.recon(tomo=[sino], sinogram_order=True, @@ -49,9 +49,9 @@ def single_sino(sino: np.ndarray, @staticmethod def full(images: ImageStack, - cors: List[ScalarCoR], + cors: list[ScalarCoR], recon_params: ReconstructionParameters, - progress: Optional[Progress] = None): + progress: Progress | None = None): """ Performs a volume reconstruction using sample data provided as sinograms. @@ -84,7 +84,7 @@ def full(images: ImageStack, return ImageStack(volume) @staticmethod - def allowed_filters() -> List[str]: + def allowed_filters() -> list[str]: return ["ramlak", 'shepp', 'cosine', 'hann', 'hamming', 'parzen', 'butterworth'] diff --git a/mantidimaging/core/rotation/data_model.py b/mantidimaging/core/rotation/data_model.py index 1161adc05ae..ccb6e67613a 100644 --- a/mantidimaging/core/rotation/data_model.py +++ b/mantidimaging/core/rotation/data_model.py @@ -3,7 +3,7 @@ from __future__ import annotations from logging import getLogger -from typing import Optional, List, Iterator, NamedTuple +from typing import Iterator, NamedTuple import numpy as np import scipy as sp @@ -19,9 +19,9 @@ class CorTiltDataModel: """ Model for finding COR/Tilt from (slice index, centre of rotation) data points """ - _cached_gradient: Optional[float] - _cached_cor: Optional[float] - _points: List[Point] + _cached_gradient: float | None + _cached_cor: float | None + _points: list[Point] def __init__(self): self._points = [] @@ -89,7 +89,7 @@ def get_cor_from_regression(self, slice_idx) -> float: cor = (self.gradient.value * slice_idx) + self.cor.value return cor - def get_all_cors_from_regression(self, image_height) -> List[ScalarCoR]: + def get_all_cors_from_regression(self, image_height) -> list[ScalarCoR]: """ :param image_height: How many cors will be generated, diff --git a/mantidimaging/core/rotation/polyfit_correlation.py b/mantidimaging/core/rotation/polyfit_correlation.py index a0a96049ea9..754ab731ba4 100644 --- a/mantidimaging/core/rotation/polyfit_correlation.py +++ b/mantidimaging/core/rotation/polyfit_correlation.py @@ -3,7 +3,7 @@ from __future__ import annotations from logging import getLogger -from typing import Tuple, TYPE_CHECKING +from typing import TYPE_CHECKING import numpy as np @@ -17,7 +17,7 @@ LOG = getLogger(__name__) -def do_calculate_correlation_err(store: np.ndarray, search_index: int, p0_and_180: Tuple[np.ndarray, np.ndarray], +def do_calculate_correlation_err(store: np.ndarray, search_index: int, p0_and_180: tuple[np.ndarray, np.ndarray], image_width: int): """ Calculates squared sum error in the difference between the projection at 0 degrees, and the one at 180 degrees @@ -25,7 +25,7 @@ def do_calculate_correlation_err(store: np.ndarray, search_index: int, p0_and_18 store[:] = np.square(np.roll(p0_and_180[0], search_index, axis=1) - p0_and_180[1]).sum(axis=1) / image_width -def find_center(images: ImageStack, progress: Progress) -> Tuple[ScalarCoR, Degrees]: +def find_center(images: ImageStack, progress: Progress) -> tuple[ScalarCoR, Degrees]: # assume the ROI is the full image, i.e. the slices are ALL rows of the image slices = np.arange(images.height) shift = pu.create_array((images.height, )) diff --git a/mantidimaging/core/utility/close_enough_point.py b/mantidimaging/core/utility/close_enough_point.py index fc6a0e3e3e8..dc6ac735b74 100644 --- a/mantidimaging/core/utility/close_enough_point.py +++ b/mantidimaging/core/utility/close_enough_point.py @@ -2,7 +2,7 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import Union, TYPE_CHECKING +from typing import TYPE_CHECKING if TYPE_CHECKING: from collections.abc import Sequence @@ -16,7 +16,7 @@ class CloseEnoughPoint: y: int x: int - def __init__(self, points: Union[Sequence[int], Sequence[float]]): + def __init__(self, points: Sequence[int] | Sequence[float]): self.y = int(points[1]) self.x = int(points[0]) diff --git a/mantidimaging/core/utility/cuda_check.py b/mantidimaging/core/utility/cuda_check.py index 6f71f590075..7c9fca0a06f 100644 --- a/mantidimaging/core/utility/cuda_check.py +++ b/mantidimaging/core/utility/cuda_check.py @@ -2,7 +2,6 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations from logging import getLogger -from typing import Tuple LOG = getLogger(__name__) @@ -28,7 +27,7 @@ def _cuda_is_present() -> bool: return cuda_is_present -def not_found_message() -> Tuple[str, str]: +def not_found_message() -> tuple[str, str]: """ Generates a message that can be displayed if a working CUDA installation isn't found. """ diff --git a/mantidimaging/core/utility/data_containers.py b/mantidimaging/core/utility/data_containers.py index c8c33830a06..38245c373f1 100644 --- a/mantidimaging/core/utility/data_containers.py +++ b/mantidimaging/core/utility/data_containers.py @@ -12,7 +12,7 @@ from enum import Enum from dataclasses import dataclass -from typing import Any, List, Optional, NamedTuple, TYPE_CHECKING +from typing import Any, NamedTuple, TYPE_CHECKING if TYPE_CHECKING: import numpy @@ -99,13 +99,13 @@ class ReconstructionParameters: algorithm: str filter_name: str num_iter: int = 1 - cor: Optional[ScalarCoR] = None - tilt: Optional[Degrees] = None + cor: ScalarCoR | None = None + tilt: Degrees | None = None pixel_size: float = 0.0 alpha: float = 0.0 non_negative: bool = False max_projection_angle: float = 360.0 - beam_hardening_coefs: Optional[List[float]] = None + beam_hardening_coefs: list[float] | None = None stochastic: bool = False projections_per_subset: int = 50 regularisation_percent: int = 30 diff --git a/mantidimaging/core/utility/imat_log_file_parser.py b/mantidimaging/core/utility/imat_log_file_parser.py index 9712fe988e3..90a2daba758 100644 --- a/mantidimaging/core/utility/imat_log_file_parser.py +++ b/mantidimaging/core/utility/imat_log_file_parser.py @@ -7,7 +7,7 @@ from enum import Enum, auto from itertools import zip_longest from logging import getLogger -from typing import Dict, List, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING import numpy @@ -42,11 +42,11 @@ class TextLogParser: EXPECTED_HEADER_FOR_IMAT_TEXT_LOG_FILE = \ ' TIME STAMP IMAGE TYPE IMAGE COUNTER COUNTS BM3 before image COUNTS BM3 after image\n' - def __init__(self, data: List[str]) -> None: + def __init__(self, data: list[str]) -> None: self.data = [line.strip().split(" ") for line in data] - def parse(self) -> Dict[IMATLogColumn, List]: - parsed_log: Dict[IMATLogColumn, List] = { + def parse(self) -> dict[IMATLogColumn, list]: + parsed_log: dict[IMATLogColumn, list] = { IMATLogColumn.TIMESTAMP: [], IMATLogColumn.PROJECTION_NUMBER: [], IMATLogColumn.PROJECTION_ANGLE: [], @@ -71,7 +71,7 @@ def parse(self) -> Dict[IMATLogColumn, List]: f"Unable to parse value from log file to correct type for row: {line} {index_error}") from index_error @staticmethod - def try_insert_header(file_contents: List[str]) -> List[str]: + def try_insert_header(file_contents: list[str]) -> list[str]: """ Attempt to normalise data where no header is present by inserting one. @@ -89,7 +89,7 @@ def try_insert_header(file_contents: List[str]) -> List[str]: return file_contents @staticmethod - def try_remove_invalid_lines(file_contents: List[str]) -> List[str]: + def try_remove_invalid_lines(file_contents: list[str]) -> list[str]: """ Attempt to normalise data where invalid lines are the incorrect number of columns are present. @@ -105,11 +105,11 @@ class CSVLogParser: EXPECTED_HEADER_FOR_IMAT_CSV_LOG_FILE = \ "TIME STAMP,IMAGE TYPE,IMAGE COUNTER,COUNTS BM3 before image,COUNTS BM3 after image\n" - def __init__(self, data: List[str]) -> None: + def __init__(self, data: list[str]) -> None: self.data = data - def parse(self) -> Dict[IMATLogColumn, List]: - parsed_log: Dict[IMATLogColumn, List] = { + def parse(self) -> dict[IMATLogColumn, list]: + parsed_log: dict[IMATLogColumn, list] = { IMATLogColumn.TIMESTAMP: [], IMATLogColumn.PROJECTION_NUMBER: [], IMATLogColumn.PROJECTION_ANGLE: [], @@ -140,7 +140,7 @@ def parse(self) -> Dict[IMATLogColumn, List]: f"Unable to parse value from log file to correct type for row: {row} {value_error}") from value_error @staticmethod - def try_insert_header(file_contents: List[str]) -> List[str]: + def try_insert_header(file_contents: list[str]) -> list[str]: """ Attempt to normalise data where no header is present by inserting one. @@ -153,7 +153,7 @@ def try_insert_header(file_contents: List[str]) -> List[str]: return file_contents @staticmethod - def try_remove_invalid_lines(file_contents: List[str]) -> List[str]: + def try_remove_invalid_lines(file_contents: list[str]) -> list[str]: """ Attempt to normalise data where invalid lines are the incorrect number of columns are present. @@ -166,14 +166,14 @@ def try_remove_invalid_lines(file_contents: List[str]) -> List[str]: class IMATLogFile: - def __init__(self, data: List[str], source_file: Path): + def __init__(self, data: list[str], source_file: Path): self._source_file = source_file self.parser = self.find_parser(data) self._data = self.parser.parse() @staticmethod - def find_parser(data: List[str]): + def find_parser(data: list[str]): """ Try and determine the format of the log file by checking the first row. for the type of seperator used and then attempting to normalise the data if needed before selecting the appropriate parser. @@ -229,7 +229,7 @@ def counts(self) -> Counts: return Counts(counts) - def raise_if_angle_missing(self, image_filenames: Optional[List[str]]) -> None: + def raise_if_angle_missing(self, image_filenames: list[str] | None) -> None: if image_filenames is None: return diff --git a/mantidimaging/core/utility/leak_tracker.py b/mantidimaging/core/utility/leak_tracker.py index 89eaaf9adc4..833179be16d 100644 --- a/mantidimaging/core/utility/leak_tracker.py +++ b/mantidimaging/core/utility/leak_tracker.py @@ -6,7 +6,7 @@ import traceback import weakref from types import FunctionType -from typing import NamedTuple, List, Set, Iterable +from typing import NamedTuple, Iterable from numpy import ndarray @@ -14,7 +14,7 @@ class ItemInfo(NamedTuple): ref: weakref.ref msg: str - created: List[str] + created: list[str] def obj_to_string(obj, relative=None) -> str: @@ -43,7 +43,7 @@ def obj_to_string(obj, relative=None) -> str: return f"{type(obj)} pyid={id(obj)} {extra_info}" -def find_owners(obj, depth: int, path: List[str] | None = None, ignore: Set[int] | None = None) -> List[List[str]]: +def find_owners(obj, depth: int, path: list[str] | None = None, ignore: set[int] | None = None) -> list[list[str]]: """Recursively track though references to objects and return a list of routes""" all_routes = [] if path is None: diff --git a/mantidimaging/core/utility/progress_reporting/progress.py b/mantidimaging/core/utility/progress_reporting/progress.py index 032a1460cb8..a2974887e37 100644 --- a/mantidimaging/core/utility/progress_reporting/progress.py +++ b/mantidimaging/core/utility/progress_reporting/progress.py @@ -5,7 +5,7 @@ import threading import time from logging import getLogger -from typing import NamedTuple, Optional, SupportsInt +from typing import NamedTuple, SupportsInt from mantidimaging.core.utility.memory_usage import get_memory_usage_linux_str @@ -30,7 +30,7 @@ class Progress(object): """ @staticmethod - def ensure_instance(p: Optional['Progress'] = None, *args, num_steps: Optional[int] = None, **kwargs) -> 'Progress': + def ensure_instance(p: 'Progress' | None = None, *args, num_steps: int | None = None, **kwargs) -> 'Progress': """ Helper function used to select either a non-None Progress instance as a parameter, or simply create and configure a new one. diff --git a/mantidimaging/core/utility/sensible_roi.py b/mantidimaging/core/utility/sensible_roi.py index 5d7fcae52de..4feccd5e84f 100644 --- a/mantidimaging/core/utility/sensible_roi.py +++ b/mantidimaging/core/utility/sensible_roi.py @@ -4,7 +4,7 @@ from collections.abc import Iterable from dataclasses import dataclass -from typing import Union, List, Iterator, TYPE_CHECKING +from typing import Iterator, TYPE_CHECKING if TYPE_CHECKING: from mantidimaging.core.utility.close_enough_point import CloseEnoughPoint @@ -29,7 +29,7 @@ def from_points(position: CloseEnoughPoint, size: CloseEnoughPoint) -> 'Sensible return SensibleROI(position.x, position.y, position.x + size.x, position.y + size.y) @staticmethod - def from_list(roi: Union[List[int], List[float]]): + def from_list(roi: list[int] | list[float]): return SensibleROI(int(roi[0]), int(roi[1]), int(roi[2]), int(roi[3])) def __iter__(self) -> Iterator[int]: diff --git a/mantidimaging/core/utility/version_check.py b/mantidimaging/core/utility/version_check.py index 85fe5f50391..97b91a73a56 100644 --- a/mantidimaging/core/utility/version_check.py +++ b/mantidimaging/core/utility/version_check.py @@ -7,7 +7,6 @@ import sys from logging import getLogger import requests -from typing import Optional from packaging.version import Version, parse import shutil @@ -30,7 +29,7 @@ class CheckVersion: _version: str _package_type: str - _conda_available_version: Optional[str] + _conda_available_version: str | None def __init__(self): self._version = package_version diff --git a/mantidimaging/gui/dialogs/async_task/model.py b/mantidimaging/gui/dialogs/async_task/model.py index a66337a1ef3..8c94955c035 100644 --- a/mantidimaging/gui/dialogs/async_task/model.py +++ b/mantidimaging/gui/dialogs/async_task/model.py @@ -3,7 +3,7 @@ from __future__ import annotations from logging import getLogger -from typing import Callable, Optional, Set +from typing import Callable from PyQt5.QtCore import QObject, pyqtSignal @@ -21,10 +21,10 @@ def __init__(self) -> None: self.task = TaskWorkerThread() self.task.finished.connect(self._on_task_exit) - self.on_complete_function: Optional[Callable] = None - self.tracker: Optional[Set] = None + self.on_complete_function: Callable | None = None + self.tracker: set | None = None - def set_tracker(self, tracker: Set): + def set_tracker(self, tracker: set): self.tracker = tracker self.tracker.add(self) diff --git a/mantidimaging/gui/dialogs/async_task/presenter.py b/mantidimaging/gui/dialogs/async_task/presenter.py index 21b451b6b48..87eb9119dc4 100644 --- a/mantidimaging/gui/dialogs/async_task/presenter.py +++ b/mantidimaging/gui/dialogs/async_task/presenter.py @@ -6,7 +6,7 @@ from logging import getLogger from enum import Enum -from typing import Callable, Set +from typing import Callable from PyQt5.QtCore import QObject, pyqtSignal @@ -48,7 +48,7 @@ def set_parameters(self, **kwargs): def set_on_complete(self, f: Callable): self.model.on_complete_function = f - def set_tracker(self, tracker: Set): + def set_tracker(self, tracker: set): self.model.set_tracker(tracker) def do_start_processing(self): diff --git a/mantidimaging/gui/dialogs/async_task/task.py b/mantidimaging/gui/dialogs/async_task/task.py index 91b2daff749..1c9edf7ecb7 100644 --- a/mantidimaging/gui/dialogs/async_task/task.py +++ b/mantidimaging/gui/dialogs/async_task/task.py @@ -3,7 +3,7 @@ from __future__ import annotations from logging import getLogger -from typing import Callable, Optional +from typing import Callable from PyQt5.QtCore import QThread @@ -27,8 +27,8 @@ class TaskWorkerThread(QThread): t.error """ - task_function: Optional[Callable] - error: Optional[Exception] + task_function: Callable | None + error: Exception | None def __init__(self, parent=None): super().__init__(parent) diff --git a/mantidimaging/gui/dialogs/async_task/view.py b/mantidimaging/gui/dialogs/async_task/view.py index f51b341c0e9..bde4d1bdd00 100644 --- a/mantidimaging/gui/dialogs/async_task/view.py +++ b/mantidimaging/gui/dialogs/async_task/view.py @@ -2,7 +2,7 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import Any, Callable, Dict, Optional, Set +from typing import Any, Callable from mantidimaging.core.utility.progress_reporting import Progress from mantidimaging.gui.mvp_base import BaseDialogView @@ -13,7 +13,7 @@ class AsyncTaskDialogView(BaseDialogView): - _presenter: Optional[AsyncTaskDialogPresenter] + _presenter: AsyncTaskDialogPresenter | None def __init__(self, parent: QMainWindow): super().__init__(parent, 'gui/ui/async_task_dialog.ui') @@ -73,9 +73,9 @@ def show_from_timer(self): def start_async_task_view(parent: QMainWindow, task: Callable, on_complete: Callable, - kwargs: Optional[Dict] = None, - tracker: Optional[Set[Any]] = None, - busy: Optional[bool] = False): + kwargs: dict | None = None, + tracker: set[Any] | None = None, + busy: bool | None = False): atd = AsyncTaskDialogView(parent) if not kwargs: kwargs = {'progress': Progress()} diff --git a/mantidimaging/gui/dialogs/cor_inspection/model.py b/mantidimaging/gui/dialogs/cor_inspection/model.py index 5a5d04bba66..4b97b8eb284 100644 --- a/mantidimaging/gui/dialogs/cor_inspection/model.py +++ b/mantidimaging/gui/dialogs/cor_inspection/model.py @@ -3,7 +3,6 @@ from __future__ import annotations from dataclasses import replace from logging import getLogger -from typing import Union from mantidimaging.core.data import ImageStack from mantidimaging.core.reconstruct import get_reconstructor_for @@ -26,7 +25,7 @@ def __init__(self, images: ImageStack, slice_idx: int, initial_cor: ScalarCoR, # Initial parameters if iters_mode: - self.centre_value: Union[int, float] = INIT_ITERS_CENTRE_VALUE + self.centre_value: int | float = INIT_ITERS_CENTRE_VALUE self.step = INIT_ITERS_STEP self.initial_cor = initial_cor self._recon_preview = self._recon_iters_preview diff --git a/mantidimaging/gui/dialogs/cor_inspection/view.py b/mantidimaging/gui/dialogs/cor_inspection/view.py index 6319a0b95cd..0ddffb17542 100644 --- a/mantidimaging/gui/dialogs/cor_inspection/view.py +++ b/mantidimaging/gui/dialogs/cor_inspection/view.py @@ -2,8 +2,6 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import Union - import numpy as np from PyQt5.QtWidgets import QPushButton, QDoubleSpinBox, QSpinBox, QStackedWidget @@ -25,7 +23,7 @@ class CORInspectionDialogView(BaseDialogView): stepStackedWidget: QStackedWidget instructionStackedWidget: QStackedWidget - spin_box: Union[QSpinBox, QDoubleSpinBox] + spin_box: QSpinBox | QDoubleSpinBox def __init__(self, parent, images: ImageStack, slice_index: int, initial_cor: ScalarCoR, recon_params: ReconstructionParameters, iters_mode: bool): diff --git a/mantidimaging/gui/mvp_base/presenter.py b/mantidimaging/gui/mvp_base/presenter.py index 50107a5e591..3c2aff5876a 100644 --- a/mantidimaging/gui/mvp_base/presenter.py +++ b/mantidimaging/gui/mvp_base/presenter.py @@ -3,14 +3,14 @@ from __future__ import annotations from logging import getLogger -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING if TYPE_CHECKING: from mantidimaging.gui.mvp_base import BaseMainWindowView, BaseDialogView # pragma: no cover class BasePresenter(object): - view: Union['BaseMainWindowView', 'BaseDialogView'] + view: 'BaseMainWindowView' | 'BaseDialogView' def __init__(self, view: 'BaseMainWindowView'): self.view = view diff --git a/mantidimaging/gui/test/gui_system_base.py b/mantidimaging/gui/test/gui_system_base.py index e544d354393..0e009c99f14 100644 --- a/mantidimaging/gui/test/gui_system_base.py +++ b/mantidimaging/gui/test/gui_system_base.py @@ -4,7 +4,6 @@ import os from pathlib import Path -from typing import Optional import unittest from unittest import mock @@ -72,7 +71,7 @@ def _click_messageBox(cls, button_text: str): f"Message box: {widget.windowTitle()} {widget.text()}") @classmethod - def _click_InputDialog(cls, set_int: Optional[int] = None): + def _click_InputDialog(cls, set_int: int | None = None): """ Needs to be queued with QTimer.singleShot before triggering the message box Will raise a RuntimeError if a QInputDialog is not found diff --git a/mantidimaging/gui/utility/qt_helpers.py b/mantidimaging/gui/utility/qt_helpers.py index bec4e217ae4..013e1f87f63 100644 --- a/mantidimaging/gui/utility/qt_helpers.py +++ b/mantidimaging/gui/utility/qt_helpers.py @@ -8,7 +8,7 @@ import os from enum import IntEnum, auto from logging import getLogger -from typing import Any, Tuple, Union, List, Callable +from typing import Any, Callable from PyQt5 import uic # type: ignore from PyQt5.QtCore import QObject, Qt @@ -26,7 +26,7 @@ class BlockQtSignals(object): Used to block Qt signals from a selection of QWidgets within a context. """ - def __init__(self, q_objects: Union[QObject, List[QObject]]): + def __init__(self, q_objects: QObject | list[QObject]): if not isinstance(q_objects, list): q_objects = [q_objects] for obj in q_objects: @@ -94,7 +94,7 @@ def on_change_and_disable(widget: QWidget, on_change: Callable): def add_property_to_form(label: str, - dtype: Union[Type, str], + dtype: Type | str, default_value=None, valid_values=None, tooltip=None, @@ -102,7 +102,7 @@ def add_property_to_form(label: str, form=None, filters_view=None, run_on_press=None, - single_step_size=None) -> Tuple[Union[QLabel, QLineEdit], Any]: + single_step_size=None) -> tuple[QLabel | QLineEdit, Any]: """ Adds a property to the algorithm dialog. @@ -256,7 +256,7 @@ def delete_all_widgets_from_layout(lo): item.widget().setParent(None) -def populate_menu(menu: QMenu, actions_list: List[QAction]): +def populate_menu(menu: QMenu, actions_list: list[QAction]): for (menu_text, func) in actions_list: if func is None: menu.addSeparator() diff --git a/mantidimaging/gui/widgets/auto_colour_menu/auto_color_menu.py b/mantidimaging/gui/widgets/auto_colour_menu/auto_color_menu.py index d2f65653d6f..4222e5233df 100644 --- a/mantidimaging/gui/widgets/auto_colour_menu/auto_color_menu.py +++ b/mantidimaging/gui/widgets/auto_colour_menu/auto_color_menu.py @@ -1,7 +1,7 @@ # Copyright (C) 2024 ISIS Rutherford Appleton Laboratory UKRI # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import Optional, TYPE_CHECKING, List +from typing import TYPE_CHECKING from PyQt5.QtWidgets import QAction @@ -21,7 +21,7 @@ class AutoColorMenu: """ def __init__(self) -> None: - self.auto_color_action: Optional[QAction] = None + self.auto_color_action: QAction | None = None @property def histogram(self) -> 'HistogramLUTItem': @@ -32,11 +32,11 @@ def image_data(self) -> 'np.ndarray': raise NotImplementedError('Required image_data property not implemented') @property - def other_histograms(self) -> 'List[HistogramLUTItem]': + def other_histograms(self) -> 'list[HistogramLUTItem]': return [] def add_auto_color_menu_action(self, - parent: 'Optional[QWidget]', + parent: 'QWidget | None', recon_mode: bool = False, index: int = DEFAULT_MENU_POSITION, set_enabled: bool = True) -> QAction: diff --git a/mantidimaging/gui/widgets/bad_data_overlay/bad_data_overlay.py b/mantidimaging/gui/widgets/bad_data_overlay/bad_data_overlay.py index 2e400a37c4e..b1b7b7837e1 100644 --- a/mantidimaging/gui/widgets/bad_data_overlay/bad_data_overlay.py +++ b/mantidimaging/gui/widgets/bad_data_overlay/bad_data_overlay.py @@ -2,7 +2,7 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import Callable, List, Optional, Tuple +from typing import Callable import numpy as np from pyqtgraph import ColorMap, ImageItem, ViewBox @@ -76,13 +76,13 @@ def image_item(self) -> ImageItem: def viewbox(self) -> ViewBox: raise NotImplementedError - def enable_nan_check(self, enable: bool = True, actions: Optional[List[Tuple[str, Callable]]] = None): + def enable_nan_check(self, enable: bool = True, actions: list[tuple[str, Callable]] | None = None): if enable: self.enable_check("nan", OVERLAY_COLOUR_NAN, 0, np.isnan, "Invalid values: Not a number", actions) else: self.disable_check("nan") - def enable_nonpositive_check(self, enable: bool = True, actions: Optional[List[Tuple[str, Callable]]] = None): + def enable_nonpositive_check(self, enable: bool = True, actions: list[tuple[str, Callable]] | None = None): if enable: def is_non_positive(data): @@ -92,8 +92,8 @@ def is_non_positive(data): else: self.disable_check("nonpos") - def enable_check(self, name: str, color: List[int], pos: int, func: Callable, message: str, - actions: Optional[List[Tuple[str, Callable]]]): + def enable_check(self, name: str, color: list[int], pos: int, func: Callable, message: str, + actions: list[tuple[str, Callable]] | None): if name not in self.enabled_checks: icon_path = finder.ROOT_PATH + "/gui/ui/images/exclamation-triangle-red.png" indicator = IndicatorIconView(self.viewbox, icon_path, pos, color, message) @@ -111,7 +111,7 @@ def disable_check(self, name: str): self.enabled_checks[name].remove() self.enabled_checks.pop(name, None) - def _get_current_slice(self) -> Optional[np.ndarray]: + def _get_current_slice(self) -> np.ndarray | None: data = self.image_item.image return data diff --git a/mantidimaging/gui/widgets/dataset_selector/presenter.py b/mantidimaging/gui/widgets/dataset_selector/presenter.py index d9ac08ff77c..f6264452a51 100644 --- a/mantidimaging/gui/widgets/dataset_selector/presenter.py +++ b/mantidimaging/gui/widgets/dataset_selector/presenter.py @@ -5,7 +5,7 @@ import traceback from enum import Enum from logging import getLogger -from typing import TYPE_CHECKING, List, Tuple, Optional +from typing import TYPE_CHECKING from uuid import UUID from mantidimaging.gui.mvp_base import BasePresenter @@ -27,10 +27,10 @@ class DatasetSelectorWidgetPresenter(BasePresenter): def __init__(self, view: 'DatasetSelectorWidgetView', show_stacks: bool = False, - relevant_dataset_types: type | Tuple[type] | None = None): + relevant_dataset_types: type | tuple[type] | None = None): super().__init__(view) - self.current_dataset: Optional['UUID'] = None + self.current_dataset: 'UUID' | None = None self.show_stacks = show_stacks self.relevant_dataset_types = relevant_dataset_types @@ -71,7 +71,7 @@ def do_reload_datasets(self) -> None: self.view.datasets_updated.emit() self.handle_selection(new_selected_index) - def _get_dataset_list(self) -> List[Tuple[UUID, str]]: + def _get_dataset_list(self) -> list[tuple[UUID, str]]: result = [] for dataset in self.view.main_window.presenter.datasets: # If no relevant dataset types have been specified then all should be included diff --git a/mantidimaging/gui/widgets/dataset_selector/view.py b/mantidimaging/gui/widgets/dataset_selector/view.py index ad65b69924a..6d6e2c9580b 100644 --- a/mantidimaging/gui/widgets/dataset_selector/view.py +++ b/mantidimaging/gui/widgets/dataset_selector/view.py @@ -3,7 +3,7 @@ from __future__ import annotations import uuid -from typing import TYPE_CHECKING, Optional, Union, Tuple, List +from typing import TYPE_CHECKING from PyQt5.QtCore import pyqtSignal from PyQt5.QtWidgets import QComboBox @@ -14,7 +14,7 @@ from mantidimaging.gui.windows.main import MainWindowView -def _string_contains_all_parts(string: str, parts: List[str]) -> bool: +def _string_contains_all_parts(string: str, parts: list[str]) -> bool: for part in parts: if part.lower() not in string: return False @@ -28,10 +28,7 @@ class DatasetSelectorWidgetView(QComboBox): main_window: 'MainWindowView' - def __init__(self, - parent, - show_stacks: bool = False, - relevant_dataset_types: Union[type, Tuple[type]] | None = None): + def __init__(self, parent, show_stacks: bool = False, relevant_dataset_types: type | tuple[type] | None = None): super().__init__(parent) self.presenter = DatasetSelectorWidgetPresenter(self, @@ -64,7 +61,7 @@ def _handle_loaded_datasets_changed(self) -> None: """ self.presenter.notify(Notification.RELOAD_DATASETS) - def current(self) -> Optional[uuid.UUID]: + def current(self) -> uuid.UUID | None: return self.presenter.current_dataset def try_to_select_relevant_stack(self, name: str) -> None: diff --git a/mantidimaging/gui/widgets/dataset_selector_dialog/dataset_selector_dialog.py b/mantidimaging/gui/widgets/dataset_selector_dialog/dataset_selector_dialog.py index 18f88c60c97..576fd4eb436 100644 --- a/mantidimaging/gui/widgets/dataset_selector_dialog/dataset_selector_dialog.py +++ b/mantidimaging/gui/widgets/dataset_selector_dialog/dataset_selector_dialog.py @@ -2,7 +2,7 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations import uuid -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel, QHBoxLayout, QPushButton @@ -17,13 +17,13 @@ class DatasetSelectorDialog(BaseDialogView): def __init__(self, - main_window: Optional['MainWindowView'], - title: Optional[str] = None, - message: Optional[str] = None, + main_window: 'MainWindowView' | None, + title: str | None = None, + message: str | None = None, show_stacks: bool = False): super().__init__(main_window) - self.selected_id: Optional[uuid.UUID] = None + self.selected_id: uuid.UUID | None = None self.setModal(True) self.setMinimumWidth(300) diff --git a/mantidimaging/gui/widgets/indicator_icon/view.py b/mantidimaging/gui/widgets/indicator_icon/view.py index 35998150387..40faf68b1d3 100644 --- a/mantidimaging/gui/widgets/indicator_icon/view.py +++ b/mantidimaging/gui/widgets/indicator_icon/view.py @@ -2,7 +2,7 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import List, Optional, Tuple, Callable +from typing import Callable import numpy as np from PIL import Image @@ -17,7 +17,7 @@ def __init__(self, parent: ViewBox, icon_path: str, icon_pos: int, - color: Optional[List[int]] = None, + color: list[int] | None = None, message: str = ""): """An indicator icon for a pyqtgraph ViewBox @@ -48,9 +48,9 @@ def __init__(self, self.connected_overlay = None - self.actions: List[QAction] = [] + self.actions: list[QAction] = [] - def set_icon(self, icon_path: str, color: Optional[List[int]] = None) -> None: + def set_icon(self, icon_path: str, color: list[int] | None = None) -> None: if color is not None: im = Image.open(icon_path) image_data = np.array(im) @@ -97,7 +97,7 @@ def hoverLeaveEvent(self, event) -> None: self.connected_overlay.setVisible(False) self.label.setVisible(False) - def add_actions(self, actions: List[Tuple[str, Callable]]) -> None: + def add_actions(self, actions: list[tuple[str, Callable]]) -> None: for text, method in actions: action = QAction(text) action.triggered.connect(method) diff --git a/mantidimaging/gui/widgets/mi_image_view/presenter.py b/mantidimaging/gui/widgets/mi_image_view/presenter.py index c022f9c0be9..7b4ac0e06af 100644 --- a/mantidimaging/gui/widgets/mi_image_view/presenter.py +++ b/mantidimaging/gui/widgets/mi_image_view/presenter.py @@ -3,7 +3,6 @@ from __future__ import annotations from logging import getLogger -from typing import List import numpy as np @@ -48,7 +47,7 @@ def get_roi(image, roi_pos, roi_size): return roi_pos, roi_size @staticmethod - def get_nearest_timeline_tick(x_pos_clicked: float, x_axis, view_range: List[int]): + def get_nearest_timeline_tick(x_pos_clicked: float, x_axis, view_range: list[int]): """ Calculate the closes point to the clicked position on the histogram's timeline. diff --git a/mantidimaging/gui/widgets/mi_image_view/test/presenter_test.py b/mantidimaging/gui/widgets/mi_image_view/test/presenter_test.py index 3a4d9f1b2ee..557bc58c419 100644 --- a/mantidimaging/gui/widgets/mi_image_view/test/presenter_test.py +++ b/mantidimaging/gui/widgets/mi_image_view/test/presenter_test.py @@ -2,8 +2,6 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import List - import numpy as np import pytest @@ -18,7 +16,7 @@ ([-50, -50], [10, 10], [0, 0], [10, 10]), # outside of bounds on the left/top ([-50, -50], [101, 101], [0, 0], [100, 100]), # also outside of bounds on the bottom/right ]) -def test_get_roi(init_pos: List[int], init_size: List[int], exp_pos: List[int], exp_size: List[int]): +def test_get_roi(init_pos: list[int], init_size: list[int], exp_pos: list[int], exp_size: list[int]): image = np.zeros((100, 100)) pres = MIImagePresenter() roi_pos = CloseEnoughPoint(init_pos) diff --git a/mantidimaging/gui/widgets/mi_image_view/view.py b/mantidimaging/gui/widgets/mi_image_view/view.py index 8f632d0512d..f3e091aa168 100644 --- a/mantidimaging/gui/widgets/mi_image_view/view.py +++ b/mantidimaging/gui/widgets/mi_image_view/view.py @@ -4,7 +4,7 @@ from math import degrees from time import sleep -from typing import Callable, Optional, Tuple, TYPE_CHECKING +from typing import Callable, TYPE_CHECKING from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QPushButton, QSizePolicy @@ -47,9 +47,9 @@ class MIImageView(ImageView, BadDataOverlay, AutoColorMenu): details: QLabel roiString = None imageItem: ImageItem - _angles: Optional[ProjectionAngles] = None + _angles: ProjectionAngles | None = None - roi_changed_callback: Optional[Callable[[SensibleROI], None]] = None + roi_changed_callback: Callable[[SensibleROI], None] | None = None def __init__(self, parent=None, @@ -139,11 +139,11 @@ def viewbox(self) -> ViewBox: return self.view @property - def angles(self) -> Optional[ProjectionAngles]: + def angles(self) -> ProjectionAngles | None: return self._angles @angles.setter - def angles(self, angles: Optional[ProjectionAngles]): + def angles(self, angles: ProjectionAngles | None): self._angles = angles self._update_message(self._last_mouse_hover_location) @@ -196,7 +196,7 @@ def roiChanged(self): self.roi_changed_callback(roi) self._refresh_message() - def _update_roi_region_avg(self) -> Optional[SensibleROI]: + def _update_roi_region_avg(self) -> SensibleROI | None: if self.image.ndim != 3: return None roi_pos, roi_size = self.get_roi() @@ -239,7 +239,7 @@ def extended_handler(ev): self.ui.roiPlot.mousePressEvent = lambda ev: extended_handler(ev) - def get_roi(self) -> Tuple[CloseEnoughPoint, CloseEnoughPoint]: + def get_roi(self) -> tuple[CloseEnoughPoint, CloseEnoughPoint]: return self.presenter.get_roi(self.image, roi_pos=CloseEnoughPoint(self.roi.pos()), roi_size=CloseEnoughPoint(self.roi.size())) diff --git a/mantidimaging/gui/widgets/mi_mini_image_view/view.py b/mantidimaging/gui/widgets/mi_mini_image_view/view.py index f1ea7dce826..6b64402428a 100644 --- a/mantidimaging/gui/widgets/mi_mini_image_view/view.py +++ b/mantidimaging/gui/widgets/mi_mini_image_view/view.py @@ -3,7 +3,7 @@ from __future__ import annotations from itertools import chain, tee -from typing import List, TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING from weakref import WeakSet from pyqtgraph import ImageItem, ViewBox @@ -36,7 +36,7 @@ class MIMiniImageView(GraphicsLayout, BadDataOverlay, AutoColorMenu): bright_levels: None | list[int] = None levels: list[float] - def __init__(self, name: str = "MIMiniImageView", parent: 'Optional[QWidget]' = None, recon_mode: bool = False): + def __init__(self, name: str = "MIMiniImageView", parent: 'QWidget | None' = None, recon_mode: bool = False): super().__init__() self.name = name.title() @@ -70,7 +70,7 @@ def histogram_region(self): return self.hist.region.getRegion() @histogram_region.setter - def histogram_region(self, new_region: tuple[Union[int, list[int]], Union[int, list[int]]]): + def histogram_region(self, new_region: tuple[int | list[int], int | list[int]]): self.hist.region.setRegion(new_region) @property @@ -78,7 +78,7 @@ def image_data(self) -> 'np.ndarray': return self.im.image @property - def other_histograms(self) -> List[HistogramLUTItem]: + def other_histograms(self) -> list[HistogramLUTItem]: return [axis.hist for axis in self.axis_siblings] @property @@ -110,7 +110,7 @@ def setImage(self, image: np.ndarray, *args, **kwargs): self.set_auto_color_enabled(image is not None) @staticmethod - def set_siblings(sibling_views: List["MIMiniImageView"], axis=False, hist=False): + def set_siblings(sibling_views: list["MIMiniImageView"], axis=False, hist=False): for view1 in sibling_views: for view2 in sibling_views: if view2 is not view1: diff --git a/mantidimaging/gui/widgets/palette_changer/presenter.py b/mantidimaging/gui/widgets/palette_changer/presenter.py index 7232558bf5a..ebee2737949 100644 --- a/mantidimaging/gui/widgets/palette_changer/presenter.py +++ b/mantidimaging/gui/widgets/palette_changer/presenter.py @@ -1,7 +1,7 @@ # Copyright (C) 2024 ISIS Rutherford Appleton Laboratory UKRI # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import List, TYPE_CHECKING +from typing import TYPE_CHECKING if TYPE_CHECKING: from pyqtgraph import HistogramLUTItem @@ -17,7 +17,7 @@ class PaletteChangerPresenter(BasePresenter): - def __init__(self, view, other_hists: 'List[HistogramLUTItem]', main_hist: 'HistogramLUTItem', image: np.ndarray, + def __init__(self, view, other_hists: 'list[HistogramLUTItem]', main_hist: 'HistogramLUTItem', image: np.ndarray, recon_mode: bool): super().__init__(view) self.rng = np.random.default_rng() @@ -57,7 +57,7 @@ def _record_old_tick_points(self): """ self.old_ticks = list(self.main_hist.gradient.ticks.keys()) - def _insert_new_ticks(self, tick_points: List[float]): + def _insert_new_ticks(self, tick_points: list[float]): """ Adds new ticks to the recon histogram. """ @@ -74,21 +74,21 @@ def _change_colour_map(self): for hist in self.other_hists + [self.main_hist]: hist.gradient.loadPreset(preset) - def _generate_otsu_tick_points(self) -> List[float]: + def _generate_otsu_tick_points(self) -> list[float]: """ Determine the Otsu threshold tick point. """ vals = filters.threshold_multiotsu(self.image, classes=self.view.num_materials) return self._normalise_tick_values(vals.tolist()) - def _generate_jenks_tick_points(self) -> List[float]: + def _generate_jenks_tick_points(self) -> list[float]: """ Determine the Jenks tick points. """ breaks = jenks_breaks(self.flattened_image, self.view.num_materials) return self._normalise_tick_values(list(breaks)[1:-1]) - def _normalise_tick_values(self, breaks: List[float]) -> List[float]: + def _normalise_tick_values(self, breaks: list[float]) -> list[float]: """ Scale the collection of break values so that they range from 0 to 1. This is done because addTick expects an x value in this range. @@ -114,7 +114,7 @@ def _update_ticks(self): self.main_hist.gradient.updateGradient() self.main_hist.gradient.sigGradientChangeFinished.emit(self.main_hist.gradient) - def _get_colours(self, num_ticks: int) -> List[float]: + def _get_colours(self, num_ticks: int) -> list[float]: """ Determine the colours that should be used for the new recon histogram ticks. Should ensure that there is a suitable amount of contrast between the different materials, even if the ticks are quite close together on diff --git a/mantidimaging/gui/widgets/roi_selector/view.py b/mantidimaging/gui/widgets/roi_selector/view.py index 1147daf9616..0683e7d576c 100644 --- a/mantidimaging/gui/widgets/roi_selector/view.py +++ b/mantidimaging/gui/widgets/roi_selector/view.py @@ -1,7 +1,7 @@ # Copyright (C) 2024 ISIS Rutherford Appleton Laboratory UKRI # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import numpy as np from PyQt5.QtWidgets import QMainWindow, QMenu, QAction, QPushButton @@ -18,7 +18,7 @@ def __init__(self, parent, image_stack: 'ImageStack', slice_idx: int, - roi_values: Optional[list[int]] = None, + roi_values: list[int] | None = None, roi_changed_callback=None) -> None: super().__init__(parent) diff --git a/mantidimaging/gui/windows/image_load_dialog/field.py b/mantidimaging/gui/windows/image_load_dialog/field.py index c75f30bc078..61c0e18da05 100644 --- a/mantidimaging/gui/windows/image_load_dialog/field.py +++ b/mantidimaging/gui/windows/image_load_dialog/field.py @@ -4,7 +4,6 @@ from pathlib import Path import numpy as np -from typing import Optional, List, Union, Tuple from PyQt5.QtWidgets import QTreeWidgetItem, QWidget, QSpinBox, QTreeWidget, QHBoxLayout, QLabel, QCheckBox, QPushButton @@ -15,11 +14,11 @@ class Field: _widget: QTreeWidgetItem _use: QCheckBox - _spinbox_widget: Optional[QWidget] = None - _start_spinbox: Optional[QSpinBox] = None - _stop_spinbox: Optional[QSpinBox] = None - _increment_spinbox: Optional[QSpinBox] = None - _shape_widget: Optional[QTreeWidgetItem] = None + _spinbox_widget: QWidget | None = None + _start_spinbox: QSpinBox | None = None + _stop_spinbox: QSpinBox | None = None + _increment_spinbox: QSpinBox | None = None + _shape_widget: QTreeWidgetItem | None = None _tree: QTreeWidget _path: QTreeWidgetItem @@ -36,7 +35,7 @@ def __init__(self, tree: QTreeWidget, widget: QTreeWidgetItem, use: QCheckBox, s if file_info == FILE_TYPES.SAMPLE: self._init_indices() - def set_images(self, image_files: List[Path]) -> None: + def set_images(self, image_files: list[Path]) -> None: if len(image_files) > 0: self.path = image_files[0] self.update_shape(len(image_files)) @@ -63,7 +62,7 @@ def path_widget(self) -> QTreeWidgetItem: return self._path @property - def path(self) -> Optional[Path]: + def path(self) -> Path | None: if path_text := self.path_widget.text(1): return Path(path_text) else: @@ -176,7 +175,7 @@ def set_preview(self, preview_mode: bool) -> None: else: self._increment_spinbox.setValue(1) - def _update_expected_mem_usage(self, shape: Tuple[int, int]) -> Tuple[int, float]: + def _update_expected_mem_usage(self, shape: tuple[int, int]) -> tuple[int, float]: num_images = size_calculator.number_of_images_from_indices(self._start.value(), self._stop.value(), self._increment.value()) @@ -185,7 +184,7 @@ def _update_expected_mem_usage(self, shape: Tuple[int, int]) -> Tuple[int, float exp_mem = round(single_mem * num_images, 2) return num_images, exp_mem - def update_shape(self, shape: Union[int, Tuple[int, int]]) -> None: + def update_shape(self, shape: int | tuple[int, int]) -> None: if isinstance(shape, int): self._shape = f"{str(shape)} images" else: diff --git a/mantidimaging/gui/windows/image_load_dialog/presenter.py b/mantidimaging/gui/windows/image_load_dialog/presenter.py index 00f1d752adb..57cebd14d37 100644 --- a/mantidimaging/gui/windows/image_load_dialog/presenter.py +++ b/mantidimaging/gui/windows/image_load_dialog/presenter.py @@ -4,7 +4,7 @@ from logging import getLogger from pathlib import Path -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from mantidimaging.core.io.filenames import FilenameGroup from mantidimaging.core.io.loader import load_log @@ -20,7 +20,7 @@ class LoadPresenter: view: 'ImageLoadDialog' - sample_fg: Optional[FilenameGroup] = None + sample_fg: FilenameGroup | None = None def __init__(self, view: 'ImageLoadDialog'): self.view = view diff --git a/mantidimaging/gui/windows/image_load_dialog/view.py b/mantidimaging/gui/windows/image_load_dialog/view.py index 10b7f5a7916..d23267ac97f 100644 --- a/mantidimaging/gui/windows/image_load_dialog/view.py +++ b/mantidimaging/gui/windows/image_load_dialog/view.py @@ -2,7 +2,6 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import Optional, Dict from PyQt5.QtWidgets import QComboBox, QCheckBox, QTreeWidget, QTreeWidgetItem, QPushButton, QSizePolicy, \ QHeaderView, QSpinBox, QFileDialog, QDialogButtonBox, QWidget @@ -25,7 +24,7 @@ class ImageLoadDialog(BaseDialogView): step_preview: QPushButton step_all: QPushButton - fields: Dict[str, Field] + fields: dict[str, Field] def __init__(self, parent: QWidget) -> None: super().__init__(parent, 'gui/ui/image_load_dialog.ui') @@ -82,7 +81,7 @@ def create_file_input(self, position: int, file_info: FILE_TYPES) -> Field: return field @staticmethod - def select_file(caption: str, image_file: bool = True) -> Optional[str]: + def select_file(caption: str, image_file: bool = True) -> str | None: """ :param caption: Title of the file browser window that will be opened :param image_file: Whether or not the file being looked for is an image diff --git a/mantidimaging/gui/windows/live_viewer/view.py b/mantidimaging/gui/windows/live_viewer/view.py index 197f26af095..1c475731f1f 100644 --- a/mantidimaging/gui/windows/live_viewer/view.py +++ b/mantidimaging/gui/windows/live_viewer/view.py @@ -2,7 +2,7 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations from pathlib import Path -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING from PyQt5.QtCore import QSignalBlocker from PyQt5.QtWidgets import QVBoxLayout @@ -36,7 +36,7 @@ def __init__(self, main_window: 'MainWindowView', live_dir_path: Path) -> None: self.imageLayout.addWidget(self.live_viewer) self.live_viewer.z_slider.valueChanged.connect(self.presenter.select_image) - self.filter_params: Dict[str, Dict] = {} + self.filter_params: dict[str, dict] = {} self.right_click_menu = self.live_viewer.image.vb.menu operations_menu = self.right_click_menu.addMenu("Operations") diff --git a/mantidimaging/gui/windows/main/image_save_dialog.py b/mantidimaging/gui/windows/main/image_save_dialog.py index 8e05f8fbb19..a767c39f577 100644 --- a/mantidimaging/gui/windows/main/image_save_dialog.py +++ b/mantidimaging/gui/windows/main/image_save_dialog.py @@ -2,7 +2,7 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations import uuid -from typing import Optional, TYPE_CHECKING +from typing import TYPE_CHECKING from PyQt5.QtWidgets import QDialogButtonBox @@ -25,7 +25,7 @@ def sort_by_tomo_and_recon(stack_id: "StackId"): class ImageSaveDialog(BaseDialogView): - selected_stack = Optional[uuid.UUID] + selected_stack = uuid.UUID | None def __init__(self, parent, stack_list): super().__init__(parent, 'gui/ui/image_save_dialog.ui') diff --git a/mantidimaging/gui/windows/main/model.py b/mantidimaging/gui/windows/main/model.py index b19f1ee27db..c5148e1391f 100644 --- a/mantidimaging/gui/windows/main/model.py +++ b/mantidimaging/gui/windows/main/model.py @@ -4,7 +4,7 @@ import uuid from logging import getLogger from pathlib import Path -from typing import Dict, Optional, List, Union, NoReturn, TYPE_CHECKING +from typing import NoReturn, TYPE_CHECKING from mantidimaging.core.data import ImageStack from mantidimaging.core.data.dataset import StrictDataset, MixedDataset @@ -19,7 +19,7 @@ logger = getLogger(__name__) -def _matching_dataset_attribute(dataset_attribute: Optional[ImageStack], images_id: uuid.UUID) -> bool: +def _matching_dataset_attribute(dataset_attribute: ImageStack | None, images_id: uuid.UUID) -> bool: return isinstance(dataset_attribute, ImageStack) and dataset_attribute.id == images_id @@ -27,9 +27,9 @@ class MainWindowModel(object): def __init__(self) -> None: super().__init__() - self.datasets: Dict[uuid.UUID, Union[MixedDataset, StrictDataset]] = {} + self.datasets: dict[uuid.UUID, MixedDataset | StrictDataset] = {} - def get_images_by_uuid(self, images_uuid: uuid.UUID) -> Optional[ImageStack]: + def get_images_by_uuid(self, images_uuid: uuid.UUID) -> ImageStack | None: for dataset in self.datasets.values(): for image in dataset.all: if images_uuid == image.id: @@ -82,15 +82,14 @@ def do_images_saving(self, images_id, output_dir, name_prefix, image_format, ove images.filenames = filenames return True - def do_nexus_saving(self, dataset_id: uuid.UUID, path: str, sample_name: str, - save_as_float: bool) -> Optional[bool]: + def do_nexus_saving(self, dataset_id: uuid.UUID, path: str, sample_name: str, save_as_float: bool) -> bool | None: if dataset_id in self.datasets and isinstance(self.datasets[dataset_id], StrictDataset): saver.nexus_save(self.datasets[dataset_id], path, sample_name, save_as_float) # type: ignore return True else: raise RuntimeError(f"Failed to get StrictDataset with ID {dataset_id}") - def get_existing_180_id(self, dataset_id: uuid.UUID) -> Optional[uuid.UUID]: + def get_existing_180_id(self, dataset_id: uuid.UUID) -> uuid.UUID | None: """ Gets the ID of the 180 projection object in a Dataset. :param dataset_id: The Dataset ID. @@ -155,7 +154,7 @@ def _remove_dataset(self, dataset_id: uuid.UUID): """ del self.datasets[dataset_id] - def remove_container(self, container_id: uuid.UUID) -> List[uuid.UUID]: + def remove_container(self, container_id: uuid.UUID) -> list[uuid.UUID]: """ Removes a container from the model. :param container_id: The ID of the dataset or image stack. @@ -182,25 +181,25 @@ def remove_container(self, container_id: uuid.UUID) -> List[uuid.UUID]: return ids_to_remove self.raise_error_when_images_not_found(container_id) - def add_dataset_to_model(self, dataset: Union[StrictDataset, MixedDataset]) -> None: + def add_dataset_to_model(self, dataset: StrictDataset | MixedDataset) -> None: self.datasets[dataset.id] = dataset @property - def image_ids(self) -> List[uuid.UUID]: + def image_ids(self) -> list[uuid.UUID]: images = [] for dataset in self.datasets.values(): images += dataset.all return [image.id for image in images if image is not None] @property - def images(self) -> List[ImageStack]: + def images(self) -> list[ImageStack]: images = [] for dataset in self.datasets.values(): images += dataset.all return images @property - def proj180s(self) -> List[ImageStack]: + def proj180s(self) -> list[ImageStack]: proj180s = [] for dataset in self.datasets.values(): if isinstance(dataset, StrictDataset) and dataset.proj180deg is not None: diff --git a/mantidimaging/gui/windows/main/nexus_save_dialog.py b/mantidimaging/gui/windows/main/nexus_save_dialog.py index 922aa415907..8d61bb64ce8 100644 --- a/mantidimaging/gui/windows/main/nexus_save_dialog.py +++ b/mantidimaging/gui/windows/main/nexus_save_dialog.py @@ -3,7 +3,6 @@ from __future__ import annotations import os import uuid -from typing import Optional, List from PyQt5.QtWidgets import QDialogButtonBox, QFileDialog, QRadioButton @@ -15,11 +14,11 @@ class NexusSaveDialog(BaseDialogView): - selected_dataset: Optional[uuid.UUID] + selected_dataset: uuid.UUID | None floatRadioButton: QRadioButton intRadioButton: QRadioButton - def __init__(self, parent, dataset_list: List[StrictDataset]): + def __init__(self, parent, dataset_list: list[StrictDataset]): super().__init__(parent, 'gui/ui/nexus_save_dialog.ui') self.browseButton.clicked.connect(self._set_save_path) @@ -28,7 +27,7 @@ def __init__(self, parent, dataset_list: List[StrictDataset]): self.savePath.editingFinished.connect(self._check_extension) self.sampleNameLineEdit.textChanged.connect(self.enable_save) - self.dataset_uuids: List[uuid.UUID] = [] + self.dataset_uuids: list[uuid.UUID] = [] self._create_dataset_lists(dataset_list) self.selected_dataset = None diff --git a/mantidimaging/gui/windows/main/presenter.py b/mantidimaging/gui/windows/main/presenter.py index f38c6dcbe15..f13cf6b1dad 100644 --- a/mantidimaging/gui/windows/main/presenter.py +++ b/mantidimaging/gui/windows/main/presenter.py @@ -6,7 +6,7 @@ from enum import Enum, auto from logging import getLogger from pathlib import Path -from typing import TYPE_CHECKING, Union, Optional, Dict, List, Any, NamedTuple, Iterable +from typing import TYPE_CHECKING, Any, NamedTuple, Iterable import numpy as np from PyQt5.QtWidgets import QTabBar, QApplication, QTreeWidgetItem @@ -67,7 +67,7 @@ class MainWindowPresenter(BasePresenter): def __init__(self, view: 'MainWindowView'): super().__init__(view) self.model = MainWindowModel() - self.stack_visualisers: Dict[uuid.UUID, StackVisualiserView] = {} + self.stack_visualisers: dict[uuid.UUID, StackVisualiserView] = {} def notify(self, signal: Notification, **baggage): try: @@ -102,7 +102,7 @@ def notify(self, signal: Notification, **baggage): self.show_error(e, traceback.format_exc()) getLogger(__name__).exception("Notification handler failed") - def _get_stack_visualiser_by_name(self, search_name: str) -> Optional[StackVisualiserView]: + def _get_stack_visualiser_by_name(self, search_name: str) -> StackVisualiserView | None: """ Uses the stack name to retrieve the QDockWidget object. :param search_name: The name of the stack widget to find. @@ -113,7 +113,7 @@ def _get_stack_visualiser_by_name(self, search_name: str) -> Optional[StackVisua return self.active_stacks[stack_id.id] return None - def get_stack_id_by_name(self, search_name: str) -> Optional[uuid.UUID]: + def get_stack_id_by_name(self, search_name: str) -> uuid.UUID | None: for stack_id in self.stack_visualiser_list: if stack_id.name == search_name: return stack_id.id @@ -218,13 +218,13 @@ def _create_and_tabify_stack_window(self, images: ImageStack, sample_dock: Stack stack_visualiser = self._create_lone_stack_window(images) self._tabify_stack_window(stack_visualiser, sample_dock) - def get_active_stack_visualisers(self) -> List[StackVisualiserView]: + def get_active_stack_visualisers(self) -> list[StackVisualiserView]: return list(self.active_stacks.values()) - def get_all_stacks(self) -> List[ImageStack]: + def get_all_stacks(self) -> list[ImageStack]: return self.model.images - def get_all_180_projections(self) -> List[ImageStack]: + def get_all_180_projections(self) -> list[ImageStack]: return self.model.proj180s def add_alternative_180_if_required(self, dataset: StrictDataset): @@ -323,9 +323,7 @@ def _create_lone_stack_window(self, images: ImageStack): self.stack_visualisers[stack_vis.id] = stack_vis return stack_vis - def _tabify_stack_window(self, - stack_window: StackVisualiserView, - tabify_stack: Optional[StackVisualiserView] = None): + def _tabify_stack_window(self, stack_window: StackVisualiserView, tabify_stack: StackVisualiserView | None = None): """ Places the newly created stack window into a tab. :param stack_window: The new stack window. @@ -399,16 +397,16 @@ def _on_save_done(self, task: 'TaskWorkerThread') -> None: self._handle_task_error(self.SAVE_ERROR_STRING, task) @property - def stack_visualiser_list(self) -> List[StackId]: + def stack_visualiser_list(self) -> list[StackId]: stacks = [StackId(stack_id, widget.windowTitle()) for stack_id, widget in self.active_stacks.items()] return sorted(stacks, key=lambda x: x.name) @property - def datasets(self) -> Iterable[Union[MixedDataset, StrictDataset]]: + def datasets(self) -> Iterable[MixedDataset | StrictDataset]: return self.model.datasets.values() @property - def strict_dataset_list(self) -> List[DatasetId]: + def strict_dataset_list(self) -> list[DatasetId]: datasets = [ DatasetId(dataset.id, dataset.name) for dataset in self.model.datasets.values() if isinstance(dataset, StrictDataset) @@ -427,10 +425,10 @@ def all_stack_ids(self) -> Iterable[uuid.UUID]: return stack_ids @property - def stack_visualiser_names(self) -> List[str]: + def stack_visualiser_names(self) -> list[str]: return [widget.windowTitle() for widget in self.stack_visualisers.values()] - def get_dataset(self, dataset_id: uuid.UUID) -> Optional[Union[MixedDataset, StrictDataset]]: + def get_dataset(self, dataset_id: uuid.UUID) -> MixedDataset | StrictDataset | None: return self.model.datasets.get(dataset_id) def get_stack_visualiser(self, stack_id: uuid.UUID) -> StackVisualiserView: @@ -442,14 +440,14 @@ def get_stack(self, stack_id: uuid.UUID) -> ImageStack: raise RuntimeError(f"Stack not found: {stack_id}") return images - def get_stack_visualiser_history(self, stack_id: uuid.UUID) -> Dict[str, Any]: + def get_stack_visualiser_history(self, stack_id: uuid.UUID) -> dict[str, Any]: return self.get_stack_visualiser(stack_id).presenter.images.metadata def get_dataset_id_for_stack(self, stack_id: uuid.UUID) -> uuid.UUID: return self.model.get_parent_dataset(stack_id) @property - def active_stacks(self) -> Dict[uuid.UUID, StackVisualiserView]: + def active_stacks(self) -> dict[uuid.UUID, StackVisualiserView]: return {stack_id: stack for (stack_id, stack) in self.stack_visualisers.items() if stack.isVisible()} @property @@ -744,7 +742,7 @@ def _add_images_to_existing_dataset(self): self.create_single_tabbed_images_stack(new_images) self.view.model_changed.emit() - def _add_recon_to_dataset_and_tree_view(self, dataset: Union[MixedDataset, StrictDataset], recon: ImageStack): + def _add_recon_to_dataset_and_tree_view(self, dataset: MixedDataset | StrictDataset, recon: ImageStack): """ Adds a recon to the dataset and updates the tree view. :param dataset: The dataset. diff --git a/mantidimaging/gui/windows/main/test/presenter_test.py b/mantidimaging/gui/windows/main/test/presenter_test.py index 53ecf639003..c7fe4a0f600 100644 --- a/mantidimaging/gui/windows/main/test/presenter_test.py +++ b/mantidimaging/gui/windows/main/test/presenter_test.py @@ -4,7 +4,6 @@ import unittest import uuid -from typing import List from unittest import mock from unittest.mock import patch, call @@ -21,7 +20,7 @@ from mantidimaging.test_helpers.unit_test_helper import generate_images -def generate_images_with_filenames(n_images: int) -> List[ImageStack]: +def generate_images_with_filenames(n_images: int) -> list[ImageStack]: images = [] for _ in range(n_images): im = generate_images() @@ -64,7 +63,7 @@ def stack_id(): def tearDown(self) -> None: self.presenter.stack_visualisers = [] - def create_mock_stacks_with_names(self, stack_names: List[str]): + def create_mock_stacks_with_names(self, stack_names: list[str]): stacks = {} for name in stack_names: stack_mock = mock.Mock() diff --git a/mantidimaging/gui/windows/main/view.py b/mantidimaging/gui/windows/main/view.py index 0aa21bec8b1..31191afc5bb 100644 --- a/mantidimaging/gui/windows/main/view.py +++ b/mantidimaging/gui/windows/main/view.py @@ -6,7 +6,7 @@ import uuid from logging import getLogger from pathlib import Path -from typing import Optional, List, Union, TYPE_CHECKING +from typing import TYPE_CHECKING from uuid import UUID import numpy as np @@ -76,7 +76,7 @@ class MainWindowView(BaseMainWindowView): menuWorkflow: QMenu menuImage: QMenu menuHelp: QMenu - menuTreeView: Optional[QMenu] = None + menuTreeView: QMenu | None = None actionRecon: QAction actionFilters: QAction @@ -93,17 +93,17 @@ class MainWindowView(BaseMainWindowView): actionSaveNeXusFile: QAction actionExit: QAction - filters: Optional[FiltersWindowView] = None - recon: Optional[ReconstructWindowView] = None - spectrum_viewer: Optional[SpectrumViewerWindowView] = None - live_viewer: Optional[LiveViewerWindowView] = None + filters: FiltersWindowView | None = None + recon: ReconstructWindowView | None = None + spectrum_viewer: SpectrumViewerWindowView | None = None + live_viewer: LiveViewerWindowView | None = None - image_load_dialog: Optional[ImageLoadDialog] = None - image_save_dialog: Optional[ImageSaveDialog] = None - nexus_load_dialog: Optional[NexusLoadDialog] = None - nexus_save_dialog: Optional[NexusSaveDialog] = None - add_to_dataset_dialog: Optional[AddImagesToDatasetDialog] = None - move_stack_dialog: Optional[MoveStackDialog] = None + image_load_dialog: ImageLoadDialog | None = None + image_save_dialog: ImageSaveDialog | None = None + nexus_load_dialog: NexusLoadDialog | None = None + nexus_save_dialog: NexusSaveDialog | None = None + add_to_dataset_dialog: AddImagesToDatasetDialog | None = None + move_stack_dialog: MoveStackDialog | None = None def __init__(self, open_dialogs: bool = True): super().__init__(None, "gui/ui/main_window.ui") @@ -198,7 +198,7 @@ def populate_image_menu(self): else: populate_menu(self.menuImage, current_stack.actions) - def current_showing_stack(self) -> Optional[StackVisualiserView]: + def current_showing_stack(self) -> StackVisualiserView | None: for stack in self.findChildren(StackVisualiserView): if not stack.visibleRegion().isEmpty(): return stack @@ -432,10 +432,10 @@ def get_images_from_stack_uuid(self, stack_uuid) -> ImageStack: def get_dataset_id_from_stack_uuid(self, stack_id: uuid.UUID) -> uuid.UUID: return self.presenter.get_dataset_id_for_stack(stack_id) - def get_dataset(self, dataset_id: uuid.UUID) -> Optional[Union['MixedDataset', StrictDataset]]: + def get_dataset(self, dataset_id: uuid.UUID) -> "MixedDataset" | StrictDataset | None: return self.presenter.get_dataset(dataset_id) - def get_all_stacks(self) -> List[ImageStack]: + def get_all_stacks(self) -> list[ImageStack]: return self.presenter.get_all_stacks() def get_all_180_projections(self): @@ -569,7 +569,7 @@ def create_child_tree_item(parent: QTreeDatasetWidgetItem, dataset_id: uuid.UUID parent.addChild(child) @staticmethod - def get_sinograms_item(parent: QTreeDatasetWidgetItem) -> Optional[QTreeDatasetWidgetItem]: + def get_sinograms_item(parent: QTreeDatasetWidgetItem) -> QTreeDatasetWidgetItem | None: """ Tries to look for a sinograms entry in a dataset tree view item. :return: The sinograms entry if found, None otherwise. @@ -645,7 +645,7 @@ def add_recon_group(dataset_item: QTreeDatasetWidgetItem, recon_id: uuid.UUID) - return recon_group @staticmethod - def get_recon_group(dataset_item: QTreeDatasetWidgetItem) -> Optional[QTreeDatasetWidgetItem]: + def get_recon_group(dataset_item: QTreeDatasetWidgetItem) -> QTreeDatasetWidgetItem | None: """ Looks for an existing recon group in a dataset tree view item. :param dataset_item: The dataset item to look for the recon group in. diff --git a/mantidimaging/gui/windows/nexus_load_dialog/presenter.py b/mantidimaging/gui/windows/nexus_load_dialog/presenter.py index c5800f542b2..369fddc853c 100644 --- a/mantidimaging/gui/windows/nexus_load_dialog/presenter.py +++ b/mantidimaging/gui/windows/nexus_load_dialog/presenter.py @@ -5,7 +5,7 @@ import traceback from enum import auto, Enum from logging import getLogger -from typing import TYPE_CHECKING, Optional, Union, Tuple +from typing import TYPE_CHECKING import h5py import numpy as np @@ -124,7 +124,7 @@ def scan_nexus_file(self): self.view.show_data_error(unable_message) self.view.disable_ok_button() - def _read_rotation_angles(self, image_key: int, before: bool | None = None) -> Optional[np.ndarray]: + def _read_rotation_angles(self, image_key: int, before: bool | None = None) -> np.ndarray | None: """ Reads the rotation angles array and coverts them to radians if needed. :param image_key: The image key for the angles to read. @@ -160,8 +160,7 @@ def _missing_data_error(self, field: str): self.view.show_data_error(error_msg) - def _look_for_tomo_data_and_update_view(self, field: str, - position: int) -> Optional[Union[h5py.Group, h5py.Dataset]]: + def _look_for_tomo_data_and_update_view(self, field: str, position: int) -> h5py.Group | h5py.Dataset | None: """ Looks for the data in the NeXus file and adds information about it to the view if it's found. :param field: The name of the NeXus field. @@ -177,7 +176,7 @@ def _look_for_tomo_data_and_update_view(self, field: str, self.view.set_data_found(position, True, self.tomo_path + "/" + field, dataset.shape) return dataset - def _look_for_image_data_and_update_view(self) -> Optional[h5py.Dataset]: + def _look_for_image_data_and_update_view(self) -> h5py.Dataset | None: position = 2 dataset = self._look_for_tomo_data(DATA_PATH) if dataset is not None: @@ -195,7 +194,7 @@ def _look_for_image_data_and_update_view(self) -> Optional[h5py.Dataset]: self.view.disable_ok_button() return None - def _look_for_nxtomo_entry(self) -> Optional[h5py.Group]: + def _look_for_nxtomo_entry(self) -> h5py.Group | None: """ Look for a tomo_entry field in the NeXus file. Generate an error and disable the view OK button if it can't be found. @@ -222,7 +221,7 @@ def _look_for_recon_entries(self): nexus_recon = self.nexus_file[key] self.recon_data.append(np.array(nexus_recon["data"]["data"])) - def _look_for_tomo_data(self, entry_path: str) -> Optional[Union[h5py.Group, h5py.Dataset]]: + def _look_for_tomo_data(self, entry_path: str) -> h5py.Group | h5py.Dataset | None: """ Retrieve data from the tomo entry field. :param entry_path: The path in which the data is found. @@ -258,7 +257,7 @@ def _get_data_from_image_key(self): self.dark_after_array = self._get_images(ImageKeys.DarkField, False) self.view.set_images_found(4, self.dark_after_array.size != 0, self.dark_after_array.shape) - def _get_images(self, image_key_number: ImageKeys, before: Optional[bool] = None) -> np.ndarray: + def _get_images(self, image_key_number: ImageKeys, before: bool | None = None) -> np.ndarray: """ Retrieve images from the data based on an image key number. :param image_key_number: The image key number. @@ -291,7 +290,7 @@ def _find_data_title(self) -> str: logger.info("A valid title couldn't be found. Using 'NeXus Data' instead.") return "NeXus Data" - def get_dataset(self) -> Tuple[StrictDataset, str]: + def get_dataset(self) -> tuple[StrictDataset, str]: """ Create a LoadingDataset and title using the arrays that have been retrieved from the NeXus file. :return: A tuple containing the Dataset and the data title string. @@ -350,7 +349,7 @@ def _create_images(self, data_array: np.ndarray, name: str) -> ImageStack: data.array[:] = data_array return ImageStack(data, [f"{name} {self.title}"]) - def _create_images_if_required(self, data_array: np.ndarray, name: str, image_key: int) -> Optional[ImageStack]: + def _create_images_if_required(self, data_array: np.ndarray, name: str, image_key: int) -> ImageStack | None: """ Create the ImageStack objects if the corresponding data was found in the NeXus file, and the user checked the "Use?" checkbox. diff --git a/mantidimaging/gui/windows/nexus_load_dialog/view.py b/mantidimaging/gui/windows/nexus_load_dialog/view.py index 020c91686b0..8857ea5e149 100644 --- a/mantidimaging/gui/windows/nexus_load_dialog/view.py +++ b/mantidimaging/gui/windows/nexus_load_dialog/view.py @@ -1,7 +1,6 @@ # Copyright (C) 2024 ISIS Rutherford Appleton Laboratory UKRI # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import Tuple from PyQt5.QtCore import Qt, QEventLoop from PyQt5.QtWidgets import QPushButton, QFileDialog, QLineEdit, QTreeWidget, QTreeWidgetItem, \ @@ -118,7 +117,7 @@ def clear_widgets(self) -> None: child.setText(column, "") self.tree.removeItemWidget(child, CHECKBOX_COLUMN) - def set_data_found(self, position: int, found: bool, path: str, shape: Tuple[int, ...]) -> None: + def set_data_found(self, position: int, found: bool, path: str, shape: tuple[int, ...]) -> None: """ Indicate on the QTreeWidget if the image key and data fields have been found or not. :param position: The row position for the data. @@ -137,7 +136,7 @@ def set_data_found(self, position: int, found: bool, path: str, shape: Tuple[int data_section.setText(PATH_COLUMN, path) data_section.setText(SHAPE_COLUMN, str(shape)) - def set_images_found(self, position: int, found: bool, shape: Tuple[int, int, int]) -> None: + def set_images_found(self, position: int, found: bool, shape: tuple[int, int, int]) -> None: """ Indicate on the QTreeWidget if the projections and dark/flat before/after images were found in the data array. :param position: The row position for the image type. diff --git a/mantidimaging/gui/windows/operations/model.py b/mantidimaging/gui/windows/operations/model.py index 94dbdcb42ae..71a811a2b4c 100644 --- a/mantidimaging/gui/windows/operations/model.py +++ b/mantidimaging/gui/windows/operations/model.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from typing import Callable, TYPE_CHECKING, List, Any, Dict +from typing import Callable, TYPE_CHECKING, Any from mantidimaging.core.operations.base_filter import FilterGroup from mantidimaging.core.operations.loader import load_filter_packages @@ -18,9 +18,9 @@ class FiltersWindowModel(object): - filters: List[BaseFilterClass] + filters: list[BaseFilterClass] selected_filter: BaseFilterClass - filter_widget_kwargs: Dict[str, Any] + filter_widget_kwargs: dict[str, Any] def __init__(self, presenter: 'FiltersWindowPresenter'): super().__init__() @@ -72,7 +72,7 @@ def filter_names(self): return filter_names def filter_registration_func( - self, filter_name: str) -> Callable[['QFormLayout', Callable, BaseMainWindowView], Dict[str, Any]]: + self, filter_name: str) -> Callable[['QFormLayout', Callable, BaseMainWindowView], dict[str, Any]]: """ Gets the function used to register the GUI of a given filter. @@ -106,7 +106,7 @@ def setup_filter(self, filter_name, filter_widget_kwargs): self.selected_filter = self.filters[filter_idx] self.filter_widget_kwargs = filter_widget_kwargs - def apply_to_stacks(self, stacks: List['ImageStack'], progress=None): + def apply_to_stacks(self, stacks: list['ImageStack'], progress=None): """ Applies the selected filter to a given image stack. @@ -134,7 +134,7 @@ def apply_to_images(self, images: ImageStack, progress=None) -> None: *exec_func.args, **exec_func.keywords) - def do_apply_filter(self, stacks: List['ImageStack'], post_filter: Callable[[Any], None]): + def do_apply_filter(self, stacks: list['ImageStack'], post_filter: Callable[[Any], None]): """ Applies the selected filter to the selected stack. """ @@ -146,7 +146,7 @@ def do_apply_filter(self, stacks: List['ImageStack'], post_filter: Callable[[Any apply_func = partial(self.apply_to_stacks, stacks) start_async_task_view(self.presenter.view, apply_func, post_filter) - def do_apply_filter_sync(self, stacks: List['ImageStack'], post_filter: Callable[[Any], None]): + def do_apply_filter_sync(self, stacks: list['ImageStack'], post_filter: Callable[[Any], None]): """ Applies the selected filter to the selected stack in a synchronous manner """ diff --git a/mantidimaging/gui/windows/operations/presenter.py b/mantidimaging/gui/windows/operations/presenter.py index 55edbb278f8..a8bbf0426db 100644 --- a/mantidimaging/gui/windows/operations/presenter.py +++ b/mantidimaging/gui/windows/operations/presenter.py @@ -8,7 +8,7 @@ from itertools import groupby from logging import getLogger from time import sleep -from typing import List, TYPE_CHECKING, Optional, Tuple +from typing import TYPE_CHECKING from uuid import UUID import numpy as np @@ -65,7 +65,7 @@ def _find_nan_change(before_image, filtered_image_data): return nan_change -def sub(x: Tuple[int, int]) -> int: +def sub(x: tuple[int, int]) -> int: """ Subtracts two tuples. Helper method for generating the negative slice list. :param x: The int tuple. @@ -74,7 +74,7 @@ def sub(x: Tuple[int, int]) -> int: return x[1] - x[0] -def _group_consecutive_values(slices: List[int]) -> List[str]: +def _group_consecutive_values(slices: list[int]) -> list[str]: """ Creates a list of slices with negative indices in a readable format. :param slices: The list of indices of the slices that contain negative values. @@ -94,7 +94,7 @@ def _group_consecutive_values(slices: List[int]) -> List[str]: class FiltersWindowPresenter(BasePresenter): view: 'FiltersWindowView' - stack: Optional[ImageStack] = None + stack: ImageStack | None = None divider = "------------------------------------" def __init__(self, view: 'FiltersWindowView', main_window: 'MainWindowView'): @@ -142,13 +142,13 @@ def max_preview_image_idx(self): else: return self.stack.num_sinograms - 1 - def set_stack_uuid(self, uuid: Optional[uuid.UUID]): + def set_stack_uuid(self, uuid: uuid.UUID | None): if uuid is not None: self.set_stack(self.main_window.get_stack(uuid)) else: self.set_stack(None) - def set_stack(self, stack: Optional[ImageStack]): + def set_stack(self, stack: ImageStack | None): self.stack = stack # Update the preview image index @@ -265,7 +265,7 @@ def _wait_for_stack_choice(self, new_stack: ImageStack, stack_uuid: UUID): def is_a_proj180deg(self, stack_to_check: ImageStack): return any(stack_to_check is stack for stack in self.main_window.get_all_180_projections()) - def _post_filter(self, updated_stacks: List[ImageStack], task): + def _post_filter(self, updated_stacks: list[ImageStack], task): try: use_new_data = True negative_stacks = [] @@ -313,7 +313,7 @@ def _post_filter(self, updated_stacks: List[ImageStack], task): self._set_apply_buttons_enabled(self.prev_apply_single_state, self.prev_apply_all_state) self.filter_is_running = False - def _do_apply_filter(self, apply_to: List[ImageStack]): + def _do_apply_filter(self, apply_to: list[ImageStack]): self.filter_is_running = True # Record the previous button states self.prev_apply_single_state = self.view.applyButton.isEnabled() @@ -462,7 +462,7 @@ def init_roi_field(self, roi_field: QLineEdit): crop_string = ", ".join(["0", "0", str(y), str(x)]) roi_field.setText(crop_string) - def _show_negative_values_error(self, negative_stacks: List[ImageStack]): + def _show_negative_values_error(self, negative_stacks: list[ImageStack]): """ Shows information on the view and in the log about negative values in the output. :param negative_stacks: A list of stacks with negative values in the data. diff --git a/mantidimaging/gui/windows/operations/test/presenter_test.py b/mantidimaging/gui/windows/operations/test/presenter_test.py index bec90cffd9c..a40eb7e970c 100644 --- a/mantidimaging/gui/windows/operations/test/presenter_test.py +++ b/mantidimaging/gui/windows/operations/test/presenter_test.py @@ -7,7 +7,6 @@ import numpy as np import numpy.testing as npt from functools import partial -from typing import List from unittest import mock from unittest.mock import DEFAULT, Mock @@ -31,7 +30,7 @@ def setUp(self) -> None: self.presenter = FiltersWindowPresenter(self.view, self.main_window) self.presenter.model.filter_widget_kwargs = {"roi_field": None} self.view.presenter = self.presenter - self.mock_stacks: List[ImageStack] = [] + self.mock_stacks: list[ImageStack] = [] for _ in range(2): mock_stack = mock.Mock() mock_stack.data = np.zeros([3, 3, 3]) @@ -197,7 +196,7 @@ def test_images_with_180_deg_proj_calls_filter_on_the_180_deg(self, _do_apply_fi mock_stack = mock.MagicMock() mock_stack.has_proj180deg.return_value = True mock_stack.data = np.arange(3) - mock_stacks: List[ImageStack] = [mock_stack] + mock_stacks: list[ImageStack] = [mock_stack] mock_task = mock.MagicMock() mock_task.error = None diff --git a/mantidimaging/gui/windows/recon/image_view.py b/mantidimaging/gui/windows/recon/image_view.py index 5fd8892dd3e..4683c14c880 100644 --- a/mantidimaging/gui/windows/recon/image_view.py +++ b/mantidimaging/gui/windows/recon/image_view.py @@ -2,7 +2,6 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations from math import isnan -from typing import Optional import numpy as np from PyQt5 import QtCore @@ -64,7 +63,7 @@ def cleanup(self): def slice_line_moved(self): self.slice_changed(int(self.slice_line.value())) - def update_projection(self, image_data: np.ndarray, preview_slice_index: int, tilt_angle: Optional[Degrees]): + def update_projection(self, image_data: np.ndarray, preview_slice_index: int, tilt_angle: Degrees | None): self.imageview_projection.clear() self.imageview_projection.setImage(image_data) self.imageview_projection.histogram.imageChanged(autoLevel=True, autoRange=True) @@ -127,7 +126,7 @@ def hide_tilt(self): if self.tilt_line.scene() is not None: self.imageview_projection.viewbox.removeItem(self.tilt_line) - def set_tilt(self, tilt: Degrees, pos: Optional[int] = None): + def set_tilt(self, tilt: Degrees, pos: int | None = None): if not isnan(tilt.value): # is isnan it means there is no tilt, i.e. the line is vertical if pos is not None: self.tilt_line.setAngle(90) diff --git a/mantidimaging/gui/windows/recon/model.py b/mantidimaging/gui/windows/recon/model.py index 45c53b9e6b6..3b1793bbccb 100644 --- a/mantidimaging/gui/windows/recon/model.py +++ b/mantidimaging/gui/windows/recon/model.py @@ -2,7 +2,7 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations from logging import getLogger -from typing import List, Optional, Tuple, Union, TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any import numpy as np @@ -28,7 +28,7 @@ class ReconstructWindowModel(object): def __init__(self, data_model: CorTiltPointQtModel): - self._images: Optional[ImageStack] = None + self._images: ImageStack | None = None self._preview_projection_idx = 0 self._preview_slice_idx = 0 self._selected_row = 0 @@ -71,7 +71,7 @@ def last_cor(self, value: ScalarCoR) -> None: def has_results(self) -> bool: return self.data_model.has_results - def get_results(self) -> Tuple[ScalarCoR, Degrees, Slope]: + def get_results(self) -> tuple[ScalarCoR, Degrees, Slope]: return self.data_model.cor, self.data_model.angle_in_degrees, self.data_model.gradient @property @@ -82,7 +82,7 @@ def images(self): def num_points(self) -> int: return self.data_model.num_points - def initial_select_data(self, images: 'Optional[ImageStack]'): + def initial_select_data(self, images: 'ImageStack | None'): self._images = images self.reset_cor_model() @@ -94,7 +94,7 @@ def reset_cor_model(self) -> None: self.preview_projection_idx = 0 self.preview_slice_idx = slice_idx - def find_initial_cor(self) -> Tuple[int, ScalarCoR]: + def find_initial_cor(self) -> tuple[int, ScalarCoR]: if self.images is None: return 0, ScalarCoR(0) @@ -119,7 +119,7 @@ def run_preview_recon(self, slice_idx: int, cor: ScalarCoR, recon_params: ReconstructionParameters, - progress: Progress | None = None) -> Optional[ImageStack]: + progress: Progress | None = None) -> ImageStack | None: # Ensure we have some sample data images = self.images if images is None: @@ -137,7 +137,7 @@ def run_preview_recon(self, recon = self._apply_pixel_size(recon, recon_params) return recon - def run_full_recon(self, recon_params: ReconstructionParameters, progress: Progress) -> Optional[ImageStack]: + def run_full_recon(self, recon_params: ReconstructionParameters, progress: Progress) -> ImageStack | None: # Ensure we have some sample data images = self.images if images is None: @@ -163,7 +163,7 @@ def _apply_pixel_size(recon, recon_params: ReconstructionParameters, progress=No return recon @property - def tilt_angle(self) -> Optional[Degrees]: + def tilt_angle(self) -> Degrees | None: if self.data_model.has_results: return self.data_model.angle_in_degrees else: @@ -190,7 +190,7 @@ def get_allowed_filters(alg_name: str) -> list: reconstructor = get_reconstructor_for(alg_name) return reconstructor.allowed_filters() - def get_me_a_cor(self, cor: Optional[ScalarCoR] = None) -> ScalarCoR: + def get_me_a_cor(self, cor: ScalarCoR | None = None) -> ScalarCoR: if cor is not None: # a rotation has been passed in! return cor @@ -214,14 +214,14 @@ def set_precalculated(self, cor: ScalarCoR, tilt: Degrees) -> None: def is_current_stack(self, uuid: "uuid.UUID") -> bool: return self.stack_id == uuid - def get_slice_indices(self, num_cors: int) -> Tuple[int, Union[np.ndarray, Tuple[np.ndarray, Optional[float]]]]: + def get_slice_indices(self, num_cors: int) -> tuple[int, np.ndarray | tuple[np.ndarray, float | None]]: # used to crop off 20% off the top and bottom, which is usually noise/empty remove_a_bit = self.images.height * 0.2 slices = np.linspace(remove_a_bit, self.images.height - remove_a_bit, num=num_cors, dtype=np.int32) return self.selected_row, slices - def auto_find_minimisation_sqsum(self, slices: List[int], recon_params: ReconstructionParameters, - initial_cor: List[float], progress: Progress) -> List[float]: + def auto_find_minimisation_sqsum(self, slices: list[int], recon_params: ReconstructionParameters, + initial_cor: list[float], progress: Progress) -> list[float]: """ :param slices: Slice indices to be reconstructed @@ -251,7 +251,7 @@ def auto_find_minimisation_sqsum(self, slices: List[int], recon_params: Reconstr progress.update(msg=f"Calculating COR for slice {slice}") return cors - def auto_find_correlation(self, progress: Progress) -> Tuple[ScalarCoR, Degrees]: + def auto_find_correlation(self, progress: Progress) -> tuple[ScalarCoR, Degrees]: return find_center(self.images, progress) @staticmethod diff --git a/mantidimaging/gui/windows/recon/point_table_model.py b/mantidimaging/gui/windows/recon/point_table_model.py index e9e9f459cdd..e0578357199 100644 --- a/mantidimaging/gui/windows/recon/point_table_model.py +++ b/mantidimaging/gui/windows/recon/point_table_model.py @@ -3,7 +3,6 @@ from __future__ import annotations from enum import Enum -from typing import List from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt @@ -77,7 +76,7 @@ def data(self, index, role=Qt.ItemDataRole.DisplayRole): return 'Centre of rotation for specific slice' return '' - def getColumn(self, column_index) -> List[int]: + def getColumn(self, column_index) -> list[int]: if column_index != 0 and column_index != 1: return [] else: diff --git a/mantidimaging/gui/windows/recon/presenter.py b/mantidimaging/gui/windows/recon/presenter.py index e20d8aabb7c..50ba6c54de4 100644 --- a/mantidimaging/gui/windows/recon/presenter.py +++ b/mantidimaging/gui/windows/recon/presenter.py @@ -6,7 +6,7 @@ from enum import Enum, auto from functools import partial from logging import getLogger -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Callable, Set +from typing import TYPE_CHECKING, Any, Callable import numpy as np from PyQt5.QtWidgets import QWidget @@ -58,8 +58,8 @@ def __init__(self, view: 'ReconstructWindowView', main_window: 'MainWindowView') super().__init__(view) self.view = view self.model = ReconstructWindowModel(self.view.cor_table_model) - self.allowed_recon_kwargs: Dict[str, List[str]] = self.model.load_allowed_recon_kwargs() - self.restricted_arg_widgets: Dict[str, List[QWidget]] = { + self.allowed_recon_kwargs: dict[str, list[str]] = self.model.load_allowed_recon_kwargs() + self.restricted_arg_widgets: dict[str, list[QWidget]] = { 'filter_name': [self.view.filterName, self.view.filterNameLabel], 'num_iter': [self.view.numIter, self.view.numIterLabel], 'alpha': [self.view.alphaSpinBox, self.view.alphaLabel], @@ -71,7 +71,7 @@ def __init__(self, view: 'ReconstructWindowView', main_window: 'MainWindowView') self.main_window = main_window self.recon_is_running = False - self.async_tracker: Set[Any] = set() + self.async_tracker: set[Any] = set() self.main_window.stack_changed.connect(self.handle_stack_changed) self.stack_changed_pending = False @@ -239,7 +239,7 @@ def _get_reconstruct_slice(self, cor, slice_idx: int, call_back: Callable[[TaskW }, tracker=self.async_tracker) - def _get_slice_index(self, slice_idx: Optional[int]) -> int: + def _get_slice_index(self, slice_idx: int | None) -> int: if slice_idx is None: slice_idx = self.model.preview_slice_idx else: @@ -248,7 +248,7 @@ def _get_slice_index(self, slice_idx: Optional[int]) -> int: def do_preview_reconstruct_slice(self, cor=None, - slice_idx: Optional[int] = None, + slice_idx: int | None = None, force_update: bool = False, reset_roi: bool = False): if self.model.images is None: @@ -272,7 +272,7 @@ def _on_preview_reconstruct_slice_done(self, task: TaskWorkerThread, reset_roi: # will still be available after this function ends self.view.update_recon_preview(np.copy(images.data[0]), reset_roi) - def do_stack_reconstruct_slice(self, cor=None, slice_idx: Optional[int] = None): + def do_stack_reconstruct_slice(self, cor=None, slice_idx: int | None = None): self.view.set_recon_buttons_enabled(False) slice_idx = self._get_slice_index(slice_idx) self._get_reconstruct_slice(cor, slice_idx, self._on_stack_reconstruct_slice_done) diff --git a/mantidimaging/gui/windows/recon/view.py b/mantidimaging/gui/windows/recon/view.py index 8f1870fc114..0230bf9a58b 100644 --- a/mantidimaging/gui/windows/recon/view.py +++ b/mantidimaging/gui/windows/recon/view.py @@ -3,7 +3,7 @@ from __future__ import annotations import uuid from logging import getLogger -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING from uuid import UUID import numpy @@ -78,7 +78,7 @@ class ReconstructWindowView(BaseMainWindowView): messageIcon: QLabel changeColourPaletteButton: QPushButton - change_colour_palette_dialog: Optional[PaletteChangerView] = None + change_colour_palette_dialog: PaletteChangerView | None = None stackSelector: DatasetSelectorWidgetView @@ -275,7 +275,7 @@ def preview_image_on_button_press(self, event) -> None: if event.button == 1 and event.ydata is not None: self.presenter.set_preview_slice_idx(int(event.ydata)) - def update_projection(self, image_data, preview_slice_index: int, tilt_angle: Optional[Degrees]): + def update_projection(self, image_data, preview_slice_index: int, tilt_angle: Degrees | None): """ Updates the preview projection image and associated annotations. @@ -350,7 +350,7 @@ def add_cor_table_row(self, row: int, slice_index: int, cor: float): self.cor_table_model.appendNewRow(row, slice_index, cor) self.tableView.selectRow(row) - def get_cor_table_selected_rows(self) -> List[int]: + def get_cor_table_selected_rows(self) -> list[int]: rows = self.tableView.selectionModel().selectedRows() return [row.row() for row in rows] @@ -428,7 +428,7 @@ def regularisation_percent(self) -> int: return self.regPercentSpinBox.value() @property - def beam_hardening_coefs(self) -> Optional[List[float]]: + def beam_hardening_coefs(self) -> list[float] | None: if not self.lbhc_enabled.isChecked(): return None params = [] @@ -466,7 +466,7 @@ def set_table_point(self, idx, slice_idx, cor): def show_recon_volume(self, data: ImageStack, stack_id: uuid.UUID) -> None: self.main_window.add_recon_to_dataset(data, stack_id) - def get_stack(self, uuid) -> Optional['ImageStack']: + def get_stack(self, uuid) -> 'ImageStack' | None: if uuid is not None: return self.main_window.get_stack(uuid) return None @@ -474,11 +474,11 @@ def get_stack(self, uuid) -> Optional['ImageStack']: def hide_tilt(self) -> None: self.image_view.hide_tilt() - def set_filters_for_recon_tool(self, filters: List[str]) -> None: + def set_filters_for_recon_tool(self, filters: list[str]) -> None: self.filterName.clear() self.filterName.insertItems(0, filters) - def get_number_of_cors(self) -> Optional[int]: + def get_number_of_cors(self) -> int | None: num, accepted = QInputDialog.getInt(self, "Number of slices", "On how many slices to run the automatic CoR finding?", diff --git a/mantidimaging/gui/windows/spectrum_viewer/model.py b/mantidimaging/gui/windows/spectrum_viewer/model.py index a4112b1f5c3..040f7758183 100644 --- a/mantidimaging/gui/windows/spectrum_viewer/model.py +++ b/mantidimaging/gui/windows/spectrum_viewer/model.py @@ -4,7 +4,7 @@ import csv from enum import Enum from pathlib import Path -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import numpy as np from math import ceil @@ -52,8 +52,8 @@ class SpectrumViewerWindowModel: The model is also responsible for saving ROI data to a csv file. """ presenter: 'SpectrumViewerWindowPresenter' - _stack: Optional[ImageStack] = None - _normalise_stack: Optional[ImageStack] = None + _stack: ImageStack | None = None + _normalise_stack: ImageStack | None = None tof_range: tuple[int, int] = (0, 0) _roi_ranges: dict[str, SensibleROI] @@ -79,7 +79,7 @@ def get_list_of_roi_names(self) -> list[str]: """ return list(self._roi_ranges.keys()) - def set_stack(self, stack: Optional[ImageStack]) -> None: + def set_stack(self, stack: ImageStack | None) -> None: """ Sets the stack to be used by the model If that stack is None, then the model will be reset @@ -105,7 +105,7 @@ def set_new_roi(self, name: str) -> None: height, width = self.get_image_shape() self.set_roi(name, SensibleROI.from_list([0, 0, width, height])) - def set_normalise_stack(self, normalise_stack: Optional[ImageStack]) -> None: + def set_normalise_stack(self, normalise_stack: ImageStack | None) -> None: self._normalise_stack = normalise_stack def set_roi(self, roi_name: str, roi: SensibleROI): @@ -122,7 +122,7 @@ def get_roi(self, roi_name: str) -> SensibleROI: raise KeyError(f"ROI {roi_name} does not exist in roi_ranges {self._roi_ranges.keys()}") return self._roi_ranges[roi_name] - def get_averaged_image(self) -> Optional['np.ndarray']: + def get_averaged_image(self) -> "np.ndarray" | None: """ Get the averaged image from the stack in the model returning as a numpy array or None if it does not @@ -133,7 +133,7 @@ def get_averaged_image(self) -> Optional['np.ndarray']: return None @staticmethod - def get_stack_spectrum(stack: Optional[ImageStack], roi: SensibleROI): + def get_stack_spectrum(stack: ImageStack | None, roi: SensibleROI): """ Computes the mean spectrum of the given image stack within the specified region of interest (ROI). If the image stack is None, an empty numpy array is returned. diff --git a/mantidimaging/gui/windows/spectrum_viewer/presenter.py b/mantidimaging/gui/windows/spectrum_viewer/presenter.py index c117d69260e..31a0d81bb38 100644 --- a/mantidimaging/gui/windows/spectrum_viewer/presenter.py +++ b/mantidimaging/gui/windows/spectrum_viewer/presenter.py @@ -4,7 +4,7 @@ from enum import Enum from functools import partial -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from logging import getLogger from mantidimaging.core.data.dataset import StrictDataset @@ -37,8 +37,8 @@ class SpectrumViewerWindowPresenter(BasePresenter): view: 'SpectrumViewerWindowView' model: SpectrumViewerWindowModel spectrum_mode: SpecType = SpecType.SAMPLE - current_stack_uuid: Optional['UUID'] = None - current_norm_stack_uuid: Optional['UUID'] = None + current_stack_uuid: 'UUID' | None = None + current_norm_stack_uuid: 'UUID' | None = None export_mode: ExportMode def __init__(self, view: 'SpectrumViewerWindowView', main_window: 'MainWindowView'): @@ -58,14 +58,14 @@ def handle_stack_changed(self) -> None: normalise_uuid = self.view.get_normalise_stack() if normalise_uuid is not None: try: - norm_stack: Optional['ImageStack'] = self.main_window.get_stack(normalise_uuid) + norm_stack: 'ImageStack' | None = self.main_window.get_stack(normalise_uuid) except RuntimeError: norm_stack = None self.model.set_normalise_stack(norm_stack) self.show_new_sample() self.redraw_all_rois() - def handle_sample_change(self, uuid: Optional['UUID']) -> None: + def handle_sample_change(self, uuid: 'UUID' | None) -> None: if uuid == self.current_stack_uuid: return else: @@ -87,7 +87,7 @@ def handle_sample_change(self, uuid: Optional['UUID']) -> None: normalise_uuid = self.view.get_normalise_stack() if normalise_uuid is not None: try: - norm_stack: Optional['ImageStack'] = self.main_window.get_stack(normalise_uuid) + norm_stack: 'ImageStack' | None = self.main_window.get_stack(normalise_uuid) except RuntimeError: norm_stack = None self.model.set_normalise_stack(norm_stack) @@ -98,7 +98,7 @@ def handle_sample_change(self, uuid: Optional['UUID']) -> None: self.show_new_sample() self.view.on_visibility_change() - def handle_normalise_stack_change(self, normalise_uuid: Optional['UUID']) -> None: + def handle_normalise_stack_change(self, normalise_uuid: 'UUID' | None) -> None: if normalise_uuid == self.current_norm_stack_uuid: return else: @@ -123,7 +123,7 @@ def auto_find_flat_stack(self, new_dataset_id): elif new_dataset.flat_after is not None: self.view.try_to_select_relevant_normalise_stack(new_dataset.flat_after.name) - def get_dataset_id_for_stack(self, stack_id: Optional['UUID']) -> Optional['UUID']: + def get_dataset_id_for_stack(self, stack_id: 'UUID' | None) -> 'UUID' | None: return None if stack_id is None else self.main_window.get_dataset_id_from_stack_uuid(stack_id) def show_new_sample(self) -> None: diff --git a/mantidimaging/gui/windows/spectrum_viewer/spectrum_widget.py b/mantidimaging/gui/windows/spectrum_viewer/spectrum_widget.py index 6d901f56449..85d17262d3e 100644 --- a/mantidimaging/gui/windows/spectrum_viewer/spectrum_widget.py +++ b/mantidimaging/gui/windows/spectrum_viewer/spectrum_widget.py @@ -2,7 +2,7 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from PyQt5.QtCore import pyqtSignal, Qt, QSignalBlocker from PyQt5.QtGui import QColor @@ -80,7 +80,7 @@ def colour(self, colour: tuple[int, int, int, int]) -> None: self.setPen(self._colour) @property - def selected_row(self) -> Optional[int]: + def selected_row(self) -> int | None: return self._selected_row def adjust_spec_roi(self, roi: SensibleROI) -> None: @@ -101,7 +101,7 @@ class SpectrumWidget(QWidget): spectrum: PlotItem range_control: LinearRegionItem - roi_dict: dict[Optional[str], ROI] + roi_dict: dict[str | None, ROI] last_clicked_roi: str range_changed = pyqtSignal(object) @@ -131,7 +131,7 @@ def __init__(self) -> None: self.spectrum_data_dict: dict[str, np.ndarray | None] = {} - self.roi_dict: dict[Optional[str], ROI] = {} + self.roi_dict: dict[str | None, ROI] = {} self.colour_index = 0 def cleanup(self): diff --git a/mantidimaging/gui/windows/spectrum_viewer/view.py b/mantidimaging/gui/windows/spectrum_viewer/view.py index 2581b648df5..cec07c199d5 100644 --- a/mantidimaging/gui/windows/spectrum_viewer/view.py +++ b/mantidimaging/gui/windows/spectrum_viewer/view.py @@ -3,7 +3,7 @@ from __future__ import annotations from pathlib import Path -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from PyQt5.QtGui import QPixmap from PyQt5.QtWidgets import QCheckBox, QVBoxLayout, QFileDialog, QPushButton, QLabel, QAbstractItemView, QHeaderView, \ @@ -37,7 +37,7 @@ class SpectrumViewerWindowView(BaseMainWindowView): exportButton: QPushButton exportTabs: QTabWidget normaliseErrorIcon: QLabel - _current_dataset_id: Optional['UUID'] + _current_dataset_id: 'UUID' | None normalise_error_issue: str = "" image_output_mode_combobox: QComboBox transmission_error_mode_combobox: QComboBox @@ -63,7 +63,7 @@ def __init__(self, main_window: 'MainWindowView'): self.selected_row: int = 0 self.current_roi: str = "" - self.selected_row_data: Optional[list] = None + self.selected_row_data: list | None = None self.roiPropertiesSpinBoxes: dict[str, QSpinBox] = {} self.roiPropertiesLabels: dict[str, QLabel] = {} self.old_table_names: list = [] @@ -261,11 +261,11 @@ def roi_table_model(self) -> TableModel: return self.tableView.model() @property - def current_dataset_id(self) -> Optional['UUID']: + def current_dataset_id(self) -> 'UUID' | None: return self._current_dataset_id @current_dataset_id.setter - def current_dataset_id(self, uuid: Optional['UUID']) -> None: + def current_dataset_id(self, uuid: 'UUID' | None) -> None: self._current_dataset_id = uuid def _configure_dropdown(self, selector: DatasetSelectorWidgetView) -> None: @@ -275,10 +275,10 @@ def _configure_dropdown(self, selector: DatasetSelectorWidgetView) -> None: def try_to_select_relevant_normalise_stack(self, name: str) -> None: self.normaliseStackSelector.try_to_select_relevant_stack(name) - def get_normalise_stack(self) -> Optional['UUID']: + def get_normalise_stack(self) -> 'UUID' | None: return self.normaliseStackSelector.current() - def get_csv_filename(self) -> Optional[Path]: + def get_csv_filename(self) -> Path | None: path = QFileDialog.getSaveFileName(self, "Save CSV file", "", "CSV file (*.csv)")[0] if path: return Path(path) @@ -371,7 +371,7 @@ def update_roi_color(self, roi_name: str, new_color: tuple) -> None: if row is not None: self.roi_table_model.update_color(row, new_color) - def find_row_for_roi(self, roi_name: str) -> Optional[int]: + def find_row_for_roi(self, roi_name: str) -> int | None: """ Returns row index for ROI name, or None if not found. @param roi_name: Name ROI find. diff --git a/mantidimaging/gui/windows/stack_choice/view.py b/mantidimaging/gui/windows/stack_choice/view.py index 1f741fe9ed1..58673da4c08 100644 --- a/mantidimaging/gui/windows/stack_choice/view.py +++ b/mantidimaging/gui/windows/stack_choice/view.py @@ -3,7 +3,7 @@ from __future__ import annotations from enum import Enum, auto -from typing import TYPE_CHECKING, Optional, Tuple, Union +from typing import TYPE_CHECKING import numpy as np from PyQt5.QtCore import Qt @@ -31,7 +31,7 @@ class StackChoiceView(BaseMainWindowView): lockHistograms: QCheckBox def __init__(self, original_stack: ImageStack, new_stack: ImageStack, - presenter: Union['StackComparePresenter', 'StackChoicePresenter'], parent: Optional[QMainWindow]): + presenter: 'StackComparePresenter' | 'StackChoicePresenter', parent: QMainWindow | None): super().__init__(parent, "gui/ui/stack_choice_window.ui") self.presenter = presenter @@ -180,14 +180,14 @@ def _set_from_old_to_new(self) -> None: """ Signal triggered when the histograms are locked and the contrast values changed. """ - levels: Tuple[float, float] = self.original_stack.ui.histogram.getLevels() + levels: tuple[float, float] = self.original_stack.ui.histogram.getLevels() self.new_stack.ui.histogram.setLevels(*levels) def _set_from_new_to_old(self) -> None: """ Signal triggered when the histograms are locked and the contrast values changed. """ - levels: Tuple[float, float] = self.new_stack.ui.histogram.getLevels() + levels: tuple[float, float] = self.new_stack.ui.histogram.getLevels() self.original_stack.ui.histogram.setLevels(*levels) def connect_histogram_changes(self): diff --git a/mantidimaging/gui/windows/stack_visualiser/test/view_test.py b/mantidimaging/gui/windows/stack_visualiser/test/view_test.py index 6422a2f1695..ad47ab2687a 100644 --- a/mantidimaging/gui/windows/stack_visualiser/test/view_test.py +++ b/mantidimaging/gui/windows/stack_visualiser/test/view_test.py @@ -2,7 +2,6 @@ # SPDX - License - Identifier: GPL-3.0-or-later from __future__ import annotations import unittest -from typing import Tuple from unittest import mock import mantidimaging.test_helpers.unit_test_helper as th @@ -24,7 +23,7 @@ def setUp(self): self.window = MainWindowView() self.view, self.test_data = self._add_stack_visualiser() - def _add_stack_visualiser(self) -> Tuple[StackVisualiserView, ImageStack]: + def _add_stack_visualiser(self) -> tuple[StackVisualiserView, ImageStack]: test_data = th.generate_images() test_data.name = "Test Data" self.window.create_new_stack(test_data) diff --git a/mantidimaging/gui/windows/wizard/model.py b/mantidimaging/gui/windows/wizard/model.py index 004feee97a4..1cdde8e36b1 100644 --- a/mantidimaging/gui/windows/wizard/model.py +++ b/mantidimaging/gui/windows/wizard/model.py @@ -3,19 +3,19 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Iterable, List, Optional, Any +from typing import Iterable, Any class EnablePredicate(ABC): @abstractmethod - def __call__(self, history: Optional[dict[str, Any]]) -> bool: + def __call__(self, history: dict[str, Any] | None) -> bool: pass class LoadedPredicate(EnablePredicate): - def __call__(self, history: Optional[dict[str, Any]]) -> bool: + def __call__(self, history: dict[str, Any] | None) -> bool: return history is not None @@ -26,7 +26,7 @@ def __init__(self, rule: str): raise ValueError self.filter_name = rule.partition(":")[2] - def __call__(self, history: Optional[dict[str, Any]]) -> bool: + def __call__(self, history: dict[str, Any] | None) -> bool: if history is not None and "operation_history" in history: for op in history["operation_history"]: if op["name"] == self.filter_name: @@ -39,7 +39,7 @@ class AndPredicate(EnablePredicate): def __init__(self, predicates: Iterable[EnablePredicate]): self.predicates = predicates - def __call__(self, history: Optional[dict[str, Any]]) -> bool: + def __call__(self, history: dict[str, Any] | None) -> bool: for predicate in self.predicates: if not predicate(history): return False @@ -47,7 +47,7 @@ def __call__(self, history: Optional[dict[str, Any]]) -> bool: def EnablePredicateFactory(rules: str) -> EnablePredicate: - predicates: List[EnablePredicate] = [] + predicates: list[EnablePredicate] = [] for rule in rules.split(","): rule = rule.strip() if rule == "loaded": diff --git a/mantidimaging/gui/windows/wizard/view.py b/mantidimaging/gui/windows/wizard/view.py index da02c89e65f..296e6c34bc5 100644 --- a/mantidimaging/gui/windows/wizard/view.py +++ b/mantidimaging/gui/windows/wizard/view.py @@ -6,7 +6,6 @@ from PyQt5.QtWidgets import QLabel, QWidget, QVBoxLayout, QGroupBox, QPushButton, QStyle from PyQt5.QtGui import QIcon from PyQt5.QtCore import pyqtSignal, Qt -from typing import List, Dict, Optional from mantidimaging.gui.mvp_base import BaseDialogView from .model import EnablePredicateFactory @@ -20,13 +19,13 @@ def __init__(self, name, parent: QWidget = None) -> None: self.layout = QVBoxLayout(self) self.title_label = QLabel("Stage: " + name) self.layout.addWidget(self.title_label) - self.steps: List[WizardStep] = [] + self.steps: list[WizardStep] = [] def add_step(self, wizard_step: WizardStep) -> None: self.steps.append(wizard_step) self.layout.addWidget(wizard_step) - def handle_stack_change(self, stack_history: Optional[dict]) -> None: + def handle_stack_change(self, stack_history: dict | None) -> None: for step in self.steps: step.handle_stack_change(stack_history) @@ -72,7 +71,7 @@ def show_step(self) -> None: def hide_step(self) -> None: self.step_box.setVisible(False) - def handle_stack_change(self, stack_history: Optional[dict]) -> None: + def handle_stack_change(self, stack_history: dict | None) -> None: enabled = self.enable_predicate(stack_history) if not enabled: @@ -90,7 +89,7 @@ class WizardView(BaseDialogView): def __init__(self, parent, presenter) -> None: super().__init__(parent, "gui/ui/wizard.ui") - self.stages: Dict[str, WizardStage] = {} + self.stages: dict[str, WizardStage] = {} self.presenter = presenter def add_stage(self, stage_name: str) -> None: diff --git a/mantidimaging/test_helpers/unit_test_helper.py b/mantidimaging/test_helpers/unit_test_helper.py index f4335ac266b..bddb37a3811 100644 --- a/mantidimaging/test_helpers/unit_test_helper.py +++ b/mantidimaging/test_helpers/unit_test_helper.py @@ -6,7 +6,6 @@ import sys from functools import partial from pathlib import Path -from typing import Optional from unittest import mock import numpy as np @@ -24,7 +23,7 @@ g_shape = (10, 8, 10) -def gen_img_numpy_rand(shape=g_shape, seed: Optional[int] = None) -> np.ndarray: +def gen_img_numpy_rand(shape=g_shape, seed: int | None = None) -> np.ndarray: if seed is not None: bg = np.random.PCG64(seed) else: @@ -33,12 +32,12 @@ def gen_img_numpy_rand(shape=g_shape, seed: Optional[int] = None) -> np.ndarray: return rng.random(shape) -def generate_images(shape=g_shape, dtype=np.float32, seed: Optional[int] = None) -> ImageStack: +def generate_images(shape=g_shape, dtype=np.float32, seed: int | None = None) -> ImageStack: d = pu.create_array(shape, dtype) return _set_random_data(d, shape, seed=seed) -def generate_images_for_parallel(shape=(15, 8, 10), dtype=np.float32, seed: Optional[int] = None) -> ImageStack: +def generate_images_for_parallel(shape=(15, 8, 10), dtype=np.float32, seed: int | None = None) -> ImageStack: """ Doesn't do anything special, just makes a number of images big enough to be ran in parallel from the logic of multiprocessing_necessary @@ -47,7 +46,7 @@ def generate_images_for_parallel(shape=(15, 8, 10), dtype=np.float32, seed: Opti return _set_random_data(d, shape, seed=seed) -def _set_random_data(shared_array, shape, seed: Optional[int] = None): +def _set_random_data(shared_array, shape, seed: int | None = None): n = gen_img_numpy_rand(shape, seed=seed) # move the data in the shared array shared_array.array[:] = n[:]