Skip to content

Commit a88976c

Browse files
Nic-Mamonai-botwyli
authored
2707 Add support to randomly fill cutout holes (#2837)
* [DLMED] support random fill Signed-off-by: Nic Ma <[email protected]> * [DLMED] update for test Signed-off-by: Nic Ma <[email protected]> * [DLMED] fix typo Signed-off-by: Nic Ma <[email protected]> * [DLMED] update dict transform Signed-off-by: Nic Ma <[email protected]> * [DLMED] fix mypy Signed-off-by: Nic Ma <[email protected]> * [DLMED] fix typo Signed-off-by: Nic Ma <[email protected]> * [DLMED] fix typo Signed-off-by: Nic Ma <[email protected]> * [DLMED] fix typo Signed-off-by: Nic Ma <[email protected]> * [DLMED] fix typo Signed-off-by: Nic Ma <[email protected]> * [DLMED] support auto computing min and max Signed-off-by: Nic Ma <[email protected]> * [MONAI] python code formatting Signed-off-by: monai-bot <[email protected]> Co-authored-by: monai-bot <[email protected]> Co-authored-by: Wenqi Li <[email protected]>
1 parent acf4a9f commit a88976c

File tree

4 files changed

+68
-20
lines changed

4 files changed

+68
-20
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: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,7 +1435,10 @@ class RandCoarseDropoutd(RandomizableTransform, MapTransform):
14351435
if some components of the `spatial_size` are non-positive values, the transform will use the
14361436
corresponding components of input img size. For example, `spatial_size=(32, -1)` will be adapted
14371437
to `(32, 64)` if the second spatial dimension size of img is `64`.
1438-
fill_value: target value to fill the dropout regions.
1438+
fill_value: target value to fill the dropout regions, if providing a number, will use it as constant
1439+
value to fill all the regions. if providing a tuple for the `min` and `max`, will randomly select
1440+
value for every pixel / voxel from the range `[min, max)`. if None, will compute the `min` and `max`
1441+
value of input image then randomly select value to fill, default to None.
14391442
max_holes: if not None, define the maximum number to randomly select the expected number of regions.
14401443
max_spatial_size: if not None, define the maximum spatial size to randomly select size for every region.
14411444
if some components of the `max_spatial_size` are non-positive values, the transform will use the
@@ -1451,7 +1454,7 @@ def __init__(
14511454
keys: KeysCollection,
14521455
holes: int,
14531456
spatial_size: Union[Sequence[int], int],
1454-
fill_value: Union[float, int] = 0,
1457+
fill_value: Optional[Union[Tuple[float, float], float]] = None,
14551458
max_holes: Optional[int] = None,
14561459
max_spatial_size: Optional[Union[Sequence[int], int]] = None,
14571460
prob: float = 0.1,
@@ -1487,7 +1490,13 @@ def __call__(self, data):
14871490
if self._do_transform:
14881491
for key in self.key_iterator(d):
14891492
for h in self.hole_coords:
1490-
d[key][h] = self.fill_value
1493+
fill_value = (d[key].min(), d[key].max()) if self.fill_value is None else self.fill_value
1494+
if isinstance(fill_value, (tuple, list)):
1495+
if len(fill_value) != 2:
1496+
raise ValueError("fill_value should contain 2 numbers if providing the `min` and `max`.")
1497+
d[key][h] = self.R.uniform(fill_value[0], fill_value[1], size=d[key][h].shape)
1498+
else:
1499+
d[key][h] = fill_value
14911500
return d
14921501

14931502

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:

0 commit comments

Comments
 (0)