Setup knitr and load utility functions

knitr::opts_chunk$set(echo = TRUE)
knitr::opts_knit$set(root.dir="E:/DISC/reproducibility")
utilities_path = "./source/utilities.r"
source(utilities_path)
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
reldist: Relative Distribution Methods
Version 1.6-6 created on 2016-10-07.
copyright (c) 2003, Mark S. Handcock, University of California-Los Angeles
 For citation information, type citation("reldist").
 Type help(package="reldist") to get started.

Load raw data and downsampling

Here, we use MELANOMA dataset as an example.
The fraction of sampling is set to 50% of the original library size across cells.
Only 1 replicate will be generated here as an example.
Only imputed genes will be kept for comparison.

raw_data = readh5_loom("./data/MELANOMA/raw.loom")
[1] "./data/MELANOMA/raw.loom"
[1] 32287  8498
ds_dir = "./data/MELANOMA/ds_0.5/r1"
dir.create(ds_dir, showWarnings = F, recursive = T)
output_dir = "./results/MELANOMA/ds_0.5/r1"
dir.create(output_dir, showWarnings = F, recursive = T)
observed_path = paste0(ds_dir, "/observed.loom")
if(!file.exists(observed_path)){
  observed_data = downsampling_cell(0.5, raw_data)
  save_h5(observed_path, t(observed_data))
}else{
  observed_data = readh5_loom(observed_path)
}
[1] "./data/MELANOMA/ds_0.5/r1/observed.loom"
[1] 32287  8498
gene_filter = gene_selection(observed_data, 10)
raw_data = raw_data[gene_filter, ]
observed_data = observed_data[gene_filter, ]
used_genes = rownames(observed_data)
print(dim(raw_data))
[1] 12509  8498
print(dim(observed_data))
[1] 12509  8498
data_list = list(Raw = raw_data, Observed = observed_data)
rm(raw_data, observed_data)

Load downsampling data and imputation results

We run imputation using downsampling data.
Here, we load all imputation results.

data_list[["DISC"]] = readh5_loom(paste0(ds_dir, "/DISC.loom"))[used_genes, ]
[1] "./data/MELANOMA/ds_0.5/r1/DISC.loom"
[1] 32287  8498
print(dim(data_list[["DISC"]]))
[1] 12509  8498
data_list[["scVI"]] = readh5_imputation(paste0(ds_dir, "/scVI.hdf5"))
[1] "./data/MELANOMA/ds_0.5/r1/scVI.hdf5"
[1] 12509  8498
data_list[["MAGIC"]] = readh5_imputation(paste0(ds_dir, "/MAGIC.hdf5"))
[1] "./data/MELANOMA/ds_0.5/r1/MAGIC.hdf5"
[1] 12509  8498
data_list[["DCA"]] = readh5_imputation(paste0(ds_dir, "/DCA.hdf5"))
[1] "./data/MELANOMA/ds_0.5/r1/DCA.hdf5"
[1] 12509  8498
data_list[["scScope"]] = readh5_imputation(paste0(ds_dir, "/scScope.hdf5"))
[1] "./data/MELANOMA/ds_0.5/r1/scScope.hdf5"
[1] 12509  8498
data_list[["DeepImpute"]] = readh5_imputation(paste0(ds_dir, "/DeepImpute.hdf5"))
[1] "./data/MELANOMA/ds_0.5/r1/DeepImpute.hdf5"
[1] 12509  8498
data_list[["VIPER"]] = readh5_imputation(paste0(ds_dir, "/VIPER.hdf5"))
[1] "./data/MELANOMA/ds_0.5/r1/VIPER.hdf5"
[1] 12509  8498
data_list[["scImpute"]] = readh5_imputation(paste0(ds_dir, "/scImpute.hdf5"))
[1] "./data/MELANOMA/ds_0.5/r1/scImpute.hdf5"
[1] 12509  8498
cell_number = ncol(data_list[["Raw"]])
gene_number = length(used_genes)

Settings

method_names = c("Observed", "DISC", "scVI", "MAGIC", "DCA", "scScope", "DeepImpute", "VIPER", "scImpute")
method_color = c("#A5A5A5", "#E83828", "#278BC4", "#EADE36", "#198B41", "#920783", "#F8B62D", "#8E5E32", "#1E2B68")
names(method_color) = method_names
text_color = rep("black", length(method_names))
names(text_color) = method_names
text_color["DISC"] = "red"

