Roser Montañana
Roser Montañana

Reputation: 13

R - Rotate coordinates in dataframe based on varying angles and origin

I have a dataframe data with several columns including ID Image (repeated for several rows) and X CenterX_um and Y CenterY_um coordinate (different for each row).

I want to rotate the X, Y coordinates based on an angle and origin that are specific for each ID. I also have this info on a separate dataframe rotation_info.

I have tried the rearrr::rotate_2d function but it doesn't let me specify the angle and origin. Rather, it wants to use the same angle and origin for all.

Any clues?

Here is a subset of each dataframe:

data <- structure(list(Image = c("P112_SOD1_scene-01_roi1", "P112_SOD1_scene-01_roi1", "P112_SOD1_scene-01_roi1", "P112_SOD1_scene-03_roi1",
                         "P112_SOD1_scene-03_roi1", "P112_SOD1_scene-03_roi1", "P112_SOD1_scene-08_roi1", "P112_SOD1_scene-08_roi1", 
                         "P112_SOD1_scene-14_roi1", "P112_SOD1_scene-14_roi1", "P112_WT_scene-13_roi1", "P112_WT_scene-13_roi1"),
               Area_pixel2 = c(6172L, 2804L, 4133L, 1547L, 9224L, 5133L, 4311L, 2740L, 2610L, 3802L, 5129L, 4106L),
               Area_um2 = c(318.03702442715, 144.48733254921, 212.96938139297, 79.715372130394, 475.30355044005, 264.49838729497, 222.14154444352, 
                            141.18947617148, 134.49070540422, 195.91328043941, 264.29227127136, 211.57809823361),
               CenterX_um = c(1149.8904493589, 1126.4609300602, 1100.8211294593, 1024.1176019595, 993.32875148771, 964.51498442025, 874.9485394368,
                              843.03397382692, 1457.7886911485, 1359.4372824824, 1549.4154976822, 1614.8843949665),
               CenterY_um = c(65.734010569428, 87.806098466489, 121.24362888879, 344.12182158201, 349.1125830895, 378.36327230154, 222.10103046474,
                              236.8534704986, 524.97197144378, 536.26329031132, 101.93303030628, 103.3766130805),
               Circularity = c(0.70514719268137, 0.73725777209061, 0.55904614141807, 0.64491724919754, 0.72628772170604, 0.68836548793912,
                               0.80455000787307, 0.75794971514553, 0.58945714820048, 0.75891540252167, 0.63520099709918, 0.79151119246911),
               Compactness = c(0.66065335617958, 0.64189735971181, 0.53383026406418, 0.53076228622962, 0.76004630952984, 0.72584335956662,
                               0.81606076244974, 0.77264654886067, 0.51250290104024, 0.78515382913133, 0.66035724052464, 0.80977712999347)),
          row.names = c(1L, 2L, 3L, 1505L, 1506L, 1507L, 8500L, 8501L, 15689L, 15690L, 30001L, 30002L), class = "data.frame")

