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