2

I have a polygon that consists of 1,000 points. Is it possible to calculate the curvature at each point? The polygon originally contains only 13 points:

43748.72 40714.19
43743.99 40716.16
43741.36 40720.19
43740.95 40726.46
43742.67 40729.28
43745.52 40730.97
43748.72 40731.14
43752.86 40729.43
43756.77 40723.24
43757.19 40719.73
43755.27 40716.68
43752.23 40714.76
43748.72 40714.19

Then I use the smooth function in smoothr package for interpolation now that the polygon has 1,000 points and looks like: enter image description here And now I want to calculate curvature at each point. But since this is a closed object, how to actually perform the calculation?

enter image description here

EDIT I finally found a cell with protrusions to test the robustness. The cell looks like: enter image description here

And the corresponding K values are: enter image description here

Indeed, this plot captures two protrusions but can the curvature value be that high? I read a paper and seems like their values are all within 1: enter image description here paper link: https://www.biorxiv.org/content/10.1101/623793v1.full

DigiPath
  • 179
  • 2
  • 10

1 Answers1

5

Your example is not fully reproducible on its own, though it can be made so with reference to your previous question:

library(sf)
library(smoothr)
library(ggplot2)

data <- structure(list(x = c(43740.95, 43741.36, 43742.67, 43743.99, 
           43745.52, 43748.72, 43748.72, 43748.72, 43752.23, 43752.86, 43755.27, 
           43756.77, 43757.19), y = c(40726.46, 40720.19, 40729.28, 40716.16, 
           40730.97, 40714.19, 40731.14, 40714.19, 40714.76, 40729.43, 40716.68, 
           40723.24, 40719.73)), class = "data.frame", row.names = c(NA,  -13L))

smooth_poly <- data %>% 
  st_as_sf(coords=c("x", "y")) %>% 
  st_union() %>% 
  st_convex_hull() %>% 
  smooth(method='spline', n=1000)

smooth_df <- as.data.frame(sf::st_coordinates(smooth_poly))

ggplot(smooth_df, aes(X, Y)) + 
  geom_polygon(alpha = 0, colour = "black", size = 1) +
  coord_equal()

enter image description here

Now we have all the X and Y co-ordinates of the smoothed polygon in a data frame called smooth_df. We can calculate the x and y components of the curvature vectors like this:

dx <- diff(c(smooth_df$X, smooth_df$X[1])) # Distance between x coords with wrap-around
dy <- diff(c(smooth_df$Y, smooth_df$Y[1])) # Distance between y coords with wrap-around
ds <- sqrt(dx^2 + dy^2)                    # Segment size between points
ddx <- dx/ds                               # Ratio of x distance to segment size
ddy <- dy/ds                               # Ratio of y distance to segment size
ds2 <- (ds + c(ds[-1], ds[1]))/2           # Mean segment length either side per point
smooth_df$Cx <- diff(c(ddx, ddx[1]))/ds2   # Change in ddx per unit length
smooth_df$Cy <- diff(c(ddy, ddy[1]))/ds2   # Change in ddy per unit length

These last two are the x and y components of the curvature vectors at each point on the periphery of the polygon. Since this polygon is smooth, the curvatures are small:

head(smooth_df)
#>          X        Y L1 L2         Cx        Cy
#> 1 43748.72 40714.19  1  1 0.02288753 0.1419567
#> 2 43748.67 40714.20  1  1 0.02324771 0.1375075
#> 3 43748.61 40714.21  1  1 0.02356064 0.1332985
#> 4 43748.56 40714.22  1  1 0.02383216 0.1293156
#> 5 43748.51 40714.23  1  1 0.02406747 0.1255458
#> 6 43748.45 40714.24  1  1 0.02427127 0.1219768

Adding these vectors to a plot would just give the inside of the polygon some "fur", since there are so many of them and they are so small, so instead we can show that the directions are correct by plotting a subset of them, magnified by 10, with arrowheads. The arrows should start on the periphery and point directly in the direction of the concavity of the polygon at that point. We should also see longer arrows where the curves are tight, and shorter arrows where the polygon is flat.

smooth_df$Cx_plot <- 10 * smooth_df$Cx + smooth_df$X
smooth_df$Cy_plot <- 10 * smooth_df$Cy + smooth_df$Y

ggplot(smooth_df, aes(X, Y)) + 
  geom_polygon(alpha = 0, colour = "black", size = 1) +
  geom_segment(data = smooth_df[seq(1, nrow(smooth_df), 50),],
               mapping = aes(xend = Cx_plot, yend = Cy_plot), 
               arrow =  arrow(length = unit(0.3, "cm"))) +
  coord_equal()

enter image description here

If you want the curvature as a single dimensional number , you can do:

smooth_df$K <- (ddy * smooth_df$Cx - ddx * smooth_df$Cy)/
               ((ddx^2 + ddy^2)^(3/2))

Which then allows you to plot the curvature as a colour. This will also give negative values when the curve is concave outwards, though I have here just plotted the convex hull again. The red indicates areas with high curvature, the blue areas are flatter.

ggplot(smooth_df, aes(X, Y)) + 
  geom_point(aes(colour = K)) +
  coord_equal() + scale_colour_gradient(low = "skyblue", high = "red")

enter image description here

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
  • Thank you so much for your answer! Really helpful – DigiPath Jun 08 '20 at 00:42
  • Sorry to ask one more question, how to actually use a single number to represent the curvature at the corresponding point? And use negative to represent concave and positive to represent convex, like the figure shows (see question main text) – DigiPath Jun 10 '20 at 21:13
  • @DigiPath this should just be the magnitude of the vector (i.e `sqrt(df$Cx^2 + df$Cy^2)`). It should be positive everywhere for a convex hull. – Allan Cameron Jun 10 '20 at 21:19
  • what about a concave hull? – DigiPath Jun 10 '20 at 21:22
  • @DigiPath a concave hull will have positive and negative values. If the vector is pointing to the right of a segment as it travels clockwise around the shape, this is positive, and if it's to the left it is negative. – Allan Cameron Jun 10 '20 at 21:37
  • Thank you, but the question is how to mathematically determine whether it's positive or not? – DigiPath Jun 11 '20 at 00:04
  • @DigiPath see my update. The value of should now be stored in `smooth_df$K` – Allan Cameron Jun 11 '20 at 13:08
  • Thank you! That's very helpful – DigiPath Jun 11 '20 at 19:05
  • @DigiPath I don't think it's the case that curvature is limited to 1. The curvature of a circle is given by K = 1/R, so if R < 1 then K > 1. However, I'm no mathematician, and this isn't a programming question, so it might be worth showing what you have so far over at the maths stack exchange site and asking there to make sure your results make sense. – Allan Cameron Jun 11 '20 at 20:47