@@ -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
6770class 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