OSCR

Three parsimonious spatiotemporal patterns in cerebellum reveal individual traits in function and behavior.

Code ↔ Paper

18 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 18 matches
  1. [1] § Methods › Simulation of phase delay structure in BOLD-like 1/f signals ↔ BOLD_simulation.py, lines 100–160 · score 0.82 · Wilson Cowan, 1–100 s, OU, field, simulated, 1 s
  2. [2] § Results › Behavioral phenotypes are associated with CPCA-derived features ↔ sCCA_analysis_wholebrain.py, lines 349–498 · score 0.82 · aggressive behavior, language comprehension, working memory, canonical components, fear, FDR
  3. [3] § Methods › Comparison with traditional spatiotemporal models ↔ figures.ipynb, lines 1580–1725 · score 0.78 · temporal ICA, spatial ICA, BOLD activity, spatial weight, spatial correlations, TICA
  4. [4] § Methods › Comparison with traditional spatiotemporal models › Quasi-periodic patterns (QPPs) ↔ run_qpp.py, lines 30–127 · score 0.77 · window length, correlation threshold, QPPs, iteratively, segment, scan
  5. [5] § Methods › Behavior data ↔ sCCA_analysis_wholebrain.py, lines 349–498 · score 0.77 · aggressive behavior, language comprehension, working memory, fear, alertness, personality
  6. [6] § Methods › Sex classification ↔ sex_LSVM.py, lines 324–392 · score 0.77 · hidden layer, confusion matrices, ROC, fold, training, curves
  7. [7] § Results › CPCA-based reconstruction preserves functional connectivity modularity ↔ figures.ipynb, lines 1203–1293 · score 0.76 · Louvain community detection, module partition, original correlation, NMI, functional connectivity, reconstructed
  8. [8] § Methods › Signal reconstruction and functional connectivity ↔ figures.ipynb, lines 1203–1293 · score 0.71 · normalized mutual information, original FC, Louvain, NMI, reconstructed, matrices
  9. [9] § Results › Three prominent spatiotemporal patterns of the cerebellum reflect FC topographies ↔ run_analysis.sh, lines 1–63 · score 0.68 · hidden Markov models, temporal ICA, spatial ICA, HMM, eigenmaps, FC
  10. [10] § Methods › Comparison with traditional spatiotemporal models ↔ CPCA_analysis.py, lines 400–467 · score 0.66 · voxel coordinates, MNI, Moran, Pearson, HMM, templates
  11. [11] § Methods › Statistical analysis ↔ CPCA_analysis.py, lines 182–203 · score 0.66 · Moran spectral randomization, spatial autocorrelation, Permutation, connectivity, components
  12. [12] § Results › Sex differences are detected by machine learning methods ↔ ROC_CM_null.py, lines 124–158 · score 0.64 · ROC curve, confusion matrix, sex classification, ANN, accuracy, predictions
  13. [13] § Results › Three prominent spatiotemporal patterns of the cerebellum reflect FC topographies ↔ figures.ipynb, lines 724–766 · score 0.60 · hidden Markov models, FC topographies, TICA, SICA, zero lag, HMM
  14. [14] § Results › Sex differences are detected by machine learning methods ↔ sex_LSVM.py, lines 324–392 · score 0.59 · ROC curve, confusion matrix, AUC, ANN, LSVM, accuracy
  15. [15] § Methods › Sparse canonical correlation analysis ↔ sCCA_analysis_wholebrain.py, lines 37–185 · score 0.57 · sparse canonical correlation, sCCA, Model
  16. [16] § Methods › Sex classification ↔ ROC_CM_null.py, lines 124–158 · score 0.56 · confusion matrices, ROC, curves, ANN, threshold, accuracy
  17. [17] § Results › Phase delay components capture temporal propagation patterns ↔ figures.ipynb, lines 1580–1725 · score 0.55 · identified recurring, low dimensional, BOLD activity, 0.01 Hz, temporally, propagation
  18. [18] § Results ↔ sCCA_analysis_wholebrain.py, lines 545–596 · score 0.51 · sCCA, rsFC, subsets, sparse, trained, transform

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Jupyter notebook · 2,209 lines · 100 KB · no license · 5 matches

  1. # %% [markdown]
  2. # # <font size="6"><b>Study Abstract</b></font>
  3. #
  4. # <font size='4'> Low-frequency blood-oxygenated-level-dependent (BOLD) signals are known to exhibit a global spatiotemporal pattern of activity at rest, labeled the quasiperiodic pattern (QPP). This spatiotemporal pattern can be described as a recurring global propagation of BOLD activity from task-positive network regions towards default mode network regions. Alongside the QPP, a wide variety of functional connectivity topographies contrasting task-positive and default mode network regions have been reported in the literature. In this study, we demonstrate that most widely-studied functional connectivity topographies arise from the same spatiotemporal dynamics - the QPP. In other words, much of the resting-state fMRI literature has been describing the same thing with different methods. Using resting-state functional magnetic resonance imaging scans from the Human Connectome Project (n=50), we examined the relationship between previously observed functional connectivity topographies and the time-lagged dynamics of the QPP. We find that the time-lagged dynamics of the QPP are distributed across multiple axes in low-dimensional functional connectivity space(s). Popular functional connectivity topographies generally correspond to one or more of these axes. Thus, functional connectivity topographies represent low-dimensional descriptions of the synchronous dynamics within the larger time-lagged QPP pattern. We further demonstrate that ‘global signal’ and ‘task-positive/task-negative’ functional connectivity topographies arise from the same dynamics of the QPP: a time-lag in BOLD signals between task-positive and task-negative network regions. Overall, we find that the QPP underlies a striking variety of previously observed phenomena in low-frequency resting-state BOLD signals.</font>
  5. # %% [markdown]
  6. # ## Module Imports
  7. # %%
  8. import matplotlib.image as mpimg
  9. import matplotlib.pyplot as plt
  10. import matplotlib.gridspec as gridspec
  11. import nibabel as nb
  12. import numpy as np
  13. import pandas as pd
  14. import pickle
  15. from brainspace.null_models import SpinPermutations
  16. from bct import community_louvain, partition_distance
  17. from IPython.core.display import HTML
  18. from matplotlib.offsetbox import OffsetImage, AnnotationBbox, TextArea
  19. from matplotlib import cm, colors, animation
  20. from matplotlib.cm import ScalarMappable
  21. from matplotlib.colors import LinearSegmentedColormap, Normalize
  22. from matplotlib.patches import FancyArrowPatch, FancyBboxPatch, BoxStyle, Rectangle
  23. from mpl_toolkits.axes_grid1 import make_axes_locatable
  24. from mpl_toolkits.mplot3d.axes3d import Axes3D
  25. from mpl_toolkits.mplot3d import proj3d
  26. from mpl_toolkits.axes_grid1 import AxesGrid
  27. from nilearn.surface import load_surf_mesh
  28. from numpy import random as rand
  29. from run_main_pca import pca as cpca, rotation
  30. from scipy import linalg
  31. from scipy.signal import resample, welch, hilbert
  32. from scipy.spatial.distance import cdist, pdist, squareform
  33. from scipy.stats import zscore
  34. from sklearn.linear_model import LinearRegression
  35. from sklearn.cluster import KMeans
  36. from sklearn.decomposition import KernelPCA, PCA, FactorAnalysis, FastICA
  37. from sklearn.manifold import SpectralEmbedding
  38. from utils.utils import load_data_and_stack, load_gifti, pull_gifti_data, load_cifti, pull_cifti_data, \
  39. write_to_gifti, write_to_cifti
  40. from utils.rotation import varimax
  41. # %% [markdown]
  42. # ## Helper Functions
  43. # %%
  44. # Global variables
  45. tr = 0.72 # HCP sampling rate
  46. n_vertices = 4801 # Number of vertices in functional scan (after pre-processing)
  47. n_vert_L=2562 # Number of vertices in left cortex (n_vert_R = n_vertices - n_vert_L)
  48. n_ts=60000 # Number of time-points in group-concatenated time courses
  49. # Helper Functions
  50. def axis3d_scaler(x, y, z):
  51. # https://stackoverflow.plex_/questions/30223161/matplotlib-mplot3d-how-to-increase-the-size-of-an-axis-stretch-in-a-3d-plot
  52. """
  53. stretch axis of 3-dimensional plot
  54. """
  55. scale=np.diag([x, y, z, 1.0])
  56. scale=scale*(1.0/scale.max())
  57. scale[3,3]=1.0
  58. return scale
  59. def circular_corr(alpha1, alpha2, nanrobust, axis=None):
  60. # Taken from:
  61. # https://github.com/jhamrick/python-snippets/blob/master/snippets/circstats.py
  62. """
  63. Calculate circulation correlation between two phase time series
  64. """
  65. if axis is not None and alpha1.shape[axis] != alpha2.shape[axis]:
  66. raise(ValueError, "shape mismatch")
  67. # compute mean directions
  68. if axis is None:
  69. n = alpha1.size
  70. else:
  71. n = alpha1.shape[axis]
  72. #################################################################
  73. c1 = np.cos(alpha1)
  74. c1_2 = np.cos(2*alpha1)
  75. c2 = np.cos(alpha2)
  76. c2_2 = np.cos(2*alpha2)
  77. s1 = np.sin(alpha1)
  78. s1_2 = np.sin(2*alpha1)
  79. s2 = np.sin(alpha2)
  80. s2_2 = np.sin(2*alpha2)
  81. if nanrobust:
  82. sumfunc = lambda x: np.nansum(x, axis=axis)
  83. else:
  84. sumfunc = lambda x: np.sum(x, axis=axis)
  85. num = 4 * (sumfunc(c1*c2) * sumfunc(s1*s2) -
  86. sumfunc(c1*s2) * sumfunc(s1*c2))
  87. den = np.sqrt((n**2 - sumfunc(c1_2)**2 - sumfunc(s1_2)**2) *
  88. (n**2 - sumfunc(c2_2)**2 - sumfunc(s2_2)**2))
  89. rho = num / den
  90. return rho
  91. def complex_motion(g1, g2, g2_phase_shift, y, w, n_samples, n_cycles, n_decay_cycles, var,
  92. decay_phase_shift=0):
  93. """
  94. Generate (damped) 2-dimensional complex motion between two spatial patterns (g1 and g2). Properties of motion
  95. are controlled by parameters described below.
  96. Parameters
  97. ----------
  98. g1: 2-d numpy array
  99. spatial pattern 1
  100. g2: 2-d numpy array
  101. spatial pattern 2
  102. g2_phase_shift: float
  103. phase shift, in radians, to be applied to g2 pattern
  104. y : float
  105. Decay amplitude of complex motion. Decay is sinusoidal, so the complex
  106. motion waxes and wanes within a cycle.
  107. w: float
  108. angular frequency of sine oscillation. Controls the frequency or speed of the oscillation
  109. n_samples: int
  110. number of time samples to generate
  111. n_cycles: int
  112. number of full oscillations to cycle through with specified number of time samples (n_samples)
  113. n_decay_cycles: int
  114. number of decay cycles to cycle through with specified number of time samples (n_samples)
  115. var: float
  116. variance of gaussian noise added to each time point
  117. Returns:
  118. list: complex motion spatial pattern at each time point (n_samples)
  119. """
  120. g1_vec = g1.flatten()
  121. g2_vec = g2.flatten()
  122. x_len, y_len = g1.shape
  123. t_vec = np.linspace(1,(2*np.pi)*n_cycles, n_samples)
  124. t_decay_vec = 0.5*np.cos(
  125. np.linspace(1,(2*np.pi)*n_decay_cycles, n_samples) + decay_phase_shift
  126. )+0.5
  127. g_anim = []
  128. for t, t_d in zip(t_vec, t_decay_vec):
  129. g_grid = []
  130. for g1, g2 in zip(g1_vec, g2_vec):
  131. g_grid.append(2*np.exp(y*t_d)*(np.sin(w*t)*g1 - np.sin(w*t + g2_phase_shift)*g2))
  132. grid = np.array(g_grid).reshape(x_len, y_len)
  133. grid += np.random.normal(0,var,(grid.shape[0], grid.shape[1]))
  134. g_anim.append(grid)
  135. return g_anim
  136. def complex_motion_animation(g_anim, fig, ax, vmin=-1, vmax=1):
  137. """Play animation of complex motion"""
  138. ims = []
  139. for g in g_anim:
  140. im = ax.imshow(g, cmap='coolwarm', vmin=vmin, vmax=vmax)
  141. ims.append([im])
  142. anim = animation.ArtistAnimation(fig, ims, interval=100, blit=True, repeat_delay=1000)
  143. return anim
  144. def convert_polar_xticks_to_radians_secs(ax, cycle_length):
  145. # https://stackoverflow.com/questions/21172228/python-matplotlib-polar-plots-with-angular-labels-in-radians
  146. # Converts x-tick labels from degrees to radians
  147. # Get the x-tick positions (returns in radians)
  148. label_positions = ax.get_xticks()
  149. # Convert to a list since we want to change the type of the elements
  150. labels = list(label_positions)
  151. # Format each label (edit this function however you'd like)
  152. labels = [format_secs_radians_label(label, cycle_length) for label in labels]
  153. ax.set_xticklabels(labels)
  154. def convert_polar_xticks_to_radians(ax):
  155. # https://stackoverflow.com/questions/21172228/python-matplotlib-polar-plots-with-angular-labels-in-radians
  156. """
  157. Converts x-tick labels from degrees to radians
  158. """
  159. # Get the x-tick positions (returns in radians)
  160. label_positions = ax.get_xticks()
  161. # Convert to a list since we want to change the type of the elements
  162. labels = list(label_positions)
  163. # Format each label (edit this function however you'd like)
  164. labels = [format_radians_label(label) for label in labels]
  165. ax.set_xticklabels(labels)
  166. def create_hrf_group(n_ts, activation_indx, ts_len, tr, amplitude, phase_jitter,
  167. amplitude_jitter, ts_sampling=0.01, repeat_n=1):
  168. """
  169. n_ts: number of timeseries
  170. activation_indx = index of activation time point
  171. ts_len: length of time series
  172. tr: the sampling rate of the original time series
  173. amplitude: amplitude of double gamma function
  174. phase_offset_window: allowable phase offsets between time series -
  175. set as a symmetric window length - sampled from uniform distribution
  176. ts_sampling: resolution of original time series - default=0.01 Hz
  177. std_noise: amount of gaussian noise to add to time series - scaling parameter between 0 and 1
  178. """
  179. hrf=double_gamma_hrf(60, ts_sampling)
  180. ts_all = np.zeros((n_ts, ts_len))
  181. for n in range(n_ts):
  182. ts = ts_all[n,:]
  183. indx = rand.randint(activation_indx - phase_jitter,
  184. activation_indx + phase_jitter)
  185. amp = rand.randint(amplitude - amplitude_jitter,
  186. amplitude + amplitude_jitter)
  187. ts[indx] = 1
  188. ts_all[n,:] = (convolve_hrf_events(hrf, ts) * amplitude)
  189. n_resample=np.int(ts_sampling*ts_len/tr)
  190. hrf_ts_resample = resample(ts_all, n_resample, axis=1)
  191. return np.tile(hrf_ts_resample, repeat_n)
  192. def cropImage(img, width_l, width_r, height):
  193. """
  194. Crop image by width (tuple) and height (tuple) ranges
  195. """
  196. slice1 = img[height[0]:height[1],width_l[0]:width_l[1],:]
  197. slice2 = img[height[0]:height[1],width_r[0]:width_r[1],:]
  198. merged_image = np.append(slice1, slice2, axis=1)
  199. return merged_image
  200. def cropImage_single(img, width, height):
  201. return img[height[0]:-height[1]:,width[0]:-width[1],:]
  202. def format_radians_label(float_in):
  203. # https://stackoverflow.com/questions/21172228/python-matplotlib-polar-plots-with-angular-labels-in-radians
  204. """
  205. Converts a float value in radians into a string representation of that float
  206. """
  207. string_out = str(float_in / (np.pi))+"π"
  208. return string_out
  209. def format_secs_radians_label(float_in, cycle_length):
  210. # https://stackoverflow.com/questions/21172228/python-matplotlib-polar-plots-with-angular-labels-in-radians
  211. # Converts a float value in radians into a
  212. # string representation of that float
  213. cycle_ratio = float_in/(2*np.pi)
  214. string_out = f'{np.round(cycle_ratio*cycle_length,2)}s\n({float_in / (np.pi)}π)'
  215. return string_out
  216. def getImage(path):
  217. """Utility function for reading image file"""
  218. return OffsetImage(plt.imread(path), zoom=0.1)
  219. def image_3d(ax, arr, label, xy, offset_x, offset_y, label_offset_y=0, zoom=0.05, pad=0):
  220. """ Place an image (arr) as annotation at position xy
  221. https://stackoverflow.com/questions/48180327/matplotlib-3d-scatter-plot-with-images-as-annotations
  222. """
  223. im = OffsetImage(arr, zoom=zoom)
  224. im.image.axes = ax
  225. ab = AnnotationBbox(im, xy, xybox=(offset_x, offset_y),
  226. xycoords='data', boxcoords="offset points",
  227. pad=pad, arrowprops=dict(arrowstyle="->"))
  228. ax.add_artist(ab)
  229. offsetbox = TextArea(label, minimumdescent=False)
  230. ab = AnnotationBbox(offsetbox, xy,
  231. xybox=(offset_x, offset_y+label_offset_y),
  232. xycoords='data',
  233. boxcoords=("offset points"))
  234. ax.add_artist(ab)
  235. def plot_sorted_corr_mat(corr_mat, cluster_assignments, ax):
  236. """
  237. Plot correlation matrix sorted by cluster assignments of nodes (must pass matplotlib axis)
  238. """
  239. # Create sorting index from factor assignments
  240. sort_indx = np.argsort(cluster_assignments)
  241. sorted_vals = np.sort(cluster_assignments)
  242. # Sort Distance Matrix
  243. sortedmat = [[corr_mat[i][j] for j in sort_indx] for i in sort_indx]
  244. # Plot Distance Matrix
  245. c = ax.pcolormesh(sortedmat, cmap='coolwarm')
  246. plt.colorbar(c, ax=ax)
  247. # Plot rectangular patches along diagnol to indicate factor assignments
  248. for i in np.unique(sorted_vals):
  249. ind = np.where(sorted_vals == i)
  250. mn = np.min(ind)
  251. mx = np.max(ind)
  252. sz=(mx-mn)+1
  253. rect = Rectangle((mn,mn), sz, sz , linewidth=1,
  254. edgecolor='black', facecolor='none')
  255. ax.add_patch(rect)
  256. def proj_3d(X, ax1, ax2):
  257. """ From a 3D point in axes ax1,
  258. calculate position in 2D in ax2
  259. https://stackoverflow.com/questions/48180327/matplotlib-3d-scatter-plot-with-images-as-annotations
  260. """
  261. x,y,z = X
  262. x2, y2, _ = proj3d.proj_transform(x,y,z, ax1.get_proj())
  263. return ax2.transData.inverted().transform(ax1.transData.transform((x2, y2)))
  264. def shiftedColorMap(cmap, start=0, midpoint=0.5, stop=1.0, name='shiftedcmap'):
  265. '''
  266. Function to offset the "center" of a colormap. Useful for
  267. data with a negative min and positive max and you want the
  268. middle of the colormap's dynamic range to be at zero
  269. Input
  270. -----
  271. cmap : The matplotlib colormap to be altered
  272. start : Offset from lowest point in the colormap's range.
  273. Defaults to 0.0 (no lower ofset). Should be between
  274. 0.0 and 1.0.
  275. midpoint : The new center of the colormap. Defaults to
  276. 0.5 (no shift). Should be between 0.0 and 1.0. In
  277. general, this should be 1 - vmax/(vmax + abs(vmin))
  278. For example if your data range from -15.0 to +5.0 and
  279. you want the center of the colormap at 0.0, `midpoint`
  280. should be set to 1 - 5/(5 + 15)) or 0.75
  281. stop : Offset from highets point in the colormap's range.
  282. Defaults to 1.0 (no upper ofset). Should be between
  283. 0.0 and 1.0.
  284. '''
  285. cdict = {
  286. 'red': [],
  287. 'green': [],
  288. 'blue': [],
  289. 'alpha': []
  290. }
  291. # regular index to compute the colors
  292. reg_index = np.linspace(start, stop, 257)
  293. # shifted index to match the data
  294. shift_index = np.hstack([
  295. np.linspace(0, midpoint, 128, endpoint=False),
  296. np.linspace(midpoint, 1.0, 129, endpoint=True)
  297. ])
  298. for ri, si in zip(reg_index, shift_index):
  299. r, g, b, a = cmap(ri)
  300. cdict['red'].append((si, r, r))
  301. cdict['green'].append((si, g, g))
  302. cdict['blue'].append((si, b, b))
  303. cdict['alpha'].append((si, a, a))
  304. newcmap = LinearSegmentedColormap(name, cdict)
  305. plt.register_cmap(cmap=newcmap)
  306. return newcmap
  307. def transition_matrix(transitions):
  308. """Generate markov transition from a 1-d sequence of state labels (numpy array)"""
  309. #https://stackoverflow.com/questions/46657221/generating-markov-transition-matrix-in-python
  310. n = 1 + np.int(np.nanmax(transitions)) #number of states
  311. M = [[0]*n for _ in range(n)]
  312. for (i,j) in zip(transitions,transitions[1:]):
  313. if ~np.isnan(i) and ~np.isnan(j):
  314. M[np.int(i)][np.int(j)] += 1
  315. #now convert to probabilities:
  316. for row in M:
  317. s = sum(row)
  318. if s > 0:
  319. row[:] = [f/s for f in row]
  320. return np.array(M)
  321. def traveling_index(real_vec, imag_vec):
  322. """
  323. Compute traveling index as the reciprocal of the condition number between the
  324. real and imaginary component from cpca
  325. """
  326. return 1/np.linalg.cond(np.vstack((real_vec, imag_vec)).T)
  327. @np.vectorize
  328. def twod_gauss(x, y, var=2):
  329. """Create two-dimensional gaussian with mean (mu) and variance parameters in the x- and y-plane"""
  330. return np.exp(-((x - 0)**2 + (y - 0)**2)/(2*(var)**2))
  331. def xcorr(x, y, maxlags=30):
  332. """Calculate cross-correlation from two time series (numpy array)"""
  333. Nx = len(x)
  334. if Nx != len(y):
  335. raise ValueError('x and y must be equal length')
  336. c = np.correlate(x, y, mode=2)
  337. c /= np.sqrt(np.dot(x, x) * np.dot(y, y))
  338. if maxlags is None:
  339. maxlags = Nx - 1
  340. if maxlags >= Nx or maxlags < 1:
  341. raise ValueError('maglags must be None or strictly '
  342. 'positive < %d' % Nx)
  343. lags = np.arange(-maxlags, maxlags + 1)
  344. c = c[Nx - 1 - maxlags:Nx + maxlags]
  345. max_r = c[np.argsort(np.abs(c))[-1]]
  346. max_lag = lags[np.argsort(np.abs(c))[-1]]
  347. return max_r, max_lag
  348. # %% [markdown]
  349. # ## Create ROY-BIG-BL brain colormap that matches HCP Workbench
  350. # %%
  351. colors_roy = ["cyan", "lime", "blueviolet", "mediumblue", "black", "red", "orange", "yellow"]
  352. roy_big_bl = LinearSegmentedColormap.from_list("roy_big_bl", colors_roy)
  353. # %% [markdown]
  354. # # <b>Figure 2 - Form and Properties of Three Dominant Spatiotemporal Patterns.</b>
  355. # %% [markdown]
  356. # ## 1. Calculation of Complex Principal Component Duration and Time-Scale
  357. # %%
  358. pca_complex_res = pickle.load(open('results/pca_rest_complex_results.pkl', 'rb'))
  359. cpca_recon = pickle.load(open('results/cpca_reconstruction_results.pkl', 'rb'))
  360. # Unwrap temporal phase angles
  361. comp0_phase = np.unwrap(np.angle(pca_complex_res['pca']['pc_scores'][:,0]))
  362. comp1_phase = np.unwrap(np.angle(pca_complex_res['pca']['pc_scores'][:,1]))
  363. comp2_phase = np.unwrap(np.angle(pca_complex_res['pca']['pc_scores'][:,2]))
  364. # Calculate average duration of complete cycle from temporal phase angles
  365. avg_cycle_comp0 = np.abs(comp0_phase[-1]-comp0_phase[0])/60000
  366. avg_cycle_comp0 = (2*np.pi)/avg_cycle_comp0
  367. avg_cycle_comp1 = np.abs(comp1_phase[-1]-comp1_phase[0])/60000
  368. avg_cycle_comp1 = (2*np.pi)/avg_cycle_comp1
  369. avg_cycle_comp2 = np.abs(comp2_phase[-1]-comp2_phase[0])/60000
  370. avg_cycle_comp2 = (2*np.pi)/avg_cycle_comp2
  371. # Calculate the range (in proportion of 2pi) of spatial phase angles
  372. comp0_phase_weights = np.angle(pca_complex_res['pca']['Va'][0,:])
  373. comp1_phase_weights = np.angle(pca_complex_res['pca']['Va'][1,:])
  374. comp2_phase_weights = np.angle(pca_complex_res['pca']['Va'][2,:])
  375. comp0_spatial_phase_range = (max(comp0_phase_weights) - min(comp0_phase_weights))/(2*np.pi)
  376. comp1_spatial_phase_range = (max(comp1_phase_weights) - min(comp1_phase_weights))/(2*np.pi)
  377. comp2_spatial_phase_range = (max(comp2_phase_weights) - min(comp2_phase_weights))/(2*np.pi)
  378. # Calculate duration of lead-lag relationships in spatial phase angles
  379. comp0_phase_weights_duration = comp0_spatial_phase_range * avg_cycle_comp0
  380. comp1_phase_weights_duration = comp1_spatial_phase_range * avg_cycle_comp1
  381. comp2_phase_weights_duration = comp2_spatial_phase_range * avg_cycle_comp2
  382. # Derive time point units (in secs) for the spatial phase maps from the CPCA reconstruction (N = 30 bins)
  383. comp0_phase_weights_units = (comp0_phase_weights_duration/30)*tr
  384. comp1_phase_weights_units = (comp1_phase_weights_duration/30)*tr
  385. comp2_phase_weights_units = (comp2_phase_weights_duration/30)*tr
  386. # %% [markdown]
  387. # ## 2. Figure
  388. # %%
  389. eigs = pickle.load(open('demo_files/pca_complex_eigenvalues.pkl', 'rb'))
  390. exp_var = [eig/(n_vertices*2) for eig in eigs]
  391. _, pca_comps, _ = pull_cifti_data(load_cifti('demo_files/pca_rest_complex_ang.dtseries.nii'))
  392. zero_mask = np.std(pca_comps, axis=0) > 0
  393. pca_comps = pca_comps[:, zero_mask]
  394. hsv_cmap = plt.get_cmap('hsv')
  395. fig = plt.figure(figsize=(18,20), constrained_layout=False)
  396. crop_width_l = (0, 1000)
  397. crop_width_r = (1300, 2100)
  398. crop_height = (0,1053)
  399. gspec = fig.add_gridspec(3,1, hspace=0.2, height_ratios=[0.33, 0.33, 0.33])
  400. ## Component 1
  401. g_sub0 = gridspec.GridSpecFromSubplotSpec(2,2, subplot_spec=gspec[0], wspace=0,
  402. width_ratios=[0.4, 0.6], height_ratios=[0.01,0.99])
  403. title_ax = fig.add_subplot(g_sub0[0,0])
  404. title_ax.set_title('A) First Complex Principal Component - Pattern One',
  405. fontsize=18, fontweight='bold', loc='left')
  406. title_ax.axis('off')
  407. # Compute travel index
  408. real_vec = np.real(pca_complex_res['pca']['loadings'][0,:]).T
  409. imag_vec = np.imag(pca_complex_res['pca']['loadings'][0,:]).T
  410. t_index = traveling_index(real_vec, imag_vec)
  411. title_ax = fig.add_subplot(g_sub0[0,1])
  412. title_ax.set_title(f'Travel index:{np.round(t_index,2)}',
  413. fontsize=14, loc='center')
  414. title_ax.axis('off')
  415. g_sub0_0 = gridspec.GridSpecFromSubplotSpec(2,1, subplot_spec=g_sub0[1:,0], hspace=0.1,
  416. height_ratios = [0.6,0.4])
  417. g_sub0_0_0 = gridspec.GridSpecFromSubplotSpec(2,1, subplot_spec=g_sub0_0[0], hspace=0.1,
  418. height_ratios = [0.01,0.99])
  419. g_sub0_0_1 = gridspec.GridSpecFromSubplotSpec(2,1, subplot_spec=g_sub0_0[1], hspace=0.9,
  420. height_ratios = [0.01,0.99])
  421. title_ax = fig.add_subplot(g_sub0_0_0[0])
  422. title_ax.set_title('Phase Delay Map',
  423. fontsize=15, loc='left')
  424. title_ax.axis('off')
  425. title_ax = fig.add_subplot(g_sub0_0_1[0])
  426. title_ax.set_title('Phase Delay Values',
  427. fontsize=13, loc='left')
  428. title_ax.axis('off')
  429. ax = fig.add_subplot(g_sub0_0_0[1])
  430. img = mpimg.imread('demo_files/pca_rest_complex_comp0_ang_hsv.png')
  431. ax.imshow(cropImage(img,crop_width_l,crop_width_r,(0,960)))
  432. ax.axis('off')
  433. box = ax.get_position()
  434. box.y0 = box.y0 + 0.005
  435. box.y1 = box.y1 + 0.005
  436. ax.set_position(box)
  437. xval = np.arange(-2.41, 2.6, 0.01)
  438. yval = np.ones_like(xval)
  439. hsv_cmap_shift0 = shiftedColorMap(hsv_cmap, midpoint=0.6)
  440. norm0 = colors.Normalize(-2.42, 2.6)
  441. ax = plt.subplot(g_sub0_0_1[1], polar=True)
  442. ax.scatter(xval, yval, c=xval, s=300, cmap=hsv_cmap_shift0, norm=norm0, linewidths=0)
  443. ax.set_yticks([])
  444. convert_polar_xticks_to_radians_secs(ax, comp0_phase_weights_duration)
  445. box = ax.get_position()
  446. box.y0 = box.y0 + 0.005
  447. box.y1 = box.y1 + 0.005
  448. ax.set_position(box)
  449. ax.tick_params(axis='both', which='major', pad=10)
  450. g_sub0_1 = gridspec.GridSpecFromSubplotSpec(2,1, subplot_spec=g_sub0[1:,1], hspace=0.05,
  451. height_ratios = [0.01,0.99])
  452. title_ax = fig.add_subplot(g_sub0_1[0])
  453. title_ax.set_title('Reconstructed Time Points',
  454. fontsize=15, loc='left')
  455. title_ax.axis('off')
  456. g_sub0_1_0 = gridspec.GridSpecFromSubplotSpec(2,3, subplot_spec=g_sub0_1[1], hspace=0.05,
  457. wspace=0.05)
  458. base_dir = 'demo_files/cpca_recon_pics'
  459. t_samples = [0, 5, 9, 15, 20, 26]
  460. for i, t in enumerate(t_samples):
  461. ax = fig.add_subplot(g_sub0_1_0[i])
  462. img = mpimg.imread(f'{base_dir}/cpca_recon_comp0_t{t}.png')
  463. ax.set_title(f'{np.round(comp0_phase_weights_units * t,1)} s', fontsize=13)
  464. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  465. ax.axis('off')
  466. # Component 2
  467. g_sub0 = gridspec.GridSpecFromSubplotSpec(2,2, subplot_spec=gspec[1], wspace=0,
  468. width_ratios=[0.4, 0.6], height_ratios=[0.01,0.99])
  469. title_ax = fig.add_subplot(g_sub0[0,0])
  470. title_ax.set_title('B) Second Complex Principal Component - Pattern Two',
  471. fontsize=18, fontweight='bold', loc='left')
  472. title_ax.axis('off')
  473. # Compute travel index
  474. real_vec = np.real(pca_complex_res['pca']['loadings'][1,:]).T
  475. imag_vec = np.imag(pca_complex_res['pca']['loadings'][1,:]).T
  476. t_index = traveling_index(real_vec, imag_vec)
  477. title_ax = fig.add_subplot(g_sub0[0,1])
  478. title_ax.set_title(f'Travel index:{np.round(t_index,2)}',
  479. fontsize=14, loc='center')
  480. title_ax.axis('off')
  481. g_sub0_0 = gridspec.GridSpecFromSubplotSpec(2,1, subplot_spec=g_sub0[1:,0], hspace=0.1,
  482. height_ratios = [0.6,0.4])
  483. g_sub0_0_0 = gridspec.GridSpecFromSubplotSpec(2,1, subplot_spec=g_sub0_0[0], hspace=0.1,
  484. height_ratios = [0.01,0.99])
  485. g_sub0_0_1 = gridspec.GridSpecFromSubplotSpec(2,1, subplot_spec=g_sub0_0[1], hspace=0.9,
  486. height_ratios = [0.01,0.99])
  487. title_ax = fig.add_subplot(g_sub0_0_0[0])
  488. title_ax.set_title('Phase Delay Map',
  489. fontsize=15, loc='left')
  490. title_ax.axis('off')
  491. title_ax = fig.add_subplot(g_sub0_0_1[0])
  492. title_ax.set_title('Phase Delay Values',
  493. fontsize=13, loc='left')
  494. title_ax.axis('off')
  495. ax = fig.add_subplot(g_sub0_0_0[1])
  496. img = mpimg.imread('demo_files/pca_rest_complex_comp1_ang_hsv.png')
  497. ax.imshow(cropImage(img,crop_width_l,crop_width_r,(0,960)))
  498. ax.axis('off')
  499. box = ax.get_position()
  500. box.y0 = box.y0 + 0.005
  501. box.y1 = box.y1 + 0.005
  502. ax.set_position(box)
  503. xval = np.arange(-2.41, 2.6, 0.01)
  504. yval = np.ones_like(xval)
  505. hsv_cmap_shift0 = shiftedColorMap(hsv_cmap, midpoint=0.6)
  506. norm0 = colors.Normalize(-2.42, 2.6)
  507. ax = plt.subplot(g_sub0_0_1[1], polar=True)
  508. ax.scatter(xval, yval, c=xval, s=300, cmap=hsv_cmap_shift0, norm=norm0, linewidths=0)
  509. ax.set_yticks([])
  510. convert_polar_xticks_to_radians_secs(ax, comp0_phase_weights_duration)
  511. box = ax.get_position()
  512. box.y0 = box.y0 + 0.005
  513. box.y1 = box.y1 + 0.005
  514. ax.set_position(box)
  515. ax.tick_params(axis='both', which='major', pad=10)
  516. g_sub0_1 = gridspec.GridSpecFromSubplotSpec(2,1, subplot_spec=g_sub0[1:,1], hspace=0.05,
  517. height_ratios = [0.01,0.99])
  518. title_ax = fig.add_subplot(g_sub0_1[0])
  519. title_ax.set_title('Reconstructed Time Points',
  520. fontsize=15, loc='left')
  521. title_ax.axis('off')
  522. g_sub0_1_0 = gridspec.GridSpecFromSubplotSpec(2,3, subplot_spec=g_sub0_1[1], hspace=0.05,
  523. wspace=0.05)
  524. base_dir = 'demo_files/cpca_recon_pics'
  525. t_samples = [0, 5, 10, 14, 19, 25]
  526. for i, t in enumerate(t_samples):
  527. ax = fig.add_subplot(g_sub0_1_0[i])
  528. img = mpimg.imread(f'{base_dir}/cpca_recon_comp1_t{t}.png')
  529. ax.set_title(f'{np.round(comp0_phase_weights_units * t,1)} s', fontsize=13)
  530. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  531. ax.axis('off')
  532. # Component 3
  533. g_sub0 = gridspec.GridSpecFromSubplotSpec(2,2, subplot_spec=gspec[2], wspace=0,
  534. width_ratios=[0.4, 0.6], height_ratios=[0.01,0.99])
  535. title_ax = fig.add_subplot(g_sub0[0,0])
  536. title_ax.set_title('C) Third Complex Principal Component - Pattern Three',
  537. fontsize=18, fontweight='bold', loc='left')
  538. title_ax.axis('off')
  539. # Compute travel index
  540. real_vec = np.real(pca_complex_res['pca']['loadings'][2,:]).T
  541. imag_vec = np.imag(pca_complex_res['pca']['loadings'][2,:]).T
  542. t_index = traveling_index(real_vec, imag_vec)
  543. title_ax = fig.add_subplot(g_sub0[0,1])
  544. title_ax.set_title(f'Travel index:{np.round(t_index,2)}',
  545. fontsize=14, loc='center')
  546. title_ax.axis('off')
  547. g_sub0_0 = gridspec.GridSpecFromSubplotSpec(2,1, subplot_spec=g_sub0[1:,0], hspace=0.1,
  548. height_ratios = [0.6,0.4])
  549. g_sub0_0_0 = gridspec.GridSpecFromSubplotSpec(2,1, subplot_spec=g_sub0_0[0], hspace=0.1,
  550. height_ratios = [0.01,0.99])
  551. g_sub0_0_1 = gridspec.GridSpecFromSubplotSpec(2,1, subplot_spec=g_sub0_0[1], hspace=0.9,
  552. height_ratios = [0.01,0.99])
  553. title_ax = fig.add_subplot(g_sub0_0_0[0])
  554. title_ax.set_title('Phase Delay Map',
  555. fontsize=15, loc='left')
  556. title_ax.axis('off')
  557. title_ax = fig.add_subplot(g_sub0_0_1[0])
  558. title_ax.set_title('Phase Delay Values',
  559. fontsize=13, loc='left')
  560. title_ax.axis('off')
  561. ax = fig.add_subplot(g_sub0_0_0[1])
  562. img = mpimg.imread('demo_files/pca_rest_complex_comp2_ang_hsv.png')
  563. ax.imshow(cropImage(img,crop_width_l,crop_width_r,(0,960)))
  564. ax.axis('off')
  565. box = ax.get_position()
  566. box.y0 = box.y0 + 0.005
  567. box.y1 = box.y1 + 0.005
  568. ax.set_position(box)
  569. xval = np.arange(-2.41, 2.6, 0.01)
  570. yval = np.ones_like(xval)
  571. hsv_cmap_shift0 = shiftedColorMap(hsv_cmap, midpoint=0.6)
  572. norm0 = colors.Normalize(-2.42, 2.6)
  573. ax = plt.subplot(g_sub0_0_1[1], polar=True)
  574. ax.scatter(xval, yval, c=xval, s=300, cmap=hsv_cmap_shift0, norm=norm0, linewidths=0)
  575. ax.set_yticks([])
  576. convert_polar_xticks_to_radians_secs(ax, comp0_phase_weights_duration)
  577. box = ax.get_position()
  578. box.y0 = box.y0 + 0.005
  579. box.y1 = box.y1 + 0.005
  580. ax.set_position(box)
  581. ax.tick_params(axis='both', which='major', pad=10)
  582. g_sub0_1 = gridspec.GridSpecFromSubplotSpec(2,1, subplot_spec=g_sub0[1:,1], hspace=0.05,
  583. height_ratios = [0.01,0.99])
  584. title_ax = fig.add_subplot(g_sub0_1[0])
  585. title_ax.set_title('Reconstructed Time Points',
  586. fontsize=15, loc='left')
  587. title_ax.axis('off')
  588. g_sub0_1_0 = gridspec.GridSpecFromSubplotSpec(2,3, subplot_spec=g_sub0_1[1], hspace=0.05,
  589. wspace=0.05)
  590. base_dir = 'demo_files/cpca_recon_pics'
  591. t_samples = [0, 5, 11, 15, 20, 25]
  592. for i, t in enumerate(t_samples):
  593. ax = fig.add_subplot(g_sub0_1_0[i])
  594. img = mpimg.imread(f'{base_dir}/cpca_recon_comp2_t{t}.png')
  595. ax.set_title(f'{np.round(comp0_phase_weights_units * t,1)} s', fontsize=13)
  596. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  597. ax.axis('off')
  598. ax.annotate('time',
  599. xy=(0.28,0.75),
  600. xytext=(0.24, 0.79),
  601. xycoords='figure fraction',
  602. textcoords='figure fraction',
  603. fontweight='bold',
  604. fontsize=12,
  605. arrowprops=dict(facecolor='black', arrowstyle='<-', connectionstyle="angle3, angleA=0, angleB=80", alpha = 0.9, linewidth=3),
  606. horizontalalignment='center',
  607. verticalalignment='center',
  608. bbox=dict(pad=5, facecolor="none", edgecolor="none")
  609. )
  610. plt.savefig('results/figures/cpca.eps', bbox_inches='tight')
  611. plt.show()
  612. # %% [markdown]
  613. # ## Figure Caption
  614. # %% [markdown]
  615. # Figure 2. Form and Properties of Three Dominant Spatiotemporal Patterns. Time-lag delay maps and reconstructed time points of the first three complex principal components. Time-lag delay maps represent the temporal ordering (in seconds) of cortical vertex BOLD time series within the spatiotemporal pattern. Time-lag delay maps describe a repeating or cyclical pattern expressed in radians (0 to 2) around a unit circle, where a phase value of 0 corresponds to the beginning of the spatiotemporal pattern, and 2 corresponds to the end of the spatiotemporal pattern. For clarity, radians are converted to temporal units (seconds) (see ‘Methods and Materials’). The values in the time-lag delay map correspond to the temporal delay (in seconds) between two cortical vertices, such that smaller values occur before larger values. Values are mapped to a cyclical color map to emphasize the cyclical temporal progression of each spatiotemporal pattern. To illustrate the temporal progression of the spatiotemporal patterns, six reconstructed time points are displayed for each pattern. A) The time-lag delay map (top) and reconstructed time points (bottom) of the first spatiotemporal pattern - ‘pattern one’. B) The time-lag delay map and reconstructed time points of the second spatiotemporal pattern - ‘pattern two’ C) The time-lag delay map and reconstructed time points of the third spatiotemporal pattern - ‘pattern three’.
  616. # %% [markdown]
  617. # # <b>Figure 3 - Survey of Zero-lag FC Topographies.</b>
  618. # %%
  619. cifti_fps = (
  620. 'demo_files/pca_rest.dtseries.nii',
  621. 'demo_files/eigenmap_p90.dtseries.nii',
  622. 'demo_files/fc_map_precuneus.dtseries.nii',
  623. 'demo_files/fc_map_sm.dtseries.nii',
  624. 'demo_files/fc_map_supramarginal.dtseries.nii',
  625. 'demo_files/pca_rest_varimax.dtseries.nii',
  626. 'demo_files/s_ica.dtseries.nii', 'demo_files/t_ica.dtseries.nii',
  627. 'demo_files/caps_precuneus_c2.dtseries.nii',
  628. 'demo_files/caps_sm_c2.dtseries.nii',
  629. 'demo_files/caps_supramarginal_c2.dtseries.nii',
  630. 'demo_files/hmm_mean_map.dtseries.nii'
  631. )
  632. labels_short = (
  633. 'Eigenmap 1', 'P Seed', 'SM Seed', 'SMG Seed',
  634. 'Varimax Comp 1', 'Varimax Comp 2', 'Varimax Comp 3', 'SICA Comp 1', 'SICA Comp 2',
  635. 'SICA Comp 3', 'TICA Comp 1', 'TICA Comp 2', 'TICA Comp 3', 'P CAP 1', 'P CAP 2',
  636. 'SM CAP 1', 'SM CAP 2', 'SMG CAP 1', 'SMG CAP 2', 'HMM State 1', 'HMM State 2',
  637. 'HMM State 3'
  638. )
  639. section_labels = (
  640. 'Laplacian Eigenmaps',
  641. 'Precuneus Seed Regression',
  642. 'Somatosensory Seed Regression',
  643. 'Supramarginal Seed Regression',
  644. 'Principal Component Analysis - Varimax Rotated',
  645. 'Spatial Independent Component Analysis',
  646. 'Temporal Independent Component Analysis',
  647. 'Precuneus Seed CAPS',
  648. 'SM Seed CAPs',
  649. 'Supramarginal Seed CAPS',
  650. 'GMM Hidden Markov Model'
  651. )
  652. ## 1. Load All Maps
  653. cifti_maps_all_orig = []
  654. for fp in cifti_fps:
  655. _, cifti_maps, n_time = pull_cifti_data(load_cifti(fp))
  656. if any([label in fp for label in ['pca_rest', 'ica', 'hmm']]):
  657. cifti_maps_all_orig.append(cifti_maps[:3, :])
  658. elif 'eigenmap' in fp:
  659. cifti_maps_all_orig.append(cifti_maps[0, :])
  660. else:
  661. cifti_maps_all_orig.append(cifti_maps[:2, :])
  662. cifti_maps_all_orig = np.vstack(cifti_maps_all_orig)
  663. zero_mask = np.std(cifti_maps_all_orig, axis=0) > 0
  664. zero_mask_indx = np.where(zero_mask)[0]
  665. cifti_maps_all = cifti_maps_all_orig[:, zero_mask].copy()
  666. # Normalize
  667. cifti_maps_all = zscore(cifti_maps_all.T)
  668. ## 2. Correlate all maps with first three principal components
  669. component_corrs = np.corrcoef(cifti_maps_all.T)[3:,:3]
  670. ## 5. Calculate PCA explained variance
  671. pca_ts = pickle.load(open('demo_files/pca_ts.pkl', 'rb'))
  672. eigs = [np.var(pca_ts[:,i]) for i in range(pca_ts.shape[1])]
  673. exp_var = [eig/n_vertices for eig in eigs]
  674. ## 6. Create Figure
  675. crop_width_l = (0, 1000)
  676. crop_width_r = (1300, 2100)
  677. crop_height = (0,1053)
  678. fig = plt.figure(figsize=(13,21), constrained_layout=False)
  679. gspec = fig.add_gridspec(2,2, hspace=0.1, wspace=0.2,
  680. width_ratios=[0.3, 0.7],
  681. height_ratios=[0.8,0.2])
  682. g_sub0 = gridspec.GridSpecFromSubplotSpec(2,1, subplot_spec=gspec[:,0],
  683. wspace=0, hspace=0.1,
  684. height_ratios=[0.01,0.99])
  685. g_sub1 = gridspec.GridSpecFromSubplotSpec(4,1, subplot_spec=gspec[0,1],
  686. wspace=0, hspace=0.2,
  687. height_ratios=[0.01,0.33,0.33,0.33])
  688. g_sub2 = gridspec.GridSpecFromSubplotSpec(1,1, subplot_spec=gspec[1,1],
  689. wspace=0)
  690. title_ax = fig.add_subplot(g_sub0[0])
  691. title_ax.set_title('A) Spatial Correlation with \n Principal Components',
  692. fontsize=16, fontweight='bold', loc='left')
  693. box = title_ax.get_position()
  694. box.y0 = box.y0 - 0.02
  695. box.y1 = box.y1 - 0.02
  696. title_ax.set_position(box)
  697. title_ax.axis('off')
  698. title_ax = fig.add_subplot(g_sub1[0])
  699. title_ax.set_title('B) Principal Component Maps',
  700. fontsize=16, fontweight='bold', loc='right')
  701. box = title_ax.get_position()
  702. box.y0 = box.y0 - 0.01
  703. box.y1 = box.y1 - 0.01
  704. title_ax.set_position(box)
  705. title_ax.axis('off')
  706. title_ax.axis('off')
  707. ax = fig.add_subplot(g_sub2[0])
  708. ax.set_aspect(0.009)
  709. ax.plot(list(range(1,11)), eigs, '-o', color='black')
  710. ax.set_xlabel('Component Number', fontsize=15)
  711. ax.set_ylabel('Eigenvalue', fontsize=15)
  712. x_adj = 0.4
  713. y_adj = [-25,40,-30]
  714. for i in range(3):
  715. ax.text((i+1)+x_adj,eigs[i]+y_adj[i],
  716. r'{}%'.format(np.round(exp_var[i]*100,1)),
  717. fontsize=15, bbox=dict(facecolor='white', alpha=0.5))
  718. ax.text(4.5, 940, 'Explained Variance', fontsize=15, bbox=dict(facecolor='white', alpha=0.5))
  719. ax.set_title('C) Explained Variance Scree Plot',
  720. fontsize=16, fontweight='bold', loc='left', pad=20)
  721. box = ax.get_position()
  722. box.x0 = box.x0 + 0.1
  723. box.x1 = box.x1 + 0.1
  724. ax.set_position(box)
  725. pca_maps = ['demo_files/pca_rest_comp0.png',
  726. 'demo_files/pca_rest_comp1.png',
  727. 'demo_files/pca_rest_comp2.png']
  728. weights_df = pd.DataFrame(component_corrs, columns=[f'Comp{i}' for i in range(3)], index=labels_short)
  729. weights_df_abs = weights_df.abs()
  730. df_list = []
  731. indx_max = weights_df_abs.idxmax(axis=1)
  732. for col in weights_df_abs.columns:
  733. comp_weights = weights_df_abs.loc[indx_max==col]
  734. comp_weights_sorted = comp_weights.sort_values(by=col, ascending=False)
  735. df_list.append(comp_weights_sorted)
  736. weights_df_abs_sorted = pd.concat(df_list)
  737. ax = fig.add_subplot(g_sub0[1])
  738. im = ax.imshow(weights_df_abs_sorted.values, aspect='auto')
  739. ax.set_xticks(np.arange(3))
  740. ax.set_yticks(np.arange(len(labels_short)))
  741. ax.set_yticklabels(weights_df_abs_sorted.index, fontsize=15)
  742. ax.set_xticklabels([f'PC{i+1}' for i in range(3)], fontsize=15,
  743. fontweight='bold')
  744. # Loop over data dimensions and create text annotations.
  745. for i in range(weights_df_abs_sorted.shape[0]):
  746. for j in range(weights_df_abs_sorted.shape[1]):
  747. text = ax.text(j, i, round(weights_df_abs_sorted.iloc[i,j],2),
  748. ha="center", va="center", color="ivory",
  749. fontweight='bold', fontsize=13)
  750. ax.tick_params(top=True, bottom=False, labeltop=True, labelbottom=False)
  751. box = ax.get_position()
  752. box.x0 = box.x0 + 0.1
  753. box.x1 = box.x1 + 0.1
  754. ax.set_position(box)
  755. divider = make_axes_locatable(ax)
  756. cax = divider.append_axes("right", size="10%", pad="20%")
  757. cax.set_aspect(30)
  758. cbar = fig.colorbar(im, cax=cax, orientation="vertical", aspect=10)
  759. cbar.ax.tick_params(labelsize=14)
  760. cbar.ax.set_title('Correlation', fontsize=15, loc='left')
  761. ax1 = fig.add_subplot(g_sub1[1])
  762. img = mpimg.imread(pca_maps[0])
  763. ax1.set_title(f'Principal Component 1', fontsize=18)
  764. ax1.imshow(cropImage(img, crop_width_l, crop_width_r, crop_height))
  765. ax1.axis('off')
  766. box = ax1.get_position()
  767. box.x0 = box.x0 + 0.1
  768. box.x1 = box.x1 + 0.1
  769. ax1.set_position(box)
  770. ax2 = fig.add_subplot(g_sub1[2])
  771. img = mpimg.imread(pca_maps[1])
  772. ax2.set_title(f'Principal Component 2', fontsize=18)
  773. ax2.imshow(cropImage(img, crop_width_l, crop_width_r, crop_height))
  774. ax2.axis('off')
  775. box = ax2.get_position()
  776. box.x0 = box.x0 + 0.1
  777. box.x1 = box.x1 + 0.1
  778. ax2.set_position(box)
  779. ax3 = fig.add_subplot(g_sub1[3])
  780. img = mpimg.imread(pca_maps[2])
  781. ax3.set_title(f'Principal Component 3', fontsize=18)
  782. ax3.imshow(cropImage(img, crop_width_l, crop_width_r, crop_height))
  783. ax3.axis('off')
  784. box = ax3.get_position()
  785. box.x0 = box.x0 + 0.1
  786. box.x1 = box.x1 + 0.1
  787. ax3.set_position(box)
  788. # plt.show()
  789. plt.savefig('results/figures/FC_survey.eps', bbox_inches='tight')
  790. # %% [markdown]
  791. # ## Figure 3 Caption
  792. # %% [markdown]
  793. # Figure 3. Form and Properties of Three Fundamental Functional Connectivity Topographies. A) The spatial correlation (abs. value) between the first three principal component maps and each FC topography displayed as a table. The color of each cell in the table is shaded from light yellow (strong correlation) to dark blue (weak correlation). All FC topographies in our survey exhibited strong spatial correlations (Pearson’s correlation) with one (or two) of the first three principal components. B) The first three principal component spatial maps. C) The scree plot that displays the explained variance in cortical time series for each successive principal component. The scree plot indicates a clear elbow after the third principal component, indicating a ‘diminishing return’ in explained variance of extracting more components. (P=precuneus, SM= somatosensory; SMG=supramarginal gyrus; Clus=cluster; Comp=component; PC = Principal Component).
  794. # %% [markdown]
  795. # ## Permutation Spin Tests of FC Topographies
  796. # %% [markdown]
  797. # Compute permuation spin tests for each FC topograhy and most correlated principal component
  798. # %%
  799. l_sphere = load_surf_mesh('templates/sphere_left.gii')
  800. r_sphere = load_surf_mesh('templates/sphere_right.gii')
  801. # Get top principal component corr
  802. sim_pairs = weights_df_abs_sorted.idxmax(axis=1)
  803. pc_sim_pair = list(zip(sim_pairs,sim_pairs.index))
  804. # Number of permutations per test
  805. n_rand = 1000
  806. # Run permutation spin test per map
  807. labels = list(labels_short)
  808. labels = ['Comp0', 'Comp1', 'Comp2'] + labels
  809. perm_r_all = []
  810. orig_r = []
  811. for pair in pc_sim_pair:
  812. print(pair)
  813. # Index cifti maps
  814. pc_indx = labels.index(pair[0])
  815. map_indx = labels.index(pair[1])
  816. pc_cifti = cifti_maps_all_orig[pc_indx, :]
  817. map_cifti = cifti_maps_all_orig[map_indx, :]
  818. orig_r.append(np.corrcoef(pc_cifti, map_cifti)[0,1])
  819. # Split cifti map into left and right hemispheres
  820. map_cifti_L, map_cifti_R = map_cifti[:n_vert_L], map_cifti[n_vert_L:]
  821. # Initialize permutation test
  822. sp = SpinPermutations(n_rep=n_rand, random_state=0)
  823. sp.fit(l_sphere.coordinates, r_sphere.coordinates)
  824. # randomize
  825. map_rotated = np.hstack(sp.randomize(map_cifti_L, map_cifti_R))
  826. # Calculate permutation distribution
  827. perm_r = []
  828. for i in range(n_rand):
  829. perm_r.append(np.corrcoef(pc_cifti, map_rotated[i,:])[0,1])
  830. perm_r_all.append(perm_r)
  831. # Calculate p-value per map
  832. pv_all = []
  833. for pair, perm_r, r_obs in zip(pc_sim_pair, perm_r_all, orig_r):
  834. # Include observed value as part of permutation distribution
  835. pv = ( (np.abs(perm_r) >= np.abs(r_obs)).sum() + 1 )/(n_rand + 1)
  836. pv_all.append((pair, pv))
  837. # %% [markdown]
  838. # # <b>Movie 1 - Visualization of Spatiotemporal Patterns</b>
  839. # %%
  840. %%HTML
  841. <video controls autoplay loop>
  842. <source
  843. src="demo_files/time_lag_structures.mp4"
  844. type="video/mp4"
  845. </video>
  846. # %% [markdown]
  847. # ## Movie 1 Caption
  848. # %% [markdown]
  849. # Visualization of Spatiotemporal Patterns. Temporal reconstruction of all three spatiotemporal patterns displayed as movies in the following order - pattern one, pattern two, and pattern three. The time points are equally-spaced samples (N=30) of the spatiotemporal patterns. The seconds since the beginning of the spatiotemporal pattern are displayed in the top left. In the bottom of the panel, the time points of the spatiotemporal pattern are displayed in three-dimensional principal component space (Figure 2). Two-dimensional slices of the three principal component space (see Figure 3) are displayed as the three 2-dimensional plots. The progression of time points in the principal component space is illustrated by a cyclical color map (light to dark to light). The movement of the spatiotemporal pattern through this space is illustrated by a moving red dot from time point-to-time point in synchronization with the temporal reconstruction in the movie.
  850. # %% [markdown]
  851. # # <b> Movie 2. Dynamic Visualization of the Quasiperiodic Pattern, Pattern One, and Global Signal.</b>
  852. # %%
  853. %%HTML
  854. <video controls autoplay loop>
  855. <source
  856. src="demo_files/qpp_comparison.mp4"
  857. type="video/mp4"
  858. </video>
  859. # %% [markdown]
  860. # ## Movie 2 Caption
  861. # %% [markdown]
  862. # Movie 2. Dynamic Visualization of the Quasiperiodic Pattern, Pattern One and Global Signal. The 30 time points (TR=0.72s) of the QPP, pattern one, and peak-average global signal displayed as a movie (in that order). The time index of each sequence is displayed in the top left. The time points of pattern one are equally-spaced phase samples (N=30) of the time point reconstruction (see above). The time points of the QPP are derived from the spatiotemporal template computed from the repeated-template-averaging procedure. The global signal visualization concatenates the left and right windows (w=15TRs) of the global signal peak-average. The time points of the global signal visualization begin at TR=-15, corresponding to 15 TRs pre-peak, and proceed to TR=15, corresponding to 15TRs post-peak.
  863. # %% [markdown]
  864. # # **Figure 4 - Similar Propagation Patterns between Average Latency Structure and Pattern One**
  865. # %%
  866. _, pca_comps, _ = pull_cifti_data(load_cifti('demo_files/pca_rest_complex_ang.dtseries.nii'))
  867. _, lag_proj, _ = pull_cifti_data(load_cifti('demo_files/lag_projection.dtseries.nii'))
  868. _, phase_circ_mean, _ = pull_cifti_data(load_cifti('demo_files/phase_circular_mean.dtseries.nii'))
  869. zero_mask = np.std(pca_comps, axis=0) > 0
  870. pca_comps = pca_comps[:, zero_mask]
  871. lag_proj = lag_proj[:, zero_mask][0,:]
  872. phase_circ_mean = phase_circ_mean[:, zero_mask]
  873. corr_circ_lag = np.corrcoef(phase_circ_mean, lag_proj)[0,1]
  874. corr_circ_p1 = np.corrcoef(phase_circ_mean, pca_comps[0,:])[0,1]
  875. fig = plt.figure(figsize=(20,10), constrained_layout=False)
  876. crop_width_l = (0, 1000)
  877. crop_width_r = (1300, 2100)
  878. crop_height = (0,1053)
  879. gspec = fig.add_gridspec(1,3, wspace=0.2, width_ratios=[0.33,0.33,0.33])
  880. ax = fig.add_subplot(gspec[0])
  881. img = mpimg.imread('demo_files/lag_projection.png')
  882. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  883. ax.axis('off')
  884. ax.set_title('Lag Projection Map', fontsize=16, fontweight='bold')
  885. ax = fig.add_subplot(gspec[1])
  886. img = mpimg.imread('demo_files/phase_circular_mean.png')
  887. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  888. ax.axis('off')
  889. ax.set_title('Circular Average of Complex Correlations', fontsize=16, fontweight='bold')
  890. ax = fig.add_subplot(gspec[2])
  891. img = mpimg.imread('demo_files/pca_rest_complex_comp0_ang.png')
  892. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  893. ax.axis('off')
  894. ax.set_title('Pattern One Phase Delay Map', fontsize=16, fontweight='bold')
  895. fig.text(0.355, 0.52, f"r = {round(corr_circ_lag, 2)}", va="center", fontsize=20)
  896. fig.text(0.635, 0.52, f"r = {round(corr_circ_p1, 2)}", va="center", fontsize=20)
  897. fig.text(0.205, 0.38, r"TR (0.72s)", va="center", fontsize=16)
  898. fig.text(0.48, 0.38, r"$\Theta$ (radian)", va="center", fontsize=16)
  899. fig.text(0.755, 0.38, r"$\Theta$ (radian)", va="center", fontsize=16)
  900. plt.savefig('results/figures/cpca_lag_proj.eps', bbox_inches='tight')
  901. plt.show()
  902. # %%
  903. np.corrcoef(phase_circ_mean, lag_proj)[0,1]
  904. # %%
  905. np.corrcoef(phase_circ_mean, pca_comps[0,:])[0,1]
  906. # %% [markdown]
  907. # ## Figure 4 Caption
  908. # %% [markdown]
  909. # Figure 4. Similar Time-lag Dynamics between Pattern One and Lag Projection. Comparison between the phase delay map of pattern one (left) and the average lag projection (right). As in Figure 2, the pattern one phase delay map represents the phase delay (in radians) of cortical BOLD time series. The lag projection map represents the average time-lag delay (in seconds) between each vertex of the cortex. The spatial correlation between the pattern one phase delay map and lag projection is r = 0.81, indicating a strong similarity in time-lag dynamics.
  910. # %% [markdown]
  911. # # <b>Figure 5 - The Task-Positive/Task-Negative Pattern, Primary Gradient, and Pattern Two Describe the Same Spatiotemporal Pattern. </b>
  912. # %%
  913. gs_signal = pickle.load(open('demo_files/gs_results.pkl', 'rb'))
  914. comp_ts = pickle.load(open('demo_files/pca_complex_ts.pkl', 'rb'))
  915. comp0_ts = np.real(comp_ts[:,0])
  916. gs_comp0_corr = np.corrcoef(gs_signal, comp0_ts)[0,1]
  917. eigenmap_indx = np.arange(0,100,10)
  918. eigenmaps = []
  919. for indx in eigenmap_indx:
  920. _, eigenmap, _ = pull_cifti_data(load_cifti(f'demo_files/eigenmap_p{indx}.dtseries.nii'))
  921. eigenmaps.append(eigenmap[0,:])
  922. eigenmaps_array = np.array(eigenmaps)
  923. zero_mask = np.std(eigenmaps_array, axis=0) > 0
  924. eigenmaps_array = eigenmaps_array[:,zero_mask].copy()
  925. _, pca_maps, _ = pull_cifti_data(load_cifti(f'demo_files/pca_rest.dtseries.nii'))
  926. pca_maps = pca_maps[:2, zero_mask].copy()
  927. pca_eigenmap_corr = np.corrcoef(pca_maps, eigenmaps_array)[:2, 2:]
  928. fig = plt.figure(figsize=(22,18), constrained_layout=False)
  929. crop_width_l = (0, 1000)
  930. crop_width_r = (1300, 2100)
  931. crop_height = (0,1053)
  932. gspec = fig.add_gridspec(6,1, hspace=0.4, wspace=0,
  933. height_ratios=[0.02,0.33,0.02,0.33,0.02,0.33])
  934. title_ax = fig.add_subplot(gspec[0,:])
  935. title_ax.set_title('A) Pattern Two, TP/TN Pattern & Primary Functional Connectivity Gradient',
  936. fontsize=18, fontweight='bold', loc='left')
  937. title_ax.axis('off')
  938. g_sub0 = gridspec.GridSpecFromSubplotSpec(1,3, subplot_spec=gspec[1],
  939. wspace=0)
  940. ax = fig.add_subplot(g_sub0[0])
  941. img = mpimg.imread('demo_files/pca_rest_comp1_flip.png')
  942. ax.set_title(f'Second Principal Component \n (sign flipped)', fontsize=16)
  943. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  944. ax.axis('off')
  945. ax = fig.add_subplot(g_sub0[1])
  946. img = mpimg.imread('demo_files/fc_map_precuneus_gs_nonsymmetric.png')
  947. ax.set_title(f'Task-Positive/Task-Negative Pattern \n (precuneus seed)', fontsize=16)
  948. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  949. ax.axis('off')
  950. ax = fig.add_subplot(g_sub0[2])
  951. img = mpimg.imread('demo_files/eigenmap_p0_comp0.png')
  952. ax.set_title(f'Primary Functional Connectivity Gradient \n (no threshold)', fontsize=16)
  953. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  954. ax.axis('off')
  955. title_ax = fig.add_subplot(gspec[2,:])
  956. title_ax.set_title('B) PCA & cPCA of Time-Point Centered Data',
  957. fontsize=18, fontweight='bold', loc='left')
  958. title_ax.axis('off')
  959. g_sub1 = gridspec.GridSpecFromSubplotSpec(1,2, subplot_spec=gspec[3], wspace=0, width_ratios=[0.62,0.38])
  960. g_sub1_0 = gridspec.GridSpecFromSubplotSpec(1,2, subplot_spec=g_sub1[0], wspace=0)
  961. g_sub1_1 = gridspec.GridSpecFromSubplotSpec(1,1, subplot_spec=g_sub1[1])
  962. ax = fig.add_subplot(g_sub1_0[0])
  963. img = mpimg.imread('demo_files/pca_rest_comp0.png')
  964. ax.set_title(f'First Principal Component \n (original)', fontsize=16)
  965. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  966. ax.axis('off')
  967. ax = fig.add_subplot(g_sub1_0[1])
  968. img = mpimg.imread('demo_files/pca_rest_comp0_tmode_flip.png')
  969. ax.set_title(f'First Principal Component \n (time-point centered)', fontsize=16)
  970. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  971. ax.axis('off')
  972. hsv_cmap = plt.get_cmap('hsv')
  973. ax = fig.add_subplot(g_sub1_1[0])
  974. img = mpimg.imread('demo_files/pca_rest_complex_tmode_comp0_ang_hsv.png')
  975. ax.set_title('First Complex Principal Component - \n Time-Lag Delay Map \n (time-point centered)', fontsize=16)
  976. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  977. ax.axis('off')
  978. title_ax = fig.add_subplot(gspec[4,:])
  979. title_ax.set_title('C) Threshold Effect on Primary Functional Connectivity Gradient',
  980. fontsize=18, fontweight='bold', loc='left')
  981. title_ax.axis('off')
  982. g_sub2 = gridspec.GridSpecFromSubplotSpec(1,2, subplot_spec=gspec[5], wspace=0.1, width_ratios=[0.25,0.75])
  983. g_sub2_0 = gridspec.GridSpecFromSubplotSpec(1,1, subplot_spec=g_sub2[0], wspace=0)
  984. g_sub2_1 = gridspec.GridSpecFromSubplotSpec(1,5, subplot_spec=g_sub2[1], wspace=0)
  985. ax = fig.add_subplot(g_sub2_0[0])
  986. ax.plot(eigenmap_indx, np.abs(pca_eigenmap_corr[0,:]), marker='o',color='b', label='PC1')
  987. ax.plot(eigenmap_indx, np.abs(pca_eigenmap_corr[1,:]), marker='o',color='r', label='PC2')
  988. ax.legend()
  989. ax.set_xticks(eigenmap_indx)
  990. ax.set_title('Spatial Correlation of Laplacian Eigenmap with \n First Two Principal Components', fontsize=15, pad=15)
  991. ax.set_xlabel('Percentile Threshold of Affinity Matrix', fontsize=15)
  992. ax.set_ylabel('Spatial Correlation (abs)', fontsize=15)
  993. crop_width2 = (35, 1200)
  994. crop_height2 = (5,20)
  995. sub_title_ax = fig.add_subplot(g_sub2_1[0,:])
  996. sub_title_ax.set_title('Laplacian Eigenmap by Percentile Threshold (Left Hemisphere)',
  997. fontsize=16, loc='left')
  998. sub_title_ax.axis('off')
  999. box = sub_title_ax.get_position()
  1000. box.y0 = box.y0 + 0.015
  1001. box.y1 = box.y1 + 0.015
  1002. sub_title_ax.set_position(box)
  1003. eigenmap_indx2 = np.arange(0,100,20)
  1004. for i, indx in enumerate(eigenmap_indx2):
  1005. ax = fig.add_subplot(g_sub2_1[i])
  1006. img = mpimg.imread(f'demo_files/eigenmap_p{indx}_comp0.png')
  1007. ax.set_title(f'{indx}%', fontsize=14)
  1008. ax.imshow(cropImage_single(img,crop_width2,crop_height2))
  1009. ax.axis('off')
  1010. # plt.show()
  1011. plt.savefig('results/figures/fpn_to_dmn.eps', bbox_inches='tight')
  1012. # %% [markdown]
  1013. # ## Figure 5 Caption
  1014. # %% [markdown]
  1015. # Figure 5. The Task-Positive/Task-Negative Pattern, Primary Gradient, and Pattern Two Describe the Same Spatiotemporal Pattern. A) From left to right, pattern two, task-positive/task-negative (TP/TN) pattern, and the PG represented by the spatial weights of the second principal component from PCA (sign flipped for consistency), seed-based correlation map (precuneus seed), and first Laplacian eigenmap with no thresholding of the affinity matrix, respectively. As can be observed visually, similar spatial patterns are produced from all three analyses - pattern two:TP/TN (r =0.96) and pattern two:PG (r = 0.83). B) From left to right, the first principal component from non-time-centered BOLD time courses (i.e. pattern one), the first principal component of time-centered BOLD time courses, and the first complex principal component time-lag delay map from time-centered BOLD time courses. As can be observed visually, time-point centering of BOLD time courses replaces the original unipolar first principal component (left; pattern one) with a bipolar (anti-correlated) principal component (middle) that resembles pattern two. In the same manner, the first complex principal component of CPCA of time-centered BOLD time courses (right) exhibits a time-lag map resembling the time-lag map of pattern two (Figure 2). C) The effect of functional connectivity (FC) matrix percentile thresholding on the resulting spatial weights of the PG, computed as the first eigenmap of the Laplacian Eigenmap algorithm (only the left hemisphere presented for space). At zero to low-thresholding of the FC matrix, the first Laplacian Eigenmap resembles pattern two (PC2). As the threshold is raised, the spatial weights of vertices within the FPN, DMN and SMLV become more uniform, and the spatial weights of the vertices within the FPN fall to zero. At higher thresholds this results in an Eigenmap that resembles pattern one.
  1016. # %% [markdown]
  1017. # # <b> Figure 6 - Comparison of Original and Reconstructed Functional Connectivity Matrices. </b>
  1018. # %%
  1019. pca_res = pickle.load(open('results/pca_rest_complex_results.pkl', 'rb'))['pca']
  1020. orig_corr = pickle.load(open('results/fc_matrix_results.pkl', 'rb'))
  1021. U = pca_res['U'][:,:3]
  1022. s = np.diag(pca_res['s'][:3])
  1023. Va = pca_res['Va'][:3,:]
  1024. recon_ts = np.real(U @ s @ Va)
  1025. recon_corr = np.corrcoef(recon_ts.T)
  1026. orig_tril = orig_corr[np.tril_indices(orig_corr.shape[0], k=1)]
  1027. recon_tril = recon_corr[np.tril_indices(recon_corr.shape[0],k=1)]
  1028. orig_recon_corr = np.corrcoef(orig_tril, recon_tril)[0,1]
  1029. orig_corr_adj = orig_corr.copy()
  1030. recon_corr_adj = recon_corr.copy()
  1031. np.fill_diagonal(orig_corr_adj, 0)
  1032. np.fill_diagonal(recon_corr_adj, 0)
  1033. # # Louvain Community detection - original and reconstructed corr matrices
  1034. labels_orig, _ = community_louvain(orig_corr_adj, B='negative_asym', seed=0)
  1035. labels_recon, _ = community_louvain(recon_corr_adj, B='negative_asym', seed=0)
  1036. # Calculate normalized mutual information
  1037. _, norm_mni = partition_distance(labels_orig, labels_recon)
  1038. # # Write module assignments to cifti file for display
  1039. # # This cannot be done without access to data, so it is commented out
  1040. # # ex_subj_file = ['data/rest/proc_3_surf_resamp/200513_LR1_rest_smooth_filt_smooth_filt_resamp.R.func.gii',
  1041. # # 'data/rest/proc_3_surf_resamp/200513_LR1_rest_smooth_filt_smooth_filt_resamp.L.func.gii']
  1042. # # hdr = load_gifti(ex_subj_file)
  1043. # # write_to_gifti(labels_orig[np.newaxis, :]+1, hdr, 'louvain_modules', zero_mask)
  1044. fig = plt.figure(figsize=(25,5), constrained_layout=False)
  1045. # Define 1 by 3 overall grid
  1046. gspec = fig.add_gridspec(1,2, wspace=0.05, width_ratios=[0.65,0.35])
  1047. # Plot original correlation matrix with community partition
  1048. gspec0 = gridspec.GridSpecFromSubplotSpec(1,2, subplot_spec=gspec[0], wspace=0.5)
  1049. ax0 = fig.add_subplot(gspec0[0])
  1050. plot_sorted_corr_mat(orig_corr, labels_orig, ax0)
  1051. ax0.set_title('Original FC Matrix', fontsize=16, pad=8, fontweight='bold')
  1052. ax0.set_xlabel('vertex', fontsize=12)
  1053. ax0.set_ylabel('vertex', fontsize=12)
  1054. ax0.text(500, 2100, 'Module 1', fontweight='bold', fontsize=12)
  1055. ax0.text(2450, 3700, 'Module 2', fontweight='bold', fontsize=12)
  1056. ax0.text(3850, 4900, 'Module 3', fontweight='bold', fontsize=12)
  1057. # Plot reconstructed correlation matrix with community partition
  1058. ax1 = fig.add_subplot(gspec0[1])
  1059. plot_sorted_corr_mat(recon_corr, labels_recon, ax1)
  1060. ax1.set_title('Reconstructed FC Matrix', fontsize=16, pad=8, fontweight='bold')
  1061. ax1.set_xlabel('vertex', fontsize=12)
  1062. ax1.set_ylabel('vertex', fontsize=12)
  1063. fig.text(0.33, 0.8, f"r = {round(orig_recon_corr, 2)}", va="center", fontsize=20)
  1064. fig.text(0.325, 0.65, f"NMI = {round(norm_mni, 2)}", va="center", fontsize=20)
  1065. # Display module assignments on cortical surface
  1066. g_sub = gridspec.GridSpecFromSubplotSpec(1,2, subplot_spec=gspec[1], wspace=0,
  1067. width_ratios=[0.7, 0.3])
  1068. ax2 = fig.add_subplot(g_sub[0])
  1069. img = mpimg.imread('demo_files/louvain_modules.png')
  1070. ax2.set_title(f'Module Partition', fontsize=16, fontweight='bold')
  1071. ax2.imshow(img)
  1072. ax2.axis('off')
  1073. # Display module labels
  1074. g_sub_sub = gridspec.GridSpecFromSubplotSpec(3,1, subplot_spec=g_sub[1], hspace = 0.1)
  1075. color_module = ['black', 'red', 'yellow']
  1076. for i, (gsub, clr) in enumerate(zip(g_sub_sub, color_module)):
  1077. sub_ax = fig.add_subplot(gsub)
  1078. sub_ax.text(0.4,0.5, f'Module {i+1}', fontsize=16)
  1079. # add a fancy box
  1080. fancybox = FancyBboxPatch((0.2,0.4),0.05,0.3,linewidth=1,
  1081. edgecolor='none',facecolor=clr,
  1082. boxstyle=BoxStyle("Round", pad=0.02, rounding_size=0.015))
  1083. sub_ax.add_patch(fancybox)
  1084. sub_ax.axis('off')
  1085. plt.savefig('results/figures/corr_compare.png', dpi=200)
  1086. # plt.show()
  1087. # %% [markdown]
  1088. # ### Figure 6 Caption
  1089. # %% [markdown]
  1090. # Figure 6. The Network Structure of Functional Connectivity Is Explained by the Three Fundamental Spatiotemporal Patterns. Comparison of the correlation matrix of cortical BOLD time courses (left) with the correlation matrix of reconstructed cortical BOLD time courses (right) derived from the three spatiotemporal patterns, and the module assignments of each vertex (bottom). The rows and columns of the original and reconstructed correlation matrix are sorted and outlined (in black) according to the modular structure estimated from the Louvain modularity algorithm. The algorithm identified three primary modules in the SMLV, DMN and FPN. Despite a higher mean value of correlations in the reconstructed correlation matrix, the pattern of correlations between the two correlation matrices is highly similar (r = 0.77). Further, the modular structure of the original correlation matrix exhibits a high degree of similarity with the modular structure of the reconstructed correlation matrix (MNI = 0.73).
  1091. # %% [markdown]
  1092. # # <b>Supplementary Figures</b>
  1093. # %% [markdown]
  1094. # # <b>Supplementary Results A - Spatiotemporal Patterns Consist of Steady States and Propagation Events That Repeat Across Patterns.</b>
  1095. # %%
  1096. V = pickle.load(open('demo_files/pca_eigen.pkl', 'rb')) # X = USV
  1097. V = zscore(V.T).T
  1098. _, cpca_comp0, n_time = pull_cifti_data(load_cifti('results/cpca_comp0_recon.dtseries.nii'))
  1099. _, cpca_comp1, n_time = pull_cifti_data(load_cifti('results/cpca_comp1_recon.dtseries.nii'))
  1100. _, cpca_comp2, n_time = pull_cifti_data(load_cifti('results/cpca_comp2_recon.dtseries.nii'))
  1101. zero_mask = np.std(cpca_comp0, axis=0) > 0
  1102. cpca_comp0 = zscore(cpca_comp0[:, zero_mask].copy().T).T
  1103. cpca_comp1 = zscore(cpca_comp1[:, zero_mask].copy().T).T
  1104. cpca_comp2 = zscore(cpca_comp2[:, zero_mask].copy().T).T
  1105. cpca_comp0_proj = cpca_comp0 @ V.T
  1106. cpca_comp1_proj = cpca_comp1 @ V.T
  1107. cpca_comp2_proj = cpca_comp2 @ V.T
  1108. fig = plt.figure(figsize=(20,25), constrained_layout=False)
  1109. crop_width_l = (0, 1000)
  1110. crop_width_r = (1300, 2100)
  1111. crop_height = (0,1053)
  1112. gspec = fig.add_gridspec(4,1, hspace=0.3, wspace=0,
  1113. height_ratios=[0.25,0.3,0.3,0.4])
  1114. g_sub0 = gridspec.GridSpecFromSubplotSpec(2,3, subplot_spec=gspec[0], wspace=0,
  1115. hspace=0.2, height_ratios=[0.01,0.99])
  1116. title_ax = fig.add_subplot(g_sub0[0,:])
  1117. title_ax.set_title('A) Principal Components',
  1118. fontsize=18, fontweight='bold', loc='left')
  1119. title_ax.axis('off')
  1120. ax = fig.add_subplot(g_sub0[1,0])
  1121. img = mpimg.imread('demo_files/pca_rest_comp0.png')
  1122. ax.set_title('PC 1', fontsize=16)
  1123. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  1124. ax.axis('off')
  1125. ax = fig.add_subplot(g_sub0[1,1])
  1126. img = mpimg.imread('demo_files/pca_rest_comp1.png')
  1127. ax.set_title('PC 2', fontsize=16)
  1128. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  1129. ax.axis('off')
  1130. ax = fig.add_subplot(g_sub0[1,2])
  1131. img = mpimg.imread('demo_files/pca_rest_comp2.png')
  1132. ax.set_title('PC 3', fontsize=16)
  1133. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  1134. ax.axis('off')
  1135. g_sub1 = gridspec.GridSpecFromSubplotSpec(2,3, subplot_spec=gspec[1], wspace=0.2,
  1136. hspace=0, height_ratios=[0.01,0.99],
  1137. width_ratios=[0.3,0.3,0.4])
  1138. title_ax = fig.add_subplot(g_sub1[0,:])
  1139. title_ax.set_title('B) Spatiotemporal Patterns in Principal Component Space',
  1140. fontsize=18, fontweight='bold', loc='left')
  1141. title_ax.axis('off')
  1142. t = np.arange(30)
  1143. ax = fig.add_subplot(g_sub1[1,0])
  1144. ax.set_xlabel('PC 1', fontsize=16, fontweight='bold')
  1145. ax.set_ylabel('PC 2', fontsize=16, fontweight='bold', labelpad=-20)
  1146. ax.scatter(cpca_comp0_proj[:,0], cpca_comp0_proj[:,1], c=t,
  1147. cmap='Blues', s=100, alpha=0.8)
  1148. ax.plot(cpca_comp0_proj[:,0], cpca_comp0_proj[:,1], alpha=0.5, color='b',
  1149. label='Pattern One')
  1150. ax.scatter(cpca_comp1_proj[:,0], cpca_comp1_proj[:,1], c=t,
  1151. cmap='Greens', s=100, alpha=0.5)
  1152. ax.plot(cpca_comp1_proj[:,0], cpca_comp1_proj[:,1], alpha=0.5, color='g',
  1153. label='Pattern Two')
  1154. ax.scatter(cpca_comp2_proj[:,0], cpca_comp2_proj[:,1], c=t,
  1155. cmap='Reds', s=100, alpha=0.5)
  1156. ax.plot(cpca_comp2_proj[:,0], cpca_comp2_proj[:,1], alpha=0.5, color='r',
  1157. label='Pattern Three')
  1158. ax.legend()
  1159. ax = fig.add_subplot(g_sub1[1,1])
  1160. ax.set_xlabel('PC 1', fontsize=16, fontweight='bold')
  1161. ax.set_ylabel('PC 3', fontsize=16, fontweight='bold', labelpad=-20)
  1162. ax.scatter(cpca_comp0_proj[:,0], cpca_comp0_proj[:,2], c=t,
  1163. cmap='Blues', s=100, alpha=0.8)
  1164. ax.plot(cpca_comp0_proj[:,0], cpca_comp0_proj[:,2], alpha=0.5, color='b')
  1165. ax.scatter(cpca_comp1_proj[:,0], cpca_comp1_proj[:,2], c=t,
  1166. cmap='Greens', s=100, alpha=0.8)
  1167. ax.plot(cpca_comp1_proj[:,0], cpca_comp1_proj[:,2], alpha=0.5, color='g')
  1168. ax.scatter(cpca_comp2_proj[:,0], cpca_comp2_proj[:,2], c=t,
  1169. cmap='Reds', s=100, alpha=0.8)
  1170. ax.plot(cpca_comp2_proj[:,0], cpca_comp2_proj[:,2], alpha=0.5, color='r')
  1171. ax = fig.add_subplot(g_sub1[1,2])
  1172. ax.set_xlabel('PC 2', fontsize=16, fontweight='bold')
  1173. ax.set_ylabel('PC 3', fontsize=16, fontweight='bold', labelpad=-20)
  1174. scatter1 = ax.scatter(cpca_comp0_proj[:,1], cpca_comp0_proj[:,2], c=t,
  1175. cmap='Blues', s=100, alpha=0.8)
  1176. ax.plot(cpca_comp0_proj[:,1], cpca_comp0_proj[:,2], alpha=0.5, color='b')
  1177. scatter2 = ax.scatter(cpca_comp1_proj[:,1], cpca_comp1_proj[:,2], c=t,
  1178. cmap='Greens', s=100, alpha=0.8)
  1179. ax.plot(cpca_comp1_proj[:,1], cpca_comp1_proj[:,2], alpha=0.5, color='g')
  1180. scatter3 = ax.scatter(cpca_comp2_proj[:,1], cpca_comp2_proj[:,2], c=t,
  1181. cmap='Reds', s=100, alpha=0.8)
  1182. ax.plot(cpca_comp2_proj[:,1], cpca_comp2_proj[:,2], alpha=0.5, color='r')
  1183. cbar = plt.colorbar(scatter3, shrink=0.7, pad=0, ax=ax, fraction=0.1)
  1184. cbar.ax.set_title('Pattern One TR', loc='left', rotation=50, fontsize=13)
  1185. cbar.ax.tick_params(labelsize=13)
  1186. cbar = plt.colorbar(scatter2, shrink=0.7, pad=0, ax=ax, fraction=0.1)
  1187. cbar.ax.set_title('Pattern Two TR', loc='left', rotation=50, fontsize=13)
  1188. cbar.set_ticks([])
  1189. cbar = plt.colorbar(scatter1, shrink=0.7, pad=0.01, ax=ax, fraction=0.1)
  1190. cbar.ax.set_title('Pattern Three TR', loc='left', rotation=50, fontsize=13)
  1191. cbar.set_ticks([])
  1192. cpca_comp_all = np.vstack([zscore(cpca_comp0.T).T, zscore(cpca_comp1.T).T, zscore(cpca_comp2.T).T])
  1193. cpca_comp_all_proj = np.vstack([cpca_comp0_proj, cpca_comp1_proj, cpca_comp2_proj])
  1194. kmeans = KMeans(n_clusters=6, n_init=10, random_state=0)
  1195. kmeans.fit(cpca_comp_all)
  1196. g_sub2 = gridspec.GridSpecFromSubplotSpec(2,3, subplot_spec=gspec[2], wspace=0.2,
  1197. hspace=0.1, width_ratios=[0.3,0.3,0.4],
  1198. height_ratios=[0.01,0.99])
  1199. title_ax = fig.add_subplot(g_sub2[0,:])
  1200. title_ax.set_title('C) Recurring Pattern Clusters in Spatiotemporal Patterns',
  1201. fontsize=18, fontweight='bold', loc='left')
  1202. title_ax.axis('off')
  1203. ax0 = fig.add_subplot(g_sub2[1,0])
  1204. ax1 = fig.add_subplot(g_sub2[1,1])
  1205. ax2 = fig.add_subplot(g_sub2[1,2])
  1206. ax0.set_xlabel('PC 1', fontsize=16, fontweight='bold')
  1207. ax0.set_ylabel('PC 2', fontsize=16, fontweight='bold', labelpad=-20)
  1208. ax1.set_xlabel('PC 1', fontsize=16, fontweight='bold')
  1209. ax1.set_ylabel('PC 3', fontsize=16, fontweight='bold', labelpad=-20)
  1210. ax2.set_xlabel('PC 2', fontsize=16, fontweight='bold')
  1211. ax2.set_ylabel('PC 3', fontsize=16, fontweight='bold', labelpad=-20)
  1212. c_colors = [plt.cm.tab20(i) for i in [14,15,16,17,18,19]]
  1213. for c_indx, label in enumerate(np.unique(kmeans.labels_)):
  1214. indx = np.where(kmeans.labels_==label)
  1215. ax0.scatter(cpca_comp_all_proj[indx,0], cpca_comp_all_proj[indx,1],
  1216. color=c_colors[c_indx], s=100, label=f'Cluster {c_indx+1}')
  1217. ax1.scatter(cpca_comp_all_proj[indx,0], cpca_comp_all_proj[indx,2],
  1218. color=c_colors[c_indx], s=100)
  1219. ax2.scatter(cpca_comp_all_proj[indx,1], cpca_comp_all_proj[indx,2],
  1220. color=c_colors[c_indx], s=100)
  1221. ax0.plot(cpca_comp0_proj[:,0], cpca_comp0_proj[:,1], alpha=0.2, color='b')
  1222. ax0.plot(cpca_comp1_proj[:,0], cpca_comp1_proj[:,1], alpha=0.2, color='g')
  1223. ax0.plot(cpca_comp2_proj[:,0], cpca_comp2_proj[:,1], alpha=0.2, color='r')
  1224. ax0.legend()
  1225. ax1.plot(cpca_comp0_proj[:,0], cpca_comp0_proj[:,2], alpha=0.2, color='b')
  1226. ax1.plot(cpca_comp1_proj[:,0], cpca_comp1_proj[:,2], alpha=0.2, color='g')
  1227. ax1.plot(cpca_comp2_proj[:,0], cpca_comp2_proj[:,2], alpha=0.2, color='r')
  1228. ax2.plot(cpca_comp0_proj[:,1], cpca_comp0_proj[:,2], alpha=0.2, color='b')
  1229. ax2.plot(cpca_comp1_proj[:,1], cpca_comp1_proj[:,2], alpha=0.2, color='g')
  1230. ax2.plot(cpca_comp2_proj[:,1], cpca_comp2_proj[:,2], alpha=0.2, color='r')
  1231. labels_c0 = kmeans.labels_[:30]
  1232. labels_c1 = kmeans.labels_[30:60]
  1233. labels_c2 = kmeans.labels_[60:]
  1234. labels_df = pd.DataFrame([labels_c0, labels_c1, labels_c2]).T
  1235. cmap = colors.ListedColormap(c_colors)
  1236. bounds=[0,1,2,3,4,5]
  1237. norm = colors.BoundaryNorm(bounds, cmap.N)
  1238. ax2_divider = make_axes_locatable(ax2)
  1239. sub_ax2 = ax2_divider.append_axes("right", size="30%", pad="10%")
  1240. sub_ax2.imshow(labels_df, cmap=cmap)
  1241. sub_ax2.set_aspect(0.5)
  1242. sub_ax2.set_ylabel('TR', fontsize=14, labelpad=-2)
  1243. sub_ax2.set_title('D) Clusters by TR', fontsize=18, fontweight='bold', pad=25)
  1244. sub_ax2.set_xticks([0,1,2])
  1245. sub_ax2.set_xticklabels(['Pattern One', 'Pattern Two', 'Pattern Three'], fontsize=12,
  1246. rotation=50, ha='center')
  1247. ## Write cluster centroids to cifti files
  1248. # This cannot be done without access to data, so it is commented out
  1249. # ex_subj_file = ['data/rest/proc_3_surf_resamp/200513_LR1_rest_smooth_filt_smooth_filt_resamp.R.func.gii',
  1250. # 'data/rest/proc_3_surf_resamp/200513_LR1_rest_smooth_filt_smooth_filt_resamp.L.func.gii']
  1251. # hdr = load_gifti(ex_subj_file)
  1252. # write_to_gifti(kmeans.cluster_centers_, hdr, 'cpca_kmeans_N6', zero_mask)
  1253. g_sub3 = gridspec.GridSpecFromSubplotSpec(3,3, subplot_spec=gspec[3], wspace=0.1,
  1254. hspace=0, height_ratios=[0.01,0.49,0.49])
  1255. title_ax = fig.add_subplot(g_sub3[0,:])
  1256. title_ax.set_title('E) Recurring Pattern Cluster Maps',
  1257. fontsize=18, fontweight='bold', loc='left')
  1258. title_ax.axis('off')
  1259. ax = fig.add_subplot(g_sub3[1,0])
  1260. img = mpimg.imread('demo_files/cpca_recon_cluster0.png')
  1261. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  1262. ax.axis('off')
  1263. ax_divider = make_axes_locatable(ax)
  1264. sub_ax = ax_divider.append_axes("top", size="10%", pad="2%")
  1265. sub_ax.text(0.4,0, 'Cluster 1', fontsize=16)
  1266. # add a fancy box
  1267. fancybox = FancyBboxPatch((0.7,0),0.05,1,linewidth=1,
  1268. edgecolor='none',facecolor=c_colors[0],
  1269. boxstyle=BoxStyle("Round", pad=0.02, rounding_size=0.015))
  1270. sub_ax.add_patch(fancybox)
  1271. sub_ax.axis('off')
  1272. ax = fig.add_subplot(g_sub3[1,1])
  1273. img = mpimg.imread('demo_files/cpca_recon_cluster1.png')
  1274. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  1275. ax.axis('off')
  1276. ax_divider = make_axes_locatable(ax)
  1277. sub_ax = ax_divider.append_axes("top", size="10%", pad="2%")
  1278. sub_ax.text(0.4,0, 'Cluster 2', fontsize=16)
  1279. # add a fancy box
  1280. fancybox = FancyBboxPatch((0.7,0),0.05,1,linewidth=1,
  1281. edgecolor='none',facecolor=c_colors[1],
  1282. boxstyle=BoxStyle("Round", pad=0.02, rounding_size=0.015))
  1283. sub_ax.add_patch(fancybox)
  1284. sub_ax.axis('off')
  1285. ax = fig.add_subplot(g_sub3[1,2])
  1286. img = mpimg.imread('demo_files/cpca_recon_cluster2.png')
  1287. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  1288. ax.axis('off')
  1289. ax_divider = make_axes_locatable(ax)
  1290. sub_ax = ax_divider.append_axes("top", size="10%", pad="2%")
  1291. sub_ax.text(0.4,0, 'Cluster 3', fontsize=16)
  1292. # add a fancy box
  1293. fancybox = FancyBboxPatch((0.7,0),0.05,1,linewidth=1,
  1294. edgecolor='none',facecolor=c_colors[2],
  1295. boxstyle=BoxStyle("Round", pad=0.02, rounding_size=0.015))
  1296. sub_ax.add_patch(fancybox)
  1297. sub_ax.axis('off')
  1298. ax = fig.add_subplot(g_sub3[2,0])
  1299. img = mpimg.imread('demo_files/cpca_recon_cluster3.png')
  1300. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  1301. ax.axis('off')
  1302. ax_divider = make_axes_locatable(ax)
  1303. sub_ax = ax_divider.append_axes("top", size="10%", pad="2%")
  1304. sub_ax.text(0.4,0, 'Cluster 4', fontsize=16)
  1305. # add a fancy box
  1306. fancybox = FancyBboxPatch((0.7,0),0.05,1,linewidth=1,
  1307. edgecolor='none',facecolor=c_colors[3],
  1308. boxstyle=BoxStyle("Round", pad=0.02, rounding_size=0.015))
  1309. sub_ax.add_patch(fancybox)
  1310. sub_ax.axis('off')
  1311. ax = fig.add_subplot(g_sub3[2,1])
  1312. img = mpimg.imread('demo_files/cpca_recon_cluster4.png')
  1313. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  1314. ax.axis('off')
  1315. ax_divider = make_axes_locatable(ax)
  1316. sub_ax = ax_divider.append_axes("top", size="10%", pad="2%")
  1317. sub_ax.text(0.4,0, 'Cluster 5', fontsize=16)
  1318. # add a fancy box
  1319. fancybox = FancyBboxPatch((0.7,0),0.05,1,linewidth=1,
  1320. edgecolor='none',facecolor=c_colors[4],
  1321. boxstyle=BoxStyle("Round", pad=0.02, rounding_size=0.015))
  1322. sub_ax.add_patch(fancybox)
  1323. sub_ax.axis('off')
  1324. ax = fig.add_subplot(g_sub3[2,2])
  1325. img = mpimg.imread('demo_files/cpca_recon_cluster5.png')
  1326. ax.imshow(cropImage(img,crop_width_l,crop_width_r,crop_height))
  1327. ax.axis('off')
  1328. ax_divider = make_axes_locatable(ax)
  1329. sub_ax = ax_divider.append_axes("top", size="10%", pad="2%")
  1330. sub_ax.text(0.4,0, 'Cluster 6', fontsize=16)
  1331. # add a fancy box
  1332. fancybox = FancyBboxPatch((0.7,-0.1),0.05,1,linewidth=1,
  1333. edgecolor='none',facecolor=c_colors[5],
  1334. boxstyle=BoxStyle("Round", pad=0.02, rounding_size=0.015))
  1335. sub_ax.add_patch(fancybox)
  1336. sub_ax.axis('off')
  1337. # plt.show()
  1338. plt.savefig('results/figures/cpca_dynamics.eps', bbox_inches='tight')
  1339. # %% [markdown]
  1340. # ## Figure Caption
  1341. # %% [markdown]
  1342. # Supplementary Figure A. Spatiotemporal Patterns Consist of Steady States and Propagation Events That Repeat Across Patterns. (PC = Principal Component) Illustration of the progression of BOLD activity over time in each spatiotemporal pattern. A) The spatial weights for the first three principal components from PCA (Figure 1B). For visualization of temporal dynamics, the reconstructed time points (N=30) from each spatiotemporal pattern were projected onto the 3-dimensional embedding space formed by the first three principal components. B) Two-dimensional slices of each spatiotemporal pattern in the 3-dimensional principal component space - PC1-PC2, PC1-PC3, and PC2-PC3 spaces. The time points of patterns one, two and three are displayed as blue, green and red points, respectively. Consecutive time points of each spatiotemporal pattern are linked by lines. The time points of each spatiotemporal pattern are colored from light to darker to visualize the progression of time (N=30). The score of each time point on a given principal component is proportional to the Pearson correlation coefficient between the BOLD activity at that time point with the spatial weights of the principal component. Examination of the movement of time points within the 3-dimensional space provides information regarding the temporal dynamics of the spatiotemporal pattern. C) The same two-dimensional slices of each spatiotemporal pattern in the 3-dimensional principal component space colored according to their cluster assignment by a k-means clustering algorithm. K-means clustering was used to identify recurring spatial patterns of BOLD activity across time points of the three spatiotemporal patterns. Six clusters were estimated. D) The cluster assignments (color) by time (y-axis) of each spatiotemporal pattern (x-axis). Note, that the same cluster assignment can occur across more than one spatiotemporal pattern. E) The cluster centroids from the k-means clustering algorithm, corresponding to the average spatial pattern of BOLD activity for the time points that belong to that cluster. Note, the cluster centroids of the first two clusters are mean-centered versions of the original unimodal (all-positive or all-negative) steady-state of pattern one, as z-score normalization of the time-points across vertices was performed beforehand.
  1343. # %% [markdown]
  1344. # # <b>Supplementary Figure A - Low-Dimensional Latent FC Topograhies</b>
  1345. # %%
  1346. cifti_fps = (
  1347. 'demo_files/pca_rest.dtseries.nii',
  1348. 'demo_files/eigenmap_p90.dtseries.nii',
  1349. 'demo_files/pca_rest_varimax.dtseries.nii',
  1350. 'demo_files/s_ica.dtseries.nii', 'demo_files/t_ica.dtseries.nii'
  1351. )
  1352. fps = (
  1353. 'demo_files/pca_rest_comp0.png', 'demo_files/pca_rest_comp1.png', 'demo_files/pca_rest_comp2.png',
  1354. 'demo_files/eigenmap_p90_comp0.png',
  1355. 'demo_files/pca_rest_varimax_comp0.png', 'demo_files/pca_rest_varimax_comp1.png',
  1356. 'demo_files/pca_rest_varimax_comp2.png', 'demo_files/spatial_ica_comp0.png', 'demo_files/spatial_ica_comp1.png',
  1357. 'demo_files/spatial_ica_comp2.png', 'demo_files/temporal_ica_comp0.png', 'demo_files/temporal_ica_comp1.png',
  1358. 'demo_files/temporal_ica_comp2.png'
  1359. )
  1360. labels_short = (
  1361. 'PCA Comp 1', 'PCA Comp 2', 'PCA Comp 3',
  1362. 'Eigenmap 1', 'Varimax Comp 1', 'Varimax Comp 2', 'Varimax Comp 3',
  1363. 'SICA Comp 1', 'SICA Comp 2', 'SICA Comp 3', 'TICA Comp 1', 'TICA Comp 2',
  1364. 'TICA Comp 3'
  1365. )
  1366. pca_ts = pickle.load(open('demo_files/pca_ts.pkl', 'rb'))[:,:3]
  1367. varimax_ts = pickle.load(open('demo_files/varimax_ts.pkl', 'rb'))
  1368. tica_ts = pickle.load(open('demo_files/tica_ts.pkl', 'rb'))
  1369. sica_ts = pickle.load(open('demo_files/sica_ts.pkl', 'rb'))
  1370. all_ts = [pca_ts, varimax_ts, sica_ts, tica_ts]
  1371. corr_time = np.corrcoef(np.hstack(all_ts).T)
  1372. ## 1. Load All Maps
  1373. cifti_maps_all = []
  1374. for fp in cifti_fps:
  1375. _, cifti_maps, n_time = pull_cifti_data(load_cifti(fp))
  1376. if any([label in fp for label in ['pca_rest', 'ica']]):
  1377. cifti_maps_all.append(cifti_maps[:3, :])
  1378. elif 'eigenmap' in fp:
  1379. cifti_maps_all.append(cifti_maps[0, :])
  1380. else:
  1381. cifti_maps_all.append(cifti_maps[:2, :])
  1382. cifti_maps_all = np.vstack(cifti_maps_all)
  1383. zero_mask = np.std(cifti_maps_all, axis=0) > 0
  1384. zero_mask_indx = np.where(zero_mask)[0]
  1385. cifti_maps_all = cifti_maps_all[:, zero_mask].copy()
  1386. # # Normalize
  1387. corr_maps = np.corrcoef(cifti_maps_all)
  1388. # cifti_maps_all = zscore(cifti_maps_all.T)
  1389. fig = plt.figure(figsize=(16,22), constrained_layout=False)
  1390. # gspec = fig.add_gridspec(5,3, hspace=0.2, wspace=0, height_ratios=[0.85,0.01,0.15])
  1391. gspec = fig.add_gridspec(9,3, hspace=0.2, wspace=0, height_ratios=[0.01,0.15,0.15,0.15,0.15,0.15,0.15,0.15,0.15])
  1392. title_ax = fig.add_subplot(gspec[0,:])
  1393. title_ax.set_title('A) Low-Dimensional FC Topograhies',
  1394. fontsize=16, fontweight='bold', loc='left')
  1395. title_ax.axis('off')
  1396. # title_ax = fig.add_subplot(gspec[1,:])
  1397. # title_ax.set_title('B) Spatial and Temporal Correlations with Principal Components',
  1398. # fontsize=16, fontweight='bold', loc='left')
  1399. # title_ax.axis('off')
  1400. # g_sub0 = gridspec.GridSpecFromSubplotSpec(2,1, subplot_spec=gspec[0], hspace=0, height_ratios=[0.73,0.27])
  1401. # g_sub0_0 = gridspec.GridSpecFromSubplotSpec(2,4, subplot_spec=g_sub0[0], hspace=0, wspace=0)
  1402. # g_sub0_1 = gridspec.GridSpecFromSubplotSpec(1,3, subplot_spec=g_sub0[1], wspace=0)
  1403. crop_width = (35, 150)
  1404. crop_height = (5,15)
  1405. # indx=3
  1406. # for i in range(3):
  1407. # for j in range(4):
  1408. # if indx < 11:
  1409. # ax = fig.add_subplot(g_sub0_0[i,j])
  1410. # elif indx < 14:
  1411. # ax = fig.add_subplot(g_sub0_1[j])
  1412. # else:
  1413. # break
  1414. # img = mpimg.imread(fps[indx])
  1415. # ax.set_title(labels_short[indx], fontsize=16)
  1416. # ax.imshow(cropImage(img,crop_width,crop_height))
  1417. # ax.axis('off')
  1418. # indx+=1
  1419. indx=0
  1420. grid_indices = ([1,0], [1,1], [1,2], [2,0], [3,0], [4,0], [5,0], [6,0],
  1421. [6,1], [6,2], [7,0], [7,1], [7,2])
  1422. for g_indx in grid_indices:
  1423. ax = fig.add_subplot(gspec[g_indx[0], g_indx[1]])
  1424. img = mpimg.imread(fps[indx])
  1425. ax.set_title(labels_short[indx], fontsize=16)
  1426. ax.imshow(cropImage_single(img,crop_width,crop_height))
  1427. ax.axis('off')
  1428. indx+=1
  1429. # g_sub1 = gridspec.GridSpecFromSubplotSpec(1,2, subplot_spec=gspec[2])
  1430. ax = fig.add_subplot(gspec[3:6,1])
  1431. im = ax.imshow(np.abs(corr_maps[:3,3:].T), aspect=0.3, cmap='coolwarm', vmin=0, vmax=0.9)
  1432. ax.set_xticks(np.arange(3))
  1433. ax.set_yticks(np.arange(len(labels_short[3:])))
  1434. ax.set_yticklabels(labels_short[3:], fontweight='bold')
  1435. ax.set_xticklabels(labels_short[:3], rotation=30, fontweight='bold')
  1436. ax.xaxis.tick_top()
  1437. ax.set_title('B) Spatial Correlation with PCs', fontsize=16, fontweight='bold', loc='center')
  1438. ax.set_aspect(1)
  1439. box = ax.get_position()
  1440. box.x0 = box.x0 - 0.01
  1441. box.x1 = box.x1 - 0.01
  1442. box.y0 = box.y0 + 0.04
  1443. box.y1 = box.y1 + 0.04
  1444. ax.set_position(box)
  1445. divider = make_axes_locatable(ax)
  1446. cax = divider.append_axes("right", size="10%", pad=0.1)
  1447. cbar = plt.colorbar(im, orientation='vertical', cax=cax)
  1448. ax = fig.add_subplot(gspec[3:6,2])
  1449. im = ax.imshow(np.abs(corr_time[:3,3:].T), aspect=0.25, cmap='coolwarm', vmin=0, vmax=0.9)
  1450. ax.set_xticks(np.arange(3))
  1451. ax.set_yticks(np.arange(len(labels_short[4:])))
  1452. ax.set_yticklabels(labels_short[4:], fontweight='bold')
  1453. ax.set_xticklabels(labels_short[:3], rotation=30, fontweight='bold')
  1454. ax.xaxis.tick_top()
  1455. ax.set_title('C) Temporal Correlations with PCs', fontsize=16, fontweight='bold', loc='center')
  1456. ax.set_aspect(1)
  1457. box = ax.get_position()
  1458. box.y0 = box.y0 + 0.04
  1459. box.y1 = box.y1 + 0.04
  1460. ax.set_position(box)
  1461. divider = make_axes_locatable(ax)
  1462. cax = divider.append_axes("right", size="10%", pad=0.1)
  1463. cbar = plt.colorbar(im, orientation='vertical', cax=cax)
  1464. plt.savefig('results/figures/supplement_latentFC.eps')
  1465. plt.show()
  1466. # %% [markdown]
  1467. # ### Supplementary Figure A Caption
  1468. # %% [markdown]
  1469. # (SICA=Spatial ICA; TICA = Temporal ICA). The spatial weights of components from PCA (N=3), Laplacian Eigenmaps (N=1), varimax rotation of principal components (N=3), spatial ICA (N=3) and temporal ICA (N=3). The temporal and spatial correlations (absolute value) between the components of dimension-reduction analyses and the first three principal components are shown in the middle of the plot. Note, due to the nature of the Laplacian Eigenmap algorithm as a non-linear manifold learning algorithm, time courses cannot be extracted for their components. As illustrated in the spatial and temporal correlations table, the dimension-reduction analyses are largely consistent in their spatial topographies and temporal dynamics with the first three principal components.
  1470. # %% [markdown]
  1471. # # <b>Supplementary Figure B - Seed-Based Topographies </b>
  1472. # %%
  1473. fps = (
  1474. ['demo_files/fc_map_sm.png', 'demo_files/fc_map_sm_gs.png'],
  1475. ['demo_files/fc_map_precuneus.png', 'demo_files/fc_map_precuneus_gs.png'],
  1476. ['demo_files/fc_map_supramarginal.png', 'demo_files/fc_map_supramarginal_gs.png'],
  1477. ['demo_files/caps_sm_cluster0_c2.png', 'demo_files/caps_sm_cluster1_c2.png'],
  1478. ['demo_files/caps_precuneus_cluster0_c2.png', 'demo_files/caps_precuneus_cluster1_c2.png'],
  1479. ['demo_files/caps_supramarginal_cluster0_c2.png', 'demo_files/caps_supramarginal_cluster1_c2.png'],
  1480. ['demo_files/caps_sm_norm_cluster0_c2.png', 'demo_files/caps_sm_norm_cluster1_c2.png'],
  1481. ['demo_files/caps_precuneus_norm_cluster0_c2.png', 'demo_files/caps_precuneus_norm_cluster1_c2.png'],
  1482. ['demo_files/caps_supramarginal_norm_cluster0_c2.png', 'demo_files/caps_supramarginal_norm_cluster1_c2.png']
  1483. )
  1484. pca_complex_res = pickle.load(open('results/pca_rest_complex_results.pkl', 'rb'))['pca']
  1485. comp_ts = np.real(pca_complex_res['pc_scores'][:,:3])
  1486. crop_width_L = (35, 1200)
  1487. crop_width_R = (1100, 150)
  1488. crop_height = (5,20)
  1489. fig = plt.figure(figsize=(20,28), constrained_layout=False)
  1490. gspec = fig.add_gridspec(2,1, hspace=0.05, wspace=0,
  1491. height_ratios=[0.6,0.4])
  1492. g_sub0 = gridspec.GridSpecFromSubplotSpec(9,6, hspace=0.2, wspace=0, subplot_spec=gspec[0],
  1493. height_ratios=[0.001,0.02,0.3,
  1494. 0.001,0.02,0.3,
  1495. 0.001,0.02,0.3])
  1496. g_sub1 = gridspec.GridSpecFromSubplotSpec(4,6, hspace=0.3, wspace=0, subplot_spec=gspec[1],
  1497. height_ratios=[0.01,0.4,
  1498. 0.1,0.4])
  1499. title_ax = fig.add_subplot(g_sub0[0,:])
  1500. title_ax.set_title('A) Seed-Based Regression Maps',
  1501. fontsize=16, fontweight='bold', loc='left')
  1502. title_ax.axis('off')
  1503. title_ax = fig.add_subplot(g_sub0[3,:])
  1504. title_ax.set_title('B) Co-activation Pattern Clusters (N=2)',
  1505. fontsize=16, fontweight='bold', loc='left')
  1506. title_ax.axis('off')
  1507. title_ax = fig.add_subplot(g_sub0[6,:])
  1508. title_ax.set_title('C) Time-point Normalized - Co-actvation Pattern Clusters (N=2)',
  1509. fontsize=16, fontweight='bold', loc='left')
  1510. title_ax.axis('off')
  1511. title_ax = fig.add_subplot(g_sub1[0,:])
  1512. title_ax.set_title('D) Overlap in Suprathreshold Time Points',
  1513. fontsize=16, fontweight='bold', loc='left')
  1514. title_ax.axis('off')
  1515. title_ax = fig.add_subplot(g_sub1[2,:])
  1516. title_ax.text(0, 0,'E) Correlation between Suprathreshold Time Points and Three Time-lag Structures',
  1517. fontsize=16, fontweight='bold')
  1518. title_ax.axis('off')
  1519. axis_rows = [1,4,7]
  1520. for row in axis_rows:
  1521. sub_title = fig.add_subplot(g_sub0[row,:2])
  1522. sub_title.text(0.2, 0, 'Somatosensory Cortex Seed',
  1523. fontsize=14, fontweight='bold')
  1524. sub_title.axis('off')
  1525. sub_title = fig.add_subplot(g_sub0[row,2:4])
  1526. sub_title.text(0.3, 0, 'Precuneus Seed',
  1527. fontsize=14, fontweight='bold')
  1528. sub_title.axis('off')
  1529. sub_title = fig.add_subplot(g_sub0[row,4:6])
  1530. sub_title.text(0.2, 0, 'Supramarginal Gryus Seed',
  1531. fontsize=14, fontweight='bold')
  1532. sub_title.axis('off')
  1533. indx = 0
  1534. for fp_list in fps[:3]:
  1535. for fp in fp_list:
  1536. if indx % 2 == 0:
  1537. crop_w = crop_width_L
  1538. label='Original'
  1539. else:
  1540. crop_w = crop_width_R
  1541. label='Global Signal Regressed'
  1542. ax = fig.add_subplot(g_sub0[2,indx])
  1543. img = mpimg.imread(fp)
  1544. ax.set_title(label, fontsize=14)
  1545. ax.imshow(cropImage_single(img,crop_w,crop_height))
  1546. ax.axis('off')
  1547. indx+=1
  1548. indx = 0
  1549. for fp_list in fps[3:6]:
  1550. for fp in fp_list:
  1551. if indx % 2 == 0:
  1552. crop_w = crop_width_L
  1553. label='Cluster 1'
  1554. else:
  1555. crop_w = crop_width_R
  1556. label='Cluster 2'
  1557. ax = fig.add_subplot(g_sub0[5,indx])
  1558. img = mpimg.imread(fp)
  1559. ax.set_title(label, fontsize=14)
  1560. ax.imshow(cropImage_single(img,crop_w,crop_height))
  1561. ax.axis('off')
  1562. indx+=1
  1563. indx = 0
  1564. for fp_list in fps[6:]:
  1565. for fp in fp_list:
  1566. if indx % 2 == 0:
  1567. crop_w = crop_width_L
  1568. label='Cluster 1'
  1569. else:
  1570. crop_w = crop_width_R
  1571. label='Cluster 2'
  1572. ax = fig.add_subplot(g_sub0[8,indx])
  1573. img = mpimg.imread(fp)
  1574. ax.set_title(label, fontsize=14)
  1575. ax.imshow(cropImage_single(img,crop_w,crop_height))
  1576. ax.axis('off')
  1577. indx+=1
  1578. # Load and create CAP time series
  1579. seeds = ['sm', 'precuneus', 'supramarginal']
  1580. seed_labels = ['SM', 'P', 'SMG']
  1581. clus_ts = {}
  1582. clus_ts_norm = {}
  1583. for seed, seed_label in zip(seeds, seed_labels):
  1584. clus_ts[seed_label] = {}
  1585. clus_ts_norm[seed_label] = {}
  1586. caps_res = pickle.load(open(f'results/caps_{seed}_c2_results.pkl', 'rb'))
  1587. caps_res_norm = pickle.load(open(f'results/caps_{seed}_norm_c2_results.pkl', 'rb'))
  1588. ts_indx = caps_res[2]
  1589. clus_indx = caps_res[1]
  1590. ts_indx_norm = caps_res_norm[2]
  1591. clus_indx_norm = caps_res_norm[1]
  1592. for clus in [0,1]:
  1593. clus_ts_indx = ts_indx[clus_indx==clus]
  1594. clus_ts_indx_norm = ts_indx_norm[clus_indx_norm==clus]
  1595. ts_tmp = np.zeros(n_ts)
  1596. ts_tmp_norm = np.zeros(n_ts)
  1597. ts_tmp[clus_ts_indx] = 1
  1598. ts_tmp_norm[clus_ts_indx_norm] = 1
  1599. clus_ts[seed_label][f'C{clus+1}'] = ts_tmp
  1600. clus_ts_norm[seed_label][f'C{clus+1}'] = ts_tmp_norm
  1601. # Create dataframe of CAP time series
  1602. all_ts = []
  1603. all_ts_norm = []
  1604. all_ts_labels = []
  1605. for seed in seed_labels:
  1606. for clus in ['C1', 'C2']:
  1607. all_ts.append(clus_ts[seed][clus])
  1608. all_ts_norm.append(clus_ts_norm[seed][clus])
  1609. all_ts_labels.append(seed + '_' + clus)
  1610. all_ts = pd.DataFrame(np.array(all_ts).T, columns=all_ts_labels)
  1611. all_ts_norm = pd.DataFrame(np.array(all_ts_norm).T, columns=all_ts_labels)
  1612. # Calculate overlap in CAP time series w/ jaccard similarity
  1613. sim_mat = np.zeros((6,6))
  1614. sim_mat_norm = np.zeros((6,6))
  1615. sim_mat_lag = np.zeros((6,6))
  1616. sim_mat_lag_norm = np.zeros((6,6))
  1617. for x in range(6):
  1618. for y in range(x,6):
  1619. x_ts = all_ts.iloc[:, [x]]; x_ts_norm = all_ts_norm.iloc[:, [x]]
  1620. y_ts = all_ts.iloc[:, [y]]; y_ts_norm = all_ts_norm.iloc[:, [y]]
  1621. lags = list(range(-30,31))
  1622. lag_jaccard = [1 - cdist(x_ts.shift(i).values.T, y_ts.values.T, 'jaccard')[0]
  1623. for i in lags]
  1624. lag_jaccard_norm = [1 - cdist(x_ts_norm.shift(i).values.T, y_ts_norm.values.T, 'jaccard')[0]
  1625. for i in lags]
  1626. max_jaccard = np.max(lag_jaccard); max_jaccard_norm = np.max(lag_jaccard_norm)
  1627. max_jaccard_lag = lags[np.argmax(lag_jaccard)]
  1628. max_jaccard_lag_norm = lags[np.argmax(lag_jaccard_norm)]
  1629. sim_mat[x,y] = max_jaccard; sim_mat_norm[x,y] = max_jaccard_norm
  1630. sim_mat_lag[x,y] = max_jaccard_lag; sim_mat_lag_norm[x,y] = max_jaccard_lag_norm
  1631. i_lower = np.tril_indices(6, -1)
  1632. sim_mat[i_lower] = sim_mat.T[i_lower]
  1633. sim_mat_norm[i_lower] = sim_mat_norm.T[i_lower]
  1634. sim_mat_lag[i_lower] = sim_mat_lag.T[i_lower]
  1635. sim_mat_lag_norm[i_lower] = sim_mat_lag_norm.T[i_lower]
  1636. # Sort jaccard similarity matrix
  1637. labels = [1, 0, 1, 0, 0, 1]
  1638. sort_indx = np.argsort(labels)
  1639. sorted_vals = np.sort(labels)
  1640. labels_sorted = [all_ts_labels[i] for i in sort_indx]
  1641. sortedmat = [[sim_mat[i,j] for j in sort_indx] for i in sort_indx]
  1642. sim_mat_sorted = pd.DataFrame(sortedmat, columns = labels_sorted, index=labels_sorted)
  1643. sortedmat = [[sim_mat_norm[i,j] for j in sort_indx] for i in sort_indx]
  1644. sim_mat_sorted_norm = pd.DataFrame(sortedmat, columns = labels_sorted, index=labels_sorted)
  1645. g_sub1_0 = gridspec.GridSpecFromSubplotSpec(1,2, subplot_spec=g_sub1[1, :], wspace=0.5)
  1646. ax1 = fig.add_subplot(g_sub1_0[0])
  1647. ax2 = fig.add_subplot(g_sub1_0[1])
  1648. im1 = ax1.imshow(sim_mat_sorted, vmin=0, vmax=0.25)
  1649. ax1.set_xticks(np.arange(6))
  1650. ax1.set_yticks(np.arange(6))
  1651. ax1.set_yticklabels(labels_sorted, fontsize=13, fontweight='bold')
  1652. ax1.set_xticklabels(labels_sorted, fontsize=13, rotation=55, fontweight='bold')
  1653. ax1.set_title('D1) Cluster Jaccard Similarity', fontsize=14,
  1654. fontweight='bold', loc='center', pad=7)
  1655. plt.colorbar(im1, ax=ax1)
  1656. im2 = ax2.imshow(sim_mat_sorted_norm, vmin=0, vmax=0.25)
  1657. ax2.set_xticks(np.arange(6))
  1658. ax2.set_yticks(np.arange(6))
  1659. ax2.set_yticklabels(labels_sorted, fontsize=13, fontweight='bold')
  1660. ax2.set_xticklabels(labels_sorted, fontsize=13, rotation=55, fontweight='bold')
  1661. ax2.set_title('D2) Cluster Jaccard Similarity - Normalized', fontsize=14,
  1662. fontweight='bold', loc='center', pad=7)
  1663. plt.colorbar(im2, ax=ax2)
  1664. g_sub1_1 = gridspec.GridSpecFromSubplotSpec(1,2, subplot_spec=g_sub1[3, :], wspace=0.5)
  1665. ax1 = fig.add_subplot(g_sub1_1[0])
  1666. ax2 = fig.add_subplot(g_sub1_1[1])
  1667. # Calculate correlation between CAP time series and time-lag structures
  1668. corr_mat = np.zeros((3,6))
  1669. corr_mat_norm = np.zeros((3,6))
  1670. corr_mat_lag = np.zeros((3,6))
  1671. corr_mat_lag_norm = np.zeros((3,6))
  1672. for x in range(3):
  1673. for y in range(6):
  1674. pc_ts = pd.Series(comp_ts[:, x])
  1675. cap_ts = all_ts.iloc[:, y]; cap_ts_norm = all_ts_norm.iloc[:, y]
  1676. lags = list(range(-30,31))
  1677. lag_corr = np.abs([pc_ts.corr(cap_ts.shift(i)) for i in lags])
  1678. lag_corr_norm = np.abs([pc_ts.corr(cap_ts_norm.shift(i)) for i in lags])
  1679. max_corr = np.max(lag_corr); max_corr_norm = np.max(lag_corr_norm)
  1680. max_corr_lag = lags[np.argmax(lag_corr)]
  1681. max_corr_lag_norm = lags[np.argmax(lag_corr_norm)]
  1682. corr_mat[x,y] = max_corr; corr_mat_norm[x,y] = max_corr_norm
  1683. corr_mat_lag[x,y] = max_corr_lag; corr_mat_lag_norm[x,y] = max_corr_lag_norm
  1684. pc_labels = ['SMLV-to-FPN', 'FPN-to-DMN', 'FPN-to-SMLV']
  1685. im1 = ax1.imshow(corr_mat, vmin=0, vmax=0.4)
  1686. cax = plt.colorbar(im1, ax=ax1)
  1687. cax.ax.set_title('Correlation \n (abs. value)')
  1688. ax1.set_xticks(np.arange(6))
  1689. ax1.set_yticks(np.arange(3))
  1690. ax1.set_yticklabels(pc_labels, fontsize=13, fontweight='bold')
  1691. ax1.set_xticklabels(all_ts_labels, fontsize=13, rotation=55, fontweight='bold')
  1692. ax1.set_title('E1) Correlations b/w CAPs and \n Time-lag Structures', fontsize=14,
  1693. fontweight='bold', loc='center', pad=7)
  1694. ax1.set_aspect(0.5)
  1695. im2 = ax2.imshow(corr_mat_norm, vmin=0, vmax=0.4)
  1696. cax = plt.colorbar(im2, ax=ax2)
  1697. cax.ax.set_title('Correlation \n (abs. value)')
  1698. ax2.set_xticks(np.arange(6))
  1699. ax2.set_yticks(np.arange(3))
  1700. ax2.set_yticklabels(pc_labels, fontsize=13, fontweight='bold')
  1701. ax2.set_xticklabels(all_ts_labels, fontsize=13, rotation=55, fontweight='bold')
  1702. ax2.set_title('E2) Correlations b/w Normalized CAPs and \n Time-lag Structures', fontsize=14,
  1703. fontweight='bold', loc='center', pad=7)
  1704. ax2.set_aspect(0.5)
  1705. plt.savefig('results/figures/supplement_seeds.eps')
  1706. plt.show()
  1707. # %% [markdown]
  1708. # ### Supplementary Figure B Caption
  1709. # %% [markdown]
  1710. # (SM = somatosensory cortex; P=Precuneus; SMG=Supramarginal Gyrus). Spatial topographies of seed-based regression maps and CAP centroids from somatosensory (SM), precuneus and supramarginal gyrus seeds. A) Seed-based regression maps with (left hemisphere) and without global signal regression (right hemisphere) for SM, precuneus and supramarginal gyrus seeds. B) CAP cluster centroids (N=2) from k-means clustering of non-normalized (i.e. not z-scored) suprathreshold time points from SM, precuneus and supramarginal seeds. C) CAP cluster centroids (N=2) of the same suprathreshold time points with normalization (i.e. z-scored) before input to the k-means clustering algorithm. D1) Temporal overlap between binary time courses (see main text) of the two CAPs from each seed using the Jaccard similarity (Jaccard index). The Jaccard similarity between two CAP binary time courses varies from 0 to 1, and reflects the ratio of overlapping onset time points (=1) to the total number of time points (N=60,000). D2) Temporal overlap between CAP binary time courses from the normalized solutions of each seed analysis. E) Temporal correlation between the beginning phase time course of the three time-lag structures (SMLV-to-FPN, FPN-to-DMN and FPN-to-SMLV) and the CAP binary time courses for the non-normalized (E1) and normalized (E) solutions.
  1711. # %% [markdown]
  1712. # # <b>Supplementary Figure D - Scree Plot from Complex Principal Component Analysis. </b>
  1713. # %%
  1714. fig, ax = plt.subplots(figsize=(7,7))
  1715. eigs = pickle.load(open('demo_files/pca_complex_eigenvalues.pkl', 'rb'))
  1716. exp_var = [eig/(n_vertices*2) for eig in eigs]
  1717. ax.plot(list(range(1,11)), eigs, '-o', color='black')
  1718. ax.set_xlabel('Component Number', fontsize=13)
  1719. ax.set_ylabel('Eigenvalue', fontsize=13)
  1720. x_adj = 0.3
  1721. y_adj = [-20,50,-30]
  1722. for i in range(3):
  1723. ax.text((i+1)+x_adj,eigs[i]+y_adj[i],
  1724. r'{}%'.format(np.round(exp_var[i]*100,1)),
  1725. fontsize=13, bbox=dict(facecolor='white', alpha=0.5))
  1726. ax.text(6, 2000, 'Explained Variance', fontsize=13, bbox=dict(facecolor='white', alpha=0.5))
  1727. ax.set_title('Complex PCA Scree Plot', fontweight='bold', fontsize=17)
  1728. plt.savefig('results/figures/supplementaryC_screeplot.eps', bbox_inches='tight')
  1729. plt.show()
  1730. # %% [markdown]
  1731. # The eigenvalue by component number plot (i.e. scree plot) used to determine the number of components to extract. There are clear elbows in the plot after one and three components, indicating a preferred solution of one or three principal components (three were chosen).
  1732. # %% [markdown]
  1733. # # <b>Supplementary Figure F. Principal Component and Functional Connectivity Gradient Topographies</b>
  1734. # %%
  1735. fps = [['demo_files/pca_rest_comp0.png', 'demo_files/pca_rest_comp1.png', 'demo_files/pca_rest_comp2.png'],
  1736. ['demo_files/pca_rest_gs_comp0.png', 'demo_files/pca_rest_gs_comp1.png', 'demo_files/pca_rest_gs_comp2.png'],
  1737. ['demo_files/pca_rest_comp0_tmode.png', 'demo_files/pca_rest_comp1_tmode.png', 'demo_files/pca_rest_comp2_tmode.png'],
  1738. ['demo_files/eigenmap_p0_comp0.png', 'demo_files/eigenmap_p0_comp1.png', 'demo_files/diffusion_emb_comp2.png']]
  1739. labels = [['Component 1', 'Component 2', 'Component 3'],
  1740. ['Component 1', 'Component 2', 'Component 3'],
  1741. ['Component 1', 'Component 2', 'Component 3'],
  1742. ['Eigenmap 1', 'Eigenmap 2', 'Eigenmap 3']]
  1743. section_labels = ['Principal Component Analysis',
  1744. 'Principal Component Analysis - Global Signal Removed',
  1745. 'Principal Component Analysis - Time-Point Centered',
  1746. 'Laplacian Eigenmaps - Manifold Learning']
  1747. fig = plt.figure(figsize=(20,20), constrained_layout=False)
  1748. gspec = fig.add_gridspec(8,3, hspace=0.05, wspace=0,
  1749. height_ratios=[0.01,0.2,0.01,0.2,0.01,0.2,0.01,0.2])
  1750. title_inds = [0,2,4,6]
  1751. for title_indx, label in zip(title_inds, section_labels):
  1752. title_ax = fig.add_subplot(gspec[title_indx,:])
  1753. title_ax.set_title(label,fontsize=16, fontweight='bold', loc='left')
  1754. title_ax.axis('off')
  1755. img_inds = [1,3,5,7]
  1756. for fp_sec, label_sec, img_indx in zip(fps, labels, img_inds):
  1757. for i in range(3):
  1758. ax = fig.add_subplot(gspec[img_indx,i])
  1759. img = mpimg.imread(fp_sec[i])
  1760. ax.set_title(label_sec[i], fontsize=15)
  1761. ax.imshow(img)
  1762. ax.axis('off')
  1763. fig.set_facecolor('w')
  1764. plt.savefig('results/figures/supplementaryD_pcagradients.eps')
  1765. plt.show()
  1766. # %% [markdown]
  1767. # ### Supplementary Figure D Caption
  1768. # %% [markdown]
  1769. # Displayed are the FC topography spatial weights from PCA, PCA on global-signal regressed data, PCA on time-point centered data, and Laplacian Eigenmaps. Note, we observed that the eigenmaps were highly positively skewed. To make the negative values of the eigenmaps more visible the colormap is made non-symmetric. The first and second eigenmaps match the second and third principal component from PCA. The first principal component is missing from the LE, global-signal regressed, and time-point centered PCA solutions.
  1770. # %% [markdown]
  1771. # # <b>Supplementary Figure E. Comparison of Lag Projections With and Without Global Signal Regression.</b>
  1772. # %%
  1773. fps = ['demo_files/lag_projection.png', 'demo_files/lag_projection_gs.png']
  1774. labels = ['Lag Projection - Without Global Signal Regression',
  1775. 'Lag Projection - With Global Signal Regression']
  1776. fig, axs = plt.subplots(figsize=(15, 15) , nrows=1, ncols=2)
  1777. img = mpimg.imread(fps[0])
  1778. axs[0].imshow(img)
  1779. axs[0].set_title(labels[0])
  1780. axs[0].axis('off')
  1781. img = mpimg.imread(fps[1])
  1782. axs[1].imshow(img)
  1783. axs[1].set_title(labels[1])
  1784. axs[1].axis('off')
  1785. fig.set_facecolor('w')
  1786. plt.savefig('results/figures/supplementaryG_lag_projection.eps', bbox_inches='tight')
  1787. # plt.show()
  1788. # %% [markdown]
  1789. # ### Figure E Caption
  1790. # %% [markdown]
  1791. # Lag projections with and without global signal regression as a preprocessing step. Values on each cortical map represent the average time-delay between each cortical vertex and all others. Time-delay values are colored from light green/blue (earlier in time) to bright yellow/green (later in time). The range between the earliest and latest time-delay values are significantly shorter for lag projections on global-signal regressed data.
  1792. # %% [markdown]
  1793. # # <b>Supplementary Figure G - Consistency in Zero-lag FC Topographies at Finer-Grained Solutions</b>
  1794. # %%
  1795. base_dir = 'results/cross_val'
  1796. fps = [
  1797. 'caps_smg.dtseries.nii',
  1798. 'caps_prec.dtseries.nii',
  1799. 'caps_sm.dtseries.nii',
  1800. 'eigenmap.dtseries.nii',
  1801. 'hmm_mean_map.dtseries.nii',
  1802. 'pca_varimax.dtseries.nii',
  1803. 'pca.dtseries.nii',
  1804. 's_ica.dtseries.nii',
  1805. 't_ica.dtseries.nii'
  1806. ]
  1807. comps_all = []
  1808. for val in range(12):
  1809. comp_dir=f'comp{val+1}'
  1810. comps_val = []
  1811. for fp in fps:
  1812. _, cifti_maps, _ = pull_cifti_data(load_cifti(f'{base_dir}/{comp_dir}/{fp}'))
  1813. comps_val.append(cifti_maps)
  1814. comps_all.append(np.vstack(comps_val))
  1815. mean_abs_corr = []
  1816. for comp_val in comps_all:
  1817. corr_mat = np.corrcoef(comp_val)
  1818. corr_ltr = corr_mat[np.tril_indices(corr_mat.shape[0],k=1)]
  1819. mean_abs_corr.append(np.mean(np.abs(corr_ltr)))
  1820. fig, ax = plt.subplots(figsize=(7,7))
  1821. ax.set_title('Mean Abs. Correlation by # of Dimensions', fontweight='bold', fontsize=17)
  1822. ax.set_xlabel('# of Dimensions', fontsize=13)
  1823. ax.set_ylabel('Mean Abs. Correlation', fontsize=13)
  1824. ax.plot(range(1,13), mean_abs_corr)
  1825. plt.savefig('results/figures/corr_by_number.eps', bbox_inches='tight')
  1826. # %% [markdown]
  1827. # ## Appendix I - Code to Calculate Average Duration of Complex Principal Components
  1828. # %% [markdown]
  1829. # <font size='4'>The following code was used to calculate the duration of the first three complex principal components. The procedure was as follows: 1) the temporal phase was derived from the complex principal component time series, 2) the phase was unwrapped, and 3) the average duration was calculated as the average time from start (0) to end (2pi) for all cycles of the component time series. </font>
  1830. # %%
  1831. # pca_complex_res = pickle.load(open('results/pca_rest_complex_results.pkl', 'rb'))
  1832. # comp0_phase = np.unwrap(np.angle(pca_complex_res['pc_scores'][:,0]))
  1833. # comp1_phase = np.unwrap(np.angle(pca_complex_res['pc_scores'][:,1]))
  1834. # comp2_phase = np.unwrap(np.angle(pca_complex_res['pc_scores'][:,2]))
  1835. # avg_cycle_comp0 = (comp0_phase[-1]-comp0_phase[0])/60000
  1836. # avg_cycle_comp0 = (2*np.pi)/avg_cycle_comp0
  1837. # avg_cycle_comp1 = (comp1_phase[-1]-comp1_phase[0])/60000
  1838. # avg_cycle_comp1 = (2*np.pi)/avg_cycle_comp1
  1839. # avg_cycle_comp2 = (comp2_phase[-1]-comp2_phase[0])/60000
  1840. # avg_cycle_comp2 = (2*np.pi)/avg_cycle_comp2
  1841. # %% [markdown]
  1842. # ## Scratch Code
  1843. # %%
  1844. pca_complex_res = pickle.load(open('results/pca_rest_complex_results.pkl', 'rb'))
  1845. pca_ts = pickle.load(open('demo_files/pca_ts.pkl', 'rb'))
  1846. sica_ts = pickle.load(open('demo_files/sica_ts.pkl', 'rb'))
  1847. pca_ts_c = pickle.load(open('demo_files/pca_gs_ts.pkl', 'rb'))
  1848. varimax_ts = pickle.load(open('demo_files/varimax_ts.pkl', 'rb'))
  1849. gs_signal = pickle.load(open('demo_files/gs_results.pkl', 'rb'))
  1850. qpp_ts = pickle.load(open('results/qpp_results.pkl', 'rb'))[3]
  1851. pca_complex_ts = pca_complex_res['pca']['pc_scores']
  1852. xcorr(zscore(np.imag(pca_complex_ts[:,0])), zscore(np.real(pca_complex_ts[:,2]).T), maxlags=30) #
  1853. # %%
  1854. _, cifti_maps_real, n_time = pull_cifti_data(load_cifti('results/pca_rest_complex_real.dtseries.nii'))
  1855. _, cifti_maps_imag, n_time = pull_cifti_data(load_cifti('results/pca_rest_complex_imag.dtseries.nii'))
  1856. _, cifti_eigenmap_p90, n_time = pull_cifti_data(load_cifti('results/eigenmap_thres/eigenmap_p90.dtseries.nii'))
  1857. _, cifti_maps_pseed, n_time = pull_cifti_data(load_cifti('results/fc_map_precuneus.dtseries.nii'))
  1858. _, cifti_maps_pseed_gs, n_time = pull_cifti_data(load_cifti('results/fc_map_gs_precuneus.dtseries.nii'))
  1859. _, cifti_maps_smseed, n_time = pull_cifti_data(load_cifti('results/fc_map_sm.dtseries.nii'))
  1860. _, cifti_maps_smseed_gs, n_time = pull_cifti_data(load_cifti('results/fc_map_gs_sm.dtseries.nii'))
  1861. _, cifti_maps_spseed, n_time = pull_cifti_data(load_cifti('results/fc_map_supramarginal.dtseries.nii'))
  1862. _, cifti_maps_spseed_gs, n_time = pull_cifti_data(load_cifti('results/fc_map_gs_supramarginal.dtseries.nii'))
  1863. zero_mask = np.std(cifti_maps_real, axis=0) > 0
  1864. cifti_maps_real = cifti_maps_real[:, zero_mask].copy()
  1865. cifti_maps_imag = cifti_maps_imag[:, zero_mask].copy()
  1866. cifti_eigenmap_p90 = cifti_eigenmap_p90[0, zero_mask].copy()
  1867. cifti_maps_pseed = cifti_maps_pseed[:, zero_mask].copy()
  1868. cifti_maps_pseed_gs = cifti_maps_pseed_gs[:, zero_mask].copy()
  1869. cifti_maps_smseed = cifti_maps_smseed[:, zero_mask].copy()
  1870. cifti_maps_smseed_gs = cifti_maps_smseed_gs[:, zero_mask].copy()
  1871. cifti_maps_spseed = cifti_maps_spseed[:, zero_mask].copy()
  1872. cifti_maps_spseed_gs = cifti_maps_spseed_gs[:, zero_mask].copy()

figures.ipynb at commit 96e91dd, no license · at the source

Overview

Authors: Shuo Lv1, Jinlong Li1, Ruoqi Yang1, Xinyu Wu1, Zhiming Wang1, Wenjing Zhu1, Tan Gao1, Jia-Hong Gao2, Guoyuan Yang1
  1. School of Interdisciplinary Science, Beijing Institute of Technology, Beijing, China
  2. McGovern Institute for Brain Research, Peking University, Beijing, China
Institutions: Beijing Institute of Technology (China); Peking University (China)
Journal: Nature communications, volume 17, issue 1, article 6232
Dates: received 7 September 2025; accepted 24 April 2026; published online 8 May 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1038/s41467-026-72931-6 · PMID 42103777 · PMCID PMC13369909 · OpenAlex W7160623821
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: fMRI (modality), human (organism), cognitive (subfield)
Methods: Spectral & time-frequency, Connectivity, Statistics, Smoothing, state filtering, decompositions, Machine learning, Preprocessing, Graphs, fMRI & imaging
Keywords: Dynamical systems, Cognitive neuroscience
MeSH: Cerebellum*, Adult, Connectome, Female, Humans, Magnetic Resonance Imaging, Male, Nerve Net, Neural Pathways, Principal Component Analysis (* major topic)
Topic: Vestibular and auditory disorders (Neurology, Neuroscience), according to OpenAlex
Funding: National Natural Science Foundation of China (National Science Foundation of China) (82302175, 62336002)
Citations: not cited yet (Europe PMC); 120 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repositories

