OSCR

Cortical and white matter myelination proceed in concert during early infancy.

Code ↔ Paper

11 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 11 matches · 2 of them tie a paragraph to a whole file, not to given lines: weak matches, whose lines are not tinted
  1. [1] § Methods › Bundle identification with pyBabyAFQ ↔ pyafq_pipeline/pyafq_pipeline_dHCP.ipynb, the whole file · a weak match · score 0.74 · babyAFQ, waypoints, template, AWS, MRtrix, space
  2. [2] § Methods › Assessing myelin-sensitive imaging metrics across tissues ↔ mrtrix_pipeline/mrtrix_pipeline_dHCP.ipynb, lines 345–389 · score 0.73 · draw EM, FreeSurfer, definition, boundaries, MRtrix, segmentations
  3. [3] § Methods › Tractography ↔ mrtrix_pipeline/mrtrix_pipeline_dHCP.ipynb, lines 442–504 · score 0.65 · IFOD1, Seeds, angle, dHCP, MRtrix, CSD
  4. [4] § Methods › Statistical analyses ↔ Figure4.ipynb, lines 217–240 · score 0.61 · motor scores, birth age, ANOVA, scan age, sex, model
  5. [5] § Results › Coupled myelin development of bundles and their cortical targets ↔ Figure3.ipynb, lines 409–543 · score 0.59 · FcMa, FcMi, regression, hues, IFOF, MLF
  6. [6] § Results › Reduced T1w/T2w coupling in preterm infants ↔ Figure5.ipynb, lines 154–192 · score 0.55 · FcMa, FcMi, IFOF, MLF, AF, UNC
  7. [7] § Methods › Tractography ↔ pyafq_pipeline/pyafq_pipeline_dHCP.ipynb, the whole file · a weak match · score 0.55 · tracking, MRtrix, CSD, tractography, segmentation, streamlines
  8. [8] § Results › Coupled myelin content of bundles and their cortical targets ↔ Figure3.ipynb, lines 409–543 · score 0.54 · FcMa, FcMi, regression, IFOF, MLF, AF
  9. [9] § Methods › Bundle identification with pyBabyAFQ ↔ EndpointsToCortex.ipynb, lines 632–719 · score 0.53 · Python, ROIs, frontal, cingulate, occipital, inferior
  10. [10] § Results › Coupled myelin content of bundles and their cortical targets ↔ Figure2.ipynb, lines 21–103 · score 0.51 · FcMa, FcMi, IFOF, MLF, AF, UNC
  11. [11] § Methods › Statistical analyses ↔ Figure3.ipynb, lines 213–309 · score 0.50 · birth age, scan age, variables, predicting, model, linear

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 · 1,128 lines · 39 KB · no license · 3 matches

  1. # %% [markdown]
  2. # ## Set up environment
  3. # %%
  4. # Set up environment
  5. import warnings;
  6. warnings.filterwarnings('ignore');
  7. from IPython.core.interactiveshell import InteractiveShell;
  8. InteractiveShell.ast_node_interactivity = "all";
  9. import pandas as pd
  10. import os
  11. import numpy as np
  12. import seaborn as sns
  13. import matplotlib.pyplot as plt
  14. import scipy
  15. from scipy import stats
  16. from matplotlib.lines import Line2D;
  17. from sklearn import preprocessing
  18. import statsmodels.formula.api as smf
  19. from utils import get_data
  20. # %%
  21. #plotting parameters, these will be the same for all plots
  22. bundles = ['AFL', 'AFR', 'ATRL', 'ATRR', 'CCL', 'CCR', 'CSL', 'CSR', 'FcMi', 'FcMa', 'IFOFL', 'IFOFR', 'ILFL', 'ILFR',
  23. 'MLFL', 'MLFR', 'ORL', 'ORR', 'SLFL', 'SLFR', 'UNCL', 'UNCR', 'VOFL', 'VOFR', 'pAFL', 'pAFR']
  24. rightBundles = ['AFR', 'ATRR', 'CCR', 'CSR', 'FcMi', 'FcMa', 'IFOFR', 'ILFR', 'MLFR', 'ORR', 'pAFR', 'SLFR', 'UNCR', 'VOFR']
  25. leftBundles = ['AFL', 'ATRL', 'CCL', 'CSL', 'FcMi', 'FcMa', 'IFOFL', 'ILFL', 'MLFL', 'ORL', 'pAFL', 'SLFL', 'UNCL', 'VOFL']
  26. tractPosHorz = {'AF': (0,0), 'ATR': (0, 1), 'CC': (0, 2), 'CS': (0, 3),
  27. 'IFOF':(1,0), 'ILF':(1,1), 'MLF':(1,2), 'OR': (1,3),
  28. 'SLF': (2, 0), 'UNC':(2,1), 'VOF': (2, 2), 'pAF': (2,3),
  29. 'FcMi': (3, 1), 'FcMa': (3, 2)};
  30. tracts=['AF', 'ATR', 'CC', 'CS', 'IFOF', 'ILF', 'MLF', 'OR', 'SLF', 'UNC', 'VOF', 'pAF', 'FcMi', 'FcMa']
  31. colors=['cyan', 'blue', 'green', 'orange', 'purple', 'brown', 'olive', 'coral', 'fuchsia', 'yellow', 'indigo', 'violet', 'salmon', 'red']
  32. color_list_all=sns.color_palette("tab20")+sns.color_palette("tab20b")
  33. color_list_complete=color_list_all
  34. color_list_chosen=color_list_all[18:20]
  35. color_list_chosen.extend(color_list_all[0:2])
  36. color_list_chosen.extend(color_list_all[4:6])
  37. color_list_chosen.extend(color_list_all[2:4])
  38. color_list_chosen.extend(color_list_all[7:8])
  39. color_list_chosen.extend(color_list_all[6:7])
  40. color_list_chosen.extend(color_list_all[8:10])
  41. color_list_chosen.extend(color_list_all[10:12])
  42. color_list_chosen.extend(color_list_all[24:25])
  43. color_list_chosen.extend(color_list_all[27:28])
  44. color_list_chosen.extend(color_list_all[34:35])
  45. color_list_chosen.extend(color_list_all[35:36])
  46. color_list_chosen.extend(color_list_all[12:14])
  47. color_list_chosen.extend(color_list_all[16:18])
  48. color_list_chosen.extend(color_list_all[22:23])
  49. color_list_chosen.extend(color_list_all[23:24])
  50. color_list_chosen.extend(color_list_all[37:38])
  51. color_list_chosen.extend(color_list_all[39:40])
  52. sns.palplot(color_list_all)
  53. sns.palplot(color_list_chosen)
  54. color_list_all=color_list_chosen
  55. color_order=[19, 18, 1, 0, 5, 4, 3, 2, 7, 6, 9, 8, 11, 10, 15, 14, 21, 20, 25, 24, 13, 12, 17, 16, 23, 22]
  56. color_list_nohemis=sns.color_palette("tab20")+sns.color_palette("tab20b")
  57. color_list_complete_nohemis=color_list_nohemis
  58. color_list_chosen_nohemis=color_list_nohemis[18:19]
  59. color_list_chosen_nohemis.extend(color_list_nohemis[0:1])
  60. color_list_chosen_nohemis.extend(color_list_nohemis[4:5])
  61. color_list_chosen_nohemis.extend(color_list_nohemis[2:3])
  62. color_list_chosen_nohemis.extend(color_list_nohemis[8:9])
  63. color_list_chosen_nohemis.extend(color_list_nohemis[10:11])
  64. color_list_chosen_nohemis.extend(color_list_nohemis[24:25])
  65. color_list_chosen_nohemis.extend(color_list_nohemis[34:35])
  66. color_list_chosen_nohemis.extend(color_list_nohemis[12:13])
  67. color_list_chosen_nohemis.extend(color_list_nohemis[16:17])
  68. color_list_chosen_nohemis.extend(color_list_nohemis[22:23])
  69. color_list_chosen_nohemis.extend(color_list_nohemis[37:38])
  70. color_list_chosen_nohemis.extend(color_list_nohemis[7:8])
  71. color_list_chosen_nohemis.extend(color_list_nohemis[6:7])
  72. sns.palplot(color_list_chosen_nohemis)
  73. color_order2=[19, 18, 5, 4, 9, 8, 11, 10, 15, 14, 21, 20, 25, 24, 13, 12, 17, 16, 23, 22, 7, 6]
  74. color_list_3=sns.color_palette("tab20")+sns.color_palette("tab20b")
  75. color_list_complete3=color_list_3
  76. color_list_chosen3=color_list_3[18:19]
  77. color_list_chosen3.extend(color_list_3[0:1])
  78. color_list_chosen3.extend(color_list_3[4:5])
  79. color_list_chosen3.extend(color_list_3[2:3])
  80. color_list_chosen3.extend(color_list_3[8:9])
  81. color_list_chosen3.extend(color_list_3[10:11])
  82. color_list_chosen3.extend(color_list_3[24:25])
  83. color_list_chosen3.extend(color_list_3[34:35])
  84. color_list_chosen3.extend(color_list_3[12:13])
  85. color_list_chosen3.extend(color_list_3[16:17])
  86. color_list_chosen3.extend(color_list_3[22:23])
  87. color_list_chosen3.extend(color_list_3[37:38])
  88. color_list_chosen3.extend(color_list_3[7:8])
  89. color_list_chosen3.extend(color_list_3[6:7])
  90. sns.palplot(color_list_chosen3)
  91. color_order3=[19, 1, 5, 3, 9, 11, 25, 35, 13, 17, 23, 38, 8, 7]
  92. # %%
  93. get_data()
  94. # %%
  95. slope_analyses_mean=pd.read_csv('./inputData/GMandWMT1wT2wSubjectsAge.csv', index_col=None)
  96. SlopeDataframe=pd.read_csv('./inputData/R1Slope.csv', index_col=None)
  97. # %% [markdown]
  98. # ## T1w/T2w
  99. # %%
  100. #Compute slope for scan age for T1w/T2w of white matter
  101. tractCount=slope_analyses_mean['tractID'].unique()
  102. coeff=[] #Creating arrays
  103. se = []
  104. coeffType=[]
  105. tractTrack1=[]
  106. intercept=[]
  107. r2 = []
  108. adjustR2 = []
  109. aic = []
  110. pvals=[]
  111. for tract in tractCount:
  112. dfForStats=slope_analyses_mean[(slope_analyses_mean['tractID']==tract)]
  113. dfForStats.reset_index(level=0, inplace=True)
  114. scanAgeArray=np.array(dfForStats['scan_age'])
  115. scanAgeArray=scanAgeArray.reshape(1,-1)
  116. scanAgeNorm=preprocessing.normalize(scanAgeArray,axis=1)
  117. scanAgeNorm=scanAgeNorm.reshape(-1)
  118. dfForStats["scanAgeNorm"]=scanAgeNorm
  119. birthAgeArray=np.array(dfForStats['birth_age'])
  120. birthAgeArray=birthAgeArray.reshape(1,-1)
  121. birthAgeNorm=preprocessing.normalize(birthAgeArray,axis=1)
  122. birthAgeNorm=birthAgeNorm.reshape(-1)
  123. dfForStats["birthAgeNorm"]=birthAgeNorm
  124. t1wt2wArray=np.array(dfForStats['t1wt2w'])
  125. t1wt2wArray=t1wt2wArray.reshape(1,-1)
  126. t1wt2wNorm=preprocessing.normalize(t1wt2wArray,axis=1)
  127. t1wt2wNorm=t1wt2wNorm.reshape(-1)
  128. dfForStats["t1wt2wNorm"]=t1wt2wNorm
  129. df = dfForStats
  130. md = smf.ols(formula="t1wt2w~ 1 + scan_age", data=df) #Predicting dti_mdNorm through the other variables
  131. mdf = md.fit() # Actually commanding the linear analysis, it could be any other analysis method
  132. results=mdf.summary()
  133. # Note that tables is a list. The table at index 1 is the "core" table. Additionally, read_html puts dfs in a list, so we want index 0
  134. results_as_html = results.tables[1].as_html()
  135. LM_results=pd.read_html(results_as_html, header=0, index_col=0)[0]
  136. results_as_html_R2 = results.tables[0].as_html()
  137. LM_results_R2=pd.read_html(results_as_html_R2, index_col=0)[0]
  138. results_R2s=LM_results_R2[3]
  139. #LM_results.head()
  140. #LM_results_R2.head()
  141. #results_R2s.head()
  142. SE=LM_results['std err']
  143. inter=mdf.params['Intercept']
  144. intercept.append(inter)
  145. #SE.head()
  146. se.append(SE["scan_age"])
  147. coeff.append(mdf.params['scan_age'])
  148. coeff_type=3
  149. tractTrack1.append(tract)
  150. #nodeTrack1.append(node)
  151. coeffType.append(coeff_type) #Add another line below
  152. r2.append(results_R2s['Dep. Variable:'])
  153. adjustR2.append(results_R2s['Model:'])
  154. aic.append(results_R2s['No. Observations:'])
  155. pvals.append(results_R2s['Date:'])
  156. print(mdf.summary())
  157. df=pd.DataFrame(tractTrack1)
  158. df.columns=['tractID']
  159. df.insert(1,"coeffType",coeffType)
  160. df.insert(2,"coeff",coeff)
  161. df.insert(3,"se",se)
  162. df.insert(4,"intercept",intercept)
  163. df.insert(5,"r2",r2)
  164. df.insert(6,"adjustR2",adjustR2)
  165. df.insert(7,"aic",aic)
  166. df.insert(8,"pvals",pvals)
  167. os.makedirs('./outputData',exist_ok=True)
  168. df.to_csv('./outputData/LM_result_scanAge_noNorm_WM_AverageSubj.csv')
  169. df.head()
  170. df.min(axis=0, numeric_only=True)
  171. df.max(axis=0, numeric_only=True)
  172. df.mean(axis=0, numeric_only=True)
  173. dfScanAge=df
  174. df.head()
  175. statsPerBundle=df.groupby('tractID').mean()
  176. statsPerBundle.head()
  177. statsPerBundle.to_csv('./outputData/LM_result_scanAge_noNorm_meanPerBundle_WM_AverageSubj.csv')
  178. # %%
  179. #Compute slope for scan age for T1w/T2w gray matter
  180. tractCount=slope_analyses_mean['tractID'].unique()
  181. coeff=[] #Creating arrays
  182. se = []
  183. coeffType=[]
  184. tractTrack1=[]
  185. intercept=[]
  186. r2 = []
  187. adjustR2 = []
  188. aic = []
  189. pvals=[]
  190. for tract in tractCount:
  191. dfForStats=slope_analyses_mean[(slope_analyses_mean['tractID']==tract)]
  192. dfForStats.reset_index(level=0, inplace=True)
  193. scanAgeArray=np.array(dfForStats['scan_age'])
  194. scanAgeArray=scanAgeArray.reshape(1,-1)
  195. scanAgeNorm=preprocessing.normalize(scanAgeArray,axis=1)
  196. scanAgeNorm=scanAgeNorm.reshape(-1)
  197. dfForStats["scanAgeNorm"]=scanAgeNorm
  198. birthAgeArray=np.array(dfForStats['birth_age'])
  199. birthAgeArray=birthAgeArray.reshape(1,-1)
  200. birthAgeNorm=preprocessing.normalize(birthAgeArray,axis=1)
  201. birthAgeNorm=birthAgeNorm.reshape(-1)
  202. dfForStats["birthAgeNorm"]=birthAgeNorm
  203. t1wt2wArray=np.array(dfForStats['WeAvGMT1wT2w'])
  204. t1wt2wArray=t1wt2wArray.reshape(1,-1)
  205. t1wt2wNorm=preprocessing.normalize(t1wt2wArray,axis=1)
  206. t1wt2wNorm=t1wt2wNorm.reshape(-1)
  207. dfForStats["t1wt2wNorm"]=t1wt2wNorm
  208. #dfForStats.head()
  209. #dfForStats.to_csv('./'+tract+'_dataCleanedForLME.csv')
  210. df = dfForStats
  211. md = smf.ols(formula="WeAvGMT1wT2w~ 1 + scan_age", data=df) #Predicting dti_mdNorm through the other variables
  212. mdf = md.fit() # Actually commanding the linear analysis, it could be any other analysis method
  213. results=mdf.summary()
  214. # Note that tables is a list. The table at index 1 is the "core" table. Additionally, read_html puts dfs in a list, so we want index 0
  215. results_as_html = results.tables[1].as_html()
  216. LM_results=pd.read_html(results_as_html, header=0, index_col=0)[0]
  217. results_as_html_R2 = results.tables[0].as_html()
  218. LM_results_R2=pd.read_html(results_as_html_R2, index_col=0)[0]
  219. results_R2s=LM_results_R2[3]
  220. #LM_results.head()
  221. #LM_results_R2.head()
  222. #results_R2s.head()
  223. SE=LM_results['std err']
  224. inter=mdf.params['Intercept']
  225. intercept.append(inter)
  226. #SE.head()
  227. se.append(SE["scan_age"])
  228. coeff.append(mdf.params['scan_age'])
  229. coeff_type=3
  230. tractTrack1.append(tract)
  231. #nodeTrack1.append(node)
  232. coeffType.append(coeff_type) #Add another line below
  233. r2.append(results_R2s['Dep. Variable:'])
  234. adjustR2.append(results_R2s['Model:'])
  235. aic.append(results_R2s['No. Observations:'])
  236. pvals.append(results_R2s['Date:'])
  237. print(mdf.summary())
  238. df=pd.DataFrame(tractTrack1)
  239. df.columns=['tractID']
  240. df.insert(1,"coeffType",coeffType)
  241. df.insert(2,"coeff_GM",coeff)
  242. df.insert(3,"se",se)
  243. df.insert(4,"intercept",intercept)
  244. df.insert(5,"r2",r2)
  245. df.insert(6,"adjustR2",adjustR2)
  246. df.insert(7,"aic",aic)
  247. df.insert(8,"pvals",pvals)
  248. df.to_csv('./outputData/LM_result_scanAge_noNorm_GM_AverageSubj.csv')
  249. df.head()
  250. df.min(axis=0, numeric_only=True)
  251. df.max(axis=0, numeric_only=True)
  252. df.mean(axis=0, numeric_only=True)
  253. dfScanAge=df
  254. df.head()
  255. statsPerBundle=df.groupby('tractID').mean()
  256. statsPerBundle.head()
  257. statsPerBundle.to_csv('./outputData/LM_result_scanAge_noNorm_meanPerBundle_GM_AverageSubj.csv')
  258. # %%
  259. #Merge statistic dataframes of gray and white matter
  260. SlopeWM = pd.read_csv('./outputData/LM_result_scanAge_noNorm_WM_AverageSubj.csv', index_col=0)
  261. SlopeGM = pd.read_csv('./outputData/LM_result_scanAge_noNorm_GM_AverageSubj.csv', index_col=0)
  262. SlopeBoth = pd.merge(SlopeWM, SlopeGM[['tractID', 'coeff_GM', 'r2', 'aic']], on='tractID')
  263. SlopeBoth=SlopeBoth.set_index(['tractID']).reindex(['AFL', 'AFR', 'ATRL', 'ATRR', 'CCL', 'CCR', 'CSL', 'CSR', 'FcMi', 'FcMa', 'IFOFL', 'IFOFR', 'ILFL', 'ILFR', 'MLFL', 'MLFR', 'ORL', 'ORR', 'SLFL', 'SLFR', 'UNCL', 'UNCR', 'VOFL', 'VOFR', 'pAFL', 'pAFR']).reset_index()
  264. SlopeBoth
  265. SlopeBoth.to_csv('./outputData/SlopeT1wT2w.csv')
  266. # %%
  267. # Paired t-test between white matter and gray matter coefficients
  268. t_stat, p_val = stats.ttest_rel(SlopeBoth['coeff'], SlopeBoth['coeff_GM'])
  269. print(f"CoeffWM vs. CoeffGM t = {t_stat:.4f}, p = {p_val:.4f}")
  270. # Print mean values
  271. mean_wm = SlopeBoth['coeff'].mean()
  272. mean_gm = SlopeBoth['coeff_GM'].mean()
  273. print(f"Mean Coeff WM: {mean_wm:.4f}")
  274. print(f"Mean Coeff GM: {mean_gm:.4f}")
  275. # Calculate difference metrics
  276. SlopeBoth['diff_r2'] = SlopeBoth['r2_x'] - SlopeBoth['r2_y']
  277. SlopeBoth['diff_aic'] = SlopeBoth['aic_x'] - SlopeBoth['aic_y']
  278. # Print mean differences
  279. mean_diff_r2 = SlopeBoth['diff_r2'].mean()
  280. mean_diff_aic = SlopeBoth['diff_aic'].mean()
  281. print(f"Mean Δr²: {mean_diff_r2:.4f}")
  282. print(f"Mean ΔAIC: {mean_diff_aic:.4f}")
  283. # %%
  284. sns.set_style('white');
  285. fig1=sns.lmplot(
  286. data=SlopeBoth, x='coeff_GM', y="coeff", hue="tractID", height=10, scatter_kws={"s": 400}, fit_reg=False, legend=False, palette=color_list_chosen
  287. )
  288. fig1=sns.regplot(data=SlopeBoth, x='coeff_GM', y="coeff", scatter=False, ax=fig1.axes[0, 0], line_kws={"color": "darkgrey"})
  289. res=scipy.stats.pearsonr(SlopeBoth['coeff_GM'], SlopeBoth['coeff'])
  290. print(res)
  291. res.confidence_interval()
  292. #fig1.legend(title='Tract', fontsize='15', title_fontsize='20', bbox_to_anchor=(0.7, 0.25, 0.5, 0.5), loc='right', borderaxespad=0, frameon=FcMilse)
  293. plt.xlabel("T1w/T2w Slope in GM", fontsize=50)
  294. plt.ylabel("T1w/T2w Slope in WM", fontsize=50)
  295. plt.xticks([0.025, 0.035, 0.045], fontsize=45)
  296. plt.yticks(fontsize=45)
  297. plt.savefig('./figures/T1wT2w_SlopeGMvsSlopeWM.png', dpi=600, bbox_inches = "tight")
  298. # %%
  299. # Define bundles
  300. b=['AFL', 'AFR', 'ATRL', 'ATRR', 'CCL', 'CCR', 'CSL', 'CSR', 'FcMi', 'FcMa', 'IFOFL', 'IFOFR', 'ILFL', 'ILFR', 'MLFL', 'MLFR', 'ORL', 'ORR', 'SLFL', 'SLFR', 'UNCL', 'UNCR', 'VOFL', 'VOFR', 'pAFL', 'pAFR']
  301. results = []
  302. for bundle in b:
  303. bundle_df = slope_analyses_mean[slope_analyses_mean['tractID'] == bundle]
  304. if len(bundle_df) >= 2:
  305. # White matter
  306. r_wm, p_wm = stats.pearsonr(bundle_df['scan_age'], bundle_df['t1wt2w'])
  307. # Gray matter
  308. r_gm, p_gm = stats.pearsonr(bundle_df['scan_age'], bundle_df['WeAvGMT1wT2w'])
  309. results.append({
  310. 'Tract': bundle,
  311. 'r_WM': r_wm,
  312. 'p_WM': p_wm,
  313. 'r2_WM': r_wm**2,
  314. 'r_GM': r_gm,
  315. 'p_GM': p_gm,
  316. 'r2_GM': r_gm**2
  317. })
  318. # Convert to DataFrame
  319. df_tract_corrs = pd.DataFrame(results)
  320. # View or export
  321. df_tract_corrs
  322. df_tract_corrs.to_csv('./outputData/tract_r2_pvalues_wm_gm_bothemis.csv', index=False)
  323. r2_min_wm = df_tract_corrs['r2_WM'].min()
  324. r2_min_gm = df_tract_corrs['r2_GM'].min()
  325. r_min_wm = df_tract_corrs['r_WM'].min()
  326. r_min_gm = df_tract_corrs['r_GM'].min()
  327. p_max_wm = df_tract_corrs['p_WM'].max()
  328. p_max_gm = df_tract_corrs['p_GM'].max()
  329. print(f"Min r² WM: {r2_min_wm}")
  330. print(f"Min r² GM: {r2_min_gm}")
  331. print(f"Min r WM: {r_min_wm}")
  332. print(f"Min r GM: {r_min_gm}")
  333. print(f"Max p WM: {p_max_wm}")
  334. print(f"Max p GM: {p_max_gm}")
  335. # %%
  336. # --- PREPARE DATA -------------------------------------------------------------
  337. df = slope_analyses_mean.copy()
  338. # Extract Hemisphere from tract names (e.g. "AF_L" → "L")
  339. df["Hemisphere"] = df["tractID"].str.extract(r'(L|R)$')
  340. # Extract base bundle name (e.g. "AF_L" → "AF")
  341. df["BundleBase"] = df["tractID"].str.replace(r'(L|R)$', '', regex=True)
  342. # --- DEFINE LAYOUT AND STYLE --------------------------------------------------
  343. tractPosHorz = {
  344. 'AF': (0,0), 'ATR': (0,1), 'CC': (0,2), 'CS': (0,3),
  345. 'IFOF':(1,0), 'ILF':(1,1), 'MLF':(1,2), 'OR':(1,3),
  346. 'SLF':(2,0), 'UNC':(2,1), 'VOF':(2,2), 'pAF':(2,3),
  347. 'FcMi':(3,1), 'FcMa':(3,2)
  348. }
  349. tracts = ['AF', 'ATR', 'CC', 'CS', 'IFOF', 'ILF', 'MLF',
  350. 'OR', 'SLF', 'UNC', 'VOF', 'pAF', 'FcMi', 'FcMa']
  351. colors = ['cyan', 'blue', 'green', 'orange', 'purple', 'brown', 'olive',
  352. 'coral', 'fuchsia', 'yellow', 'indigo', 'violet', 'salmon', 'red']
  353. sns.set(font_scale=2)
  354. sns.set_style("white")
  355. fig, axes = plt.subplots(4, 4, figsize=(14,17), frameon=False)
  356. fig.subplots_adjust(wspace=0.1, hspace=0.9)
  357. import matplotlib.colors as mcolors
  358. def lighten_color(color, amount=0.5):
  359. """
  360. Lightens the given color by blending it with white.
  361. amount=0 → white, amount=1 → original color.
  362. """
  363. try:
  364. c = mcolors.cnames[color]
  365. except KeyError:
  366. c = color
  367. rgb = mcolors.to_rgb(c)
  368. return tuple(1 - (1 - x) * amount for x in rgb)
  369. # --- MAIN LOOP ---------------------------------------------------------------
  370. for ct, (bundle, color) in enumerate(zip(tracts, colors)):
  371. if bundle not in tractPosHorz:
  372. continue
  373. ax = axes[tractPosHorz[bundle]]
  374. # Filter data for the base bundle (both hemispheres)
  375. SlopeTract = df.query("BundleBase == @bundle")
  376. # Aggregate per subject, hemisphere, etc.
  377. SlopeTractGM = SlopeTract.groupby(
  378. ['subjectID', 'scan_age', 'BundleBase', 'Hemisphere'],
  379. as_index=False, sort=False
  380. )['WeAvGMT1wT2w'].mean()
  381. SlopeTractWM = SlopeTract.groupby(
  382. ['subjectID', 'scan_age', 'BundleBase', 'Hemisphere'],
  383. as_index=False, sort=False
  384. )['t1wt2w'].mean()
  385. # Make a lighter version of the current tract color for the right hemisphere
  386. light_color = lighten_color(color, amount=0.5)
  387. # Plot white matter (same hue, lighter for R)
  388. sns.scatterplot(
  389. data=SlopeTractWM, x='scan_age', y='t1wt2w', hue='Hemisphere', style='Hemisphere',
  390. markers={'L': 'o', 'R': 'o'}, s=50,
  391. palette={'L': color, 'R': light_color}, ax=ax,
  392. alpha=0.5, legend=False
  393. )
  394. # Regression lines per hemisphere
  395. for hemi, line_color in zip(['L', 'R'], [color, light_color]):
  396. sns.regplot(
  397. data=SlopeTractWM[SlopeTractWM['Hemisphere'] == hemi],
  398. x='scan_age', y='t1wt2w', scatter=False, ax=ax,
  399. line_kws={'color': line_color, 'alpha': 0.8}
  400. )
  401. # Plot gray matter (with slightly lighter colors)
  402. sns.scatterplot(
  403. data=SlopeTractGM, x='scan_age', y='WeAvGMT1wT2w', hue='Hemisphere', style='Hemisphere',
  404. markers={'L': 'o', 'R': 'o'}, s=50,
  405. palette={'L': 'darkgray', 'R': 'lightgray'}, ax=ax,
  406. alpha=0.5, legend=False
  407. )
  408. for hemi, line_color in zip(['L', 'R'], ['darkgray', 'lightgray']):
  409. sns.regplot(
  410. data=SlopeTractGM[SlopeTractGM['Hemisphere'] == hemi],
  411. x='scan_age', y='WeAvGMT1wT2w', scatter=False, ax=ax,
  412. line_kws={'color': line_color, 'alpha': 0.8}
  413. )
  414. # --- Titles & labels
  415. ax.set_title(bundle, pad=5, fontsize=25)
  416. ax.set_xlabel('Scan Age', labelpad=15, fontsize=20)
  417. if ct in [0, 4, 8, 12]: # first column
  418. _=ax.set_ylabel("T1w/T2w",fontsize=20);
  419. _=ax.set_xticks([30, 34, 38, 42]);
  420. #_=ax.set_xticklabels(fontsize=20);
  421. #_=ax.set_yticks([0.4, 0.45, 0.5]);
  422. #_=ax.set_yticklabels(fontsize=20)
  423. _=ax.spines['right'].set_visible(False);
  424. _=ax.spines['top'].set_visible(False);
  425. _=line1=Line2D([],[],color=color_list_all[ct],linestyle='dashed');
  426. _=line2=Line2D([],[],color=color_list_all[ct],linestyle='-');
  427. else:
  428. _=ax.set_ylabel('T1w/T2w', fontsize=14);
  429. _=ax.set_xticks([30, 34, 38, 42]);
  430. #_=ax.set_xticklabels(fontsize=20);
  431. #_=ax.set_yticks([0.4, 0.45, 0.5]);
  432. _=ax.yaxis.set_visible(False);
  433. _=ax.spines['right'].set_visible(False);
  434. _=ax.spines['left'].set_visible(False);
  435. _=ax.spines['top'].set_visible(False);
  436. # Clean frame
  437. ax.spines['right'].set_visible(False)
  438. ax.spines['top'].set_visible(False)
  439. # Turn off unused axes
  440. axes[3,3].axis("off")
  441. axes[3,0].axis("off")
  442. # --- SAVE & SHOW -------------------------------------------------------------
  443. fig.savefig('./figures/scanage_per_bundle_bothhemis.png', dpi=600)
  444. plt.show()
  445. # %%
  446. # --- PREPARE DATA -------------------------------------------------------------
  447. df = slope_analyses_mean.copy()
  448. # 1) Extract Hemisphere (only if tract ends with L or R)
  449. df["Hemisphere"] = df["tractID"].str.extract(r'(L|R)$')
  450. # 2) Extract BundleBase safely
  451. df["BundleBase"] = df["tractID"] # start with full name
  452. mask = df["tractID"].str.endswith(("L", "R")) # true hemisphere bundles
  453. df.loc[mask, "BundleBase"] = df.loc[mask, "tractID"].str.replace(r"(L|R)$", "", regex=True)
  454. # For FcMi & FcMa → both-hemisphere bundles → assign pseudo-hemi
  455. df.loc[df["BundleBase"].isin(["FcMi", "FcMa"]), "Hemisphere"] = "B"
  456. # --- DEFINE LAYOUT AND STYLE --------------------------------------------------
  457. tractPosHorz = {
  458. 'AF': (0,0), 'ATR': (0,1), 'CC': (0,2), 'CS': (0,3),
  459. 'IFOF': (1,0), 'ILF': (1,1), 'MLF': (1,2), 'OR': (1,3),
  460. 'SLF': (2,0), 'UNC': (2,1), 'VOF': (2,2), 'pAF': (2,3),
  461. 'FcMi': (3,1), 'FcMa': (3,2)
  462. }
  463. tracts = ['AF', 'ATR', 'CC', 'CS', 'IFOF', 'ILF', 'MLF',
  464. 'OR', 'SLF', 'UNC', 'VOF', 'pAF', 'FcMi', 'FcMa']
  465. colors = ['cyan', 'blue', 'green', 'orange', 'purple', 'brown', 'olive',
  466. 'coral', 'fuchsia', 'yellow', 'indigo', 'violet', 'salmon', 'red']
  467. sns.set(font_scale=2)
  468. sns.set_style("white")
  469. fig, axes = plt.subplots(4, 4, figsize=(14,17), frameon=False)
  470. fig.subplots_adjust(wspace=0.1, hspace=0.9)
  471. import matplotlib.colors as mcolors
  472. def lighten_color(color, amount=0.5):
  473. try:
  474. c = mcolors.cnames[color]
  475. except KeyError:
  476. c = color
  477. rgb = mcolors.to_rgb(c)
  478. return tuple(1 - (1 - x) * amount for x in rgb)
  479. # --- MAIN LOOP ---------------------------------------------------------------
  480. for ct, (bundle, color) in enumerate(zip(tracts, colors)):
  481. if bundle not in tractPosHorz:
  482. continue
  483. ax = axes[tractPosHorz[bundle]]
  484. # Filter data for the base bundle (both hemispheres)
  485. SlopeTract = df.query("BundleBase == @bundle")
  486. # Aggregate per subject & hemisphere
  487. SlopeTractGM = SlopeTract.groupby(
  488. ['subjectID', 'scan_age', 'BundleBase', 'Hemisphere'],
  489. as_index=False, sort=False
  490. )['WeAvGMT1wT2w'].mean()
  491. SlopeTractWM = SlopeTract.groupby(
  492. ['subjectID', 'scan_age', 'BundleBase', 'Hemisphere'],
  493. as_index=False, sort=False
  494. )['t1wt2w'].mean()
  495. # Special case: FcMi & FcMa (ONLY ONE "hemisphere")
  496. if bundle in ["FcMi", "FcMa"]:
  497. sns.scatterplot(
  498. data=SlopeTractWM, x='scan_age', y='t1wt2w',
  499. color=color, s=60, alpha=0.5, ax=ax
  500. )
  501. sns.regplot(
  502. data=SlopeTractWM,
  503. x='scan_age', y='t1wt2w', scatter=False, ax=ax,
  504. line_kws={'color': color, 'alpha': 0.9}
  505. )
  506. sns.scatterplot(
  507. data=SlopeTractGM, x='scan_age', y='WeAvGMT1wT2w',
  508. color='gray', s=60, alpha=0.5, ax=ax
  509. )
  510. sns.regplot(
  511. data=SlopeTractGM,
  512. x='scan_age', y='WeAvGMT1wT2w', scatter=False, ax=ax,
  513. line_kws={'color': 'gray', 'alpha': 0.9}
  514. )
  515. else:
  516. # Normal L/R hemisphere bundles --------------------------------------
  517. light_color = lighten_color(color, amount=0.5)
  518. # WM scatter
  519. sns.scatterplot(
  520. data=SlopeTractWM, x='scan_age', y='t1wt2w',
  521. hue='Hemisphere', style='Hemisphere',
  522. markers={'L': 'o', 'R': 'o'}, s=50,
  523. palette={'L': color, 'R': light_color}, ax=ax,
  524. alpha=0.5, legend=False
  525. )
  526. # WM regression lines
  527. for hemi, line_color in zip(['L', 'R'], [color, light_color]):
  528. sns.regplot(
  529. data=SlopeTractWM[SlopeTractWM['Hemisphere'] == hemi],
  530. x='scan_age', y='t1wt2w', scatter=False, ax=ax,
  531. line_kws={'color': line_color, 'alpha': 0.8}
  532. )
  533. # GM scatter
  534. sns.scatterplot(
  535. data=SlopeTractGM, x='scan_age', y='WeAvGMT1wT2w',
  536. hue='Hemisphere', style='Hemisphere',
  537. markers={'L': 'o', 'R': 'o'}, s=50,
  538. palette={'L': 'darkgray', 'R': 'lightgray'}, ax=ax,
  539. alpha=0.5, legend=False
  540. )
  541. # GM regression
  542. for hemi, line_color in zip(['L', 'R'], ['darkgray', 'lightgray']):
  543. sns.regplot(
  544. data=SlopeTractGM[SlopeTractGM['Hemisphere'] == hemi],
  545. x='scan_age', y='WeAvGMT1wT2w', scatter=False, ax=ax,
  546. line_kws={'color': line_color, 'alpha': 0.8}
  547. )
  548. # --- Titles & labels -----------------------------------------------------
  549. ax.set_title(bundle, pad=5, fontsize=25)
  550. ax.set_xlabel('Scan Age', labelpad=15, fontsize=20)
  551. if ct in [0, 4, 8, 12]: # first column
  552. ax.set_ylabel("T1w/T2w", fontsize=20)
  553. ax.set_xticks([30, 34, 38, 42])
  554. ax.spines['right'].set_visible(False)
  555. ax.spines['top'].set_visible(False)
  556. else:
  557. ax.set_ylabel('T1w/T2w', fontsize=14)
  558. ax.set_xticks([30, 34, 38, 42])
  559. ax.yaxis.set_visible(False)
  560. ax.spines['right'].set_visible(False)
  561. ax.spines['left'].set_visible(False)
  562. ax.spines['top'].set_visible(False)
  563. ax.spines['right'].set_visible(False)
  564. ax.spines['top'].set_visible(False)
  565. # Turn off unused axes
  566. axes[3,3].axis("off")
  567. axes[3,0].axis("off")
  568. # --- SAVE & SHOW -------------------------------------------------------------
  569. fig.savefig('./figures/scanage_per_bundle_bothhemis.png', dpi=600)
  570. plt.show()
  571. # %% [markdown]
  572. # ## R1
  573. # %%
  574. #Compute slope for scan age for R1 of white matter
  575. tractCount=SlopeDataframe['tractID'].unique()
  576. coeff=[] #Creating arrays
  577. se = []
  578. coeffType=[]
  579. tractTrack1=[]
  580. intercept=[]
  581. r2 = []
  582. adjustR2 = []
  583. aic = []
  584. pvals=[]
  585. for tract in tractCount:
  586. dfForStats=SlopeDataframe[(SlopeDataframe['tractID']==tract)]
  587. dfForStats.reset_index(level=0, inplace=True)
  588. scanAgeArray=np.array(dfForStats['age'])
  589. scanAgeArray=scanAgeArray.reshape(1,-1)
  590. scanAgeNorm=preprocessing.normalize(scanAgeArray,axis=1)
  591. scanAgeNorm=scanAgeNorm.reshape(-1)
  592. dfForStats["scanAgeNorm"]=scanAgeNorm
  593. R1Array=np.array(dfForStats['R1'])
  594. R1Array=R1Array.reshape(1,-1)
  595. R1Norm=preprocessing.normalize(R1Array,axis=1)
  596. R1Norm=R1Norm.reshape(-1)
  597. dfForStats["R1Norm"]=R1Norm
  598. df = dfForStats
  599. md = smf.ols(formula="R1~ 1 + age", data=df) #Predicting dti_mdNorm through the other variables
  600. mdf = md.fit() # Actually commanding the linear analysis, it could be any other analysis method
  601. results=mdf.summary()
  602. # Note that tables is a list. The table at index 1 is the "core" table. Additionally, read_html puts dfs in a list, so we want index 0
  603. results_as_html = results.tables[1].as_html()
  604. LM_results=pd.read_html(results_as_html, header=0, index_col=0)[0]
  605. results_as_html_R2 = results.tables[0].as_html()
  606. LM_results_R2=pd.read_html(results_as_html_R2, index_col=0)[0]
  607. results_R2s=LM_results_R2[3]
  608. #LM_results.head()
  609. #LM_results_R2.head()
  610. #results_R2s.head()
  611. SE=LM_results['std err']
  612. inter=mdf.params['Intercept']
  613. intercept.append(inter)
  614. #SE.head()
  615. se.append(SE["age"])
  616. coeff.append(mdf.params['age'])
  617. coeff_type=3
  618. tractTrack1.append(tract)
  619. #nodeTrack1.append(node)
  620. coeffType.append(coeff_type) #Add another line below
  621. r2.append(results_R2s['Dep. Variable:'])
  622. adjustR2.append(results_R2s['Model:'])
  623. aic.append(results_R2s['No. Observations:'])
  624. pvals.append(results_R2s['Date:'])
  625. print(mdf.summary())
  626. df=pd.DataFrame(tractTrack1)
  627. df.columns=['tractID']
  628. df.insert(1,"coeffType",coeffType)
  629. df.insert(2,"coeff",coeff)
  630. df.insert(3,"se",se)
  631. df.insert(4,"intercept",intercept)
  632. df.insert(5,"r2",r2)
  633. df.insert(6,"adjustR2",adjustR2)
  634. df.insert(7,"aic",aic)
  635. df.insert(8,"pvals",pvals)
  636. os.makedirs('./outputData',exist_ok=True)
  637. df.to_csv('./outputData/LM_result_scanAge_noNorm_WM_AverageSubj_R1.csv')
  638. df.head()
  639. df.min(axis=0, numeric_only=True)
  640. df.max(axis=0, numeric_only=True)
  641. df.mean(axis=0, numeric_only=True)
  642. dfScanAge=df
  643. df.head()
  644. statsPerBundle=df.groupby('tractID').mean()
  645. statsPerBundle.head()
  646. statsPerBundle.to_csv('./outputData/LM_result_scanAge_noNorm_meanPerBundle_WM_AverageSubj_R1.csv')
  647. # %%
  648. #Compute slope for scan age for R1 of gray matter
  649. tractCount=SlopeDataframe['tractID'].unique()
  650. coeff=[] #Creating arrays
  651. se = []
  652. coeffType=[]
  653. tractTrack1=[]
  654. intercept=[]
  655. r2 = []
  656. adjustR2 = []
  657. aic = []
  658. pvals=[]
  659. for tract in tractCount:
  660. dfForStats=SlopeDataframe[(SlopeDataframe['tractID']==tract)]
  661. dfForStats.reset_index(level=0, inplace=True)
  662. scanAgeArray=np.array(dfForStats['age'])
  663. scanAgeArray=scanAgeArray.reshape(1,-1)
  664. scanAgeNorm=preprocessing.normalize(scanAgeArray,axis=1)
  665. scanAgeNorm=scanAgeNorm.reshape(-1)
  666. dfForStats["scanAgeNorm"]=scanAgeNorm
  667. R1Array=np.array(dfForStats['GM_R1'])
  668. R1Array=R1Array.reshape(1,-1)
  669. R1Norm=preprocessing.normalize(R1Array,axis=1)
  670. R1Norm=R1Norm.reshape(-1)
  671. dfForStats["R1Norm"]=R1Norm
  672. df = dfForStats
  673. md = smf.ols(formula="GM_R1~ 1 + age", data=df) #Predicting dti_mdNorm through the other variables
  674. mdf = md.fit() # Actually commanding the linear analysis, it could be any other analysis method
  675. results=mdf.summary()
  676. # Note that tables is a list. The table at index 1 is the "core" table. Additionally, read_html puts dfs in a list, so we want index 0
  677. results_as_html = results.tables[1].as_html()
  678. LM_results=pd.read_html(results_as_html, header=0, index_col=0)[0]
  679. results_as_html_R2 = results.tables[0].as_html()
  680. LM_results_R2=pd.read_html(results_as_html_R2, index_col=0)[0]
  681. results_R2s=LM_results_R2[3]
  682. #LM_results.head()
  683. #LM_results_R2.head()
  684. #results_R2s.head()
  685. SE=LM_results['std err']
  686. inter=mdf.params['Intercept']
  687. intercept.append(inter)
  688. #SE.head()
  689. se.append(SE["age"])
  690. coeff.append(mdf.params['age'])
  691. coeff_type=3
  692. tractTrack1.append(tract)
  693. #nodeTrack1.append(node)
  694. coeffType.append(coeff_type) #Add another line below
  695. r2.append(results_R2s['Dep. Variable:'])
  696. adjustR2.append(results_R2s['Model:'])
  697. aic.append(results_R2s['No. Observations:'])
  698. pvals.append(results_R2s['Date:'])
  699. print(mdf.summary())
  700. df=pd.DataFrame(tractTrack1)
  701. df.columns=['tractID']
  702. df.insert(1,"coeffType",coeffType)
  703. df.insert(2,"coeff_GM",coeff)
  704. df.insert(3,"se",se)
  705. df.insert(4,"intercept",intercept)
  706. df.insert(5,"r2",r2)
  707. df.insert(6,"adjustR2",adjustR2)
  708. df.insert(7,"aic",aic)
  709. df.insert(8,"pvals",pvals)
  710. os.makedirs('./outputData',exist_ok=True)
  711. df.to_csv('./outputData/LM_result_scanAge_noNorm_GM_AverageSubj_R1.csv')
  712. df.head()
  713. df.min(axis=0, numeric_only=True)
  714. df.max(axis=0, numeric_only=True)
  715. df.mean(axis=0, numeric_only=True)
  716. dfScanAge=df
  717. df.head()
  718. statsPerBundle=df.groupby('tractID').mean()
  719. statsPerBundle.head()
  720. statsPerBundle.to_csv('./outputData/LM_result_scanAge_noNorm_meanPerBundle_GM_AverageSubj_R1.csv')
  721. # %%
  722. SlopeWM_R1 = pd.read_csv('./outputData/LM_result_scanAge_noNorm_WM_AverageSubj_R1.csv', index_col=0)
  723. SlopeGM_R1 = pd.read_csv('./outputData/LM_result_scanAge_noNorm_GM_AverageSubj_R1.csv', index_col=0)
  724. SlopeBothR1 = pd.merge(SlopeWM_R1, SlopeGM_R1[['tractID', 'coeff_GM', 'r2', 'aic']], on='tractID')
  725. SlopeBothR1
  726. SlopeBothR1=SlopeBothR1.set_index(['tractID']).reindex(['AFL', 'AFR', 'ATRL', 'ATRR', 'CCL', 'CCR', 'CSL', 'CSR', 'FcMi', 'FcMa', 'IFOFL', 'IFOFR', 'ILFL', 'ILFR', 'MLFL', 'MLFR', 'ORL', 'ORR', 'SLFL', 'SLFR', 'UNCL', 'UNCR', 'VOFL', 'VOFR', 'pAFL', 'pAFR']).reset_index()
  727. SlopeBothR1
  728. # %%
  729. # Paired t-test between white matter and gray matter coefficients
  730. t_stat, p_val = stats.ttest_rel(SlopeBothR1['coeff'], SlopeBothR1['coeff_GM'])
  731. print(f"CoeffWM vs. CoeffGM t = {t_stat:.4f}, p = {p_val:.4f}")
  732. # Print mean values
  733. mean_wm = SlopeBothR1['coeff'].mean()
  734. mean_gm = SlopeBothR1['coeff_GM'].mean()
  735. print(f"Mean Coeff WM: {mean_wm:.4f}")
  736. print(f"Mean Coeff GM: {mean_gm:.4f}")
  737. # Calculate difference metrics y=GM, x=WM
  738. SlopeBothR1['diff_r2'] = SlopeBothR1['r2_x'] - SlopeBothR1['r2_y']
  739. SlopeBothR1['diff_aic'] = SlopeBothR1['aic_x'] - SlopeBothR1['aic_y']
  740. # Print mean differences
  741. mean_diff_r2 = SlopeBothR1['diff_r2'].mean()
  742. mean_diff_aic = SlopeBothR1['diff_aic'].mean()
  743. print(f"Mean Δr²: {mean_diff_r2:.4f}")
  744. print(f"Mean ΔAIC: {mean_diff_aic:.4f}")
  745. # %%
  746. sns.set_style('white');
  747. fig1=sns.lmplot(
  748. data=SlopeBothR1, x='coeff_GM', y="coeff", hue="tractID", height=10, scatter_kws={"s": 400}, fit_reg=False, legend=False, palette=color_list_chosen
  749. )
  750. fig1=sns.regplot(data=SlopeBothR1, x='coeff_GM', y="coeff", scatter=False, ax=fig1.axes[0, 0], line_kws={"color": "darkgrey"})
  751. res=scipy.stats.pearsonr(SlopeBothR1['coeff_GM'], SlopeBothR1['coeff'])
  752. print(res)
  753. res.confidence_interval()
  754. fig1.legend(title='Tract', fontsize='15', title_fontsize='20', bbox_to_anchor=(0.7, 0.25, 0.5, 0.5), loc='right', borderaxespad=0, frameon=False)
  755. plt.xlabel("R1 Slope in GM [s$^{-1}$/w]", fontsize=50)
  756. plt.ylabel("R1 Slope in WM [s$^{-1}$/w]", fontsize=50)
  757. plt.xticks([0.001, 0.003, 0.005], fontsize=45)
  758. plt.yticks(fontsize=45)
  759. plt.savefig('./figures/R1Slope.png', dpi=600, bbox_inches='tight', pad_inches=0.1)
  760. # %%
  761. import os
  762. import numpy as np
  763. import pandas as pd
  764. import seaborn as sns
  765. import matplotlib.pyplot as plt
  766. from matplotlib.lines import Line2D
  767. import matplotlib.colors as mcolors
  768. # --- PREPARE DATA -------------------------------------------------------------
  769. df = SlopeDataframe.copy()
  770. # Extract Hemisphere from tract names (e.g. "AF_L" → "L"); midline bundles will become NaN here
  771. df["Hemisphere"] = df["tractID"].str.extract(r'(L|R)$')
  772. # Extract base bundle name (e.g. "AF_L" → "AF")
  773. df["BundleBase"] = df["tractID"].str.replace(r'(L|R)$', '', regex=True)
  774. # --- DEFINE LAYOUT AND STYLE --------------------------------------------------
  775. tractPosHorz = {
  776. 'AF': (0,0), 'ATR': (0,1), 'CC': (0,2), 'CS': (0,3),
  777. 'IFOF':(1,0), 'ILF':(1,1), 'MLF':(1,2), 'OR':(1,3),
  778. 'SLF':(2,0), 'UNC':(2,1), 'VOF':(2,2), 'pAF':(2,3),
  779. 'FcMi':(3,1), 'FcMa':(3,2)
  780. }
  781. tracts = ['AF', 'ATR', 'CC', 'CS', 'IFOF', 'ILF', 'MLF',
  782. 'OR', 'SLF', 'UNC', 'VOF', 'pAF', 'FcMi', 'FcMa']
  783. colors = ['cyan', 'blue', 'green', 'orange', 'purple', 'brown', 'olive',
  784. 'coral', 'fuchsia', 'yellow', 'indigo', 'violet', 'salmon', 'red']
  785. sns.set(font_scale=2)
  786. sns.set_style("white")
  787. fig, axes = plt.subplots(4, 4, figsize=(14,17), frameon=False)
  788. fig.subplots_adjust(wspace=0.1, hspace=0.9)
  789. def lighten_color(color, amount=0.5):
  790. """
  791. Lightens the given color by blending it with white.
  792. amount=0 → white, amount=1 → original color.
  793. """
  794. try:
  795. c = mcolors.cnames[color]
  796. except KeyError:
  797. c = color
  798. rgb = mcolors.to_rgb(c)
  799. return tuple(1 - (1 - x) * amount for x in rgb)
  800. # --- MAIN LOOP ---------------------------------------------------------------
  801. midline_bundles = ["FcMi", "FcMa"] # bundles without L/R hemispheres
  802. for ct, (bundle, color) in enumerate(zip(tracts, colors)):
  803. # skip if not in layout mapping
  804. if bundle not in tractPosHorz:
  805. continue
  806. ax = axes[tractPosHorz[bundle]]
  807. # Filter data for this bundle base (works for both midline and hemispheric bundles)
  808. SlopeTract = df.query("BundleBase == @bundle")
  809. # If there's no data at all for this bundle, skip plotting (avoid empty panels)
  810. if SlopeTract.shape[0] == 0:
  811. ax.text(0.5, 0.5, "No data", ha='center', va='center', transform=ax.transAxes, fontsize=14)
  812. ax.set_title(bundle, pad=5, fontsize=20)
  813. ax.set_xticks([])
  814. ax.set_yticks([])
  815. continue
  816. # --- Different grouping for midline vs. hemispheric bundles ----------------
  817. if bundle in midline_bundles:
  818. # Group WITHOUT 'Hemisphere' because it's NaN for midline bundles
  819. SlopeTractWM = (
  820. SlopeTract
  821. .groupby(['subjectID', 'age', 'BundleBase'], as_index=False, sort=False)['R1']
  822. .mean()
  823. )
  824. SlopeTractGM = (
  825. SlopeTract
  826. .groupby(['subjectID', 'age', 'BundleBase'], as_index=False, sort=False)['GM_R1']
  827. .mean()
  828. )
  829. # Mark as midline for plotting convenience
  830. SlopeTractWM["Hemisphere"] = "M"
  831. SlopeTractGM["Hemisphere"] = "M"
  832. # Plot WM (single color)
  833. sns.scatterplot(
  834. data=SlopeTractWM, x='age', y='R1',
  835. color=color, s=50, alpha=0.5, ax=ax
  836. )
  837. sns.regplot(
  838. data=SlopeTractWM, x='age', y='R1',
  839. scatter=False, ax=ax,
  840. line_kws={'color': color, 'alpha': 0.8}
  841. )
  842. # Plot GM (single gray color)
  843. sns.scatterplot(
  844. data=SlopeTractGM, x='age', y='GM_R1',
  845. color='gray', s=50, alpha=0.5, ax=ax
  846. )
  847. sns.regplot(
  848. data=SlopeTractGM, x='age', y='GM_R1',
  849. scatter=False, ax=ax,
  850. line_kws={'color': 'gray', 'alpha': 0.8}
  851. )
  852. else:
  853. # Hemispheric bundles: group including Hemisphere field
  854. # Note: groupby will ignore NaN Hemispheres—this is fine because hemispheric bundles should have L/R
  855. SlopeTractWM = SlopeTract.groupby(
  856. ['subjectID', 'age', 'BundleBase', 'Hemisphere'],
  857. as_index=False, sort=False
  858. )['R1'].mean()
  859. SlopeTractGM = SlopeTract.groupby(
  860. ['subjectID', 'age', 'BundleBase', 'Hemisphere'],
  861. as_index=False, sort=False
  862. )['GM_R1'].mean()
  863. light_color = lighten_color(color, amount=0.5)
  864. # White matter scatter (L/R)
  865. sns.scatterplot(
  866. data=SlopeTractWM, x='age', y='R1', hue='Hemisphere', style='Hemisphere',
  867. markers={'L': 'o', 'R': 'o'}, s=50,
  868. palette={'L': color, 'R': light_color}, ax=ax,
  869. alpha=0.5, legend=False
  870. )
  871. # Regression lines for L and R (if any data present)
  872. for hemi, line_color in zip(['L','R'], [color, light_color]):
  873. hemi_df = SlopeTractWM[SlopeTractWM['Hemisphere'] == hemi]
  874. if hemi_df.shape[0] > 1: # need at least 2 points for a regression line
  875. sns.regplot(
  876. data=hemi_df, x='age', y='R1', scatter=False, ax=ax,
  877. line_kws={'color': line_color, 'alpha': 0.8}
  878. )
  879. # Gray matter scatter & lines
  880. sns.scatterplot(
  881. data=SlopeTractGM, x='age', y='GM_R1', hue='Hemisphere', style='Hemisphere',
  882. markers={'L': 'o', 'R': 'o'}, s=50,
  883. palette={'L':'darkgray', 'R':'lightgray'}, ax=ax,
  884. alpha=0.5, legend=False
  885. )
  886. for hemi, line_color in zip(['L','R'], ['darkgray','lightgray']):
  887. hemi_gm = SlopeTractGM[SlopeTractGM['Hemisphere'] == hemi]
  888. if hemi_gm.shape[0] > 1:
  889. sns.regplot(
  890. data=hemi_gm, x='age', y='GM_R1', scatter=False, ax=ax,
  891. line_kws={'color': line_color, 'alpha': 0.8}
  892. )
  893. # --- TITLES AND LABELS -----------------------------------------------------
  894. ax.set_title(bundle, pad=5, fontsize=25)
  895. ax.set_xlabel('Scan Age', labelpad=15, fontsize=20)
  896. if ct in [0, 4, 8, 12]: # first column
  897. ax.set_ylabel("R1 [s$^{-1}$]", fontsize=20)
  898. ax.set_xticks([40, 42, 44, 46])
  899. ax.set_xticklabels([40, 42, 44, 46], fontsize=20)
  900. ax.set_yticks([0.4, 0.45, 0.5])
  901. ax.set_yticklabels([0.4, 0.45, 0.5], fontsize=20)
  902. ax.spines['right'].set_visible(False)
  903. ax.spines['top'].set_visible(False)
  904. else:
  905. ax.set_ylabel('R1 [s$^{-1}$]', fontsize=14)
  906. ax.set_xticks([40, 42, 44, 46])
  907. ax.set_xticklabels([40, 42, 44, 46], fontsize=20)
  908. ax.set_yticks([0.4, 0.45, 0.5])
  909. ax.yaxis.set_visible(False)
  910. ax.spines['right'].set_visible(False)
  911. ax.spines['left'].set_visible(False)
  912. ax.spines['top'].set_visible(False)
  913. # Clean frame (ensure spines hidden where desired)
  914. ax.spines['right'].set_visible(False)
  915. ax.spines['top'].set_visible(False)
  916. # Turn off unused axes
  917. axes[3,3].axis("off")
  918. axes[3,0].axis("off")
  919. # --- SAVE & SHOW -------------------------------------------------------------
  920. os.makedirs('./figures', exist_ok=True)
  921. plt.savefig('./figures/R1ScanAge_Hemispheres.png', dpi=600, bbox_inches='tight')
  922. plt.show()

Figure3.ipynb at commit 57e9055, no license · at the source

Overview

  1. Department of Psychology, Philipps-Universität Marburg, Marburg, Germany
  2. Center for Mind, Brain and Behavior - CMBB, Universities of Marburg, Gießen, Darmstadt, Germany
  3. Lise Meitner Research Group Neuroplasticity in Development and Learning, Max Planck Institute for Human Development, Berlin, Germany
  4. Department of Psychology, University of Washington, Seattle, WA USA
  5. eScience Institute, University of Washington, Seattle, WA USA
  6. Department of Psychology, Stanford University, Stanford, CA USA
  7. Institute of Science and Technology for Brain-Inspired Intelligence, Fudan University, Shanghai, China
  8. Wu Tsai Neurosciences Institute, Stanford University, Stanford, CA USA
Journal: Nature communications, volume 17, issue 1, article 5353
Dates: received 8 July 2025; accepted 7 May 2026; published online 17 June 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41467-026-73366-9 · PMID 42310317 · PMCID PMC13276173 · OpenAlex W7165007514
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), developmental (subfield)
Methods: Statistics, Smoothing, state filtering, decompositions, Preprocessing, Connectivity, fMRI & imaging
Keywords: Human behaviour, Developmental neurogenesis, Neuronal development
MeSH: Cerebral Cortex*, Gray Matter*, Myelin Sheath*, White Matter*, Child, Preschool, Female, Humans, Infant, Infant, Newborn, Magnetic Resonance Imaging, Male (* major topic)
Topic: Advanced Neuroimaging Techniques and Applications (Radiology, Nuclear Medicine and Imaging, Medicine), according to OpenAlex
Funding: NIH National Institutes of Health & National Science Foundation (R01-EY033835, MH121868, R01-EB027585, R01-EY033628); European Research Council (319456); NIH NatioNIH National Institutes of Health & National Science Foundationnal Institutes of Health & National Science Foundation (MH121867)
Citations: cited by 1 paper (Europe PMC); 131 references in the paper

