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 function that allows to map values onto atlas regions #3824

Open
1 task done
JohannesWiesner opened this issue Jul 7, 2023 · 6 comments
Open
1 task done
Labels
Enhancement for feature requests

Comments

@JohannesWiesner
Copy link
Contributor

JohannesWiesner commented Jul 7, 2023

Is there an existing issue for this?

  • I have searched the existing issues

Describe your proposed enhancement in detail.

When working with parcellated brain data one often wants to visualize statistical coefficients for each atlas region (e.g. brain region-behavior relationships). For that one needs to map those coefficients on the atlas indices to obtain a brain image that can then be visualized. I wrote a function that can do this. I am not sure if this is of interest to the Nilearn package? If yes, I could send a PR

Benefits to the change

Function is quite fast thanks to this thread:

https://stackoverflow.com/a/16993364/8792159

Pseudocode for the new behavior, if applicable

import numpy as np
from nilearn.image import new_img_like

def map_on_atlas(atlas_img,mapping_dict,background_label=0):
    '''Map values onto atlas regions using a dictionary

    Parameters
    ----------
    atlas_img : niimg-like object
        An atlas image.
    mapping_dict : dict
        A dictionary that maps atlas region indices onto values for each
        region. Must not contain the background label.
    background_label : int or float, optional
        Label used in atlas_img to represent background. Default=0.

    Returns
    -------
    niimg-like object
        A niimg-like object with values mapped onto brain regions.

    '''

    # mapping dict must not contain the background label as key
    if background_label in mapping_dict.keys():
        raise KeyError('Dictionary must not contain the background label as key')

    # get data from atlas and make sure that the atlas indices are integers
    atlas_img_data = atlas_img.get_fdata().astype(int)

    # map each idx-value combination on atlas data
    # use this approach: https://stackoverflow.com/a/16993364/8792159
    u,inv = np.unique(atlas_img_data,return_inverse = True)
    atlas_img_data_mapping = np.array([mapping_dict.get(x,background_label) for x in u])[inv].reshape(atlas_img_data.shape)

    # return nifti-file
    return new_img_like(atlas_img,atlas_img_data_mapping)
@JohannesWiesner JohannesWiesner added the Enhancement for feature requests label Jul 7, 2023
@bthirion
Copy link
Member

bthirion commented Jul 7, 2023

Thx.
I'm wondering: You can't do that with the inverse_transform method of a masker ?

@JohannesWiesner
Copy link
Contributor Author

Ah, true. Note, however, that this method would also work if you just have one vector and an atlas image (e.g. your colleague gives you the coefficients and the atlas image that they used throughout their analysis). A masker object must always be fitted before being able to call inverse_transform()

@htwangtw
Copy link
Member

htwangtw commented Jul 7, 2023

That is true but I don't see why is that a problem to Bertrand's suggestion.
You can feed any array of the values you want to inverse_transform() and get the new map.

Here's how you do it with a test to show it works

import numpy as np
from nilearn.datasets import fetch_atlas_basc_multiscale_2015
from nilearn.maskers import NiftiLabelsMasker
from nilearn.image import load_img

mist = fetch_atlas_basc_multiscale_2015(resolution=12)
mist_nii = load_img(mist.map)
new_values = np.random.randint(100, size=12)
old_values = np.unique(mist_nii.dataobj)[1:]  # the first unique value is 0 (background)

# replace value
mist_masker = NiftiLabelsMasker(mist.map).fit()
new_mask_nii= mist_masker.inverse_transform(new_values)

# test if the first old value has been replaced by the first new assigned value
index_of_original = np.where(mist_nii.dataobj == old_values[0])
index_of_new = np.where(new_mask_nii.dataobj == new_values[0])

for i in range(len(index_of_original)):
    np.testing.assert_array_equal(index_of_new[i], index_of_original[i])

@JohannesWiesner
Copy link
Contributor Author

You're right! Haven't thought about the fact, that you don't need to pass anything to .fit(). Closing this issue

@JohannesWiesner
Copy link
Contributor Author

JohannesWiesner commented May 14, 2024

Hey, I am gonna re-open this issue because I do think there's a use-case for my function. A lot of my brain-behavior analyses contain penalization techniques or other steps where certain brain regions are being discarded. In this case, I
1.) can't use the inverse_transform() method won't work here because new_values might only contain a subset of regions (the array only contains a subset of the regions of old_values).
2.) want the discarded regions to be displayed "empty" (aka. fill with background label) in my final statistical map.

@bthirion
Copy link
Member

Thx for reponing the discussion.
Still, isn't the simpleset solution to create a vector of values by imputing the missing values with a given reference ans then applying an inverse_transform() to the imputed vector ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement for feature requests
Projects
None yet
Development

No branches or pull requests

3 participants