Here, we inspect CellProfiler features generated from a standard Cell Painting dataset to identify features that don’t capture biologically relevant attributes.

show_table <- print

If running interactively in RStudio,

  • change output in the header of this markdown to html_notebook and
  • change to eval=TRUE below

When knitting for pushing to GitHub,

  • change output in the header of this markdown to github_document and
  • change to eval=FALSE below
show_table <- knitr::kable
suppressPackageStartupMessages(library(magrittr))
suppressPackageStartupMessages(library(tidyverse))

1 Sample

Randomly sample ~5000 cells.

sqlite_file <- "single_cell.sqlite"

db <- DBI::dbConnect(RSQLite::SQLite(), sqlite_file, loadable.extensions = TRUE)

nuclei <- 
  DBI::dbGetQuery(db, "SELECT * FROM Nuclei ORDER BY RANDOM() LIMIT 5000;") %>%
  collect() %>%
  select(-ObjectNumber)

nuclei_lut <-
  nuclei %>%
  select(TableNumber, ImageNumber, Nuclei_Number_Object_Number)

cells <- 
  tbl(src = db, "Cells") %>% 
  select(-ObjectNumber) %>% 
  inner_join(
    nuclei_lut,
    by = c("TableNumber", 
           "ImageNumber", 
           "Cells_Parent_Nuclei" = "Nuclei_Number_Object_Number"),
    copy = TRUE
  ) %>%
  collect()

cytoplasm <- 
  tbl(src = db, "Cytoplasm") %>% 
  select(-ObjectNumber) %>% 
  inner_join(
    nuclei_lut,
    by = c("TableNumber", 
           "ImageNumber", 
           "Cytoplasm_Parent_Nuclei" = "Nuclei_Number_Object_Number"),
    copy = TRUE
  ) %>%
  collect()

nuclei_cells <- 
  inner_join(
    nuclei,
    cells,
    by = c("TableNumber", 
           "ImageNumber", 
           "Nuclei_Number_Object_Number" = "Cells_Parent_Nuclei")
  )

nuclei_cells_cytoplasm <- 
  inner_join(
    nuclei_cells,
    cytoplasm,
    by = c("TableNumber",
           "ImageNumber",
           "Cells_Number_Object_Number" = "Cytoplasm_Parent_Cells")
  )

nuclei_cells_cytoplasm %>% write_csv("single_cell_sampled.csv.gz")

2 Load

Load the sample

sampled_cells <-
  read_csv(
    file.path(
      Sys.getenv("HOME"),
      "Downloads",
      "single_cell_sampled.csv.gz"
    ),
    col_types = cols(.default = col_double())
  )

3 Select features

Specify which features are going to be checked for 0 or NA. In this case, we want to check for all features.

no_check_empty_features <- 
  c("TableNumber", "ImageNumber")

check_empty_features <- 
  setdiff(
    names(sampled_cells),
    no_check_empty_features
  )

4 Create summary data frame

Summarize number of cells with 0 or NA per feature

empty_frequency <- 
  inner_join(
    sampled_cells %>% 
      summarize_at(check_empty_features,  ~sum(. == 0, na.rm = TRUE)) %>%
      pivot_longer(everything(), names_to = "cp_feature_name", values_to = "number_of_zero"),    
    sampled_cells %>% 
      summarize_at(check_empty_features,  ~sum(is.na(.))) %>%
      pivot_longer(everything(), names_to = "cp_feature_name", values_to = "number_of_na"),
    by = "cp_feature_name"
  )

empty_frequency %<>%
  separate(col = "cp_feature_name",
           into = c("compartment", "feature_group", "feature_name"),
           sep = "_", extra = "merge", remove = FALSE)

channels <- c("DNA", "RNA", "ER", "Mito", "AGP", "Brightfield")

channels <- c(as.vector(outer(channels, channels, FUN = paste, sep = "_")),
              channels)

# get channel name
channel_name <- function(feature_name) {
  name <- channels[which(stringr::str_detect(feature_name, channels))[1]]

  if (is.na(name)) {
    name <- "None"
  }

  name
}

# add channel name to row annotations
empty_frequency %<>%
  rowwise() %>%
  mutate(channel_name = channel_name(feature_name)) %>%
  ungroup() %>%
  select(c("cp_feature_name",
           "compartment",
           "channel_name",
           "feature_group",
           "feature_name",
           "number_of_zero",
           "number_of_na")
         )

Add texture angle+scale, granularity scale

empty_frequency %<>%
  rowwise() %>%
  mutate(t_scale =
           as.integer(
             str_match(
               cp_feature_name,
               "Texture_([A-Za-z0-9]+)_([A-Za-z0-9]+)_([0-9]+)_([0-9]+)"
             )[[4]]
           )) %>%
  mutate(t_angle =
           as.integer(
             str_match(
               cp_feature_name,
               "Texture_([A-Za-z0-9]+)_([A-Za-z0-9]+)_([0-9]+)_([0-9]+)"
             )[[5]]
           )) %>%
  mutate(g_scale =
           as.integer(str_match(
             cp_feature_name, "Granularity_([0-9]+)_"
           )[[2]])) %>%
  ungroup()
empty_frequency %<>%
  rowwise() %>%
  mutate(t_feature_name =
          str_match(
               feature_name,
               "([A-Za-z0-9]+)_([A-Za-z0-9]+)_([0-9]+)_([0-9]+)"
             )[[2]]
           ) %>%
  ungroup()
empty_frequency %<>%
  rowwise() %>%
  mutate(channel_name_1 =
          str_match(
               channel_name,
               "([A-Za-z0-9]+)_([A-Za-z0-9]+)"
             )[[2]]
           ) %>%
  mutate(channel_name_2 =
          str_match(
               channel_name,
               "([A-Za-z0-9]+)_([A-Za-z0-9]+)"
             )[[3]]
           ) %>%
  mutate(c_feature_name =
          str_match(
               feature_name,
               "([A-Za-z0-9]+)_([A-Za-z0-9]+)_([A-Za-z0-9]+)"
             )[[2]]
           ) %>%
  ungroup()

5 Display summary

empty_frequency %>% sample_n(5)

6 Inspect summary for NAs

empty_frequency %>%
  filter(number_of_na > 0) %>%
  count(number_of_na) %>% 
  arrange(desc(n)) %>%
  show_table()
empty_frequency %>%
  filter(number_of_na > 9) %>%
  ggplot(aes(number_of_na)) + geom_histogram(binwidth = 5)

empty_frequency %>%
  filter(number_of_na > 9) %>%
  show_table()

empty_frequency %>%
  filter(number_of_na > 9) %>%
  group_by(feature_group, c_feature_name) %>%
  tally() %>%
  show_table()

7 Inspect summary for zeros

7.1 All except correlation and no-channel

Get an overview of all features, excluding (for now) - Correlation features - features not associated with a channel

empty_frequency %>%
  filter(feature_group != "Correlation") %>% 
  filter(channel_name != "None") %>%
  ggplot(aes(compartment, number_of_zero)) + 
  geom_boxplot() + 
  facet_grid(channel_name ~ feature_group) + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0))

