Skip to content

Commit fc55489

Browse files
authored
Merge pull request #410 from ANTsX/LabelReg
ENH: Add label image registration.
2 parents 1b60c5a + 23b73f8 commit fc55489

File tree

3 files changed

+482
-0
lines changed

3 files changed

+482
-0
lines changed

NAMESPACE

+1
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ export(knnSmoothingMatrix)
233233
export(labelClusters)
234234
export(labelGeometryMeasures)
235235
export(labelImageCentroids)
236+
export(labelImageRegistration)
236237
export(labelOverlapMeasures)
237238
export(labelStats)
238239
export(labels2matrix)

R/labelImageRegistration.R

+390
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
#' Perform label image registration.
2+
#'
3+
#' Perform pairwise registration using fixed and moving sets of label
4+
#' images (and, optionally, sets of corresponding intensity images).
5+
#'
6+
#' @param fixedLabelImages A single (or set of) fixed label image(s).
7+
#' @param movingLabelImages A single (or set of) moving label image(s).
8+
#' @param fixedIntensityImages Optional---a single (or set of) fixed
9+
#' intensity image(s).
10+
#' @param movingIntensityImages Optional---a single (or set of) moving
11+
#' intensity image(s).
12+
#' @param fixedMask Defines region for similarity metric calculation
13+
#' in the space of the fixed image.
14+
#' @param movingMask Defines region for similarity metric calculation
15+
#' in the space of the moving image.
16+
#' @param typeOfLinearTransform Use label images with the centers of
17+
#' mass to a calculate linear transform of type \code{'rigid'},
18+
#' \code{'similarity'}, \code{'affine'}.
19+
#' @param typeOfTransform Only works with deformable-only transforms,
20+
#' specifically the family of \code{antsRegistrationSyN*[so]} or
21+
#' \code{antsRegistrationSyN*[bo]} transforms. See 'type_of_transform'
22+
#' in \code{antsRegistration}.
23+
#' @param labelImageWeighting Float or vector of floats giving the relative
24+
#' weighting for the label images.
25+
#' @param outputPrefix String definining the output prefix for the filenames
26+
#' of the output transform files.
27+
#' @param randomSeed Definition for deformable registration.
28+
#' @param verbose Print progress to the screen.
29+
#' @return outputs a list containing:
30+
#' \itemize{
31+
#' \item{fwdtransforms: }{Transforms to move from moving to fixed image.}
32+
#' \item{invtransforms: }{Transforms to move from fixed to moving image.}
33+
#' }
34+
#' Output of 1 indicates failure.
35+
#' @author Tustison NJ
36+
#' @examples
37+
#' \dontrun{
38+
#' r16 <- antsImageRead( getANTsRData( "r16" ) )
39+
#' r16Seg1 <- thresholdImage( r16, "Kmeans", 3 ) - 1
40+
#' r16Seg2 <- thresholdImage( r16, "Kmeans", 5 ) - 1
41+
#' r64 <- antsImageRead( getANTsRData( "r64" ) )
42+
#' r64Seg1 <- thresholdImage( r64, "Kmeans", 3 ) - 1
43+
#' r64Seg2 <- thresholdImage( r64, "Kmeans", 5 ) - 1
44+
#' reg <- labelImageRegistration( list( r16Seg1, r16Seg2 ),
45+
#' list( r64Seg1, r64Seg2 ),
46+
#' fixedIntensityImages = r16,
47+
#' movingIntensityImages = r64,
48+
#' typeOfLinearTransform = 'affine',
49+
#' typeOfTransform = 'antsRegistrationSyNQuick[bo]',
50+
#' labelImageWeighting = c( 1.0, 2.0 ),
51+
#' verbose = TRUE )
52+
#' }
53+
#'
54+
#' @export labelImageRegistration
55+
labelImageRegistration <- function( fixedLabelImages, movingLabelImages,
56+
fixedIntensityImages = NULL, movingIntensityImages = NULL,
57+
fixedMask = NULL, movingMask = NULL,
58+
typeOfLinearTransform = 'affine',
59+
typeOfTransform = 'antsRegistrationSyNQuick[so]',
60+
labelImageWeighting = 1.0,
61+
outputPrefix = '',
62+
randomSeed = NULL,
63+
verbose = FALSE )
64+
{
65+
# Preform validation check on the input
66+
67+
if( ANTsR::is.antsImage( fixedLabelImages ) )
68+
{
69+
fixedLabelImages <- list( antsImageClone( fixedLabelImages ) )
70+
}
71+
if( ANTsR::is.antsImage( movingLabelImages ) )
72+
{
73+
movingLabelImages <- list( antsImageClone( movingLabelImages ) )
74+
}
75+
76+
if( length( fixedLabelImages ) != length( movingLabelImages ) )
77+
{
78+
stop( "The number of fixed and moving label images do not match." )
79+
}
80+
81+
if( ! is.null( fixedIntensityImages ) || ! is.null( movingIntensityImages ) )
82+
{
83+
if( ANTsR::is.antsImage( fixedIntensityImages ) )
84+
{
85+
fixedIntensityImages <- list( antsImageClone( fixedIntensityImages ) )
86+
}
87+
if( ANTsR::is.antsImage( movingIntensityImages ) )
88+
{
89+
movingIntensityImages <- list( antsImageClone( movingIntensityImages ) )
90+
}
91+
if( length( fixedIntensityImages ) != length( movingIntensityImages ) )
92+
{
93+
stop( "The number of fixed and moving intensity images do not match." )
94+
}
95+
}
96+
97+
labelImageWeights <- labelImageWeighting
98+
if( is.numeric( labelImageWeighting ) && ! is.vector( labelImageWeighting ) )
99+
{
100+
labelImageWeights <- rep( labelImageWeighting, length( fixedLabelImages ) )
101+
} else {
102+
if( length( fixedLabelImages ) != length( labelImageWeights ) )
103+
{
104+
stop( paste( "The length of labelImageWeights must",
105+
"match the number of label image pairs." ) )
106+
}
107+
}
108+
109+
imageDimension <- fixedLabelImages[[1]]@dimension
110+
111+
if( outputPrefix == '' || is.null( outputPrefix ) || length( outputPrefix ) == 0 )
112+
{
113+
outputPrefix <- tempfile()
114+
}
115+
116+
allowableLinearTransforms <- c( 'rigid', 'similarity', 'affine' )
117+
if( ! typeOfLinearTransform %in% allowableLinearTransforms )
118+
{
119+
stop( "Unrecognized linear transform." )
120+
}
121+
122+
doDeformable <- FALSE
123+
if( ! is.null( typeOfTransform ) || length( typeOfTransform ) > 0 )
124+
{
125+
doDeformable <- TRUE
126+
}
127+
128+
commonLabelIds <- list()
129+
totalNumberOfLabels <- 0
130+
for( i in seq.int( length( fixedLabelImages ) ) )
131+
{
132+
fixedLabelGeoms <- labelGeometryMeasures( fixedLabelImages[[i]] )
133+
fixedLabelIds <-fixedLabelGeoms$Label
134+
movingLabelGeoms <- labelGeometryMeasures( movingLabelImages[[i]] )
135+
movingLabelIds <- movingLabelGeoms$Label
136+
commonLabelIds[[i]] <- intersect( fixedLabelIds, movingLabelIds )
137+
totalNumberOfLabels = totalNumberOfLabels + length( commonLabelIds[[i]] )
138+
if( verbose )
139+
{
140+
message( paste0( "Common label ids for image pair ", i, ": ", commonLabelIds[i] ) )
141+
}
142+
if( length( commonLabelIds[[i]]) == 0 )
143+
{
144+
stop( paste0( "No common labels for image pair ", i ) )
145+
}
146+
}
147+
148+
if( verbose )
149+
{
150+
message( "Total number of labels: ", totalNumberOfLabels )
151+
}
152+
153+
##############################
154+
#
155+
# Linear transform
156+
#
157+
##############################
158+
159+
linearXfrm <- NULL
160+
if( ! is.null( typeOfLinearTransform ) )
161+
{
162+
if( verbose )
163+
{
164+
message( "\n\nComputing linear transform.\n" )
165+
}
166+
167+
if( totalNumberOfLabels < 3 )
168+
{
169+
stop( " Number of labels must be >= 3." )
170+
}
171+
172+
fixedCentersOfMass <- array( data = 0, c( totalNumberOfLabels, imageDimension ) )
173+
movingCentersOfMass <- array( data = 0, c( totalNumberOfLabels, imageDimension ) )
174+
175+
deformableMultivariateExtras <- list()
176+
177+
count <- 1
178+
for( i in seq.int( length( commonLabelIds ) ) )
179+
{
180+
for( j in seq.int( length( commonLabelIds[[i]] ) ) )
181+
{
182+
label <- commonLabelIds[[i]][j]
183+
if( verbose )
184+
{
185+
message( paste0( " Finding centers of mass for image pair ", i, ", label ", label ) )
186+
}
187+
fixedSingleLabelImage <- thresholdImage( fixedLabelImages[[i]], label, label, 1, 0 )
188+
fixedCentersOfMass[count,] <- getCenterOfMass( fixedSingleLabelImage )
189+
movingSingleLabelImage <- thresholdImage( movingLabelImages[[i]], label, label, 1, 0 )
190+
movingCentersOfMass[count,] <- getCenterOfMass( movingSingleLabelImage )
191+
count <- count + 1
192+
if( doDeformable )
193+
{
194+
deformableMultivariateExtras[[i]] <- list( "MSQ", fixedSingleLabelImage,
195+
movingSingleLabelImage,
196+
labelImageWeights[i], 0)
197+
}
198+
}
199+
}
200+
201+
linearXfrm <- fitTransformToPairedPoints( movingCentersOfMass,
202+
fixedCentersOfMass,
203+
transformType = typeOfLinearTransform,
204+
verbose = verbose )
205+
206+
linearXfrmFile <- paste0( outputPrefix, "0GenericAffine.mat" )
207+
writeAntsrTransform( linearXfrm, linearXfrmFile )
208+
}
209+
210+
##############################
211+
#
212+
# Deformable transform
213+
#
214+
##############################
215+
216+
if( doDeformable )
217+
{
218+
if( verbose )
219+
{
220+
message( "\n\nComputing deformable transform using images.\n" )
221+
}
222+
223+
doQuick <- FALSE
224+
doRepro <- FALSE
225+
226+
if( grepl( "Quick", typeOfTransform ) )
227+
{
228+
doQuick <- TRUE
229+
} else {
230+
doRepro <- TRUE
231+
randomSeed <- 1
232+
}
233+
234+
intensityMetricParameter <- NULL
235+
splineDistance <- 26
236+
if( grepl( "\\[", typeOfTransform ) && grepl("\\]", typeOfTransform ) )
237+
{
238+
subtypeOfTransform <- strsplit( strsplit( typeOfTransform, "\\[")[[1]][2], "\\]" )[[1]][1]
239+
if( ! ( grepl( "bo", subtypeOfTransform ) || grepl( "so", subtypeOfTransform ) ) )
240+
{
241+
stop( "Only 'so' or 'bo' transforms are available." )
242+
}
243+
if( grepl( ",", subtypeOfTransform ) )
244+
{
245+
subtypeOfTransformArgs <- strsplit( subtypeOfTransform, "," )[[1]]
246+
subtypeOfTransform <- subtypeOfTransformArgs[1]
247+
intensityMetricParameter <- subtypeOfTransformArgs[2]
248+
if( length( subtypeOfTransformArgs ) > 2 )
249+
{
250+
splineDistance <- subtypeOfTransformArgs[3]
251+
}
252+
}
253+
}
254+
255+
synTransform <- "SyN[0.1,3,0]"
256+
if( subtypeOfTransform == "bo" )
257+
{
258+
synTransform <- paste0("BSplineSyN[0.1,", splineDistance, ",0,3]")
259+
}
260+
synStage <- list( "--transform", synTransform )
261+
262+
if( doQuick )
263+
{
264+
synConvergence <- "[100x70x50x0,1e-6,10]"
265+
} else {
266+
synConvergence <- "[100x70x50x20,1e-6,10]"
267+
}
268+
synShrinkFactors <- "8x4x2x1"
269+
synSmoothingSigmas <- "3x2x1x0vox"
270+
271+
synStage <- lappend( synStage, list(
272+
"--convergence", synConvergence,
273+
"--shrink-factors", synShrinkFactors,
274+
"--smoothing-sigmas", synSmoothingSigmas
275+
) )
276+
277+
intensityMetric <- NULL
278+
if( ! is.null( fixedIntensityImages ) && ! is.null( movingIntensityImages ) )
279+
{
280+
if( doQuick )
281+
{
282+
intensityMetric <- "MI"
283+
if( is.null( intensityMetricParameter ) )
284+
{
285+
intensityMetricParameter <- 32
286+
}
287+
}
288+
if( ! doQuick || doRepro )
289+
{
290+
intensityMetric <- "CC"
291+
if( is.null( intensityMetricParameter ) )
292+
{
293+
intensityMetricParameter <- 2
294+
}
295+
}
296+
for( i in seq.int( length( fixedIntensityImages ) ) )
297+
{
298+
synStage <- lappend( synStage, list(
299+
"--metric", paste0( intensityMetric, "[",
300+
antsrGetPointerName( fixedIntensityImages[[i]] ), ",",
301+
antsrGetPointerName( movingIntensityImages[[i]] ), ",",
302+
"1.0,", as.character( intensityMetricParameter ), "]" ) ) )
303+
}
304+
}
305+
for( kk in seq.int( length( deformableMultivariateExtras ) ) )
306+
{
307+
synStage <- lappend( synStage, list(
308+
"--metric", paste0( "MSQ[",
309+
antsrGetPointerName( deformableMultivariateExtras[[kk]][[2]] ), ",",
310+
antsrGetPointerName( deformableMultivariateExtras[[kk]][[3]] ), ",",
311+
as.character( deformableMultivariateExtras[[kk]][[4]] ), ",0.0]" ) ) )
312+
}
313+
314+
args <- NULL
315+
if( is.null( linearXfrm ) )
316+
{
317+
args <- list(
318+
"-d", as.character( imageDimension ),
319+
"-o", outputPrefix )
320+
} else {
321+
args <- list(
322+
"-d", as.character( imageDimension ),
323+
"-r", linearXfrmFile,
324+
"-o", outputPrefix )
325+
}
326+
args <- lappend( args, synStage )
327+
328+
fixedMaskString <- "NA"
329+
if( ! is.null( fixedMask ) )
330+
{
331+
fixedMaskBinary <- antsImageClone( thresholdImage( fixedMask, 0, 0, 0, 1), "unsigned char" )
332+
fixedMaskString <- antsrGetPointerName( fixedMaskBinary )
333+
}
334+
335+
movingMaskString <- "NA"
336+
if( ! is.null( movingMask ) )
337+
{
338+
movingMaskBinary <- antsImageClone( thresholdImage( movingMask, 0, 0, 0, 1), "unsigned char" )
339+
movingMaskString <- antsrGetPointerName( movingMaskBinary )
340+
}
341+
342+
maskOption <- paste0( "[", fixedMaskString, ",", movingMaskString, "]" )
343+
args <- lappend( args, list( "-x", maskOption ) )
344+
345+
args <- lappend( args, list( "--float", "1" ) )
346+
347+
if( ! is.null( randomSeed ) )
348+
{
349+
args <- lappend( "--random-seed", randomSeed )
350+
}
351+
352+
if( verbose )
353+
{
354+
args <- lappend( args, list( "-v", "1" ) )
355+
cat( "antsRegistration", paste( unlist( args ) ), "\n" )
356+
}
357+
args <- .int_antsProcessArguments( c( args ) )
358+
ANTsRCore::AntsRegistration( args )
359+
}
360+
361+
allXfrms <- Sys.glob( paste0( outputPrefix, "*", "[0-9]*" ) )
362+
363+
findInverseWarps <- grep( "[0-9]InverseWarp.nii.gz", allXfrms )
364+
findForwardWarps <- grep( "[0-9]Warp.nii.gz", allXfrms )
365+
366+
if( length( findInverseWarps ) > 0 )
367+
{
368+
fwdtransforms <- c( allXfrms[findForwardWarps[1]], linearXfrmFile )
369+
invtransforms <- c( linearXfrmFile, allXfrms[findInverseWarps[1]] )
370+
} else {
371+
fwdtransforms <- c( linearXfrmFile )
372+
invtransforms <- c( linearXfrmFile )
373+
}
374+
375+
if( verbose )
376+
{
377+
message( "\n\nResulting transforms" )
378+
if( length( findInverseWarps ) > 0 )
379+
{
380+
message( paste0( " fwdtransforms: [", fwdtransforms[1], ", ", fwdtransforms[2], "]" ) )
381+
message( paste0( " invtransforms: [", invtransforms[1], ", ", invtransforms[2], "]" ) )
382+
} else {
383+
message( paste0( " fwdtransforms: [", fwdtransforms[1], "]" ) )
384+
message( paste0( " invtransforms: [", invtransforms[1], "]" ) )
385+
}
386+
}
387+
388+
return( list( fwdtransforms = fwdtransforms,
389+
invtransforms = invtransforms ) )
390+
}

0 commit comments

Comments
 (0)