|
| 1 | +# ============================================================================ |
| 2 | +# |
| 3 | +# PUBLIC DOMAIN NOTICE |
| 4 | +# |
| 5 | +# National Institute on Deafness and Other Communication Disorders |
| 6 | +# |
| 7 | +# This software/database is a "United States Government Work" under the |
| 8 | +# terms of the United States Copyright Act. It was written as part of |
| 9 | +# the author's official duties as a United States Government employee and |
| 10 | +# thus cannot be copyrighted. This software/database is freely available |
| 11 | +# to the public for use. The NIDCD and the U.S. Government have not placed |
| 12 | +# any restriction on its use or reproduction. |
| 13 | +# |
| 14 | +# Although all reasonable efforts have been taken to ensure the accuracy |
| 15 | +# and reliability of the software and data, the NIDCD and the U.S. Government |
| 16 | +# do not and cannot warrant the performance or results that may be obtained |
| 17 | +# by using this software or data. The NIDCD and the U.S. Government disclaim |
| 18 | +# all warranties, express or implied, including warranties of performance, |
| 19 | +# merchantability or fitness for any particular purpose. |
| 20 | +# |
| 21 | +# Please cite the author in any work or product based on this material. |
| 22 | +# |
| 23 | +# ========================================================================== |
| 24 | + |
| 25 | + |
| 26 | + |
| 27 | +# *************************************************************************** |
| 28 | +# |
| 29 | +# Large-Scale Neural Modeling software (LSNM) |
| 30 | +# |
| 31 | +# Section on Brain Imaging and Modeling |
| 32 | +# Voice, Speech and Language Branch |
| 33 | +# National Institute on Deafness and Other Communication Disorders |
| 34 | +# National Institutes of Health |
| 35 | +# |
| 36 | +# This file (compute_BOLD_balloon_model_auditory.py) was created on April 1, 2016. |
| 37 | +# |
| 38 | +# |
| 39 | +# Author: Antonio Ulloa |
| 40 | +# |
| 41 | +# Last updated by Antonio Ulloa on April 1 2016 |
| 42 | +# |
| 43 | +# **************************************************************************/ |
| 44 | + |
| 45 | +# compute_BOLD_balloon_model_auditory.py |
| 46 | +# |
| 47 | +# Calculate and plot fMRI BOLD signal, using the |
| 48 | +# Balloon model, as described by Stephan et al (2007) |
| 49 | +# and Friston et al (2000), and |
| 50 | +# the BOLD signal model, as described by Stephan et al (2007) and |
| 51 | +# Friston et al (2000). |
| 52 | +# Parameters for both were taken from Friston et al (2000) and they were |
| 53 | +# estimated using a 2T scanner, with a TR of 2 seconds, |
| 54 | +# |
| 55 | +# ... using data from auditory delay-match-to-sample simulation stored in 4 |
| 56 | +# different synaptic activity files |
| 57 | +# It also saves the BOLD timeseries for each and all modules in a python data file |
| 58 | +# (*.npy) |
| 59 | +# |
| 60 | +# The input data (synaptic activities) and the output (BOLD time-series) are numpy arrays |
| 61 | +# with columns in the following order: |
| 62 | +# |
| 63 | +# A1 ROI (right hemisphere, includes LSNM units and TVB nodes) |
| 64 | +# A2 ROI (right hemisphere, includes LSNM units and TVB nodes) |
| 65 | +# ST ROI (right hemisphere, includes LSNM units and TVB nodes) |
| 66 | +# PF ROI (right hemisphere, includes LSNM units and TVB nodes) |
| 67 | +# |
| 68 | +# Note: only the BOLD activity of one of the synaptic activity files is computed and stored. |
| 69 | + |
| 70 | +import numpy as np |
| 71 | + |
| 72 | +import matplotlib.pyplot as plt |
| 73 | + |
| 74 | +from scipy.integrate import odeint |
| 75 | + |
| 76 | +# define the name of the input files where the synaptic activities are stored |
| 77 | +# we have four files: TC-PSL, Tones-PSL, TC-DMS, Tones-DMS |
| 78 | +SYN_file_2 = 'output.TC_PSL/synaptic_in_ROI.npy' |
| 79 | +SYN_file_3 = 'output.Tones_PSL/synaptic_in_ROI.npy' |
| 80 | +SYN_file_4 = 'output.TC_DMS/synaptic_in_ROI.npy' |
| 81 | +SYN_file_1 = 'output.Tones_DMS/synaptic_in_ROI.npy' |
| 82 | + |
| 83 | +# define the name of the output file where the BOLD timeseries will be stored |
| 84 | +BOLD_file = 'output.Tones_DMS/lsnm_bold_balloon.npy' |
| 85 | + |
| 86 | +# define balloon model parameters... |
| 87 | +tau_s = 1.5 # rate constant of vasodilatory signal decay in seconds |
| 88 | + # from Friston et al, 2000 |
| 89 | + |
| 90 | +tau_f = 4.5 # Time of flow-dependent elimination or feedback regulation |
| 91 | + # in seconds, from Friston et al, 2000 |
| 92 | + |
| 93 | +alpha = 0.2 # Grubb's vessel stiffness exponent, from Friston et al, 2000 |
| 94 | + |
| 95 | +tau_0 = 1.0 # Hemodynamic transit time in seconds |
| 96 | + # from Friston et al, 2000 |
| 97 | + |
| 98 | +epsilon = 0.1 # efficacy of synaptic activity to induce the signal, |
| 99 | + # from Friston et al, 2000 |
| 100 | + |
| 101 | +# define BOLD model parameters... |
| 102 | +r_0 = 25.0 # Slope of intravascular relaxation rate (Hz) |
| 103 | + # (Obata et al, 2004) |
| 104 | + |
| 105 | +nu_0 = 40.3 # Frequency offset at the outer surface of magnetized |
| 106 | + # vessels (Hz) (Obata et al, 2004) |
| 107 | + |
| 108 | +e = 1.43 # Ratio of intra- and extravascular BOLD signal at rest |
| 109 | + # (Obata et al, 2004) |
| 110 | + |
| 111 | +V_0 = 0.02 # Resting blood volume fraction, from Friston et al, 2000 |
| 112 | + |
| 113 | +E_0 = 0.8 # Resting oxygen extraction fraction (Friston et al, 2000) |
| 114 | + |
| 115 | +TE = 0.040 # Echo time for a 1.5T scanner (Friston et al, 2000) |
| 116 | + |
| 117 | +# calculate BOLD model coefficients, from Friston et al (2000) |
| 118 | +k1 = 4.3 * nu_0 * E_0 * TE |
| 119 | +k2 = e * r_0 * E_0 * TE |
| 120 | +k3 = 1.0 - epsilon |
| 121 | + |
| 122 | +def balloon_function(y, t, syn): |
| 123 | + ''' |
| 124 | + Balloon model of hemodynamic change |
| 125 | + |
| 126 | + ''' |
| 127 | + |
| 128 | + # unpack initial values |
| 129 | + s = y[0] |
| 130 | + f = y[1] |
| 131 | + v = y[2] |
| 132 | + q = y[3] |
| 133 | + |
| 134 | + x = syn[np.floor(t * synaptic_timesteps / T)] |
| 135 | + |
| 136 | + # the balloon model equations |
| 137 | + ds = epsilon * x - (1. / tau_s) * s - (1. / tau_f) * (f - 1) |
| 138 | + df = s |
| 139 | + dv = (1. / tau_0) * (f - v ** (1. / alpha)) |
| 140 | + dq = (1. / tau_0) * ((f * (1. - (1. - E_0) ** (1. / f)) / E_0) - |
| 141 | + (v ** (1. / alpha)) * (q / v)) |
| 142 | + |
| 143 | + return [ds, df, dv, dq] |
| 144 | + |
| 145 | +# define neural synaptic time interval in seconds. The simulation data is collected |
| 146 | +# one data point at synaptic intervals (10 simulation timesteps). Every simulation |
| 147 | +# timestep is equivalent to 3.5 ms. |
| 148 | +Ti = 0.0035 * 10 |
| 149 | + |
| 150 | +# Total time of scanning experiment in seconds (timesteps X 5) |
| 151 | +T = 34.3 |
| 152 | + |
| 153 | +# Time for one complete trial in milliseconds |
| 154 | +Ttrial = 2.8 |
| 155 | + |
| 156 | +# the scanning happened every Tr interval below (in milliseconds). This |
| 157 | +# is the time needed to sample hemodynamic activity to produce |
| 158 | +# each fMRI image. |
| 159 | +Tr = 2 |
| 160 | + |
| 161 | +# how many scans do you want to remove from beginning of BOLD timeseries? |
| 162 | +scans_to_remove = 0 |
| 163 | + |
| 164 | +# read the input file that contains the synaptic activities of all ROIs |
| 165 | +syn1 = np.load(SYN_file_1) |
| 166 | +syn2 = np.load(SYN_file_2) |
| 167 | +syn3 = np.load(SYN_file_3) |
| 168 | +syn4 = np.load(SYN_file_4) |
| 169 | + |
| 170 | +print 'LENGTH OF SYNAPTIC TIME-SERIES: ', syn1[0].size |
| 171 | + |
| 172 | +# Throw away first value of each synaptic array (it is always zero) |
| 173 | +a1_syn1 = np.delete(syn1[0], 0) |
| 174 | +a2_syn1 = np.delete(syn1[1], 0) |
| 175 | +st_syn1 = np.delete(syn1[2], 0) |
| 176 | +pf_syn1 = np.delete(syn1[3], 0) |
| 177 | +a1_syn2 = np.delete(syn2[0], 0) |
| 178 | +a2_syn2 = np.delete(syn2[1], 0) |
| 179 | +st_syn2 = np.delete(syn2[2], 0) |
| 180 | +pf_syn2 = np.delete(syn2[3], 0) |
| 181 | +a1_syn3 = np.delete(syn3[0], 0) |
| 182 | +a2_syn3 = np.delete(syn3[1], 0) |
| 183 | +st_syn3 = np.delete(syn3[2], 0) |
| 184 | +pf_syn3 = np.delete(syn3[3], 0) |
| 185 | +a1_syn4 = np.delete(syn4[0], 0) |
| 186 | +a2_syn4 = np.delete(syn4[1], 0) |
| 187 | +st_syn4 = np.delete(syn4[2], 0) |
| 188 | +pf_syn4 = np.delete(syn4[3], 0) |
| 189 | + |
| 190 | +# put together all the synaptic activity array that form part of a study, only |
| 191 | +# for normalization purposes |
| 192 | +# put together synaptic arrays that correspond to the same brain region |
| 193 | +a1 = np.append(a1_syn1, [a1_syn2, a1_syn3, a1_syn4]) |
| 194 | +a2 = np.append(a2_syn1, [a2_syn2, a2_syn3, a2_syn4]) |
| 195 | +st = np.append(st_syn1, [st_syn2, st_syn3, st_syn4]) |
| 196 | +pf = np.append(pf_syn1, [pf_syn2, pf_syn3, pf_syn4]) |
| 197 | + |
| 198 | +# extract the synaptic activities corresponding to each ROI, and normalize to (0,1): |
| 199 | +a1_syn = (a1_syn1-a1.min()) / (a1.max() - a1.min()) |
| 200 | +a2_syn = (a2_syn1-a2.min()) / (a2.max() - a2.min()) |
| 201 | +st_syn = (st_syn1-st.min()) / (st.max() - st.min()) |
| 202 | +pf_syn = (pf_syn1-pf.min()) / (pf.max() - pf.min()) |
| 203 | + |
| 204 | +# Extract number of timesteps from one of the synaptic activity arrays |
| 205 | +synaptic_timesteps = a1_syn.size |
| 206 | +print 'Size of synaptic arrays: ', synaptic_timesteps |
| 207 | + |
| 208 | +# Given neural synaptic time interval and total time of scanning experiment, |
| 209 | +# construct a numpy array of time points (data points provided in data files) |
| 210 | +time_in_seconds = np.arange(0, T, Ti) |
| 211 | + |
| 212 | +# Hard coded initial conditions |
| 213 | +s = 0 # s, blood flow |
| 214 | +f = 1. # f, blood inflow |
| 215 | +v = 1. # v, venous blood volume |
| 216 | +q = 1. # q, deoxyhemoglobin content |
| 217 | + |
| 218 | +# initial conditions vectors |
| 219 | +y_0_a1 = [s, f, v, q] |
| 220 | +y_0_a2 = [s, f, v, q] |
| 221 | +y_0_st = [s, f, v, q] |
| 222 | +y_0_pf = [s, f, v, q] |
| 223 | + |
| 224 | +# generate synaptic time array |
| 225 | +t_syn = np.arange(0, synaptic_timesteps) |
| 226 | + |
| 227 | +# generate time array for solution |
| 228 | +t = time_in_seconds |
| 229 | + |
| 230 | +# Use the following lines to test the balloon BOLD model |
| 231 | +#v1_syn[0:300] = .01 # 15 seconds do nothing |
| 232 | +#v1_syn[300:320] = .1 # One-second stimulus |
| 233 | +#v1_syn[320:920] = .01 # 30-second do nothing |
| 234 | +#v1_syn[920:940] = .1 # two-second stimulus |
| 235 | +#v1_syn[940:1540] = .01 # 30-second do nothing |
| 236 | +#v1_syn[1540:1560]= .1 # three-second stimulus |
| 237 | +#v1_syn[1560:2160]= .01 # 30-second do nothing |
| 238 | +#v1_syn[2160:2180]= .1 # four-second stimulus |
| 239 | +#v1_syn[2180:2780]= .01 # 30-second do nothing |
| 240 | +#v1_syn[2780:2800]= .1 # five-second stimulus |
| 241 | +#v1_syn[2800:3400]= .01 # 30-second do nothing |
| 242 | +#v1_syn[3400:3420]= .1 # one-second stimulus |
| 243 | +#v1_syn[3420:3959]= .01 # 30-second do nothing |
| 244 | + |
| 245 | +# solve the ODEs for given initial conditions, parameters, and timesteps |
| 246 | +state_a1 = odeint(balloon_function, y_0_a1, t, args=(a1_syn,) ) |
| 247 | +state_a2 = odeint(balloon_function, y_0_a2, t, args=(a2_syn,) ) |
| 248 | +state_st = odeint(balloon_function, y_0_st, t, args=(st_syn,) ) |
| 249 | +state_pf = odeint(balloon_function, y_0_pf, t, args=(pf_syn,) ) |
| 250 | + |
| 251 | +# Unpack the state variables used in the BOLD model |
| 252 | +s_a1 = state_a1[:, 0] |
| 253 | +f_a1 = state_a1[:, 1] |
| 254 | +v_a1 = state_a1[:, 2] |
| 255 | +q_a1 = state_a1[:, 3] |
| 256 | + |
| 257 | +s_a2 = state_a2[:, 0] |
| 258 | +f_a2 = state_a2[:, 1] |
| 259 | +v_a2 = state_a2[:, 2] |
| 260 | +q_a2 = state_a2[:, 3] |
| 261 | + |
| 262 | +s_st = state_st[:, 0] |
| 263 | +f_st = state_st[:, 1] |
| 264 | +v_st = state_st[:, 2] |
| 265 | +q_st = state_st[:, 3] |
| 266 | + |
| 267 | +s_pf = state_pf[:, 0] |
| 268 | +f_pf = state_pf[:, 1] |
| 269 | +v_pf = state_pf[:, 2] |
| 270 | +q_pf = state_pf[:, 3] |
| 271 | + |
| 272 | +# now, we need to calculate BOLD signal at each timestep, based on v and q obtained from solving |
| 273 | +# balloon model ODE above. |
| 274 | +a1_BOLD = np.array(V_0 * (k1 * (1. - q_a1) + k2 * (1. - q_a1 / v_a1) + k3 * (1. - v_a1)) ) |
| 275 | +a2_BOLD = np.array(V_0 * (k1 * (1. - q_a2) + k2 * (1. - q_a2 / v_a2) + k3 * (1. - v_a2)) ) |
| 276 | +st_BOLD = np.array(V_0 * (k1 * (1. - q_st) + k2 * (1. - q_st / v_st) + k3 * (1. - v_st)) ) |
| 277 | +pf_BOLD = np.array(V_0 * (k1 * (1. - q_pf) + k2 * (1. - q_pf / v_pf) + k3 * (1. - v_pf)) ) |
| 278 | + |
| 279 | +# The following is for display purposes only: |
| 280 | +# Construct a numpy array of timesteps (data points provided in data file) |
| 281 | +# to convert from timesteps to time in seconds we do the following: |
| 282 | +# Each simulation time-step equals 5 milliseconds |
| 283 | +# However, we are recording only once every 10 time-steps |
| 284 | +# Therefore, each data point in the output files represents 50 milliseconds. |
| 285 | +# Thus, we need to multiply the datapoint times 50 ms... |
| 286 | +# ... and divide by 1000 to convert to seconds |
| 287 | +#t = np.linspace(0, 659*50./1000., num=660) |
| 288 | +t = np.linspace(0, synaptic_timesteps * 35.0 / 1000., num=synaptic_timesteps) |
| 289 | + |
| 290 | +# downsample the BOLD signal arrays to produce scan rate of 2 per second |
| 291 | +scanning_timescale = np.arange(0, synaptic_timesteps, synaptic_timesteps / (T/Tr)) |
| 292 | +scanning_timescale = scanning_timescale.astype(int) # don't forget to convert indices to integer |
| 293 | +mr_time = t[scanning_timescale] |
| 294 | +a1_BOLD = a1_BOLD[scanning_timescale] |
| 295 | +a2_BOLD = a2_BOLD[scanning_timescale] |
| 296 | +st_BOLD = st_BOLD[scanning_timescale] |
| 297 | +pf_BOLD = pf_BOLD[scanning_timescale] |
| 298 | + |
| 299 | +print 'Size of BOLD arrays: ', a1_BOLD.size |
| 300 | + |
| 301 | +# round of mr time for display purposes |
| 302 | +mr_time = np.round(mr_time, decimals=0) |
| 303 | + |
| 304 | +# create a numpy array of timeseries |
| 305 | +lsnm_BOLD = np.array([a1_BOLD, a2_BOLD, st_BOLD, pf_BOLD]) |
| 306 | + |
| 307 | +# now, save all BOLD timeseries to a single file |
| 308 | +np.save(BOLD_file, lsnm_BOLD) |
| 309 | + |
| 310 | +# increase font size for display purposes |
| 311 | +#plt.rcParams.update({'font.size': 30}) |
| 312 | + |
| 313 | +# Set up figure to plot synaptic activity |
| 314 | +plt.figure() |
| 315 | + |
| 316 | +plt.suptitle('SIMULATED SYNAPTIC ACTIVITY') |
| 317 | + |
| 318 | +# Plot synaptic activities |
| 319 | +plt.plot(t, a1_syn, label='A1') |
| 320 | +plt.plot(t, a2_syn, label='A2') |
| 321 | +plt.plot(t, st_syn, label='ST') |
| 322 | +plt.plot(t, pf_syn, label='PFC') |
| 323 | + |
| 324 | +plt.legend() |
| 325 | + |
| 326 | +# Set up separate figures to plot fMRI BOLD signal |
| 327 | +plt.figure() |
| 328 | + |
| 329 | +plt.suptitle('SIMULATED fMRI BOLD SIGNAL') |
| 330 | + |
| 331 | +plt.plot(mr_time, a1_BOLD, label='A1') |
| 332 | + |
| 333 | +plt.plot(mr_time, a2_BOLD, label='A2') |
| 334 | + |
| 335 | +plt.plot(mr_time, st_BOLD, label='ST') |
| 336 | + |
| 337 | +plt.plot(mr_time, pf_BOLD, label='PFC') |
| 338 | + |
| 339 | +plt.legend() |
| 340 | + |
| 341 | +# Show the plots on the screen |
| 342 | +plt.show() |
0 commit comments