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

First draft of surface stuff after discussion during meetings & drop-in hours #3799

Merged
merged 9 commits into from Jul 19, 2023

Conversation

jeromedockes
Copy link
Member

as discussed with @Remi-Gau and @ymzayek
this adds the surface image and maskers module in a nilearn.experimental module

for now this is just a draft of the basic functionality, some features are missing as well as tests and an example
also it doesn't pass the pre-commit checks (missing docstrings)

the file in tests/example.py is the notebook that was used for discussion and is also in nilearn_sandbox and it should run without issues

@jeromedockes jeromedockes marked this pull request as draft July 3, 2023 14:55
@github-actions
Copy link
Contributor

github-actions bot commented Jul 3, 2023

👋 @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
Member

@ymzayek ymzayek left a comment

Choose a reason for hiding this comment

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

First run through the core API and it looks good so far. Next I'll go through the masker and plotting APIs.

)


def load_fsaverage(mesh_name: str = "fsaverage5") -> Dict[str, PolyMesh]:
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add argument for mesh type so that not all meshes are loaded

Copy link
Member Author

Choose a reason for hiding this comment

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

right, I remember we thought about that. but I wonder if it will result in user having to call that function several times, as it is quite frequent to need 2 or 3 of them -- eg pial and white matter for volume to surface projection, then inflated for plotting? but at the same time when only 1 is needed adding the argument would make it faster

nilearn/experimental/surface/_surface_image.py Outdated Show resolved Hide resolved
return _io.read_mesh(self._file)["faces"]


class InMemoryMesh(Mesh):
Copy link
Member

Choose a reason for hiding this comment

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

Why not just expand the type of input for class Mesh since load_surf_mesh that's called in read_mesh takes in loaded arrays

Copy link
Member Author

Choose a reason for hiding this comment

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

for now it uses load_surf_mesh just so we can focus on the surface image and maskers first and deal with the io later. but I think it might simplify things to have a distinction between files on disk and in-memory meshes, to avoid (i) functions like load_surf_mesh that have parameters that can be of several completely different types and (ii) a surface image that has several redundant/alternative attributes, some of which can be missing, and always having to deal with those multiple possible configurations

Copy link
Member

@ymzayek ymzayek left a comment

Choose a reason for hiding this comment

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

Looking good so far!

nilearn/experimental/surface/_plotting.py Outdated Show resolved Hide resolved


@dataclasses.dataclass
class SurfaceImage:
Copy link
Member

Choose a reason for hiding this comment

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

Does it happen that an analysis pipeline uses just one hemisphere? If so and it's common we can consider PolyData keys to also map to a path and then set a method within this class to load a specific hemisphere array

Copy link
Member Author

Choose a reason for hiding this comment

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

I think we will often need all the data, unlike the meshes which we won't use most of the time, so I'd rather keep things a bit simpler for now

Copy link
Member Author

Choose a reason for hiding this comment

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

btw we can easily have a SurfaceImage that contains only one hemisphere (the polymesh and polydata would have just 1 element each). that can be done when creating the image or if it's useful we could have methods to extract one part of the image something like full_img.get_part("left_hemisphere")

@Remi-Gau
Copy link
Collaborator

@jeromedockes
I merged main into your branch to make those failing tests disappear (hopefully)

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.

The API looks great. In need to take more time to look at the internals of this.

mask_img_: SurfaceImage | None
output_dimension_: int | None

def _fit_mask_img(self, img: SurfaceImage) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand what to expect from this method. A short docstring could help.

self._coordinates = coordinates
self._faces = faces
self.n_vertices = coordinates.shape[0]

Copy link
Member

Choose a reason for hiding this comment

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

Do we need some checks that faces and coordinates are consistent ?

@Remi-Gau
Copy link
Collaborator

Remi-Gau commented Jul 12, 2023

@jeromedockes I merged main into your branch to make those failing tests disappear (hopefully)

tests are still failing

@ymzayek and I suppose this is because of the monkeypatch in the example

this leads to a more generic question - that we don't have to answer right now but it would be good to get to it at some point: where do we keep examples for experimental features?

quick look at scikit-learn shows that there is doc for the experimental features but not necessarily examples.

https://scikit-learn.org/stable/metadata_routing.html#metadata-routing

possibility: have the example in our examples folder but do not include it in the doc build.

@codecov
Copy link

codecov bot commented Jul 19, 2023

Codecov Report

Merging #3799 (ab00401) into main (91218eb) will decrease coverage by 0.65%.
The diff coverage is 41.11%.

@@            Coverage Diff             @@
##             main    #3799      +/-   ##
==========================================
- Coverage   91.59%   90.95%   -0.65%     
==========================================
  Files         134      139       +5     
  Lines       15694    15874     +180     
  Branches     3270     3294      +24     
==========================================
+ Hits        14375    14438      +63     
- Misses        770      888     +118     
+ Partials      549      548       -1     
Flag Coverage Δ
macos-latest_3.10 90.94% <41.11%> (-0.58%) ⬇️
macos-latest_3.11 ?
macos-latest_3.8 90.91% <41.11%> (-0.58%) ⬇️
macos-latest_3.9 90.91% <41.11%> (-0.58%) ⬇️
ubuntu-latest_3.10 ?
ubuntu-latest_3.11 ?
ubuntu-latest_3.8 ?
ubuntu-latest_3.9 ?
windows-latest_3.10 ?
windows-latest_3.11 90.89% <41.11%> (-0.58%) ⬇️
windows-latest_3.8 ?
windows-latest_3.9 90.85% <41.11%> (-0.58%) ⬇️

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

Impacted Files Coverage Δ
nilearn/experimental/surface/_datasets.py 25.00% <25.00%> (ø)
nilearn/experimental/surface/_maskers.py 25.88% <25.88%> (ø)
nilearn/experimental/surface/_surface_image.py 64.81% <64.81%> (ø)
nilearn/experimental/surface/_io.py 66.66% <66.66%> (ø)
nilearn/experimental/surface/__init__.py 100.00% <100.00%> (ø)

... and 5 files with indirect coverage changes

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@htwangtw htwangtw merged commit b9a2a82 into nilearn:main Jul 19, 2023
26 of 29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants