@@ -31,7 +31,10 @@ class AtroposInputSpec(ANTSCommandInputSpec):
31
31
requires = ["number_of_tissue_classes" ],
32
32
mandatory = True ,
33
33
)
34
- prior_probability_images = InputMultiPath (File (exists = True ))
34
+ kmeans_init_centers = traits .List (traits .Either (traits .Int , traits .Float ), minlen = 1 )
35
+ prior_image = traits .Either (
36
+ File (exists = True ), traits .Str ,
37
+ desc = "either a string pattern (e.g., 'prior%%02d.nii') or an existing vector-image file." )
35
38
number_of_tissue_classes = traits .Int (mandatory = True )
36
39
prior_weighting = traits .Float ()
37
40
prior_probability_threshold = traits .Float (requires = ["prior_weighting" ])
@@ -65,40 +68,80 @@ class AtroposOutputSpec(TraitedSpec):
65
68
66
69
67
70
class Atropos (ANTSCommand ):
68
- """A finite mixture modeling (FMM) segmentation approach with possibilities for
71
+ """
72
+ A multivariate n-class segmentation algorithm.
73
+
74
+ A finite mixture modeling (FMM) segmentation approach with possibilities for
69
75
specifying prior constraints. These prior constraints include the specification
70
76
of a prior label image, prior probability images (one for each class), and/or an
71
77
MRF prior to enforce spatial smoothing of the labels. Similar algorithms include
72
78
FAST and SPM.
73
79
74
80
Examples
75
81
--------
76
-
77
82
>>> from nipype.interfaces.ants import Atropos
78
- >>> at = Atropos()
79
- >>> at.inputs.dimension = 3
80
- >>> at.inputs.intensity_images = 'structural.nii'
81
- >>> at.inputs.mask_image = 'mask.nii'
83
+ >>> at = Atropos(
84
+ ... dimension=3, intensity_images='structural.nii', mask_image='mask.nii',
85
+ ... number_of_tissue_classes=2, likelihood_model='Gaussian', save_posteriors=True,
86
+ ... mrf_smoothing_factor=0.2, mrf_radius=[1, 1, 1], icm_use_synchronous_update=True,
87
+ ... maximum_number_of_icm_terations=1, n_iterations=5, convergence_threshold=0.000001,
88
+ ... posterior_formulation='Socrates', use_mixture_model_proportions=True)
89
+ >>> at.inputs.initialization = 'Random'
90
+ >>> at.cmdline
91
+ 'Atropos --image-dimensionality 3 --icm [1,1] \
92
+ --initialization Random[2] --intensity-image structural.nii \
93
+ --likelihood-model Gaussian --mask-image mask.nii --mrf [0.2,1x1x1] --convergence [5,1e-06] \
94
+ --output [structural_labeled.nii,POSTERIOR_%02d.nii.gz] --posterior-formulation Socrates[1] \
95
+ --use-random-seed 1'
96
+
97
+ >>> at = Atropos(
98
+ ... dimension=3, intensity_images='structural.nii', mask_image='mask.nii',
99
+ ... number_of_tissue_classes=2, likelihood_model='Gaussian', save_posteriors=True,
100
+ ... mrf_smoothing_factor=0.2, mrf_radius=[1, 1, 1], icm_use_synchronous_update=True,
101
+ ... maximum_number_of_icm_terations=1, n_iterations=5, convergence_threshold=0.000001,
102
+ ... posterior_formulation='Socrates', use_mixture_model_proportions=True)
103
+ >>> at.inputs.initialization = 'KMeans'
104
+ >>> at.inputs.kmeans_init_centers = [100, 200]
105
+ >>> at.cmdline
106
+ 'Atropos --image-dimensionality 3 --icm [1,1] \
107
+ --initialization KMeans[2,100,200] --intensity-image structural.nii \
108
+ --likelihood-model Gaussian --mask-image mask.nii --mrf [0.2,1x1x1] --convergence [5,1e-06] \
109
+ --output [structural_labeled.nii,POSTERIOR_%02d.nii.gz] --posterior-formulation Socrates[1] \
110
+ --use-random-seed 1'
111
+
112
+ >>> at = Atropos(
113
+ ... dimension=3, intensity_images='structural.nii', mask_image='mask.nii',
114
+ ... number_of_tissue_classes=2, likelihood_model='Gaussian', save_posteriors=True,
115
+ ... mrf_smoothing_factor=0.2, mrf_radius=[1, 1, 1], icm_use_synchronous_update=True,
116
+ ... maximum_number_of_icm_terations=1, n_iterations=5, convergence_threshold=0.000001,
117
+ ... posterior_formulation='Socrates', use_mixture_model_proportions=True)
82
118
>>> at.inputs.initialization = 'PriorProbabilityImages'
83
- >>> at.inputs.prior_probability_images = ['rc1s1.nii', 'rc1s2.nii']
84
- >>> at.inputs.number_of_tissue_classes = 2
119
+ >>> at.inputs.prior_image = 'BrainSegmentationPrior%02d.nii.gz'
85
120
>>> at.inputs.prior_weighting = 0.8
86
121
>>> at.inputs.prior_probability_threshold = 0.0000001
87
- >>> at.inputs.likelihood_model = 'Gaussian'
88
- >>> at.inputs.mrf_smoothing_factor = 0.2
89
- >>> at.inputs.mrf_radius = [1, 1, 1]
90
- >>> at.inputs.icm_use_synchronous_update = True
91
- >>> at.inputs.maximum_number_of_icm_terations = 1
92
- >>> at.inputs.n_iterations = 5
93
- >>> at.inputs.convergence_threshold = 0.000001
94
- >>> at.inputs.posterior_formulation = 'Socrates'
95
- >>> at.inputs.use_mixture_model_proportions = True
96
- >>> at.inputs.save_posteriors = True
97
122
>>> at.cmdline
98
123
'Atropos --image-dimensionality 3 --icm [1,1] \
99
- --initialization PriorProbabilityImages[2,priors/priorProbImages%02d.nii,0.8,1e-07] --intensity-image structural.nii \
124
+ --initialization PriorProbabilityImages[2,BrainSegmentationPrior%02d.nii.gz,0.8,1e-07] \
125
+ --intensity-image structural.nii --likelihood-model Gaussian --mask-image mask.nii \
126
+ --mrf [0.2,1x1x1] --convergence [5,1e-06] --output [structural_labeled.nii,POSTERIOR_%02d.nii.gz] \
127
+ --posterior-formulation Socrates[1] --use-random-seed 1'
128
+
129
+ >>> at = Atropos(
130
+ ... dimension=3, intensity_images='structural.nii', mask_image='mask.nii',
131
+ ... number_of_tissue_classes=2, likelihood_model='Gaussian', save_posteriors=True,
132
+ ... mrf_smoothing_factor=0.2, mrf_radius=[1, 1, 1], icm_use_synchronous_update=True,
133
+ ... maximum_number_of_icm_terations=1, n_iterations=5, convergence_threshold=0.000001,
134
+ ... posterior_formulation='Socrates', use_mixture_model_proportions=True)
135
+ >>> at.inputs.initialization = 'PriorLabelImage'
136
+ >>> at.inputs.prior_image = 'segmentation0.nii.gz'
137
+ >>> at.inputs.number_of_tissue_classes = 2
138
+ >>> at.inputs.prior_weighting = 0.8
139
+ >>> at.cmdline
140
+ 'Atropos --image-dimensionality 3 --icm [1,1] \
141
+ --initialization PriorLabelImage[2,segmentation0.nii.gz,0.8] --intensity-image structural.nii \
100
142
--likelihood-model Gaussian --mask-image mask.nii --mrf [0.2,1x1x1] --convergence [5,1e-06] \
101
- --output [structural_labeled.nii,POSTERIOR_%02d.nii.gz] --posterior-formulation Socrates[1] --use-random-seed 1'
143
+ --output [structural_labeled.nii,POSTERIOR_%02d.nii.gz] --posterior-formulation Socrates[1] \
144
+ --use-random-seed 1'
102
145
103
146
"""
104
147
@@ -108,20 +151,53 @@ class Atropos(ANTSCommand):
108
151
109
152
def _format_arg (self , opt , spec , val ):
110
153
if opt == "initialization" :
111
- retval = "--initialization %s[%d" % (
112
- val ,
113
- self .inputs .number_of_tissue_classes ,
114
- )
115
- if val == "PriorProbabilityImages" :
116
- _ , _ , ext = split_filename (self .inputs .prior_probability_images [0 ])
117
- retval += (
118
- ",priors/priorProbImages%02d"
119
- + ext
120
- + ",%g" % self .inputs .prior_weighting
121
- )
122
- if isdefined (self .inputs .prior_probability_threshold ):
123
- retval += ",%g" % self .inputs .prior_probability_threshold
124
- return retval + "]"
154
+ n_classes = self .inputs .number_of_tissue_classes
155
+ brackets = ['%d' % n_classes ]
156
+ if val == 'KMeans' and isdefined (self .inputs .kmeans_init_centers ):
157
+ centers = sorted (set (self .inputs .kmeans_init_centers ))
158
+ if len (centers ) != n_classes :
159
+ raise ValueError (
160
+ "KMeans initialization with initial cluster centers requires "
161
+ "the number of centers to match number_of_tissue_classes"
162
+ )
163
+ brackets += ["%g" % c for c in centers ]
164
+
165
+ if val in ("PriorProbabilityImages" , "PriorLabelImage" ):
166
+ if (
167
+ not isdefined (self .inputs .prior_image )
168
+ or not isdefined (self .inputs .prior_weighting )
169
+ ):
170
+ raise ValueError (
171
+ "'%s' initialization requires setting "
172
+ "prior_image and prior_weighting" % val
173
+ )
174
+
175
+ priors_paths = [self .inputs .prior_image ]
176
+ if "%02d" in priors_paths [0 ]:
177
+ if val == "PriorLabelImage" :
178
+ raise ValueError (
179
+ "'PriorLabelImage' initialization does not "
180
+ "accept patterns for prior_image."
181
+ )
182
+ priors_paths = [
183
+ priors_paths [0 ] % i
184
+ for i in range (1 , n_classes + 1 )
185
+ ]
186
+
187
+ if not all ([os .path .exists (p ) for p in priors_paths ]):
188
+ raise FileNotFoundError (
189
+ "One or more prior images do not exist: "
190
+ "%s." % ', ' .join (priors_paths )
191
+ )
192
+ brackets += [self .inputs .prior_image ,
193
+ "%g" % self .inputs .prior_weighting ]
194
+
195
+ if (
196
+ val == "PriorProbabilityImages"
197
+ and isdefined (self .inputs .prior_probability_threshold )
198
+ ):
199
+ brackets .append ("%g" % self .inputs .prior_probability_threshold )
200
+ return "--initialization %s[%s]" % (val , ',' .join (brackets ))
125
201
if opt == "mrf_smoothing_factor" :
126
202
retval = "--mrf [%g" % val
127
203
if isdefined (self .inputs .mrf_radius ):
@@ -151,29 +227,6 @@ def _format_arg(self, opt, spec, val):
151
227
return retval + "]"
152
228
return super (Atropos , self )._format_arg (opt , spec , val )
153
229
154
- def _run_interface (self , runtime , correct_return_codes = [0 ]):
155
- if self .inputs .initialization == "PriorProbabilityImages" :
156
- priors_directory = os .path .join (os .getcwd (), "priors" )
157
- if not os .path .exists (priors_directory ):
158
- os .makedirs (priors_directory )
159
- _ , _ , ext = split_filename (self .inputs .prior_probability_images [0 ])
160
- for i , f in enumerate (self .inputs .prior_probability_images ):
161
- target = os .path .join (
162
- priors_directory , "priorProbImages%02d" % (i + 1 ) + ext
163
- )
164
- if not (
165
- os .path .exists (target )
166
- and os .path .realpath (target ) == os .path .abspath (f )
167
- ):
168
- copyfile (
169
- os .path .abspath (f ),
170
- os .path .join (
171
- priors_directory , "priorProbImages%02d" % (i + 1 ) + ext
172
- ),
173
- )
174
- runtime = super (Atropos , self )._run_interface (runtime )
175
- return runtime
176
-
177
230
def _gen_filename (self , name ):
178
231
if name == "out_classified_image_name" :
179
232
output = self .inputs .out_classified_image_name
0 commit comments