[05a195]: / lung.py

Download this file

156 lines (116 with data), 5.6 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# Copyright 2020 University of New South Wales, University of Sydney, Ingham Institute
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import SimpleITK as sitk
def detect_holes(img, lower_threshold=-10000, upper_threshold=-400):
"""
Detect all (air) holes in given image. Default threshold values given are defined for CT.
Args:
img (sitk.Image): The image in which to detect holes.
lower_threshold (int, optional): The lower threshold of intensities. Defaults to -10000.
upper_threshold (int, optional): The upper threshold of intensities. Defaults to -400.
Returns:
label_image: image containing labels of all holes detected
labels: a list of holes detected and some of their properties
"""
# Threshold to get holes
btif = sitk.BinaryThresholdImageFilter()
btif.SetInsideValue(1)
btif.SetOutsideValue(0)
btif.SetLowerThreshold(lower_threshold)
btif.SetUpperThreshold(upper_threshold)
holes = btif.Execute(img)
# Get connected components
ccif = sitk.ConnectedComponentImageFilter()
label_image = ccif.Execute(holes)
labels = []
lssif = sitk.LabelShapeStatisticsImageFilter()
lssif.Execute(label_image)
for region in range(1, ccif.GetObjectCount()):
label = {
"label": region,
"phys_size": lssif.GetPhysicalSize(region),
"elongation": lssif.GetElongation(region),
"roundness": lssif.GetRoundness(region),
"perimeter": lssif.GetPerimeter(region),
"flatness": lssif.GetFlatness(region),
}
labels.append(label)
# Sort by size
labels = sorted(labels, key=lambda i: i["phys_size"], reverse=True)
return label_image, labels
def get_external_mask(label_image, labels, kernel_radius=5):
"""Get a External mask given a label image and labels computed with detect_holes
Args:
label_image (sitk.Image): Label image from detect_holes
labels (list): List of labels from detect_holes
kernel_radius (int, optional): The width of the radius used by binary close. Defaults to 5.
Returns:
sitk.Image: External mask
"""
bmcif = sitk.BinaryMorphologicalClosingImageFilter()
bmcif.SetKernelType(sitk.sitkBall)
bmcif.SetKernelRadius(kernel_radius)
# Save the largest as the external contour
external_mask = sitk.BinaryThreshold(label_image, labels[0]["label"], labels[0]["label"])
external_mask = bmcif.Execute(external_mask)
return external_mask
def get_lung_mask(label_image, labels, kernel_radius=2):
"""Get a Lung mask given a label image and labels computed with detect_holes
Args:
label_image (sitk.Image): Label image from detect_holes
labels (list): List of labels from detect_holes
kernel_radius (int, optional): The width of the radius used by binary close. Defaults to 2.
Returns:
sitk.Image: Combined lung mask
"""
# Find potential lung regions (typically the 2nd and 3rd largest regions after the external air)
lung_candidates = []
for idx in range(1, min(5, len(labels))): # Check among top 5 largest regions
if 1.5 < labels[idx]["flatness"] < 3.0 and labels[idx]["phys_size"] > 1000:
lung_candidates.append(labels[idx]["label"])
if len(lung_candidates) == 2: # Stop after finding 2 lung regions
break
if len(lung_candidates) < 2:
print("Warning: Could not detect both lungs!")
return None
bmcif = sitk.BinaryMorphologicalClosingImageFilter()
bmcif.SetKernelType(sitk.sitkBall)
bmcif.SetKernelRadius(kernel_radius)
# Create masks for both lungs and combine them
lung_mask = sitk.BinaryThreshold(label_image, lung_candidates[0], lung_candidates[0])
lung_mask2 = sitk.BinaryThreshold(label_image, lung_candidates[1], lung_candidates[1])
lung_mask = sitk.Or(lung_mask, lung_mask2)
lung_mask = bmcif.Execute(lung_mask)
return lung_mask
def fill_holes(img, label_image, external_mask, lung_mask, fill_value=50):
"""Returns the input image with all holes filled (except for the external and lung holes).
Args:
img (sitk.Image): The image to fill
label_image (sitk.Image): Label image from detect_holes
external_mask (sitk.Image): The external mask of the patient.
lung_mask (sitk.Image): The lung mask of the patient.
fill_value (int): The value to use to fill the holes. Defaults to 50.
Returns:
sitk.Image: The image with holes filled
"""
img_array = sitk.GetArrayFromImage(img)
bdif = sitk.BinaryDilateImageFilter()
bdif.SetKernelType(sitk.sitkBall)
bdif.SetKernelRadius(3)
mask = sitk.BinaryThreshold(label_image, 1, int(img_array.max()))
mask = sitk.Subtract(mask, external_mask)
mask = sitk.Subtract(mask, lung_mask)
mask = bdif.Execute(mask)
mask_array = sitk.GetArrayFromImage(mask)
img_array[mask_array == 1] = fill_value
filled_img = sitk.GetImageFromArray(img_array)
filled_img.CopyInformation(img)
return filled_img