Skip to content

2707 Add support to randomly fill cutout holes #2837

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Aug 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions monai/transforms/intensity/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1643,7 +1643,10 @@ class RandCoarseDropout(RandomizableTransform):
if some components of the `spatial_size` are non-positive values, the transform will use the
corresponding components of input img size. For example, `spatial_size=(32, -1)` will be adapted
to `(32, 64)` if the second spatial dimension size of img is `64`.
fill_value: target value to fill the dropout regions.
fill_value: target value to fill the dropout regions, if providing a number, will use it as constant
value to fill all the regions. if providing a tuple for the `min` and `max`, will randomly select
value for every pixel / voxel from the range `[min, max)`. if None, will compute the `min` and `max`
value of input image then randomly select value to fill, default to None.
max_holes: if not None, define the maximum number to randomly select the expected number of regions.
max_spatial_size: if not None, define the maximum spatial size to randomly select size for every region.
if some components of the `max_spatial_size` are non-positive values, the transform will use the
Expand All @@ -1657,7 +1660,7 @@ def __init__(
self,
holes: int,
spatial_size: Union[Sequence[int], int],
fill_value: Union[float, int] = 0,
fill_value: Optional[Union[Tuple[float, float], float]] = None,
max_holes: Optional[int] = None,
max_spatial_size: Optional[Union[Sequence[int], int]] = None,
prob: float = 0.1,
Expand Down Expand Up @@ -1688,7 +1691,13 @@ def __call__(self, img: np.ndarray):
self.randomize(img.shape[1:])
if self._do_transform:
for h in self.hole_coords:
img[h] = self.fill_value
fill_value = (img.min(), img.max()) if self.fill_value is None else self.fill_value
if isinstance(fill_value, (tuple, list)):
if len(fill_value) != 2:
raise ValueError("fill_value should contain 2 numbers if providing the `min` and `max`.")
img[h] = self.R.uniform(fill_value[0], fill_value[1], size=img[h].shape)
else:
img[h] = fill_value

return img

Expand Down
15 changes: 12 additions & 3 deletions monai/transforms/intensity/dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -1435,7 +1435,10 @@ class RandCoarseDropoutd(RandomizableTransform, MapTransform):
if some components of the `spatial_size` are non-positive values, the transform will use the
corresponding components of input img size. For example, `spatial_size=(32, -1)` will be adapted
to `(32, 64)` if the second spatial dimension size of img is `64`.
fill_value: target value to fill the dropout regions.
fill_value: target value to fill the dropout regions, if providing a number, will use it as constant
value to fill all the regions. if providing a tuple for the `min` and `max`, will randomly select
value for every pixel / voxel from the range `[min, max)`. if None, will compute the `min` and `max`
value of input image then randomly select value to fill, default to None.
max_holes: if not None, define the maximum number to randomly select the expected number of regions.
max_spatial_size: if not None, define the maximum spatial size to randomly select size for every region.
if some components of the `max_spatial_size` are non-positive values, the transform will use the
Expand All @@ -1451,7 +1454,7 @@ def __init__(
keys: KeysCollection,
holes: int,
spatial_size: Union[Sequence[int], int],
fill_value: Union[float, int] = 0,
fill_value: Optional[Union[Tuple[float, float], float]] = None,
max_holes: Optional[int] = None,
max_spatial_size: Optional[Union[Sequence[int], int]] = None,
prob: float = 0.1,
Expand Down Expand Up @@ -1487,7 +1490,13 @@ def __call__(self, data):
if self._do_transform:
for key in self.key_iterator(d):
for h in self.hole_coords:
d[key][h] = self.fill_value
fill_value = (d[key].min(), d[key].max()) if self.fill_value is None else self.fill_value
if isinstance(fill_value, (tuple, list)):
if len(fill_value) != 2:
raise ValueError("fill_value should contain 2 numbers if providing the `min` and `max`.")
d[key][h] = self.R.uniform(fill_value[0], fill_value[1], size=d[key][h].shape)
else:
d[key][h] = fill_value
return d


Expand Down
29 changes: 22 additions & 7 deletions tests/test_rand_coarse_dropout.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,37 @@
TEST_CASE_0 = [
{"holes": 2, "spatial_size": [2, 2, 2], "fill_value": 5, "prob": 1.0},
np.random.randint(0, 2, size=[3, 3, 3, 4]),
(3, 3, 3, 4),
]

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

TEST_CASE_2 = [
{"holes": 2, "spatial_size": [2, 2, 2], "fill_value": 5, "max_spatial_size": [4, 4, 3], "prob": 1.0},
np.random.randint(0, 2, size=[3, 3, 3, 4]),
(3, 3, 3, 4),
]

TEST_CASE_3 = [
{"holes": 2, "spatial_size": [2, -1, 2], "fill_value": 5, "max_spatial_size": [4, 4, -1], "prob": 1.0},
np.random.randint(0, 2, size=[3, 3, 3, 4]),
(3, 3, 3, 4),
]

TEST_CASE_4 = [
{"holes": 2, "spatial_size": [2, 2, 2], "fill_value": (3, 6), "prob": 1.0},
np.random.randint(0, 2, size=[3, 3, 3, 4]),
]

TEST_CASE_5 = [
{"holes": 2, "spatial_size": [2, 2, 2], "fill_value": None, "prob": 1.0},
np.random.randint(0, 2, size=[3, 3, 3, 4]),
]


class TestRandCoarseDropout(unittest.TestCase):
@parameterized.expand([TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3])
def test_value(self, input_param, input_data, expected_shape):
@parameterized.expand([TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4, TEST_CASE_5])
def test_value(self, input_param, input_data):
dropout = RandCoarseDropout(**input_param)
result = dropout(input_data)
holes = input_param.get("holes")
Expand All @@ -60,7 +66,16 @@ def test_value(self, input_param, input_data, expected_shape):

for h in dropout.hole_coords:
data = result[h]
np.testing.assert_allclose(data, input_param.get("fill_value", 0))
fill_value = input_param.get("fill_value", None)
if isinstance(fill_value, (int, float)):
np.testing.assert_allclose(data, fill_value)
elif fill_value is not None:
min_value = data.min()
max_value = data.max()
self.assertGreaterEqual(max_value, min_value)
self.assertGreaterEqual(min_value, fill_value[0])
self.assertLess(max_value, fill_value[1])

if max_spatial_size is None:
self.assertTupleEqual(data.shape[1:], tuple(spatial_size))
else:
Expand Down
29 changes: 22 additions & 7 deletions tests/test_rand_coarse_dropoutd.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@
TEST_CASE_0 = [
{"keys": "img", "holes": 2, "spatial_size": [2, 2, 2], "fill_value": 5, "prob": 1.0},
{"img": np.random.randint(0, 2, size=[3, 3, 3, 4])},
(3, 3, 3, 4),
]

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

TEST_CASE_2 = [
Expand All @@ -39,7 +37,6 @@
"prob": 1.0,
},
{"img": np.random.randint(0, 2, size=[3, 3, 3, 4])},
(3, 3, 3, 4),
]

TEST_CASE_3 = [
Expand All @@ -52,13 +49,22 @@
"prob": 1.0,
},
{"img": np.random.randint(0, 2, size=[3, 3, 3, 4])},
(3, 3, 3, 4),
]

