MRI-3T-rsfMRI

Overview

This notebook will examine 3T MRI rsfMRI measures projected onto hippocampal surfaces and averaged across 99 subjects. We will then take a closer look at functional connectivity (FC) of the hippocampus, and break it down into primary Gradients.

[1]:
import numpy as np
import matplotlib.pyplot as plt
import nibabel as nib
import hippomaps as hm
from brainspace.gradient import GradientMaps
from brainspace.datasets import load_conte69, load_parcellation
from brainspace.plotting import plot_hemispheres
from brainspace.mesh.mesh_io import read_surface
[2]:
# locate input data
ses = '01'
micapipe_dir = '/data/mica3/BIDS_MICs/derivatives/micapipe_v0.2.0'
hippunfold_dir = '/data/mica3/BIDS_MICs/derivatives/hippunfold_v1.3.0/hippunfold'
tmp_dir = 'tmp_MICs-FC'

# define which subjects and surfaces to examine
subs = [
 'HC048', 'HC043', 'HC087', 'HC037', 'HC055', 'HC100', 'HC036', 'HC017', 'HC088', 'HC040',
 'HC058', 'HC076', 'HC090', 'HC059', 'HC101', 'HC063', 'HC094', 'HC024', 'HC050', 'HC080',
 'HC013', 'HC026', 'HC001', 'HC084', 'HC105', 'HC083', 'HC042', 'HC014', 'HC033', 'HC081',
 'HC106', 'HC108', 'HC095', 'HC002', 'HC102', 'HC028', 'HC020', 'HC049', 'HC007', 'HC023',
 'HC065', 'HC025', 'HC056', 'HC003', 'HC015', 'HC077', 'HC067', 'HC072', 'HC109', 'HC086',
 'HC089', 'HC091', 'HC031', 'HC039', 'HC112', 'HC068', 'HC034', 'HC032', 'HC060', 'HC047',
 'HC103', 'HC046', 'HC009', 'HC097', 'HC116', 'HC053', 'HC079', 'HC029', 'HC075', 'HC078',
 'HC057', 'HC018', 'HC074', 'HC064', 'HC096', 'HC010', 'HC038', 'HC093', 'HC082', 'HC092',
 'HC027', 'HC019', 'HC005', 'HC008', 'HC011', 'HC044', 'HC030', 'HC035', 'HC085', 'HC069',
 'HC041', 'HC012', 'HC054', 'HC022', 'HC016', 'HC099', 'HC073', 'HC052', 'HC045']
hemis = ['L','R']
labels = ['hipp']# ,'dentate']
den='2mm'
sigma = 1 # Gaussian smoothing kernal sigma (mm) to apply to surface data
timepoints = 695 # number of volumes
TR = 0.6 # repetition time (seconds)

# get expected number of vertices and their indices
nV,iV = hm.config.get_nVertices(labels,den)

# Load neocortical surfaces for visualzation (assets from micapipe)
parcL = nib.load('/data/mica1/01_programs/micapipe-v0.2.0/parcellations/schaefer-400_conte69_lh.label.gii').darrays[0].data
parcR = nib.load('/data/mica1/01_programs/micapipe-v0.2.0/parcellations/schaefer-400_conte69_rh.label.gii').darrays[0].data
nP = len(np.unique(parcL))-1 # number of neocortical parcels (one hemisphere)
parc = np.concatenate((parcL,parcR))
c69_inf_lh= read_surface('/data/mica1/01_programs/micapipe-v0.2.0/surfaces/fsLR-32k.L.inflated.surf.gii', itype='gii')
c69_inf_rh = read_surface('/data/mica1/01_programs/micapipe-v0.2.0/surfaces/fsLR-32k.R.inflated.surf.gii', itype='gii')
labels_c69 = load_parcellation('schaefer', scale=400, join=True)

0) Map rsfMRI data to hippocampal surfaces

As in all tutorials here, this step is OPTIONAL, and provides an example of how data can be mapped to hippocampal surfaces outside of python (using ANTs and/or wb_command). This relies on having the data stored locally, and should be considered example code. This code may differ depending on where/how your data is stored and formatted, and so may require some customization for new projects. For the purposes of this tutorial, we provide a matrix of loaded data at the end, so skip to the next step).

In this example, we loop through subjects and hemipsheres, and sample volumetric timeseries data onto hippocampal surfaces. We then apply smoothing. Finally, we load the data from all surfaces into a single matrix.

