{ "metadata": { "name": "", "signature": "sha256:af48a011b61c0135723423c8add07044fe151f8b889b6f5ef70264370ba07c08" }, "name": "hrf_convolution", "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "code", "collapsed": false, "input": [ "# - compatibility with Python 3\n", "from __future__ import print_function # print('me') instead of print 'me'\n", "from __future__ import division # 1/2 == 0.5, not 0" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "# - show figures inside the notebook\n", "%matplotlib inline" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "# - import common modules\n", "import numpy as np # the Python array package\n", "import matplotlib.pyplot as plt # the Python plotting package" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Making our own hemodynamic response function" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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).\n", "\n", "Here are the first four rows. The numbers are in exponential floating point format:\n", "\n", "```\n", "1.394086932967517900e-01\n", "3.938585701431884800e-01\n", "5.012927230566770476e-01\n", "5.676763716149294536e-01\n", "```\n", "\n", "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:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Load the estimated values from the text file into an array\n", "# Make an array of corresponding times\n", "# Plot signal by time" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We want to make a hemodynamic response function that matches this shape.\n", "\n", "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.\n", "\n", "Like SPM, I'm going to try using the sum of two [gamma distribution](https://en.wikipedia.org/wiki/Gamma_distribution) probability density functions." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# - import the gamma density function\n", "from scipy.stats import gamma" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here's my first shot:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# - my attempt - you can do better than this\n", "def not_great_hrf(times):\n", " \"\"\" Return values for HRF at given times \"\"\"\n", " # Gamma pdf for the peak\n", " peak_values = gamma.pdf(times, 6)\n", " # Gamma pdf for the undershoot\n", " undershoot_values = gamma.pdf(times, 12)\n", " # Combine them\n", " values = peak_values - 0.35 * undershoot_values\n", " # Scale max to 0.6\n", " return values / np.max(values) * 0.6" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "# - plot the data against our estimate\n", "plt.plot(mt_hrf_times, not_great_hrf(mt_hrf_times), label='not_great_hrf')\n", "plt.plot(mt_hrf_times, mt_hrf_estimates, label='mt_hrf_estimates')\n", "plt.legend()" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Your \"mt_hrf\" function here" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "# Plot your function against the mt_hrf_estimates data to test" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For extra points - other than looking at these plots, how would you convince me your function is better than mine?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Getting a predicted neural time course" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We are going to be analyzing the data for the 4D image `ds114_sub009_t2r1.nii` again.\n", "\n", "Load the image into an image object with nibabel, and get the image data array. Print the shape." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Load the image with nibabel, get the image data array, print the data shape" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Last week we read in the stimulus file for this run to make an on - off timeseries.\n", "\n", "The stimulus file is `ds114_sub009_t2r1_cond.txt`.\n", "\n", "Here's a version of the same thing we did last week, as a function:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# - read in stimulus file, return neural prediction\n", "def events2neural(task_fname, tr, n_trs):\n", " \"\"\" Return predicted neural time course from event file\n", " \n", " Parameters\n", " ----------\n", " task_fname : str\n", " Filename of event file\n", " tr : float\n", " TR in seconds\n", " n_trs : int\n", " Number of TRs in functional run\n", " \n", " Returns\n", " -------\n", " time_course : array shape (n_trs,)\n", " Predicted neural time course, one value per TR\n", " \"\"\"\n", " task = np.loadtxt(task_fname)\n", " if task.ndim != 2 or task.shape[1] != 3:\n", " raise ValueError(\"Is {0} really a task file?\", task_fname)\n", " task[:, :2] = task[:, :2] / tr\n", " time_course = np.zeros(n_trs)\n", " for onset, duration, amplitude in task:\n", " time_course[onset:onset + duration] = amplitude\n", " return time_course" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use this function to read `ds114_sub009_t2r1_cond.txt` and return a predicted neural time course.\n", "\n", "The TR for this run is 2.5. You know the number of TRs from the image data shape above." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Read the stimulus data file and return a predicted neural time course.\n", "# Plot the predicted neural time course." ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Make new array excluding the first volume\n", "# data_no_0 = ?" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Our neural prediction time series currently has one value per volume, including the first volume. To match the data,\n", "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`." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Knock the first element off the neural prediction time series\n", "# neural_prediction_no_0 = ?" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For now, we're going to play with data for a single voxel.\n", "\n", "Last week, we subtracted the rest from the task scans, something like this:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# - subtracting rest from task scans\n", "task_scans = data_no_0[..., neural_prediction_no_0 == 1]\n", "rest_scans = data_no_0[..., neural_prediction_no_0 == 0]\n", "difference = np.mean(task_scans, axis=-1) - np.mean(rest_scans, axis=-1)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "# - showing slice 14 from the difference image\n", "plt.imshow(difference[:, :, 14], cmap='gray')" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It looks like there's a voxel that is greater for activation than rest at about (i, j, k) == (45, 43, 14).\n", "\n", "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." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Get the values for (i, j, k) = (45, 43, 14) and every volume\n", "# Plot the values (voxel time course)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Correlate the predicted neural time series with the voxel time course:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Correlation of predicted neural time course with voxel signal time course" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we will do a predicted hemodynamic time course using convolution.\n", "\n", "Next we need to get the HRF vector to convolve with.\n", "\n", "Remember we have defined the HRF as a function of time, not TRs.\n", "\n", "For our convolution, we need to *sample* the HRF at times corresponding the start of the TRs.\n", "\n", "So, we need to sample at times (0, 2.5, ...)\n", "\n", "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)." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Make a vector of times at which to sample the HRF" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sample your HRF function at these times and plot:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Sample HRF at given times\n", "# Plot HRF samples against times" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Convolve the predicted neural time course with the HRF samples:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Convolve predicted neural time course with HRF samples" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Remove extra tail of values put there by the convolution" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Plot the convolved neural prediction, and then, on the same plot, plot the unconvolved neural prediction." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Plot convolved neural prediction and unconvolved neural prediction" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Does the new convolved time course correlate better with the voxel time course?" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Correlation of the convolved time course with voxel time course" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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?" ] }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [] } ], "metadata": {} } ] }