[ENH]: Obtain subject/image-specific mask for ParcelAggregation by use of a function #170
Labels
No labels
CRITICAL
Stale
WIP
bug
concept
coordinate
dataset
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
maintenance
maps
marker
mask
on hold
parcellation
preprocess
question
ready
storage
template-space
triage
wontfix
No milestone
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
juaml/junifer#170
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Are you requiring a new dataset or marker?
Which feature do you want to include?
Currently it is possible to give the junifer ParcelAggregation in-built binary masks to determine which voxels should be considered for aggregating the data. However, nilearn provides a few useful functions to obtain image-specific masks for different use cases. Similarly, some datasets provide some subject-specific masks (for example fMRIprep provides masks in a format like "sub-{sub}_task-{task}_space-{space}_desc-brain_mask.nii.gz") that may be useful/desirable to include.
How do you imagine this integrated in junifer?
It would be very useful to give the ParcelAggregation "mask" parameter a function that returns a mask like this (either by passing a function or by passing a function name as a string similar to passing an aggregation function). This would of course also require another parameter like "mask_params" as a dictionary that can be used to hand over keyword arguments to the masking functions. Similarly, it would be useful to be able to configure this for other parts of the pipeline that use binary masks (for example "clean_img" in the preprocessing module.)
Do you have a sample code that implements this outside of junifer?
No response
Anything else to say?
No response
@fraimondo i think an even bigger underlying issue that I had not thought of before is that the mask used during the cleaning/preprocessing is/can be a different one, from the mask used during marker computation/parcellation which I guess can lead to uncleaned voxels being included in the marker computation.
Indeed this is an issue that we can't really solve. If the user does not specify a mask, it uses
compute_brain_mask. If there is another mask in the parcel/sphere/marker/etc which uses unclean voxels, then there's an issue.This is also the case if you preprocess a BOLD and you do not apply any mask in the Parcel/Sphere aggregation. If the parcel has voxels that are not part of the computed brain mask (
compute_brain_mask) or the one specified by the user, it will be a mixture of clean and unclean voxels.I can think of two options for the moment:
Set the preprocessor so it cleans voxels inside the mask, but voxels outside the mask are set to NAN. This way they could also be detected further down the stream.
Add a "mask" key/value pair in the "BOLD" value of the data object. So the marker can then apply the same mask, or the intersection between masks.
Number 1 looks like a hack. Number 2 will also allow to have subject-specific masks.
As for what you suggest, it is not possible to have parameters of markers that are not string/int/bool, or List, Dict, Tuple of those.
I think number 2 is what I thought of now upon closer inspection as well, i.e. add a binary mask either available from the dataset or by computing it once, that can then be accessed by all pipeline steps from the data object
Since there are different functions to compute masks provided in nilearn i think it would be good to choose by name i.e. using a string in the yaml file, similar to choosing parcellations by name
That's what I thought. If it's not in the dataset, we can add the mask in the preprocessing step.
Agree, and one of the strings for the preprocessor could be "dataset" or "datagrabber" which means using the mask provided by the datagrabber. If there is no mask, fail.
The question is how to deal with setting different masks at different places. Do we allow that? @kaurao: what do you think?
Case A (without pre-processing, without subject-specific dataset masks): User can set different masks for each marker. This has no problems.
Case B (with pre-processing): User can set masks for preprocessing and for each marker. This could be either 1) restricted so only the pre-processing mask can be set or 2) markers use the "union" of masks (+ parcels if needed).
Case C (without pre-processing, with subject-specific masks): User can set different masks for each marker. It has a conflict with the subject-specific masks, in the same way as case B.
For B, implementing 1 generates an issue: How to differentiate between case B and C. I would go for solution 2: that is, using always the union between the preprocessing/subject-specific masks from the dataset and the parcels/masks for each marker. With this case, we guarantee that every voxel used is a "clean" one.
@LeSasse Can you propose a list of names (and maps to the corresponding nilearn functions)?
i would probably just take the literal name of the function and for now implement it with these three functions until there is demand for more:
"compute_epi_mask" -> https://nilearn.github.io/dev/modules/generated/nilearn.masking.compute_epi_mask.html#nilearn.masking.compute_epi_mask
"compute_brain_mask" -> https://nilearn.github.io/dev/modules/generated/nilearn.masking.compute_brain_mask.html#nilearn.masking.compute_brain_mask
"compute_background_mask" -> https://nilearn.github.io/dev/modules/generated/nilearn.masking.compute_background_mask.html#nilearn.masking.compute_background_mask
I think most of the parameters for the functions should be possible to use as well, the important ones are strings and numbers I think. In the very least i think, for "compute_brain_mask" you can set the mask_type as a string. There is an optional target affine parameter as well, but this is only required if you also want to do resampling, which is not needed in this particular step.
I think, if there is preprocessing, ideally it should fail for markers where preprocessing is expected, but masks are different so unclean voxels can be involved in marker computation. For other markers, that have no such dependencies probably a warning may still be enough.
I tend to favour this actually. I think, best to keep it simple, and I can't imagine many use cases where one would really want to use different masks for different markers (perhaps, if one marker is supposed to be just on grey matter, and one just on white matter), but I think until it comes up, best to keep it simple and correct.
overall, I see it as creating a mask by combining all the options that a user as set. If a user asks let's say a user asks for
compute_epi_maskand also provides a general GM mask then we should take the intersection of the voxels the survive in both. Not a detailed reply but I hope this is enough to proceed? If not then, of course, happy to discuss details.Yes, intersection rather than union
yes, that's what I meant. "and"
actually the nilearn function https://nilearn.github.io/dev/modules/generated/nilearn.masking.intersect_masks.html can allow intersection, union, and thresholds in between. I guess, this is more interesting when there is actually a lot of different masks, rather than in our case where there might be only few masks, but may be still interesting to use.
my suggestion is to use the
nilearn.masking.intersect_masksfunction which gives us flexibility and also compatibility with nilearn.@fraimondo @synchon I think this issue has been solved with #174 and #175
I think too. Closing!