Repository: Project-MONAI/MONAI

Issue:
review `img.meta['affine']` and `img.affine`
**Is your feature request related to a problem? Please describe.**
writing the affine could currently be achieved with two methods:
`img.meta['affine']=matrix` and `img.affine=matrix`, the property setter is safer with an additional type constraint.


existing usages should be reviewed and made consistent.


Discussion:

Resolving patch:
diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py
--- a/monai/transforms/croppad/array.py
+++ b/monai/transforms/croppad/array.py
@@ -198,7 +198,7 @@ def update_meta(self, tensor: MetaTensor, to_pad: List[Tuple[int, int]]):
         spatial_rank = max(len(tensor.affine) - 1, 1)
         to_shift = [-s[0] for s in to_pad[1:]]  # skipping the channel pad
         mat = create_translate(spatial_rank, to_shift)
-        tensor.meta["affine"] = tensor.affine @ convert_to_dst_type(mat, tensor.affine)[0]
+        tensor.affine = tensor.affine @ convert_to_dst_type(mat, tensor.affine)[0]
 
     def inverse(self, data: MetaTensor) -> MetaTensor:
         transform = self.pop_transform(data)
@@ -450,7 +450,7 @@ def update_meta(self, tensor: MetaTensor, slices: Tuple[slice, ...]):
         spatial_rank = max(len(tensor.affine) - 1, 1)
         to_shift = [s.start if s.start is not None else 0 for s in ensure_tuple(slices)[1:]]
         mat = create_translate(spatial_rank, to_shift)
-        tensor.meta["affine"] = tensor.affine @ convert_to_dst_type(mat, tensor.affine)[0]
+        tensor.affine = tensor.affine @ convert_to_dst_type(mat, tensor.affine)[0]
 
     def inverse(self, img: MetaTensor) -> MetaTensor:
         transform = self.pop_transform(img)
diff --git a/monai/utils/deprecate_utils.py b/monai/utils/deprecate_utils.py
--- a/monai/utils/deprecate_utils.py
+++ b/monai/utils/deprecate_utils.py
@@ -57,7 +57,7 @@ def deprecated(
 
     Args:
         since: version at which the definition was marked deprecated but not removed.
-        removed: version at which the definition was removed and no longer usable.
+        removed: version at which the definition was/will be removed and no longer usable.
         msg_suffix: message appended to warning/exception detailing reasons for deprecation and what to use instead.
         version_val: (used for testing) version to compare since and removed against, default is MONAI version.
         warning_category: a warning category class, defaults to `FutureWarning`.
@@ -147,7 +147,7 @@ def deprecated_arg(
     Args:
         name: name of position or keyword argument to mark as deprecated.
         since: version at which the argument was marked deprecated but not removed.
-        removed: version at which the argument was removed and no longer usable.
+        removed: version at which the argument was/will be removed and no longer usable.
         msg_suffix: message appended to warning/exception detailing reasons for deprecation and what to use instead.
         version_val: (used for testing) version to compare since and removed against, default is MONAI version.
         new_name: name of position or keyword argument to replace the deprecated argument.


Test patch:
diff --git a/tests/test_compute_confusion_matrix.py b/tests/test_compute_confusion_matrix.py
--- a/tests/test_compute_confusion_matrix.py
+++ b/tests/test_compute_confusion_matrix.py
@@ -12,7 +12,6 @@
 import unittest
 from typing import Any, Dict, List
 
-import numpy as np
 import torch
 from parameterized import parameterized
 
@@ -22,20 +21,24 @@
     do_metric_reduction,
     get_confusion_matrix,
 )
+from tests.utils import assert_allclose
 
+_device = "cuda:0" if torch.cuda.is_available() else "cpu"
 # input data
 data: Dict[Any, Any] = {
     "y_pred": torch.tensor(
         [
             [[[0.0, 1.0], [0.0, 0.0]], [[0.0, 0.0], [1.0, 1.0]], [[1.0, 0.0], [0.0, 0.0]]],
             [[[0.0, 0.0], [0.0, 1.0]], [[1.0, 0.0], [0.0, 0.0]], [[0.0, 1.0], [1.0, 0.0]]],
-        ]
+        ],
+        device=_device,
     ),
     "y": torch.tensor(
         [
             [[[0.0, 0.0], [0.0, 1.0]], [[1.0, 0.0], [1.0, 0.0]], [[0.0, 1.0], [0.0, 0.0]]],
             [[[0.0, 0.0], [0.0, 1.0]], [[1.0, 1.0], [0.0, 0.0]], [[0.0, 0.0], [1.0, 0.0]]],
-        ]
+        ],
+        device=_device,
     ),
 }
 
@@ -211,10 +214,10 @@ def test_value(self, input_data, expected_value):
         # include or ignore background
         input_data["include_background"] = True
         result = get_confusion_matrix(**input_data)
-        np.testing.assert_allclose(result, expected_value, atol=1e-4, rtol=1e-4)
+        assert_allclose(result, expected_value, atol=1e-4, rtol=1e-4)
         input_data["include_background"] = False
         result = get_confusion_matrix(**input_data)
-        np.testing.assert_allclose(result, expected_value[:, 1:, :], atol=1e-4, rtol=1e-4)
+        assert_allclose(result, expected_value[:, 1:, :], atol=1e-4, rtol=1e-4)
 
     @parameterized.expand(TEST_CASES_COMPUTE_SAMPLE)
     def test_compute_sample(self, input_data, expected_value):
@@ -225,7 +228,7 @@ def test_compute_sample(self, input_data, expected_value):
         metric = ConfusionMatrixMetric(**params)
         metric(**vals)
         result, _ = metric.aggregate()[0]
-        np.testing.assert_allclose(result, expected_value, atol=1e-4, rtol=1e-4)
+        assert_allclose(result, expected_value, atol=1e-4, rtol=1e-4)
 
     @parameterized.expand(TEST_CASES_COMPUTE_SAMPLE_MULTI_METRICS)
     def test_compute_sample_multiple_metrics(self, input_data, expected_values):
@@ -239,7 +242,7 @@ def test_compute_sample_multiple_metrics(self, input_data, expected_values):
         for idx in range(len(results)):
             result = results[idx][0]
             expected_value = expected_values[idx]
-            np.testing.assert_allclose(result, expected_value, atol=1e-4, rtol=1e-4)
+            assert_allclose(result, expected_value, atol=1e-4, rtol=1e-4)
 
     @parameterized.expand(TEST_CASES_COMPUTE_SAMPLE_NAN)
     def test_compute_sample_with_nan(self, input_data, expected_value, expected_not_nans):
