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:
system.file(package="gdi")
fdir <-
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_lateral <- 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 measurements_dorsal <-
We now have a data.frame containing the respective y centers and the diameters, which should look like this:
::kable(head(measurements_lateral, 10)) knitr
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:
::kable(head(gdiresults)) knitr
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)
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.
measuresil(file.path(fdir,"exdata","hl.png"),align="v", return="all")
hindlimb_lateral <- measuresil(file.path(fdir,"exdata","fl.png"), align="v", return="all")
forelimb_lateral <-
gdi(hindlimb_lateral,0.7*hindlimb_lateral,scale=100, return="all")
hl<-gdi(forelimb_lateral, 0.7*forelimb_lateral, scale=100, return="all") fl<-
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
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:
c(axialV, forelimbV, hindlimbV)
volumes<-c(hCOM(gdiresults), vCOM(fl), vCOM(hl))
x_COM<-c(vCOM(gdiresults), hCOM(fl, align="v"), hCOM(hl,align="v"))
y_COM<-#> [1] "converted from top-down measurement"
#> [1] "converted from top-down measurement"
::kable(data.frame(volumes,x_COM,y_COM, row.names=c("axial_total","forelimb","hindlimb"))) knitr
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)
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.