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)