Skip to content

Commit e232f1b

Browse files
committed
Enable pytype variable type checking
- Add type concept for index selection types - Enable precise return checking for invalid function calls - Test solve unknown types to label with structural types Add conceptual type for dictionary selection variables Values used to select dictionary subsets must be containers of hashable elements. File "monai/monai/transforms/io/dictionary.py", line 55, in __init__: Function LoadNiftid.__init__ was called with the wrong arguments [wrong-arg-types] Expected: (self, keys: Container[Hashable]) Actually passed: (self, keys: Hashable) The following methods aren't implemented on Hashable: __iter__
1 parent 40db6da commit e232f1b

File tree

13 files changed

+136
-75
lines changed

13 files changed

+136
-75
lines changed

monai/config/type_definitions.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2020 MONAI Consortium
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
# Unless required by applicable law or agreed to in writing, software
7+
# distributed under the License is distributed on an "AS IS" BASIS,
8+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
# See the License for the specific language governing permissions and
10+
# limitations under the License.
11+
12+
# Commonly used concepts are documented in this `type_definitions.py` file
13+
# to explicitly identify what information that should be used consistently throughout
14+
# the entire MONAI package. Type would be named as type_definitions.DictKeySelection
15+
# which states what we want to say in the name itself.
16+
#
17+
# The definitions in this file map context meaningful names to the underlying
18+
# storage types.
19+
#
20+
# A conceptual type is represented by a new type name but is also one which
21+
# can be different depending on environment. Consistent use of the concept
22+
# and recorded documentation of the rationale and convention behind it lowers
23+
# the learning curve for new developers. For readability, shorten names are
24+
# preferred.
25+
#
26+
27+
from typing import Hashable, Iterable, Union, Collection
28+
29+
# The KeysCollection type is used to for defining variables
30+
# that store a subset of keys to select items from a dictionary.
31+
# The container of keys must contain hashable elements.
32+
KeysCollection = Collection[Hashable]
33+
34+
# The IndexSelection type is used to for defining variables
35+
# that store a subset of indexes to select items from a List or Array like objects.
36+
# The indexes must be integers, and if a container of indexes is specified, the
37+
# container must be iterable.
38+
IndexSelection = Union[Iterable[int], int]

monai/transforms/compose.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from abc import ABC, abstractmethod
1818
import numpy as np
1919

20+
from monai.config.type_definitions import KeysCollection
2021
from monai.utils.misc import ensure_tuple, get_seed
2122
from .utils import apply_transform
2223

@@ -219,8 +220,8 @@ def __call__(self, data):
219220
220221
"""
221222

222-
def __init__(self, keys: Hashable):
223-
self.keys = ensure_tuple(keys)
223+
def __init__(self, keys: KeysCollection):
224+
self.keys: KeysCollection = ensure_tuple(keys)
224225
if not self.keys:
225226
raise ValueError("keys unspecified")
226227
for key in self.keys:

monai/transforms/croppad/array.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import numpy as np
1919

20+
from monai.config.type_definitions import IndexSelection
2021
from monai.data.utils import get_random_patch, get_valid_patch_size
2122
from monai.transforms.compose import Transform, Randomizable
2223
from monai.transforms.utils import generate_spatial_bounding_box
@@ -179,11 +180,13 @@ class CropForeground(Transform):
179180
180181
"""
181182

182-
def __init__(self, select_fn: Callable = lambda x: x > 0, channel_indexes=None, margin: int = 0):
183+
def __init__(
184+
self, select_fn: Callable = lambda x: x > 0, channel_indexes: Optional[IndexSelection] = None, margin: int = 0,
185+
):
183186
"""
184187
Args:
185188
select_fn (Callable): function to select expected foreground, default is to select values > 0.
186-
channel_indexes (int, tuple or list): if defined, select foreground only on the specified channels
189+
channel_indexes: if defined, select foreground only on the specified channels
187190
of image. if None, select foreground on the whole image.
188191
margin (int): add margin to all dims of the bounding box.
189192
"""

monai/transforms/croppad/dictionary.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
Class names are ended with 'd' to denote dictionary-based transforms.
1616
"""
1717

18-
from typing import Union, Hashable, Optional, Callable
18+
from typing import Union, Optional, Callable, Iterable
1919

20+
from monai.config.type_definitions import KeysCollection, IndexSelection
2021
from monai.data.utils import get_random_patch, get_valid_patch_size
2122
from monai.transforms.compose import MapTransform, Randomizable
2223
from monai.transforms.croppad.array import SpatialCrop, CenterSpatialCrop, SpatialPad
@@ -30,7 +31,7 @@ class SpatialPadd(MapTransform):
3031
Performs padding to the data, symmetric for all sides or all on one side for each dimension.
3132
"""
3233

33-
def __init__(self, keys: Hashable, spatial_size, method: str = "symmetric", mode="constant"):
34+
def __init__(self, keys: KeysCollection, spatial_size, method: str = "symmetric", mode="constant"):
3435
"""
3536
Args:
3637
keys (hashable items): keys of the corresponding items to be transformed.
@@ -60,7 +61,7 @@ class SpatialCropd(MapTransform):
6061
are not provided, the start and end coordinates of the ROI must be provided.
6162
"""
6263

63-
def __init__(self, keys: Hashable, roi_center=None, roi_size=None, roi_start=None, roi_end=None):
64+
def __init__(self, keys: KeysCollection, roi_center=None, roi_size=None, roi_start=None, roi_end=None):
6465
"""
6566
Args:
6667
keys (hashable items): keys of the corresponding items to be transformed.
@@ -90,7 +91,7 @@ class CenterSpatialCropd(MapTransform):
9091
roi_size (list, tuple): the size of the crop region e.g. [224,224,128]
9192
"""
9293

93-
def __init__(self, keys: Hashable, roi_size):
94+
def __init__(self, keys: KeysCollection, roi_size):
9495
super().__init__(keys)
9596
self.cropper = CenterSpatialCrop(roi_size)
9697

@@ -118,7 +119,7 @@ class RandSpatialCropd(Randomizable, MapTransform):
118119
The actual size is sampled from `randint(roi_size, img_size)`.
119120
"""
120121

121-
def __init__(self, keys: Hashable, roi_size, random_center: bool = True, random_size: bool = True):
122+
def __init__(self, keys: KeysCollection, roi_size, random_center: bool = True, random_size: bool = True):
122123
super().__init__(keys)
123124
self.roi_size = roi_size
124125
self.random_center = random_center
@@ -159,19 +160,19 @@ class CropForegroundd(MapTransform):
159160

160161
def __init__(
161162
self,
162-
keys: Hashable,
163+
keys: KeysCollection,
163164
source_key: str,
164165
select_fn: Callable = lambda x: x > 0,
165-
channel_indexes: Callable = None,
166+
channel_indexes: Optional[IndexSelection] = None,
166167
margin=0,
167168
):
168169
"""
169170
Args:
170-
keys (hashable items): keys of the corresponding items to be transformed.
171+
keys: keys of the corresponding items to be transformed.
171172
See also: :py:class:`monai.transforms.compose.MapTransform`
172173
source_key (str): data source to generate the bounding box of foreground, can be image or label, etc.
173174
select_fn (Callable): function to select expected foreground, default is to select values > 0.
174-
channel_indexes (int, tuple or list): if defined, select foreground only on the specified channels
175+
channel_indexes: if defined, select foreground only on the specified channels
175176
of image. if None, select foreground on the whole image.
176177
margin (int): add margin to all dims of the bounding box.
177178
"""
@@ -215,7 +216,7 @@ class RandCropByPosNegLabeld(Randomizable, MapTransform):
215216

216217
def __init__(
217218
self,
218-
keys: Hashable,
219+
keys: KeysCollection,
219220
label_key: str,
220221
size,
221222
pos: Union[int, float] = 1,

monai/transforms/intensity/dictionary.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
Class names are ended with 'd' to denote dictionary-based transforms.
1616
"""
1717

18-
from typing import Hashable, Union, Optional
18+
from typing import Union, Optional
1919

2020
import numpy as np
21+
22+
from monai.config.type_definitions import KeysCollection
2123
from monai.transforms.compose import MapTransform, Randomizable
2224
from monai.transforms.intensity.array import (
2325
NormalizeIntensity,
@@ -41,7 +43,7 @@ class RandGaussianNoised(Randomizable, MapTransform):
4143
std (float): Standard deviation (spread) of distribution.
4244
"""
4345

44-
def __init__(self, keys: Hashable, prob: float = 0.1, mean=0.0, std: float = 0.1):
46+
def __init__(self, keys: KeysCollection, prob: float = 0.1, mean=0.0, std: float = 0.1):
4547
super().__init__(keys)
4648
self.prob = prob
4749
self.mean = mean
@@ -70,7 +72,7 @@ class ShiftIntensityd(MapTransform):
7072
dictionary-based wrapper of :py:class:`monai.transforms.ShiftIntensity`.
7173
"""
7274

73-
def __init__(self, keys: Hashable, offset: Union[int, float]):
75+
def __init__(self, keys: KeysCollection, offset: Union[int, float]):
7476
"""
7577
Args:
7678
keys (hashable items): keys of the corresponding items to be transformed.
@@ -92,7 +94,7 @@ class RandShiftIntensityd(Randomizable, MapTransform):
9294
dictionary-based version :py:class:`monai.transforms.RandShiftIntensity`.
9395
"""
9496

95-
def __init__(self, keys: Hashable, offsets, prob: float = 0.1):
97+
def __init__(self, keys: KeysCollection, offsets, prob: float = 0.1):
9698
"""
9799
Args:
98100
keys (hashable items): keys of the corresponding items to be transformed.
@@ -132,7 +134,7 @@ class ScaleIntensityd(MapTransform):
132134

133135
def __init__(
134136
self,
135-
keys: Hashable,
137+
keys: KeysCollection,
136138
minv: Union[int, float] = 0.0,
137139
maxv: Union[int, float] = 1.0,
138140
factor: Optional[float] = None,
@@ -161,7 +163,7 @@ class RandScaleIntensityd(Randomizable, MapTransform):
161163
dictionary-based version :py:class:`monai.transforms.RandScaleIntensity`.
162164
"""
163165

164-
def __init__(self, keys: Hashable, factors, prob: float = 0.1):
166+
def __init__(self, keys: KeysCollection, factors, prob: float = 0.1):
165167
"""
166168
Args:
167169
keys (hashable items): keys of the corresponding items to be transformed.
@@ -211,7 +213,7 @@ class NormalizeIntensityd(MapTransform):
211213

212214
def __init__(
213215
self,
214-
keys: Hashable,
216+
keys: KeysCollection,
215217
subtrahend: Optional[np.ndarray] = None,
216218
divisor: Optional[np.ndarray] = None,
217219
nonzero: bool = False,
@@ -239,7 +241,9 @@ class ThresholdIntensityd(MapTransform):
239241
cval (float or int): value to fill the remaining parts of the image, default is 0.
240242
"""
241243

242-
def __init__(self, keys: Hashable, threshold: Union[int, float], above: bool = True, cval: Union[int, float] = 0):
244+
def __init__(
245+
self, keys: KeysCollection, threshold: Union[int, float], above: bool = True, cval: Union[int, float] = 0,
246+
):
243247
super().__init__(keys)
244248
self.filter = ThresholdIntensity(threshold, above, cval)
245249

@@ -266,7 +270,7 @@ class ScaleIntensityRanged(MapTransform):
266270

267271
def __init__(
268272
self,
269-
keys: Hashable,
273+
keys: KeysCollection,
270274
a_min: Union[int, float],
271275
a_max: Union[int, float],
272276
b_min: Union[int, float],
@@ -294,7 +298,7 @@ class AdjustContrastd(MapTransform):
294298
gamma (float): gamma value to adjust the contrast as function.
295299
"""
296300

297-
def __init__(self, keys: Hashable, gamma: Union[int, float]):
301+
def __init__(self, keys: KeysCollection, gamma: Union[int, float]):
298302
super().__init__(keys)
299303
self.adjuster = AdjustContrast(gamma)
300304

monai/transforms/io/dictionary.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
Class names are ended with 'd' to denote dictionary-based transforms.
1616
"""
1717

18-
from typing import Hashable, Optional
18+
from typing import Optional
1919

2020
import numpy as np
2121

22+
from monai.config.type_definitions import KeysCollection
2223
from monai.transforms.compose import MapTransform
2324
from monai.transforms.io.array import LoadNifti, LoadPNG
2425

@@ -35,7 +36,7 @@ class LoadNiftid(MapTransform):
3536

3637
def __init__(
3738
self,
38-
keys: Hashable,
39+
keys: KeysCollection,
3940
as_closest_canonical: bool = False,
4041
dtype: Optional[np.dtype] = np.float32,
4142
meta_key_format: str = "{}.{}",
@@ -77,7 +78,7 @@ class LoadPNGd(MapTransform):
7778
Dictionary-based wrapper of :py:class:`monai.transforms.LoadPNG`.
7879
"""
7980

80-
def __init__(self, keys: Hashable, dtype: Optional[np.dtype] = np.float32, meta_key_format: str = "{}.{}"):
81+
def __init__(self, keys: KeysCollection, dtype: Optional[np.dtype] = np.float32, meta_key_format: str = "{}.{}"):
8182
"""
8283
Args:
8384
keys (hashable items): keys of the corresponding items to be transformed.

monai/transforms/post/dictionary.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
Class names are ended with 'd' to denote dictionary-based transforms.
1616
"""
1717

18-
from typing import Optional, Hashable
18+
from typing import Optional
1919

20+
from monai.config.type_definitions import KeysCollection
2021
from monai.utils.misc import ensure_tuple_rep
2122
from monai.transforms.compose import MapTransform
2223
from monai.transforms.post.array import SplitChannel, Activations, AsDiscrete, KeepLargestConnectedComponent
@@ -29,7 +30,7 @@ class SplitChanneld(MapTransform):
2930
3031
"""
3132

32-
def __init__(self, keys: Hashable, output_postfixes, to_onehot=False, num_classes=None):
33+
def __init__(self, keys: KeysCollection, output_postfixes, to_onehot=False, num_classes=None):
3334
"""
3435
Args:
3536
keys (hashable items): keys of the corresponding items to be transformed.
@@ -65,7 +66,7 @@ class Activationsd(MapTransform):
6566
Add activation layers to the input data specified by `keys`.
6667
"""
6768

68-
def __init__(self, keys: Hashable, output_postfix: str = "act", sigmoid=False, softmax=False, other=None):
69+
def __init__(self, keys: KeysCollection, output_postfix: str = "act", sigmoid=False, softmax=False, other=None):
6970
"""
7071
Args:
7172
keys (hashable items): keys of the corresponding items to model output and label.
@@ -106,7 +107,7 @@ class AsDiscreted(MapTransform):
106107

107108
def __init__(
108109
self,
109-
keys: Hashable,
110+
keys: KeysCollection,
110111
output_postfix: str = "discreted",
111112
argmax=False,
112113
to_onehot=False,
@@ -161,7 +162,7 @@ class KeepLargestConnectedComponentd(MapTransform):
161162

162163
def __init__(
163164
self,
164-
keys: Hashable,
165+
keys: KeysCollection,
165166
applied_values,
166167
independent: bool = True,
167168
background: int = 0,

0 commit comments

Comments
 (0)