dbdicom 0.2.0__py3-none-any.whl → 0.3.16__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. dbdicom/__init__.py +3 -25
  2. dbdicom/api.py +496 -0
  3. dbdicom/const.py +144 -0
  4. dbdicom/database.py +133 -0
  5. dbdicom/dataset.py +471 -0
  6. dbdicom/dbd.py +1290 -0
  7. dbdicom/external/__pycache__/__init__.cpython-311.pyc +0 -0
  8. dbdicom/external/dcm4che/__pycache__/__init__.cpython-311.pyc +0 -0
  9. dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-311.pyc +0 -0
  10. dbdicom/external/dcm4che/bin/emf2sf +57 -57
  11. dbdicom/register.py +402 -0
  12. dbdicom/{ds/types → sop_classes}/ct_image.py +2 -16
  13. dbdicom/{ds/types → sop_classes}/enhanced_mr_image.py +206 -160
  14. dbdicom/sop_classes/mr_image.py +338 -0
  15. dbdicom/sop_classes/parametric_map.py +381 -0
  16. dbdicom/sop_classes/secondary_capture.py +140 -0
  17. dbdicom/sop_classes/segmentation.py +311 -0
  18. dbdicom/{ds/types → sop_classes}/ultrasound_multiframe_image.py +1 -15
  19. dbdicom/{ds/types → sop_classes}/xray_angiographic_image.py +2 -17
  20. dbdicom/utils/arrays.py +142 -0
  21. dbdicom/utils/files.py +0 -20
  22. dbdicom/utils/image.py +43 -466
  23. dbdicom/utils/pydicom_dataset.py +386 -0
  24. dbdicom-0.3.16.dist-info/METADATA +26 -0
  25. dbdicom-0.3.16.dist-info/RECORD +54 -0
  26. {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info}/WHEEL +1 -1
  27. dbdicom/create.py +0 -450
  28. dbdicom/ds/__init__.py +0 -10
  29. dbdicom/ds/create.py +0 -63
  30. dbdicom/ds/dataset.py +0 -841
  31. dbdicom/ds/dictionaries.py +0 -620
  32. dbdicom/ds/types/mr_image.py +0 -267
  33. dbdicom/ds/types/parametric_map.py +0 -226
  34. dbdicom/external/__pycache__/__init__.cpython-310.pyc +0 -0
  35. dbdicom/external/__pycache__/__init__.cpython-37.pyc +0 -0
  36. dbdicom/external/dcm4che/__pycache__/__init__.cpython-310.pyc +0 -0
  37. dbdicom/external/dcm4che/__pycache__/__init__.cpython-37.pyc +0 -0
  38. dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-310.pyc +0 -0
  39. dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-37.pyc +0 -0
  40. dbdicom/external/dcm4che/lib/linux-x86/libclib_jiio.so +0 -0
  41. dbdicom/external/dcm4che/lib/linux-x86-64/libclib_jiio.so +0 -0
  42. dbdicom/external/dcm4che/lib/linux-x86-64/libopencv_java.so +0 -0
  43. dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio.so +0 -0
  44. dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio_vis.so +0 -0
  45. dbdicom/external/dcm4che/lib/solaris-sparc/libclib_jiio_vis2.so +0 -0
  46. dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio.so +0 -0
  47. dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio_vis.so +0 -0
  48. dbdicom/external/dcm4che/lib/solaris-sparcv9/libclib_jiio_vis2.so +0 -0
  49. dbdicom/external/dcm4che/lib/solaris-x86/libclib_jiio.so +0 -0
  50. dbdicom/external/dcm4che/lib/solaris-x86-64/libclib_jiio.so +0 -0
  51. dbdicom/manager.py +0 -2077
  52. dbdicom/message.py +0 -119
  53. dbdicom/record.py +0 -1526
  54. dbdicom/types/database.py +0 -107
  55. dbdicom/types/instance.py +0 -184
  56. dbdicom/types/patient.py +0 -40
  57. dbdicom/types/series.py +0 -816
  58. dbdicom/types/study.py +0 -58
  59. dbdicom/utils/variables.py +0 -155
  60. dbdicom/utils/vreg.py +0 -2626
  61. dbdicom/wrappers/__init__.py +0 -7
  62. dbdicom/wrappers/dipy.py +0 -462
  63. dbdicom/wrappers/elastix.py +0 -855
  64. dbdicom/wrappers/numpy.py +0 -119
  65. dbdicom/wrappers/scipy.py +0 -1413
  66. dbdicom/wrappers/skimage.py +0 -1030
  67. dbdicom/wrappers/sklearn.py +0 -151
  68. dbdicom/wrappers/vreg.py +0 -273
  69. dbdicom-0.2.0.dist-info/METADATA +0 -276
  70. dbdicom-0.2.0.dist-info/RECORD +0 -81
  71. {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info/licenses}/LICENSE +0 -0
  72. {dbdicom-0.2.0.dist-info → dbdicom-0.3.16.dist-info}/top_level.txt +0 -0
dbdicom/utils/image.py CHANGED
@@ -1,359 +1,15 @@
1
- import numpy as np
2
- from scipy.interpolate import interpn
3
- from scipy.ndimage import affine_transform
4
-
5
-
6
- def multislice_affine_transform(array_source, affine_source, output_affine, slice_thickness=None, **kwargs):
7
- """Generalization of scipy's affine transform.
8
-
9
- This version also works when the source array is 2D and when it is multislice 2D (ie. slice thickness < slice spacing).
10
- In these scenarios each slice is first reshaped into a volume with provided slice thickness and mapped separately.
11
- """
12
-
13
- slice_spacing = np.linalg.norm(affine_source[:3,2])
14
-
15
- # Single-slice 2D sequence
16
- if array_source.shape[2] == 1:
17
- return _map_multislice_array(array_source, affine_source, output_affine, **kwargs)
18
-
19
- # Multi-slice 2D sequence
20
- elif slice_spacing != slice_thickness:
21
- return _map_multislice_array(array_source, affine_source, output_affine, slice_thickness=slice_thickness, **kwargs)
22
-
23
- # 3D volume sequence
24
- else:
25
- return _map_volume_array(array_source, affine_source, output_affine, **kwargs)
26
-
27
-
28
-
29
- def _map_multislice_array(array, affine, output_affine, output_shape=None, slice_thickness=None, mask=False, label=False, cval=0):
30
-
31
- # Turn each slice into a volume and map as volume.
32
- array_mapped = None
33
- for z in range(array.shape[2]):
34
- array_z, affine_z = slice_to_volume(array, affine, z, slice_thickness=slice_thickness)
35
- array_mapped_z = _map_volume_array(array_z, affine_z, output_affine, output_shape=output_shape, cval=cval)
36
- if array_mapped is None:
37
- array_mapped = array_mapped_z
38
- else:
39
- array_mapped += array_mapped_z
40
-
41
- # If source is a mask array, set values to [0,1].
42
- if mask:
43
- array_mapped[array_mapped > 0.5] = 1
44
- array_mapped[array_mapped <= 0.5] = 0
45
- elif label:
46
- array_mapped = np.around(array_mapped)
47
-
48
- return array_mapped
49
-
50
-
51
- def slice_to_volume(array, affine, z=0, slice_thickness=None):
52
-
53
- # Reshape array to 4D (x,y,z + remainder)
54
- shape = array.shape
55
- if len(shape) > 3:
56
- nk = np.prod(shape[3:])
57
- else:
58
- nk = 1
59
- array = array.reshape(shape[:3] + (nk,))
60
-
61
- # Extract a 2D array
62
- array_z = array[:,:,z,:]
63
- array_z = array_z[:,:,np.newaxis,:]
64
-
65
- # Duplicate the array in the z-direction to create 2 slices.
66
- nz = 2
67
- array_z = np.repeat(array_z, nz, axis=2)
68
-
69
- # Reshape to original nr of dimensions
70
- if len(shape) > 3:
71
- dim = shape[:2] + (nz,) + shape[3:]
72
- else:
73
- dim = shape[:2] + (nz,)
74
- array_z = array_z.reshape(dim)
75
-
76
- # Offset the slice position accordingly
77
- affine_z = affine.copy()
78
- affine_z[:3,3] += z*affine_z[:3,2]
79
-
80
- # Set the slice spacing to equal the slice thickness
81
- if slice_thickness is not None:
82
- slice_spacing = np.linalg.norm(affine_z[:3,2])
83
- affine_z[:3,2] *= slice_thickness/slice_spacing
84
-
85
- # Offset the slice position by half of the slice thickness.
86
- affine_z[:3,3] -= affine_z[:3,2]/2
87
-
88
- return array_z, affine_z
89
-
90
-
91
- def _map_volume_array(array, affine, output_affine, output_shape=None, mask=False, label=False, cval=0):
92
-
93
- shape = array.shape
94
- if shape[2] == 1:
95
- msg = 'This function only works for an array with at least 2 slices'
96
- raise ValueError(msg)
97
-
98
- # Get transformation matrix
99
- source_to_target = np.linalg.inv(affine).dot(output_affine)
100
- source_to_target = np.around(source_to_target, 3) # remove round-off errors in the inversion
101
-
102
- # Reshape array to 4D (x,y,z + remainder)
103
- if output_shape is None:
104
- output_shape = shape[:3]
105
- nk = np.prod(shape[3:])
106
- output = np.empty(output_shape + (nk,))
107
- array = array.reshape(shape[:3] + (nk,))
108
-
109
- #Perform transformation
110
- for k in range(nk):
111
- output[:,:,:,k] = affine_transform(
112
- array[:,:,:,k],
113
- matrix = source_to_target[:3,:3],
114
- offset = source_to_target[:3,3],
115
- output_shape = output_shape,
116
- cval = cval,
117
- order = 0 if mask else 3,
118
- )
119
-
120
- # If source is a mask array, set values to [0,1]
121
- if mask:
122
- output[output > 0.5] = 1
123
- output[output <= 0.5] = 0
124
-
125
- # If source is a label array, round to integers
126
- elif label:
127
- output = np.around(output)
128
-
129
- return output.reshape(output_shape + shape[3:])
130
-
131
-
132
-
133
- # https://discovery.ucl.ac.uk/id/eprint/10146893/1/geometry_medim.pdf
134
-
135
- def interpolate3d_scale(array, scale=2):
136
-
137
- array, _ = interpolate3d_isotropic(array, [1,1,1], isotropic_spacing=1/scale)
138
- return array
139
-
140
-
141
- def interpolate3d_isotropic(array, spacing, isotropic_spacing=None):
142
-
143
- if isotropic_spacing is None:
144
- isotropic_spacing = np.amin(spacing)
145
-
146
- # Get x, y, z coordinates for array
147
- nx = array.shape[0]
148
- ny = array.shape[1]
149
- nz = array.shape[2]
150
- Lx = (nx-1)*spacing[0]
151
- Ly = (ny-1)*spacing[1]
152
- Lz = (nz-1)*spacing[2]
153
- x = np.linspace(0, Lx, nx)
154
- y = np.linspace(0, Ly, ny)
155
- z = np.linspace(0, Lz, nz)
156
-
157
- # Get x, y, z coordinates for isotropic array
158
- nxi = 1 + np.floor(Lx/isotropic_spacing)
159
- nyi = 1 + np.floor(Ly/isotropic_spacing)
160
- nzi = 1 + np.floor(Lz/isotropic_spacing)
161
- Lxi = (nxi-1)*isotropic_spacing
162
- Lyi = (nyi-1)*isotropic_spacing
163
- Lzi = (nzi-1)*isotropic_spacing
164
- xi = np.linspace(0, Lxi, nxi.astype(int))
165
- yi = np.linspace(0, Lyi, nyi.astype(int))
166
- zi = np.linspace(0, Lzi, nzi.astype(int))
167
-
168
- # Interpolate to isotropic
169
- ri = np.meshgrid(xi,yi,zi, indexing='ij')
170
- array = interpn((x,y,z), array, np.stack(ri, axis=-1))
171
- return array, isotropic_spacing
172
-
173
-
174
- def bounding_box(
175
- image_orientation, # ImageOrientationPatient (assume same for all slices)
176
- image_positions, # ImagePositionPatient for all slices
177
- pixel_spacing, # PixelSpacing (assume same for all slices)
178
- rows, # Number of rows
179
- columns): # Number of columns
180
-
181
- """
182
- Calculate the bounding box of an 3D image stored in slices in the DICOM file format.
183
-
184
- Parameters:
185
- image_orientation (list):
186
- a list of 6 elements representing the ImageOrientationPatient DICOM tag for the image.
187
- This specifies the orientation of the image slices in 3D space.
188
- image_positions (list):
189
- a list of 3-element lists representing the ImagePositionPatient DICOM tag for each slice in the image.
190
- This specifies the position of each slice in 3D space.
191
- pixel_spacing (list):
192
- a list of 2 elements representing the PixelSpacing DICOM tag for the image.
193
- This specifies the spacing between pixels in the rows and columns of each slice.
194
- rows (int):
195
- an integer representing the number of rows in each slice.
196
- columns (int):
197
- an integer representing the number of columns in each slice.
198
-
199
- Returns:
200
- dict: a dictionary with keys 'RPF', 'LPF', 'LPH', 'RPH', 'RAF', 'LAF', 'LAH', and 'RAH',
201
- representing the Right Posterior Foot, Left Posterior Foot, Left Posterior Head,
202
- Right Posterior Head, Right Anterior Foot, Left Anterior Foot,
203
- Left Anterior Head, and Right Anterior Head, respectively.
204
- Each key maps to a list of 3 elements representing the x, y, and z coordinates
205
- of the corresponding corner of the bounding box.
206
-
207
- """
208
-
209
- row_spacing = pixel_spacing[0]
210
- column_spacing = pixel_spacing[1]
211
-
212
- row_cosine = np.array(image_orientation[:3])
213
- column_cosine = np.array(image_orientation[3:])
214
- slice_cosine = np.cross(row_cosine, column_cosine)
215
-
216
- number_of_slices = len(image_positions)
217
- image_locations = [np.dot(np.array(pos), slice_cosine) for pos in image_positions]
218
- slab_thickness = max(image_locations) - min(image_locations)
219
- slice_spacing = slab_thickness / (number_of_slices - 1)
220
- image_position_first_slice = image_positions[image_locations.index(min(image_locations))]
221
-
222
- # ul = Upper Left corner of a slice
223
- # ur = Upper Right corner of a slice
224
- # bl = Bottom Left corner of a slice
225
- # br = Bottom Right corner of a slice
226
-
227
- # Initialize with the first slice
228
- ul = image_position_first_slice
229
- ur = ul + row_cosine * (columns-1) * column_spacing
230
- br = ur + column_cosine * (rows-1) * row_spacing
231
- bl = ul + column_cosine * (rows-1) * row_spacing
232
- corners = np.array([ul, ur, br, bl])
233
- amin = np.amax(corners, axis=0)
234
- amax = np.amax(corners, axis=0)
235
- box = {
236
- 'RPF': [amin[0],amax[1],amin[2]], # Right Posterior Foot
237
- 'LPF': [amax[0],amax[1],amin[2]], # Left Posterior Foot
238
- 'LPH': [amax[0],amax[1],amax[2]], # Left Posterior Head
239
- 'RPH': [amin[0],amax[1],amax[2]], # Right Posterior Head
240
- 'RAF': [amin[0],amin[1],amin[2]], # Right Anterior Foot
241
- 'LAF': [amax[0],amin[1],amin[2]], # Left Anterior Foot
242
- 'LAH': [amax[0],amin[1],amax[2]], # Left Anterior Head
243
- 'RAH': [amin[0],amin[1],amax[2]], # Right Anterior Head
244
- }
245
-
246
- # Update with all other slices
247
- # PROBABLY SUFFICIENT TO USE ONLY THE OUTER SLICES!!
248
- for _ in range(1, number_of_slices):
249
-
250
- ul += slice_cosine * slice_spacing
251
- ur = ul + row_cosine * (columns-1) * column_spacing
252
- br = ur + column_cosine * (rows-1) * row_spacing
253
- bl = ul + column_cosine * (rows-1) * row_spacing
254
-
255
- corners = np.array([ul, ur, br, bl])
256
- amin = np.amin(corners, axis=0)
257
- amax = np.amax(corners, axis=0)
258
-
259
- box['RPF'][0] = min([box['RPF'][0], amin[0]])
260
- box['RPF'][1] = max([box['RPF'][1], amax[1]])
261
- box['RPF'][2] = min([box['RPF'][2], amin[2]])
262
-
263
- box['LPF'][0] = max([box['LPF'][0], amax[0]])
264
- box['LPF'][1] = max([box['LPF'][1], amax[1]])
265
- box['LPF'][2] = min([box['LPF'][2], amin[2]])
266
-
267
- box['LPH'][0] = max([box['LPH'][0], amax[0]])
268
- box['LPH'][1] = max([box['LPH'][1], amax[1]])
269
- box['LPH'][2] = max([box['LPH'][2], amax[2]])
270
-
271
- box['RPH'][0] = min([box['RPH'][0], amin[0]])
272
- box['RPH'][1] = max([box['RPH'][1], amax[1]])
273
- box['RPH'][2] = max([box['RPH'][2], amax[2]])
274
-
275
- box['RAF'][0] = min([box['RAF'][0], amin[0]])
276
- box['RAF'][1] = min([box['RAF'][1], amin[1]])
277
- box['RAF'][2] = min([box['RAF'][2], amin[2]])
278
-
279
- box['LAF'][0] = max([box['LAF'][0], amax[0]])
280
- box['LAF'][1] = min([box['LAF'][1], amin[1]])
281
- box['LAF'][2] = min([box['LAF'][2], amin[2]])
282
-
283
- box['LAH'][0] = max([box['LAH'][0], amax[0]])
284
- box['LAH'][1] = min([box['LAH'][1], amin[1]])
285
- box['LAH'][2] = max([box['LAH'][2], amax[2]])
286
-
287
- box['RAH'][0] = min([box['RAH'][0], amin[0]])
288
- box['RAH'][1] = min([box['RAH'][1], amin[1]])
289
- box['RAH'][2] = max([box['RAH'][2], amax[2]])
290
-
291
- return box
292
-
293
1
 
