Repository: Project-MONAI/MONAI

Issue:
Bug in RandAffined transformations
**Describe the bug**
When RandAffined is called on a dict containing more than one array (for example an image and a segmentation), and if `self.rand_affine.do_transform` is false, then the transformation returns the wrong result for everything after the first array.


**To Reproduce**
The transforms_demo_2d notebook can be easily repurposed to demonstrate this bug.

**Screenshots**
This is the output from two identical RandAffined transformations.

With prob = 1.0:
![image](https://user-images.githubusercontent.com/11088372/81538294-7b221580-9366-11ea-9556-582629de5e87.png)

With prob = 0.0:
![image](https://user-images.githubusercontent.com/11088372/81538487-b6244900-9366-11ea-938d-7a4eb11db9e2.png)


**Environment (please complete the following information):**
 - OS: Centos 7
 - Python version: 3.7
 - MONAI version [e.g. git commit hash]: 0.1.0+34.gad06dff.dirty

**Additional context**
I did some digging on this in my own fork of the code. The relevant code in RandAffined is:
https://github.com/Project-MONAI/MONAI/blob/ad06dff7f85711048690b2e85c99d51001612708/monai/transforms/spatial/dictionary.py#L313-L330

The bug occurs because 
https://github.com/Project-MONAI/MONAI/blob/ad06dff7f85711048690b2e85c99d51001612708/monai/transforms/spatial/dictionary.py#L321
returns a numpy array, whereas
https://github.com/Project-MONAI/MONAI/blob/ad06dff7f85711048690b2e85c99d51001612708/monai/transforms/spatial/dictionary.py#L319
returns a ``torch.Tensor``. And the ``Resample`` class behaves differently with these different types of input. For ``torch.Tensor`` grids, Resample uses ``clone()``, and operates on a copy, whereas with numpy inputs, ``torch.as_tensor()`` is called which then references the original data. Just changing the constructor from ``torch.as_tensor()`` to ``torch.tensor()`` ensures that torch makes a copy of the grid, which means future calls to ``Resample`` will have the correct grid values.

I'll submit a PR from my fork with the fix?

Note that this bug will also happen with ``Rand2DElasticd`` and ``Rand3DElasticd`` since they follow the same pattern, but this fix to ``Resample`` will also apply to them.




Discussion:
Hi @murraycutforth ,

Thanks very much for your bug report and detailed debug information!
Please submit your fix PR as you said.
Thanks in advance!

Resolving patch:
diff --git a/monai/data/utils.py b/monai/data/utils.py
--- a/monai/data/utils.py
+++ b/monai/data/utils.py
@@ -348,8 +348,8 @@ def to_affine_nd(r, affine):
     Returns:
         a (r+1) x (r+1) matrix
     """
-    affine = np.array(affine, dtype=np.float64)
-    if affine.ndim != 2:
+    affine_ = np.array(affine, dtype=np.float64)
+    if affine_.ndim != 2:
         raise ValueError("input affine must have two dimensions")
     new_affine = np.array(r, dtype=np.float64, copy=True)
     if new_affine.ndim == 0:
@@ -357,10 +357,10 @@ def to_affine_nd(r, affine):
         if not np.isfinite(sr) or sr < 0:
             raise ValueError("r must be positive.")
         new_affine = np.eye(sr + 1, dtype=np.float64)
-    d = max(min(len(new_affine) - 1, len(affine) - 1), 1)
-    new_affine[:d, :d] = affine[:d, :d]
+    d = max(min(len(new_affine) - 1, len(affine_) - 1), 1)
+    new_affine[:d, :d] = affine_[:d, :d]
     if d > 1:
-        new_affine[:d, -1] = affine[:d, -1]
+        new_affine[:d, -1] = affine_[:d, -1]
     return new_affine
 
 
diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py
--- a/monai/transforms/spatial/array.py
+++ b/monai/transforms/spatial/array.py
@@ -645,7 +645,7 @@ def __call__(self, spatial_size=None, grid=None):
             affine = affine @ create_scale(spatial_dims, self.scale_params)
         affine = torch.as_tensor(np.ascontiguousarray(affine), device=self.device)
 
-        grid = torch.as_tensor(np.ascontiguousarray(grid)) if not torch.is_tensor(grid) else grid.detach().clone()
+        grid = torch.tensor(grid) if not torch.is_tensor(grid) else grid.detach().clone()
         if self.device:
             grid = grid.to(self.device)
         grid = (affine.float() @ grid.reshape((grid.shape[0], -1)).float()).reshape([-1] + list(grid.shape[1:]))
@@ -795,7 +795,7 @@ def __call__(self, img, grid, mode="bilinear"):
         """
         if not torch.is_tensor(img):
             img = torch.as_tensor(np.ascontiguousarray(img))
-        grid = torch.tensor(np.ascontiguousarray(grid)) if not torch.is_tensor(grid) else grid.detach().clone()
+        grid = torch.tensor(grid) if not torch.is_tensor(grid) else grid.detach().clone()
         if self.device:
             img = img.to(self.device)
             grid = grid.to(self.device)


Test patch: