{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import HTML, Markdown, display\n", "\n", "import numpy.random as npr\n", "import numpy as np\n", "import pandas as pd\n", "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "import scipy.stats as stats\n", "import statsmodels.formula.api as smf\n", "\n", "import ipywidgets as widgets\n", "\n", "import requests\n", "import zipfile\n", "import os\n", "import nibabel\n", "from scipy.stats import gamma\n", "\n", "# Enable plots inside the Jupyter Notebook\n", "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Lab 3: fMRI and Multiple Comparisons" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Authored by *Shannon Tubridy*, *Hillary Raab*, and *Todd Gureckis*, adapted by *Brenden Lake* \n", "Aspects borrowed from [Carsten Klein's Intro to fMRI analysis in Python](https://github.com/akcarsten/fMRI_data_analysis).\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This lab will introduce you to manipulating fMRI data in python and will apply the correlation and regression concepts from previous sections" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Introduction to MRI data\n", "\n", "MRI scanners can be used to create 3D images. The scanners can be tuned to be sensitive to different kinds of physical materials present in the object (like a brain) being imaged.\n", "\n", "The two primary image types that are used in cognitive neuroscience are __structural__ (sometimes called __anatomical__) and __functional__ images.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Structural images provide information about the anatomy of what's being imaged by showing differences between different kinds of tissues, bone, cerebro-spinal fluid and so on. A structural MRI image is a relatively high spatial resolution (compared to fMRI) 3D image that can be used to assess the internal anatomy of individual brains.\n", "\n", "Functional MRI scans produce a set of 3D images recorded over time. These images are lower spatial resolution than structual images. But unlike structural images, fMRI scans measure a signal that is related to neural activity. \n", "\n", "The BOLD signal commonly measured with fMRI is a time-varying signal modulated by changes in the oxygen content of blood. For people interested in the brain and behavior this is useful because that change in the oxygen content of blood is related to neural activity.\n", "\n", "This means that researchers can record the BOLD signal from locations throughout the brain while people engage in a variety of cognitive and perceptual tasks. By assessing whether the BOLD fMRI signal in different brain areas changes in response to changes in people's perceptual or cognitive states researchers are able to make advances in understanding how our brains produce our thoughts and behavior.\n", "\n", "Let's get into some data." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Loading data.\n", "\n", "First we start with importing the libraries for downloading, organizing and visualizing the data which comes from the SPM homepage (http://www.fil.ion.ucl.ac.uk/spm/). SPM is a popular Matlab Toolbox for analyzing fMRI brain imaging data and on their homepage some example datasets are provided. I previously downloaded these files to the `/shared` folder of your home directory.\n", "\n", "(There are a number of data pre-processing steps involved in going from the MRI scanner to the data we're going to download here. These primarily have to do with accounting for distortions, artifacts, or other unwanted contaminations of the signal of interest. Although those preprocessing steps are crucial for the kinds of data examined in this notebook, they tend to be less related to questions about cognition and perception and so we'll jump ahead.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's take a look at what we have in the `shared` folder. There is a subfolder inside of there called `/fmri_data/`.\n", "\n", "There are two folders with various files inside them. The first folder (\"sM00223\") contains a high resolution structural scan of the subject. \n", "\n", "The functional data is located in the other folder (\"fM00223\"). \n", "\n", "You'll notice that there pairs of files in these folders. These pairs have the same filename but one has the extension .hdr and the other has the exenstion .img. The .hdr is a file that contains meta-information related to the MRI data collection and the .img files have the actual MRI data inside.\n", "\n", "The NiBabel provides tools for using python to read and write MRI-related file types like `.hdr`, `.img`, and `.nii`.\n", "\n", "First lets read in the structural data using the the NiBabel package. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Find all files in the structural data folder\n", "data_path = os.path.expanduser(\"~\") + '/shared/fMRI_data/' + 'sM00223/'\n", "files = sorted(os.listdir(data_path))\n", "\n", "# take a look at what files are listed:\n", "print(files)\n", "\n", "# Read in the data by looping over the list of files and if the last three characters in the name are hdr\n", "# then we will use nibabel to load the data\n", "for data_file in files:\n", " # chec\n", " if data_file[-3:] == 'hdr':\n", " structural_data = nibabel.load(data_path + data_file).get_fdata() " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now lets load the functional data. \n", "\n", "There is a README.txt file that comes with the dataset. The README.txt includes details of the data acquisition which we need to read the files. The key parameters we have to know are the size of each image, the number of slices that were collected and how many volumes were acquired; that is the number of timepoints that were sampled." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Basic information about the data acquisition\n", "# how big is the x dimension?\n", "x_size = 64\n", "# how big is the y dimension?\n", "y_size = 64\n", "# how big is the z dimension (called slices here)?\n", "z_size = 64\n", "# How many volumes were collected? It's one volume per timepoint, so this is also how many \n", "# timepoints we have in our functional data\n", "n_volumes = 96\n", "\n", "# Find all files in the data functional data folder\n", "data_path = os.path.expanduser(\"~\") + '/shared/fMRI_data/' + '/fM00223/'\n", "files = sorted(os.listdir(data_path))\n", "\n", "# Read in the data and organize it with respect to the acquisition parameters\n", "# The data are stored as one volume with dimensions 64x64x64 for each timepoint\n", "# We will loop over the files, load them one at a time using nibabel storing the single \n", "# timepoint data in `data`, and appending each timepoint to `data_all` so that we end up with \n", "# a list of numpy arrays\n", "data_all = []\n", "for data_file in files:\n", " if data_file[-3:] == 'hdr' and data_file[0] == 'f':\n", " data = nibabel.load(data_path + data_file).get_fdata() \n", " data_all.append(data.reshape(x_size, y_size, z_size))\n", " \n", "# do some bookkeeping and index swapping so that end up with a 4D numpy array with dimensions\n", "# x,y,z, and time\n", "functional_data = np.transpose(np.array(data_all),(1,2,3,0))\n", "# rotate into same alignment as struct image\n", "functional_data = np.rot90(functional_data)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We have loaded our MRI data, and we have stored them in numpy arrays. \n", "\n", "Numpy arrays are a way to store data in python. You have seen them before here and there in previous sections but we will make extensive use of them in this section so let's orient ourselves.\n", "\n", "A numpy array has $N$ dimensions, and each of the dimensions can have some number of elements stored inside of it.\n", "\n", "That description is kind of abstract so let's make it concrete.\n", "\n", "Here is the image from the introduction to this notebook.\n", "\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Take a look at the schematic of the structural data.\n", "\n", "The data are three dimensional because we have created an image of a three dimensional object. For the structural data we can think about three spatial dimensions $x$, $y$, $z$.\n", "\n", "The large 3D box that covers the whole head is called a ***volume***.\n", "\n", "If you look again at the example illustration you'll see that each dimension of the volume is segmented into a number of smaller boxes or voxels. The number of voxels along each dimension is the length of that dimension. \n", "\n", "In this illustration the length of each dimension is nine voxels, so the total number of voxels in the volume is 9 x 9 x 9 (length x width x height....)\n", "\n", "We can check the dimensions of our actual data array using `shape` like we did with data frames. Data frames are 2 dimensional and the number of elements per dimension corresponds to the number of rows and columns. \n", "\n", "Let's see what the shape of our structural data is:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "structural_data.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This output tells us that we have four dimensions to our data. \n", "\n", "The first three are the spatial dimensions (so our 3D box is 256 x 256 x 54 voxels in size). The fourth dimension is time $t$. \n", "\n", "One way to think about this is that there is a 3D array of size 256x256x54 covering the brain and there is a version of this box for every element in the fourth dimension of time.\n", "\n", "For the structural image there is only data for a single timepoint because we are recording the overall structure of the brain which does not change from moment to moment.\n", "\n", "For convenience we can get rid of the fourth dimension because it doesn't have any information. We'll use a numpy method called squeeze, which basically flattens any dimensions that have length == 1 and at the same time we'll rotate the image 90 degrees for viewing purposes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "structural_data = np.rot90(structural_data.squeeze(), 1)\n", "\n", "# now let's check the shape again:\n", "structural_data.shape\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "OK, now we have our structural data in a single 3D array just like in the illustration." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "