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/wrappers/scipy.py DELETED
@@ -1,1413 +0,0 @@
1
- import numpy as np
2
- import pandas as pd
3
- import scipy
4
- from scipy.ndimage import affine_transform
5
- import dbdicom
6
- from dbdicom.utils.image import multislice_affine_transform
7
-
8
-
9
-
10
- def _equal_geometry(affine1, affine2):
11
- # Check if both are the same,
12
- # ignoring the order in the list
13
- if not isinstance(affine2, list):
14
- affine2 = [affine2]
15
- if not isinstance(affine1, list):
16
- affine1 = [affine1]
17
- if len(affine1) != len(affine2):
18
- return False
19
- unmatched = list(range(len(affine2)))
20
- for a1 in affine1:
21
- imatch = None
22
- for i in unmatched:
23
- if np.array_equal(a1[0], affine2[i][0]):
24
- # If a slice group with the same affine is found,
25
- # check if the image dimensions are the same too.
26
- dim1 = a1[1][0].array().shape
27
- dim2 = affine2[i][1][0].array().shape
28
- if dim1 == dim2:
29
- imatch = i
30
- break
31
- if imatch is not None:
32
- unmatched.remove(imatch)
33
- return unmatched == []
34
-
35
-
36
- # Better use set(tuple())
37
- def _lists_have_equal_items(list1, list2):
38
- # Convert the lists to sets
39
- set1 = set([tuple(x) for x in list1])
40
- set2 = set([tuple(x) for x in list2])
41
-
42
- # Check if the sets are equal
43
- return set1 == set2
44
-
45
-
46
- def mask_curve_3d(masks, images, **kwargs):
47
- if not isinstance(masks, list):
48
- masks = [masks]
49
- if not isinstance(images, list):
50
- images = [images]
51
- df_all = []
52
- for mask in masks:
53
- for img in images:
54
- df = _mask_curve_3d(mask, img, **kwargs)
55
- df_all.append(df)
56
- return df_all
57
-
58
-
59
- def _mask_curve_3d(mask, series, dim='InstanceNumber'):
60
-
61
- # Get 4D mask array overlaid on 4D series
62
- msk_arr, img_hdrs = mask_array(mask, on=series, dim=dim)
63
-
64
- # Define variables
65
- vars = ['PatientID', 'StudyDescription', 'SeriesDescription', 'Region of Interest']
66
- vars += [str(dim), 'Mean', 'Stdev', 'Max', 'Min', 'Median', '2.5 perc', '97.5 perc']
67
-
68
- # Read values
69
- img = series.instance()
70
- ids = [img.PatientID, img.StudyDescription, img.SeriesDescription, mask.instance().SeriesDescription]
71
- if isinstance(msk_arr, list):
72
- data = _mask_curve_3d_data(msk_arr, img_hdrs, ids, dim)
73
- else:
74
- data = _mask_curve_3d_data_slice_group(msk_arr, img_hdrs, ids, dim)
75
-
76
- # Return as dataframe
77
- return pd.DataFrame(data, columns=vars)
78
-
79
-
80
- def _mask_curve_3d_data_slice_group(msk_arr, img_hdrs, ids, dim):
81
- data = []
82
- nt = msk_arr.shape[-1]
83
- for t in range(nt):
84
- img_hdrs[0,0].status.progress(t+1, nt, 'Extracting mask time curves..')
85
- arr = _mask_data(msk_arr[...,t], img_hdrs[...,t])
86
- vals = [
87
- img_hdrs[0,t][dim], # 3d-assuming all slice locations have the same time coordinate
88
- np.mean(arr),
89
- np.std(arr),
90
- np.amax(arr),
91
- np.amin(arr),
92
- np.percentile(arr, 50),
93
- np.percentile(arr, 2.5),
94
- np.percentile(arr, 97.5),
95
- ]
96
- data.append(ids + vals)
97
- return data
98
-
99
-
100
- def _mask_curve_3d_data(msk_arr, img_hdrs, ids, dim):
101
- data = []
102
- nt = msk_arr[0].shape[-1]
103
- for t in range(nt):
104
- img_hdrs[0][0,0].status.progress(t+1, nt, 'Extracting mask time curves..')
105
- # Concatenate data at time t for each slice group
106
- arr = [_mask_data(arr_i[...,t], img_hdrs[i][...,t]) for i, arr_i in enumerate(msk_arr)]
107
- arr = [d for d in arr if d is not None]
108
- arr = np.concatenate(arr)
109
- # Get values
110
- vals = [
111
- img_hdrs[0][0,t][dim], # 3d-assuming all slice locations have the same time coordinate
112
- np.mean(arr),
113
- np.std(arr),
114
- np.amax(arr),
115
- np.amin(arr),
116
- np.percentile(arr, 50),
117
- np.percentile(arr, 2.5),
118
- np.percentile(arr, 97.5),
119
- ]
120
- data.append(ids + vals)
121
- return data
122
-
123
-
124
- def mask_statistics(masks, images):
125
- if not isinstance(masks, list):
126
- masks = [masks]
127
- if not isinstance(images, list):
128
- images = [images]
129
- df_all_masks = None
130
- for mask in masks:
131
- df_mask = None
132
- for img in images:
133
- df_img = _mask_statistics(mask, img)
134
- if df_mask is None:
135
- df_mask = df_img
136
- else:
137
- df_mask = pd.concat([df_mask, df_img], ignore_index=True)
138
- if df_all_masks is None:
139
- df_all_masks = df_mask
140
- else:
141
- df_all_masks = pd.concat([df_all_masks, df_mask], ignore_index=True)
142
- return df_all_masks
143
-
144
-
145
- def _mask_statistics(mask, image):
146
-
147
- # Get mask array
148
- msk_arr, img_hdrs = mask_array(mask, on=image)
149
- data = _mask_data_slice_groups(msk_arr, img_hdrs)
150
- props = _summary_stats(data)
151
- instance = image.instance()
152
- columns = ['PatientID', 'StudyDescription', 'SeriesDescription', 'Region of Interest', 'Parameter', 'Value', 'Unit']
153
- ids = [instance.PatientID, instance.StudyDescription, instance.SeriesDescription, mask.instance().SeriesDescription]
154
- data = []
155
- for par, val in props.items():
156
- row = ids + [par, val, '']
157
- data.append(row)
158
- return pd.DataFrame(data, columns=columns)
159
-
160
-
161
- def _mask_data_slice_groups(msk_arr, img_hdrs):
162
- if isinstance(msk_arr, list):
163
- # Loop over slice groups
164
- data = [_mask_data(arr, img_hdrs[m]) for m, arr in enumerate(msk_arr)]
165
- data = [d for d in data if d is not None]
166
- if data == []:
167
- data = None
168
- else:
169
- data = np.concatenate(data)
170
- else:
171
- # single slice group
172
- data = _mask_data(msk_arr, img_hdrs)
173
- return data
174
-
175
-
176
- def _mask_data(msk_arr, imgs):
177
- data = []
178
- for i, image in np.ndenumerate(imgs):
179
- if image is not None:
180
- if len(i) == 1:
181
- mask = msk_arr[:,:,i[0]]
182
- elif len(i) == 2:
183
- mask = msk_arr[:,:,i[0],i[1]]
184
- if np.count_nonzero(mask) > 0:
185
- array = image.array()
186
- array = array[mask > 0.5]
187
- data.append(array.ravel())
188
- if data == []:
189
- return None
190
- else:
191
- return np.concatenate(data)
192
-
193
-
194
- def _summary_stats(data):
195
- if data is None:
196
- return {}
197
- return {
198
- 'Mean': np.mean(data),
199
- 'Standard deviation': np.std(data),
200
- 'Maximum': np.amax(data),
201
- 'Minimum': np.amin(data),
202
- '2.5% percentile': np.percentile(data, 2.5),
203
- '5% percentile': np.percentile(data, 5),
204
- '10% percentile': np.percentile(data, 10),
205
- '25% percentile': np.percentile(data, 25),
206
- 'Median': np.percentile(data, 50),
207
- '75% percentile': np.percentile(data, 75),
208
- '90% percentile': np.percentile(data, 90),
209
- '95% percentile': np.percentile(data, 95),
210
- '97.5% percentile': np.percentile(data, 97.5),
211
- 'Range': np.amax(data) - np.amin(data),
212
- 'Interquartile range':np.percentile(data, 75) - np.percentile(data, 25),
213
- '90 percent range': np.percentile(data, 95) - np.percentile(data, 5),
214
- 'Coefficient of variation': np.std(data)/np.mean(data),
215
- 'Heterogeneity': (np.percentile(data, 95) - np.percentile(data, 5))/np.percentile(data, 50),
216
- 'Kurtosis': scipy.stats.kurtosis(data),
217
- 'Skewness': scipy.stats.skew(data),
218
- }
219
-
220
-
221
- def array(series, on=None, **kwargs):
222
- """Return the array overlaid on another series"""
223
-
224
- if on is None:
225
- array, _ = series.array(**kwargs)
226
- else:
227
- series_map = map_to(series, on)
228
- array, _ = series_map.array(**kwargs)
229
- if series_map != series:
230
- series_map.remove()
231
- return array
232
-
233
-
234
- def overlay(features):
235
- """ Ensure all the features are in the same geometry as the reference feature"""
236
-
237
- msg = 'Mapping all features on the same geometry'
238
- mapped_features = [features[0]]
239
- for f, feature in enumerate(features[1:]):
240
- feature.status.progress(f+1, len(features)-1, msg)
241
- mapped = map_to(feature, features[0])
242
- mapped_features.append(mapped)
243
- return mapped_features
244
-
245
-
246
- def map_to(source, target, **kwargs):
247
- """Map non-zero pixels onto another series"""
248
-
249
- # Get transformation matrix
250
- source.status.message('Loading transformation matrices..')
251
- affine_source = source.affine_matrix()
252
- affine_target = target.affine_matrix()
253
- if _equal_geometry(affine_source, affine_target):
254
- source.status.hide()
255
- return source
256
-
257
- if isinstance(affine_target, list):
258
- mapped_series = []
259
- for affine_slice_group in affine_target:
260
- slice_group_target = target.new_sibling()
261
- slice_group_target.adopt(affine_slice_group[1])
262
- mapped = _map_series_to_slice_group(source, slice_group_target, affine_source, affine_slice_group[0], **kwargs)
263
- mapped_series.append(mapped)
264
- slice_group_target.remove()
265
- desc = source.instance().SeriesDescription + ' [overlay]'
266
- mapped_series = dbdicom.merge(mapped_series, inplace=True)
267
- mapped_series.SeriesDescription = desc
268
- else:
269
- mapped_series = _map_series_to_slice_group(source, target, affine_source, affine_target[0], **kwargs)
270
- return mapped_series
271
-
272
-
273
- def _map_series_to_slice_group(source, target, affine_source, affine_target, **kwargs):
274
-
275
- if isinstance(affine_source, list):
276
- mapped_series = []
277
- for affine_slice_group in affine_source:
278
- slice_group_source = source.new_sibling()
279
- slice_group_source.adopt(affine_slice_group[1])
280
- mapped = _map_slice_group_to_slice_group(slice_group_source, affine_slice_group[0], target, affine_target, **kwargs)
281
- mapped_series.append(mapped)
282
- slice_group_source.remove()
283
- return dbdicom.merge(mapped_series, inplace=True)
284
- else:
285
- return _map_slice_group_to_slice_group(source, affine_source[0], target, affine_target, **kwargs)
286
-
287
-
288
- def _map_slice_group_to_slice_group(source, affine_source, target, output_affine, **kwargs):
289
-
290
- # Get source arrays
291
- array_source, headers_source = source.array(['SliceLocation','AcquisitionTime'], pixels_first=True)
292
- array_target, headers_target = target.array(['SliceLocation','AcquisitionTime'], pixels_first=True)
293
-
294
- # Get message status updates
295
- source_desc = source.instance().SeriesDescription
296
- target_desc = target.instance().SeriesDescription
297
- message = 'Mapping ' + source_desc + ' onto ' + target_desc
298
- source.message(message)
299
-
300
- array_mapped = multislice_affine_transform(
301
- array_source,
302
- affine_source,
303
- output_affine,
304
- output_shape = array_target.shape[:3],
305
- slice_thickness = headers_source[0,0,0].SliceThickness,
306
- **kwargs,
307
- )
308
-
309
- # TODO
310
- # Preserve source window settings and set the same in result
311
- #
312
-
313
- # Retain source acquisition times
314
- # Assign acquisition time of slice=0 to all slices
315
- nt = headers_source.shape[1]
316
- acq_times = [headers_source[0,t,0].AcquisitionTime for t in range(nt)]
317
-
318
- # Create new series
319
- mapped_series = source.new_sibling(suffix='overlay')
320
- nt, nk = array_source.shape[3], array_source.shape[4]
321
- ns = headers_target.shape[0]
322
- cnt=0
323
- for t in range(nt):
324
- for k in range(nk):
325
- for s in range(ns):
326
- cnt+=1
327
- source.progress(cnt, ns*nt*nk, 'Saving results..')
328
- image = headers_target[s,0,0].copy_to(mapped_series)
329
- image.AcquisitionTime = acq_times[t]
330
- image.set_array(array_mapped[:,:,s,t,k])
331
- return mapped_series
332
-
333
-
334
-
335
- def mask_array(mask, on=None, dim='InstanceNumber'):
336
- """Map non-zero pixels onto another series"""
337
-
338
- if on is None:
339
- return dbdicom.array(mask, sortby=['SliceLocation', dim], mask=True, pixels_first=True, first_volume=True)
340
-
341
- # Get transformation matrix
342
- mask.status.message('Loading transformation matrices..')
343
- affine_source = mask.affine_matrix()
344
- affine_target = on.affine_matrix()
345
-
346
- if isinstance(affine_target, list):
347
- mapped_arrays = []
348
- mapped_headers = []
349
- for affine_slice_group_target in affine_target:
350
- mapped, headers = _map_mask_series_to_slice_group(
351
- mask,
352
- affine_slice_group_target[1],
353
- affine_source,
354
- affine_slice_group_target[0],
355
- dim=dim,
356
- )
357
- mapped_arrays.append(mapped)
358
- mapped_headers.append(headers)
359
- else:
360
- mapped_arrays, mapped_headers = _map_mask_series_to_slice_group(
361
- mask, on, affine_source, affine_target[0], dim=dim)
362
- return mapped_arrays, mapped_headers
363
-
364
-
365
- def _map_mask_series_to_slice_group(source, target, affine_source, affine_target, **kwargs):
366
-
367
- if isinstance(affine_source, list):
368
- mapped_arrays = []
369
- for affine_slice_group in affine_source:
370
- mapped, headers = _map_mask_slice_group_to_slice_group(
371
- affine_slice_group[1],
372
- target,
373
- affine_slice_group[0],
374
- affine_target,
375
- **kwargs,
376
- )
377
- mapped_arrays.append(mapped)
378
- array = np.logical_or(mapped_arrays[0], mapped_arrays[1])
379
- for a in mapped_arrays[2:]:
380
- array = np.logical_or(array, a)
381
- return array, headers
382
- else:
383
- return _map_mask_slice_group_to_slice_group(source, target, affine_source[0], affine_target, **kwargs)
384
-
385
-
386
- def _map_mask_slice_group_to_slice_group(source, target, affine_source, affine_target, dim='InstanceNumber'):
387
-
388
- if isinstance(source, list):
389
- status = source[0].status
390
- else:
391
- status = source.status
392
-
393
- # Get arrays
394
- array_source, headers_source = dbdicom.array(source, sortby=['SliceLocation',dim], pixels_first=True, first_volume=True)
395
- array_target, headers_target = dbdicom.array(target, sortby=['SliceLocation',dim], pixels_first=True, first_volume=True)
396
-
397
- # For mapping mask onto series, the time dimensions must be the same.
398
- # If they are not, the mask is extruded on to the series time dimensions.
399
- nk = array_target.shape[3]
400
- if array_source.shape[3] != nk:
401
- status.message('Extruding ROI on time series..')
402
- array_source = np.amax(array_source, axis=-1)
403
- array_source = np.repeat(array_source[:,:,:,np.newaxis], nk, axis=3)
404
-
405
- # If the dimensions and affines are equal there is nothing to do
406
- if np.array_equal(affine_source, affine_target):
407
- if array_source.shape == array_target.shape:
408
- # Make sure the result is a mask
409
- array_source[array_source > 0.5] = 1
410
- array_source[array_source <= 0.5] = 0
411
- return array_source, headers_target
412
-
413
- # Perform the affine transformation
414
- array_target = multislice_affine_transform(
415
- array_source,
416
- affine_source,
417
- affine_target,
418
- output_shape = array_target.shape[:3],
419
- slice_thickness = headers_source[0,0].SliceThickness,
420
- mask = True,
421
- )
422
-
423
- return array_target, headers_target
424
-
425
-
426
-
427
-
428
-
429
-
430
- # SEGMENTATION
431
-
432
-
433
- def label_2d(input, **kwargs):
434
- """
435
- Labels structures in an image
436
-
437
- Wrapper for scipy.ndimage.label function.
438
-
439
- Parameters
440
- ----------
441
- input: dbdicom series
442
-
443
- Returns
444
- -------
445
- filtered : dbdicom series
446
- """
447
- suffix = ' [label 2D]'
448
- desc = input.instance().SeriesDescription
449
- filtered = input.copy(SeriesDescription = desc+suffix)
450
- #images = filtered.instances() # setting sort=False should be faster - TEST!!!!!!!
451
- images = filtered.images()
452
- for i, image in enumerate(images):
453
- input.status.progress(i+1, len(images), 'Labelling ' + desc)
454
- image.read()
455
- array = image.array()
456
- array, _ = scipy.ndimage.label(array, **kwargs)
457
- image.set_array(array)
458
- _reset_window(image, array)
459
- image.clear()
460
- input.status.hide()
461
- return filtered
462
-
463
-
464
- def label_3d(input, **kwargs):
465
- """
466
- Labels structures in a 3D volume
467
-
468
- Wrapper for scipy.ndimage.label function.
469
-
470
- Parameters
471
- ----------
472
- input: dbdicom series
473
-
474
- Returns
475
- -------
476
- filtered : dbdicom series
477
- """
478
- desc = input.instance().SeriesDescription + ' [label 3D]'
479
- transform = input.new_sibling(SeriesDescription = desc)
480
- array, headers = input.array('SliceLocation', pixels_first=True)
481
- if array is None:
482
- return transform
483
- for t in range(array.shape[3]):
484
- input.status.progress(t, array.shape[3], 'Calculating ' + desc)
485
- array[:,:,:,t], _ = scipy.ndimage.label(array[:,:,:,t], **kwargs)
486
- transform.set_array(array[:,:,:,t], headers[:,t], pixels_first=True)
487
- _reset_window(transform, array)
488
- input.status.hide()
489
- return transform
490
-
491
-
492
- def extract_largest_cluster_3d(input, **kwargs):
493
- """
494
- Label structures in 3D and then extract the largest cluster, return as a mask.
495
-
496
- Parameters
497
- ----------
498
- input: dbdicom series
499
-
500
- Returns
501
- -------
502
- dbdicom series
503
- """
504
- desc = input.instance().SeriesDescription + ' [Largest cluster 3D]'
505
- transform = input.new_sibling(SeriesDescription = desc)
506
- array, headers = input.array('SliceLocation', pixels_first=True)
507
- if array is None:
508
- return transform
509
- for t in range(array.shape[3]):
510
- input.status.progress(t, array.shape[3], 'Calculating ' + desc)
511
- label_img, cnt = scipy.ndimage.label(array[:,:,:,t], **kwargs)
512
- # Find the label of the largest feature
513
- labels = range(1,cnt+1)
514
- size = [np.count_nonzero(label_img==l) for l in labels]
515
- max_label = labels[size.index(np.amax(size))]
516
- # Create a mask corresponding to the largest feature
517
- label_img = label_img==max_label
518
- #label_img = label_img[label_img==max_label]
519
- #label_img /= max_label
520
- transform.set_array(label_img, headers[:,t], pixels_first=True)
521
- _reset_window(transform, array)
522
- input.status.hide()
523
- return transform
524
-
525
-
526
- def binary_fill_holes(input, **kwargs):
527
- """
528
- Fill holes in an existing segmentation.
529
-
530
- Wrapper for scipy.ndimage.binary_fill_holes function.
531
-
532
- Parameters
533
- ----------
534
- input: dbdicom series
535
-
536
- Returns
537
- -------
538
- filtered : dbdicom series
539
- """
540
- suffix = ' [Fill holes]'
541
- desc = input.instance().SeriesDescription
542
- filtered = input.copy(SeriesDescription = desc+suffix)
543
- #images = filtered.instances()
544
- images = filtered.images()
545
- for i, image in enumerate(images):
546
- input.status.progress(i+1, len(images), 'Filling holes ' + desc)
547
- image.read()
548
- array = image.array()
549
- array = scipy.ndimage.binary_fill_holes(array, **kwargs)
550
- image.set_array(array)
551
- #array = array.astype(np.ubyte)
552
- #_reset_window(image, array)
553
- image.clear()
554
- input.status.hide()
555
- return filtered
556
-
557
-
558
- def distance_transform_edt_3d(input, **kwargs):
559
- """
560
- Euclidian distance transform in 3D
561
-
562
- Wrapper for scipy.ndimage.distance_transform_edt function.
563
-
564
- Parameters
565
- ----------
566
- input: dbdicom series
567
- markers: dbdicom series of the same dimensions as series
568
-
569
- Returns
570
- -------
571
- filtered : dbdicom series
572
- """
573
- desc = input.instance().SeriesDescription + ' [distance transform 3D]'
574
- #transform = input.copy(SeriesDescription = desc)
575
- transform = input.new_sibling(SeriesDescription = desc)
576
- array, headers = input.array('SliceLocation', pixels_first=True)
577
- if array is None:
578
- return transform
579
- for t in range(array.shape[3]):
580
- if array.shape[3] > 1:
581
- input.status.progress(t, array.shape[3], 'Calculating ' + desc)
582
- else:
583
- input.status.message('Calculating ' + desc + '. Please bear with me..')
584
- array[:,:,:,t] = scipy.ndimage.distance_transform_edt(array[:,:,:,t], **kwargs)
585
- transform.set_array(array[:,:,:,t], headers[:,t], pixels_first=True)
586
- _reset_window(transform, array)
587
- input.status.hide()
588
- return transform
589
-
590
-
591
-
592
- # FILTERS
593
-
594
-
595
-
596
-
597
- # https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.fourier_uniform.html#scipy.ndimage.fourier_ellipsoid
598
- def fourier_ellipsoid(input, size, **kwargs):
599
- """
600
- wrapper for scipy.ndimage.fourier_ellipsoid
601
-
602
- Parameters
603
- ----------
604
- input: dbdicom series
605
-
606
- Returns
607
- -------
608
- filtered : dbdicom series
609
- """
610
- suffix = ' [Fourier Ellipsoid x ' + str(size) + ' ]'
611
- desc = input.instance().SeriesDescription
612
- filtered = input.copy(SeriesDescription = desc + suffix)
613
- #images = filtered.instances()
614
- images = filtered.images()
615
- for i, image in enumerate(images):
616
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
617
- image.read()
618
- array = image.array()
619
- array = np.fft.fft2(array)
620
- array = scipy.ndimage.fourier_ellipsoid(array, size, **kwargs)
621
- array = np.fft.ifft2(array).real
622
- image.set_array(array)
623
- image.clear()
624
- input.status.hide()
625
- return filtered
626
-
627
-
628
- # https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.fourier_uniform.html#scipy.ndimage.fourier_uniform
629
- def fourier_uniform(input, size, **kwargs):
630
- """
631
- wrapper for scipy.ndimage.fourier_uniform
632
-
633
- Parameters
634
- ----------
635
- input: dbdicom series
636
-
637
- Returns
638
- -------
639
- filtered : dbdicom series
640
- """
641
- suffix = ' [Fourier Uniform x ' + str(size) + ' ]'
642
- desc = input.instance().SeriesDescription
643
- filtered = input.copy(SeriesDescription = desc + suffix)
644
- #images = filtered.instances()
645
- images = filtered.images()
646
- for i, image in enumerate(images):
647
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
648
- image.read()
649
- array = image.array()
650
- array = np.fft.fft2(array)
651
- array = scipy.ndimage.fourier_uniform(array, size, **kwargs)
652
- array = np.fft.ifft2(array).real
653
- image.set_array(array)
654
- image.clear()
655
- input.status.hide()
656
- return filtered
657
-
658
-
659
- # https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.fourier_shift.html#scipy.ndimage.fourier_shift
660
- def fourier_gaussian(input, sigma, **kwargs):
661
- """
662
- wrapper for scipy.ndimage.fourier_gaussian.
663
-
664
- Parameters
665
- ----------
666
- input: dbdicom series
667
-
668
- Returns
669
- -------
670
- filtered : dbdicom series
671
- """
672
- suffix = ' [Fourier Gaussian x ' + str(sigma) + ' ]'
673
- desc = input.instance().SeriesDescription
674
- filtered = input.copy(SeriesDescription = desc + suffix)
675
- #images = filtered.instances()
676
- images = filtered.images()
677
- for i, image in enumerate(images):
678
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
679
- image.read()
680
- array = image.array()
681
- array = np.fft.fft2(array)
682
- array = scipy.ndimage.fourier_gaussian(array, sigma, **kwargs)
683
- array = np.fft.ifft2(array).real
684
- image.set_array(array)
685
- image.clear()
686
- input.status.hide()
687
- return filtered
688
-
689
-
690
- # https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.gaussian_gradient_magnitude.html#scipy.ndimage.gaussian_gradient_magnitude
691
- def gaussian_gradient_magnitude(input, sigma, **kwargs):
692
- """
693
- wrapper for scipy.ndimage.gaussian_gradient_magnitude.
694
-
695
- Parameters
696
- ----------
697
- input: dbdicom series
698
-
699
- Returns
700
- -------
701
- filtered : dbdicom series
702
- """
703
- suffix = ' [Gaussian Gradient Magnitude x ' + str(sigma) + ' ]'
704
- desc = input.instance().SeriesDescription
705
- filtered = input.copy(SeriesDescription = desc + suffix)
706
- #images = filtered.instances()
707
- images = filtered.images()
708
- for i, image in enumerate(images):
709
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
710
- image.read()
711
- array = image.array()
712
- array = scipy.ndimage.gaussian_gradient_magnitude(array, sigma, **kwargs)
713
- image.set_array(array)
714
- _reset_window(image, array)
715
- image.clear()
716
- input.status.hide()
717
- return filtered
718
-
719
-
720
- # https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.gaussian_laplace.html#scipy.ndimage.gaussian_laplace
721
- def gaussian_laplace(input, sigma, **kwargs):
722
- """
723
- wrapper for scipy.ndimage.gaussian_laplace.
724
-
725
- Parameters
726
- ----------
727
- input: dbdicom series
728
-
729
- Returns
730
- -------
731
- filtered : dbdicom series
732
- """
733
- suffix = ' [Gaussian Laplace x ' + str(sigma) + ' ]'
734
- desc = input.instance().SeriesDescription
735
- filtered = input.copy(SeriesDescription = desc + suffix)
736
- #images = filtered.instances()
737
- images = filtered.images()
738
- for i, image in enumerate(images):
739
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
740
- image.read()
741
- array = image.array()
742
- array = scipy.ndimage.gaussian_laplace(array, sigma, **kwargs)
743
- image.set_array(array)
744
- _reset_window(image, array)
745
- image.clear()
746
- input.status.hide()
747
- return filtered
748
-
749
-
750
- #https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.laplace.html#scipy.ndimage.laplace
751
- def laplace(input, **kwargs):
752
- """
753
- wrapper for scipy.ndimage.sobel.
754
-
755
- Parameters
756
- ----------
757
- input: dbdicom series
758
-
759
- Returns
760
- -------
761
- filtered : dbdicom series
762
- """
763
- suffix = ' [Laplace Filter]'
764
- desc = input.instance().SeriesDescription
765
- filtered = input.copy(SeriesDescription = desc + suffix)
766
- #images = filtered.instances()
767
- images = filtered.images()
768
- for i, image in enumerate(images):
769
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
770
- image.read()
771
- array = image.array()
772
- array = scipy.ndimage.laplace(array, **kwargs)
773
- image.set_array(array)
774
- _reset_window(image, array)
775
- image.clear()
776
- input.status.hide()
777
- return filtered
778
-
779
-
780
- #https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.sobel.html#scipy.ndimage.sobel
781
- def sobel_filter(input, axis=-1, **kwargs):
782
- """
783
- wrapper for scipy.ndimage.sobel.
784
-
785
- Parameters
786
- ----------
787
- input: dbdicom series
788
-
789
- Returns
790
- -------
791
- filtered : dbdicom series
792
- """
793
- suffix = ' [Sobel Filter along axis ' + str(axis) + ' ]'
794
- desc = input.instance().SeriesDescription
795
- filtered = input.copy(SeriesDescription = desc + suffix)
796
- #images = filtered.instances()
797
- images = filtered.images()
798
- for i, image in enumerate(images):
799
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
800
- image.read()
801
- array = image.array()
802
- array = scipy.ndimage.sobel(array, axis=axis, **kwargs)
803
- image.set_array(array)
804
- _reset_window(image, array)
805
- image.clear()
806
- input.status.hide()
807
- return filtered
808
-
809
-
810
- #https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.prewitt.html#scipy.ndimage.prewitt
811
- def prewitt_filter(input, axis=-1, **kwargs):
812
- """
813
- wrapper for scipy.ndimage.prewitt.
814
-
815
- Parameters
816
- ----------
817
- input: dbdicom series
818
-
819
- Returns
820
- -------
821
- filtered : dbdicom series
822
- """
823
- suffix = ' [Prewitt Filter along axis ' + str(axis) + ' ]'
824
- desc = input.instance().SeriesDescription
825
- filtered = input.copy(SeriesDescription = desc + suffix)
826
- #images = filtered.instances()
827
- images = filtered.images()
828
- for i, image in enumerate(images):
829
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
830
- image.read()
831
- array = image.array()
832
- array = scipy.ndimage.prewitt(array, axis=axis, **kwargs)
833
- image.set_array(array)
834
- _reset_window(image, array)
835
- image.clear()
836
- input.status.hide()
837
- return filtered
838
-
839
-
840
- #https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.median_filter.html#scipy.ndimage.median_filter
841
- def median_filter(input, size=3, **kwargs):
842
- """
843
- wrapper for scipy.ndimage.median_filter.
844
-
845
- Parameters
846
- ----------
847
- input: dbdicom series
848
-
849
- Returns
850
- -------
851
- filtered : dbdicom series
852
- """
853
- suffix = ' [Median Filter with size ' + str(size) + ' ]'
854
- desc = input.instance().SeriesDescription
855
- filtered = input.copy(SeriesDescription = desc + suffix)
856
- #images = filtered.instances()
857
- images = filtered.images()
858
- for i, image in enumerate(images):
859
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
860
- image.read()
861
- array = image.array()
862
- array = scipy.ndimage.median_filter(array, size=size, **kwargs)
863
- image.set_array(array)
864
- image.clear()
865
- input.status.hide()
866
- return filtered
867
-
868
-
869
- #https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.percentile_filter.html#scipy.ndimage.percentile_filter
870
- def percentile_filter(input, percentile, **kwargs):
871
- """
872
- wrapper for scipy.ndimage.percentile_filter.
873
-
874
- Parameters
875
- ----------
876
- input: dbdicom series
877
-
878
- Returns
879
- -------
880
- filtered : dbdicom series
881
- """
882
- suffix = ' [Percentile Filter x ' + str(percentile) + ' ]'
883
- desc = input.instance().SeriesDescription
884
- filtered = input.copy(SeriesDescription = desc + suffix)
885
- #images = filtered.instances()
886
- images = filtered.images()
887
- for i, image in enumerate(images):
888
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
889
- image.read()
890
- array = image.array()
891
- array = scipy.ndimage.percentile_filter(array, percentile, **kwargs)
892
- image.set_array(array)
893
- image.clear()
894
- input.status.hide()
895
- return filtered
896
-
897
-
898
- #https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.rank_filter.html#scipy.ndimage.rank_filter
899
- def rank_filter(input, rank, **kwargs):
900
- """
901
- wrapper for scipy.ndimage.rank_filter.
902
-
903
- Parameters
904
- ----------
905
- input: dbdicom series
906
-
907
- Returns
908
- -------
909
- filtered : dbdicom series
910
- """
911
- suffix = ' [Rank Filter x ' + str(rank) + ' ]'
912
- desc = input.instance().SeriesDescription
913
- filtered = input.copy(SeriesDescription = desc + suffix)
914
- #images = filtered.instances()
915
- images = filtered.images()
916
- for i, image in enumerate(images):
917
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
918
- image.read()
919
- array = image.array()
920
- array = scipy.ndimage.rank_filter(array, rank, **kwargs)
921
- image.set_array(array)
922
- image.clear()
923
- input.status.hide()
924
- return filtered
925
-
926
-
927
- #https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.minimum_filter.html#scipy.ndimage.maximum_filter
928
- def maximum_filter(input, size=3, **kwargs):
929
- """
930
- wrapper for scipy.ndimage.maximum_filter.
931
-
932
- Parameters
933
- ----------
934
- input: dbdicom series
935
-
936
- Returns
937
- -------
938
- filtered : dbdicom series
939
- """
940
- suffix = ' [Maximum Filter x ' + str(size) + ' ]'
941
- desc = input.instance().SeriesDescription
942
- filtered = input.copy(SeriesDescription = desc + suffix)
943
- #images = filtered.instances()
944
- images = filtered.images()
945
- for i, image in enumerate(images):
946
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
947
- image.read()
948
- array = image.array()
949
- array = scipy.ndimage.maximum_filter(array, size=size, **kwargs)
950
- image.set_array(array)
951
- image.clear()
952
- input.status.hide()
953
- return filtered
954
-
955
-
956
- #https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.minimum_filter.html#scipy.ndimage.minimum_filter
957
- def minimum_filter(input, size=3, **kwargs):
958
- """
959
- wrapper for scipy.ndimage.minimum_filter.
960
-
961
- Parameters
962
- ----------
963
- input: dbdicom series
964
-
965
- Returns
966
- -------
967
- filtered : dbdicom series
968
- """
969
- suffix = ' [Minimum Filter x ' + str(size) + ' ]'
970
- desc = input.instance().SeriesDescription
971
- filtered = input.copy(SeriesDescription = desc + suffix)
972
- #images = filtered.instances()
973
- images = filtered.images()
974
- for i, image in enumerate(images):
975
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
976
- image.read()
977
- array = image.array()
978
- array = scipy.ndimage.minimum_filter(array, size=size, **kwargs)
979
- image.set_array(array)
980
- image.clear()
981
- input.status.hide()
982
- return filtered
983
-
984
-
985
- #https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.uniform_filter.html#scipy.ndimage.uniform_filter
986
- def uniform_filter(input, size=3, **kwargs):
987
- """
988
- wrapper for scipy.ndimage.uniform_filter.
989
-
990
- Parameters
991
- ----------
992
- input: dbdicom series
993
-
994
- Returns
995
- -------
996
- filtered : dbdicom series
997
- """
998
- suffix = ' [Uniform Filter x ' + str(size) + ' ]'
999
- desc = input.instance().SeriesDescription
1000
- filtered = input.copy(SeriesDescription = desc + suffix)
1001
- #images = filtered.instances()
1002
- images = filtered.images()
1003
- for i, image in enumerate(images):
1004
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
1005
- image.read()
1006
- array = image.array()
1007
- array = scipy.ndimage.uniform_filter(array, size=size, **kwargs)
1008
- image.set_array(array)
1009
- image.clear()
1010
- input.status.hide()
1011
- return filtered
1012
-
1013
-
1014
- # https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.gaussian_filter.html#scipy.ndimage.gaussian_filter
1015
- def gaussian_filter(input, sigma, **kwargs):
1016
- """
1017
- wrapper for scipy.ndimage.gaussian_filter.
1018
-
1019
- Parameters
1020
- ----------
1021
- input: dbdicom series
1022
-
1023
- Returns
1024
- -------
1025
- filtered : dbdicom series
1026
- """
1027
- suffix = ' [Gaussian Filter x ' + str(sigma) + ' ]'
1028
- desc = input.instance().SeriesDescription
1029
- filtered = input.copy(SeriesDescription = desc + suffix)
1030
- #images = filtered.instances()
1031
- images = filtered.images()
1032
- for i, image in enumerate(images):
1033
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
1034
- image.read()
1035
- array = image.array()
1036
- array = scipy.ndimage.gaussian_filter(array, sigma, **kwargs)
1037
- image.set_array(array)
1038
- if 'order' in kwargs:
1039
- if kwargs['order'] > 0:
1040
- _reset_window(image, array)
1041
- image.clear()
1042
- input.status.hide()
1043
- return filtered
1044
-
1045
-
1046
- # https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.fourier_shift.html#scipy.ndimage.fourier_shift
1047
- def fourier_shift(input, shift, **kwargs):
1048
- """
1049
- wrapper for scipy.ndimage.fourier_shift.
1050
-
1051
- Parameters
1052
- ----------
1053
- input: dbdicom series
1054
-
1055
- Returns
1056
- -------
1057
- filtered : dbdicom series
1058
- """
1059
- suffix = ' [Fourier Shift]'
1060
- desc = input.instance().SeriesDescription
1061
- filtered = input.copy(SeriesDescription = desc + suffix)
1062
- #images = filtered.instances()
1063
- images = filtered.images()
1064
- for i, image in enumerate(images):
1065
- input.status.progress(i+1, len(images), 'Filtering ' + desc)
1066
- image.read()
1067
- array = image.array()
1068
- array = np.fft.fft2(array)
1069
- array = scipy.ndimage.fourier_shift(array, shift, **kwargs)
1070
- array = np.fft.ifft2(array).real
1071
- image.set_array(array)
1072
- image.clear()
1073
- input.status.hide()
1074
- return filtered
1075
-
1076
-
1077
-
1078
-
1079
- # RESCALE AND RESLICE
1080
-
1081
-
1082
-
1083
- def series_calculator(series, operation='1 - series'):
1084
-
1085
- desc = series.instance().SeriesDescription
1086
- result = series.copy(SeriesDescription = desc + ' [' + operation + ']')
1087
- images = result.images()
1088
- for i, img in enumerate(images):
1089
- series.status.progress(i+1, len(images), 'Calculating..')
1090
- img.read()
1091
- array = img.array()
1092
- if operation == '1 - series':
1093
- array = 1 - array
1094
- elif operation == '- series':
1095
- array = -array
1096
- elif operation == '1 / series':
1097
- array = 1 / array
1098
- elif operation == 'exp(- series)':
1099
- array = np.exp(-array)
1100
- elif operation == 'exp(+ series)':
1101
- array = np.exp(array)
1102
- elif operation == 'integer(series)':
1103
- array = np.around(array)
1104
- array[~np.isfinite(array)] = 0
1105
- img.set_array(array)
1106
- _reset_window(img, array)
1107
- img.clear()
1108
- series.status.hide()
1109
- return result
1110
-
1111
-
1112
- def image_calculator(series1, series2, operation='series 1 - series 2', integer=False):
1113
-
1114
- result = map_to(series2, series1)
1115
- if result == series2: # same geometry
1116
- result = series2.copy()
1117
- images1 = series1.images(sortby=['SliceLocation', 'AcquisitionTime'])
1118
- images2 = result.images(sortby=['SliceLocation', 'AcquisitionTime'])
1119
- for i, img1 in enumerate(images1):
1120
- series1.status.progress(i+1, len(images1), 'Calculating..')
1121
- if i > len(images2)-1:
1122
- break
1123
- img2 = images2[i]
1124
- img2.read()
1125
- array1 = img1.array()
1126
- array2 = img2.array()
1127
- if operation == 'series 1 + series 2':
1128
- array = array1 + array2
1129
- desc = ' [add]'
1130
- elif operation == 'series 1 - series 2':
1131
- array = array1 - array2
1132
- desc = ' [difference]'
1133
- elif operation == 'series 1 / series 2':
1134
- array = array1 / array2
1135
- desc = ' [divide]'
1136
- elif operation == 'series 1 * series 2':
1137
- array = array1 * array2
1138
- desc = ' [multiply]'
1139
- elif operation == '(series 1 - series 2)/series 2':
1140
- array = (array1 - array2)/array2
1141
- desc = ' [relative difference]'
1142
- elif operation == 'average(series 1, series 2)':
1143
- array = (array1 + array2)/2
1144
- desc = ' [average]'
1145
- array[~np.isfinite(array)] = 0
1146
- if integer:
1147
- array = np.around(array)
1148
- img2.set_array(array)
1149
- _reset_window(img2, array.astype(np.ubyte))
1150
- img2.clear()
1151
- result.SeriesDescription = result.instance().SeriesDescription + desc
1152
- return result
1153
-
1154
-
1155
- def n_images_calculator(series, operation='mean'):
1156
-
1157
- # Use the first series as geometrical reference
1158
- reference = series[0]
1159
-
1160
- # Get arrays for all series and stack into one array
1161
- array_ref, headers_ref = reference.array(sortby='SliceLocation', pixels_first=True)
1162
- array_all = [array_ref]
1163
- for i, s in enumerate(series[1:]):
1164
- reference.progress(i+1, len(series[1:]), 'Loading arrays')
1165
- array_s = array(s, on=reference, sortby='SliceLocation', pixels_first=True)
1166
- array_all.append(array_s)
1167
- array_all = np.stack(array_all, axis=-1)
1168
-
1169
- # Perform calculation
1170
- reference.message('Calculating ' + operation)
1171
- if operation == 'sum':
1172
- array_all = np.sum(array_all, axis=-1)
1173
- elif operation == 'mean':
1174
- array_all = np.mean(array_all, axis=-1)
1175
-
1176
- # Save as new series and return
1177
- result = reference.new_sibling(suffix=operation)
1178
- result.set_array(array_all, headers_ref, pixels_first=True)
1179
- return result
1180
-
1181
-
1182
-
1183
-
1184
- # https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.zoom.html#scipy.ndimage.zoom
1185
- def zoom(input, zoom, **kwargs):
1186
- """
1187
- wrapper for scipy.ndimage.zoom.
1188
-
1189
- Parameters
1190
- ----------
1191
- input: dbdicom series
1192
-
1193
- Returns
1194
- -------
1195
- zoomed : dbdicom series
1196
- """
1197
- suffix = ' [Resize x ' + str(zoom) + ' ]'
1198
- desc = input.instance().SeriesDescription
1199
- zoomed = input.copy(SeriesDescription = desc + suffix)
1200
- #images = zoomed.instances()
1201
- images = zoomed.images()
1202
- for i, image in enumerate(images):
1203
- input.status.progress(i+1, len(images), 'Resizing ' + desc)
1204
- image.read()
1205
- array = image.array()
1206
- array = scipy.ndimage.zoom(array, zoom, **kwargs)
1207
- image.set_array(array)
1208
- pixel_spacing = image.PixelSpacing
1209
- if type(zoom) is tuple:
1210
- image.PixelSpacing = [pixel_spacing[i]/zoom[i] for i in range(2)]
1211
- else:
1212
- image.PixelSpacing = [p/zoom for p in pixel_spacing]
1213
- image.clear()
1214
- input.status.hide()
1215
- return zoomed
1216
-
1217
-
1218
- def resample(series, voxel_size=[1.0, 1.0, 1.0]):
1219
- series.status.message('Reading transformations..')
1220
- affine_source = series.affine_matrix()
1221
- if affine_source is None:
1222
- return
1223
- if isinstance(affine_source, list):
1224
- mapped_series = []
1225
- for affine_slice_group in affine_source:
1226
- mapped = _resample_slice_group(series, affine_slice_group[0], affine_slice_group[0], voxel_size=voxel_size)
1227
- mapped_series.append(mapped)
1228
- desc = series.instance().SeriesDescription + '[resampled]'
1229
- mapped_series = dbdicom.merge(mapped_series, inplace=True)
1230
- mapped_series.SeriesDescription = desc
1231
- else:
1232
- mapped_series = _resample_slice_group(series, affine_source[0], affine_source[1], voxel_size=voxel_size)
1233
- return mapped_series
1234
-
1235
-
1236
-
1237
-
1238
-
1239
- def _resample_slice_group(series, affine_source, slice_group, voxel_size=[1.0, 1.0, 1.0]):
1240
-
1241
- # Create new resliced series
1242
- desc = series.instance().SeriesDescription + '[resampled]'
1243
- resliced_series = series.new_sibling(SeriesDescription = desc)
1244
-
1245
- # Work out the affine matrix of the new series
1246
- p = dbdicom.utils.image.dismantle_affine_matrix(affine_source)
1247
- affine_target = affine_source.copy()
1248
- affine_target[:3, 0] = voxel_size[0] * np.array(p['ImageOrientationPatient'][:3])
1249
- affine_target[:3, 1] = voxel_size[1] * np.array(p['ImageOrientationPatient'][3:])
1250
- affine_target[:3, 2] = voxel_size[2] * np.array(p['slice_cosine'])
1251
-
1252
- # If the series already is in the right orientation, return a copy
1253
- if np.array_equal(affine_source, affine_target):
1254
- series.status.message('Series is already in the right orientation..')
1255
- resliced_series.adopt(slice_group)
1256
- return resliced_series
1257
-
1258
- # Get arrays
1259
- array, headers = series.array(['SliceLocation','AcquisitionTime'], pixels_first=True)
1260
- if array is None:
1261
- return resliced_series
1262
-
1263
- # Perform transformation on the arrays to determine the output shape
1264
- dim = [
1265
- array.shape[0] * p['PixelSpacing'][1],
1266
- array.shape[1] * p['PixelSpacing'][0],
1267
- array.shape[2] * p['SpacingBetweenSlices'],
1268
- ]
1269
- output_shape = [1 + round(dim[i]/voxel_size[i]) for i in range(3)]
1270
-
1271
- # Determine the transformation matrix and offset
1272
- source_to_target = np.linalg.inv(affine_source).dot(affine_target)
1273
- #matrix, offset = nib.affines.to_matvec(source_to_target)
1274
- matrix, offset = source_to_target[:3,:3], source_to_target[:3,3]
1275
-
1276
- # Perform the affine transformation
1277
- cnt=0
1278
- ns, nt, nk = output_shape[2], array.shape[-2], array.shape[-1]
1279
- pos, loc = dbdicom.utils.image.image_position_patient(affine_target, ns)
1280
- for t in range(nt):
1281
- for k in range(nk):
1282
- cnt+=1
1283
- series.status.progress(cnt, nt*nk, 'Performing transformation..')
1284
- resliced = affine_transform(
1285
- array[:,:,:,t,k],
1286
- matrix = matrix,
1287
- offset = offset,
1288
- output_shape = output_shape,
1289
- )
1290
- resliced_series.set_array(resliced,
1291
- source = headers[0,t,k],
1292
- pixels_first = True,
1293
- affine_matrix = affine_target,
1294
- ImagePositionPatient = pos,
1295
- SliceLocation = loc,
1296
- )
1297
- series.status.message('Finished mapping..')
1298
- return resliced_series
1299
-
1300
-
1301
- def reslice(series, orientation='axial'):
1302
-
1303
- # Define geometry of axial series (isotropic)
1304
- series.status.message('Reading transformations..')
1305
- affine_source = series.affine_matrix()
1306
- if isinstance(affine_source, list):
1307
- mapped_series = []
1308
- for affine_slice_group in affine_source:
1309
- mapped = _reslice_slice_group(series, affine_slice_group[0], affine_slice_group[0], orientation=orientation)
1310
- mapped_series.append(mapped)
1311
- #slice_group.remove()
1312
- desc = series.instance().SeriesDescription + '['+orientation+']'
1313
- mapped_series = dbdicom.merge(mapped_series, inplace=True)
1314
- mapped_series.SeriesDescription = desc
1315
- else:
1316
- mapped_series = _reslice_slice_group(series, affine_source[0], affine_source[1], orientation=orientation)
1317
- return mapped_series
1318
-
1319
-
1320
- def _reslice_slice_group(series, affine_source, slice_group, orientation='axial'):
1321
-
1322
- # Create new resliced series
1323
- desc = series.instance().SeriesDescription + '['+orientation+']'
1324
- resliced_series = series.new_sibling(SeriesDescription = desc)
1325
-
1326
- # Work out the affine matrix of the new series
1327
- p = dbdicom.utils.image.dismantle_affine_matrix(affine_source)
1328
- image_positions = [s.ImagePositionPatient for s in slice_group]
1329
- rows = slice_group[0].Rows
1330
- columns = slice_group[0].Columns
1331
- box = dbdicom.utils.image.bounding_box(
1332
- p['ImageOrientationPatient'],
1333
- image_positions,
1334
- p['PixelSpacing'],
1335
- rows,
1336
- columns)
1337
- spacing = np.mean([p['PixelSpacing'][0], p['PixelSpacing'][1], p['SpacingBetweenSlices']])
1338
- affine_target = dbdicom.utils.image.standard_affine_matrix(
1339
- box,
1340
- [spacing, spacing],
1341
- spacing,
1342
- orientation=orientation)
1343
-
1344
- # If the series already is in the right orientation, return a copy
1345
- if np.array_equal(affine_source, affine_target):
1346
- series.status.message('Series is already in the right orientation..')
1347
- resliced_series.adopt(slice_group)
1348
- return resliced_series
1349
-
1350
- #Perform transformation on the arrays to determine the output shape
1351
- if orientation == 'axial':
1352
- dim = [
1353
- np.linalg.norm(np.array(box['RAF'])-np.array(box['LAF'])),
1354
- np.linalg.norm(np.array(box['RAF'])-np.array(box['RPF'])),
1355
- np.linalg.norm(np.array(box['RAF'])-np.array(box['RAH'])),
1356
- ]
1357
- elif orientation == 'coronal':
1358
- dim = [
1359
- np.linalg.norm(np.array(box['RAH'])-np.array(box['LAH'])),
1360
- np.linalg.norm(np.array(box['RAH'])-np.array(box['RAF'])),
1361
- np.linalg.norm(np.array(box['RAH'])-np.array(box['RPH'])),
1362
- ]
1363
- elif orientation == 'sagittal':
1364
- dim = [
1365
- np.linalg.norm(np.array(box['LAH'])-np.array(box['LPH'])),
1366
- np.linalg.norm(np.array(box['LAH'])-np.array(box['LAF'])),
1367
- np.linalg.norm(np.array(box['LAH'])-np.array(box['RAH'])),
1368
- ]
1369
- output_shape = [1 + round(d/spacing) for d in dim]
1370
-
1371
- # Determine the transformation matrix and offset
1372
- source_to_target = np.linalg.inv(affine_source).dot(affine_target)
1373
- #matrix, offset = nib.affines.to_matvec(source_to_target)
1374
- matrix, offset = source_to_target[:3,:3], source_to_target[:3,3]
1375
-
1376
- # Get arrays
1377
- array, headers = series.array(['SliceLocation','AcquisitionTime'], pixels_first=True)
1378
-
1379
- # Perform the affine transformation and save results
1380
- cnt=0
1381
- ns, nt, nk = output_shape[2], array.shape[-2], array.shape[-1]
1382
- pos, loc = dbdicom.utils.image.image_position_patient(affine_target, ns)
1383
- for t in range(nt):
1384
- for k in range(nk):
1385
- cnt+=1
1386
- series.status.progress(cnt, nt*nk, 'Calculating..')
1387
- resliced = affine_transform(
1388
- array[:,:,:,t,k],
1389
- matrix = matrix,
1390
- offset = offset,
1391
- output_shape = output_shape,
1392
- )
1393
- # Saving results at each time to avoid memory problems.
1394
- # Assign acquisition time of slice=0 to all slices
1395
- resliced_series.set_array(resliced,
1396
- source = headers[0,t,k],
1397
- pixels_first = True,
1398
- affine_matrix = affine_target,
1399
- ImagePositionPatient = pos,
1400
- SliceLocation = loc,
1401
- )
1402
- series.status.message('Finished mapping..')
1403
- return resliced_series
1404
-
1405
-
1406
-
1407
- # Helper functions
1408
-
1409
- def _reset_window(image, array):
1410
- min = np.amin(array)
1411
- max = np.amax(array)
1412
- image.WindowCenter= (max+min)/2
1413
- image.WindowWidth = 0.9*(max-min)