[ ]:
# Note that all data is not yet aligned in space-T1w AKA space-nativepro, we we need to apply transforms to make it so. In micapipe, this involves two affines and a warp transform.

# intialize the matrix for loading data into
cdata_hipp = np.ones((nV,len(hemis),timepoints,len(subs)))*np.nan
!mkdir -p {tmp_dir}

for sub in subs:
    # convert affines
    cmd1a = f'c3d_affine_tool '\
        f'-itk {micapipe_dir}/sub-{sub}/ses-{ses}/xfm/sub-{sub}_ses-{ses}_from-se_task-rest_acq-AP_bold_to-nativepro_mode-image_desc-affine_0GenericAffine.mat '\
        f'-o {tmp_dir}/sub-{sub}_ses-{ses}_tmp0GenericAffine0.txt'
    !{cmd1a}
    cmd1b = f'c3d_affine_tool '\
        f'-itk {micapipe_dir}/sub-{sub}/ses-{ses}/xfm/sub-{sub}_ses-{ses}_from-nativepro_func_to-se_task-rest_acq-AP_bold_mode-image_desc-SyN_0GenericAffine.mat '\
        f'-inv '\
        f'-o {tmp_dir}/sub-{sub}_ses-{ses}_tmp0GenericAffine1.txt'
    !{cmd1b}

    for h,hemi in enumerate(hemis):
        for l,label in enumerate(labels):
            #apply affines
            cmd2a = f'wb_command -surface-apply-affine '\
                f'{hippunfold_dir}/sub-{sub}/ses-{ses}/surf/sub-{sub}_ses-{ses}_hemi-{hemi}_space-T1w_den-0p5mm_label-{label}_midthickness.surf.gii '\
                f'{tmp_dir}/sub-{sub}_ses-{ses}_tmp0GenericAffine0.txt '\
                f'{tmp_dir}/sub-{sub}_ses-{ses}_{h}_{l}_aff0.surf.gii'
            !{cmd2a}
            cmd2b = f'wb_command -surface-apply-affine '\
                f'{tmp_dir}/sub-{sub}_ses-{ses}_{h}_{l}_aff0.surf.gii '\
                f'{tmp_dir}/sub-{sub}_ses-{ses}_tmp0GenericAffine1.txt '\
                f'{tmp_dir}/sub-{sub}_ses-{ses}_{h}_{l}_aff1.surf.gii'
            !{cmd2b}
            # apply warp (Note this is actually the INVERSE warp)
            cmd3 = f'wb_command -surface-apply-warpfield '\
                f'{tmp_dir}/sub-{sub}_ses-{ses}_{h}_{l}_aff1.surf.gii '\
                f'{micapipe_dir}/sub-{sub}/ses-{ses}/xfm/sub-{sub}_ses-{ses}_from-nativepro_func_to-se_task-rest_acq-AP_bold_mode-image_desc-SyN_1Warp.nii.gz '\
                f'{tmp_dir}/sub-{sub}_ses-{ses}_{h}_{l}_deform.surf.gii'
            !{cmd3}

            # sample
            cmd4 = f'wb_command -volume-to-surface-mapping '\
                f'{micapipe_dir}/sub-{sub}/ses-{ses}/func/desc-se_task-rest_acq-AP_bold/volumetric/sub-{sub}_ses-{ses}_space-func_desc-se_preproc.nii.gz '\
                f'{tmp_dir}/sub-{sub}_ses-{ses}_{h}_{l}_deform.surf.gii '\
                f'{tmp_dir}/sub-{sub}_ses-{ses}_{h}_{l}_rsfMRI.func.gii '\
                f'-enclosing'
            !{cmd4}

            # smooth
            cmd5 = f'wb_command -metric-smoothing '\
                f'{hippunfold_dir}/sub-{sub}/ses-{ses}/surf/sub-{sub}_ses-{ses}_hemi-{hemi}_space-T1w_den-0p5mm_label-{label}_midthickness.surf.gii '\
                f'{tmp_dir}/sub-{sub}_ses-{ses}_{h}_{l}_rsfMRI.func.gii '\
                f'{sigma} '\
                f'{tmp_dir}/sub-{sub}_ses-{ses}_{h}_{l}_rsfMRI_smooth.func.gii '
            !{cmd5}

            # downsample
            func = nib.load(f'{tmp_dir}/sub-{sub}_ses-{ses}_{h}_{l}_rsfMRI_smooth.func.gii')
            out_array = np.ones((len(func.darrays),shp[l]))
            for k in range(len(func.darrays)):
                data, f, v = utils.density_interp('0p5mm', '2mm',func.darrays[k].data, label,'nearest')
                cdata_hipp[iV[l],h,:,s] = data