Its files are read in the Code ↔ Paper reader above, with 18 matches between paragraphs and lines of code.

RaichleLab/lag-code

License: other
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: e8b6e17d42d1808476bdcdc81230291b698a4f6c, 6 November 2024
Languages: MATLAB (8)
Size: 10 files, 8 scripts
Software Heritage: not archived
Found in: the text, “Lag projection”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
10 files

BIT-YangLab/CPCA_Cerebellum

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: db528107547c07f91093022b5b46532c4a7b2d09, 2 April 2026
Languages: Python (35), Shell (1)
Size: 446 files, 36 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (34 files), SciPy (31 files), NiBabel (25 files), Matplotlib (20 files), pandas (7 files), scikit-learn (7 files), BrainSpace (2 files), seaborn (2 files), statsmodels (2 files), PyTorch (1 file), Connectome Workbench (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
37 files

tsb46/BOLD_WAVES

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 96e91ddccdacee4f6a0faa623d28528937fc9313, 5 September 2023
Languages: Python (17), Shell (9), Jupyter (2), R (1)
Size: 202 files, 29 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, environment (requirements.txt, requirements_notebook.txt), 2 notebooks
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (19 files), SciPy (16 files), scikit-learn (9 files), NiBabel (5 files), Connectome Workbench (5 files), Matplotlib (3 files), pandas (2 files), Brain Connectivity Toolbox (1 file), BrainSpace (1 file), Nilearn (1 file), statsmodels (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
30 files

Code availability statement

The paper has a code availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1038/s41467-026-72931-6.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 3 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 73 scripts, each with its path and the digest of its content;
  • 18 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Data availability statement

The paper has a data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1038/s41467-026-72931-6.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 28 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 9 authors, 2 keywords, 10 MeSH terms, 1 funder, 118 references.

Cite

This paper

Lv, S., Li, J., Yang, R., Wu, X., Wang, Z., Zhu, W., Gao, T., Gao, J.-H., & Yang, G. (2026). Three parsimonious spatiotemporal patterns in cerebellum reveal individual traits in function and behavior. Nature communications, 17(1), 6232. https://doi.org/10.1038/s41467-026-72931-6

BibTeX

@article{lv2026three,
author = {Lv, Shuo and Li, Jinlong and Yang, Ruoqi and Wu, Xinyu and Wang, Zhiming and Zhu, Wenjing and Gao, Tan and Gao, Jia-Hong and Yang, Guoyuan},
title = {{Three parsimonious spatiotemporal patterns in cerebellum reveal individual traits in function and behavior}},
journal = {Nature communications},
year = {2026},
month = may,
volume = {17},
number = {1},
pages = {6232},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-72931-6},
url = {https://doi.org/10.1038/s41467-026-72931-6},
pmid = {42103777},
pmcid = {PMC13369909}
}

RIS

TY - JOUR
AU - Lv, Shuo
AU - Li, Jinlong
AU - Yang, Ruoqi
AU - Wu, Xinyu
AU - Wang, Zhiming
AU - Zhu, Wenjing
AU - Gao, Tan
AU - Gao, Jia-Hong
AU - Yang, Guoyuan
TI - Three parsimonious spatiotemporal patterns in cerebellum reveal individual traits in function and behavior
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/05/08
VL - 17
IS - 1
SP - 6232
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-72931-6
UR - https://doi.org/10.1038/s41467-026-72931-6
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-72931-6",
"type": "article-journal",
"title": "Three parsimonious spatiotemporal patterns in cerebellum reveal individual traits in function and behavior",
"container-title": "Nature communications",
"author": [
{
"family": "Lv",
"given": "Shuo"
},
{
"family": "Li",
"given": "Jinlong"
},
{
"family": "Yang",
"given": "Ruoqi"
},
{
"family": "Wu",
"given": "Xinyu"
},
{
"family": "Wang",
"given": "Zhiming"
},
{
"family": "Zhu",
"given": "Wenjing"
},
{
"family": "Gao",
"given": "Tan"
},
{
"family": "Gao",
"given": "Jia-Hong"
},
{
"family": "Yang",
"given": "Guoyuan"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "6232",
"DOI": "10.1038/s41467-026-72931-6",
"PMID": "42103777",
"PMCID": "PMC13369909",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-72931-6",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
8
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s41467-026-76011-7 [code]
Human cortex organizes dynamic co-fluctuations along the sensorimotor-association axis.
Journal: Nature communications
In common: BrainSpace, Brain Connectivity Toolbox, Connectome Workbench, 7 other tools, 13 references
[2] doi:10.21203/rs.3.rs-9326213/v1 [code]
Multi-task fMRI outperforms resting-state fMRI for revealing task-invariant organization of the human brain
Journal: Research Square (preprint)
In common: Connectome Workbench, Nilearn, NiBabel, 7 other tools, humanconnectome.org/study/hcp-young-adult, fMRI, cognitive, 11 references
[3] doi:10.64898/2026.03.09.710558 [code]
Multi-task fMRI outperforms resting-state fMRI for revealing task-invariant organization of the human brain
Journal: bioRxiv (preprint)
In common: Connectome Workbench, Nilearn, NiBabel, 7 other tools, humanconnectome.org/study/hcp-young-adult, fMRI, cognitive, 11 references
[4] doi:10.1038/s41467-026-72940-5 [code]
Cerebellar growth is associated with domain-specific cerebral maturation and socio-linguistic behavior.
Journal: Nature communications
In common: Connectome Workbench, NiBabel, statsmodels, 6 other tools, 13 references
[5] doi:10.1002/hbm.70483 [code]
Untamed: Unconstrained Tensor Decomposition and Graph Node Embedding for Cortical Parcellation.
Journal: Human brain mapping
In common: Connectome Workbench, Nilearn, Signal Processing Toolbox, 8 other tools, humanconnectome.org/study/hcp-young-adult, fMRI, 4 references
[6] doi:10.1038/s41467-026-71270-w [code]
Spatiotemporal dynamics of the human cortical functional hierarchy across the lifespan.
Journal: Nature communications
In common: BrainSpace, Connectome Workbench, Nilearn, 8 other tools, fMRI, 3 references, author Jia-Hong Gao
[7] doi:10.1016/j.isci.2026.116903 [code]
Neurobiological and behavioral relevance of intrinsic functional connectome constraints on task-evoked neural activation.
Journal: iScience
In common: Connectome Workbench, Nilearn, NiBabel, 6 other tools, humanconnectome.org/study/hcp-young-adult, fMRI, cognitive, 4 references
[8] doi:10.1038/s41467-026-71151-2 [code]
Common and distinct neural correlates of social interaction processing and theory of mind in narratives.
Journal: Nature communications
In common: Brain Connectivity Toolbox, Connectome Workbench, Nilearn, 10 other tools, cognitive, 3 references
[9] doi:10.1038/s41398-026-04025-2 [code]
Brain energetic landscapes shape state dysregulation in major depressive disorder: a morphological network controllability perspective.
Journal: Translational psychiatry
In common: BrainSpace, Brain Connectivity Toolbox, Connectome Workbench, 10 other tools, 3 references
[10] doi:10.1038/s41467-026-75959-w [code]
Charting higher-order models of brain function beyond pairwise interactions.
Journal: Nature communications
In common: BrainSpace, Nilearn, NiBabel, 8 other tools, 6 references

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.