Switch to unified view

a b/ants/segmentation/atropos.py
1
"""
2
Atropos segmentation
3
"""
4
5
6
7
__all__ = ['atropos']
8
9
import os
10
import glob
11
import warnings
12
from tempfile import mktemp
13
14
15
import ants
16
from ants.internal import get_lib_fn, get_pointer_string, process_arguments
17
18
19
def atropos(a, x, i='Kmeans[3]', m='[0.2,1x1]', c='[5,0]',
20
            priorweight=0.25, **kwargs):
21
    """
22
    A finite mixture modeling (FMM) segmentation approach with possibilities
23
    for specifying prior constraints. These prior constraints include the
24
    specification of a prior label image, prior probability images (one for
25
    each class), and/or an MRF prior to enforce spatial smoothing of the
26
    labels. Similar algorithms include FAST and SPM. atropos can also perform
27
    multivariate segmentation if you pass a list of images in: e.g. a=(img1,img2).
28
29
    ANTsR function: `atropos`
30
31
    Arguments
32
    ---------
33
    a : ANTsImage or list/tuple of ANTsImage types
34
        One or more scalar images to segment. If priors are not used,
35
        the intensities of the first image are used to order the classes
36
        in the segmentation output, from lowest to highest intensity. Otherwise
37
        the order of the classes is dictated by the order of the prior images.
38
39
    x : ANTsImage
40
        mask image.
41
42
    i : string
43
        initialization usually KMeans[N] for N classes or a list of N prior
44
        probability images. See Atropos in ANTs for full set of options.
45
46
    m : string
47
        mrf parameters as a string, usually "[smoothingFactor,radius]" where
48
        smoothingFactor determines the amount of smoothing and radius determines
49
        the MRF neighborhood, as an ANTs style neighborhood vector eg "1x1x1"
50
        for a 3D image. The radius must match the dimensionality of the image,
51
        eg 1x1 for 2D and The default in ANTs is smoothingFactor=0.3 and
52
        radius=1. See Atropos for more options.
53
54
    c : string
55
        convergence parameters, "[numberOfIterations,convergenceThreshold]".
56
        A threshold of 0 runs the full numberOfIterations, otherwise Atropos
57
        tests convergence by comparing the mean maximum posterior probability
58
        over the whole region of interest defined by the mask x.
59
60
    priorweight : scalar
61
        usually 0 (priors used for initialization only), 0.25 or 0.5.
62
63
    kwargs : keyword arguments
64
        more parameters, see Atropos help in ANTs
65
66
    Returns
67
    -------
68
    dictionary with the following key/value pairs:
69
        `segmentation`: ANTsImage
70
            actually segmented image
71
        `probabilityimages` : list of ANTsImage types
72
            one image for each segmentation class
73
74
    Example
75
    -------
76
    >>> import ants
77
    >>> img = ants.image_read(ants.get_ants_data('r16'))
78
    >>> img = ants.resample_image(img, (64,64), 1, 0)
79
    >>> mask = ants.get_mask(img)
80
    >>> ants.atropos( a = img, m = '[0.2,1x1]', c = '[2,0]',  i = 'kmeans[3]', x = mask )
81
    >>> seg2 = ants.atropos( a = img, m = '[0.2,1x1]', c = '[2,0]', i = seg['probabilityimages'], x = mask, priorweight=0.25 )
82
    """
83
    probs = mktemp(prefix='antsr', suffix='prob%02d.nii.gz')
84
    tdir = probs.replace(os.path.basename(probs),'')
85
    probsbase = os.path.basename(probs)
86
    searchpattern = probsbase.replace('%02d', '*')
87
88
    ct = 0
89
    if isinstance(i, (list,tuple)) and (len(i) > 1):
90
        while ct < len(i):
91
            probchar = str(ct+1)
92
            if ct < 9:
93
                probchar = '0%s' % probchar
94
            tempfn = probs.replace('%02d', probchar)
95
            ants.image_write(i[ct], tempfn)
96
            ct += 1
97
        i = 'PriorProbabilityImages[%s,%s,%s]' % (str(len(i)), probs, str(priorweight))
98
99
    if isinstance(a, (list,tuple)):
100
        outimg = a[0].clone('unsigned int')
101
    else:
102
        outimg = a.clone('unsigned int')
103
104
    mydim = outimg.dimension
105
    outs = '[%s,%s]' % (get_pointer_string(outimg), probs)
106
    mymask = x.clone('unsigned int')
107
108
    if (not isinstance(a, (list,tuple))) or (len(a) == 1):
109
        myargs = {
110
            'd': mydim,
111
            'a': a,
112
            'm-MULTINAME-0': m,
113
            'o': outs,
114
            'c': c,
115
            'm-MULTINAME-1': m,
116
            'i': i,
117
            'x': mymask
118
        }
119
        for k, v in kwargs.items():
120
            myargs[k] = v
121
122
    elif isinstance(a, (list, tuple)):
123
        if len(a) > 6:
124
            print('more than 6 input images not really supported, using first 6')
125
            a = a[:6]
126
        myargs = {
127
            'd': mydim,
128
            'm-MULTINAME-0': m,
129
            'o': outs,
130
            'c': c,
131
            'm-MULTINAME-1': m,
132
            'i': i,
133
            'x': mymask
134
        }
135
        for aa_idx, aa in enumerate(a):
136
            myargs['a-MULTINAME-%i'%aa_idx] = aa
137
138
    processed_args = process_arguments(myargs)
139
    libfn = get_lib_fn('Atropos')
140
    retval = libfn(processed_args)
141
142
    if retval != 0:
143
        raise Exception(f"Atropos exited with non-zero status {retval}. Run with verbose=1 to see error messages")
144
145
    probsout = glob.glob(os.path.join(tdir,'*'+searchpattern))
146
147
    if probsout is None or len(probsout) == 0:
148
        raise Exception('No Atropos output probability images found. Run with verbose=1 to see error messages')
149
150
    probsout.sort()
151
    probimgs = [ants.image_read(probsout[0])]
152
    for idx in range(1, len(probsout)):
153
        probimgs.append(ants.image_read(probsout[idx]))
154
155
    outimg = outimg.clone('float')
156
    return {'segmentation': outimg,
157
            'probabilityimages': probimgs}