The HCP 7T retinotopyy dataset comprises fMRI retinotopy from 183 human subjects. The NMA-curated dataset includes the average data over all those subjects.
In order to use this dataset, please electronically sign the HCP data use terms at ConnectomeDB. Instructions for this are on pp. 24-25 of the HCP Reference Manual.
The data and experiment are decribed in detail in Benson et al.
# @title Install dependencies
!pip install nilearn --quiet
import os
import numpy as np
import nibabel as nib
import matplotlib.pyplot as plt
from nilearn import plotting
# @title Figure settings
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
plt.style.use("https://raw.githubusercontent.com/NeuromatchAcademy/course-content/main/nma.mplstyle")
# The download cells will store the data in nested directories starting here:
DATA_DIR = "./hcp_retino"
if not os.path.isdir(DATA_DIR):
os.mkdir(DATA_DIR)
# The data acquisition rate
TR = 1 # Time resolution, in sec
# Time series data are organized by experiment, with each experiment
# having an LR and RL (phase-encode direction) acquistion
RUN_NAMES = [
"BAR1", # Sweeping Bars repeat 1
"BAR2", # Sweeping Bars repeat 2
"CCW", # Counter Clockwise rotating wedge
"CW", # Clockwise rotating wedge
"EXP", # Expanding ring
"CON" # Contracting ring
]
# @title Download the data
import os, requests, tarfile
fname = "hcp_retino.tgz"
url = "https://osf.io/d25b4/download"
if not os.path.isfile(fname):
try:
r = requests.get(url)
except requests.ConnectionError:
print("!!! Failed to download data !!!")
else:
if r.status_code != requests.codes.ok:
print("!!! Failed to download data !!!")
else:
print(f"Downloading {fname}...")
with open(fname, "wb") as fid:
fid.write(r.content)
print(f"Download {fname} completed!")
# @title Extract the data in `DATA_DIR`
fname_ex = "HCP7T_retino"
path_name = os.path.join(DATA_DIR, fname_ex)
if not os.path.exists(path_name):
print(f"Extracting {fname}...")
with tarfile.open(f"{fname}") as fzip:
fzip.extractall(DATA_DIR)
else:
print(f"File {fname}.tgz has already been extracted.")
File hcp_retino.tgz.tgz has already been extracted.
Pick one of the runs explained above and load the timeseries data
# pick a run
run = 'EXP'
# define filename
filename = os.path.join(DATA_DIR, fname_ex, f'tfMRI_RET{run}_7T_AP.dtseries.nii')
# load the file with nibabel
cifti = nib.load(filename)
print(cifti.get_fdata().shape)
(300, 91282)
From the shape of the data matrix, you should be able to see that there are 300 time points and 91282 brain locations (this is a mixture of cortical surface vertices and subcortical voxels, which the cifti format allows to store together).
Now let's run a simple analysis. We know that some of the runs are presenting a period stimulus (EXP, CON, CW, and CCW). We will design a linear model to fit the data with a sinusoidal signal with the same period.
Let's first define this model.
# Simple sinusoidal model
# The task timing is:
# 22 sec, 8 cycles x 32sec, 22 sec
n_before = 22
period = 32
cycles = 8
n_after = 22
t = np.arange(0, period * cycles, 1/TR)
time_var_cos = np.concatenate((np.zeros(n_before),
np.cos(2 * np.pi * t / period),
np.zeros(n_after)))
time_var_sin = np.concatenate((np.zeros(n_before),
np.sin(2 * np.pi * t / period),
np.zeros(n_after)))
# Create design matrix
design_matrix = np.asarray([time_var_cos,
time_var_sin,
np.ones_like(time_var_sin)]).T
The design matrrix above is made of three columns. One for a cosine wave, one for a sine wave, and one constant columns.
The first two columns together can fit a sinusoid of aritrary phase. The last column will help fit the mean of the data.
This is a linear model of the form $y = M\beta$ which we can invert using $\hat{\beta}=M^+y$ where $M^+$ is the pseudoinverse of $M$.
# Invert model
beta = np.linalg.pinv(design_matrix) @ cifti.get_fdata()
A little bit more maths. Our model is:
\begin{equation} y=\beta_0 cos(2\pi t/T) + \beta_1 sin(2\pi t/T) + \beta_2 \end{equation}To relate $\beta_0$ and $\beta_1$ to the amplitude $A$ and phase $\Phi$ of the data, we can use the trigonometric identity:
\begin{equation} A cos(2\pi t/T + \Phi) = \underbrace{Acos(\Phi)}_{\beta_0} cos(2\pi t/T) + \underbrace{Asin(\Phi)}_{\beta_1} sin(2\pi t/T) \end{equation}Which means that:
\begin{equation} A = \sqrt{\beta_0^2 + \beta_1^2} \end{equation}and
\begin{equation} \tan(\Phi) = \frac{\beta_1}{\beta_0} \end{equation}angle = np.arctan2(beta[1, :],beta[0, :])
amplitude = np.sqrt(np.sum(beta[:1, :]**2, axis=0))
Let's look at how this model fits the data. We will look at the brain location with largest amplitude from thee model fit.
idx = np.argmax(amplitude)
data = cifti.get_fdata()[:, idx]
# Model prediction is M*beta
pred = design_matrix @ beta[:, idx]
plt.figure()
plt.plot(data, label='data')
plt.plot(pred, label='model')
plt.legend()
plt.show()
It's an okay fit. Clearly the shape of the data is not correctly captured, but at least we can get the phase information (although this phase is probably wrong due to the fact that the BOLD signal lags behind the neural signal by about 6-10 seconds, which is why we need Haemodynamic response modelling. That it something you can try to do as part of your project.
Now let's visualise the phase on a brain surface.
# @title Helper function
# Helper function
def surf_data_from_cifti(data, cifti, surf_name):
"""Maps data from cifti file onto a surface
Args:
data : 2D array (nvertices x p)
cifti : Cifti2Image object
surf_name : str (either 'LEFT' or 'RIGHT')
"""
surf_name_long = 'CIFTI_STRUCTURE_CORTEX_'+surf_name
axis = cifti.header.get_axis(1) # The brain axis
for name, data_indices, model in axis.iter_structures():
if name == surf_name_long:
vtx_indices = model.vertex
surf_data = np.zeros(axis.nvertices[surf_name_long])
surf_data[vtx_indices] = data[data_indices]
return surf_data
raise ValueError(f"No structure named {surf_name_long}")
# This uses the nilearn package
# Plot the angle
data = angle
data[amplitude < 20] = 0
# If you want you can change the surface. Have a look in hcp_retino/surf for more options
filename = "Q1-Q6_R440.L.inflated.32k_fs_LR.surf.gii"
pathf = os.path.join(DATA_DIR, fname_ex, "surf", filename)
plotting.view_surf(surf_mesh=pathf,
surf_map=surf_data_from_cifti(data,cifti,'LEFT'),
cmap='hsv', threshold=.0001)