Repository: Project-MONAI/MONAI

Issue:
Default times of copyitems to 1
**Is your feature request related to a problem? Please describe.**
Feature request from @holgerroth : why not default to times=1 in CopyItemsd? Seems copying one time would be the most common usage. https://github.com/Project-MONAI/MONAI/blob/dev/monai/transforms/utility/dictionary.py#L861



Discussion:

Patch:
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
@@ -858,17 +858,22 @@ class CopyItemsd(MapTransform):
     backend = [TransformBackends.TORCH, TransformBackends.NUMPY]
 
     def __init__(
-        self, keys: KeysCollection, times: int, names: KeysCollection, allow_missing_keys: bool = False
+        self,
+        keys: KeysCollection,
+        times: int = 1,
+        names: Optional[KeysCollection] = None,
+        allow_missing_keys: bool = False,
     ) -> None:
         """
         Args:
             keys: keys of the corresponding items to be transformed.
                 See also: :py:class:`monai.transforms.compose.MapTransform`
             times: expected copy times, for example, if keys is "img", times is 3,
-                it will add 3 copies of "img" data to the dictionary.
+                it will add 3 copies of "img" data to the dictionary, default to 1.
             names: the names corresponding to the newly copied data,
                 the length should match `len(keys) x times`. for example, if keys is ["img", "seg"]
                 and times is 2, names can be: ["img_1", "seg_1", "img_2", "seg_2"].
+                if None, use "{key}_{index}" as key for copy times `N`, index from `0` to `N-1`.
             allow_missing_keys: don't raise exception if key is missing.
 
         Raises:
@@ -880,7 +885,7 @@ def __init__(
         if times < 1:
             raise ValueError(f"times must be positive, got {times}.")
         self.times = times
-        names = ensure_tuple(names)
+        names = [f"{k}_{i}" for k in self.keys for i in range(self.times)] if names is None else ensure_tuple(names)
         if len(names) != (len(self.keys) * times):
             raise ValueError(
                 "len(names) must match len(keys) * times, "


Test patch:
diff --git a/tests/test_copy_itemsd.py b/tests/test_copy_itemsd.py
--- a/tests/test_copy_itemsd.py
+++ b/tests/test_copy_itemsd.py
@@ -39,13 +39,20 @@ def test_numpy_values(self, keys, times, names):
         np.testing.assert_allclose(result["img_1"], np.array([[1, 2], [2, 3]]))
         np.testing.assert_allclose(result["img"], np.array([[0, 1], [1, 2]]))
 
+    def test_default_names(self):
+        input_data = {"img": np.array([[0, 1], [1, 2]]), "seg": np.array([[3, 4], [4, 5]])}
+        result = CopyItemsd(keys=["img", "seg"], times=2, names=None)(input_data)
+        for name in ["img_0", "seg_0", "img_1", "seg_1"]:
+            self.assertTrue(name in result)
+
     def test_tensor_values(self):
         device = torch.device("cuda:0") if torch.cuda.is_available() else torch.device("cpu:0")
         input_data = {
             "img": torch.tensor([[0, 1], [1, 2]], device=device),
             "seg": torch.tensor([[0, 1], [1, 2]], device=device),
         }
-        result = CopyItemsd(keys="img", times=1, names="img_1")(input_data)
+        # test default `times=1`
+        result = CopyItemsd(keys="img", names="img_1")(input_data)
         self.assertTrue("img_1" in result)
         result["img_1"] += 1
         torch.testing.assert_allclose(result["img"], torch.tensor([[0, 1], [1, 2]], device=device))