Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH] add plotting functions to the experimental surface module #4235

Open
wants to merge 46 commits into
base: main
Choose a base branch
from

Conversation

jeromedockes
Copy link
Member

@jeromedockes jeromedockes commented Jan 23, 2024

added a placeholder basic plotting function

TODO

  • change PolyMesh, PolyData to constrain keys to be "left" and "right" (required for plotting when we want medial or lateral view)
  • split experimental package into: maskers, plotting, datasets to make them easier to later integrate into the rest of the code base
  • add functions to return fsaverage data (sulcal, curvature...) as SurfaceImage instance with specific mesh (pial, flat...)
  • bg_map of the new plotting functions can be a SurfaceImage instance
  • add method to SurfaceImage to check the presence of a given hemisphere in data and mesh
  • more plotting functions

Examples to adapt:

  • examples/01_plotting/plot_3d_map_to_surface_projection.py
  • examples/01_plotting/plot_surf_atlas.py
  • examples/01_plotting/plot_surf_stat_map.py

@jeromedockes jeromedockes marked this pull request as draft January 23, 2024 18:03
Copy link
Contributor

👋 @jeromedockes 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. In particular, be sure it checks the boxes listed below.

  • PR has an interpretable title.
  • PR links to Github issue with mention Closes #XXXX (see our documentation on PR structure)
  • Code is PEP8-compliant (see our documentation on coding style)
  • Changelog or what's new entry in doc/changes/latest.rst (see our documentation on PR structure)

For new features:

  • There is at least one unit test per new function / class (see our documentation on testing)
  • The new feature is demoed in at least one relevant example.

For bug fixes:

  • There is at least one test that would fail under the original bug conditions.

We will review it as quick as possible, feel free to ping us with questions if needed.

Copy link

codecov bot commented Jan 23, 2024

Codecov Report

Attention: Patch coverage is 80.74866% with 36 lines in your changes are missing coverage. Please review.

Project coverage is 92.03%. Comparing base (abb80ff) to head (a2f0a55).
Report is 49 commits behind head on main.

Files Patch % Lines
nilearn/experimental/plotting/_surface_plotting.py 57.57% 21 Missing and 7 partials ⚠️
nilearn/experimental/surface/_datasets.py 84.00% 2 Missing and 2 partials ⚠️
nilearn/experimental/surface/_surface_image.py 20.00% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4235      +/-   ##
==========================================
+ Coverage   91.85%   92.03%   +0.17%     
==========================================
  Files         144      146       +2     
  Lines       16419    16534     +115     
  Branches     3434     3463      +29     
==========================================
+ Hits        15082    15217     +135     
+ Misses        792      765      -27     
- Partials      545      552       +7     
Flag Coverage Δ
macos-latest_3.10_test_plotting 91.82% <80.74%> (?)
macos-latest_3.11_test_plotting 91.82% <80.74%> (-0.03%) ⬇️
macos-latest_3.8_test_plotting 91.78% <80.74%> (?)
ubuntu-latest_3.10_test_plotting 91.82% <80.74%> (-0.03%) ⬇️
ubuntu-latest_3.11_test_plotting 91.82% <80.74%> (?)
ubuntu-latest_3.12_test_plotting 91.82% <80.74%> (?)
ubuntu-latest_3.12_test_pre 91.82% <80.74%> (?)
ubuntu-latest_3.8_test_min 68.76% <59.32%> (?)
ubuntu-latest_3.8_test_plot_min 91.54% <80.74%> (?)
ubuntu-latest_3.8_test_plotting 91.78% <80.74%> (?)
ubuntu-latest_3.9_test_plotting 91.79% <80.74%> (?)
windows-latest_3.10_test_plotting 91.81% <80.74%> (?)
windows-latest_3.11_test_plotting 91.81% <80.74%> (?)
windows-latest_3.12_test_plotting 91.81% <80.74%> (?)
windows-latest_3.8_test_plotting 91.77% <80.74%> (?)
windows-latest_3.9_test_plotting 91.78% <80.74%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