@@ -250,8 +253,8 @@ def test_compute_sample_with_nan(self, input_data, expected_value, expected_not_
         metric = ConfusionMatrixMetric(**params)
         metric(**vals)
         result, not_nans = metric.aggregate()[0]
-        np.testing.assert_allclose(result, expected_value, atol=1e-4, rtol=1e-4)
-        np.testing.assert_allclose(not_nans, expected_not_nans, atol=1e-4, rtol=1e-4)
+        assert_allclose(result, expected_value, atol=1e-4, rtol=1e-4)
+        assert_allclose(not_nans, expected_not_nans, atol=1e-4, rtol=1e-4)
 
     @parameterized.expand([TEST_CASES_CLF])
     def test_clf_with_nan(self, input_data, expected_value):
@@ -261,11 +264,11 @@ def test_clf_with_nan(self, input_data, expected_value):
         vals["y"] = params.pop("y")
         metric = ConfusionMatrixMetric(**params)
         result = metric(**vals)
-        np.testing.assert_allclose(result, expected_value, atol=1e-4, rtol=1e-4)
+        assert_allclose(result, expected_value, atol=1e-4, rtol=1e-4)
         result, _ = metric.aggregate(reduction="mean_channel")[0]
         expected_value, _ = do_metric_reduction(expected_value, "mean_channel")
         expected_value = compute_confusion_matrix_metric("tpr", expected_value)
-        np.testing.assert_allclose(result, expected_value, atol=1e-4, rtol=1e-4)
+        assert_allclose(result, expected_value, atol=1e-4, rtol=1e-4)
 
 
 if __name__ == "__main__":
diff --git a/tests/test_compute_froc.py b/tests/test_compute_froc.py
--- a/tests/test_compute_froc.py
+++ b/tests/test_compute_froc.py
@@ -17,11 +17,12 @@
 
 from monai.metrics import compute_fp_tp_probs, compute_froc_curve_data, compute_froc_score
 
+_device = "cuda:0" if torch.cuda.is_available() else "cpu"
 TEST_CASE_1 = [
     {
-        "probs": torch.tensor([1, 0.6, 0.8]),
-        "y_coord": torch.tensor([0, 2, 3]),
-        "x_coord": torch.tensor([3, 0, 1]),
+        "probs": torch.tensor([1, 0.6, 0.8], device=_device),
+        "y_coord": torch.tensor([0, 2, 3], device=_device),
+        "x_coord": torch.tensor([3, 0, 1], device=_device),
         "evaluation_mask": np.array([[0, 0, 1, 1], [2, 2, 0, 0], [0, 3, 3, 0], [0, 3, 3, 3]]),
         "labels_to_exclude": [2],
         "resolution_level": 0,
diff --git a/tests/test_compute_meandice.py b/tests/test_compute_meandice.py
--- a/tests/test_compute_meandice.py
+++ b/tests/test_compute_meandice.py
@@ -17,11 +17,12 @@
 
 from monai.metrics import DiceMetric, compute_meandice
 
+_device = "cuda:0" if torch.cuda.is_available() else "cpu"
 # keep background
 TEST_CASE_1 = [  # y (1, 1, 2, 2), y_pred (1, 1, 2, 2), expected out (1, 1)
     {
-        "y_pred": torch.tensor([[[[1.0, 0.0], [0.0, 1.0]]]]),
-        "y": torch.tensor([[[[1.0, 0.0], [1.0, 1.0]]]]),
+        "y_pred": torch.tensor([[[[1.0, 0.0], [0.0, 1.0]]]], device=_device),
+        "y": torch.tensor([[[[1.0, 0.0], [1.0, 1.0]]]], device=_device),
         "include_background": True,
     },
     [[0.8]],
diff --git a/tests/test_compute_meaniou.py b/tests/test_compute_meaniou.py
--- a/tests/test_compute_meaniou.py
+++ b/tests/test_compute_meaniou.py
@@ -17,11 +17,12 @@
 
 from monai.metrics import MeanIoU, compute_meaniou
 
+_device = "cuda:0" if torch.cuda.is_available() else "cpu"
 # keep background
 TEST_CASE_1 = [  # y (1, 1, 2, 2), y_pred (1, 1, 2, 2), expected out (1, 1)
     {
-        "y_pred": torch.tensor([[[[1.0, 0.0], [0.0, 1.0]]]]),
-        "y": torch.tensor([[[[1.0, 0.0], [1.0, 1.0]]]]),
+        "y_pred": torch.tensor([[[[1.0, 0.0], [0.0, 1.0]]]], device=_device),
+        "y": torch.tensor([[[[1.0, 0.0], [1.0, 1.0]]]], device=_device),
         "include_background": True,
     },
     [[0.6667]],
diff --git a/tests/test_compute_roc_auc.py b/tests/test_compute_roc_auc.py
--- a/tests/test_compute_roc_auc.py
+++ b/tests/test_compute_roc_auc.py
@@ -19,9 +19,10 @@
 from monai.metrics import ROCAUCMetric, compute_roc_auc
 from monai.transforms import Activations, AsDiscrete, Compose, ToTensor
 
+_device = "cuda:0" if torch.cuda.is_available() else "cpu"
 TEST_CASE_1 = [
-    torch.tensor([[0.1, 0.9], [0.3, 1.4], [0.2, 0.1], [0.1, 0.5]]),
-    torch.tensor([[0], [1], [0], [1]]),
+    torch.tensor([[0.1, 0.9], [0.3, 1.4], [0.2, 0.1], [0.1, 0.5]], device=_device),
+    torch.tensor([[0], [1], [0], [1]], device=_device),
     True,
     2,
     "macro",
diff --git a/tests/test_shuffle_buffer.py b/tests/test_shuffle_buffer.py
--- a/tests/test_shuffle_buffer.py
+++ b/tests/test_shuffle_buffer.py
@@ -19,7 +19,7 @@
 from tests.utils import SkipIfBeforePyTorchVersion
 
 
-@SkipIfBeforePyTorchVersion((1, 8))
+@SkipIfBeforePyTorchVersion((1, 12))
 class TestShuffleBuffer(unittest.TestCase):
     def test_shape(self):
         buffer = ShuffleBuffer([1, 2, 3, 4], seed=0)
@@ -34,8 +34,8 @@ def test_shape(self):
             np.testing.assert_allclose(output, [[2, 1], [3, 4]])
             np.testing.assert_allclose(output2, [[3, 1], [2, 4]])
         else:  # multiprocess shuffle
-            np.testing.assert_allclose(output, [[2, 3], [1, 4]])
-            np.testing.assert_allclose(output2, [[1, 4], [2, 3]])
+            np.testing.assert_allclose(output, [[2, 3], [1, 4]], err_msg=f"seed {buffer.seed}")
+            np.testing.assert_allclose(output2, [[1, 4], [2, 3]], err_msg=f"seed {buffer.seed}")
 
 
 if __name__ == "__main__":