Repository: nilearn/nilearn

Issue:
test_nifti_labels_masker_with_nans_and_infs and test_connected_label_regions fail on Arm
With nilearn 0.6.2, some tests are failing on aarch64 and armv7:
```
[  338s] =========================== short test summary info ============================
[  338s] FAILED nilearn/input_data/tests/test_nifti_labels_masker.py::test_nifti_labels_masker_with_nans_and_infs
[  338s] FAILED nilearn/regions/tests/test_region_extractor.py::test_connected_label_regions
[  338s] ===== 2 failed, 504 passed, 4 skipped, 2435 warnings in 295.69s (0:04:55) ======
``` 

```
[  335s] =================================== FAILURES ===================================
[  335s] _________________ test_nifti_labels_masker_with_nans_and_infs __________________
[  335s] [gw1] linux -- Python 3.8.5 /usr/bin/python3
[  335s] 
[  335s]     def test_nifti_labels_masker_with_nans_and_infs():
[  335s]         length = 3
[  335s]         n_regions = 9
[  335s]         fmri_img, mask_img = generate_random_img((13, 11, 12),
[  335s]                                                  affine=np.eye(4), length=length)
[  335s]         labels_img = data_gen.generate_labeled_regions((13, 11, 12),
[  335s]                                                        affine=np.eye(4),
[  335s]                                                        n_regions=n_regions)
[  335s]         # nans
[  335s]         mask_data = get_data(mask_img)
[  335s]         mask_data[:, :, 7] = np.nan
[  335s]         mask_data[:, :, 4] = np.inf
[  335s]         mask_img = nibabel.Nifti1Image(mask_data, np.eye(4))
[  335s]     
[  335s]         masker = NiftiLabelsMasker(labels_img, mask_img=mask_img)
[  335s] >       sig = masker.fit_transform(fmri_img)
[  335s] 
[  335s] nilearn/input_data/tests/test_nifti_labels_masker.py:126: 
[  335s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
[  335s] nilearn/input_data/nifti_labels_masker.py:226: in fit_transform
[  335s]     return self.fit().transform(imgs, confounds=confounds)
[  335s] nilearn/input_data/nifti_labels_masker.py:215: in fit
[  335s]     mask_data, mask_affine = masking._load_mask_img(self.mask_img_)
[  335s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
[  335s] 
[  335s] mask_img = <nibabel.nifti1.Nifti1Image object at 0xffff891331c0>
[  335s] allow_empty = False
[  335s] 
[  335s]     def _load_mask_img(mask_img, allow_empty=False):
[  335s]         """Check that a mask is valid, ie with two values including 0 and load it.
[  335s]     
[  335s]         Parameters
[  335s]         ----------
[  335s]         mask_img: Niimg-like object
[  335s]             See http://nilearn.github.io/manipulating_images/input_output.html
[  335s]             The mask to check
[  335s]     
[  335s]         allow_empty: boolean, optional
[  335s]             Allow loading an empty mask (full of 0 values)
[  335s]     
[  335s]         Returns
[  335s]         -------
[  335s]         mask: numpy.ndarray
[  335s]             boolean version of the mask
[  335s]         """
[  335s]         mask_img = _utils.check_niimg_3d(mask_img)
[  335s]         mask = get_data(mask_img)
[  335s]         values = np.unique(mask)
[  335s]     
[  335s]         if len(values) == 1:
[  335s]             # We accept a single value if it is not 0 (full true mask).
[  335s]             if values[0] == 0 and not allow_empty:
[  335s]                 raise ValueError(
[  335s]                     'The mask is invalid as it is empty: it masks all data.')
[  335s]         elif len(values) == 2:
[  335s]             # If there are 2 different values, one of them must be 0 (background)
[  335s]             if 0 not in values:
[  335s]                 raise ValueError('Background of the mask must be represented with'
[  335s]                                  '0. Given mask contains: %s.' % values)
[  335s]         elif len(values) != 2:
[  335s]             # If there are more than 2 values, the mask is invalid
[  335s] >           raise ValueError('Given mask is not made of 2 values: %s'
[  335s]                              '. Cannot interpret as true or false'
[  335s]                              % values)
[  335s] E           ValueError: Given mask is not made of 2 values: [-1  0  1]. Cannot interpret as true or false
[  335s] 
[  335s] nilearn/masking.py:61: ValueError
```

