[853718]: / bm_experiments / bm_DROP.py

Download this file

279 lines (228 with data), 11.1 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
"""
Benchmark for DROP.
DROP is a approach for image registration and motion estimation based on Markov Random Fields.
.. seealso:: https://www.mrf-registration.net
Related Publication:
1. Deformable Medical Image Registration: Setting the State of the Art with Discrete Methods
Authors: Ben Glocker, Aristeidis Sotiras, Nikos Komodakis, Nikos Paragios
Published in: Annual Review of Biomedical Engineering, Vol. 12, 2011, pp. 219-244
**Installation for Linux**
1. Download executable according your operation system,
https://www.mrf-registration.net/deformable/index.html
2. Copy/extract executables and libraries to you favourite destination
3. Install all missing libraries such as QT4 with OpenGL support
4. Test calling the executable `./dropreg2d` which should return something like::
Usage: dropreg2d <source> <target> <result> <paramfile> [mask]
Usage
-----
To see the explanation of particular parameters see the User Manual
http://www.mrf-registration.net/download/drop_user_guide_V1.05.pdf
Sample run::
mkdir ./results
python bm_experiments/bm_DROP.py \
-t ./data-images/pairs-imgs-lnds_histol.csv \
-d ./data-images \
-o ./results \
-DROP $HOME/Applications/DROP/dropreg2d \
--path_config ./configs/DROP.txt \
--visual --unique
.. note:: experiments was tested on Ubuntu (Linux) based OS system
.. note:: to check whether you have all needed libraries on Linux use `ldd dropreg2d`,
see: https://askubuntu.com/a/709271/863070
AND set path to the `libdroplib.so` as `export LD_LIBRARY_PATH=/lib:/usr/lib:/usr/local/lib`,
see: https://unix.stackexchange.com/a/67783 ; https://stackoverflow.com/a/49660575/4521646
.. note:: This method is not optimized nor suitable for large images, so all used images
are first scaled to be 2000x2000 pixels and then the registration is performed.
After registration is resulting image scaled back. The landmarks are scalded accordingly.
Glocker, Ben, et al. "Deformable medical image registration: setting the state of the art
with discrete methods." Annual review of biomedical engineering 13 (2011): 219-244.
https://pdfs.semanticscholar.org/0c5f/277d357e3667b18b5420fd660f221c935fcc.pdf
Copyright (C) 2017-2019 Jiri Borovec <jiri.borovec@fel.cvut.cz>
"""
import glob
import logging
import os
import shutil
import sys
import time
import numpy as np
import SimpleITK as sitk
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_mhd,
convert_image_to_mhd,
image_sizes,
load_landmarks,
save_landmarks,
)
from bm_experiments import bm_comp_perform
class BmDROP(ImRegBenchmark):
""" Benchmark for DROP
no run test while this method requires manual installation of DROP
For the app installation details, see module details.
.. note:: DROP requires gray scale images in MHD format where pixel values
are in range (0, 255) of uint8.
Example
-------
>>> from birl.utilities.data_io import create_folder, update_path
>>> path_out = create_folder('temp_results')
>>> 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,
... 'visual': True,
... 'exec_DROP': 'dropreg2d',
... 'path_config': os.path.join(update_path('configs'), 'DROP.txt')}
>>> benchmark = BmDROP(params)
>>> benchmark.run() # doctest: +SKIP
>>> import shutil
>>> shutil.rmtree(path_out, ignore_errors=True)
"""
#: required experiment parameters
REQUIRED_PARAMS = ImRegBenchmark.REQUIRED_PARAMS + ['exec_DROP', 'path_config']
#: maximal image size (diagonal in pixels) recommended for DROP registration
MAX_IMAGE_DIAGONAL = int(np.sqrt(2e3**2 + 2e3**2))
#: time need for image conversion and optional scaling
COL_TIME_CONVERT = 'conversion time [s]'
def _prepare(self):
logging.info('-> copy configuration...')
self._copy_config_to_expt('path_config')
def _prepare_img_registration(self, item):
""" converting the input images to gra-scale and MHD format
:param dict item: dictionary with registration params
:return dict: the same or updated registration info
"""
logging.debug('.. converting images to MHD')
path_im_ref, path_im_move, _, _ = self._get_paths(item)
path_reg_dir = self._get_path_reg_dir(item)
diags = [image_sizes(p_img)[1] for p_img in (path_im_ref, path_im_move)]
item['scaling'] = max(1, max(diags) / float(self.MAX_IMAGE_DIAGONAL))
t_start = time.time()
for path_img, col in [(path_im_ref, self.COL_IMAGE_REF), (path_im_move, self.COL_IMAGE_MOVE)]:
item[col + self.COL_IMAGE_EXT_TEMP] = convert_image_to_mhd(
path_img, path_out_dir=path_reg_dir, overwrite=False, to_gray=True, scaling=item.get('scaling', 1.)
)
item[self.COL_TIME_CONVERT] = time.time() - t_start
return item
def _generate_regist_command(self, item):
""" generate the registration command
:param dict item: dictionary with registration params
:return str|list(str): the execution commands
"""
logging.debug('.. prepare DROP registration command')
path_im_ref, path_im_move, _, _ = self._get_paths(item)
path_dir = self._get_path_reg_dir(item)
# NOTE: for some reason " in the command makes it crash in Run mode
# somehow it works fine even with " in Debug mode
command = ' '.join([
self.params['exec_DROP'],
path_im_move,
path_im_ref,
os.path.join(path_dir, 'output'),
self.params['path_config'],
])
return command
def _extract_warped_image_landmarks(self, item):
""" get registration results - warped registered images and landmarks
:param dict item: dictionary with registration params
:return dict: paths to warped images/landmarks
"""
path_reg_dir = self._get_path_reg_dir(item)
_, path_im_move, path_lnds_ref, _ = self._get_paths(item)
# convert MHD image
path_img_ = convert_image_from_mhd(os.path.join(path_reg_dir, 'output.mhd'), scaling=item.get('scaling', 1.))
img_name, _ = os.path.splitext(os.path.basename(path_im_move))
_, img_ext = os.path.splitext(os.path.basename(path_img_))
path_img_warp = path_img_.replace('output' + img_ext, img_name + img_ext)
shutil.move(path_img_, path_img_warp)
# load transform and warp landmarks
# lnds_move = load_landmarks(path_lnds_move)
lnds_ref = load_landmarks(path_lnds_ref)
lnds_name = os.path.basename(path_lnds_ref)
path_lnds_warp = os.path.join(path_reg_dir, lnds_name)
if lnds_ref is None:
raise ValueError('missing landmarks to be transformed "%s"' % lnds_name)
# down-scale landmarks if defined
lnds_ref = lnds_ref / item.get('scaling', 1.)
# extract deformation
path_deform_x = os.path.join(path_reg_dir, 'output_x.mhd')
path_deform_y = os.path.join(path_reg_dir, 'output_y.mhd')
try:
shift = self.extract_landmarks_shift_from_mhd(path_deform_x, path_deform_y, lnds_ref)
except Exception:
logging.exception(path_reg_dir)
shift = np.zeros(lnds_ref.shape)
# lnds_warp = lnds_move - shift
lnds_warp = lnds_ref + shift
# upscale landmarks if defined
lnds_warp = lnds_warp * item.get('scaling', 1.)
save_landmarks(path_lnds_warp, lnds_warp)
# return formatted results
return {
self.COL_IMAGE_MOVE_WARP: path_img_warp,
self.COL_POINTS_REF_WARP: path_lnds_warp,
}
def _clear_after_registration(self, item, patterns=('output*', '*.mhd', '*.raw')):
""" clean unnecessarily files after the registration
:param dict item: dictionary with registration information
:param list(str) patterns: string patterns of file names
:return dict: the same or updated registration info
"""
logging.debug('.. cleaning after registration experiment, remove `output`')
path_reg_dir = self._get_path_reg_dir(item)
for ptn in patterns:
for p_file in glob.glob(os.path.join(path_reg_dir, ptn)):
os.remove(p_file)
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(
'-DROP', '--exec_DROP', type=str, required=True, help='path to DROP executable, use `dropreg2d`'
)
arg_parser.add_argument(
'-cfg', '--path_config', type=str, required=True, help='parameters for DROP registration'
)
return arg_parser
@staticmethod
def extract_landmarks_shift_from_mhd(path_deform_x, path_deform_y, lnds):
""" given pair of deformation fields and landmark positions get shift
:param str path_deform_x: path to deformation field in X axis
:param str path_deform_y: path to deformation field in Y axis
:param ndarray lnds: landmarks
:return ndarray: shift for each landmarks
"""
# define function for parsing particular shift from MHD
def __parse_shift(path_deform_, lnds):
if not os.path.isfile(path_deform_):
raise FileNotFoundError('missing deformation: %s' % path_deform_)
deform_ = sitk.GetArrayFromImage(sitk.ReadImage(path_deform_))
if deform_ is None:
raise ValueError('loaded deformation is Empty - %s' % path_deform_)
lnds_max = np.max(lnds, axis=0)[::-1]
if not all(ln < dim for ln, dim in zip(lnds_max, deform_.shape)):
raise ValueError(
'landmarks max %s is larger then (exceeded) deformation shape %s' %
(lnds_max.tolist(), deform_.shape)
)
shift_ = deform_[lnds[:, 1], lnds[:, 0]]
return shift_
lnds = np.array(np.round(lnds), dtype=int)
# get shift in both axis
shift_x = __parse_shift(path_deform_x, lnds)
shift_y = __parse_shift(path_deform_y, lnds)
# concatenate
shift = np.array([shift_x, shift_y]).T
return shift
# RUN by given parameters
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
logging.info(__doc__)
arg_params, path_expt = BmDROP.main()
if arg_params.get('run_comp_benchmark', False):
bm_comp_perform.main(path_expt)