MAE

MAE_mat = matrix(nrow = cell_number, ncol = length(method_names), dimnames = list(c(), method_names))
ls_raw = colSums(data_list[["Raw"]])
for(ii in method_names){
  ls_this = colSums(data_list[[ii]])
    scale_factor = ls_raw / ls_this
    MAE_cell = sapply(seq(cell_number), function(x){
      expressed_mask = data_list[["Raw"]][, x] > 0
      expressed_number = sum(expressed_mask)
      error = data_list[["Raw"]][expressed_mask, x] - (data_list[[ii]][expressed_mask, x] * scale_factor[x])
      return(sum(abs(error)) / expressed_number)
    })
  MAE_mat[, ii] = MAE_cell
  print(ii)
}
[1] "Observed"
[1] "DISC"
[1] "scVI"
[1] "MAGIC"
[1] "DCA"
[1] "scScope"
[1] "DeepImpute"
[1] "VIPER"
[1] "scImpute"
MAE_mat = MAE_mat[rowSums(is.na(MAE_mat)) < 1, ]
MAE_df = melt(t(MAE_mat))
MAE_levels = colnames(MAE_mat)[c(1, order(colMeans(MAE_mat)[-1], decreasing = F) + 1)]
ggplot(MAE_df, aes(x = factor(Var1, levels = MAE_levels), y = value, fill = factor(Var1, levels = MAE_levels))) +
  geom_boxplot(outlier.shape = NA) + stat_boxplot(geom = "errorbar", width = 0.3) +
  ylim(min(apply(MAE_mat, 2, quantile, 0.1)), max(apply(MAE_mat, 2, quantile, 0.9))) + theme_classic() +
  labs(x="", y="MAE") + scale_fill_manual(values=method_color) +
  theme(axis.text.x = element_text(size = 12,angle = 45, hjust = 1, vjust = 1, face = "bold"),
        axis.text.y = element_text(size = 12, hjust = 1, vjust = 1, face = "bold"),
        axis.title.y = element_text(size = 15, face = "bold"),
        legend.position = "none")

CMD

vst_file = paste0(output_dir, "/vst_gene.tsv")
if(file.exists(vst_file)){
  hvg_info = read.table(vst_file)
  print("load vst_file")
}else{
  hvg_info = FindVariableFeatures_vst_by_genes(data_list[["Raw"]])
  hvg_info = hvg_info[order(hvg_info$variance.standardized, decreasing = T), ]
  write.table(hvg_info, paste0(output_dir, "/vst_gene.tsv"), sep = "\t", quote = F, row.names = T, col.names = T)
}
used_feature_genes = rownames(hvg_info)[1:300]
cor_all = list()
for(ii in names(data_list)){
  if(ii == "Raw"){
    cor_all[[ii]] = calc_cor_mat(data_list[[ii]][used_feature_genes, ])
  }else{
    cor_all[[ii]] = calc_cor_mat(delete_lt0.5(data_list[[ii]])[used_feature_genes, ])
  }
  print(ii)
}
[1] "Raw"
[1] "Observed"
[1] "DISC"
[1] "scVI"
[1] "MAGIC"
[1] "DCA"
[1] "scScope"
[1] "DeepImpute"
[1] "VIPER"
[1] "scImpute"
saveRDS(cor_all, paste0(output_dir, "/cor_all.rds"))
cmd_value = c()
for(ii in method_names){
  cmd_value = c(cmd_value, calc_cmd(cor_all[["Raw"]], cor_all[[ii]]))
}
names(cmd_value) = method_names
barplot_usage(cmd_value, main = "", cex.main = 1.5, bar_color = method_color, text_color = text_color, use_data_order = T, ylab = "CMD", cex.lab = 1.5, font.main = 1, ylim = c(-0.1, 1), use_border = F)

Gene correlation

