Repository: nilearn/nilearn

Issue:
[ENH] `NiftiLabelsMasker` reports use appropriate cut coordinates
Closes #3110 

This PR makes sure the NiftiLabelsMasker's reports display the functional image with appropriate cut coordinates. @walkerped could you see if this works as you expected?

Example:

```python
from nilearn.input_data import NiftiLabelsMasker
from nilearn.image import load_img
from nilearn.datasets import fetch_development_fmri

atlasI = load_img('/home/nicolas/Downloads/MNI152_T1_1mm-RBNST.nii.gz', dtype=int)
labelL = ['Background','RBNST']
data = fetch_development_fmri(n_subjects=1)
masker = NiftiLabelsMasker(
    atlasI, labels=labelL, standardize=False,
    verbose=0, detrend=False
)
masker.fit(data.func[0])
report = masker.generate_report()
report
```
![cut_report](https://user-images.githubusercontent.com/2639645/148212024-5f2b9d1a-e15f-4a41-aaf6-30f63dfa0bbc.png)




Discussion:
👋 @NicolasGensollen Thanks for creating a PR!

Until this PR is ready for review, you can include the [WIP] tag in its title, or leave it as a github draft.

Please make sure it is compliant with our [contributing guidelines](https://nilearn.github.io/development.html#contribution-guidelines). In particular, be sure it checks the boxes listed below.
- [ ] PR has an interpretable title.
- [ ] PR links to Github issue with mention "Closes #XXXX"
- [ ] Code is PEP8-compliant.
- [ ] (Bug fixes) There is at least one test that would fail under the original bug conditions.
- [ ] (New features) There is at least one unit test per new function / class.
- [ ] (New features) The new feature is demoed in at least one relevant example.

We will review it as quick as possible, feel free to ping us with questions if needed.
# [Codecov](https://codecov.io/gh/nilearn/nilearn/pull/3116?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) Report
> Merging [#3116](https://codecov.io/gh/nilearn/nilearn/pull/3116?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) (ef33ee1) into [main](https://codecov.io/gh/nilearn/nilearn/commit/9920f36ce4a0900327443db6195fa13998a8831b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) (9920f36) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/nilearn/nilearn/pull/3116/graphs/tree.svg?width=650&height=150&src=pr&token=KpYArSdyXv&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn)](https://codecov.io/gh/nilearn/nilearn/pull/3116?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn)

```diff
@@           Coverage Diff           @@
##             main    #3116   +/-   ##
=======================================
  Coverage   90.20%   90.20%           
=======================================
  Files         112      112           
  Lines       14444    14445    +1     
  Branches     2960     2960           
=======================================
+ Hits        13029    13030    +1     
  Misses        843      843           
  Partials      572      572           
```


| [Impacted Files](https://codecov.io/gh/nilearn/nilearn/pull/3116?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn) | Coverage Δ | |
|---|---|---|
| [nilearn/input\_data/nifti\_labels\_masker.py](https://codecov.io/gh/nilearn/nilearn/pull/3116/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn#diff-bmlsZWFybi9pbnB1dF9kYXRhL25pZnRpX2xhYmVsc19tYXNrZXIucHk=) | `89.94% <100.00%> (+0.05%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/nilearn/nilearn/pull/3116?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/nilearn/nilearn/pull/3116?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn). Last update [9920f36...ef33ee1](https://codecov.io/gh/nilearn/nilearn/pull/3116?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=nilearn).

@NicolasGensollen I can pick this one up and finish it if you don't mind :)
@ymzayek Absolutely not ! Please go for it ! :)

Resolving patch:
diff --git a/nilearn/maskers/nifti_labels_masker.py b/nilearn/maskers/nifti_labels_masker.py
--- a/nilearn/maskers/nifti_labels_masker.py
+++ b/nilearn/maskers/nifti_labels_masker.py
@@ -324,6 +324,12 @@ def _reporting(self):
 
             img = self._reporting_data['img']
 
+            # compute the cut coordinates on the label image in case
+            # we have a functional image
+            cut_coords = plotting.find_xyz_cut_coords(
+                labels_image, activation_threshold=.5
+            )
+
             # If we have a func image to show in the report, use it
             if img is not None:
                 if self._reporting_data["dim"] == 5:
@@ -335,6 +341,7 @@ def _reporting(self):
                     self._report_content['warning_message'] = msg
                 display = plotting.plot_img(
                     img,
+                    cut_coords=cut_coords,
                     black_bg=False,
                     cmap=self.cmap,
                 )


Test patch:
diff --git a/nilearn/reporting/tests/test_html_report.py b/nilearn/reporting/tests/test_html_report.py
--- a/nilearn/reporting/tests/test_html_report.py
+++ b/nilearn/reporting/tests/test_html_report.py
@@ -50,33 +50,31 @@ def mask(affine_eye):
 
 
 @pytest.fixture
-def niftimapsmasker_inputs():
+def niftimapsmasker_inputs(affine_eye):
     n_regions = 9
     shape = (13, 11, 12)
-    affine = np.diag([2, 2, 2, 1])
     label_img, _ = generate_maps(
-        shape, n_regions=n_regions, affine=affine
+        shape, n_regions=n_regions, affine=affine_eye
     )
     return {"maps_img": label_img}
 
 
 @pytest.fixture
-def input_parameters(masker_class, data_img_3d):
+def input_parameters(masker_class, data_img_3d, affine_eye):
     n_regions = 9
     shape = (13, 11, 12)
-    affine = np.diag([2, 2, 2, 1])
     labels = ["background"]
     labels += [f"region_{i}" for i in range(1, n_regions + 1)]
     if masker_class in (NiftiMasker, MultiNiftiMasker):
         return {"mask_img": data_img_3d}
     if masker_class in (NiftiLabelsMasker, MultiNiftiLabelsMasker):
         labels_img = generate_labeled_regions(
-            shape, n_regions=n_regions, affine=affine
+            shape, n_regions=n_regions, affine=affine_eye
         )
         return {"labels_img": labels_img, "labels": labels}
     elif masker_class in (NiftiMapsMasker, MultiNiftiMapsMasker):
         label_img, _ = generate_maps(
-            shape, n_regions=n_regions, affine=affine
+            shape, n_regions=n_regions, affine=affine_eye
         )
         return {"maps_img": label_img}
 
@@ -253,9 +251,8 @@ def test_nifti_maps_masker_report_image_in_fit(niftimapsmasker_inputs,
     assert html.body.count("<img") == 2
 
 
-def test_nifti_labels_masker_report(data_img_3d, mask):
+def test_nifti_labels_masker_report(data_img_3d, mask, affine_eye):
     shape = (13, 11, 12)
-    affine = np.diag([2, 2, 2, 1])
     n_regions = 9
     labels = (
         ['background'] +
@@ -266,7 +263,7 @@ def test_nifti_labels_masker_report(data_img_3d, mask):
                         'size (in mm^3)',
                         'relative size (in %)']
     labels_img = generate_labeled_regions(
-        shape, affine=affine, n_regions=n_regions
+        shape, affine=affine_eye, n_regions=n_regions
     )
     labels_img_floats = new_img_like(
         labels_img, get_data(labels_img).astype(float)
@@ -332,7 +329,7 @@ def test_nifti_labels_masker_report(data_img_3d, mask):
         assert_almost_equal(
             masker._report_content['summary']['size (in mm^3)'][r - 1],
             expected_region_sizes[r]
-            * np.abs(np.linalg.det(affine[:3, :3])))
+            * np.abs(np.linalg.det(affine_eye[:3, :3])))
 
     # Check that region labels are no displayed in the report
     # when they were not provided by the user.
@@ -347,6 +344,21 @@ def test_nifti_labels_masker_report(data_img_3d, mask):
             assert len(masker._report_content['summary'][col]) == n_regions
 
 
+@pytest.mark.parametrize("masker_class", [NiftiLabelsMasker])
+def test_nifti_labels_masker_report_cut_coords(
+    masker_class, input_parameters, data_img_3d
+):
+    """Test cut coordinate are equal with and without passing data to fit."""
+    masker = masker_class(**input_parameters, reports=True)
+    # Get display without data
+    masker.fit()
+    display = masker._reporting()
+    # Get display with data
+    masker.fit(data_img_3d)
+    display_data = masker._reporting()
+    assert display[0].cut_coords == display_data[0].cut_coords
+
+
 def test_4d_reports(mask, affine_eye):
     # Dummy 4D data
     data = np.zeros((10, 10, 10, 3), dtype="int32")