Repository: nilearn/nilearn

Issue:
copy_img has a side effect on image
This is due to the use of non safe `get_data` in `copy_img`.

Typically this invalidate cache:

``` python

def f(img):
    print('Not cached')

mem = Memory(cache_dir='nilearn_cache')
img = NiftiImage(np.ones(10, 10, 10, 10), affine=np.ones(4))

mem.cache(f)(img)
mem.cache(f)(img)
img2 = copy_img(img)
mem.cache(f)(img)
```

Output:

```
Not cached
Not cached
```



Discussion:
Good catch!

A PR to fix this would be great.

Fix is straightforward, I just need to write a test and I will push it

> Fix is straight forward,

It is indeed, because you understand the package :)

@arthurmensch : can you either do this today, or help Aina do it

Cc @ainafp : I think that this is something that you could work on.

The idea is to use nilearn._utils.niimg._safe_get_data instead of the get_data method.

@GaelVaroquaux @arthurmensch :

I changed it but it still creates a new image because it uses new_img_like() from nilearn.image.image
I can substitute the whole line and return just _safe_get_data(img). 
Is this fine? In this case, instead of 
`new_img_like(img, img.get_data().copy(), img.get_affine().copy(),  copy_header=True)`
it just makes a deep copy
`img = copy.deepcopy(img)`
if it is not cached inside _safe_get_data. 

If you agree I change it. I wrote the test based on what Arthur did. 

@GaelVaroquaux 
In new_img_like() in from nilearn.image.image, when copy_header is set to true, changing  header['scl_slope'], header['scl_inter'],  header['cal_max'] or header['cal_max'] change the value of the hash. 

```
img = Nifti1Image(np.ones((2, 2, 2, 2)), affine=np.eye(4))
joblib.hash(img)
img2 = new_img_like(img, np.ones((2, 2, 2, 2)), img.get_affine().copy(), copy_header=True)
joblib.hash(img)  
```

Nevermind. It was just a copy.copy of the header inside new_img_like(). I changed it to copy.deepcopy()


Resolving patch:
diff --git a/nilearn/_utils/niimg.py b/nilearn/_utils/niimg.py
--- a/nilearn/_utils/niimg.py
+++ b/nilearn/_utils/niimg.py
@@ -128,7 +128,7 @@ def copy_img(img):
 
     if not isinstance(img, nibabel.spatialimages.SpatialImage):
         raise ValueError("Input value is not an image")
-    return new_img_like(img, img.get_data().copy(), img.get_affine().copy(),
+    return new_img_like(img, _safe_get_data(img).copy(), img.get_affine().copy(),
                         copy_header=True)
 
 
diff --git a/nilearn/image/image.py b/nilearn/image/image.py
--- a/nilearn/image/image.py
+++ b/nilearn/image/image.py
@@ -589,7 +589,7 @@ def new_img_like(ref_niimg, data, affine=None, copy_header=False):
         data = as_ndarray(data, dtype=default_dtype)
     header = None
     if copy_header:
-        header = copy.copy(ref_niimg.get_header())
+        header = copy.deepcopy(ref_niimg.get_header())
         header['scl_slope'] = 0.
         header['scl_inter'] = 0.
         header['glmax'] = 0.


Test patch:
diff --git a/nilearn/tests/test_niimg.py b/nilearn/tests/test_niimg.py
--- a/nilearn/tests/test_niimg.py
+++ b/nilearn/tests/test_niimg.py
@@ -1,7 +1,16 @@
 import os
+import numpy as np
 
-from nilearn._utils.testing import assert_raises_regex
+from nose.tools import assert_equal
+
+from nibabel import Nifti1Image
+from sklearn.externals import joblib
+
+from nilearn.image import new_img_like
 from nilearn._utils import niimg
+from nilearn._utils.testing import assert_raises_regex
+
+
 
 currdir = os.path.dirname(os.path.abspath(__file__))
 
@@ -9,3 +18,19 @@
 def test_copy_img():
     assert_raises_regex(ValueError, "Input value is not an image",
                         niimg.copy_img, 3)
+
+
+def test_copy_img_side_effect():
+    img1 = Nifti1Image(np.ones((2, 2, 2, 2)), affine=np.eye(4))
+    hash1 = joblib.hash(img1)
+    img2 = niimg.copy_img(img1)
+    hash2 = joblib.hash(img1)
+    assert_equal(hash1, hash2)
+
+
+def test_new_img_like_side_effect():
+    img1 = Nifti1Image(np.ones((2, 2, 2, 2)), affine=np.eye(4))
+    hash1 = joblib.hash(img1)
+    img2 = new_img_like(img1, np.ones((2, 2, 2, 2)), img1.get_affine().copy(), copy_header=True)
+    hash2 = joblib.hash(img1)
+    assert_equal(hash1, hash2)