gene_corr_mat = matrix(nrow = gene_number, ncol = length(method_names), dimnames = list(c(), method_names))
for(ii in method_names){
  gene_corr_mat[, ii] = calc_corr(data_list[["Raw"]], data_list[[ii]], "gene")
  print(ii)
}
[1] "Observed"
[1] "DISC"
[1] "scVI"
[1] "MAGIC"
[1] "DCA"
[1] "scScope"
[1] "DeepImpute"
[1] "VIPER"
[1] "scImpute"
gene_corr_mat = gene_corr_mat[rowSums(is.na(gene_corr_mat)) < 1, ]
gene_corr_df = melt(t(gene_corr_mat))
gene_corr_levels = colnames(gene_corr_mat)[c(1, order(colMeans(gene_corr_mat)[-1], decreasing = T) + 1)]
ggplot(gene_corr_df, aes(x = factor(Var1, levels = gene_corr_levels), y = value, fill = factor(Var1, levels = gene_corr_levels))) +
  geom_boxplot(outlier.shape = NA) + stat_boxplot(geom = "errorbar", width = 0.3) +
  ylim(min(apply(gene_corr_mat, 2, quantile, 0.1)), max(apply(gene_corr_mat, 2, quantile, 0.9))) + theme_classic() +
  labs(x="", y="Gene correlation with reference") + scale_fill_manual(values=method_color) +
  theme(axis.text.x = element_text(size = 12,angle = 45, hjust = 1, vjust = 1, face = "bold"),
        axis.text.y = element_text(size = 12, hjust = 1, vjust = 1, face = "bold"),
        axis.title.y = element_text(size = 15, face = "bold"),
        legend.position = "none")

Cell correlation

cell_corr_mat = matrix(nrow = cell_number, ncol = length(method_names), dimnames = list(c(), method_names))
for(ii in method_names){
  cell_corr_mat[, ii] = calc_corr(data_list[["Raw"]], data_list[[ii]], "cell")
  print(ii)
}
[1] "Observed"
[1] "DISC"
[1] "scVI"
[1] "MAGIC"
[1] "DCA"
[1] "scScope"
[1] "DeepImpute"
[1] "VIPER"
[1] "scImpute"
cell_corr_mat = cell_corr_mat[rowSums(is.na(cell_corr_mat)) < 1, ]
cell_corr_df = melt(t(cell_corr_mat))
cell_corr_levels = colnames(cell_corr_mat)[c(1, order(colMeans(cell_corr_mat)[-1], decreasing = T) + 1)]
ggplot(cell_corr_df, aes(x = factor(Var1, levels = cell_corr_levels), y = value, fill = factor(Var1, levels = cell_corr_levels))) +
  geom_boxplot(outlier.shape = NA) + stat_boxplot(geom = "errorbar", width = 0.3) +
  ylim(min(apply(cell_corr_mat, 2, quantile, 0.1)), max(apply(cell_corr_mat, 2, quantile, 0.9))) + theme_classic() +
  labs(x="", y="Cell correlation with reference") + scale_fill_manual(values=method_color) +
  theme(axis.text.x = element_text(size = 12,angle = 45, hjust = 1, vjust = 1, face = "bold"),
        axis.text.y = element_text(size = 12, hjust = 1, vjust = 1, face = "bold"),
        axis.title.y = element_text(size = 15, face = "bold"),
        legend.position = "none")

