[853718]: / bm_experiments / bm_ANTs.py

Download this file

322 lines (267 with data), 12.0 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
"""
Benchmark for ANTs
see:
* http://stnava.github.io/ANTs
* https://sourceforge.net/projects/advants/
* https://github.com/stnava/ANTsDoc/issues/1
INSTALLATION:
See: https://brianavants.wordpress.com/2012/04/13/updated-ants-compile-instructions-april-12-2012/
* Do NOT download the binary code, there is an issue:
- https://sourceforge.net/projects/advants/files/ANTS/ANTS_Latest
- https://github.com/ANTsX/ANTs/issues/733
* Compile from source::
git clone git://github.com/stnava/ANTs.git
mkdir antsbin
cd antsbin
ccmake ../ANTs
make -j$(nproc)
Discussion
----------
I. believes he found the "problem" and indeed it has to do with the file format we use (JPEG).
I. converts the kidney images to 8-bit and then .NII.GZ and the whole pipeline works fine.
.. note:: For showing parameter/options setting run `antsRegistration --help`
1) Convert images to 8-bit using Fiji (this is only because I didn't see any ITK format to store RGB images).
2) Convert the 8-bit images to .nii.gz (using ANTs script `ConvertImagePixelType`)::
ConvertImagePixelType \
Rat_Kidney_PanCytokeratin-8bit.png \
Rat_Kidney_PanCytokeratin.nii.gz \
1
3) Register images using `antsRegistrationSyN.sh`::
antsRegistrationSyN.sh \
-d 2 \
-m Rat_Kidney_PanCytokeratin.nii.gz \
-f Rat_Kidney_HE.nii.gz \
-j 1 \
-t s \
-o output \
> stdout-reg.txt 2> stderr-reg.txt
4) Apply transform to points::
antsApplyTransformsToPoints \
-d 2 \
-i Rat_Kidney_PanCytokeratin.csv \
-o testPointsHE.csv \
-t [ output0GenericAffine.mat, 1 ] \
-t output1InverseWarp.nii.gz
Usage
-----
Run the basic ANT registration with original parameters::
python bm_experiments/bm_ANTs.py \
-t ./data-images/pairs-imgs-lnds_anhir.csv \
-d ./data-images \
-o ./results \
--path_ANTs $HOME/Applications/antsbin/bin \
--path_config ./configs/ANTs_SyN.txt
.. note:: it was needed to use own compiled version
Copyright (C) 2017-2019 Jiri Borovec <jiri.borovec@fel.cvut.cz>
"""
import glob
import logging
import os
import shutil
import sys
import pandas as pd
sys.path += [os.path.abspath('.'), os.path.abspath('..')] # Add path to root
from birl.benchmark import ImRegBenchmark
from birl.utilities.data_io import (
convert_image_from_nifti,
convert_image_to_nifti_gray,
load_config_args,
load_landmarks,
save_landmarks,
)
from birl.utilities.experiments import exec_commands
from bm_experiments import bm_comp_perform
class BmANTs(ImRegBenchmark):
""" Benchmark for ANTs
no run test while this method requires manual compilation of ANTs
EXAMPLE
-------
>>> from birl.utilities.data_io import create_folder, update_path
>>> path_out = create_folder('temp_results')
>>> fn_path_conf = lambda n: os.path.join(update_path('configs'), n)
>>> path_csv = os.path.join(update_path('data-images'), 'pairs-imgs-lnds_mix.csv')
>>> params = {'path_table': path_csv,
... 'path_out': path_out,
... 'nb_workers': 2,
... 'unique': False,
... 'path_ANTs': '.',
... 'path_config': '.'}
>>> benchmark = BmANTs(params)
>>> benchmark.EXECUTE_TIMEOUT
10800
>>> benchmark.run() # doctest: +SKIP
>>> shutil.rmtree(path_out, ignore_errors=True)
"""
#: timeout for executing single image registration
EXECUTE_TIMEOUT = 3 * 60 * 60 # default = 3 hour
#: required experiment parameters
REQUIRED_PARAMS = ImRegBenchmark.REQUIRED_PARAMS + ['path_config']
#: executable for performing image registration
EXEC_REGISTRATION = 'antsRegistration'
#: executable for performing image transformation
EXEC_TRANSFORM_IMAGE = 'antsApplyTransforms'
#: executable for performing landmarks transformation
EXEC_TRANSFORM_POINTS = 'antsApplyTransformsToPoints'
#: command for executing the image registration
COMMAND_REGISTER = '%(antsRegistration)s \
--dimensionality 2 \
%(config)s \
--output [%(output)s/trans]'
#: command for executing the warping image
COMMAND_WARP_IMAGE = '%(antsApplyTransforms)s \
--dimensionality 2 \
--input %(img_source)s \
--output %(output)s/%(img_name)s.nii \
--reference-image %(img_target)s \
--transform %(transfs)s \
--interpolation Linear'
#: command for executing the warping landmarks
COMMAND_WARP_POINTS = '%(antsApplyTransformsToPoints)s \
--dimensionality 2 \
--input %(path_points)s \
--output %(output)s/%(pts_name)s.csv \
--transform %(transfs)s'
#: column name of temporary Nifty reference image
COL_IMAGE_REF_NII = ImRegBenchmark.COL_IMAGE_REF + ' Nifty'
#: column name of temporary Nifty noving image
COL_IMAGE_MOVE_NII = ImRegBenchmark.COL_IMAGE_MOVE + ' Nifty'
def _prepare(self):
""" prepare BM - copy configurations """
logging.info('-> copy configuration...')
self._copy_config_to_expt('path_config')
# this is not possible since the executables can be in std path
# REQUIRED_EXECUTABLES = (self.EXEC_REGISTRATION,
# self.EXEC_TRANSFORM_IMAGE,
# self.EXEC_TRANSFORM_POINTS)
# path_execs = [os.path.join(self.params['path_ANTs'], execute)
# for execute in REQUIRED_EXECUTABLES]
# assert all(os.path.isfile(p) for p in path_execs), \
# 'Some executables are missing: %r' \
# % [p for p in path_execs if not os.path.isfile(p)]
p_ants = self.params.get('path_ANTs', '')
if p_ants and os.path.isdir(p_ants):
logging.info('using local executions from: %s', p_ants)
def _exec_update(executable):
is_path = p_ants and os.path.isdir(p_ants)
return os.path.join(p_ants, executable) if is_path else executable
self.exec_register = _exec_update(self.EXEC_REGISTRATION)
self.exec_transf_img = _exec_update(self.EXEC_TRANSFORM_IMAGE)
self.exec_transf_pts = _exec_update(self.EXEC_TRANSFORM_POINTS)
def _prepare_img_registration(self, item):
""" prepare the experiment folder if it is required,
eq. copy some extra files
:param dict item: dictionary with regist. params
:return dict: the same or updated registration info
"""
logging.debug('.. generate command before registration experiment')
# set the paths for this experiment
path_dir = self._get_path_reg_dir(item)
path_im_ref, path_im_move, _, _ = self._get_paths(item)
# Convert images to Nifty
try: # catching issue with too large images
item[self.COL_IMAGE_REF_NII] = convert_image_to_nifti_gray(path_im_ref, path_dir)
except Exception:
logging.exception('Converting: %s', path_im_ref)
return
try: # catching issue with too large images
item[self.COL_IMAGE_MOVE_NII] = convert_image_to_nifti_gray(path_im_move, path_dir)
except Exception:
logging.exception('Converting: %s', path_im_move)
return
return item
def _generate_regist_command(self, item):
""" generate the registration command(s)
:param dict item: dictionary {str: str|float} with registration params
:return str|list(str): the execution commands
"""
path_dir = self._get_path_reg_dir(item)
config = load_config_args(self.params['path_config'])
config = config % {'target-image': item[self.COL_IMAGE_REF_NII], 'source-image': item[self.COL_IMAGE_MOVE_NII]}
cmd = self.COMMAND_REGISTER % {'config': config, 'antsRegistration': self.exec_register, 'output': path_dir}
return cmd
def _extract_warped_image_landmarks(self, item):
""" get registration results - warped registered images and landmarks
:param dict item: dictionary {str: value} with registration params
:return dict: paths to results
"""
path_dir = self._get_path_reg_dir(item)
_, _, _, path_lnds_move = self._get_paths(item)
name_im_move, _ = os.path.splitext(os.path.basename(path_lnds_move))
name_lnds_move, _ = os.path.splitext(os.path.basename(path_lnds_move))
# simplified version of landmarks
lnds = load_landmarks(path_lnds_move)
path_lnds_warp = os.path.join(path_dir, name_lnds_move + '.csv')
# https://github.com/ANTsX/ANTs/issues/733#issuecomment-472049427
pd.DataFrame(lnds * -1, columns=['x', 'y']).to_csv(path_lnds_warp, index=None)
# list output transformations
tf_elast_inv = sorted(glob.glob(os.path.join(path_dir, 'trans*InverseWarp.nii*')))
tf_elast = [os.path.join(os.path.dirname(p), os.path.basename(p).replace('Inverse', '')) for p in tf_elast_inv]
tf_affine = sorted(glob.glob(os.path.join(path_dir, 'trans*GenericAffine.mat')))
# generate commands
cmd_warp_img = self.COMMAND_WARP_IMAGE % {
'antsApplyTransforms': self.exec_transf_img,
'output': path_dir,
'img_target': item[self.COL_IMAGE_REF_NII],
'img_source': item[self.COL_IMAGE_MOVE_NII],
'transfs': ' -t '.join(sorted(tf_affine + tf_elast, reverse=True)),
'img_name': name_im_move
}
_transfs = ' -t '.join(['[ %s , 1]' % tf if 'Affine' in tf else tf for tf in sorted(tf_affine + tf_elast_inv)])
cmd_warp_pts = self.COMMAND_WARP_POINTS % {
'antsApplyTransformsToPoints': self.exec_transf_pts,
'output': path_dir,
'path_points': path_lnds_warp,
'transfs': _transfs,
'pts_name': name_lnds_move
}
# execute commands
exec_commands([cmd_warp_img, cmd_warp_pts], path_logger=os.path.join(path_dir, 'warping.log'))
path_im_nii = os.path.join(path_dir, name_im_move + '.nii')
if os.path.isfile(path_im_nii):
path_img_warp = convert_image_from_nifti(path_im_nii)
else:
path_img_warp = None
if os.path.isfile(path_lnds_warp):
lnds = pd.read_csv(path_lnds_warp, index_col=None).values
save_landmarks(path_lnds_warp, lnds * -1)
else:
path_lnds_warp = None
return {
self.COL_IMAGE_MOVE_WARP: path_img_warp,
self.COL_POINTS_MOVE_WARP: path_lnds_warp,
}
def _clear_after_registration(self, item):
""" clean unnecessarily files after the registration
:param dict item: dictionary with regist. information
:return dict: the same or updated registration info
"""
for ext in ['*.nii', '*.nii.gz', '*.mat']:
for p in glob.glob(os.path.join(self._get_path_reg_dir(item), ext)):
os.remove(p)
del item[self.COL_IMAGE_REF_NII]
del item[self.COL_IMAGE_MOVE_NII]
return item
@staticmethod
def extend_parse(arg_parser):
""" extent the basic arg parses by some extra required parameters
:return object:
"""
# SEE: https://docs.python.org/3/library/argparse.html
arg_parser.add_argument(
'-ANTs',
'--path_ANTs',
type=str,
required=False,
help='path to the ANTs executables (if they are not directly callable)'
)
arg_parser.add_argument(
'-cfg', '--path_config', required=True, type=str, help='path to the ANTs regist. configuration'
)
return arg_parser
# RUN by given parameters
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
logging.info(__doc__)
arg_params, path_expt = BmANTs.main()
if arg_params.get('run_comp_benchmark', False):
bm_comp_perform.main(path_expt)