home   >   tutorials   >   stereomorph user guide   >   10 reflecting bilateral landmarks

10 Reflect missing bilateral landmarks

When digitizing landmarks and curves you might not be able to digitize every point on both the left and right side. For objects that have bilateral symmetry you can use pairs of left and right points and points within the midline plane to define the midline plane and then project landmarks that are missing on one side across the midline plane. Even if you have data for both the left and right side you might want to average the sides to create a final shape that has perfect bilateral symmetry. This can be useful for reducing noise or for making subsequent analyses simpler. This section will show how to use the StereoMorph function reflectMissingShapes() to do both of these operations.

If you'd like to try using reflectMissingShapes() with an example dataset you can download this stereo landmark and curve set (10 KB). Unzip the folder's contents into your current R working directory.

1. Begin by loading the StereoMorph library.

# Load the StereoMorph package
library(StereoMorph)

In order for reflectMissingShapes() to recognize which landmarks are left, right, or on the midline, your landmark names will have to follow a particular convention. Landmark names that are right or left should end in "_" followed by either "R" or "L". Capitalization doesn't matter, so "r" or "l" will also work. These can be followed by numbers (e.g. for curve points) but should not be followed by other letters. All landmarks that don't have these endings will be treated as midline landmarks. For example, here are examples of acceptable landmark names to indicate a side:

jugal_upperbeak_L
jugal_upperbeak_r
jugal_upperbeak_R0202

2. The reflectMissingShapes() function takes two main inputs:

  • shapes: the shape data to be reflected
  • file: where the reflected shapes should be saved

But these two inputs allow quite a bit of flexibility. For example, the function call to reflect missing landmarks for one 3D shape file looks like this:

# Reflect missing shape data
rms <- reflectMissingShapes(shapes='Shapes 3D/bubo_virginianus_FMNH488595.txt', 
	file='Reflected/bubo_virginianus_FMNH488595.txt', average=TRUE)

If the directory in "file" doesn't exist, one will automatically be created. Note that average is set to TRUE (default is FALSE). This will average all of the bilateral landmarks such that all midline landmarks are within a single, midline plane and all left and right landmarks are reflected perfectly across the midline plane.

If the input to reflectMissingShapes() is a single set of shapes, the function will output the reflected shape data as a shape data structure. As shown in Reading shape data, you can access the reflected landmarks using the "$" operator:

# Print reflected landmarks
rms$landmarks

2. You can run reflectMissingShapes() over multiple files at once by making shapes and file vectors of corresponding file paths:

# Set shape files to reflect
specimen <- c('bubo_virginianus_FMNH488595.txt', 
   'psittacus_erithacus_FMNH312899_a1.txt')

# Reflect missing shape data for specified files
rms <- reflectMissingShapes(shapes=paste0('Shapes 3D/', specimen), 
   file=paste0('Reflected/', specimen), average=TRUE)

Currently, for this input type the function returns NULL.

3. To run reflectMissingShapes() over all the files in a particular folder, use paths to folders as input rather than to particular files. If a folder input to file doesn't exist then one will be created.

# Reflect missing shape data for all files in a folder
rms <- reflectMissingShapes(shapes='Shapes 3D', file='Reflected', 
   average=TRUE)

You can also input a shape structure rather than a shape file.

# Read shape file
shapes <- readShapes(file='Shapes 3D/bubo_virginianus_FMNH488595.txt')

# Reflect missing shape data for shape data
rms <- reflectMissingShapes(shapes=shapes, average=TRUE)

file can be omitted, in which case the function will not create a file for the reflected shape data and will only return a shapes list. file can only be omitted for single shape set input to reflectMissingShapes().

4. Lastly, print.progress can be set to TRUE in order to view the reflection errors:

# Reflect missing shapes and print error summary
rms <- reflectMissingShapes(shapes=shapes, average=TRUE, 
   print.progress=TRUE)

5. To visualize the reflected shape data, you can use the plot3d() function as shown previously in Visualizing shape data.

# Load the rgl package
library(rgl)

# Read shapes
shapes <- readShapes(file='Reflected/bubo_virginianus_FMNH488595.txt')

# Get landmarks
lm <- shapes$landmarks

# Set landmark range
r <- apply(lm, 2, 'max') - apply(lm, 2, 'min')

# Plot landmarks
plot3d(lm, aspect=c(r/r[3]), size=7)

# Plot curves
lapply(shapes$curves, plot3d, size=4, col='lightblue', add=TRUE)
Plot of reflected landmarks and curves using plot3d() in the rgl package.