Skip to content

Commit 38403ed

Browse files
authored
cuCIM Transform (#2932)
* Implement CuCIM wrapper transfrom Signed-off-by: Behrooz <3968947+drbeh@users.noreply.github.com>
1 parent 71ebd91 commit 38403ed

File tree

8 files changed

+859
-1
lines changed

8 files changed

+859
-1
lines changed

docs/source/transforms.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,18 @@ Utility
733733
:members:
734734
:special-members: __call__
735735

736+
`CuCIM`
737+
"""""""
738+
.. autoclass:: CuCIM
739+
:members:
740+
:special-members: __call__
741+
742+
`RandCuCIM`
743+
"""""""""""
744+
.. autoclass:: RandCuCIM
745+
:members:
746+
:special-members: __call__
747+
736748

737749
Dictionary Transforms
738750
---------------------
@@ -1374,6 +1386,17 @@ Utility (Dict)
13741386
:members:
13751387
:special-members: __call__
13761388

1389+
`CuCIMd`
1390+
""""""""
1391+
.. autoclass:: CuCIMd
1392+
:members:
1393+
:special-members: __call__
1394+
1395+
`RandCuCIMd`
1396+
""""""""""""
1397+
.. autoclass:: RandCuCIMd
1398+
:members:
1399+
:special-members: __call__
13771400

13781401
Transform Adaptors
13791402
------------------

monai/transforms/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@
359359
CastToType,
360360
ClassesToIndices,
361361
ConvertToMultiChannelBasedOnBratsClasses,
362+
CuCIM,
362363
DataStats,
363364
EnsureChannelFirst,
364365
EnsureType,
@@ -368,6 +369,7 @@
368369
LabelToMask,
369370
Lambda,
370371
MapLabelValue,
372+
RandCuCIM,
371373
RandLambda,
372374
RemoveRepeatedChannel,
373375
RepeatChannel,
@@ -410,6 +412,9 @@
410412
CopyItemsd,
411413
CopyItemsD,
412414
CopyItemsDict,
415+
CuCIMd,
416+
CuCIMD,
417+
CuCIMDict,
413418
DataStatsd,
414419
DataStatsD,
415420
DataStatsDict,
@@ -440,6 +445,9 @@
440445
MapLabelValued,
441446
MapLabelValueD,
442447
MapLabelValueDict,
448+
RandCuCIMd,
449+
RandCuCIMD,
450+
RandCuCIMDict,
443451
RandLambdad,
444452
RandLambdaD,
445453
RandLambdaDict,

monai/transforms/utility/array.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
PILImageImage, has_pil = optional_import("PIL.Image", name="Image")
5050
pil_image_fromarray, _ = optional_import("PIL.Image", name="fromarray")
5151
cp, has_cp = optional_import("cupy")
52-
cp_ndarray, _ = optional_import("cupy", name="ndarray")
52+
5353

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

11501150
return img.to(self.device, **self.kwargs)
1151+
1152+
1153+
class CuCIM(Transform):
1154+
"""
1155+
Wrap a non-randomized cuCIM transform, defined based on the transform name and args.
1156+
For randomized transforms (or randomly applying a transform) use :py:class:`monai.transforms.RandCuCIM`.
1157+
1158+
Args:
1159+
name: the transform name in CuCIM package
1160+
args: parameters for the CuCIM transform
1161+
kwargs: parameters for the CuCIM transform
1162+
1163+
Note:
1164+
CuCIM transform only work with CuPy arrays, so this transform expects input data to be `cupy.ndarray`.
1165+
Users can call `ToCuPy` transform to convert a numpy array or torch tensor to cupy array.
1166+
"""
1167+
1168+
def __init__(self, name: str, *args, **kwargs) -> None:
1169+
super().__init__()
1170+
self.transform, _ = optional_import("cucim.core.operations.expose.transform", name=name)
1171+
self.args = args
1172+
self.kwargs = kwargs
1173+
1174+
def __call__(self, data):
1175+
"""
1176+
Args:
1177+
data: a CuPy array (`cupy.ndarray`) for the cuCIM transform
1178+
1179+
Returns:
1180+
`cupy.ndarray`
1181+
1182+
"""
1183+
return self.transform(data, *self.args, **self.kwargs)
1184+
1185+
1186+
class RandCuCIM(CuCIM, RandomizableTransform):
1187+
"""
1188+
Wrap a randomized cuCIM transform, defined based on the transform name and args,
1189+
or randomly apply a non-randomized transform.
1190+
For deterministic non-randomized transforms use :py:class:`monai.transforms.CuCIM`.
1191+
1192+
Args:
1193+
name: the transform name in CuCIM package.
1194+
apply_prob: the probability to apply the transform (default=1.0)
1195+
args: parameters for the CuCIM transform.
1196+
kwargs: parameters for the CuCIM transform.
1197+
1198+
Note:
1199+
- CuCIM transform only work with CuPy arrays, so this transform expects input data to be `cupy.ndarray`.
1200+
Users can call `ToCuPy` transform to convert a numpy array or torch tensor to cupy array.
1201+
- If the cuCIM transform is already randomized the `apply_prob` argument has nothing to do with
1202+
the randomness of the underlying cuCIM transform. `apply_prob` defines if the transform (either randomized
1203+
or non-randomized) being applied randomly, so it can apply non-randomized tranforms randomly but be careful
1204+
with setting `apply_prob` to anything than 1.0 when using along with cuCIM's randomized transforms.
1205+
- If the random factor of the underlying cuCIM transform is not derived from `self.R`,
1206+
the results may not be deterministic. See Also: :py:class:`monai.transforms.Randomizable`.
1207+
"""
1208+
1209+
def __init__(self, name: str, apply_prob: float = 1.0, *args, **kwargs) -> None:
1210+
CuCIM.__init__(self, name, *args, **kwargs)
1211+
RandomizableTransform.__init__(self, prob=apply_prob)
1212+
1213+
def __call__(self, data):
1214+
"""
1215+
Args:
1216+
data: a CuPy array (`cupy.ndarray`) for the cuCIM transform
1217+
1218+
Returns:
1219+
`cupy.ndarray`
1220+
1221+
"""
1222+
self.randomize(data)
1223+
if not self._do_transform:
1224+
return data
1225+
return super().__call__(data)

monai/transforms/utility/dictionary.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
CastToType,
3737
ClassesToIndices,
3838
ConvertToMultiChannelBasedOnBratsClasses,
39+
CuCIM,
3940
DataStats,
4041
EnsureChannelFirst,
4142
EnsureType,
@@ -87,6 +88,9 @@
8788
"CopyItemsD",
8889
"CopyItemsDict",
8990
"CopyItemsd",
91+
"CuCIMd",
92+
"CuCIMD",
93+
"CuCIMDict",
9094
"DataStatsD",
9195
"DataStatsDict",
9296
"DataStatsd",
@@ -117,6 +121,9 @@
117121
"MapLabelValueD",
118122
"MapLabelValueDict",
119123
"MapLabelValued",
124+
"RandCuCIMd",
125+
"RandCuCIMD",
126+
"RandCuCIMDict",
120127
"RandLambdaD",
121128
"RandLambdaDict",
122129
"RandLambdad",
@@ -1481,6 +1488,99 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> Dict[Hashable, torc
14811488
return d
14821489

14831490

1491+
class CuCIMd(MapTransform):
1492+
"""
1493+
Dictionary-based wrapper of :py:class:`monai.transforms.CuCIM` for non-randomized transforms.
1494+
For randomized transforms of CuCIM use :py:class:`monai.transforms.RandCuCIMd`.
1495+
1496+
Args:
1497+
keys: keys of the corresponding items to be transformed.
1498+
See also: :py:class:`monai.transforms.compose.MapTransform`
1499+
name: The transform name in CuCIM package.
1500+
allow_missing_keys: don't raise exception if key is missing.
1501+
args: parameters for the CuCIM transform.
1502+
kwargs: parameters for the CuCIM transform.
1503+
1504+
Note:
1505+
CuCIM transforms only work with CuPy arrays, this transform expects input data to be `cupy.ndarray`.
1506+
Users can call `ToCuPy` transform to convert a numpy array or torch tensor to cupy array.
1507+
"""
1508+
1509+
def __init__(
1510+
self,
1511+
keys: KeysCollection,
1512+
name: str,
1513+
allow_missing_keys: bool = False,
1514+
*args,
1515+
**kwargs,
1516+
) -> None:
1517+
super().__init__(keys=keys, allow_missing_keys=allow_missing_keys)
1518+
self.trans = CuCIM(name, *args, **kwargs)
1519+
1520+
def __call__(self, data):
1521+
"""
1522+
Args:
1523+
data: Dict[Hashable, `cupy.ndarray`]
1524+
1525+
Returns:
1526+
Dict[Hashable, `cupy.ndarray`]
1527+
1528+
"""
1529+
d = dict(data)
1530+
for key in self.key_iterator(d):
1531+
d[key] = self.trans(d[key])
1532+
return d
1533+
1534+
1535+
class RandCuCIMd(CuCIMd, RandomizableTransform):
1536+
"""
1537+
Dictionary-based wrapper of :py:class:`monai.transforms.CuCIM` for randomized transforms.
1538+
For deterministic non-randomized transforms of CuCIM use :py:class:`monai.transforms.CuCIMd`.
1539+
1540+
Args:
1541+
keys: keys of the corresponding items to be transformed.
1542+
See also: :py:class:`monai.transforms.compose.MapTransform`
1543+
name: The transform name in CuCIM package.
1544+
apply_prob: the probability to apply the transform (default=1.0)
1545+
allow_missing_keys: don't raise exception if key is missing.
1546+
args: parameters for the CuCIM transform.
1547+
kwargs: parameters for the CuCIM transform.
1548+
1549+
Note:
1550+
- CuCIM transform only work with CuPy arrays, so this transform expects input data to be `cupy.ndarray`.
1551+
Users can call `ToCuPy` transform to convert a numpy array or torch tensor to cupy array.
1552+
- If the cuCIM transform is already randomized the `apply_prob` argument has nothing to do with
1553+
the randomness of the underlying cuCIM transform. `apply_prob` defines if the transform (either randomized
1554+
or non-randomized) being applied randomly, so it can apply non-randomized tranforms randomly but be careful
1555+
with setting `apply_prob` to anything than 1.0 when using along with cuCIM's randomized transforms.
1556+
- If the random factor of the underlying cuCIM transform is not derived from `self.R`,
1557+
the results may not be deterministic. See Also: :py:class:`monai.transforms.Randomizable`.
1558+
"""
1559+
1560+
def __init__(
1561+
self,
1562+
apply_prob: float = 1.0,
1563+
*args,
1564+
**kwargs,
1565+
) -> None:
1566+
CuCIMd.__init__(self, *args, **kwargs)
1567+
RandomizableTransform.__init__(self, prob=apply_prob)
1568+
1569+
def __call__(self, data):
1570+
"""
1571+
Args:
1572+
data: Dict[Hashable, `cupy.ndarray`]
1573+
1574+
Returns:
1575+
Dict[Hashable, `cupy.ndarray`]
1576+
1577+
"""
1578+
self.randomize(data)
1579+
if not self._do_transform:
1580+
return dict(data)
1581+
return super().__call__(data)
1582+
1583+
14841584
IdentityD = IdentityDict = Identityd
14851585
AsChannelFirstD = AsChannelFirstDict = AsChannelFirstd
14861586
AsChannelLastD = AsChannelLastDict = AsChannelLastd
@@ -1517,3 +1617,5 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> Dict[Hashable, torc
15171617
MapLabelValueD = MapLabelValueDict = MapLabelValued
15181618
IntensityStatsD = IntensityStatsDict = IntensityStatsd
15191619
ToDeviceD = ToDeviceDict = ToDeviced
1620+
CuCIMD = CuCIMDict = CuCIMd
1621+
RandCuCIMD = RandCuCIMDict = RandCuCIMd

0 commit comments

Comments
 (0)