np.save("../checkpoints/MRI-3T-rsfMRI",cdata_hipp, allow_pickle=True)
!rm -r {tmp_dir}

1) Intrinsic Timescale of hippocampal rsfMRI

Our data is now organized into a single matrix of (number of vertices nV) x (number of hemsipheres 2) x (timepoints) x (number of subjects)

Intrinsic timescale is a measure of how long it takes for the temporal autocorrelation of rsfMRI data to decay beyond a treshold. In this case, until an autocorrelation of 0 is reached.

[3]:
cdata_hipp = np.load("../checkpoints/MRI-3T-rsfMRI.npy")
[22]:
# sample a random vertex to view its autocorrelation function (acf)
t = cdata_hipp[5,0,:,0]
m = np.mean(t)
var = np.var(t)
ndat = t - m
acf = np.correlate(ndat, ndat, 'full')[len(ndat)-1:]
acf = acf / var / len(ndat)
plt.plot(acf[:30])
plt.axhline(y = 0, color = 'k', linestyle = '--')
[22]:
<matplotlib.lines.Line2D at 0x7f24064e1a60>
../_images/tutorials_MRI-3T-rsfMRI_7_1.png
[23]:
def IntrinsicTimescale(data, TR=1, threshold=0):
    '''computes instrinsic timescale - the AUC of the autocorrelation up to the point
    where the autocorrelation reaches threshold.
    Input
      img: input ND data, time being the last dimension
    '''
    shp = data.shape
    i = data.reshape(-1, shp[-1])
    out = np.zeros(i.shape[0])
    for v in range(i.shape[0]):
        m = np.mean(i[v,:])
        var = np.var(i[v,:])
        ndat = i[v,:] - m
        acf = np.correlate(ndat, ndat, 'full')[len(ndat)-1:]
        acf = acf / var / len(ndat)
        f = np.where(acf<=threshold)[0]
        if len(f)==0:
            out[v] = np.nan
        else:
            out[v] = f[0]
    out = np.reshape(out,shp[:-1])*TR
    return out
[27]:
IntTS = np.ones((nV,len(hemis),len(subs)))*np.nan
for s,sub in enumerate(subs):
    IntTS[:,:,s] = IntrinsicTimescale(cdata_hipp[:,:,:,s],TR)
/data/mica1/01_programs/tmp/ipykernel_9740/2710468927.py:15: RuntimeWarning: invalid value encountered in true_divide
  acf = acf / var / len(ndat)
[29]:
hm.plotting.surfplot_canonical_foldunfold(np.nanmean(IntTS,axis=(1,2)), den='2mm', hemis=['L'], labels=labels, unfoldAPrescale=True, tighten_cwindow=True, cmap='cividis', share='row', color_bar='right', embed_nb=True)
/export03/data/opt/venv/lib/python3.8/site-packages/brainspace/plotting/base.py:287: UserWarning: Interactive mode requires 'panel'. Setting 'interactive=False'
  warnings.warn("Interactive mode requires 'panel'. "
[29]:
../_images/tutorials_MRI-3T-rsfMRI_10_1.png
[30]:
plt.hist(IntTS.flatten(),bins=50);
plt.xlim([0,15])
[30]:
(0.0, 15.0)
../_images/tutorials_MRI-3T-rsfMRI_11_1.png

Note that in many cases, the ACF drops below 0 after only 1 TR, meaning there is no autocorrelation. This is slightly not ideal, but seems to be concistent with other neocortical data and other studies.

2) Calculate Regional Homogeneity

Regional homogeneity is a measure of local functional connectivity, or how similar each vertex is to all nieghbours across time. Here we will start with an illustrative example

[35]:
# Here, we take a conconical midthickness surface and highlight one vertex compared with its neighbours
# This is mostly just to illustrate the logic of the method.

from mpl_toolkits.mplot3d import art3d
resourcesdir = '/export03/data/opt/hippunfold_toolbox/resources'
gii = nib.load(f'{resourcesdir}/canonical_surfs/tpl-avg_space-canonical_den-{den}_label-hipp_midthickness.surf.gii')
v = gii.get_arrays_from_intent('NIFTI_INTENT_POINTSET')[0].data
f = gii.get_arrays_from_intent('NIFTI_INTENT_TRIANGLE')[0].data

def plotwire(ax,f,v):
    pc = art3d.Poly3DCollection(v[f], faceColor=[0, 0, 0, 0], edgeColor=[0,0,0,1])
    ax.add_collection(pc)
    ax.set_xlim([np.min(v[:,0]),np.max(v[:,0])])
    ax.set_ylim([np.min(v[:,1]),np.max(v[:,1])])
    ax.set_zlim([np.min(v[:,2]),np.max(v[:,2])])
    ax.view_init(elev=90, azim=-90)
    ax = set_axes_equal(ax)
    ax.axis('off')
    return ax
def set_axes_equal(ax):
    '''Make axes of 3D plot have equal scale.  This is one possible solution to Matplotlib's
    ax.set_aspect('equal') and ax.axis('equal') not working for 3D.
    Input
      ax: a matplotlib axis, e.g., as output from plt.gca().'''
    x_limits = ax.get_xlim3d()
    y_limits = ax.get_ylim3d()
    z_limits = ax.get_zlim3d()
    x_range = abs(x_limits[1] - x_limits[0])
    x_middle = np.mean(x_limits)
    y_range = abs(y_limits[1] - y_limits[0])
    y_middle = np.mean(y_limits)
    z_range = abs(z_limits[1] - z_limits[0])
    z_middle = np.mean(z_limits)
    # The plot bounding box is a sphere in the sense of the infinity
    # norm, hence I call half the max range the plot radius.
    plot_radius = 0.5*max([x_range, y_range, z_range])
    ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])
    ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])
    ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius])
    return ax

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(12,12), subplot_kw={'projection': "3d"})
plotwire(ax,f,v)
i = 199
frows = np.unique(np.where(np.isin(f,i))[0])
verts = np.unique(f[frows,:])
verts = np.delete(verts,verts==i)
ax.scatter(v[verts,0],v[verts,1],v[verts,2],marker='o',color='none', alpha=1, edgecolor='r', linewidth =12)
ax.scatter(v[i,0],v[i,1],v[i,2],marker='o',color='none', alpha=1, edgecolor='b', linewidth =12)
/data/mica1/01_programs/tmp/ipykernel_9740/1934700871.py:7: MatplotlibDeprecationWarning: Case-insensitive properties were deprecated in 3.3 and support will be removed two minor releases later
  pc = art3d.Poly3DCollection(v[f], faceColor=[0, 0, 0, 0], edgeColor=[0,0,0,1])
[35]:
<mpl_toolkits.mplot3d.art3d.Path3DCollection at 0x7f23fc398d30>
../_images/tutorials_MRI-3T-rsfMRI_14_2.png
[31]:
# Here we define ReHo as the vertex-wise Kendall's W across connected vertices

def kendall_w(expt_ratings):
    # code from https://stackoverflow.com/questions/48893689/kendalls-coefficient-of-concordance-w-in-python
    if expt_ratings.ndim!=2:
        raise 'ratings matrix must be 2-dimensional'
    m = expt_ratings.shape[0] #raters
    n = expt_ratings.shape[1] # items rated
    denom = m**2*(n**3-n)
    rating_sums = np.sum(expt_ratings, axis=0)
    S = n*np.var(rating_sums)
    return 12*S/denom

def calc_reho(ts,F):
    # note ts should be shape VxT
    reho = np.ones((ts.shape[0]))*np.nan
    for v in range(ts.shape[0]):
        # find faces involving this row
        frows = np.unique(np.where(np.isin(F,v))[0])
        # find unique vertices of those faces
        verts = np.unique(F[frows,:])
        # assign that neighbourhood Kendall's W to the vertex in question
        reho[v] = kendall_w(ts[verts,:])
    return reho
[97]:
# now we can loop through all data and apply

reho = np.ones((nV,len(hemis),len(subs)))*np.nan
for s,sub in enumerate(subs):
    for h,hemi in enumerate(hemis):
        for l,label in enumerate(labels):
            reho[iV[l],h,s] = calc_reho(cdata_hipp[iV[l],h,:,s],f)
