# - compatibility with Python 3
from __future__ import print_function # print('me') instead of print 'me'
from __future__ import division # 1/2 == 0.5, not 0
# - show figures inside the notebook
%matplotlib inline
# - import common modules
import numpy as np # the Python array package
import matplotlib.pyplot as plt # the Python plotting package
The file mt_hrf_estimates.txt contains the estimated FMRI signal from voxels in the MT motion area at 0, 2, 4, ..., 28 seconds after a brief moving visual stimulus (see: http://nipy.org/nitime/examples/event_related_fmri.html).
Here are the first four rows. The numbers are in exponential floating point format:
1.394086932967517900e-01
3.938585701431884800e-01
5.012927230566770476e-01
5.676763716149294536e-01
Read the values from the file into an array mt_hrf_estimates. Make a new array mt_hrf_times with the times of acquisition (0, 2, ...). Plot them together to see the HRF estimate at these times:
# Load the estimated values from the text file into an array
# Make an array of corresponding times
# Plot signal by time
mt_hrf_estimates = np.loadtxt('mt_hrf_estimates.txt')
mt_hrf_times = np.arange(0, 30, 2)
plt.plot(mt_hrf_times, mt_hrf_estimates)
We want to make a hemodynamic response function that matches this shape.
Our function will accept an array that gives the times we want to calculate the HRF for, and returns the values of the HRF for those times. We will assume that the true HRF starts at zero, and gets to zero sometime before 35 seconds.
Like SPM, I'm going to try using the sum of two gamma distribution probability density functions.
# - import the gamma density function
from scipy.stats import gamma
Here's my first shot:
# - my attempt - you can do better than this
def not_great_hrf(times):
""" Return values for HRF at given times """
# Gamma pdf for the peak
peak_values = gamma.pdf(times, 6)
# Gamma pdf for the undershoot
undershoot_values = gamma.pdf(times, 12)
# Combine them
values = peak_values - 0.35 * undershoot_values
# Scale max to 0.6
return values / np.max(values) * 0.6
# - plot the data against our estimate
plt.plot(mt_hrf_times, not_great_hrf(mt_hrf_times), label='not_great_hrf')
plt.plot(mt_hrf_times, mt_hrf_estimates, label='mt_hrf_estimates')
plt.legend()
Now see if you can make a better function by playing with the Gamma distribution pdf parameter, and the mix of the two gamma distribution functions. Call your function mt_hrf
# Your "mt_hrf" function here
def mt_hrf(times):
""" Return values for HRF at given times """
# Gamma pdf for the peak
peak_values = gamma.pdf(times, 7)
# Gamma pdf for the undershoot
undershoot_values = gamma.pdf(times, 20)
# Combine them
values = peak_values - undershoot_values
# Scale max to 0.6
return values / np.max(values) * 0.6
# Plot your function against the mt_hrf_estimates data to test
plt.plot(mt_hrf_times, mt_hrf(mt_hrf_times), label='mt_hrf')
plt.plot(mt_hrf_times, mt_hrf_estimates, label='mt_hrf_estimates')
plt.legend()
For extra points - other than looking at these plots, how would you convince me your function is better than mine?
We are going to be analyzing the data for the 4D image ds114_sub009_t2r1.nii again.
Load the image into an image object with nibabel, and get the image data array. Print the shape.
# Load the image with nibabel, get the image data array, print the data shape
import nibabel as nib
img = nib.load('ds114_sub009_t2r1.nii')
data = img.get_data()
data.shape
Last week we read in the stimulus file for this run to make an on - off timeseries.
The stimulus file is ds114_sub009_t2r1_cond.txt.
Here's a version of the same thing we did last week, as a function:
# - read in stimulus file, return neural prediction
def events2neural(task_fname, tr, n_trs):
""" Return predicted neural time course from event file
Parameters
----------
task_fname : str
Filename of event file
tr : float
TR in seconds
n_trs : int
Number of TRs in functional run
Returns
-------
time_course : array shape (n_trs,)
Predicted neural time course, one value per TR
"""
task = np.loadtxt(task_fname)
if task.ndim != 2 or task.shape[1] != 3:
raise ValueError("Is {0} really a task file?", task_fname)
task[:, :2] = task[:, :2] / tr
time_course = np.zeros(n_trs)
for onset, duration, amplitude in task:
time_course[onset:onset + duration] = amplitude
return time_course
Use this function to read ds114_sub009_t2r1_cond.txt and return a predicted neural time course.
The TR for this run is 2.5. You know the number of TRs from the image data shape above.
# Read the stimulus data file and return a predicted neural time course.
# Plot the predicted neural time course.
tr = 2.5
neural_prediction = events2neural('ds114_sub009_t2r1_cond.txt', tr, data.shape[-1])
plt.plot(neural_prediction)
plt.ylim(0, 1.2)
We discovered last week that the first volume in this run was bad. Use your slicing skills to make a new array called data_no_0 that is the data array without the first volume:
# Make new array excluding the first volume
# data_no_0 = ?
data_no_0 = data[..., 1:]
Our neural prediction time series currently has one value per volume, including the first volume. To match the data,
make a new neural prediction variable that does not include the first value of the time series. Call this new variable neural_prediction_no_0.
# Knock the first element off the neural prediction time series
# neural_prediction_no_0 = ?
neural_prediction_no_0 = neural_prediction[1:]
For now, we're going to play with data for a single voxel.
Last week, we subtracted the rest from the task scans, something like this:
# - subtracting rest from task scans
task_scans = data_no_0[..., neural_prediction_no_0 == 1]
rest_scans = data_no_0[..., neural_prediction_no_0 == 0]
difference = np.mean(task_scans, axis=-1) - np.mean(rest_scans, axis=-1)
# - showing slice 14 from the difference image
plt.imshow(difference[:, :, 14], cmap='gray')
It looks like there's a voxel that is greater for activation than rest at about (i, j, k) == (45, 43, 14).
Get and plot the values for this voxel position, for every volume in the 4D data (not including the first volume). You can do it with a loop, but slicing is much nicer.
# Get the values for (i, j, k) = (45, 43, 14) and every volume
# Plot the values (voxel time course)
voxel_values = data_no_0[45, 43, 14, :]
plt.plot(voxel_values)
Correlate the predicted neural time series with the voxel time course:
# Correlation of predicted neural time course with voxel signal time course
np.corrcoef(neural_prediction_no_0, voxel_values)
Now we will do a predicted hemodynamic time course using convolution.
Next we need to get the HRF vector to convolve with.
Remember we have defined the HRF as a function of time, not TRs.
For our convolution, we need to sample the HRF at times corresponding the start of the TRs.
So, we need to sample at times (0, 2.5, ...)
Make a vector of times at which to sample the HRF. We want to sample every TR up until (but not including) somewhere near 35 seconds (where the HRF should have got close to zero again).
# Make a vector of times at which to sample the HRF
hrf_times = np.arange(0, 35, tr)
hrf_times
Sample your HRF function at these times and plot:
# Sample HRF at given times
# Plot HRF samples against times
hrf_signal = mt_hrf(hrf_times)
plt.plot(hrf_times, hrf_signal)
Convolve the predicted neural time course with the HRF samples:
# Convolve predicted neural time course with HRF samples
hemodynamic_prediction = np.convolve(neural_prediction_no_0, hrf_signal)
The default output of convolve is longer than the input neural prediction vector, by the length of the convolving vector (the HRF samples) minus 1. Knock these last values off the result of the convolution to get a vector the same length as the neural prediction:
# Remove extra tail of values put there by the convolution
hemodynamic_prediction = hemodynamic_prediction[:len(neural_prediction_no_0)]
Plot the convolved neural prediction, and then, on the same plot, plot the unconvolved neural prediction.
# Plot convolved neural prediction and unconvolved neural prediction
plt.plot(neural_prediction, label='unconvolved')
plt.plot(hemodynamic_prediction, label='convolved')
plt.legend()
Does the new convolved time course correlate better with the voxel time course?
# Correlation of the convolved time course with voxel time course
np.corrcoef(hemodynamic_prediction, voxel_values)
Plot the hemodynamic prediction against the actual signal (voxel values). Remember to use a marker such as '+' to give you a scatter plot. How does it look?
plt.plot(hemodynamic_prediction, voxel_values, '+')
plt.xlabel('hemodynamic prediction')
plt.ylabel('voxel values')