2
+ import numpy as np
294
3
 
295
- def standard_affine_matrix(
296
- bounding_box,
297
- pixel_spacing,
298
- slice_spacing,
299
- orientation = 'axial'):
300
-
301
- row_spacing = pixel_spacing[0]
302
- column_spacing = pixel_spacing[1]
303
-
304
- if orientation == 'axial':
305
- image_position = bounding_box['RAF']
306
- row_cosine = np.array([1,0,0])
307
- column_cosine = np.array([0,1,0])
308
- slice_cosine = np.array([0,0,1])
309
- elif orientation == 'coronal':
310
- image_position = bounding_box['RAH']
311
- row_cosine = np.array([1,0,0])
312
- column_cosine = np.array([0,0,-1])
313
- slice_cosine = np.array([0,1,0])
314
- elif orientation == 'sagittal':
315
- image_position = bounding_box['LAH']
316
- row_cosine = np.array([0,1,0])
317
- column_cosine = np.array([0,0,-1])
318
- slice_cosine = np.array([-1,0,0])
319
-
320
- affine = np.identity(4, dtype=np.float32)
321
- affine[:3, 0] = row_cosine * column_spacing
322
- affine[:3, 1] = column_cosine * row_spacing
323
- affine[:3, 2] = slice_cosine * slice_spacing
324
- affine[:3, 3] = image_position
325
-
326
- return affine
327
4
 
328
5
 
329
6
  def affine_matrix( # single slice function
330
- image_orientation, # ImageOrientationPatient
331
- image_position, # ImagePositionPatient
332
- pixel_spacing, # PixelSpacing
333
- slice_spacing): # SpacingBetweenSlices
334
- """
335
- Calculate an affine transformation matrix for a single slice of an image in the DICOM file format.
336
- The affine transformation matrix can be used to transform the image from its original coordinates
337
- to a new set of coordinates.
338
-
339
- Parameters:
340
- image_orientation (list): a list of 6 elements representing the ImageOrientationPatient
341
- DICOM tag for the image. This specifies the orientation of the
342
- image slices in 3D space.
343
- image_position (list): a list of 3 elements representing the ImagePositionPatient DICOM
344
- tag for the slice. This specifies the position of the slice in 3D space.
345
- pixel_spacing (list): a list of 2 elements representing the PixelSpacing DICOM tag for the
346
- image. This specifies the spacing between pixels in the rows and columns
347
- of each slice.
348
- slice_spacing (float): a float representing the SpacingBetweenSlices DICOM tag for the image. This
349
- specifies the spacing between slices in the image.
350
-
351
- Returns:
352
- np.ndarray: an affine transformation matrix represented as a 4x4 NumPy array with dtype `float32`.
353
- The matrix can be used to transform the image from its original coordinates to a new set
354
- of coordinates.
355
- """
356
-
7
+ image_orientation, # ImageOrientationPatient
8
+ image_position, # ImagePositionPatient
9
+ pixel_spacing, # PixelSpacing
10
+ slice_spacing, # SpacingBetweenSlices
11
+ # slice_location=None,
12
+ ):
357
13
  row_spacing = pixel_spacing[0]
358
14
  column_spacing = pixel_spacing[1]
359
15
 
@@ -366,54 +22,16 @@ def affine_matrix( # single slice function
366
22
  affine[:3, 1] = column_cosine * row_spacing
367
23
  affine[:3, 2] = slice_cosine * slice_spacing
368
24
  affine[:3, 3] = image_position
369
-
370
- return affine
371
-
372
-
373
- def slice_location(
374
- image_orientation, # ImageOrientationPatient
375
- image_position, # ImagePositionPatient
376
- ):
377
- """Calculate Slice Location"""
378
-
379
- row_cosine = np.array(image_orientation[:3])
380
- column_cosine = np.array(image_orientation[3:])
381
- slice_cosine = np.cross(row_cosine, column_cosine)
382
-
383
- return np.dot(np.array(image_position), slice_cosine)
384
25
 
26
+ # if slice_location is not None:
27
+ # # Use slice location to resolve ambiguity in slice cosine
28
+ # loc = np.dot(image_position, slice_cosine)
29
+ # # If loc = -slice_location then flip the slice cosine
30
+ # # Using np.abs() here to avoid effect of small numerical error
31
+ # if np.abs(loc + slice_location) < np.abs(loc - slice_location):
32
+ # affine[:3, 2] *= -1
385
33
 
386
- def affine_matrix_multislice(
387
- image_orientation, # ImageOrientationPatient (assume same for all slices)
388
- image_positions, # ImagePositionPatient for all slices
389
- pixel_spacing): # PixelSpacing (assume same for all slices)
390
-
391
- row_spacing = pixel_spacing[0]
392
- column_spacing = pixel_spacing[1]
393
-
394
- row_cosine = np.array(image_orientation[:3])
395
- column_cosine = np.array(image_orientation[3:])
396
- slice_cosine = np.cross(row_cosine, column_cosine)
397
-
398
- image_locations = [np.dot(np.array(pos), slice_cosine) for pos in image_positions]
399
- #number_of_slices = len(image_positions)
400
- number_of_slices = np.unique(image_locations).size
401
- if number_of_slices == 1:
402
- msg = 'Cannot calculate affine matrix for the slice group. \n'
403
- msg += 'All slices have the same location. \n'
404
- msg += 'Use the single-slice affine formula instead.'
405
- raise ValueError(msg)
406
- slab_thickness = np.amax(image_locations) - np.amin(image_locations)
407
- slice_spacing = slab_thickness / (number_of_slices - 1)
408
- image_position_first_slice = image_positions[image_locations.index(np.amin(image_locations))]
409
-
410
- affine = np.identity(4, dtype=np.float32)
411
- affine[:3, 0] = row_cosine * column_spacing
412
- affine[:3, 1] = column_cosine * row_spacing
413
- affine[:3, 2] = slice_cosine * slice_spacing
414
- affine[:3, 3] = image_position_first_slice
415
-
416
- return affine
34
+ return affine
417
35
 
418
36
 
419
37
  def dismantle_affine_matrix(affine):
@@ -427,36 +45,16 @@ def dismantle_affine_matrix(affine):
427
45
  row_cosine = affine[:3, 0] / column_spacing
428
46
  column_cosine = affine[:3, 1] / row_spacing
429
47
  slice_cosine = affine[:3, 2] / slice_spacing
48
+ # Change to the most common definition - affine[:3, 2] is not even guaranteed to be perp.
49
+ # It is also consistent with def affine_matrix
50
+ # slice_cosine = np.cross(row_cosine, column_cosine)
430
51
  return {
431
52
  'PixelSpacing': [row_spacing, column_spacing],
432
- 'SpacingBetweenSlices': slice_spacing, # This is really spacing between slices
53
+ 'SpacingBetweenSlices': slice_spacing,
433
54
  'ImageOrientationPatient': row_cosine.tolist() + column_cosine.tolist(),
434
55
  'ImagePositionPatient': affine[:3, 3].tolist(), # first slice for a volume
435
56
  'slice_cosine': slice_cosine.tolist()}
436
57
 
437
- def affine_to_RAH(affine):
438
- """Convert to the coordinate system used in NifTi"""
439
-
440
- rot_180 = np.identity(4, dtype=np.float32)
441
- rot_180[:2,:2] = [[-1,0],[0,-1]]
442
- return np.matmul(rot_180, affine)
443
-
444
-
445
- def image_position_patient(affine, number_of_slices):
446
- slab = dismantle_affine_matrix(affine)
447
- image_positions = []
448
- image_locations = []
449
- for s in range(number_of_slices):
450
- pos = [
451
- slab['ImagePositionPatient'][i]
452
- + s*slab['SpacingBetweenSlices']*slab['slice_cosine'][i]
453
- for i in range(3)
454
- ]
455
- loc = np.dot(np.array(pos), np.array(slab['slice_cosine']))
456
- image_positions.append(pos)
457
- image_locations.append(loc)
458
- return image_positions, image_locations
459
-
460
58
 
461
59
  def clip(array, value_range = None):
462
60
 
@@ -467,62 +65,41 @@ def clip(array, value_range = None):
467
65
  return np.clip(array, value_range[0], value_range[1])
468
66
 
469
67
 
470
- def scale_to_range(array, bits_allocated):
68
+ def scale_to_range(array, bits_allocated, signed=False):
471
69
 
472
70
  range = 2.0**bits_allocated - 1
71
+ if signed:
72
+ minval = -2.0**(bits_allocated-1)
73
+ else:
74
+ minval = 0
473
75
  maximum = np.amax(array)
474
76
  minimum = np.amin(array)
475
77
  if maximum == minimum:
476
78
  slope = 1
477
79
  else:
478
80
  slope = range / (maximum - minimum)
479
- intercept = -slope * minimum
480
- array *= slope
481
- array += intercept
81
+ intercept = -slope * minimum + minval
82
+ array = array * slope
83
+ array = array + intercept
482
84
 
483
85
  if bits_allocated == 8:
484
- return array.astype(np.uint8), slope, intercept
86
+ if signed:
87
+ return array.astype(np.int8), slope, intercept
88
+ else:
89
+ return array.astype(np.uint8), slope, intercept
485
90
  if bits_allocated == 16:
486
- return array.astype(np.uint16), slope, intercept
91
+ if signed:
92
+ return array.astype(np.int16), slope, intercept
93
+ else:
94
+ return array.astype(np.uint16), slope, intercept
487
95
  if bits_allocated == 32:
488
- return array.astype(np.uint32), slope, intercept
96
+ if signed:
97
+ return array.astype(np.int32), slope, intercept
98
+ else:
99
+ return array.astype(np.uint32), slope, intercept
489
100
  if bits_allocated == 64:
490
- return array.astype(np.uint64), slope, intercept
491
-
492
-
493
- def BGRA(array, RGBlut=None, width=None, center=None):
494
-
495
- if (width is None) or (center is None):
496
- max = np.amax(array)
497
- min = np.amin(array)
498
- else:
499
- max = center+width/2
500
- min = center-width/2
501
-
502
- # Scale pixel array into byte range
503
- array = np.clip(array, min, max)
504
- array -= min
505
- if max > min:
506
- array *= 255/(max-min)
507
- array = array.astype(np.ubyte)
508
-
509
- BGRA = np.empty(array.shape[:2]+(4,), dtype=np.ubyte)
510
- BGRA[:,:,3] = 255 # Alpha channel
511
-
512
- if RGBlut is None:
513
- # Greyscale image
514
- for c in range(3):
515
- BGRA[:,:,c] = array
516
- else:
517
- # Scale LUT into byte range
518
- RGBlut *= 255
519
- RGBlut = RGBlut.astype(np.ubyte)
520
- # Create RGB array by indexing LUT with pixel array
521
- for c in range(3):
522
- BGRA[:,:,c] = RGBlut[array,2-c]
523
-
524
- return BGRA
525
-
526
-
527
-
101
+ if signed:
102
+ return array.astype(np.int64), slope, intercept
103
+ else:
104
+ return array.astype(np.uint64), slope, intercept
528
105