Diff of /lung_segmentation.py [000000] .. [4f54f1]

Switch to unified view

a b/lung_segmentation.py
1
import numpy as np
2
import os
3
4
from skimage import measure, morphology, segmentation
5
from skimage.morphology import ball, disk, binary_erosion, binary_closing
6
from skimage.measure import label, regionprops
7
from skimage.filters import roberts
8
from skimage.segmentation import clear_border
9
10
from scipy import ndimage as ndi
11
import matplotlib.pyplot as plt
12
import scipy.misc
13
14
import config
15
16
17
# Optimal threshold, found in an article about segmentation algorithm using
18
# morphological operations. This is in HU units.
19
threshold = -420
20
nodule_threshold = -180
21
22
23
class SegmentationAlgorithm(object):
24
    def __init__(self, threshold):
25
        self._threshold = threshold
26
27
    def get_segmented_lungs(self, plot=False):
28
        pass
29
30
    def apply_threshold(self, scan):
31
        scan[scan < nodule_threshold] = 0
32
        return scan
33
34
    def get_lung_nodules_candidates(self, patient_imgs):
35
        nodules = [self.apply_threshold(scan) for scan in patient_imgs]
36
        return np.stack([nodule for nodule in nodules if nodule.any()])
37
38
    def get_slices_with_nodules(self, patient_imgs):
39
        return np.stack([slice_ for slice_ in patient_imgs if 
40
            self._has_nodule(slice_)])
41
42
    def _has_nodule(self, scan):
43
        scan_copy = scan.copy()
44
        scan_copy[scan_copy < nodule_threshold] = 0
45
        return scan_copy.any()
46
47
48
class MorphologicalSegmentation(SegmentationAlgorithm):
49
    def __init__(self, threshold=-420):
50
        super(MorphologicalSegmentation, self).__init__(threshold)
51
52
    def get_segmented_lungs(self, im, plot=False):
53
        '''
54
        This funtion segments the lungs from the given 2D slice.
55
        '''
56
        '''
57
        Step 1: Convert into a binary image. 
58
        '''
59
        binary = im < self._threshold
60
        if plot == True:
61
            plt.imshow(binary, cmap=plt.cm.bone)
62
            plt.show()
63
        '''
64
        Step 2: Remove the blobs connected to the border of the image.
65
        '''
66
        cleared = clear_border(binary)
67
        if plot == True:
68
            plt.imshow(cleared, cmap=plt.cm.bone)
69
            plt.show()
70
        '''
71
        Step 3: Closure operation with a disk of radius 2. This operation is 
72
        to keep nodules attached to the lung wall.
73
        '''
74
        selem = disk(2)
75
        binary = binary_closing(cleared, selem)
76
        if plot == True:
77
            plt.imshow(binary, cmap=plt.cm.bone)
78
            plt.show()
79
        '''    
80
        Step 4: Label the image.
81
        '''
82
        label_image = label(binary)
83
        if plot == True:
84
            plt.imshow(label_image, cmap=plt.cm.bone)
85
            plt.show() 
86
        '''
87
        Step 5: Keep the labels with 2 largest areas.
88
        '''
89
        areas = [r.area for r in regionprops(label_image)]
90
91
        areas.sort()
92
        if len(areas) > 2:
93
            for region in regionprops(label_image):
94
                if region.area < areas[-2]:
95
                    for coordinates in region.coords:                
96
                           label_image[coordinates[0], coordinates[1]] = 0
97
        binary = label_image > 0
98
        if plot == True:
99
            plt.imshow(binary, cmap=plt.cm.bone)
100
            plt.show() 
101
        '''
102
        Step 6: Closure operation with a disk of radius 2. This operation is 
103
        to keep nodules attached to the lung wall.
104
        '''
105
        selem = disk(12)
106
        binary = binary_closing(binary, selem)
107
        if plot == True:
108
            plt.imshow(binary, cmap=plt.cm.bone)
109
            plt.show()
110
        '''
111
        Step 7: Fill in the small holes inside the binary mask of lungs.
112
        '''
113
        edges = roberts(binary)
114
        binary = ndi.binary_fill_holes(edges)
115
        if plot == True:
116
            plt.imshow(binary, cmap=plt.cm.bone)
117
            plt.show()
118
        '''
119
        Step 8: Erosion operation with a disk of radius 2. This operation is 
120
        seperate the lung nodules attached to the blood vessels.
121
        '''
122
        selem = disk(2)
123
        binary = binary_erosion(binary, selem)
124
        if plot == True:
125
            plt.imshow(binary, cmap=plt.cm.bone)
126
            plt.show() 
127
       
128
        '''
129
        Step 9: Superimpose the binary mask on the input image.
130
        '''
131
        get_high_vals = binary == 0
132
        im[get_high_vals] = 0
133
        if plot == True:
134
            plt.imshow(im, cmap=plt.cm.bone)
135
            plt.show() 
136
            
137
        return im
138
139
140
class WatershedSegmentation(SegmentationAlgorithm):
141
    def __init__(self, threshold=-400):
142
        super(WatershedSegmentation, self).__init__(threshold)
143
144
    def get_segmented_lungs(self, image, plot=False):
145
        # TODO: might add the logic for plotting the filters applied in the process
146
        #Creation of the markers as shown above:
147
        marker_internal, marker_external, marker_watershed = self.generate_markers(image)
148
        
149
        #Creation of the Sobel-Gradient
150
        sobel_filtered_dx = ndi.sobel(image, 1)
151
        sobel_filtered_dy = ndi.sobel(image, 0)
152
        sobel_gradient = np.hypot(sobel_filtered_dx, sobel_filtered_dy)
153
        sobel_gradient *= 255.0 / np.max(sobel_gradient)
154
        
155
        #Watershed algorithm
156
        watershed = morphology.watershed(sobel_gradient, marker_watershed)
157
        
158
        #Reducing the image created by the Watershed algorithm to its outline
159
        outline = ndi.morphological_gradient(watershed, size=(3,3))
160
        outline = outline.astype(bool)
161
        
162
        #Performing Black-Tophat Morphology for reinclusion
163
        #Creation of the disk-kernel and increasing its size a bit
164
        blackhat_struct = [[0, 0, 1, 1, 1, 0, 0],
165
                           [0, 1, 1, 1, 1, 1, 0],
166
                           [1, 1, 1, 1, 1, 1, 1],
167
                           [1, 1, 1, 1, 1, 1, 1],
168
                           [1, 1, 1, 1, 1, 1, 1],
169
                           [0, 1, 1, 1, 1, 1, 0],
170
                           [0, 0, 1, 1, 1, 0, 0]]
171
        blackhat_struct = ndi.iterate_structure(blackhat_struct, 8)
172
        #Perform the Black-Hat
173
        outline += ndi.black_tophat(outline, structure=blackhat_struct)
174
        
175
        #Use the internal marker and the Outline that was just created to generate the lungfilter
176
        lungfilter = np.bitwise_or(marker_internal, outline)
177
        #Close holes in the lungfilter
178
        #fill_holes is not used here, since in some slices the heart would be reincluded by accident
179
        lungfilter = ndi.morphology.binary_closing(lungfilter, structure=np.ones((5,5)), iterations=3)
180
        
181
        #Apply the lungfilter (the filtered areas being assigned 0 HU
182
        segmented = np.where(lungfilter == 1, image, np.zeros(image.shape))
183
184
        return segmented
185
186
187
    def generate_markers(self, image):
188
        #Creation of the internal Marker
189
        marker_internal = image < self._threshold
190
        marker_internal = segmentation.clear_border(marker_internal)
191
        marker_internal_labels = measure.label(marker_internal)
192
        areas = [r.area for r in measure.regionprops(marker_internal_labels)]
193
        areas.sort()
194
        if len(areas) > 2:
195
            for region in measure.regionprops(marker_internal_labels):
196
                if region.area < areas[-2]:
197
                    for coordinates in region.coords:                
198
                           marker_internal_labels[coordinates[0], coordinates[1]] = 0
199
        marker_internal = marker_internal_labels > 0
200
        #Creation of the external Marker
201
        external_a = ndi.binary_dilation(marker_internal, iterations=10)
202
        external_b = ndi.binary_dilation(marker_internal, iterations=55)
203
        marker_external = external_b ^ external_a
204
        #Creation of the Watershed Marker matrix
205
        marker_watershed = np.zeros(image.shape, dtype=np.int)
206
        marker_watershed += marker_internal * 255
207
        marker_watershed += marker_external * 128
208
        
209
        return marker_internal, marker_external, marker_watershed
210
211
212
def get_segmentation_algorithm():
213
    if config.SEGMENTATION_ALGO == config.MORPHOLOGICAL_OPERATIONS:
214
        return MorphologicalSegmentation()
215
    if config.SEGMENTATION_ALGO == config.WATERSHED:
216
        return WatershedSegmentation()
217
218
    return WatershedSegmentation()