Repository: nilearn/nilearn

Issue:
Non-descriptive runtime warning when using NiftiMasker on beta images
What version of Nilearn are you using? 
0.5.2

What were you trying to do? 
Use NiftiMasker to read fMRI beta images that came from a first level analysis in SPM.

What actually happened?
Encoutered a non-descriptive RuntimeWarning that seems to be passed from numpy:
```
[NiftiMasker.fit] Loading data from E:\beta_0001.nii
[NiftiMasker.fit] Computing the mask
[NiftiMasker.fit] Resampling mask
[NiftiMasker.transform_single_imgs] Loading data from Nifti1Image(
shape=(79, 95, 79, 1),
affine=array([[  -2.,    0.,    0.,   78.],
       [   0.,    2.,    0., -112.],
       [   0.,    0.,    2.,  -70.],
       [   0.,    0.,    0.,    1.]])
)
[NiftiMasker.transform_single_imgs] Extracting region signals
[NiftiMasker.transform_single_imgs] Cleaning extracted signalsC:\Users\Felix\Anaconda3\lib\site-packages\numpy\lib\function_base.py:3405: RuntimeWarning: Invalid value encountered in median
  r = func(a, **kwargs)
``` 

Code to reproduce:
```python
from nilearn.input_data import NiftiMasker

masker = NiftiMasker(verbose=True)

beta_img = 'E:\\beta_0001.nii'
t_img = 'E:\\spmT_0001.nii'

X_beta = masker.fit_transform(beta_img)
X_t = masker.fit_transform(t_img)
``` 

I have attached both the beta- and the t-nifti images. Strangely, the issue doesn't occur for the t-image, but only for the beta-image, although they come from the same participant. Any ideas what the issue could be? Thanks!
[images.zip](https://github.com/nilearn/nilearn/files/3707175/images.zip)






Discussion:
I've looked into this again and found that the warning seems to be triggered during the computation of the background mask by the following call to `np.median()`:

https://github.com/nilearn/nilearn/blob/2d3364fc3b6bdc335bb78051f719ed9c944a248d/nilearn/masking.py#L436-L442

It looks like SPM uses `NaN` for voxels outside the brain in beta images, which leads `np.median()` to return `NaN` and issue the warning above. Based on the comment it seems like this issue is already known and accounted for. I still think that the warning might be confusing for users, as I originally though it might indicate a more serious issue. 

Couldn't we check if the data contain any `NaN` and only call `np.median()` when this is not the case and else execute line 440 right away?
Arguably, we could change this line to use np.nanmedian?

I'm not sure if `np.nanmedian()` works as intended:

```python
# current version
background = np.median(get_border_data(data, border_size))
if np.isnan(background):
    # We absolutely need to catter for NaNs as a background:
    # SPM does that by default
    mask = np.logical_not(np.isnan(data))
else:
    mask = data != background
```

```python
# proposed new version
background_new = np.nanmedian(get_border_data(data, border_size))
mask_new = data != background_new
```

I think the new version does not lead to the desired result. Instead, all but one voxel (probably the median voxel) in the mask seem to be True:
```python
mask_new.shape
> (79, 95, 79)

79*95*79
> 592895

np.sum(mask_new)
> 592894
```

Comparing the old and new version:
```python
np.allclose(mask, mask_new, equal_nan=True)
> False
```




I think that  simply testing whether at least half of the background voxels are NaN is equivalent to testing that the median is NaN. This would solve your problem I think.
Thanks @bthirion for your suggestion. `np.nanmedian()` returns the median while ignoring all NaNs, so I'm not sure what you mean by 'whether at least half of the background voxels are NaN'? Since SPM uses NaN for the background, `np.nanmedian()` computes the median of only voxels within the brain. I don't think that's the intention of the code above, right?
The idea is not to use the median, but instead to test whether at least half of the background voxels are nans
```
if np.sum(np.isnan(background)) > np.size(background)  / 2:
    ...
```
This would avoid the unwanted message ?
Sorry if I misunderstood your point.
Thanks, but I'm still not sure why we should test whether at least half of the voxels are NaNs. As soon as at least one NaN is present, `np.median` throws the warning and returns NaN. So wouldn't it be enough to test for any NaNs like so?

```python
if np.isnan(get_border_data(data, border_size)).any():
    mask = np.logical_not(np.isnan(data))
else:
    background = np.median(get_border_data(data, border_size)) 
    mask = data != background
```

I think this code should lead to the same behavior as the current version, but avoid the warning.
Hi @fkalbe 
Since numpy `1.17.0`, `median` and `percentile` functions no longer warn about nans (see [here](https://github.com/numpy/numpy/releases/tag/v1.17.0)), such that we don't have to deal with this warning with newer versions of numpy.
That being said, we still support older versions (down to `1.11`) such that we might want to suppress this warning with your fix (AFAICT it seems to solve the issue). Do you want to open a PR?

Resolving patch:
diff --git a/nilearn/masking.py b/nilearn/masking.py
--- a/nilearn/masking.py
+++ b/nilearn/masking.py
@@ -434,12 +434,12 @@ def compute_background_mask(data_imgs, border_size=2,
                                                 target_shape=target_shape,
                                                 smooth=False)
 
-    background = np.median(get_border_data(data, border_size))
-    if np.isnan(background):
+    if np.isnan(get_border_data(data, border_size)).any():
         # We absolutely need to catter for NaNs as a background:
         # SPM does that by default
         mask = np.logical_not(np.isnan(data))
     else:
+        background = np.median(get_border_data(data, border_size))
         mask = data != background
 
     mask, affine = _post_process_mask(mask, affine, opening=opening,


Test patch: