|
a |
|
b/R/segment_lung_lr.R |
|
|
1 |
#' Lung Segmentation |
|
|
2 |
#' |
|
|
3 |
#' This function segments the right and left lungs from a CT scan. |
|
|
4 |
#' |
|
|
5 |
#' @param img CT scan in ANTs or NIfTI file format |
|
|
6 |
#' @param lthresh Threshold used to find the lung and airways, |
|
|
7 |
#' from the CT. Default: -300 HU. |
|
|
8 |
#' @param verbose Print diagnostic messages. |
|
|
9 |
#' |
|
|
10 |
#' @return Lung mask in ANTs file format. Right lung = 1, left lung = 2, non-lung = 0 |
|
|
11 |
#' @importFrom ANTsR maskImage |
|
|
12 |
#' @importFrom ANTsRCore iMath labelClusters makeImage |
|
|
13 |
#' @export |
|
|
14 |
segment_lung_lr = function(img, lthresh = -300, verbose = TRUE){ |
|
|
15 |
|
|
|
16 |
orig_img = check_ants(img) |
|
|
17 |
img = antsImageClone(orig_img) |
|
|
18 |
if (verbose) { |
|
|
19 |
message("# Resampling Image to 1x1x1") |
|
|
20 |
} |
|
|
21 |
img = resampleImage(img, c(1,1,1)) |
|
|
22 |
|
|
|
23 |
|
|
|
24 |
# Make all values positive, so 0s are 0s |
|
|
25 |
img = img + 1025 |
|
|
26 |
orig_lthresh = lthresh |
|
|
27 |
lthresh = lthresh + 1025 |
|
|
28 |
|
|
|
29 |
|
|
|
30 |
# Segmenting Lung and Airways (using code from segment_lung) |
|
|
31 |
if (verbose) { |
|
|
32 |
message("# Segmenting Lung and Airways: Thresholding") |
|
|
33 |
} |
|
|
34 |
body_img = img <= lthresh |
|
|
35 |
|
|
|
36 |
|
|
|
37 |
# Find the lungs and trachea from the image |
|
|
38 |
if (verbose) { |
|
|
39 |
message("# Segmenting Lung and Airways: Largest Component") |
|
|
40 |
} |
|
|
41 |
first_img = iMath(img = body_img, operation = "GetLargestComponent") |
|
|
42 |
|
|
|
43 |
# Check to see if we grabbed the background instead of the lung/trachea |
|
|
44 |
# The back 10 slice and/or front 10 slices should have mean ~ 1, if selected |
|
|
45 |
n_max = dim(first_img)[2] |
|
|
46 |
mean_back = mean(apply(as.array(first_img)[,1:10,],2,mean)) |
|
|
47 |
mean_front = mean(apply(as.array(first_img)[,(n_max-9):n_max,],2,mean)) |
|
|
48 |
|
|
|
49 |
# Remove background, as necessary |
|
|
50 |
if(mean_back > .5 | mean_front > .5) |
|
|
51 |
{ |
|
|
52 |
first_img_hold = antsImageClone(first_img) |
|
|
53 |
|
|
|
54 |
# Remove background from rest of image |
|
|
55 |
first_img = (1-first_img_hold) * body_img |
|
|
56 |
first_img = iMath(img = first_img, operation = "GetLargestComponent") |
|
|
57 |
|
|
|
58 |
# Check to see if that fixed the problem |
|
|
59 |
mean_back = mean(apply(as.array(first_img)[,1:10,],2,mean)) |
|
|
60 |
mean_front = mean(apply(as.array(first_img)[,(n_max-9):n_max,],2,mean)) |
|
|
61 |
|
|
|
62 |
# Keep fixing, as necessary |
|
|
63 |
if(mean_back > .5 | mean_front > .5) # Still background |
|
|
64 |
{ |
|
|
65 |
first_img_hold[first_img==1] = 1 |
|
|
66 |
first_img = (1-first_img_hold) * body_img |
|
|
67 |
first_img = iMath(img = first_img, operation = "GetLargestComponent") |
|
|
68 |
|
|
|
69 |
# Check to see if that fixed the problem |
|
|
70 |
mean_back = mean(apply(as.array(first_img)[,1:10,],2,mean)) |
|
|
71 |
mean_front = mean(apply(as.array(first_img)[,(n_max-9):n_max,],2,mean)) |
|
|
72 |
if(mean_back > .5 | mean_front > .5){ |
|
|
73 |
first_img = segment_lung(orig_img, lthresh = orig_lthresh) |
|
|
74 |
first_img <- first_img$lung_mask |
|
|
75 |
#stop("Can't find lungs beneath background noise. More coding needed") |
|
|
76 |
} |
|
|
77 |
|
|
|
78 |
} |
|
|
79 |
} |
|
|
80 |
lung_air_mask = iMath(first_img, "MC", 2) |
|
|
81 |
|
|
|
82 |
|
|
|
83 |
|
|
|
84 |
# Segmenting Airways |
|
|
85 |
if (verbose) { |
|
|
86 |
message("# Segmenting Airways: Thresholding") |
|
|
87 |
} |
|
|
88 |
air_mask = antsImageClone(lung_air_mask) |
|
|
89 |
air = maskImage(img,lung_air_mask) |
|
|
90 |
air_thres = quantile(air[air>0],.04) |
|
|
91 |
air_mask[img > air_thres] = 0 |
|
|
92 |
|
|
|
93 |
if (verbose) { |
|
|
94 |
message("# Segmenting Airways: Identifying Airway Location") |
|
|
95 |
} |
|
|
96 |
fimg <- air_mask > 2 |
|
|
97 |
mydim = dim(fimg) |
|
|
98 |
fimg<-as.array(fimg,dim=mydim) |
|
|
99 |
fimg[round(mydim[1]/4):(round(mydim[1]/4)*3),round(mydim[2]/4):(round(mydim[2]/4)*3),round(mydim[3]/2):mydim[3]] <- 1 |
|
|
100 |
fimg<-makeImage(mydim,fimg) |
|
|
101 |
air_mask <- maskImage(air_mask,fimg) |
|
|
102 |
|
|
|
103 |
if (verbose) { |
|
|
104 |
message("# Segmenting Airways: Smoothing") |
|
|
105 |
} |
|
|
106 |
air_mask = iMath(air_mask, "MC", 1) |
|
|
107 |
air_mask = iMath(air_mask, "ME", 1) |
|
|
108 |
air_mask = iMath(air_mask, "MD", 2) |
|
|
109 |
|
|
|
110 |
|
|
|
111 |
# Segmenting Lung |
|
|
112 |
if (verbose) { |
|
|
113 |
message("# Segmenting Lung: Removing Airways from Lung") |
|
|
114 |
} |
|
|
115 |
lung_mask = maskImage(lung_air_mask, 1-air_mask) |
|
|
116 |
img_masked = maskImage(img, lung_mask) |
|
|
117 |
|
|
|
118 |
if (verbose) { |
|
|
119 |
message("# Segmenting Lung: Finding Left and Right Lungs") |
|
|
120 |
} |
|
|
121 |
left_right_mask = labelClusters(lung_mask, minClusterSize = 50000) |
|
|
122 |
n_clus = length(unique(left_right_mask)) |
|
|
123 |
|
|
|
124 |
if (n_clus == 1) { |
|
|
125 |
message("# Error: Can't find lungs, returning current mask") |
|
|
126 |
return(lung_mask) |
|
|
127 |
} |
|
|
128 |
|
|
|
129 |
# Left and right lung are still connected |
|
|
130 |
i = 0 |
|
|
131 |
j = 0 |
|
|
132 |
lung_mask2 = antsImageClone(lung_mask) |
|
|
133 |
while(n_clus == 2){ |
|
|
134 |
if(i == 0) { |
|
|
135 |
lung_mask2 = iMath(lung_mask2, "ME", 1) |
|
|
136 |
j = j + 1 |
|
|
137 |
left_right_mask = labelClusters(lung_mask2, minClusterSize = 50000) |
|
|
138 |
n_clus = length(unique(left_right_mask)) |
|
|
139 |
} |
|
|
140 |
i = i + 1 |
|
|
141 |
new_lthresh = lthresh - 50*i |
|
|
142 |
if(new_lthresh < 0){ |
|
|
143 |
message("# Error: Can't distinguish left/right lungs, returning left/right combined mask") |
|
|
144 |
return(lung_mask) |
|
|
145 |
} |
|
|
146 |
if(new_lthresh <= 200) { |
|
|
147 |
lung_mask2 = iMath(lung_mask2, "ME", 1) |
|
|
148 |
j = j + 1 |
|
|
149 |
} |
|
|
150 |
img_mask = img_masked < new_lthresh |
|
|
151 |
lung_mask2[img_mask == 0] = 0 |
|
|
152 |
left_right_mask = labelClusters(lung_mask2, minClusterSize = 50000) |
|
|
153 |
num = unique(left_right_mask) |
|
|
154 |
n_clus = length(num) |
|
|
155 |
} |
|
|
156 |
|
|
|
157 |
# Correct number of clusters |
|
|
158 |
if (n_clus == 3) { |
|
|
159 |
|
|
|
160 |
# Find which value is the background (Note: this was added because sometimes the background is non-zero) |
|
|
161 |
left_right_mask = left_right_mask + 1 |
|
|
162 |
fimg <- as.array(left_right_mask) |
|
|
163 |
last <- dim(fimg)[1] |
|
|
164 |
left <- unique(as.vector(fimg[1,,])) |
|
|
165 |
right <- unique(as.vector(fimg[last,,])) |
|
|
166 |
both <- c(left,right) |
|
|
167 |
# Logic: only the background value will be on both the left and right sides |
|
|
168 |
value <- both[duplicated(both)] |
|
|
169 |
|
|
|
170 |
# Make background value 0, and the other two values 1 and 2 |
|
|
171 |
left_right_mask[left_right_mask == value] <- 0 |
|
|
172 |
num = unique(left_right_mask) |
|
|
173 |
if(1 %in% num & 3 %in% num){ |
|
|
174 |
left_right_mask[left_right_mask==3] <- 2 |
|
|
175 |
} |
|
|
176 |
if(2 %in% num & 3 %in% num){ |
|
|
177 |
left_right_mask[left_right_mask==2] <- 1 |
|
|
178 |
left_right_mask[left_right_mask==3] <- 2 |
|
|
179 |
} |
|
|
180 |
|
|
|
181 |
|
|
|
182 |
# Find coordinates of left and right clusters |
|
|
183 |
coord = sapply(1:2, function(i){ |
|
|
184 |
img = left_right_mask == i |
|
|
185 |
loc = which(as.array(img) == 1, arr.ind = T) |
|
|
186 |
x = median(loc[,1]) |
|
|
187 |
return(x) |
|
|
188 |
}) |
|
|
189 |
if (coord[1] < coord[2]){ |
|
|
190 |
right_mask = left_right_mask == 1 |
|
|
191 |
left_mask = left_right_mask == 2 |
|
|
192 |
} else { |
|
|
193 |
right_mask = left_right_mask == 2 |
|
|
194 |
left_mask = left_right_mask == 1 |
|
|
195 |
} |
|
|
196 |
|
|
|
197 |
|
|
|
198 |
if (verbose) { |
|
|
199 |
message("# Segmenting Lung: Finishing Touches") |
|
|
200 |
} |
|
|
201 |
left_mask = iMath(left_mask, "MC", 8+2*j) |
|
|
202 |
left_mask = iMath(img = left_mask, operation = "GetLargestComponent") |
|
|
203 |
right_mask = iMath(right_mask, "MC", 8+2*j) |
|
|
204 |
right_mask = iMath(img = right_mask, operation = "GetLargestComponent") |
|
|
205 |
left_right_mask = right_mask + left_mask * 2 |
|
|
206 |
left_right_mask[left_right_mask == 3] = 0 |
|
|
207 |
left_right_mask[lung_air_mask == 0] = 0 |
|
|
208 |
|
|
|
209 |
} else{message("# Error: Too many clusters")} |
|
|
210 |
|
|
|
211 |
|
|
|
212 |
if (verbose) { |
|
|
213 |
message("# Resampling Back to Original Image Size") |
|
|
214 |
} |
|
|
215 |
left_right_mask = resampleImage(left_right_mask, |
|
|
216 |
resampleParams = dim(orig_img), |
|
|
217 |
useVoxels = TRUE, |
|
|
218 |
interpType = 1) |
|
|
219 |
return(left_right_mask) |
|
|
220 |
|
|
|
221 |
|
|
|
222 |
} |
|
|
223 |
|
|
|
224 |
|
|
|
225 |
|
|
|
226 |
|
|
|
227 |
|
|
|
228 |
|
|
|
229 |
|
|
|
230 |
|
|
|
231 |
|
|
|
232 |
|
|
|
233 |
|
|
|
234 |
|
|
|
235 |
|
|
|
236 |
|
|
|
237 |
|
|
|
238 |
|
|
|
239 |
|
|
|
240 |
|
|
|
241 |
|
|
|
242 |
|
|
|
243 |
|
|
|
244 |
|
|
|
245 |
|
|
|
246 |
|
|
|
247 |
|