[98]:
hm.plotting.surfplot_canonical_foldunfold(np.nanmean(reho,axis=(1,2)), den='2mm', hemis=['L'], labels=labels, unfoldAPrescale=True, tighten_cwindow=True, cmap='cividis', share='row', color_bar='right', embed_nb=True)
[98]:
../_images/tutorials_MRI-3T-rsfMRI_17_0.png

Calculate tSNR

fMRI signal is generally lower for more medial and inferior structures, and those near tissue interfaces like the sinuses and eardrums. Here, we map that onto the hippocampus. (TODO: get volumetric version generated from micapipe)

[40]:
tSNR = np.mean(cdata_hipp,axis=2) / np.std(cdata_hipp,axis=2)
hm.plotting.surfplot_canonical_foldunfold(np.nanmean(tSNR,axis=(1,2)), den='2mm', hemis=['L'], labels=labels, unfoldAPrescale=True, tighten_cwindow=True, cmap='cividis', share='row', color_bar='right', embed_nb=True)
/data/mica1/01_programs/tmp/ipykernel_9740/1555471391.py:1: RuntimeWarning: invalid value encountered in true_divide
  tSNR = np.mean(cdata_hipp,axis=2) / np.std(cdata_hipp,axis=2)
[40]:
../_images/tutorials_MRI-3T-rsfMRI_19_1.png

3) Functional connectivity of the hippocampus to neocortex

Here we look at FC of each hippocampal vertex to ipsilateral neocortical vertices.

[61]:
FC = np.ones((nV,nP,len(hemis),len(subs)))*np.nan
for s,sub in enumerate(subs):
    neo_ts = nib.load(f'{micapipe_dir}/sub-{sub}/ses-{ses}/func/desc-se_task-rest_acq-AP_bold/surf/sub-{sub}_ses-{ses}_surf-fsLR-32k_desc-timeseries_clean.shape.gii').darrays[0].data
    neo_ts_parc = np.ones((neo_ts.shape[0],nP*2))
    for i in range(nP*2):
         neo_ts_parc[:,i] = np.nanmean(neo_ts[:,parc==(i+1)],axis=1)
    neo_tsL = neo_ts_parc[:,:nP]
    neo_tsR = neo_ts_parc[:,nP:]
    for h,hemi in enumerate(hemis):
        if hemi == 'L':
            FC[:,:,h,s] = np.corrcoef(cdata_hipp[:,h,:,s],neo_tsL[:timepoints,:].T)[:nV,nV:]
        else:
            FC[:,:,h,s] = np.corrcoef(cdata_hipp[:,h,:,s],neo_tsR[:timepoints,:].T)[:nV,nV:]

np.save("../checkpoints/MRI-3T-rsfMRI-FC",FC, allow_pickle=True)
[4]:
FC = np.load("../checkpoints/MRI-3T-rsfMRI-FC.npy")
[5]:
FC.shape
[5]:
(419, 200, 2, 99)

FC should skew positive, since negative FC values are generally not interpretable

[5]:
plt.hist(FC.flatten(),bins=50);
../_images/tutorials_MRI-3T-rsfMRI_25_0.png
[83]:
# view left and right FC (nV x nP) after averaging over subjects

fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(3*2,3))
ax[0].imshow(np.nanmean(FC[:,:,0,:],axis=(2)), vmin=-.5, vmax=.5, cmap='bwr')
ax[1].imshow(np.nanmean(FC[:,:,1,:],axis=(2)), vmin=-.5, vmax=.5, cmap='bwr')
[83]:
<matplotlib.image.AxesImage at 0x7f23f1728d60>
../_images/tutorials_MRI-3T-rsfMRI_26_1.png

Here we see that FC is generally positive, both across all FC calculations and after averaging over subjects. This is a good sanity check.

[66]:
# plot averaging over hemispheres, subjects, and necortical parcels
hm.plotting.surfplot_canonical_foldunfold(np.nanmean(FC,axis=(1,2,3)), den='2mm', hemis=['L'], labels=labels, unfoldAPrescale=True, tighten_cwindow=True, cmap='cividis', share='row', color_bar='right', embed_nb=True)
[66]:
../_images/tutorials_MRI-3T-rsfMRI_28_0.png
[7]:
# plot the neocortical counterparts by averaging over subjects and hippocampal vertices
mc = np.ones([c69_inf_lh.n_points + c69_inf_rh.n_points])*np.nan
for h,hemi in enumerate(hemis):
    for i in range(nP):
        mc[parc==(i+1+(h*nP))] = np.nanmean(FC[:,i,h,:],axis=(0,1))