7.2 Always remove location

Exclude Location because we definitely want to exclude them

Location features are generated by:

location_features <-
  str_subset(check_empty_features, 
             "(Location_Center_(X|Y|Z))|(Location_(X|Y|Z))|(Location_(CenterMass|Max)Intensity_(X|Y|Z))") %>%
  sort()

tibble(location_features) %>%
  show_table()

7.3 Only no-channel

Get an overview of only features not associated with a channel

empty_frequency %>%
  filter(channel_name == "None") %>%
  filter(!(cp_feature_name %in% c(location_features))) %>%
  ggplot(aes(compartment, number_of_zero)) + 
  geom_boxplot() + 
  facet_grid(channel_name ~ feature_group) + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0))

empty_frequency %>%
  filter(channel_name == "None") %>%
  filter(!(cp_feature_name %in% c(location_features))) %>%
  filter(number_of_zero > 0) %>%
  arrange(desc(number_of_zero)) %>%
  select(cp_feature_name, feature_group, number_of_zero, number_of_na)

7.3.1 Exclude Euler and Center

Exclude

  • EulerNumber - this may be informative in some cases, but we drop in this analysis
  • AreaShape_Center_{X,Y,Z}

From MeasureObjectSizeShape: Center_X, Center_Y, Center_Z: The x-, y-, and (for 3D objects) z- coordinates of the point farthest away from any object edge (the centroid). Note that this is not the same as the Location-X and -Y measurements produced by the Identify or Watershed modules or the Location-Z measurement produced by the Watershed module.

empty_frequency %>%
  filter(channel_name == "None") %>%
  filter(!(cp_feature_name %in% c(location_features))) %>%
  filter(!str_detect(feature_name, "EulerNumber")) %>%
  filter(!str_detect(cp_feature_name, "AreaShape_Center_(X|Y|Z)")) %>%
  filter(number_of_zero > 0) %>%
  arrange(desc(number_of_zero)) %>%
  select(cp_feature_name, feature_group, number_of_zero, number_of_na)

7.3.2 Exclude very small distances

Nuclei_Neighbors_*_2 is mostly 0 because two pixels is too little - this may be informative at lower magnifications, but we drop in this analysis

Do we actually need Nuclei_Neighbors_* or is Cells_Neighbors_* sufficient? Not sure.

empty_frequency %>%
  filter(channel_name == "None") %>%
  filter(!(cp_feature_name %in% c(location_features))) %>%
  filter(!str_detect(feature_name, "EulerNumber")) %>%
  filter(!str_detect(cp_feature_name, "AreaShape_Center_(X|Y|Z)")) %>%
  filter(!str_detect(cp_feature_name, "Nuclei_Neighbors_.*_2")) %>%
  filter(number_of_zero > 0) %>%
  arrange(desc(number_of_zero)) %>%
  select(cp_feature_name, feature_group, number_of_zero, number_of_na)

7.3.3 Exclude ClosestObjectNumber

  • Cells_Neighbors_NumberOfNeighbors_Adjacent == 0 are “isolated” cells
  • *ClosestObjectNumber should be dropped because it is the index of the first or second closest object
empty_frequency %>%
  filter(channel_name == "None") %>%
  filter(!(cp_feature_name %in% c(location_features))) %>%
  filter(!str_detect(feature_name, "EulerNumber")) %>%
  filter(!str_detect(cp_feature_name, "AreaShape_Center_(X|Y|Z)")) %>%
  filter(!str_detect(cp_feature_name, "Nuclei_Neighbors_.*_2")) %>%
  filter(!str_detect(cp_feature_name, "Neighbors_(First|Second)ClosestObjectNumber_.*")) %>%
  filter(number_of_zero > 0) %>%
  arrange(desc(number_of_zero)) %>%
  select(cp_feature_name, feature_group, number_of_zero, number_of_na)

The rest of the measurements above are reasonable to preserve.

7.4 All except correlation and no-channel

empty_frequency %>%
  filter(!(cp_feature_name %in% c(location_features))) %>%
  filter(feature_group != "Correlation") %>% 
  filter(channel_name != "None") %>%
  ggplot(aes(compartment, number_of_zero)) + 
  geom_boxplot() + 
  facet_grid(channel_name ~ feature_group) + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0))

7.4.1 Only granularity

More zeros at larger scales

empty_frequency %>%
  filter(feature_group == "Granularity") %>% 
  ggplot(aes(g_scale, number_of_zero, color = compartment)) + 
  geom_line() + 
  facet_wrap(~channel_name) + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0))

7.4.2 Only texture

More zeros at larger scales

empty_frequency %>%
  filter(feature_group == "Texture") %>%
  ggplot(aes(compartment, number_of_zero)) +
  geom_boxplot() +
  facet_grid(t_scale ~ channel_name) +
  theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0))

Things seems a bit off in - Mito - Scale = 20 in Nuclei

7.4.2.1 Mito

empty_frequency %>%
  filter(feature_group == "Texture") %>%
  filter(channel_name == "Mito") %>%
  ggplot(aes(t_feature_name, number_of_zero, color = as.factor(t_scale))) +
  geom_point() +
  facet_wrap(~ compartment) +
  theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0))

7.4.2.2 Scale=20 in Nuclei, non-Mito

empty_frequency %>%
  filter(feature_group == "Texture") %>%
  filter(t_scale == 20 & compartment == "Nuclei" & channel_name != "Mito") %>%
  ggplot(aes(t_feature_name, number_of_zero, color = channel_name)) +
  geom_point() +
  theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0))

7.4.3 No granularity and texture

empty_frequency %>%
  filter(!(cp_feature_name %in% c(location_features))) %>%
  filter(feature_group != "Correlation") %>% 
  filter(channel_name != "None") %>%
  filter(!(feature_group %in% c("Granularity", "Texture"))) %>% 
  filter(number_of_zero > 0) %>%
  arrange(desc(number_of_zero)) %>%
  select(cp_feature_name, feature_group, number_of_zero, number_of_na)

The innermost ring of RadialDistribution is expected to be zero in some cases, for cytoplasm, so this is fine.

7.5 Only correlation

empty_frequency %>%
  filter(!(cp_feature_name %in% c(location_features))) %>%
  filter(feature_group == "Correlation") %>% 
  ggplot(aes(compartment, number_of_zero, color = c_feature_name)) + 
  geom_point() + 
  facet_grid(channel_name_1 ~ channel_name_2) + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0))

7.5.1 No Costes

empty_frequency %>%
  filter(!(cp_feature_name %in% c(location_features))) %>%
  filter(feature_group == "Correlation") %>% 
  filter(c_feature_name != "Costes") %>% 
  ggplot(aes(compartment, number_of_zero, color = c_feature_name)) + 
  geom_point() + 
  facet_grid(channel_name_1 ~ channel_name_2) + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0))

