Atlas-based seed connectivity maps¶
This example computes voxel-wise seed-based functional connectivity maps: register a
single-slice fUSI recording to an Allen-space template, bring an SeedBasedMaps. Each resulting map is
displayed with plot_stat_map, using the
resampled Allen reference volume as background.
We use an awake freely-running acquisition from subject CR022, session 20201007,
in the Nunez-Elizalde 2022 dataset,
and the
Fetch the recording and register to the Allen atlas¶
The recording is a single coronal slice imaged for approximately 4 minutes at 3.33 Hz. Registration works on a static anatomical image, so we use the temporal mean, converted to decibels for a more stable dynamic range.
from pathlib import Path
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import xarray as xr
import confusius as cf
# Adapt background color to the current Matplotlib style.
bg_color = mpl.colors.to_hex(mpl.rcParams["figure.facecolor"])
xr.set_options(display_expand_data=False)
template = cf.datasets.fetch_template_pepe_mariani_2026().compute()
bids_root = cf.datasets.fetch_nunez_elizalde_2022(
subjects="CR022", sessions="20201007", tasks="spontaneous", acqs="slice02"
)
If you use this template in your work, please cite the following source: Pepe, C., Mariani, J.-C., Urosevic, M., Gini, S., Stuefer, A., Ricci, F., Galbusera, A., Iurilli, G., & Gozzi, A. (2026). Structural and dynamic embedding of the mouse functional connectome revealed by functional ultrasound imaging (fUSI). bioRxiv. https://doi.org/10.64898/2026.02.05.704055
If you use this dataset in your work, please cite the following source: Nunez-Elizalde, A. O., Krumin, M., Reddy, C. B., Montaldo, G., Urban, A., Harris, K. D., & Carandini, M. (2022). Neural correlates of blood flow measured by ultrasound. Neuron, 110(10), 1631–1640.e4. https://doi.org/10.1016/j.neuron.2022.02.012
data_path = (
Path(bids_root)
/ "sub-CR022"
/ "ses-20201007"
/ "fusi"
/ "sub-CR022_ses-20201007_task-spontaneous_acq-slice02_pwd.nii.gz"
)
# The recording's timepoints are not perfectly uniformly spaced so we resample to a
# uniform grid before any time-domain processing (filtering below requires it).
data = cf.timing.resample_to_uniform_time(cf.load(data_path))
moving = data.mean(dim="time").fusi.scale.db().compute()
We don't describe the registration process here to keep the notebook focused on seed-based connectivity, but the key steps are:
- Get an initial affine transform using napari's "Transform" tool.
- Use
register_volumeto refine the alignment. - Resample the Allen atlas into the recording's native space with
[
resample_like][confusius.atlas.Atlas.resample_like].
For the full walkthrough, see Register a recording to an Allen fUSI template.
Registration and atlas resampling
napari_affine = np.array(
[
[1.0, 0.0, 0.0, 5.594638656430411],
[0.0, 1.0, 0.0, -2.50293925701927],
[0.0, 0.0, 1.0, 5.6650243788545875],
[0.0, 0.0, 0.0, 1.0],
]
)
initialization = np.linalg.inv(napari_affine)
# Crop the template to a thin band around the recording's expected location to improve
# registration speed and visualization.
target_z = napari_affine[0, 3] + moving.fusi.origin["z"]
fixed = template.sel(z=slice(target_z - 1.0, target_z + 1.0))
registered, affine, diagnostics = cf.registration.register_volume(
moving=moving,
fixed=fixed,
transform_type="affine",
metric="correlation",
convergence_window_size=100,
number_of_iterations=500,
learning_rate=1,
initialization=initialization,
show_progress=False,
)
world_to_sform = template.attrs["affines"]["world_to_sform"]
subject_to_atlas = world_to_sform @ np.linalg.inv(affine)
atlas = cf.datasets.fetch_brainglobe_atlas("allen_mouse_100um", check_latest=False)
atlas_native = atlas.atlas.resample_like(moving, subject_to_atlas)
Choose seed regions¶
We pick one seed from each of four functional systems, to contrast their
connectivity patterns: the primary somatosensory barrel field cortex ("SSp-bfd"), a
classic resting-state seed with a strong, well-documented bilateral
(interhemispheric) correlation signature; the retrosplenial cortex ("RSP"), a
default-mode-like hub expected to correlate broadly across much of cortex; the
hippocampus ("HIP"), which should instead correlate with a more localized,
hippocampal-formation-restricted network; and the ventral posteromedial thalamic
nucleus ("VPM"), a somatosensory relay nucleus expected to correlate with
"SSp-bfd" through the thalamocortical pathway. All seeds are taken from the right
hemisphere only, so that any left-hemisphere correlation in the resulting maps
reflects genuine interhemispheric connectivity rather than the seed leaking into its
own mask.
get_masks returns a stacked
(mask, k, j, i) integer DataArray—one layer per requested region—which
SeedBasedMaps accepts directly as
seed_masks.
Smooth and compute nuisance regressors¶
We lightly smooth the recording spatially with
smooth_volume (0.1 mm FWHM) to improve the
voxel-wise SNR before extracting confound signals and fitting the seed-based maps.
As in the correlation-matrix example, we regress out an
aCompCor component extracted from
white-matter voxels (the Allen ontology's "fiber tracts" division) together with a
low_cutoff high-pass cosine filter for slow drift. Both are passed to
SeedBasedMaps via clean_kwargs, which cleans the full voxel-wise recording
before extracting the seed signals, so seeds and voxels are preprocessed
consistently.
data = cf.spatial.smooth_volume(data, fwhm=0.1)
white_matter = atlas_native.atlas.get_masks("fiber tracts").isel(mask=0)
acompcor = cf.signal.compute_compcor_confounds(
data, noise_mask=white_matter, n_components=1, variance_threshold=0.95
)
Compute the seed-based correlation maps¶
SeedBasedMaps extracts each seed's average
signal, correlates it against every voxel in the recording, and returns one Pearson r
map per seed, stacked along a region dimension.
mapper = cf.connectivity.SeedBasedMaps(
seed_masks=seed_masks,
clean_kwargs={"low_cutoff": 0.01, "filter_method": "cosine", "confounds": acompcor},
)
mapper.fit(data)
mapper.maps_
- region: 4
- k: 1
- j: 125
- i: 80
- dask.array<chunksize=(4, 1, 125, 80), meta=np.ndarray>
Array Chunk Bytes 156.25 kiB 156.25 kiB Shape (4, 1, 125, 80) (4, 1, 125, 80) Dask graph 1 chunks in 54 graph layers Data type float32 numpy.ndarray - region(region)<U9'SSp-bfd_R' 'RSP_R' 'HIP_R' 'VPM_R'
array(['SSp-bfd_R', 'RSP_R', 'HIP_R', 'VPM_R'], dtype='<U9')
- k(k)int640
array([0])
- j(j)int640 1 2 3 4 5 ... 120 121 122 123 124
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124]) - i(i)int640 1 2 3 4 5 6 ... 74 75 76 77 78 79
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79]) - z(k, j, i)float640.4 0.4 0.4 0.4 ... 0.4 0.4 0.4 0.4
- units :
- mm
[10000 values with dtype=float64]
- y(k, j, i)float642.996 2.996 2.996 ... 8.988 8.988
- units :
- mm
[10000 values with dtype=float64]
- x(k, j, i)float64-3.95 -3.85 -3.75 ... 3.85 3.95
- units :
- mm
[10000 values with dtype=float64]
- kPandasIndex
PandasIndex(Index([0], dtype='int64', name='k'))
- jPandasIndex
PandasIndex(Index([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ... 115, 116, 117, 118, 119, 120, 121, 122, 123, 124], dtype='int64', name='j', length=125)) - iPandasIndex
PandasIndex(Index([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79], dtype='int64', name='i')) - z
y
xVoxelToWorldIndex<confusius._utils.geometry.VoxelToWorldIndex object at 0x7f0ac4e6ec30>
- regionPandasIndex
PandasIndex(Index(['SSp-bfd_R', 'RSP_R', 'HIP_R', 'VPM_R'], dtype='str', name='region'))
- qform_code :
- 1
- manufacturer :
- Verasonics
- manufacturers_model_name :
- Vantage 128
- software_version :
- Alan Urban Technology & Consulting (AUTC)
- probe_manufacturer :
- Vermon
- probe_type :
- linear
- probe_model :
- L22-XTech
- probe_central_frequency :
- 15000000.0
- probe_number_of_elements :
- 128
- probe_pitch :
- 0.1
- probe_focal_width :
- 0.4
- probe_focal_depth :
- 8.0
- power_doppler_integration_duration :
- 0.3
- power_doppler_integration_stride :
- 0.3
- clutter_filter_window_duration :
- 0.4
- clutter_filter_window_stride :
- 0.3
- clutter_filters :
- ['highpass:15Hz', 'svd:remove_first_15_components']
- task_name :
- spontaneous
- task_description :
- Spontaneous activity without explicit visual stimulation.
- depth :
- [0.0, 5.991680000000001]
- transmit_frequency :
- 15625000.0
- compound_sampling_frequency :
- 500.0
- plane_wave_angles :
- [-10.0, -7.9, -5.8, -3.6999999999999993, -1.5999999999999996, 0.5000000000000018, 2.6000000000000014, 4.700000000000002, 6.8000000000000025, 8.900000000000002]
- probe_voltage :
- 25.0
- affines :
- {'world_to_qform': array([[1., 0., 0., 0.], [0., 1., 0., 0.], [0., 0., 1., 0.], [0., 0., 0., 1.]])}
- long_name :
- Pearson r
- cmap :
- coolwarm
- norm :
- <matplotlib.colors.Normalize object at 0x7f0ac4f69940>
Plot the seed maps¶
Each seed's map is plotted with plot_stat_map
over the resampled Allen reference volume, with vmax=0.8 fixing the colormap to a
shared range so the four seeds are directly comparable. mapper.maps_ stacks all
four seed maps along a region dimension, so a single plot_stat_map call with
slice_mode="region" plots one panel per seed as long as the background is broadcast
to the same region dimension first. We outline each seed's own ROI with
VolumePlotter.add_contours, leaving
colors unset so each region is drawn in its canonical Allen color (read from the
atlas mask's attrs["cmap"]/attrs["norm"], the same convention used by
get_masks elsewhere).
# coolwarm's white midpoint reads as a washed-out hole on a dark background, so switch
# to berlin (Crameri's perceptually uniform diverging colormap, black midpoint) when the
# current Matplotlib style is dark.
is_dark_theme = sum(mpl.colors.to_rgb(bg_color)) / 3 < 0.5
cmap = "berlin" if is_dark_theme else None
# Broadcast the shared background and brain outline across a "region" dimension
# matching mapper.maps_, so plot_stat_map can slice both by region in one call.
bg_by_region = atlas_native.reference.expand_dims(region=mapper.maps_.region)
fig, axes = plt.subplots(2, 2, figsize=(8, 6), constrained_layout=True)
fig.patch.set_facecolor(bg_color)
plotter = cf.plotting.plot_stat_map(
mapper.maps_,
bg_volume=bg_by_region,
slice_mode="region",
cmap=cmap,
vmax=0.8,
threshold=0.2,
cbar_label="Pearson correlation",
show_axes=False,
figure=fig,
axes=axes,
bg_color=bg_color,
)
plotter.add_contours(seed_masks.rename(mask="region"), linewidths=1.5)
_ = fig.suptitle("Seed-based connectivity maps", fontsize=16)
Total running time: 15.9 s

