highspeed-masks.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # ======================================================================
  4. # SCRIPT INFORMATION:
  5. # ======================================================================
  6. # SCRIPT: CREATE BINARIZED MASKS FROM SEGMENTED FUNCTIONAL IMAGES
  7. # PROJECT: HIGHSPEED
  8. # WRITTEN BY LENNART WITTKUHN, 2018 - 2020
  9. # CONTACT: WITTKUHN AT MPIB HYPHEN BERLIN DOT MPG DOT DE
  10. # MAX PLANCK RESEARCH GROUP NEUROCODE
  11. # MAX PLANCK INSTITUTE FOR HUMAN DEVELOPMENT
  12. # MAX PLANCK UCL CENTRE FOR COMPUTATIONAL PSYCHIATRY AND AGEING RESEARCH
  13. # LENTZEALLEE 94, 14195 BERLIN, GERMANY
  14. # ======================================================================
  15. # IMPORT RELEVANT PACKAGES
  16. # ======================================================================
  17. # import basic libraries:
  18. import os
  19. import sys
  20. import glob
  21. import warnings
  22. from os.path import join as opj
  23. # import nipype libraries:
  24. from nipype.interfaces.utility import IdentityInterface
  25. from nipype.interfaces.io import SelectFiles, DataSink
  26. from nipype.pipeline.engine import Workflow, Node, MapNode
  27. # import libraries for bids interaction:
  28. from bids.layout import BIDSLayout
  29. # import freesurfer interfaces:
  30. from nipype.interfaces.freesurfer import Binarize
  31. # import fsl interfaces:
  32. from niflow.nipype1.workflows.fmri.fsl import create_susan_smooth
  33. import datalad.api as dl
  34. # ======================================================================
  35. # ENVIRONMENT SETTINGS (DEALING WITH ERRORS AND WARNINGS):
  36. # ======================================================================
  37. # set the fsl output type environment variable:
  38. os.environ['FSLOUTPUTTYPE'] = 'NIFTI_GZ'
  39. # set the freesurfer subject directory:
  40. os.environ['SUBJECTS_DIR'] = '/opt/software/freesurfer/6.0.0/subjects'
  41. # deal with nipype-related warnings:
  42. os.environ['TF_CPP_MIN_LOG_LEVEL'] = "3"
  43. # filter out warnings related to the numpy package:
  44. warnings.filterwarnings("ignore", message="numpy.dtype size changed*")
  45. warnings.filterwarnings("ignore", message="numpy.ufunc size changed*")
  46. # ======================================================================
  47. # DEFINE PBS CLUSTER JOB TEMPLATE (NEEDED WHEN RUNNING ON THE CLUSTER):
  48. # ======================================================================
  49. job_template = """
  50. #PBS -l walltime=5:00:00
  51. #PBS -j oe
  52. #PBS -o $HOME/highspeed/highspeed-masks/logs
  53. #PBS -m n
  54. #PBS -v FSLOUTPUTTYPE=NIFTI_GZ
  55. source /etc/bash_completion.d/virtualenvwrapper
  56. workon highspeed-masks
  57. module load fsl/5.0
  58. module load freesurfer/6.0.0
  59. """
  60. # ======================================================================
  61. # DEFINE PATHS AND SUBJECTS
  62. # ======================================================================
  63. path_root = None
  64. sub_list = None
  65. # path to the project root:
  66. project_name = 'highspeed-masks'
  67. path_root = os.getenv('PWD').split(project_name)[0] + project_name
  68. # grab the list of subjects from the bids data set:
  69. path_bids = opj(path_root, 'bids')
  70. dl.get(opj('bids', 'participants.json'))
  71. dl.get(glob.glob(opj(path_root, 'bids', 'sub-*', 'ses-*', '*', '*.json')))
  72. dl.get(glob.glob(opj(path_root, 'bids', 'sub-*', 'ses-*', '*.json')))
  73. dl.get(glob.glob(opj(path_root, 'bids', '*.json')))
  74. layout = BIDSLayout(path_bids)
  75. # get all subject ids:
  76. sub_list = sorted(layout.get_subjects())
  77. # create a template to add the "sub-" prefix to the ids
  78. sub_template = ['sub-'] * len(sub_list)
  79. # add the prefix to all ids:
  80. sub_list = ["%s%s" % t for t in zip(sub_template, sub_list)]
  81. # if user defined to run specific subject
  82. sub_list = sub_list[int(sys.argv[1]):int(sys.argv[2])]
  83. # ======================================================================
  84. # DEFINE NODE: INFOSOURCE
  85. # ======================================================================
  86. # define the infosource node that collects the data:
  87. infosource = Node(IdentityInterface(
  88. fields=['subject_id']), name='infosource')
  89. # let the node iterate (parallelize) over all subjects:
  90. infosource.iterables = [('subject_id', sub_list)]
  91. # ======================================================================
  92. # DEFINE SELECTFILES NODE
  93. # ======================================================================
  94. path_func = opj(path_root, 'fmriprep', 'fmriprep', '*', '*',
  95. 'func', '*space-T1w*preproc_bold.nii.gz')
  96. path_func_parc = opj(path_root, 'fmriprep', 'fmriprep', '*',
  97. '*', 'func', '*space-T1w*aparcaseg_dseg.nii.gz')
  98. path_wholemask = opj(path_root, 'fmriprep', 'fmriprep', '*',
  99. '*', 'func', '*space-T1w*brain_mask.nii.gz')
  100. dl.get(glob.glob(path_func))
  101. dl.get(glob.glob(path_func_parc))
  102. dl.get(glob.glob(path_wholemask))
  103. # define all relevant files paths:
  104. templates = dict(
  105. func=opj(path_root, 'fmriprep', 'fmriprep', '{subject_id}', '*',
  106. 'func', '*space-T1w*preproc_bold.nii.gz'),
  107. func_parc=opj(path_root, 'fmriprep', 'fmriprep', '{subject_id}',
  108. '*', 'func', '*space-T1w*aparcaseg_dseg.nii.gz'),
  109. wholemask=opj(path_root, 'fmriprep', 'fmriprep', '{subject_id}',
  110. '*', 'func', '*space-T1w*brain_mask.nii.gz'),
  111. )
  112. # define the selectfiles node:
  113. selectfiles = Node(SelectFiles(templates, sort_filelist=True),
  114. name='selectfiles')
  115. # set expected thread and memory usage for the node:
  116. selectfiles.interface.num_threads = 1
  117. selectfiles.interface.estimated_memory_gb = 0.1
  118. # selectfiles.inputs.subject_id = 'sub-20'
  119. # selectfiles_results = selectfiles.run()
  120. # ======================================================================
  121. # DEFINE CREATE_SUSAN_SMOOTH WORKFLOW NODE
  122. # ======================================================================
  123. # define the susan smoothing node and specify the smoothing fwhm:
  124. susan = create_susan_smooth()
  125. # set the smoothing kernel:
  126. susan.inputs.inputnode.fwhm = 4
  127. # set expected thread and memory usage for the nodes:
  128. susan.get_node('inputnode').interface.num_threads = 1
  129. susan.get_node('inputnode').interface.estimated_memory_gb = 0.1
  130. susan.get_node('median').interface.num_threads = 1
  131. susan.get_node('median').interface.estimated_memory_gb = 3
  132. susan.get_node('mask').interface.num_threads = 1
  133. susan.get_node('mask').interface.estimated_memory_gb = 3
  134. susan.get_node('meanfunc2').interface.num_threads = 1
  135. susan.get_node('meanfunc2').interface.estimated_memory_gb = 3
  136. susan.get_node('merge').interface.num_threads = 1
  137. susan.get_node('merge').interface.estimated_memory_gb = 3
  138. susan.get_node('multi_inputs').interface.num_threads = 1
  139. susan.get_node('multi_inputs').interface.estimated_memory_gb = 3
  140. susan.get_node('smooth').interface.num_threads = 1
  141. susan.get_node('smooth').interface.estimated_memory_gb = 3
  142. susan.get_node('outputnode').interface.num_threads = 1
  143. susan.get_node('outputnode').interface.estimated_memory_gb = 0.1
  144. # ======================================================================
  145. # DEFINE BINARIZE NODE
  146. # ======================================================================
  147. mask_visual_labels = [
  148. 1005, 2005, # cuneus
  149. 1011, 2011, # lateral occipital
  150. 1021, 2021, # pericalcarine
  151. 1029, 2029, # superioparietal
  152. 1013, 2013, # lingual
  153. 1008, 2008, # inferioparietal
  154. 1007, 2007, # fusiform
  155. 1009, 2009, # inferiotemporal
  156. 1016, 2016, # parahippocampal
  157. 1015, 2015, # middle temporal
  158. ]
  159. mask_hippocampus_labels = [
  160. 17, 53, # left and right hippocampus
  161. ]
  162. mask_mtl_labels = [
  163. 17, 53, # left and right hippocampus
  164. 1016, 2016, # parahippocampal
  165. 1006, 2006, # ctx-entorhinal
  166. ]
  167. # function: use freesurfer mri_binarize to threshold an input volume
  168. mask_visual = MapNode(
  169. interface=Binarize(), name='mask_visual', iterfield=['in_file'])
  170. # input: match instead of threshold
  171. mask_visual.inputs.match = mask_visual_labels
  172. # optimize the efficiency of the node:
  173. mask_visual.plugin_args = {'qsub_args': '-l nodes=1:ppn=1', 'overwrite': True}
  174. mask_visual.plugin_args = {'qsub_args': '-l mem=100MB', 'overwrite': True}
  175. # function: use freesurfer mri_binarize to threshold an input volume
  176. mask_hippocampus = MapNode(
  177. interface=Binarize(), name='mask_hippocampus', iterfield=['in_file'])
  178. # input: match instead of threshold
  179. mask_hippocampus.inputs.match = mask_hippocampus_labels
  180. # optimize the efficiency of the node:
  181. mask_hippocampus.plugin_args = {
  182. 'qsub_args': '-l nodes=1:ppn=1', 'overwrite': True}
  183. mask_hippocampus.plugin_args = {
  184. 'qsub_args': '-l mem=100MB', 'overwrite': True}
  185. # function: use freesurfer mri_binarize to threshold an input volume
  186. mask_mtl = MapNode(
  187. interface=Binarize(), name='mask_mtl', iterfield=['in_file'])
  188. # input: match instead of threshold
  189. mask_mtl.inputs.match = mask_mtl_labels
  190. # optimize the efficiency of the node:
  191. mask_mtl.plugin_args = {'qsub_args': '-l nodes=1:ppn=1', 'overwrite': True}
  192. mask_mtl.plugin_args = {'qsub_args': '-l mem=100MB', 'overwrite': True}
  193. # ======================================================================
  194. # CREATE DATASINK NODE
  195. # ======================================================================
  196. # create a node of the function:
  197. datasink = Node(DataSink(), name='datasink')
  198. # assign the path to the base directory:
  199. datasink.inputs.base_directory = opj(path_root, 'masks')
  200. # create a list of substitutions to adjust the filepaths of datasink:
  201. substitutions = [('_subject_id_', '')]
  202. # assign the substitutions to the datasink command:
  203. datasink.inputs.substitutions = substitutions
  204. # determine whether to store output in parameterized form:
  205. datasink.inputs.parameterization = True
  206. # set expected thread and memory usage for the node:
  207. datasink.interface.num_threads = 1
  208. datasink.interface.estimated_memory_gb = 0.2
  209. # ======================================================================
  210. # DEFINE WORKFLOW PIPELINE:
  211. # ======================================================================
  212. # initiation of the 1st-level analysis workflow:
  213. wf = Workflow(name='masks')
  214. # stop execution of the workflow if an error is encountered:
  215. wf.config = {'execution': {'stop_on_first_crash': True}}
  216. # define the base directory of the workflow:
  217. wf.base_dir = opj(path_root, 'work')
  218. # connect infosource to selectfiles node:
  219. wf.connect(infosource, 'subject_id', selectfiles, 'subject_id')
  220. # connect functional files to smoothing workflow:
  221. wf.connect(selectfiles, 'func', susan, 'inputnode.in_files')
  222. wf.connect(selectfiles, 'wholemask', susan, 'inputnode.mask_file')
  223. wf.connect(susan, 'outputnode.smoothed_files', datasink, 'smooth')
  224. # connect segmented functional files to visual mask node
  225. wf.connect(selectfiles, 'func_parc', mask_visual, 'in_file')
  226. wf.connect(mask_visual, 'binary_file', datasink, 'mask_visual.@binary')
  227. wf.connect(mask_visual, 'count_file', datasink, 'mask_visual.@count')
  228. # connect segmented functional files to hippocampus node
  229. wf.connect(selectfiles, 'func_parc', mask_hippocampus, 'in_file')
  230. wf.connect(mask_hippocampus, 'binary_file', datasink, 'mask_hippocampus.@binary')
  231. wf.connect(mask_hippocampus, 'count_file', datasink, 'mask_hippocampus.@count')
  232. # connect segmented functional files to mtl node
  233. wf.connect(selectfiles, 'func_parc', mask_mtl, 'in_file')
  234. wf.connect(mask_mtl, 'binary_file', datasink, 'mask_mtl.@binary')
  235. wf.connect(mask_mtl, 'count_file', datasink, 'mask_mtl.@count')
  236. # ======================================================================
  237. # WRITE GRAPH AND EXECUTE THE WORKFLOW
  238. # ======================================================================
  239. # write the graph:
  240. wf.write_graph(graph2use='colored', simple_form=True)
  241. # set the maximum resources the workflow can utilize:
  242. # args_dict = {'status_callback' : log_nodes_cb}
  243. # execute the workflow depending on the operating system:
  244. if 'darwin' in sys.platform:
  245. # will execute the workflow using all available cpus:
  246. wf.run(plugin='MultiProc')
  247. elif 'linux' in sys.platform:
  248. wf.run(plugin='PBS', plugin_args=dict(template=job_template))