From e3d5580e7d7da893f48ba0349852f797f4b69978 Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Sun, 12 Dec 2021 20:36:47 +0800 Subject: [PATCH 1/3] [DLMED] support string dtype Signed-off-by: Nic Ma --- monai/config/type_definitions.py | 4 ++-- monai/utils/type_conversion.py | 2 +- tests/test_normalize_intensity.py | 6 +++--- tests/test_to_numpy.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/monai/config/type_definitions.py b/monai/config/type_definitions.py index f5f5fc2626..5341e61596 100644 --- a/monai/config/type_definitions.py +++ b/monai/config/type_definitions.py @@ -60,8 +60,8 @@ # container must be iterable. IndexSelection = Union[Iterable[int], int] -#: Type of datatypes: Adapted from https://github.com/numpy/numpy/blob/master/numpy/typing/_dtype_like.py -DtypeLike = Union[np.dtype, type, None] +#: Type of datatypes: Adapted from https://github.com/numpy/numpy/blob/v1.21.4/numpy/typing/_dtype_like.py#L121 +DtypeLike = Union[np.dtype, type, str, None] #: NdarrayTensor # diff --git a/monai/utils/type_conversion.py b/monai/utils/type_conversion.py index 6a90d21a09..378fa15bc7 100644 --- a/monai/utils/type_conversion.py +++ b/monai/utils/type_conversion.py @@ -59,7 +59,7 @@ def dtype_torch_to_numpy(dtype): def dtype_numpy_to_torch(dtype): """Convert a numpy dtype to its torch equivalent.""" # np dtypes can be given as np.float32 and np.dtype(np.float32) so unify them - dtype = np.dtype(dtype) if isinstance(dtype, type) else dtype + dtype = np.dtype(dtype) if isinstance(dtype, (type, str)) else dtype return look_up_option(dtype, _np_to_torch_dtype) diff --git a/tests/test_normalize_intensity.py b/tests/test_normalize_intensity.py index 41c6b053ec..128949c5ea 100644 --- a/tests/test_normalize_intensity.py +++ b/tests/test_normalize_intensity.py @@ -41,7 +41,7 @@ TESTS.append( [ p, - {"nonzero": False, "channel_wise": True, "subtrahend": [1, 2, 3]}, + {"nonzero": False, "channel_wise": True, "subtrahend": [1, 2, 3], "dtype": np.float32}, p(np.ones((3, 2, 2))), p(np.array([[[0.0, 0.0], [0.0, 0.0]], [[-1.0, -1.0], [-1.0, -1.0]], [[-2.0, -2.0], [-2.0, -2.0]]])), ] @@ -49,7 +49,7 @@ TESTS.append( [ p, - {"nonzero": True, "channel_wise": True, "subtrahend": [1, 2, 3], "divisor": [0, 0, 2]}, + {"nonzero": True, "channel_wise": True, "subtrahend": [1, 2, 3], "divisor": [0, 0, 2], "dtype": "float32"}, p(np.ones((3, 2, 2))), p(np.array([[[0.0, 0.0], [0.0, 0.0]], [[-1.0, -1.0], [-1.0, -1.0]], [[-1.0, -1.0], [-1.0, -1.0]]])), ] @@ -57,7 +57,7 @@ TESTS.append( [ p, - {"nonzero": True, "channel_wise": False, "subtrahend": 2, "divisor": 0}, + {"nonzero": True, "channel_wise": False, "subtrahend": 2, "divisor": 0, "dtype": torch.float32}, p(np.ones((3, 2, 2))), p(np.ones((3, 2, 2)) * -1.0), ] diff --git a/tests/test_to_numpy.py b/tests/test_to_numpy.py index 983e29647c..de9ac3323b 100644 --- a/tests/test_to_numpy.py +++ b/tests/test_to_numpy.py @@ -47,7 +47,7 @@ def test_tensor_input(self): test_data = torch.tensor([[1, 2], [3, 4]]) test_data = test_data.rot90() self.assertFalse(test_data.is_contiguous()) - result = ToNumpy()(test_data) + result = ToNumpy(dtype=torch.unit8)(test_data) self.assertTrue(isinstance(result, np.ndarray)) self.assertTrue(result.flags["C_CONTIGUOUS"]) assert_allclose(result, test_data, type_test=False) @@ -73,7 +73,7 @@ def test_list_tuple(self): def test_single_value(self): for test_data in [5, np.array(5), torch.tensor(5)]: - result = ToNumpy()(test_data) + result = ToNumpy(dtype=np.uint8)(test_data) self.assertTrue(isinstance(result, np.ndarray)) assert_allclose(result, np.asarray(test_data), type_test=False) self.assertEqual(result.ndim, 0) From 8e615692a7cc54a316d1101d726f099db9ce48bc Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Mon, 13 Dec 2021 10:09:33 +0800 Subject: [PATCH 2/3] [DLMED] fix typo Signed-off-by: Nic Ma --- tests/test_to_numpy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_to_numpy.py b/tests/test_to_numpy.py index de9ac3323b..3bf6636d22 100644 --- a/tests/test_to_numpy.py +++ b/tests/test_to_numpy.py @@ -47,7 +47,7 @@ def test_tensor_input(self): test_data = torch.tensor([[1, 2], [3, 4]]) test_data = test_data.rot90() self.assertFalse(test_data.is_contiguous()) - result = ToNumpy(dtype=torch.unit8)(test_data) + result = ToNumpy(dtype=torch.uint8)(test_data) self.assertTrue(isinstance(result, np.ndarray)) self.assertTrue(result.flags["C_CONTIGUOUS"]) assert_allclose(result, test_data, type_test=False) From 421c8c171fe2b2516ca0ab6376d7e0a30f3a7d28 Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Mon, 13 Dec 2021 19:26:54 +0800 Subject: [PATCH 3/3] [DLMED] enhance dtype in ToCupy Signed-off-by: Nic Ma --- monai/transforms/utility/array.py | 4 +++- monai/transforms/utility/dictionary.py | 8 +++++++- monai/utils/type_conversion.py | 20 ++++++++++---------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/monai/transforms/utility/array.py b/monai/transforms/utility/array.py index 84fdc0a78d..dc684f726a 100644 --- a/monai/transforms/utility/array.py +++ b/monai/transforms/utility/array.py @@ -446,6 +446,8 @@ class ToCupy(Transform): Args: dtype: data type specifier. It is inferred from the input by default. + if not None, must be an argument of `numpy.dtype`, for more details: + https://docs.cupy.dev/en/stable/reference/generated/cupy.array.html. wrap_sequence: if `False`, then lists will recursively call this function, default to `True`. E.g., if `False`, `[1, 2]` -> `[array(1), array(2)]`, if `True`, then `[1, 2]` -> `array([1, 2])`. @@ -453,7 +455,7 @@ class ToCupy(Transform): backend = [TransformBackends.TORCH, TransformBackends.NUMPY] - def __init__(self, dtype=None, wrap_sequence: bool = True) -> None: + def __init__(self, dtype: Optional[np.dtype] = None, wrap_sequence: bool = True) -> None: super().__init__() self.dtype = dtype self.wrap_sequence = wrap_sequence diff --git a/monai/transforms/utility/dictionary.py b/monai/transforms/utility/dictionary.py index 082be8dea8..a15d5368fa 100644 --- a/monai/transforms/utility/dictionary.py +++ b/monai/transforms/utility/dictionary.py @@ -592,6 +592,8 @@ class ToCupyd(MapTransform): keys: keys of the corresponding items to be transformed. See also: :py:class:`monai.transforms.compose.MapTransform` dtype: data type specifier. It is inferred from the input by default. + if not None, must be an argument of `numpy.dtype`, for more details: + https://docs.cupy.dev/en/stable/reference/generated/cupy.array.html. wrap_sequence: if `False`, then lists will recursively call this function, default to `True`. E.g., if `False`, `[1, 2]` -> `[array(1), array(2)]`, if `True`, then `[1, 2]` -> `array([1, 2])`. allow_missing_keys: don't raise exception if key is missing. @@ -600,7 +602,11 @@ class ToCupyd(MapTransform): backend = ToCupy.backend def __init__( - self, keys: KeysCollection, dtype=None, wrap_sequence: bool = True, allow_missing_keys: bool = False + self, + keys: KeysCollection, + dtype: Optional[np.dtype] = None, + wrap_sequence: bool = True, + allow_missing_keys: bool = False, ) -> None: super().__init__(keys, allow_missing_keys) self.converter = ToCupy(dtype=dtype, wrap_sequence=wrap_sequence) diff --git a/monai/utils/type_conversion.py b/monai/utils/type_conversion.py index 378fa15bc7..1dfcc1499a 100644 --- a/monai/utils/type_conversion.py +++ b/monai/utils/type_conversion.py @@ -151,8 +151,8 @@ def convert_to_numpy(data, dtype: DtypeLike = None, wrap_sequence: bool = False) will convert Tensor, Numpy array, float, int, bool to numpy arrays, strings and objects keep the original. for dictionary, list or tuple, convert every item to a numpy array if applicable. dtype: target data type when converting to numpy array. - wrap_sequence: if `False`, then lists will recursively call this function. E.g., `[1, 2]` -> `[array(1), array(2)]`. - If `True`, then `[1, 2]` -> `array([1, 2])`. + wrap_sequence: if `False`, then lists will recursively call this function. + E.g., `[1, 2]` -> `[array(1), array(2)]`. If `True`, then `[1, 2]` -> `array([1, 2])`. """ if isinstance(data, torch.Tensor): data = data.detach().to(dtype=get_equivalent_dtype(dtype, torch.Tensor), device="cpu").numpy() @@ -175,19 +175,19 @@ def convert_to_numpy(data, dtype: DtypeLike = None, wrap_sequence: bool = False) return data -def convert_to_cupy(data, dtype, wrap_sequence: bool = False): +def convert_to_cupy(data, dtype: Optional[np.dtype] = None, wrap_sequence: bool = False): """ Utility to convert the input data to a cupy array. If passing a dictionary, list or tuple, recursively check every item and convert it to cupy array. Args: data: input data can be PyTorch Tensor, numpy array, cupy array, list, dictionary, int, float, bool, str, etc. - Tensor, numpy array, cupy array, float, int, bool are converted to cupy arrays - + Tensor, numpy array, cupy array, float, int, bool are converted to cupy arrays, for dictionary, list or tuple, convert every item to a numpy array if applicable. - dtype: target data type when converting to Cupy array. - wrap_sequence: if `False`, then lists will recursively call this function. E.g., `[1, 2]` -> `[array(1), array(2)]`. - If `True`, then `[1, 2]` -> `array([1, 2])`. + dtype: target data type when converting to Cupy array, tt must be an argument of `numpy.dtype`, + for more details: https://docs.cupy.dev/en/stable/reference/generated/cupy.array.html. + wrap_sequence: if `False`, then lists will recursively call this function. + E.g., `[1, 2]` -> `[array(1), array(2)]`. If `True`, then `[1, 2]` -> `array([1, 2])`. """ # direct calls @@ -227,8 +227,8 @@ def convert_data_type( dtype: dtype of output data. Converted to correct library type (e.g., `np.float32` is converted to `torch.float32` if output type is `torch.Tensor`). If left blank, it remains unchanged. - wrap_sequence: if `False`, then lists will recursively call this function. E.g., `[1, 2]` -> `[array(1), array(2)]`. - If `True`, then `[1, 2]` -> `array([1, 2])`. + wrap_sequence: if `False`, then lists will recursively call this function. + E.g., `[1, 2]` -> `[array(1), array(2)]`. If `True`, then `[1, 2]` -> `array([1, 2])`. Returns: modified data, orig_type, orig_device