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/skimage.py
DELETED
|
@@ -1,1030 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import pandas as pd
|
|
3
|
-
import scipy.ndimage as ndi
|
|
4
|
-
import skimage
|
|
5
|
-
|
|
6
|
-
from dbdicom.extensions import scipy
|
|
7
|
-
from dbdicom.utils.image import interpolate3d_isotropic
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def volume_features(series):
|
|
11
|
-
|
|
12
|
-
if isinstance(series, list):
|
|
13
|
-
df = None
|
|
14
|
-
for sery in series:
|
|
15
|
-
df_sery = volume_features(sery)
|
|
16
|
-
if df is None:
|
|
17
|
-
df = df_sery
|
|
18
|
-
else:
|
|
19
|
-
df = pd.concat([df, df_sery], ignore_index=True)
|
|
20
|
-
return df
|
|
21
|
-
|
|
22
|
-
n_steps = 8
|
|
23
|
-
step = 0
|
|
24
|
-
|
|
25
|
-
step+=1
|
|
26
|
-
series.status.progress(step, n_steps, 'Reading affine matrix..')
|
|
27
|
-
|
|
28
|
-
affine = series.affine_matrix()
|
|
29
|
-
if isinstance(affine, list):
|
|
30
|
-
series.dialog.information('This series contains multiple orientations')
|
|
31
|
-
return
|
|
32
|
-
else:
|
|
33
|
-
affine = affine[0]
|
|
34
|
-
|
|
35
|
-
step+=1
|
|
36
|
-
series.status.progress(step, n_steps, 'Reading array..')
|
|
37
|
-
|
|
38
|
-
# Get array sorted by slice location
|
|
39
|
-
arr, _ = series.array('SliceLocation', pixels_first=True)
|
|
40
|
-
|
|
41
|
-
# If there are multiple volumes, show only the first one
|
|
42
|
-
arr = arr[...,0]
|
|
43
|
-
|
|
44
|
-
series_props = _volume_features(arr, affine=affine, show_progress=series.status.progress)
|
|
45
|
-
|
|
46
|
-
instance = series.instance()
|
|
47
|
-
columns = ['PatientID', 'StudyDescription', 'SeriesDescription', 'Parameter', 'Value', 'Unit']
|
|
48
|
-
ids = [instance.PatientID, instance.StudyDescription, instance.SeriesDescription]
|
|
49
|
-
data = []
|
|
50
|
-
for par, val in series_props.items():
|
|
51
|
-
row = ids + [par, val[0], val[1]]
|
|
52
|
-
data.append(row)
|
|
53
|
-
return pd.DataFrame(data, columns = columns)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _volume_features(arr, spacing=[1,1,1], affine=None, show_progress=print):
|
|
57
|
-
"""Calculate shape features from a mask array
|
|
58
|
-
|
|
59
|
-
This function calculates various shape features from a given mask array.
|
|
60
|
-
|
|
61
|
-
Arguments:
|
|
62
|
-
- arr (numpy.ndarray): The input mask array - 3D but does not have to be binary
|
|
63
|
-
- spacing (list, optional): The voxel dimensions in mm. Default is [1, 1, 1].
|
|
64
|
-
- affine (numpy.ndarray, optional): The affine transformation matrix. Default is None. If the affine is
|
|
65
|
-
provided then the spacing argument is ignored and spacing is derived from the affine.
|
|
66
|
-
- show_progress (function, optional): A function to display progress messages. Default is print.
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
- dict: A dictionary containing the calculated shape features with their corresponding units.
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
show_progress(1, 6, 'Preprocessing mask...')
|
|
73
|
-
|
|
74
|
-
# Scale array in the range [0,1] so it can be treated as mask
|
|
75
|
-
# Motivation: the function is intended for mask arrays but this will make
|
|
76
|
-
# sure the results are meaningful even if non-binary arrays are provided.
|
|
77
|
-
max = np.amax(arr)
|
|
78
|
-
min = np.amin(arr)
|
|
79
|
-
arr -= min
|
|
80
|
-
arr /= max-min
|
|
81
|
-
# Add zeropadding at the boundary slices for masks that extend to the edge
|
|
82
|
-
# Motivation: this could have some effect if surfaces are extracted - could create issues
|
|
83
|
-
# if the values extend right up to the boundary of the slab.
|
|
84
|
-
shape = list(arr.shape)
|
|
85
|
-
shape[-1] = shape[-1] + 2*4
|
|
86
|
-
array = np.zeros(shape)
|
|
87
|
-
array[:,:,4:-4] = arr
|
|
88
|
-
|
|
89
|
-
show_progress(2, 6, 'Extracting surface...')
|
|
90
|
-
|
|
91
|
-
# Get voxel dimensions from the affine
|
|
92
|
-
# We are assuming here the voxel dimensions are in mm.
|
|
93
|
-
# If not the units provided with the return values are incorrect.
|
|
94
|
-
if affine is not None:
|
|
95
|
-
column_spacing = np.linalg.norm(affine[:3, 0])
|
|
96
|
-
row_spacing = np.linalg.norm(affine[:3, 1])
|
|
97
|
-
slice_spacing = np.linalg.norm(affine[:3, 2])
|
|
98
|
-
spacing = (column_spacing, row_spacing, slice_spacing)
|
|
99
|
-
voxel_volume = spacing[0]*spacing[1]*spacing[2]
|
|
100
|
-
nr_of_voxels = np.count_nonzero(array > 0.5)
|
|
101
|
-
volume = nr_of_voxels * voxel_volume
|
|
102
|
-
# Surface properties - for now only extracting surface area
|
|
103
|
-
# Note: this is smoothing the surface first - not tested in depth whether this is necessary or helpful.
|
|
104
|
-
# It does appear to make a big difference on surface area so should be looked at more carefully.
|
|
105
|
-
smooth_array = ndi.gaussian_filter(array, 1.0)
|
|
106
|
-
verts, faces, _, _ = skimage.measure.marching_cubes(smooth_array, spacing=spacing, level=0.5, step_size=1.0)
|
|
107
|
-
surface_area = skimage.measure.mesh_surface_area(verts, faces)
|
|
108
|
-
|
|
109
|
-
show_progress(3, 6, 'Interpolating to isotropic...')
|
|
110
|
-
|
|
111
|
-
# Interpolate to isotropic for non-isotropic voxels
|
|
112
|
-
# Motivation: this is required by the region_props function
|
|
113
|
-
spacing = np.array(spacing)
|
|
114
|
-
if np.amin(spacing) != np.amax(spacing):
|
|
115
|
-
array, isotropic_spacing = interpolate3d_isotropic(array, spacing)
|
|
116
|
-
isotropic_voxel_volume = isotropic_spacing**3
|
|
117
|
-
else:
|
|
118
|
-
isotropic_spacing = np.mean(spacing)
|
|
119
|
-
isotropic_voxel_volume = voxel_volume
|
|
120
|
-
|
|
121
|
-
show_progress(4, 6, 'Extracting volume properties...')
|
|
122
|
-
|
|
123
|
-
# Get volume properties - mostly from region_props, except for compactness and depth
|
|
124
|
-
array = np.round(array).astype(np.int16)
|
|
125
|
-
region_props_3D = skimage.measure.regionprops(array)[0]
|
|
126
|
-
# Calculate 'compactness' (our definition) - define as volume to surface ratio
|
|
127
|
-
# expressed as a percentage of the volume-to-surface ration of an equivalent sphere.
|
|
128
|
-
# The sphere is the most compact of all shapes, i.e. it has the largest volume to surface area ratio,
|
|
129
|
-
# so this is guaranteed to be between 0 and 100%
|
|
130
|
-
radius = region_props_3D['equivalent_diameter_area']*isotropic_spacing/2 # mm
|
|
131
|
-
v2s = volume/surface_area # mm
|
|
132
|
-
v2s_equivalent_sphere = radius/3 # mm
|
|
133
|
-
compactness = 100 * v2s/v2s_equivalent_sphere # %
|
|
134
|
-
# Fractional anisotropy - in analogy with FA in diffusion
|
|
135
|
-
m0 = region_props_3D['inertia_tensor_eigvals'][0]
|
|
136
|
-
m1 = region_props_3D['inertia_tensor_eigvals'][1]
|
|
137
|
-
m2 = region_props_3D['inertia_tensor_eigvals'][2]
|
|
138
|
-
m = (m0 + m1 + m2)/3 # average moment of inertia (trace of the inertia tensor)
|
|
139
|
-
FA = np.sqrt(3/2) * np.sqrt((m0-m)**2 + (m1-m)**2 + (m2-m)**2) / np.sqrt(m0**2 + m1**2 + m2**2)
|
|
140
|
-
|
|
141
|
-
show_progress(5, 6, 'Calculating depth...')
|
|
142
|
-
|
|
143
|
-
# Measure maximum depth (our definition)
|
|
144
|
-
distance = ndi.distance_transform_edt(array)
|
|
145
|
-
max_depth = np.amax(distance)
|
|
146
|
-
|
|
147
|
-
show_progress(6, 6, 'Creating output...')
|
|
148
|
-
|
|
149
|
-
# Summarise all values with human-readable names and proper units in a dictionary with values and units.
|
|
150
|
-
# Some of the definitions are rephrased or tweaked for more intuitive interpretation.
|
|
151
|
-
# The volume can be calculated independently from regionprops - included as sanity check.
|
|
152
|
-
series_props = {
|
|
153
|
-
'Surface area': (surface_area/100, 'cm^2'),
|
|
154
|
-
'Volume': (volume/1000, 'mL'),
|
|
155
|
-
'Bounding box volume': (region_props_3D['area_bbox']*isotropic_voxel_volume/1000, 'mL'),
|
|
156
|
-
'Convex hull volume': (region_props_3D['area_convex']*isotropic_voxel_volume/1000, 'mL'),
|
|
157
|
-
'Volume of holes': ((region_props_3D['area_filled']-region_props_3D['area'])*isotropic_voxel_volume/1000, 'mL'),
|
|
158
|
-
'Extent': (region_props_3D['extent']*100, '%'), # Percentage of bounding box filled
|
|
159
|
-
'Solidity': (region_props_3D['solidity']*100, '%'), # Percentage of convex hull filled
|
|
160
|
-
'Compactness': (compactness, '%'),
|
|
161
|
-
'Long axis length': (region_props_3D['axis_major_length']*isotropic_spacing/10, 'cm'),
|
|
162
|
-
'Short axis length': (region_props_3D['axis_minor_length']*isotropic_spacing/10, 'cm'),
|
|
163
|
-
'Equivalent diameter': (region_props_3D['equivalent_diameter_area']*isotropic_spacing/10, 'cm'),
|
|
164
|
-
'Longest caliper diameter': (region_props_3D['feret_diameter_max']*isotropic_spacing/10, 'cm'),
|
|
165
|
-
'Maximum depth': (max_depth*isotropic_spacing/10, 'cm'),
|
|
166
|
-
'Primary moment of inertia': (region_props_3D['inertia_tensor_eigvals'][0]*isotropic_spacing**2/100, 'cm^2'),
|
|
167
|
-
'Second moment of inertia': (region_props_3D['inertia_tensor_eigvals'][1]*isotropic_spacing**2/100, 'cm^2'),
|
|
168
|
-
'Third moment of inertia': (region_props_3D['inertia_tensor_eigvals'][2]*isotropic_spacing**2/100, 'cm^2'),
|
|
169
|
-
'Mean moment of inertia': (m*isotropic_spacing**2/100, 'cm^2'),
|
|
170
|
-
'Fractional anisotropy of inertia': (100*FA, '%'),
|
|
171
|
-
'QC - Volume check': (region_props_3D['area']*isotropic_voxel_volume/1000, 'mL'),
|
|
172
|
-
# From eigenvectors of inertia tensor:
|
|
173
|
-
# Include orientation info with respect to LPH coordinate system (tilt, roll)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return series_props
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def area_opening_2d(input, **kwargs):
|
|
181
|
-
"""
|
|
182
|
-
Return grayscale area opening of an image.
|
|
183
|
-
|
|
184
|
-
Wrapper for skimage.morphology.area_opening.
|
|
185
|
-
|
|
186
|
-
Parameters
|
|
187
|
-
----------
|
|
188
|
-
input: dbdicom series
|
|
189
|
-
|
|
190
|
-
Returns
|
|
191
|
-
-------
|
|
192
|
-
output : dbdicom series
|
|
193
|
-
"""
|
|
194
|
-
desc = input.instance().SeriesDescription + ' [area opening 2D]'
|
|
195
|
-
result = input.copy(SeriesDescription = desc)
|
|
196
|
-
images = result.images()
|
|
197
|
-
for i, image in enumerate(images):
|
|
198
|
-
input.status.progress(i+1, len(images), 'Calculating ' + desc)
|
|
199
|
-
image.read()
|
|
200
|
-
array = image.array()
|
|
201
|
-
array = skimage.morphology.area_opening(array, **kwargs)
|
|
202
|
-
image.set_array(array)
|
|
203
|
-
_reset_window(image, array)
|
|
204
|
-
image.clear()
|
|
205
|
-
input.status.hide()
|
|
206
|
-
return result
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def area_opening_3d(input, **kwargs):
|
|
210
|
-
"""
|
|
211
|
-
Return grayscale area opening of an image.
|
|
212
|
-
|
|
213
|
-
Wrapper for skimage.morphology.area_opening.
|
|
214
|
-
|
|
215
|
-
Parameters
|
|
216
|
-
----------
|
|
217
|
-
input: dbdicom series
|
|
218
|
-
|
|
219
|
-
Returns
|
|
220
|
-
-------
|
|
221
|
-
output : dbdicom series
|
|
222
|
-
"""
|
|
223
|
-
array, headers = input.array('SliceLocation', pixels_first=True)
|
|
224
|
-
if array is None:
|
|
225
|
-
return input
|
|
226
|
-
desc = input.instance().SeriesDescription + ' [area opening 3D]'
|
|
227
|
-
result = input.new_sibling(SeriesDescription = desc)
|
|
228
|
-
for t in range(array.shape[3]):
|
|
229
|
-
input.status.progress(t, array.shape[3], 'Calculating ' + desc)
|
|
230
|
-
array[...,t] = skimage.morphology.area_opening(array[...,t], **kwargs)
|
|
231
|
-
result.set_array(array[...,t], headers[:,t], pixels_first=True)
|
|
232
|
-
_reset_window(result, array)
|
|
233
|
-
input.status.hide()
|
|
234
|
-
return result
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def area_closing_2d(input, **kwargs):
|
|
238
|
-
"""
|
|
239
|
-
Return grayscale area closing of an image.
|
|
240
|
-
|
|
241
|
-
Wrapper for skimage.morphology.area_closing.
|
|
242
|
-
|
|
243
|
-
Parameters
|
|
244
|
-
----------
|
|
245
|
-
input: dbdicom series
|
|
246
|
-
|
|
247
|
-
Returns
|
|
248
|
-
-------
|
|
249
|
-
output : dbdicom series
|
|
250
|
-
"""
|
|
251
|
-
desc = input.instance().SeriesDescription + ' [area closing 2D]'
|
|
252
|
-
result = input.copy(SeriesDescription = desc)
|
|
253
|
-
images = result.images()
|
|
254
|
-
for i, image in enumerate(images):
|
|
255
|
-
input.status.progress(i+1, len(images), 'Calculating ' + desc)
|
|
256
|
-
image.read()
|
|
257
|
-
array = image.array()
|
|
258
|
-
array = skimage.morphology.area_closing(array, **kwargs)
|
|
259
|
-
image.set_array(array)
|
|
260
|
-
_reset_window(image, array)
|
|
261
|
-
image.clear()
|
|
262
|
-
input.status.hide()
|
|
263
|
-
return result
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def area_closing_3d(input, **kwargs):
|
|
267
|
-
"""
|
|
268
|
-
Return grayscale area closing of an image.
|
|
269
|
-
|
|
270
|
-
Wrapper for skimage.morphology.area_closing.
|
|
271
|
-
|
|
272
|
-
Parameters
|
|
273
|
-
----------
|
|
274
|
-
input: dbdicom series
|
|
275
|
-
|
|
276
|
-
Returns
|
|
277
|
-
-------
|
|
278
|
-
output : dbdicom series
|
|
279
|
-
"""
|
|
280
|
-
array, headers = input.array('SliceLocation', pixels_first=True)
|
|
281
|
-
if array is None:
|
|
282
|
-
return input
|
|
283
|
-
desc = input.instance().SeriesDescription + ' [area closing 3D]'
|
|
284
|
-
result = input.new_sibling(SeriesDescription = desc)
|
|
285
|
-
for t in range(array.shape[3]):
|
|
286
|
-
input.status.progress(t, array.shape[3], 'Calculating ' + desc)
|
|
287
|
-
array[...,t] = skimage.morphology.area_closing(array[...,t], **kwargs)
|
|
288
|
-
result.set_array(array[...,t], headers[:,t], pixels_first=True)
|
|
289
|
-
_reset_window(result, array)
|
|
290
|
-
input.status.hide()
|
|
291
|
-
return result
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
def opening_2d(input, **kwargs):
|
|
295
|
-
"""
|
|
296
|
-
Return grayscale morphological opening of an image.
|
|
297
|
-
|
|
298
|
-
Wrapper for skimage.morphology.opening.
|
|
299
|
-
|
|
300
|
-
Parameters
|
|
301
|
-
----------
|
|
302
|
-
input: dbdicom series
|
|
303
|
-
|
|
304
|
-
Returns
|
|
305
|
-
-------
|
|
306
|
-
output : dbdicom series
|
|
307
|
-
"""
|
|
308
|
-
desc = input.instance().SeriesDescription + ' [opening 2D]'
|
|
309
|
-
result = input.copy(SeriesDescription = desc)
|
|
310
|
-
images = result.images()
|
|
311
|
-
for i, image in enumerate(images):
|
|
312
|
-
input.status.progress(i+1, len(images), 'Calculating ' + desc)
|
|
313
|
-
image.read()
|
|
314
|
-
array = image.array()
|
|
315
|
-
array = skimage.morphology.opening(array, **kwargs)
|
|
316
|
-
image.set_array(array)
|
|
317
|
-
_reset_window(image, array)
|
|
318
|
-
image.clear()
|
|
319
|
-
input.status.hide()
|
|
320
|
-
return result
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
def opening_3d(input, **kwargs):
|
|
324
|
-
"""
|
|
325
|
-
Return grayscale morphological opening of an image.
|
|
326
|
-
|
|
327
|
-
Wrapper for skimage.morphology.opening.
|
|
328
|
-
|
|
329
|
-
Parameters
|
|
330
|
-
----------
|
|
331
|
-
input: dbdicom series
|
|
332
|
-
|
|
333
|
-
Returns
|
|
334
|
-
-------
|
|
335
|
-
output : dbdicom series
|
|
336
|
-
"""
|
|
337
|
-
array, headers = input.array('SliceLocation', pixels_first=True)
|
|
338
|
-
if array is None:
|
|
339
|
-
return input
|
|
340
|
-
desc = input.instance().SeriesDescription + ' [opening 3D]'
|
|
341
|
-
result = input.new_sibling(SeriesDescription = desc)
|
|
342
|
-
for t in range(array.shape[3]):
|
|
343
|
-
input.status.progress(t, array.shape[3], 'Calculating ' + desc)
|
|
344
|
-
array[...,t] = skimage.morphology.opening(array[...,t], **kwargs)
|
|
345
|
-
result.set_array(array[...,t], headers[:,t], pixels_first=True)
|
|
346
|
-
_reset_window(result, array)
|
|
347
|
-
input.status.hide()
|
|
348
|
-
return result
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
def closing_2d(input, **kwargs):
|
|
352
|
-
"""
|
|
353
|
-
Return grayscale morphological closing of an image.
|
|
354
|
-
|
|
355
|
-
Wrapper for skimage.morphology.closing.
|
|
356
|
-
|
|
357
|
-
Parameters
|
|
358
|
-
----------
|
|
359
|
-
input: dbdicom series
|
|
360
|
-
|
|
361
|
-
Returns
|
|
362
|
-
-------
|
|
363
|
-
output : dbdicom series
|
|
364
|
-
"""
|
|
365
|
-
desc = input.instance().SeriesDescription + ' [closing 2D]'
|
|
366
|
-
result = input.copy(SeriesDescription = desc)
|
|
367
|
-
images = result.images()
|
|
368
|
-
for i, image in enumerate(images):
|
|
369
|
-
input.status.progress(i+1, len(images), 'Calculating ' + desc)
|
|
370
|
-
image.read()
|
|
371
|
-
array = image.array()
|
|
372
|
-
array = skimage.morphology.closing(array, **kwargs)
|
|
373
|
-
image.set_array(array)
|
|
374
|
-
_reset_window(image, array)
|
|
375
|
-
image.clear()
|
|
376
|
-
input.status.hide()
|
|
377
|
-
return result
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
def closing_3d(input, **kwargs):
|
|
381
|
-
"""
|
|
382
|
-
Return grayscale morphological closing of an image.
|
|
383
|
-
|
|
384
|
-
Wrapper for skimage.morphology.closing.
|
|
385
|
-
|
|
386
|
-
Parameters
|
|
387
|
-
----------
|
|
388
|
-
input: dbdicom series
|
|
389
|
-
|
|
390
|
-
Returns
|
|
391
|
-
-------
|
|
392
|
-
output : dbdicom series
|
|
393
|
-
"""
|
|
394
|
-
array, headers = input.array('SliceLocation', pixels_first=True)
|
|
395
|
-
if array is None:
|
|
396
|
-
return input
|
|
397
|
-
desc = input.instance().SeriesDescription + ' [closing 3D]'
|
|
398
|
-
result = input.new_sibling(SeriesDescription = desc)
|
|
399
|
-
for t in range(array.shape[3]):
|
|
400
|
-
input.status.progress(t, array.shape[3], 'Calculating ' + desc)
|
|
401
|
-
array[...,t] = skimage.morphology.closing(array[...,t], **kwargs)
|
|
402
|
-
result.set_array(array[...,t], headers[:,t], pixels_first=True)
|
|
403
|
-
_reset_window(result, array)
|
|
404
|
-
input.status.hide()
|
|
405
|
-
return result
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
def remove_small_holes_2d(input, **kwargs):
|
|
409
|
-
"""
|
|
410
|
-
Remove contiguous holes smaller than the specified size.
|
|
411
|
-
|
|
412
|
-
Wrapper for skimage.morphology.remove_small_holes.
|
|
413
|
-
|
|
414
|
-
Parameters
|
|
415
|
-
----------
|
|
416
|
-
input: dbdicom series
|
|
417
|
-
|
|
418
|
-
Returns
|
|
419
|
-
-------
|
|
420
|
-
output : dbdicom series
|
|
421
|
-
"""
|
|
422
|
-
desc = input.instance().SeriesDescription + ' [remove small holes 2D]'
|
|
423
|
-
result = input.copy(SeriesDescription = desc)
|
|
424
|
-
images = result.images()
|
|
425
|
-
for i, image in enumerate(images):
|
|
426
|
-
input.status.progress(i+1, len(images), 'Calculating ' + desc)
|
|
427
|
-
image.read()
|
|
428
|
-
array = image.array().astype(np.int16)
|
|
429
|
-
array = skimage.morphology.remove_small_holes(array, **kwargs)
|
|
430
|
-
image.set_array(array)
|
|
431
|
-
_reset_window(image, array)
|
|
432
|
-
image.clear()
|
|
433
|
-
input.status.hide()
|
|
434
|
-
return result
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
def remove_small_holes_3d(input, **kwargs):
|
|
438
|
-
"""
|
|
439
|
-
Remove contiguous holes smaller than the specified size.
|
|
440
|
-
|
|
441
|
-
Wrapper for skimage.morphology.remove_small_holes.
|
|
442
|
-
|
|
443
|
-
Parameters
|
|
444
|
-
----------
|
|
445
|
-
input: dbdicom series
|
|
446
|
-
|
|
447
|
-
Returns
|
|
448
|
-
-------
|
|
449
|
-
output : dbdicom series
|
|
450
|
-
"""
|
|
451
|
-
array, headers = input.array('SliceLocation', pixels_first=True)
|
|
452
|
-
if array is None:
|
|
453
|
-
return input
|
|
454
|
-
else:
|
|
455
|
-
array = array.astype(np.int16)
|
|
456
|
-
desc = input.instance().SeriesDescription + ' [remove holes 3D]'
|
|
457
|
-
result = input.new_sibling(SeriesDescription = desc)
|
|
458
|
-
for t in range(array.shape[3]):
|
|
459
|
-
input.status.progress(t, array.shape[3], 'Calculating ' + desc)
|
|
460
|
-
array[...,t] = skimage.morphology.remove_small_holes(array[...,t], **kwargs)
|
|
461
|
-
result.set_array(array[...,t], headers[:,t], pixels_first=True)
|
|
462
|
-
_reset_window(result, array)
|
|
463
|
-
input.status.hide()
|
|
464
|
-
return result
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
def watershed_2d(input, markers=None, mask=None, **kwargs):
|
|
468
|
-
"""
|
|
469
|
-
Labels structures in an image
|
|
470
|
-
|
|
471
|
-
Wrapper for skimage.segmentation.watershed function.
|
|
472
|
-
|
|
473
|
-
Parameters
|
|
474
|
-
----------
|
|
475
|
-
input: dbdicom series
|
|
476
|
-
markers: dbdicom series of the same dimensions as series
|
|
477
|
-
|
|
478
|
-
Returns
|
|
479
|
-
-------
|
|
480
|
-
filtered : dbdicom series
|
|
481
|
-
"""
|
|
482
|
-
desc = input.instance().SeriesDescription + ' [watershed 2D]'
|
|
483
|
-
result = input.copy(SeriesDescription = desc)
|
|
484
|
-
sortby = ['SliceLocation', 'AcquisitionTime']
|
|
485
|
-
images = result.images(sortby=sortby)
|
|
486
|
-
if markers is not None:
|
|
487
|
-
markers = scipy.map_to(markers, input, label=True)
|
|
488
|
-
markers = markers.images(sortby=sortby)
|
|
489
|
-
if mask is not None:
|
|
490
|
-
mask = scipy.map_to(mask, input, mask=True)
|
|
491
|
-
mask = mask.images(sortby=sortby)
|
|
492
|
-
for i, image in enumerate(images):
|
|
493
|
-
input.status.progress(i+1, len(images), 'Calculating ' + desc)
|
|
494
|
-
image.read()
|
|
495
|
-
array = image.array()
|
|
496
|
-
if markers is None:
|
|
497
|
-
mrk = None
|
|
498
|
-
else:
|
|
499
|
-
mrk = markers[i].array().astype(np.uint)
|
|
500
|
-
if mask is None:
|
|
501
|
-
msk = None
|
|
502
|
-
else:
|
|
503
|
-
msk = mask[i].array().astype(np.bool8)
|
|
504
|
-
array = skimage.segmentation.watershed(array, markers=mrk, mask=msk, **kwargs)
|
|
505
|
-
array.astype(np.float32) # unnecessary - test
|
|
506
|
-
image.set_array(array)
|
|
507
|
-
_reset_window(image, array)
|
|
508
|
-
image.clear()
|
|
509
|
-
input.status.hide()
|
|
510
|
-
return result
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
def watershed_3d(input, markers=None, mask=None, **kwargs):
|
|
514
|
-
"""
|
|
515
|
-
Determine watershed in 3D
|
|
516
|
-
|
|
517
|
-
Wrapper for skimage.segmentation.watershed function.
|
|
518
|
-
|
|
519
|
-
Parameters
|
|
520
|
-
----------
|
|
521
|
-
input: dbdicom series
|
|
522
|
-
|
|
523
|
-
Returns
|
|
524
|
-
-------
|
|
525
|
-
filtered : dbdicom series
|
|
526
|
-
"""
|
|
527
|
-
array, headers = input.array('SliceLocation', pixels_first=True)
|
|
528
|
-
if array is None:
|
|
529
|
-
return input
|
|
530
|
-
if markers is not None:
|
|
531
|
-
markers = scipy.map_to(markers, input)
|
|
532
|
-
markers, _ = markers.array('SliceLocation', pixels_first=True)
|
|
533
|
-
markers = markers.astype(np.uint)
|
|
534
|
-
if mask is not None:
|
|
535
|
-
mask = scipy.map_to(mask, input)
|
|
536
|
-
mask, _ = mask.array('SliceLocation', pixels_first=True)
|
|
537
|
-
mask = mask.astype(np.bool8)
|
|
538
|
-
desc = input.instance().SeriesDescription + ' [watershed 3D]'
|
|
539
|
-
result = input.new_sibling(SeriesDescription = desc)
|
|
540
|
-
for t in range(array.shape[3]):
|
|
541
|
-
input.status.progress(t, array.shape[3], 'Calculating ' + desc)
|
|
542
|
-
array[...,t] = skimage.segmentation.watershed(
|
|
543
|
-
array[...,t],
|
|
544
|
-
markers = None if markers is None else markers[...,t],
|
|
545
|
-
mask = None if mask is None else mask[...,t],
|
|
546
|
-
**kwargs)
|
|
547
|
-
result.set_array(array[...,t], headers[:,t], pixels_first=True)
|
|
548
|
-
_reset_window(result, array)
|
|
549
|
-
input.status.hide()
|
|
550
|
-
return result
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
def skeletonize(input, **kwargs):
|
|
554
|
-
"""
|
|
555
|
-
Labels structures in an image
|
|
556
|
-
|
|
557
|
-
Wrapper for skimage.segmentation.watershed function.
|
|
558
|
-
|
|
559
|
-
Parameters
|
|
560
|
-
----------
|
|
561
|
-
input: dbdicom series
|
|
562
|
-
markers: dbdicom series of the same dimensions as series
|
|
563
|
-
|
|
564
|
-
Returns
|
|
565
|
-
-------
|
|
566
|
-
filtered : dbdicom series
|
|
567
|
-
"""
|
|
568
|
-
desc = input.instance().SeriesDescription + ' [2d skeleton]'
|
|
569
|
-
filtered = input.copy(SeriesDescription = desc)
|
|
570
|
-
#images = filtered.instances() #sort=False should be faster - check
|
|
571
|
-
images = filtered.images()
|
|
572
|
-
for i, image in enumerate(images):
|
|
573
|
-
input.status.progress(i+1, len(images), 'Calculating ' + desc)
|
|
574
|
-
image.read()
|
|
575
|
-
array = image.array()
|
|
576
|
-
array = skimage.morphology.skeletonize(array, **kwargs)
|
|
577
|
-
#array.astype(np.float32)
|
|
578
|
-
image.set_array(array)
|
|
579
|
-
_reset_window(image, array)
|
|
580
|
-
image.clear()
|
|
581
|
-
input.status.hide()
|
|
582
|
-
return filtered
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
def skeletonize_3d(input, **kwargs):
|
|
586
|
-
"""
|
|
587
|
-
Labels structures in an image
|
|
588
|
-
|
|
589
|
-
Wrapper for skimage.segmentation.watershed function.
|
|
590
|
-
|
|
591
|
-
Parameters
|
|
592
|
-
----------
|
|
593
|
-
input: dbdicom series
|
|
594
|
-
markers: dbdicom series of the same dimensions as series
|
|
595
|
-
|
|
596
|
-
Returns
|
|
597
|
-
-------
|
|
598
|
-
filtered : dbdicom series
|
|
599
|
-
"""
|
|
600
|
-
desc = input.instance().SeriesDescription + ' [skeleton 3D]'
|
|
601
|
-
filtered = input.copy(SeriesDescription = desc)
|
|
602
|
-
array, headers = filtered.array('SliceLocation', pixels_first=True)
|
|
603
|
-
if array is None:
|
|
604
|
-
return filtered
|
|
605
|
-
for t in range(array.shape[3]):
|
|
606
|
-
if array.shape[3] > 1:
|
|
607
|
-
input.status.progress(t, array.shape[3], 'Calculating ' + desc)
|
|
608
|
-
else:
|
|
609
|
-
input.status.message('Calculating ' + desc + '. Please bear with me..')
|
|
610
|
-
array[:,:,:,t] = skimage.morphology.skeletonize_3d(array[:,:,:,t], **kwargs)
|
|
611
|
-
filtered.set_array(array, headers[:,t], pixels_first=True)
|
|
612
|
-
_reset_window(filtered, array)
|
|
613
|
-
input.status.hide()
|
|
614
|
-
return filtered
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
def peak_local_max_3d(input, labels=None, **kwargs):
|
|
618
|
-
"""
|
|
619
|
-
Determine local maxima
|
|
620
|
-
|
|
621
|
-
Wrapper for skimage.feature.peak_local_max function.
|
|
622
|
-
# https://scikit-image.org/docs/stable/api/skimage.feature.html#skimage.feature.peak_local_max
|
|
623
|
-
|
|
624
|
-
Parameters
|
|
625
|
-
----------
|
|
626
|
-
input: dbdicom series
|
|
627
|
-
|
|
628
|
-
Returns
|
|
629
|
-
-------
|
|
630
|
-
filtered : dbdicom series
|
|
631
|
-
"""
|
|
632
|
-
array, headers = input.array('SliceLocation', pixels_first=True)
|
|
633
|
-
if array is None:
|
|
634
|
-
return input
|
|
635
|
-
if labels is not None:
|
|
636
|
-
labels = scipy.map_to(labels, input)
|
|
637
|
-
labels_array, _ = labels.array('SliceLocation', pixels_first=True)
|
|
638
|
-
desc = input.instance().SeriesDescription + ' [peak local max 3D]'
|
|
639
|
-
filtered = input.new_sibling(SeriesDescription = desc)
|
|
640
|
-
for t in range(array.shape[3]):
|
|
641
|
-
input.status.progress(t, array.shape[3], 'Calculating ' + desc)
|
|
642
|
-
if labels is None:
|
|
643
|
-
labels_t = None
|
|
644
|
-
else:
|
|
645
|
-
labels_t = labels_array[:,:,:,t].astype(np.int16)
|
|
646
|
-
coords = skimage.feature.peak_local_max(array[:,:,:,t], labels=labels_t, **kwargs)
|
|
647
|
-
mask = np.zeros(array.shape[:3], dtype=bool)
|
|
648
|
-
mask[tuple(coords.T)] = True
|
|
649
|
-
filtered.set_array(mask, headers[:,t], pixels_first=True)
|
|
650
|
-
_reset_window(filtered, array)
|
|
651
|
-
input.status.hide()
|
|
652
|
-
return filtered
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
def canny(input, sigma=1.0, **kwargs):
|
|
656
|
-
"""
|
|
657
|
-
wrapper for skimage.feature.canny
|
|
658
|
-
# https://scikit-image.org/docs/dev/api/skimage.feature.html#skimage.feature.canny
|
|
659
|
-
|
|
660
|
-
Parameters
|
|
661
|
-
----------
|
|
662
|
-
input: dbdicom series
|
|
663
|
-
|
|
664
|
-
Returns
|
|
665
|
-
-------
|
|
666
|
-
filtered : dbdicom series
|
|
667
|
-
"""
|
|
668
|
-
suffix = ' [Canny filter x ' + str(sigma) + ' ]'
|
|
669
|
-
desc = input.instance().SeriesDescription
|
|
670
|
-
filtered = input.copy(SeriesDescription = desc+suffix)
|
|
671
|
-
#images = filtered.instances()
|
|
672
|
-
images = filtered.images()
|
|
673
|
-
for i, image in enumerate(images):
|
|
674
|
-
input.status.progress(i+1, len(images), 'Filtering ' + desc)
|
|
675
|
-
image.read()
|
|
676
|
-
array = image.array()
|
|
677
|
-
array = skimage.feature.canny(array, sigma=sigma, **kwargs)
|
|
678
|
-
image.set_array(array)
|
|
679
|
-
array = array.astype(np.ubyte)
|
|
680
|
-
_reset_window(image, array)
|
|
681
|
-
image.clear()
|
|
682
|
-
input.status.hide()
|
|
683
|
-
return filtered
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
def convex_hull_image(series, **kwargs):
|
|
687
|
-
"""
|
|
688
|
-
wrapper for skimage.morphology.convex_hull_image
|
|
689
|
-
|
|
690
|
-
Parameters
|
|
691
|
-
----------
|
|
692
|
-
input: dbdicom series
|
|
693
|
-
|
|
694
|
-
Returns
|
|
695
|
-
-------
|
|
696
|
-
filtered : dbdicom series
|
|
697
|
-
"""
|
|
698
|
-
suffix = ' [Convex hull 2D]'
|
|
699
|
-
desc = series.instance().SeriesDescription
|
|
700
|
-
chull = series.copy(SeriesDescription = desc+suffix)
|
|
701
|
-
images = chull.images()
|
|
702
|
-
for i, image in enumerate(images):
|
|
703
|
-
series.status.progress(i+1, len(images), 'Calculating convex hull for ' + desc)
|
|
704
|
-
image.read()
|
|
705
|
-
array = np.around(image.array())
|
|
706
|
-
array = skimage.morphology.convex_hull_image(array, **kwargs)
|
|
707
|
-
image.set_array(array)
|
|
708
|
-
array = array.astype(np.ubyte)
|
|
709
|
-
_reset_window(image, array)
|
|
710
|
-
image.clear()
|
|
711
|
-
series.status.hide()
|
|
712
|
-
return chull
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
def convex_hull_image_3d(input, **kwargs):
|
|
716
|
-
"""
|
|
717
|
-
wrapper for skimage.morphology.convex_hull_image
|
|
718
|
-
|
|
719
|
-
Parameters
|
|
720
|
-
----------
|
|
721
|
-
input: dbdicom series
|
|
722
|
-
|
|
723
|
-
Returns
|
|
724
|
-
-------
|
|
725
|
-
filtered : dbdicom series
|
|
726
|
-
"""
|
|
727
|
-
array, headers = input.array('SliceLocation', pixels_first=True)
|
|
728
|
-
if array is None:
|
|
729
|
-
return input
|
|
730
|
-
desc = input.instance().SeriesDescription + ' [Convex hull 3D]'
|
|
731
|
-
result = input.new_sibling(SeriesDescription = desc)
|
|
732
|
-
for t in range(array.shape[3]):
|
|
733
|
-
input.status.progress(t, array.shape[3], 'Calculating ' + desc)
|
|
734
|
-
volume = np.around(array[:,:,:,t])
|
|
735
|
-
hull = skimage.morphology.convex_hull_image(volume, **kwargs)
|
|
736
|
-
result.set_array(hull, headers[:,t], pixels_first=True)
|
|
737
|
-
_reset_window(result, hull)
|
|
738
|
-
input.status.hide()
|
|
739
|
-
return result
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
def coregister_2d_to_2d(moving, fixed, return_array=False, attachment=1):
|
|
743
|
-
# https://scikit-image.org/docs/stable/api/skimage.registration.html#skimage.registration.optical_flow_tvl1
|
|
744
|
-
|
|
745
|
-
#fixed = fixed.map_to(moving)
|
|
746
|
-
fixed = scipy.map_to(fixed, moving)
|
|
747
|
-
|
|
748
|
-
# Get arrays for fixed and moving series
|
|
749
|
-
array_fixed, _ = fixed.array('SliceLocation', pixels_first=True)
|
|
750
|
-
array_moving, headers = moving.array('SliceLocation', pixels_first=True)
|
|
751
|
-
if array_fixed is None or array_moving is None:
|
|
752
|
-
return fixed
|
|
753
|
-
array_moving, headers, array_fixed = array_moving[...,0], headers[...,0], array_fixed[...,0]
|
|
754
|
-
|
|
755
|
-
# Coregister fixed and moving slice-by-slice
|
|
756
|
-
row_coords, col_coords = np.meshgrid(
|
|
757
|
-
np.arange(array_moving.shape[0]),
|
|
758
|
-
np.arange(array_moving.shape[1]),
|
|
759
|
-
indexing='ij')
|
|
760
|
-
deformation = np.empty(array_moving.shape + (2,))
|
|
761
|
-
for z in range(array_moving.shape[2]):
|
|
762
|
-
moving.status.progress(z+1, array_moving.shape[2], 'Performing coregistration..')
|
|
763
|
-
image0 = array_fixed[:,:,z]
|
|
764
|
-
image1 = array_moving[:,:,z]
|
|
765
|
-
v, u = skimage.registration.optical_flow_tvl1(image0, image1, attachment=attachment)
|
|
766
|
-
new_coords = np.array([row_coords + v, col_coords + u])
|
|
767
|
-
array_moving[:,:,z] = skimage.transform.warp(image1, new_coords, mode='edge')
|
|
768
|
-
deformation[:,:,z,:] = np.stack([v, u], axis=-1)
|
|
769
|
-
|
|
770
|
-
# Return array or new series
|
|
771
|
-
if return_array:
|
|
772
|
-
moving.status.message('Finished coregistration..')
|
|
773
|
-
return array_moving, deformation, headers
|
|
774
|
-
else:
|
|
775
|
-
moving.status.message('Writing coregistered series to database..')
|
|
776
|
-
# Create new dicom series
|
|
777
|
-
desc = moving.instance().SeriesDescription
|
|
778
|
-
coreg = moving.new_sibling(SeriesDescription = desc + ' [coregistered]')
|
|
779
|
-
deform = moving.new_sibling(SeriesDescription = desc + ' [deformation field]')
|
|
780
|
-
# Set arrays of new series
|
|
781
|
-
coreg.set_array(array_moving, headers, pixels_first=True)
|
|
782
|
-
for dim in range(deformation.shape[-1]):
|
|
783
|
-
deform.set_array(deformation[...,dim], headers, pixels_first=True)
|
|
784
|
-
moving.status.message('Finished coregistration..')
|
|
785
|
-
return coreg, deform
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
def coregister_3d_to_3d(moving, fixed, return_array=False, attachment=1):
|
|
789
|
-
# https://scikit-image.org/docs/stable/api/skimage.registration.html#skimage.registration.optical_flow_tvl1
|
|
790
|
-
|
|
791
|
-
fixed = scipy.map_to(fixed, moving)
|
|
792
|
-
|
|
793
|
-
# Get arrays for fixed and moving series
|
|
794
|
-
array_fixed, _ = fixed.array('SliceLocation', pixels_first=True)
|
|
795
|
-
array_moving, headers = moving.array('SliceLocation', pixels_first=True)
|
|
796
|
-
if array_fixed is None or array_moving is None:
|
|
797
|
-
return fixed
|
|
798
|
-
array_moving, headers, array_fixed = array_moving[...,0], headers[...,0], array_fixed[...,0]
|
|
799
|
-
|
|
800
|
-
moving.status.message('Performing coregistration. Please be patient. Its hard work and I need to concentrate..')
|
|
801
|
-
# Coregister fixed and moving slice-by-slice
|
|
802
|
-
row_coords, col_coords, slice_coords = np.meshgrid(
|
|
803
|
-
np.arange(array_moving.shape[0]),
|
|
804
|
-
np.arange(array_moving.shape[1]),
|
|
805
|
-
np.arange(array_moving.shape[2]),
|
|
806
|
-
indexing='ij')
|
|
807
|
-
v, u, w = skimage.registration.optical_flow_tvl1(array_fixed, array_moving, attachment=attachment)
|
|
808
|
-
new_coords = np.array([row_coords + v, col_coords + u, slice_coords + w])
|
|
809
|
-
array_moving = skimage.transform.warp(array_moving, new_coords, mode='edge')
|
|
810
|
-
deformation = np.stack([v, u, w], axis=-1)
|
|
811
|
-
|
|
812
|
-
# Return array or new series
|
|
813
|
-
if return_array:
|
|
814
|
-
moving.status.message('Finished coregistration..')
|
|
815
|
-
return array_moving, deformation, headers
|
|
816
|
-
else:
|
|
817
|
-
moving.status.message('Writing coregistered series to database..')
|
|
818
|
-
# Create new dicom series
|
|
819
|
-
desc = moving.instance().SeriesDescription
|
|
820
|
-
coreg = moving.new_sibling(SeriesDescription = desc + ' [coregistered]')
|
|
821
|
-
deform = moving.new_sibling(SeriesDescription = desc + ' [deformation field]')
|
|
822
|
-
# Set arrays of new series
|
|
823
|
-
coreg.set_array(array_moving, headers, pixels_first=True)
|
|
824
|
-
for dim in range(deformation.shape[-1]):
|
|
825
|
-
deform.set_array(deformation[...,dim], headers, pixels_first=True)
|
|
826
|
-
moving.status.message('Finished coregistration..')
|
|
827
|
-
return coreg, deform
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
def warp(image, deformation_field):
|
|
831
|
-
|
|
832
|
-
# Get arrays for fixed and moving series
|
|
833
|
-
array, headers = image.array('SliceLocation', pixels_first=True, first_volume=True)
|
|
834
|
-
array_deform, _ = deformation_field.array('SliceLocation', pixels_first=True, first_volume=True)
|
|
835
|
-
|
|
836
|
-
# For the deformation field the last dimension is the components
|
|
837
|
-
# For the image use only the first time point
|
|
838
|
-
# array, headers = array[...,0], headers[...,0]
|
|
839
|
-
|
|
840
|
-
# For this function, image and deformation field must be aligned
|
|
841
|
-
if array.shape != array_deform.shape[:-1]:
|
|
842
|
-
msg = 'The dimensions of image and deformation field are not matching up. \n'
|
|
843
|
-
msg += 'Please select two series with matching dimensions.'
|
|
844
|
-
raise ValueError(msg)
|
|
845
|
-
|
|
846
|
-
# Warp the arrays
|
|
847
|
-
if array_deform.shape[-1] == 3:
|
|
848
|
-
x, y, z = np.meshgrid(
|
|
849
|
-
np.arange(array.shape[0]),
|
|
850
|
-
np.arange(array.shape[1]),
|
|
851
|
-
np.arange(array.shape[2]),
|
|
852
|
-
indexing='ij')
|
|
853
|
-
v, u, w = array_deform[...,0], array_deform[...,1], array_deform[...,2]
|
|
854
|
-
new_coords = np.array([x+v, y+u, z+w])
|
|
855
|
-
array = skimage.transform.warp(array, new_coords, mode='edge')
|
|
856
|
-
elif array_deform.shape[-1] == 2:
|
|
857
|
-
x, y = np.meshgrid(
|
|
858
|
-
np.arange(array.shape[0]),
|
|
859
|
-
np.arange(array.shape[1]),
|
|
860
|
-
indexing='ij')
|
|
861
|
-
for z in range(array.shape[2]):
|
|
862
|
-
image.status.progress(z+1, array.shape[2], 'Deforming slices..')
|
|
863
|
-
v, u = array_deform[...,z,0], array_deform[...,z,1]
|
|
864
|
-
new_coords = np.array([x+v, y+u])
|
|
865
|
-
array[...,z] = skimage.transform.warp(array[...,z], new_coords, mode='edge')
|
|
866
|
-
else:
|
|
867
|
-
msg = 'The deformation field does not have the correct dimensions. \n'
|
|
868
|
-
msg += 'This needs to have the either 2 or 3 images for each slice location.'
|
|
869
|
-
raise ValueError(msg)
|
|
870
|
-
|
|
871
|
-
# Create new dicom series
|
|
872
|
-
warped = image.new_sibling(suffix='warped')
|
|
873
|
-
warped.set_array(array, headers, pixels_first=True)
|
|
874
|
-
|
|
875
|
-
return warped
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
def mdreg_constant_3d(series, attachment=1, max_improvement=1, max_iter=5):
|
|
880
|
-
|
|
881
|
-
# Get arrays for fixed and moving series
|
|
882
|
-
array, headers = series.array(['SliceLocation','AcquisitionTime'], pixels_first=True)
|
|
883
|
-
if array is None:
|
|
884
|
-
return
|
|
885
|
-
array, headers = array[...,0], headers[...,0]
|
|
886
|
-
|
|
887
|
-
# Coregister fixed and moving slice-by-slice
|
|
888
|
-
row_coords, col_coords, slice_coords = np.meshgrid(
|
|
889
|
-
np.arange(array.shape[0]),
|
|
890
|
-
np.arange(array.shape[1]),
|
|
891
|
-
np.arange(array.shape[2]),
|
|
892
|
-
indexing='ij')
|
|
893
|
-
v, u, w = np.zeros(array.shape), np.zeros(array.shape), np.zeros(array.shape)
|
|
894
|
-
coreg = array.copy()
|
|
895
|
-
for it in range(max_iter):
|
|
896
|
-
target = np.mean(coreg, axis=3) # constant model
|
|
897
|
-
cnt=0
|
|
898
|
-
improvement = 0 # pixel sizes
|
|
899
|
-
for t in range(array.shape[3]):
|
|
900
|
-
cnt+=1
|
|
901
|
-
msg = 'Performing iteration ' + str(it) + ' < ' + str(max_iter)
|
|
902
|
-
msg += ' (best improvement so far = ' + str(round(improvement,2)) + ' pixels)'
|
|
903
|
-
series.status.progress(cnt, array.shape[3], msg)
|
|
904
|
-
v_t, u_t, w_t = skimage.registration.optical_flow_tvl1(
|
|
905
|
-
target,
|
|
906
|
-
array[:,:,:,t],
|
|
907
|
-
attachment=attachment)
|
|
908
|
-
coreg[:,:,:,t] = skimage.transform.warp(
|
|
909
|
-
array[:,:,:,t],
|
|
910
|
-
np.array([row_coords + v_t, col_coords + u_t, slice_coords + w_t]),
|
|
911
|
-
mode='edge')
|
|
912
|
-
improvement_t = np.amax(np.sqrt(np.square(v_t-v[:,:,:,t]) + np.square(u_t-u[:,:,:,t]) + np.square(w_t-w[:,:,:,t])))
|
|
913
|
-
if improvement_t > improvement:
|
|
914
|
-
improvement = improvement_t
|
|
915
|
-
v[:,:,:,t], u[:,:,:,t], w[:,:,:,t] = v_t, u_t, w_t
|
|
916
|
-
if improvement < max_improvement:
|
|
917
|
-
break
|
|
918
|
-
|
|
919
|
-
series.status.message('Writing coregistered series to database..')
|
|
920
|
-
desc = series.instance().SeriesDescription + ' [coregistered]'
|
|
921
|
-
registered_series = series.new_sibling(SeriesDescription=desc)
|
|
922
|
-
registered_series.set_array(coreg, headers, pixels_first=True)
|
|
923
|
-
series.status.message('Finished coregistration..')
|
|
924
|
-
return registered_series
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
def coregister_series_2d_to_2d(series, attachment=1):
|
|
929
|
-
|
|
930
|
-
# Get arrays for fixed and moving series
|
|
931
|
-
array, headers = series.array(['SliceLocation','AcquisitionTime'], pixels_first=True)
|
|
932
|
-
if array is None:
|
|
933
|
-
return
|
|
934
|
-
array, headers = array[:,:,:,:,0], headers[:,:,0]
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
# Coregister fixed and moving slice-by-slice
|
|
938
|
-
row_coords, col_coords = np.meshgrid(
|
|
939
|
-
np.arange(array.shape[0]),
|
|
940
|
-
np.arange(array.shape[1]),
|
|
941
|
-
indexing='ij')
|
|
942
|
-
target = np.mean(array, axis=3)
|
|
943
|
-
cnt=0
|
|
944
|
-
for t in range(array.shape[3]):
|
|
945
|
-
for z in range(array.shape[2]):
|
|
946
|
-
cnt+=1
|
|
947
|
-
series.status.progress(cnt, array.shape[2]*array.shape[3], 'Performing coregistration..')
|
|
948
|
-
fixed = target[:,:,z]
|
|
949
|
-
moving = array[:,:,z,t]
|
|
950
|
-
v, u = skimage.registration.optical_flow_tvl1(fixed, moving, attachment=attachment)
|
|
951
|
-
array[:,:,z,t] = skimage.transform.warp(
|
|
952
|
-
moving,
|
|
953
|
-
np.array([row_coords + v, col_coords + u]),
|
|
954
|
-
mode='edge')
|
|
955
|
-
|
|
956
|
-
# Return array or new series
|
|
957
|
-
series.status.message('Writing coregistered series to database..')
|
|
958
|
-
desc = series.instance().SeriesDescription + ' [coregistered]'
|
|
959
|
-
registered_series = series.new_sibling(SeriesDescription=desc)
|
|
960
|
-
registered_series.set_array(array, headers, pixels_first=True)
|
|
961
|
-
series.status.message('Finished coregistration..')
|
|
962
|
-
return registered_series
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
def mdreg_constant_2d(series, attachment=1, max_improvement=1, max_iter=5):
|
|
966
|
-
|
|
967
|
-
# Get arrays for fixed and moving series
|
|
968
|
-
array, headers = series.array(['SliceLocation','AcquisitionTime'], pixels_first=True)
|
|
969
|
-
if array is None:
|
|
970
|
-
return
|
|
971
|
-
array, headers = array[:,:,:,:,0], headers[:,:,0]
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
# Coregister fixed and moving slice-by-slice
|
|
975
|
-
row_coords, col_coords = np.meshgrid(
|
|
976
|
-
np.arange(array.shape[0]),
|
|
977
|
-
np.arange(array.shape[1]),
|
|
978
|
-
indexing='ij')
|
|
979
|
-
v, u = np.zeros(array.shape), np.zeros(array.shape)
|
|
980
|
-
coreg = array.copy()
|
|
981
|
-
for it in range(max_iter):
|
|
982
|
-
target = np.mean(coreg, axis=3) # constant model
|
|
983
|
-
cnt=0
|
|
984
|
-
improvement = 0 # pixel sizes
|
|
985
|
-
for t in range(array.shape[3]):
|
|
986
|
-
for z in range(array.shape[2]):
|
|
987
|
-
cnt+=1
|
|
988
|
-
msg = 'Performing iteration ' + str(it) + ' < ' + str(max_iter)
|
|
989
|
-
msg += ' (best improvement so far = ' + str(round(improvement,2)) + ' pixels)'
|
|
990
|
-
series.status.progress(cnt, array.shape[2]*array.shape[3], msg)
|
|
991
|
-
v_zt, u_zt = skimage.registration.optical_flow_tvl1(
|
|
992
|
-
target[:,:,z],
|
|
993
|
-
array[:,:,z,t],
|
|
994
|
-
attachment=attachment)
|
|
995
|
-
coreg[:,:,z,t] = skimage.transform.warp(
|
|
996
|
-
array[:,:,z,t],
|
|
997
|
-
np.array([row_coords + v_zt, col_coords + u_zt]),
|
|
998
|
-
mode='edge')
|
|
999
|
-
improvement_zt = np.amax(np.sqrt(np.square(v_zt-v[:,:,z,t]) + np.square(u_zt-u[:,:,z,t])))
|
|
1000
|
-
if improvement_zt > improvement:
|
|
1001
|
-
improvement = improvement_zt
|
|
1002
|
-
v[:,:,z,t], u[:,:,z,t] = v_zt, u_zt
|
|
1003
|
-
if improvement < max_improvement:
|
|
1004
|
-
break
|
|
1005
|
-
|
|
1006
|
-
series.status.message('Writing coregistered series to database..')
|
|
1007
|
-
desc = series.instance().SeriesDescription + ' [coregistered]'
|
|
1008
|
-
registered_series = series.new_sibling(SeriesDescription=desc)
|
|
1009
|
-
registered_series.set_array(coreg, headers, pixels_first=True)
|
|
1010
|
-
series.status.message('Finished coregistration..')
|
|
1011
|
-
return registered_series
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
# Helper functions
|
|
1018
|
-
|
|
1019
|
-
def _reset_window(image, array):
|
|
1020
|
-
arr = array.astype(np.float32)
|
|
1021
|
-
min = np.amin(arr)
|
|
1022
|
-
max = np.amax(arr)
|
|
1023
|
-
image.WindowCenter= (max+min)/2
|
|
1024
|
-
if min==max:
|
|
1025
|
-
if min == 0:
|
|
1026
|
-
image.WindowWidth = 1
|
|
1027
|
-
else:
|
|
1028
|
-
image.WindowWidth = min
|
|
1029
|
-
else:
|
|
1030
|
-
image.WindowWidth = 0.9*(max-min)
|