Skip to content

Commit 69ff653

Browse files
authored
Merge branch 'dev' into utility_transforms
2 parents 0d27527 + 26f8446 commit 69ff653

File tree

6 files changed

+89
-35
lines changed

6 files changed

+89
-35
lines changed

monai/transforms/intensity/array.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,7 +1643,10 @@ class RandCoarseDropout(RandomizableTransform):
16431643
if some components of the `spatial_size` are non-positive values, the transform will use the
16441644
corresponding components of input img size. For example, `spatial_size=(32, -1)` will be adapted
16451645
to `(32, 64)` if the second spatial dimension size of img is `64`.
1646-
fill_value: target value to fill the dropout regions.
1646+
fill_value: target value to fill the dropout regions, if providing a number, will use it as constant
1647+
value to fill all the regions. if providing a tuple for the `min` and `max`, will randomly select
1648+
value for every pixel / voxel from the range `[min, max)`. if None, will compute the `min` and `max`
1649+
value of input image then randomly select value to fill, default to None.
16471650
max_holes: if not None, define the maximum number to randomly select the expected number of regions.
16481651
max_spatial_size: if not None, define the maximum spatial size to randomly select size for every region.
16491652
if some components of the `max_spatial_size` are non-positive values, the transform will use the
@@ -1657,7 +1660,7 @@ def __init__(
16571660
self,
16581661
holes: int,
16591662
spatial_size: Union[Sequence[int], int],
1660-
fill_value: Union[float, int] = 0,
1663+
fill_value: Optional[Union[Tuple[float, float], float]] = None,
16611664
max_holes: Optional[int] = None,
16621665
max_spatial_size: Optional[Union[Sequence[int], int]] = None,
16631666
prob: float = 0.1,
@@ -1688,7 +1691,13 @@ def __call__(self, img: np.ndarray):
16881691
self.randomize(img.shape[1:])
16891692
if self._do_transform:
16901693
for h in self.hole_coords:
1691-
img[h] = self.fill_value
1694+
fill_value = (img.min(), img.max()) if self.fill_value is None else self.fill_value
1695+
if isinstance(fill_value, (tuple, list)):
1696+
if len(fill_value) != 2:
1697+
raise ValueError("fill_value should contain 2 numbers if providing the `min` and `max`.")
1698+
img[h] = self.R.uniform(fill_value[0], fill_value[1], size=img[h].shape)
1699+
else:
1700+
img[h] = fill_value
16921701

16931702
return img
16941703

monai/transforms/intensity/dictionary.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ class ShiftIntensityd(MapTransform):
245245
Dictionary-based wrapper of :py:class:`monai.transforms.ShiftIntensity`.
246246
"""
247247

248+
backend = ShiftIntensity.backend
249+
248250
def __init__(
249251
self,
250252
keys: KeysCollection,
@@ -283,7 +285,7 @@ def __init__(
283285
self.meta_key_postfix = ensure_tuple_rep(meta_key_postfix, len(self.keys))
284286
self.shifter = ShiftIntensity(offset)
285287

286-
def __call__(self, data) -> Dict[Hashable, np.ndarray]:
288+
def __call__(self, data) -> Dict[Hashable, NdarrayOrTensor]:
287289
d = dict(data)
288290
for key, factor_key, meta_key, meta_key_postfix in self.key_iterator(
289291
d, self.factor_key, self.meta_keys, self.meta_key_postfix
@@ -300,6 +302,8 @@ class RandShiftIntensityd(RandomizableTransform, MapTransform):
300302
Dictionary-based version :py:class:`monai.transforms.RandShiftIntensity`.
301303
"""
302304

305+
backend = ShiftIntensity.backend
306+
303307
def __init__(
304308
self,
305309
keys: KeysCollection,
@@ -355,7 +359,7 @@ def randomize(self, data: Optional[Any] = None) -> None:
355359
self._offset = self.R.uniform(low=self.offsets[0], high=self.offsets[1])
356360
super().randomize(None)
357361

358-
def __call__(self, data) -> Dict[Hashable, np.ndarray]:
362+
def __call__(self, data) -> Dict[Hashable, NdarrayOrTensor]:
359363
d = dict(data)
360364
self.randomize()
361365
if not self._do_transform:
@@ -1435,7 +1439,10 @@ class RandCoarseDropoutd(RandomizableTransform, MapTransform):
14351439
if some components of the `spatial_size` are non-positive values, the transform will use the
14361440
corresponding components of input img size. For example, `spatial_size=(32, -1)` will be adapted
14371441
to `(32, 64)` if the second spatial dimension size of img is `64`.
1438-
fill_value: target value to fill the dropout regions.
1442+
fill_value: target value to fill the dropout regions, if providing a number, will use it as constant
1443+
value to fill all the regions. if providing a tuple for the `min` and `max`, will randomly select
1444+
value for every pixel / voxel from the range `[min, max)`. if None, will compute the `min` and `max`
1445+
value of input image then randomly select value to fill, default to None.
14391446
max_holes: if not None, define the maximum number to randomly select the expected number of regions.
14401447
max_spatial_size: if not None, define the maximum spatial size to randomly select size for every region.
14411448
if some components of the `max_spatial_size` are non-positive values, the transform will use the
@@ -1451,7 +1458,7 @@ def __init__(
14511458
keys: KeysCollection,
14521459
holes: int,
14531460
spatial_size: Union[Sequence[int], int],
1454-
fill_value: Union[float, int] = 0,
1461+
fill_value: Optional[Union[Tuple[float, float], float]] = None,
14551462
max_holes: Optional[int] = None,
14561463
max_spatial_size: Optional[Union[Sequence[int], int]] = None,
14571464
prob: float = 0.1,
@@ -1487,7 +1494,13 @@ def __call__(self, data):
14871494
if self._do_transform:
14881495
for key in self.key_iterator(d):
14891496
for h in self.hole_coords:
1490-
d[key][h] = self.fill_value
1497+
fill_value = (d[key].min(), d[key].max()) if self.fill_value is None else self.fill_value
1498+
if isinstance(fill_value, (tuple, list)):
1499+
if len(fill_value) != 2:
1500+
raise ValueError("fill_value should contain 2 numbers if providing the `min` and `max`.")
1501+
d[key][h] = self.R.uniform(fill_value[0], fill_value[1], size=d[key][h].shape)
1502+
else:
1503+
d[key][h] = fill_value
14911504
return d
14921505

14931506

tests/test_rand_coarse_dropout.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,31 +20,37 @@
2020
TEST_CASE_0 = [
2121
{"holes": 2, "spatial_size": [2, 2, 2], "fill_value": 5, "prob": 1.0},
2222
np.random.randint(0, 2, size=[3, 3, 3, 4]),
23-
(3, 3, 3, 4),
2423
]
2524

2625
TEST_CASE_1 = [
2726
{"holes": 1, "spatial_size": [1, 2, 3], "fill_value": 5, "max_holes": 5, "prob": 1.0},
2827
np.random.randint(0, 2, size=[3, 3, 3, 4]),
29-
(3, 3, 3, 4),
3028
]
3129

3230
TEST_CASE_2 = [
3331
{"holes": 2, "spatial_size": [2, 2, 2], "fill_value": 5, "max_spatial_size": [4, 4, 3], "prob": 1.0},
3432
np.random.randint(0, 2, size=[3, 3, 3, 4]),
35-
(3, 3, 3, 4),
3633
]
3734

3835
TEST_CASE_3 = [
3936
{"holes": 2, "spatial_size": [2, -1, 2], "fill_value": 5, "max_spatial_size": [4, 4, -1], "prob": 1.0},
4037
np.random.randint(0, 2, size=[3, 3, 3, 4]),
41-
(3, 3, 3, 4),
38+
]
39+
40+
TEST_CASE_4 = [
41+
{"holes": 2, "spatial_size": [2, 2, 2], "fill_value": (3, 6), "prob": 1.0},
42+
np.random.randint(0, 2, size=[3, 3, 3, 4]),
43+
]
44+
45+
TEST_CASE_5 = [
46+
{"holes": 2, "spatial_size": [2, 2, 2], "fill_value": None, "prob": 1.0},
47+
np.random.randint(0, 2, size=[3, 3, 3, 4]),
4248
]
4349

4450

4551
class TestRandCoarseDropout(unittest.TestCase):
46-
@parameterized.expand([TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3])
47-
def test_value(self, input_param, input_data, expected_shape):
52+
@parameterized.expand([TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4, TEST_CASE_5])
53+
def test_value(self, input_param, input_data):
4854
dropout = RandCoarseDropout(**input_param)
4955
result = dropout(input_data)
5056
holes = input_param.get("holes")
@@ -60,7 +66,16 @@ def test_value(self, input_param, input_data, expected_shape):
6066

6167
for h in dropout.hole_coords:
6268
data = result[h]
63-
np.testing.assert_allclose(data, input_param.get("fill_value", 0))
69+
fill_value = input_param.get("fill_value", None)
70+
if isinstance(fill_value, (int, float)):
71+
np.testing.assert_allclose(data, fill_value)
72+
elif fill_value is not None:
73+
min_value = data.min()
74+
max_value = data.max()
75+
self.assertGreaterEqual(max_value, min_value)
76+
self.assertGreaterEqual(min_value, fill_value[0])
77+
self.assertLess(max_value, fill_value[1])
78+
6479
if max_spatial_size is None:
6580
self.assertTupleEqual(data.shape[1:], tuple(spatial_size))
6681
else:

tests/test_rand_coarse_dropoutd.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,11 @@
2020
TEST_CASE_0 = [
2121
{"keys": "img", "holes": 2, "spatial_size": [2, 2, 2], "fill_value": 5, "prob": 1.0},
2222
{"img": np.random.randint(0, 2, size=[3, 3, 3, 4])},
23-
(3, 3, 3, 4),
2423
]
2524

2625
TEST_CASE_1 = [
2726
{"keys": "img", "holes": 1, "spatial_size": [1, 2, 3], "fill_value": 5, "max_holes": 5, "prob": 1.0},
2827
{"img": np.random.randint(0, 2, size=[3, 3, 3, 4])},
29-
(3, 3, 3, 4),
3028
]
3129

3230
TEST_CASE_2 = [
@@ -39,7 +37,6 @@
3937
"prob": 1.0,
4038
},
4139
{"img": np.random.randint(0, 2, size=[3, 3, 3, 4])},
42-
(3, 3, 3, 4),
4340
]
4441

4542
TEST_CASE_3 = [
@@ -52,13 +49,22 @@
5249
"prob": 1.0,
5350
},
5451
{"img": np.random.randint(0, 2, size=[3, 3, 3, 4])},
55-
(3, 3, 3, 4),
52+
]
53+
54+
TEST_CASE_4 = [
55+
{"keys": "img", "holes": 2, "spatial_size": [2, 2, 2], "fill_value": (0.2, 0.6), "prob": 1.0},
56+
{"img": np.random.rand(3, 3, 3, 4)},
57+
]
58+
59+
TEST_CASE_5 = [
60+
{"keys": "img", "holes": 2, "spatial_size": [2, 2, 2], "fill_value": None, "prob": 1.0},
61+
{"img": np.random.rand(3, 3, 3, 4)},
5662
]
5763

5864

5965
class TestRandCoarseDropoutd(unittest.TestCase):
60-
@parameterized.expand([TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3])
61-
def test_value(self, input_param, input_data, expected_shape):
66+
@parameterized.expand([TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4])
67+
def test_value(self, input_param, input_data):
6268
dropout = RandCoarseDropoutd(**input_param)
6369
result = dropout(input_data)["img"]
6470
holes = input_param.get("holes")
@@ -74,7 +80,16 @@ def test_value(self, input_param, input_data, expected_shape):
7480

7581
for h in dropout.hole_coords:
7682
data = result[h]
77-
np.testing.assert_allclose(data, input_param.get("fill_value", 0))
83+
fill_value = input_param.get("fill_value", 0)
84+
if isinstance(fill_value, (int, float)):
85+
np.testing.assert_allclose(data, fill_value)
86+
elif fill_value is not None:
87+
min_value = data.min()
88+
max_value = data.max()
89+
self.assertGreaterEqual(max_value, min_value)
90+
self.assertGreaterEqual(min_value, fill_value[0])
91+
self.assertLess(max_value, fill_value[1])
92+
7893
if max_spatial_size is None:
7994
self.assertTupleEqual(data.shape[1:], tuple(spatial_size))
8095
else:

tests/test_rand_shift_intensityd.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,19 @@
1414
import numpy as np
1515

1616
from monai.transforms import IntensityStatsd, RandShiftIntensityd
17-
from tests.utils import NumpyImageTestCase2D
17+
from tests.utils import TEST_NDARRAYS, NumpyImageTestCase2D, assert_allclose
1818

1919

2020
class TestRandShiftIntensityd(NumpyImageTestCase2D):
2121
def test_value(self):
22-
key = "img"
23-
shifter = RandShiftIntensityd(keys=[key], offsets=1.0, prob=1.0)
24-
shifter.set_random_state(seed=0)
25-
result = shifter({key: self.imt})
26-
np.random.seed(0)
27-
expected = self.imt + np.random.uniform(low=-1.0, high=1.0)
28-
np.testing.assert_allclose(result[key], expected)
22+
for p in TEST_NDARRAYS:
23+
key = "img"
24+
shifter = RandShiftIntensityd(keys=[key], offsets=1.0, prob=1.0)
25+
shifter.set_random_state(seed=0)
26+
result = shifter({key: p(self.imt)})
27+
np.random.seed(0)
28+
expected = self.imt + np.random.uniform(low=-1.0, high=1.0)
29+
assert_allclose(result[key], expected)
2930

3031
def test_factor(self):
3132
key = "img"

tests/test_shift_intensityd.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@
1414
import numpy as np
1515

1616
from monai.transforms import IntensityStatsd, ShiftIntensityd
17-
from tests.utils import NumpyImageTestCase2D
17+
from tests.utils import TEST_NDARRAYS, NumpyImageTestCase2D, assert_allclose
1818

1919

2020
class TestShiftIntensityd(NumpyImageTestCase2D):
2121
def test_value(self):
2222
key = "img"
23-
shifter = ShiftIntensityd(keys=[key], offset=1.0)
24-
result = shifter({key: self.imt})
25-
expected = self.imt + 1.0
26-
np.testing.assert_allclose(result[key], expected)
23+
for p in TEST_NDARRAYS:
24+
shifter = ShiftIntensityd(keys=[key], offset=1.0)
25+
result = shifter({key: p(self.imt)})
26+
expected = self.imt + 1.0
27+
assert_allclose(result[key], expected)
2728

2829
def test_factor(self):
2930
key = "img"

0 commit comments

Comments
 (0)