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

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