data
#                        Image Area_pixel2  Area_um2 CenterX_um CenterY_um Circularity Compactness
#1     P112_SOD1_scene-01_roi1        6172 318.03702  1149.8904   65.73401   0.7051472   0.6606534
#2     P112_SOD1_scene-01_roi1        2804 144.48733  1126.4609   87.80610   0.7372578   0.6418974
#3     P112_SOD1_scene-01_roi1        4133 212.96938  1100.8211  121.24363   0.5590461   0.5338303
#1505  P112_SOD1_scene-03_roi1        1547  79.71537  1024.1176  344.12182   0.6449172   0.5307623
#1506  P112_SOD1_scene-03_roi1        9224 475.30355   993.3288  349.11258   0.7262877   0.7600463
#1507  P112_SOD1_scene-03_roi1        5133 264.49839   964.5150  378.36327   0.6883655   0.7258434
#8500  P112_SOD1_scene-08_roi1        4311 222.14154   874.9485  222.10103   0.8045500   0.8160608
#8501  P112_SOD1_scene-08_roi1        2740 141.18948   843.0340  236.85347   0.7579497   0.7726465
#15689 P112_SOD1_scene-14_roi1        2610 134.49071  1457.7887  524.97197   0.5894571   0.5125029
#15690 P112_SOD1_scene-14_roi1        3802 195.91328  1359.4373  536.26329   0.7589154   0.7851538
#30001   P112_WT_scene-13_roi1        5129 264.29227  1549.4155  101.93303   0.6352010   0.6603572
#30002   P112_WT_scene-13_roi1        4106 211.57810  1614.8844  103.37661   0.7915112   0.8097771
rotation_info <- structure(list(Image = c("P112_SOD1_scene-01_roi1", "P112_SOD1_scene-03_roi1", 
"P112_SOD1_scene-08_roi1", "P112_SOD1_scene-14_roi1", "P112_WT_scene-13_roi1"
), Angle = c(199.289658091447, 202.184697711029, 158.990932730799, 
180.612494033135, 15.223442470483), CC_X = c(989.039056641, 970.652055588, 
1104.355063245, 781.334044746, 1034.893059267), CC_Y = c(792.457045383, 
798.359045721, 940.915053885, 747.057042783, 709.829040651)), row.names = c(NA, 
-5L), class = "data.frame")

rotation_info
#                    Image     Angle      CC_X     CC_Y
#1 P112_SOD1_scene-01_roi1 199.28966  989.0391 792.4570
#2 P112_SOD1_scene-03_roi1 202.18470  970.6521 798.3590
#3 P112_SOD1_scene-08_roi1 158.99093 1104.3551 940.9151
#4 P112_SOD1_scene-14_roi1 180.61249  781.3340 747.0570
#5   P112_WT_scene-13_roi1  15.22344 1034.8931 709.8290

My idea was to first merge both dataframes based on Image so that the angle and origin would be duplicated for each row (this works well), and then apply the rearrr::rotate_2d function to each row. This is when I run into trouble because it wants the same angle and origin for all rows.

# Merge rotation info into data
data <- merge(data, rotation_info, by="Image", all.x=TRUE)

# Rotate based on angle ("Angle") and origin ("CC_X", "CC_Y")
data <- rotate_2d(data, degrees = data$Angle, x_col = "CenterX_um", y_col = "CenterY_um",
                  origin = c(data$CC_X, data$CC_Y) , suffix = "", overwrite = TRUE)

Any other approach is welcome. I don't necessarily need the rotation info in my dataframe (I was planning to drop it after, actually).

Thanks in advance!

Upvotes: 1

Views: 790

Answers (1)

Dan Adams
Dan Adams

Reputation: 5254

It seems that rearrr::rotate_2d() is not really designed to take in degrees and origin arguments from the source data in the way that you're trying to. If done this way it will rotate every point by every Angle around every origin.

Instead I used purrr::group_map() to split the data by Image and separately operate on each with a single degrees and origin argument (collapsed by mean(., na.rm = T) although you could use unique() or min() or anything else to get a single value instead of a vector of repeated values).

See plot at bottom where I show original and rotated points in red and blue respectively and the origin for each with the *.

library(tidyverse)
library(rearrr)

