dbdicom 0.2.3__py3-none-any.whl → 0.2.5__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.

Potentially problematic release.


This version of dbdicom might be problematic. Click here for more details.

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