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.
- dbdicom/__init__.py +1 -28
- dbdicom/api.py +287 -0
- dbdicom/const.py +144 -0
- dbdicom/dataset.py +721 -0
- dbdicom/dbd.py +736 -0
- dbdicom/external/__pycache__/__init__.cpython-311.pyc +0 -0
- dbdicom/external/dcm4che/__pycache__/__init__.cpython-311.pyc +0 -0
- dbdicom/external/dcm4che/bin/__pycache__/__init__.cpython-311.pyc +0 -0
- dbdicom/register.py +527 -0
- dbdicom/{ds/types → sop_classes}/ct_image.py +2 -16
- dbdicom/{ds/types → sop_classes}/enhanced_mr_image.py +153 -26
- dbdicom/{ds/types → sop_classes}/mr_image.py +185 -140
- dbdicom/sop_classes/parametric_map.py +310 -0
- dbdicom/sop_classes/secondary_capture.py +140 -0
- dbdicom/sop_classes/segmentation.py +311 -0
- dbdicom/{ds/types → sop_classes}/ultrasound_multiframe_image.py +1 -15
- dbdicom/{ds/types → sop_classes}/xray_angiographic_image.py +2 -17
- dbdicom/utils/arrays.py +36 -0
- dbdicom/utils/files.py +0 -20
- dbdicom/utils/image.py +10 -629
- dbdicom-0.3.1.dist-info/METADATA +28 -0
- dbdicom-0.3.1.dist-info/RECORD +53 -0
- dbdicom/create.py +0 -457
- dbdicom/dro.py +0 -174
- dbdicom/ds/__init__.py +0 -10
- dbdicom/ds/create.py +0 -63
- dbdicom/ds/dataset.py +0 -869
- dbdicom/ds/dictionaries.py +0 -620
- dbdicom/ds/types/parametric_map.py +0 -226
- dbdicom/extensions/__init__.py +0 -9
- dbdicom/extensions/dipy.py +0 -448
- dbdicom/extensions/elastix.py +0 -503
- dbdicom/extensions/matplotlib.py +0 -107
- dbdicom/extensions/numpy.py +0 -271
- dbdicom/extensions/scipy.py +0 -1512
- dbdicom/extensions/skimage.py +0 -1030
- dbdicom/extensions/sklearn.py +0 -243
- dbdicom/extensions/vreg.py +0 -1390
- dbdicom/manager.py +0 -2132
- dbdicom/message.py +0 -119
- dbdicom/pipelines.py +0 -66
- dbdicom/record.py +0 -1893
- dbdicom/types/database.py +0 -107
- dbdicom/types/instance.py +0 -231
- dbdicom/types/patient.py +0 -40
- dbdicom/types/series.py +0 -2874
- dbdicom/types/study.py +0 -58
- dbdicom-0.2.6.dist-info/METADATA +0 -72
- dbdicom-0.2.6.dist-info/RECORD +0 -66
- {dbdicom-0.2.6.dist-info → dbdicom-0.3.1.dist-info}/WHEEL +0 -0
- {dbdicom-0.2.6.dist-info → dbdicom-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {dbdicom-0.2.6.dist-info → dbdicom-0.3.1.dist-info}/top_level.txt +0 -0
dbdicom/extensions/scipy.py
DELETED
|
@@ -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)
|