TEST_CASE_4 = [
{"keys": "img", "holes": 2, "spatial_size": [2, 2, 2], "fill_value": (0.2, 0.6), "prob": 1.0},
{"img": np.random.rand(3, 3, 3, 4)},
]

TEST_CASE_5 = [
{"keys": "img", "holes": 2, "spatial_size": [2, 2, 2], "fill_value": None, "prob": 1.0},
{"img": np.random.rand(3, 3, 3, 4)},
]


class TestRandCoarseDropoutd(unittest.TestCase):
@parameterized.expand([TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3])
def test_value(self, input_param, input_data, expected_shape):
@parameterized.expand([TEST_CASE_0, TEST_CASE_1, TEST_CASE_2, TEST_CASE_3, TEST_CASE_4])
def test_value(self, input_param, input_data):
dropout = RandCoarseDropoutd(**input_param)
result = dropout(input_data)["img"]
holes = input_param.get("holes")
Expand All @@ -74,7 +80,16 @@ def test_value(self, input_param, input_data, expected_shape):

for h in dropout.hole_coords:
data = result[h]
np.testing.assert_allclose(data, input_param.get("fill_value", 0))
fill_value = input_param.get("fill_value", 0)
if isinstance(fill_value, (int, float)):
np.testing.assert_allclose(data, fill_value)
elif fill_value is not None:
min_value = data.min()
max_value = data.max()
self.assertGreaterEqual(max_value, min_value)
self.assertGreaterEqual(min_value, fill_value[0])
self.assertLess(max_value, fill_value[1])

if max_spatial_size is None:
self.assertTupleEqual(data.shape[1:], tuple(spatial_size))
else:
Expand Down