```
[  335s] _________________________ test_connected_label_regions _________________________
[  335s] [gw0] linux -- Python 3.8.5 /usr/bin/python3
[  335s] 
[  335s]     def test_connected_label_regions():
[  335s]         shape = (13, 11, 12)
[  335s]         affine = np.eye(4)
[  335s]         n_regions = 9
[  335s]         labels_img = generate_labeled_regions(shape, affine=affine,
[  335s]                                               n_regions=n_regions)
[  335s]         labels_data = get_data(labels_img)
[  335s]         n_labels_wo_reg_ext = len(np.unique(labels_data))
[  335s]     
[  335s]         # region extraction without specifying min_size
[  335s]         extracted_regions_on_labels_img = connected_label_regions(labels_img)
[  335s]         extracted_regions_labels_data = get_data(extracted_regions_on_labels_img)
[  335s]         n_labels_wo_min = len(np.unique(extracted_regions_labels_data))
[  335s]     
[  335s]         assert n_labels_wo_reg_ext < n_labels_wo_min
[  335s]     
[  335s]         # with specifying min_size
[  335s]         extracted_regions_with_min = connected_label_regions(labels_img,
[  335s]                                                              min_size=100)
[  335s]         extracted_regions_with_min_data = get_data(extracted_regions_with_min)
[  335s]         n_labels_with_min = len(np.unique(extracted_regions_with_min_data))
[  335s]     
[  335s]         assert n_labels_wo_min > n_labels_with_min
[  335s]     
[  335s]         # Test connect_diag=False
[  335s]         ext_reg_without_connect_diag = connected_label_regions(labels_img,
[  335s]                                                                connect_diag=False)
[  335s]         data_wo_connect_diag = get_data(ext_reg_without_connect_diag)
[  335s]         n_labels_wo_connect_diag = len(np.unique(data_wo_connect_diag))
[  335s]         assert n_labels_wo_connect_diag > n_labels_wo_reg_ext
[  335s]     
[  335s]         # If min_size is large and if all the regions are removed then empty image
[  335s]         # will be returned
[  335s]         extract_reg_min_size_large = connected_label_regions(labels_img,
[  335s]                                                              min_size=500)
[  335s]         assert np.unique(get_data(extract_reg_min_size_large)) == 0
[  335s]     
[  335s]         # Test the names of the brain regions given in labels.
[  335s]         # Test labels for 9 regions in n_regions
[  335s]         labels = ['region_a', 'region_b', 'region_c', 'region_d', 'region_e',
[  335s]                   'region_f', 'region_g', 'region_h', 'region_i']
[  335s]     
[  335s]         # If labels are provided, first return will contain extracted labels image
[  335s]         # and second return will contain list of new names generated based on same
[  335s]         # name with assigned on both hemispheres for example.
[  335s]         extracted_reg, new_labels = connected_label_regions(labels_img,
[  335s]                                                             min_size=100,
[  335s]                                                             labels=labels)
[  335s]         # The length of new_labels returned can differ depending upon min_size. If
[  335s]         # min_size given is more small regions can be removed therefore newly
[  335s]         # generated labels can be less than original size of labels. Or if min_size
[  335s]         # is less then newly generated labels can be more.
[  335s]     
[  335s]         # We test here whether labels returned are empty or not.
[  335s]         assert new_labels != ''
[  335s]         assert len(new_labels) <= len(labels)
[  335s]     
[  335s]         # labels given in numpy array
[  335s]         labels = np.asarray(labels)
[  335s]         extracted_reg2, new_labels2 = connected_label_regions(labels_img,
[  335s]                                                               labels=labels)
[  335s]         assert new_labels != ''
[  335s]         # By default min_size is less, so newly generated labels can be more.
[  335s]         assert len(new_labels2) >= len(labels)
[  335s]     
[  335s]         # If number of labels provided are wrong (which means less than number of
[  335s]         # unique labels in labels_img), then we raise an error
[  335s]     
[  335s]         # Test whether error raises
[  335s]         unique_labels = set(np.unique(np.asarray(get_data(labels_img))))
[  335s]         unique_labels.remove(0)
[  335s]     
[  335s]         # labels given are less than n_regions=9
[  335s]         provided_labels = ['region_a', 'region_c', 'region_f',
[  335s]                            'region_g', 'region_h', 'region_i']
[  335s]     
[  335s]         assert len(provided_labels) < len(unique_labels)
[  335s]     
[  335s]         with pytest.raises(ValueError):
[  335s]             connected_label_regions(labels_img, labels=provided_labels)
[  335s]     
[  335s]         # Test if unknown/negative integers are provided as labels in labels_img,
[  335s]         # we raise an error and test the same whether error is raised.
[  335s]         labels_data = np.zeros(shape, dtype=np.int)
[  335s]         h0 = shape[0] // 2
[  335s]         h1 = shape[1] // 2
[  335s]         h2 = shape[2] // 2
[  335s]         labels_data[:h0, :h1, :h2] = 1
[  335s]         labels_data[:h0, :h1, h2:] = 2
[  335s]         labels_data[:h0, h1:, :h2] = 3
[  335s]         labels_data[:h0, h1:, h2:] = 4
[  335s]         labels_data[h0:, :h1, :h2] = 5
[  335s]         labels_data[h0:, :h1, h2:] = 6
[  335s]         labels_data[h0:, h1:, :h2] = np.nan
[  335s]         labels_data[h0:, h1:, h2:] = np.inf
[  335s]     
[  335s]         neg_labels_img = nibabel.Nifti1Image(labels_data, affine)
[  335s]         with pytest.raises(ValueError):
[  335s] >           connected_label_regions(labels_img=neg_labels_img)
[  335s] E           Failed: DID NOT RAISE <class 'ValueError'>
[  335s] 
[  335s] nilearn/regions/tests/test_region_extractor.py:367: Failed
```


