Skip to content

Commit

Permalink
cuCIM Transform (Project-MONAI#2932)
Browse files Browse the repository at this point in the history
* Implement CuCIM wrapper transfrom

Signed-off-by: Behrooz <[email protected]>
  • Loading branch information
bhashemian authored Sep 16, 2021
1 parent 71ebd91 commit 38403ed
Show file tree
Hide file tree
Showing 8 changed files with 859 additions and 1 deletion.
23 changes: 23 additions & 0 deletions docs/source/transforms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,18 @@ Utility
:members:
:special-members: __call__

`CuCIM`
"""""""
.. autoclass:: CuCIM
:members:
:special-members: __call__

`RandCuCIM`
"""""""""""
.. autoclass:: RandCuCIM
:members:
:special-members: __call__


Dictionary Transforms
---------------------
Expand Down Expand Up @@ -1374,6 +1386,17 @@ Utility (Dict)
:members:
:special-members: __call__

`CuCIMd`
""""""""
.. autoclass:: CuCIMd
:members:
:special-members: __call__

`RandCuCIMd`
""""""""""""
.. autoclass:: RandCuCIMd
:members:
:special-members: __call__

Transform Adaptors
------------------
Expand Down
8 changes: 8 additions & 0 deletions monai/transforms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@
CastToType,
ClassesToIndices,
ConvertToMultiChannelBasedOnBratsClasses,
CuCIM,
DataStats,
EnsureChannelFirst,
EnsureType,
Expand All @@ -368,6 +369,7 @@
LabelToMask,
Lambda,
MapLabelValue,
RandCuCIM,
RandLambda,
RemoveRepeatedChannel,
RepeatChannel,
Expand Down Expand Up @@ -410,6 +412,9 @@
CopyItemsd,
CopyItemsD,
CopyItemsDict,
CuCIMd,
CuCIMD,
CuCIMDict,
DataStatsd,
DataStatsD,
DataStatsDict,
Expand Down Expand Up @@ -440,6 +445,9 @@
MapLabelValued,
MapLabelValueD,
MapLabelValueDict,
RandCuCIMd,
RandCuCIMD,
RandCuCIMDict,
RandLambdad,
RandLambdaD,
RandLambdaDict,
Expand Down
77 changes: 76 additions & 1 deletion monai/transforms/utility/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
PILImageImage, has_pil = optional_import("PIL.Image", name="Image")
pil_image_fromarray, _ = optional_import("PIL.Image", name="fromarray")
cp, has_cp = optional_import("cupy")
cp_ndarray, _ = optional_import("cupy", name="ndarray")


__all__ = [
"Identity",
Expand Down Expand Up @@ -1148,3 +1148,78 @@ def __call__(self, img: torch.Tensor):
raise ValueError("img must be PyTorch Tensor, consider converting img by `EnsureType` transform first.")

return img.to(self.device, **self.kwargs)


class CuCIM(Transform):
"""
Wrap a non-randomized cuCIM transform, defined based on the transform name and args.
For randomized transforms (or randomly applying a transform) use :py:class:`monai.transforms.RandCuCIM`.
Args:
name: the transform name in CuCIM package
args: parameters for the CuCIM transform
kwargs: parameters for the CuCIM transform
Note:
CuCIM transform only work with CuPy arrays, so this transform expects input data to be `cupy.ndarray`.
Users can call `ToCuPy` transform to convert a numpy array or torch tensor to cupy array.
"""

def __init__(self, name: str, *args, **kwargs) -> None:
super().__init__()
self.transform, _ = optional_import("cucim.core.operations.expose.transform", name=name)
self.args = args
self.kwargs = kwargs

def __call__(self, data):
"""
Args:
data: a CuPy array (`cupy.ndarray`) for the cuCIM transform
Returns:
`cupy.ndarray`
"""
return self.transform(data, *self.args, **self.kwargs)


class RandCuCIM(CuCIM, RandomizableTransform):
"""
Wrap a randomized cuCIM transform, defined based on the transform name and args,
or randomly apply a non-randomized transform.
For deterministic non-randomized transforms use :py:class:`monai.transforms.CuCIM`.
Args:
name: the transform name in CuCIM package.
apply_prob: the probability to apply the transform (default=1.0)
args: parameters for the CuCIM transform.
kwargs: parameters for the CuCIM transform.
Note:
- CuCIM transform only work with CuPy arrays, so this transform expects input data to be `cupy.ndarray`.
Users can call `ToCuPy` transform to convert a numpy array or torch tensor to cupy array.
- If the cuCIM transform is already randomized the `apply_prob` argument has nothing to do with
the randomness of the underlying cuCIM transform. `apply_prob` defines if the transform (either randomized
or non-randomized) being applied randomly, so it can apply non-randomized tranforms randomly but be careful
with setting `apply_prob` to anything than 1.0 when using along with cuCIM's randomized transforms.
- If the random factor of the underlying cuCIM transform is not derived from `self.R`,
the results may not be deterministic. See Also: :py:class:`monai.transforms.Randomizable`.
"""

def __init__(self, name: str, apply_prob: float = 1.0, *args, **kwargs) -> None:
CuCIM.__init__(self, name, *args, **kwargs)
RandomizableTransform.__init__(self, prob=apply_prob)

def __call__(self, data):
"""
Args:
data: a CuPy array (`cupy.ndarray`) for the cuCIM transform
Returns:
`cupy.ndarray`
"""
self.randomize(data)
if not self._do_transform:
return data
return super().__call__(data)
102 changes: 102 additions & 0 deletions monai/transforms/utility/dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
CastToType,
ClassesToIndices,
ConvertToMultiChannelBasedOnBratsClasses,
CuCIM,
DataStats,
EnsureChannelFirst,
EnsureType,
Expand Down Expand Up @@ -87,6 +88,9 @@
"CopyItemsD",
"CopyItemsDict",
"CopyItemsd",
"CuCIMd",
"CuCIMD",
"CuCIMDict",
"DataStatsD",
"DataStatsDict",
"DataStatsd",
Expand Down Expand Up @@ -117,6 +121,9 @@
"MapLabelValueD",
"MapLabelValueDict",
"MapLabelValued",
"RandCuCIMd",
"RandCuCIMD",
"RandCuCIMDict",
"RandLambdaD",
"RandLambdaDict",
"RandLambdad",
Expand Down Expand Up @@ -1481,6 +1488,99 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> Dict[Hashable, torc
return d


class CuCIMd(MapTransform):
"""
Dictionary-based wrapper of :py:class:`monai.transforms.CuCIM` for non-randomized transforms.
For randomized transforms of CuCIM use :py:class:`monai.transforms.RandCuCIMd`.
Args:
keys: keys of the corresponding items to be transformed.
See also: :py:class:`monai.transforms.compose.MapTransform`
name: The transform name in CuCIM package.
allow_missing_keys: don't raise exception if key is missing.
args: parameters for the CuCIM transform.
kwargs: parameters for the CuCIM transform.
Note:
CuCIM transforms only work with CuPy arrays, this transform expects input data to be `cupy.ndarray`.
Users can call `ToCuPy` transform to convert a numpy array or torch tensor to cupy array.
"""

def __init__(
self,
keys: KeysCollection,
name: str,
allow_missing_keys: bool = False,
*args,
**kwargs,
) -> None:
super().__init__(keys=keys, allow_missing_keys=allow_missing_keys)
self.trans = CuCIM(name, *args, **kwargs)

def __call__(self, data):
"""
Args:
data: Dict[Hashable, `cupy.ndarray`]
Returns:
Dict[Hashable, `cupy.ndarray`]
"""
d = dict(data)
for key in self.key_iterator(d):
d[key] = self.trans(d[key])
return d


class RandCuCIMd(CuCIMd, RandomizableTransform):
"""
Dictionary-based wrapper of :py:class:`monai.transforms.CuCIM` for randomized transforms.
For deterministic non-randomized transforms of CuCIM use :py:class:`monai.transforms.CuCIMd`.
Args:
keys: keys of the corresponding items to be transformed.
See also: :py:class:`monai.transforms.compose.MapTransform`
name: The transform name in CuCIM package.
apply_prob: the probability to apply the transform (default=1.0)
allow_missing_keys: don't raise exception if key is missing.
args: parameters for the CuCIM transform.
kwargs: parameters for the CuCIM transform.
Note:
- CuCIM transform only work with CuPy arrays, so this transform expects input data to be `cupy.ndarray`.
Users can call `ToCuPy` transform to convert a numpy array or torch tensor to cupy array.
- If the cuCIM transform is already randomized the `apply_prob` argument has nothing to do with
the randomness of the underlying cuCIM transform. `apply_prob` defines if the transform (either randomized
or non-randomized) being applied randomly, so it can apply non-randomized tranforms randomly but be careful
with setting `apply_prob` to anything than 1.0 when using along with cuCIM's randomized transforms.
- If the random factor of the underlying cuCIM transform is not derived from `self.R`,
the results may not be deterministic. See Also: :py:class:`monai.transforms.Randomizable`.
"""

def __init__(
self,
apply_prob: float = 1.0,
*args,
**kwargs,
) -> None:
CuCIMd.__init__(self, *args, **kwargs)
RandomizableTransform.__init__(self, prob=apply_prob)

def __call__(self, data):
"""
Args:
data: Dict[Hashable, `cupy.ndarray`]
Returns:
Dict[Hashable, `cupy.ndarray`]
"""
self.randomize(data)
if not self._do_transform:
return dict(data)
return super().__call__(data)


IdentityD = IdentityDict = Identityd
AsChannelFirstD = AsChannelFirstDict = AsChannelFirstd
AsChannelLastD = AsChannelLastDict = AsChannelLastd
Expand Down Expand Up @@ -1517,3 +1617,5 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> Dict[Hashable, torc
MapLabelValueD = MapLabelValueDict = MapLabelValued
IntensityStatsD = IntensityStatsDict = IntensityStatsd
ToDeviceD = ToDeviceDict = ToDeviced
CuCIMD = CuCIMDict = CuCIMd
RandCuCIMD = RandCuCIMDict = RandCuCIMd
Loading

0 comments on commit 38403ed

Please sign in to comment.