Abstract

The infant brain undergoes rapid myelination that is critical for healthy brain function. This development has been characterized for gray and white matter independently, but the link between gray and white matter myelination remains unexplored. To close this knowledge gap, we evaluated two complementary myelin-sensitive imaging metrics: Large-scale (N = 273) T1w/T2w and quantitative (N = 21) R1 data. Automated software was employed to identify 26 white matter bundles and map their cortical terminations, before evaluating T1w/T2w and R1 development shortly after birth. Here we show that for both metrics mean values as well as developmental slopes are correlated across tissues. The synchrony of brain T1w/T2w is impacted by postmenstrual age and prematurity, whereas inter-individual differences in this synchrony predict motor outcomes at 17 − 25 months of age. As T1w/T2w and R1 are associated with myelin content, our results reveal an intricate relationship between gray and white matter myelination.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

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

EduNeuroLab/WMGMMyelinInfants

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 57e9055dc133eca1fdbe06690a8fd9d233fb5e5c, 31 March 2026
Languages: Jupyter (8), Python (1)
Size: 12 files, 9 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, 8 notebooks
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: pandas (6 files), Matplotlib (5 files), NumPy (5 files), SciPy (5 files), seaborn (5 files), NiBabel (4 files), FreeSurfer (3 files), statsmodels (3 files), MRtrix3 (2 files), Nipype (2 files), scikit-learn (2 files), DIPY (1 file), scikit-image (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
10 files

Code availability

All data were analyzed using open-source software, including MRtrix110,128 and pyBabyAFQ, which we shared as a component of pyAFQ (https://tractometry.org/pyAFQ)124,129,130. Code that implements the tractography pipeline, example code to perform bundle identification with pyBabyAFQ, and code used to generate the main figures of this manuscript are made available in GitHub (https://github.com/EduNeuroLab/WMGMMyelinInfants)131.

Reproduced under the paper's license (CC BY), from the paper cited above.

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:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 9 scripts, each with its path and the digest of its content;
  • 11 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

All data required to generate the main figures are provided as a Source Data file with this paper and are also made available in GitHub (https://github.com/EduNeuroLab/WMGMMyelinInfants). In addition, data for analyses within this work are provided in Figshare (https://figshare.com/articles/dataset/Data_from_Cortical_and_white_matter_myelination_proceed_in_concert_during_early_infancy_/30667400), in accordance with dHCP data use terms and conditions. Source data are provided with this paper.

All data were analyzed using open-source software, including MRtrix110,128 and pyBabyAFQ, which we shared as a component of pyAFQ (https://tractometry.org/pyAFQ)124,129,130. Code that implements the tractography pipeline, example code to perform bundle identification with pyBabyAFQ, and code used to generate the main figures of this manuscript are made available in GitHub (https://github.com/EduNeuroLab/WMGMMyelinInfants)131.

Reproduced under the paper's license (CC BY), from the paper cited above.

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, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 10 authors, 3 keywords, 11 MeSH terms, 3 funders, 122 references.

Cite

This paper

Zika, S., Chang, K., Orhon, A., Kruper, J., Tyagi, C., Yan, X., Tung, S., Grill-Spector, K., Rokem, A., & Grotheer, M. (2026). Cortical and white matter myelination proceed in concert during early infancy. Nature communications, 17(1), 5353. https://doi.org/10.1038/s41467-026-73366-9

BibTeX

@article{zika2026cortical,
author = {Zika, Stephanie and Chang, Kelly and Orhon, Altan and Kruper, John and Tyagi, Christina and Yan, Xiaoqian and Tung, Sarah and Grill-Spector, Kalanit and Rokem, Ariel and Grotheer, Mareike},
title = {{Cortical and white matter myelination proceed in concert during early infancy}},
journal = {Nature communications},
year = {2026},
month = jun,
volume = {17},
number = {1},
pages = {5353},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-73366-9},
url = {https://doi.org/10.1038/s41467-026-73366-9},
pmid = {42310317},
pmcid = {PMC13276173}
}

RIS

TY - JOUR
AU - Zika, Stephanie
AU - Chang, Kelly
AU - Orhon, Altan
AU - Kruper, John
AU - Tyagi, Christina
AU - Yan, Xiaoqian
AU - Tung, Sarah
AU - Grill-Spector, Kalanit
AU - Rokem, Ariel
AU - Grotheer, Mareike
TI - Cortical and white matter myelination proceed in concert during early infancy
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/06/17
VL - 17
IS - 1
SP - 5353
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-73366-9
UR - https://doi.org/10.1038/s41467-026-73366-9
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-73366-9",
"type": "article-journal",
"title": "Cortical and white matter myelination proceed in concert during early infancy",
"container-title": "Nature communications",
"author": [
{
"family": "Zika",
"given": "Stephanie"
},
{
"family": "Chang",
"given": "Kelly"
},
{
"family": "Orhon",
"given": "Altan"
},
{
"family": "Kruper",
"given": "John"
},
{
"family": "Tyagi",
"given": "Christina"
},
{
"family": "Yan",
"given": "Xiaoqian"
},
{
"family": "Tung",
"given": "Sarah"
},
{
"family": "Grill-Spector",
"given": "Kalanit"
},
{
"family": "Rokem",
"given": "Ariel"
},
{
"family": "Grotheer",
"given": "Mareike"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "5353",
"DOI": "10.1038/s41467-026-73366-9",
"PMID": "42310317",
"PMCID": "PMC13276173",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-73366-9",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
17
]
]
}
}

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.1126/sciadv.aec2348 [code]
Congenital blindness reduces myelination in human visual cortex.
Journal: Science advances
In common: FreeSurfer, NiBabel, pandas, 1 other tool, 19 references
[2] doi:10.1162/imag.a.1325 [code]
Decoding everyday levels of musical training from subcortical white-matter architecture.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: MRtrix3, FreeSurfer, statsmodels, 5 other tools, 11 references
[3] doi:10.1162/imag.a.1341 [code]
Massively parallelized brain tractography using compute clusters, supercomputers, and graphics processing units.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: DIPY, NiBabel, pandas, 3 other tools, 5 references, 2 authors
[4] doi:10.1093/cercor/bhag132 [code]
Spatiotemporal white-matter development across early childhood.
Journal: Cerebral cortex (New York, N.Y. : 1991)
In common: DIPY, MRtrix3, scikit-image, 5 other tools, developmental, 9 references
[5] doi:10.1038/s41467-026-73072-6 [code]
Mapping the spatiotemporal continuum of structural connectivity development across the human connectome in youth.
Journal: Nature communications
In common: MRtrix3, FreeSurfer, NiBabel, 3 other tools, developmental, 12 references
[6] doi:10.1038/s41597-026-06869-1 [code]
Individual Brain Charting: fifth release of high-resolution fMRI data for cognitive mapping.
Journal: Scientific data
In common: DIPY, MRtrix3, Nipype, 9 other tools, author Kalanit Grill-Spector
[7] doi:10.1016/j.dcn.2026.101775 [code]
Neonatal brain-age models in full- and preterm infants.
Journal: Developmental cognitive neuroscience
In common: NiBabel, statsmodels, seaborn, 5 other tools, developmental, 9 references
[8] doi:10.1038/s41598-026-51531-w [code]
Multimodal age-dependent diffusion-MRI analysis of the neocortex in a rat model of cortical dysplasia.
Journal: Scientific reports
In common: DIPY, MRtrix3, scikit-image, 6 other tools, 6 references
[9] doi:10.1002/hbm.70562 [code]
Distinct Physiological Mechanisms Drive Grey Matter Plasticity in Complex Versus Simple Sequence Learning.
Journal: Human brain mapping
In common: MRtrix3, NiBabel, statsmodels, 6 other tools, 6 references
[10] doi:10.1038/s42003-026-10276-y [code]
The cellular correlates and adolescent reorganisation of cortical myelination networks in the common marmoset.
Journal: Communications biology
In common: Nipype, FreeSurfer, NiBabel, 7 other tools, 5 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.