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.
Files changed (72) hide show
  1. dbdicom/__init__.py +3 -25
  2. dbdicom/api.py +496 -0
  3. dbdicom/const.py +144 -0
  4. dbdicom/database.py +133 -0
  5. dbdicom/dataset.py +471 -0
  6. dbdicom/dbd.py +1290 -0
  7. dbdicom/external/__pycache__/__init__.cpython-311.pyc +0 -0
  8. dbdicom/external/dcm4che/__pycache__/__init__.cpython-311.pyc +0 -0
  9. dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-311.pyc +0 -0
  10. dbdicom/external/dcm4che/bin/emf2sf +57 -57
  11. dbdicom/register.py +402 -0
  12. dbdicom/{ds/types → sop_classes}/ct_image.py +2 -16
  13. dbdicom/{ds/types → sop_classes}/enhanced_mr_image.py +206 -160
  14. dbdicom/sop_classes/mr_image.py +338 -0
  15. dbdicom/sop_classes/parametric_map.py +381 -0
  16. dbdicom/sop_classes/secondary_capture.py +140 -0
  17. dbdicom/sop_classes/segmentation.py +311 -0
  18. dbdicom/{ds/types → sop_classes}/ultrasound_multiframe_image.py +1 -15
  19. dbdicom/{ds/types → sop_classes}/xray_angiographic_image.py +2 -17
  20. dbdicom/utils/arrays.py +142 -0
  21. dbdicom/utils/files.py +0 -20
  22. dbdicom/utils/image.py +43 -466
  23. dbdicom/utils/pydicom_dataset.py +386 -0
  24. dbdicom-0.3.16.dist-info/METADATA +26 -0
  25. dbdicom-0.3.16.dist-info/RECORD +54 -0
  26. {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info}/WHEEL +1 -1
  27. dbdicom/create.py +0 -450
  28. dbdicom/ds/__init__.py +0 -10
  29. dbdicom/ds/create.py +0 -63
  30. dbdicom/ds/dataset.py +0 -841
  31. dbdicom/ds/dictionaries.py +0 -620
  32. dbdicom/ds/types/mr_image.py +0 -267
  33. dbdicom/ds/types/parametric_map.py +0 -226
  34. dbdicom/external/__pycache__/__init__.cpython-310.pyc +0 -0
  35. dbdicom/external/__pycache__/__init__.cpython-37.pyc +0 -0
  36. dbdicom/external/dcm4che/__pycache__/__init__.cpython-310.pyc +0 -0
  37. dbdicom/external/dcm4che/__pycache__/__init__.cpython-37.pyc +0 -0
  38. dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-310.pyc +0 -0
  39. dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-37.pyc +0 -0
  40. dbdicom/external/dcm4che/lib/linux-x86/libclib_jiio.so +0 -0
  41. dbdicom/external/dcm4che/lib/linux-x86-64/libclib_jiio.so +0 -0
  42. dbdicom/external/dcm4che/lib/linux-x86-64/libopencv_java.so +0 -0
  43. dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio.so +0 -0
  44. dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio_vis.so +0 -0
  45. dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio_vis2.so +0 -0
  46. dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio.so +0 -0
  47. dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio_vis.so +0 -0
  48. dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio_vis2.so +0 -0
  49. dbdicom/external/dcm4che/lib/solaris-x86/libclib_jiio.so +0 -0
  50. dbdicom/external/dcm4che/lib/solaris-x86-64/libclib_jiio.so +0 -0
  51. dbdicom/manager.py +0 -2077
  52. dbdicom/message.py +0 -119
  53. dbdicom/record.py +0 -1526
  54. dbdicom/types/database.py +0 -107
  55. dbdicom/types/instance.py +0 -184
  56. dbdicom/types/patient.py +0 -40
  57. dbdicom/types/series.py +0 -816
  58. dbdicom/types/study.py +0 -58
  59. dbdicom/utils/variables.py +0 -155
  60. dbdicom/utils/vreg.py +0 -2626
  61. dbdicom/wrappers/__init__.py +0 -7
  62. dbdicom/wrappers/dipy.py +0 -462
  63. dbdicom/wrappers/elastix.py +0 -855
  64. dbdicom/wrappers/numpy.py +0 -119
  65. dbdicom/wrappers/scipy.py +0 -1413
  66. dbdicom/wrappers/skimage.py +0 -1030
  67. dbdicom/wrappers/sklearn.py +0 -151
  68. dbdicom/wrappers/vreg.py +0 -273
  69. dbdicom-0.2.0.dist-info/METADATA +0 -276
  70. dbdicom-0.2.0.dist-info/RECORD +0 -81
  71. {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info/licenses}/LICENSE +0 -0
  72. {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
-