Estimating Center of Mass

library(gdi)
#> Loading required package: jpeg
#> Loading required package: png

Basic Workflow for COM Estimation

This vignette demonstrates use of the gdi package for estimating the position of the center of mass (COM) of an animal body.

As with volume estimation, the process starts with aligned silhouette images representing at least two views, which we measure and then perform gdi() on (for details, see vignette: gdi).

For this example, we will import the sample images provided with the package:

fdir <- system.file(package="gdi")

measurements_lateral <- measuresil(file.path(fdir,"exdata","lat.png"), return="all") # using the "return" parameter, we can make measuresil output a data.frame containing the centers on the y axis
measurements_dorsal <- measuresil(file.path(fdir,"exdata","dors.png")) #because our organism is bilaterally symmetric, we are not interested in the location of the COM on the transverse axis, and can therefore disregard recording centers for the dorsal view

We now have a data.frame containing the respective y centers and the diameters, which should look like this:

knitr::kable(head(measurements_lateral, 10))
diameter center
0 NaN
0 NaN
0 NaN
11 809.0
17 809.0
21 809.0
26 809.5
30 809.5
32 809.5
36 809.5

To perform a graphic double integration to get the volumes, simply do:

gdi(measurements_lateral, measurements_dorsal, scale=100, return="all")->gdiresults # the parameter setting for "return" makes gdi() return a data.frame with all individual segment volumes, rather than only a single volume for the entire shape

We now have a data.frame containing the respective segment volumes and y-axis centers, rather than the single numeric containing the total volume for the same shape (40 l). Our resulting data.frame should look like this:

knitr::kable(head(gdiresults))
ydiam_raw zdiam_raw ydiam_scaled zdiam_scaled slice_length x_center y_center A V
0 0 0.00 0.00 0.01 0.5 NaN 0.0000000 0.0000000
0 0 0.00 0.00 0.01 1.5 NaN 0.0000000 0.0000000
0 0 0.00 0.00 0.01 2.5 NaN 0.0000000 0.0000000
11 8 0.11 0.08 0.01 3.5 809 0.0069115 0.0000691
17 12 0.17 0.12 0.01 4.5 809 0.0160221 0.0001602
21 15 0.21 0.15 0.01 5.5 809 0.0247400 0.0002474

We can now pass this data.frame on to the functions hCOM() and vCOM(), for estimating the horizontal and vertical COM position, calculated from the x and y coordinates for the segments, weighted by their respective volumes:

hCOM(gdiresults)
#> [1] 1005.173
vCOM(gdiresults)
#> [1] 795.6165

For now, these methods only work for the “raw” method of gdi(), i.e. treating each segment as an elliptical prism. They can, however, also be used with manually supplied vectors with segment COM positions, if desired. We will cover the automatic way, using the outputs of gdi() and measuresil() here, and assume a silhouette with sufficient resolution to make simple prismatic approximation accurate.

So the coordinates, in pixels, of the estimated center of mass of the shape are approximately 1005 horizontally, and 796 vertically.

We can visualize the results using the function plot_sil():

plot_sil(measurements_lateral, asp=1)
points(hCOM(gdiresults),vCOM(gdiresults))
abline(v=hCOM(gdiresults), lty=2)
abline(h=vCOM(gdiresults), lty=2)

Plotted silhouette, with horizontal and vertical COM position marked.

In addition, the functions have options for supplying a vector of volumes to be subtracted (e.g. internal airspaces, parameter “subtract”) or of segment densities (parameter “densities”) to use in calculation of COM position.

More Complex Shapes

As with volume estimation, protruding elements, such as limbs, are best estimated separately and then combined as a final step.

hindlimb_lateral <- measuresil(file.path(fdir,"exdata","hl.png"),align="v", return="all")
forelimb_lateral <- measuresil(file.path(fdir,"exdata","fl.png"), align="v", return="all")

hl<-gdi(hindlimb_lateral,0.7*hindlimb_lateral,scale=100, return="all")
fl<-gdi(forelimb_lateral, 0.7*forelimb_lateral, scale=100, return="all")

Here we need to keep in mind that the orientation of the different elements is not the same. Since we are measuring the silhouettes of the limbs as vertically aligned, we have to use vCOM() instead of hCOM() and hCOM() instead of vCOM(). Moreover, care needs to be taken with pixel coordinates, as these are measured from top down in an image, but from bottom up in plots.

Hence, it is recommended to use the built-in plotting function to verify plausibility of results:

plot_sil(measurements_lateral, asp=1)
plot_sil(forelimb_lateral, flip=T, add=T)#for silhouettes that were aligned, but digitized with align="v", we need to specify flip=TRUE here
plot_sil(hindlimb_lateral, flip=T, add=T)

points(hCOM(gdiresults),vCOM(gdiresults))#plot COM of axial segment
points(vCOM(fl),hCOM(fl,align="v"))#plot COM of forelimb segment
points(vCOM(hl),hCOM(hl,align="v"))#plot COM of forelimb segment

Plotted silhouette, including limbs and individual segment COM positions

As we can see, our COM positions seem to be correctly aligned. As you can see we additionally specify align=“v” with hCOM for the limb segments, because the horizontal values of the vertically aligned silhouette are measured from the top down. If we want to know the overall COM position, we can now simply take a weighted mean of all the segments. For this we need segment volumes as a weighting factor, which we can easily calculate:

gdi(hindlimb_lateral,0.7*hindlimb_lateral,scale=100)->hindlimbV
gdi(forelimb_lateral, 0.7*forelimb_lateral, scale=100)->forelimbV
gdi(measurements_lateral, measurements_dorsal, scale=100, method="raw")->axialV

We can now create a table of individual segment volumes and COM positions:

volumes<-c(axialV, forelimbV, hindlimbV)
x_COM<-c(hCOM(gdiresults), vCOM(fl), vCOM(hl))
y_COM<-c(vCOM(gdiresults), hCOM(fl, align="v"), hCOM(hl,align="v"))
#> [1] "converted from top-down measurement"
#> [1] "converted from top-down measurement"
knitr::kable(data.frame(volumes,x_COM,y_COM, row.names=c("axial_total","forelimb","hindlimb")))
volumes x_COM y_COM
axial_total 40.2567743 1005.1733 795.6165
forelimb 0.3791994 648.5819 581.6615
hindlimb 0.7045106 1058.6803 522.9656

Finally, overall COM is calculated as the weighted mean of all segment values:

weighted.mean(x_COM,w=volumes*c(1,2,2))#horizontal COM position
#> [1] 1000.576
weighted.mean(y_COM,w=volumes*c(1,2,2))#vertical COM position
#> [1] 782.7362
plot_sil(measurements_lateral, asp=1)
plot_sil(forelimb_lateral, flip=T, add=T)#for silhouettes that were aligned, but digitized with align="v", we need to specify flip=TRUE here
plot_sil(hindlimb_lateral, flip=T, add=T)

points(weighted.mean(x_COM,w=volumes*c(1,2,2)), weighted.mean(y_COM,w=volumes*c(1,2,2)), pch=16)
abline(v=weighted.mean(x_COM,w=volumes*c(1,2,2)), lty=2)
abline(h=weighted.mean(y_COM,w=volumes*c(1,2,2)), lty=2)

Plotted silhouette with overall COM position highlighted

It should be noted that precision with vertical COM position estimation is limited compared to the horizontal position, as differences in density can be taken into account to the nearest pixel for the latter, while for the former the centroid of each (elliptical or superelliptical) cross-section is calculated, irrespective of heterogeneous densities or differences in internal composition throughout the structure.