Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
beab17d
backends -> backend
rijobro Aug 25, 2021
c3192ba
code format
rijobro Aug 25, 2021
f790ad7
code format2
rijobro Aug 25, 2021
1d28704
AddChannel, AsChannelFirst, AsChannelLast, EnsureChannelFirst, Identi…
rijobro Aug 25, 2021
fe0c787
Merge remote-tracking branch 'MONAI/dev' into utility_transforms
rijobro Aug 25, 2021
be7eac2
moveaxis backwards compatible
rijobro Aug 25, 2021
0d27527
code format
rijobro Aug 25, 2021
69ff653
Merge branch 'dev' into utility_transforms
wyli Aug 25, 2021
95622bc
Merge branch 'dev' into utility_transforms
wyli Aug 26, 2021
c9dcd8d
EnsureType, RemoveRepeatedChannel, SplitChannel, ToCupy, ToNumpy, ToP…
rijobro Aug 26, 2021
754a684
trigger ci/cd
rijobro Aug 26, 2021
e38e7a3
permute requires positive indices
rijobro Aug 26, 2021
0a1b4a5
Merge branch 'utility_transforms' into utility_transforms2
rijobro Aug 26, 2021
24c136d
Merge branch 'dev' into utility_transforms
wyli Aug 26, 2021
2971cdb
Merge branch 'dev' into utility_transforms
rijobro Aug 27, 2021
af2d2ec
correct permute
rijobro Aug 27, 2021
b1e476d
correct permute
rijobro Aug 27, 2021
dce485e
Merge branch 'utility_transforms' into utility_transforms2
rijobro Aug 27, 2021
baecdf8
Merge branch 'dev' into utility_transforms
rijobro Aug 27, 2021
4c05342
Merge remote-tracking branch 'rijobro/utility_transforms' into utilit…
rijobro Aug 27, 2021
3a9e170
has_pil
rijobro Aug 27, 2021
6363721
Merge branch 'dev' into utility_transforms2
rijobro Aug 27, 2021
162e7d5
DataStats, LabelToMask, Lambda, RandLambda, SqueezeDim, is_module_ver…
rijobro Aug 27, 2021
2cd4b99
cumpy->cupy
rijobro Aug 27, 2021
e17e778
Merge remote-tracking branch 'MONAI/dev' into utility_transforms2
rijobro Aug 27, 2021
c24c697
Merge branch 'utility_transforms2' into utility_transforms3
rijobro Aug 27, 2021
85c2be5
code format
rijobro Aug 27, 2021
994b36a
Merge branch 'utility_transforms2' into utility_transforms3
rijobro Aug 27, 2021
dede72b
Merge branch 'dev' into utility_transforms3
wyli Aug 30, 2021
c548b1d
fixes unit tests
wyli Aug 30, 2021
452fd8c
Merge branch 'dev' into utility_transforms3
wyli Aug 30, 2021
c6e7235
style fixes
wyli Aug 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion monai/transforms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,4 +518,4 @@
weighted_patch_samples,
zero_margins,
)
from .utils_pytorch_numpy_unification import moveaxis
from .utils_pytorch_numpy_unification import in1d, moveaxis
86 changes: 60 additions & 26 deletions monai/transforms/utility/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import numpy as np
import torch

from monai.config import DtypeLike, NdarrayTensor
from monai.config import DtypeLike
from monai.config.type_definitions import NdarrayOrTensor
from monai.transforms.transform import Randomizable, RandomizableTransform, Transform
from monai.transforms.utils import (
Expand All @@ -31,9 +31,10 @@
map_binary_to_indices,
map_classes_to_indices,
)
from monai.transforms.utils_pytorch_numpy_unification import moveaxis
from monai.transforms.utils_pytorch_numpy_unification import in1d, moveaxis
from monai.utils import convert_to_numpy, convert_to_tensor, ensure_tuple, look_up_option, min_version, optional_import
from monai.utils.enums import TransformBackends
from monai.utils.misc import is_module_ver_at_least
from monai.utils.type_conversion import convert_data_type

PILImageImage, has_pil = optional_import("PIL.Image", name="Image")
Expand Down Expand Up @@ -445,6 +446,8 @@ class SqueezeDim(Transform):
Squeeze a unitary dimension.
"""

backend = [TransformBackends.TORCH, TransformBackends.NUMPY]

def __init__(self, dim: Optional[int] = 0) -> None:
"""
Args:
Expand All @@ -459,12 +462,17 @@ def __init__(self, dim: Optional[int] = 0) -> None:
raise TypeError(f"dim must be None or a int but is {type(dim).__name__}.")
self.dim = dim

def __call__(self, img: NdarrayTensor) -> NdarrayTensor:
def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor:
"""
Args:
img: numpy arrays with required dimension `dim` removed
"""
return img.squeeze(self.dim) # type: ignore
if self.dim is None:
return img.squeeze()
# for pytorch/numpy unification
if img.shape[self.dim] != 1:
raise ValueError("Can only squeeze singleton dimension")
return img.squeeze(self.dim)


class DataStats(Transform):
Expand All @@ -475,6 +483,8 @@ class DataStats(Transform):
so it can be used in pre-processing and post-processing.
"""

backend = [TransformBackends.TORCH, TransformBackends.NUMPY]

def __init__(
self,
prefix: str = "Data",
Expand Down Expand Up @@ -523,14 +533,14 @@ def __init__(

def __call__(
self,
img: NdarrayTensor,
img: NdarrayOrTensor,
prefix: Optional[str] = None,
data_type: Optional[bool] = None,
data_shape: Optional[bool] = None,
value_range: Optional[bool] = None,
data_value: Optional[bool] = None,
additional_info: Optional[Callable] = None,
) -> NdarrayTensor:
) -> NdarrayOrTensor:
"""
Apply the transform to `img`, optionally take arguments similar to the class constructor.
"""
Expand Down Expand Up @@ -570,6 +580,8 @@ class SimulateDelay(Transform):
to sub-optimal design choices.
"""

backend = [TransformBackends.TORCH, TransformBackends.NUMPY]

def __init__(self, delay_time: float = 0.0) -> None:
"""
Args:
Expand All @@ -579,7 +591,7 @@ def __init__(self, delay_time: float = 0.0) -> None:
super().__init__()
self.delay_time: float = delay_time

def __call__(self, img: NdarrayTensor, delay_time: Optional[float] = None) -> NdarrayTensor:
def __call__(self, img: NdarrayOrTensor, delay_time: Optional[float] = None) -> NdarrayOrTensor:
"""
Args:
img: data remain unchanged throughout this transform.
Expand Down Expand Up @@ -612,12 +624,14 @@ class Lambda(Transform):

"""

backend = [TransformBackends.TORCH, TransformBackends.NUMPY]

def __init__(self, func: Optional[Callable] = None) -> None:
if func is not None and not callable(func):
raise TypeError(f"func must be None or callable but is {type(func).__name__}.")
self.func = func

def __call__(self, img: Union[np.ndarray, torch.Tensor], func: Optional[Callable] = None):
def __call__(self, img: NdarrayOrTensor, func: Optional[Callable] = None):
"""
Apply `self.func` to `img`.

Expand Down Expand Up @@ -648,14 +662,15 @@ class RandLambda(Lambda, RandomizableTransform):
prob: probability of executing the random function, default to 1.0, with 100% probability to execute.

For more details, please check :py:class:`monai.transforms.Lambda`.

"""

backend = Lambda.backend

def __init__(self, func: Optional[Callable] = None, prob: float = 1.0) -> None:
Lambda.__init__(self=self, func=func)
RandomizableTransform.__init__(self=self, prob=prob)

def __call__(self, img: Union[np.ndarray, torch.Tensor], func: Optional[Callable] = None):
def __call__(self, img: NdarrayOrTensor, func: Optional[Callable] = None):
self.randomize(img)
return super().__call__(img=img, func=func) if self._do_transform else img

Expand All @@ -679,6 +694,8 @@ class LabelToMask(Transform):

"""

backend = [TransformBackends.TORCH, TransformBackends.NUMPY]

def __init__( # pytype: disable=annotation-type-mismatch
self,
select_labels: Union[Sequence[int], int],
Expand All @@ -688,8 +705,11 @@ def __init__( # pytype: disable=annotation-type-mismatch
self.merge_channels = merge_channels

def __call__(
self, img: np.ndarray, select_labels: Optional[Union[Sequence[int], int]] = None, merge_channels: bool = False
):
self,
img: NdarrayOrTensor,
select_labels: Optional[Union[Sequence[int], int]] = None,
merge_channels: bool = False,
) -> NdarrayOrTensor:
"""
Args:
select_labels: labels to generate mask from. for 1 channel label, the `select_labels`
Expand All @@ -706,26 +726,40 @@ def __call__(
if img.shape[0] > 1:
data = img[[*select_labels]]
else:
data = np.where(np.in1d(img, select_labels), True, False).reshape(img.shape)
where = np.where if isinstance(img, np.ndarray) else torch.where
if isinstance(img, np.ndarray) or is_module_ver_at_least(torch, (1, 8, 0)):
data = where(in1d(img, select_labels), True, False).reshape(img.shape)
# pre pytorch 1.8.0, need to use 1/0 instead of True/False
else:
data = where(
in1d(img, select_labels), torch.tensor(1, device=img.device), torch.tensor(0, device=img.device)
).reshape(img.shape)

return np.any(data, axis=0, keepdims=True) if (merge_channels or self.merge_channels) else data
if merge_channels or self.merge_channels:
if isinstance(img, np.ndarray) or is_module_ver_at_least(torch, (1, 8, 0)):
return data.any(0)[None]
# pre pytorch 1.8.0 compatibility
return data.to(torch.uint8).any(0)[None].to(bool) # type: ignore

return data


class FgBgToIndices(Transform):
def __init__(self, image_threshold: float = 0.0, output_shape: Optional[Sequence[int]] = None) -> None:
"""
Compute foreground and background of the input label data, return the indices.
If no output_shape specified, output data will be 1 dim indices after flattening.
This transform can help pre-compute foreground and background regions for other transforms.
A typical usage is to randomly select foreground and background to crop.
The main logic is based on :py:class:`monai.transforms.utils.map_binary_to_indices`.
"""
Compute foreground and background of the input label data, return the indices.
If no output_shape specified, output data will be 1 dim indices after flattening.
This transform can help pre-compute foreground and background regions for other transforms.
A typical usage is to randomly select foreground and background to crop.
The main logic is based on :py:class:`monai.transforms.utils.map_binary_to_indices`.

Args:
image_threshold: if enabled `image` at runtime, use ``image > image_threshold`` to
determine the valid image content area and select background only in this area.
output_shape: expected shape of output indices. if not None, unravel indices to specified shape.
Args:
image_threshold: if enabled `image` at runtime, use ``image > image_threshold`` to
determine the valid image content area and select background only in this area.
output_shape: expected shape of output indices. if not None, unravel indices to specified shape.

"""
"""

def __init__(self, image_threshold: float = 0.0, output_shape: Optional[Sequence[int]] = None) -> None:
self.image_threshold = image_threshold
self.output_shape = output_shape

Expand Down
Loading