OSCR

Spinal cord structural and functional architecture and its shared organization with the brain across the adult lifespan.

Code ↔ Paper

21 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 21 matches
  1. [1] § Methods › Functional data processing › Preprocessing ↔ code/brsc_preprocess.py, lines 787–906 · score 0.88 · sct_fmri_moco, framewise displacement, quality control, motion parameters, Motion correction, FD
  2. [2] § Methods › Functional data processing › Structural and functional coupling ↔ code/brsc_statistics.py, lines 57–149 · score 0.83 · n_nodes, upper triangle, Spearman correlations, functional coupling, edges, stacked
  3. [3] § Results › Aging of the spinal cord microstructure ↔ notebook/main_figures/Fig01_SpiMorpho.ipynb, lines 455–545 · score 0.81 · p_perm_mae, p_perm_r2, shuffled age, age prediction, Permutation, elastic
  4. [4] § Results ↔ code/brsc_preprocess.py, lines 787–906 · score 0.81 · framewise displacement, quality control, MRI volumes, fMRI, spinal cord imaging, FD
  5. [5] § Methods › Structural data processing › Age effects and prediction analyses ↔ notebook/main_figures/Fig01_SpiMorpho.ipynb, lines 455–545 · score 0.80 · age prediction models, ElasticNet, shuffling age, permutation, MAE, alpha
  6. [6] § Methods › Functional data processing › Preprocessing ↔ notebook/preprocessing/02_brsc_denoising.ipynb, lines 309–376 · score 0.74 · MNI space, warping field, PAM50 template, T1w image, template space, coregistered
  7. [7] § Methods › Functional data processing › Time series denoising ↔ code/brsc_denoising.py, lines 583–683 · score 0.71 · CompCor, motion parameters, Tapas, RETROICOR, DCT, confounds
  8. [8] § Methods › Functional data processing › CAnonical Time-series Characteristics (catch-22) extraction ↔ code/connectivity/alff.py, lines 361–404 · score 0.70 · power spectrum, square root, detrended, amplitude, ALFF
  9. [9] § Methods › Structural data processing › Age effects and prediction analyses ↔ code/brsc_classification.py, lines 78–116 · score 0.70 · cross validation, ElasticNet, training, MAE, split, R2
  10. [10] § Methods › Structural data processing › Spinal cord microstructural metric extraction ↔ notebook/preprocessing/04_sc_preprocess_diffusion.ipynb, lines 94–149 · score 0.68 · warping field, dti, v6, tract, coregistered, diffusivity
  11. [11] § Methods › Structural data processing › Brain and spinal cord preprocessing ↔ code/brsc_preprocess.py, lines 908–1011 · score 0.65 · sct_deepseg_gm, sct_propseg, preprocessing, WM, Spinal cord, segmentation
  12. [12] § Methods › Data curation ↔ code/brsc_preprocess.py, lines 355–457 · score 0.62 · fMRI, Brain Imaging, FSL, python, BIDS, SCT
  13. [13] § Methods › Functional data processing › Preprocessing ↔ code/brsc_preprocess.py, lines 1157–1249 · score 0.61 · sct_register_multimodal, warping field, coregistered, PAM50, template, Preprocessing
  14. [14] § Methods › Functional data processing › Statistical analyses ↔ code/brsc_statistics.py, lines 272–393 · score 0.60 · age terms, regression models, mixed, OLS, squares, covariate
  15. [15] § Methods › Functional data processing › Statistical analyses ↔ code/brsc_statistics.py, lines 272–393 · score 0.60 · confidence intervals, replacement, resampling, bootstrap, beta, OLS
  16. [16] § Methods › Structural data processing › Spinal cord microstructural metric extraction ↔ notebook/preprocessing/03_sc_preprocess_microstructural.ipynb, lines 118–164 · score 0.59 · warping field, PAM50 template, v6, coregistered, Microstructural, transform
  17. [17] § Methods › Functional data processing › ICAPs framework ↔ code/connectivity/post_icaps.py, lines 334–414 · score 0.58 · Dice coefficient, iCAPs, components, atlas, cord
  18. [18] § Results ↔ notebook/main_figures/Fig01_SpiMorpho.ipynb, lines 401–453 · score 0.57 · prediction model, predicted age, donut, elastic, ratio, coefficients
  19. [19] § Methods › Structural data processing › Brain and spinal cord preprocessing ↔ notebook/preprocessing/04_sc_preprocess_diffusion.ipynb, lines 94–149 · score 0.55 · PAM50 warping field, moco, T1w, DWI, SCT, preprocessing
  20. [20] § Methods › Structural data processing › Brain and spinal cord preprocessing ↔ notebook/preprocessing/02_brsc_denoising.ipynb, lines 309–376 · score 0.55 · MNI space, T1w images, DARTEL, fields, template, preprocessing
  21. [21] § Methods › Functional data processing › Preprocessing ↔ code/brsc_preprocess.py, lines 908–1011 · score 0.52 · sct_propseg, centerline, CSF, Tissue, WM, GM

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

Python · 1,680 lines · 83 KB · BSD-3-Clause · 6 matches

  1. # -*- coding: utf-8 -*-
  2. import os, glob, shutil, re
  3. import json
  4. import pandas as pd
  5. import numpy as np
  6. import nibabel as nib
  7. import fnmatch
  8. import matlab.engine
  9. from joblib import Parallel, delayed
  10. import brsc_utils
  11. #plotting:
  12. import matplotlib
  13. import matplotlib.pyplot as plt
  14. from nilearn import image
  15. class Preprocess_BrSc:
  16. '''
  17. The Preprocess class is used to compute Brain and spinal cord preprocessings simultaneously
  18. Slice timing & image cropping are availables
  19. Attributes
  20. ----------
  21. config : dict
  22. '''
  23. def __init__(self, config, verbose=True):
  24. self.config = config # load config info
  25. self.participant_IDs= self.config["participants_IDs"] # list of the participants to analyze
  26. self.main_dir=self.config["main_dir"] # main drectory of the project
  27. if verbose==True:
  28. print("The config files should be manually modified first")
  29. print("All the raw data should be store in BIDS format")
  30. print(" ")
  31. # Create participant directories (if not already existed)
  32. for ID in self.participant_IDs:
  33. if "preprocess_dir" in self.config.keys():
  34. preproc_dir=self.config["main_dir"]+ self.config["preprocess_dir"]["bmpd_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["preprocess_dir"]["main_dir"]
  35. ID_preproc_dir=preproc_dir + "/sub-" + ID
  36. if not os.path.exists(ID_preproc_dir):
  37. os.mkdir(ID_preproc_dir)
  38. # create 1 folder per session if there are multiple sessions (exemple multiple days of acquisition)
  39. for ses_name in self.config["design_exp"]['ses_names']:
  40. ses_dir="/" + ses_name if int(self.config["design_exp"]["ses_nb"])>0 else ""
  41. if ses_dir != "":
  42. os.mkdir(ID_preproc_dir + ses_dir)
  43. os.mkdir(ID_preproc_dir + ses_dir + "/anat/")
  44. os.mkdir(ID_preproc_dir + ses_dir + "/anat/brain/")
  45. os.mkdir(ID_preproc_dir + ses_dir + "/func/")
  46. print("New folders in preprocess dir have been created")
  47. # Create manual directory
  48. if "manual_dir" in self.config.keys():
  49. manual_dir=self.config["main_dir"]+ self.config["manual_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["manual_dir"]
  50. ID_manual_dir=manual_dir + "/sub-" + ID
  51. if not os.path.exists(ID_manual_dir):
  52. os.makedirs(ID_manual_dir)
  53. for ses_name in self.config["design_exp"]['ses_names']:
  54. ses_dir="/" + ses_name if int(self.config["design_exp"]["ses_nb"])>0 else ""
  55. if ses_dir != "":
  56. os.mkdir(ID_manual_dir + ses_dir)
  57. os.mkdir(ID_manual_dir + ses_dir + "/anat/")
  58. os.mkdir(ID_manual_dir + ses_dir + "/func/")
  59. # if there are multiple runs create a folder for each runs in func folder:
  60. if "design_exp" in self.config.keys():
  61. for ses_name in self.config["design_exp"]['ses_names']:
  62. ses_dir=ses_name if int(self.config["design_exp"]["ses_nb"])>0 else ""
  63. for task_name in self.config["design_exp"]['task_names']:
  64. task_dir='task-'+task_name if int(self.config["design_exp"]["task_nb"])>1 else ""
  65. if not os.path.exists(ID_preproc_dir +"/" +ses_dir +"/func/" + task_dir):
  66. os.mkdir(ID_preproc_dir +"/" +ses_dir +"/func/" + task_dir)
  67. # Check if there is specificity for anat or func filename:
  68. if 'files_specificities' in self.config.keys():
  69. if ID in config['files_specificities']["T1w"]:
  70. print("sub-" + ID + " have a anat filename specitity: " + config['files_specificities']["T1w"][ID])
  71. if ID in config['files_specificities']["func"]:
  72. print("sub-" + ID + " have a anat filename specitity: " + config['files_specificities']["func"][ID])
  73. def stc(self,ID=None,i_img=None,json_f=None,ses_name='',task_name='',tag='',t_custom=False,down=None,odd=None, redo=False,verbose=True):
  74. '''
  75. Slice timing correction is applied in order to minimize the effect of slice ordering in the acquisition of the images.
  76. https://poc.vl-e.nl/distribution/manual/fsl-3.2/slicetimer/index.html
  77. Attributes:
  78. ----------
  79. ID: name of the participant
  80. i_img: input filename of functional images (str, default:None, the file will be defined by default in the function)
  81. json_f: json file with relevant info (str, default:None, the file will be defined by default in the function)
  82. ses_name: if the ses have any specific name (ses- in BIDS format)
  83. task_name: if the run have any specific name (task- in BIDS format)
  84. tag: if there is any specific tag related to the filename. eg. run-02
  85. tr: Specify TR of data - /!\ (str, default:3)
  86. down: reverse slice indexing (If slices were acquired from the top of the SC to the bottom)
  87. odd: use it for interleaved acquisition (str, default:)
  88. tcustom: set True to use this option > filename of single-column custom interleave timing file. The units should be in TRs, with 0.5 corresponding to no shift. Therefore a sensible range of values will be between 0 and 1. option parameters provide in the json file "SliceTiming" can be used. The array is in seconds, one number per slice is provide and the maximum value is always be less that the TR.
  89. This array should be transform in TRs units (value/TR) before using it with slicetimer it was not the case in this script but it should not change the results for resting state correlation analyses </font>
  90. Outputs:
  91. ----------
  92. Slice time corrected image *stc.nii
  93. slice timings file : *stc.txt
  94. nb: One value (ie for each slice) on each line of a text file. The units are in TRs, with 0.5 corresponding to no shift.
  95. '''
  96. if ID==None:
  97. raise Warning("Please provide the ID of the participant, ex: _.stc(ID='A001')")
  98. # Select the default input filename if it is not provided
  99. if i_img==None:
  100. raw_dir=self.config["main_dir"]+ self.config["bmpd_raw_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["raw_dir"]
  101. print(raw_dir + "sub-" + ID+ "/" + ses_name + "/func/sub-" + "*" + task_name +"*" +'*' + tag + "*.nii*")
  102. i_img=glob.glob(raw_dir + "sub-" + ID+ "/" + ses_name + "/func/sub-" + "*" + task_name +"*" +'*' + tag + "*.nii*")[0]
  103. if json_f==None:
  104. json_f=glob.glob(i_img.split(".")[0] + "*.json")[0]
  105. #print("input image is: " + i_img)
  106. # Create output directories if no existed
  107. preprocess_dir=self.config["main_dir"]+ self.config["preprocess_dir"]["bmpd_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["preprocess_dir"]["main_dir"]
  108. ID_dir=preprocess_dir + "/sub-" + ID
  109. stc_dir=ID_dir + '/'+ses_name+"/func/"+task_name+ '/' +self.config["preprocess_dir"]["func_stc"]
  110. if not os.path.exists(stc_dir):
  111. os.mkdir(stc_dir)
  112. os.mkdir(stc_dir + "/brain")
  113. os.mkdir(stc_dir + "/spinalcord")
  114. # Define output name:
  115. o_img=stc_dir + os.path.basename(i_img.split(".")[0] + "_stc.nii.gz")
  116. # for some particpant that were miss named
  117. if ID in self.config["double_IDs"]:
  118. o_img=stc_dir + os.path.basename((("sub-" + ID + i_img.split("sub-" + self.config["double_IDs"][ID])[-1]).split(".")[0]) + "_stc.nii.gz")
  119. if not os.path.exists(o_img) or redo==True:
  120. print(">>>>> slice timing correction is running for sub-" + ID)
  121. # read the json file to extract some info
  122. with open(json_f) as g:
  123. params = json.load(g)
  124. tr=params["RepetitionTime"] # extract the time repetition value
  125. if t_custom ==True:
  126. o_txt=stc_dir + os.path.basename(i_img.split(".")[0] + "_stc.txt") # output with slicetiming info
  127. stc_info=params["SliceTiming"] # provide info about interleave slice order
  128. with open(o_txt, 'w') as f:
  129. for item in stc_info:
  130. item_tr=item/tr # Transform slicetiming in secs in st in TRs units
  131. f.write('{}\n'.format(item_tr))
  132. f.close()
  133. del f, item
  134. # run slice timing correction:
  135. string="slicetimer -i "+ i_img + " -o " + o_img +" -r " + str(tr) + " --odd --tcustom=" + o_txt
  136. os.system(string)
  137. print("done")
  138. else:
  139. raise Warning("No other option that interleaved acquisition for sct was implemented yet, you should cutomise the code here to add an option")
  140. if os.path.exists(stc_dir) and redo==False:
  141. print(">>>>> slice timing correction was already completed for sub-" + ID)
  142. return o_img
  143. def crop_img(self,ID=None,i_img=None,o_folder=None,tsv_f=None,structure=None,ses_name='',task_name='',tag='',img_type="func",redo=False,verbose=True):
  144. '''
  145. This function will help to crop brain and spinale cord from an image included both in one single FOV
  146. to improve: the option to use automatic detection of the brain and the spinal cord
  147. Attributes:
  148. ----------
  149. ID: name of the participant
  150. i_img: input filename of functional images (str, default:None, an error will be raise)
  151. o_img: output folder name filename (str, default:None, the input folder will be used)
  152. tsv_f: tsv file with relevant info (str, default:None, the file will be defined by default in the function)
  153. should include at least the following columns: participant_id anat_crop_brain anat_crop_spine func_crop_brain func_crop_spine
  154. ses_name: if the session have any specific name (ses- in BIDS format)
  155. task_name: if the run have any specific name (task- in BIDS format)
  156. img_type: the type of input image should be specify "func" or "anat"
  157. Outputs:
  158. ----------
  159. Image cropped
  160. '''
  161. if ID==None:
  162. raise Warning("Please provide the ID of the participant, ex: _.stc(ID='A001')")
  163. if i_img==None:
  164. raise Warning("Please provide filename of the input file")
  165. # Select the default output directory (input directory)
  166. if o_folder==None:
  167. o_folder=os.path.dirname(i_img)
  168. #read tsv file
  169. preprocess_dir=self.config["main_dir"]+ self.config["preprocess_dir"]["bmpd_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["preprocess_dir"]["main_dir"]
  170. if tsv_f==None:
  171. tsv_f=glob.glob(preprocess_dir + "*participants.tsv")[0]
  172. df = pd.read_csv(tsv_f, sep='\t') # read the file
  173. # this lines should be remove for next version
  174. if img_type=="func" and structure==None:
  175. o_img_brain=o_folder + "/brain/" + os.path.basename(i_img.split(".")[0] + "_brain.nii.gz")
  176. o_img_sc=o_folder + "/spinalcord/" + os.path.basename(i_img.split(".")[0] + "_sc.nii.gz")
  177. else :
  178. # for some particpant that were miss named
  179. if ID in self.config["double_IDs"]:
  180. o_img_brain=o_folder + os.path.basename((("sub-" + ID + i_img.split("sub-" + self.config["double_IDs"][ID])[-1]).split(".")[0]) + "_brain.nii.gz")
  181. o_img_sc=o_folder + os.path.basename((("sub-" + ID + i_img.split("sub-" + self.config["double_IDs"][ID])[-1]).split(".")[0]) + "_sc.nii.gz")
  182. else:
  183. o_img_brain=o_folder + os.path.basename(i_img.split(".")[0] + "_brain.nii.gz")
  184. o_img_sc=o_folder + os.path.basename(i_img.split(".")[0] + "_sc.nii.gz")
  185. # crop brain:
  186. if not os.path.exists(o_img_sc) or redo==True:
  187. print(o_img_sc)
  188. if structure==None or structure=="brain":
  189. print(">>>>> brain and spinal cord cropping is running for sub-" + ID + " " + img_type)
  190. z_max=int(df[img_type+"_zmax_brain"][df["participant_id"]==ID].values[0]) if img_type+"_zmax_brain" in df.columns else nib.load(i_img).shape[2]
  191. z_min=int(df[img_type+"_zmin_brain"][df["participant_id"]==ID].values[0]) if img_type+"_zmin_brain" in df.columns else 0
  192. string_br='sct_crop_image -i ' + i_img+ ' -o '+ o_img_brain+' -zmin ' + str(z_min) + ' -zmax ' + str(z_max)
  193. os.system(string_br) # run the string as a command line
  194. if structure==None or structure=="spinalcord":
  195. # crop spinalcord:
  196. z_max=int(df[img_type+"_zmax_sc"][df["participant_id"]==ID].values[0]) if img_type+"_zmax_sc" in df.columns else nib.load(i_img).shape[2]
  197. z_min=int(df[img_type+"_zmin_sc"][df["participant_id"]==ID].values[0]) if img_type+"_zmin_sc" in df.columns else 0
  198. string_sc='sct_crop_image -i ' + i_img+ ' -o '+ o_img_sc+' -zmin '+str(z_min)+' -zmax ' + str(z_max)
  199. os.system(string_sc) # run the string as a command line
  200. return o_img_brain, o_img_sc
  201. def smooth_img(self,i_img=None,ID=None,o_folder=None,fwhm=[0,0,0],ses_name='',task_name='',tag='_s',n_jobs=1,mean=True,redo=False,verbose=True):
  202. '''
  203. This function will smooth the 3D input image using nilearn see:
  204. https://nilearn.github.io/stable/modules/generated/nilearn.image.smooth_img.html#nilearn.image.smooth_img
  205. Attributes:
  206. ----------
  207. i_img <filename>, mendatory, default=None : input filename of functional images (str, default:None, an error will be raise)
  208. o_img <filename>, optional, default=None : output folder name filename (str, default:None, the input folder will be used)
  209. fwhm <array>, optional, default=[0,0,0]: it must have 3 elements, giving the FWHM along each axis. If any of the elements is 0 or None, smoothing is not performed along that axis.
  210. ses_name: if the run have any specific name (ses- in BIDS format)
  211. task_name: if the run have any specific name (task- in BIDS format)
  212. img_type: the type of input image should be specify "func" or "anat"
  213. Outputs:
  214. ----------
  215. Image cropped
  216. '''
  217. if i_img==None:
  218. raise Warning("Please provide filename of the input file (i_img),and participant(s) ID (ID)")
  219. i_imgs=[i_img] if isinstance(i_img,str) else i_img
  220. IDs=[ID] if isinstance(ID,str) else ID
  221. if o_folder==None:
  222. o_folders=[]
  223. for i in range(len(i_imgs)):
  224. o_folders.append(os.path.dirname(i_imgs[i]) + "/")
  225. elif isinstance(o_folder,str):
  226. o_folders=[o_folder]
  227. else:
  228. o_folders=o_folder
  229. #define output filename:
  230. o_imgs=[]
  231. for ID_nb, filename in enumerate(i_imgs):
  232. o_imgs.append(o_folders[ID_nb] + os.path.basename(i_imgs[ID_nb]).split('.')[0] + tag + ".nii.gz")
  233. if not os.path.exists(o_imgs[0]) or redo==True:
  234. print(" ")
  235. print(">>>>> Apply transformation is running with " + str(n_jobs)+ " parallel jobs on " +str(len(IDs)) + " participant(s)")
  236. Parallel(n_jobs=n_jobs)(delayed(self._run_smooth_img)(i_img=i_imgs[ID_nb],
  237. o_img=o_imgs[ID_nb],
  238. ID=IDs[ID_nb],
  239. fwhm=fwhm,
  240. mean=mean)
  241. for ID_nb in range(len(i_imgs)))
  242. else:
  243. if verbose:
  244. print("Smoothing was already done, put redo=True to redo that step")
  245. return o_imgs
  246. def _run_smooth_img(self,i_img,o_img,ID,fwhm,mean):
  247. smoothed_image=image.smooth_img(i_img, fwhm)
  248. smoothed_image.to_filename(o_img)
  249. if mean==True:
  250. string='fslmaths '+o_img+' -Tmean '+o_img.split('.')[0] + '_mean.nii.gz'
  251. os.system(string)
  252. # save smoothing parameters
  253. with open(o_img.split('.')[0] + '.json', 'w') as f:
  254. json.dump(fwhm, f) # save info
  255. print("Smoothing done: " + os.path.basename(o_img))
  256. return o_img
  257. ########################################################################
  258. ########################################################################
  259. class Preprocess_Br:
  260. '''
  261. The Preprocess_Br class is used to compute brain preprocessings
  262. Motion correction, Segmentation, vertebral labelling and coregistration steps are available
  263. Attributes
  264. ----------
  265. config : dict
  266. '''
  267. def __init__(self, config, verbose=True):
  268. self.config = config # load config info
  269. self.participant_IDs = self.config.get("participants_IDs") or self.config.get("participants_IDs_ALL") or []# list of the participants to analyze
  270. self.main_dir=self.config["main_dir"] # main drectory of the project
  271. self.spm_dir=self.config["tools_dir"]["spm_dir"]
  272. def moco(self,ID=None,i_img=None,o_folder=None,params=None, ses_name='',task_name='',tag='',plot_show=True,redo=False,verbose=True):
  273. '''
  274. This function will correct the brain images for motion using the fsl
  275. Calculate framewise displacement (FD)
  276. Plot the motion parameters
  277. https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/MCFLIRT
  278. Attributes:
  279. ----------
  280. ID: name of the participant
  281. i_img: input filename of functional images 4D (str, default:None, an error will be raise)
  282. o_folder: output folder (str, default:None, the input folder will be used)
  283. params: see sct toolbox for more details, should not be modified across participants
  284. ses_name: if the run have any specific name (ses- in BIDS format)
  285. task_name: if the run have any specific name (task- in BIDS format)
  286. Outputs:
  287. ----------
  288. The outputs of the motion correction process are:
  289. - the motion-corrected fMRI volumes: *_sc_moco.nii & one temporary file for each volume
  290. - the time average of the corrected fMRI volumes: - Time average of corrected vols: *_sc_moco_mean.nii
  291. - a time-series with 1 voxel in the XY plane, for the X and Y motion direction (two separate files).
  292. - a TSV file with the slice-wise average of the motion correction for XY (one file), that can be used for Quality Control.
  293. - Motion corrected volumes:
  294. - Time-series with 1 voxel in the XY plane: *_sc_moco_params_X.nii & params_Y.nii
  295. - Slice-wise average of MOCO for XY : moco_params.tsv & moco_params.txt
  296. '''
  297. if ID==None:
  298. raise Warning("Please provide the ID of the participant, ex: _.stc(ID='A001')")
  299. if i_img==None:
  300. raise Warning("Please provide filename of the input file")
  301. #1. create ouput folder
  302. preprocess_dir=self.config["main_dir"]+ self.config["preprocess_dir"]["bmpd_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["preprocess_dir"]["main_dir"]
  303. if o_folder is None : # gave the default folder name if not provided
  304. o_folder=preprocess_dir + "sub-" + ID+ "/" + ses_name + "/func/"+task_name+ "/" +self.config["preprocess_dir"]["func_moco"]
  305. if not os.path.exists(o_folder):
  306. os.mkdir(o_folder)
  307. if not os.path.exists(o_folder+ "/brain/"):
  308. os.mkdir(o_folder + "/brain/")
  309. moco_file= o_folder+"/brain/"+ os.path.basename(i_img).split(".")[0] + "_moco.nii.gz"
  310. moco_mean_file= o_folder+"/brain/"+ os.path.basename(i_img).split(".")[0] + "_moco_mean.nii.gz"
  311. if not os.path.exists(moco_file) or redo==True:
  312. print(">>>>> Moco is running for the brain image of the sub-" + ID + " ")
  313. # 1. Create a binary mask before apply moco
  314. if not os.path.exists(preprocess_dir + "sub-" + ID+ "/" + ses_name + "/func/"+task_name+ "/" +self.config["preprocess_dir"]["func_seg"]["brain"]):
  315. os.makedirs(preprocess_dir + "sub-" + ID+ "/" + ses_name + "/func/"+task_name+ "/" +self.config["preprocess_dir"]["func_seg"]["brain"])# create directory
  316. mask_img= preprocess_dir + "sub-" + ID+ "/" + ses_name + "/func/"+task_name+ "/" +self.config["preprocess_dir"]["func_seg"]["brain"]+ os.path.basename(i_img).split(".")[0] + "_masked.nii.gz"
  317. string_mask="bet "+i_img+ " "+mask_img+" -F"
  318. os.system(string_mask)
  319. # 2. compute motion correction
  320. string_moco="mcflirt -in "+ mask_img+" -out "+moco_file+" -mats -plots"
  321. os.system(string_moco)
  322. #3. Calculate the mean image
  323. string_mean="fslmaths " +moco_file+ " -Tmean " + moco_mean_file
  324. os.system(string_mean)
  325. if plot_show==True:
  326. #4. Calculate Framewise displacement
  327. output_fd=o_folder + '/brain/' + os.path.basename(i_img).split('.')[0] + "_FD_brain"
  328. if not os.path.exists(output_fd + ".txt") or redo==True:
  329. print('Compute framewise displacement for sub-' +ID)
  330. string_fd="fsl_motion_outliers -i "+mask_img+" -o "+o_folder+"/brain/"+" -s "+ output_fd + ".txt -p "+ output_fd + ".png --fd"
  331. os.system(string_fd)
  332. FD=pd.read_csv(output_fd + '.txt', delimiter=',',header=None)
  333. FD_mean = np.mean(FD[0])
  334. print('Mean FD for sub-' +ID + ": " + str(round(FD_mean,3)) + ' mm')
  335. #5. Plot motion parameters
  336. fig, axs = plt.subplots(2,1, figsize=(18, 6), facecolor='w', edgecolor='k')
  337. fig.tight_layout()
  338. fig.subplots_adjust(hspace = .5, wspace=.001)
  339. moco_params=pd.read_csv(moco_file + '.par',delimiter=' ',header=None,engine='python')
  340. axs[0].plot(moco_params.iloc[:,0:3])
  341. axs[0].set_title(ID + " " + ses_name)
  342. axs[1].plot(moco_params.iloc[:,3:6])
  343. axs[0].set_ylabel("Rotation (rad)")
  344. axs[1].set_ylabel("Translation (mm)")
  345. axs[1].set_xlabel("Volumes")
  346. if not os.path.exists(o_folder + "/brain/moco_params.png") or redo==True:
  347. plt.savefig(o_folder + "/brain/moco_params.png")
  348. np.savetxt(o_folder + '/brain/FD_mean.txt', [FD_mean])
  349. plt.show(block=False)
  350. print(" ")
  351. return moco_file, moco_mean_file
  352. def segmentation(self,ID=None,i_img=None,o_folder=None,ses_name='',task_name='',tag='',img_type="anat",extract_rois=False,redo=False,verbose=True):
  353. '''
  354. This function will segment the brain using CAT12 toolbox:
  355. https://neuro-jena.github.io/cat/
  356. Attributes:
  357. ----------
  358. ID: name of the participant
  359. i_img: input filename of functional images 3D (str, default:None, an error will be raise)
  360. o_folder: output folder (str, default:None, the input folder will be used)
  361. ses_name: if the session have any specific name (ses- in BIDS format)
  362. task_name: if the run have any specific name (task- in BIDS format)
  363. img_type: the type of input image should be specify "func" or "anat"
  364. Outputs:
  365. ----------
  366. '''
  367. if ID==None:
  368. raise Warning("Please provide the ID of the participant, ex: _.stc(ID='A001')")
  369. if i_img==None:
  370. raise Warning("Please provide filename of the input file")
  371. #1. create ouput folder
  372. preprocess_dir=self.config["main_dir"]+ self.config["preprocess_dir"]["bmpd_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["preprocess_dir"]["main_dir"]
  373. if o_folder is None : # gave the default folder name if not provided
  374. if img_type=="func":
  375. o_folder=preprocess_dir + "sub-" + ID+ "/" + ses_name + "/func/"+task_name+ "/" +self.config["preprocess_dir"]["func_brain_seg"]
  376. elif img_type=="anat":
  377. o_folder=preprocess_dir + "sub-" + ID+ "/" + ses_name + "/anat/"+self.config["preprocess_dir"]["T1w_brain_seg"]
  378. if not os.path.exists(o_folder):
  379. os.makedirs(o_folder)
  380. #2. unzip files
  381. if os.path.basename(i_img).split(".")[-1] == "gz" :
  382. unzip_i_img=brsc_utils.unzip_file(i_img,o_folder=o_folder,ext=".nii",zip_file=False, redo=False,verbose=False)
  383. else:
  384. unzip_i_img=i_img
  385. ##3. run segmentation with cat12
  386. if not os.path.exists(o_folder + "/mri/") or redo==True:
  387. print('Brain segmentation is running for sub-' +ID)
  388. os.chdir(self.config["tools_dir"]["main_codes"] + '/code/spm/')# need to change the directory to find the CAT12 function
  389. eng = matlab.engine.start_matlab()
  390. eng.CAT12_BrainSeg(unzip_i_img, self.config["tools_dir"]["main_codes"] + '/code/spm/Cat12_log/',self.spm_dir)
  391. #Mask the brain image using segmented tissues
  392. mask=o_folder + "/mri/p0*" + os.path.basename(unzip_i_img)
  393. string_mask='fslmaths '+unzip_i_img +" -mas " + mask + " >0 " +unzip_i_img.split(".")[0] + "_masked.nii.gz"
  394. os.system(string_mask)
  395. #create binary mask
  396. for tissue in ["p1","p2","p3"]:
  397. #mask the tissue probability map
  398. prob_img=o_folder + "/mri/" + tissue + os.path.basename(unzip_i_img)
  399. mask_img=o_folder + "/mri/" + tissue + os.path.basename(unzip_i_img).split(".")[0] + "_mask.nii.gz"
  400. string_mask='fslmaths '+prob_img +" -thr 0.5 -bin " + mask_img
  401. os.system(string_mask)
  402. mask_img_unzip=brsc_utils.unzip_file(mask_img,o_folder=o_folder + "/mri/",ext=".nii",zip_file=False, redo=False,verbose=False)
  403. os.remove(mask_img)
  404. elif verbose==True:
  405. print('Brain segmentation alredy exists for sub-' +ID + " here: " + o_folder)
  406. # --- Atlas roi extraction
  407. if extract_rois:
  408. print('Brain roi extractraction is running for sub-' +ID)
  409. os.chdir(self.config["tools_dir"]["main_codes"] + '/code/spm/')# need to change the directory to find the CAT12 function
  410. eng = matlab.engine.start_matlab()
  411. eng.CAT12_Rois(unzip_i_img, self.config["tools_dir"]["main_codes"] + '/code/spm/Cat12_log/',self.spm_dir)
  412. return o_folder + "/mri/"
  413. def coregistration_func2anat(self,ID=None,func_img=None,anat_img=None,filenames_func4D="",o_folder=None,ses_name='',task_name='',tag='',threshold=False,redo=False,verbose=True):
  414. '''
  415. Attributes:
  416. ----------
  417. ID: name of the participant
  418. i_img: filename
  419. input filename of functional image 3D (str, default:None, an error will be raise)
  420. anat_img: filename
  421. anatomical image (str, default:None, an error will be raise)
  422. filenames_func4D: filename
  423. 4d functional image
  424. o_folder: output folder (str, default:None, the input folder will be used)
  425. ses_name: if the session have any specific name (ses- in BIDS format)
  426. task_name: if the run have any specific name (task- in BIDS format)
  427. img_type: the type of input image should be specify "func" or "anat"
  428. Outputs:
  429. ----------
  430. '''
  431. if ID==None:
  432. raise Warning("Please provide the ID of the participant, ex: _.stc(ID='A001')")
  433. if anat_img==None or func_img==None:
  434. raise Warning("Please provide filename of the source_img and ref_img ")
  435. # Check if unzip file exists
  436. if os.path.splitext(func_img)[1]==".gz":
  437. unzip_img=brsc_utils.unzip_file(func_img,o_folder="/"+os.path.dirname(func_img)+"/",ext=".nii",redo=False,verbose=False)
  438. func_img=unzip_img
  439. if os.path.splitext(anat_img)[1]==".gz":
  440. unzip_img=brsc_utils.unzip_file(anat_img,o_folder="/"+os.path.dirname(anat_img)+"/",ext="_SPM.nii",redo=False,verbose=False)
  441. anat_img=unzip_img
  442. if filenames_func4D!="" and os.path.splitext(filenames_func4D)[1]==".gz":
  443. unzip_img=brsc_utils.unzip_file(filenames_func4D,o_folder="/"+os.path.dirname(filenames_func4D)+"/",ext=".nii",redo=False,verbose=False)
  444. filenames_func4D=unzip_img
  445. preprocess_dir=self.config["main_dir"]+ self.config["preprocess_dir"]["bmpd_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["preprocess_dir"]["main_dir"]
  446. if o_folder==None:
  447. o_folder=preprocess_dir + "sub-" + ID+ "/" + ses_name + "/func/"+task_name+ "/" +self.config["preprocess_dir"]["func_coreg"]["brain"]
  448. if not os.path.exists(preprocess_dir + "sub-" + ID+ "/" + ses_name + "/func/"+task_name+ "/" +self.config["preprocess_dir"]["func_coreg"]["main"]):
  449. os.mkdir(preprocess_dir + "sub-" + ID+ "/" + ses_name + "/func/"+task_name+ "/" +self.config["preprocess_dir"]["func_coreg"]["main"])
  450. if not os.path.exists(o_folder):
  451. os.makedirs(o_folder)# create output folder if not exists
  452. # run coregistration
  453. o_filename=o_folder + os.path.basename(func_img).split(".")[0] + tag + ".nii"
  454. if filenames_func4D!="":
  455. o_4d_filename=o_folder + os.path.basename(filenames_func4D).split(".")[0] + tag + ".nii"
  456. if not os.path.exists(o_filename) or redo==True:
  457. print('Coregistration is running for sub-' +ID)
  458. os.chdir(self.config["tools_dir"]["main_codes"] + '/code/spm/')# need to change the directory to find the CAT12 function
  459. eng = matlab.engine.start_matlab()
  460. eng.BrainCoregistration(func_img, anat_img,os.path.dirname(filenames_func4D),os.path.basename(filenames_func4D),self.spm_dir)
  461. #rename the output
  462. spm_out=os.path.dirname(func_img) + "/r" + os.path.basename(func_img)
  463. os.rename(spm_out,o_filename)
  464. if filenames_func4D!="":
  465. spm_out=os.path.dirname(filenames_func4D) + "/r" + os.path.basename(filenames_func4D)
  466. os.rename(spm_out,o_4d_filename)
  467. else:
  468. print('Brain functional coregistration into anat space was already done, set redo=True to run again the coregistration')
  469. return (o_filename, o_4d_filename) if filenames_func4D != "" else (o_filename,)
  470. if threshold:
  471. # b. Transform the output image in a binary image
  472. string2="fslmaths "+o_img+" -thr "+threshold+" -bin " + o_img
  473. #os.system(string2)
  474. else:
  475. if verbose:
  476. print("Tranformation was already applied put redo=True to redo that step")
  477. return o_img
  478. def normalisation(self,ID=None,warp_file=None,coreg2anat_file=None,o_file=None,brain_mask=None,redo=False):
  479. '''
  480. ID: name of the participant
  481. warp_file <filename>: warping field from anat to MNI space
  482. coreg2anat_file <filename>: functional image coregister into anat space
  483. o_file <filename>: output filename
  484. brain_mask <filename>: apply a mask
  485. '''
  486. if not os.path.exists(o_file) or redo==True:
  487. os.chdir(self.config["tools_dir"]["main_codes"] +'/code/spm/') # need to change the directory to find the function
  488. eng = matlab.engine.start_matlab()
  489. print(eng.BrainNormalisation(warp_file,coreg2anat_file,self.spm_dir)) #for quality check
  490. o_files_def=os.path.split(coreg2anat_file)[0]+'/w'+coreg2anat_file.split('/')[-1] # default output
  491. print(o_files_def)
  492. os.rename(o_files_def,o_file) #move and rename the file
  493. string="fslmaths "+o_file+ " -nan " +o_file; os.system(string)# remove nan values
  494. os.remove(o_file) # remove .nii file and keep only .nii.gz
  495. if brain_mask is not None:
  496. string='fslmaths '+o_file+'.gz -mas '+brain_mask+' '+o_file
  497. os.system(string)
  498. #os.remove(func_norm_file)
  499. print("output: " + os.path.basename(o_file))
  500. return print('Normalisation into MNI space done')
  501. else:
  502. return print('Normalisation into MNI space was already done, set redo=True to run again the coregistration')
  503. def dartel_norm(self,ID=None,dartel_template=None,warp_file=None,i_file=None,o_file=None,brain_mask=None,resolution=[2,2,2],redo=False):
  504. if dartel_template==None or warp_file==None or i_file==None:
  505. raise Warning("Please provide filename of the template, warping field and input_img")
  506. if (not os.path.exists(o_file) and not os.path.exists(o_file +".gz")) or redo==True:
  507. os.chdir(self.config["tools_dir"]["main_codes"] +'/code/spm/') # need to change the directory to find the function
  508. eng = matlab.engine.start_matlab()
  509. matlab_resolution = ' '.join(map(str, resolution)) # transform the resolution in a string
  510. print(eng.BrainDartelNormalisation(dartel_template,warp_file,i_file,self.spm_dir,matlab_resolution)) #for quality check
  511. o_files_def=glob.glob(os.path.split(i_file)[0]+'/*w'+i_file.split('/')[-1])[0] # default output
  512. os.rename(o_files_def,o_file) #move and rename the file
  513. string="fslmaths "+o_file+ " -nan " +o_file; os.system(string)# remove nan values
  514. os.remove(o_file) # remove .nii file and keep only .nii.gz
  515. if brain_mask is not None:
  516. string='fslmaths '+o_file+'.gz -mas '+brain_mask+' '+o_file
  517. os.system(string)
  518. o_file =o_file +".gz"
  519. #os.remove(func_norm_file)
  520. print("output: " + os.path.basename(o_file))
  521. print('Normalisation into MNI space done')
  522. return o_file
  523. else:
  524. if brain_mask is not None:
  525. o_file =o_file +".gz"
  526. print('Normalisation into MNI space was already done, set redo=True to run again ')
  527. return o_file
  528. ########################################################################
  529. ########################################################################
  530. class Preprocess_Sc:
  531. '''
  532. The Preprocess class is used to compute spinal cord preprocessings
  533. Motion correction, Segmentation and coregistration steps are available
  534. Attributes
  535. ----------
  536. config : dict
  537. '''
  538. def __init__(self, config, verbose=True):
  539. self.config = config # load config info
  540. self.participant_IDs = self.config.get("participants_IDs") or self.config.get("participants_IDs_ALL") or []# list of the participants to analyze
  541. self.main_dir=self.config["main_dir"] # main drectory of the project
  542. def moco_mask(self,ID=None,i_img=None,o_folder=None, radius_size=15,task_name='',ses_name='',tag='',manual=False,redo_ctrl=False,redo_mask=False,verbose=True):
  543. '''
  544. This function will create mask arround a centerline
  545. https://spinalcordtoolbox.com/user_section/command-line.html#sct-get-centerline
  546. https://spinalcordtoolbox.com/user_section/command-line.html#sct-create-mask
  547. to do: rename name of the output folder by moco_mask
  548. Attributes:
  549. ----------
  550. ID: name of the participant
  551. i_img: input filename of functional images (str, default:None, an error will be raise)
  552. o_img: output folder name filename (str, default:None, the input folder will be used)
  553. radius_size: value of the diameter of the surrounding mask in voxels (default is 15)
  554. sesname: if the session have any specific name (ses- in BIDS format)
  555. task_name: if the run have any specific name (task- in BIDS format)
  556. Outputs:
  557. ----------
  558. Image cropped
  559. '''
  560. if ID==None:
  561. raise Warning("Please provide the ID of the participant, ex: _.stc(ID='A001')")
  562. if i_img==None:
  563. raise Warning("Please provide filename of the input file")
  564. #1. create ouput folder
  565. preprocess_dir=self.config["main_dir"]+ self.config["preprocess_dir"]["bmpd_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["preprocess_dir"]["main_dir"]
  566. if o_folder is None : # gave the default folder name if not provided
  567. o_folder=preprocess_dir + "sub-" + ID+ "/" + ses_name + "/func/"+task_name+ "/" + self.config["preprocess_dir"]["func_mask"]
  568. if not os.path.exists(o_folder):
  569. os.makedirs(o_folder+ "/spinalcord/")
  570. #2 Create the centerline, if manual centerline is needed change: -method optic by -method viewer
  571. centerline_f=o_folder + "/spinalcord/" + os.path.basename(i_img).split(".")[0] + "_centerline"
  572. mask_f=o_folder + "/spinalcord/" + os.path.basename(i_img).split(".")[0] + "_mask.nii.gz"
  573. #if not os.path.exists(mask_f):
  574. # if o_folder + "/spinalcord/" + os.path.basename(i_img).split(".")[0] + "_seg.nii.gz":
  575. # shutil.copy(o_folder + "/spinalcord/" + os.path.basename(i_img).split(".")[0] + "_seg.nii.gz",mask_f)
  576. # select the method
  577. if manual==True:
  578. method="viewer"
  579. else:
  580. method="optic"
  581. if not os.path.exists(mask_f) or redo_ctrl==True:
  582. print("Centerline for sub-" + ID)
  583. string_cntr="sct_get_centerline -i "+ i_img +" -o "+centerline_f +" -c t1 -method "+ method+" -centerline-algo bspline"
  584. os.system(string_cntr)
  585. if not os.path.exists(mask_f) or redo_mask==True:
  586. #3. Create the mask arround the centerline
  587. print("Create a mask for sub-" + ID)
  588. string_mask="sct_create_mask -i "+i_img +" -p centerline,"+centerline_f+".nii.gz -size "+ str(radius_size)+ " -o " + mask_f
  589. os.system(string_mask)
  590. if verbose ==True:
  591. print("Mask created, check the outputs files in fsleyes by copy and paste:")
  592. print("fsleyes " + mask_f)
  593. print("use manual=False if the centerline need manual corrections")
  594. return mask_f, centerline_f+'.nii.gz'
  595. def moco(self,ID=None,i_img=None,mask_img=None,o_folder=None,params=None,
  596. ses_name='',task_name='',tag='',abs_moco_param=False,plot_show=True,redo=False,verbose=True):
  597. '''
  598. This function will correct the spinal cord images for motion using the sct toolbox
  599. Calculate framewise displacement (FD)
  600. Plot the motion parameters
  601. https://spinalcordtoolbox.com/user_section/command-line.html#sct-fmri-moco
  602. to do: rename name of the output folder by moco_mask
  603. recalculate volume wise motion parameters : erros in the SCT
  604. Attributes:
  605. ----------
  606. ID: name of the participant
  607. i_img: input filename of functional images 4D (str, default:None, an error will be raise)
  608. mask_img: Binary mask to limit voxels considered by the registration metric (str, default:None, an error will be raise)
  609. o_folder: output folder (str, default:None, the input folder will be used)
  610. params: see sct toolbox for more details, should not be modified across participants
  611. ses_name: if the session have any specific name (ses- in BIDS format)
  612. task_name: if the run have any specific name (task- in BIDS format)
  613. abs_moco_param <Bool> optional, defautl=False: the default moco params were wrongly calculated by the sct toolbox, we propose to calculate the absolute mean value instead
  614. Outputs:
  615. ----------
  616. The outputs of the motion correction process are:
  617. - the motion-corrected fMRI volumes: *_sc_moco.nii & one temporary file for each volume
  618. - the time average of the corrected fMRI volumes: - Time average of corrected vols: *_sc_moco_mean.nii
  619. - a time-series with 1 voxel in the XY plane, for the X and Y motion direction (two separate files).
  620. - a TSV file with the slice-wise average of the motion correction for XY (one file), that can be used for Quality Control.
  621. - Motion corrected volumes:
  622. - Time-series with 1 voxel in the XY plane: *_sc_moco_params_X.nii & params_Y.nii
  623. - Slice-wise average of MOCO for XY : moco_params.tsv & moco_params.txt
  624. '''
  625. if ID==None:
  626. raise Warning("Please provide the ID of the participant, ex: _.stc(ID='A001')")
  627. if i_img==None:
  628. raise Warning("Please provide filename of the input file")
  629. if mask_img==None:
  630. raise Warning("Please provide filename of the mask file")
  631. preprocess_dir=self.config["main_dir"]+ self.config["preprocess_dir"]["bmpd_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["preprocess_dir"]["main_dir"]
  632. #1. create ouput folder
  633. if o_folder is None : # gave the default folder name if not provided
  634. o_folder=preprocess_dir + "sub-" + ID+ "/" + ses_name + "/func/"+task_name+ "/" +self.config["preprocess_dir"]["func_moco"]
  635. if not os.path.exists(o_folder):
  636. os.mkdir(o_folder)
  637. if not os.path.exists(o_folder+ "/spinalcord/"):
  638. os.mkdir(o_folder + "/spinalcord/")
  639. # Define moco paramaters (should be the same for the entire dataset)
  640. if params==None:
  641. params = 'poly=0,smooth=1,metric=MeanSquares,gradStep=1,sampling=0.2'#,numTarget=' + str(num_target)
  642. moco_file= o_folder+"/spinalcord/"+ os.path.basename(i_img).split(".")[0] + "_moco.nii.gz"
  643. moco_mean_file= o_folder+"/spinalcord/"+ os.path.basename(i_img).split(".")[0] + "_moco_mean.nii.gz"
  644. print(moco_file)
  645. if not os.path.exists(moco_file) or redo==True:
  646. #print(mask_img)
  647. print(">>>>> Moco is running for the spinal cord image of the sub-" + ID + " ")
  648. string="sct_fmri_moco -i "+i_img+" -m "+mask_img+" -param "+params+" -ofolder "+o_folder + "/spinalcord/"+" -x spline -g 1 -r 1"
  649. print(string)
  650. os.system(string)
  651. # # Calculating the slice-wise absolute average moco estimate
  652. params_txt=os.path.dirname(moco_file) + '/moco_params_abs.txt'
  653. if abs_moco_param==True and not os.path.exists(params_txt):
  654. motion_abs={}
  655. for dim in ["x","y"]:
  656. img=nib.load(glob.glob(os.path.dirname(moco_file) + "/moco_params_"+dim+".nii.gz")[0])
  657. data_array = img.get_fdata()
  658. motion_abs[dim]=np.mean(np.abs(np.squeeze(data_array)),axis=0)
  659. moco_param_2D=np.column_stack((motion_abs["x"], motion_abs["y"]))
  660. pd.DataFrame(moco_param_2D).to_csv(params_txt,index=False, header=None)
  661. # Read moco parameters (absolute or not)
  662. params_tsv=o_folder + "/spinalcord/" + 'moco_params.tsv'
  663. data=pd.read_csv(params_tsv, delimiter='\t')
  664. params_txt=params_tsv.split('.')[0] + '.txt'
  665. data.to_csv(params_txt,index=False, header=None)
  666. params_txt=params_tsv.split('.')[0] + '_abs.txt' if abs_moco_param else params_tsv.split('.')[0] + '.txt'
  667. params_data=pd.read_csv(params_txt, delimiter=',', header=None)
  668. # Plot moco parameters
  669. fig, axs = plt.subplots(1,1, figsize=(16, 5), facecolor='w', edgecolor='k')
  670. fig.tight_layout()
  671. fig.subplots_adjust(hspace = .5, wspace=.001)
  672. axs.plot(params_data[0]) # add axs[] if more than one run
  673. axs.plot(params_data[1])
  674. axs.set_title(ID + " " + task_name)
  675. axs.set_ylabel("Translation (mm)")
  676. axs.set_xlabel("Volumes")
  677. if not os.path.exists(params_txt.split(".")[0] + ".png") or redo==True:
  678. plt.savefig(params_txt.split(".")[0] + ".png")
  679. if plot_show==True:
  680. plt.show(block=False)
  681. # Calculate Framewise displacement (abs difference of displacement between each volumes)
  682. print('Framewise displacement, sub-' +ID)
  683. diff_X = np.abs(np.diff(params_data[0]))
  684. diff_Y = np.abs(np.diff(params_data[1]))
  685. meandiff=[np.mean(diff_X),np.mean(diff_Y)]
  686. print('Diff_X: ' + str(round(meandiff[0],3)) + ' mm')
  687. print('Diff_Y: ' + str(round(meandiff[1],3)) + ' mm')
  688. if not os.path.exists(o_folder + '/spinalcord/FD_mean.txt') or redo==True:
  689. np.savetxt(o_folder + '/spinalcord/FD_mean.txt', [meandiff])
  690. return moco_file, moco_mean_file
  691. def segmentation(self,ID=None,i_img=None,i_gm_img=None,ctr_img=None,o_folder=None,task_name='',ses_name='',tag='',tissue=None,img_type="anat",contrast_anat="t1",redo=False,verbose=True):
  692. '''
  693. This function will segment the spinal cord:
  694. - GM from the anatomical image
  695. - GM + Wm from the func image
  696. Visual inspection and manual corrections are sometime needed
  697. https://spinalcordtoolbox.com/user_section/command-line.html#sct-propseg
  698. to do:
  699. - supress the directory /func/spinalcord/cord/
  700. Attributes:
  701. ----------
  702. ID: name of the participant
  703. i_img: input filename of functional images 3D (str, default:None, an error will be raise)
  704. ctr_img: Centerline image, need for img_type="func" (str, default:None)
  705. o_folder: output folder (str, default:None, the input folder will be used)
  706. ses_name: if the session have any specific name (ses- in BIDS format)
  707. task_name: if the run have any specific name (task- in BIDS format)
  708. img_type: the type of input image should be specify "func" or "anat"
  709. Outputs:
  710. ----------
  711. '''
  712. if ID==None:
  713. raise Warning("Please provide the ID of the participant, ex: _.stc(ID='A001')")
  714. if i_img==None:
  715. raise Warning("Please provide filename of the input file")
  716. if ctr_img==None and img_type=="func":
  717. raise Warning("Please provide centerline filename")
  718. #1. create ouput folder
  719. preprocess_dir=self.config["main_dir"]+ self.config["preprocess_dir"]["bmpd_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["preprocess_dir"]["main_dir"]
  720. if o_folder is None : # gave the default folder name if not provided
  721. if img_type=="func":
  722. o_folder=preprocess_dir + "sub-" + ID+ "/" + ses_name + "/func/"+task_name+ "/" +self.config["preprocess_dir"]["func_seg"]["spinalcord"]
  723. print(o_folder)
  724. else:
  725. o_folder=preprocess_dir + "sub-" + ID+ "/" + ses_name + "/anat/"+self.config["preprocess_dir"][img_type+"_sc_seg"]
  726. print(o_folder)
  727. if not os.path.exists(o_folder):
  728. os.makedirs(o_folder)
  729. # 2. output filename
  730. if img_type=="func" and tag=='':
  731. o_folder=o_folder
  732. tag="mean_seg.nii.gz"; o_img= o_folder +os.path.basename(i_img).split('mean.')[0] + tag
  733. elif img_type!="func"and tag=='':
  734. tag="_seg.nii.gz"; o_img= o_folder +os.path.basename(i_img).split('.')[0] + tag
  735. elif tissue=="wm":
  736. o_img= o_folder +os.path.basename(i_img).split('_cord')[0] + tag
  737. else:
  738. o_img= o_folder +os.path.basename(i_img).split('.')[0] + tag
  739. #3. run segmentation
  740. if len([file for file in os.listdir(o_folder) if re.search(tag.split("*")[-1], file)]) < 1 or redo==True:
  741. if img_type=="func":
  742. string="sct_deepseg_sc -i " +i_img +" -c t2s -centerline file -file_centerline "+ctr_img +" -o " + o_img
  743. elif img_type!="func":
  744. if tissue=="gm":
  745. string="sct_deepseg_gm -i " +i_img +" -thr 0.01 -o " + o_img
  746. elif tissue=="wm":
  747. string1="fslmaths " + i_img+" -sub " + i_gm_img +" " + o_img
  748. os.system(string1)
  749. string="fslmaths " + o_img+" -thr 0 " + o_img
  750. elif tissue=="csf":
  751. string1="sct_propseg -i " +i_img +" -c "+contrast_anat+" -CSF -o " + o_img
  752. os.system(string1)
  753. csf_mask=glob.glob(os.path.dirname(o_img) + "/*_CSF_*")[0]
  754. cordcsf_mask=os.path.dirname(csf_mask) + "/"+ os.path.basename(csf_mask).split("CSF")[0] + "cordCSF_seg.nii.gz"
  755. string="fslmaths " + o_img + " -add " + csf_mask +" -bin " + cordcsf_mask
  756. else:
  757. string="sct_deepseg_sc -i " +i_img +" -c "+contrast_anat+" -thr 0.01 -o " + o_img
  758. print(">>>>> Segmentation is running for the "+img_type + " image of the sub-" + ID + " ")
  759. os.system(string)
  760. print("Check the output and correct manually if needed" )
  761. print("fsleyes " + o_img)
  762. elif os.path.exists(glob.glob(o_img)[0]) and verbose==True:
  763. print(">>>>> Segmentation file already exists for the "+img_type + "image of the sub-" + ID + " ")
  764. print("fsleyes " + o_img)
  765. print(" ")
  766. return o_img
  767. def label_vertebrae(self,ID=None,i_img=None,nb_labels=15,o_folder=None,ses_name='',task_name='',tag='',run_local=None,redo=False,verbose=True):
  768. '''
  769. Labelisation on the image
  770. https://spinalcordtoolbox.com/user_section/command-line.html#sct-label-utils
  771. Attributes:
  772. ----------
  773. ID: name of the participant
  774. i_img: input filename of functional images 3D (str, default:None, an error will be raise)
  775. nb_labels: total number of labels needed (int, default= 15)
  776. o_folder: output folder (str, default:None, the input folder will be used)
  777. ses_name: if the session have any specific name (ses- in BIDS format)
  778. task_name: if the run have any specific name (task- in BIDS format)
  779. run_local: to run the command on local laptop
  780. Outputs:
  781. ----------
  782. '''
  783. if ID==None:
  784. raise Warning("Please provide the ID of the participant, ex: _.stc(ID='A001')")
  785. if i_img==None:
  786. raise Warning("Please provide filename of the input file")
  787. #1. create ouput folder
  788. if o_folder is None : # gave the default folder name if not provided
  789. o_folder=self.config["manual_dir"] + "/sub-" + ID+ "/"+ses_name+"/anat/"
  790. if not os.path.exists(o_folder):
  791. if not os.path.exists(self.config["manual_dir"] + "/sub-" + ID):
  792. os.mkdir(self.config["manual_dir"] + "/sub-" + ID)
  793. os.mkdir(o_folder)
  794. #2. Create the labels
  795. o_img= o_folder + os.path.basename(i_img).split(".")[0] + '_space-orig_label-ivd_mask.nii.gz'
  796. if not os.path.exists(o_img) or redo==True:
  797. nb=np.array(range(1,nb_labels+1)) # array with label numbers
  798. string="sct_label_utils -i " +i_img + " -o "+o_img+" -create-viewer " + ', '.join(map(str, nb)).replace(" ","") #1,2,3,4,5,6,7,8,9,10,11,12,13,14,15"
  799. print("Place labels at the posterior tip of each inter-vertebral disc for sub-" + ID)
  800. if run_local:
  801. print("Copy and path the command line locally, with corrected path: ")
  802. i_img_local=run_local +i_img.split("dataset")[1]
  803. o_img_local=run_local +o_img.split("dataset")[1]
  804. string_local="sct_label_utils -i " +i_img_local + " -o "+o_img_local+" -create-viewer " + ', '.join(map(str, nb)).replace(" ","") #1,2,3,4,5,6,7,8,9,10,11,12,13,14,15"
  805. print(string_local)
  806. else:
  807. os.system(string)
  808. else:
  809. print(o_folder + '*_space-orig_label-ivd_mask.nii.gz')
  810. o_img= glob.glob(o_folder + '*_label-ivd_mask.nii.gz')[0]
  811. if verbose==True:
  812. print(">>>>> Check if label file already exists for sub-" + ID)
  813. print(" ")
  814. return o_img
  815. def coreg_anat2PAM50(self,ID=None,i_img=None,o_folder=None,seg_img=None,labels_img=None,img_type="t2",param=None,ses_name='',task_name='',tag='T2w',redo=False,verbose=True):
  816. '''
  817. Register anat to template and warp the template into the anatomical space
  818. https://spinalcordtoolbox.com/user_section/command-line.html#sct-register-to-template
  819. https://spinalcordtoolbox.com/user_section/command-line.html#sct-warp-template
  820. Attributes:
  821. ----------
  822. ID: name of the participant
  823. i_img <filename>: input filename of functional images 3D (str, default:None, an error will be raise)
  824. o_folder <folder dir>: output folder (str, default:None, the input folder will be used)
  825. ses_name <str>: if the session have any specific name (ses- in BIDS format)
  826. task_name <str>: if the run have any specific name (task- in BIDS format)
  827. img_type <filename>: Contrast to use for registration. (default: t2)
  828. param <list>: Parameters for registration default:
  829. "step=1,type=seg,algo=centermassrot:step=2,type=im,algo=syn,iter=5,slicewise=1,metric=CC,smooth=0"
  830. tag <str>: specify tag for the outputs. default T2w
  831. Outputs:
  832. ----------
  833. '''
  834. if ID==None:
  835. raise Warning("Please provide the ID of the participant, ex: _.stc(ID='A001')")
  836. if i_img==None or seg_img==None or labels_img==None:
  837. raise Warning("Please provide filename of the input file (i_img), segmentation file (seg_img) and inter disk labels(labels_img)")
  838. if param==None:
  839. param= "step=1,type=seg,algo=centermassrot:step=2,type=im,algo=syn,iter=5,slicewise=1,metric=CC,smooth=0"
  840. #1. create ouput folder
  841. preprocess_dir=self.config["main_dir"]+ self.config["preprocess_dir"]["bmpd_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["preprocess_dir"]["main_dir"]
  842. if o_folder is None : # gave the default folder name if not provided
  843. o_folder=preprocess_dir + "sub-" + ID+ "/"+ses_name+"/anat/"+self.config["preprocess_dir"][tag +"_sc_coreg"]
  844. if not os.path.exists(o_folder):
  845. os.mkdir(o_folder)
  846. #2. Coregistration between anat and template
  847. warp_from_anat2PAM50=o_folder+"/sub-"+ID+"_from-"+tag+"_to-PAM50_mode-image_xfm.nii.gz" # warping field form anat to PAM50
  848. warp_from_PAM502anat=o_folder+"/sub-"+ID+ "_from-PAM50_to-"+tag+"_mode-image_xfm.nii.gz" # warping field form PAM50 to anat
  849. # check if a manual file exists
  850. if os.path.exists(self.config["manual_dir"] + "sub-" + ID+ "/"+ses_name+"/anat/"+ os.path.basename(seg_img)):
  851. print("Segmentation file will be the manually corrected file")
  852. if not os.path.exists(warp_from_anat2PAM50) or redo==True:
  853. string_coreg="sct_register_to_template -i "+i_img +" -s " + seg_img+" -ldisc " + labels_img +" -c " + img_type +" -param " + param +" -ofolder " +o_folder
  854. print(">>>>> Registration step is running for sub-" + ID)
  855. os.system(string_coreg)
  856. # rename the warping files
  857. os.rename(os.path.join(o_folder, "warp_anat2template.nii.gz"), warp_from_anat2PAM50)
  858. os.rename(os.path.join(o_folder, "warp_template2anat.nii.gz"), warp_from_PAM502anat)
  859. # 3. create the template in anat
  860. string_template="sct_warp_template -d " + i_img +" -w " + warp_from_PAM502anat +" -s 1 -ofolder " +o_folder +"/template_in_" + tag + " -a 0 -s 0"
  861. os.system(string_template)
  862. if verbose==True:
  863. print("Done here: " + o_folder)
  864. else:
  865. if verbose==True:
  866. print(">>>>> Registration file anat2template already exists for sub-" + ID)
  867. print(" ")
  868. return warp_from_PAM502anat, warp_from_anat2PAM50
  869. def coreg_img2PAM50(self,ID=None,i_img=None,o_folder=None,i_seg=None,PAM50_cord=None,PAM50_t2=None,mask_img=None,img_type="func",coreg_type="slicereg",initwarp=None,initwarpinv=None,param=None,ses_name='',task_name='',redo=False,verbose=True):
  870. '''
  871. Register mean functional image to template
  872. https://spinalcordtoolbox.com/user_section/command-line.html#sct-register-multimodal
  873. Attributes:
  874. ----------
  875. ID: name of the participant
  876. i_img <filename>: functional mean image filename 3D (str, default:None, an error will be raise)
  877. i_seg <filename>: segmentation file of the functional image 3D
  878. PAM50_cord <filename>: PAM50 cord image cropped in restrictive shape to reduce computational time
  879. init_wrap <filename>: initial warping field (usually from PAM502anat)
  880. initwarpinv <filename>: initial inverse warping field (usually from anat2PAM50)
  881. o_folder <folder dir>: output folder (str, default:None, the input folder will be used)
  882. ses_name <str>: if the session have any specific name (ses- in BIDS format)
  883. task_name <str>: if the run have any specific name (task- in BIDS format)
  884. param <list>: Parameters for registration default:
  885. "step=1,type=imseg,algo=centermassrot,rot_method=hog,metric=MeanSquares,smooth=2:step=2,type=im,algo=syn,metric=CC,iter=3,slicewise=1"
  886. Outputs:
  887. ----------
  888. '''
  889. if ID==None:
  890. raise Warning("Please provide the ID of the participant, ex: _.stc(ID='A001')")
  891. if i_img==None or i_seg==None or initwarp==None or initwarpinv==None:
  892. raise Warning("Please provide filename of the input file (i_img), segmentation files (func_seg, PAM50_cord) and warping fields(init_wrap, initwarpinv)")
  893. if PAM50_cord==None:
  894. PAM50_cord=self.config["tools_dir"]["main_codes"] + "/template/"+self.config["PAM50_cord"]
  895. if PAM50_t2==None:
  896. PAM50_t2=self.config["tools_dir"]["main_codes"] + "/template/"+ self.config["PAM50_t2"]
  897. if param==None:
  898. if img_type=="func" and coreg_type=="slicereg":
  899. param="step=1,type=seg,algo=slicereg,metric=MeanSquares,smooth=2:step=2,type=im,algo=syn,metric=CC,iter=3,slicewise=1"
  900. elif img_type=="func" and coreg_type=="centermass": # this option can be used for very curvate spine
  901. param="step=1,type=seg,algo=centermass,metric=MeanSquares,smooth=2:step=2,type=im,algo=syn,metric=CC,iter=3,slicewise=1"
  902. elif img_type=="t2s":
  903. param="step=1,type=seg,algo=rigid:step=2,type=seg,metric=CC,algo=bsplinesyn,slicewise=1,iter=3:step=3,type=im,metric=CC,algo=syn,slicewise=1,iter=2"
  904. elif img_type=="mtr":
  905. param='step=1,type=seg,algo=centermass,metric=MeanSquares:step=2,algo=bsplinesyn,type=seg,slicewise=1,iter=5'
  906. elif img_type=="dwi":
  907. param='step=1,type=seg,algo=centermass,metric=MeanSquares:step=2,algo=bsplinesyn,type=seg,slicewise=1,iter=5'
  908. #1. create ouput folder
  909. preprocess_dir=self.config["main_dir"]+ self.config["preprocess_dir"]["bmpd_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["preprocess_dir"]["main_dir"]
  910. if o_folder is None : # gave the default folder name if not provided
  911. o_folder=preprocess_dir + "sub-" + ID+"/"+ses_name+ "/func/"+ task_name+self.config["preprocess_dir"]["func_coreg"]["spinalcord"]
  912. if img_type=="func":
  913. if not os.path.exists(preprocess_dir + "sub-" + ID+"/"+ses_name+ "/func/"+ task_name+self.config["preprocess_dir"]["func_coreg"]["main"]):
  914. os.mkdir(preprocess_dir + "sub-" + ID+"/"+ses_name+ "/func/"+ task_name+self.config["preprocess_dir"]["func_coreg"]["main"])
  915. if not os.path.exists(o_folder):
  916. os.makedirs(o_folder,exist_ok=True)
  917. #2. Coregistration between anat and template
  918. o_img= o_folder+ os.path.basename(i_img).split('.')[0]+'_coreg_in_PAM50.nii.gz'
  919. o_warpinv_img=o_folder+ "/sub-"+ID+"_from-PAM50_to_"+img_type+"_mode-image_xfm.nii.gz"
  920. o_warp_img=o_folder+ "/sub-"+ID+'_from-'+img_type+'_to_PAM50_mode-image_xfm.nii.gz'
  921. if not os.path.exists(o_img) or redo==True:
  922. string_coreg="sct_register_multimodal -d "+PAM50_t2 +" -dseg " + PAM50_cord+" -i " + i_img +" -iseg " + i_seg +" -param " + param +" -initwarp "+initwarp+ " -initwarpinv " + initwarpinv +" -owarp " + o_warp_img +" -owarpinv "+ o_warpinv_img +" -ofolder "+ o_folder + " -x spline"
  923. print(">>>>> Registration step is running for sub-" + ID)
  924. os.system(string_coreg)
  925. os.rename(o_folder+ os.path.basename(i_img).split('.')[0]+ "_reg.nii.gz",o_img)
  926. if verbose==True:
  927. print("Done here: " + o_folder)
  928. else:
  929. if verbose==True:
  930. print(">>>>> Registration between func image and PAM50 already exists for sub-" + ID)
  931. print(" ")
  932. return (o_folder, o_warp_img,o_warpinv_img)
  933. def apply_warp(self,i_img=None,ID=None,o_folder=None,dest_img=None,warping_field=None,ses_name='',task_name='',tag='_w',threshold=None,mean=False,method='spline',redo=False,verbose=True,n_jobs=1):
  934. '''
  935. Apply warping field to spinalcord input image in a destination image using sct_apply_transfo (sct toolbox)
  936. https://spinalcordtoolbox.com/user_section/command-line.html#sct-apply-transfo
  937. Attributes
  938. ----------
  939. ID: name of the participant
  940. i_img <filename>: input image filename 3D or 3D (str, default:None, an error will be raise)
  941. o_folder <folder dir>: output folder (str, default:None, the input folder will be used)
  942. dest_img <filename>: destination filename
  943. warping_fields <filename>: Transformation(s), which can be warping fields (nifti image) or affine transformation matrix (text file). Separate with space.
  944. ses_name <str>: if the run have any specific name (ses- in BIDS format)
  945. task_name <str>: if the run have any specific name (task- in BIDS format)
  946. tag <str>: specify a tag for the output
  947. redo <Bolean> optional, to binarize the output file (default: False)
  948. redo <Bolean> optional, to rerun the analysis put True (default: False)
  949. return
  950. ----------
  951. o_img <filename>
  952. '''
  953. if i_img==None or warping_field==None or ID==None:
  954. raise Warning("Please provide filename of the input file (i_img), transformation files (warping_fields) and participant(s) ID (ID)")
  955. i_imgs=[i_img] if isinstance(i_img,str) else i_img
  956. warping_fields=[warping_field] if isinstance(warping_field,str) else warping_field
  957. IDs=[ID] if isinstance(ID,str) else ID
  958. if dest_img==None:
  959. dest_img=[]
  960. for ID_nb in enumerate(i_imgs):
  961. dest_img.append(self.config["tools_dir"]["main_codes"] + "/template/"+ self.config["PAM50_t2"])
  962. else:
  963. dest_img=[dest_img] if isinstance(dest_img,str) else dest_img
  964. if o_folder==None:
  965. o_folders=[]
  966. for i in range(len(warping_fields)):
  967. o_folders.append(os.path.dirname(warping_fields[i]) + "/")
  968. elif isinstance(o_folder,str):
  969. o_folders=[o_folder]
  970. else:
  971. o_folders=o_folder
  972. #define output filename:
  973. o_imgs=[]
  974. for ID_nb, filename in enumerate(i_imgs):
  975. #print(i_imgs[ID_nb])
  976. o_imgs.append(o_folders[ID_nb] + os.path.basename(i_imgs[ID_nb]).split('.')[0] + tag + ".nii.gz")
  977. if not os.path.exists(o_imgs[0]) or redo==True:
  978. print(" ")
  979. print(">>>>> Apply transformation is running with " + str(n_jobs)+ " parallel jobs on " +str(len(self.participant_IDs)) + " participant(s)")
  980. Parallel(n_jobs=n_jobs)(delayed(self._run_apply_warp)(i_img=i_imgs[ID_nb],
  981. dest_img=dest_img[ID_nb],
  982. warp_file=warping_fields[ID_nb],
  983. o_folder=o_folders[ID_nb],
  984. ID=IDs[ID_nb],
  985. tag=tag,
  986. threshold=threshold,
  987. mean=mean,
  988. method=method)
  989. for ID_nb in range(len(warping_fields)))
  990. else:
  991. if verbose:
  992. print("Tranformation was already applied put redo=True to redo that step")
  993. return o_imgs
  994. def _run_apply_warp(self,i_img,dest_img,warp_file,o_folder,ID,tag,threshold,mean,method):
  995. o_img= o_folder + os.path.basename(i_img).split('.')[0] + tag + ".nii.gz"
  996. string='sct_apply_transfo -i '+i_img+' -d '+dest_img+' -w '+warp_file+' -x '+method+' -o ' + o_img
  997. os.system(string)
  998. if threshold:
  999. #Transform the output image in a binary image
  1000. string2="fslmaths "+o_img+" -thr "+str(threshold)+" -bin " + o_img
  1001. os.system(string2)
  1002. if mean==True:
  1003. o_mean_img= o_folder + os.path.basename(i_img).split('.')[0] + tag + "_mean.nii.gz"
  1004. string='fslmaths '+o_img+' -Tmean '+o_mean_img
  1005. os.system(string)
  1006. print("New warped image was generated for " + ID)
  1007. return o_img
  1008. def csf_masks(self,ID=None,i_img=None,o_folder=None,task_name='',ses_name='',tag='',redo=False,verbose=True):
  1009. '''
  1010. Warp PAM50 csf into func space
  1011. Attributes:
  1012. ----------
  1013. ID: name of the participant
  1014. i_img: input filename of input mask images 3D, should be a cord mask (str, default:None, an error will be raise)
  1015. o_folder: output folder (str, default:None, the input folder will be used)
  1016. ses_name: if the session have any specific name (ses- in BIDS format)
  1017. task_name: if the run have any specific name (task- in BIDS format)
  1018. img_type: the type of input image should be specify "func" or "anat"
  1019. Outputs:
  1020. ----------
  1021. '''
  1022. if ID==None:
  1023. raise Warning("Please provide the ID of the participant, ex: _.stc(ID='A001')")
  1024. if i_img==None:
  1025. raise Warning("Please provide filename of the input file")
  1026. #1. create ouput folder
  1027. if o_folder is None : # gave the default folder name if not provided
  1028. o_folder=os.path.dirname(i_img) + "/"
  1029. if not os.path.exists(o_folder):
  1030. os.mkdir(o_folder)
  1031. # B. Use CSF+seg from T2 template image and check the output
  1032. run_proc('fslmaths {} -add {} {}'.format(template_out + '/template/PAM50_cord.nii.gz',template_out + '/template/PAM50_csf.nii.gz', template_out + '/template/PAM50_csfwmgm.nii.gz'))
  1033. #2. run dilatation of the mask
  1034. o_dil1_img= o_folder +os.path.basename(i_img).split('.')[0] + "_dilated1.nii.gz"
  1035. o_dil2_img= o_folder +os.path.basename(i_img).split('.')[0] + "_dilated2.nii.gz"
  1036. o_csf_img= o_folder +os.path.basename(i_img).split('.')[0] + "_"+tag+".nii.gz"
  1037. string1="fslmaths " +i_img +" -kernel 2D -dilM "+o_dil1_img
  1038. string2="fslmaths " +o_dil1_img +" -dilM "+o_dil2_img
  1039. string3="fslmaths " +o_dil2_img +" -sub "+i_img + " " +o_csf_img
  1040. if not os.path.exists(o_csf_img) or redo==True:
  1041. print(">>>>> Dilatation is running for the sub-" + ID + " ")
  1042. os.system(string1);os.system(string2)
  1043. os.system(string3);
  1044. print("Check the output and correct manually if needed" )
  1045. print("fsleyes " + o_csf_img)
  1046. os.remove(o_dil1_img);
  1047. elif os.path.exists(o_csf_img) and verbose==True:
  1048. print(">>>>> Surrounded file already exists for sub-" + ID + " ")
  1049. print("fsleyes " + o_csf_img)
  1050. print(" ")
  1051. return o_csf_img
  1052. ########################################################################
  1053. ########################################################################
  1054. class Preprocess_DWI_Sc:
  1055. def __init__(self, config):
  1056. '''
  1057. This function will allow for the different diffusion steps
  1058. Attributes
  1059. ----------
  1060. config : dict
  1061. '''
  1062. # Intialize the class
  1063. self.config = config # load config info
  1064. self.config["main_dir"]=config["main_dir"]
  1065. self.dwi_raw_files={}; self.dwi_files={}; self.warpT1w_PAM50_files=[]; self.warpPAM50_T1w_files=[]
  1066. self.dwi_files["nifti"]=[];self.dwi_files["bval"]=[];self.dwi_files["bvec"]=[];self.dwi_files["bvec_transposed"]=[];self.dwi_files["nifti_mean"]=[];
  1067. self.dwi_raw_files["nifti"]=[]; self.dwi_raw_files["bval"]=[];self.dwi_raw_files["bvec"]=[];
  1068. for ID_nb,ID in enumerate(self.config["participants_IDs"]):
  1069. tag_anat=""
  1070. if ID in config['files_specificities']["dwi"]:
  1071. tag_anat=config['files_specificities']["dwi"][ID]
  1072. preprocess_dir=self.config["main_dir"]+ self.config["preprocess_dir"]["bmpd_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["preprocess_dir"]["main_dir"]
  1073. os.makedirs(preprocess_dir + "sub-" + ID + "/dwi/",exist_ok=True)
  1074. # Initiate the nifit, bval and bvec, raw variables
  1075. raw_dir= self.config["main_dir"]+ self.config["bmpd_raw_dir"] if ID[0]=="P" else self.config["main_dir"]+ self.config["raw_dir"]
  1076. # check whether their are files specificity (like several runs for the same subject)
  1077. if ID in config['files_specificities']["dwi"]:
  1078. tag_anat=config['files_specificities']["dwi"][ID] # if you provided filename specifity it will be take into account
  1079. #Check the participant ID
  1080. IDbis=ID
  1081. if ID in config["double_IDs"]:
  1082. IDbis=self.config["double_IDs"][ID]
  1083. # Load the raw files
  1084. print(raw_dir + "/sub-" + ID+ "/"+ self.config["design_exp"]["ses_names"][0]+ "/dwi/sub-"+IDbis+tag_anat+"_dwi.nii.gz")
  1085. self.dwi_raw_files["nifti"].append(glob.glob(raw_dir + "/sub-" + ID+ "/"+ self.config["design_exp"]["ses_names"][0]+ "/dwi/sub-"+IDbis+tag_anat+"_dwi.nii.gz")[0])
  1086. self.dwi_raw_files["bvec"].append(glob.glob(raw_dir + "/sub-" + ID+ "/"+ self.config["design_exp"]["ses_names"][0]+ "/dwi/sub-"+IDbis+tag_anat+"_dwi.bvec")[0])
  1087. self.dwi_raw_files["bval"].append(glob.glob(raw_dir + "/sub-" + ID+ "/"+ self.config["design_exp"]["ses_names"][0]+ "/dwi/sub-"+IDbis+tag_anat+"_dwi.bval")[0])
  1088. # Copy and rename if necessary (some indivudals were not well named P was used instead of A)
  1089. if ID in config["double_IDs"]:
  1090. # rename with the right ID
  1091. self.dwi_files["nifti"].append(preprocess_dir + "sub-" + ID + "/dwi/sub-" + ID + os.path.basename(self.dwi_raw_files["nifti"][ID_nb]).split(ID[1:4])[1])
  1092. self.dwi_files["bvec"].append(preprocess_dir + "sub-" + ID + "/dwi/sub-" + ID + os.path.basename(self.dwi_raw_files["bvec"][ID_nb]).split(ID[1:4])[1])
  1093. self.dwi_files["bval"].append(preprocess_dir + "sub-" + ID + "/dwi/sub-" + ID + os.path.basename(self.dwi_raw_files["bval"][ID_nb]).split(ID[1:4])[1])
  1094. else:
  1095. # or keep the same ID when it was ok
  1096. self.dwi_files["nifti"].append(preprocess_dir + "sub-" + ID + "/dwi/" +os.path.basename(self.dwi_raw_files["nifti"][ID_nb]))
  1097. self.dwi_files["bvec"].append(preprocess_dir + "sub-" + ID + "/dwi/" +os.path.basename(self.dwi_raw_files["bvec"][ID_nb]))
  1098. self.dwi_files["bval"].append(preprocess_dir + "sub-" + ID + "/dwi/" +os.path.basename(self.dwi_raw_files["bval"][ID_nb]))
  1099. if not os.path.exists(self.dwi_files["bval"][ID_nb]):
  1100. shutil.copy(self.dwi_raw_files["nifti"][ID_nb], self.dwi_files["nifti"][ID_nb])
  1101. shutil.copy(self.dwi_raw_files["bvec"][ID_nb], self.dwi_files["bvec"][ID_nb])
  1102. shutil.copy(self.dwi_raw_files["bval"][ID_nb], self.dwi_files["bval"][ID_nb])
  1103. #transpose bvec file
  1104. self.dwi_files["bvec_transposed"].append(self.dwi_files["bvec"][ID_nb].split('.bvec')[0] + "_transposed.bvec")
  1105. if not os.path.exists(self.dwi_files["bvec_transposed"][ID_nb]):
  1106. string=f"sct_dmri_transpose_bvecs -bvec {self.dwi_files['bvec'][ID_nb]} -o {self.dwi_files['bvec'][ID_nb].split('.bvec')[0]}_transposed.bvec"
  1107. os.system(string)
  1108. # Calculate the raw mean image
  1109. self.dwi_files["nifti_mean"].append(self.dwi_files["nifti"][ID_nb].split('.')[0] + "_mean.nii.gz")
  1110. if not os.path.exists(self.dwi_files["nifti_mean"][ID_nb]):
  1111. string=f"fslmaths {self.dwi_files['nifti'][ID_nb]} -Tmean {self.dwi_files['nifti_mean'][ID_nb]}"
  1112. os.system(string)
  1113. def separate_b0_dwi(self,redo=False,verbose=False):
  1114. '''
  1115. Separate the b0 and dwi images
  1116. Inputs
  1117. ----------
  1118. redo: boolean
  1119. if True this step will be rerun even if the file already exists
  1120. verbose: boolean
  1121. if True will provide more information about the process
  1122. Returns
  1123. ----------
  1124. dwi_cut_files: dict
  1125. dictionary containing the dwi and b0 images
  1126. '''
  1127. self.dwi_dir=[]; self.dwi_cut_files={}
  1128. self.dwi_cut_files["dwi"]=[];self.dwi_cut_files["b0"]=[];self.dwi_cut_files["dwi_raw"]=[];self.dwi_cut_files["dwi_mean"]=[];self.dwi_cut_files["b0_mean"]=[]
  1129. self.dwi_cut_files["dwi_raw_mean"]=[];
  1130. for ID_nb,ID in enumerate(self.config["participants_IDs"]):
  1131. preprocess_dir=self.config["main_dir"]+ self.config["preprocess_dir"]["bmpd_dir"] if ID[0]=="P" else self.config["main_dir"] +self.config["preprocess_dir"]["main_dir"]
  1132. self.dwi_dir.append(preprocess_dir + "sub-" + ID + "/dwi/")
  1133. self.dwi_cut_files["dwi_raw"].append(self.dwi_dir[ID_nb] + os.path.basename(self.dwi_files["nifti"][ID_nb]))
  1134. self.dwi_cut_files["dwi_raw_mean"].append(self.dwi_dir[ID_nb] + os.path.basename(self.dwi_files["nifti"][ID_nb]).split('.')[0] + "_mean.nii.gz")
  1135. self.dwi_cut_files["dwi"].append(self.dwi_dir[ID_nb] + os.path.basename(self.dwi_files["nifti"][ID_nb]).split('.')[0] + "_dwi.nii.gz")
  1136. self.dwi_cut_files["dwi_mean"].append(self.dwi_dir[ID_nb] + os.path.basename(self.dwi_files["nifti"][ID_nb]).split('.')[0] + "_dwi_mean.nii.gz")
  1137. self.dwi_cut_files["b0"].append(self.dwi_dir[ID_nb] + os.path.basename(self.dwi_files["nifti"][ID_nb]).split('.')[0] + "_b0.nii.gz")
  1138. self.dwi_cut_files["b0_mean"].append(self.dwi_dir[ID_nb] + os.path.basename(self.dwi_files["nifti"][ID_nb]).split('.')[0] + "_b0_mean.nii.gz")
  1139. string="sct_dmri_separate_b0_and_dwi -i "+ self.dwi_files["nifti"][ID_nb] +" -bvec " + self.dwi_files["bvec"][ID_nb]+ " -bval " + self.dwi_files["bval"][ID_nb] +" -ofolder " + self.dwi_dir[ID_nb] + " -v 0"
  1140. if not os.path.exists(self.dwi_cut_files["dwi"][ID_nb]) or redo==True:
  1141. if verbose==True:
  1142. print("DWI and b0 images separation is running for sub-" + ID)
  1143. os.system(string)
  1144. elif redo==False and verbose==True:
  1145. print("DWI and b0 images separation was already done for sub-" + ID)
  1146. return self.dwi_cut_files
  1147. def segmentation(self,i_files=None,redo=False,verbose=False):
  1148. '''
  1149. DWI segmentation
  1150. Inputs
  1151. ----------
  1152. i_file: list
  1153. list of the files to be segmented
  1154. redo: boolean
  1155. if True this step will be rerun even if the file already exists
  1156. verbose: boolean
  1157. if True will provide more information about the process
  1158. Returns
  1159. ----------
  1160. seg_file: list
  1161. list of the segmentation files
  1162. '''
  1163. seg_dir=[];seg_files=[]
  1164. if i_files is None:
  1165. raise ValueError("Please provide the file to be segmented")
  1166. for ID_nb, ID in enumerate(self.config["participants_IDs"]):
  1167. seg_dir.append(self.dwi_dir[ID_nb]+"/segmentation/")
  1168. #Create the segmentation directory
  1169. if not os.path.exists(seg_dir[ID_nb]):
  1170. os.mkdir(seg_dir[ID_nb])
  1171. #Initiate the segmentation file
  1172. seg_files.append(seg_dir[ID_nb] + os.path.basename(i_files[ID_nb]).split(".nii.gz")[0] + "_seg.nii.gz")
  1173. string=f"sct_propseg -i {i_files[ID_nb]} -c dwi -o {seg_files[ID_nb]} -v 0"
  1174. if not os.path.exists(seg_files[ID_nb]) or redo==True:
  1175. if verbose==True:
  1176. print("DWI segmentation is running for sub-" + ID)
  1177. os.system(string)
  1178. elif redo==False and verbose==True:
  1179. print("DWI segmentation was already done for sub-" + ID)
  1180. return seg_files
  1181. def moco(self,seg_files=None,mask_size='20',redo=False,verbose=False):
  1182. '''
  1183. DWI motion correction
  1184. https://spinalcordtoolbox.com/stable/user_section/command-line/sct_dmri_moco.html
  1185. Inputs
  1186. ----------
  1187. seg_files: list
  1188. list of the segmentation files
  1189. mask_size: int
  1190. size of the mask in mm, default is 15
  1191. redo: boolean
  1192. if True this step will be rerun even if the file already exists
  1193. verbose: boolean
  1194. if True will provide more information about the process
  1195. Returns
  1196. ----------
  1197. '''
  1198. # Initiate the motion correction directory
  1199. self.moco_dir=[];self.moco_files={};self.moco_files["moco"]=[];self.moco_files["moco_mean"]=[];self.mask_files=[]
  1200. if seg_files==None:
  1201. raise ValueError("Please provide the file to be segmented")
  1202. for ID_nb, ID in enumerate(self.config["participants_IDs"]):
  1203. self.moco_dir.append(self.dwi_dir[ID_nb]+"/moco/")
  1204. #Create the motion correction directory
  1205. if not os.path.exists(self.moco_dir[ID_nb]):
  1206. os.mkdir(self.moco_dir[ID_nb])
  1207. #Initiate the motion correction file
  1208. self.moco_files["moco"].append(self.moco_dir[ID_nb] + os.path.basename(self.dwi_files["nifti"][ID_nb]).split('.')[0] + "_moco.nii.gz")
  1209. self.moco_files["moco_mean"].append(self.moco_dir[ID_nb] + os.path.basename(self.moco_files["moco"][ID_nb]).split('.')[0] + "_dwi_mean.nii.gz")
  1210. self.mask_files.append(self.moco_dir[ID_nb] + os.path.basename(self.dwi_files["nifti"][ID_nb]).split('.')[0] + "_mean_mask.nii.gz")
  1211. # Create a coarse mask
  1212. if not os.path.exists(self.mask_files[ID_nb]) or redo==True:
  1213. string=f"sct_create_mask -i {self.dwi_files['nifti'][ID_nb]} -p centerline,{seg_files[ID_nb]} -o {self.mask_files[ID_nb]} -size {mask_size}mm -v 0"
  1214. os.system(string)
  1215. # Motion correction
  1216. if not os.path.exists(self.moco_files["moco"][ID_nb]) or redo==True:
  1217. if verbose==True:
  1218. print("DWI motion correction is running for sub-" + ID)
  1219. string=f"sct_dmri_moco -i {self.dwi_files['nifti'][ID_nb]} -bvec {self.dwi_files['bvec'][ID_nb]} -x spline -param metric=CC -o {self.moco_dir[ID_nb]}"
  1220. os.system(string)
  1221. elif redo==False and verbose==True:
  1222. print("DWI motion correction was already done for sub-" + ID)
  1223. return self.moco_files
  1224. def compute_dti(self,i_files=None,redo=False,verbose=False):
  1225. '''
  1226. Compute diffusion tensor images using dipy
  1227. https://spinalcordtoolbox.com/stable/user_section/command-line/sct_dmri_compute_dti.html
  1228. Inputs
  1229. ----------
  1230. i_files: list
  1231. list of the files to be computed
  1232. redo: boolean
  1233. if True this step will be rerun even if the file already exists
  1234. verbose: boolean
  1235. if True will provide more information about the process
  1236. Returns
  1237. ----------
  1238. '''
  1239. if i_files==None:
  1240. raise ValueError("Please provide the file to be computed")
  1241. #Output directory
  1242. results_dir=[]
  1243. output_files={}
  1244. output_files["FA"]=[];output_files["MD"]=[];output_files["AD"]=[];output_files["RD"]=[]
  1245. for ID_nb, ID in enumerate(self.config["participants_IDs"]):
  1246. results_dir.append(self.dwi_dir[ID_nb]+"/dti_metrics/")
  1247. if not os.path.exists(results_dir[ID_nb]):
  1248. os.mkdir(results_dir[ID_nb])
  1249. string=f"sct_dmri_compute_dti -i {i_files[ID_nb]} -bvec {self.dwi_files['bvec'][ID_nb]} -bval {self.dwi_files['bval'][ID_nb]} -method standard -o {results_dir[ID_nb]}/sub-{ID}_dwi_dti_"
  1250. if not os.path.exists(results_dir[ID_nb] + "/sub-" + ID + "_dwi_dti_FA.nii.gz") or redo==True:
  1251. if verbose==True:
  1252. print("DWI diffusion tensor computation is running for sub-" + ID)
  1253. os.system(string)
  1254. elif redo==False and verbose==True:
  1255. print("DWI diffusion tensor computation was already done for sub-" + ID)
  1256. for metric in ["FA","MD","AD","RD"]:
  1257. output_files[metric].append(results_dir[ID_nb] + "/sub-" + ID + "_dwi_dti_"+metric+".nii.gz")
  1258. return output_files

brsc_preprocess.py at commit 85ebbaa, under BSD-3-Clause · at the source

Overview

Authors: Caroline Landelle1, Nawal Kinany2,3, Samuelle St-Onge4,5, Ovidiu Lungu1,6, Dimitri Van De Ville2,3, Bratislav Misic1, Véronique Marchand-Pauvert7, Benjamin De Leener4,5,8, Julien Doyon1
  1. McConnell Brain Imaging Centre, Department of Neurology and Neurosurgery, Montreal Neurological Institute, McGill University, Montreal, QC Canada
  2. Department of Radiology and Medical Informatics, University of Geneva, Geneva, Switzerland
  3. Neuro-X Institute, Ecole Polytechnique Fédérale de Lausanne (EPFL), Geneva, Switzerland
  4. NeuroPoly Lab, Institute of Biomedical Engineering, Polytechnique Montreal, Montreal, QC Canada
  5. CHU Sainte-Justine Research Centre, Montreal, QC Canada
  6. Centre de recherche de l’Institut Universitaire de Gériatrie de Montréal, Centre de Recherche de l’Institut Universitaire en Santé Mentale de Montréal et Département de Psychiatrie et Addictologie, Université de Montréal, Montréal, QC Canada
  7. Sorbonne Université, Inserm, CNRS, Laboratoire d’Imagerie Biomédicale, LIB, Paris, France
  8. Department of Computer Engineering and Software Engineering, Polytechnique Montreal, Montreal, QC Canada
Journal: Nature communications, volume 17, issue 1, article 5269
Dates: received 13 October 2025; accepted 3 April 2026; published online 16 April 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41467-026-71963-2 · PMID 41991947 · PMCID PMC13265777 · OpenAlex W4414804586
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism)
Methods: Spectral & time-frequency, Connectivity, Statistics, Smoothing, state filtering, decompositions, Machine learning, Preprocessing, fMRI & imaging
Keywords: Neural ageing, Spinal cord, Sensory processing
MeSH: Aging*, Brain*, Longevity*, Spinal Cord*, Animals, Female, Gray Matter, Humans, Magnetic Resonance Imaging (* major topic)
Topic: Functional Brain Connectivity Studies (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: Gouvernement du Canada | Canadian Institutes of Health Research (13556932); Canadian Network for Research and Innovation in Machining Technology, Natural Sciences and Engineering Research Council of Canada (NSERC Canadian Network for Research and Innovation in Machining Technology) (248074); Swiss National Science Foundation (205321, 207493); Fondation Brain Canada (255934); Healthy Brain for Healthy Lives
Citations: not cited yet (Europe PMC); 91 references in the paper

Abstract

The spinal cord connects the brain to peripheral systems. Yet its integration with cerebral networks remains a key neuroscience question. Capturing structural and functional central nervous system (CNS) changes throughout the lifespan is essential for characterizing healthy and pathological aging. Leveraging a unique multimodal dataset combining spinal and cerebrospinal imaging, we jointly mapped the spinal cord structural and functional architecture across adulthood. Our results revealed age-related changes across these modalities and identified organizational principles shared with the brain. These changes were most pronounced in the somatosensory pathway, with microstructural decline coupled to shifts in functional connectivity and local spontaneous activity as aging progresses. Extending analyses to the brain uncovered convergent CNS-wide aging mechanisms, including gray matter loss, decreased functional segregation, and increased spontaneous activity, highlighting shared neural aging trajectories. Together, our findings provide a systems-level view of the age-related alteration in the CNS, offering a foundation for future studies investigating potential imaging markers of sensorimotor dysfunction.

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

Repositories

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

CarolineLndl/Landelle_spinebrain_aging

License: BSD-3-Clause
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Commit: 85ebbaa10596d768857c847788ab9c99c35b768f, 18 April 2026
Languages: Jupyter (18), Python (14), MATLAB (5), Shell (1)
Size: 72 files, 38 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, 18 notebooks
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (28 files), pandas (27 files), Matplotlib (20 files), SciPy (19 files), NiBabel (18 files), seaborn (15 files), statsmodels (14 files), FSL (9 files), Nilearn (8 files), scikit-learn (6 files), SPM (5 files), Nipype (2 files), Pingouin (2 files)
Availability: 1 check, the latest on 29 September 2026: the link answers
  • 29 September 2026: the link answers
40 files

Zenodo 18675164

License: BSD-3-Clause
State: the link answers, verified on 29 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: the references
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (28 files), pandas (27 files), Matplotlib (20 files), SciPy (19 files), NiBabel (18 files), seaborn (15 files), statsmodels (14 files), FSL (9 files), Nilearn (8 files), scikit-learn (6 files), SPM (5 files), Nipype (2 files), Pingouin (2 files)
Availability: 1 check, the latest on 29 September 2026: the link answers (HTTP 200)
  • 29 September 2026: the link answers (HTTP 200)
40 files
At the source:

Code availability

The code used for data preprocessing and analysis is available for public use here79: https://github.com/CarolineLndl/Landelle_spinebrain_aging.

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:

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

The data generated in this study are available under restricted access as they are part of an ongoing study. The complete dataset is available upon reasonable request by contacting the corresponding author. Data from one representative participant are available at 10.18112/openneuro.ds007313.v1.0.0, and functional data from the 32 younger participants are also publicly available91 at 10.18112/openneuro.ds005075.v1.0.1. Source data are provided with this paper.

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

Recorded: type, language, journal, volume, issue, pages, dates, 9 authors, 3 keywords, 9 MeSH terms, 5 funders, 87 references.

Cite

This paper

Landelle, C., Kinany, N., St-Onge, S., Lungu, O., Van De Ville, D., Misic, B., Marchand-Pauvert, V., De Leener, B., & Doyon, J. (2026). Spinal cord structural and functional architecture and its shared organization with the brain across the adult lifespan. Nature communications, 17(1), 5269. https://doi.org/10.1038/s41467-026-71963-2

BibTeX

@article{landelle2026spinal,
author = {Landelle, Caroline and Kinany, Nawal and St-Onge, Samuelle and Lungu, Ovidiu and Van De Ville, Dimitri and Misic, Bratislav and Marchand-Pauvert, Véronique and De Leener, Benjamin and Doyon, Julien},
title = {{Spinal cord structural and functional architecture and its shared organization with the brain across the adult lifespan}},
journal = {Nature communications},
year = {2026},
month = apr,
volume = {17},
number = {1},
pages = {5269},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-71963-2},
url = {https://doi.org/10.1038/s41467-026-71963-2},
pmid = {41991947},
pmcid = {PMC13265777}
}

RIS

TY - JOUR
AU - Landelle, Caroline
AU - Kinany, Nawal
AU - St-Onge, Samuelle
AU - Lungu, Ovidiu
AU - Van De Ville, Dimitri
AU - Misic, Bratislav
AU - Marchand-Pauvert, Véronique
AU - De Leener, Benjamin
AU - Doyon, Julien
TI - Spinal cord structural and functional architecture and its shared organization with the brain across the adult lifespan
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/04/16
VL - 17
IS - 1
SP - 5269
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-71963-2
UR - https://doi.org/10.1038/s41467-026-71963-2
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-71963-2",
"type": "article-journal",
"title": "Spinal cord structural and functional architecture and its shared organization with the brain across the adult lifespan",
"container-title": "Nature communications",
"author": [
{
"family": "Landelle",
"given": "Caroline"
},
{
"family": "Kinany",
"given": "Nawal"
},
{
"family": "St-Onge",
"given": "Samuelle"
},
{
"family": "Lungu",
"given": "Ovidiu"
},
{
"family": "Van De Ville",
"given": "Dimitri"
},
{
"family": "Misic",
"given": "Bratislav"
},
{
"family": "Marchand-Pauvert",
"given": "Véronique"
},
{
"family": "De Leener",
"given": "Benjamin"
},
{
"family": "Doyon",
"given": "Julien"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "5269",
"DOI": "10.1038/s41467-026-71963-2",
"PMID": "41991947",
"PMCID": "PMC13265777",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-71963-2",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
16
]
]
}
}

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.1002/hbm.70593 [code]
Test-Retest Reliability of Sensorimotor Activity Measured With Spinal Cord fMRI.
Journal: Human brain mapping
In common: FSL, statsmodels, seaborn, 4 other tools, 9 references
[2] doi:10.1371/journal.pbio.3003856 [code]
Aging and metabolism contribute separately to brain-body health.
Journal: PLoS biology
In common: FSL, Nilearn, SPM, 8 other tools, 4 references, author Bratislav Misic
[3] doi:10.21203/rs.3.rs-10155262/v1
Functional Gradients of the Neuraxis: Reorganization from Cortex to Spinal Cord
Journal: Research Square (preprint)
In common: 10 references, author Dimitri Van De Ville
[4] doi:10.7554/elife.103097 [code]
Canonical neurodevelopmental trajectories of structural and functional manifolds.
Journal: eLife
In common: Pingouin, Nilearn, NiBabel, 5 other tools, 8 references
[5] 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: Nipype, Pingouin, FSL, 9 other tools, 3 references
[6] doi:10.1093/nc/niag029 [code]
A data-driven approach to identifying and evaluating connectivity-based neural correlates of conscious visual perception.
Journal: Neuroscience of consciousness
In common: Pingouin, FSL, Nilearn, 9 other tools, 3 references
[7] doi:10.1038/s41467-026-74466-2 [code]
Neuromorphic hierarchical modular reservoirs.
Journal: Nature communications
In common: Pingouin, Nilearn, NiBabel, 7 other tools, 2 references, author Bratislav Misic
[8] doi:10.1038/s41593-026-02205-3 [code]
Competitive interactions shape mammalian brain network dynamics and computation.
Journal: Nature neuroscience
In common: Nipype, FSL, SPM, 7 other tools, 1 reference, author Bratislav Misic
[9] 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, Pingouin, SPM, 8 other tools, 3 references
[10] doi:10.1371/journal.pbio.3003684 [code]
The retrieval of previously learned motor memories is facilitated by the reinstatement of default mode network manifold structures.
Journal: PLoS biology
In common: Pingouin, Nilearn, NiBabel, 6 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.