[ENH]: Obtain subject/image-specific mask for ParcelAggregation by use of a function #170

Closed
opened 2023-01-05 07:48:14 +00:00 by LeSasse · 15 comments
LeSasse commented 2023-01-05 07:48:14 +00:00 (Migrated from github.com)

Are you requiring a new dataset or marker?

  • I understand this is not a marker or dataset request

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

### Are you requiring a new dataset or marker? - [X] I understand this is not a marker or dataset request ### Which feature do you want to include? Currently it is possible to give the [junifer ParcelAggregation](https://juaml.github.io/junifer/main/api/markers.html#junifer.markers.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](https://nilearn.github.io/dev/modules/masking.html) 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_
LeSasse commented 2023-01-11 14:52:00 +00:00 (Migrated from github.com)

@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.

@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.
fraimondo commented 2023-01-11 15:11:09 +00:00 (Migrated from github.com)

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:

  1. 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.

  2. 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.

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: 1) 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. 2) 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.
LeSasse commented 2023-01-11 15:14:11 +00:00 (Migrated from github.com)

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

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
LeSasse commented 2023-01-11 15:18:23 +00:00 (Migrated from github.com)

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

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
fraimondo commented 2023-01-12 08:18:14 +00:00 (Migrated from github.com)

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

That's what I thought. If it's not in the dataset, we can add the mask in the preprocessing step.

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

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.

> 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 That's what I thought. If it's not in the dataset, we can add the mask in the preprocessing step. > 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 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.
fraimondo commented 2023-01-12 08:18:46 +00:00 (Migrated from github.com)

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

@LeSasse Can you propose a list of names (and maps to the corresponding nilearn functions)?

> 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 @LeSasse Can you propose a list of names (and maps to the corresponding nilearn functions)?
LeSasse commented 2023-01-12 08:51:59 +00:00 (Migrated from github.com)

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 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.
LeSasse commented 2023-01-12 09:04:40 +00:00 (Migrated from github.com)

The question is how to deal with setting different masks at different places. Do we allow that? @kaurao: what do you think?

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.

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.

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.

> The question is how to deal with setting different masks at different places. Do we allow that? @kaurao: what do you think? 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. > 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. 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.
kaurao commented 2023-01-12 10:12:44 +00:00 (Migrated from github.com)

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_mask and 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.

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_mask` and 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.
LeSasse commented 2023-01-12 12:18:43 +00:00 (Migrated from github.com)

Yes, intersection rather than union

Yes, intersection rather than union
fraimondo commented 2023-01-13 10:41:31 +00:00 (Migrated from github.com)

yes, that's what I meant. "and"

yes, that's what I meant. "and"
LeSasse commented 2023-01-13 10:45:01 +00:00 (Migrated from github.com)

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.

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.
kaurao commented 2023-01-13 11:21:39 +00:00 (Migrated from github.com)

my suggestion is to use the nilearn.masking.intersect_masks function which gives us flexibility and also compatibility with nilearn.

my suggestion is to use the `nilearn.masking.intersect_masks` function which gives us flexibility and also compatibility with nilearn.
LeSasse commented 2023-03-28 08:47:24 +00:00 (Migrated from github.com)

@fraimondo @synchon I think this issue has been solved with #174 and #175

@fraimondo @synchon I think this issue has been solved with #174 and #175
fraimondo commented 2023-03-31 08:11:47 +00:00 (Migrated from github.com)

I think too. Closing!

I think too. Closing!
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
juaml/junifer#170
No description provided.