|
a |
|
b/ants/segmentation/functional_lung_segmentation.py |
|
|
1 |
|
|
|
2 |
__all__ = ['functional_lung_segmentation'] |
|
|
3 |
|
|
|
4 |
import ants |
|
|
5 |
|
|
|
6 |
def functional_lung_segmentation(image, |
|
|
7 |
mask=None, |
|
|
8 |
number_of_iterations=2, |
|
|
9 |
number_of_atropos_iterations=5, |
|
|
10 |
mrf_parameters="[0.7,2x2x2]", |
|
|
11 |
number_of_clusters = 6, |
|
|
12 |
cluster_centers = None, |
|
|
13 |
bias_correction = "n4", |
|
|
14 |
verbose=True): |
|
|
15 |
|
|
|
16 |
""" |
|
|
17 |
Ventilation-based segmentation of hyperpolarized gas lung MRI. |
|
|
18 |
|
|
|
19 |
Lung segmentation into classes based on ventilation as described in |
|
|
20 |
this paper: |
|
|
21 |
|
|
|
22 |
https://pubmed.ncbi.nlm.nih.gov/21837781/ |
|
|
23 |
|
|
|
24 |
Arguments |
|
|
25 |
--------- |
|
|
26 |
image : ANTs image |
|
|
27 |
Input proton-weighted MRI. |
|
|
28 |
|
|
|
29 |
mask : ANTs image |
|
|
30 |
Mask image designating the region to segment. 0/1 = background/foreground. |
|
|
31 |
|
|
|
32 |
number_of_iterations : integer |
|
|
33 |
Number of Atropos <--> bias correction iterations (outer loop). |
|
|
34 |
|
|
|
35 |
number_of_atropos_iterations : integer |
|
|
36 |
Number of Atropos iterations (inner loop). If number_of_atropos_iterations = 0, |
|
|
37 |
this is equivalent to K-means with no MRF priors. |
|
|
38 |
|
|
|
39 |
mrf_parameters : string |
|
|
40 |
Parameters for MRF in Atropos. |
|
|
41 |
|
|
|
42 |
number_of_clusters : integer |
|
|
43 |
Number of tissue classes. |
|
|
44 |
|
|
|
45 |
cluster_centers: array or tuple |
|
|
46 |
Initialization centers for k-means. |
|
|
47 |
|
|
|
48 |
bias_correction: string |
|
|
49 |
Apply "n3", "n4", or no bias correction (default = "n4"). |
|
|
50 |
|
|
|
51 |
verbose : boolean |
|
|
52 |
Print progress to the screen. |
|
|
53 |
|
|
|
54 |
Returns |
|
|
55 |
------- |
|
|
56 |
Dictionary with segmentation image, probability images, and |
|
|
57 |
processed image. |
|
|
58 |
|
|
|
59 |
Example |
|
|
60 |
------- |
|
|
61 |
>>> import ants |
|
|
62 |
>>> image = ants.image_read(ants.get_data("mni")).resample_image((4,4,4)) |
|
|
63 |
>>> mask = image.get_mask() |
|
|
64 |
>>> seg = ants.functional_lung_segmentation(image, mask, verbose=True, |
|
|
65 |
number_of_iterations=1, |
|
|
66 |
number_of_clusters=2, |
|
|
67 |
number_of_atropos_iterations=1) |
|
|
68 |
""" |
|
|
69 |
|
|
|
70 |
if image.dimension != 3: |
|
|
71 |
raise ValueError("Function only works for 3-D images.") |
|
|
72 |
|
|
|
73 |
if mask is None: |
|
|
74 |
raise ValueError("Mask is missing.") |
|
|
75 |
|
|
|
76 |
if number_of_iterations < 1: |
|
|
77 |
raise ValueError("number_of_iterations must be >= 1.") |
|
|
78 |
|
|
|
79 |
def generate_pure_tissue_n4_weight_mask(probability_images): |
|
|
80 |
number_of_probability_images = len(probability_images) |
|
|
81 |
|
|
|
82 |
pure_tissue_mask = probability_images[0] * 0 |
|
|
83 |
for i in range(number_of_probability_images): |
|
|
84 |
negation_image = probability_images[0] * 0 + 1 |
|
|
85 |
for j in range(number_of_probability_images): |
|
|
86 |
if i != j: |
|
|
87 |
negation_image = negation_image * (probability_images[j] * -1.0 + 1.0) |
|
|
88 |
pure_tissue_mask = pure_tissue_mask + negation_image * probability_images[i] |
|
|
89 |
return(pure_tissue_mask) |
|
|
90 |
|
|
|
91 |
weight_mask = None |
|
|
92 |
|
|
|
93 |
# This is a multiplicative factor for both the image |
|
|
94 |
# and the cluster centers which shouldn't effect the |
|
|
95 |
# user. Otherwise, we'll get a singular covariance |
|
|
96 |
# complaint from Atropos. |
|
|
97 |
image_scale_factor = 1000.0 |
|
|
98 |
|
|
|
99 |
number_of_atropos_n4_iterations = number_of_iterations |
|
|
100 |
for i in range(number_of_atropos_n4_iterations): |
|
|
101 |
if verbose == True: |
|
|
102 |
print("Outer iteration: ", i, " out of ", number_of_atropos_n4_iterations) |
|
|
103 |
|
|
|
104 |
preprocessed_image = ants.image_clone(image) |
|
|
105 |
|
|
|
106 |
quantiles = (preprocessed_image.quantile(0.0), preprocessed_image.quantile(0.995)) |
|
|
107 |
preprocessed_image[preprocessed_image < quantiles[0]] = quantiles[0] |
|
|
108 |
preprocessed_image[preprocessed_image > quantiles[1]] = quantiles[1] |
|
|
109 |
|
|
|
110 |
if verbose == True: |
|
|
111 |
print("Outer: bias correction.") |
|
|
112 |
|
|
|
113 |
if bias_correction.lower() == "n4": |
|
|
114 |
preprocessed_image = ants.n4_bias_field_correction(preprocessed_image, |
|
|
115 |
mask=mask, shrink_factor=2, convergence={'iters': [50, 50, 50, 50], 'tol': 0.0000000001}, |
|
|
116 |
spline_param=200, return_bias_field=False, weight_mask=weight_mask, verbose=verbose) |
|
|
117 |
elif bias_correction.lower == "n3": |
|
|
118 |
preprocessed_image = ants.n3_bias_field_correction(preprocessed_image, downsample_factor=2) |
|
|
119 |
|
|
|
120 |
preprocessed_image = ((preprocessed_image - preprocessed_image.min()) |
|
|
121 |
/(preprocessed_image.max() - preprocessed_image.min())) |
|
|
122 |
preprocessed_image *= image_scale_factor |
|
|
123 |
|
|
|
124 |
if verbose == True: |
|
|
125 |
print("Outer: Atropos segmentation.") |
|
|
126 |
|
|
|
127 |
atropos_initialization = "Kmeans[" + str(number_of_clusters) + "]" |
|
|
128 |
if cluster_centers is not None: |
|
|
129 |
if len(cluster_centers) != number_of_clusters: |
|
|
130 |
raise ValueError("number_of_clusters should match the vector size of the cluster_centers.") |
|
|
131 |
else: |
|
|
132 |
scaled_cluster_centers = image_scale_factor * cluster_centers |
|
|
133 |
cluster_centers_string = 'x'.join(str(s) for s in set(scaled_cluster_centers)) |
|
|
134 |
atropos_initialization = "Kmeans[" + str(number_of_clusters) + "," + cluster_centers_string + "]" |
|
|
135 |
|
|
|
136 |
posterior_formulation = "Socrates[0]" |
|
|
137 |
if i > 0: |
|
|
138 |
atropos_initialization = atropos_output['probabilityimages'] |
|
|
139 |
posterior_formulation = "Socrates[1]" |
|
|
140 |
|
|
|
141 |
iterations = "[" + str(number_of_atropos_iterations) + ",0]" |
|
|
142 |
atropos_verbose = 0 |
|
|
143 |
if verbose == True: |
|
|
144 |
atropos_verbose = 1 |
|
|
145 |
atropos_output = ants.atropos(preprocessed_image, x=mask, i=atropos_initialization, |
|
|
146 |
m=mrf_parameters, c=iterations, priorweight=0.0, v=atropos_verbose, p=posterior_formulation) |
|
|
147 |
|
|
|
148 |
weight_mask = generate_pure_tissue_n4_weight_mask(atropos_output['probabilityimages'][1:number_of_clusters]) |
|
|
149 |
|
|
|
150 |
masked_segmentation_image = atropos_output['segmentation'] * mask |
|
|
151 |
masked_probability_images = list() |
|
|
152 |
for i in range(len(atropos_output['probabilityimages'])): |
|
|
153 |
masked_probability_images.append(atropos_output['probabilityimages'][i] * mask) |
|
|
154 |
|
|
|
155 |
return_dict = {'segmentation_image' : masked_segmentation_image, |
|
|
156 |
'probability_images' : masked_probability_images, |
|
|
157 |
'processed_image' : (preprocessed_image / image_scale_factor)} |
|
|
158 |
return(return_dict) |