plot_hemispheres( c69_inf_lh,  c69_inf_rh,array_name=np.hsplit(mc,1),
                 size=(800,200), color_bar=True, cmap='cividis', embed_nb=True,nan_color=(1, 1, 1, 1))
/export03/data/opt/venv/lib/python3.8/site-packages/brainspace/plotting/base.py:287: UserWarning: Interactive mode requires 'panel'. Setting 'interactive=False'
  warnings.warn("Interactive mode requires 'panel'. "
[7]:
../_images/tutorials_MRI-3T-rsfMRI_29_1.png
[8]:
# plot the neocortical counterparts by averaging over subjects, hemispheres, and hippocampal vertices
mc = np.ones([c69_inf_lh.n_points + c69_inf_rh.n_points])*np.nan
for i in range(200):
    mc[parc==(i+1)] = np.nanmean(FC[:,i,:,:],axis=(0,1,2))
plot_hemispheres( c69_inf_lh,  c69_inf_rh,array_name=np.hsplit(mc,1),
                 size=(800,200), color_bar=True, cmap='cividis', embed_nb=True,nan_color=(1, 1, 1, 1))
/export03/data/opt/venv/lib/python3.8/site-packages/brainspace/plotting/utils.py:303: RuntimeWarning: All-NaN axis encountered
  a, b = np.nanmin(x), np.nanmax(x)
[8]:
../_images/tutorials_MRI-3T-rsfMRI_30_1.png

4) Check consistency for all measures

To make sure our measures are consistent, we will check whether they are correlated across samples (that is, across subjects and hemispheres)

[84]:
from scipy.stats import ttest_1samp
[92]:
feats = ["IntTS", "ReHo", "FC"]
mfcorr = []
sdfcorr = []

corr = np.corrcoef(IntTS.reshape((nV,-1)).T)
fcorr = corr[np.triu_indices(len(subs)*2,k=1)]
print(ttest_1samp(fcorr,0,nan_policy='omit'))
mfcorr.append(np.nanmean(fcorr))
sdfcorr.append(np.nanstd(fcorr))

corr = np.corrcoef(reho.reshape((nV,-1)).T)
fcorr = corr[np.triu_indices(len(subs)*2,k=1)]
print(ttest_1samp(fcorr,0,nan_policy='omit'))
mfcorr.append(np.nanmean(fcorr))
sdfcorr.append(np.nanstd(fcorr))

corr = np.corrcoef(FC.reshape((nV,-1)).T)
fcorr = corr[np.triu_indices(len(subs)*2*200,k=1)]
print(ttest_1samp(fcorr,0,nan_policy='omit'))
mfcorr.append(np.nanmean(fcorr))
sdfcorr.append(np.nanstd(fcorr))
Ttest_1sampResult(statistic=79.39621481646617, pvalue=0.0)
Ttest_1sampResult(statistic=354.88439139080623, pvalue=0.0)
Ttest_1sampResult(statistic=13993.126375721766, pvalue=0.0)
[93]:
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(3*2,3))
plt.bar(range(3),mfcorr)
plt.errorbar(range(3),mfcorr, yerr=sdfcorr, fmt=".")
plt.xticks(ticks=range(3),labels=feats,rotation=30)
plt.ylim([0,.9]);
../_images/tutorials_MRI-3T-rsfMRI_34_0.png

Though the inter-subject correlation is somewhat low in IntTS and FC, it is reliably above 0

5) Gradients of differential hippocampal FC

Dimensionality reduction allows us to summarize many maps as primary components, or Gradients. Here we will use BrainSpace to compute nonlinear Gradients using diffusion map embedding. Briefly, this typically consists of computing an affinity matrix (e.g. a correlation between all maps) and then grouping them into a few consistent patterns (Gradients). In this case, the affinity matrix is between hippocampal vertices, based on how similar is their neocortical FC.

