Repository: Project-MONAI/MONAI

Issue:
Ensure usage of anything deprecated since 0.8 is removed
There are several components that have been deprecated since v0.8 and many of them has been removed including all pathology related classes (addressed in PR #6262); however, there are still classes that have been used vastly throughout test cases, tutorials and bundles, so removing them requires some coordination with other monai repos and modifying the code that are utilizing them before removing them.

[UPDATE] The usage of below classes will be removed but they will be kept as a wrapper with an associated remove version of 1.3.

- [x] AsChannelFirst and AsChannelFirstd
- [x] AddChannel and AddChanneld
- [x] SplitChannel and SplitChannels



Discussion:

Resolving patch:
diff --git a/monai/transforms/transform.py b/monai/transforms/transform.py
--- a/monai/transforms/transform.py
+++ b/monai/transforms/transform.py
@@ -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.
 
@@ -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.
 
diff --git a/monai/transforms/utility/array.py b/monai/transforms/utility/array.py
--- a/monai/transforms/utility/array.py
+++ b/monai/transforms/utility/array.py
@@ -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.
@@ -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.
@@ -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.
@@ -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.
diff --git a/monai/transforms/utility/dictionary.py b/monai/transforms/utility/dictionary.py
--- a/monai/transforms/utility/dictionary.py
+++ b/monai/transforms/utility/dictionary.py
@@ -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,
@@ -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`.
@@ -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`.
@@ -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`.
@@ -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`.


Test patch:
diff --git a/tests/test_arraydataset.py b/tests/test_arraydataset.py
--- a/tests/test_arraydataset.py
+++ b/tests/test_arraydataset.py
@@ -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),
 ]
@@ -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):
diff --git a/tests/test_compose.py b/tests/test_compose.py
--- a/tests/test_compose.py
+++ b/tests/test_compose.py
@@ -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):
@@ -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()
diff --git a/tests/test_cross_validation.py b/tests/test_cross_validation.py
--- a/tests/test_cross_validation.py
+++ b/tests/test_cross_validation.py
@@ -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
 
 
@@ -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"])
 
diff --git a/tests/test_decathlondataset.py b/tests/test_decathlondataset.py
--- a/tests/test_decathlondataset.py
+++ b/tests/test_decathlondataset.py
@@ -18,7 +18,7 @@
 
 from monai.apps import 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
 
 
@@ -27,7 +27,11 @@ class TestDecathlonDataset(unittest.TestCase):
     def test_values(self):
         testing_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "testing_data")
         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"),
+            ]
         )
 
         def _test_dataset(dataset):
diff --git a/tests/test_decollate.py b/tests/test_decollate.py
--- a/tests/test_decollate.py
+++ b/tests/test_decollate.py
@@ -22,9 +22,9 @@
 from monai.data import CacheDataset, DataLoader, Dataset, create_test_image_2d
 from monai.data.utils import decollate_batch
 from monai.transforms import (
-    AddChannel,
-    AddChanneld,
     Compose,
+    EnsureChannelFirst,
+    EnsureChannelFirstd,
     LoadImage,
     LoadImaged,
     RandAffine,
@@ -129,7 +129,7 @@ def check_decollate(self, dataset):
 
     @parameterized.expand(TESTS_DICT)
     def test_decollation_dict(self, *transforms):
-        t_compose = Compose([AddChanneld(KEYS), Compose(transforms), ToTensord(KEYS)])
+        t_compose = Compose([EnsureChannelFirstd(KEYS, channel_dim="no_channel"), Compose(transforms), ToTensord(KEYS)])
         # If nibabel present, read from disk
         if has_nib:
             t_compose = Compose([LoadImaged("image", image_only=True), t_compose])
@@ -139,7 +139,7 @@ def test_decollation_dict(self, *transforms):
 
     @parameterized.expand(TESTS_LIST)
     def test_decollation_tensor(self, *transforms):
-        t_compose = Compose([AddChannel(), Compose(transforms), ToTensor()])
+        t_compose = Compose([EnsureChannelFirst(channel_dim="no_channel"), Compose(transforms), ToTensor()])
         # If nibabel present, read from disk
         if has_nib:
             t_compose = Compose([LoadImage(image_only=True), t_compose])
@@ -149,7 +149,7 @@ def test_decollation_tensor(self, *transforms):
 
     @parameterized.expand(TESTS_LIST)
     def test_decollation_list(self, *transforms):
-        t_compose = Compose([AddChannel(), Compose(transforms), ToTensor()])
+        t_compose = Compose([EnsureChannelFirst(channel_dim="no_channel"), Compose(transforms), ToTensor()])
         # If nibabel present, read from disk
         if has_nib:
             t_compose = Compose([LoadImage(image_only=True), t_compose])
diff --git a/tests/test_integration_classification_2d.py b/tests/test_integration_classification_2d.py
--- a/tests/test_integration_classification_2d.py
+++ b/tests/test_integration_classification_2d.py
@@ -27,9 +27,9 @@
 from monai.networks.nets import DenseNet121
 from monai.transforms import (
     Activations,
-    AddChannel,
     AsDiscrete,
     Compose,
+    EnsureChannelFirst,
     LoadImage,
     RandFlip,
     RandRotate,
@@ -63,7 +63,7 @@ def run_training_test(root_dir, train_x, train_y, val_x, val_y, device="cuda:0",
     train_transforms = Compose(
         [
             LoadImage(image_only=True, simple_keys=True),
-            AddChannel(),
+            EnsureChannelFirst(channel_dim="no_channel"),
             Transpose(indices=[0, 2, 1]),
             ScaleIntensity(),
             RandRotate(range_x=np.pi / 12, prob=0.5, keep_size=True, dtype=np.float64),
@@ -73,7 +73,12 @@ def run_training_test(root_dir, train_x, train_y, val_x, val_y, device="cuda:0",
     )
     train_transforms.set_random_state(1234)
     val_transforms = Compose(
-        [LoadImage(image_only=True, simple_keys=True), AddChannel(), Transpose(indices=[0, 2, 1]), ScaleIntensity()]
+        [
+            LoadImage(image_only=True, simple_keys=True),
+            EnsureChannelFirst(channel_dim="no_channel"),
+            Transpose(indices=[0, 2, 1]),
+            ScaleIntensity(),
+        ]
     )
     y_pred_trans = Compose([Activations(softmax=True)])
     y_trans = AsDiscrete(to_onehot=len(np.unique(train_y)))
@@ -152,7 +157,9 @@ def run_training_test(root_dir, train_x, train_y, val_x, val_y, device="cuda:0",
 
 def run_inference_test(root_dir, test_x, test_y, device="cuda:0", num_workers=10):
     # define transforms for image and classification
-    val_transforms = Compose([LoadImage(image_only=True), AddChannel(), ScaleIntensity()])
+    val_transforms = Compose(
+        [LoadImage(image_only=True), EnsureChannelFirst(channel_dim="no_channel"), ScaleIntensity()]
+    )
     val_ds = MedNISTDataset(test_x, test_y, val_transforms)
     val_loader = DataLoader(val_ds, batch_size=300, num_workers=num_workers)
 
diff --git a/tests/test_integration_determinism.py b/tests/test_integration_determinism.py
--- a/tests/test_integration_determinism.py
+++ b/tests/test_integration_determinism.py
@@ -20,7 +20,7 @@
 from monai.data import create_test_image_2d
 from monai.losses import DiceLoss
 from monai.networks.nets import UNet
-from monai.transforms import AddChannel, Compose, RandRotate90, RandSpatialCrop, ScaleIntensity
+from monai.transforms import Compose, EnsureChannelFirst, RandRotate90, RandSpatialCrop, ScaleIntensity
 from monai.utils import set_determinism
 from tests.utils import DistTestCase, TimedCall
 
@@ -49,7 +49,12 @@ def __len__(self):
     loss = DiceLoss(sigmoid=True)
     opt = torch.optim.Adam(net.parameters(), 1e-2)
     train_transforms = Compose(
-        [AddChannel(), ScaleIntensity(), RandSpatialCrop((96, 96), random_size=False), RandRotate90()]
+        [
+            EnsureChannelFirst(channel_dim="no_channel"),
+            ScaleIntensity(),
+            RandSpatialCrop((96, 96), random_size=False),
+            RandRotate90(),
+        ]
     )
 
     src = DataLoader(_TestBatch(train_transforms), batch_size=batch_size, shuffle=True)
diff --git a/tests/test_integration_sliding_window.py b/tests/test_integration_sliding_window.py
--- a/tests/test_integration_sliding_window.py
+++ b/tests/test_integration_sliding_window.py
@@ -25,13 +25,19 @@
 from monai.inferers import sliding_window_inference
 from monai.networks import eval_mode, predict_segmentation
 from monai.networks.nets import UNet
-from monai.transforms import AddChannel, SaveImage
+from monai.transforms import EnsureChannelFirst, SaveImage
 from monai.utils import pytorch_after, set_determinism
 from tests.utils import DistTestCase, TimedCall, make_nifti_image, skip_if_quick
 
 
 def run_test(batch_size, img_name, seg_name, output_dir, device="cuda:0"):
-    ds = ImageDataset([img_name], [seg_name], transform=AddChannel(), seg_transform=AddChannel(), image_only=True)
+    ds = ImageDataset(
+        [img_name],
+        [seg_name],
+        transform=EnsureChannelFirst(channel_dim="no_channel"),
+        seg_transform=EnsureChannelFirst(channel_dim="no_channel"),
+        image_only=True,
+    )
     loader = DataLoader(ds, batch_size=1, pin_memory=torch.cuda.is_available())
 
     net = UNet(
diff --git a/tests/test_integration_workflows.py b/tests/test_integration_workflows.py
--- a/tests/test_integration_workflows.py
+++ b/tests/test_integration_workflows.py
@@ -41,9 +41,9 @@
 from monai.inferers import SimpleInferer, SlidingWindowInferer
 from monai.transforms import (
     Activationsd,
-    AsChannelFirstd,
     AsDiscreted,
     Compose,
+    EnsureChannelFirstd,
     KeepLargestConnectedComponentd,
     LoadImaged,
     RandCropByPosNegLabeld,
@@ -71,7 +71,7 @@ def run_training_test(root_dir, device="cuda:0", amp=False, num_workers=4):
     train_transforms = Compose(
         [
             LoadImaged(keys=["image", "label"]),
-            AsChannelFirstd(keys=["image", "label"], channel_dim=-1),
+            EnsureChannelFirstd(keys=["image", "label"], channel_dim=-1),
             ScaleIntensityd(keys=["image", "label"]),
             RandCropByPosNegLabeld(
                 keys=["image", "label"], label_key="label", spatial_size=[96, 96, 96], pos=1, neg=1, num_samples=4
@@ -82,7 +82,7 @@ def run_training_test(root_dir, device="cuda:0", amp=False, num_workers=4):
     val_transforms = Compose(
         [
             LoadImaged(keys=["image", "label"]),
-            AsChannelFirstd(keys=["image", "label"], channel_dim=-1),
+            EnsureChannelFirstd(keys=["image", "label"], channel_dim=-1),
             ScaleIntensityd(keys=["image", "label"]),
         ]
     )
@@ -224,7 +224,7 @@ def run_inference_test(root_dir, model_file, device="cuda:0", amp=False, num_wor
     val_transforms = Compose(
         [
             LoadImaged(keys=["image", "label"]),
-            AsChannelFirstd(keys=["image", "label"], channel_dim=-1),
+            EnsureChannelFirstd(keys=["image", "label"], channel_dim=-1),
             ScaleIntensityd(keys=["image", "label"]),
         ]
     )
diff --git a/tests/test_integration_workflows_gan.py b/tests/test_integration_workflows_gan.py
--- a/tests/test_integration_workflows_gan.py
+++ b/tests/test_integration_workflows_gan.py
@@ -27,7 +27,7 @@
 from monai.handlers import CheckpointSaver, StatsHandler, TensorBoardStatsHandler
 from monai.networks import normal_init
 from monai.networks.nets import Discriminator, Generator
-from monai.transforms import AsChannelFirstd, Compose, LoadImaged, RandFlipd, ScaleIntensityd
+from monai.transforms import Compose, EnsureChannelFirstd, LoadImaged, RandFlipd, ScaleIntensityd
 from monai.utils import GanKeys as Keys
 from monai.utils import set_determinism
 from tests.utils import DistTestCase, TimedCall, skip_if_quick
@@ -41,7 +41,7 @@ def run_training_test(root_dir, device="cuda:0"):
     train_transforms = Compose(
         [
             LoadImaged(keys=["reals"]),
-            AsChannelFirstd(keys=["reals"]),
+            EnsureChannelFirstd(keys=["reals"]),
             ScaleIntensityd(keys=["reals"]),
             RandFlipd(keys=["reals"], prob=0.5),
         ]
diff --git a/tests/test_inverse.py b/tests/test_inverse.py
--- a/tests/test_inverse.py
+++ b/tests/test_inverse.py
@@ -26,7 +26,6 @@
 from monai.data import CacheDataset, DataLoader, MetaTensor, create_test_image_2d, create_test_image_3d, decollate_batch
 from monai.networks.nets import UNet
 from monai.transforms import (
-    AddChanneld,
     Affined,
     BorderPadd,
     CenterScaleCropd,
@@ -34,6 +33,7 @@
     Compose,
     CropForegroundd,
     DivisiblePadd,
+    EnsureChannelFirstd,
     Flipd,
     FromMetaTensord,
     InvertibleTransform,
@@ -388,7 +388,9 @@ def setUp(self):
         im_2d_fname, seg_2d_fname = (make_nifti_image(i) for i in create_test_image_2d(101, 100))
         im_3d_fname, seg_3d_fname = (make_nifti_image(i, affine) for i in create_test_image_3d(100, 101, 107))
 
-        load_ims = Compose([LoadImaged(KEYS), AddChanneld(KEYS), FromMetaTensord(KEYS)])
+        load_ims = Compose(
+            [LoadImaged(KEYS), EnsureChannelFirstd(KEYS, channel_dim="no_channel"), FromMetaTensord(KEYS)]
+        )
         self.all_data["2D"] = load_ims({"image": im_2d_fname, "label": seg_2d_fname})
         self.all_data["3D"] = load_ims({"image": im_3d_fname, "label": seg_3d_fname})
 
@@ -471,7 +473,9 @@ def test_inverse_inferred_seg(self, extra_transform):
         batch_size = 10
         # num workers = 0 for mac
         num_workers = 2 if sys.platform == "linux" else 0
-        transforms = Compose([AddChanneld(KEYS), SpatialPadd(KEYS, (150, 153)), extra_transform])
+        transforms = Compose(
+            [EnsureChannelFirstd(KEYS, channel_dim="no_channel"), SpatialPadd(KEYS, (150, 153)), extra_transform]
+        )
 
         dataset = CacheDataset(test_data, transform=transforms, progress=False)
         loader = DataLoader(dataset, batch_size=batch_size, shuffle=False, num_workers=num_workers)
diff --git a/tests/test_inverse_array.py b/tests/test_inverse_array.py
--- a/tests/test_inverse_array.py
+++ b/tests/test_inverse_array.py
@@ -17,7 +17,7 @@
 from parameterized import parameterized
 
 from monai.data import MetaTensor
-from monai.transforms import AddChannel, Compose, Flip, Orientation, Spacing
+from monai.transforms import Compose, EnsureChannelFirst, Flip, Orientation, Spacing
 from monai.transforms.inverse import InvertibleTransform
 from monai.utils import optional_import
 from tests.utils import TEST_DEVICES
@@ -42,7 +42,14 @@ def get_image(dtype, device) -> MetaTensor:
     @parameterized.expand(TESTS)
     def test_inverse_array(self, use_compose: bool, dtype: torch.dtype, device: torch.device):
         img: MetaTensor
-        tr = Compose([AddChannel(), Orientation("RAS"), Flip(1), Spacing([1.0, 1.2, 0.9], align_corners=False)])
+        tr = Compose(
+            [
+                EnsureChannelFirst(channel_dim="no_channel"),
+                Orientation("RAS"),
+                Flip(1),
+                Spacing([1.0, 1.2, 0.9], align_corners=False),
+            ]
+        )
         num_invertible = len([i for i in tr.transforms if isinstance(i, InvertibleTransform)])
 
         # forward
diff --git a/tests/test_inverse_collation.py b/tests/test_inverse_collation.py
--- a/tests/test_inverse_collation.py
+++ b/tests/test_inverse_collation.py
@@ -29,8 +29,8 @@
     pad_list_data_collate,
 )
 from monai.transforms import (
-    AddChanneld,
     Compose,
+    EnsureChannelFirstd,
     Flipd,
     LoadImaged,
     RandAffined,
@@ -98,12 +98,12 @@ def setUp(self):
 
         b_size = 11
         im_fname, seg_fname = (make_nifti_image(i) for i in create_test_image_3d(101, 100, 107))
-        load_ims = Compose([LoadImaged(KEYS), AddChanneld(KEYS)])
+        load_ims = Compose([LoadImaged(KEYS), EnsureChannelFirstd(KEYS, channel_dim="no_channel")])
         self.data_3d = [load_ims({"image": im_fname, "label": seg_fname}) for _ in range(b_size)]
 
         b_size = 8
         im_fname, seg_fname = (make_nifti_image(i) for i in create_test_image_2d(62, 37, rad_max=10))
-        load_ims = Compose([LoadImaged(KEYS), AddChanneld(KEYS)])
+        load_ims = Compose([LoadImaged(KEYS), EnsureChannelFirstd(KEYS, channel_dim="no_channel")])
         self.data_2d = [load_ims({"image": im_fname, "label": seg_fname}) for _ in range(b_size)]
 
         self.batch_size = 7
diff --git a/tests/test_load_spacing_orientation.py b/tests/test_load_spacing_orientation.py
--- a/tests/test_load_spacing_orientation.py
+++ b/tests/test_load_spacing_orientation.py
@@ -21,7 +21,7 @@
 from nibabel.processing import resample_to_output
 from parameterized import parameterized
 
-from monai.transforms import AddChanneld, Compose, LoadImaged, Orientationd, Spacingd
+from monai.transforms import Compose, EnsureChannelFirstd, LoadImaged, Orientationd, Spacingd
 
 FILES = tuple(
     os.path.join(os.path.dirname(__file__), "testing_data", filename)
@@ -33,7 +33,7 @@ class TestLoadSpacingOrientation(unittest.TestCase):
     @staticmethod
     def load_image(filename):
         data = {"image": filename}
-        t = Compose([LoadImaged(keys="image"), AddChanneld(keys="image")])
+        t = Compose([LoadImaged(keys="image"), EnsureChannelFirstd(keys="image", channel_dim="no_channel")])
         return t(data)
 
     @parameterized.expand(FILES)
diff --git a/tests/test_lr_finder.py b/tests/test_lr_finder.py
--- a/tests/test_lr_finder.py
+++ b/tests/test_lr_finder.py
@@ -24,7 +24,7 @@
 from monai.apps import MedNISTDataset
 from monai.networks.nets import DenseNet
 from monai.optimizers import LearningRateFinder
-from monai.transforms import AddChanneld, Compose, LoadImaged, ScaleIntensityd, ToTensord
+from monai.transforms import Compose, EnsureChannelFirstd, LoadImaged, ScaleIntensityd, ToTensord
 from monai.utils import optional_import, set_determinism
 from monai.utils.misc import MONAIEnvVars
 from tests.utils import skip_if_downloading_fails
@@ -56,7 +56,7 @@ def setUp(self):
         self.transforms = Compose(
             [
                 LoadImaged(keys="image"),
-                AddChanneld(keys="image"),
+                EnsureChannelFirstd(keys="image", channel_dim="no_channel"),
                 ScaleIntensityd(keys="image"),
                 ToTensord(keys="image"),
             ]
diff --git a/tests/test_matshow3d.py b/tests/test_matshow3d.py
--- a/tests/test_matshow3d.py
+++ b/tests/test_matshow3d.py
@@ -17,7 +17,14 @@
 
 import numpy as np
 
-from monai.transforms import AddChanneld, Compose, LoadImaged, RandSpatialCropSamplesd, RepeatChanneld, ScaleIntensityd
+from monai.transforms import (
+    Compose,
+    EnsureChannelFirstd,
+    LoadImaged,
+    RandSpatialCropSamplesd,
+    RepeatChanneld,
+    ScaleIntensityd,
+)
 from monai.utils import optional_import
 from monai.visualize.utils import matshow3d
 from tests.utils import SkipIfNoModule
@@ -31,7 +38,13 @@ class TestMatshow3d(unittest.TestCase):
     def test_3d(self):
         testing_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "testing_data")
         keys = "image"
-        xforms = Compose([LoadImaged(keys=keys), AddChanneld(keys=keys), ScaleIntensityd(keys=keys)])
+        xforms = Compose(
+            [
+                LoadImaged(keys=keys),
+                EnsureChannelFirstd(keys=keys, channel_dim="no_channel"),
+                ScaleIntensityd(keys=keys),
+            ]
+        )
         image_path = os.path.join(testing_dir, "anatomical.nii")
         ims = xforms({keys: image_path})
 
@@ -53,7 +66,7 @@ def test_samples(self):
         xforms = Compose(
             [
                 LoadImaged(keys=keys),
-                AddChanneld(keys=keys),
+                EnsureChannelFirstd(keys=keys),
                 ScaleIntensityd(keys=keys),
                 RandSpatialCropSamplesd(keys=keys, roi_size=(8, 8, 5), random_size=True, num_samples=10),
             ]
@@ -82,7 +95,7 @@ def test_3d_rgb(self):
         xforms = Compose(
             [
                 LoadImaged(keys=keys),
-                AddChanneld(keys=keys),
+                EnsureChannelFirstd(keys=keys, channel_dim="no_channel"),
                 ScaleIntensityd(keys=keys),
                 # change to RGB color image
                 RepeatChanneld(keys=keys, repeats=3),
diff --git a/tests/test_mednistdataset.py b/tests/test_mednistdataset.py
--- a/tests/test_mednistdataset.py
+++ b/tests/test_mednistdataset.py
@@ -18,7 +18,7 @@
 
 from monai.apps import MedNISTDataset
 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
 
 MEDNIST_FULL_DATASET_LENGTH = 58954
@@ -28,7 +28,13 @@ class TestMedNISTDataset(unittest.TestCase):
     @skip_if_quick
     def test_values(self):
         testing_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "testing_data")
-        transform = Compose([LoadImaged(keys="image"), AddChanneld(keys="image"), ScaleIntensityd(keys="image")])
+        transform = Compose(
+            [
+                LoadImaged(keys="image"),
+                EnsureChannelFirstd(keys="image", channel_dim="no_channel"),
+                ScaleIntensityd(keys="image"),
+            ]
+        )
 
         def _test_dataset(dataset):
             self.assertEqual(len(dataset), int(MEDNIST_FULL_DATASET_LENGTH * dataset.test_frac))
diff --git a/tests/test_tciadataset.py b/tests/test_tciadataset.py
--- a/tests/test_tciadataset.py
+++ b/tests/test_tciadataset.py
@@ -18,7 +18,7 @@
 from monai.apps import TciaDataset
 from monai.apps.tcia import TCIA_LABEL_DICT
 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
 
 
@@ -33,7 +33,7 @@ def test_values(self):
         transform = Compose(
             [
                 LoadImaged(keys=["image", "seg"], reader="PydicomReader", label_dict=TCIA_LABEL_DICT[collection]),
-                AddChanneld(keys="image"),
+                EnsureChannelFirstd(keys="image", channel_dim="no_channel"),
                 ScaleIntensityd(keys="image"),
             ]
         )
diff --git a/tests/test_testtimeaugmentation.py b/tests/test_testtimeaugmentation.py
--- a/tests/test_testtimeaugmentation.py
+++ b/tests/test_testtimeaugmentation.py
@@ -25,11 +25,11 @@
 from monai.networks.nets import UNet
 from monai.transforms import (
     Activations,
-    AddChanneld,
     AsDiscrete,
     Compose,
     CropForegroundd,
     DivisiblePadd,
+    EnsureChannelFirstd,
     RandAffined,
     RandScaleIntensityd,
 )
@@ -84,7 +84,7 @@ def test_test_time_augmentation(self):
 
         transforms = Compose(
             [
-                AddChanneld(keys),
+                EnsureChannelFirstd(keys, channel_dim="no_channel"),
                 RandAffined(
                     keys,
                     prob=1.0,
@@ -145,13 +145,17 @@ def test_test_time_augmentation(self):
         self.assertIsInstance(vvc, float)
 
     def test_warn_non_random(self):
-        transforms = Compose([AddChanneld("im"), SpatialPadd("im", 1)])
+        transforms = Compose([EnsureChannelFirstd("im", channel_dim="no_channel"), SpatialPadd("im", 1)])
         with self.assertWarns(UserWarning):
             TestTimeAugmentation(transforms, None, None, None)
 
     def test_warn_random_but_has_no_invertible(self):
         transforms = Compose(
-            [AddChanneld("image"), RandFlipd("image", prob=1.0), RandScaleIntensityd("image", 0.1, prob=1.0)]
+            [
+                EnsureChannelFirstd("image", channel_dim="no_channel"),
+                RandFlipd("image", prob=1.0),
+                RandScaleIntensityd("image", 0.1, prob=1.0),
+            ]
         )
         with self.assertWarns(UserWarning):
             tta = TestTimeAugmentation(transforms, 5, 0, orig_key="image")
@@ -159,7 +163,9 @@ def test_warn_random_but_has_no_invertible(self):
 
     def test_warn_random_but_all_not_invertible(self):
         """test with no invertible stack"""
-        transforms = Compose([AddChanneld("image"), RandScaleIntensityd("image", 0.1, prob=1.0)])
+        transforms = Compose(
+            [EnsureChannelFirstd("image", channel_dim="no_channel"), RandScaleIntensityd("image", 0.1, prob=1.0)]
+        )
         with self.assertWarns(UserWarning):
             tta = TestTimeAugmentation(transforms, 1, 0, orig_key="image")
             tta(self.get_data(1, (20, 20), data_type=np.float32))