Skip to content

Instantly share code, notes, and snippets.

@davidckatz
Last active September 13, 2019 15:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save davidckatz/3812f9270991b09eef3a1cd45913d3e1 to your computer and use it in GitHub Desktop.
Save davidckatz/3812f9270991b09eef3a1cd45913d3e1 to your computer and use it in GitHub Desktop.
preferred.rotation: orient a three-dimensional shape object so that major axes align with Cartesian planes
INSTRUCTIONS (see file below for R script)
This function conveniently aligns a specimen to anatomical position or a similar orientation chosen by the user, with the midline landmarks flush to the XY-plane. Once computed, the rotation matrix from the aligned specimen (e.g., the consensus configuration) can be used to rotate each of the observations. After all specimens are aligned, differences on the z-axis can be interpreted as transformations of width; the y-axis as of height; and the x-axis as of length. This becomes particularly useful when shape variables are fit in linear models, because it makes the coefficients more interpretable. It can also be useful when generating figures, as it ensures a consistent orientation.
An example of the 3D output is provided in the first Comment to this page.
INPUTS
1. specimen: Landmarks*dimensions matrix of shape to be rotated.
2. mids.end: Last midline landmark in specimen.
3. anterior: Anteriormost landmark point of reference for line that should be made horizontal to x-axis.
4. posterior: Posteriormost landmark point of reference for line that should be made horizontal to x-axis.
5. lowest: A landmark that should be inferior to the horizontal line specified by 3 and 4 when the specimen is properly oriented.
VALUES (returns)
1. flush: Rotated specimen. The midline landmarks will be flush to the xy-plane. The rotation relative to the z-axis will be as determined by inputs 3-5.
2. flush.rm: Dimensions*dimensions rotation matrix that transforms specimen to flush.
NOTES
1. There must be more than two (maybe more than 3) midline landmarks.
2. The midline landmarks must be ordered consecutively in the specimen matrix, and start at row one. That is, 1:mids.end are all midline landmarks, and are the only midline landmarks.
3. Arguments 2-5 can be provided as row numbers (e.g., mids.end=5) or, if the rows have names, as row names (e.g., mids.end = “gn”).
4. If the specimen that is re-oriented is the mean shape from a Generalized Procrustes Analysis, then the Procrustes superimposed observations can be rigidly rotated using the flush.rm output. Computation of Procrustes residuals and analysis of the observations can then proceed in the transformed space.
5. As written, the function only accepts 3D observations.
STEP-THROUGH
1. Minimize deviance between midline landmarks and xy-plane (equivalent to minimizing deviance along the z-axis) while preventing shape reflection (Claude, 2008 p. 62).
2. Force the x-axis to be the antero-posterior anatomical direction.
3. Rotate specimen so that a line from the “anterior” to “posterior” landmarks is horizontal to the x-axis.
4. Force the “lowest” landmark to be inferior to the horizontal line described in 3.
# Instructions for this R script are provided in the file above.
#An example of the 3D output is provided in the first Comment to this page.
preferred.rotation <- function(specimen, mids.end, anterior,
posterior, lowest)
{
# INDEXING
if(is.character(anterior)){anterior <- which(rownames(specimen)==anterior)}
if(is.character(posterior)){posterior <- which(rownames(specimen)==posterior)}
if(is.character(mids.end)){mids.end <- which(rownames(specimen)==mids.end)}
if(is.character(lowest)){lowest <- which(rownames(specimen)==lowest)}
# midline landmark subset
midlines <- specimen[1:mids.end,]
#########################################################################
# REORIENTATIONS
# Identity matrix
id.rm <- diag(ncol(specimen))
# ROTATIONS AND CONDITIONAL ROTATIONS
# 1. primary rotation: minimize z-axis variation for midline landmarks
minz.rm <- eigen(var(midlines))$vectors
minz.shape<-specimen%*%minz.rm
# 2. If reflection, reverse sign on z-axis values.
if(sign(det(minz.rm))==-1) {reflectz.rm <- diag(c(1,1,-1))} else
{reflectz.rm<-id.rm}
reflectz.shape <- minz.shape%*%reflectz.rm
# 3. Ensure X-axis is anatomical antero-posterior.
antpost.test <- (reflectz.shape[anterior,1]-
reflectz.shape[posterior,1])^2 >=
(reflectz.shape[anterior,2]-reflectz.shape[posterior,2])^2
if(!antpost.test){
ap.rads <- 90*(pi/180)
ap.rm <- matrix(c(cos(ap.rads), sin(ap.rads), 0,
-sin(ap.rads),cos(ap.rads), 0,
0,0,1),3,3)} else {ap.rm <- id.rm}
ap.shape <- reflectz.shape%*%ap.rm
# 4. Rotate so that ant and post are horizontal to the X-axis
# compute angle to x-axis (in radians)
hz.rads <- atan((ap.shape[anterior,2]-ap.shape[posterior,2])/
(ap.shape[anterior,1]-ap.shape[posterior,1]))
# rotation matrix
hz.rm <- matrix(c(cos(hz.rads), sin(hz.rads), 0,
-sin(hz.rads), cos(hz.rads), 0,
0,0,1), 3, 3)
hz.shape <- ap.shape%*%hz.rm
# 5. Ensure up is + and down is - on the Y-axis. If not, flip.
updown.test <- hz.shape[lowest,2]<hz.shape[anterior,2]
if(!updown.test){ud.rm<-diag(c(1,-1,-1))} else {ud.rm<-id.rm}
ud.shape <- hz.shape%*%ud.rm
# COMBINE ROTATION MATRICES INTO A SINGLE MATRIX
flush.rm <- minz.rm%*%reflectz.rm%*%ap.rm%*%hz.rm%*%ud.rm
flush<-specimen%*%full.rm
##########################################################################
# OUTPUT
list(flush=flush,
flush.rm=flush.rm)
}
@davidckatz
Copy link
Author

davidckatz commented Sep 10, 2016

Images of a human mandible wireframe after alignment with preferred.rotation.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment