Help with Nilearn Visualization – Black Background for Non-Matching Regions

Hi everyone,

I’m working on visualizing brain regions associated with different subtypes and metrics using Nilearn. The visualization works well for regions with matches, but for those without matches, the output shows a black background.

I’ve attached the code I used with Nilearn. Could anyone please help me understand why this is happening and how to fix it?

import matplotlib.pyplot as plt
from nilearn import plotting, image, datasets
import pandas as pd
import nibabel as nib
import numpy as np
from nilearn.image import new_img_like

# === Load Gordon atlas and labels ===
atlas_path = "/content/atlas-Gordon_space-MNI152NLin2009cAsym_dseg.nii.gz"
labels_path = "/content/atlas-Gordon_dseg.tsv"
atlas_img = nib.load(atlas_path)
labels_df = pd.read_csv(labels_path, sep='\t')

# === File mapping: metric → CSV file ===
data_files = {
    "ALFF": "/content/ALFF_control_comparisons_FWE.csv",
    "fALFF": "/content/fALFF_control_comparisons_FWE.csv",
    "ReHo": "/content/REHO_control_comparisons_FWE.csv"
}

# === Subtype mapping ===
subtype_mapping = {
    "ALFF": ["bvFTD", "svFTD", "pnfa"],
    "fALFF": ["bv", "sv", "pnfa"],
    "ReHo": ["bvFTD", "svFTD", "pnfa"]
}

# === Display names for rows ===
group_display_names = {
    "bvFTD": "bvFTD",
    "svFTD": "svPPA",
    "pnfa": "nfvPPA",
    "bv": "bvFTD",
    "sv": "svPPA"
}

# === Load background template ===
mni_template = datasets.load_mni152_template()

# === Create white-background figure ===
plt.style.use('default')
fig, axes = plt.subplots(3, len(data_files), figsize=(15, 10), facecolor='white',
                         subplot_kw={'xticks': [], 'yticks': []})
#fig.suptitle("ROI-wise Brain Alterations in FTD Subtypes Compared to Controls Across rs-fMRI Metrics\n(-log10 of FWE-corrected p-values)", fontsize=14)

# === Column titles ===
#for col, metric in enumerate(data_files.keys()):
    #axes[0, col].set_title(metric, fontsize=12, pad=20)

column_titles = list(data_files.keys())
n_cols = len(column_titles)

for i, title in enumerate(column_titles):
    xpos = (i + 0.5) / n_cols  # center over each column
    fig.text(xpos, 0.90, title, ha='center', va='bottom', fontsize=14)

# === Row labels ===
for row, code in enumerate(["bvFTD", "svFTD", "pnfa"]):
    label = group_display_names[code]
    axes[row, 0].annotate(label, xy=(0, 0.5), xytext=(-axes[row, 0].bbox.width * 0.2, 0),
                          xycoords='axes fraction', textcoords='offset points',
                          size='large', ha='right', va='center', rotation=90)

# === Plotting loop ===
for col, (metric, file_path) in enumerate(data_files.items()):
    df = pd.read_csv(file_path)
    if "FWE_corrected_p" in df.columns:
        df["neg_log10_p"] = -np.log10(df["FWE_corrected_p"].clip(lower=1e-10))

    subtype_list = subtype_mapping[metric]

    for row, subtype in enumerate(subtype_list):
        ax = axes[row, col]
        ax.set_facecolor('white')

        sub_df = df[(df["Group1"] == "control") & (df["Group2"] == subtype) & (df["Significant (FWE)"] == True)]

        # === Clean and match ROI names ===
        prefix = f"{metric.upper()}_" if metric != "fALFF" else ""
        cleaned_roi_names = [roi.replace(prefix, "") for roi in sub_df["ROI"]]
        matched = labels_df[labels_df["label"].isin(cleaned_roi_names)]

        if matched.empty:
            print(f"⚠️ No matching ROIs for: {subtype} in {metric}.")
            print("  ROI column sample:", list(sub_df["ROI"].unique())[:5])
            print("  Cleaned ROI sample:", cleaned_roi_names[:5])
            print("  Available atlas labels:", labels_df["label"].unique()[:5])
            plotting.plot_anat(
                anat_img=mni_template,
                axes=ax,
                display_mode="z",
                draw_cross=False,
                annotate=False,
                cut_coords=1,
                black_bg=False
            )
            continue

        # === Generate mask image with -log10(p) as intensity ===
        data = np.zeros(atlas_img.shape)
        for _, row_label in matched.iterrows():
            roi_idx = int(row_label["index"])
            roi_label = row_label["label"]
            p_val_row = sub_df[sub_df["ROI"].str.contains(roi_label)]
            if not p_val_row.empty:
                p_val = p_val_row["FWE_corrected_p"].values[0]
                neg_log_p = -np.log10(max(p_val, 1e-10))
                data[atlas_img.get_fdata() == roi_idx] = neg_log_p

        neglog_img = new_img_like(atlas_img, data)

        # === Plot with white background ===
        display = plotting.plot_stat_map(
            neglog_img,
            bg_img=mni_template,
            axes=ax,
            display_mode="z",
            draw_cross=False,
            cut_coords=1,
            cmap="autumn",
            annotate=False,
            threshold=1.3,
            vmax=5,
            black_bg=False,
            colorbar=True
        )

        # === Add colorbar label (safe method) ===
        fig_colorbar = display._cbar if hasattr(display, "_cbar") else None
        if fig_colorbar and hasattr(fig_colorbar, "ax"):
            fig_colorbar.ax.set_ylabel("-log10(p)", rotation=90, fontsize=8, labelpad=5)
            fig_colorbar.ax.yaxis.label.set_color("black")
            fig_colorbar.ax.tick_params(colors="black", labelsize=8)

# === Final layout ===
plt.tight_layout(rect=[0.05, 0.03, 0.98, 0.95])
plt.show()

Thank you in advance!

Thank you for posting !
If I understand correctly, sometimes the statistical test delivers no significant region, and in that case, the plot is different, with a black background. Can you confirm ?
If yes, this is clearly an undesirable behavior and we need to fix that.
Best,
Bertrand

Hi , thanks a lot for your reponse,
Yes, exactly — you understood me well.
Please, if you have any idea how to do it, and if I need to share a folder with all the files and the Colab notebook, let me know !

Sorry, I need to dig a bit more into that.
Bertrand

1 Like

Oh, actually, the reason is that you use plot_anat vs plot_stat_map depending on whether there is any threshold-surviving voxel. Both functions have different defaults. You probably don’t want to use plot_anat here ?
You can also set black_bg to true or False as you wish.
Best,

Thanks a lot, I really appreciate your time, but I’m still having issues with using that function too


I don’t get the brain map when there are no significant matching regions.

Here’s my hack for this: use plot_stat_map with an arbitrary image (here I just use the MNI template) but with arbitrary high threshold to ‘hide’ the stat map and only leave the background image.

import matplotlib.pyplot as plt
from nilearn import plotting, datasets

data = datasets.load_sample_motor_activation_image()

mni_template = datasets.load_mni152_template()

fig, axes = plt.subplots(1, 2, figsize=(15, 10), facecolor='white',
                         subplot_kw={'xticks': [], 'yticks': []})

for to_plot, ax in zip([data, None], axes):

        if to_plot is None:
            plotting.plot_anat(
                mni_template,
                bg_img=mni_template,
                axes=ax,
                display_mode="z",
                draw_cross=False,
                annotate=False,
                cut_coords=1,
                black_bg=False,
                threshold=1000, # use arbitrary very high value to 'hide' the stat map
            )
            continue


        # === Plot with white background ===
        display = plotting.plot_stat_map(
            to_plot,
            bg_img=mni_template,
            axes=ax,
            display_mode="z",
            draw_cross=False,
            cut_coords=1,
            cmap="autumn",
            annotate=False,
            threshold=1.3,
            vmax=5,
            black_bg=False,
        )


plt.show()

I agree that it’d be nice to have the possibility to easily have a full white background in plot_anat. Will try to look into how easy it can be done.

1 Like