A specific null model for coordinate-based meta-analyses
WARNING: This functionality is experimental and requires further testing!
Requires:
pip install nimare
Coordinate-based meta-analysis (CBMA) aggregates reported peak activation coordinates across many neuroimaging studies to identify brain regions that are consistently activated for a given cognitive domain or clinical population. NiMARE is a Python library for neuroimaging meta-analyses, including ALE (Activation Likelihood Estimation) — this notebook uses NiMARE to run an ALE analysis on a pain dataset and then feeds the result into NiSpace for colocalization with PET receptor reference maps.
[2]:
import tqdm.notebook
tqdm.notebook.tqdm = tqdm.tqdm
import numpy as np
import pandas as pd
import nimare
from nimare.meta.cbma import ALE
from nimare.correct import FWECorrector
from nimare.tests.utils import get_test_data_path
from nispace import NiSpace, null_maps_from_nimare, get_binary_cluster_map
from nispace.datasets import fetch_reference
from nispace.plotting import brainplot
from nispace.workflows import nimare_colocalization, colocalization
# parcellation to use
PARC = "Yan200+TianS2"
Why a special null model for meta-analytic maps?
Standard spatial null models may test the wrong thing here. Moran spectral randomization and spin tests generate surrogates that preserve the spatial autocorrelation (SA) of the observed map. They ask: “is this colocalization stronger than we would expect from two spatially autocorrelated maps of similar smoothness, correlated by chance?” This is the right question for a single observed brain image — a PET scan, an fMRI contrast, a group-level Cohen’s d map — where SA is an intrinsic property of the measurement.
ALE maps have a different generative model. An ALE map is the output of a process: study peak coordinates → Gaussian activation kernels → summed probability field. The SA in the resulting image reflects the spatial clustering of reported peaks across studies, not measurement noise or biological SA. When you ask whether a pain ALE map colocalizes with serotonin receptor density, your hypothesis may often be: do the brain regions consistently reported in the pain literature overlap with serotonin-rich areas more than would be expected if those peak coordinates had no specific spatial preference? This is a question about the generative process — the distribution of input coordinates — not about the spatial smoothness of the output image.
The coordinate-sampling null. NiMARE’s own FWE correction already answers this question internally: for each permutation it replaces all study coordinates with random draws from the brain mask and recomputes the ALE map. null_maps_from_nimare() re-implements this loop at the parcellated level, so the same principled null can be used for colocalization testing in NiSpace. We will compare it with the standard Moran-on-X null empirically below — the two approaches are complementary and test
subtly different hypotheses.
ALE analysis: the pain dataset
We use NiMARE’s built-in pain test dataset — a collection of peak activation coordinates from pain neuroimaging studies.
[3]:
# Load the NiMARE pain dataset
dset_path = get_test_data_path() + "/test_pain_dataset.json"
dset = nimare.dataset.Dataset(dset_path)
print(f"Dataset: {len(dset.ids)} studies, {len(dset.coordinates)} peak coordinates")
Dataset: 21 studies, 243 peak coordinates
The ALE maps: continuous and binary
Two output types are relevant for NiSpace colocalization:
result.get_map("stat"))get_binary_cluster_map(corrected))Both can serve as Y in NiSpace. The continuous map preserves more spatial information; the binary map focuses colocalization on the statistically significant regions only.
Note on thresholding: NiMARE’s cluster-corrected logp maps contain values for all suprathreshold clusters, including non-significant ones (0 < logp < 1.301 for α=0.05).
get_binary_cluster_map()applies the correct thresholdlogp ≥ −log10(α)— using a plain> 0threshold would include far too many voxels.
[4]:
# Run ALE
ale_result = ALE().fit(dset)
print(f"ALE result maps: {[k for k, v in ale_result.maps.items() if v is not None]}")
# FWE cluster correction (1000 iterations; may take a few minutes)
ale_fwe = FWECorrector(method="montecarlo", voxel_thresh=0.001, n_iters=1000, n_cores=-1)
ale_corrected = ale_fwe.transform(ale_result)
# get the results maps
ale_img = ale_result.get_map("stat")
binary_img = get_binary_cluster_map(ale_corrected, cluster_stat="size", alpha=0.05)
print(f"Significant voxels in binary map: {int(binary_img.get_fdata().sum())}")
INFO:nimare.dataset:Retaining 19/21 studies
INFO:nimare.correct:Using correction method implemented in Estimator: nimare.meta.cbma.ale.ALE.correct_fwe_montecarlo.
ALE result maps: ['stat', 'p', 'z']
100%|███████████████████████████████████████████████████████████████████████████████| 1000/1000 [01:30<00:00, 11.01it/s]
INFO:nimare.meta.cbma.base:Using null distribution for voxel-level FWE correction.
INFO:nispace.helpers.nimare:Binary cluster map from 'logp_desc-size_level-cluster_corr-FWE_method-montecarlo' (logp >= 1.3010, alpha=0.05): 2436 significant voxels.
Significant voxels in binary map: 2436
[5]:
# Continuous ALE stat map
brainplot(ale_img, title="Pain ALE stat map (continuous)", colorbar=True, threshold=0)
WARNING:nispace.plotting:Brain plotting in NiSpace is experimental. If things look off, feel free to raise a GitHub issue!
[5]:
(<Figure size 720x180 with 6 Axes>, [<Axes: >])
[6]:
# FWE-corrected binary cluster map
brainplot(binary_img, title="Pain ALE — FWE cluster map (binary, α=0.05)", colorbar=False)
WARNING:nispace.plotting:Brain plotting in NiSpace is experimental. If things look off, feel free to raise a GitHub issue!
[6]:
(<Figure size 720x180 with 5 Axes>, [<Axes: >])
NiMARE coordinate-sampling null (continuous mode)
Now let’s use the coordinate-sampling null. Y is the same ALE stat map; the null maps are generated by re-running ALE with random coordinates.
Our main function: nispace.helpers.null_maps_from_nimare
map_label must match y_labels. The dict returned by null_maps_from_nimare() has the label as key. NiSpace looks up the null array by matching this key to the Y-map label. A mismatch raises an error.
1: Fetch reference data
[7]:
# Load PET receptor reference maps (cortical, Yan200 parcellation)
pet_maps = fetch_reference(
"pet",
parcellation=PARC,
collection="UniqueTracers",
print_references=False
)
print(f"PET maps: {pet_maps.shape[0]} receptors, {pet_maps.shape[1]} parcels")
INFO:nispace.datasets:Loading pet maps.
INFO:nispace.datasets:Loading integrated collection 'UniqueTracers' for dataset 'pet'.
INFO:nispace.datasets:Filtering maps by collection.
INFO:nispace.datasets:Loading and inner-merging data parcellated with 'Yan200' and 'TianS2'
PET maps: 29 receptors, 232 parcels
2: Generate NiMARE null maps
[8]:
# Generate coordinate-sampling null maps (re-runs ALE with random coordinates)
null_maps = null_maps_from_nimare(
ale_result,
parcellation=PARC,
outcome_map="stat",
map_label="pain_ale", # must match y_labels above
observed_map=ale_img, # triggers sanity check (r ≥ 0.99)
n_perm=1000,
seed=42,
n_proc=-1,
)
print(f"Null maps shape: {null_maps['pain_ale'].shape}")
WARNING:nispace.helpers.nimare:null_maps_from_nimare() is experimental. The API and behaviour may change in future releases without notice.
INFO:nispace.helpers.nimare:Fetching parcellation 'Yan200+TianS2' for null map generation.
INFO:nispace.core.parcellation:Building combined Parcellation 'Yan200+TianS2' from library.
INFO:nispace.core.parcellation: Common MNI space(s) for combined: ['MNI152NLin2009cAsym', 'MNI152NLin6Asym'].
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin6Asym'.
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsLR' (for spin tests).
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsaverage' (for spin tests).
INFO:nispace.core.parcellation:Combined parcellation 'Yan200+TianS2' ready. MNI space(s): ['MNI152NLin2009cAsym', 'MNI152NLin6Asym']. Cx surface space(s) for spins: ['fsLR', 'fsaverage'].
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': validation passed.
INFO:nispace.helpers.nimare:Pre-resampling parcellation to ALE native space.
INFO:nispace.helpers.nimare:Observed-map sanity check passed (r=1.000).
INFO:nispace.helpers.nimare:Generating 1000 continuous null maps from NiMARE ALE estimator (n_proc=-1).
NiMARE null maps: 100%|█████████████████████████████████████████████████████████████| 1000/1000 [02:04<00:00, 8.03it/s]
INFO:nispace.helpers.nimare:Done. null_maps['pain_ale'] shape: (1000, 232)
INFO:nispace.helpers.nimare:=== Validation (continuous mode) ===
INFO:nispace.helpers.nimare: Empirical p (our null) vs NiMARE parametric p-map: Spearman r=0.973 (p=0.0000)
INFO:nispace.helpers.nimare: Note: NiMARE p is voxelwise-approximate; ours is parcel-level Monte Carlo — perfect agreement is not expected, but r should be high.
Null maps shape: (1000, 232)
3: Run the analysis
[9]:
nsp = nimare_colocalization(
x=pet_maps,
y={"pain_ale": ale_img},
parcellation=PARC,
seed=42,
n_proc=-1,
n_perm=1000,
nimare_nulls=null_maps, # here, we pass the nimare nulls
return_nispace_only=True
)
INFO:nispace.api:*** NiSpace.fit() - Data extraction and preparation. ***
INFO:nispace.core.parcellation:Building combined Parcellation 'Yan200+TianS2' from library.
INFO:nispace.core.parcellation: Common MNI space(s) for combined: ['MNI152NLin2009cAsym', 'MNI152NLin6Asym'].
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin6Asym'.
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsLR' (for spin tests).
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsaverage' (for spin tests).
INFO:nispace.core.parcellation:Combined parcellation 'Yan200+TianS2' ready. MNI space(s): ['MNI152NLin2009cAsym', 'MNI152NLin6Asym']. Cx surface space(s) for spins: ['fsLR', 'fsaverage'].
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': validation passed.
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': active space set to 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation:Combined parcellation: cx-LH parcels = 100, cx-RH parcels = 100.
INFO:nispace.api:Checking input data for 'x' (should be, e.g., PET data):
INFO:nispace.io:Input type: DataFrame, assuming parcellated data with shape (n_files/subjects/etc, n_parcels).
WARNING:nispace.io:Parcellated data contains nan values!
INFO:nispace.api:Got 'x' data for 29 x 232 parcels.
INFO:nispace.api:Checking input data for 'y' (should be, e.g., subject data):
INFO:nispace.io:Input type: dict, assuming (img_name, img) pairs for imaging data.
INFO:nispace.io:Background (bg) handling: background_value=False; reporting bg-only parcels: False
INFO:nispace.io:Parcellating imaging data.
Parcellating (-1 proc): 100%|████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 913.99it/s]
INFO:nispace.api:Got 'y' data for 1 x 232 parcels.
INFO:nispace.api:Z-standardizing 'X' data.
INFO:nispace.api:*** NiSpace.colocalize() - Estimating X & Y colocalizations. ***
INFO:nispace.api:Running 'spearman' colocalization.
INFO:nispace.api:Pre-ranking X and Y data.
Colocalizing (spearman, -1 proc): 100%|█████████████████████████████████████████████████| 1/1 [00:00<00:00, 2154.24it/s]
INFO:nispace.api:*** NiSpace.permute() - Estimate exact non-parametric p values. ***
INFO:nispace.api:Permutation of: Y maps.
INFO:nispace.api:Using default split null method (cx='moran', sc='moran') (cx space: 'MNI152NLin6Asym', sc space: 'MNI152NLin6Asym').
INFO:nispace.api:Loading observed colocalizations (method = 'spearman').
INFO:nispace.core.permute:Wrapping provided dict null maps into NullMaps.
INFO:nispace.core.permute:Using provided null maps.
Processing null arrays (-1 proc): 100%|████████████████████████████████████████████| 1000/1000 [00:01<00:00, 685.81it/s]
Null colocalizations (spearman, -1 proc): 100%|███████████████████████████████████| 1000/1000 [00:00<00:00, 3081.08it/s]
INFO:nispace.core.permute:Calculating exact p-values (tails = {'rho': 'two'}).
INFO:nispace.plotting:Significance annotation: 11/29 p_uncorrected < 0.05, 1/29 p_meffgalwey < 0.05
3.1 Inspect parcellated maps
[10]:
# observed map (now parcellated)
nsp.plot_brain("y")
# null maps (always parcellated)
brainplot(
null_maps["pain_ale"][:6, :],
parcellation=PARC,
title=[f"null-{i}" for i in range(6)],
threshold=0,
ncols=2
)
WARNING:nispace.plotting:Brain plotting in NiSpace is experimental. If things look off, feel free to raise a GitHub issue!
INFO:nispace.plotting:brainplot: threshold='auto' → 4.604904782143371e-13
INFO:nispace.plotting:brainplot: kind='glass', img_mode='None', surf_space='None', mni_space='MNI152NLin2009cAsym', surf_mesh='inflated'
WARNING:nispace.plotting:Brain plotting in NiSpace is experimental. If things look off, feel free to raise a GitHub issue!
INFO:nispace.core.parcellation:Building combined Parcellation 'Yan200+TianS2' from library.
INFO:nispace.core.parcellation: Common MNI space(s) for combined: ['MNI152NLin2009cAsym', 'MNI152NLin6Asym'].
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin6Asym'.
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsLR' (for spin tests).
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsaverage' (for spin tests).
INFO:nispace.core.parcellation:Combined parcellation 'Yan200+TianS2' ready. MNI space(s): ['MNI152NLin2009cAsym', 'MNI152NLin6Asym']. Cx surface space(s) for spins: ['fsLR', 'fsaverage'].
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': validation passed.
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': active space set to 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation:Combined parcellation: cx-LH parcels = 100, cx-RH parcels = 100.
INFO:nispace.plotting:brainplot: kind='glass', img_mode='None', surf_space='None', mni_space='MNI152NLin2009cAsym', surf_mesh='inflated'
[10]:
(<Figure size 1440x630 with 36 Axes>,
[<Axes: >, <Axes: >, <Axes: >, <Axes: >, <Axes: >, <Axes: >])
3.2: Results
[11]:
rho = nsp.get_colocalizations()
p_nimare = nsp.get_p_values()
print("p_nimare shape (1 ale map x N pet maps):", p_nimare.shape)
p_nimare shape (1 ale map x N pet maps): (1, 29)
Extra: Without workflow function
Why maps_which="Y"? By default, permute("maps") generates surrogates for X (the reference atlas maps) and recomputes correlations against a fixed Y. Here the null model belongs to Y (the meta-analytic image) — we want to ask whether the observed PET receptors correlate with the ALE map more than they would with a random ALE map. maps_which="Y" tells NiSpace to use the provided maps_nulls as surrogates for Y while keeping X fixed.
[12]:
# Set up NiSpace: X=PET receptors, Y=continuous ALE stat map
nsp = NiSpace(
x=pet_maps,
y=ale_img,
y_labels=["pain_ale"],
parcellation=PARC,
seed=42,
n_proc=-1
)
nsp.fit(background_value={"y": False}) # CAVE: BACKGROUND SETTING STRICTLY NECESSARY FOR ALE (Y only)
# Generate nimare nulls (done above -> commented out)
# null_maps = null_maps_from_nimare(
# ale_result,
# parcellation=nsp._parc, # extract the Parcellation object form the fitted NiSpace instance
# outcome_map="stat",
# map_label="pain_ale", # must match y_labels above
# observed_map=ale_img, # triggers sanity check (r ≥ 0.99)
# n_perm=1000,
# seed=42,
# n_proc=-1,
# )
# Run colocalization
nsp.colocalize("spearman")
# Permute Y (not X) using the NiMARE null maps
nsp.permute("maps", maps_which="Y", maps_nulls=null_maps, n_perm=1000, p_tails="two")
# correct for multiple comparisons
nsp.correct_p()
INFO:nispace.core.parcellation:Building combined Parcellation 'Yan200+TianS2' from library.
INFO:nispace.core.parcellation: Common MNI space(s) for combined: ['MNI152NLin2009cAsym', 'MNI152NLin6Asym'].
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin6Asym'.
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsLR' (for spin tests).
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsaverage' (for spin tests).
INFO:nispace.core.parcellation:Combined parcellation 'Yan200+TianS2' ready. MNI space(s): ['MNI152NLin2009cAsym', 'MNI152NLin6Asym']. Cx surface space(s) for spins: ['fsLR', 'fsaverage'].
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': validation passed.
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': active space set to 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation:Combined parcellation: cx-LH parcels = 100, cx-RH parcels = 100.
INFO:nispace.io:Input type: DataFrame, assuming parcellated data with shape (n_files/subjects/etc, n_parcels).
WARNING:nispace.io:Parcellated data contains nan values!
INFO:nispace.io:Input type: list, assuming imaging data.
INFO:nispace.io:Background (bg) handling: background_value=False; reporting bg-only parcels: False
INFO:nispace.io:Parcellating imaging data.
Parcellating (-1 proc): 100%|███████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 1109.90it/s]
Colocalizing (spearman, -1 proc): 100%|█████████████████████████████████████████████████| 1/1 [00:00<00:00, 1782.53it/s]
INFO:nispace.core.permute:Wrapping provided dict null maps into NullMaps.
INFO:nispace.core.permute:Using provided null maps.
Processing null arrays (-1 proc): 100%|███████████████████████████████████████████| 1000/1000 [00:00<00:00, 5083.78it/s]
Null colocalizations (spearman, -1 proc): 100%|███████████████████████████████████| 1000/1000 [00:00<00:00, 7710.05it/s]
INFO:nispace.core.permute:Calculating exact p-values (tails = {'rho': 'two'}).
[12]:
<nispace.api.NiSpace at 0x3037fffd0>
Note: Image validation - the observed_map sanity check
A brief note on the observed_map= parameter in null_maps_from_nimare().
When you pass the same image you fed into NiSpace as y=, the function parcellates it internally using the same parcellater and computes Pearson r against the internally computed reference:
Continuous mode: internal reference is
result.get_map(outcome_map)parcellated with the same parcellater. Pearson r ≥ 0.99 → sanity check passed; r < 0.99 → warning (space or resampling mismatch).Binary cluster mode: internal reference is the observed z-map re-thresholded and cluster-filtered with the same parameters you provided. This confirms that
get_binary_cluster_map()and the internal pipeline use identical settings.
The check is logged (Observed-map sanity check passed (r=...)) or triggers a warning. Always pass observed_map= during development — it catches mismatches before they silently corrupt results.
Comparison to standard spatial colocalization
To put these results in context, we also run the standard colocalization() workflow. Here we again permute Y (using permute_kwargs={"maps_which": "Y"}), but instead of coordinate-sampling, NiSpace generates Moran spectral surrogates of the ALE map — random maps that preserve the spatial autocorrelation of the observed ALE image while scrambling its specific pattern.
This makes the comparison clean: both tests permute Y, but with different null generators:
Moran null on Y: asks whether the spatial autocorrelation structure of the ALE map explains the colocalization
Coordinate-sampling null on Y: asks whether the spatial convergence of peak coordinates explains the colocalization
Because the ALE map is a volumetric image (no surface projection), and the parcellation Yan200+TianS2 is a combined atlas, Moran is auto-selected as the null method.
[13]:
# Standard colocalization workflow (Moran null on X maps)
nsp_standard = colocalization(
x="pet",
y={"pain_ale": ale_img},
parcellation=PARC,
fit_kwargs={"background_value": {"y": False}}, # CAVE: STRICTLY NECESSARY FOR ALE (Y only)
permute_kwargs={"maps_which": "Y"}, # for comparison
seed=42,
n_proc=-1,
return_nispace_only=True,
)
# get results
rho_std = nsp_standard.get_colocalizations()
p_std = nsp_standard.get_p_values()
INFO:nispace.workflows:Loading integrated pet dataset as X data.
INFO:nispace.workflows:Using default collection 'UniqueTracers'.
INFO:nispace.datasets:Loading pet maps.
INFO:nispace.datasets:Loading integrated collection 'UniqueTracers' for dataset 'pet'.
INFO:nispace.datasets:Filtering maps by collection.
INFO:nispace.datasets:Loading and inner-merging data parcellated with 'Yan200' and 'TianS2'
INFO:nispace.datasets:Fetching map info for dataset 'pet'.
INFO:nispace.core.parcellation:Building combined Parcellation 'Yan200+TianS2' from library.
INFO:nispace.core.parcellation: Common MNI space(s) for combined: ['MNI152NLin2009cAsym', 'MNI152NLin6Asym'].
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin6Asym'.
The NiSpace "PET" dataset contains a growing collection of nuclear imaging maps from different
source publications. Most maps were originally accessed via neuromaps (https://neuromaps-
main.readthedocs.io/) and can be downloaded directly via neuromaps using the "original" spaces
"MNIOriginal", "fsaverageOriginal", or "fsLROriginal". Maps requested in a defined space
("MNI152NLin2009cAsym", "MNI152NLin6Asym", "fsaverage", or "fsLR") are sourced from the NiSpace-data
GitHub repo. These maps were directly registered to 2mm MNI152NLin6Asym space, and transformed to
2mm MNI152NLin2009cAsym with a pre-estimated MNI-to-MNI transformation using SynthMorph v4. The
resulting maps were masked with a liberal grey matter mask generated from the Harvard-Oxford atlas
and scaled from 1e-6 to 1. The accompanying metadata table contains detailed information about
tracers, source samples, original publications and data sources, as well as the publication
licenses. Every map should be cited when used. The responsibility for this lies with the user!
- Markello et al., 2022 https://doi.org/10.1038/s41592-022-01625-w
- Hansen et al., 2022 https://doi.org/10.1038/s41593-022-01186-3
- Dukart et al., 2021 https://doi.org/10.1002/hbm.25244
- Hoffmann et al., 2024 https://doi.org/10.1162/imag_a_00197
To ensure reproducibility, note the NiSpace version: 0.0.2b2.dev48+g51f21588b.d20260717 (commit: g51f21588b).
5HT1a [11C]WAY-100635 CC BY-NC-SA 4.0 https://doi.org/10.1016/j.neuroimage.2012.07.001
5HT1b [11C]P943 CC BY-NC-SA 4.0 https://doi.org/10.1016/j.neuroimage.2012.07.001
5HT2a [18F]Altanserin CC BY-NC-SA 4.0 https://doi.org/10.1016/j.neuroimage.2012.07.001
5HT4 [11C]SB207145 CC BY-NC-SA 4.0 https://doi.org/10.1523/JNEUROSCI.2830-16.2016
CAVE: Processed in fsaverage space, use volumetric maps only for subcortex!
5HT6 [11C]GSK215083 CC BY-NC-SA 4.0 https://doi.org/10.2967/jnumed.117.206516; https://doi.org/10.1016/j.pscychresns.2019.111007
5HTT [11C]DASB CC BY-NC-SA 4.0 https://doi.org/10.1016/j.neuroimage.2012.07.001
A4B2 [18F]Flubatine CC BY-NC-SA 4.0 https://doi.org/10.1016/j.neuroimage.2016.07.026; https://doi.org/10.1093/ntr/ntx091
CB1 [11C]OMAR CC BY-NC-SA 4.0 https://doi.org/10.1038/jcbfm.2015.46; https://doi.org/10.1016/j.bpsc.2015.09.008; https://doi.org/10.1016/j.biopsych.2015.08.021; https://doi.org/10.1111/j.1530-0277.2012.01815.x
CMRglu [18F]FDG CC BY-NC-SA 4.0 https://doi.org/10.1126/sciadv.adi7632
COX1 [11C]PS13 CC0 1.0 https://doi.org/10.1007/s00259-020-04855-2; https://doi.org/10.18112/openneuro.ds004401.v1.0.1
D1 [11C]SCH23390 CC BY-NC-SA 4.0 https://doi.org/10.1007/s00259-017-3645-0
D23 [11C]FLB-457 CC BY-NC-SA 4.0 https://doi.org/10.1038/jcbfm.2014.237; https://doi.org/10.1177/0271678X17737693; https://doi.org/10.1038/s41386-019-0456-y; https://doi.org/10.1001/jamapsychiatry.2014.2414; https://doi.org/10.1038/npp.2017.223
DAT [123I]-FP-CIT CC BY-NC-SA 4.0 https://doi.org/10.1038/s41598-018-22444-0
CAVE: SPECT, not PET!
FDOPA [18F]DOPA free https://doi.org/10.33588/imagendiagnostica.901.2
CAVE: Unlike other tracers, [18F]DOPA measures AADC activity (dopamine synthesis), not receptor/transporter density
GABAa [11C]Flumazenil CC BY-NC-SA 4.0 https://doi.org/10.1038/s41598-018-22444-0
GABAa5 [11C]Ro15-4513 CC BY 4.0 https://doi.org/10.1038/s42003-022-03268-1
H3 [11C]GSK189254 CC BY-NC-SA 4.0 https://doi.org/10.1177/0271678X16650697; https://doi.org/10.1038/jcbfm.2009.195
HDAC [11C]Martinostat CC0 1.0 https://doi.org/10.1126/scitranslmed.aaf7551
KOR [11C]LY2795050 CC BY-NC-SA 4.0 https://doi.org/10.1038/s41386-018-0199-1
M1 [11C]LSN3172176 CC BY-NC-SA 4.0 https://doi.org/10.2967/jnumed.120.246967
mGluR5 [11C]ABP688 CC BY-NC-SA 4.0 https://doi.org/10.1007/s00259-018-4252-4
MOR [11C]Carfentanil CC BY-NC-SA 4.0 https://doi.org/10.1038/mp.2017.183
NET [11C]MRB CC BY-NC-SA 4.0 https://doi.org/10.1007/s00259-016-3590-3
NMDA [18F]GE-179 CC BY-NC-SA 4.0 https://doi.org/10.1001/jamaneurol.2022.4352; https://doi.org/10.1016/j.neuroimage.2021.118194; https://doi.org/10.2967/jnumed.113.130641
CAVE: Unlike other tracers, [18F]GE-179 binds to open (active) NMDA receptors!
rCPS [11C]Leucine CC0 1.0 https://doi.org/10.18112/openneuro.ds004654.v1.0.1; https://doi.org/10.18112/openneuro.ds004730.v1.0.0; https://doi.org/10.18112/openneuro.ds004731.v1.0.0; https://doi.org/10.18112/openneuro.ds004733.v1.0.0; https://doi.org/10.1016/j.nbd.2020.104978; https://doi.org/10.1177/0271678X221090997; https://doi.org/10.1038/jcbfm.2009.7; https://doi.org/10.1093/sleep/zsy088; https://doi.org/10.1177/0271678X221121873
SV2A [11C]UCB-J CC BY-NC-SA 4.0 https://doi.org/10.1177/0271678X17724947; https://doi.org/10.2967/jnumed.120.246967; https://doi.org/10.1177/0271678X211004312; https://doi.org/10.1186/s13195-020-00742-y; https://doi.org/10.1177/0271678X20946198; https://doi.org/10.1093/cid/ciab484; https://doi.org/10.1038/s41380-021-01184-0; https://doi.org/10.1016/j.bpsc.2015.09.008; https://doi.org/10.1111/epi.16653; https://doi.org/10.1186/s13550-020-00670-w; https://doi.org/10.1002/alz.12097; https://doi.org/10.1111/epi.14701; https://doi.org/10.1038/s41467-019-09562-7; https://doi.org/10.1001/jamaneurol.2018.1836
TSPO [ 11C]PBR28 MIT https://doi.org/10.1021/acschemneuro.8b00072; https://doi.org/10.5281/zenodo.1174364
VAChT [18F]FEOBV CC BY-NC-SA 4.0 https://doi.org/10.1038/mp.2017.183
VMAT2 [11C]DTBZ CC0 1.0 https://doi.org/10.18112/openneuro.ds002385.v1.1.0; https://doi.org/10.1038/s41467-020-14693-3
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsLR' (for spin tests).
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsaverage' (for spin tests).
INFO:nispace.core.parcellation:Combined parcellation 'Yan200+TianS2' ready. MNI space(s): ['MNI152NLin2009cAsym', 'MNI152NLin6Asym']. Cx surface space(s) for spins: ['fsLR', 'fsaverage'].
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': validation passed.
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': active space set to 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation:Combined parcellation: cx-LH parcels = 100, cx-RH parcels = 100.
INFO:nispace.io:Input type: DataFrame, assuming parcellated data with shape (n_files/subjects/etc, n_parcels).
WARNING:nispace.io:Parcellated data contains nan values!
INFO:nispace.io:Input type: dict, assuming (img_name, img) pairs for imaging data.
INFO:nispace.io:Background (bg) handling: background_value=False; reporting bg-only parcels: False
INFO:nispace.io:Parcellating imaging data.
Parcellating (-1 proc): 100%|███████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 1613.81it/s]
Colocalizing (spearman, -1 proc): 100%|█████████████████████████████████████████████████| 1/1 [00:00<00:00, 1927.53it/s]
INFO:nispace.core.permute:Generating null maps (n = 10000, null_method = 'moran+moran').
INFO:nispace.core.parcellation:Lazy-loading sc dist mat for 'TianS2' (space 'MNI152NLin6Asym').
INFO:nispace.core.parcellation:Lazy-loading cx dist mat for 'Yan200' (space 'fsLR').
INFO:nispace.nulls:Split null method: cx='moran' (200 parcels), sc='moran' (32 parcels).
INFO:nispace.nulls:Null map generation: Assuming n = 1 data vector(s) for n = 200 parcels.
INFO:nispace.nulls:Using provided distance matrix/matrices.
Moran null maps (-1 proc): 100%|████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 1584.55it/s]
INFO:nispace.nulls:Null data generation finished.
INFO:nispace.nulls:Null map generation: Assuming n = 1 data vector(s) for n = 32 parcels.
INFO:nispace.nulls:Using provided distance matrix/matrices.
Moran null maps (-1 proc): 100%|████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 1461.94it/s]
INFO:nispace.nulls:Null data generation finished.
Processing null arrays (-1 proc): 100%|████████████████████████████████████████| 10000/10000 [00:00<00:00, 25819.55it/s]
Null colocalizations (spearman, -1 proc): 100%|████████████████████████████████| 10000/10000 [00:00<00:00, 37175.31it/s]
INFO:nispace.core.permute:Calculating exact p-values (tails = {'rho': 'two'}).
INFO:nispace.plotting:Significance annotation: 6/29 p_uncorrected < 0.05, 0/29 p_meffgalwey < 0.05
The table below shows both p-value columns side by side:
p_moran: Moran null on Y — tests whether the spatial autocorrelation structure of the ALE map explains the colocalizationp_nimare: coordinate-sampling null on Y — tests whether the spatial convergence of peak coordinates in the meta-analysis explains the colocalization
For most receptors the two are broadly consistent. Meaningful divergence between the two p-values indicates that one of these factors is driving the result — scientifically informative.
[14]:
comparison = pd.DataFrame({
"rho": rho.T["pain_ale"],
"p_moran": p_std.T["pain_ale"],
"p_nimare": p_nimare.T["pain_ale"],
}).sort_values("p_nimare")
# show
print("Top 10 by NiMARE p-value:")
display(comparison.head(10))
# plot transformed p values
(
comparison[["p_moran", "p_nimare"]]
.transform(lambda x: -np.log10(x))
.plot(kind="scatter", x="p_moran", y="p_nimare",
title=f"Uncorrected significant: > {-np.log10(0.05):.03f}")
)
Top 10 by NiMARE p-value:
| rho | p_moran | p_nimare | ||
|---|---|---|---|---|
| set | map | |||
| Glutamate | target-mGluR5_tracer-abp688_n-73_dx-hc_pub-smart2019 | 0.301870 | 0.1832 | 0.004 |
| Dopamine | target-DAT_tracer-fpcit_n-174_dx-hc_pub-dukart2018 | 0.311011 | 0.0274 | 0.006 |
| Serotonin | target-5HTT_tracer-dasb_n-18_dx-hc_pub-savli2012 | 0.313451 | 0.0156 | 0.006 |
| Glutamate | target-NMDA_tracer-ge179_n-29_dx-hc_pub-galovic2021 | 0.277631 | 0.0084 | 0.006 |
| General | target-HDAC_tracer-martinostat_n-8_dx-hc_pub-wey2016 | 0.265147 | 0.1396 | 0.008 |
| target-VMAT2_tracer-dtbz_n-76_dx-hc_pub-larsen2020 | 0.304177 | 0.0128 | 0.012 | |
| Noradrenaline/Acetylcholine | target-VAChT_tracer-feobv_n-18_dx-hc_pub-aghourian2017 | 0.238652 | 0.0476 | 0.014 |
| Dopamine | target-FDOPA_tracer-fluorodopa_n-12_dx-hc_pub-garciagomez2018 | 0.263921 | 0.0258 | 0.018 |
| Noradrenaline/Acetylcholine | target-NET_tracer-mrb_n-10_dx-hc_pub-hesse2017 | 0.208625 | 0.2056 | 0.022 |
| Dopamine | target-D23_tracer-flb457_n-55_dx-hc_pub-sandiego2015 | 0.233546 | 0.0536 | 0.028 |
[14]:
<Axes: title={'center': 'Uncorrected significant: > 1.301'}, xlabel='p_moran', ylabel='p_nimare'>
Binary cluster mode: colocalization with the FWE-significant regions
In binary mode, Y is the FWE-corrected cluster image from get_binary_cluster_map(). After parcellation, each parcel receives the fraction of its voxels that fall inside the significant cluster — so parcel values are continuous in [0, 1], not truly binary. The observed map typically has values in the range 0–0.5 (most parcels are entirely outside the cluster, boundary parcels have intermediate fractions).
Why Pearson? Pearson correlation on a fractional cluster-coverage Y is the natural choice: it directly asks “do parcels with more cluster coverage have higher receptor density?” and is the exact formula of the point-biserial correlation when Y is truly binary (0/1). Spearman is less appropriate here because the many tied zeros at the parcel level degrade its sensitivity.
How binary null maps are constructed — and why their values look different from the observed map:
For each coordinate-sampling permutation, the null ALE map is converted to z-scores and thresholded at the same voxel-level CDT (p < 0.001, z ≥ 3.09). The resulting binary voxel map is then parcellated as a coverage fraction — exactly as for the observed map. The FWE cluster extent filter is not applied to null maps. Applying it would make ~95% of null maps entirely empty by construction: the FWE threshold is the 95th percentile of the max cluster size under the null, so only ~5% of null permutations produce a cluster that survives it. Empty null maps make the colocalization test undefined.
The value range difference is therefore expected and meaningful:
Observed map (0→~0.5): real coordinates cluster strongly in specific brain regions → one large focused cluster → high parcel coverage in that region.
Null maps (0→~0.1): random coordinates scatter across the brain → many small clusters → low fractional coverage per parcel.
This contrast — focused real cluster vs. diffuse random activation — is exactly the signal the permutation test captures.
NOTE: Currently, only colocalization with all significant ALE clusters simultaneously is supported. Individual clusters can be isolated by masking the binary image, but this requires manual steps not yet validated in this workflow.
1: Generate binary NiMARE null maps
[15]:
# Generate binary null maps (corrector= enables binary cluster mode)
null_maps_bin = null_maps_from_nimare(
ale_result,
corrected_result=ale_corrected, # passing the FWE corrector enables binary cluster mode
parcellation=nsp._parc,
cluster_stat="size",
voxel_thresh=0.001,
alpha=0.05,
map_label="pain_cluster", # must match y_labels above
observed_map=binary_img, # sanity check: r ≥ 0.99 confirms consistent thresholding
n_perm=1000,
seed=42,
n_proc=-1,
)
WARNING:nispace.helpers.nimare:null_maps_from_nimare() is experimental. The API and behaviour may change in future releases without notice.
INFO:nispace.helpers.nimare:Voxel z-threshold (p<0.001): 3.0902
INFO:nispace.helpers.nimare:Cluster size threshold (FWE alpha=0.05): 105.
INFO:nispace.helpers.nimare:Pre-resampling parcellation to ALE native space.
INFO:nispace.helpers.nimare:Observed-map sanity check passed (r=1.000).
INFO:nispace.helpers.nimare:Generating 1000 binary cluster null maps from NiMARE ALE estimator (n_proc=-1).
INFO:nispace.helpers.nimare: voxel_thresh_z=3.090, min_cluster_voxels=105
NiMARE null maps: 100%|█████████████████████████████████████████████████████████████| 1000/1000 [02:11<00:00, 7.60it/s]
INFO:nispace.helpers.nimare:Done. null_maps['pain_cluster'] shape: (1000, 232)
INFO:nispace.helpers.nimare:=== Validation (cluster mode) ===
INFO:nispace.helpers.nimare: NiMARE FWE size threshold (from corrector): 105
INFO:nispace.helpers.nimare: Our null-derived size threshold (95th pctile of 1000 perms): 102.0
INFO:nispace.helpers.nimare: Ratio (ours / NiMARE): 0.971 (should be ~1.0 for large n_perm; deviations are sampling noise)
INFO:nispace.helpers.nimare: Re-derived cluster map vs observed_map: r=1.0000 (should be ~1.0; low r → threshold mismatch or wrong cluster_stat)
2: Run analysis
[16]:
nsp_bin = nimare_colocalization(
x=pet_maps,
y={"pain_cluster": binary_img},
binary_y=True, # central parameter: sets some specific defaults
parcellation=PARC,
seed=42,
n_proc=-1,
n_perm=1000,
nimare_nulls=null_maps_bin, # here, we pass the nimare nulls
return_nispace_only=True
)
INFO:nispace.core.parcellation:Building combined Parcellation 'Yan200+TianS2' from library.
INFO:nispace.core.parcellation: Common MNI space(s) for combined: ['MNI152NLin2009cAsym', 'MNI152NLin6Asym'].
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin6Asym'.
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsLR' (for spin tests).
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsaverage' (for spin tests).
INFO:nispace.core.parcellation:Combined parcellation 'Yan200+TianS2' ready. MNI space(s): ['MNI152NLin2009cAsym', 'MNI152NLin6Asym']. Cx surface space(s) for spins: ['fsLR', 'fsaverage'].
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': validation passed.
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': active space set to 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation:Combined parcellation: cx-LH parcels = 100, cx-RH parcels = 100.
INFO:nispace.io:Input type: DataFrame, assuming parcellated data with shape (n_files/subjects/etc, n_parcels).
WARNING:nispace.io:Parcellated data contains nan values!
INFO:nispace.io:Input type: dict, assuming (img_name, img) pairs for imaging data.
INFO:nispace.io:Background (bg) handling: background_value=False; reporting bg-only parcels: False
INFO:nispace.io:Parcellating imaging data.
Parcellating (-1 proc): 100%|███████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 1677.72it/s]
Colocalizing (pearson, -1 proc): 100%|██████████████████████████████████████████████████| 1/1 [00:00<00:00, 1953.56it/s]
INFO:nispace.core.permute:Wrapping provided dict null maps into NullMaps.
INFO:nispace.core.permute:Using provided null maps.
Null colocalizations (pearson, -1 proc): 100%|████████████████████████████████████| 1000/1000 [00:00<00:00, 4831.22it/s]
INFO:nispace.core.permute:Calculating exact p-values (tails = {'rho': 'two'}).
INFO:nispace.plotting:Significance annotation: 5/29 p_uncorrected < 0.05, 3/29 p_meffgalwey < 0.05
2.1 Inspect null maps
[17]:
# observed map (parcellated binary -> for each parcel the coverage relative to source cluster)
nsp_bin.plot_brain("y")
# null maps (similarly parcellated binary)
brainplot(
null_maps_bin["pain_cluster"][:6, :],
parcellation=PARC,
title=[f"null-{i}" for i in range(6)],
threshold=0,
ncols=2,
)
WARNING:nispace.plotting:Brain plotting in NiSpace is experimental. If things look off, feel free to raise a GitHub issue!
INFO:nispace.plotting:brainplot: threshold='auto' → 0.0006622516666539013
INFO:nispace.plotting:brainplot: kind='glass', img_mode='None', surf_space='None', mni_space='MNI152NLin2009cAsym', surf_mesh='inflated'
WARNING:nispace.plotting:Brain plotting in NiSpace is experimental. If things look off, feel free to raise a GitHub issue!
INFO:nispace.core.parcellation:Building combined Parcellation 'Yan200+TianS2' from library.
INFO:nispace.core.parcellation: Common MNI space(s) for combined: ['MNI152NLin2009cAsym', 'MNI152NLin6Asym'].
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin6Asym'.
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsLR' (for spin tests).
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsaverage' (for spin tests).
INFO:nispace.core.parcellation:Combined parcellation 'Yan200+TianS2' ready. MNI space(s): ['MNI152NLin2009cAsym', 'MNI152NLin6Asym']. Cx surface space(s) for spins: ['fsLR', 'fsaverage'].
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': validation passed.
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': active space set to 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation:Combined parcellation: cx-LH parcels = 100, cx-RH parcels = 100.
INFO:nispace.plotting:brainplot: kind='glass', img_mode='None', surf_space='None', mni_space='MNI152NLin2009cAsym', surf_mesh='inflated'
[17]:
(<Figure size 1440x630 with 36 Axes>,
[<Axes: >, <Axes: >, <Axes: >, <Axes: >, <Axes: >, <Axes: >])
2.2 Results
[18]:
p_bin = nsp_bin.get_p_values()
rho_bin = nsp_bin.get_colocalizations()
Extra: Without workflow function
[19]:
# Set up NiSpace
nsp_bin = NiSpace(
x=pet_maps,
y=binary_img,
y_labels=["pain_cluster"],
binary_y=True,
parcellation=PARC,
seed=42,
n_proc=-1
)
nsp_bin.fit() # background_value=False enforced for Y via binary mode
# Generate nimare nulls (done above -> commented out)
# null_maps_bin = null_maps_from_nimare(
# ale_result,
# corrected_result=ale_corrected, # passing the FWE corrector enables binary cluster mode
# parcellation=nsp._parc,
# cluster_stat="size",
# voxel_thresh=0.001,
# alpha=0.05,
# map_label="pain_cluster", # must match y_labels above
# observed_map=binary_img, # sanity check: r ≥ 0.99 confirms consistent thresholding
# n_perm=1000,
# seed=42,
# n_proc=-1,
# )
# Run colocalization
nsp_bin.colocalize("pearson") # pearson to perform appr. point-biserial correlation
# Permute Y (not X) using the NiMARE null maps
nsp_bin.permute("maps", maps_which="Y", maps_nulls=null_maps_bin, n_perm=1000, p_tails="two")
# correct for multiple comparisons
nsp_bin.correct_p()
INFO:nispace.core.parcellation:Building combined Parcellation 'Yan200+TianS2' from library.
INFO:nispace.core.parcellation: Common MNI space(s) for combined: ['MNI152NLin2009cAsym', 'MNI152NLin6Asym'].
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation: Merging 'Yan200' and 'TianS2' for space 'MNI152NLin6Asym'.
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsLR' (for spin tests).
INFO:nispace.core.parcellation: Fetching cx surface data for 'Yan200' in 'fsaverage' (for spin tests).
INFO:nispace.core.parcellation:Combined parcellation 'Yan200+TianS2' ready. MNI space(s): ['MNI152NLin2009cAsym', 'MNI152NLin6Asym']. Cx surface space(s) for spins: ['fsLR', 'fsaverage'].
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': validation passed.
INFO:nispace.core.parcellation:Parcellation 'Yan200+TianS2': active space set to 'MNI152NLin2009cAsym'.
INFO:nispace.core.parcellation:Combined parcellation: cx-LH parcels = 100, cx-RH parcels = 100.
INFO:nispace.io:Input type: DataFrame, assuming parcellated data with shape (n_files/subjects/etc, n_parcels).
WARNING:nispace.io:Parcellated data contains nan values!
INFO:nispace.io:Input type: list, assuming imaging data.
INFO:nispace.io:Background (bg) handling: background_value=False; reporting bg-only parcels: False
INFO:nispace.io:Parcellating imaging data.
Parcellating (-1 proc): 100%|███████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 1112.55it/s]
Colocalizing (pearson, -1 proc): 100%|██████████████████████████████████████████████████| 1/1 [00:00<00:00, 1760.09it/s]
INFO:nispace.core.permute:Wrapping provided dict null maps into NullMaps.
INFO:nispace.core.permute:Using provided null maps.
Null colocalizations (pearson, -1 proc): 100%|████████████████████████████████████| 1000/1000 [00:00<00:00, 8272.04it/s]
INFO:nispace.core.permute:Calculating exact p-values (tails = {'rho': 'two'}).
[19]:
<nispace.api.NiSpace at 0x3d8eed370>
Conclusion
See below for a comparison of the three final results.
Based on these results, we currently recommend to use both continuous and binary NiMARE nulls for spatial colocalization analysis of coordinate-based meta-analyis results, expecting the binary nulls to show higher specificity.
[20]:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1,3, figsize=(12, 8))
for nispace_obj, ax, title in [
(nsp_standard, axes[0], "Moran SA-corrected nulls\n(continuous)"),
(nsp, axes[1], "NiMARE nulls\ncontinuous mode"),
(nsp_bin, axes[2], "NiMARE nulls\nbinary mode"),
]:
# plot r values normalized against null
nispace_obj.normalize_colocalizations()
nispace_obj.plot(fig=fig, ax=ax, show=False, values="z")
# limit x axis to custom setting
ax.set_xlim(-3.5 ,5.5)
# layout
ax.annotate(title, xy=(0.5, 1.1), xycoords="axes fraction",
ha="center", size=12, weight="bold")
if not ax.get_subplotspec().is_first_col():
ax.set_yticklabels([])
else:
ax.set_yticklabels(ax.get_yticklabels())
if not ax.get_subplotspec().is_last_col():
ax.legend().set_visible(False)
INFO:nispace.plotting:Significance annotation: 6/29 p_uncorrected < 0.05, 0/29 p_meffgalwey < 0.05
/var/folders/6n/h4150p8d5gz5kbnqv5_406940000gp/T/ipykernel_28870/3645975165.py:23: UserWarning: set_ticklabels() should only be used with a fixed number of ticks, i.e. after set_ticks() or using a FixedLocator.
ax.set_yticklabels(ax.get_yticklabels())
/var/folders/6n/h4150p8d5gz5kbnqv5_406940000gp/T/ipykernel_28870/3645975165.py:25: UserWarning: No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
ax.legend().set_visible(False)
INFO:nispace.plotting:Significance annotation: 11/29 p_uncorrected < 0.05, 1/29 p_meffgalwey < 0.05
/var/folders/6n/h4150p8d5gz5kbnqv5_406940000gp/T/ipykernel_28870/3645975165.py:25: UserWarning: No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
ax.legend().set_visible(False)
INFO:nispace.plotting:Significance annotation: 5/29 p_uncorrected < 0.05, 3/29 p_meffgalwey < 0.05
Summary
Mode |
Y |
|
What the null maps contain |
|---|---|---|---|
Continuous |
|
|
Parcellated raw ALE values from random coordinates |
Binary cluster |
|
|
CDT-thresholded cluster-coverage fractions from random coordinates |
Key points:
Which null for what question: the standard Moran/spin null on X tests whether SA in the reference maps explains the colocalization. The coordinate-sampling null on Y tests whether the spatial convergence of peak coordinates in the meta-analysis explains the colocalization. The two are complementary — use the NiMARE null when you want to honor the ALE generative process.
Binary mode uses Pearson, not Spearman: parcellated Y is a fractional cluster-coverage map (not truly binary), and Pearson directly measures the point-biserial relationship. Spearman degrades due to heavy ties at zero.
Binary null maps are CDT-thresholded only (no FWE extent filter): applying FWE to null maps makes ~95% of them empty by construction (that is what FWE alpha=0.05 means). The lower value range of null maps (0→0.05–0.1) vs observed (0→0.4) reflects that random coordinates produce small scattered clusters, not focused ones.
maps_which="Y"inpermute()— the null maps replace Y (the meta-analytic image), not X (the receptor maps).map_label=must equaly_labels=in NiSpace — the dict key routes the null array to the correct Y map.observed_map=triggers an internal r ≥ 0.99 sanity check — always pass it during development to catch space or threshold mismatches early.Requires
pip install nimare; NiMARE ≥ 0.2.0; volumetric/MNI parcellation only (surface parcellations not supported).