dbdicom 0.2.0__py3-none-any.whl → 0.3.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dbdicom/__init__.py +3 -25
- dbdicom/api.py +496 -0
- dbdicom/const.py +144 -0
- dbdicom/database.py +133 -0
- dbdicom/dataset.py +471 -0
- dbdicom/dbd.py +1290 -0
- dbdicom/external/__pycache__/__init__.cpython-311.pyc +0 -0
- dbdicom/external/dcm4che/__pycache__/__init__.cpython-311.pyc +0 -0
- dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-311.pyc +0 -0
- dbdicom/external/dcm4che/bin/emf2sf +57 -57
- dbdicom/register.py +402 -0
- dbdicom/{ds/types → sop_classes}/ct_image.py +2 -16
- dbdicom/{ds/types → sop_classes}/enhanced_mr_image.py +206 -160
- dbdicom/sop_classes/mr_image.py +338 -0
- dbdicom/sop_classes/parametric_map.py +381 -0
- dbdicom/sop_classes/secondary_capture.py +140 -0
- dbdicom/sop_classes/segmentation.py +311 -0
- dbdicom/{ds/types → sop_classes}/ultrasound_multiframe_image.py +1 -15
- dbdicom/{ds/types → sop_classes}/xray_angiographic_image.py +2 -17
- dbdicom/utils/arrays.py +142 -0
- dbdicom/utils/files.py +0 -20
- dbdicom/utils/image.py +43 -466
- dbdicom/utils/pydicom_dataset.py +386 -0
- dbdicom-0.3.16.dist-info/METADATA +26 -0
- dbdicom-0.3.16.dist-info/RECORD +54 -0
- {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info}/WHEEL +1 -1
- dbdicom/create.py +0 -450
- dbdicom/ds/__init__.py +0 -10
- dbdicom/ds/create.py +0 -63
- dbdicom/ds/dataset.py +0 -841
- dbdicom/ds/dictionaries.py +0 -620
- dbdicom/ds/types/mr_image.py +0 -267
- dbdicom/ds/types/parametric_map.py +0 -226
- dbdicom/external/__pycache__/__init__.cpython-310.pyc +0 -0
- dbdicom/external/__pycache__/__init__.cpython-37.pyc +0 -0
- dbdicom/external/dcm4che/__pycache__/__init__.cpython-310.pyc +0 -0
- dbdicom/external/dcm4che/__pycache__/__init__.cpython-37.pyc +0 -0
- dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-310.pyc +0 -0
- dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-37.pyc +0 -0
- dbdicom/external/dcm4che/lib/linux-x86/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/linux-x86-64/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/linux-x86-64/libopencv_java.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio_vis.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio_vis2.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio_vis.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio_vis2.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-x86/libclib_jiio.so +0 -0
- dbdicom/external/dcm4che/lib/solaris-x86-64/libclib_jiio.so +0 -0
- dbdicom/manager.py +0 -2077
- dbdicom/message.py +0 -119
- dbdicom/record.py +0 -1526
- dbdicom/types/database.py +0 -107
- dbdicom/types/instance.py +0 -184
- dbdicom/types/patient.py +0 -40
- dbdicom/types/series.py +0 -816
- dbdicom/types/study.py +0 -58
- dbdicom/utils/variables.py +0 -155
- dbdicom/utils/vreg.py +0 -2626
- dbdicom/wrappers/__init__.py +0 -7
- dbdicom/wrappers/dipy.py +0 -462
- dbdicom/wrappers/elastix.py +0 -855
- dbdicom/wrappers/numpy.py +0 -119
- dbdicom/wrappers/scipy.py +0 -1413
- dbdicom/wrappers/skimage.py +0 -1030
- dbdicom/wrappers/sklearn.py +0 -151
- dbdicom/wrappers/vreg.py +0 -273
- dbdicom-0.2.0.dist-info/METADATA +0 -276
- dbdicom-0.2.0.dist-info/RECORD +0 -81
- {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info/licenses}/LICENSE +0 -0
- {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info}/top_level.txt +0 -0
dbdicom/utils/vreg.py
DELETED
|
@@ -1,2626 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
import numpy as np
|
|
3
|
-
import scipy.stats as stats
|
|
4
|
-
import scipy.optimize as opt
|
|
5
|
-
import scipy.ndimage as ndi
|
|
6
|
-
from scipy.interpolate import interpn
|
|
7
|
-
from scipy.spatial.transform import Rotation
|
|
8
|
-
import sklearn
|
|
9
|
-
|
|
10
|
-
import pyvista as pv
|
|
11
|
-
|
|
12
|
-
from skimage.draw import ellipsoid
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
##########################
|
|
16
|
-
# Helper functions
|
|
17
|
-
##########################
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def volume_coordinates(shape, position=[0,0,0]):
|
|
21
|
-
|
|
22
|
-
# data are defined at the middle of the voxels - use p+0.5 offset.
|
|
23
|
-
xo, yo, zo = np.meshgrid(
|
|
24
|
-
np.arange(position[0]+0.5, position[0]+0.5+shape[0]),
|
|
25
|
-
np.arange(position[1]+0.5, position[1]+0.5+shape[1]),
|
|
26
|
-
np.arange(position[2]+0.5, position[2]+0.5+shape[2]),
|
|
27
|
-
indexing = 'ij')
|
|
28
|
-
return np.column_stack((xo.ravel(), yo.ravel(), zo.ravel()))
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
# def _interpolate_displacement(displacement_field, shape, **kwargs):
|
|
32
|
-
|
|
33
|
-
# # Get x, y, z coordinates for deformation field
|
|
34
|
-
# w = np.array(displacement_field.shape[:-1])-1
|
|
35
|
-
# x = np.linspace(0, w[0], displacement_field.shape[0])
|
|
36
|
-
# y = np.linspace(0, w[1], displacement_field.shape[1])
|
|
37
|
-
# z = np.linspace(0, w[2], displacement_field.shape[2])
|
|
38
|
-
|
|
39
|
-
# # Get x, y, z coordinates for voxel centers
|
|
40
|
-
# di = np.divide(w, shape)
|
|
41
|
-
# xi = np.linspace(0.5*di[0], w[0]-0.5*di[0], shape[0])
|
|
42
|
-
# yi = np.linspace(0.5*di[1], w[1]-0.5*di[1], shape[1])
|
|
43
|
-
# zi = np.linspace(0.5*di[2], w[2]-0.5*di[2], shape[2])
|
|
44
|
-
|
|
45
|
-
# # Create coordinate array
|
|
46
|
-
# dim = np.arange(3)
|
|
47
|
-
# #ri = np.meshgrid(xi, yi, zi, indexing='ij')
|
|
48
|
-
# ri = np.meshgrid(xi, yi, zi, dim, indexing='ij')
|
|
49
|
-
# ri = np.stack(ri, axis=-1)
|
|
50
|
-
|
|
51
|
-
# # Interpolate the displacement field in the voxel centers
|
|
52
|
-
# #dx = interpn((x,y,z), displacement_field[...,0], ri, method='linear')
|
|
53
|
-
# #dy = interpn((x,y,z), displacement_field[...,1], ri, method='linear')
|
|
54
|
-
# #dz = interpn((x,y,z), displacement_field[...,2], ri, method='linear')
|
|
55
|
-
# #displacement_field = np.column_stack((dx,dy,dz))
|
|
56
|
-
# displacement_field = interpn((x,y,z,dim), displacement_field, ri, method='linear')
|
|
57
|
-
# displacement_field = np.reshape(displacement_field, (np.prod(shape), 3))
|
|
58
|
-
|
|
59
|
-
# # Return results
|
|
60
|
-
# return displacement_field
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def interpolate_displacement_coords(dshape, shape):
|
|
64
|
-
"""Get the coordinates of the interpolated displacement field."""
|
|
65
|
-
|
|
66
|
-
w = np.array(dshape[:-1])-1
|
|
67
|
-
d = np.divide(w, shape)
|
|
68
|
-
xo, yo, zo = np.meshgrid(
|
|
69
|
-
np.linspace(0.5*d[0], w[0]-0.5*d[0], shape[0]),
|
|
70
|
-
np.linspace(0.5*d[1], w[1]-0.5*d[1], shape[1]),
|
|
71
|
-
np.linspace(0.5*d[2], w[2]-0.5*d[2], shape[2]),
|
|
72
|
-
indexing = 'ij')
|
|
73
|
-
return np.column_stack((xo.ravel(), yo.ravel(), zo.ravel())).T
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def interpolate_displacement(displacement_field, shape, coord=None, **kwargs):
|
|
77
|
-
|
|
78
|
-
if coord is None:
|
|
79
|
-
coord = interpolate_displacement_coords(displacement_field.shape, shape)
|
|
80
|
-
|
|
81
|
-
# Interpolate displacement field in volume coordinates.
|
|
82
|
-
dx = ndi.map_coordinates(displacement_field[...,0], coord, **kwargs)
|
|
83
|
-
dy = ndi.map_coordinates(displacement_field[...,1], coord, **kwargs)
|
|
84
|
-
dz = ndi.map_coordinates(displacement_field[...,2], coord, **kwargs)
|
|
85
|
-
deformation = np.column_stack((dx,dy,dz))
|
|
86
|
-
#deformation = np.reshape(deformation, (np.prod(shape), 3))
|
|
87
|
-
|
|
88
|
-
return deformation
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def freeform_interpolator(dshape, shape, **kwargs):
|
|
92
|
-
iind = []
|
|
93
|
-
ival = []
|
|
94
|
-
coord = interpolate_displacement_coords(dshape, shape)
|
|
95
|
-
displacement = np.zeros(dshape, dtype=np.float32)
|
|
96
|
-
for i in range(displacement.size):
|
|
97
|
-
c = np.unravel_index(i, displacement.shape)
|
|
98
|
-
displacement[c] = 1
|
|
99
|
-
v = interpolate_displacement(displacement, shape, coord, **kwargs)
|
|
100
|
-
v = np.ravel(v)
|
|
101
|
-
displacement[c] = 0
|
|
102
|
-
ind = np.where(v != 0)
|
|
103
|
-
val = v[ind]
|
|
104
|
-
iind.append(ind)
|
|
105
|
-
ival.append(val)
|
|
106
|
-
return {'ind':iind, 'val':ival}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def interpolator_displacement(displacement, shape, interpolator):
|
|
110
|
-
|
|
111
|
-
displacement_interp = np.zeros((np.prod(shape)*3,), np.float32)
|
|
112
|
-
for i in range(displacement.size):
|
|
113
|
-
c = np.unravel_index(i, displacement.shape)
|
|
114
|
-
displacement_interp[interpolator['ind'][i]] += displacement[c]*interpolator['val'][i]
|
|
115
|
-
displacement_interp = np.reshape(displacement_interp, (np.prod(shape), 3))
|
|
116
|
-
return displacement_interp
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def surface_coordinates(shape):
|
|
121
|
-
|
|
122
|
-
# data are defined at the edge of volume - extend shape with 1.
|
|
123
|
-
xo, yo, zo = np.meshgrid(
|
|
124
|
-
np.arange(1.0 + shape[0]),
|
|
125
|
-
np.arange(1.0 + shape[1]),
|
|
126
|
-
np.arange(1.0 + shape[2]),
|
|
127
|
-
indexing = 'ij')
|
|
128
|
-
return np.column_stack((xo.ravel(), yo.ravel(), zo.ravel()))
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def extend_border(r, shape):
|
|
132
|
-
|
|
133
|
-
# Shift with half a voxel because data are defined at voxel centers
|
|
134
|
-
r -= 0.5
|
|
135
|
-
|
|
136
|
-
# Set coordinates at 0.5 pixel from the borders equal to the borders
|
|
137
|
-
x0, x1 = 0, shape[0]-1
|
|
138
|
-
y0, y1 = 0, shape[1]-1
|
|
139
|
-
z0, z1 = 0, shape[2]-1
|
|
140
|
-
|
|
141
|
-
r[np.where(np.logical_and(x0-0.5 <= r[:,0], r[:,0] <= x0)), 0] = x0
|
|
142
|
-
r[np.where(np.logical_and(x1+0.5 >= r[:,0], r[:,0] >= x1)), 0] = x1
|
|
143
|
-
r[np.where(np.logical_and(y0-0.5 <= r[:,1], r[:,1] <= y0)), 1] = y0
|
|
144
|
-
r[np.where(np.logical_and(y1+0.5 >= r[:,1], r[:,1] >= y1)), 1] = y1
|
|
145
|
-
r[np.where(np.logical_and(z0-0.5 <= r[:,2], r[:,2] <= z0)), 2] = z0
|
|
146
|
-
r[np.where(np.logical_and(z1+0.5 >= r[:,2], r[:,2] >= z1)), 2] = z1
|
|
147
|
-
|
|
148
|
-
return r
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def pv_contour(values, data, affine, surface=False):
|
|
152
|
-
|
|
153
|
-
# For display of the surface, interpolate from volume to surface array
|
|
154
|
-
surf_shape = 1 + np.array(data.shape)
|
|
155
|
-
r = surface_coordinates(data.shape)
|
|
156
|
-
r = extend_border(r, data.shape)
|
|
157
|
-
surf_data = ndi.map_coordinates(data, r.T, order=3)
|
|
158
|
-
surf_data = np.reshape(surf_data, surf_shape)
|
|
159
|
-
|
|
160
|
-
rotation, translation, pixel_spacing = affine_components(affine)
|
|
161
|
-
grid = pv.UniformGrid(dimensions=surf_shape, spacing=pixel_spacing)
|
|
162
|
-
surf = grid.contour(values, surf_data.flatten(order="F"), method='marching_cubes')
|
|
163
|
-
surf = surf.rotate_vector(rotation, np.linalg.norm(rotation)*180/np.pi, inplace=False)
|
|
164
|
-
surf = surf.translate(translation, inplace=False)
|
|
165
|
-
if surface:
|
|
166
|
-
surf = surf.reconstruct_surface()
|
|
167
|
-
return surf
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def parallellepid(L, affine=None):
|
|
171
|
-
|
|
172
|
-
c = np.array([0,0,0])
|
|
173
|
-
x = np.array([1,0,0])*L[0]
|
|
174
|
-
y = np.array([0,1,0])*L[1]
|
|
175
|
-
z = np.array([0,0,1])*L[2]
|
|
176
|
-
|
|
177
|
-
# mesh points
|
|
178
|
-
vertices = np.array(
|
|
179
|
-
[ c,
|
|
180
|
-
c+x,
|
|
181
|
-
c+x+z,
|
|
182
|
-
c+z,
|
|
183
|
-
c+y,
|
|
184
|
-
c+y+x,
|
|
185
|
-
c+y+x+z,
|
|
186
|
-
c+y+z,
|
|
187
|
-
]
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
if affine is not None:
|
|
191
|
-
nd = 3
|
|
192
|
-
matrix = affine[:nd,:nd]
|
|
193
|
-
offset = affine[:nd, nd]
|
|
194
|
-
vertices = np.dot(vertices, matrix.T) + offset
|
|
195
|
-
|
|
196
|
-
# mesh faces
|
|
197
|
-
faces = np.hstack(
|
|
198
|
-
[
|
|
199
|
-
[4, 0, 1, 2, 3], #right
|
|
200
|
-
[4, 4, 5, 6, 7], #left
|
|
201
|
-
[4, 0, 1, 5, 4], #bottom
|
|
202
|
-
[4, 2, 3, 7, 6], #top
|
|
203
|
-
[4, 0, 3, 7, 4], #front
|
|
204
|
-
[4, 1, 2, 6, 5], #back
|
|
205
|
-
]
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
return vertices, faces
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
def rotation_displacement(rotation, center):
|
|
212
|
-
|
|
213
|
-
rot = Rotation.from_rotvec(rotation)
|
|
214
|
-
center_rot = rot.apply(center)
|
|
215
|
-
return center_rot-center
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
def center_of_mass(volume, affine):
|
|
219
|
-
"""Return the center of mass in absolute coordinates"""
|
|
220
|
-
|
|
221
|
-
center_of_mass = ndi.center_of_mass(volume)
|
|
222
|
-
nd = volume.ndim
|
|
223
|
-
matrix = affine[:nd,:nd]
|
|
224
|
-
offset = affine[:nd, nd]
|
|
225
|
-
return np.dot(matrix, center_of_mass) + offset
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def affine_components(matrix):
|
|
229
|
-
"""Extract rotation, translation and pixel spacing vector from affine matrix"""
|
|
230
|
-
|
|
231
|
-
nd = matrix.shape[0]-1
|
|
232
|
-
translation = matrix[:nd, nd].copy()
|
|
233
|
-
rotation_matrix = matrix[:nd, :nd].copy()
|
|
234
|
-
pixel_spacing = np.linalg.norm(matrix[:nd, :nd], axis=0)
|
|
235
|
-
for c in range(nd):
|
|
236
|
-
rotation_matrix[:nd, c] /= pixel_spacing[c]
|
|
237
|
-
rot = Rotation.from_matrix(rotation_matrix)
|
|
238
|
-
rotation = rot.as_rotvec()
|
|
239
|
-
return rotation, translation, pixel_spacing
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def affine_resolution(shape, spacing):
|
|
243
|
-
"""Smallest detectable rotation, translation and stretching of a volume with given shape and resolution."""
|
|
244
|
-
|
|
245
|
-
translation_res = spacing
|
|
246
|
-
# Geometry-based rotation resolution needs some more thinking - this produces too small values
|
|
247
|
-
# rot_res_x = min([spacing[1],spacing[2]])/max([shape[1],shape[2]])
|
|
248
|
-
# rot_res_y = min([spacing[2],spacing[0]])/max([shape[2],shape[0]])
|
|
249
|
-
# rot_res_z = min([spacing[0],spacing[1]])/max([shape[0],shape[1]])
|
|
250
|
-
# rot_res = np.array([rot_res_x, rot_res_y, rot_res_z])
|
|
251
|
-
rot_res = np.array([np.pi/180, np.pi/180, np.pi/180])
|
|
252
|
-
scaling_res = np.array([0.01, 0.01, 0.01])
|
|
253
|
-
return rot_res, translation_res, scaling_res
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
def affine_matrix(rotation=None, translation=None, pixel_spacing=None, center=None):
|
|
257
|
-
|
|
258
|
-
nd = 3
|
|
259
|
-
matrix = np.eye(1+nd)
|
|
260
|
-
|
|
261
|
-
if rotation is not None:
|
|
262
|
-
rot = Rotation.from_rotvec(rotation)
|
|
263
|
-
matrix[:nd,:nd] = rot.as_matrix()
|
|
264
|
-
|
|
265
|
-
# Shift to rotate around center
|
|
266
|
-
if center is not None:
|
|
267
|
-
center_rot = rot.apply(center)
|
|
268
|
-
offset = center_rot-center
|
|
269
|
-
matrix[:nd, nd] -= offset
|
|
270
|
-
|
|
271
|
-
if translation is not None:
|
|
272
|
-
matrix[:nd, nd] += translation
|
|
273
|
-
|
|
274
|
-
if pixel_spacing is not None:
|
|
275
|
-
for c in range(nd):
|
|
276
|
-
matrix[:nd, c] *= pixel_spacing[c]
|
|
277
|
-
|
|
278
|
-
return matrix
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
def envelope(d, affine):
|
|
282
|
-
|
|
283
|
-
corners, _ = parallellepid(np.array(d), affine)
|
|
284
|
-
|
|
285
|
-
x0 = np.amin(corners[:,0])
|
|
286
|
-
x1 = np.amax(corners[:,0])
|
|
287
|
-
y0 = np.amin(corners[:,1])
|
|
288
|
-
y1 = np.amax(corners[:,1])
|
|
289
|
-
z0 = np.amin(corners[:,2])
|
|
290
|
-
z1 = np.amax(corners[:,2])
|
|
291
|
-
|
|
292
|
-
nx = np.ceil(x1-x0).astype(np.int16)
|
|
293
|
-
ny = np.ceil(y1-y0).astype(np.int16)
|
|
294
|
-
nz = np.ceil(z1-z0).astype(np.int16)
|
|
295
|
-
|
|
296
|
-
output_shape = (nx, ny, nz)
|
|
297
|
-
output_pos = [x0, y0, z0]
|
|
298
|
-
|
|
299
|
-
return output_shape, output_pos
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
def apply_affine(affine, coord):
|
|
304
|
-
"""Apply affine transformation to an array of coordinates"""
|
|
305
|
-
|
|
306
|
-
nd = affine.shape[0]-1
|
|
307
|
-
matrix = affine[:nd,:nd]
|
|
308
|
-
offset = affine[:nd, nd]
|
|
309
|
-
return np.dot(coord, matrix.T) + offset
|
|
310
|
-
#return np.dot(matrix, co.T).T + offset
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
def deformation_field_shape(data_shape, nodes):
|
|
314
|
-
# Work out the exact number of nodes for each dimensions
|
|
315
|
-
# Assuming the given number of nodes is exact for the largest dimension
|
|
316
|
-
# And the distance between nodes is approximately the same in other directions
|
|
317
|
-
|
|
318
|
-
distance_between_nodes = np.amax(data_shape)/(nodes-1)
|
|
319
|
-
shape = 1 + np.ceil(data_shape/distance_between_nodes)
|
|
320
|
-
return tuple(shape.astype(np.int16))
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
def affine_deformation_field(affine, output_shape, nodes, output_to_input=np.eye(4)):
|
|
324
|
-
|
|
325
|
-
# Initialise the inverse deformation field
|
|
326
|
-
shape = deformation_field_shape(output_shape, nodes)
|
|
327
|
-
|
|
328
|
-
# Get coordinates in output reference frame
|
|
329
|
-
xo, yo, zo = np.meshgrid(
|
|
330
|
-
np.linspace(0, output_shape[0], shape[0]),
|
|
331
|
-
np.linspace(0, output_shape[1], shape[1]),
|
|
332
|
-
np.linspace(0, output_shape[2], shape[2]),
|
|
333
|
-
indexing = 'ij')
|
|
334
|
-
coordinates = np.column_stack((xo.ravel(), yo.ravel(), zo.ravel()))
|
|
335
|
-
|
|
336
|
-
# Express output coordinates in reference frame of the input volume
|
|
337
|
-
coordinates = apply_affine(output_to_input, coordinates)
|
|
338
|
-
|
|
339
|
-
# Apply affine in reference frame of input
|
|
340
|
-
new_coordinates = apply_affine(affine, coordinates)
|
|
341
|
-
|
|
342
|
-
# Get the deformation field in shape (x,y,z,dim)
|
|
343
|
-
deformation_field = new_coordinates - coordinates
|
|
344
|
-
deformation_field = np.reshape(deformation_field, shape + (3,))
|
|
345
|
-
|
|
346
|
-
return deformation_field
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
def upsample_deformation_field(field, ni):
|
|
350
|
-
|
|
351
|
-
new_field = np.empty(ni + (3,))
|
|
352
|
-
L = np.array(field.shape[:3])-1
|
|
353
|
-
|
|
354
|
-
# Get x, y, z coordinates for current field
|
|
355
|
-
x = np.linspace(0, L[0], field.shape[0])
|
|
356
|
-
y = np.linspace(0, L[1], field.shape[0])
|
|
357
|
-
z = np.linspace(0, L[0], field.shape[0])
|
|
358
|
-
|
|
359
|
-
# Get x, y, z coordinates for new field
|
|
360
|
-
xi = np.linspace(0, L[0], new_field.shape[0])
|
|
361
|
-
yi = np.linspace(0, L[1], new_field.shape[1])
|
|
362
|
-
zi = np.linspace(0, L[2], new_field.shape[2])
|
|
363
|
-
|
|
364
|
-
# Interpolate to new resolution
|
|
365
|
-
ri = np.meshgrid(xi, yi, zi, indexing='ij')
|
|
366
|
-
ri = np.stack(ri, axis=-1)
|
|
367
|
-
for d in range(3):
|
|
368
|
-
new_field[...,i] = interpn((x,y,z), field[...,i], ri)
|
|
369
|
-
return new_field
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
def align_freeform(nodes=[2,4,8], static=None, parameters=None, optimization=None, **kwargs):
|
|
373
|
-
|
|
374
|
-
transformation = freeform
|
|
375
|
-
if parameters is None:
|
|
376
|
-
# Initialise the inverse deformation field
|
|
377
|
-
dim = deformation_field_shape(static.shape, nodes[0])
|
|
378
|
-
parameters = np.zeros(dim + (3,))
|
|
379
|
-
|
|
380
|
-
for i in range(nodes):
|
|
381
|
-
|
|
382
|
-
if optimization['method'] == 'GD':
|
|
383
|
-
# Define the step size
|
|
384
|
-
step = np.full(parameters.shape, 0.1)
|
|
385
|
-
optimization['options']['gradient step'] = step
|
|
386
|
-
|
|
387
|
-
# Coregister for nr nodes
|
|
388
|
-
try:
|
|
389
|
-
parameters = align(
|
|
390
|
-
static= static,
|
|
391
|
-
parameters = parameters,
|
|
392
|
-
optimization = optimization,
|
|
393
|
-
transformation = transformation,
|
|
394
|
-
**kwargs,
|
|
395
|
-
)
|
|
396
|
-
except:
|
|
397
|
-
print('Failed to align volumes. Returning zeros as best guess..')
|
|
398
|
-
dim = deformation_field_shape(static.shape, nodes[0])
|
|
399
|
-
return np.zeros(dim + (3,))
|
|
400
|
-
|
|
401
|
-
if i+1 < len(nodes):
|
|
402
|
-
dim = deformation_field_shape(static.shape, nodes[i+1])
|
|
403
|
-
parameters = upsample_deformation_field(parameters, dim)
|
|
404
|
-
|
|
405
|
-
return parameters
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
def apply_inverse_affine(input_data, inverse_affine, output_shape, output_coordinates=None, **kwargs):
|
|
409
|
-
|
|
410
|
-
# Create an array of all coordinates in the output volume
|
|
411
|
-
if output_coordinates is None:
|
|
412
|
-
output_coordinates = volume_coordinates(output_shape)
|
|
413
|
-
|
|
414
|
-
# Apply affine transformation to all coordinates in the output volume
|
|
415
|
-
# nd = inverse_affine.shape[0]-1
|
|
416
|
-
# matrix = inverse_affine[:nd,:nd]
|
|
417
|
-
# offset = inverse_affine[:nd, nd]
|
|
418
|
-
# input_coordinates = np.dot(output_coordinates, matrix.T) + offset
|
|
419
|
-
# #co = np.dot(matrix, co.T).T + offset
|
|
420
|
-
input_coordinates = apply_affine(inverse_affine, output_coordinates)
|
|
421
|
-
|
|
422
|
-
# Extend with constant value for half a voxel outside of the boundary
|
|
423
|
-
input_coordinates = extend_border(input_coordinates, input_data.shape)
|
|
424
|
-
|
|
425
|
-
# Interpolate the volume in the transformed coordinates
|
|
426
|
-
output_data = ndi.map_coordinates(input_data, input_coordinates.T, **kwargs)
|
|
427
|
-
output_data = np.reshape(output_data, output_shape)
|
|
428
|
-
|
|
429
|
-
return output_data
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
def affine_output_geometry(input_shape, input_affine, transformation):
|
|
433
|
-
|
|
434
|
-
# Determine output shape and position
|
|
435
|
-
affine_transformed = transformation.dot(input_affine)
|
|
436
|
-
forward = np.linalg.inv(input_affine).dot(affine_transformed) # Ai T A
|
|
437
|
-
output_shape, output_pos = envelope(input_shape, forward)
|
|
438
|
-
|
|
439
|
-
# Determine output affine by shifting affine to the output position
|
|
440
|
-
nd = input_affine.shape[0]-1
|
|
441
|
-
matrix = input_affine[:nd,:nd]
|
|
442
|
-
offset = input_affine[:nd, nd]
|
|
443
|
-
output_affine = input_affine.copy()
|
|
444
|
-
output_affine[:nd, nd] = np.dot(matrix, output_pos) + offset
|
|
445
|
-
|
|
446
|
-
return output_shape, output_affine
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
####################################
|
|
450
|
-
## Affine transformation and reslice
|
|
451
|
-
####################################
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
# TODO This needs to become a private helper function
|
|
455
|
-
def affine_transform(input_data, input_affine, transformation, reshape=False, **kwargs):
|
|
456
|
-
|
|
457
|
-
# If 2d array, add a 3d dimension of size 1
|
|
458
|
-
if input_data.ndim == 2:
|
|
459
|
-
input_data = np.expand_dims(input_data, axis=-1)
|
|
460
|
-
|
|
461
|
-
if reshape:
|
|
462
|
-
output_shape, output_affine = affine_output_geometry(input_data.shape, input_affine, transformation)
|
|
463
|
-
else:
|
|
464
|
-
output_shape, output_affine = input_data.shape, input_affine.copy()
|
|
465
|
-
|
|
466
|
-
# Perform the inverse transformation
|
|
467
|
-
affine_transformed = transformation.dot(input_affine)
|
|
468
|
-
inverse = np.linalg.inv(affine_transformed).dot(output_affine) # Ainv Tinv B
|
|
469
|
-
output_data = apply_inverse_affine(input_data, inverse, output_shape, **kwargs)
|
|
470
|
-
|
|
471
|
-
return output_data, output_affine
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
# TODO This needs to become a private helper function
|
|
475
|
-
def affine_reslice(input_data, input_affine, output_affine, output_shape=None, **kwargs):
|
|
476
|
-
|
|
477
|
-
# If 2d array, add a 3d dimension of size 1
|
|
478
|
-
if input_data.ndim == 2:
|
|
479
|
-
input_data = np.expand_dims(input_data, axis=-1)
|
|
480
|
-
|
|
481
|
-
# If no output shape is provided, retain the physical volume of the input datas
|
|
482
|
-
if output_shape is None:
|
|
483
|
-
|
|
484
|
-
# Get field of view from input data
|
|
485
|
-
_, _, input_pixel_spacing = affine_components(input_affine)
|
|
486
|
-
field_of_view = np.multiply(np.array(input_data.shape), input_pixel_spacing)
|
|
487
|
-
|
|
488
|
-
# Find output shape for the same field of view
|
|
489
|
-
output_rotation, output_translation, output_pixel_spacing = affine_components(output_affine)
|
|
490
|
-
output_shape = np.around(np.divide(field_of_view, output_pixel_spacing)).astype(np.int16)
|
|
491
|
-
output_shape[np.where(output_shape==0)] = 1
|
|
492
|
-
|
|
493
|
-
# Adjust output pixel spacing to fit the field of view
|
|
494
|
-
output_pixel_spacing = np.divide(field_of_view, output_shape)
|
|
495
|
-
output_affine = affine_matrix(rotation=output_rotation, translation=output_translation, pixel_spacing=output_pixel_spacing)
|
|
496
|
-
|
|
497
|
-
# Reslice input data to output geometry
|
|
498
|
-
transform = np.linalg.inv(input_affine).dot(output_affine) # Ai B
|
|
499
|
-
output_data = apply_inverse_affine(input_data, transform, output_shape, **kwargs)
|
|
500
|
-
|
|
501
|
-
return output_data, output_affine
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
def to_3d(array):
|
|
505
|
-
# Ensures that the data are 3D
|
|
506
|
-
# Find out where is the right place to do this
|
|
507
|
-
if array.ndim == 2:
|
|
508
|
-
return np.expand_dims(array, axis=-1)
|
|
509
|
-
else:
|
|
510
|
-
return array
|
|
511
|
-
|
|
512
|
-
# This needs a reshape option to expand to the envelope in the new reference frame
|
|
513
|
-
def affine_transform_and_reslice(input_data, input_affine, output_shape, output_affine, transformation, **kwargs):
|
|
514
|
-
|
|
515
|
-
input_data = to_3d(input_data)
|
|
516
|
-
affine_transformed = transformation.dot(input_affine)
|
|
517
|
-
inverse = np.linalg.inv(affine_transformed).dot(output_affine) # Ai Ti B
|
|
518
|
-
output_data = apply_inverse_affine(input_data, inverse, output_shape, **kwargs)
|
|
519
|
-
|
|
520
|
-
return output_data
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
# # Deformation define in absolute coordinates
|
|
524
|
-
# def absolute_freeform(input_data, input_affine, output_shape, output_affine, displacement, output_coordinates=None, **kwargs):
|
|
525
|
-
|
|
526
|
-
# # Create an array of all coordinates in the output volume
|
|
527
|
-
# if output_coordinates is None:
|
|
528
|
-
# output_coordinates = volume_coordinates(output_shape)
|
|
529
|
-
|
|
530
|
-
# # Express output coordinates in the scanner reference frame
|
|
531
|
-
# reference_output_coordinates = apply_affine(output_affine, output_coordinates)
|
|
532
|
-
|
|
533
|
-
# # Apply free-from deformation to all output coordinates
|
|
534
|
-
# deformation = interpolate_displacement(displacement, output_shape)
|
|
535
|
-
# reference_input_coordinates = reference_output_coordinates + deformation
|
|
536
|
-
|
|
537
|
-
# # Express new coordinates in reference frame of the input volume
|
|
538
|
-
# input_affine_inv = np.linalg.inv(input_affine)
|
|
539
|
-
# input_coordinates = apply_affine(input_affine_inv, reference_input_coordinates)
|
|
540
|
-
|
|
541
|
-
# # Extend with constant value for half a voxel outside of the boundary
|
|
542
|
-
# # TODO make this an option - costs time and is not necessary when a window is taken inside the FOV (better way to deal with borders)
|
|
543
|
-
# input_coordinates = extend_border(input_coordinates, input_data.shape)
|
|
544
|
-
|
|
545
|
-
# # Interpolate the input data in the transformed coordinates
|
|
546
|
-
# output_data = ndi.map_coordinates(input_data, input_coordinates.T, **kwargs)
|
|
547
|
-
# output_data = np.reshape(output_data, output_shape)
|
|
548
|
-
|
|
549
|
-
# return output_data
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
# Note: to apply to a window, adjust output_shape and output_affine (position vector)
|
|
554
|
-
# Deformation defined in input coordinates
|
|
555
|
-
def freeform_deformation(input_data, displacement, output_shape=None, output_to_input=np.eye(4), output_coordinates=None, **kwargs):
|
|
556
|
-
"""Freeform deformation assuming deformation field is defined in the reference frame of input data"""
|
|
557
|
-
|
|
558
|
-
# Set defaults
|
|
559
|
-
# If 2d array, add a 3d dimension of size 1
|
|
560
|
-
if input_data.ndim == 2:
|
|
561
|
-
input_data = np.expand_dims(input_data, axis=-1)
|
|
562
|
-
if output_shape is None:
|
|
563
|
-
output_shape = input_data.shape
|
|
564
|
-
else:
|
|
565
|
-
output_shape = tuple(output_shape)
|
|
566
|
-
|
|
567
|
-
# Create an array of all coordinates in the output volume
|
|
568
|
-
# Optional argument as this can be precomputed for registration purposes
|
|
569
|
-
if output_coordinates is None:
|
|
570
|
-
# Creat output coordinates
|
|
571
|
-
output_coordinates = volume_coordinates(output_shape)
|
|
572
|
-
# Express output coordinates in reference frame of the input volume
|
|
573
|
-
output_coordinates = apply_affine(output_to_input, output_coordinates)
|
|
574
|
-
|
|
575
|
-
# Apply free-from deformation to all output coordinates
|
|
576
|
-
t = time.time()
|
|
577
|
-
deformation = interpolate_displacement(displacement, output_shape, order=1)
|
|
578
|
-
t1 = time.time()-t
|
|
579
|
-
|
|
580
|
-
input_coordinates = output_coordinates + deformation
|
|
581
|
-
|
|
582
|
-
# Extend with constant value for half a voxel outside of the boundary
|
|
583
|
-
# TODO make this an option in 3D - costs time and is not necessary when a window is taken inside the FOV (better way to deal with borders)
|
|
584
|
-
input_coordinates = extend_border(input_coordinates, input_data.shape)
|
|
585
|
-
|
|
586
|
-
# Interpolate the input data in the transformed coordinates
|
|
587
|
-
t = time.time()
|
|
588
|
-
output_data = ndi.map_coordinates(input_data, input_coordinates.T, order=1)
|
|
589
|
-
t2 = time.time()-t
|
|
590
|
-
output_data = np.reshape(output_data, output_shape)
|
|
591
|
-
|
|
592
|
-
#print('Interpolate displacement', t1)
|
|
593
|
-
#print('Map coordinates', t2)
|
|
594
|
-
#print('Interp1/interp2', t1/t2)
|
|
595
|
-
|
|
596
|
-
return output_data
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
# Note: to apply to a window, adjust output_shape and output_affine (position vector)
|
|
600
|
-
# Deformation defined in input coordinates
|
|
601
|
-
def freeform_deformation_align(input_data, displacement, output_shape=None, output_to_input=np.eye(4), output_coordinates=None, interpolator=None, **kwargs):
|
|
602
|
-
"""Freeform deformation with precomputing options optimized for use in coregistration"""
|
|
603
|
-
|
|
604
|
-
# Set defaults
|
|
605
|
-
# If 2d array, add a 3d dimension of size 1
|
|
606
|
-
if input_data.ndim == 2:
|
|
607
|
-
input_data = np.expand_dims(input_data, axis=-1)
|
|
608
|
-
if output_shape is None:
|
|
609
|
-
output_shape = input_data.shape
|
|
610
|
-
else:
|
|
611
|
-
output_shape = tuple(output_shape)
|
|
612
|
-
|
|
613
|
-
# Create an array of all coordinates in the output volume
|
|
614
|
-
# Optional argument as this can be precomputed for registration purposes
|
|
615
|
-
if output_coordinates is None:
|
|
616
|
-
# Creat output coordinates
|
|
617
|
-
output_coordinates = volume_coordinates(output_shape)
|
|
618
|
-
# Express output coordinates in reference frame of the input volume
|
|
619
|
-
output_coordinates = apply_affine(output_to_input, output_coordinates)
|
|
620
|
-
|
|
621
|
-
if interpolator is None:
|
|
622
|
-
interpolator = freeform_interpolator(displacement.shape, output_shape, order=1)
|
|
623
|
-
|
|
624
|
-
# Apply free-from deformation to all output coordinates
|
|
625
|
-
t = time.time()
|
|
626
|
-
deformation = interpolator_displacement(displacement, output_shape, interpolator)
|
|
627
|
-
t1 = time.time()-t
|
|
628
|
-
|
|
629
|
-
input_coordinates = output_coordinates + deformation
|
|
630
|
-
|
|
631
|
-
# Extend with constant value for half a voxel outside of the boundary
|
|
632
|
-
# TODO make this an option in 3D - costs time and is not necessary when a window is taken inside the FOV (better way to deal with borders)
|
|
633
|
-
input_coordinates = extend_border(input_coordinates, input_data.shape)
|
|
634
|
-
|
|
635
|
-
# Interpolate the input data in the transformed coordinates
|
|
636
|
-
t = time.time()
|
|
637
|
-
output_data = ndi.map_coordinates(input_data, input_coordinates.T, order=1)
|
|
638
|
-
t2 = time.time()-t
|
|
639
|
-
|
|
640
|
-
output_data = np.reshape(output_data, output_shape)
|
|
641
|
-
|
|
642
|
-
#print('Interpolate displacement', t1)
|
|
643
|
-
#print('Map coordinates', t2)
|
|
644
|
-
#print('Interp1/interp2', t1/t2)
|
|
645
|
-
|
|
646
|
-
return output_data
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
def extract_slice(array, affine, z, slice_thickness=None):
|
|
650
|
-
|
|
651
|
-
# Get the slice and its affine
|
|
652
|
-
array_z = array[:,:,z]
|
|
653
|
-
affine_z = affine.copy()
|
|
654
|
-
affine_z[:3,3] += z*affine[:3,2]
|
|
655
|
-
# Set the slice spacing to equal the slice thickness.
|
|
656
|
-
# Note: both are equal for 3D array but different for 2D multislice
|
|
657
|
-
if slice_thickness is not None:
|
|
658
|
-
slice_spacing = np.linalg.norm(affine[:3,2])
|
|
659
|
-
affine_z[:3,2] *= slice_thickness[z]/slice_spacing
|
|
660
|
-
|
|
661
|
-
return array_z, affine_z
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
# def _transform_slice_by_slice(
|
|
665
|
-
# moving = None,
|
|
666
|
-
# moving_affine = None,
|
|
667
|
-
# static_shape = None,
|
|
668
|
-
# static_affine = None,
|
|
669
|
-
# parameters = None,
|
|
670
|
-
# transformation = translate,
|
|
671
|
-
# slice_thickness = None):
|
|
672
|
-
|
|
673
|
-
# # Note this does not work for center of mass rotation because weight array has different center of mass.
|
|
674
|
-
|
|
675
|
-
# nz = moving.shape[2]
|
|
676
|
-
# if slice_thickness is not None:
|
|
677
|
-
# if not isinstance(slice_thickness, list):
|
|
678
|
-
# slice_thickness = [slice_thickness]*nz
|
|
679
|
-
|
|
680
|
-
# weight = np.zeros(static_shape + (nz,))
|
|
681
|
-
# coregistered = np.zeros(static_shape + (nz,))
|
|
682
|
-
|
|
683
|
-
# for z in range(nz):
|
|
684
|
-
# moving_z, moving_affine_z = extract_slice(moving, moving_affine, z, slice_thickness)
|
|
685
|
-
# weight[...,z] = transformation(np.ones(moving.shape[:2]), moving_affine_z, static_shape, static_affine, parameters[z])
|
|
686
|
-
# coregistered[...,z] = transformation(moving_z, moving_affine_z, static_shape, static_affine, parameters[z])
|
|
687
|
-
|
|
688
|
-
# # Average each pixel value over all slices that have sampled it
|
|
689
|
-
# coregistered = np.sum(weight*coregistered, axis=-1)
|
|
690
|
-
# weight = np.sum(weight, axis=-1)
|
|
691
|
-
# nozero = np.where(weight > 0)
|
|
692
|
-
# coregistered[nozero] = coregistered[nozero]/weight[nozero]
|
|
693
|
-
|
|
694
|
-
# return coregistered
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
####################################
|
|
699
|
-
# wrappers for use in align function
|
|
700
|
-
####################################
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
def translate(input_data, input_affine, output_shape, output_affine, translation, **kwargs):
|
|
705
|
-
transformation = affine_matrix(translation=translation)
|
|
706
|
-
return affine_transform_and_reslice(input_data, input_affine, output_shape, output_affine, transformation, **kwargs)
|
|
707
|
-
|
|
708
|
-
def translate_reshape(input_data, input_affine, translation, **kwargs):
|
|
709
|
-
transformation = affine_matrix(translation=translation)
|
|
710
|
-
return affine_transform(input_data, input_affine, transformation, reshape=True, **kwargs)
|
|
711
|
-
|
|
712
|
-
def rotate(input_data, input_affine, output_shape, output_affine, rotation, **kwargs):
|
|
713
|
-
transformation = affine_matrix(rotation=rotation)
|
|
714
|
-
return affine_transform_and_reslice(input_data, input_affine, output_shape, output_affine, transformation, **kwargs)
|
|
715
|
-
|
|
716
|
-
def rotate_reshape(input_data, input_affine, rotation, **kwargs):
|
|
717
|
-
transformation = affine_matrix(rotation=rotation)
|
|
718
|
-
return affine_transform(input_data, input_affine, transformation, reshape=True, **kwargs)
|
|
719
|
-
|
|
720
|
-
def stretch(input_data, input_affine, output_shape, output_affine, stretch, **kwargs):
|
|
721
|
-
transformation = affine_matrix(pixel_spacing=stretch)
|
|
722
|
-
return affine_transform_and_reslice(input_data, input_affine, output_shape, output_affine, transformation, **kwargs)
|
|
723
|
-
|
|
724
|
-
def stretch_reshape(input_data, input_affine, stretch, **kwargs):
|
|
725
|
-
transformation = affine_matrix(pixel_spacing=stretch)
|
|
726
|
-
return affine_transform(input_data, input_affine, transformation, reshape=True, **kwargs)
|
|
727
|
-
|
|
728
|
-
def rotate_around(input_data, input_affine, output_shape, output_affine, parameters, **kwargs):
|
|
729
|
-
transformation = affine_matrix(rotation=parameters[:3], center=parameters[3:])
|
|
730
|
-
return affine_transform_and_reslice(input_data, input_affine, output_shape, output_affine, transformation, **kwargs)
|
|
731
|
-
|
|
732
|
-
def rotate_around_com(input_data, input_affine, output_shape, output_affine, parameters, **kwargs):
|
|
733
|
-
input_data = to_3d(input_data) # need for com - not the right place
|
|
734
|
-
input_com = center_of_mass(input_data, input_affine) # can be precomputed
|
|
735
|
-
transformation = affine_matrix(rotation=parameters, center=input_com)
|
|
736
|
-
return affine_transform_and_reslice(input_data, input_affine, output_shape, output_affine, transformation, **kwargs)
|
|
737
|
-
|
|
738
|
-
def rotate_around_reshape(input_data, input_affine, rotation, center, **kwargs):
|
|
739
|
-
transformation = affine_matrix(rotation=rotation, center=center)
|
|
740
|
-
return affine_transform(input_data, input_affine, transformation, reshape=True, **kwargs)
|
|
741
|
-
|
|
742
|
-
def rigid(input_data, input_affine, output_shape, output_affine, parameters, **kwargs):
|
|
743
|
-
transformation = affine_matrix(rotation=parameters[:3], translation=parameters[3:])
|
|
744
|
-
return affine_transform_and_reslice(input_data, input_affine, output_shape, output_affine, transformation, **kwargs)
|
|
745
|
-
|
|
746
|
-
def rigid_around(input_data, input_affine, output_shape, output_affine, parameters, **kwargs):
|
|
747
|
-
transformation = affine_matrix(rotation=parameters[:3], center=parameters[3:6], translation=parameters[6:])
|
|
748
|
-
return affine_transform_and_reslice(input_data, input_affine, output_shape, output_affine, transformation, **kwargs)
|
|
749
|
-
|
|
750
|
-
def rigid_around_com(input_data, input_affine, output_shape, output_affine, parameters, **kwargs):
|
|
751
|
-
input_data = to_3d(input_data) # need for com - not the right place
|
|
752
|
-
input_com = center_of_mass(input_data, input_affine) # Can be precomputed once a generic precomputing step is built into align.
|
|
753
|
-
transformation = affine_matrix(rotation=parameters[:3], center=parameters[3:]+input_com, translation=parameters[3:])
|
|
754
|
-
return affine_transform_and_reslice(input_data, input_affine, output_shape, output_affine, transformation, **kwargs)
|
|
755
|
-
|
|
756
|
-
def rigid_reshape(input_data, input_affine, rotation, translation, **kwargs):
|
|
757
|
-
transformation = affine_matrix(rotation=rotation, translation=translation)
|
|
758
|
-
return affine_transform(input_data, input_affine, transformation, reshape=True, **kwargs)
|
|
759
|
-
|
|
760
|
-
def affine(input_data, input_affine, output_shape, output_affine, parameters, **kwargs):
|
|
761
|
-
transformation = affine_matrix(rotation=parameters[:3], translation=parameters[3:6], pixel_spacing=parameters[6:])
|
|
762
|
-
return affine_transform_and_reslice(input_data, input_affine, output_shape, output_affine, transformation, **kwargs)
|
|
763
|
-
|
|
764
|
-
def affine_reshape(input_data, input_affine, rotation, translation, stretch, **kwargs):
|
|
765
|
-
transformation = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=stretch)
|
|
766
|
-
return affine_transform(input_data, input_affine, transformation, reshape=True, **kwargs)
|
|
767
|
-
|
|
768
|
-
def freeform(input_data, input_affine, output_shape, output_affine, parameters, **kwargs):
|
|
769
|
-
output_to_input = np.linalg.inv(input_affine).dot(output_affine)
|
|
770
|
-
return freeform_deformation(input_data, parameters, output_shape, output_to_input, **kwargs)
|
|
771
|
-
|
|
772
|
-
def freeform_align(input_data, input_affine, output_shape, output_affine, parameters, **kwargs):
|
|
773
|
-
output_to_input = np.linalg.inv(input_affine).dot(output_affine)
|
|
774
|
-
return freeform_deformation_align(input_data, parameters, output_shape, output_to_input, **kwargs)
|
|
775
|
-
|
|
776
|
-
def transform_slice_by_slice(input_data, input_affine, output_shape, output_affine, parameters, transformation=translate, slice_thickness=None):
|
|
777
|
-
|
|
778
|
-
# Note this does not work for center of mass rotation because weight array has different center of mass.
|
|
779
|
-
|
|
780
|
-
nz = input_data.shape[2]
|
|
781
|
-
if slice_thickness is not None:
|
|
782
|
-
if not isinstance(slice_thickness, list):
|
|
783
|
-
slice_thickness = [slice_thickness]*nz
|
|
784
|
-
|
|
785
|
-
weight = np.zeros(output_shape)
|
|
786
|
-
coregistered = np.zeros(output_shape)
|
|
787
|
-
input_ones_z = np.ones(input_data.shape[:2])
|
|
788
|
-
for z in range(nz):
|
|
789
|
-
input_data_z, input_affine_z = extract_slice(input_data, input_affine, z, slice_thickness)
|
|
790
|
-
weight_z = transformation(input_ones_z, input_affine_z, output_shape, output_affine, parameters[z])
|
|
791
|
-
coregistered_z = transformation(input_data_z, input_affine_z, output_shape, output_affine, parameters[z])
|
|
792
|
-
weight += weight_z
|
|
793
|
-
coregistered += weight_z*coregistered_z
|
|
794
|
-
|
|
795
|
-
# Average each pixel value over all slices that have sampled it
|
|
796
|
-
nozero = np.where(weight > 0)
|
|
797
|
-
coregistered[nozero] = coregistered[nozero]/weight[nozero]
|
|
798
|
-
|
|
799
|
-
return coregistered
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
# default metrics
|
|
803
|
-
# ---------------
|
|
804
|
-
|
|
805
|
-
def sum_of_squares(static, transformed, nan=None):
|
|
806
|
-
if nan is not None:
|
|
807
|
-
i = np.where(transformed != nan)
|
|
808
|
-
return np.sum(np.square(static[i]-transformed[i]))
|
|
809
|
-
else:
|
|
810
|
-
return np.sum(np.square(static-transformed))
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
def mutual_information(static, transformed, nan=None):
|
|
814
|
-
if nan is not None:
|
|
815
|
-
i = np.where(transformed != nan)
|
|
816
|
-
return sklearn.metrics.mutual_info_score(static[i], transformed[i])
|
|
817
|
-
else:
|
|
818
|
-
return sklearn.metrics.mutual_info_score(static, transformed)
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
def normalized_mutual_information(static, transformed, nan=None):
|
|
822
|
-
if nan is not None:
|
|
823
|
-
i = np.where(transformed != nan)
|
|
824
|
-
return sklearn.metrics.normalized_mutual_info_score(static[i], transformed[i])
|
|
825
|
-
else:
|
|
826
|
-
return sklearn.metrics.normalized_mutual_info_score(static, transformed)
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
# Generic objective function
|
|
831
|
-
def _objective_function(
|
|
832
|
-
params,
|
|
833
|
-
moving: np.ndarray,
|
|
834
|
-
static: np.ndarray,
|
|
835
|
-
transformation,
|
|
836
|
-
metric,
|
|
837
|
-
moving_affine,
|
|
838
|
-
static_affine,
|
|
839
|
-
moving_mask,
|
|
840
|
-
static_mask,
|
|
841
|
-
transformation_args,
|
|
842
|
-
metric_args):
|
|
843
|
-
|
|
844
|
-
# # Get static size (TODO: use shape instead of size in transformations)
|
|
845
|
-
# _, _, static_pixel_spacing = affine_components(static_affine)
|
|
846
|
-
# static_size = np.multiply(np.array(static.shape)-1, static_pixel_spacing)
|
|
847
|
-
|
|
848
|
-
# Transform the moving image
|
|
849
|
-
nan = 0
|
|
850
|
-
if transformation_args is None:
|
|
851
|
-
transformed = transformation(moving, moving_affine, static.shape, static_affine, params, cval=nan)
|
|
852
|
-
else:
|
|
853
|
-
transformed = transformation(moving, moving_affine, static.shape, static_affine, params, transformation_args, cval=nan)
|
|
854
|
-
|
|
855
|
-
# If a moving mask is provided, transform this as well
|
|
856
|
-
if moving_mask is not None:
|
|
857
|
-
if transformation_args is None:
|
|
858
|
-
transformed_mask = transformation(moving_mask, moving_affine, static.shape, static_affine, params)
|
|
859
|
-
else:
|
|
860
|
-
transformed_mask = transformation(moving_mask, moving_affine, static.shape, static_affine, params, transformation_args)
|
|
861
|
-
transformed_mask[transformed_mask > 0.5] = 1
|
|
862
|
-
transformed_mask[transformed_mask <= 0.5] = 0
|
|
863
|
-
|
|
864
|
-
# Get the indices in the static data where the mask is non-zero
|
|
865
|
-
if static_mask is not None and moving_mask is not None:
|
|
866
|
-
ind = np.where(transformed!=nan and static_mask==1 and transformed_mask==1)
|
|
867
|
-
elif static_mask is not None:
|
|
868
|
-
ind = np.where(transformed!=nan and static_mask==1)
|
|
869
|
-
elif moving_mask is not None:
|
|
870
|
-
ind = np.where(transformed!=nan and transformed_mask==1)
|
|
871
|
-
else:
|
|
872
|
-
ind = np.where(transformed!=nan)
|
|
873
|
-
|
|
874
|
-
# Calculate the cost function
|
|
875
|
-
if metric_args is None:
|
|
876
|
-
return metric(static[ind], transformed[ind])
|
|
877
|
-
else:
|
|
878
|
-
return metric(static[ind], transformed[ind], metric_args)
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
def print_current(xk):
|
|
882
|
-
print('Current parameter estimates: ' , xk)
|
|
883
|
-
return False
|
|
884
|
-
|
|
885
|
-
def print_current_norm(xk):
|
|
886
|
-
print('Norm of current parameter estimates: ' , np.linalg.norm(xk))
|
|
887
|
-
return False
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
def minimize(*args, method='GD', **kwargs):
|
|
891
|
-
"Wrapper around opt.minimize which also has a gradient descent option"
|
|
892
|
-
|
|
893
|
-
if method == 'GD':
|
|
894
|
-
return minimize_gd(*args, **kwargs)
|
|
895
|
-
else:
|
|
896
|
-
res = opt.minimize(*args, method=method, **kwargs)
|
|
897
|
-
return res.x
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
def minimize_gd(cost_function, parameters, args=None, callback=None, options={}, bounds=None):
|
|
901
|
-
|
|
902
|
-
# Set default values for global options
|
|
903
|
-
if 'max_iter' in options:
|
|
904
|
-
max_iter = options['max_iter']
|
|
905
|
-
else:
|
|
906
|
-
max_iter = 100
|
|
907
|
-
if 'gradient step' in options:
|
|
908
|
-
step = options['gradient step']
|
|
909
|
-
else:
|
|
910
|
-
step = np.ones(parameters.shape)
|
|
911
|
-
|
|
912
|
-
# set defaults for line search options
|
|
913
|
-
ls_pars = ['tolerance', 'scale_down', 'scale_up', 'stepsize_max']
|
|
914
|
-
ls_options = dict((k, options[k]) for k in ls_pars if k in options)
|
|
915
|
-
|
|
916
|
-
stepsize = 1.0 # initial stepsize
|
|
917
|
-
n_iter = 0
|
|
918
|
-
cost = cost_function(parameters, *args)
|
|
919
|
-
while True:
|
|
920
|
-
n_iter+=1
|
|
921
|
-
print('iteration: ', n_iter)
|
|
922
|
-
grad = gradient(cost_function, parameters, cost, step, bounds, *args)
|
|
923
|
-
parameters, stepsize, cost = line_search(cost_function, grad, parameters, stepsize, cost, bounds, *args, **ls_options)
|
|
924
|
-
if callback is not None:
|
|
925
|
-
callback(parameters)
|
|
926
|
-
if cost == 0:
|
|
927
|
-
return parameters
|
|
928
|
-
if stepsize == 0:
|
|
929
|
-
return parameters
|
|
930
|
-
if n_iter == max_iter:
|
|
931
|
-
return parameters
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
def gradient(cost_function, parameters, f0, step, bounds, *args):
|
|
936
|
-
grad = np.empty(parameters.shape)
|
|
937
|
-
for i in range(parameters.size):
|
|
938
|
-
c = np.unravel_index(i, parameters.shape)
|
|
939
|
-
pc = parameters[c]
|
|
940
|
-
sc = step[c]
|
|
941
|
-
parameters[c] = pc+sc
|
|
942
|
-
parameters = project_on(parameters, bounds, coord=c)
|
|
943
|
-
fp = cost_function(parameters, *args)
|
|
944
|
-
parameters[c] = pc-sc
|
|
945
|
-
parameters = project_on(parameters, bounds, coord=c)
|
|
946
|
-
fn = cost_function(parameters, *args)
|
|
947
|
-
parameters[c] = pc
|
|
948
|
-
grad[c] = (fp-fn)/2
|
|
949
|
-
#grad[i] = (fp-fn)/(2*step[i])
|
|
950
|
-
#grad[i] = stats.linregress([-step[i],0,step[i]], [fn,f0,fp]).slope
|
|
951
|
-
|
|
952
|
-
# Normalize the gradient
|
|
953
|
-
grad_norm = np.linalg.norm(grad)
|
|
954
|
-
if grad_norm == 0:
|
|
955
|
-
msg = 'Gradient has length zero - cannot perform gradient descent'
|
|
956
|
-
raise ValueError(msg)
|
|
957
|
-
grad /= grad_norm
|
|
958
|
-
grad = np.multiply(step, grad)
|
|
959
|
-
|
|
960
|
-
return grad
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
def project_on(par, bounds, coord=None):
|
|
964
|
-
if bounds is None:
|
|
965
|
-
return par
|
|
966
|
-
if len(bounds) != len(par):
|
|
967
|
-
msg = 'Parameter and bounds must have the same length'
|
|
968
|
-
raise ValueError(msg)
|
|
969
|
-
if coord is not None: # project only that index
|
|
970
|
-
pc = par[coord]
|
|
971
|
-
bc = bounds[coord]
|
|
972
|
-
if pc <= bc[0]:
|
|
973
|
-
pc = bc[0]
|
|
974
|
-
if pc >= bc[1]:
|
|
975
|
-
pc = bc[1]
|
|
976
|
-
else: # project all indices
|
|
977
|
-
for i in range(par.size):
|
|
978
|
-
c = np.unravel_index(i, par.shape)
|
|
979
|
-
pc = par[c]
|
|
980
|
-
bc = bounds[c]
|
|
981
|
-
if pc <= bc[0]:
|
|
982
|
-
par[c] = bc[0]
|
|
983
|
-
if pc >= bc[1]:
|
|
984
|
-
par[c] = bc[1]
|
|
985
|
-
return par
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
def line_search(cost_function, grad, p0, stepsize0, f0, bounds, *args, tolerance=0.1, scale_down=5.0, scale_up=1.5, stepsize_max=1000.0):
|
|
989
|
-
|
|
990
|
-
# Initialize stepsize to current optimal stepsize
|
|
991
|
-
stepsize_try = stepsize0 / scale_down
|
|
992
|
-
p_init = p0.copy()
|
|
993
|
-
|
|
994
|
-
# backtrack in big steps until reduction in cost
|
|
995
|
-
while True:
|
|
996
|
-
|
|
997
|
-
# Take a step and evaluate the cost
|
|
998
|
-
p_try = p_init - stepsize_try*grad
|
|
999
|
-
p_try = project_on(p_try, bounds)
|
|
1000
|
-
f_try = cost_function(p_try, *args)
|
|
1001
|
-
|
|
1002
|
-
print('cost: ', f_try, ' stepsize: ', stepsize_try, ' par: ', p_try)
|
|
1003
|
-
|
|
1004
|
-
# If a reduction in cost is found, move on to the next part
|
|
1005
|
-
if f_try < f0:
|
|
1006
|
-
break
|
|
1007
|
-
|
|
1008
|
-
# Otherwise reduce the stepsize and try again
|
|
1009
|
-
else:
|
|
1010
|
-
stepsize_try /= scale_down
|
|
1011
|
-
|
|
1012
|
-
# If the stepsize has been reduced below the resolution without reducing the cost,
|
|
1013
|
-
# then the initial values were at the minimum (stepsize=0).
|
|
1014
|
-
if stepsize_try < tolerance:
|
|
1015
|
-
return p0, 0, f0 # converged
|
|
1016
|
-
|
|
1017
|
-
if stepsize_try < tolerance:
|
|
1018
|
-
return p_try, 0, f_try # converged
|
|
1019
|
-
|
|
1020
|
-
# If a reduction in cost has been found, then refine it
|
|
1021
|
-
# by moving forward in babysteps until the cost increases again.
|
|
1022
|
-
while True:
|
|
1023
|
-
|
|
1024
|
-
# Update the current optimum
|
|
1025
|
-
stepsize0 = stepsize_try
|
|
1026
|
-
f0 = f_try
|
|
1027
|
-
p0 = p_try
|
|
1028
|
-
|
|
1029
|
-
# Take a baby step and evaluate the cost
|
|
1030
|
-
stepsize_try *= scale_up
|
|
1031
|
-
p_try = p_init - stepsize_try*grad
|
|
1032
|
-
p_try = project_on(p_try, bounds)
|
|
1033
|
-
f_try = cost_function(p_try, *args)
|
|
1034
|
-
|
|
1035
|
-
print('cost: ', f_try, ' stepsize: ', stepsize_try, ' par: ', p_try)
|
|
1036
|
-
|
|
1037
|
-
# If the cost has increased then a minimum was found
|
|
1038
|
-
if f_try >= f0:
|
|
1039
|
-
return p0, stepsize0, f0
|
|
1040
|
-
|
|
1041
|
-
# emergency stop
|
|
1042
|
-
if stepsize_try > stepsize_max:
|
|
1043
|
-
msg = 'Line search failed to find a minimum'
|
|
1044
|
-
raise ValueError(msg)
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
def goodness_of_alignment(params, transformation, metric, moving, moving_affine, static, static_affine, coord):
|
|
1048
|
-
|
|
1049
|
-
# Transform the moving image
|
|
1050
|
-
nan = 2**16-2 #np.nan does not work
|
|
1051
|
-
transformed = transformation(moving, moving_affine, static.shape, static_affine, params, output_coordinates=coord, cval=nan)
|
|
1052
|
-
|
|
1053
|
-
# Calculate the cost function
|
|
1054
|
-
ls = metric(static, transformed, nan=nan)
|
|
1055
|
-
|
|
1056
|
-
return ls
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
def align(
|
|
1060
|
-
moving = None,
|
|
1061
|
-
static = None,
|
|
1062
|
-
parameters = None,
|
|
1063
|
-
moving_affine = None,
|
|
1064
|
-
static_affine = None,
|
|
1065
|
-
transformation = translate,
|
|
1066
|
-
metric = sum_of_squares,
|
|
1067
|
-
optimization = {'method':'GD', 'options':{}},
|
|
1068
|
-
resolutions = [1]):
|
|
1069
|
-
|
|
1070
|
-
# Set defaults
|
|
1071
|
-
if moving is None:
|
|
1072
|
-
msg = 'The moving volume is a required argument for alignment'
|
|
1073
|
-
raise ValueError(msg)
|
|
1074
|
-
if static is None:
|
|
1075
|
-
msg = 'The static volume is a required argument for alignment'
|
|
1076
|
-
raise ValueError(msg)
|
|
1077
|
-
if parameters is None:
|
|
1078
|
-
msg = 'Initial values for alignment must be provided'
|
|
1079
|
-
raise ValueError(msg)
|
|
1080
|
-
if moving.ndim == 2: # If 2d array, add a 3d dimension of size 1
|
|
1081
|
-
moving = np.expand_dims(moving, axis=-1)
|
|
1082
|
-
if static.ndim == 2: # If 2d array, add a 3d dimension of size 1
|
|
1083
|
-
static = np.expand_dims(static, axis=-1)
|
|
1084
|
-
if moving_affine is None:
|
|
1085
|
-
moving_affine = np.eye(1 + moving.ndim)
|
|
1086
|
-
if static_affine is None:
|
|
1087
|
-
static_affine = np.eye(1 + static.ndim)
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
# Perform multi-resolution loop
|
|
1091
|
-
for res in resolutions:
|
|
1092
|
-
print('DOWNSAMPLE BY FACTOR: ', res)
|
|
1093
|
-
|
|
1094
|
-
if res == 1:
|
|
1095
|
-
moving_resampled, moving_resampled_affine = moving, moving_affine
|
|
1096
|
-
static_resampled, static_resampled_affine = static, static_affine
|
|
1097
|
-
else:
|
|
1098
|
-
# Downsample moving data
|
|
1099
|
-
r, t, p = affine_components(moving_affine)
|
|
1100
|
-
moving_resampled_affine = affine_matrix(rotation=r, translation=t, pixel_spacing=p*res)
|
|
1101
|
-
moving_resampled, moving_resampled_affine = affine_reslice(moving, moving_affine, moving_resampled_affine)
|
|
1102
|
-
#moving_resampled_data, moving_resampled_affine = moving_data, moving_affine
|
|
1103
|
-
|
|
1104
|
-
# Downsample static data
|
|
1105
|
-
r, t, p = affine_components(static_affine)
|
|
1106
|
-
static_resampled_affine = affine_matrix(rotation=r, translation=t, pixel_spacing=p*res)
|
|
1107
|
-
static_resampled, static_resampled_affine = affine_reslice(static, static_affine, static_resampled_affine)
|
|
1108
|
-
|
|
1109
|
-
coord = volume_coordinates(static_resampled.shape)
|
|
1110
|
-
# Here we need a generic precomputation step:
|
|
1111
|
-
# prec = precompute(moving_resampled, moving_resampled_affine, static_resampled, static_resampled_affine)
|
|
1112
|
-
# args = (transformation, metric, moving_resampled, moving_resampled_affine, static_resampled, static_resampled_affine, coord, prec)
|
|
1113
|
-
args = (transformation, metric, moving_resampled, moving_resampled_affine, static_resampled, static_resampled_affine, coord)
|
|
1114
|
-
parameters = minimize(goodness_of_alignment, parameters, args=args, **optimization)
|
|
1115
|
-
|
|
1116
|
-
return parameters
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
def align_slice_by_slice(
|
|
1123
|
-
moving = None,
|
|
1124
|
-
static = None,
|
|
1125
|
-
parameters = None,
|
|
1126
|
-
moving_affine = None,
|
|
1127
|
-
static_affine = None,
|
|
1128
|
-
transformation = translate,
|
|
1129
|
-
metric = sum_of_squares,
|
|
1130
|
-
optimization = {'method':'GD', 'options':{}},
|
|
1131
|
-
resolutions = [1],
|
|
1132
|
-
slice_thickness = None,
|
|
1133
|
-
progress = None):
|
|
1134
|
-
|
|
1135
|
-
# If a single slice thickness is provided, turn it into a list.
|
|
1136
|
-
nz = moving.shape[2]
|
|
1137
|
-
if slice_thickness is not None:
|
|
1138
|
-
if not isinstance(slice_thickness, list):
|
|
1139
|
-
slice_thickness = [slice_thickness]*nz
|
|
1140
|
-
|
|
1141
|
-
estimate = []
|
|
1142
|
-
for z in range(nz):
|
|
1143
|
-
|
|
1144
|
-
if progress is not None:
|
|
1145
|
-
progress(z, nz)
|
|
1146
|
-
|
|
1147
|
-
# Get the slice and its affine
|
|
1148
|
-
moving_z, moving_affine_z = extract_slice(moving, moving_affine, z, slice_thickness)
|
|
1149
|
-
|
|
1150
|
-
# Align volumes
|
|
1151
|
-
try:
|
|
1152
|
-
estimate_z = align(
|
|
1153
|
-
moving = moving_z,
|
|
1154
|
-
moving_affine = moving_affine_z,
|
|
1155
|
-
static = static,
|
|
1156
|
-
static_affine = static_affine,
|
|
1157
|
-
parameters = parameters,
|
|
1158
|
-
resolutions = resolutions,
|
|
1159
|
-
transformation = transformation,
|
|
1160
|
-
metric = metric,
|
|
1161
|
-
optimization = optimization,
|
|
1162
|
-
)
|
|
1163
|
-
except:
|
|
1164
|
-
estimate_z = parameters
|
|
1165
|
-
|
|
1166
|
-
estimate.append(estimate_z)
|
|
1167
|
-
|
|
1168
|
-
return estimate
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
#############################
|
|
1175
|
-
# Plot results
|
|
1176
|
-
#############################
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
def plot_volume(volume, affine):
|
|
1180
|
-
|
|
1181
|
-
clr, op = (255,255,255), 0.5
|
|
1182
|
-
#clr, op = (255,0,0), 1.0
|
|
1183
|
-
|
|
1184
|
-
pl = pv.Plotter()
|
|
1185
|
-
pl.add_axes()
|
|
1186
|
-
|
|
1187
|
-
# Plot the surface
|
|
1188
|
-
surf = pv_contour([0.5], volume, affine)
|
|
1189
|
-
if len(surf.points) == 0:
|
|
1190
|
-
print('Cannot plot the reference surface. It has no points inside the volume. ')
|
|
1191
|
-
else:
|
|
1192
|
-
pl.add_mesh(surf,
|
|
1193
|
-
color=clr,
|
|
1194
|
-
opacity=op,
|
|
1195
|
-
show_edges=False,
|
|
1196
|
-
smooth_shading=True,
|
|
1197
|
-
specular=0,
|
|
1198
|
-
show_scalar_bar=False,
|
|
1199
|
-
)
|
|
1200
|
-
|
|
1201
|
-
# Plot wireframe around edges of reference volume
|
|
1202
|
-
vertices, faces = parallellepid(volume.shape, affine=affine)
|
|
1203
|
-
box = pv.PolyData(vertices, faces)
|
|
1204
|
-
pl.add_mesh(box,
|
|
1205
|
-
style='wireframe',
|
|
1206
|
-
opacity=op,
|
|
1207
|
-
color=clr,
|
|
1208
|
-
)
|
|
1209
|
-
|
|
1210
|
-
return pl
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
def plot_affine_resliced(volume, affine, volume_resliced, affine_resliced):
|
|
1214
|
-
|
|
1215
|
-
clr, op = (255,0,0), 1.0
|
|
1216
|
-
|
|
1217
|
-
pl = plot_volume(volume, affine)
|
|
1218
|
-
|
|
1219
|
-
# Plot the resliced surface
|
|
1220
|
-
surf = pv_contour([0.5], volume_resliced, affine_resliced)
|
|
1221
|
-
if len(surf.points) == 0:
|
|
1222
|
-
print('Cannot plot the resliced surface. It has no points inside the volume. ')
|
|
1223
|
-
else:
|
|
1224
|
-
pl.add_mesh(surf,
|
|
1225
|
-
color=clr,
|
|
1226
|
-
opacity=op,
|
|
1227
|
-
show_edges=False,
|
|
1228
|
-
smooth_shading=True,
|
|
1229
|
-
specular=0,
|
|
1230
|
-
show_scalar_bar=False,
|
|
1231
|
-
)
|
|
1232
|
-
|
|
1233
|
-
# Plot wireframe around edges of resliced volume
|
|
1234
|
-
vertices, faces = parallellepid(volume_resliced.shape, affine=affine_resliced)
|
|
1235
|
-
box = pv.PolyData(vertices, faces)
|
|
1236
|
-
pl.add_mesh(box,
|
|
1237
|
-
style='wireframe',
|
|
1238
|
-
opacity=op,
|
|
1239
|
-
color=clr,
|
|
1240
|
-
)
|
|
1241
|
-
|
|
1242
|
-
return pl
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
def plot_affine_transformed(input_data, input_affine, output_data, output_affine, transformation):
|
|
1246
|
-
|
|
1247
|
-
pl = plot_affine_resliced(input_data, input_affine, output_data, output_affine)
|
|
1248
|
-
|
|
1249
|
-
# Plot the reference surface
|
|
1250
|
-
surf = pv_contour([0.5], output_data, output_affine)
|
|
1251
|
-
if len(surf.points) == 0:
|
|
1252
|
-
print('Cannot plot the surface. It has no points inside the volume. ')
|
|
1253
|
-
else:
|
|
1254
|
-
pl.add_mesh(surf,
|
|
1255
|
-
color=(0,0,255),
|
|
1256
|
-
opacity=0.25,
|
|
1257
|
-
show_edges=False,
|
|
1258
|
-
smooth_shading=True,
|
|
1259
|
-
specular=0,
|
|
1260
|
-
show_scalar_bar=False,
|
|
1261
|
-
)
|
|
1262
|
-
|
|
1263
|
-
# Create blue reference box showing transformation
|
|
1264
|
-
vertices, faces = parallellepid(input_data.shape, affine=np.dot(transformation, input_affine))
|
|
1265
|
-
box = pv.PolyData(vertices, faces)
|
|
1266
|
-
pl.add_mesh(box,
|
|
1267
|
-
style='wireframe',
|
|
1268
|
-
color=(0,0,255),
|
|
1269
|
-
opacity=0.5,
|
|
1270
|
-
)
|
|
1271
|
-
|
|
1272
|
-
pl.show()
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
def plot_freeform_transformed(input_data, input_affine, output_data, output_affine):
|
|
1276
|
-
|
|
1277
|
-
pl = plot_affine_resliced(input_data, input_affine, output_data, output_affine)
|
|
1278
|
-
|
|
1279
|
-
# Plot the reference surface
|
|
1280
|
-
surf = pv_contour([0.5], output_data, output_affine)
|
|
1281
|
-
if len(surf.points) == 0:
|
|
1282
|
-
print('Cannot plot the surface. It has no points inside the volume. ')
|
|
1283
|
-
else:
|
|
1284
|
-
pl.add_mesh(surf,
|
|
1285
|
-
color=(0,0,255),
|
|
1286
|
-
opacity=0.25,
|
|
1287
|
-
show_edges=False,
|
|
1288
|
-
smooth_shading=True,
|
|
1289
|
-
specular=0,
|
|
1290
|
-
show_scalar_bar=False,
|
|
1291
|
-
)
|
|
1292
|
-
|
|
1293
|
-
pl.show()
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
def plot_affine_transform_reslice(input_data, input_affine, output_data, output_affine, transformation):
|
|
1297
|
-
|
|
1298
|
-
# Plot reslice
|
|
1299
|
-
pl = plot_affine_resliced(input_data, input_affine, output_data, output_affine)
|
|
1300
|
-
|
|
1301
|
-
# Show in blue transparent the transformation without reslicing
|
|
1302
|
-
output_data, output_affine = affine_transform(input_data, input_affine, transformation, reshape=True)
|
|
1303
|
-
|
|
1304
|
-
# Plot the reference surface
|
|
1305
|
-
surf = pv_contour([0.5], output_data, output_affine)
|
|
1306
|
-
if len(surf.points) == 0:
|
|
1307
|
-
print('Cannot plot the surface. It has no points inside the volume. ')
|
|
1308
|
-
else:
|
|
1309
|
-
pl.add_mesh(surf,
|
|
1310
|
-
color=(0,0,255),
|
|
1311
|
-
opacity=0.25,
|
|
1312
|
-
show_edges=False,
|
|
1313
|
-
smooth_shading=True,
|
|
1314
|
-
specular=0,
|
|
1315
|
-
show_scalar_bar=False,
|
|
1316
|
-
)
|
|
1317
|
-
|
|
1318
|
-
# Create blue reference box showing transformation
|
|
1319
|
-
vertices, faces = parallellepid(input_data.shape, affine=np.dot(transformation, input_affine)) # is this correct to take the product? (?need a function affine_compose(A,B))
|
|
1320
|
-
box = pv.PolyData(vertices, faces)
|
|
1321
|
-
pl.add_mesh(box,
|
|
1322
|
-
style='wireframe',
|
|
1323
|
-
color=(0,0,255),
|
|
1324
|
-
opacity=0.5,
|
|
1325
|
-
)
|
|
1326
|
-
|
|
1327
|
-
pl.show()
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
#############################
|
|
1331
|
-
# Generate test data
|
|
1332
|
-
#############################
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
def generate(structure='ellipsoid', shape=None, affine=None, markers=True):
|
|
1336
|
-
|
|
1337
|
-
# Default shape
|
|
1338
|
-
if shape is None:
|
|
1339
|
-
shape = (256, 256, 40)
|
|
1340
|
-
|
|
1341
|
-
# Default affine
|
|
1342
|
-
if affine is None:
|
|
1343
|
-
pixel_spacing = np.array([1.5, 1.5, 5]) # mm
|
|
1344
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1345
|
-
rotation_angle = -0.2 * (np.pi/2) # radians
|
|
1346
|
-
rotation_axis = [1,0,0]
|
|
1347
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1348
|
-
affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1349
|
-
|
|
1350
|
-
_, _ , pixel_spacing = affine_components(affine)
|
|
1351
|
-
data = np.zeros(shape, dtype=np.float32)
|
|
1352
|
-
|
|
1353
|
-
if markers:
|
|
1354
|
-
# Insert cube markers at corners
|
|
1355
|
-
marker_width = 20 # marker width in mm
|
|
1356
|
-
w = np.around(np.divide(np.array([marker_width]*3), pixel_spacing))
|
|
1357
|
-
w = w.astype(np.int16)
|
|
1358
|
-
data[0:w[0],0:w[1],0:w[2]] = 1
|
|
1359
|
-
data[-w[0]:,0:w[1],0:w[2]] = 1
|
|
1360
|
-
data[0:w[0],-w[1]:,0:w[2]] = 1
|
|
1361
|
-
data[-w[0]:,-w[1]:,0:w[2]] = 1
|
|
1362
|
-
data[0:w[0],0:w[1],-w[2]:] = 1
|
|
1363
|
-
data[-w[0]:,0:w[1],-w[2]:] = 1
|
|
1364
|
-
data[0:w[0],-w[1]:,-w[2]:] = 1
|
|
1365
|
-
data[-w[0]:,-w[1]:,-w[2]:] = 1
|
|
1366
|
-
|
|
1367
|
-
if structure == 'ellipsoid':
|
|
1368
|
-
half_length = (20, 30, 40) # mm
|
|
1369
|
-
ellip = ellipsoid(half_length[0], half_length[1], half_length[2], spacing=pixel_spacing, levelset=False)
|
|
1370
|
-
d = ellip.shape
|
|
1371
|
-
p = [30, 30, 10]
|
|
1372
|
-
data[p[0]:p[0]+d[0], p[1]:p[1]+d[1], p[2]:p[2]+d[2]] = ellip
|
|
1373
|
-
return data, affine
|
|
1374
|
-
|
|
1375
|
-
elif structure == 'double ellipsoid':
|
|
1376
|
-
half_length1 = np.array([10, 20, 30]) # mm
|
|
1377
|
-
half_length2 = np.array([5, 10, 15]) # mm
|
|
1378
|
-
pos = np.array([150, 50, 0]) # mm
|
|
1379
|
-
ellip1 = ellipsoid(half_length1[0], half_length1[1], half_length1[2], spacing=pixel_spacing, levelset=False)
|
|
1380
|
-
ellip2 = ellipsoid(half_length2[0], half_length2[1], half_length2[2], spacing=pixel_spacing, levelset=False)
|
|
1381
|
-
ellip1 = ellip1.astype(np.int16)
|
|
1382
|
-
ellip2 = ellip2.astype(np.int16)
|
|
1383
|
-
|
|
1384
|
-
p = np.around(np.divide(pos, pixel_spacing)).astype(np.int16)
|
|
1385
|
-
d = ellip1.shape
|
|
1386
|
-
data[p[0]:p[0]+d[0], p[1]:p[1]+d[1], p[2]:p[2]+d[2]] = ellip1
|
|
1387
|
-
|
|
1388
|
-
p += np.around([d[0], d[1]/4, d[2]/2]).astype(np.int16)
|
|
1389
|
-
d = ellip2.shape
|
|
1390
|
-
data[p[0]:p[0]+d[0], p[1]:p[1]+d[1], p[2]:p[2]+d[2]] = ellip2
|
|
1391
|
-
|
|
1392
|
-
return data, affine
|
|
1393
|
-
|
|
1394
|
-
elif structure == 'triple ellipsoid':
|
|
1395
|
-
half_length1 = np.array([10, 20, 30]) # mm
|
|
1396
|
-
half_length2 = np.array([5, 10, 15]) # mm
|
|
1397
|
-
p1 = np.array([150, 50, 10]) # mm
|
|
1398
|
-
p2 = np.array([170, 70, 20]) # mm
|
|
1399
|
-
p3 = np.array([150, 150, 10]) # mm
|
|
1400
|
-
|
|
1401
|
-
ellip1 = ellipsoid(half_length1[0], half_length1[1], half_length1[2], spacing=pixel_spacing, levelset=False)
|
|
1402
|
-
ellip2 = ellipsoid(half_length2[0], half_length2[1], half_length2[2], spacing=pixel_spacing, levelset=False)
|
|
1403
|
-
ellip1 = ellip1.astype(np.int16)
|
|
1404
|
-
ellip2 = ellip2.astype(np.int16)
|
|
1405
|
-
|
|
1406
|
-
p = np.around(np.divide(p1, pixel_spacing)).astype(np.int16)
|
|
1407
|
-
d = ellip1.shape
|
|
1408
|
-
data[p[0]:p[0]+d[0], p[1]:p[1]+d[1], p[2]:p[2]+d[2]] = ellip1
|
|
1409
|
-
|
|
1410
|
-
p = np.around(np.divide(p2, pixel_spacing)).astype(np.int16)
|
|
1411
|
-
d = ellip2.shape
|
|
1412
|
-
data[p[0]:p[0]+d[0], p[1]:p[1]+d[1], p[2]:p[2]+d[2]] = ellip2
|
|
1413
|
-
|
|
1414
|
-
p = np.around(np.divide(p3, pixel_spacing)).astype(np.int16)
|
|
1415
|
-
d = ellip1.shape
|
|
1416
|
-
data[p[0]:p[0]+d[0], p[1]:p[1]+d[1], p[2]:p[2]+d[2]] = ellip1
|
|
1417
|
-
|
|
1418
|
-
return data, affine
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
def generate_plot_data_1():
|
|
1422
|
-
|
|
1423
|
-
# Define geometry of input data
|
|
1424
|
-
pixel_spacing = np.array([2.0, 2.0, 10.0]) # mm
|
|
1425
|
-
input_shape = np.array([100, 100, 10], dtype=np.int16)
|
|
1426
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1427
|
-
rotation_angle = 0.0 * (np.pi/2) # radians
|
|
1428
|
-
rotation_axis = [1,0,0]
|
|
1429
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1430
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1431
|
-
|
|
1432
|
-
# Generate ground truth data
|
|
1433
|
-
input_data, input_affine = generate('triple ellipsoid', shape=input_shape, affine=input_affine, markers=True)
|
|
1434
|
-
|
|
1435
|
-
return input_data, input_affine
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
def generate_reslice_data_1():
|
|
1439
|
-
|
|
1440
|
-
# Downsample
|
|
1441
|
-
# Reslice high-res volume to lower resolution.
|
|
1442
|
-
# Values are chosen so that the field of view stays the same.
|
|
1443
|
-
|
|
1444
|
-
# Define geometry of input data
|
|
1445
|
-
matrix = np.array([400, 300, 120])
|
|
1446
|
-
pixel_spacing = np.array([1.0, 1.0, 1.0]) # mm
|
|
1447
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1448
|
-
rotation_angle = -0.0 * (np.pi/2) # radians
|
|
1449
|
-
rotation_axis = [1,0,0]
|
|
1450
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1451
|
-
|
|
1452
|
-
# Define geometry of output data
|
|
1453
|
-
output_pixel_spacing = np.array([1.25, 5.0, 10.0]) # mm
|
|
1454
|
-
output_shape = None # retain field of view
|
|
1455
|
-
|
|
1456
|
-
# Generate data
|
|
1457
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1458
|
-
output_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=output_pixel_spacing)
|
|
1459
|
-
input_data, input_affine = generate('triple ellipsoid', shape=matrix, affine=input_affine, markers=True)
|
|
1460
|
-
|
|
1461
|
-
return input_data, input_affine, output_affine, output_shape
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
def generate_reslice_data_2():
|
|
1465
|
-
|
|
1466
|
-
# Upsample
|
|
1467
|
-
# Reslice low-res volume to higher resolution.
|
|
1468
|
-
# Values are chosen so that the field of view stays the same.
|
|
1469
|
-
|
|
1470
|
-
# Define geometry of input data
|
|
1471
|
-
matrix = np.array([320, 60, 8])
|
|
1472
|
-
pixel_spacing = np.array([1.25, 5.0, 15.0]) # mm
|
|
1473
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1474
|
-
rotation_angle = -0.0 * (np.pi/2) # radians
|
|
1475
|
-
rotation_axis = [1,0,0]
|
|
1476
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1477
|
-
|
|
1478
|
-
# Define geometry of output data
|
|
1479
|
-
output_pixel_spacing = np.array([1.0, 1.0, 1.0]) # mm
|
|
1480
|
-
output_shape = None # retain field of view
|
|
1481
|
-
|
|
1482
|
-
# Generate data
|
|
1483
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1484
|
-
output_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=output_pixel_spacing)
|
|
1485
|
-
input_data, input_affine = generate('triple ellipsoid', shape=matrix, affine=input_affine, markers=False)
|
|
1486
|
-
|
|
1487
|
-
return input_data, input_affine, output_affine, output_shape
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
def generate_reslice_data_3():
|
|
1491
|
-
|
|
1492
|
-
# resample to lower resolution with a
|
|
1493
|
-
# 90 degree rotation around x + translation along y
|
|
1494
|
-
|
|
1495
|
-
# Define source data
|
|
1496
|
-
matrix = np.array([400, 300, 120])
|
|
1497
|
-
pixel_spacing = np.array([1.0, 1.0, 1.0]) # mm
|
|
1498
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1499
|
-
rotation_angle = 0 * (np.pi/2) # radians
|
|
1500
|
-
rotation_axis = [1,0,0]
|
|
1501
|
-
|
|
1502
|
-
# Generate source data
|
|
1503
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1504
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1505
|
-
input_data, input_affine = generate('triple ellipsoid', shape=matrix, affine=input_affine)
|
|
1506
|
-
|
|
1507
|
-
# Define geometry of new slab
|
|
1508
|
-
pixel_spacing = np.array([1.25, 1.25, 10.0]) # mm
|
|
1509
|
-
translation = np.array([0, 120, 0]) # mm
|
|
1510
|
-
rotation_angle = 1.0 * (np.pi/2) # radians
|
|
1511
|
-
rotation_axis = [1,0,0]
|
|
1512
|
-
|
|
1513
|
-
# Reslice current slab to geometry of new slab
|
|
1514
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1515
|
-
output_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1516
|
-
output_shape = None
|
|
1517
|
-
return input_data, input_affine, output_affine, output_shape
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
def generate_reslice_data_4():
|
|
1521
|
-
|
|
1522
|
-
# Rotation at low resolution
|
|
1523
|
-
|
|
1524
|
-
# Define geometry of input data
|
|
1525
|
-
input_shape = np.array([40, 40, 20], dtype=np.int16)
|
|
1526
|
-
pixel_spacing = np.array([6, 6, 6.0]) # mm
|
|
1527
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1528
|
-
rotation_angle = 0.0 * (np.pi/2) # radians
|
|
1529
|
-
rotation_axis = [1,0,0]
|
|
1530
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1531
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1532
|
-
|
|
1533
|
-
# Define output affine
|
|
1534
|
-
#pixel_spacing = np.array([0.5, 0.5, 0.5]) # mm
|
|
1535
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1536
|
-
rotation_angle = 0.15 * (np.pi/2) # radians
|
|
1537
|
-
#translation = np.array([0, 0, 30]) # mm
|
|
1538
|
-
rotation_axis = [1,0,0]
|
|
1539
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1540
|
-
output_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1541
|
-
|
|
1542
|
-
# Generate input data data
|
|
1543
|
-
input_data, input_affine = generate('triple ellipsoid', shape=input_shape, affine=input_affine, markers=True)
|
|
1544
|
-
|
|
1545
|
-
return input_data, input_affine, output_affine, None
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
def generate_reslice_data_5():
|
|
1549
|
-
|
|
1550
|
-
# Reslice an object with its own affine
|
|
1551
|
-
|
|
1552
|
-
# Define geometry of input data
|
|
1553
|
-
input_size = np.array([400, 300, 120]) # mm
|
|
1554
|
-
input_shape = np.array([400, 300, 120], dtype=np.int16)
|
|
1555
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1556
|
-
rotation_angle = 0.0 * (np.pi/2) # radians
|
|
1557
|
-
rotation_axis = [1,0,0]
|
|
1558
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1559
|
-
pixel_spacing = np.divide(input_size, input_shape)
|
|
1560
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1561
|
-
|
|
1562
|
-
# Define output affine
|
|
1563
|
-
pixel_spacing = np.array([1.6, 2.6, 7.5]) # mm
|
|
1564
|
-
translation = np.array([100, 0, 0]) # mm
|
|
1565
|
-
rotation_angle = 0.1 * (np.pi/2) # radians
|
|
1566
|
-
rotation_axis = [1,0,0]
|
|
1567
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1568
|
-
output_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1569
|
-
|
|
1570
|
-
# Generate input data data
|
|
1571
|
-
input_data, input_affine = generate('triple ellipsoid', shape=input_shape, affine=input_affine, markers=False)
|
|
1572
|
-
|
|
1573
|
-
# Reslice to output affine keeping the same field of view
|
|
1574
|
-
output_data, output_affine = affine_reslice(input_data, input_affine, output_affine)
|
|
1575
|
-
|
|
1576
|
-
return output_data, output_affine, output_affine, None
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
def generate_reslice_data_6():
|
|
1580
|
-
|
|
1581
|
-
# 1-pixel thick - does not work yet!!
|
|
1582
|
-
|
|
1583
|
-
# Define geometry of input data
|
|
1584
|
-
pixel_spacing = np.array([1.25, 1.25, 5.0]) # mm
|
|
1585
|
-
input_shape = np.array([300, 200, 20], dtype=np.int16)
|
|
1586
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1587
|
-
rotation_angle = 0.0 * (np.pi/2) # radians
|
|
1588
|
-
rotation_axis = [1,0,0]
|
|
1589
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1590
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1591
|
-
|
|
1592
|
-
# Define geometry of output data
|
|
1593
|
-
pixel_spacing = np.array([1.25, 1.25, 10.0]) # mm
|
|
1594
|
-
output_shape = np.array([300, 250, 1], dtype=np.int16)
|
|
1595
|
-
translation = np.array([0, 0, 5]) # mm
|
|
1596
|
-
rotation_angle = 0.2 * (np.pi/2) # radians
|
|
1597
|
-
rotation_axis = [1,0,0]
|
|
1598
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1599
|
-
output_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1600
|
-
|
|
1601
|
-
# Generate ground truth data
|
|
1602
|
-
input_data, input_affine = generate('triple ellipsoid', shape=input_shape, affine=input_affine, markers=True)
|
|
1603
|
-
|
|
1604
|
-
return input_data, input_affine, output_affine, output_shape
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
def generate_translated_data_1():
|
|
1608
|
-
|
|
1609
|
-
# Define geometry of input data
|
|
1610
|
-
pixel_spacing = np.array([1.25, 1.25, 5.0]) # mm
|
|
1611
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1612
|
-
rotation_angle = 0.5 * (np.pi/2) # radians
|
|
1613
|
-
rotation_axis = [1,0,0]
|
|
1614
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1615
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1616
|
-
input_shape = np.array([300, 200, 25])
|
|
1617
|
-
|
|
1618
|
-
# Define affine transformation
|
|
1619
|
-
translation = np.array([10, -10, 10]) # mm
|
|
1620
|
-
|
|
1621
|
-
# Define geometry of output data (exactly equal to translated volume)
|
|
1622
|
-
transformation = affine_matrix(translation=translation)
|
|
1623
|
-
output_shape, output_affine = affine_output_geometry(input_shape, input_affine, transformation)
|
|
1624
|
-
|
|
1625
|
-
# Generate ground truth data
|
|
1626
|
-
input_data, input_affine = generate('triple ellipsoid', shape=input_shape, affine=input_affine, markers=False)
|
|
1627
|
-
output_data = translate(input_data, input_affine, output_shape, output_affine, translation)
|
|
1628
|
-
|
|
1629
|
-
return input_data, input_affine, output_data, output_affine, translation
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
def generate_translated_data_2():
|
|
1633
|
-
|
|
1634
|
-
# Model for 3D to 2D registration
|
|
1635
|
-
|
|
1636
|
-
# Define geometry of input data
|
|
1637
|
-
pixel_spacing = np.array([1.0, 1.0, 1.0]) # mm
|
|
1638
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1639
|
-
rotation_angle = 0.0 * (np.pi/2) # radians
|
|
1640
|
-
rotation_axis = [1,0,0]
|
|
1641
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1642
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1643
|
-
input_shape = np.array([300, 200, 100])
|
|
1644
|
-
|
|
1645
|
-
# Define affine transformation
|
|
1646
|
-
active_translation = np.array([10, -10, 10]) # mm
|
|
1647
|
-
|
|
1648
|
-
# Define geometry of output data
|
|
1649
|
-
output_shape = np.array([150, 200, 1])
|
|
1650
|
-
pixel_spacing = np.array([1.25, 1.25, 7.5]) # mm
|
|
1651
|
-
#translation = np.array([100, 0, 50]) # mm
|
|
1652
|
-
translation = np.array([100, 0, 25]) # mm
|
|
1653
|
-
rotation_angle = 0.1 * (np.pi/2) # radians
|
|
1654
|
-
rotation_axis = [1,0,0]
|
|
1655
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1656
|
-
output_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1657
|
-
|
|
1658
|
-
# Generate ground truth data
|
|
1659
|
-
input_data, input_affine = generate('triple ellipsoid', shape=input_shape, affine=input_affine, markers=False)
|
|
1660
|
-
output_data = translate(input_data, input_affine, output_shape, output_affine, active_translation)
|
|
1661
|
-
|
|
1662
|
-
return input_data, input_affine, output_data, output_affine, active_translation
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
def generate_translated_data_3():
|
|
1666
|
-
|
|
1667
|
-
# Model for 2D to 3D registration
|
|
1668
|
-
# Same as 2 but input and output reversed
|
|
1669
|
-
|
|
1670
|
-
# Define geometry of input data
|
|
1671
|
-
pixel_spacing = np.array([1.0, 1.0, 1.0]) # mm
|
|
1672
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1673
|
-
rotation_angle = 0.0 * (np.pi/2) # radians
|
|
1674
|
-
rotation_axis = [1,0,0]
|
|
1675
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1676
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1677
|
-
input_shape = np.array([300, 200, 100])
|
|
1678
|
-
|
|
1679
|
-
# Define affine transformation
|
|
1680
|
-
active_translation = np.array([10, -10, 10]) # mm
|
|
1681
|
-
|
|
1682
|
-
# Define geometry of output data
|
|
1683
|
-
output_shape = np.array([150, 200, 1]) # mm
|
|
1684
|
-
pixel_spacing = np.array([1.25, 1.25, 7.5]) # mm
|
|
1685
|
-
# translation = np.array([100, 0, 50]) # mm
|
|
1686
|
-
translation = np.array([100, 0, 25]) # mm
|
|
1687
|
-
rotation_angle = 0.1 * (np.pi/2) # radians
|
|
1688
|
-
rotation_axis = [1,0,0]
|
|
1689
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1690
|
-
output_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1691
|
-
|
|
1692
|
-
# Generate ground truth data
|
|
1693
|
-
input_data, input_affine = generate('triple ellipsoid', shape=input_shape, affine=input_affine, markers=False)
|
|
1694
|
-
output_data = translate(input_data, input_affine, output_shape, output_affine, active_translation)
|
|
1695
|
-
|
|
1696
|
-
return output_data, output_affine, input_data, input_affine, -active_translation
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
############################
|
|
1700
|
-
#### Define tests
|
|
1701
|
-
############################
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
def test_plot(n=1):
|
|
1705
|
-
|
|
1706
|
-
if n==1:
|
|
1707
|
-
input_data, input_affine = generate_plot_data_1()
|
|
1708
|
-
|
|
1709
|
-
pl = plot_volume(input_data, input_affine)
|
|
1710
|
-
pl.show_grid()
|
|
1711
|
-
pl.show()
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
def test_affine_reslice(n=1):
|
|
1715
|
-
|
|
1716
|
-
if n==1:
|
|
1717
|
-
input_data, input_affine, output_affine, output_shape = generate_reslice_data_1()
|
|
1718
|
-
elif n==2:
|
|
1719
|
-
input_data, input_affine, output_affine, output_shape = generate_reslice_data_2()
|
|
1720
|
-
elif n==3:
|
|
1721
|
-
input_data, input_affine, output_affine, output_shape = generate_reslice_data_3()
|
|
1722
|
-
elif n==4:
|
|
1723
|
-
input_data, input_affine, output_affine, output_shape = generate_reslice_data_4()
|
|
1724
|
-
elif n==5:
|
|
1725
|
-
input_data, input_affine, output_affine, output_shape = generate_reslice_data_5()
|
|
1726
|
-
elif n==6:
|
|
1727
|
-
input_data, input_affine, output_affine, output_shape = generate_reslice_data_6()
|
|
1728
|
-
|
|
1729
|
-
start_time = time.time()
|
|
1730
|
-
output_data, output_affine = affine_reslice(input_data, input_affine, output_affine, output_shape=output_shape)
|
|
1731
|
-
end_time = time.time()
|
|
1732
|
-
|
|
1733
|
-
# Display results
|
|
1734
|
-
print('Computation time (sec): ', end_time-start_time)
|
|
1735
|
-
pl = plot_affine_resliced(input_data, input_affine, output_data, output_affine)
|
|
1736
|
-
pl.show_grid()
|
|
1737
|
-
pl.show()
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
def test_affine_transform():
|
|
1741
|
-
|
|
1742
|
-
# Define geometry of source data
|
|
1743
|
-
input_shape = np.array([300, 250, 25]) # mm
|
|
1744
|
-
pixel_spacing = np.array([1.25, 1.25, 5.0]) # mm
|
|
1745
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1746
|
-
rotation_angle = 0.2 * (np.pi/2) # radians
|
|
1747
|
-
rotation_axis = [1,0,0]
|
|
1748
|
-
|
|
1749
|
-
# Generate source volume data
|
|
1750
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1751
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1752
|
-
input_data, input_affine = generate('triple ellipsoid', shape=input_shape, affine=input_affine)
|
|
1753
|
-
|
|
1754
|
-
# Define affine transformation
|
|
1755
|
-
stretch = [1.0, 1, 2.0]
|
|
1756
|
-
translation = np.array([0, 20, 0]) # mm
|
|
1757
|
-
rotation_angle = 0.20 * (np.pi/2)
|
|
1758
|
-
rotation_axis = [0,0,1]
|
|
1759
|
-
|
|
1760
|
-
# Perform affine transformation
|
|
1761
|
-
start_time = time.time()
|
|
1762
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1763
|
-
transformation = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=stretch)
|
|
1764
|
-
output_data, output_affine = affine_transform(input_data, input_affine, transformation)
|
|
1765
|
-
end_time = time.time()
|
|
1766
|
-
|
|
1767
|
-
# Display results
|
|
1768
|
-
print('Computation time (sec): ', end_time-start_time)
|
|
1769
|
-
plot_affine_transformed(input_data, input_affine, output_data, output_affine, transformation)
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
def test_affine_transform_reshape():
|
|
1773
|
-
|
|
1774
|
-
# Define geometry of source data
|
|
1775
|
-
input_shape = np.array([300, 250, 25]) # mm
|
|
1776
|
-
pixel_spacing = np.array([1.25, 1.25, 5.0]) # mm
|
|
1777
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1778
|
-
rotation_angle = 0.2 * (np.pi/2) # radians
|
|
1779
|
-
rotation_axis = [1,0,0]
|
|
1780
|
-
|
|
1781
|
-
# Generate source volume data
|
|
1782
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1783
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1784
|
-
input_data, input_affine = generate('triple ellipsoid', shape=input_shape, affine=input_affine)
|
|
1785
|
-
|
|
1786
|
-
# Define affine transformation
|
|
1787
|
-
stretch = [1.0, 1, 2.0]
|
|
1788
|
-
translation = np.array([0, 20, 0]) # mm
|
|
1789
|
-
rotation_angle = 0.20 * (np.pi/2)
|
|
1790
|
-
rotation_axis = [0,0,1]
|
|
1791
|
-
|
|
1792
|
-
# Perform affine transformation
|
|
1793
|
-
start_time = time.time()
|
|
1794
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1795
|
-
transformation = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=stretch)
|
|
1796
|
-
output_data, output_affine = affine_transform(input_data, input_affine, transformation, reshape=True)
|
|
1797
|
-
end_time = time.time()
|
|
1798
|
-
|
|
1799
|
-
# Display results
|
|
1800
|
-
print('Computation time (sec): ', end_time-start_time)
|
|
1801
|
-
plot_affine_transformed(input_data, input_affine, output_data, output_affine, transformation)
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
def test_affine_transform_and_reslice():
|
|
1805
|
-
|
|
1806
|
-
# Define geometry of input data
|
|
1807
|
-
input_shape = np.array([400, 300, 120]) # mm
|
|
1808
|
-
pixel_spacing = np.array([1.0, 1.0, 1.0]) # mm
|
|
1809
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1810
|
-
rotation_angle = -0.2 * (np.pi/2) # radians
|
|
1811
|
-
rotation_axis = [1,0,0]
|
|
1812
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1813
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1814
|
-
|
|
1815
|
-
# Define geometry of output data
|
|
1816
|
-
output_shape = np.array([350, 300, 10])
|
|
1817
|
-
pixel_spacing = np.array([1.25, 1.25, 5.0]) # mm
|
|
1818
|
-
translation = np.array([100, -30, -40]) # mm
|
|
1819
|
-
rotation_angle = 0.0 * (np.pi/2) # radians
|
|
1820
|
-
rotation_axis = [1,0,0]
|
|
1821
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1822
|
-
output_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1823
|
-
|
|
1824
|
-
# Define affine transformation
|
|
1825
|
-
stretch = [1.25, 1, 1.0]
|
|
1826
|
-
translation = np.array([20, 0, 0]) # mm
|
|
1827
|
-
rotation_angle = 0.1 * (np.pi/2)
|
|
1828
|
-
rotation_axis = [1,0,0]
|
|
1829
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1830
|
-
transformation = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=stretch)
|
|
1831
|
-
|
|
1832
|
-
# Generate input data
|
|
1833
|
-
input_data, input_affine = generate('triple ellipsoid', shape=input_shape, affine=input_affine)
|
|
1834
|
-
|
|
1835
|
-
# Calculate affine transform
|
|
1836
|
-
output_data = affine_transform_and_reslice(input_data, input_affine, output_shape, output_affine, transformation)
|
|
1837
|
-
|
|
1838
|
-
# Display results
|
|
1839
|
-
plot_affine_transform_reslice(input_data, input_affine, output_data, output_affine, transformation)
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
def test_translate(n):
|
|
1843
|
-
|
|
1844
|
-
if n==1:
|
|
1845
|
-
input_data, input_affine, output_data, output_affine, translation = generate_translated_data_1()
|
|
1846
|
-
elif n==2:
|
|
1847
|
-
input_data, input_affine, output_data, output_affine, translation = generate_translated_data_2()
|
|
1848
|
-
elif n==3:
|
|
1849
|
-
input_data, input_affine, output_data, output_affine, translation = generate_translated_data_3()
|
|
1850
|
-
|
|
1851
|
-
transformation = affine_matrix(translation=translation)
|
|
1852
|
-
plot_affine_transform_reslice(input_data, input_affine, output_data, output_affine, transformation)
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
def test_translate_reshape():
|
|
1856
|
-
|
|
1857
|
-
# Define geometry of source data
|
|
1858
|
-
input_shape = np.array([300, 250, 12]) # mm
|
|
1859
|
-
pixel_spacing = np.array([1.25, 1.25, 10.0]) # mm
|
|
1860
|
-
rotation_angle = 0.0 * (np.pi/2) # radians
|
|
1861
|
-
rotation_axis = [1,0,0]
|
|
1862
|
-
translation = np.array([0, 0, 0]) # mm
|
|
1863
|
-
|
|
1864
|
-
# Generate reference volume
|
|
1865
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
1866
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
1867
|
-
input_data, input_affine = generate('triple ellipsoid', shape=input_shape, affine=input_affine)
|
|
1868
|
-
|
|
1869
|
-
# Perform translation with reshaping
|
|
1870
|
-
translation = np.array([0, -30, 0]) # mm
|
|
1871
|
-
start_time = time.time()
|
|
1872
|
-
ouput_data, output_affine = translate_reshape(input_data, input_affine, translation)
|
|
1873
|
-
end_time = time.time()
|
|
1874
|
-
|
|
1875
|
-
# Display results
|
|
1876
|
-
print('Computation time (sec): ', end_time-start_time)
|
|
1877
|
-
transformation = affine_matrix(translation=translation)
|
|
1878
|
-
plot_affine_transformed(input_data, input_affine, ouput_data, output_affine, transformation)
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
def test_rotate(show=True):
|
|
1882
|
-
|
|
1883
|
-
# Generate reference volume
|
|
1884
|
-
input_data, input_affine = generate('triple ellipsoid', markers=False)
|
|
1885
|
-
|
|
1886
|
-
# Define rotation
|
|
1887
|
-
angle = 0.1 * (np.pi/2)
|
|
1888
|
-
axis = [1,0,0]
|
|
1889
|
-
rotation = angle * np.array(axis)/np.linalg.norm(axis)
|
|
1890
|
-
|
|
1891
|
-
# Define output_volume
|
|
1892
|
-
output_shape = input_data.shape
|
|
1893
|
-
output_affine = input_affine
|
|
1894
|
-
|
|
1895
|
-
# Perform rotation
|
|
1896
|
-
start_time = time.time()
|
|
1897
|
-
output_data = rotate(input_data, input_affine, output_shape, output_affine, rotation) # specifying outputshape and affine should not be required
|
|
1898
|
-
end_time = time.time()
|
|
1899
|
-
|
|
1900
|
-
# Display results
|
|
1901
|
-
if show is True:
|
|
1902
|
-
print('Computation time (sec): ', end_time-start_time)
|
|
1903
|
-
transformation = affine_matrix(rotation=rotation)
|
|
1904
|
-
plot_affine_transformed(input_data, input_affine, output_data, output_affine, transformation)
|
|
1905
|
-
|
|
1906
|
-
return input_data, input_affine, output_data, output_affine, rotation
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
def test_rotate_reshape():
|
|
1910
|
-
|
|
1911
|
-
# Generate reference volume
|
|
1912
|
-
input_data, input_affine = generate('triple ellipsoid')
|
|
1913
|
-
|
|
1914
|
-
# Define rotation
|
|
1915
|
-
angle = 0.1 * (np.pi/2)
|
|
1916
|
-
axis = [1,0,0]
|
|
1917
|
-
rotation = angle * np.array(axis)/np.linalg.norm(axis)
|
|
1918
|
-
|
|
1919
|
-
# Perform rotation
|
|
1920
|
-
start_time = time.time()
|
|
1921
|
-
output_data, output_affine = rotate_reshape(input_data, input_affine, rotation) # not logical that this returns the output_affine
|
|
1922
|
-
end_time = time.time()
|
|
1923
|
-
|
|
1924
|
-
# Display results
|
|
1925
|
-
print('Computation time (sec): ', end_time-start_time)
|
|
1926
|
-
transformation = affine_matrix(rotation=rotation)
|
|
1927
|
-
plot_affine_transformed(input_data, input_affine, output_data, output_affine, transformation)
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
def test_stretch(n=1, show=True):
|
|
1931
|
-
|
|
1932
|
-
# Generate reference volume
|
|
1933
|
-
input_data, input_affine = generate('triple ellipsoid', markers=False)
|
|
1934
|
-
|
|
1935
|
-
# Define transformation
|
|
1936
|
-
if n==1:
|
|
1937
|
-
stretch_factor = np.array([2.0, 2.5, 0.5])
|
|
1938
|
-
elif n==2:
|
|
1939
|
-
stretch_factor = np.array([1.0, 1.5, 1.0])
|
|
1940
|
-
elif n==3:
|
|
1941
|
-
stretch_factor = np.array([1.0, 1.1, 1.0])
|
|
1942
|
-
|
|
1943
|
-
# Define output_volume
|
|
1944
|
-
output_shape = input_data.shape
|
|
1945
|
-
output_affine = input_affine
|
|
1946
|
-
|
|
1947
|
-
# Perform rotation
|
|
1948
|
-
start_time = time.time()
|
|
1949
|
-
output_data = stretch(input_data, input_affine, output_shape, output_affine, stretch_factor) # specifying outputshape and affine should not be required
|
|
1950
|
-
end_time = time.time()
|
|
1951
|
-
|
|
1952
|
-
# Display results
|
|
1953
|
-
if show is True:
|
|
1954
|
-
print('Computation time (sec): ', end_time-start_time)
|
|
1955
|
-
transformation = affine_matrix(pixel_spacing=stretch_factor)
|
|
1956
|
-
plot_affine_transformed(input_data, input_affine, output_data, output_affine, transformation)
|
|
1957
|
-
|
|
1958
|
-
return input_data, input_affine, output_data, output_affine, stretch_factor
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
def test_stretch_reshape(show=True):
|
|
1962
|
-
|
|
1963
|
-
# Generate reference volume
|
|
1964
|
-
input_data, input_affine = generate('triple ellipsoid', markers=False)
|
|
1965
|
-
|
|
1966
|
-
# Define transformation
|
|
1967
|
-
stretch_factor = np.array([2.0, 2.5, 0.5])
|
|
1968
|
-
|
|
1969
|
-
# Perform transformation
|
|
1970
|
-
start_time = time.time()
|
|
1971
|
-
output_data, output_affine = stretch_reshape(input_data, input_affine, stretch_factor) # specifying outputshape and affine should not be required
|
|
1972
|
-
end_time = time.time()
|
|
1973
|
-
|
|
1974
|
-
# Display results
|
|
1975
|
-
if show is True:
|
|
1976
|
-
print('Computation time (sec): ', end_time-start_time)
|
|
1977
|
-
transformation = affine_matrix(pixel_spacing=stretch_factor)
|
|
1978
|
-
plot_affine_transformed(input_data, input_affine, output_data, output_affine, transformation)
|
|
1979
|
-
|
|
1980
|
-
return input_data, input_affine, output_data, output_affine, stretch_factor
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
def test_rotate_around():
|
|
1984
|
-
|
|
1985
|
-
# Generate reference volume
|
|
1986
|
-
input_data, input_affine = generate('ellipsoid', markers=False)
|
|
1987
|
-
|
|
1988
|
-
# Define rotation
|
|
1989
|
-
rotation = 0.5 * np.pi/2 * np.array([1, 0, 0]) # radians
|
|
1990
|
-
com = center_of_mass(input_data, input_affine)
|
|
1991
|
-
|
|
1992
|
-
# Define output_volume
|
|
1993
|
-
output_shape = input_data.shape
|
|
1994
|
-
output_affine = input_affine
|
|
1995
|
-
|
|
1996
|
-
# Perform rotation
|
|
1997
|
-
start_time = time.time()
|
|
1998
|
-
parameters = np.concatenate((rotation, com))
|
|
1999
|
-
output_data = rotate_around(input_data, input_affine, output_shape, output_affine, parameters)
|
|
2000
|
-
end_time = time.time()
|
|
2001
|
-
|
|
2002
|
-
# Display results
|
|
2003
|
-
print('Computation time (sec): ', end_time-start_time)
|
|
2004
|
-
transformation = affine_matrix(rotation=rotation, center=com)
|
|
2005
|
-
plot_affine_transformed(input_data, input_affine, output_data, output_affine, transformation)
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
def test_rotate_around_reshape():
|
|
2009
|
-
|
|
2010
|
-
# Generate reference volume
|
|
2011
|
-
input_data, input_affine = generate('ellipsoid', markers=False)
|
|
2012
|
-
|
|
2013
|
-
# Define rotation
|
|
2014
|
-
rotation = 0.5 * np.pi/2 * np.array([1, 0, 0]) # radians
|
|
2015
|
-
com = center_of_mass(input_data, input_affine)
|
|
2016
|
-
|
|
2017
|
-
# Perform rotation
|
|
2018
|
-
start_time = time.time()
|
|
2019
|
-
output_data, output_affine = rotate_around_reshape(input_data, input_affine, rotation, com)
|
|
2020
|
-
end_time = time.time()
|
|
2021
|
-
|
|
2022
|
-
# Display results
|
|
2023
|
-
print('Computation time (sec): ', end_time-start_time)
|
|
2024
|
-
transformation = affine_matrix(rotation=rotation, center=com)
|
|
2025
|
-
plot_affine_transformed(input_data, input_affine, output_data, output_affine, transformation)
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
def test_rigid(show=True):
|
|
2029
|
-
|
|
2030
|
-
# Generate reference volume
|
|
2031
|
-
input_data, input_affine = generate('ellipsoid', markers=False)
|
|
2032
|
-
|
|
2033
|
-
# Define rigid transformation
|
|
2034
|
-
angle = 0.5 * (np.pi/2)
|
|
2035
|
-
axis = [1,0,0]
|
|
2036
|
-
translation = np.array([0, 60, -40]) # mm
|
|
2037
|
-
rotation = angle * np.array(axis)/np.linalg.norm(axis)
|
|
2038
|
-
|
|
2039
|
-
# Define output_volume
|
|
2040
|
-
output_shape = input_data.shape
|
|
2041
|
-
output_affine = input_affine
|
|
2042
|
-
|
|
2043
|
-
# Perform rigid transformation
|
|
2044
|
-
start_time = time.time()
|
|
2045
|
-
parameters = np.concatenate((rotation, translation))
|
|
2046
|
-
output_data = rigid(input_data, input_affine, output_shape, output_affine, parameters)
|
|
2047
|
-
end_time = time.time()
|
|
2048
|
-
|
|
2049
|
-
# Display results
|
|
2050
|
-
if show is True:
|
|
2051
|
-
print('Computation time (sec): ', end_time-start_time)
|
|
2052
|
-
transformation = affine_matrix(rotation=rotation, translation=translation)
|
|
2053
|
-
plot_affine_transformed(input_data, input_affine, output_data, output_affine, transformation)
|
|
2054
|
-
|
|
2055
|
-
return input_data, input_affine, output_data, output_affine, parameters
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
def test_rigid_reshape():
|
|
2059
|
-
|
|
2060
|
-
# Generate input data
|
|
2061
|
-
input_data, input_affine = generate('ellipsoid', markers=False)
|
|
2062
|
-
|
|
2063
|
-
# Define rigid transformation
|
|
2064
|
-
angle = 0.5 * (np.pi/2)
|
|
2065
|
-
axis = [1,0,0]
|
|
2066
|
-
translation = np.array([0, 60, -40]) # mm
|
|
2067
|
-
rotation = angle * np.array(axis)/np.linalg.norm(axis)
|
|
2068
|
-
|
|
2069
|
-
# Perform rigid transformation
|
|
2070
|
-
start_time = time.time()
|
|
2071
|
-
output_data, output_affine = rigid_reshape(input_data, input_affine, rotation, translation)
|
|
2072
|
-
end_time = time.time()
|
|
2073
|
-
|
|
2074
|
-
# Display results
|
|
2075
|
-
print('Computation time (sec): ', end_time-start_time)
|
|
2076
|
-
transformation = affine_matrix(rotation=rotation, translation=translation)
|
|
2077
|
-
plot_affine_transformed(input_data, input_affine, output_data, output_affine, transformation)
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
def test_affine(show=True):
|
|
2081
|
-
|
|
2082
|
-
# Define geometry of source data
|
|
2083
|
-
input_shape = np.array([300, 250, 25]) # mm
|
|
2084
|
-
pixel_spacing = np.array([1.25, 1.25, 5.0]) # mm
|
|
2085
|
-
translation = np.array([0, 0, 0]) # mm
|
|
2086
|
-
rotation_angle = 0.2 * (np.pi/2) # radians
|
|
2087
|
-
rotation_axis = [1,0,0]
|
|
2088
|
-
|
|
2089
|
-
# Generate source volume data
|
|
2090
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
2091
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
2092
|
-
input_data, input_affine = generate('triple ellipsoid', shape=input_shape, affine=input_affine, markers=False)
|
|
2093
|
-
|
|
2094
|
-
# Define affine transformation
|
|
2095
|
-
stretch = [1.0, 1.5, 1.5]
|
|
2096
|
-
translation = np.array([30, -80, -20]) # mm
|
|
2097
|
-
rotation_angle = 0.20 * (np.pi/2)
|
|
2098
|
-
rotation_axis = [0,0,1]
|
|
2099
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
2100
|
-
|
|
2101
|
-
# Define output_volume
|
|
2102
|
-
output_shape = input_data.shape
|
|
2103
|
-
output_affine = input_affine
|
|
2104
|
-
|
|
2105
|
-
# Apply affine
|
|
2106
|
-
start_time = time.time()
|
|
2107
|
-
parameters = np.concatenate((rotation, translation, stretch))
|
|
2108
|
-
output_data = affine(input_data, input_affine, output_shape, output_affine, parameters)
|
|
2109
|
-
end_time = time.time()
|
|
2110
|
-
|
|
2111
|
-
# Display results
|
|
2112
|
-
if show:
|
|
2113
|
-
print('Computation time (sec): ', end_time-start_time)
|
|
2114
|
-
transformation = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=stretch)
|
|
2115
|
-
plot_affine_transformed(input_data, input_affine, output_data, output_affine, transformation)
|
|
2116
|
-
|
|
2117
|
-
return input_data, input_affine, output_data, output_affine, parameters
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
def test_freeform(show=True, n=1):
|
|
2121
|
-
|
|
2122
|
-
window = False
|
|
2123
|
-
nodes = 4
|
|
2124
|
-
|
|
2125
|
-
if n==1:
|
|
2126
|
-
pass
|
|
2127
|
-
elif n==2:
|
|
2128
|
-
window=True
|
|
2129
|
-
elif n==3:
|
|
2130
|
-
window=True
|
|
2131
|
-
nodes=2
|
|
2132
|
-
elif n==4:
|
|
2133
|
-
window=False
|
|
2134
|
-
nodes=2
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
# Define geometry of source data
|
|
2138
|
-
input_shape = np.array([300, 250, 25]) # mm
|
|
2139
|
-
pixel_spacing = np.array([1.25, 1.25, 5.0]) # mm
|
|
2140
|
-
translation = np.array([0, 0, 0]) # mm
|
|
2141
|
-
rotation_angle = 0.2 * (np.pi/2) # radians
|
|
2142
|
-
rotation_axis = [1,0,0]
|
|
2143
|
-
|
|
2144
|
-
# Generate source data
|
|
2145
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
2146
|
-
input_affine = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=pixel_spacing)
|
|
2147
|
-
input_data, input_affine = generate('triple ellipsoid', shape=input_shape, affine=input_affine, markers=False)
|
|
2148
|
-
|
|
2149
|
-
# Define affine transformation
|
|
2150
|
-
stretch = [1.0, 1.5, 1.5]
|
|
2151
|
-
translation = np.array([30, -80, -20]) # mm
|
|
2152
|
-
rotation_angle = 0.20 * (np.pi/2)
|
|
2153
|
-
rotation_axis = [0,0,1]
|
|
2154
|
-
rotation = rotation_angle * np.array(rotation_axis)/np.linalg.norm(rotation_axis)
|
|
2155
|
-
|
|
2156
|
-
# Define output_volume
|
|
2157
|
-
output_shape = list(input_data.shape)
|
|
2158
|
-
output_affine = input_affine.copy()
|
|
2159
|
-
|
|
2160
|
-
if window:
|
|
2161
|
-
output_shape[0] = 100
|
|
2162
|
-
output_shape[1] = 100
|
|
2163
|
-
output_affine[0,3] = output_affine[0,3] + 80
|
|
2164
|
-
output_affine[1,3] = output_affine[1,3] + 80
|
|
2165
|
-
output_affine[2,3] = output_affine[2,3] + 40
|
|
2166
|
-
|
|
2167
|
-
# Apply freeform deformation derived from affine transformation
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
# Get exact results for affine transformation
|
|
2171
|
-
parameters = np.concatenate((rotation, translation, stretch))
|
|
2172
|
-
exact_output_data = affine(input_data, input_affine, output_shape, output_affine, parameters)
|
|
2173
|
-
|
|
2174
|
-
# Define affine transformation
|
|
2175
|
-
affine_transformation_abs = affine_matrix(rotation=rotation, translation=translation, pixel_spacing=stretch)
|
|
2176
|
-
|
|
2177
|
-
# Express affine transformation in input coordinates
|
|
2178
|
-
affine_transformation = np.linalg.inv(input_affine).dot(affine_transformation_abs).dot(input_affine)
|
|
2179
|
-
|
|
2180
|
-
# Invert affine transformation
|
|
2181
|
-
affine_transformation_inv = np.linalg.inv(affine_transformation)
|
|
2182
|
-
|
|
2183
|
-
# Get corresponding inverse deformation field
|
|
2184
|
-
o2i = np.linalg.inv(input_affine).dot(output_affine)
|
|
2185
|
-
inverse_deformation_field = affine_deformation_field(affine_transformation_inv, output_shape, nodes, output_to_input=o2i)
|
|
2186
|
-
|
|
2187
|
-
# Apply deformation field
|
|
2188
|
-
start_time = time.time()
|
|
2189
|
-
#inverse_deformation_field[0,0,0,0] += 10
|
|
2190
|
-
output_data = freeform(input_data, input_affine, output_shape, output_affine, inverse_deformation_field)
|
|
2191
|
-
#output_data = freeform_align(input_data, input_affine, output_shape, output_affine, inverse_deformation_field)
|
|
2192
|
-
end_time = time.time()
|
|
2193
|
-
|
|
2194
|
-
error = np.linalg.norm(output_data-exact_output_data)/np.linalg.norm(exact_output_data)
|
|
2195
|
-
|
|
2196
|
-
# Display results
|
|
2197
|
-
if show:
|
|
2198
|
-
print('Computation time (sec): ', end_time-start_time)
|
|
2199
|
-
print('Error (%): ', 100*error)
|
|
2200
|
-
#plot_affine_transformed(input_data, input_affine, exact_output_data, output_affine, affine_transformation_abs)
|
|
2201
|
-
plot_affine_transformed(input_data, input_affine, output_data, output_affine, affine_transformation_abs)
|
|
2202
|
-
#plot_affine_transformed(input_data, input_affine, output_data-exact_output_data, output_affine, affine_transformation_abs)
|
|
2203
|
-
|
|
2204
|
-
return input_data, input_affine, output_data, output_affine, inverse_deformation_field
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
def test_align_translation(n=1):
|
|
2208
|
-
|
|
2209
|
-
if n==1:
|
|
2210
|
-
input_data, input_affine, output_data, output_affine, translation = generate_translated_data_1()
|
|
2211
|
-
elif n==2:
|
|
2212
|
-
input_data, input_affine, output_data, output_affine, translation = generate_translated_data_2()
|
|
2213
|
-
elif n==3:
|
|
2214
|
-
input_data, input_affine, output_data, output_affine, translation = generate_translated_data_3()
|
|
2215
|
-
|
|
2216
|
-
# Define initial values and step size
|
|
2217
|
-
_, _, output_pixel_spacing = affine_components(output_affine)
|
|
2218
|
-
initial_guess = np.array([0, 0, 0], dtype=np.float32) # mm
|
|
2219
|
-
gradient_step = output_pixel_spacing
|
|
2220
|
-
|
|
2221
|
-
# Define optimization method
|
|
2222
|
-
optimization = {'method':'GD', 'options':{'gradient step': gradient_step, 'tolerance': 0.001}, 'callback':print_current}
|
|
2223
|
-
#optimization = {'method':'Powell', 'options':{'xtol': 1.0}, 'callback':print_current}
|
|
2224
|
-
#optimization = {'method':'BFGS', 'options':{'eps': gradient_step}, 'callback':print_current}
|
|
2225
|
-
|
|
2226
|
-
# Define transformation
|
|
2227
|
-
transformation = translate
|
|
2228
|
-
|
|
2229
|
-
# Align volumes
|
|
2230
|
-
start_time = time.time()
|
|
2231
|
-
try:
|
|
2232
|
-
translation_estimate = align(
|
|
2233
|
-
moving = input_data,
|
|
2234
|
-
moving_affine = input_affine,
|
|
2235
|
-
static = output_data,
|
|
2236
|
-
static_affine = output_affine,
|
|
2237
|
-
parameters = initial_guess,
|
|
2238
|
-
resolutions = [4,2,1],
|
|
2239
|
-
transformation = transformation,
|
|
2240
|
-
metric = sum_of_squares,
|
|
2241
|
-
optimization = optimization,
|
|
2242
|
-
)
|
|
2243
|
-
except:
|
|
2244
|
-
print('Failed to align volumes. Returning initial value as current best guess..')
|
|
2245
|
-
translation_estimate = initial_guess
|
|
2246
|
-
end_time = time.time()
|
|
2247
|
-
|
|
2248
|
-
# Calculate estimate of static image
|
|
2249
|
-
output_data_estimate = transformation(input_data, input_affine, output_data.shape, output_affine, translation_estimate)
|
|
2250
|
-
|
|
2251
|
-
# Display results
|
|
2252
|
-
print('Ground truth parameter: ', translation)
|
|
2253
|
-
print('Parameter estimate: ', translation_estimate)
|
|
2254
|
-
print('Parameter error (%): ', 100*np.linalg.norm(translation_estimate-translation)/np.linalg.norm(translation))
|
|
2255
|
-
print('Computation time (mins): ', (end_time-start_time)/60.0)
|
|
2256
|
-
pl = plot_affine_resliced(output_data_estimate, output_affine, output_data, output_affine)
|
|
2257
|
-
pl.show()
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
def test_align_rotation(n=1):
|
|
2261
|
-
|
|
2262
|
-
if n==1:
|
|
2263
|
-
input_data, input_affine, output_data, output_affine, rotation = test_rotate(show=False)
|
|
2264
|
-
|
|
2265
|
-
# Define initial values and step size
|
|
2266
|
-
_, _, output_pixel_spacing = affine_components(output_affine)
|
|
2267
|
-
initial_guess = np.array([0, 0, 0], dtype=np.float32) # mm
|
|
2268
|
-
#gradient_step = np.array([np.pi/180]*3)
|
|
2269
|
-
gradient_step, _, _ = affine_resolution(output_data.shape, output_pixel_spacing)
|
|
2270
|
-
|
|
2271
|
-
# Define optimization method
|
|
2272
|
-
# Define a precision for each parameter and stop iterating when the largest change for
|
|
2273
|
-
# any of the parameters is less than its precision.
|
|
2274
|
-
# The gradient step is also the precision and the tolarance becomes unnecessary
|
|
2275
|
-
optimization = {'method':'GD', 'options':{'gradient step': gradient_step, 'tolerance': 0.1}, 'callback':print_current}
|
|
2276
|
-
#optimization = {'method':'Powell', 'options':{'xtol': 1.0}, 'callback':print_current}
|
|
2277
|
-
#optimization = {'method':'BFGS', 'options':{'eps': gradient_step}, 'callback':print_current}
|
|
2278
|
-
|
|
2279
|
-
# Define transformation
|
|
2280
|
-
transformation = rotate
|
|
2281
|
-
|
|
2282
|
-
# Align volumes
|
|
2283
|
-
start_time = time.time()
|
|
2284
|
-
try:
|
|
2285
|
-
estimate = align(
|
|
2286
|
-
moving = input_data,
|
|
2287
|
-
moving_affine = input_affine,
|
|
2288
|
-
static = output_data,
|
|
2289
|
-
static_affine = output_affine,
|
|
2290
|
-
parameters = initial_guess,
|
|
2291
|
-
resolutions = [4,2,1],
|
|
2292
|
-
transformation = transformation,
|
|
2293
|
-
metric = sum_of_squares,
|
|
2294
|
-
optimization = optimization,
|
|
2295
|
-
)
|
|
2296
|
-
except:
|
|
2297
|
-
print('Failed to align volumes. Returning initial value as current best guess..')
|
|
2298
|
-
estimate = initial_guess
|
|
2299
|
-
end_time = time.time()
|
|
2300
|
-
|
|
2301
|
-
# Calculate estimate of static image
|
|
2302
|
-
output_data_estimate = transformation(input_data, input_affine, output_data.shape, output_affine, estimate)
|
|
2303
|
-
|
|
2304
|
-
# Display results
|
|
2305
|
-
print('Ground truth parameter: ', rotation)
|
|
2306
|
-
print('Parameter estimate: ', estimate)
|
|
2307
|
-
print('Parameter error (%): ', 100*np.linalg.norm(estimate-rotation)/np.linalg.norm(rotation))
|
|
2308
|
-
print('Computation time (mins): ', (end_time-start_time)/60.0)
|
|
2309
|
-
pl = plot_affine_resliced(output_data_estimate, output_affine, output_data, output_affine)
|
|
2310
|
-
pl.show()
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
def test_align_stretch(n=1):
|
|
2314
|
-
|
|
2315
|
-
input_data, input_affine, output_data, output_affine, parameters = test_stretch(n=n, show=False)
|
|
2316
|
-
|
|
2317
|
-
# Define initial values and step size
|
|
2318
|
-
_, _, output_pixel_spacing = affine_components(output_affine)
|
|
2319
|
-
initial_guess = np.array([1, 1, 1], dtype=np.float32) # mm
|
|
2320
|
-
_, _, step = affine_resolution(output_data.shape, output_pixel_spacing)
|
|
2321
|
-
tol = 0.1
|
|
2322
|
-
#bounds = [(tol*step[0], np.inf), (tol*step[1], np.inf), (tol*step[2], np.inf)]
|
|
2323
|
-
bounds = [(0.5, np.inf), (0.5, np.inf), (0.5, np.inf)]
|
|
2324
|
-
|
|
2325
|
-
# Define registration method
|
|
2326
|
-
optimization = {'method':'GD', 'bounds':bounds, 'options':{'gradient step': step, 'tolerance': tol}, 'callback':print_current}
|
|
2327
|
-
transformation = stretch
|
|
2328
|
-
metric = sum_of_squares
|
|
2329
|
-
|
|
2330
|
-
# Align volumes
|
|
2331
|
-
start_time = time.time()
|
|
2332
|
-
try:
|
|
2333
|
-
estimate = align(
|
|
2334
|
-
moving = input_data,
|
|
2335
|
-
moving_affine = input_affine,
|
|
2336
|
-
static = output_data,
|
|
2337
|
-
static_affine = output_affine,
|
|
2338
|
-
parameters = initial_guess,
|
|
2339
|
-
resolutions = [4,2,1],
|
|
2340
|
-
transformation = transformation,
|
|
2341
|
-
metric = metric,
|
|
2342
|
-
optimization = optimization,
|
|
2343
|
-
)
|
|
2344
|
-
except:
|
|
2345
|
-
print('Failed to align volumes. Returning initial value as current best guess..')
|
|
2346
|
-
estimate = initial_guess
|
|
2347
|
-
end_time = time.time()
|
|
2348
|
-
|
|
2349
|
-
# Calculate estimate of static image and cost functions
|
|
2350
|
-
output_data_estimate = transformation(input_data, input_affine, output_data.shape, output_affine, estimate)
|
|
2351
|
-
cost_after = goodness_of_alignment(estimate, transformation, metric, input_data, input_affine, output_data, output_affine, None)
|
|
2352
|
-
cost_after *= 100/np.sum(np.square(output_data))
|
|
2353
|
-
cost_before = goodness_of_alignment(initial_guess, transformation, metric, input_data, input_affine, output_data, output_affine, None)
|
|
2354
|
-
cost_before *= 100/np.sum(np.square(output_data))
|
|
2355
|
-
|
|
2356
|
-
# Display results
|
|
2357
|
-
print('Ground truth parameter: ', parameters)
|
|
2358
|
-
print('Parameter estimate: ', estimate)
|
|
2359
|
-
print('Cost before alignment (%): ', cost_before)
|
|
2360
|
-
print('Cost after alignment (%): ', cost_after)
|
|
2361
|
-
print('Parameter error (%): ', 100*np.linalg.norm(estimate-parameters)/np.linalg.norm(parameters))
|
|
2362
|
-
print('Computation time (mins): ', (end_time-start_time)/60.0)
|
|
2363
|
-
pl = plot_affine_resliced(output_data_estimate, output_affine, output_data, output_affine)
|
|
2364
|
-
pl.show()
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
def test_align_rigid(n=1):
|
|
2368
|
-
|
|
2369
|
-
if n==1:
|
|
2370
|
-
input_data, input_affine, output_data, output_affine, parameters = test_rigid(show=False)
|
|
2371
|
-
if n==2:
|
|
2372
|
-
input_data, input_affine, output_data, output_affine, rotation = test_rotate(show=False)
|
|
2373
|
-
translation = np.zeros(3, dtype=np.float32)
|
|
2374
|
-
parameters = np.concatenate((rotation, translation))
|
|
2375
|
-
|
|
2376
|
-
# Define initial values and step size
|
|
2377
|
-
_, _, output_pixel_spacing = affine_components(output_affine)
|
|
2378
|
-
initial_guess = np.zeros(parameters.shape, dtype=np.float32) # mm
|
|
2379
|
-
rot_gradient_step, translation_gradient_step, _ = affine_resolution(output_data.shape, output_pixel_spacing)
|
|
2380
|
-
gradient_step = np.concatenate((1.0*rot_gradient_step, 0.5*translation_gradient_step))
|
|
2381
|
-
|
|
2382
|
-
# Define registration
|
|
2383
|
-
optimization = {'method':'GD', 'options':{'gradient step': gradient_step, 'tolerance': 0.1}, 'callback':print_current}
|
|
2384
|
-
transformation = rigid
|
|
2385
|
-
metric = sum_of_squares
|
|
2386
|
-
|
|
2387
|
-
# Align volumes
|
|
2388
|
-
start_time = time.time()
|
|
2389
|
-
try:
|
|
2390
|
-
estimate = align(
|
|
2391
|
-
moving = input_data,
|
|
2392
|
-
moving_affine = input_affine,
|
|
2393
|
-
static = output_data,
|
|
2394
|
-
static_affine = output_affine,
|
|
2395
|
-
parameters = initial_guess,
|
|
2396
|
-
resolutions = [4,2,1],
|
|
2397
|
-
transformation = transformation,
|
|
2398
|
-
metric = metric,
|
|
2399
|
-
optimization = optimization,
|
|
2400
|
-
)
|
|
2401
|
-
except:
|
|
2402
|
-
print('Failed to align volumes. Returning initial value as current best guess..')
|
|
2403
|
-
estimate = initial_guess
|
|
2404
|
-
end_time = time.time()
|
|
2405
|
-
|
|
2406
|
-
# Calculate estimate of static image and cost functions
|
|
2407
|
-
output_data_estimate = transformation(input_data, input_affine, output_data.shape, output_affine, estimate)
|
|
2408
|
-
cost_after = goodness_of_alignment(estimate, transformation, metric, input_data, input_affine, output_data, output_affine, None)
|
|
2409
|
-
cost_after *= 100/np.sum(np.square(output_data))
|
|
2410
|
-
cost_before = goodness_of_alignment(initial_guess, transformation, metric, input_data, input_affine, output_data, output_affine, None)
|
|
2411
|
-
cost_before *= 100/np.sum(np.square(output_data))
|
|
2412
|
-
|
|
2413
|
-
# Display results
|
|
2414
|
-
print('Ground truth parameter: ', parameters)
|
|
2415
|
-
print('Parameter estimate: ', estimate)
|
|
2416
|
-
print('Cost before alignment (%): ', cost_before)
|
|
2417
|
-
print('Cost after alignment (%): ', cost_after)
|
|
2418
|
-
print('Parameter error (%): ', 100*np.linalg.norm(estimate-parameters)/np.linalg.norm(parameters))
|
|
2419
|
-
print('Computation time (mins): ', (end_time-start_time)/60.0)
|
|
2420
|
-
pl = plot_affine_resliced(output_data_estimate, output_affine, output_data, output_affine)
|
|
2421
|
-
pl.show()
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
def test_align_affine(n=1):
|
|
2425
|
-
|
|
2426
|
-
if n==1:
|
|
2427
|
-
input_data, input_affine, output_data, output_affine, parameters = test_affine(show=False)
|
|
2428
|
-
|
|
2429
|
-
# Define initial values and step size
|
|
2430
|
-
_, _, output_pixel_spacing = affine_components(output_affine)
|
|
2431
|
-
initial_guess = np.array([0,0,0,0,0,0,1,1,1], dtype=np.float32)
|
|
2432
|
-
rot_gradient_step, translation_gradient_step, stretch_gradient_step = affine_resolution(output_data.shape, output_pixel_spacing)
|
|
2433
|
-
step = np.concatenate((1.0*rot_gradient_step, 0.5*translation_gradient_step, stretch_gradient_step))
|
|
2434
|
-
bounds = [
|
|
2435
|
-
(0, 2*np.pi), (0, 2*np.pi), (0, 2*np.pi),
|
|
2436
|
-
(-np.inf, np.inf), (-np.inf, np.inf), (-np.inf, np.inf),
|
|
2437
|
-
(0.5, np.inf), (0.5, np.inf), (0.5, np.inf),
|
|
2438
|
-
]
|
|
2439
|
-
|
|
2440
|
-
# Define registration
|
|
2441
|
-
optimization = {'method':'GD', 'bounds': bounds, 'options':{'gradient step': step, 'tolerance': 0.1}, 'callback':print_current}
|
|
2442
|
-
transformation = affine
|
|
2443
|
-
metric = sum_of_squares
|
|
2444
|
-
|
|
2445
|
-
# Align volumes
|
|
2446
|
-
start_time = time.time()
|
|
2447
|
-
try:
|
|
2448
|
-
estimate = align(
|
|
2449
|
-
moving = input_data,
|
|
2450
|
-
moving_affine = input_affine,
|
|
2451
|
-
static = output_data,
|
|
2452
|
-
static_affine = output_affine,
|
|
2453
|
-
parameters = initial_guess,
|
|
2454
|
-
resolutions = [4,2,1],
|
|
2455
|
-
transformation = transformation,
|
|
2456
|
-
metric = metric,
|
|
2457
|
-
optimization = optimization,
|
|
2458
|
-
)
|
|
2459
|
-
except:
|
|
2460
|
-
print('Failed to align volumes. Returning initial value as current best guess..')
|
|
2461
|
-
estimate = initial_guess
|
|
2462
|
-
end_time = time.time()
|
|
2463
|
-
|
|
2464
|
-
# Calculate estimate of static image and cost functions
|
|
2465
|
-
output_data_estimate = transformation(input_data, input_affine, output_data.shape, output_affine, estimate)
|
|
2466
|
-
cost_after = goodness_of_alignment(estimate, transformation, metric, input_data, input_affine, output_data, output_affine, None)
|
|
2467
|
-
cost_after *= 100/np.sum(np.square(output_data))
|
|
2468
|
-
cost_before = goodness_of_alignment(initial_guess, transformation, metric, input_data, input_affine, output_data, output_affine, None)
|
|
2469
|
-
cost_before *= 100/np.sum(np.square(output_data))
|
|
2470
|
-
|
|
2471
|
-
# Display results
|
|
2472
|
-
print('Ground truth parameter: ', parameters)
|
|
2473
|
-
print('Parameter estimate: ', estimate)
|
|
2474
|
-
print('Cost before alignment (%): ', cost_before)
|
|
2475
|
-
print('Cost after alignment (%): ', cost_after)
|
|
2476
|
-
print('Parameter error (%): ', 100*np.linalg.norm(estimate-parameters)/np.linalg.norm(parameters))
|
|
2477
|
-
print('Computation time (mins): ', (end_time-start_time)/60.0)
|
|
2478
|
-
pl = plot_affine_resliced(output_data_estimate, output_affine, output_data, output_affine)
|
|
2479
|
-
pl.show()
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
def test_align_freeform(n=1):
|
|
2483
|
-
|
|
2484
|
-
input_data, input_affine, output_data, output_affine, parameters = test_freeform(show=False, n=n)
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
# Define registration
|
|
2488
|
-
initial_guess = np.zeros(parameters.shape)
|
|
2489
|
-
transformation = freeform
|
|
2490
|
-
metric = sum_of_squares
|
|
2491
|
-
step = np.full(initial_guess.shape, 1.0)
|
|
2492
|
-
optimization = {'method':'GD', 'options':{'gradient step':step, 'tolerance': 0.1}, 'callback': print_current_norm}
|
|
2493
|
-
|
|
2494
|
-
# Align volumes
|
|
2495
|
-
start_time = time.time()
|
|
2496
|
-
try:
|
|
2497
|
-
estimate = align(
|
|
2498
|
-
moving = input_data,
|
|
2499
|
-
moving_affine = input_affine,
|
|
2500
|
-
static = output_data,
|
|
2501
|
-
static_affine = output_affine,
|
|
2502
|
-
resolutions = [1],
|
|
2503
|
-
parameters = initial_guess,
|
|
2504
|
-
transformation = transformation,
|
|
2505
|
-
metric = metric,
|
|
2506
|
-
optimization = optimization,
|
|
2507
|
-
)
|
|
2508
|
-
except:
|
|
2509
|
-
print('Failed to align volumes. Returning initial value as current best guess..')
|
|
2510
|
-
estimate = initial_guess
|
|
2511
|
-
end_time = time.time()
|
|
2512
|
-
|
|
2513
|
-
# Calculate estimate of static image and cost functions
|
|
2514
|
-
output_data_estimate = transformation(input_data, input_affine, output_data.shape, output_affine, estimate)
|
|
2515
|
-
cost_after = goodness_of_alignment(estimate, transformation, metric, input_data, input_affine, output_data, output_affine, None)
|
|
2516
|
-
cost_after *= 100/np.sum(np.square(output_data))
|
|
2517
|
-
cost_before = goodness_of_alignment(initial_guess, transformation, metric, input_data, input_affine, output_data, output_affine, None)
|
|
2518
|
-
cost_before *= 100/np.sum(np.square(output_data))
|
|
2519
|
-
|
|
2520
|
-
# Display results
|
|
2521
|
-
print('Ground truth parameter: ', parameters)
|
|
2522
|
-
print('Parameter estimate: ', estimate)
|
|
2523
|
-
print('Cost before alignment (%): ', cost_before)
|
|
2524
|
-
print('Cost after alignment (%): ', cost_after)
|
|
2525
|
-
print('Parameter error (%): ', 100*np.linalg.norm(estimate-parameters)/np.linalg.norm(parameters))
|
|
2526
|
-
print('Computation time (mins): ', (end_time-start_time)/60.0)
|
|
2527
|
-
pl = plot_affine_resliced(output_data_estimate, output_affine, output_data, output_affine)
|
|
2528
|
-
pl.show()
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
def test_align_freeform_pyramid(n=1):
|
|
2533
|
-
|
|
2534
|
-
if n==1:
|
|
2535
|
-
input_data, input_affine, output_data, output_affine, inverse_deformation_field, affine_transformation_abs = test_freeform(show=False)
|
|
2536
|
-
|
|
2537
|
-
# Define registration
|
|
2538
|
-
optimization = {'method':'GD', 'options':{'tolerance': 0.1}, 'callback':print_current}
|
|
2539
|
-
metric = sum_of_squares
|
|
2540
|
-
|
|
2541
|
-
# Align volumes
|
|
2542
|
-
start_time = time.time()
|
|
2543
|
-
try:
|
|
2544
|
-
estimate = align_freeform(
|
|
2545
|
-
moving = input_data,
|
|
2546
|
-
moving_affine = input_affine,
|
|
2547
|
-
static = output_data,
|
|
2548
|
-
static_affine = output_affine,
|
|
2549
|
-
resolutions = [4,2,1],
|
|
2550
|
-
metric = metric,
|
|
2551
|
-
optimization = optimization,
|
|
2552
|
-
nodes = [2], # TODO: Provide only the final nr of nodes and do the pyramid scheme automatically from 2 to final_nr
|
|
2553
|
-
)
|
|
2554
|
-
except:
|
|
2555
|
-
print('Failed to align volumes. Returning initial value as current best guess..')
|
|
2556
|
-
estimate = initial_guess
|
|
2557
|
-
end_time = time.time()
|
|
2558
|
-
|
|
2559
|
-
# Calculate estimate of static image and cost functions
|
|
2560
|
-
transformation = freeform
|
|
2561
|
-
output_data_estimate = transformation(input_data, input_affine, output_data.shape, output_affine, estimate)
|
|
2562
|
-
cost_after = goodness_of_alignment(estimate, transformation, metric, input_data, input_affine, output_data, output_affine, None)
|
|
2563
|
-
cost_after *= 100/np.sum(np.square(output_data))
|
|
2564
|
-
cost_before = goodness_of_alignment(initial_guess, transformation, metric, input_data, input_affine, output_data, output_affine, None)
|
|
2565
|
-
cost_before *= 100/np.sum(np.square(output_data))
|
|
2566
|
-
|
|
2567
|
-
# Display results
|
|
2568
|
-
print('Ground truth parameter: ', parameters)
|
|
2569
|
-
print('Parameter estimate: ', estimate)
|
|
2570
|
-
print('Cost before alignment (%): ', cost_before)
|
|
2571
|
-
print('Cost after alignment (%): ', cost_after)
|
|
2572
|
-
print('Parameter error (%): ', 100*np.linalg.norm(estimate-parameters)/np.linalg.norm(parameters))
|
|
2573
|
-
print('Computation time (mins): ', (end_time-start_time)/60.0)
|
|
2574
|
-
pl = plot_affine_resliced(output_data_estimate, output_affine, output_data, output_affine)
|
|
2575
|
-
pl.show()
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
if __name__ == "__main__":
|
|
2585
|
-
|
|
2586
|
-
dataset=2
|
|
2587
|
-
|
|
2588
|
-
# Test plotting
|
|
2589
|
-
# -------------
|
|
2590
|
-
# test_plot(1)
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
# Test affine transformations
|
|
2594
|
-
# ---------------------------
|
|
2595
|
-
# test_affine_reslice(6)
|
|
2596
|
-
# test_affine_transform()
|
|
2597
|
-
# test_affine_transform_reshape()
|
|
2598
|
-
# test_affine_transform_and_reslice()
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
# Test forward models
|
|
2602
|
-
# -------------------
|
|
2603
|
-
# test_translate(dataset)
|
|
2604
|
-
# test_translate_reshape()
|
|
2605
|
-
# test_rotate()
|
|
2606
|
-
# test_rotate_reshape()
|
|
2607
|
-
# test_stretch(n=3)
|
|
2608
|
-
# test_stretch_reshape()
|
|
2609
|
-
# test_rotate_around()
|
|
2610
|
-
# test_rotate_around_reshape()
|
|
2611
|
-
# test_rigid()
|
|
2612
|
-
# test_rigid_reshape()
|
|
2613
|
-
# test_affine()
|
|
2614
|
-
# test_freeform(n=3)
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
# Test coregistration
|
|
2618
|
-
# -------------------
|
|
2619
|
-
# test_align_translation(dataset)
|
|
2620
|
-
# test_align_rotation()
|
|
2621
|
-
# test_align_stretch(n=2)
|
|
2622
|
-
# test_align_rigid(n=1)
|
|
2623
|
-
# test_align_affine(n=1)
|
|
2624
|
-
test_align_freeform(n=3)
|
|
2625
|
-
|
|
2626
|
-
|