From 8aade1d27a8fdd748ad9a283e3dc270559d4b61f Mon Sep 17 00:00:00 2001 From: Gregory Jefferis Date: Tue, 10 Oct 2023 00:45:18 +0100 Subject: [PATCH 1/2] WIP first version of tps speedup * separate computation of tps xform from applying it * memoise as well so it only runs once * much faster for larger/unstable tps pointsets --- R/xform.R | 11 +++++++++-- man/tpsreg.Rd | 12 +++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/R/xform.R b/R/xform.R index 4a766445..c0b9ba84 100644 --- a/R/xform.R +++ b/R/xform.R @@ -436,12 +436,19 @@ tpsreg<-function(sample, reference, ...){ #' @param points The 3D points to transform #' @param swap Whether to change the direction of registration (default of #' \code{NULL} checks if reg has a \code{attr('swap'=TRUE)}) otherwise +#' @param FallBackToAffine,na.action Not applicable to this function #' @export -xformpoints.tpsreg <- function(reg, points, swap=NULL, ...){ +xformpoints.tpsreg <- function(reg, points, swap=NULL, threads=1, FallBackToAffine = NULL, na.action = NULL, ...){ if(isTRUE(swap) || isTRUE(attr(reg, 'swap'))) { tmp=reg$refmat reg$refmat=reg$tarmat reg$tarmat=tmp } - do.call(Morpho::tps3d, c(list(x=points), reg, list(...))) + coeff <- compute_tps(x = reg$tarmat, y = reg$refmat, threads=threads, ...) + Morpho::applyTransform(points, coeff, threads=threads, ...) } + +compute_tps <- memoise::memoise(function(x,y,threads=1, ...){ + Morpho::computeTransform(x, y, type = "tps", threads=threads, ...) +}) + diff --git a/man/tpsreg.Rd b/man/tpsreg.Rd index 2ae39c72..43a3cf54 100644 --- a/man/tpsreg.Rd +++ b/man/tpsreg.Rd @@ -7,7 +7,15 @@ \usage{ tpsreg(sample, reference, ...) -\method{xformpoints}{tpsreg}(reg, points, swap = NULL, ...) +\method{xformpoints}{tpsreg}( + reg, + points, + swap = NULL, + threads = 1, + FallBackToAffine = NULL, + na.action = NULL, + ... +) } \arguments{ \item{sample, reference}{Matrices defining the sample (or floating) and @@ -21,6 +29,8 @@ reference (desired target after transformation) spaces. See details.} \item{swap}{Whether to change the direction of registration (default of \code{NULL} checks if reg has a \code{attr('swap'=TRUE)}) otherwise} + +\item{FallBackToAffine, na.action}{Not applicable to this function} } \description{ \code{tpsreg} creates an object encapsulating a thin plate spine From da771b07de6b4e0ba5cc1f034cb24532fcd99e11 Mon Sep 17 00:00:00 2001 From: Gregory Jefferis Date: Tue, 16 Jan 2024 11:19:54 +0000 Subject: [PATCH 2/2] switch to precomputing tps registration * => change in tpsreg class * will occupy more space - wondering if memoisation might still be a better fit --- R/xform.R | 28 +++++++++++++++++----------- man/tpsreg.Rd | 1 - tests/testthat/test-xformpoints.R | 2 +- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/R/xform.R b/R/xform.R index c0b9ba84..178052ab 100644 --- a/R/xform.R +++ b/R/xform.R @@ -423,8 +423,12 @@ mirror.neuronlist<-function(x, subset=NULL, OmitFailures=NA, ...){ #' plot(da2pns.R.L, col='blue', add=TRUE) #' } tpsreg<-function(sample, reference, ...){ - structure(list(refmat=data.matrix(sample), tarmat=data.matrix(reference), ...), + reg=structure(list(refmat=data.matrix(sample), tarmat=data.matrix(reference), ...), class='tpsreg') + # precompute forward and reverse + attr(reg,'fwd')=Morpho::computeTransform(x=reg$tarmat, y=reg$refmat, type = "tps", ...) + attr(reg,'rev')=Morpho::computeTransform(x=reg$refmat, y=reg$tarmat, type = "tps", ...) + reg } #' @description \code{xformpoints.tpsreg} enables \code{\link[nat]{xform}} and @@ -438,17 +442,19 @@ tpsreg<-function(sample, reference, ...){ #' \code{NULL} checks if reg has a \code{attr('swap'=TRUE)}) otherwise #' @param FallBackToAffine,na.action Not applicable to this function #' @export -xformpoints.tpsreg <- function(reg, points, swap=NULL, threads=1, FallBackToAffine = NULL, na.action = NULL, ...){ - if(isTRUE(swap) || isTRUE(attr(reg, 'swap'))) { - tmp=reg$refmat - reg$refmat=reg$tarmat - reg$tarmat=tmp +xformpoints.tpsreg <- function(reg, points, swap=NULL, FallBackToAffine = NULL, na.action = NULL, ...){ + + dir <- ifelse(isTRUE(swap) || isTRUE(attr(reg, 'swap')), 'rev', 'fwd') + trafo <- attr(reg, dir) + if(is.null(trafo)) { + warning("Incomplete thin plate splines registration!\n", + "I am recomputing, but please use `tpsreg()` to make a new registration") + trafo <- if(dir=='fwd') Morpho::computeTransform(x=reg$tarmat, y=reg$refmat, type = "tps", ...) + else + Morpho::computeTransform(x=reg$refmat, y=reg$tarmat, type = "tps", ...) } - coeff <- compute_tps(x = reg$tarmat, y = reg$refmat, threads=threads, ...) - Morpho::applyTransform(points, coeff, threads=threads, ...) + + Morpho::applyTransform(points, trafo, ...) } -compute_tps <- memoise::memoise(function(x,y,threads=1, ...){ - Morpho::computeTransform(x, y, type = "tps", threads=threads, ...) -}) diff --git a/man/tpsreg.Rd b/man/tpsreg.Rd index 43a3cf54..f5c8c08d 100644 --- a/man/tpsreg.Rd +++ b/man/tpsreg.Rd @@ -11,7 +11,6 @@ tpsreg(sample, reference, ...) reg, points, swap = NULL, - threads = 1, FallBackToAffine = NULL, na.action = NULL, ... diff --git a/tests/testthat/test-xformpoints.R b/tests/testthat/test-xformpoints.R index 14cb48c0..dda690df 100644 --- a/tests/testthat/test-xformpoints.R +++ b/tests/testthat/test-xformpoints.R @@ -92,7 +92,7 @@ test_that("Thin plate spine registration", { landmarks=xyz[sids,] #take some points as landmarks landmarkst=scale(xyz[sids,], center = T, scale = rep(1.5, 3)) #now scale the landmarks.. - #compute a transformation with sameple as scaled landmarks and reference as the original landmarks + #compute a transformation with sample as scaled landmarks and reference as the original landmarks mytps=tpsreg(sample=landmarkst, reference = landmarks) kcs20.t=xform(kcs20, mytps)