diff --git a/monai/apps/auto3dseg/ensemble_builder.py b/monai/apps/auto3dseg/ensemble_builder.py index ae213ebfd0..766dda4119 100644 --- a/monai/apps/auto3dseg/ensemble_builder.py +++ b/monai/apps/auto3dseg/ensemble_builder.py @@ -221,7 +221,7 @@ def __init__(self, n_fold: int = 5): super().__init__() self.n_fold = n_fold - def collect_algos(self): + def collect_algos(self) -> None: """ Rank the algos by finding the best model in each cross-validation fold """ diff --git a/monai/apps/auto3dseg/hpo_gen.py b/monai/apps/auto3dseg/hpo_gen.py index f9f709053b..6a4d2c8065 100644 --- a/monai/apps/auto3dseg/hpo_gen.py +++ b/monai/apps/auto3dseg/hpo_gen.py @@ -12,6 +12,7 @@ import os from abc import abstractmethod from copy import deepcopy +from typing import Optional from warnings import warn from monai.apps.auto3dseg.bundle_gen import BundleAlgo @@ -96,7 +97,7 @@ class NNIGen(HPOGen): NNI command manually. """ - def __init__(self, algo=None, params=None): + def __init__(self, algo: Optional[Algo] = None, params=None): self.algo: Algo self.hint = "" self.obj_filename = "" @@ -115,7 +116,7 @@ def __init__(self, algo=None, params=None): else: self.algo = algo - if isinstance(algo, BundleAlgo): + if isinstance(self.algo, BundleAlgo): self.obj_filename = algo_to_pickle(self.algo, template_path=self.algo.template_path) self.print_bundle_algo_instruction() else: @@ -271,7 +272,7 @@ class OptunaGen(HPOGen): """ - def __init__(self, algo=None, params=None): + def __init__(self, algo: Optional[Algo] = None, params=None) -> None: self.algo: Algo self.obj_filename = "" @@ -289,7 +290,7 @@ def __init__(self, algo=None, params=None): else: self.algo = algo - if isinstance(algo, BundleAlgo): + if isinstance(self.algo, BundleAlgo): self.obj_filename = algo_to_pickle(self.algo, template_path=self.algo.template_path) else: self.obj_filename = algo_to_pickle(self.algo) diff --git a/monai/apps/deepgrow/transforms.py b/monai/apps/deepgrow/transforms.py index 9340a80f7a..2cecf2727c 100644 --- a/monai/apps/deepgrow/transforms.py +++ b/monai/apps/deepgrow/transforms.py @@ -14,7 +14,7 @@ import numpy as np import torch -from monai.config import IndexSelection, KeysCollection +from monai.config import IndexSelection, KeysCollection, NdarrayOrTensor from monai.networks.layers import GaussianFilter from monai.transforms import Resize, SpatialCrop from monai.transforms.transform import MapTransform, Randomizable, Transform @@ -50,7 +50,7 @@ def _apply(self, label): sids.append(sid) return np.asarray(sids) - def __call__(self, data): + def __call__(self, data) -> Dict: d: Dict = dict(data) label = d[self.label].numpy() if isinstance(data[self.label], torch.Tensor) else data[self.label] if label.shape[0] != 1: @@ -654,7 +654,7 @@ def bounding_box(self, points, img_shape): box_start[di], box_end[di] = min_d, max_d return box_start, box_end - def __call__(self, data): + def __call__(self, data) -> Dict: d: Dict = dict(data) first_key: Hashable = self.first_key(d) if first_key == (): @@ -741,7 +741,7 @@ def __init__( self.meta_key_postfix = meta_key_postfix self.cropped_shape_key = cropped_shape_key - def __call__(self, data): + def __call__(self, data) -> Dict: d = dict(data) guidance = d[self.guidance] meta_dict: Dict = d[self.meta_keys or f"{self.ref_image}_{self.meta_key_postfix}"] @@ -836,7 +836,7 @@ def __init__( self.original_shape_key = original_shape_key self.cropped_shape_key = cropped_shape_key - def __call__(self, data): + def __call__(self, data) -> Dict: d = dict(data) meta_dict: Dict = d[f"{self.ref_image}_{self.meta_key_postfix}"] @@ -857,8 +857,9 @@ def __call__(self, data): box_end = meta_dict[self.end_coord_key] spatial_dims = min(len(box_start), len(image.shape[1:])) - slices = [slice(None)] + [slice(s, e) for s, e in zip(box_start[:spatial_dims], box_end[:spatial_dims])] - slices = tuple(slices) + slices = tuple( + [slice(None)] + [slice(s, e) for s, e in zip(box_start[:spatial_dims], box_end[:spatial_dims])] + ) result[slices] = image # Undo Spacing @@ -869,10 +870,11 @@ def __call__(self, data): if np.any(np.not_equal(current_size, spatial_size)): resizer = Resize(spatial_size=spatial_size, mode=mode) - result = resizer(result, mode=mode, align_corners=align_corners) + result = resizer(result, mode=mode, align_corners=align_corners) # type: ignore # Undo Slicing slice_idx = meta_dict.get("slice_idx") + final_result: NdarrayOrTensor if slice_idx is None or self.slice_only: final_result = result if len(result.shape) <= 3 else result[0] else: diff --git a/monai/auto3dseg/analyzer.py b/monai/auto3dseg/analyzer.py index f1f5fc41c0..966c0a08b9 100644 --- a/monai/auto3dseg/analyzer.py +++ b/monai/auto3dseg/analyzer.py @@ -12,7 +12,7 @@ import time from abc import ABC, abstractmethod from copy import deepcopy -from typing import Any, Dict, List, Optional +from typing import Any, Dict, Hashable, List, Mapping, Optional import numpy as np import torch @@ -377,7 +377,7 @@ def __init__(self, image_key: str, label_key: str, stats_name: str = "label_stat self.label_key = label_key self.do_ccp = do_ccp - report_format: Dict[str, Any] = { + report_format: Dict[LabelStatsKeys, Any] = { LabelStatsKeys.LABEL_UID: None, LabelStatsKeys.IMAGE_INTST: None, LabelStatsKeys.LABEL: [{LabelStatsKeys.PIXEL_PCT: None, LabelStatsKeys.IMAGE_INTST: None}], @@ -394,7 +394,7 @@ def __init__(self, image_key: str, label_key: str, stats_name: str = "label_stat id_seq = ID_SEP_KEY.join([LabelStatsKeys.LABEL, "0", LabelStatsKeys.IMAGE_INTST]) self.update_ops_nested_label(id_seq, SampleOperations()) - def __call__(self, data): + def __call__(self, data: Mapping[Hashable, MetaTensor]) -> Dict[Hashable, MetaTensor]: """ Callable to execute the pre-defined functions. @@ -442,7 +442,7 @@ def __call__(self, data): The stats operation uses numpy and torch to compute max, min, and other functions. If the input has nan/inf, the stats results will be nan/inf. """ - d = dict(data) + d: Dict[Hashable, MetaTensor] = dict(data) start = time.time() if isinstance(d[self.image_key], (torch.Tensor, MetaTensor)) and d[self.image_key].device.type == "cuda": using_cuda = True @@ -451,13 +451,13 @@ def __call__(self, data): restore_grad_state = torch.is_grad_enabled() torch.set_grad_enabled(False) - ndas = [d[self.image_key][i] for i in range(d[self.image_key].shape[0])] - ndas_label = d[self.label_key] # (H,W,D) + ndas: List[MetaTensor] = [d[self.image_key][i] for i in range(d[self.image_key].shape[0])] # type: ignore + ndas_label: MetaTensor = d[self.label_key] # (H,W,D) if ndas_label.shape != ndas[0].shape: raise ValueError(f"Label shape {ndas_label.shape} is different from image shape {ndas[0].shape}") - nda_foregrounds = [get_foreground_label(nda, ndas_label) for nda in ndas] + nda_foregrounds: List[torch.Tensor] = [get_foreground_label(nda, ndas_label) for nda in ndas] nda_foregrounds = [nda if nda.numel() > 0 else torch.Tensor([0]) for nda in nda_foregrounds] unique_label = unique(ndas_label) diff --git a/monai/data/dataset.py b/monai/data/dataset.py index e525941c03..3e8c87961a 100644 --- a/monai/data/dataset.py +++ b/monai/data/dataset.py @@ -1075,7 +1075,7 @@ def randomize(self, data: Sequence) -> None: except TypeError as e: warnings.warn(f"input data can't be shuffled in SmartCacheDataset with numpy.random.shuffle(): {e}.") - def _compute_data_idx(self): + def _compute_data_idx(self) -> None: """ Update the replacement data position in the total data. @@ -1209,7 +1209,7 @@ def _try_manage_replacement(self, check_round): self._compute_replacements() return False, self._round - def manage_replacement(self): + def manage_replacement(self) -> None: """ Background thread for replacement. diff --git a/monai/data/image_reader.py b/monai/data/image_reader.py index fd9852115e..66c41a1ba8 100644 --- a/monai/data/image_reader.py +++ b/monai/data/image_reader.py @@ -277,7 +277,7 @@ def read(self, data: Union[Sequence[PathLike], PathLike], **kwargs): img_.append(itk.imread(name, **kwargs_)) return img_ if len(filenames) > 1 else img_[0] - def get_data(self, img): + def get_data(self, img) -> Tuple[np.ndarray, Dict]: """ Extract data array and metadata from loaded image and return them. This function returns two objects, first is numpy array of image data, second is dict of metadata. @@ -564,7 +564,7 @@ def _combine_dicom_series(self, data): return stack_array, stack_metadata - def get_data(self, data): + def get_data(self, data) -> Tuple[np.ndarray, Dict]: """ Extract data array and metadata from loaded image and return them. This function returns two objects, first is numpy array of image data, second is dict of metadata. @@ -929,7 +929,7 @@ def read(self, data: Union[Sequence[PathLike], PathLike], **kwargs): img_.append(img) return img_ if len(filenames) > 1 else img_[0] - def get_data(self, img): + def get_data(self, img) -> Tuple[np.ndarray, Dict]: """ Extract data array and metadata from loaded image and return them. This function returns two objects, first is numpy array of image data, second is dict of metadata. @@ -1095,7 +1095,7 @@ def read(self, data: Union[Sequence[PathLike], PathLike], **kwargs): return img_ if len(img_) > 1 else img_[0] - def get_data(self, img): + def get_data(self, img) -> Tuple[np.ndarray, Dict]: """ Extract data array and metadata from loaded image and return them. This function returns two objects, first is numpy array of image data, second is dict of metadata. @@ -1113,7 +1113,7 @@ def get_data(self, img): img = (img,) for i in ensure_tuple(img): - header = {} + header: Dict[MetaKeys, Any] = {} if isinstance(i, np.ndarray): # if `channel_dim` is None, can not detect the channel dim, use all the dims as spatial_shape spatial_shape = np.asarray(i.shape) @@ -1184,7 +1184,7 @@ def read(self, data: Union[Sequence[PathLike], PathLike, np.ndarray], **kwargs): return img_ if len(filenames) > 1 else img_[0] - def get_data(self, img): + def get_data(self, img) -> Tuple[np.ndarray, Dict]: """ Extract data array and metadata from loaded image and return them. This function returns two objects, first is numpy array of image data, second is dict of metadata. diff --git a/monai/data/meta_obj.py b/monai/data/meta_obj.py index 74daf59ba8..67f4109c86 100644 --- a/monai/data/meta_obj.py +++ b/monai/data/meta_obj.py @@ -79,7 +79,7 @@ class MetaObj: """ - def __init__(self): + def __init__(self) -> None: self._meta: dict = MetaObj.get_default_meta() self._applied_operations: list = MetaObj.get_default_applied_operations() self._pending_operations: list = MetaObj.get_default_applied_operations() # the same default as applied_ops diff --git a/monai/data/meta_tensor.py b/monai/data/meta_tensor.py index e91888a665..1077d055e0 100644 --- a/monai/data/meta_tensor.py +++ b/monai/data/meta_tensor.py @@ -354,7 +354,7 @@ def get_array(self, output_type=np.ndarray, dtype=None, device=None, *_args, **_ """ return convert_data_type(self, output_type=output_type, dtype=dtype, device=device, wrap_sequence=True)[0] - def set_array(self, src, non_blocking=False, *_args, **_kwargs): + def set_array(self, src, non_blocking: bool = False, *_args, **_kwargs): """ Copies the elements from src into self tensor and returns self. The src tensor must be broadcastable with the self tensor. @@ -369,11 +369,11 @@ def set_array(self, src, non_blocking=False, *_args, **_kwargs): _args: currently unused parameters. _kwargs: currently unused parameters. """ - src: torch.Tensor = convert_to_tensor(src, track_meta=False, wrap_sequence=True) + converted: torch.Tensor = convert_to_tensor(src, track_meta=False, wrap_sequence=True) try: - return self.copy_(src, non_blocking=non_blocking) + return self.copy_(converted, non_blocking=non_blocking) except RuntimeError: # skip the shape checking - self.data = src + self.data = converted return self @property diff --git a/monai/metrics/metric.py b/monai/metrics/metric.py index 13e0aef89e..3d017a0490 100644 --- a/monai/metrics/metric.py +++ b/monai/metrics/metric.py @@ -166,7 +166,7 @@ class Cumulative: """ - def __init__(self): + def __init__(self) -> None: """ Initialize the internal buffers. `self._buffers` are local buffers, they are not usually used directly. diff --git a/monai/networks/blocks/warp.py b/monai/networks/blocks/warp.py index 7a28e86301..018d694407 100644 --- a/monai/networks/blocks/warp.py +++ b/monai/networks/blocks/warp.py @@ -151,7 +151,7 @@ def __init__( self.num_steps = num_steps self.warp_layer = Warp(mode=mode, padding_mode=padding_mode) - def forward(self, dvf): + def forward(self, dvf: torch.Tensor) -> torch.Tensor: """ Args: dvf: dvf to be transformed, in shape (batch, ``spatial_dims``, H, W[,D]) diff --git a/monai/networks/nets/ahnet.py b/monai/networks/nets/ahnet.py index b481374aa1..aafab57968 100644 --- a/monai/networks/nets/ahnet.py +++ b/monai/networks/nets/ahnet.py @@ -270,7 +270,7 @@ def __init__(self, spatial_dims: int, psp_block_num: int, in_ch: int, upsample_m pad_size = (2 ** (i + 3), 2 ** (i + 3), 0)[-spatial_dims:] self.up_modules.append(conv_trans_type(1, 1, kernel_size=size, stride=size, padding=pad_size)) - def forward(self, x): + def forward(self, x: torch.Tensor) -> torch.Tensor: outputs = [] if self.upsample_mode == "transpose": for (project_module, pool_module, up_module) in zip( diff --git a/monai/transforms/intensity/dictionary.py b/monai/transforms/intensity/dictionary.py index efdfd502ca..25e21add63 100644 --- a/monai/transforms/intensity/dictionary.py +++ b/monai/transforms/intensity/dictionary.py @@ -1577,7 +1577,7 @@ def set_random_state( self.dropper.set_random_state(seed, state) return self - def __call__(self, data): + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: d = dict(data) self.randomize(None) if not self._do_transform: @@ -1650,7 +1650,7 @@ def set_random_state( self.shuffle.set_random_state(seed, state) return self - def __call__(self, data): + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: d = dict(data) self.randomize(None) if not self._do_transform: diff --git a/monai/transforms/io/dictionary.py b/monai/transforms/io/dictionary.py index e0a96c8242..9817a92807 100644 --- a/monai/transforms/io/dictionary.py +++ b/monai/transforms/io/dictionary.py @@ -71,7 +71,7 @@ class LoadImaged(MapTransform): def __init__( self, keys: KeysCollection, - reader: Optional[Union[ImageReader, str]] = None, + reader: Union[Type[ImageReader], str, None] = None, dtype: DtypeLike = np.float32, meta_keys: Optional[KeysCollection] = None, meta_key_postfix: str = DEFAULT_POST_FIX, diff --git a/setup.cfg b/setup.cfg index 13df590e4d..0addf8ad0a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -157,8 +157,8 @@ ignore_missing_imports = True no_implicit_optional = True # Warns about casting an expression to its inferred type. warn_redundant_casts = True -# No error on unneeded # type: ignore comments. -warn_unused_ignores = False +# Warns about unneeded # type: ignore comments. +warn_unused_ignores = True # Shows a warning when returning a value with type Any from a function declared with a non-Any return type. warn_return_any = True # Prohibit equality checks, identity checks, and container checks between non-overlapping types. @@ -169,6 +169,12 @@ show_column_numbers = True show_error_codes = True # Use visually nicer output in error messages: use soft word wrap, show source code snippets, and show error location markers. pretty = False +# Warns about per-module sections in the config file that do not match any files processed when invoking mypy. +warn_unused_configs = True +# Make arguments prepended via Concatenate be truly positional-only. +strict_concatenate = True + +exclude = venv/ [mypy-versioneer] # Ignores all non-fatal errors. diff --git a/tests/test_flexible_unet.py b/tests/test_flexible_unet.py index b71afc80cf..7960f35925 100644 --- a/tests/test_flexible_unet.py +++ b/tests/test_flexible_unet.py @@ -10,7 +10,7 @@ # limitations under the License. import unittest -from typing import Union +from typing import Dict, List, Type, Union import torch from parameterized import parameterized @@ -88,14 +88,14 @@ def get_inplanes(): return [64, 128, 256, 512] @classmethod - def get_encoder_parameters(cls): + def get_encoder_parameters(cls) -> List[Dict]: """ Get parameter list to initialize encoder networks. Each parameter dict must have `spatial_dims`, `in_channels` and `pretrained` parameters. """ parameter_list = [] - res_type: Union[ResNetBlock, ResNetBottleneck] + res_type: Union[Type[ResNetBlock], Type[ResNetBottleneck]] for backbone in range(len(cls.backbone_names)): if backbone < 3: res_type = ResNetBlock diff --git a/tests/test_inverse_array.py b/tests/test_inverse_array.py index bb9669e8df..c86bf92bef 100644 --- a/tests/test_inverse_array.py +++ b/tests/test_inverse_array.py @@ -38,7 +38,7 @@ def get_image(dtype, device) -> MetaTensor: return MetaTensor(img, affine=affine) @parameterized.expand(TESTS) - def test_inverse_array(self, use_compose, dtype, device): + def test_inverse_array(self, use_compose: bool, dtype: torch.dtype, device: torch.device): img: MetaTensor tr = Compose([AddChannel(), Orientation("RAS"), Flip(1), Spacing([1.0, 1.2, 0.9], align_corners=False)]) num_invertible = len([i for i in tr.transforms if isinstance(i, InvertibleTransform)]) diff --git a/tests/test_spacing.py b/tests/test_spacing.py index 90fc05b40f..a1e289d19b 100644 --- a/tests/test_spacing.py +++ b/tests/test_spacing.py @@ -10,6 +10,7 @@ # limitations under the License. import unittest +from typing import Dict, List import numpy as np import torch @@ -22,7 +23,7 @@ from monai.utils import fall_back_tuple from tests.utils import TEST_DEVICES, TEST_NDARRAYS_ALL, assert_allclose, skip_if_quick -TESTS = [] +TESTS: List[List] = [] for device in TEST_DEVICES: TESTS.append( [ @@ -264,7 +265,15 @@ @skip_if_quick class TestSpacingCase(unittest.TestCase): @parameterized.expand(TESTS) - def test_spacing(self, init_param, img, affine, data_param, expected_output, device): + def test_spacing( + self, + init_param: Dict, + img: torch.Tensor, + affine: torch.Tensor, + data_param: Dict, + expected_output: torch.Tensor, + device: torch.device, + ): img = MetaTensor(img, affine=affine).to(device) res: MetaTensor = Spacing(**init_param)(img, **data_param) self.assertEqual(img.device, res.device) @@ -275,7 +284,7 @@ def test_spacing(self, init_param, img, affine, data_param, expected_output, dev init_param["pixdim"] = [init_param["pixdim"]] * sr init_pixdim = init_param["pixdim"][:sr] norm = affine_to_spacing(res.affine, sr).cpu().numpy() - assert_allclose(fall_back_tuple(init_pixdim, norm), norm, type_test=False) + assert_allclose(fall_back_tuple(init_pixdim, norm), norm, type_test=False) # type: ignore @parameterized.expand(TESTS_TORCH) def test_spacing_torch(self, pixdim, img, track_meta: bool): diff --git a/tests/test_splitdimd.py b/tests/test_splitdimd.py index ee8cc043e4..85184b494a 100644 --- a/tests/test_splitdimd.py +++ b/tests/test_splitdimd.py @@ -30,14 +30,16 @@ class TestSplitDimd(unittest.TestCase): + data: MetaTensor + @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: arr = np.random.rand(2, 10, 8, 7) affine = make_rand_affine() data = {"i": make_nifti_image(arr, affine)} loader = LoadImaged("i") - cls.data: MetaTensor = loader(data) + cls.data = loader(data) @parameterized.expand(TESTS) def test_correct(self, keepdim, im_type, update_meta, list_output): diff --git a/tests/test_wsireader.py b/tests/test_wsireader.py index 5fb113e193..45f11bb138 100644 --- a/tests/test_wsireader.py +++ b/tests/test_wsireader.py @@ -11,12 +11,14 @@ import os import unittest +from typing import Any, Tuple from unittest import skipUnless import numpy as np from numpy.testing import assert_array_equal from parameterized import parameterized +from monai.config import PathLike from monai.data import DataLoader, Dataset from monai.data.image_reader import WSIReader as WSIReaderDeprecated from monai.data.wsi_reader import WSIReader @@ -252,7 +254,9 @@ def test_read_malformats(self, img_expected): reader.get_data(img_obj) @parameterized.expand([TEST_CASE_TRANSFORM_0]) - def test_with_dataloader(self, file_path, level, expected_spatial_shape, expected_shape): + def test_with_dataloader( + self, file_path: PathLike, level: int, expected_spatial_shape: Any, expected_shape: Tuple[int, ...] + ): train_transform = Compose( [ LoadImaged(keys=["image"], reader=WSIReaderDeprecated, backend=self.backend, level=level), @@ -357,7 +361,9 @@ def test_read_malformats(self, img_expected): reader.get_data(img_obj) @parameterized.expand([TEST_CASE_TRANSFORM_0]) - def test_with_dataloader(self, file_path, level, expected_spatial_shape, expected_shape): + def test_with_dataloader( + self, file_path: PathLike, level: int, expected_spatial_shape: Any, expected_shape: Tuple[int, ...] + ): train_transform = Compose( [ LoadImaged(keys=["image"], reader=WSIReader, backend=self.backend, level=level), @@ -372,7 +378,9 @@ def test_with_dataloader(self, file_path, level, expected_spatial_shape, expecte self.assertTupleEqual(data["image"].shape, expected_shape) @parameterized.expand([TEST_CASE_TRANSFORM_0]) - def test_with_dataloader_batch(self, file_path, level, expected_spatial_shape, expected_shape): + def test_with_dataloader_batch( + self, file_path: PathLike, level: int, expected_spatial_shape: Any, expected_shape: Tuple[int, ...] + ): train_transform = Compose( [ LoadImaged(keys=["image"], reader=WSIReader, backend=self.backend, level=level),