diff --git a/monai/transforms/__init__.py b/monai/transforms/__init__.py index 826f66a34c..20e24b3958 100644 --- a/monai/transforms/__init__.py +++ b/monai/transforms/__init__.py @@ -535,8 +535,11 @@ from .utils_pytorch_numpy_unification import ( any_np_pt, clip, + concatenate, + cumsum, floor_divide, in1d, + isfinite, maximum, moveaxis, nonzero, diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py index c2cf03d6fe..b3ab865c52 100644 --- a/monai/transforms/croppad/array.py +++ b/monai/transforms/croppad/array.py @@ -792,20 +792,25 @@ class RandWeightedCrop(Randomizable, Transform): It should be a single-channel array in shape, for example, `(1, spatial_dim_0, spatial_dim_1, ...)`. """ + backend = SpatialCrop.backend + def __init__( - self, spatial_size: Union[Sequence[int], int], num_samples: int = 1, weight_map: Optional[np.ndarray] = None + self, + spatial_size: Union[Sequence[int], int], + num_samples: int = 1, + weight_map: Optional[NdarrayOrTensor] = None, ): self.spatial_size = ensure_tuple(spatial_size) self.num_samples = int(num_samples) self.weight_map = weight_map self.centers: List[np.ndarray] = [] - def randomize(self, weight_map: np.ndarray) -> None: + def randomize(self, weight_map: NdarrayOrTensor) -> None: self.centers = weighted_patch_samples( spatial_size=self.spatial_size, w=weight_map[0], n_samples=self.num_samples, r_state=self.R ) # using only the first channel as weight map - def __call__(self, img: np.ndarray, weight_map: Optional[np.ndarray] = None) -> List[np.ndarray]: + def __call__(self, img: NdarrayOrTensor, weight_map: Optional[NdarrayOrTensor] = None) -> List[NdarrayOrTensor]: """ Args: img: input image to sample patches from. assuming `img` is a channel-first array. @@ -816,7 +821,6 @@ def __call__(self, img: np.ndarray, weight_map: Optional[np.ndarray] = None) -> Returns: A list of image patches """ - img, *_ = convert_data_type(img, np.ndarray) # type: ignore if weight_map is None: weight_map = self.weight_map if weight_map is None: @@ -824,15 +828,12 @@ def __call__(self, img: np.ndarray, weight_map: Optional[np.ndarray] = None) -> if img.shape[1:] != weight_map.shape[1:]: raise ValueError(f"image and weight map spatial shape mismatch: {img.shape[1:]} vs {weight_map.shape[1:]}.") - weight_map, *_ = convert_data_type(weight_map, np.ndarray) # type: ignore - self.randomize(weight_map) _spatial_size = fall_back_tuple(self.spatial_size, weight_map.shape[1:]) - results = [] + results: List[NdarrayOrTensor] = [] for center in self.centers: cropper = SpatialCrop(roi_center=center, roi_size=_spatial_size) - cropped: np.ndarray = cropper(img) # type: ignore - results.append(cropped) + results.append(cropper(img)) return results diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 222d6ae17c..eea00a6c4b 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -929,6 +929,8 @@ class RandWeightedCropd(Randomizable, MapTransform, InvertibleTransform): :py:class:`monai.transforms.RandWeightedCrop` """ + backend = SpatialCrop.backend + def __init__( self, keys: KeysCollection, @@ -951,18 +953,18 @@ def __init__( self.meta_key_postfix = ensure_tuple_rep(meta_key_postfix, len(self.keys)) self.centers: List[np.ndarray] = [] - def randomize(self, weight_map: np.ndarray) -> None: + def randomize(self, weight_map: NdarrayOrTensor) -> None: self.centers = weighted_patch_samples( spatial_size=self.spatial_size, w=weight_map[0], n_samples=self.num_samples, r_state=self.R ) - def __call__(self, data: Mapping[Hashable, np.ndarray]) -> List[Dict[Hashable, np.ndarray]]: + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> List[Dict[Hashable, NdarrayOrTensor]]: d = dict(data) self.randomize(d[self.w_key]) _spatial_size = fall_back_tuple(self.spatial_size, d[self.w_key].shape[1:]) # initialize returned list with shallow copy to preserve key ordering - results: List[Dict[Hashable, np.ndarray]] = [dict(data) for _ in range(self.num_samples)] + results: List[Dict[Hashable, NdarrayOrTensor]] = [dict(data) for _ in range(self.num_samples)] # fill in the extra keys with unmodified data for i in range(self.num_samples): for key in set(data.keys()).difference(set(self.keys)): @@ -977,8 +979,7 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> List[Dict[Hashable, n for i, center in enumerate(self.centers): cropper = SpatialCrop(roi_center=center, roi_size=_spatial_size) orig_size = img.shape[1:] - cropped: np.ndarray = cropper(img) # type: ignore - results[i][key] = cropped + results[i][key] = cropper(img) self.push_transform(results[i], key, extra_info={"center": center}, orig_size=orig_size) if self.center_coord_key: results[i][self.center_coord_key] = center @@ -989,7 +990,7 @@ def __call__(self, data: Mapping[Hashable, np.ndarray]) -> List[Dict[Hashable, n meta_key = meta_key or f"{key}_{meta_key_postfix}" if meta_key not in results[i]: results[i][meta_key] = {} # type: ignore - results[i][meta_key][Key.PATCH_INDEX] = i + results[i][meta_key][Key.PATCH_INDEX] = i # type: ignore return results @@ -1001,7 +1002,7 @@ def inverse(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, Nd orig_size = np.asarray(transform[InverseKeys.ORIG_SIZE]) current_size = np.asarray(d[key].shape[1:]) center = transform[InverseKeys.EXTRA_INFO]["center"] - cropper = SpatialCrop(roi_center=tuple(center), roi_size=self.spatial_size) + cropper = SpatialCrop(roi_center=center, roi_size=self.spatial_size) # get required pad to start and end pad_to_start = np.array([s.indices(o)[0] for s, o in zip(cropper.slices, orig_size)]) pad_to_end = orig_size - current_size - pad_to_start diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 9ccd315354..162e207074 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -33,6 +33,7 @@ create_translate, map_spatial_axes, ) +from monai.transforms.utils_pytorch_numpy_unification import concatenate from monai.utils import ( GridSampleMode, GridSamplePadMode, @@ -1977,6 +1978,8 @@ class AddCoordinateChannels(Transform): Liu, R. et al. An Intriguing Failing of Convolutional Neural Networks and the CoordConv Solution, NeurIPS 2018. """ + backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + def __init__( self, spatial_channels: Sequence[int], @@ -1989,12 +1992,11 @@ def __init__( """ self.spatial_channels = spatial_channels - def __call__(self, img: Union[np.ndarray, torch.Tensor]): + def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor: """ Args: img: data to be transformed, assuming `img` is channel first. """ - img, *_ = convert_data_type(img, np.ndarray) # type: ignore if max(self.spatial_channels) > img.ndim - 1: raise ValueError( f"input has {img.ndim-1} spatial dimensions, cannot add AddCoordinateChannels channel for " @@ -2005,7 +2007,8 @@ def __call__(self, img: Union[np.ndarray, torch.Tensor]): spatial_dims = img.shape[1:] coord_channels = np.array(np.meshgrid(*tuple(np.linspace(-0.5, 0.5, s) for s in spatial_dims), indexing="ij")) + coord_channels, *_ = convert_to_dst_type(coord_channels, img) # type: ignore # only keep required dimensions. need to subtract 1 since im will be 0-based # but user input is 1-based (because channel dim is 0) coord_channels = coord_channels[[s - 1 for s in self.spatial_channels]] - return np.concatenate((img, coord_channels), axis=0) + return concatenate((img, coord_channels), axis=0) diff --git a/monai/transforms/spatial/dictionary.py b/monai/transforms/spatial/dictionary.py index bd10aad8f7..fa23c557b2 100644 --- a/monai/transforms/spatial/dictionary.py +++ b/monai/transforms/spatial/dictionary.py @@ -1758,6 +1758,8 @@ class AddCoordinateChannelsd(MapTransform): Dictionary-based wrapper of :py:class:`monai.transforms.AddCoordinateChannels`. """ + backend = AddCoordinateChannels.backend + def __init__(self, keys: KeysCollection, spatial_channels: Sequence[int], allow_missing_keys: bool = False) -> None: """ Args: @@ -1772,9 +1774,7 @@ def __init__(self, keys: KeysCollection, spatial_channels: Sequence[int], allow_ super().__init__(keys, allow_missing_keys) self.add_coordinate_channels = AddCoordinateChannels(spatial_channels) - def __call__( - self, data: Mapping[Hashable, Union[np.ndarray, torch.Tensor]] - ) -> Dict[Hashable, Union[np.ndarray, torch.Tensor]]: + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: d = dict(data) for key in self.key_iterator(d): d[key] = self.add_coordinate_channels(d[key]) diff --git a/monai/transforms/utility/array.py b/monai/transforms/utility/array.py index ffc3b99cb5..28ca813b1c 100644 --- a/monai/transforms/utility/array.py +++ b/monai/transforms/utility/array.py @@ -31,7 +31,7 @@ map_binary_to_indices, map_classes_to_indices, ) -from monai.transforms.utils_pytorch_numpy_unification import in1d, moveaxis, unravel_indices +from monai.transforms.utils_pytorch_numpy_unification import concatenate, in1d, moveaxis, unravel_indices from monai.utils import ( convert_data_type, convert_to_cupy, @@ -45,6 +45,7 @@ ) from monai.utils.enums import TransformBackends from monai.utils.misc import is_module_ver_at_least +from monai.utils.type_conversion import convert_to_dst_type PILImageImage, has_pil = optional_import("PIL.Image", name="Image") pil_image_fromarray, _ = optional_import("PIL.Image", name="fromarray") @@ -915,22 +916,24 @@ class AddExtremePointsChannel(Randomizable, Transform): ValueError: When label image is not single channel. """ + backend = [TransformBackends.TORCH, TransformBackends.NUMPY] + def __init__(self, background: int = 0, pert: float = 0.0) -> None: self._background = background self._pert = pert self._points: List[Tuple[int, ...]] = [] - def randomize(self, label: np.ndarray) -> None: + def randomize(self, label: NdarrayOrTensor) -> None: self._points = get_extreme_points(label, rand_state=self.R, background=self._background, pert=self._pert) def __call__( self, - img: np.ndarray, - label: Optional[np.ndarray] = None, + img: NdarrayOrTensor, + label: Optional[NdarrayOrTensor] = None, sigma: Union[Sequence[float], float, Sequence[torch.Tensor], torch.Tensor] = 3.0, rescale_min: float = -1.0, rescale_max: float = 1.0, - ): + ) -> NdarrayOrTensor: """ Args: img: the image that we want to add new channel to. @@ -947,17 +950,14 @@ def __call__( if label.shape[0] != 1: raise ValueError("Only supports single channel labels!") - img, *_ = convert_data_type(img, np.ndarray) # type: ignore - label, *_ = convert_data_type(label, np.ndarray) # type: ignore - # Generate extreme points self.randomize(label[0, :]) points_image = extreme_points_to_image( points=self._points, label=label, sigma=sigma, rescale_min=rescale_min, rescale_max=rescale_max ) - - return np.concatenate([img, points_image], axis=0) + points_image, *_ = convert_to_dst_type(points_image, img) # type: ignore + return concatenate((img, points_image), axis=0) class TorchVision: diff --git a/monai/transforms/utility/dictionary.py b/monai/transforms/utility/dictionary.py index cefb654698..0db3aeded2 100644 --- a/monai/transforms/utility/dictionary.py +++ b/monai/transforms/utility/dictionary.py @@ -30,6 +30,7 @@ from monai.transforms.transform import MapTransform, Randomizable, RandomizableTransform from monai.transforms.utility.array import ( AddChannel, + AddExtremePointsChannel, AsChannelFirst, AsChannelLast, CastToType, @@ -59,8 +60,10 @@ Transpose, ) from monai.transforms.utils import extreme_points_to_image, get_extreme_points +from monai.transforms.utils_pytorch_numpy_unification import concatenate from monai.utils import convert_to_numpy, ensure_tuple, ensure_tuple_rep from monai.utils.enums import InverseKeys, TransformBackends +from monai.utils.type_conversion import convert_to_dst_type __all__ = [ "AddChannelD", @@ -1231,6 +1234,8 @@ class AddExtremePointsChanneld(Randomizable, MapTransform): """ + backend = AddExtremePointsChannel.backend + def __init__( self, keys: KeysCollection, @@ -1251,10 +1256,10 @@ def __init__( self.rescale_min = rescale_min self.rescale_max = rescale_max - def randomize(self, label: np.ndarray) -> None: + def randomize(self, label: NdarrayOrTensor) -> None: self.points = get_extreme_points(label, rand_state=self.R, background=self.background, pert=self.pert) - def __call__(self, data): + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: d = dict(data) label = d[self.label_key] if label.shape[0] != 1: @@ -1272,7 +1277,8 @@ def __call__(self, data): rescale_min=self.rescale_min, rescale_max=self.rescale_max, ) - d[key] = np.concatenate([img, points_image], axis=0) + points_image, *_ = convert_to_dst_type(points_image, img) # type: ignore + d[key] = concatenate([img, points_image], axis=0) return d diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index 15543e91ef..75260af5b6 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -25,7 +25,16 @@ from monai.networks.layers import GaussianFilter from monai.transforms.compose import Compose, OneOf from monai.transforms.transform import MapTransform, Transform, apply_transform -from monai.transforms.utils_pytorch_numpy_unification import any_np_pt, nonzero, ravel, unravel_index, where +from monai.transforms.utils_pytorch_numpy_unification import ( + any_np_pt, + cumsum, + isfinite, + nonzero, + ravel, + searchsorted, + unravel_index, + where, +) from monai.utils import ( GridSampleMode, InterpolateMode, @@ -348,7 +357,7 @@ def map_classes_to_indices( def weighted_patch_samples( spatial_size: Union[int, Sequence[int]], - w: np.ndarray, + w: NdarrayOrTensor, n_samples: int = 1, r_state: Optional[np.random.RandomState] = None, ) -> List: @@ -377,17 +386,20 @@ def weighted_patch_samples( s = tuple(slice(w // 2, m - w + w // 2) if m > w else slice(m // 2, m // 2 + 1) for w, m in zip(win_size, img_size)) v = w[s] # weight map in the 'valid' mode v_size = v.shape - v = v.ravel() - if np.any(v < 0): - v -= np.min(v) # shifting to non-negative - v = v.cumsum() - if not v[-1] or not np.isfinite(v[-1]) or v[-1] < 0: # uniform sampling + v = ravel(v) + if (v < 0).any(): + v -= v.min() # shifting to non-negative + v = cumsum(v) + if not v[-1] or not isfinite(v[-1]) or v[-1] < 0: # uniform sampling idx = r_state.randint(0, len(v), size=n_samples) else: - idx = v.searchsorted(r_state.random(n_samples) * v[-1], side="right") + r, *_ = convert_to_dst_type(r_state.random(n_samples), v) # type: ignore + idx = searchsorted(v, r * v[-1], right=True) + idx, *_ = convert_to_dst_type(idx, v, dtype=torch.int) # type: ignore # compensate 'valid' mode diff = np.minimum(win_size, img_size) // 2 - return [np.unravel_index(i, v_size) + diff for i in np.asarray(idx, dtype=int)] + diff, *_ = convert_to_dst_type(diff, v) # type: ignore + return [unravel_index(i, v_size) + diff for i in idx] def correct_crop_centers( @@ -971,7 +983,7 @@ def fill_holes( def get_extreme_points( - img: np.ndarray, rand_state: Optional[np.random.RandomState] = None, background: int = 0, pert: float = 0.0 + img: NdarrayOrTensor, rand_state: Optional[np.random.RandomState] = None, background: int = 0, pert: float = 0.0 ) -> List[Tuple[int, ...]]: """ Generate extreme points from an image. These are used to generate initial segmentation @@ -995,7 +1007,7 @@ def get_extreme_points( """ if rand_state is None: rand_state = np.random.random.__self__ # type: ignore - indices = np.where(img != background) + indices = where(img != background) if np.size(indices[0]) == 0: raise ValueError("get_extreme_points: no foreground object in mask!") @@ -1007,7 +1019,9 @@ def _get_point(val, dim): val : value for comparison dim : dimension in which to look for value """ - idx = rand_state.choice(np.where(indices[dim] == val)[0]) + idx = where(indices[dim] == val)[0] + idx = idx.cpu() if isinstance(idx, torch.Tensor) else idx + idx = rand_state.choice(idx) pt = [] for j in range(img.ndim): # add +- pert to each dimension @@ -1019,19 +1033,19 @@ def _get_point(val, dim): points = [] for i in range(img.ndim): - points.append(tuple(_get_point(np.min(indices[i][...]), i))) - points.append(tuple(_get_point(np.max(indices[i][...]), i))) + points.append(tuple(_get_point(indices[i].min(), i))) + points.append(tuple(_get_point(indices[i].max(), i))) return points def extreme_points_to_image( points: List[Tuple[int, ...]], - label: np.ndarray, + label: NdarrayOrTensor, sigma: Union[Sequence[float], float, Sequence[torch.Tensor], torch.Tensor] = 0.0, rescale_min: float = -1.0, rescale_max: float = 1.0, -): +) -> torch.Tensor: """ Please refer to :py:class:`monai.transforms.AddExtremePointsChannel` for the usage. @@ -1049,21 +1063,26 @@ def extreme_points_to_image( rescale_max: maximum value of output data. """ # points to image - points_image = torch.zeros(label.shape[1:], dtype=torch.float) + # points_image = torch.zeros(label.shape[1:], dtype=torch.float) + points_image = torch.zeros_like(torch.as_tensor(label[0]), dtype=torch.float) for p in points: points_image[p] = 1.0 + if isinstance(sigma, Sequence): + sigma = [torch.as_tensor(s, device=points_image.device) for s in sigma] + else: + sigma = torch.as_tensor(sigma, device=points_image.device) + # add channel and add batch points_image = points_image.unsqueeze(0).unsqueeze(0) gaussian_filter = GaussianFilter(label.ndim - 1, sigma=sigma) - points_image = gaussian_filter(points_image).squeeze(0).detach().numpy() + points_image = gaussian_filter(points_image).squeeze(0).detach() # rescale the points image to [rescale_min, rescale_max] - min_intensity = np.min(points_image) - max_intensity = np.max(points_image) + min_intensity = points_image.min() + max_intensity = points_image.max() points_image = (points_image - min_intensity) / (max_intensity - min_intensity) - points_image = points_image * (rescale_max - rescale_min) + rescale_min - return points_image + return points_image * (rescale_max - rescale_min) + rescale_min def map_spatial_axes( diff --git a/monai/transforms/utils_pytorch_numpy_unification.py b/monai/transforms/utils_pytorch_numpy_unification.py index 52c5248f96..3cb6b0a22d 100644 --- a/monai/transforms/utils_pytorch_numpy_unification.py +++ b/monai/transforms/utils_pytorch_numpy_unification.py @@ -30,6 +30,9 @@ "ravel", "any_np_pt", "maximum", + "concatenate", + "cumsum", + "isfinite", ] @@ -261,3 +264,38 @@ def maximum(a: NdarrayOrTensor, b: NdarrayOrTensor) -> NdarrayOrTensor: return torch.maximum(a, b) return torch.stack((a, b)).max(dim=0)[0] return np.maximum(a, b) + + +def concatenate(to_cat: Sequence[NdarrayOrTensor], axis: int = 0, out=None) -> NdarrayOrTensor: + """`np.concatenate` with equivalent implementation for torch (`torch.cat`).""" + if isinstance(to_cat[0], np.ndarray): + return np.concatenate(to_cat, axis, out) # type: ignore + else: + return torch.cat(to_cat, dim=axis, out=out) # type: ignore + + +def cumsum(a: NdarrayOrTensor, axis=None): + """`np.cumsum` with equivalent implementation for torch.""" + if isinstance(a, np.ndarray): + return np.cumsum(a, axis) + else: + if axis is None: + return torch.cumsum(a[:], 0) + else: + return torch.cumsum(a, dim=axis) + + +def isfinite(x): + """`np.isfinite` with equivalent implementation for torch.""" + if not isinstance(x, torch.Tensor): + return np.isfinite(x) + else: + return torch.isfinite(x) + + +def searchsorted(a: NdarrayOrTensor, v: NdarrayOrTensor, right=False, sorter=None): + if isinstance(a, np.ndarray): + side = "right" if right else "left" + return np.searchsorted(a, v, side, sorter) # type: ignore + else: + return torch.searchsorted(a, v, right=right) # type: ignore diff --git a/tests/test_add_coordinate_channels.py b/tests/test_add_coordinate_channels.py index 3399008e02..4d779cffff 100644 --- a/tests/test_add_coordinate_channels.py +++ b/tests/test_add_coordinate_channels.py @@ -12,32 +12,36 @@ import unittest import numpy as np +import torch from parameterized import parameterized from monai.transforms import AddCoordinateChannels +from tests.utils import TEST_NDARRAYS, assert_allclose -TEST_CASE_1 = [{"spatial_channels": (1, 2, 3)}, np.random.randint(0, 2, size=(1, 3, 3, 3)), (4, 3, 3, 3)] - -TEST_CASE_2 = [{"spatial_channels": (1,)}, np.random.randint(0, 2, size=(1, 3, 3, 3)), (2, 3, 3, 3)] - -TEST_CASE_ERROR_3 = [{"spatial_channels": (3,)}, np.random.randint(0, 2, size=(1, 3, 3))] - -TEST_CASE_ERROR_4 = [{"spatial_channels": (0, 1, 2)}, np.random.randint(0, 2, size=(1, 3, 3))] +TESTS, TEST_CASES_ERROR_1, TEST_CASES_ERROR_2 = [], [], [] +for p in TEST_NDARRAYS: + TESTS.append([{"spatial_channels": (1, 2, 3)}, p(np.random.randint(0, 2, size=(1, 3, 3, 3))), (4, 3, 3, 3)]) + TESTS.append([{"spatial_channels": (1,)}, p(np.random.randint(0, 2, size=(1, 3, 3, 3))), (2, 3, 3, 3)]) + TEST_CASES_ERROR_1.append([{"spatial_channels": (3,)}, p(np.random.randint(0, 2, size=(1, 3, 3)))]) + TEST_CASES_ERROR_2.append([{"spatial_channels": (0, 1, 2)}, p(np.random.randint(0, 2, size=(1, 3, 3)))]) class TestAddCoordinateChannels(unittest.TestCase): - @parameterized.expand([TEST_CASE_1, TEST_CASE_2]) + @parameterized.expand(TESTS) def test_shape(self, input_param, input, expected_shape): result = AddCoordinateChannels(**input_param)(input) + self.assertEqual(type(result), type(input)) + if isinstance(result, torch.Tensor): + self.assertEqual(result.device, input.device) self.assertEqual(list(result.shape), list(expected_shape)) - np.testing.assert_array_equal(input[0, ...], result[0, ...]) + assert_allclose(input[0, ...], result[0, ...]) - @parameterized.expand([TEST_CASE_ERROR_3]) + @parameterized.expand(TEST_CASES_ERROR_1) def test_max_channel(self, input_param, input): with self.assertRaises(ValueError): AddCoordinateChannels(**input_param)(input) - @parameterized.expand([TEST_CASE_ERROR_4]) + @parameterized.expand(TEST_CASES_ERROR_2) def test_channel_dim(self, input_param, input): with self.assertRaises(ValueError): AddCoordinateChannels(**input_param)(input) diff --git a/tests/test_add_coordinate_channelsd.py b/tests/test_add_coordinate_channelsd.py index 0fa6aae1c9..08d9e62468 100644 --- a/tests/test_add_coordinate_channelsd.py +++ b/tests/test_add_coordinate_channelsd.py @@ -12,40 +12,54 @@ import unittest import numpy as np +import torch from parameterized import parameterized from monai.transforms import AddCoordinateChannelsd +from tests.utils import TEST_NDARRAYS, assert_allclose -TEST_CASE_1 = [ - {"spatial_channels": (1, 2, 3), "keys": ["img"]}, - {"img": np.random.randint(0, 2, size=(1, 3, 3, 3))}, - (4, 3, 3, 3), -] +TESTS, TEST_CASES_ERROR_1, TEST_CASES_ERROR_2 = [], [], [] +for p in TEST_NDARRAYS: + TESTS.append( + [ + {"spatial_channels": (1, 2, 3), "keys": ["img"]}, + {"img": p(np.random.randint(0, 2, size=(1, 3, 3, 3)))}, + (4, 3, 3, 3), + ] + ) + TESTS.append( + [ + {"spatial_channels": (1,), "keys": ["img"]}, + {"img": p(np.random.randint(0, 2, size=(1, 3, 3, 3)))}, + (2, 3, 3, 3), + ] + ) -TEST_CASE_2 = [ - {"spatial_channels": (1,), "keys": ["img"]}, - {"img": np.random.randint(0, 2, size=(1, 3, 3, 3))}, - (2, 3, 3, 3), -] - -TEST_CASE_ERROR_3 = [{"spatial_channels": (3,), "keys": ["img"]}, {"img": np.random.randint(0, 2, size=(1, 3, 3))}] - -TEST_CASE_ERROR_4 = [{"spatial_channels": (0, 1, 2), "keys": ["img"]}, {"img": np.random.randint(0, 2, size=(1, 3, 3))}] + TEST_CASES_ERROR_1.append( + [{"spatial_channels": (3,), "keys": ["img"]}, {"img": p(np.random.randint(0, 2, size=(1, 3, 3)))}] + ) + TEST_CASES_ERROR_2.append( + [{"spatial_channels": (0, 1, 2), "keys": ["img"]}, {"img": p(np.random.randint(0, 2, size=(1, 3, 3)))}] + ) class TestAddCoordinateChannels(unittest.TestCase): - @parameterized.expand([TEST_CASE_1, TEST_CASE_2]) + @parameterized.expand(TESTS) def test_shape(self, input_param, input, expected_shape): - result = AddCoordinateChannelsd(**input_param)(input) - self.assertEqual(list(result["img"].shape), list(expected_shape)) - np.testing.assert_array_equal(input["img"][0, ...], result["img"][0, ...]) + result = AddCoordinateChannelsd(**input_param)(input)["img"] + input = input["img"] + self.assertEqual(type(result), type(input)) + if isinstance(result, torch.Tensor): + self.assertEqual(result.device, input.device) + self.assertEqual(result.shape, expected_shape) + assert_allclose(input[0, ...], result[0, ...]) - @parameterized.expand([TEST_CASE_ERROR_3]) + @parameterized.expand(TEST_CASES_ERROR_1) def test_max_channel(self, input_param, input): with self.assertRaises(ValueError): AddCoordinateChannelsd(**input_param)(input) - @parameterized.expand([TEST_CASE_ERROR_4]) + @parameterized.expand(TEST_CASES_ERROR_2) def test_channel_dim(self, input_param, input): with self.assertRaises(ValueError): AddCoordinateChannelsd(**input_param)(input) diff --git a/tests/test_add_extreme_points_channel.py b/tests/test_add_extreme_points_channel.py index ecf2c83d3c..06d26dfdfc 100644 --- a/tests/test_add_extreme_points_channel.py +++ b/tests/test_add_extreme_points_channel.py @@ -15,52 +15,63 @@ from parameterized import parameterized from monai.transforms import AddExtremePointsChannel +from tests.utils import TEST_NDARRAYS, assert_allclose IMG_CHANNEL = 3 +TESTS = [] +for p in TEST_NDARRAYS: + for q in TEST_NDARRAYS: + TESTS.append( + [ + { + "img": p(np.zeros((IMG_CHANNEL, 4, 3))), + "label": q(np.array([[[0, 1, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0]]])), + "sigma": 1.0, + "rescale_min": 0.0, + "rescale_max": 1.0, + }, + p( + np.array( + [ + [0.38318458, 0.98615628, 0.85551184], + [0.35422316, 0.94430935, 1.0], + [0.46000731, 0.57319659, 0.46000722], + [0.64577687, 0.38318464, 0.0], + ] + ) + ), + ] + ) -TEST_CASE_1 = [ - { - "img": np.zeros((IMG_CHANNEL, 4, 3)), - "label": np.array([[[0, 1, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0]]]), - "sigma": 1.0, - "rescale_min": 0.0, - "rescale_max": 1.0, - }, - np.array( - [ - [0.38318458, 0.98615628, 0.85551184], - [0.35422316, 0.94430935, 1.0], - [0.46000731, 0.57319659, 0.46000722], - [0.64577687, 0.38318464, 0.0], - ] - ), -] - -TEST_CASE_2 = [ - { - "img": np.zeros((IMG_CHANNEL, 4, 3)), - "label": np.array([[[0, 1, 0], [1, 1, 1], [0, 1, 0], [0, 1, 0]]]), - "sigma": 1.0, - "rescale_min": 0.0, - "rescale_max": 1.0, - }, - np.array( - [ - [0.44628328, 0.80495411, 0.44628328], - [0.6779086, 1.0, 0.67790854], - [0.33002687, 0.62079221, 0.33002687], - [0.0, 0.31848389, 0.0], - ] - ), -] + TESTS.append( + [ + { + "img": p(np.zeros((IMG_CHANNEL, 4, 3))), + "label": q(np.array([[[0, 1, 0], [1, 1, 1], [0, 1, 0], [0, 1, 0]]])), + "sigma": 1.0, + "rescale_min": 0.0, + "rescale_max": 1.0, + }, + p( + np.array( + [ + [0.44628328, 0.80495411, 0.44628328], + [0.6779086, 1.0, 0.67790854], + [0.33002687, 0.62079221, 0.33002687], + [0.0, 0.31848389, 0.0], + ] + ) + ), + ] + ) class TestAddExtremePointsChannel(unittest.TestCase): - @parameterized.expand([TEST_CASE_1, TEST_CASE_2]) + @parameterized.expand(TESTS) def test_correct_results(self, input_data, expected): add_extreme_points_channel = AddExtremePointsChannel() result = add_extreme_points_channel(**input_data) - np.testing.assert_allclose(result[IMG_CHANNEL], expected, rtol=1e-4) + assert_allclose(result[IMG_CHANNEL], expected, rtol=1e-4) if __name__ == "__main__": diff --git a/tests/test_add_extreme_points_channeld.py b/tests/test_add_extreme_points_channeld.py index e33bb0838c..acd0ce69ce 100644 --- a/tests/test_add_extreme_points_channeld.py +++ b/tests/test_add_extreme_points_channeld.py @@ -15,42 +15,60 @@ from parameterized import parameterized from monai.transforms import AddExtremePointsChanneld +from tests.utils import TEST_NDARRAYS, assert_allclose IMG_CHANNEL = 3 -TEST_CASE_1 = [ - {"img": np.zeros((IMG_CHANNEL, 4, 3)), "label": np.array([[[0, 1, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0]]])}, - np.array( - [ - [0.38318458, 0.98615628, 0.85551184], - [0.35422316, 0.94430935, 1.0], - [0.46000731, 0.57319659, 0.46000722], - [0.64577687, 0.38318464, 0.0], - ] - ), -] - -TEST_CASE_2 = [ - {"img": np.zeros((IMG_CHANNEL, 4, 3)), "label": np.array([[[0, 1, 0], [1, 1, 1], [0, 1, 0], [0, 1, 0]]])}, - np.array( - [ - [0.44628328, 0.80495411, 0.44628328], - [0.6779086, 1.0, 0.67790854], - [0.33002687, 0.62079221, 0.33002687], - [0.0, 0.31848389, 0.0], - ] - ), -] +TESTS = [] +for p in TEST_NDARRAYS: + for q in TEST_NDARRAYS: + TESTS.append( + [ + { + "img": p(np.zeros((IMG_CHANNEL, 4, 3))), + "label": q(np.array([[[0, 1, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0]]])), + }, + p( + np.array( + [ + [0.38318458, 0.98615628, 0.85551184], + [0.35422316, 0.94430935, 1.0], + [0.46000731, 0.57319659, 0.46000722], + [0.64577687, 0.38318464, 0.0], + ] + ) + ), + ] + ) + + TESTS.append( + [ + { + "img": p(np.zeros((IMG_CHANNEL, 4, 3))), + "label": q(np.array([[[0, 1, 0], [1, 1, 1], [0, 1, 0], [0, 1, 0]]])), + }, + p( + np.array( + [ + [0.44628328, 0.80495411, 0.44628328], + [0.6779086, 1.0, 0.67790854], + [0.33002687, 0.62079221, 0.33002687], + [0.0, 0.31848389, 0.0], + ] + ) + ), + ] + ) class TestAddExtremePointsChanneld(unittest.TestCase): - @parameterized.expand([TEST_CASE_1, TEST_CASE_2]) + @parameterized.expand(TESTS) def test_correct_results(self, input_data, expected): add_extreme_points_channel = AddExtremePointsChanneld( keys="img", label_key="label", sigma=1.0, rescale_min=0.0, rescale_max=1.0 ) result = add_extreme_points_channel(input_data) - np.testing.assert_allclose(result["img"][IMG_CHANNEL], expected, rtol=1e-4) + assert_allclose(result["img"][IMG_CHANNEL], expected, rtol=1e-4) if __name__ == "__main__": diff --git a/tests/test_get_extreme_points.py b/tests/test_get_extreme_points.py index a334c12415..269cf63cce 100644 --- a/tests/test_get_extreme_points.py +++ b/tests/test_get_extreme_points.py @@ -15,30 +15,37 @@ from parameterized import parameterized from monai.transforms import get_extreme_points - -TEST_CASE_1 = [ - { - "img": np.array([[0, 1, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0]]), - "rand_state": np.random, - "background": 0, - "pert": 0.0, - }, - [(0, 1), (3, 0), (3, 0), (1, 2)], -] - -TEST_CASE_2 = [ - { - "img": np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0], [0, 1, 0]]), - "rand_state": np.random, - "background": 0, - "pert": 0.0, - }, - [(0, 1), (3, 1), (1, 0), (1, 2)], -] +from tests.utils import TEST_NDARRAYS + +TESTS = [] +for p in TEST_NDARRAYS: + TESTS.append( + [ + { + "img": p(np.array([[0, 1, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0]])), + "rand_state": np.random, + "background": 0, + "pert": 0.0, + }, + [(0, 1), (3, 0), (3, 0), (1, 2)], + ] + ) + + TESTS.append( + [ + { + "img": p(np.array([[0, 1, 0], [1, 1, 1], [0, 1, 0], [0, 1, 0]])), + "rand_state": np.random, + "background": 0, + "pert": 0.0, + }, + [(0, 1), (3, 1), (1, 0), (1, 2)], + ] + ) class TestGetExtremePoints(unittest.TestCase): - @parameterized.expand([TEST_CASE_1, TEST_CASE_2]) + @parameterized.expand(TESTS) def test_type_shape(self, input_data, expected): result = get_extreme_points(**input_data) self.assertEqual(result, expected) diff --git a/tests/test_rand_weighted_crop.py b/tests/test_rand_weighted_crop.py index 39a9439122..eb0e4244f7 100644 --- a/tests/test_rand_weighted_crop.py +++ b/tests/test_rand_weighted_crop.py @@ -12,127 +12,159 @@ import unittest import numpy as np +import torch +from parameterized.parameterized import parameterized from monai.transforms.croppad.array import RandWeightedCrop -from tests.utils import NumpyImageTestCase2D, NumpyImageTestCase3D +from tests.utils import TEST_NDARRAYS, NumpyImageTestCase2D, NumpyImageTestCase3D, assert_allclose -class TestRandWeightedCrop2D(NumpyImageTestCase2D): - def test_rand_weighted_crop_small_roi(self): - img = self.seg1[0] - n_samples = 3 - crop = RandWeightedCrop((10, 12), n_samples) - weight = np.zeros_like(img) - weight[0, 30, 17] = 1.1 - weight[0, 40, 31] = 1 - weight[0, 80, 21] = 1 - crop.set_random_state(10) - result = crop(img, weight) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0].shape, (1, 10, 12)) - np.testing.assert_allclose(np.asarray(crop.centers), [[80, 21], [30, 17], [40, 31]]) +def get_data(ndim): + im_gen = NumpyImageTestCase2D() if ndim == 2 else NumpyImageTestCase3D() + im_gen.setUp() + return im_gen.imt[0], im_gen.seg1[0], im_gen.segn[0] + + +IMT_2D, SEG1_2D, SEGN_2D = get_data(ndim=2) +IMT_3D, SEG1_3D, SEGN_3D = get_data(ndim=3) + - def test_rand_weighted_crop_default_roi(self): - img = self.imt[0] - n_samples = 3 - crop = RandWeightedCrop((10, -1), n_samples) - weight = np.zeros_like(img) +TESTS = [] +for p in TEST_NDARRAYS: + for q in TEST_NDARRAYS: + im = SEG1_2D + weight = np.zeros_like(im) weight[0, 30, 17] = 1.1 weight[0, 40, 31] = 1 weight[0, 80, 21] = 1 - crop.set_random_state(10) - result = crop(img, weight) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0].shape, (1, 10, 64)) - np.testing.assert_allclose(np.asarray(crop.centers), [[14, 32], [105, 32], [20, 32]]) - - def test_rand_weighted_crop_large_roi(self): - img = self.segn[0] - n_samples = 3 - crop = RandWeightedCrop((10000, 400), n_samples) - weight = np.zeros_like(img) + TESTS.append( + [ + "small roi 2d", + dict(spatial_size=(10, 12), num_samples=3), + p(im), + q(weight), + (1, 10, 12), + [[80, 21], [30, 17], [40, 31]], + ] + ) + im = IMT_2D + TESTS.append( + [ + "default roi 2d", + dict(spatial_size=(10, -1), num_samples=3), + p(im), + q(weight), + (1, 10, 64), + [[14, 32], [105, 32], [20, 32]], + ] + ) + im = SEGN_2D + weight = np.zeros_like(im) weight[0, 30, 17] = 1.1 weight[0, 10, 1] = 1 - crop.set_random_state(10) - result = crop(img, weight) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0].shape, (1, 128, 64)) - np.testing.assert_allclose(np.asarray(crop.centers), [[64, 32], [64, 32], [64, 32]]) - for res in result: - np.testing.assert_allclose(res, self.segn[0]) - - def test_rand_weighted_crop_bad_w(self): - img = self.imt[0] - n_samples = 3 - crop = RandWeightedCrop((20, 40), n_samples) - weight = np.zeros_like(img) + TESTS.append( + [ + "large roi 2d", + dict(spatial_size=(10000, 400), num_samples=3), + p(im), + q(weight), + (1, 128, 64), + [[64, 32], [64, 32], [64, 32]], + ] + ) + im = IMT_2D + weight = np.zeros_like(im) weight[0, 30, 17] = np.inf weight[0, 10, 1] = -np.inf weight[0, 10, 20] = -np.nan - crop.set_random_state(10) - result = crop(img, weight) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0].shape, (1, 20, 40)) - np.testing.assert_allclose(np.asarray(crop.centers), [[63, 37], [31, 43], [66, 20]]) - - -class TestRandWeightedCrop(NumpyImageTestCase3D): - def test_rand_weighted_crop_small_roi(self): - img = self.seg1[0] - n_samples = 3 - crop = RandWeightedCrop((8, 10, 12), n_samples) - weight = np.zeros_like(img) + TESTS.append( + [ + "bad w 2d", + dict(spatial_size=(20, 40), num_samples=3), + p(im), + q(weight), + (1, 20, 40), + [[63, 37], [31, 43], [66, 20]], + ] + ) + im = SEG1_3D + weight = np.zeros_like(im) weight[0, 5, 30, 17] = 1.1 weight[0, 8, 40, 31] = 1 weight[0, 11, 23, 21] = 1 - crop.set_random_state(10) - result = crop(img, weight) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0].shape, (1, 8, 10, 12)) - np.testing.assert_allclose(np.asarray(crop.centers), [[11, 23, 21], [5, 30, 17], [8, 40, 31]]) - - def test_rand_weighted_crop_default_roi(self): - img = self.imt[0] - n_samples = 3 - crop = RandWeightedCrop((10, -1, -1), n_samples) - weight = np.zeros_like(img) + TESTS.append( + [ + "small roi 3d", + dict(spatial_size=(8, 10, 12), num_samples=3), + p(im), + q(weight), + (1, 8, 10, 12), + [[11, 23, 21], [5, 30, 17], [8, 40, 31]], + ] + ) + im = IMT_3D + weight = np.zeros_like(im) weight[0, 7, 17] = 1.1 weight[0, 13, 31] = 1.1 weight[0, 24, 21] = 1 - crop.set_random_state(10) - result = crop(img, weight) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0].shape, (1, 10, 64, 80)) - np.testing.assert_allclose(np.asarray(crop.centers), [[14, 32, 40], [41, 32, 40], [20, 32, 40]]) - - def test_rand_weighted_crop_large_roi(self): - img = self.segn[0] - n_samples = 3 - crop = RandWeightedCrop((10000, 400, 80), n_samples) - weight = np.zeros_like(img) + TESTS.append( + [ + "default roi 3d", + dict(spatial_size=(10, -1, -1), num_samples=3), + p(im), + q(weight), + (1, 10, 64, 80), + [[14, 32, 40], [41, 32, 40], [20, 32, 40]], + ] + ) + im = SEGN_3D + weight = np.zeros_like(im) weight[0, 30, 17, 20] = 1.1 weight[0, 10, 1, 17] = 1 - crop.set_random_state(10) - result = crop(img, weight) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0].shape, (1, 48, 64, 80)) - np.testing.assert_allclose(np.asarray(crop.centers), [[24, 32, 40], [24, 32, 40], [24, 32, 40]]) - for res in result: - np.testing.assert_allclose(res, self.segn[0]) - - def test_rand_weighted_crop_bad_w(self): - img = self.imt[0] - n_samples = 3 - crop = RandWeightedCrop((48, 64, 80), n_samples) - weight = np.zeros_like(img) + TESTS.append( + [ + "large roi 3d", + dict(spatial_size=(10000, 400, 80), num_samples=3), + p(im), + q(weight), + (1, 48, 64, 80), + [[24, 32, 40], [24, 32, 40], [24, 32, 40]], + ] + ) + im = IMT_3D + weight = np.zeros_like(im) weight[0, 30, 17] = np.inf weight[0, 10, 1] = -np.inf weight[0, 10, 20] = -np.nan + TESTS.append( + [ + "bad w 3d", + dict(spatial_size=(48, 64, 80), num_samples=3), + p(im), + q(weight), + (1, 48, 64, 80), + [[24, 32, 40], [24, 32, 40], [24, 32, 40]], + ] + ) + + +class TestRandWeightedCrop(unittest.TestCase): + @parameterized.expand(TESTS) + def test_rand_weighted_crop(self, _, input_params, img, weight, expected_shape, expected_vals): + crop = RandWeightedCrop(**input_params) crop.set_random_state(10) result = crop(img, weight) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0].shape, (1, 48, 64, 80)) - np.testing.assert_allclose(np.asarray(crop.centers), [[24, 32, 40], [24, 32, 40], [24, 32, 40]]) + self.assertTrue(len(result) == input_params["num_samples"]) + assert_allclose(result[0].shape, expected_shape) + for c, e in zip(crop.centers, expected_vals): + assert_allclose(c, e, type_test=False) + # if desired ROI is larger than image, check image is unchanged + if all(s >= i for i, s in zip(img.shape[1:], input_params["spatial_size"])): + for res in result: + self.assertEqual(type(img), type(res)) + if isinstance(img, torch.Tensor): + self.assertEqual(res.device, img.device) + assert_allclose(res, img) if __name__ == "__main__": diff --git a/tests/test_rand_weighted_cropd.py b/tests/test_rand_weighted_cropd.py index 367ce3beb9..f53238d17c 100644 --- a/tests/test_rand_weighted_cropd.py +++ b/tests/test_rand_weighted_cropd.py @@ -14,148 +14,177 @@ import numpy as np from monai.transforms.croppad.dictionary import RandWeightedCropd -from tests.utils import NumpyImageTestCase2D, NumpyImageTestCase3D +from tests.utils import TEST_NDARRAYS, NumpyImageTestCase2D, NumpyImageTestCase3D, assert_allclose class TestRandWeightedCrop(NumpyImageTestCase2D): def test_rand_weighted_crop_small_roi(self): - img = self.seg1[0] - n_samples = 3 - crop = RandWeightedCropd("img", "w", (10, 12), n_samples) - weight = np.zeros_like(img) - weight[0, 30, 17] = 1.1 - weight[0, 40, 31] = 1 - weight[0, 80, 21] = 1 - crop.set_random_state(10) - d = {"img": img, "w": weight} - result = crop(d) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0]["img"].shape, (1, 10, 12)) - np.testing.assert_allclose(np.asarray(crop.centers), [[80, 21], [30, 17], [40, 31]]) + for p in TEST_NDARRAYS: + for q in TEST_NDARRAYS: + img = self.seg1[0] + n_samples = 3 + crop = RandWeightedCropd("img", "w", (10, 12), n_samples) + weight = np.zeros_like(img) + weight[0, 30, 17] = 1.1 + weight[0, 40, 31] = 1 + weight[0, 80, 21] = 1 + crop.set_random_state(10) + d = {"img": p(img), "w": q(weight)} + result = crop(d) + self.assertTrue(len(result) == n_samples) + np.testing.assert_allclose(result[0]["img"].shape, (1, 10, 12)) + for c, e in zip(crop.centers, [[80, 21], [30, 17], [40, 31]]): + assert_allclose(c, e, type_test=False) def test_rand_weighted_crop_default_roi(self): - img = self.imt[0] - n_samples = 3 - crop = RandWeightedCropd("im", "weight", (10, -1), n_samples, "coords") - weight = np.zeros_like(img) - weight[0, 30, 17] = 1.1 - weight[0, 40, 31] = 1 - weight[0, 80, 21] = 1 - crop.set_random_state(10) - data = {"im": img, "weight": weight, "others": np.nan} - result = crop(data) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0]["im"].shape, (1, 10, 64)) - np.testing.assert_allclose(np.asarray(crop.centers), [[14, 32], [105, 32], [20, 32]]) - np.testing.assert_allclose(result[1]["coords"], [105, 32]) + for p in TEST_NDARRAYS: + for q in TEST_NDARRAYS: + img = self.imt[0] + n_samples = 3 + crop = RandWeightedCropd("im", "weight", (10, -1), n_samples, "coords") + weight = np.zeros_like(img) + weight[0, 30, 17] = 1.1 + weight[0, 40, 31] = 1 + weight[0, 80, 21] = 1 + crop.set_random_state(10) + data = {"im": p(img), "weight": q(weight), "others": np.nan} + result = crop(data) + self.assertTrue(len(result) == n_samples) + np.testing.assert_allclose(result[0]["im"].shape, (1, 10, 64)) + for c, e in zip(crop.centers, [[14, 32], [105, 32], [20, 32]]): + assert_allclose(c, e, type_test=False) + assert_allclose(result[1]["coords"], [105, 32], type_test=False) def test_rand_weighted_crop_large_roi(self): - img = self.segn[0] - n_samples = 3 - crop = RandWeightedCropd(("img", "seg"), "weight", (10000, 400), n_samples, "location") - weight = np.zeros_like(img) - weight[0, 30, 17] = 1.1 - weight[0, 10, 1] = 1 - crop.set_random_state(10) - data = {"img": img, "seg": self.imt[0], "weight": weight} - result = crop(data) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0]["img"].shape, (1, 128, 64)) - np.testing.assert_allclose(result[0]["seg"].shape, (1, 128, 64)) - np.testing.assert_allclose(np.asarray(crop.centers), [[64, 32], [64, 32], [64, 32]]) - np.testing.assert_allclose(result[1]["location"], [64, 32]) + for p in TEST_NDARRAYS: + for q in TEST_NDARRAYS: + img = self.segn[0] + n_samples = 3 + crop = RandWeightedCropd(("img", "seg"), "weight", (10000, 400), n_samples, "location") + weight = np.zeros_like(img) + weight[0, 30, 17] = 1.1 + weight[0, 10, 1] = 1 + crop.set_random_state(10) + data = {"img": p(img), "seg": p(self.imt[0]), "weight": q(weight)} + result = crop(data) + self.assertTrue(len(result) == n_samples) + np.testing.assert_allclose(result[0]["img"].shape, (1, 128, 64)) + np.testing.assert_allclose(result[0]["seg"].shape, (1, 128, 64)) + for c, e in zip(crop.centers, [[64, 32], [64, 32], [64, 32]]): + assert_allclose(c, e, type_test=False) + assert_allclose(result[1]["location"], [64, 32], type_test=False) def test_rand_weighted_crop_bad_w(self): - img = self.imt[0] - n_samples = 3 - crop = RandWeightedCropd(("img", "seg"), "w", (20, 40), n_samples) - weight = np.zeros_like(img) - weight[0, 30, 17] = np.inf - weight[0, 10, 1] = -np.inf - weight[0, 10, 20] = -np.nan - crop.set_random_state(10) - result = crop({"img": img, "seg": self.segn[0], "w": weight}) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0]["img"].shape, (1, 20, 40)) - np.testing.assert_allclose(result[0]["seg"].shape, (1, 20, 40)) - np.testing.assert_allclose(np.asarray(crop.centers), [[63, 37], [31, 43], [66, 20]]) + for p in TEST_NDARRAYS: + for q in TEST_NDARRAYS: + img = self.imt[0] + n_samples = 3 + crop = RandWeightedCropd(("img", "seg"), "w", (20, 40), n_samples) + weight = np.zeros_like(img) + weight[0, 30, 17] = np.inf + weight[0, 10, 1] = -np.inf + weight[0, 10, 20] = -np.nan + crop.set_random_state(10) + result = crop({"img": p(img), "seg": p(self.segn[0]), "w": q(weight)}) + self.assertTrue(len(result) == n_samples) + np.testing.assert_allclose(result[0]["img"].shape, (1, 20, 40)) + np.testing.assert_allclose(result[0]["seg"].shape, (1, 20, 40)) + for c, e in zip(crop.centers, [[63, 37], [31, 43], [66, 20]]): + assert_allclose(c, e, type_test=False) class TestRandWeightedCrop3D(NumpyImageTestCase3D): def test_rand_weighted_crop_small_roi(self): - img = self.seg1[0] - n_samples = 3 - crop = RandWeightedCropd("img", "w", (8, 10, 12), n_samples) - weight = np.zeros_like(img) - weight[0, 5, 30, 17] = 1.1 - weight[0, 8, 40, 31] = 1 - weight[0, 11, 23, 21] = 1 - crop.set_random_state(10) - result = crop({"img": img, "w": weight}) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0]["img"].shape, (1, 8, 10, 12)) - np.testing.assert_allclose(np.asarray(crop.centers), [[11, 23, 21], [5, 30, 17], [8, 40, 31]]) + for p in TEST_NDARRAYS: + for q in TEST_NDARRAYS: + img = self.seg1[0] + n_samples = 3 + crop = RandWeightedCropd("img", "w", (8, 10, 12), n_samples) + weight = np.zeros_like(img) + weight[0, 5, 30, 17] = 1.1 + weight[0, 8, 40, 31] = 1 + weight[0, 11, 23, 21] = 1 + crop.set_random_state(10) + result = crop({"img": p(img), "w": q(weight)}) + self.assertTrue(len(result) == n_samples) + np.testing.assert_allclose(result[0]["img"].shape, (1, 8, 10, 12)) + for c, e in zip(crop.centers, [[11, 23, 21], [5, 30, 17], [8, 40, 31]]): + assert_allclose(c, e, type_test=False) def test_rand_weighted_crop_default_roi(self): - img = self.imt[0] - n_samples = 3 - crop = RandWeightedCropd(("img", "seg"), "w", (10, -1, -1), n_samples) - weight = np.zeros_like(img) - weight[0, 7, 17] = 1.1 - weight[0, 13, 31] = 1.1 - weight[0, 24, 21] = 1 - crop.set_random_state(10) - result = crop({"img": img, "seg": self.segn[0], "w": weight}) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0]["img"].shape, (1, 10, 64, 80)) - np.testing.assert_allclose(result[0]["seg"].shape, (1, 10, 64, 80)) - np.testing.assert_allclose(np.asarray(crop.centers), [[14, 32, 40], [41, 32, 40], [20, 32, 40]]) + for p in TEST_NDARRAYS: + for q in TEST_NDARRAYS: + img = self.imt[0] + n_samples = 3 + crop = RandWeightedCropd(("img", "seg"), "w", (10, -1, -1), n_samples) + weight = np.zeros_like(img) + weight[0, 7, 17] = 1.1 + weight[0, 13, 31] = 1.1 + weight[0, 24, 21] = 1 + crop.set_random_state(10) + result = crop({"img": p(img), "seg": p(self.segn[0]), "w": q(weight)}) + self.assertTrue(len(result) == n_samples) + np.testing.assert_allclose(result[0]["img"].shape, (1, 10, 64, 80)) + np.testing.assert_allclose(result[0]["seg"].shape, (1, 10, 64, 80)) + for c, e in zip(crop.centers, [[14, 32, 40], [41, 32, 40], [20, 32, 40]]): + assert_allclose(c, e, type_test=False) def test_rand_weighted_crop_large_roi(self): - img = self.segn[0] - n_samples = 3 - crop = RandWeightedCropd("img", "w", (10000, 400, 80), n_samples) - weight = np.zeros_like(img) - weight[0, 30, 17, 20] = 1.1 - weight[0, 10, 1, 17] = 1 - crop.set_random_state(10) - result = crop({"img": img, "w": weight}) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0]["img"].shape, (1, 48, 64, 80)) - np.testing.assert_allclose(np.asarray(crop.centers), [[24, 32, 40], [24, 32, 40], [24, 32, 40]]) + for p in TEST_NDARRAYS: + for q in TEST_NDARRAYS: + img = self.segn[0] + n_samples = 3 + crop = RandWeightedCropd("img", "w", (10000, 400, 80), n_samples) + weight = np.zeros_like(img) + weight[0, 30, 17, 20] = 1.1 + weight[0, 10, 1, 17] = 1 + crop.set_random_state(10) + result = crop({"img": p(img), "w": q(weight)}) + self.assertTrue(len(result) == n_samples) + np.testing.assert_allclose(result[0]["img"].shape, (1, 48, 64, 80)) + for c, e in zip(crop.centers, [[24, 32, 40], [24, 32, 40], [24, 32, 40]]): + assert_allclose(c, e, type_test=False) def test_rand_weighted_crop_bad_w(self): - img = self.imt[0] - n_samples = 3 - crop = RandWeightedCropd(("img", "seg"), "w", (48, 64, 80), n_samples) - weight = np.zeros_like(img) - weight[0, 30, 17] = np.inf - weight[0, 10, 1] = -np.inf - weight[0, 10, 20] = -np.nan - crop.set_random_state(10) - result = crop({"img": img, "seg": self.segn[0], "w": weight}) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(result[0]["img"].shape, (1, 48, 64, 80)) - np.testing.assert_allclose(result[0]["seg"].shape, (1, 48, 64, 80)) - np.testing.assert_allclose(np.asarray(crop.centers), [[24, 32, 40], [24, 32, 40], [24, 32, 40]]) + for p in TEST_NDARRAYS: + for q in TEST_NDARRAYS: + img = self.imt[0] + n_samples = 3 + crop = RandWeightedCropd(("img", "seg"), "w", (48, 64, 80), n_samples) + weight = np.zeros_like(img) + weight[0, 30, 17] = np.inf + weight[0, 10, 1] = -np.inf + weight[0, 10, 20] = -np.nan + crop.set_random_state(10) + result = crop({"img": p(img), "seg": p(self.segn[0]), "w": q(weight)}) + self.assertTrue(len(result) == n_samples) + np.testing.assert_allclose(result[0]["img"].shape, (1, 48, 64, 80)) + np.testing.assert_allclose(result[0]["seg"].shape, (1, 48, 64, 80)) + for c, e in zip(crop.centers, [[24, 32, 40], [24, 32, 40], [24, 32, 40]]): + assert_allclose(c, e, type_test=False) def test_rand_weighted_crop_patch_index(self): - img = self.imt[0] - n_samples = 3 - crop = RandWeightedCropd(("img", "seg"), "w", (10, -1, -1), n_samples) - weight = np.zeros_like(img) - weight[0, 7, 17] = 1.1 - weight[0, 13, 31] = 1.1 - weight[0, 24, 21] = 1 - crop.set_random_state(10) - result = crop({"img": img, "seg": self.segn[0], "w": weight, "img_meta_dict": {"affine": None}}) - self.assertTrue(len(result) == n_samples) - np.testing.assert_allclose(np.asarray(crop.centers), [[14, 32, 40], [41, 32, 40], [20, 32, 40]]) - for i in range(n_samples): - np.testing.assert_allclose(result[i]["img"].shape, (1, 10, 64, 80)) - np.testing.assert_allclose(result[i]["seg"].shape, (1, 10, 64, 80)) - np.testing.assert_allclose(result[i]["img_meta_dict"]["patch_index"], i) - np.testing.assert_allclose(result[i]["seg_meta_dict"]["patch_index"], i) + for p in TEST_NDARRAYS: + for q in TEST_NDARRAYS: + img = self.imt[0] + n_samples = 3 + crop = RandWeightedCropd(("img", "seg"), "w", (10, -1, -1), n_samples) + weight = np.zeros_like(img) + weight[0, 7, 17] = 1.1 + weight[0, 13, 31] = 1.1 + weight[0, 24, 21] = 1 + crop.set_random_state(10) + result = crop( + {"img": p(img), "seg": p(self.segn[0]), "w": q(weight), "img_meta_dict": {"affine": None}} + ) + self.assertTrue(len(result) == n_samples) + for c, e in zip(crop.centers, [[14, 32, 40], [41, 32, 40], [20, 32, 40]]): + assert_allclose(c, e, type_test=False) + for i in range(n_samples): + np.testing.assert_allclose(result[i]["img"].shape, (1, 10, 64, 80)) + np.testing.assert_allclose(result[i]["seg"].shape, (1, 10, 64, 80)) + np.testing.assert_allclose(result[i]["img_meta_dict"]["patch_index"], i) + np.testing.assert_allclose(result[i]["seg_meta_dict"]["patch_index"], i) if __name__ == "__main__":