data <- structure(list(Image = c("P112_SOD1_scene-01_roi1", "P112_SOD1_scene-01_roi1", "P112_SOD1_scene-01_roi1", "P112_SOD1_scene-03_roi1",
                                 "P112_SOD1_scene-03_roi1", "P112_SOD1_scene-03_roi1", "P112_SOD1_scene-08_roi1", "P112_SOD1_scene-08_roi1", 
                                 "P112_SOD1_scene-14_roi1", "P112_SOD1_scene-14_roi1", "P112_WT_scene-13_roi1", "P112_WT_scene-13_roi1"),
                       Area_pixel2 = c(6172L, 2804L, 4133L, 1547L, 9224L, 5133L, 4311L, 2740L, 2610L, 3802L, 5129L, 4106L),
                       Area_um2 = c(318.03702442715, 144.48733254921, 212.96938139297, 79.715372130394, 475.30355044005, 264.49838729497, 222.14154444352, 
                                    141.18947617148, 134.49070540422, 195.91328043941, 264.29227127136, 211.57809823361),
                       CenterX_um = c(1149.8904493589, 1126.4609300602, 1100.8211294593, 1024.1176019595, 993.32875148771, 964.51498442025, 874.9485394368,
                                      843.03397382692, 1457.7886911485, 1359.4372824824, 1549.4154976822, 1614.8843949665),
                       CenterY_um = c(65.734010569428, 87.806098466489, 121.24362888879, 344.12182158201, 349.1125830895, 378.36327230154, 222.10103046474,
                                      236.8534704986, 524.97197144378, 536.26329031132, 101.93303030628, 103.3766130805),
                       Circularity = c(0.70514719268137, 0.73725777209061, 0.55904614141807, 0.64491724919754, 0.72628772170604, 0.68836548793912,
                                       0.80455000787307, 0.75794971514553, 0.58945714820048, 0.75891540252167, 0.63520099709918, 0.79151119246911),
                       Compactness = c(0.66065335617958, 0.64189735971181, 0.53383026406418, 0.53076228622962, 0.76004630952984, 0.72584335956662,
                                       0.81606076244974, 0.77264654886067, 0.51250290104024, 0.78515382913133, 0.66035724052464, 0.80977712999347)),
                  row.names = c(1L, 2L, 3L, 1505L, 1506L, 1507L, 8500L, 8501L, 15689L, 15690L, 30001L, 30002L), class = "data.frame")


rotation_info <- structure(list(Image = c("P112_SOD1_scene-01_roi1", "P112_SOD1_scene-03_roi1", 
                                          "P112_SOD1_scene-08_roi1", "P112_SOD1_scene-14_roi1", "P112_WT_scene-13_roi1"), 
                                Angle = c(199.289658091447, 202.184697711029, 158.990932730799, 180.612494033135, 15.223442470483), 
                                CC_X = c(989.039056641, 970.652055588, 1104.355063245, 781.334044746, 1034.893059267), 
                                CC_Y = c(792.457045383, 798.359045721, 940.915053885, 747.057042783, 709.829040651)), 
                           row.names = c(NA, -5L), class = "data.frame")

# Merge rotation info into data
data2 <- merge(data, rotation_info, by="Image", all.x=TRUE)

# rotate each image by its respective origin and angle
data3 <- data2 %>%
  group_by(Image) %>%
  group_map(
    ~ rotate_2d(
      data = .,
      degrees = mean(.[["Angle"]], na.rm = T),
      x_col = "CenterX_um",
      y_col = "CenterY_um",
      origin = c(mean(.[["CC_X"]], na.rm = T), mean(.[["CC_Y"]], na.rm = T)), 
      keep_original = TRUE
    ),
    .keep = T
  ) %>% 
  bind_rows()

# visualize rotated result vs original
data3 %>% 
  select(Image, CC_X:CC_Y, starts_with("Center")) %>% 
  group_by(Image) %>% 
  mutate(point_id = row_number()) %>% 
  ungroup() %>% 
  pivot_longer(
    cols = -c(Image, point_id, CC_X, CC_Y),
    names_to = c("axis", "units", "rotation"),
    names_prefix = "Center",
    names_sep = "_",
    values_to = "position"
  ) %>% 
  replace_na(list(rotation = "original")) %>% 
  pivot_wider(names_from = axis, values_from = position) %>% 
  ggplot(aes(X, Y, color = rotation)) +
  geom_point() +
  geom_path() +
  geom_point(aes(CC_X, CC_Y), size = 4, color = "black", shape = 8) +
  facet_wrap(~Image, scales = "free")
#> Warning: Expected 3 pieces. Missing pieces filled with `NA` in 2 rows [1, 2].

Created on 2022-01-27 by the reprex package (v2.0.1)

Upvotes: 2

Related Questions