LS0tDQp0aXRsZTogIkRyb3BvdXQgZXZlbnQgcmVjb3ZlcnkiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KIyMjIFNldHVwIGtuaXRyIGFuZCBsb2FkIHV0aWxpdHkgZnVuY3Rpb25zDQpgYGB7ciBzZXR1cH0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmtuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyPSJFOi9ESVNDL3JlcHJvZHVjaWJpbGl0eSIpDQpgYGANCmBgYHtyfQ0KdXRpbGl0aWVzX3BhdGggPSAiLi9zb3VyY2UvdXRpbGl0aWVzLnIiDQpzb3VyY2UodXRpbGl0aWVzX3BhdGgpDQpgYGANCiMjIyBMb2FkIHJhdyBkYXRhIGFuZCBkb3duc2FtcGxpbmcNCkhlcmUsIHdlIHVzZSBNRUxBTk9NQSBkYXRhc2V0IGFzIGFuIGV4YW1wbGUuPC9icj4NClRoZSBmcmFjdGlvbiBvZiBzYW1wbGluZyBpcyBzZXQgdG8gNTAlIG9mIHRoZSBvcmlnaW5hbCBsaWJyYXJ5IHNpemUgYWNyb3NzIGNlbGxzLjwvYnI+DQpPbmx5IDEgcmVwbGljYXRlIHdpbGwgYmUgZ2VuZXJhdGVkIGhlcmUgYXMgYW4gZXhhbXBsZS48L2JyPg0KT25seSBpbXB1dGVkIGdlbmVzIHdpbGwgYmUga2VwdCBmb3IgY29tcGFyaXNvbi4NCmBgYHtyfQ0KcmF3X2RhdGEgPSByZWFkaDVfbG9vbSgiLi9kYXRhL01FTEFOT01BL3Jhdy5sb29tIikNCmRzX2RpciA9ICIuL2RhdGEvTUVMQU5PTUEvZHNfMC41L3IxIg0KZGlyLmNyZWF0ZShkc19kaXIsIHNob3dXYXJuaW5ncyA9IEYsIHJlY3Vyc2l2ZSA9IFQpDQpvdXRwdXRfZGlyID0gIi4vcmVzdWx0cy9NRUxBTk9NQS9kc18wLjUvcjEiDQpkaXIuY3JlYXRlKG91dHB1dF9kaXIsIHNob3dXYXJuaW5ncyA9IEYsIHJlY3Vyc2l2ZSA9IFQpDQpvYnNlcnZlZF9wYXRoID0gcGFzdGUwKGRzX2RpciwgIi9vYnNlcnZlZC5sb29tIikNCmlmKCFmaWxlLmV4aXN0cyhvYnNlcnZlZF9wYXRoKSl7DQogIG9ic2VydmVkX2RhdGEgPSBkb3duc2FtcGxpbmdfY2VsbCgwLjUsIHJhd19kYXRhKQ0KICBzYXZlX2g1KG9ic2VydmVkX3BhdGgsIHQob2JzZXJ2ZWRfZGF0YSkpDQp9ZWxzZXsNCiAgb2JzZXJ2ZWRfZGF0YSA9IHJlYWRoNV9sb29tKG9ic2VydmVkX3BhdGgpDQp9DQpnZW5lX2ZpbHRlciA9IGdlbmVfc2VsZWN0aW9uKG9ic2VydmVkX2RhdGEsIDEwKQ0KcmF3X2RhdGEgPSByYXdfZGF0YVtnZW5lX2ZpbHRlciwgXQ0Kb2JzZXJ2ZWRfZGF0YSA9IG9ic2VydmVkX2RhdGFbZ2VuZV9maWx0ZXIsIF0NCnVzZWRfZ2VuZXMgPSByb3duYW1lcyhvYnNlcnZlZF9kYXRhKQ0KcHJpbnQoZGltKHJhd19kYXRhKSkNCnByaW50KGRpbShvYnNlcnZlZF9kYXRhKSkNCmRhdGFfbGlzdCA9IGxpc3QoUmF3ID0gcmF3X2RhdGEsIE9ic2VydmVkID0gb2JzZXJ2ZWRfZGF0YSkNCnJtKHJhd19kYXRhLCBvYnNlcnZlZF9kYXRhKQ0KYGBgDQojIyMgTG9hZCBkb3duc2FtcGxpbmcgZGF0YSBhbmQgaW1wdXRhdGlvbiByZXN1bHRzDQpXZSA8YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vaXloYW9vL0RJU0MvYmxvYi9tYXN0ZXIvcmVwcm9kdWNpYmlsaXR5L3R1dG9yaWFscy9ydW5faW1wdXRhdGlvbi5tZCI+cnVuIGltcHV0YXRpb248L2E+ICB1c2luZyBkb3duc2FtcGxpbmcgZGF0YS48L2JyPg0KSGVyZSwgd2UgbG9hZCBhbGwgaW1wdXRhdGlvbiByZXN1bHRzLg0KYGBge3J9DQpkYXRhX2xpc3RbWyJESVNDIl1dID0gcmVhZGg1X2xvb20ocGFzdGUwKGRzX2RpciwgIi9ESVNDLmxvb20iKSlbdXNlZF9nZW5lcywgXQ0KcHJpbnQoZGltKGRhdGFfbGlzdFtbIkRJU0MiXV0pKQ0KZGF0YV9saXN0W1sic2NWSSJdXSA9IHJlYWRoNV9pbXB1dGF0aW9uKHBhc3RlMChkc19kaXIsICIvc2NWSS5oZGY1IikpDQpkYXRhX2xpc3RbWyJNQUdJQyJdXSA9IHJlYWRoNV9pbXB1dGF0aW9uKHBhc3RlMChkc19kaXIsICIvTUFHSUMuaGRmNSIpKQ0KZGF0YV9saXN0W1siRENBIl1dID0gcmVhZGg1X2ltcHV0YXRpb24ocGFzdGUwKGRzX2RpciwgIi9EQ0EuaGRmNSIpKQ0KZGF0YV9saXN0W1sic2NTY29wZSJdXSA9IHJlYWRoNV9pbXB1dGF0aW9uKHBhc3RlMChkc19kaXIsICIvc2NTY29wZS5oZGY1IikpDQpkYXRhX2xpc3RbWyJEZWVwSW1wdXRlIl1dID0gcmVhZGg1X2ltcHV0YXRpb24ocGFzdGUwKGRzX2RpciwgIi9EZWVwSW1wdXRlLmhkZjUiKSkNCmRhdGFfbGlzdFtbIlZJUEVSIl1dID0gcmVhZGg1X2ltcHV0YXRpb24ocGFzdGUwKGRzX2RpciwgIi9WSVBFUi5oZGY1IikpDQpkYXRhX2xpc3RbWyJzY0ltcHV0ZSJdXSA9IHJlYWRoNV9pbXB1dGF0aW9uKHBhc3RlMChkc19kaXIsICIvc2NJbXB1dGUuaGRmNSIpKQ0KY2VsbF9udW1iZXIgPSBuY29sKGRhdGFfbGlzdFtbIlJhdyJdXSkNCmdlbmVfbnVtYmVyID0gbGVuZ3RoKHVzZWRfZ2VuZXMpDQpgYGANCiMjIyBTZXR0aW5ncw0KYGBge3J9DQptZXRob2RfbmFtZSA9IGMoIk9ic2VydmVkIiwgIkRJU0MiLCAic2NWSSIsICJNQUdJQyIsICJEQ0EiLCAic2NTY29wZSIsICJEZWVwSW1wdXRlIiwgIlZJUEVSIiwgInNjSW1wdXRlIikNCm1ldGhvZF9jb2xvciA9IGMoIiNBNUE1QTUiLCAiI0U4MzgyOCIsICIjMjc4QkM0IiwgIiNFQURFMzYiLCAiIzE5OEI0MSIsICIjOTIwNzgzIiwgIiNGOEI2MkQiLCAiIzhFNUUzMiIsICIjMUUyQjY4IikNCm5hbWVzKG1ldGhvZF9jb2xvcikgPSBtZXRob2RfbmFtZQ0KdGV4dF9jb2xvciA9IHJlcCgiYmxhY2siLCBsZW5ndGgobWV0aG9kX25hbWUpKQ0KbmFtZXModGV4dF9jb2xvcikgPSBtZXRob2RfbmFtZQ0KdGV4dF9jb2xvclsiRElTQyJdID0gInJlZCINCmBgYA0KIyMjIE1BRQ0KYGBge3J9DQpNQUVfbWF0ID0gbWF0cml4KG5yb3cgPSBjZWxsX251bWJlciwgbmNvbCA9IGxlbmd0aChtZXRob2RfbmFtZSksIGRpbW5hbWVzID0gbGlzdChjKCksIG1ldGhvZF9uYW1lKSkNCmxzX3JhdyA9IGNvbFN1bXMoZGF0YV9saXN0W1siUmF3Il1dKQ0KZm9yKGlpIGluIG1ldGhvZF9uYW1lKXsNCiAgbHNfdGhpcyA9IGNvbFN1bXMoZGF0YV9saXN0W1tpaV1dKQ0KICAgIHNjYWxlX2ZhY3RvciA9IGxzX3JhdyAvIGxzX3RoaXMNCiAgICBNQUVfY2VsbCA9IHNhcHBseShzZXEoY2VsbF9udW1iZXIpLCBmdW5jdGlvbih4KXsNCiAgICAgIGV4cHJlc3NlZF9tYXNrID0gZGF0YV9saXN0W1siUmF3Il1dWywgeF0gPiAwDQogICAgICBleHByZXNzZWRfbnVtYmVyID0gc3VtKGV4cHJlc3NlZF9tYXNrKQ0KICAgICAgZXJyb3IgPSBkYXRhX2xpc3RbWyJSYXciXV1bZXhwcmVzc2VkX21hc2ssIHhdIC0gKGRhdGFfbGlzdFtbaWldXVtleHByZXNzZWRfbWFzaywgeF0gKiBzY2FsZV9mYWN0b3JbeF0pDQogICAgICByZXR1cm4oc3VtKGFicyhlcnJvcikpIC8gZXhwcmVzc2VkX251bWJlcikNCiAgICB9KQ0KICBNQUVfbWF0WywgaWldID0gTUFFX2NlbGwNCiAgcHJpbnQoaWkpDQp9DQpNQUVfbWF0ID0gTUFFX21hdFtyb3dTdW1zKGlzLm5hKE1BRV9tYXQpKSA8IDEsIF0NCk1BRV9kZiA9IG1lbHQodChNQUVfbWF0KSkNCk1BRV9sZXZlbHMgPSBjb2xuYW1lcyhNQUVfbWF0KVtjKDEsIG9yZGVyKGNvbE1lYW5zKE1BRV9tYXQpWy0xXSwgZGVjcmVhc2luZyA9IEYpICsgMSldDQoNCmBgYA0KDQpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD01fQ0KZ2dwbG90KE1BRV9kZiwgYWVzKHggPSBmYWN0b3IoVmFyMSwgbGV2ZWxzID0gTUFFX2xldmVscyksIHkgPSB2YWx1ZSwgZmlsbCA9IGZhY3RvcihWYXIxLCBsZXZlbHMgPSBNQUVfbGV2ZWxzKSkpICsNCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGUgPSBOQSkgKyBzdGF0X2JveHBsb3QoZ2VvbSA9ICJlcnJvcmJhciIsIHdpZHRoID0gMC4zKSArDQogIHlsaW0obWluKGFwcGx5KE1BRV9tYXQsIDIsIHF1YW50aWxlLCAwLjEpKSwgbWF4KGFwcGx5KE1BRV9tYXQsIDIsIHF1YW50aWxlLCAwLjkpKSkgKyB0aGVtZV9jbGFzc2ljKCkgKw0KICBsYWJzKHg9IiIsIHk9Ik1BRSIpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPW1ldGhvZF9jb2xvcikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsYW5nbGUgPSA0NSwgaGp1c3QgPSAxLCB2anVzdCA9IDEsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGhqdXN0ID0gMSwgdmp1c3QgPSAxLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNSwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCmBgYA0KDQojIyMgQ01EDQpgYGB7cn0NCnZzdF9maWxlID0gcGFzdGUwKG91dHB1dF9kaXIsICIvdnN0X2dlbmUudHN2IikNCmlmKGZpbGUuZXhpc3RzKHZzdF9maWxlKSl7DQogIGh2Z19pbmZvID0gcmVhZC50YWJsZSh2c3RfZmlsZSkNCiAgcHJpbnQoImxvYWQgdnN0X2ZpbGUiKQ0KfWVsc2V7DQogIGh2Z19pbmZvID0gRmluZFZhcmlhYmxlRmVhdHVyZXNfdnN0X2J5X2dlbmVzKGRhdGFfbGlzdFtbIlJhdyJdXSkNCiAgaHZnX2luZm8gPSBodmdfaW5mb1tvcmRlcihodmdfaW5mbyR2YXJpYW5jZS5zdGFuZGFyZGl6ZWQsIGRlY3JlYXNpbmcgPSBUKSwgXQ0KICB3cml0ZS50YWJsZShodmdfaW5mbywgcGFzdGUwKG91dHB1dF9kaXIsICIvdnN0X2dlbmUudHN2IiksIHNlcCA9ICJcdCIsIHF1b3RlID0gRiwgcm93Lm5hbWVzID0gVCwgY29sLm5hbWVzID0gVCkNCn0NCnVzZWRfZmVhdHVyZV9nZW5lcyA9IHJvd25hbWVzKGh2Z19pbmZvKVsxOjMwMF0NCmNvcl9hbGwgPSBsaXN0KCkNCmZvcihpaSBpbiBuYW1lcyhkYXRhX2xpc3QpKXsNCiAgaWYoaWkgPT0gIlJhdyIpew0KICAgIGNvcl9hbGxbW2lpXV0gPSBjYWxjX2Nvcl9tYXQoZGF0YV9saXN0W1tpaV1dW3VzZWRfZmVhdHVyZV9nZW5lcywgXSkNCiAgfWVsc2V7DQogICAgY29yX2FsbFtbaWldXSA9IGNhbGNfY29yX21hdChkZWxldGVfbHQwLjUoZGF0YV9saXN0W1tpaV1dKVt1c2VkX2ZlYXR1cmVfZ2VuZXMsIF0pDQogIH0NCiAgcHJpbnQoaWkpDQp9DQpzYXZlUkRTKGNvcl9hbGwsIHBhc3RlMChvdXRwdXRfZGlyLCAiL2Nvcl9hbGwucmRzIikpDQpjbWRfdmFsdWUgPSBjKCkNCmZvcihpaSBpbiBtZXRob2RfbmFtZSl7DQogIGNtZF92YWx1ZSA9IGMoY21kX3ZhbHVlLCBjYWxjX2NtZChjb3JfYWxsW1siUmF3Il1dLCBjb3JfYWxsW1tpaV1dKSkNCn0NCm5hbWVzKGNtZF92YWx1ZSkgPSBtZXRob2RfbmFtZQ0KYGBgDQpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD01fQ0KYmFycGxvdF91c2FnZShjbWRfdmFsdWUsIG1haW4gPSAiIiwgY2V4Lm1haW4gPSAxLjUsIGJhcl9jb2xvciA9IG1ldGhvZF9jb2xvciwgdGV4dF9jb2xvciA9IHRleHRfY29sb3IsIHVzZV9kYXRhX29yZGVyID0gVCwgeWxhYiA9ICJDTUQiLCBjZXgubGFiID0gMS41LCBmb250Lm1haW4gPSAxLCB5bGltID0gYygtMC4xLCAxKSwgdXNlX2JvcmRlciA9IEYpDQpgYGANCg0KDQojIyMgR2VuZSBjb3JyZWxhdGlvbg0KYGBge3J9DQpnZW5lX2NvcnJfbWF0ID0gbWF0cml4KG5yb3cgPSBnZW5lX251bWJlciwgbmNvbCA9IGxlbmd0aChtZXRob2RfbmFtZSksIGRpbW5hbWVzID0gbGlzdChjKCksIG1ldGhvZF9uYW1lKSkNCmZvcihpaSBpbiBtZXRob2RfbmFtZSl7DQogIGdlbmVfY29ycl9tYXRbLCBpaV0gPSBjYWxjX2NvcnIoZGF0YV9saXN0W1siUmF3Il1dLCBkYXRhX2xpc3RbW2lpXV0sICJnZW5lIikNCiAgcHJpbnQoaWkpDQp9DQpnZW5lX2NvcnJfbWF0ID0gZ2VuZV9jb3JyX21hdFtyb3dTdW1zKGlzLm5hKGdlbmVfY29ycl9tYXQpKSA8IDEsIF0NCmdlbmVfY29ycl9kZiA9IG1lbHQodChnZW5lX2NvcnJfbWF0KSkNCmdlbmVfY29ycl9sZXZlbHMgPSBjb2xuYW1lcyhnZW5lX2NvcnJfbWF0KVtjKDEsIG9yZGVyKGNvbE1lYW5zKGdlbmVfY29ycl9tYXQpWy0xXSwgZGVjcmVhc2luZyA9IFQpICsgMSldDQpgYGANCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTV9DQpnZ3Bsb3QoZ2VuZV9jb3JyX2RmLCBhZXMoeCA9IGZhY3RvcihWYXIxLCBsZXZlbHMgPSBnZW5lX2NvcnJfbGV2ZWxzKSwgeSA9IHZhbHVlLCBmaWxsID0gZmFjdG9yKFZhcjEsIGxldmVscyA9IGdlbmVfY29ycl9sZXZlbHMpKSkgKw0KICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZSA9IE5BKSArIHN0YXRfYm94cGxvdChnZW9tID0gImVycm9yYmFyIiwgd2lkdGggPSAwLjMpICsNCiAgeWxpbShtaW4oYXBwbHkoZ2VuZV9jb3JyX21hdCwgMiwgcXVhbnRpbGUsIDAuMSkpLCBtYXgoYXBwbHkoZ2VuZV9jb3JyX21hdCwgMiwgcXVhbnRpbGUsIDAuOSkpKSArIHRoZW1lX2NsYXNzaWMoKSArDQogIGxhYnMoeD0iIiwgeT0iR2VuZSBjb3JyZWxhdGlvbiB3aXRoIHJlZmVyZW5jZSIpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPW1ldGhvZF9jb2xvcikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsYW5nbGUgPSA0NSwgaGp1c3QgPSAxLCB2anVzdCA9IDEsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGhqdXN0ID0gMSwgdmp1c3QgPSAxLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNSwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCmBgYA0KIyMjIENlbGwgY29ycmVsYXRpb24NCmBgYHtyfQ0KY2VsbF9jb3JyX21hdCA9IG1hdHJpeChucm93ID0gY2VsbF9udW1iZXIsIG5jb2wgPSBsZW5ndGgobWV0aG9kX25hbWUpLCBkaW1uYW1lcyA9IGxpc3QoYygpLCBtZXRob2RfbmFtZSkpDQpmb3IoaWkgaW4gbWV0aG9kX25hbWUpew0KICBjZWxsX2NvcnJfbWF0WywgaWldID0gY2FsY19jb3JyKGRhdGFfbGlzdFtbIlJhdyJdXSwgZGF0YV9saXN0W1tpaV1dLCAiY2VsbCIpDQogIHByaW50KGlpKQ0KfQ0KY2VsbF9jb3JyX21hdCA9IGNlbGxfY29ycl9tYXRbcm93U3Vtcyhpcy5uYShjZWxsX2NvcnJfbWF0KSkgPCAxLCBdDQpjZWxsX2NvcnJfZGYgPSBtZWx0KHQoY2VsbF9jb3JyX21hdCkpDQpjZWxsX2NvcnJfbGV2ZWxzID0gY29sbmFtZXMoY2VsbF9jb3JyX21hdClbYygxLCBvcmRlcihjb2xNZWFucyhjZWxsX2NvcnJfbWF0KVstMV0sIGRlY3JlYXNpbmcgPSBUKSArIDEpXQ0KYGBgDQpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD00LjV9DQpnZ3Bsb3QoY2VsbF9jb3JyX2RmLCBhZXMoeCA9IGZhY3RvcihWYXIxLCBsZXZlbHMgPSBjZWxsX2NvcnJfbGV2ZWxzKSwgeSA9IHZhbHVlLCBmaWxsID0gZmFjdG9yKFZhcjEsIGxldmVscyA9IGNlbGxfY29ycl9sZXZlbHMpKSkgKw0KICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZSA9IE5BKSArIHN0YXRfYm94cGxvdChnZW9tID0gImVycm9yYmFyIiwgd2lkdGggPSAwLjMpICsNCiAgeWxpbShtaW4oYXBwbHkoY2VsbF9jb3JyX21hdCwgMiwgcXVhbnRpbGUsIDAuMSkpLCBtYXgoYXBwbHkoY2VsbF9jb3JyX21hdCwgMiwgcXVhbnRpbGUsIDAuOSkpKSArIHRoZW1lX2NsYXNzaWMoKSArDQogIGxhYnMoeD0iIiwgeT0iQ2VsbCBjb3JyZWxhdGlvbiB3aXRoIHJlZmVyZW5jZSIpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPW1ldGhvZF9jb2xvcikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsYW5nbGUgPSA0NSwgaGp1c3QgPSAxLCB2anVzdCA9IDEsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGhqdXN0ID0gMSwgdmp1c3QgPSAxLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNSwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCmBgYA0KDQoNCg0KDQoNCg0KDQo=