diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py index b36c7adf96..a68f6a0a2e 100644 --- a/monai/transforms/intensity/array.py +++ b/monai/transforms/intensity/array.py @@ -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 @@ -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, @@ -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 diff --git a/monai/transforms/intensity/dictionary.py b/monai/transforms/intensity/dictionary.py index 227b6fb434..beb210c645 100644 --- a/monai/transforms/intensity/dictionary.py +++ b/monai/transforms/intensity/dictionary.py @@ -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 @@ -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, @@ -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 diff --git a/tests/test_rand_coarse_dropout.py b/tests/test_rand_coarse_dropout.py index 235a391567..18d026e573 100644 --- a/tests/test_rand_coarse_dropout.py +++ b/tests/test_rand_coarse_dropout.py @@ -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") @@ -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: diff --git a/tests/test_rand_coarse_dropoutd.py b/tests/test_rand_coarse_dropoutd.py index d189a80f56..932e65c8cf 100644 --- a/tests/test_rand_coarse_dropoutd.py +++ b/tests/test_rand_coarse_dropoutd.py @@ -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 = [ @@ -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 = [ @@ -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") @@ -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: