Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
bcd7674
remove deprecated AsChannelFirst and AsChannelFirstd
bhashemian Apr 3, 2023
2b881e2
Merge branch 'dev' into remove-dep-0.8
bhashemian May 15, 2023
b79971d
Merge branch 'dev' of github.com:Project-MONAI/MONAI into remove-dep-0.8
bhashemian May 19, 2023
326d391
Merge branch 'remove-dep-0.8' of github.com:drbeh/MONAI into remove-d…
bhashemian May 19, 2023
60745fb
Merge branch 'dev' into remove-dep-0.8
bhashemian May 19, 2023
76ea0a0
Merge branch 'remove-dep-0.8' of github.com:drbeh/MONAI into remove-d…
bhashemian May 19, 2023
8768438
Merge branch 'dev' into remove-dep-0.8
bhashemian May 30, 2023
3061115
Merge branch 'remove-dep-0.8' of github.com:drbeh/MONAI into remove-d…
bhashemian May 30, 2023
6f459eb
sort imports
bhashemian May 30, 2023
bf1ef3f
add aliases
bhashemian Jun 5, 2023
4ae77f6
Merge branch 'dev' of github.com:Project-MONAI/MONAI into remove-dep-0.8
bhashemian Jun 5, 2023
5fd308c
replace aliases with wrappers
bhashemian Jun 5, 2023
b090411
update docs
bhashemian Jun 5, 2023
e19639a
clean up imports
bhashemian Jun 5, 2023
4d2f7c6
add removed version for AddChannel and AddChanneld
bhashemian Jun 5, 2023
de5659d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 5, 2023
1f6533d
replace AddChannel
bhashemian Jun 5, 2023
36b45d5
remove usage of AddChannel
bhashemian Jun 5, 2023
93253a9
Merge branch 'remove-dep-0.8' of github.com:drbeh/MONAI into remove-d…
bhashemian Jun 5, 2023
655bbd4
add removed version to SplitChannel
bhashemian Jun 5, 2023
5151cd9
add channel_dim
bhashemian Jun 6, 2023
0fc662d
few fixes
bhashemian Jun 6, 2023
b13e466
Merge branch 'dev' into remove-dep-0.8
bhashemian Jun 7, 2023
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
6 changes: 2 additions & 4 deletions monai/transforms/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,7 @@ def __call__(self, data: Any):

#. string data without shape, `LoadImage` transform expects file paths,
#. most of the pre-/post-processing transforms expect: ``(num_channels, spatial_dim_1[, spatial_dim_2, ...])``,
except for example: `AddChannel` expects (spatial_dim_1[, spatial_dim_2, ...]) and
`AsChannelFirst` expects (spatial_dim_1[, spatial_dim_2, ...], num_channels),
except for example: `AddChannel` expects (spatial_dim_1[, spatial_dim_2, ...])

- the channel dimension is often not omitted even if number of channels is one.

Expand Down Expand Up @@ -441,8 +440,7 @@ def __call__(self, data):

#. string data without shape, `LoadImaged` transform expects file paths,
#. most of the pre-/post-processing transforms expect: ``(num_channels, spatial_dim_1[, spatial_dim_2, ...])``,
except for example: `AddChanneld` expects (spatial_dim_1[, spatial_dim_2, ...]) and
`AsChannelFirstd` expects (spatial_dim_1[, spatial_dim_2, ...], num_channels)
except for example: `AddChanneld` expects (spatial_dim_1[, spatial_dim_2, ...])

- the channel dimension is often not omitted even if number of channels is one.

Expand Down
107 changes: 49 additions & 58 deletions monai/transforms/utility/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,38 +142,6 @@ def __call__(self, data: Any) -> Any:
return data


@deprecated(since="0.8", msg_suffix="please use MetaTensor data type and monai.transforms.EnsureChannelFirst instead.")
class AsChannelFirst(Transform):
"""
Change the channel dimension of the image to the first dimension.

Most of the image transformations in ``monai.transforms``
assume the input image is in the channel-first format, which has the shape
(num_channels, spatial_dim_1[, spatial_dim_2, ...]).

This transform could be used to convert, for example, a channel-last image array in shape
(spatial_dim_1[, spatial_dim_2, ...], num_channels) into the channel-first format,
so that the multidimensional image array can be correctly interpreted by the other transforms.

Args:
channel_dim: which dimension of input image is the channel, default is the last dimension.
"""

backend = [TransformBackends.TORCH, TransformBackends.NUMPY]

def __init__(self, channel_dim: int = -1) -> None:
if not (isinstance(channel_dim, int) and channel_dim >= -1):
raise ValueError(f"invalid channel dimension ({channel_dim}).")
self.channel_dim = channel_dim

def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor:
"""
Apply the transform to `img`.
"""
out: NdarrayOrTensor = convert_to_tensor(moveaxis(img, self.channel_dim, 0), track_meta=get_track_meta())
return out


class AsChannelLast(Transform):
"""
Change the channel dimension of the image to the last dimension.
Expand Down Expand Up @@ -204,31 +172,6 @@ def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor:
return out


@deprecated(since="0.8", msg_suffix="please use MetaTensor data type and monai.transforms.EnsureChannelFirst instead.")
class AddChannel(Transform):
"""
Adds a 1-length channel dimension to the input image.

Most of the image transformations in ``monai.transforms``
assumes the input image is in the channel-first format, which has the shape
(num_channels, spatial_dim_1[, spatial_dim_2, ...]).

This transform could be used, for example, to convert a (spatial_dim_1[, spatial_dim_2, ...])
spatial image into the channel-first format so that the
multidimensional image array can be correctly interpreted by the other
transforms.
"""

backend = [TransformBackends.TORCH, TransformBackends.NUMPY]

def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor:
"""
Apply the transform to `img`.
"""
out: NdarrayOrTensor = convert_to_tensor(img[None], track_meta=get_track_meta())
return out


class EnsureChannelFirst(Transform):
"""
Adjust or add the channel dimension of input data to ensure `channel_first` shape.
Expand Down Expand Up @@ -291,6 +234,54 @@ def __call__(self, img: torch.Tensor, meta_dict: Mapping | None = None) -> torch
return convert_to_tensor(result, track_meta=get_track_meta()) # type: ignore


@deprecated(
since="0.8",
removed="1.3",
msg_suffix="please use MetaTensor data type and monai.transforms.EnsureChannelFirst instead.",
)
class AsChannelFirst(EnsureChannelFirst):
"""
Change the channel dimension of the image to the first dimension.
Most of the image transformations in ``monai.transforms``
assume the input image is in the channel-first format, which has the shape
(num_channels, spatial_dim_1[, spatial_dim_2, ...]).
This transform could be used to convert, for example, a channel-last image array in shape
(spatial_dim_1[, spatial_dim_2, ...], num_channels) into the channel-first format,
so that the multidimensional image array can be correctly interpreted by the other transforms.
Args:
channel_dim: which dimension of input image is the channel, default is the last dimension.
"""

def __init__(self, channel_dim: int = -1) -> None:
super().__init__(channel_dim=channel_dim)


@deprecated(
since="0.8",
removed="1.3",
msg_suffix="please use MetaTensor data type and monai.transforms.EnsureChannelFirst instead"
" with `channel_dim='no_channel'`.",
)
class AddChannel(EnsureChannelFirst):
"""
Adds a 1-length channel dimension to the input image.

Most of the image transformations in ``monai.transforms``
assumes the input image is in the channel-first format, which has the shape
(num_channels, spatial_dim_1[, spatial_dim_2, ...]).

This transform could be used, for example, to convert a (spatial_dim_1[, spatial_dim_2, ...])
spatial image into the channel-first format so that the
multidimensional image array can be correctly interpreted by the other
transforms.
"""

backend = [TransformBackends.TORCH, TransformBackends.NUMPY]

def __init__(self) -> None:
super().__init__(channel_dim="no_channel")


class RepeatChannel(Transform):
"""
Repeat channel data to construct expected input shape for models.
Expand Down Expand Up @@ -391,7 +382,7 @@ def __call__(self, img: torch.Tensor) -> list[torch.Tensor]:
return outputs


@deprecated(since="0.8", msg_suffix="please use `SplitDim` instead.")
@deprecated(since="0.8", removed="1.3", msg_suffix="please use `SplitDim` instead.")
class SplitChannel(SplitDim):
"""
Split Numpy array or PyTorch Tensor data according to the channel dim.
Expand Down
97 changes: 45 additions & 52 deletions monai/transforms/utility/dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,8 @@
from monai.transforms.traits import MultiSampleTrait, RandomizableTrait
from monai.transforms.transform import MapTransform, Randomizable, RandomizableTransform
from monai.transforms.utility.array import (
AddChannel,
AddCoordinateChannels,
AddExtremePointsChannel,
AsChannelFirst,
AsChannelLast,
CastToType,
ClassesToIndices,
Expand Down Expand Up @@ -221,31 +219,6 @@ def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> dict[Hashable, N
return d


class AsChannelFirstd(MapTransform):
"""
Dictionary-based wrapper of :py:class:`monai.transforms.AsChannelFirst`.
"""

backend = AsChannelFirst.backend

def __init__(self, keys: KeysCollection, channel_dim: int = -1, allow_missing_keys: bool = False) -> None:
"""
Args:
keys: keys of the corresponding items to be transformed.
See also: :py:class:`monai.transforms.compose.MapTransform`
channel_dim: which dimension of input image is the channel, default is the last dimension.
allow_missing_keys: don't raise exception if key is missing.
"""
super().__init__(keys, allow_missing_keys)
self.converter = AsChannelFirst(channel_dim=channel_dim)

def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> dict[Hashable, NdarrayOrTensor]:
d = dict(data)
for key in self.key_iterator(d):
d[key] = self.converter(d[key])
return d


class AsChannelLastd(MapTransform):
"""
Dictionary-based wrapper of :py:class:`monai.transforms.AsChannelLast`.
Expand All @@ -271,30 +244,6 @@ def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> dict[Hashable, N
return d


class AddChanneld(MapTransform):
"""
Dictionary-based wrapper of :py:class:`monai.transforms.AddChannel`.
"""

backend = AddChannel.backend

def __init__(self, keys: KeysCollection, allow_missing_keys: bool = False) -> None:
"""
Args:
keys: keys of the corresponding items to be transformed.
See also: :py:class:`monai.transforms.compose.MapTransform`
allow_missing_keys: don't raise exception if key is missing.
"""
super().__init__(keys, allow_missing_keys)
self.adder = AddChannel()

def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> dict[Hashable, NdarrayOrTensor]:
d = dict(data)
for key in self.key_iterator(d):
d[key] = self.adder(d[key])
return d


class EnsureChannelFirstd(MapTransform):
"""
Dictionary-based wrapper of :py:class:`monai.transforms.EnsureChannelFirst`.
Expand Down Expand Up @@ -336,6 +285,50 @@ def __call__(self, data: Mapping[Hashable, torch.Tensor]) -> dict[Hashable, torc
return d


@deprecated(
since="0.8",
removed="1.3",
msg_suffix="please use MetaTensor data type and monai.transforms.EnsureChannelFirstd instead.",
)
class AsChannelFirstd(EnsureChannelFirstd):
"""
Dictionary-based wrapper of :py:class:`monai.transforms.AsChannelFirst`.
"""

def __init__(self, keys: KeysCollection, channel_dim: int = -1, allow_missing_keys: bool = False) -> None:
"""
Args:
keys: keys of the corresponding items to be transformed.
See also: :py:class:`monai.transforms.compose.MapTransform`
channel_dim: which dimension of input image is the channel, default is the last dimension.
allow_missing_keys: don't raise exception if key is missing.
"""
super().__init__(keys=keys, channel_dim=channel_dim, allow_missing_keys=allow_missing_keys)


@deprecated(
since="0.8",
removed="1.3",
msg_suffix="please use MetaTensor data type and monai.transforms.EnsureChannelFirstd instead"
" with `channel_dim='no_channel'`.",
)
class AddChanneld(EnsureChannelFirstd):
"""
Dictionary-based wrapper of :py:class:`monai.transforms.AddChannel`.
"""

backend = EnsureChannelFirstd.backend

def __init__(self, keys: KeysCollection, allow_missing_keys: bool = False) -> None:
"""
Args:
keys: keys of the corresponding items to be transformed.
See also: :py:class:`monai.transforms.compose.MapTransform`
allow_missing_keys: don't raise exception if key is missing.
"""
super().__init__(keys, allow_missing_keys, channel_dim="no_channel")


class RepeatChanneld(MapTransform):
"""
Dictionary-based wrapper of :py:class:`monai.transforms.RepeatChannel`.
Expand Down Expand Up @@ -452,7 +445,7 @@ def __call__(
return d


@deprecated(since="0.8", msg_suffix="please use `SplitDimd` instead.")
@deprecated(since="0.8", removed="1.3", msg_suffix="please use `SplitDimd` instead.")
class SplitChanneld(SplitDimd):
"""
Dictionary-based wrapper of :py:class:`monai.transforms.SplitChannel`.
Expand Down
33 changes: 25 additions & 8 deletions tests/test_arraydataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@
from torch.utils.data import DataLoader

from monai.data import ArrayDataset
from monai.transforms import AddChannel, Compose, LoadImage, RandAdjustContrast, RandGaussianNoise, Spacing
from monai.transforms import Compose, EnsureChannelFirst, LoadImage, RandAdjustContrast, RandGaussianNoise, Spacing

TEST_CASE_1 = [
Compose([LoadImage(image_only=True), AddChannel(), RandGaussianNoise(prob=1.0)]),
Compose([LoadImage(image_only=True), AddChannel(), RandGaussianNoise(prob=1.0)]),
Compose([LoadImage(image_only=True), EnsureChannelFirst(channel_dim="no_channel"), RandGaussianNoise(prob=1.0)]),
Compose([LoadImage(image_only=True), EnsureChannelFirst(channel_dim="no_channel"), RandGaussianNoise(prob=1.0)]),
(0, 1),
(1, 128, 128, 128),
]

TEST_CASE_2 = [
Compose([LoadImage(image_only=True), AddChannel(), RandAdjustContrast(prob=1.0)]),
Compose([LoadImage(image_only=True), AddChannel(), RandAdjustContrast(prob=1.0)]),
Compose([LoadImage(image_only=True), EnsureChannelFirst(channel_dim="no_channel"), RandAdjustContrast(prob=1.0)]),
Compose([LoadImage(image_only=True), EnsureChannelFirst(channel_dim="no_channel"), RandAdjustContrast(prob=1.0)]),
(0, 1),
(1, 128, 128, 128),
]
Expand All @@ -50,13 +50,30 @@ def __call__(self, input_):


TEST_CASE_3 = [
TestCompose([LoadImage(image_only=True), AddChannel(), Spacing(pixdim=(2, 2, 4)), RandAdjustContrast(prob=1.0)]),
TestCompose([LoadImage(image_only=True), AddChannel(), Spacing(pixdim=(2, 2, 4)), RandAdjustContrast(prob=1.0)]),
TestCompose(
[
LoadImage(image_only=True),
EnsureChannelFirst(channel_dim="no_channel"),
Spacing(pixdim=(2, 2, 4)),
RandAdjustContrast(prob=1.0),
]
),
TestCompose(
[
LoadImage(image_only=True),
EnsureChannelFirst(channel_dim="no_channel"),
Spacing(pixdim=(2, 2, 4)),
RandAdjustContrast(prob=1.0),
]
),
(0, 2),
(1, 64, 64, 33),
]

TEST_CASE_4 = [Compose([LoadImage(image_only=True), AddChannel(), RandGaussianNoise(prob=1.0)]), (1, 128, 128, 128)]
TEST_CASE_4 = [
Compose([LoadImage(image_only=True), EnsureChannelFirst(channel_dim="no_channel"), RandGaussianNoise(prob=1.0)]),
(1, 128, 128, 128),
]


class TestArrayDataset(unittest.TestCase):
Expand Down
6 changes: 3 additions & 3 deletions tests/test_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ def __call__(self, data):
c.randomize()

def test_err_msg(self):
transforms = mt.Compose([abs, mt.AddChannel(), round])
with self.assertRaisesRegex(Exception, "AddChannel"):
transforms = mt.Compose([abs, mt.EnsureChannelFirst(), round])
with self.assertRaisesRegex(Exception, "EnsureChannelFirst"):
transforms(42.1)

def test_data_loader(self):
Expand Down Expand Up @@ -244,7 +244,7 @@ def test_data_loader_2(self):
set_determinism(None)

def test_flatten_and_len(self):
x = mt.AddChannel()
x = mt.EnsureChannelFirst(channel_dim="no_channel")
t1 = mt.Compose([x, x, x, x, mt.Compose([mt.Compose([x, x]), x, x])])

t2 = t1.flatten()
Expand Down
8 changes: 6 additions & 2 deletions tests/test_cross_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from monai.apps import CrossValidation, DecathlonDataset
from monai.data import MetaTensor
from monai.transforms import AddChanneld, Compose, LoadImaged, ScaleIntensityd
from monai.transforms import Compose, EnsureChannelFirstd, LoadImaged, ScaleIntensityd
from tests.utils import skip_if_downloading_fails, skip_if_quick


Expand All @@ -25,7 +25,11 @@ class TestCrossValidation(unittest.TestCase):
def test_values(self):
testing_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "testing_data")
train_transform = Compose(
[LoadImaged(keys=["image", "label"]), AddChanneld(keys=["image", "label"]), ScaleIntensityd(keys="image")]
[
LoadImaged(keys=["image", "label"]),
EnsureChannelFirstd(keys=["image", "label"], channel_dim="no_channel"),
ScaleIntensityd(keys="image"),
]
)
val_transform = LoadImaged(keys=["image", "label"])

Expand Down
Loading