empty_frequency %>%
  filter(!(cp_feature_name %in% c(location_features))) %>%
  filter(feature_group == "Correlation") %>% 
  filter(c_feature_name != "Costes") %>% 
  ggplot(aes(compartment, number_of_na, color = c_feature_name)) + 
  geom_point() + 
  facet_grid(channel_name_1 ~ channel_name_2) + 
  theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0))

LS0tCnRpdGxlOiAiSW5zcGVjdCBDZWxsUHJvZmlsZXIgcmVhZG91dHMgZm9yIHplcm8gYW5kIE5BIC12YWx1ZWQgZmVhdHVyZXMiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICB0b2NfZGVwdGg6IDMKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgdGhlbWU6IGx1bWVuCi0tLQoKSGVyZSwgd2UgaW5zcGVjdCBDZWxsUHJvZmlsZXIgZmVhdHVyZXMgZ2VuZXJhdGVkIGZyb20gYSBzdGFuZGFyZCBDZWxsIFBhaW50aW5nIGRhdGFzZXQgdG8gaWRlbnRpZnkgZmVhdHVyZXMgdGhhdCBkb24ndCBjYXB0dXJlIGJpb2xvZ2ljYWxseSByZWxldmFudCBhdHRyaWJ1dGVzLgoKYGBge3IgZWNobz1UUlVFfQpzaG93X3RhYmxlIDwtIHByaW50CmBgYAoKSWYgcnVubmluZyBpbnRlcmFjdGl2ZWx5IGluIFJTdHVkaW8sIAoKLSBjaGFuZ2UgYG91dHB1dGAgaW4gdGhlIGhlYWRlciBvZiB0aGlzIG1hcmtkb3duIHRvIGBodG1sX25vdGVib29rYCBhbmQKLSBjaGFuZ2UgdG8gYGV2YWw9VFJVRWAgYmVsb3cKCldoZW4ga25pdHRpbmcgZm9yIHB1c2hpbmcgdG8gR2l0SHViLAoKLSBjaGFuZ2UgYG91dHB1dGAgaW4gdGhlIGhlYWRlciBvZiB0aGlzIG1hcmtkb3duIHRvIGBnaXRodWJfZG9jdW1lbnRgIGFuZAotIGNoYW5nZSB0byBgZXZhbD1GQUxTRWAgYmVsb3cKCmBgYHtyIGV2YWw9RkFMU0V9CnNob3dfdGFibGUgPC0ga25pdHI6OmthYmxlCmBgYAoKCmBgYHtyfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShtYWdyaXR0cikpCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KHRpZHl2ZXJzZSkpCmBgYAoKIyBTYW1wbGUgCgpSYW5kb21seSBzYW1wbGUgfjUwMDAgY2VsbHMuCgpgYGB7ciBldmFsPUZBTFNFfQpzcWxpdGVfZmlsZSA8LSAic2luZ2xlX2NlbGwuc3FsaXRlIgoKZGIgPC0gREJJOjpkYkNvbm5lY3QoUlNRTGl0ZTo6U1FMaXRlKCksIHNxbGl0ZV9maWxlLCBsb2FkYWJsZS5leHRlbnNpb25zID0gVFJVRSkKCm51Y2xlaSA8LSAKICBEQkk6OmRiR2V0UXVlcnkoZGIsICJTRUxFQ1QgKiBGUk9NIE51Y2xlaSBPUkRFUiBCWSBSQU5ET00oKSBMSU1JVCA1MDAwOyIpICU+JQogIGNvbGxlY3QoKSAlPiUKICBzZWxlY3QoLU9iamVjdE51bWJlcikKCm51Y2xlaV9sdXQgPC0KICBudWNsZWkgJT4lCiAgc2VsZWN0KFRhYmxlTnVtYmVyLCBJbWFnZU51bWJlciwgTnVjbGVpX051bWJlcl9PYmplY3RfTnVtYmVyKQoKY2VsbHMgPC0gCiAgdGJsKHNyYyA9IGRiLCAiQ2VsbHMiKSAlPiUgCiAgc2VsZWN0KC1PYmplY3ROdW1iZXIpICU+JSAKICBpbm5lcl9qb2luKAogICAgbnVjbGVpX2x1dCwKICAgIGJ5ID0gYygiVGFibGVOdW1iZXIiLCAKICAgICAgICAgICAiSW1hZ2VOdW1iZXIiLCAKICAgICAgICAgICAiQ2VsbHNfUGFyZW50X051Y2xlaSIgPSAiTnVjbGVpX051bWJlcl9PYmplY3RfTnVtYmVyIiksCiAgICBjb3B5ID0gVFJVRQogICkgJT4lCiAgY29sbGVjdCgpCgpjeXRvcGxhc20gPC0gCiAgdGJsKHNyYyA9IGRiLCAiQ3l0b3BsYXNtIikgJT4lIAogIHNlbGVjdCgtT2JqZWN0TnVtYmVyKSAlPiUgCiAgaW5uZXJfam9pbigKICAgIG51Y2xlaV9sdXQsCiAgICBieSA9IGMoIlRhYmxlTnVtYmVyIiwgCiAgICAgICAgICAgIkltYWdlTnVtYmVyIiwgCiAgICAgICAgICAgIkN5dG9wbGFzbV9QYXJlbnRfTnVjbGVpIiA9ICJOdWNsZWlfTnVtYmVyX09iamVjdF9OdW1iZXIiKSwKICAgIGNvcHkgPSBUUlVFCiAgKSAlPiUKICBjb2xsZWN0KCkKCm51Y2xlaV9jZWxscyA8LSAKICBpbm5lcl9qb2luKAogICAgbnVjbGVpLAogICAgY2VsbHMsCiAgICBieSA9IGMoIlRhYmxlTnVtYmVyIiwgCiAgICAgICAgICAgIkltYWdlTnVtYmVyIiwgCiAgICAgICAgICAgIk51Y2xlaV9OdW1iZXJfT2JqZWN0X051bWJlciIgPSAiQ2VsbHNfUGFyZW50X051Y2xlaSIpCiAgKQoKbnVjbGVpX2NlbGxzX2N5dG9wbGFzbSA8LSAKICBpbm5lcl9qb2luKAogICAgbnVjbGVpX2NlbGxzLAogICAgY3l0b3BsYXNtLAogICAgYnkgPSBjKCJUYWJsZU51bWJlciIsCiAgICAgICAgICAgIkltYWdlTnVtYmVyIiwKICAgICAgICAgICAiQ2VsbHNfTnVtYmVyX09iamVjdF9OdW1iZXIiID0gIkN5dG9wbGFzbV9QYXJlbnRfQ2VsbHMiKQogICkKCm51Y2xlaV9jZWxsc19jeXRvcGxhc20gJT4lIHdyaXRlX2Nzdigic2luZ2xlX2NlbGxfc2FtcGxlZC5jc3YuZ3oiKQpgYGAKCiMgTG9hZAoKTG9hZCB0aGUgc2FtcGxlCgpgYGB7ciBldmFsPVRSVUV9CnNhbXBsZWRfY2VsbHMgPC0KICByZWFkX2NzdigKICAgIGZpbGUucGF0aCgKICAgICAgU3lzLmdldGVudigiSE9NRSIpLAogICAgICAiRG93bmxvYWRzIiwKICAgICAgInNpbmdsZV9jZWxsX3NhbXBsZWQuY3N2Lmd6IgogICAgKSwKICAgIGNvbF90eXBlcyA9IGNvbHMoLmRlZmF1bHQgPSBjb2xfZG91YmxlKCkpCiAgKQpgYGAKCiMgU2VsZWN0IGZlYXR1cmVzCgpTcGVjaWZ5IHdoaWNoIGZlYXR1cmVzIGFyZSBnb2luZyB0byBiZSBjaGVja2VkIGZvciBgMGAgb3IgYE5BYC4KSW4gdGhpcyBjYXNlLCB3ZSB3YW50IHRvIGNoZWNrIGZvciBhbGwgZmVhdHVyZXMuCgpgYGB7cn0Kbm9fY2hlY2tfZW1wdHlfZmVhdHVyZXMgPC0gCiAgYygiVGFibGVOdW1iZXIiLCAiSW1hZ2VOdW1iZXIiKQoKY2hlY2tfZW1wdHlfZmVhdHVyZXMgPC0gCiAgc2V0ZGlmZigKICAgIG5hbWVzKHNhbXBsZWRfY2VsbHMpLAogICAgbm9fY2hlY2tfZW1wdHlfZmVhdHVyZXMKICApCmBgYAoKIyBDcmVhdGUgc3VtbWFyeSBkYXRhIGZyYW1lCgpTdW1tYXJpemUgbnVtYmVyIG9mIGNlbGxzIHdpdGggYDBgIG9yIGBOQWAgcGVyIGZlYXR1cmUKCmBgYHtyfQplbXB0eV9mcmVxdWVuY3kgPC0gCiAgaW5uZXJfam9pbigKICAgIHNhbXBsZWRfY2VsbHMgJT4lIAogICAgICBzdW1tYXJpemVfYXQoY2hlY2tfZW1wdHlfZmVhdHVyZXMsICB+c3VtKC4gPT0gMCwgbmEucm0gPSBUUlVFKSkgJT4lCiAgICAgIHBpdm90X2xvbmdlcihldmVyeXRoaW5nKCksIG5hbWVzX3RvID0gImNwX2ZlYXR1cmVfbmFtZSIsIHZhbHVlc190byA9ICJudW1iZXJfb2ZfemVybyIpLCAgICAKICAgIHNhbXBsZWRfY2VsbHMgJT4lIAogICAgICBzdW1tYXJpemVfYXQoY2hlY2tfZW1wdHlfZmVhdHVyZXMsICB+c3VtKGlzLm5hKC4pKSkgJT4lCiAgICAgIHBpdm90X2xvbmdlcihldmVyeXRoaW5nKCksIG5hbWVzX3RvID0gImNwX2ZlYXR1cmVfbmFtZSIsIHZhbHVlc190byA9ICJudW1iZXJfb2ZfbmEiKSwKICAgIGJ5ID0gImNwX2ZlYXR1cmVfbmFtZSIKICApCgplbXB0eV9mcmVxdWVuY3kgJTw+JQogIHNlcGFyYXRlKGNvbCA9ICJjcF9mZWF0dXJlX25hbWUiLAogICAgICAgICAgIGludG8gPSBjKCJjb21wYXJ0bWVudCIsICJmZWF0dXJlX2dyb3VwIiwgImZlYXR1cmVfbmFtZSIpLAogICAgICAgICAgIHNlcCA9ICJfIiwgZXh0cmEgPSAibWVyZ2UiLCByZW1vdmUgPSBGQUxTRSkKCmNoYW5uZWxzIDwtIGMoIkROQSIsICJSTkEiLCAiRVIiLCAiTWl0byIsICJBR1AiLCAiQnJpZ2h0ZmllbGQiKQoKY2hhbm5lbHMgPC0gYyhhcy52ZWN0b3Iob3V0ZXIoY2hhbm5lbHMsIGNoYW5uZWxzLCBGVU4gPSBwYXN0ZSwgc2VwID0gIl8iKSksCiAgICAgICAgICAgICAgY2hhbm5lbHMpCgojIGdldCBjaGFubmVsIG5hbWUKY2hhbm5lbF9uYW1lIDwtIGZ1bmN0aW9uKGZlYXR1cmVfbmFtZSkgewogIG5hbWUgPC0gY2hhbm5lbHNbd2hpY2goc3RyaW5ncjo6c3RyX2RldGVjdChmZWF0dXJlX25hbWUsIGNoYW5uZWxzKSlbMV1dCgogIGlmIChpcy5uYShuYW1lKSkgewogICAgbmFtZSA8LSAiTm9uZSIKICB9CgogIG5hbWUKfQoKIyBhZGQgY2hhbm5lbCBuYW1lIHRvIHJvdyBhbm5vdGF0aW9ucwplbXB0eV9mcmVxdWVuY3kgJTw+JQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoY2hhbm5lbF9uYW1lID0gY2hhbm5lbF9uYW1lKGZlYXR1cmVfbmFtZSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBzZWxlY3QoYygiY3BfZmVhdHVyZV9uYW1lIiwKICAgICAgICAgICAiY29tcGFydG1lbnQiLAogICAgICAgICAgICJjaGFubmVsX25hbWUiLAogICAgICAgICAgICJmZWF0dXJlX2dyb3VwIiwKICAgICAgICAgICAiZmVhdHVyZV9uYW1lIiwKICAgICAgICAgICAibnVtYmVyX29mX3plcm8iLAogICAgICAgICAgICJudW1iZXJfb2ZfbmEiKQogICAgICAgICApCmBgYAoKQWRkIHRleHR1cmUgYW5nbGUrc2NhbGUsIGdyYW51bGFyaXR5IHNjYWxlCgpgYGB7cn0KZW1wdHlfZnJlcXVlbmN5ICU8PiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKHRfc2NhbGUgPQogICAgICAgICAgIGFzLmludGVnZXIoCiAgICAgICAgICAgICBzdHJfbWF0Y2goCiAgICAgICAgICAgICAgIGNwX2ZlYXR1cmVfbmFtZSwKICAgICAgICAgICAgICAgIlRleHR1cmVfKFtBLVphLXowLTldKylfKFtBLVphLXowLTldKylfKFswLTldKylfKFswLTldKykiCiAgICAgICAgICAgICApW1s0XV0KICAgICAgICAgICApKSAlPiUKICBtdXRhdGUodF9hbmdsZSA9CiAgICAgICAgICAgYXMuaW50ZWdlcigKICAgICAgICAgICAgIHN0cl9tYXRjaCgKICAgICAgICAgICAgICAgY3BfZmVhdHVyZV9uYW1lLAogICAgICAgICAgICAgICAiVGV4dHVyZV8oW0EtWmEtejAtOV0rKV8oW0EtWmEtejAtOV0rKV8oWzAtOV0rKV8oWzAtOV0rKSIKICAgICAgICAgICAgIClbWzVdXQogICAgICAgICAgICkpICU+JQogIG11dGF0ZShnX3NjYWxlID0KICAgICAgICAgICBhcy5pbnRlZ2VyKHN0cl9tYXRjaCgKICAgICAgICAgICAgIGNwX2ZlYXR1cmVfbmFtZSwgIkdyYW51bGFyaXR5XyhbMC05XSspXyIKICAgICAgICAgICApW1syXV0pKSAlPiUKICB1bmdyb3VwKCkKYGBgCgoKYGBge3J9CmVtcHR5X2ZyZXF1ZW5jeSAlPD4lCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZSh0X2ZlYXR1cmVfbmFtZSA9CiAgICAgICAgICBzdHJfbWF0Y2goCiAgICAgICAgICAgICAgIGZlYXR1cmVfbmFtZSwKICAgICAgICAgICAgICAgIihbQS1aYS16MC05XSspXyhbQS1aYS16MC05XSspXyhbMC05XSspXyhbMC05XSspIgogICAgICAgICAgICAgKVtbMl1dCiAgICAgICAgICAgKSAlPiUKICB1bmdyb3VwKCkKYGBgCgoKYGBge3J9CmVtcHR5X2ZyZXF1ZW5jeSAlPD4lCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZShjaGFubmVsX25hbWVfMSA9CiAgICAgICAgICBzdHJfbWF0Y2goCiAgICAgICAgICAgICAgIGNoYW5uZWxfbmFtZSwKICAgICAgICAgICAgICAgIihbQS1aYS16MC05XSspXyhbQS1aYS16MC05XSspIgogICAgICAgICAgICAgKVtbMl1dCiAgICAgICAgICAgKSAlPiUKICBtdXRhdGUoY2hhbm5lbF9uYW1lXzIgPQogICAgICAgICAgc3RyX21hdGNoKAogICAgICAgICAgICAgICBjaGFubmVsX25hbWUsCiAgICAgICAgICAgICAgICIoW0EtWmEtejAtOV0rKV8oW0EtWmEtejAtOV0rKSIKICAgICAgICAgICAgIClbWzNdXQogICAgICAgICAgICkgJT4lCiAgbXV0YXRlKGNfZmVhdHVyZV9uYW1lID0KICAgICAgICAgIHN0cl9tYXRjaCgKICAgICAgICAgICAgICAgZmVhdHVyZV9uYW1lLAogICAgICAgICAgICAgICAiKFtBLVphLXowLTldKylfKFtBLVphLXowLTldKylfKFtBLVphLXowLTldKykiCiAgICAgICAgICAgICApW1syXV0KICAgICAgICAgICApICU+JQogIHVuZ3JvdXAoKQpgYGAKCiMgRGlzcGxheSBzdW1tYXJ5CgpgYGB7cn0KZW1wdHlfZnJlcXVlbmN5ICU+JSBzYW1wbGVfbig1KQpgYGAKCiMgSW5zcGVjdCBzdW1tYXJ5IGZvciBOQXMKCmBgYHtyfQplbXB0eV9mcmVxdWVuY3kgJT4lCiAgZmlsdGVyKG51bWJlcl9vZl9uYSA+IDApICU+JQogIGNvdW50KG51bWJlcl9vZl9uYSkgJT4lIAogIGFycmFuZ2UoZGVzYyhuKSkgJT4lCiAgc2hvd190YWJsZSgpCmBgYApgYGB7cn0KZW1wdHlfZnJlcXVlbmN5ICU+JQogIGZpbHRlcihudW1iZXJfb2ZfbmEgPiA5KSAlPiUKICBnZ3Bsb3QoYWVzKG51bWJlcl9vZl9uYSkpICsgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSA1KQpgYGAKYGBge3J9CmVtcHR5X2ZyZXF1ZW5jeSAlPiUKICBmaWx0ZXIobnVtYmVyX29mX25hID4gOSkgJT4lCiAgc2hvd190YWJsZSgpCgplbXB0eV9mcmVxdWVuY3kgJT4lCiAgZmlsdGVyKG51bWJlcl9vZl9uYSA+IDkpICU+JQogIGdyb3VwX2J5KGZlYXR1cmVfZ3JvdXAsIGNfZmVhdHVyZV9uYW1lKSAlPiUKICB0YWxseSgpICU+JQogIHNob3dfdGFibGUoKQpgYGAKCgojIEluc3BlY3Qgc3VtbWFyeSBmb3IgemVyb3MKCiMjIEFsbCBleGNlcHQgY29ycmVsYXRpb24gYW5kIG5vLWNoYW5uZWwKCkdldCBhbiBvdmVydmlldyBvZiBhbGwgZmVhdHVyZXMsIGV4Y2x1ZGluZyAoZm9yIG5vdykgCi0gYENvcnJlbGF0aW9uYCBmZWF0dXJlcyAKLSBmZWF0dXJlcyBub3QgYXNzb2NpYXRlZCB3aXRoIGEgY2hhbm5lbAoKYGBge3J9CmVtcHR5X2ZyZXF1ZW5jeSAlPiUKICBmaWx0ZXIoZmVhdHVyZV9ncm91cCAhPSAiQ29ycmVsYXRpb24iKSAlPiUgCiAgZmlsdGVyKGNoYW5uZWxfbmFtZSAhPSAiTm9uZSIpICU+JQogIGdncGxvdChhZXMoY29tcGFydG1lbnQsIG51bWJlcl9vZl96ZXJvKSkgKyAKICBnZW9tX2JveHBsb3QoKSArIAogIGZhY2V0X2dyaWQoY2hhbm5lbF9uYW1lIH4gZmVhdHVyZV9ncm91cCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDAsIHZqdXN0ID0gMCkpCmBgYAoKIyMgQWx3YXlzIHJlbW92ZSBsb2NhdGlvbgoKRXhjbHVkZSBgTG9jYXRpb25gIGJlY2F1c2Ugd2UgZGVmaW5pdGVseSB3YW50IHRvIGV4Y2x1ZGUgdGhlbQoKYExvY2F0aW9uYCBmZWF0dXJlcyBhcmUgZ2VuZXJhdGVkIGJ5OgoKLSBbTWVhc3VyZU9iamVjdFNpemVTaGFwZV0oaHR0cDovL2NlbGxwcm9maWxlci1tYW51YWwuczMuYW1hem9uYXdzLmNvbS9DZWxsUHJvZmlsZXItMy4xLjUvbW9kdWxlcy9tZWFzdXJlbWVudC5odG1sI21lYXN1cmVvYmplY3RzaXplc2hhcGUpICAoYExvY2F0aW9uX0NlbnRlcl97WHxZfWApCi0gW01lYXN1cmVPYmplY3RJbnRlbnNpdHldKGh0dHA6Ly9jZWxscHJvZmlsZXItbWFudWFsLnMzLmFtYXpvbmF3cy5jb20vQ2VsbFByb2ZpbGVyLTMuMS41L21vZHVsZXMvbWVhc3VyZW1lbnQuaHRtbCNtZWFzdXJlb2JqZWN0aW50ZW5zaXR5KSAgKGBMb2NhdGlvbl97Q2VudGVyTWFzc3xNYXh9SW50ZW5zaXR5YCkKLSBbSWRlbnRpZnlQcmltYXJ5T2JqZWN0c10oaHR0cDovL2NlbGxwcm9maWxlci1tYW51YWwuczMuYW1hem9uYXdzLmNvbS9DZWxsUHJvZmlsZXItMy4xLjUvbW9kdWxlcy9tZWFzdXJlbWVudC5odG1sI2lkZW50aWZ5cHJpbWFyeW9iamVjdHMpIG9yIFtJZGVudGlmeVNlY29uZGFyeU9iamVjdHNdKGh0dHA6Ly9jZWxscHJvZmlsZXItbWFudWFsLnMzLmFtYXpvbmF3cy5jb20vQ2VsbFByb2ZpbGVyLTMuMS41L21vZHVsZXMvbWVhc3VyZW1lbnQuaHRtbCNpZGVudGlmeXNlY29uZGFyeW9iamVjdHMpICAoYExvY2F0aW9uX3tYfFl9YCkKCmBgYHtyfQpsb2NhdGlvbl9mZWF0dXJlcyA8LQogIHN0cl9zdWJzZXQoY2hlY2tfZW1wdHlfZmVhdHVyZXMsIAogICAgICAgICAgICAgIihMb2NhdGlvbl9DZW50ZXJfKFh8WXxaKSl8KExvY2F0aW9uXyhYfFl8WikpfChMb2NhdGlvbl8oQ2VudGVyTWFzc3xNYXgpSW50ZW5zaXR5XyhYfFl8WikpIikgJT4lCiAgc29ydCgpCgp0aWJibGUobG9jYXRpb25fZmVhdHVyZXMpICU+JQogIHNob3dfdGFibGUoKQpgYGAKCiMjIE9ubHkgbm8tY2hhbm5lbAoKR2V0IGFuIG92ZXJ2aWV3IG9mIG9ubHkgZmVhdHVyZXMgbm90IGFzc29jaWF0ZWQgd2l0aCBhIGNoYW5uZWwKCmBgYHtyfQplbXB0eV9mcmVxdWVuY3kgJT4lCiAgZmlsdGVyKGNoYW5uZWxfbmFtZSA9PSAiTm9uZSIpICU+JQogIGZpbHRlcighKGNwX2ZlYXR1cmVfbmFtZSAlaW4lIGMobG9jYXRpb25fZmVhdHVyZXMpKSkgJT4lCiAgZ2dwbG90KGFlcyhjb21wYXJ0bWVudCwgbnVtYmVyX29mX3plcm8pKSArIAogIGdlb21fYm94cGxvdCgpICsgCiAgZmFjZXRfZ3JpZChjaGFubmVsX25hbWUgfiBmZWF0dXJlX2dyb3VwKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMCwgdmp1c3QgPSAwKSkKYGBgCgoKYGBge3J9CmVtcHR5X2ZyZXF1ZW5jeSAlPiUKICBmaWx0ZXIoY2hhbm5lbF9uYW1lID09ICJOb25lIikgJT4lCiAgZmlsdGVyKCEoY3BfZmVhdHVyZV9uYW1lICVpbiUgYyhsb2NhdGlvbl9mZWF0dXJlcykpKSAlPiUKICBmaWx0ZXIobnVtYmVyX29mX3plcm8gPiAwKSAlPiUKICBhcnJhbmdlKGRlc2MobnVtYmVyX29mX3plcm8pKSAlPiUKICBzZWxlY3QoY3BfZmVhdHVyZV9uYW1lLCBmZWF0dXJlX2dyb3VwLCBudW1iZXJfb2ZfemVybywgbnVtYmVyX29mX25hKQpgYGAKCiMjIyBFeGNsdWRlIEV1bGVyIGFuZCBDZW50ZXIgCgpFeGNsdWRlIAoKLSBgRXVsZXJOdW1iZXJgIC0gKnRoaXMgbWF5IGJlIGluZm9ybWF0aXZlIGluIHNvbWUgY2FzZXMsIGJ1dCB3ZSBkcm9wIGluIHRoaXMgYW5hbHlzaXMqCi0gYEFyZWFTaGFwZV9DZW50ZXJfe1gsWSxafWAKCkZyb20gW01lYXN1cmVPYmplY3RTaXplU2hhcGVdKGh0dHA6Ly9jZWxscHJvZmlsZXItbWFudWFsLnMzLmFtYXpvbmF3cy5jb20vQ2VsbFByb2ZpbGVyLTMuMS41L21vZHVsZXMvbWVhc3VyZW1lbnQuaHRtbCNtZWFzdXJlb2JqZWN0c2l6ZXNoYXBlKTogX0NlbnRlcl9YLCBDZW50ZXJfWSwgQ2VudGVyX1o6IFRoZSB4LSwgeS0sIGFuZCAoZm9yIDNEIG9iamVjdHMpIHotIGNvb3JkaW5hdGVzIG9mIHRoZSBwb2ludCBmYXJ0aGVzdCBhd2F5IGZyb20gYW55IG9iamVjdCBlZGdlICh0aGUgY2VudHJvaWQpLiBOb3RlIHRoYXQgdGhpcyBpcyBub3QgdGhlIHNhbWUgYXMgdGhlIExvY2F0aW9uLVggYW5kIC1ZIG1lYXN1cmVtZW50cyBwcm9kdWNlZCBieSB0aGUgSWRlbnRpZnkgb3IgV2F0ZXJzaGVkIG1vZHVsZXMgb3IgdGhlIExvY2F0aW9uLVogbWVhc3VyZW1lbnQgcHJvZHVjZWQgYnkgdGhlIFdhdGVyc2hlZCBtb2R1bGUuXwoKCmBgYHtyfQplbXB0eV9mcmVxdWVuY3kgJT4lCiAgZmlsdGVyKGNoYW5uZWxfbmFtZSA9PSAiTm9uZSIpICU+JQogIGZpbHRlcighKGNwX2ZlYXR1cmVfbmFtZSAlaW4lIGMobG9jYXRpb25fZmVhdHVyZXMpKSkgJT4lCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KGZlYXR1cmVfbmFtZSwgIkV1bGVyTnVtYmVyIikpICU+JQogIGZpbHRlcighc3RyX2RldGVjdChjcF9mZWF0dXJlX25hbWUsICJBcmVhU2hhcGVfQ2VudGVyXyhYfFl8WikiKSkgJT4lCiAgZmlsdGVyKG51bWJlcl9vZl96ZXJvID4gMCkgJT4lCiAgYXJyYW5nZShkZXNjKG51bWJlcl9vZl96ZXJvKSkgJT4lCiAgc2VsZWN0KGNwX2ZlYXR1cmVfbmFtZSwgZmVhdHVyZV9ncm91cCwgbnVtYmVyX29mX3plcm8sIG51bWJlcl9vZl9uYSkKYGBgCgojIyMgRXhjbHVkZSB2ZXJ5IHNtYWxsIGRpc3RhbmNlcwoKYE51Y2xlaV9OZWlnaGJvcnNfKl8yYCBpcyBtb3N0bHkgYDBgIGJlY2F1c2UgdHdvIHBpeGVscyBpcyB0b28gbGl0dGxlIC0gKnRoaXMgbWF5IGJlIGluZm9ybWF0aXZlIGF0IGxvd2VyIG1hZ25pZmljYXRpb25zLCBidXQgd2UgZHJvcCBpbiB0aGlzIGFuYWx5c2lzKiAKCkRvIHdlIGFjdHVhbGx5IG5lZWQgYE51Y2xlaV9OZWlnaGJvcnNfKmAgb3IgaXMgYENlbGxzX05laWdoYm9yc18qYCBzdWZmaWNpZW50PyBOb3Qgc3VyZS4KCmBgYHtyfQplbXB0eV9mcmVxdWVuY3kgJT4lCiAgZmlsdGVyKGNoYW5uZWxfbmFtZSA9PSAiTm9uZSIpICU+JQogIGZpbHRlcighKGNwX2ZlYXR1cmVfbmFtZSAlaW4lIGMobG9jYXRpb25fZmVhdHVyZXMpKSkgJT4lCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KGZlYXR1cmVfbmFtZSwgIkV1bGVyTnVtYmVyIikpICU+JQogIGZpbHRlcighc3RyX2RldGVjdChjcF9mZWF0dXJlX25hbWUsICJBcmVhU2hhcGVfQ2VudGVyXyhYfFl8WikiKSkgJT4lCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KGNwX2ZlYXR1cmVfbmFtZSwgIk51Y2xlaV9OZWlnaGJvcnNfLipfMiIpKSAlPiUKICBmaWx0ZXIobnVtYmVyX29mX3plcm8gPiAwKSAlPiUKICBhcnJhbmdlKGRlc2MobnVtYmVyX29mX3plcm8pKSAlPiUKICBzZWxlY3QoY3BfZmVhdHVyZV9uYW1lLCBmZWF0dXJlX2dyb3VwLCBudW1iZXJfb2ZfemVybywgbnVtYmVyX29mX25hKQpgYGAKCiMjIyBFeGNsdWRlIENsb3Nlc3RPYmplY3ROdW1iZXIKCi0gYENlbGxzX05laWdoYm9yc19OdW1iZXJPZk5laWdoYm9yc19BZGphY2VudCA9PSAwYCBhcmUgImlzb2xhdGVkIiBjZWxscyAKLSBgKkNsb3Nlc3RPYmplY3ROdW1iZXJgIHNob3VsZCBiZSBkcm9wcGVkIGJlY2F1c2UgaXQgaXMgdGhlIGluZGV4IG9mIHRoZSBmaXJzdCBvciBzZWNvbmQgY2xvc2VzdCBvYmplY3QKCmBgYHtyfQplbXB0eV9mcmVxdWVuY3kgJT4lCiAgZmlsdGVyKGNoYW5uZWxfbmFtZSA9PSAiTm9uZSIpICU+JQogIGZpbHRlcighKGNwX2ZlYXR1cmVfbmFtZSAlaW4lIGMobG9jYXRpb25fZmVhdHVyZXMpKSkgJT4lCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KGZlYXR1cmVfbmFtZSwgIkV1bGVyTnVtYmVyIikpICU+JQogIGZpbHRlcighc3RyX2RldGVjdChjcF9mZWF0dXJlX25hbWUsICJBcmVhU2hhcGVfQ2VudGVyXyhYfFl8WikiKSkgJT4lCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KGNwX2ZlYXR1cmVfbmFtZSwgIk51Y2xlaV9OZWlnaGJvcnNfLipfMiIpKSAlPiUKICBmaWx0ZXIoIXN0cl9kZXRlY3QoY3BfZmVhdHVyZV9uYW1lLCAiTmVpZ2hib3JzXyhGaXJzdHxTZWNvbmQpQ2xvc2VzdE9iamVjdE51bWJlcl8uKiIpKSAlPiUKICBmaWx0ZXIobnVtYmVyX29mX3plcm8gPiAwKSAlPiUKICBhcnJhbmdlKGRlc2MobnVtYmVyX29mX3plcm8pKSAlPiUKICBzZWxlY3QoY3BfZmVhdHVyZV9uYW1lLCBmZWF0dXJlX2dyb3VwLCBudW1iZXJfb2ZfemVybywgbnVtYmVyX29mX25hKQpgYGAKClRoZSByZXN0IG9mIHRoZSBtZWFzdXJlbWVudHMgYWJvdmUgYXJlIHJlYXNvbmFibGUgdG8gcHJlc2VydmUuCgojIyBBbGwgZXhjZXB0IGNvcnJlbGF0aW9uIGFuZCBuby1jaGFubmVsICAKCmBgYHtyfQplbXB0eV9mcmVxdWVuY3kgJT4lCiAgZmlsdGVyKCEoY3BfZmVhdHVyZV9uYW1lICVpbiUgYyhsb2NhdGlvbl9mZWF0dXJlcykpKSAlPiUKICBmaWx0ZXIoZmVhdHVyZV9ncm91cCAhPSAiQ29ycmVsYXRpb24iKSAlPiUgCiAgZmlsdGVyKGNoYW5uZWxfbmFtZSAhPSAiTm9uZSIpICU+JQogIGdncGxvdChhZXMoY29tcGFydG1lbnQsIG51bWJlcl9vZl96ZXJvKSkgKyAKICBnZW9tX2JveHBsb3QoKSArIAogIGZhY2V0X2dyaWQoY2hhbm5lbF9uYW1lIH4gZmVhdHVyZV9ncm91cCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDAsIHZqdXN0ID0gMCkpCmBgYAoKIyMjIE9ubHkgZ3JhbnVsYXJpdHkKCk1vcmUgemVyb3MgYXQgbGFyZ2VyIHNjYWxlcwoKYGBge3J9CmVtcHR5X2ZyZXF1ZW5jeSAlPiUKICBmaWx0ZXIoZmVhdHVyZV9ncm91cCA9PSAiR3JhbnVsYXJpdHkiKSAlPiUgCiAgZ2dwbG90KGFlcyhnX3NjYWxlLCBudW1iZXJfb2ZfemVybywgY29sb3IgPSBjb21wYXJ0bWVudCkpICsgCiAgZ2VvbV9saW5lKCkgKyAKICBmYWNldF93cmFwKH5jaGFubmVsX25hbWUpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAwLCB2anVzdCA9IDApKQpgYGAKCgojIyMgT25seSB0ZXh0dXJlCgpNb3JlIHplcm9zIGF0IGxhcmdlciBzY2FsZXMKCmBgYHtyfQplbXB0eV9mcmVxdWVuY3kgJT4lCiAgZmlsdGVyKGZlYXR1cmVfZ3JvdXAgPT0gIlRleHR1cmUiKSAlPiUKICBnZ3Bsb3QoYWVzKGNvbXBhcnRtZW50LCBudW1iZXJfb2ZfemVybykpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZmFjZXRfZ3JpZCh0X3NjYWxlIH4gY2hhbm5lbF9uYW1lKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAwLCB2anVzdCA9IDApKQpgYGAKClRoaW5ncyBzZWVtcyBhIGJpdCBvZmYgaW4gCi0gTWl0bwotIFNjYWxlID0gMjAgaW4gTnVjbGVpCgojIyMjIE1pdG8KCmBgYHtyfQplbXB0eV9mcmVxdWVuY3kgJT4lCiAgZmlsdGVyKGZlYXR1cmVfZ3JvdXAgPT0gIlRleHR1cmUiKSAlPiUKICBmaWx0ZXIoY2hhbm5lbF9uYW1lID09ICJNaXRvIikgJT4lCiAgZ2dwbG90KGFlcyh0X2ZlYXR1cmVfbmFtZSwgbnVtYmVyX29mX3plcm8sIGNvbG9yID0gYXMuZmFjdG9yKHRfc2NhbGUpKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZmFjZXRfd3JhcCh+IGNvbXBhcnRtZW50KSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAwLCB2anVzdCA9IDApKQpgYGAKCiMjIyMgU2NhbGU9MjAgaW4gTnVjbGVpLCBub24tTWl0bwoKYGBge3J9CmVtcHR5X2ZyZXF1ZW5jeSAlPiUKICBmaWx0ZXIoZmVhdHVyZV9ncm91cCA9PSAiVGV4dHVyZSIpICU+JQogIGZpbHRlcih0X3NjYWxlID09IDIwICYgY29tcGFydG1lbnQgPT0gIk51Y2xlaSIgJiBjaGFubmVsX25hbWUgIT0gIk1pdG8iKSAlPiUKICBnZ3Bsb3QoYWVzKHRfZmVhdHVyZV9uYW1lLCBudW1iZXJfb2ZfemVybywgY29sb3IgPSBjaGFubmVsX25hbWUpKSArCiAgZ2VvbV9wb2ludCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDAsIHZqdXN0ID0gMCkpCmBgYAoKIyMjIE5vIGdyYW51bGFyaXR5IGFuZCB0ZXh0dXJlCgoKYGBge3J9CmVtcHR5X2ZyZXF1ZW5jeSAlPiUKICBmaWx0ZXIoIShjcF9mZWF0dXJlX25hbWUgJWluJSBjKGxvY2F0aW9uX2ZlYXR1cmVzKSkpICU+JQogIGZpbHRlcihmZWF0dXJlX2dyb3VwICE9ICJDb3JyZWxhdGlvbiIpICU+JSAKICBmaWx0ZXIoY2hhbm5lbF9uYW1lICE9ICJOb25lIikgJT4lCiAgZmlsdGVyKCEoZmVhdHVyZV9ncm91cCAlaW4lIGMoIkdyYW51bGFyaXR5IiwgIlRleHR1cmUiKSkpICU+JSAKICBmaWx0ZXIobnVtYmVyX29mX3plcm8gPiAwKSAlPiUKICBhcnJhbmdlKGRlc2MobnVtYmVyX29mX3plcm8pKSAlPiUKICBzZWxlY3QoY3BfZmVhdHVyZV9uYW1lLCBmZWF0dXJlX2dyb3VwLCBudW1iZXJfb2ZfemVybywgbnVtYmVyX29mX25hKQpgYGAKClRoZSBpbm5lcm1vc3QgcmluZyBvZiBgUmFkaWFsRGlzdHJpYnV0aW9uYCBpcyBleHBlY3RlZCB0byBiZSB6ZXJvIGluIHNvbWUgY2FzZXMsIGZvciBjeXRvcGxhc20sIHNvIHRoaXMgaXMgZmluZS4KCiMjIE9ubHkgY29ycmVsYXRpb24KCmBgYHtyfQplbXB0eV9mcmVxdWVuY3kgJT4lCiAgZmlsdGVyKCEoY3BfZmVhdHVyZV9uYW1lICVpbiUgYyhsb2NhdGlvbl9mZWF0dXJlcykpKSAlPiUKICBmaWx0ZXIoZmVhdHVyZV9ncm91cCA9PSAiQ29ycmVsYXRpb24iKSAlPiUgCiAgZ2dwbG90KGFlcyhjb21wYXJ0bWVudCwgbnVtYmVyX29mX3plcm8sIGNvbG9yID0gY19mZWF0dXJlX25hbWUpKSArIAogIGdlb21fcG9pbnQoKSArIAogIGZhY2V0X2dyaWQoY2hhbm5lbF9uYW1lXzEgfiBjaGFubmVsX25hbWVfMikgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDAsIHZqdXN0ID0gMCkpCmBgYAojIyMgTm8gQ29zdGVzCgpgYGB7cn0KZW1wdHlfZnJlcXVlbmN5ICU+JQogIGZpbHRlcighKGNwX2ZlYXR1cmVfbmFtZSAlaW4lIGMobG9jYXRpb25fZmVhdHVyZXMpKSkgJT4lCiAgZmlsdGVyKGZlYXR1cmVfZ3JvdXAgPT0gIkNvcnJlbGF0aW9uIikgJT4lIAogIGZpbHRlcihjX2ZlYXR1cmVfbmFtZSAhPSAiQ29zdGVzIikgJT4lIAogIGdncGxvdChhZXMoY29tcGFydG1lbnQsIG51bWJlcl9vZl96ZXJvLCBjb2xvciA9IGNfZmVhdHVyZV9uYW1lKSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBmYWNldF9ncmlkKGNoYW5uZWxfbmFtZV8xIH4gY2hhbm5lbF9uYW1lXzIpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAwLCB2anVzdCA9IDApKQpgYGAKYGBge3J9CmVtcHR5X2ZyZXF1ZW5jeSAlPiUKICBmaWx0ZXIoIShjcF9mZWF0dXJlX25hbWUgJWluJSBjKGxvY2F0aW9uX2ZlYXR1cmVzKSkpICU+JQogIGZpbHRlcihmZWF0dXJlX2dyb3VwID09ICJDb3JyZWxhdGlvbiIpICU+JSAKICBmaWx0ZXIoY19mZWF0dXJlX25hbWUgIT0gIkNvc3RlcyIpICU+JSAKICBnZ3Bsb3QoYWVzKGNvbXBhcnRtZW50LCBudW1iZXJfb2ZfbmEsIGNvbG9yID0gY19mZWF0dXJlX25hbWUpKSArIAogIGdlb21fcG9pbnQoKSArIAogIGZhY2V0X2dyaWQoY2hhbm5lbF9uYW1lXzEgfiBjaGFubmVsX25hbWVfMikgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDAsIHZqdXN0ID0gMCkpCmBgYAoK