Morpho: Slide surface semi-landmarks without the actual surfaces

In Biology and Physical Anthropology, sliding semi-landmarks are widely used to estimate the surface shape of a biological structure. The “sliding” part means that (mathematical) homology between specimens is enforced by allowing the coordinates to slide along the surfaces in order to minimize some metric (bending energy, Procrustes distance). As the surface is usually curved, it has to be approximated locally, to allow formulating the minimization as a linear problem. This is done by calulating the tangent plane at each semi-landmark - usually by the plane orthogonal of the surface’s normal at that point. After each iteration, the slided coordinates are reprojected back onto the surface representation (usually a triangular mesh). Based on a user request, I added a procedure to allow sliding without having the actual surface available. The main issue, hereby is to determine the tangent plane. To accomplish this, the normal at each coordinate is computed based on the positions of the 10 nearest neighbours (using Rvcg::vcgUpdateNormals). Based on these normals the tangent planse are calculated as orthogonal complements (Morpho::tangentPlane).

CAVEATS:

Example 1

Estimate the normals from the pointcloud and visualize the resulting tangent planes (Figure 1).

require(Morpho);require(Rvcg)
data(nose)
normalcloud <- vcgUpdateNormals(shortnose.lm)

## visualize normals
require(rgl)
spheres3d(shortnose.lm,col="red",radius=0.2)
plotNormals(normalcloud,lwd=2,long=2)

# visualize tangent planes

## estimate plane vectors from normals
planes <- apply(normalcloud$normals,2,function(x) x <- tangentPlane(x[1:3]))

## small helperfunction to create a mesh with 2 triangles forming a square representing the tangent plane
tan2mesh <- function(x,y,pt) {
    vb <- matrix(1,4,4)
    vb[1:3,1] <- pt[1:3]
    vb[1:3,2] <- pt[1:3]+x
    vb[1:3,3] <- pt[1:3]+y
    vb[1:3,4] <- vb[1:3,3]+x
    cent <- (vb[,4]-vb[,1])/2
    vb <- vb-cent
    it <- matrix(c(1,4,3,1,2,4),3,2)
    out <- list(vb=vb,it=it)
    class(out) <- "mesh3d"
    return(out)
}
## create all the little meshes and concatenate them
planemeshes <- mergeMeshes(lapply(1:nrow(shortnose.lm), function(x) x <- tan2mesh(planes[[x]]$y,planes[[x]]$z,normalcloud$vb[,x])))
## render 
shade3d(planemeshes)

example 1
Fig. 1: Normals and tangents estimated from point cloud.

Example 2

We use the example data included in Morpho and compare the results with and without surfaces. We can see that the result is very close to the one including surfaces (Figure 2) - only the lonely coordinates around the nostrils that are allowed to slide differ as the reprojection onto the surface is missing.

require(Morpho)
data(nose)
## create mesh for longnose
longnose.mesh <- tps3d(shortnose.mesh,shortnose.lm,longnose.lm,threads=1)
meshlist <- list(shortnose.mesh,longnose.mesh)
data <- bindArr(shortnose.lm, longnose.lm, along=3)
dimnames(data)[[3]] <- c("shortnose", "longnose")

## define curves and surface landmarks
fix <- c(1:5,20:21)
outline1 <- c(304:323)
outline2 <- c(604:623)
outlines <- 611:623
outlines <- list(outline1,outline2)
surp <- c(1:623)[-c(fix,outline1,outline2)]
slideWithCurves <- slider3d(data, SMvector=fix, deselect=TRUE, surp=surp, meshlist=meshlist,iterations=3,outlines=outlines)

## An example with sliding without meshes by estimating the surface from the
## semi-landmarks

slideWithCurvesNoMeshes <- slider3d(data, SMvector=fix, deselect=TRUE, surp=surp,iterations=3,outlines=outlines)

## compare it to the data with surfaces for the 1st specimen
deformGrid3d(slideWithCurves$dataslide[,,1],slideWithCurvesNoMeshes$dataslide[,,1],ngrid = 0)

example 1
Fig. 2: Differences between sliding with actual and with approximated surface. Red: slided along surface. Green: slided along approximated surface.