Discussion:
Thanks for reporting this!
which versions of numpy, nibabel and pytest are you using?

thanks

* numpy: 1.19.1
* nibabel: not used
* pytest: 6.0.1
Thx.
@jeromedockes are you looking at this ? Best,
> @jeromedockes are you looking at this ? Best,

I haven't looked at it yet. I can give it a try but if by any chance
someone who has easy access to an arm machine wants to debug it that
would be great

I had a look and the difference whether the data type is ```int``` or ```float``` to take in ```np.nan``` or ```np.inf```.
See the example below which can be run on any platform either aarch64 or x86_64

```python
import numpy as np

rand_gen=np.random.RandomState(0)
data = rand_gen.standard_normal((13, 11, 12)) > 0.2

int_data_dtype = data.astype(np.int)
float_data_dtype = data.astype(np.float)

int_data_dtype[:, 5, 7] = np.nan
int_data_dtype[:, 2, 4] = np.inf

print(int_data_dtype[:, 5, 7])
print(int_data_dtype[:, 2, 4])

float_data_dtype[:, 5, 7] = np.nan
float_data_dtype[:, 2, 4] = np.inf

print(float_data_dtype[:, 5, 7])
print(float_data_dtype[:, 2, 4])

# ARM-like infrastructure counts np.inf as -1 for integer data type
```
The idea would be to change dtype of the data from int to float wherever the tests are failing.


Thx @KamalakerDadi 
Can you start working on it ?
Thx,

Resolving patch:
diff --git a/nilearn/masking.py b/nilearn/masking.py
--- a/nilearn/masking.py
+++ b/nilearn/masking.py
@@ -44,7 +44,7 @@ def _load_mask_img(mask_img, allow_empty=False):
         boolean version of the mask
     """
     mask_img = _utils.check_niimg_3d(mask_img)
-    mask = get_data(mask_img)
+    mask = _safe_get_data(mask_img, ensure_finite=True)
     values = np.unique(mask)
 
     if len(values) == 1:


Test patch:
diff --git a/nilearn/input_data/tests/test_nifti_labels_masker.py b/nilearn/input_data/tests/test_nifti_labels_masker.py
--- a/nilearn/input_data/tests/test_nifti_labels_masker.py
+++ b/nilearn/input_data/tests/test_nifti_labels_masker.py
@@ -117,8 +117,9 @@ def test_nifti_labels_masker_with_nans_and_infs():
     labels_img = data_gen.generate_labeled_regions((13, 11, 12),
                                                    affine=np.eye(4),
                                                    n_regions=n_regions)
-    # nans
-    mask_data = get_data(mask_img)
+    # Introduce nans with data type float
+    # See issue: https://github.com/nilearn/nilearn/issues/2580
+    mask_data = get_data(mask_img).astype(np.float32)
     mask_data[:, :, 7] = np.nan
     mask_data[:, :, 4] = np.inf
     mask_img = nibabel.Nifti1Image(mask_data, np.eye(4))
diff --git a/nilearn/regions/tests/test_region_extractor.py b/nilearn/regions/tests/test_region_extractor.py
--- a/nilearn/regions/tests/test_region_extractor.py
+++ b/nilearn/regions/tests/test_region_extractor.py
@@ -355,14 +355,15 @@ def test_connected_label_regions():
 
     # Test if unknown/negative integers are provided as labels in labels_img,
     # we raise an error and test the same whether error is raised.
-    labels_data = np.zeros(shape, dtype=np.int)
+    # Introduce data type of float, see issue: https://github.com/nilearn/nilearn/issues/2580
+    labels_data = np.zeros(shape, dtype=np.float32)
     h0 = shape[0] // 2
     h1 = shape[1] // 2
     h2 = shape[2] // 2
     labels_data[:h0, :h1, :h2] = 1
     labels_data[:h0, :h1, h2:] = 2
     labels_data[:h0, h1:, :h2] = 3
-    labels_data[:h0, h1:, h2:] = 4
+    labels_data[:h0, h1:, h2:] = -4
     labels_data[h0:, :h1, :h2] = 5
     labels_data[h0:, :h1, h2:] = 6
     labels_data[h0:, h1:, :h2] = np.nan