|
a |
|
b/utils.py |
|
|
1 |
import numpy as np |
|
|
2 |
from scipy.spatial import distance |
|
|
3 |
from scipy import ndimage |
|
|
4 |
from skimage import measure |
|
|
5 |
|
|
|
6 |
##################################################### |
|
|
7 |
# Pre-define parameters |
|
|
8 |
##################################################### |
|
|
9 |
|
|
|
10 |
def define_parameter(): |
|
|
11 |
|
|
|
12 |
params = {} |
|
|
13 |
|
|
|
14 |
#---------------------------------------------------- |
|
|
15 |
# Parameters for intensity (fixed) |
|
|
16 |
#---------------------------------------------------- |
|
|
17 |
|
|
|
18 |
params['lungMinValue'] = -1024 |
|
|
19 |
params['lungMaxValue'] = -400 |
|
|
20 |
params['lungThreshold'] = -900 |
|
|
21 |
|
|
|
22 |
#---------------------------------------------------- |
|
|
23 |
# Parameters for lung segmentation (fixed) |
|
|
24 |
#---------------------------------------------------- |
|
|
25 |
|
|
|
26 |
params['xRangeRatio1'] = 0.4 |
|
|
27 |
params['xRangeRatio2'] = 0.75 |
|
|
28 |
params['zRangeRatio1'] = 0.5 |
|
|
29 |
params['zRangeRatio2'] = 0.75 |
|
|
30 |
|
|
|
31 |
#---------------------------------------------------- |
|
|
32 |
# Parameters for airway segmentation |
|
|
33 |
# NEED TO ADAPT for image resolution and orientation |
|
|
34 |
# [current values work for demo image with resolution = 1mm^3] |
|
|
35 |
#---------------------------------------------------- |
|
|
36 |
params['airwayRadiusMask'] = 15 # increase the value if you have high resolution image |
|
|
37 |
params['airwayRadiusX'] = 8 # ditto |
|
|
38 |
params['airwayRadiusZ'] = 15 # ditto |
|
|
39 |
params['super2infer'] = 0 # value = 1 if slice no. increases from superior to inferior, else value = 0 |
|
|
40 |
|
|
|
41 |
return params |
|
|
42 |
|
|
|
43 |
##################################################### |
|
|
44 |
# Generate binary structure to mimic trachea |
|
|
45 |
##################################################### |
|
|
46 |
|
|
|
47 |
def generate_structure_trachea(Radius, RadiusX, RadiusZ): |
|
|
48 |
|
|
|
49 |
struct_trachea = np.zeros([2*Radius+1,2*Radius+1,RadiusZ]) |
|
|
50 |
for i in range(0,2*Radius+1): |
|
|
51 |
for j in range(0,2*Radius+1): |
|
|
52 |
if distance.euclidean([Radius+1,Radius+1],[i,j]) < RadiusX: |
|
|
53 |
struct_trachea[i,j,:] = 1 |
|
|
54 |
else: |
|
|
55 |
struct_trachea[i,j,:] = 0 |
|
|
56 |
|
|
|
57 |
return struct_trachea |
|
|
58 |
|
|
|
59 |
##################################################### |
|
|
60 |
# Generate bounding box |
|
|
61 |
##################################################### |
|
|
62 |
|
|
|
63 |
def bbox2_3D(img,label,margin,limit): |
|
|
64 |
|
|
|
65 |
imgtmp = np.zeros(img.shape) |
|
|
66 |
imgtmp[img == label] = 1 |
|
|
67 |
|
|
|
68 |
x = np.any(imgtmp, axis=(1, 2)) |
|
|
69 |
y = np.any(imgtmp, axis=(0, 2)) |
|
|
70 |
z = np.any(imgtmp, axis=(0, 1)) |
|
|
71 |
|
|
|
72 |
xmin, xmax = np.where(x)[0][[0, -1]] |
|
|
73 |
ymin, ymax = np.where(y)[0][[0, -1]] |
|
|
74 |
zmin, zmax = np.where(z)[0][[0, -1]] |
|
|
75 |
|
|
|
76 |
xmin = xmin - margin - 1 |
|
|
77 |
xmin = max(0,xmin) |
|
|
78 |
ymin = ymin - margin - 1 |
|
|
79 |
ymin = max(0,ymin) |
|
|
80 |
zmin = zmin - margin - 1 |
|
|
81 |
zmin = max(0,zmin) |
|
|
82 |
xmax = xmax + margin + 1 |
|
|
83 |
xmax = min(xmax,limit[0]) |
|
|
84 |
ymax = ymax + margin + 1 |
|
|
85 |
ymax = min(ymax,limit[1]) |
|
|
86 |
zmax = zmax + margin + 1 |
|
|
87 |
zmax = min(zmax,limit[2]) |
|
|
88 |
|
|
|
89 |
return xmin, xmax, ymin, ymax, zmin, zmax |
|
|
90 |
|
|
|
91 |
##################################################### |
|
|
92 |
# Generate inital point in trachea |
|
|
93 |
##################################################### |
|
|
94 |
|
|
|
95 |
def generate_initLoc(params, I, Mlung, Radius, RadiusZ, struct_trachea): |
|
|
96 |
|
|
|
97 |
mind = np.argwhere(Mlung == 1) |
|
|
98 |
initLoc = [0,0,0] |
|
|
99 |
minDiff = float('inf') |
|
|
100 |
|
|
|
101 |
if params['super2infer']: |
|
|
102 |
slice_no = np.min(mind[:,2]) |
|
|
103 |
Itmp = I[:,:,slice_no:slice_no+RadiusZ] |
|
|
104 |
else: |
|
|
105 |
slice_no = np.max(mind[:,2]) |
|
|
106 |
Itmp = I[:,:,slice_no-RadiusZ:slice_no] |
|
|
107 |
|
|
|
108 |
Mtmp = np.ones(Itmp.shape); |
|
|
109 |
Mtmp[Itmp < params['lungMinValue']] = 0 |
|
|
110 |
Mtmp[Itmp > params['lungMaxValue']] = 0 |
|
|
111 |
Itmp = Mtmp; |
|
|
112 |
Mtmp = np.sum(Mtmp, axis = 2) |
|
|
113 |
|
|
|
114 |
for i in range(Radius, Itmp.shape[0]-Radius): |
|
|
115 |
for j in range(Radius, Itmp.shape[1]-Radius): |
|
|
116 |
if Mtmp[i,j] > 0: |
|
|
117 |
struct_Itmp = Itmp[i-Radius:i+Radius+1,j-Radius:j+Radius+1,:] |
|
|
118 |
currVal = struct_Itmp - struct_trachea |
|
|
119 |
currVal = np.sum(np.square(currVal)) |
|
|
120 |
|
|
|
121 |
if currVal < minDiff: |
|
|
122 |
initLoc = [i,j,slice_no] |
|
|
123 |
minDiff = currVal |
|
|
124 |
|
|
|
125 |
print 'initial location = '+str(initLoc) |
|
|
126 |
|
|
|
127 |
return slice_no, initLoc |
|
|
128 |
|
|
|
129 |
##################################################### |
|
|
130 |
# Closed space dialation to segment airway |
|
|
131 |
##################################################### |
|
|
132 |
|
|
|
133 |
def close_space_dilation(params, I, Mlung, Radius, RadiusX, RadiusZ, struct_s, slice_no, initLoc): |
|
|
134 |
|
|
|
135 |
iterNoPerSlice = RadiusX |
|
|
136 |
maxFactor = RadiusX/2 |
|
|
137 |
maxChange = RadiusX*RadiusX*RadiusX*50 |
|
|
138 |
totalChange = 1 |
|
|
139 |
tempCheck = 0 |
|
|
140 |
[m,n,p] = I.shape |
|
|
141 |
Mtmp = np.zeros([m,n,p]) |
|
|
142 |
struct_m = ndimage.iterate_structure(struct_s, 2) |
|
|
143 |
|
|
|
144 |
if params['super2infer']: |
|
|
145 |
Mtmp[initLoc[0]-Radius:initLoc[0]+Radius+1, |
|
|
146 |
initLoc[1]-Radius:initLoc[1]+Radius+1, |
|
|
147 |
0:slice_no+RadiusZ] = 1 |
|
|
148 |
else: |
|
|
149 |
Mtmp[initLoc[0]-Radius:initLoc[0]+Radius+1, |
|
|
150 |
initLoc[1]-Radius:initLoc[1]+Radius+1, |
|
|
151 |
slice_no-RadiusZ:p-1] = 1 |
|
|
152 |
Mtmp = np.multiply(Mtmp, Mlung) |
|
|
153 |
Minit = ndimage.binary_closing(Mtmp, structure = struct_s, iterations = 1) |
|
|
154 |
Minit = np.int8(Minit) |
|
|
155 |
Minit[Minit > 0] = 2 |
|
|
156 |
|
|
|
157 |
while totalChange > 0: |
|
|
158 |
|
|
|
159 |
maxSegmentChange = 0; |
|
|
160 |
tempCheck = tempCheck + 1 |
|
|
161 |
L = measure.label(np.floor(Minit/2)) |
|
|
162 |
Minit[Minit > 1] = 1 |
|
|
163 |
|
|
|
164 |
for label in np.unique(L[:]): |
|
|
165 |
|
|
|
166 |
if label != 0 and np.sum(L[:] == label) > 10: |
|
|
167 |
|
|
|
168 |
# Process each component in local FOV |
|
|
169 |
|
|
|
170 |
xmin, xmax, ymin, ymax, zmin, zmax = bbox2_3D(L,label,iterNoPerSlice,[m,n,p]) |
|
|
171 |
Mtmp = Minit[xmin:xmax,ymin:ymax,zmin:zmax] |
|
|
172 |
Itmp = I[xmin:xmax,ymin:ymax,zmin:zmax] |
|
|
173 |
Ltmp = L[xmin:xmax,ymin:ymax,zmin:zmax] |
|
|
174 |
Ltmp[Ltmp != label] = 0 |
|
|
175 |
Ltmp[Ltmp > 0] = 1; |
|
|
176 |
|
|
|
177 |
for iterCount in range(0, iterNoPerSlice): |
|
|
178 |
Ltmp = ndimage.binary_dilation(Ltmp, structure = struct_s, iterations = 1) |
|
|
179 |
Ltmp = np.int8(Ltmp) |
|
|
180 |
Ltmp[Itmp > params['lungThreshold']] = 0 |
|
|
181 |
|
|
|
182 |
Ltmp = ndimage.binary_closing(Ltmp, structure = struct_s, iterations = 1) |
|
|
183 |
Ltmp = np.int8(Ltmp) |
|
|
184 |
Ltmp[Mtmp > 0] = 0 |
|
|
185 |
Ltmp[Ltmp > 0] = 2 |
|
|
186 |
Ltmp = Ltmp + Mtmp |
|
|
187 |
|
|
|
188 |
segmentChange = np.sum(Ltmp[:]>1) |
|
|
189 |
if segmentChange < maxChange or tempCheck < 10: |
|
|
190 |
Minit[xmin:xmax,ymin:ymax,zmin:zmax] = Ltmp |
|
|
191 |
if segmentChange > maxSegmentChange: |
|
|
192 |
maxSegmentChange = segmentChange |
|
|
193 |
|
|
|
194 |
if tempCheck < 10: |
|
|
195 |
maxChange = max(maxFactor*maxSegmentChange,maxChange) |
|
|
196 |
else: |
|
|
197 |
maxChange = min(maxFactor*maxSegmentChange,maxChange) |
|
|
198 |
|
|
|
199 |
totalChange = np.sum(Minit[:]>1) |
|
|
200 |
|
|
|
201 |
print 'iter = '+str(tempCheck)+' airway sum = '+str(np.sum(Minit[:]>0))\ |
|
|
202 |
+' airway change = '+str(totalChange) |
|
|
203 |
|
|
|
204 |
Minit[Minit > 0] = 1 |
|
|
205 |
Minit = ndimage.binary_opening(Minit, structure = struct_s, iterations = 1) |
|
|
206 |
Minit = ndimage.binary_dilation(Minit, structure = struct_m, iterations = 1) |
|
|
207 |
Maw = np.int8(Minit) |
|
|
208 |
|
|
|
209 |
return Maw |