…` attribute

This allows enforcing the fact that at least one key is present and keys must be
in ["left", "right"], and exposing the `shape` and `n_vertices` attributes
Copy link
Member

@bthirion bthirion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx for kicking that off !

@Remi-Gau Remi-Gau added this to the release 0.11.0 milestone Jan 24, 2024
@Remi-Gau Remi-Gau added Surface Related to surface data or surface analysis. Plotting The issue is related to plotting functionalities. labels Jan 24, 2024
@Remi-Gau
Copy link
Collaborator

Note to ourselves: most likely there will be some merge conflicts with #4229

@Remi-Gau Remi-Gau changed the title [WIP] add some plotting functions to the experimental surface module [WIP][ENH] add some plotting functions to the experimental surface module Jan 24, 2024
@jeromedockes
Copy link
Member Author

Note to ourselves: most likely there will be some merge conflicts with #4229

yes 😰

@Remi-Gau
Copy link
Collaborator

@jeromedockes
to maybe minimize conflicts, maybe check #4229 and let me know if some bits should be adapted to make it easier to merge this after.

@jeromedockes
Copy link
Member Author

jeromedockes commented Jan 24, 2024 via email

@Remi-Gau
Copy link
Collaborator

I will be there so we can plan the next steps then

@Remi-Gau
Copy link
Collaborator

@jeromedockes

added the examples to adapt in the top message of the PR.

I am OK to tackle those in a separate PR if you prefer. This is just to let you know.

@Remi-Gau
Copy link
Collaborator

@jeromedockes
want me to take over this one?

@jeromedockes
Copy link
Member Author

jeromedockes commented Feb 2, 2024 via email

@Remi-Gau
Copy link
Collaborator

Remi-Gau commented Feb 5, 2024

@jeromedockes

Will try to adapt this one first. It cannot take THAT long to revert. #FamousLastWords

@Remi-Gau
Copy link
Collaborator

PR is getting pretty long because of:

  • making copies of the examples to adapt into experimental examples to test the new plotting functions.
  • splitting the experimental module further into datasets, maskers...

Probably worth stopping here for now and make further changes in separate PR.

I can also split the examples in different PRs if easier for review.

@Remi-Gau
Copy link
Collaborator

@jeromedockes if you have time to cross check 😉

Comment on lines +16 to +18
surf_mesh: (
str | list[numpy.array, numpy.array] | Mesh | PolyMesh | None
) = None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that currently surface_mesh can be a str pointing to a file containing a mesh, I think it may be appropriate to also be able to pass a SurfaceImage to surf_mesh

like is done for bg_map

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note sure I follow you here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not 100% sure about this, so to be discussed but it seems that it would convenient if users could directly pass a SurfaceImage instance to this parameter and let nilearn get the Polymesh from the instance rather than forcing users to pass just a Mesh / PolyMesh.

I may be overthinking this but if users can just deal with fewer types of objects (SurfaceImage).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be weird to pass a different image than the one we are plotting just to grab the mesh from it. either the mesh is None in which case we use the mesh that comes with surf_map, or we want to provide a different mesh (eg an inflated surface) in which case we pass surf_mesh, which should be a mesh. if it happens to be stored as part of another image (which is not very likely if it is an inflated or flat surface used just for plotting) the user can always do img.mesh to access it

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okidoki
not a hill I wanna die on and if users request it we can think about changing that later

but then do you think that it makes sense to be able to pass a SurfaceImage for the background map the way I have done?

Comment on lines +58 to +68
img = SurfaceImage(
mesh=fsaverage_meshes["pial"],
data={
"left": surface.vol_to_surf(
stat_img, fsaverage_meshes["pial"]["left"]
),
"right": surface.vol_to_surf(
stat_img, fsaverage_meshes["pial"]["right"]
),
},
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO in another PR: add method to SurfaceImage to handle vol to surf

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

surfaceimage is for holding surface data, i don't think it should be responsible for converting volumes

surf_mesh = surf_map.mesh

surf_map._check_hemi(hemi)
_check_hemi_present(surf_mesh, hemi)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will fail if surf_mesh is not a PolyMesh
fixing this will probably need better define the PolyMesh class

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify the discussion here ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_check_hemi_present is just to make sure that the "PolyMesh" dict has the correct hemisphere

But most of our new code at the moment, does not use the PolyMesh class but simple dictionaries instead, so we cannot do a isinstance(surf_mesh, PolyMesh). So we should probably develop the PolyMesh class, because at the moment it is only PolyMesh = Dict[str, Mesh].

Also at this stage surf_mesh can also be a str, list of two numpy.ndarray, Mesh and none of those cases are handled.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not I could start making those changes in this PR but I was getting afraid of having an even bigger PR

return images


def fetch_destrieux(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testing this will require improving the mocking of the dataset: would probably do this in another PR

@Remi-Gau Remi-Gau marked this pull request as ready for review February 12, 2024 12:51
@Remi-Gau Remi-Gau changed the title [WIP][ENH] add some plotting functions to the experimental surface module [ENH] add some plotting functions to the experimental surface module Feb 12, 2024
Copy link
Member

@bthirion bthirion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs more discussion.

}
for mesh_type, mesh_name in renaming.items()
}
renaming = {"curv": "curvature", "sulc": "sulcal", "thick": "thickness"}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you doing this here ?

Comment on lines +16 to +18
surf_mesh: (
str | list[numpy.array, numpy.array] | Mesh | PolyMesh | None
) = None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note sure I follow you here.

surf_mesh = surf_map.mesh

surf_map._check_hemi(hemi)
_check_hemi_present(surf_mesh, hemi)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify the discussion here ?

Copy link
Member Author

@jeromedockes jeromedockes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok I'll try to have a look this week. My first comment is I don't see the need for the change from

module.py

to

module/
   __init__.py (empty besides grabbing names from _module.py)
   _module.py
   (no other submodules)

Comment on lines +16 to +18
surf_mesh: (
str | list[numpy.array, numpy.array] | Mesh | PolyMesh | None
) = None,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be weird to pass a different image than the one we are plotting just to grab the mesh from it. either the mesh is None in which case we use the mesh that comes with surf_map, or we want to provide a different mesh (eg an inflated surface) in which case we pass surf_mesh, which should be a mesh. if it happens to be stored as part of another image (which is not very likely if it is an inflated or flat surface used just for plotting) the user can always do img.mesh to access it

@Remi-Gau
Copy link
Collaborator

ok I'll try to have a look this week. My first comment is I don't see the need for the change from

module.py

to

module/
   __init__.py (empty besides grabbing names from _module.py)
   _module.py
   (no other submodules)

For this I was mostly thinking of making a bit easier to integrate all this code into the rest of the code base later.

@jeromedockes
Copy link
Member Author

jeromedockes commented Feb 13, 2024 via email

@Remi-Gau
Copy link
Collaborator

Remi-Gau commented Feb 13, 2024

but then do you think that it makes sense to be able to pass a SurfaceImage for the background map the way I have done?
yes because the background map is an actual image that has both a mesh and data, right?

yeah that was my reasoning, but your argument of saying:

"well users should just do img.data"

could also sort of apply: so that's why I was feeling a bit awkward to say you can do it for background data but not meshes...

@@ -2200,13 +2200,12 @@ def fetch_language_localizer_demo_dataset(
- 'description' : :obj:`str`, dataset description

Legacy output
-------------
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removes a warning from the doc build

@Remi-Gau
Copy link
Collaborator

@jeromedockes
I moved things back into a single subpackage.

Let me know if you think that I should start creating "proper" classes for PolyMesh and PolyData in this PR or not

@Remi-Gau Remi-Gau changed the title [ENH] add some plotting functions to the experimental surface module [ENH] add plotting functions to the experimental surface module Feb 28, 2024
@Remi-Gau Remi-Gau added the Priority: high The task is urgent and needs to be addressed as soon as possible. label Apr 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Blocker Plotting The issue is related to plotting functionalities. Priority: high The task is urgent and needs to be addressed as soon as possible. Surface Related to surface data or surface analysis.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[DOC] experimental-plot-surface-image-and-maskers example does not render well
3 participants