Skip to content

Commit 909301e

Browse files
committed
fix #3178: improve the parsing of Atropos
1 parent b75a7ce commit 909301e

File tree

1 file changed

+111
-58
lines changed

1 file changed

+111
-58
lines changed

nipype/interfaces/ants/segmentation.py

Lines changed: 111 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ class AtroposInputSpec(ANTSCommandInputSpec):
3131
requires=["number_of_tissue_classes"],
3232
mandatory=True,
3333
)
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.")
3538
number_of_tissue_classes = traits.Int(mandatory=True)
3639
prior_weighting = traits.Float()
3740
prior_probability_threshold = traits.Float(requires=["prior_weighting"])
@@ -65,40 +68,80 @@ class AtroposOutputSpec(TraitedSpec):
6568

6669

6770
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
6975
specifying prior constraints. These prior constraints include the specification
7076
of a prior label image, prior probability images (one for each class), and/or an
7177
MRF prior to enforce spatial smoothing of the labels. Similar algorithms include
7278
FAST and SPM.
7379
7480
Examples
7581
--------
76-
7782
>>> 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)
82118
>>> 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'
85120
>>> at.inputs.prior_weighting = 0.8
86121
>>> 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
97122
>>> at.cmdline
98123
'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 \
100142
--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'
102145
103146
"""
104147

@@ -108,20 +151,53 @@ class Atropos(ANTSCommand):
108151

109152
def _format_arg(self, opt, spec, val):
110153
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))
125201
if opt == "mrf_smoothing_factor":
126202
retval = "--mrf [%g" % val
127203
if isdefined(self.inputs.mrf_radius):
@@ -151,29 +227,6 @@ def _format_arg(self, opt, spec, val):
151227
return retval + "]"
152228
return super(Atropos, self)._format_arg(opt, spec, val)
153229

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-
177230
def _gen_filename(self, name):
178231
if name == "out_classified_image_name":
179232
output = self.inputs.out_classified_image_name

0 commit comments

Comments
 (0)