# 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 | sliceL_raw | x_center | y_center | A | V
--- | --- | --- | --- | --- | --- | --- | --- | --- | ---
0 | 0 | 0.00 | 0.00 | 0.01 | 1 | 0.5 | NaN | 0.0000000 | 0.0000000
0 | 0 | 0.00 | 0.00 | 0.01 | 1 | 1.5 | NaN | 0.0000000 | 0.0000000
0 | 0 | 0.00 | 0.00 | 0.01 | 1 | 2.5 | NaN | 0.0000000 | 0.0000000
11 | 8 | 0.11 | 0.08 | 0.01 | 1 | 3.5 | 809 | 0.0069115 | 0.0000691
17 | 12 | 0.17 | 0.12 | 0.01 | 1 | 4.5 | 809 | 0.0160221 | 0.0001602
21 | 15 | 0.21 | 0.15 | 0.01 | 1 | 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.