|
a |
|
b/lung_segmentation.py |
|
|
1 |
import numpy as np |
|
|
2 |
import skimage.measure |
|
|
3 |
import skimage.segmentation |
|
|
4 |
import skimage.morphology |
|
|
5 |
import skimage.filters |
|
|
6 |
import scipy.ndimage |
|
|
7 |
import utils_plots |
|
|
8 |
|
|
|
9 |
|
|
|
10 |
def segment_HU_scan(x): |
|
|
11 |
mask = np.asarray(x < -350, dtype='int32') |
|
|
12 |
for iz in xrange(mask.shape[0]): |
|
|
13 |
skimage.segmentation.clear_border(mask[iz], in_place=True) |
|
|
14 |
skimage.morphology.binary_opening(mask[iz], selem=skimage.morphology.disk(5), out=mask[iz]) |
|
|
15 |
if np.sum(mask[iz]): |
|
|
16 |
mask[iz] = skimage.morphology.convex_hull_image(mask[iz]) |
|
|
17 |
return mask |
|
|
18 |
|
|
|
19 |
|
|
|
20 |
def segment_HU_scan_frederic(x, threshold=-350): |
|
|
21 |
mask = np.copy(x) |
|
|
22 |
binary_part = mask > threshold |
|
|
23 |
selem1 = skimage.morphology.disk(8) |
|
|
24 |
selem2 = skimage.morphology.disk(2) |
|
|
25 |
selem3 = skimage.morphology.disk(13) |
|
|
26 |
|
|
|
27 |
for iz in xrange(mask.shape[0]): |
|
|
28 |
# fill the body part |
|
|
29 |
filled = scipy.ndimage.binary_fill_holes(binary_part[iz]) # fill body |
|
|
30 |
filled_borders_mask = skimage.morphology.binary_erosion(filled, selem1) |
|
|
31 |
mask[iz] *= filled_borders_mask |
|
|
32 |
|
|
|
33 |
|
|
|
34 |
mask[iz] = skimage.morphology.closing(mask[iz], selem2) |
|
|
35 |
mask[iz] = skimage.morphology.erosion(mask[iz], selem3) |
|
|
36 |
mask[iz] = mask[iz] < threshold |
|
|
37 |
|
|
|
38 |
return mask |
|
|
39 |
|
|
|
40 |
|
|
|
41 |
def segment_HU_scan_elias(x, threshold=-350, pid='test', plot=False, verbose=False): |
|
|
42 |
mask = np.copy(x) |
|
|
43 |
binary_part = mask > threshold |
|
|
44 |
selem1 = skimage.morphology.disk(8) |
|
|
45 |
selem2 = skimage.morphology.disk(2) |
|
|
46 |
selem3 = skimage.morphology.disk(13) |
|
|
47 |
|
|
|
48 |
for iz in xrange(mask.shape[0]): |
|
|
49 |
# fill the body part |
|
|
50 |
filled = scipy.ndimage.binary_fill_holes(binary_part[iz]) # fill body |
|
|
51 |
filled_borders_mask = skimage.morphology.binary_erosion(filled, selem1) |
|
|
52 |
mask[iz] *= filled_borders_mask |
|
|
53 |
|
|
|
54 |
mask[iz] = skimage.morphology.closing(mask[iz], selem2) |
|
|
55 |
mask[iz] = mask[iz] < threshold |
|
|
56 |
|
|
|
57 |
# params |
|
|
58 |
overlap_treshold = 7 |
|
|
59 |
ratio_overlap_treshold = 0.015 |
|
|
60 |
|
|
|
61 |
#discard disconnected regions, start at the middle slice and go to the head |
|
|
62 |
for iz in range(mask.shape[0]/2, mask.shape[0]-1): |
|
|
63 |
overlap = mask[iz] * mask[iz+1] |
|
|
64 |
label_image = skimage.measure.label(mask[iz+1]) |
|
|
65 |
if verbose: |
|
|
66 |
print 'iz', iz |
|
|
67 |
for idx, region in enumerate(skimage.measure.regionprops(label_image)): |
|
|
68 |
total_overlap = 0 |
|
|
69 |
for coordinates in region.coords: |
|
|
70 |
total_overlap += overlap[coordinates[0], coordinates[1]] |
|
|
71 |
ratio_overlap = 1.*total_overlap/region.area |
|
|
72 |
if verbose: |
|
|
73 |
print 'region', idx, ', t_overlap', total_overlap, ', r_overlap ', ratio_overlap, ', area ', region.area, ', center', np.round(region.centroid) |
|
|
74 |
if total_overlap < overlap_treshold or ratio_overlap < ratio_overlap_treshold: |
|
|
75 |
if verbose: |
|
|
76 |
print 'region', idx, 'in slice z=', iz-1, 'has a low overlap (', total_overlap, ratio_overlap, ') and will be discarded' |
|
|
77 |
for coordinates in region.coords: |
|
|
78 |
mask[iz+1, coordinates[0], coordinates[1]] = 0 |
|
|
79 |
|
|
|
80 |
#discard disconnected regions, start at the middle slice and go to the head |
|
|
81 |
for iz in range(mask.shape[0]/2,0,-1 ): |
|
|
82 |
overlap = mask[iz] * mask[iz-1] |
|
|
83 |
label_image = skimage.measure.label(mask[iz-1]) |
|
|
84 |
if verbose: |
|
|
85 |
print 'iz', iz |
|
|
86 |
for idx, region in enumerate(skimage.measure.regionprops(label_image)): |
|
|
87 |
total_overlap = 0 |
|
|
88 |
for coordinates in region.coords: |
|
|
89 |
total_overlap += overlap[coordinates[0], coordinates[1]] |
|
|
90 |
ratio_overlap = 1.*total_overlap/region.area |
|
|
91 |
if verbose: |
|
|
92 |
print 'region', idx, ', t_overlap', total_overlap, ', r_overlap ', ratio_overlap, ', area ', region.area, ', center', np.round(region.centroid) |
|
|
93 |
if total_overlap < overlap_treshold or ratio_overlap < ratio_overlap_treshold: |
|
|
94 |
if verbose: |
|
|
95 |
print 'region', idx, 'in slice z=', iz-1, 'has a low overlap (', total_overlap, ratio_overlap, ') and will be discarded' |
|
|
96 |
for coordinates in region.coords: |
|
|
97 |
mask[iz-1, coordinates[0], coordinates[1]] = 0 |
|
|
98 |
|
|
|
99 |
|
|
|
100 |
#erode out the blood vessels and the borders of the lung for a cleaner mask |
|
|
101 |
for iz in xrange(mask.shape[0]): |
|
|
102 |
mask[iz] = skimage.morphology.binary_dilation(mask[iz], selem3) |
|
|
103 |
#mask[iz] = scipy.ndimage.binary_fill_holes(mask[iz]) |
|
|
104 |
|
|
|
105 |
|
|
|
106 |
if plot: |
|
|
107 |
utils_plots.plot_all_slices(x, mask, pid, './plots/segment_HU_scan_elias/') |
|
|
108 |
|
|
|
109 |
return mask |
|
|
110 |
|
|
|
111 |
|
|
|
112 |
def segment_HU_scan_ira(x, threshold=-350, min_area=300): |
|
|
113 |
mask = np.asarray(x < threshold, dtype='int8') |
|
|
114 |
|
|
|
115 |
for zi in xrange(mask.shape[0]): |
|
|
116 |
skimage.segmentation.clear_border(mask[zi, :, :], in_place=True) |
|
|
117 |
|
|
|
118 |
# noise reduction |
|
|
119 |
mask = skimage.morphology.binary_opening(mask, skimage.morphology.cube(2)) |
|
|
120 |
mask = np.asarray(mask, dtype='int8') |
|
|
121 |
|
|
|
122 |
# label regions |
|
|
123 |
label_image = skimage.measure.label(mask) |
|
|
124 |
region_props = skimage.measure.regionprops(label_image) |
|
|
125 |
sorted_regions = sorted(region_props, key=lambda x: x.area, reverse=True) |
|
|
126 |
lung_label = sorted_regions[0].label |
|
|
127 |
lung_mask = np.asarray((label_image == lung_label), dtype='int8') |
|
|
128 |
|
|
|
129 |
# convex hull mask |
|
|
130 |
lung_mask_convex = np.zeros_like(lung_mask) |
|
|
131 |
for i in range(lung_mask.shape[2]): |
|
|
132 |
if np.any(lung_mask[:, :, i]): |
|
|
133 |
lung_mask_convex[:, :, i] = skimage.morphology.convex_hull_image(lung_mask[:, :, i]) |
|
|
134 |
|
|
|
135 |
# old mask inside the convex hull |
|
|
136 |
mask *= lung_mask_convex |
|
|
137 |
label_image = skimage.measure.label(mask) |
|
|
138 |
region_props = skimage.measure.regionprops(label_image) |
|
|
139 |
sorted_regions = sorted(region_props, key=lambda x: x.area, reverse=True) |
|
|
140 |
|
|
|
141 |
for r in sorted_regions[1:]: |
|
|
142 |
if r.area > min_area: |
|
|
143 |
# make an image only containing that region |
|
|
144 |
label_image_r = label_image == r.label |
|
|
145 |
# grow the mask |
|
|
146 |
label_image_r = scipy.ndimage.binary_dilation(label_image_r, |
|
|
147 |
structure=scipy.ndimage.generate_binary_structure(3, 2)) |
|
|
148 |
# compute the overlap with true lungs |
|
|
149 |
overlap = label_image_r * lung_mask |
|
|
150 |
if not np.any(overlap): |
|
|
151 |
for i in range(label_image_r.shape[0]): |
|
|
152 |
if np.any(label_image_r[i]): |
|
|
153 |
label_image_r[i] = skimage.morphology.convex_hull_image(label_image_r[i]) |
|
|
154 |
lung_mask_convex *= 1 - label_image_r |
|
|
155 |
|
|
|
156 |
return lung_mask_convex |