co_registration.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. from helper_functions import pretty, make_dirs, slice_volume, files_in_dir, files_ind_dir_wo_string, make_volume
  2. from nipype.interfaces import ants
  3. from os import path
  4. from shutil import rmtree
  5. import sys
  6. import getopt
  7. # Help message.
  8. tab = ' '
  9. usage_message = '\nCOMMAND:\n' + tab + sys.argv[0] + \
  10. '\n\nOPTIONS:\n' + \
  11. tab + '-h, help menu\n' + \
  12. tab + '-e, --template, <input template>\n' + \
  13. tab + '-s, --slices, <directory with input slices>\n' + \
  14. tab + '-r, --rawvolume, <raw volume of slices>\n' + \
  15. tab + '-t, --inittrans, <initial transform, roughly transforming raw slice volume to match input ' \
  16. 'volume, manually created>\n' + \
  17. tab + '-d, --id, <brain id>\n' + \
  18. tab + '-o, --output, <output directory, will create new directory if not present>\n' + \
  19. tab + '-l, --log, [bool]>\n'
  20. def registration(fixed, moving, result, reg_type, sigma, shrink, param, dim, initial_trans='', inverse_result=''):
  21. """
  22. Registration function. Registeres the moving image to the fixed with given parameters. If initial transform is not
  23. given then the images are aligned by center of mass. Outputs inversely transformed result if given.
  24. :param fixed: Path to fixed image.
  25. :param moving: Path to moving image.
  26. :param result: Path to result image.
  27. :param reg_type: Registration type. List containing, Rigid, Affine, and/or SyN.
  28. :param sigma: List of sigma parameters. Ordered according to the reg_type parameter.
  29. :param shrink: List of shrink parameters. Ordered according to the reg_type parameter.
  30. :param param: List of transform parameters. Ordered according to the reg_type parameter.
  31. :param dim: Dimension of images. Typically 2 or 3.
  32. :param initial_trans: Optional path to initial moving transform. If not given, the images will be matched initially
  33. by aligning the center of mass.
  34. :param inverse_result: Optional path to the inversely transformed fixed image.
  35. :return: Returns nothing.
  36. """
  37. # Extract number of registrations.
  38. steps = len(reg_type)
  39. # Add 1000 iterations for each step.
  40. if steps == 1:
  41. iterations = [[1000] * len(sigma[0])]
  42. else:
  43. iterations = []
  44. for i, reg in enumerate(reg_type):
  45. iteration = [1000] * len(sigma[i])
  46. iterations.append(iteration)
  47. # Create registration instance.
  48. reg_instance = ants.Registration(dimension=dim,
  49. transforms=reg_type,
  50. transform_parameters=param,
  51. metric=['MeanSquares']*steps,
  52. interpolation='BSpline',
  53. fixed_image=[fixed],
  54. moving_image=[moving],
  55. metric_weight=[1]*steps,
  56. radius_or_number_of_bins=[32]*steps,
  57. number_of_iterations=iterations,
  58. smoothing_sigmas=sigma,
  59. shrink_factors=shrink,
  60. convergence_threshold=[1.e-7],
  61. sampling_strategy=['Regular']*steps,
  62. use_histogram_matching=True,
  63. winsorize_lower_quantile=0.05,
  64. winsorize_upper_quantile=0.95,
  65. write_composite_transform=True,
  66. output_warped_image=result,
  67. output_transform_prefix=result.split('.')[0] + '-Transformation',
  68. num_threads=12)
  69. # Add initial moving transform if given, else match by center of mass.
  70. if not initial_trans == '':
  71. reg_instance.inputs.initial_moving_transform = initial_trans
  72. else:
  73. reg_instance.inputs.initial_moving_transform_com = 1
  74. # Output reverse results if path is given.
  75. if not inverse_result == '':
  76. reg_instance.inputs.output_inverse_warped_image = inverse_result
  77. # Run registration.
  78. reg_instance.run()
  79. def co_register(template, slices, raw_volume, initial_transform, out_dir, brain_id, r=3, a=2, nl=3, print_log=False):
  80. """
  81. Function for reconstructing brain volume by iteratively registering to given template. The slices are stacked into a
  82. 3-dimensional volume that is registered to the template. The template is inversely transformed and sliced, resulting
  83. in slice pairs with a template slice and an input slice. For each pair, the input slice is registered to the
  84. template slice, and all the resulting registered slices are stacked to a volume. This process is repeated r times
  85. rigid, a times rigid and affine, and nl times rigid, affine, and non-linear. Returns the path to the final
  86. reconstructed brain volume.
  87. :param template: Path to template used as a reference for brain reconstruction.
  88. :param slices: List of paths to slices that needs to be reconstructed.
  89. :param raw_volume: Path to the initial stacked volume, needed for first rigid registration step.
  90. :param initial_transform:Initial 3-dimensional transform of the raw volume to the template. If empty string given,
  91. they will be matched by the center of mass.
  92. :param out_dir: Output directory.
  93. :param brain_id: Mouse id.
  94. :param r: Int [Default: 3]. Number of rigid registrations.
  95. :param a: Int [Default: 2]. Number of rigid and affine registrations.
  96. :param nl: Int [Default: 3]. Number of rigid, affine, and non-linear registrations.
  97. :param print_log: Boolean [Default: False]. Will print log if true.
  98. :return: Return path to the final reconstructed brain volume.
  99. """
  100. # Determine whether the reference template is the Allen atlas (1. iteration) or the DAPI-template (2. iteration).
  101. if 'allen' in template:
  102. template_type = 'allen'
  103. else:
  104. template_type = 'dapi'
  105. volume_postfix = '_hv_reg_' + template_type + '_' + brain_id
  106. # List of slices.
  107. raw_slices = files_in_dir(slices, '.nii')
  108. if print_log:
  109. print('Volume given interpreted as:', template_type)
  110. print('Checking paths.')
  111. if not path.exists(raw_volume):
  112. print('The path:', raw_volume, 'does not exist.')
  113. if not path.exists(template):
  114. print('The path:', template, 'does not exist.')
  115. if not path.exists(initial_transform):
  116. print('The path:', initial_transform, 'does not exist.')
  117. if print_log:
  118. print('Defining iterations.')
  119. reg_types = ['Rigid'] * r + ['Affine'] * a + ['SyN'] * nl
  120. if print_log:
  121. print(reg_types)
  122. # Run through iterations.
  123. for i, iteration in enumerate(reg_types):
  124. cur_work_dir = out_dir + pretty(i) + '/'
  125. make_dirs(cur_work_dir)
  126. if print_log:
  127. print('Running iteration', pretty(i))
  128. print('Current work dir:', cur_work_dir)
  129. if i == 0:
  130. hv = raw_volume
  131. else:
  132. hv = out_dir + pretty(i) + volume_postfix + '.nii'
  133. if print_log:
  134. print('Deleting prev. iteration work dir:', out_dir + pretty(i - 1) + '/')
  135. rmtree(out_dir + pretty(i - 1) + '/')
  136. # Iteration specific registration parameters.
  137. if iteration == 'Rigid':
  138. reg = ['Rigid']
  139. params = [(0.25,)]
  140. vol_sigma = [[4, 4, 2, 2]]
  141. vol_shrink = [[32, 16, 8, 4]]
  142. slice_sigma = [[4, 4, 2, 2]]
  143. slice_shrink = [[32, 16, 8, 4]]
  144. if i == 0:
  145. vol_sigma = [[4, 4, 4]]
  146. vol_shrink = [[32, 16, 8]]
  147. slice_sigma = [[4, 4, 2]]
  148. slice_shrink = [[32, 16, 8]]
  149. elif iteration == 'Affine':
  150. reg = ['Rigid', 'Affine']
  151. params = [(0.25,), (0.25,), ]
  152. vol_sigma = [[4, 4, 2, 2], [4, 4, 2, 2]]
  153. vol_shrink = [[32, 16, 8, 4], [32, 16, 8, 4]]
  154. slice_sigma = [[4, 4, 2, 2], [4, 4, 2, 2]]
  155. slice_shrink = [[32, 16, 8, 4], [32, 16, 8, 4]]
  156. elif iteration == 'SyN':
  157. reg = ['Rigid', 'Affine', 'SyN']
  158. params = [(0.25,), (0.25,), (0.15, 3, 0.5)]
  159. vol_sigma = [[4, 4, 2, 2], [4, 4, 2, 2], [4, 4, 4, 2]]
  160. vol_shrink = [[32, 16, 8, 4], [32, 16, 8, 4], [64, 32, 16, 8]]
  161. slice_sigma = [[4, 4, 2, 2], [4, 4, 2, 2], [4, 4, 4, 2, 2]]
  162. slice_shrink = [[32, 16, 8, 4], [32, 16, 8, 4], [64, 32, 16, 8, 4]]
  163. if i == len(reg_types)-1:
  164. slice_sigma = [[4, 4, 2, 2], [4, 4, 2, 2], [4, 4, 4, 2, 2, 1]]
  165. slice_shrink = [[32, 16, 8, 4], [32, 16, 8, 4], [64, 32, 16, 8, 4, 2]]
  166. else:
  167. sys.exit('The registration type "' + str(iteration) + '" does not exist. Reconstruction stopped.')
  168. # Define name and path for registered template to hv.
  169. cur_reg_name = pretty(i) + '-' + template_type + '_reg_to_hv.nii'
  170. cur_reg_path = cur_work_dir + cur_reg_name
  171. if print_log:
  172. print('Registering hv to given volume - either MR or atlas')
  173. if not path.exists(cur_reg_path):
  174. registration(template, hv, cur_work_dir + 'hv_to_template.nii', reg, vol_sigma,
  175. vol_shrink, params, dim=3, initial_trans=initial_transform,
  176. inverse_result=cur_reg_path)
  177. # Define and create directory for sliced template.
  178. template_slice_dir = cur_work_dir + cur_reg_name.split('.')[0] + '-slices/'
  179. make_dirs(template_slice_dir)
  180. if print_log:
  181. print('Slicing', cur_reg_path, 'into', template_slice_dir)
  182. # Slice template.
  183. slice_volume(cur_reg_path, template_slice_dir)
  184. template_slices = files_in_dir(template_slice_dir, '.nii')
  185. if len(raw_slices) == len(template_slices):
  186. print('Number of images matches.')
  187. else:
  188. sys.exit('Mismatch between number of images.')
  189. # Make directory for slice to slice registrations.
  190. reg_slices_dir = cur_work_dir + pretty(i) + '-raw_reg_template/'
  191. make_dirs(reg_slices_dir)
  192. # Registered slice postfix.
  193. reg_slice_postfix = '_reg'
  194. # Perform slice to slice registrations.
  195. for n, (raw_slice, template_slice) in enumerate(zip(raw_slices, template_slices)):
  196. reg_slice = reg_slices_dir + pretty(i) + '_S' + pretty(n) + reg_slice_postfix + '.nii'
  197. if not path.exists(reg_slice):
  198. registration(template_slice, raw_slice, reg_slice, reg,
  199. slice_sigma, slice_shrink, params, 2)
  200. # Stack registered slices into new volume.
  201. reg_slices = files_ind_dir_wo_string(reg_slices_dir, exclusion_string='-Transformation')
  202. final_volume = out_dir + pretty(i + 1) + volume_postfix + '.nii'
  203. make_volume(reg_slices, final_volume)
  204. # Return final reconstructed brain volume.
  205. return final_volume
  206. if __name__ == '__main__':
  207. arguments = sys.argv
  208. log = False
  209. if len(arguments) == 1:
  210. print(usage_message)
  211. else:
  212. try:
  213. opts, args = getopt.getopt(arguments[1:],
  214. "he:s:r:t:d:o:l:",
  215. ["template=", "slices=", "rawvolume", "inittrans", "id", "output", "log="])
  216. except getopt.GetoptError:
  217. print('\nSomething is not right in your syntax. Tak a look at your possibilities:\n', usage_message)
  218. sys.exit(2)
  219. for opt, arg in opts:
  220. if opt == "-h":
  221. print(usage_message)
  222. sys.exit()
  223. elif opt in ("-e", "--template"):
  224. input_template = arg
  225. elif opt in ("-s", "--slices"):
  226. input_slices = arg
  227. elif opt in ("-r", "--rawvolume"):
  228. input_raw_volume = arg
  229. elif opt in ("-t", "--inittrans"):
  230. input_init_trans = arg
  231. elif opt in ("-d", "--id"):
  232. input_brain_id = arg
  233. elif opt in ("-o", "--output"):
  234. output_dir = arg
  235. elif opt in ("-l", "--log"):
  236. if arg == 'True' or arg == 'true':
  237. input_print_log = True
  238. elif arg == 'False' or arg == 'false':
  239. input_print_log = False
  240. else:
  241. print('\nSomething is not right in your syntax. Tak a look at your possibilities:\n', usage_message)
  242. sys.exit(2)
  243. try:
  244. co_register(input_template,
  245. input_slices,
  246. input_raw_volume,
  247. input_init_trans,
  248. output_dir,
  249. input_brain_id,
  250. print_log=input_print_log)
  251. except NameError:
  252. print('\nYou are not (properly) defining all the input variables. Tak a look at what is needed:\n',
  253. usage_message)