[102]:
# multimodal gradient map (mGM) decomposition using default parameters
FCGM = GradientMaps(n_components=5, kernel='normalized_angle')
FCGM.fit(np.nanmean(FC,axis=(2,3)))
[102]:
GradientMaps(kernel='normalized_angle', n_components=5)
[103]:
# As above, we can make a nice plot for each of the resulting gradients
hm.plotting.surfplot_canonical_foldunfold(FCGM.gradients_, den='2mm', hemis=['L'], labels=labels, unfoldAPrescale=True, tighten_cwindow=True, cmap='magma', share='row', color_bar='right', embed_nb=True)
[103]:
../_images/tutorials_MRI-3T-rsfMRI_38_0.png
[104]:
# we can also see the lambda value (or eigenvalue) for each gradient
plt.plot(FCGM.lambdas_)
[104]:
[<matplotlib.lines.Line2D at 0x7f23f1503490>]
../_images/tutorials_MRI-3T-rsfMRI_39_1.png
[105]:
# convert into a percentage of explained variance
FCGM.lambdas_/np.sum(FCGM.lambdas_)
[105]:
array([0.38785603, 0.2904295 , 0.13625114, 0.09743545, 0.08802788])

To help contextualize how these gradients relate to FC, we can plot the FCs from only to top and the bottom 25% vertices for each gradient.

[106]:
# look only at FC to the rest of the neocortex for the top-bottom 10% of each gradient

nvertsplit = int(nV*.25)

diffval = np.ones([c69_inf_lh.n_points + c69_inf_rh.n_points,ngrads])*np.nan
botval = np.ones(c69_inf_lh.n_points + c69_inf_rh.n_points)*np.nan
topval = np.ones(c69_inf_lh.n_points + c69_inf_rh.n_points)*np.nan
for g in range(ngrads):
    bot = np.argpartition(gm.gradients_[:,g],nvertsplit)[:nvertsplit]
    top = np.argpartition(gm.gradients_[:,g],-nvertsplit)[-nvertsplit:]
    for i in range(200):
        botval[parc==(i+1)] = np.nanmean(FC[bot,i,:,:],axis=(0,1,2))
        topval[parc==(i+1)] = np.nanmean(FC[top,i,:,:],axis=(0,1,2))
    diffval[:,g] = topval-botval
plot_hemispheres( c69_inf_lh,  c69_inf_rh,array_name=np.hsplit(diffval,ngrads),
                 size=(800,200*ngrads), color_bar=True, cmap='magma', embed_nb=True,nan_color=(1, 1, 1, 1))
/export03/data/opt/venv/lib/python3.8/site-packages/brainspace/plotting/utils.py:303: RuntimeWarning: All-NaN axis encountered
  a, b = np.nanmin(x), np.nanmax(x)
/export03/data/opt/venv/lib/python3.8/site-packages/brainspace/plotting/base.py:287: UserWarning: Interactive mode requires 'panel'. Setting 'interactive=False'
  warnings.warn("Interactive mode requires 'panel'. "
[106]:
../_images/tutorials_MRI-3T-rsfMRI_42_1.png

save

[9]:
# save a copy of the 2D map
for h,hemi in enumerate(hemis):
    for l,label in enumerate(labels):
        cdat = np.nanmean(IntTS,axis=2)[ind[l],h]
        data_array = nib.gifti.GiftiDataArray(data=cdat)
        image = nib.gifti.GiftiImage()
        image.add_gifti_data_array(data_array)
        nib.save(image, f'../maps/HippoMaps-initializationMaps/Dataset-MICs/MRI-3T-rsfMRI-IntTS_average-{len(subs)}_hemi-{hemi}_den-2mm_label-{label}.shape.gii')
[ ]:
# save a copy of the 2D map
for h,hemi in enumerate(hemis):
    for l,label in enumerate(labels):
        cdat = np.nanmean(reho,axis=2)[ind[l],h]
        data_array = nib.gifti.GiftiDataArray(data=cdat)
        image = nib.gifti.GiftiImage()
        image.add_gifti_data_array(data_array)
        nib.save(image, f'../maps/HippoMaps-initializationMaps/Dataset-MICs/MRI-3T-rsfMRI-ReHo_average-99_hemi-{hemi}_den-2mm_label-{label}.shape.gii')
[19]:
# save a copy of the 2D map
for h,hemi in enumerate(hemis):
    for l,label in enumerate(labels):
        cdat = np.nanmean(FC,axis=(1,3))[ind[l],h]
        data_array = nib.gifti.GiftiDataArray(data=cdat)
        image = nib.gifti.GiftiImage()
        image.add_gifti_data_array(data_array)
        nib.save(image, f'../maps/HippoMaps-initializationMaps/Dataset-MICs/MRI-3T-rsfMRI-avgFCneocort_average-{len(subs)}_hemi-{hemi}_den-2mm_label-{label}.shape.gii')
[ ]: