1 Introducción

Un píxel (de picture element) es la unidad más pequeña de una imagen digital: un cuadrito de un solo color.

Una imagen digital es un arreglo de intensidades cuyos valores representan los niveles de color de cada píxel, la imagen también se puede ver como una cuadrícula de píxeles organizada en filas y columnas que se representa como un arreglo de \(n_r \times n_c \times 3\) números (filas, columnas y los tres canales de color).

Una red de adyacencia de regiones (RAG, region adjacency graph) describe la relación entre sus partes:

  • cada vértice es un superpíxel, es decir, una región pequeña, conexa y de color aproximadamente homogéneo;
  • cada arista une dos superpíxeles que se tocan (comparten al menos un lado de píxel, adyacencia de 4 vecinos);
  • cada arista lleva un peso de similitud visual que indica qué tan parecidos son los colores de las dos regiones.

El objetivo no es reconocer objetos sino caracterizar la estructura visual de la imagen. Las comunidades de la red agrupan regiones visualmente relacionadas, que pueden coincidir o no con objetos reales.

1.1 Superpíxeles con SLIC

SLIC (Simple Linear Iterative Clustering) es un \(k\)-medias local en el espacio (color, posición). Se fija el número deseado de superpíxeles \(K\), se inicializan centros en una rejilla de paso \(S=\sqrt{N/K}\) (\(N\) = número de píxeles) y cada píxel se asigna al centro cercano que minimiza

\[ D=\sqrt{d_c^2+\left(\frac{m}{S}\,d_s\right)^2}, \]

donde \(d_c\) es la distancia de color, \(d_s\) la distancia espacial y \(m\) la compactness. Valores grandes de \(m\) producen regiones regulares (casi cuadradas); valores pequeños hacen que las regiones sigan mejor los bordes de color.

1.2 Espacios de color: RGB y CIELAB

Los colores de una imagen pueden describirse en distintos espacios de color. La imagen se lee en RGB, pero las distancias entre colores se calculan en CIELAB. La razón es que en RGB la distancia euclidiana no corresponde a la diferencia de color que percibe una persona, y en CIELAB sí, de forma aproximada.

Rueda de tonos con sus coordenadas RGB. Cada tono puro tiene un ángulo (0° rojo, 120° verde, 240° azul) y una terna (R, G, B) en la escala 0–255.

Rueda de tonos con sus coordenadas RGB. Cada tono puro tiene un ángulo (0° rojo, 120° verde, 240° azul) y una terna (R, G, B) en la escala 0–255.

RGB es un modelo aditivo: cada píxel se describe por la intensidad de tres luces primarias, rojo (R), verde (G) y azul (B), cada una entre 0 y 255. El negro es (0, 0, 0), el blanco (255, 255, 255) y los colores intermedios se obtienen sumando luces: rojo + verde = amarillo (255, 255, 0), verde + azul = cian (0, 255, 255) y rojo + azul = magenta (255, 0, 255), como se ve en la rueda. Es el formato natural de las cámaras y las pantallas, pero tiene dos problemas para medir diferencias de color:

  • No es perceptualmente uniforme. La misma distancia euclidiana en RGB puede corresponder a diferencias muy visibles en una zona del espacio y casi imperceptibles en otra. En la rueda, por ejemplo, la franja que el ojo ve como “verde” ocupa un rango de ángulos mucho más amplio que la del amarillo o el cian: muchos valores RGB distintos se perciben casi iguales.
  • Mezcla luminosidad y color. Oscurecer un color reduce los tres canales a la vez, así que una misma superficie bajo luz y bajo sombra queda lejos en RGB aunque su color sea el mismo.
Representación del espacio CIELAB. El eje vertical es la luminosidad L* (negro abajo, blanco arriba); el plano horizontal contiene los ejes de color a* (verde a rojo) y b* (azul a amarillo).

Representación del espacio CIELAB. El eje vertical es la luminosidad L* (negro abajo, blanco arriba); el plano horizontal contiene los ejes de color a* (verde a rojo) y b* (azul a amarillo).

CIELAB (o L*a*b*, definido por la Comisión Internacional de la Iluminación en 1976) separa la luminosidad del color y organiza los colores como lo hace la visión humana, mediante pares de colores opuestos:

  • L* es la luminosidad, de 0 (negro) a 100 (blanco): el eje vertical de la esfera.
  • a* va del verde (valores negativos) al rojo (positivos).
  • b* va del azul (negativos) al amarillo (positivos).

En el plano horizontal, el ángulo indica el tono y la distancia al eje central indica la saturación: los grises están sobre el eje y los colores más vivos, en el borde. CIELAB se diseñó para que la distancia euclidiana entre dos colores, llamada \(\Delta E\), sea aproximadamente proporcional a la diferencia percibida: un \(\Delta E\approx 2{,}3\) es, en promedio, la menor diferencia que una persona distingue, y diferencias mayores que 10 se perciben como colores claramente distintos. Por eso es el espacio adecuado para definir \(d_{ij}\) y, con ella, el peso de similitud visual.

La conversión de RGB a CIELAB (función rgb_to_lab) tiene tres pasos:

  1. Linealización. Los valores RGB de una imagen digital (sRGB) están codificados con una corrección gamma. Se pasan a \([0,1]\) y se deshace esa corrección para obtener intensidades de luz proporcionales a la energía física.
  2. Paso a XYZ. Una matriz lineal fija transforma las intensidades RGB en las coordenadas XYZ de la CIE, que describen el color según la respuesta de los tres tipos de conos del ojo. Se dividen por el blanco de referencia D65 (luz de día).
  3. Paso a L*a*b*. Se aplica una raíz cúbica (con un tramo lineal cerca de cero), que imita la respuesta no lineal del ojo a la intensidad, y se combinan los resultados: L* sale de Y, a* de la diferencia entre X e Y y b* de la diferencia entre Y y Z.

1.3 Atributos, distancia de color y pesos

Para cada superpíxel \(i\) se calcula su área, su centroide y su color medio en CIELAB, \((\bar L_i,\bar a_i,\bar b_i)\). CIELAB se usa porque las distancias euclidianas en ese espacio se aproximan a diferencias de color percibidas. La distancia entre regiones vecinas es

\[ d_{ij}=\sqrt{(\bar L_i-\bar L_j)^2+(\bar a_i-\bar a_j)^2+(\bar b_i-\bar b_j)^2}, \]

y la similitud visual es

\[ w_{ij}=\exp\!\left(-\frac{d_{ij}}{h}\right),\qquad h>0,\qquad w_{ij}\in(0,1]. \]

El parámetro \(h\) es una escala de color: indica cuántas unidades de \(\Delta E\) hacen caer la similitud a \(e^{-1}\approx 0.37\). Para medidas basadas en caminos (intermediación) se usa el costo \(\ell_{ij}=1/w_{ij}\). Además de \(w_{ij}\), cada arista guarda la longitud de frontera \(b_{ij}\) (número de lados de píxel compartidos).

La función exponencial no es la única opción: puede usarse cualquier similitud que decrezca con la distancia de color, por ejemplo un núcleo gaussiano \(\exp(-d_{ij}^2/2h^2)\). Con la elegida, si dos superpíxeles tienen el mismo color medio en CIELAB, \(d_{ij}=0\) y \(w_{ij}=1\); a medida que \(d_{ij}\) crece, \(w_{ij}\) decrece hacia 0 sin alcanzarlo (asíntota en 0). Por eso los pesos son intensidades: un valor grande indica regiones parecidas, y para las medidas basadas en caminos se usa el costo \(\ell_{ij}=1/w_{ij}\).

1.4 Filtrado por umbral \(\tau\)

Como \(w_{ij}>0\) para todo par de superpíxeles vecinos, la red de regiones es conexa y conserva aristas incluso entre regiones de colores muy distintos. Para estudiar la estructura de la red se conservan solo las aristas con \(w_{ij}\ge\tau\). Como \(w_{ij}\) es monótona en \(d_{ij}\), esto equivale a

\[ w_{ij}\ge\tau \iff d_{ij}\le -h\ln\tau . \]

Al aumentar \(\tau\) se eliminan las conexiones entre regiones de colores distintos y la red se fragmenta en componentes.

Por referencia se utiliza \(\tau=0.5\).

1.5 Comunidades: Louvain y Leiden

Ambos algoritmos maximizan la modularidad ponderada

\[ Q=\frac{1}{2W}\sum_{i,j}\left(w_{ij}-\frac{s_i s_j}{2W}\right)\delta(c_i,c_j), \]

donde \(s_i=\sum_j w_{ij}\) es la fuerza del vértice \(i\) y \(W\) la suma de pesos. Louvain alterna movimientos locales de vértices y agregación de comunidades. Leiden añade una fase de refinamiento que garantiza comunidades internamente conectadas, y suele alcanzar modularidades iguales o mayores. Proyectando la comunidad de cada superpíxel sobre sus píxeles se obtiene una segmentación de la imagen. Una modularidad alta no garantiza una segmentación “buena”: \(Q\) es una medida interna de la red, no de la calidad visual.

1.6 Evaluación y robustez

Se distingue evaluación interna (modularidad) y externa (ARI, IoU, F1 de fronteras, que requieren una segmentación de referencia), y se propone estudiar la sensibilidad a \(K\), \(m\), \(h\) y \(\tau\), y la robustez frente a ruido, suavizado o cambios de iluminación. Los experimentos de esta relatoría se ubican precisamente en ese último bloque.

2 Implementación

Las siguientes funciones nos van a permitir la construcción del análisis: preparación de la imagen, SLIC, grafo de regiones con distancias CIELAB y pesos \(w_{ij}\), filtrado por \(\tau\), detección de comunidades y proyección a píxeles.

suppressPackageStartupMessages({
  library(OpenImageR)
  library(igraph)
  library(dplyr)
  library(tidyr)
  library(ggplot2)
  library(knitr)
})
set.seed(2026)

2.1 Funciones de gráficos

Se definen primero para poder mostrar el resultado de cada paso en las secciones siguientes.

boundary_mask <- function(part) {
  b <- matrix(FALSE, nrow(part), ncol(part))
  b[, -ncol(part)] <- b[, -ncol(part)] | part[, -ncol(part)] != part[, -1]
  b[-nrow(part), ] <- b[-nrow(part), ] | part[-nrow(part), ] != part[-1, ]
  b
}

# Cada píxel se pinta con el color medio de su región/comunidad
mean_color_image <- function(img, part) {
  out <- img
  for (k in 1:3) {
    ch <- img[, , k]; mu <- tapply(ch, part, mean)
    out[, , k] <- matrix(mu[as.character(part)], nrow(part))
  }
  out
}

plot_panel <- function(img, part = NULL, main = "", fill = TRUE,
                       border_col = c(0, 0, 0), interpolate = is.null(part)) {
  nr <- dim(img)[1]; nc <- dim(img)[2]
  ras <- if (!is.null(part) && fill) mean_color_image(img, part) else img
  ras <- ras / 255
  if (!is.null(part)) {
    b <- boundary_mask(part)
    for (k in 1:3) { tmp <- ras[, , k]; tmp[b] <- border_col[k]; ras[, , k] <- tmp }
  }
  plot.new()
  plot.window(c(0, nc), c(0, nr), asp = 1, xaxs = "i", yaxs = "i")
  rasterImage(as.raster(ras), 0, 0, nc, nr, interpolate = interpolate)
  title(main, cex.main = 0.85, font.main = 1, line = 0.3)
}

# Red de centroides sobre la imagen; grosor y opacidad proporcionales a w_ij
plot_graph_overlay <- function(res, main = "") {
  g <- res$graph; img <- res$img; nr <- dim(img)[1]
  plot_panel(img, main = main)
  el <- ends(g, E(g), names = FALSE)
  x <- V(g)$col - 0.5; y <- nr - V(g)$row + 0.5
  segments(x[el[, 1]], y[el[, 1]], x[el[, 2]], y[el[, 2]],
           lwd = 0.4 + 2.2 * E(g)$w,
           col = rgb(1, 1, 1, alpha = 0.25 + 0.75 * E(g)$w))
  points(x, y, pch = 19, cex = 0.35, col = "#FFD400")
}
# Imagen usada para ilustrar cada paso de la implementación
ruta_ej <- "paisaje_rdavila.jpg"

2.2 Preparación de la imagen y ruido

# Lee una imagen, la deja en RGB con valores en [0, 255] y la reduce de tamaño
prepare_rgb <- function(path, max_side = 240) {
  img <- OpenImageR::readImage(path)
  if (length(dim(img)) == 2) img <- array(img, c(dim(img), 3))
  if (dim(img)[3] == 4) img <- img[, , 1:3]
  if (max(img) <= 1) img <- img * 255
  nr <- dim(img)[1]; nc <- dim(img)[2]
  s <- max_side / max(nr, nc)
  if (s < 1) {
    img <- OpenImageR::resizeImage(img, round(nr * s), round(nc * s),
                                   method = "bilinear")
    if (max(img) <= 1) img <- img * 255
  }
  pmin(pmax(img, 0), 255)
}

# Ruido gaussiano aditivo N(0, sigma^2) en cada canal (escala 0-255)
add_noise <- function(img, sigma, seed = 1) {
  if (sigma == 0) return(img)
  set.seed(seed)
  out <- img + array(rnorm(length(img), 0, sigma), dim(img))
  pmin(pmax(out, 0), 255)
}

# sRGB (0-255) -> CIELAB (iluminante D65)
rgb_to_lab <- function(rgb) {
  v <- rgb / 255
  v <- ifelse(v <= 0.04045, v / 12.92, ((v + 0.055) / 1.055)^2.4)
  M <- matrix(c(0.4124564, 0.3575761, 0.1804375,
                0.2126729, 0.7151522, 0.0721750,
                0.0193339, 0.1191920, 0.9503041), 3, byrow = TRUE)
  xyz <- v %*% t(M)
  xyz <- sweep(xyz, 2, c(0.95047, 1, 1.08883), "/")
  f <- ifelse(xyz > (6 / 29)^3, xyz^(1 / 3), xyz / (3 * (6 / 29)^2) + 4 / 29)
  cbind(L = 116 * f[, 2] - 16,
        a = 500 * (f[, 1] - f[, 2]),
        b = 200 * (f[, 2] - f[, 3]))
}
img_ej <- prepare_rgb(ruta_ej, max_side = 400)
lab_ej <- rgb_to_lab(matrix(img_ej, ncol = 3))
L_ej   <- matrix(lab_ej[, "L"], nrow(img_ej), ncol(img_ej))   # luminancia L* (0-100)

op <- par(mfrow = c(1, 3), mar = c(0.3, 0.3, 1.6, 0.3))
plot_panel(img_ej, main = sprintf("Imagen preparada (%d x %d px)", nrow(img_ej), ncol(img_ej)))
plot_panel(add_noise(img_ej, 25), main = "Con ruido gaussiano, sigma = 25")
plot_panel(array(L_ej / 100 * 255, c(dim(L_ej), 3)), main = "Canal L* de CIELAB")

par(op)

Como ilustración se usa la fotografía del paisaje, reducida a 400 píxeles en su lado mayor como en el resto del análisis. El ruido gaussiano se suma canal por canal y se recorta a [0, 255]. El tercer panel muestra el canal L* de CIELAB: solo la luminosidad, sin información de color. Las zonas iluminadas de la fachada y el cielo tienen L* alto y los árboles y la vía, L* bajo; los canales a* y b* (no mostrados) contienen el color.

2.3 Superpíxeles y grafo de regiones

# SLIC: devuelve una matriz de etiquetas 1, ..., K'
run_slic <- function(img, n_superpixels = 180, compactness = 20) {
  s <- OpenImageR::superpixels(img, method = "slic",
                               superpixel = n_superpixels,
                               compactness = compactness,
                               return_slic_data = FALSE,
                               return_labels = TRUE, verbose = FALSE)
  lab <- s$labels
  matrix(as.integer(factor(lab)), nrow(lab), ncol(lab))
}

# Pares de regiones vecinas (4 vecinos) y longitud de frontera b_ij
region_edges <- function(lab) {
  hz <- cbind(as.vector(lab[, -ncol(lab)]), as.vector(lab[, -1]))
  vt <- cbind(as.vector(lab[-nrow(lab), ]), as.vector(lab[-1, ]))
  p  <- rbind(hz, vt)
  p  <- p[p[, 1] != p[, 2], , drop = FALSE]
  data.frame(from = pmin(p[, 1], p[, 2]), to = pmax(p[, 1], p[, 2])) |>
    count(from, to, name = "border")
}

# Construye la red de regiones. Si 'labels' se da, se usa esa partición fija
image_to_graph <- function(img, labels = NULL, n_superpixels = 180,
                           compactness = 20, h = 20) {
  if (is.null(labels)) labels <- run_slic(img, n_superpixels, compactness)
  nr <- nrow(labels); nc <- ncol(labels)
  px  <- matrix(img, ncol = 3)                 # píxeles x canales
  lab_px <- rgb_to_lab(px)
  id  <- as.vector(labels)
  rr  <- as.vector(row(labels)); cc <- as.vector(col(labels))
  K   <- max(id)
  attr_v <- data.frame(
    name = as.character(1:K),
    area = tabulate(id, K),
    row  = tapply(rr, id, mean), col = tapply(cc, id, mean),
    R = tapply(px[, 1], id, mean), G = tapply(px[, 2], id, mean),
    B = tapply(px[, 3], id, mean),
    L = tapply(lab_px[, 1], id, mean), a = tapply(lab_px[, 2], id, mean),
    b = tapply(lab_px[, 3], id, mean))
  e <- region_edges(labels)
  e$d <- sqrt((attr_v$L[e$from] - attr_v$L[e$to])^2 +
              (attr_v$a[e$from] - attr_v$a[e$to])^2 +
              (attr_v$b[e$from] - attr_v$b[e$to])^2)
  e$w    <- exp(-e$d / h)
  e$cost <- 1 / e$w
  e$from <- as.character(e$from); e$to <- as.character(e$to)
  g <- graph_from_data_frame(e, directed = FALSE, vertices = attr_v)
  g$h <- h
  list(graph = g, labels = labels, img = img)
}

# Recalcula los pesos con otra escala h (no cambia la topología)
set_h <- function(g, h) {
  E(g)$w <- exp(-E(g)$d / h); E(g)$cost <- 1 / E(g)$w; g$h <- h; g
}

# Conserva solo las aristas con w_ij >= tau
filter_graph <- function(g, tau) delete_edges(g, E(g)[w < tau])
res_ej <- image_to_graph(img_ej, n_superpixels = 180, compactness = 20, h = 20)
g_ej   <- res_ej$graph

op <- par(mfrow = c(2, 2), mar = c(0.3, 0.3, 1.6, 0.3))
plot_panel(img_ej, res_ej$labels, fill = FALSE, border_col = c(1, 1, 0),
           main = sprintf("SLIC: %d superpíxeles", vcount(g_ej)))
plot_panel(img_ej, res_ej$labels, main = "Color medio de cada superpíxel")
plot_graph_overlay(res_ej, main = sprintf("Grafo de regiones (%d aristas)", ecount(g_ej)))
plot_graph_overlay(list(graph = filter_graph(g_ej, 0.5), img = img_ej),
                   main = sprintf("Grafo filtrado, tau = 0,5 (%d aristas)",
                                  ecount(filter_graph(g_ej, 0.5))))

par(op)
op <- par(mfrow = c(1, 2), mar = c(4.2, 4.2, 2, 1))
hist(E(g_ej)$d, breaks = 30, col = "#2471A3", border = "white",
     main = "Distancia de color entre vecinos", xlab = expression(d[ij] ~ "(CIELAB)"), ylab = "Aristas")
hist(E(g_ej)$w, breaks = 30, col = "#C0392B", border = "white",
     main = "Pesos de las aristas (h = 20)", xlab = expression(w[ij] == exp(-d[ij] / h)), ylab = "Aristas")
abline(v = 0.5, lty = 2)

par(op)

Cada superpíxel es un vértice y dos superpíxeles vecinos (que comparten frontera) están unidos por una arista. En el grafo sobre la imagen, las líneas más gruesas y opacas son las de mayor peso \(w_{ij}\), es decir, las de regiones con color parecido. El filtrado por \(\tau\) elimina las aristas entre regiones de color muy distinto, que son las que cruzan los bordes de los objetos.

2.4 Comunidades y segmentación

find_communities <- function(g, method = c("louvain", "leiden"), seed = 123) {
  method <- match.arg(method)
  if (ecount(g) == 0) return(list(membership = seq_len(vcount(g)), Q = 0))
  set.seed(seed)
  cl <- switch(method,
    louvain = cluster_louvain(g, weights = E(g)$w),
    leiden  = cluster_leiden(g, objective_function = "modularity",
                             weights = E(g)$w, n_iterations = -1))
  m <- as.integer(membership(cl))
  list(membership = m, Q = modularity(g, m, weights = E(g)$w))
}

# Proyecta la comunidad de cada superpíxel sobre sus píxeles
pixel_partition <- function(labels, membership) {
  matrix(membership[labels], nrow(labels), ncol(labels))
}

# Índice de Rand ajustado entre dos segmentaciones a nivel de píxel
ari <- function(p1, p2) igraph::compare(as.vector(p1), as.vector(p2),
                                        method = "adjusted.rand")
lv_ej <- find_communities(g_ej, "louvain")
ld_ej <- find_communities(g_ej, "leiden")
seg_lv <- pixel_partition(res_ej$labels, lv_ej$membership)
seg_ld <- pixel_partition(res_ej$labels, ld_ej$membership)

op <- par(mfrow = c(1, 3), mar = c(0.3, 0.3, 1.6, 0.3))
plot_panel(img_ej, main = "Imagen original")
plot_panel(img_ej, seg_lv, main = sprintf("Louvain: %d comunidades, Q = %.3f",
                                          max(lv_ej$membership), lv_ej$Q))
plot_panel(img_ej, seg_ld, main = sprintf("Leiden: %d comunidades, Q = %.3f",
                                          max(ld_ej$membership), ld_ej$Q))

par(op)

Cada comunidad del grafo de regiones se proyecta sobre los píxeles de sus superpíxeles y se pinta con su color medio. El índice de Rand ajustado entre las dos segmentaciones es 0.837 (1 = idénticas, cerca de 0 = no más parecidas que al azar).

2.5 Funciones de experimentos

# Resumen estructural de la red (ponderada por w)
net_summary <- function(g) {
  comp <- components(g)
  data.frame(vertices = vcount(g), aristas = ecount(g),
             densidad = round(edge_density(g), 4),
             grado_medio = round(mean(degree(g)), 2),
             w_media = round(mean(E(g)$w), 3),
             transitividad = round(transitivity(g, type = "global"), 3),
             componentes = comp$no,
             aislados = sum(degree(g) == 0))
}

# Segmentaciones Louvain y Leiden para varios h (grafo completo)
experiment_h <- function(res, hs) {
  lapply(hs, function(h) {
    g <- set_h(res$graph, h)
    lv <- find_communities(g, "louvain"); ld <- find_communities(g, "leiden")
    list(h = h, g = g, lv = lv, ld = ld,
         tab = data.frame(h = h,
                          w_media = round(mean(E(g)$w), 3),
                          w_mediana = round(median(E(g)$w), 3),
                          com_louvain = max(lv$membership),
                          Q_louvain = round(lv$Q, 3),
                          com_leiden = max(ld$membership),
                          Q_leiden = round(ld$Q, 3),
                          ARI_louvain_leiden = round(ari(lv$membership, ld$membership), 3)))
  })
}

# Segmentaciones Louvain y Leiden sobre el grafo filtrado G_tau
experiment_tau <- function(res, taus, h = 20, min_area = 0) {
  g0 <- set_h(res$graph, h)
  lapply(taus, function(tau) {
    g <- filter_graph(g0, tau)
    lv <- find_communities(g, "louvain"); ld <- find_communities(g, "leiden")
    comp <- components(g)
    list(tau = tau, g = g, lv = lv, ld = ld,
         tab = data.frame(tau = tau,
                          d_max = ifelse(tau > 0, round(-h * log(tau), 2), Inf),
                          aristas = ecount(g),
                          componentes = comp$no,
                          aislados = sum(degree(g) == 0),
                          com_louvain = max(lv$membership),
                          Q_louvain = round(lv$Q, 3),
                          com_leiden = max(ld$membership),
                          Q_leiden = round(ld$Q, 3),
                            ARI_louvain_leiden = round(ari(lv$membership, ld$membership), 3)
                          
                          )
         
         )
    
  })
}

# Robustez frente a ruido gaussiano.
# - SLIC fijo: los superpíxeles son los de la imagen limpia; el ruido solo cambia
#   los colores medios (y por tanto d_ij y w_ij).
# - SLIC variable: SLIC se recalcula sobre la imagen ruidosa.
# La referencia es la segmentación de la imagen limpia con el mismo método.
experiment_noise <- function(res, sigmas, reps = 5, h = 20,
                             n_superpixels = 180, compactness = 20) {
  ref <- list(louvain = pixel_partition(res$labels, find_communities(res$graph, "louvain")$membership),
              leiden  = pixel_partition(res$labels, find_communities(res$graph, "leiden")$membership))
  out <- list()
  for (s in sigmas) for (r in seq_len(reps)) {
    noisy <- add_noise(res$img, s, seed = 100 * r + s)
    fixed <- image_to_graph(noisy, labels = res$labels, h = h)
    varia <- image_to_graph(noisy, n_superpixels = n_superpixels,
                            compactness = compactness, h = h)
    for (sl in c("fijo", "variable")) {
      rr <- if (sl == "fijo") fixed else varia
      for (m in c("louvain", "leiden")) {
        cm <- find_communities(rr$graph, m)
        pp <- pixel_partition(rr$labels, cm$membership)
        out[[length(out) + 1]] <- data.frame(
          sigma = s, rep = r, slic = sl, metodo = m,
          comunidades = max(cm$membership), Q = cm$Q,
          ARI = ari(pp, ref[[m]]))
      }
    }
  }
  bind_rows(out)
}

# Una realización por sigma para mostrar las segmentaciones (SLIC variable)
noise_examples <- function(res, sigmas, h = 20) {
  lapply(sigmas, function(s) {
    noisy <- add_noise(res$img, s, seed = 100 + s)
    rr <- image_to_graph(noisy, h = h)
    list(sigma = s, img = noisy, res = rr,
         lv = pixel_partition(rr$labels, find_communities(rr$graph, "louvain")$membership),
         ld = pixel_partition(rr$labels, find_communities(rr$graph, "leiden")$membership))
  })
}

# Gráficos de los experimentos
plot_exp_grid <- function(res, ex, key, lab) {
  op <- par(mfrow = c(2, length(ex)), mar = c(0.3, 0.3, 1.6, 0.3))
  for (e in ex) plot_panel(res$img, pixel_partition(res$labels, e$lv$membership),
                           sprintf("Louvain, %s = %s (%d com.)", lab, e[[key]], max(e$lv$membership)))
  for (e in ex) plot_panel(res$img, pixel_partition(res$labels, e$ld$membership),
                           sprintf("Leiden, %s = %s (%d com.)", lab, e[[key]], max(e$ld$membership)))
  par(op)
}

plot_noise_grid <- function(ne) {
  op <- par(mfrow = c(3, length(ne)), mar = c(0.3, 0.3, 1.6, 0.3))
  for (e in ne) plot_panel(e$img, main = sprintf("Imagen, sigma = %s", e$sigma))
  for (e in ne) plot_panel(e$img, e$lv, sprintf("Louvain (%d com.)", max(e$lv)))
  for (e in ne) plot_panel(e$img, e$ld, sprintf("Leiden (%d com.)", max(e$ld)))
  par(op)
}

plot_noise_ari <- function(nz, titulo) {
  nz |>
    group_by(sigma, slic, metodo) |>
    summarise(media = mean(ARI), sd = sd(ARI), .groups = "drop") |>
    ggplot(aes(sigma, media, colour = metodo, linetype = slic)) +
    geom_line(linewidth = 0.8) + geom_point(size = 2) +
    geom_errorbar(aes(ymin = media - sd, ymax = media + sd), width = 1.5) +
    scale_colour_manual(values = c(leiden = "#C0392B", louvain = "#2471A3")) +
    coord_cartesian(ylim = c(0, 1)) +
    labs(x = expression(sigma ~ "del ruido (escala 0-255)"),
         y = "ARI respecto a la imagen limpia",
         colour = "Método", linetype = "SLIC", title = titulo) +
    theme_minimal(base_size = 12)
}

summary_noise <- function(nz) {
  nz |>
    group_by(sigma, slic, metodo) |>
    summarise(comunidades = round(mean(comunidades), 1),
              Q = round(mean(Q), 3),
              ARI_media = round(mean(ARI), 3),
              ARI_sd = round(sd(ARI), 3), .groups = "drop") |>
    arrange(slic, metodo, sigma)
}

3 Imágenes de prueba

  • Paisaje arquitectónico: edificio en concreto con fachada iluminada, zonas verdes, vía con charcos y cielo nublado. Fotografía © Rodrigo Dávila; se usa aquí únicamente con fines académicos.
  • Agnus Dei, Francisco de Zurbarán, c. 1635–1640, Museo del Prado. Obra de dominio público. Es una imagen de bajo contraste cromático: un cordero claro sobre un fondo oscuro casi uniforme, con iluminación tenebrista.

Para el análisis las imágenes se reducen a 400 píxeles en su lado mayor, para que SLIC y los algoritmos de red sean rápidos; las imágenes originales se muestran en alta resolución.

paisaje <- prepare_rgb("paisaje_rdavila.jpg", max_side = 400)
cordero <- prepare_rgb("agnus_dei_zurbaran.jpg", max_side = 400)
# Versiones en alta resolución, solo para mostrar las imágenes originales
paisaje_hd <- prepare_rgb("paisaje_rdavila.jpg", max_side = 1000)
cordero_hd <- prepare_rgb("agnus_dei_zurbaran.jpg", max_side = 1000)
rbind(paisaje = dim(paisaje), agnus_dei = dim(cordero))
##           [,1] [,2] [,3]
## paisaje    258  400    3
## agnus_dei  342  400    3

Parámetros comunes : \(K=180\) superpíxeles solicitados, compactness \(m=20\) y \(h=20\) como valor de referencia.

K <- 180; m <- 20; h0 <- 20
hs     <- c(5, 10, 20, 40, 80)       # escalas de color para w_ij
taus   <- c(0, 0.3, 0.5, 0.7)        # umbrales de filtrado
sigmas <- c(0, 10, 25, 50)           # desviación estándar del ruido
reps   <- 5                          # réplicas de ruido por sigma

4 Paisaje

La fotografía de Rodrigo Dávila muestra el nuevo edificio de la Facultad de Enfermería de la Universidad Nacional de Colombia, sede Bogotá, al atardecer.

En primer plano aparecen zonas verdes del campus, una vía húmeda con charcos que reflejan la luz, árboles y algunos peatones, y al fondo se alcanzan a ver los edificios de ladrillo característicos de la Ciudad Universitaria.

4.1 Red de regiones

res_p <- image_to_graph(paisaje, n_superpixels = K, compactness = m, h = h0)
net_summary(res_p$graph)
vertices aristas densidad grado_medio w_media transitividad componentes aislados
152 407 0.0355 5.36 0.492 0.393 1 0
par(mfrow = c(1, 3), mar = c(0.3, 0.3, 1.6, 0.3))
plot_panel(paisaje_hd, main = "Imagen original")
plot_panel(paisaje, res_p$labels, "Superpíxeles SLIC (color medio)")
plot_graph_overlay(res_p, "Red de regiones (grosor ~ w_ij, h = 20)")

SLIC devuelve 152 superpíxeles (el número final no coincide exactamente con los 180 solicitados porque SLIC ajusta la rejilla inicial a las proporciones de la imagen) y la red tiene 407 aristas. Es una red espacial, casi plana, con grado medio de 5.36, como se espera de una teselación de la imagen. En la superposición se ve que las aristas gruesas (alta similitud) se concentran en el cielo y en el césped, y que las que cruzan la fachada iluminada son delgadas.

4.2 Sensibilidad a \(h\) en la similitud visual

La escala \(h\) fija cuánto “castiga” la similitud una diferencia de color. Con \(h\) pequeño, solo las regiones casi idénticas tienen peso alto y la mayoría de aristas valen casi cero. Con \(h\) grande, todos los pesos se acercan a 1 y la red se vuelve casi no ponderada.

curva <- expand.grid(d = seq(0, 80, by = 0.5), h = hs) |>
  mutate(w = exp(-d / h), h = factor(h))
dist_p <- data.frame(d = E(res_p$graph)$d)
ggplot(curva, aes(d, w, colour = h)) +
  geom_rug(data = dist_p, aes(x = d), inherit.aes = FALSE, alpha = 0.25) +
  geom_line(linewidth = 0.9) +
  labs(x = expression(d[ij] ~ "(" * Delta * E * ", CIELAB)"),
       y = expression(w[ij] == exp(-d[ij] / h)), colour = "h",
       title = "Similitud en función de la distancia de color (marcas: d_ij del paisaje)") +
  theme_minimal(base_size = 12)

ex_h_p <- experiment_h(res_p, hs)
bind_rows(lapply(ex_h_p, `[[`, "tab"))
h w_media w_mediana com_louvain Q_louvain com_leiden Q_leiden ARI_louvain_leiden
5 0.227 0.038 12 0.685 12 0.691 0.986
10 0.340 0.196 11 0.718 11 0.719 0.953
20 0.492 0.442 10 0.711 10 0.724 0.782
40 0.658 0.665 9 0.689 8 0.701 0.594
80 0.796 0.816 8 0.668 9 0.689 0.545
plot_exp_grid(res_p, ex_h_p, "h", "h")

Lectura. La modularidad es invariante a multiplicar todos los pesos por una constante, de modo que \(h\) no actúa como un simple factor de escala: cambia el contraste relativo entre aristas de colores parecidos y distintos. En el paisaje:

  • Con \(h=5\) la mediana de \(w_{ij}\) es apenas 0.038: solo las regiones casi idénticas quedan fuertemente unidas y la segmentación sigue los cambios de color. Separa los dos prados de césped, la vía con charcos, la fachada, los árboles y el cielo en bloques distintos (12 comunidades con ambos métodos).
  • Al aumentar \(h\), los pesos tienden a 1 (mediana 0.816 con \(h=80\)) y la partición se acerca a la de la red no ponderada: las comunidades se convierten en bloques espaciales compactos. Con \(h=40\) el prado de la izquierda se une con los árboles y la parte baja de la fachada en un gran bloque verde; con \(h=80\) ocurre lo mismo a la derecha y la vía se une con el centro de la fachada. El cielo y la fachada se cortan en bloques por razones geométricas, no de color.
  • La modularidad es máxima en \(h=10\) con Louvain (0.718) y en \(h=20\) con Leiden (0.724), y el número de comunidades baja de 12 a 8–9. El acuerdo entre Louvain y Leiden es casi perfecto con \(h=5\) (ARI = 0.99) y cae a 0.55 con \(h=80\): cuando los pesos se igualan aparecen muchas particiones de modularidad parecida que difieren en cortes poco marcados.

Valores intermedios (\(h\approx 10\)–\(20\)) equilibran color y contigüidad.

4.3 Segmentaciones para varios valores de \(\tau\)

Fijado \(h=20\), se filtra la red con \(w_{ij}\ge\tau\) y se aplican Louvain y Leiden sobre la red filtrada \(G_\tau\). La columna d_max es la máxima distancia de color permitida, \(-h\ln\tau\).

ex_t_p <- experiment_tau(res_p, taus, h = h0)
bind_rows(lapply(ex_t_p, `[[`, "tab"))
tau d_max aristas componentes aislados com_louvain Q_louvain com_leiden Q_leiden ARI_louvain_leiden
0.0 Inf 407 1 0 10 0.711 10 0.724 0.782
0.3 24.08 260 17 13 24 0.733 24 0.747 0.873
0.5 13.86 188 39 22 43 0.757 43 0.758 0.975
0.7 7.13 125 79 60 82 0.677 84 0.680 0.762
plot_exp_grid(res_p, ex_t_p, "tau", "tau")

Lectura. Con \(\tau=0\) se usa la red completa. Al subir el umbral se cortan las aristas que cruzan bordes de color, así que ningún algoritmo puede unir regiones de componentes distintos: cada componente de \(G_\tau\) es, como mínimo, una comunidad. En el paisaje la red pasa de 1 componente a 17, 39 y 79 (con \(\tau=0.7\), 60 de ellos son superpíxeles aislados), y el número de comunidades sube de 10 a 24, 43 y 82–84.

  • Con \(\tau=0.3\) (\(d_{ij}\le 24\)) aparecen como comunidades propias las ventanas iluminadas, una ventana oscura de la torre, los charcos y el borde del prado, que la red completa absorbía en bloques más grandes.
  • Con \(\tau=0.5\) emerge la estructura vertical de la fachada (parasoles y ventanales) y la segmentación empieza a parecer un mapa de bordes.
  • Con \(\tau=0.7\) (\(d_{ij}\le 7.1\)) se acerca al nivel de superpíxel: el filtrado, más que los algoritmos de comunidades, determina la partición.

La modularidad sube hasta \(\tau=0.5\), porque la red filtrada ya está “pre-segmentada”, y baja con \(\tau=0.7\), cuando quedan demasiadas comunidades triviales. Desde \(\tau=0.3\) Louvain y Leiden dan prácticamente la misma partición: casi no queda margen de decisión dentro de componentes tan pequeñas.

4.4 Robustez frente a ruido gaussiano

Se suma ruido \(\varepsilon\sim N(0,\sigma^2)\) a cada canal (escala 0–255) y se compara la segmentación ruidosa con la de la imagen limpia mediante el índice de Rand ajustado (ARI) a nivel de píxel (\(1\) = misma partición, \(\approx 0\) = coincidencia al azar). Se consideran dos escenarios:

  • SLIC fijo: se conservan los superpíxeles de la imagen limpia; el ruido solo altera los colores medios y por tanto los pesos.
  • SLIC variable: SLIC se recalcula sobre la imagen ruidosa; el ruido afecta también la teselación.
ne_p <- noise_examples(res_p, sigmas, h = h0)
plot_noise_grid(ne_p)

nz_p <- experiment_noise(res_p, sigmas, reps = reps, h = h0,
                         n_superpixels = K, compactness = m)
summary_noise(nz_p)
sigma slic metodo comunidades Q ARI_media ARI_sd
0 fijo leiden 10.0 0.724 1.000 0.000
10 fijo leiden 10.0 0.722 0.940 0.055
25 fijo leiden 10.0 0.718 0.861 0.098
50 fijo leiden 10.0 0.717 0.905 0.046
0 fijo louvain 10.0 0.711 1.000 0.000
10 fijo louvain 9.6 0.714 0.832 0.058
25 fijo louvain 9.6 0.713 0.810 0.051
50 fijo louvain 10.0 0.709 0.805 0.086
0 variable leiden 10.0 0.724 1.000 0.000
10 variable leiden 9.2 0.715 0.597 0.072
25 variable leiden 8.0 0.682 0.420 0.071
50 variable leiden 5.2 0.580 0.289 0.050
0 variable louvain 10.0 0.711 1.000 0.000
10 variable louvain 8.6 0.708 0.591 0.031
25 variable louvain 7.8 0.678 0.405 0.074
50 variable louvain 5.2 0.577 0.276 0.039
plot_noise_ari(nz_p, "Paisaje: estabilidad de la segmentación frente al ruido")

Lectura. Los dos escenarios se comportan de forma muy distinta.

  • SLIC fijo. El ARI se mantiene alto, entre 0.81 y 0.94 (Leiden 0.86–0.94, Louvain 0.81–0.83), sin una tendencia clara con \(\sigma\): el de Leiden incluso pasa de 0.86 con \(\sigma=25\) a 0.91 con \(\sigma=50\). El número de comunidades (9.6–10) y la modularidad (0.71–0.72) casi no cambian. El ruido se promedia dentro de cada superpíxel (la desviación del color medio cae como \(\sigma/\sqrt{\text{área}}\)), de modo que los pesos apenas cambian. Las diferencias que aparecen se deben sobre todo a la inestabilidad de la maximización de la modularidad: hay varias particiones casi igual de buenas (por ejemplo, dónde cortar la fachada) y una pequeña perturbación de los pesos basta para pasar de una a otra.
  • SLIC variable. Aquí sí hay un deterioro claro: el ARI baja a ≈ 0.59–0.60 con \(\sigma=10\), ≈ 0.41–0.42 con \(\sigma=25\) y ≈ 0.28–0.29 con \(\sigma=50\); el número de comunidades cae de 10 a unas 5 y la modularidad de 0.72 a 0.58. El ruido afecta la teselación: el término de color de SLIC se vuelve poco informativo, los superpíxeles dejan de seguir los bordes y la red hereda regiones que mezclan objetos. Además, el recorte a \([0,255]\) sesga los píxeles oscuros hacia arriba (para un píxel negro, la media de \(\max(0,\varepsilon)\) es \(\sigma/\sqrt{2\pi}\approx 0.4\sigma\)), lo que reduce el contraste. En las figuras, con \(\sigma=25\) la fachada se mezcla con la vía y con los árboles de la izquierda, y con \(\sigma=50\) las comunidades son bloques casi verticales en los que el cielo apenas se distingue.

5 Agnus Dei

Agnus Dei (c. 1635–1640, Museo del Prado) es una de las obras más conocidas de Francisco de Zurbarán. Muestra un cordero blanco tendido de lado sobre una repisa de piedra, con las patas atadas y la cabeza apoyada con mansedumbre, recortado contra un fondo negro casi absoluto. La escena funciona como un bodegón religioso: el cordero simboliza a Cristo como víctima del sacrificio, idea que refuerzan el tenue halo sobre su cabeza y la inscripción latina Tamquam agnus (“como un cordero”), que alude al pasaje de Isaías sobre el cordero llevado al matadero.

Zurbarán emplea un tenebrismo intenso: una luz lateral modela con enorme detalle la textura de la lana, mientras el resto del cuadro se hunde en la penumbra. Esa paleta reducida de blancos cálidos, ocres y negros, con un único contraste dominante entre la figura y el fondo, la hace especialmente interesante para el análisis por redes. Su estructura cromática es mucho más simple que la de una fotografía, pero el claroscuro dentro del cordero genera variaciones sutiles de color.

5.1 Red de regiones

res_c <- image_to_graph(cordero, n_superpixels = K, compactness = m, h = h0)
net_summary(res_c$graph)
vertices aristas densidad grado_medio w_media transitividad componentes aislados
161 435 0.0338 5.4 0.635 0.403 1 0
par(mfrow = c(1, 3), mar = c(0.3, 0.3, 1.6, 0.3))
plot_panel(cordero_hd, main = "Agnus Dei (Zurbarán)")
plot_panel(cordero, res_c$labels, "Superpíxeles SLIC (color medio)")
plot_graph_overlay(res_c, "Red de regiones (grosor ~ w_ij, h = 20)")

SLIC devuelve 161 superpíxeles y 435 aristas. A diferencia del paisaje, esta pintura tiene una paleta muy reducida (blancos cálidos, ocres y negros), de modo que muchas aristas del fondo tienen \(d_{ij}\) pequeño y peso cercano a 1: la similitud media es 0.635, frente a 0.492 en el paisaje.

5.2 Sensibilidad a \(h\)

ex_h_c <- experiment_h(res_c, hs)
bind_rows(lapply(ex_h_c, `[[`, "tab"))
h w_media w_mediana com_louvain Q_louvain com_leiden Q_leiden ARI_louvain_leiden
5 0.358 0.277 10 0.793 11 0.794 0.745
10 0.493 0.526 10 0.788 10 0.791 0.961
20 0.635 0.726 10 0.756 10 0.759 0.916
40 0.763 0.852 9 0.715 9 0.721 0.855
80 0.860 0.923 9 0.685 9 0.692 0.770
plot_exp_grid(res_c, ex_h_c, "h", "h")

Lectura. Para \(h\le 20\) ambos algoritmos separan con claridad el cordero, la repisa ocre y el fondo oscuro, y dividen el cordero en tres o cuatro zonas de luz y sombra. Para \(h\ge 40\) el contraste figura-fondo deja de dominar: la cabeza y la parte delantera del cordero se unen con la repisa y las comunidades pasan a ser bloques espaciales, aunque el fondo oscuro se mantiene aparte. El número de comunidades casi no cambia (9–11), pero la modularidad baja de forma monótona (0.79 → 0.69). El acuerdo Louvain-Leiden es máximo con \(h=10\) (ARI = 0.96) y baja en ambos extremos: 0.75 con \(h=5\), cuando casi todos los pesos son pequeños y la partición depende de unas pocas aristas fuertes, y 0.86 y 0.77 con \(h=40\) y \(h=80\), cuando los pesos se igualan y aparecen muchas particiones equivalentes, de modo que la modularidad ya solo encuentra estructura geométrica.

5.3 Segmentaciones para varios valores de \(\tau\)

ex_t_c <- experiment_tau(res_c, taus, h = h0)
bind_rows(lapply(ex_t_c, `[[`, "tab"))
tau d_max aristas componentes aislados com_louvain Q_louvain com_leiden Q_leiden ARI_louvain_leiden
0.0 Inf 435 1 0 10 0.756 10 0.759 0.916
0.3 24.08 354 1 0 9 0.781 10 0.786 0.777
0.5 13.86 289 7 4 15 0.809 16 0.817 0.747
0.7 7.13 225 31 15 34 0.808 34 0.815 0.780
plot_exp_grid(res_c, ex_t_c, "tau", "tau")

Lectura. El patrón es opuesto al del paisaje. Con \(\tau=0.3\) la red sigue siendo conexa (1 componente) y la segmentación cambia poco, porque las transiciones de color de la pintura son graduales. Con \(\tau=0.5\) hay 7 componentes y empiezan a separarse las patas y zonas de sombra del cordero. Con \(\tau=0.7\) hay 31 componentes y 34 comunidades, pero la fragmentación ocurre dentro del cordero: el modelado en claroscuro de la lana genera diferencias de color mayores que 7 unidades \(\Delta E\). El fondo, en cambio, se mantiene en pocos bloques grandes porque es oscuro y homogéneo. La modularidad sube de 0.76 a 0.81–0.82 con \(\tau=0.5\) y se mantiene con \(\tau=0.7\); como en el paisaje, Louvain y Leiden coinciden casi por completo con umbrales altos.

5.4 Robustez frente a ruido gaussiano

ne_c <- noise_examples(res_c, sigmas, h = h0)
plot_noise_grid(ne_c)

nz_c <- experiment_noise(res_c, sigmas, reps = reps, h = h0,
                         n_superpixels = K, compactness = m)
summary_noise(nz_c)
sigma slic metodo comunidades Q ARI_media ARI_sd
0 fijo leiden 10.0 0.759 1.000 0.000
10 fijo leiden 9.8 0.757 0.922 0.090
25 fijo leiden 10.0 0.754 0.930 0.058
50 fijo leiden 9.6 0.745 0.835 0.072
0 fijo louvain 10.0 0.756 1.000 0.000
10 fijo louvain 9.8 0.751 0.889 0.048
25 fijo louvain 10.0 0.751 0.883 0.044
50 fijo louvain 9.0 0.740 0.807 0.066
0 variable leiden 10.0 0.759 1.000 0.000
10 variable leiden 9.8 0.754 0.764 0.085
25 variable leiden 7.6 0.694 0.450 0.046
50 variable leiden 6.4 0.621 0.258 0.030
0 variable louvain 10.0 0.756 1.000 0.000
10 variable louvain 9.4 0.750 0.685 0.088
25 variable louvain 7.2 0.689 0.444 0.037
50 variable louvain 6.4 0.620 0.260 0.037
plot_noise_ari(nz_c, "Agnus Dei: estabilidad de la segmentación frente al ruido")

Lectura.

  • SLIC fijo. Ambos métodos son estables, Leiden un poco más: ARI ≈ 0.92–0.93 con \(\sigma\le 25\) y 0.84 con \(\sigma=50\), frente a 0.88–0.89 y 0.81 con Louvain. Como en el paisaje, la diferencia se debe sobre todo a que Louvain cambia de solución entre particiones casi equivalentes.
  • SLIC variable. El ARI cae a ≈ 0.69–0.76, 0.44–0.45 y 0.26 para \(\sigma=10, 25, 50\): algo mejor que en el paisaje con ruido leve y prácticamente igual con ruido fuerte. En las figuras, con \(\sigma=10\) el cordero aún se separa del fondo, pero la repisa se une con el cordero; con \(\sigma=25\) su silueta se pierde; con \(\sigma=50\) las comunidades son franjas verticales sin relación con la escena.
  • El recorte a \([0,255]\) pesa más aquí: el fondo es casi negro, así que el ruido recortado lo aclara de forma sistemática y reduce justo el contraste figura-fondo que sostenía la segmentación.

6 Comparación entre imágenes

Lectura. Las dos redes tienen tamaño y estructura casi iguales (152 y 161 vértices, grado medio ≈ 5.4, transitividad ≈ 0.40): la topología la impone SLIC, no la imagen. Lo que las distingue son los pesos. En el Agnus Dei la mediana de \(d_{ij}\) es 6.42, frente a 16.32 en el paisaje, y el 66.4 % de las aristas tiene \(w_{ij}\ge 0.5\), frente al 46.2 %: la paleta reducida de la pintura produce una red mucho más homogénea. Pese a esto, la robustez frente a ruido fuerte (\(\sigma=50\)) es muy parecida en ambas: con SLIC fijo el ARI está entre 0.81 y 0.91, y con SLIC variable cae a 0.26–0.29, lo que confirma que el factor determinante es la teselación y no el contenido de la imagen.

comp <- rbind(cbind(imagen = "Paisaje", net_summary(res_p$graph)),
              cbind(imagen = "Agnus Dei", net_summary(res_c$graph)))
comp
imagen vertices aristas densidad grado_medio w_media transitividad componentes aislados
Paisaje 152 407 0.0355 5.36 0.492 0.393 1 0
Agnus Dei 161 435 0.0338 5.40 0.635 0.403 1 0
dist_both <- rbind(data.frame(imagen = "Paisaje", d = E(res_p$graph)$d),
                   data.frame(imagen = "Agnus Dei", d = E(res_c$graph)$d))
dist_both |>
  group_by(imagen) |>
  summarise(d_mediana = round(median(d), 2), d_media = round(mean(d), 2),
            prop_w_mayor_0.5 = round(mean(exp(-d / h0) >= 0.5), 3))
imagen d_mediana d_media prop_w_mayor_0.5
Agnus Dei 6.42 13.58 0.664
Paisaje 16.32 19.90 0.462
ggplot(dist_both, aes(exp(-d / h0), fill = imagen)) +
  geom_histogram(bins = 30, alpha = 0.6, position = "identity") +
  scale_fill_manual(values = c("Agnus Dei" = "#7D6608", "Paisaje" = "#2471A3")) +
  labs(x = expression(w[ij] ~ "(h = 20)"), y = "Número de aristas", fill = NULL,
       title = "Distribución de similitudes visuales") +
  theme_minimal(base_size = 12)

bind_rows(mutate(nz_p, imagen = "Paisaje"), mutate(nz_c, imagen = "Agnus Dei")) |>
  filter(sigma == max(sigmas)) |>
  group_by(imagen, slic, metodo) |>
  summarise(ARI_media = round(mean(ARI), 3), .groups = "drop")
imagen slic metodo ARI_media
Agnus Dei fijo leiden 0.835
Agnus Dei fijo louvain 0.807
Agnus Dei variable leiden 0.258
Agnus Dei variable louvain 0.260
Paisaje fijo leiden 0.905
Paisaje fijo louvain 0.805
Paisaje variable leiden 0.289
Paisaje variable louvain 0.276

7 Conclusiones

  1. El parámetro \(h\) controla el balance entre color y geometría. Valores pequeños hacen que las comunidades sigan los bordes de color; valores grandes aplanan los pesos y la partición se aproxima a la de la red no ponderada, que solo refleja la estructura espacial de la teselación.
  2. El umbral \(\tau\) impone fronteras duras. Filtrar antes de detectar comunidades garantiza que las regiones de color muy distinto nunca queden juntas, pero produce muchas comunidades diminutas (superpíxeles aislados) cuando \(\tau\) es alto. En la práctica conviene usar \(\tau\) moderados o fusionar después las comunidades pequeñas.
  3. Louvain y Leiden dan particiones parecidas, con Leiden siempre igual o ligeramente superior en modularidad y con la garantía de comunidades conexas. Sus diferencias se concentran en zonas homogéneas, donde hay muchas particiones casi equivalentes (por ejemplo, el fondo del Agnus Dei con \(h=80\) el ARI entre ambos es 0.55 en el paisaje y 0.77 en el Agnus Dei), y desaparecen cuando el filtrado por \(\tau\) ya fija la estructura.
  4. Robustez frente al ruido. El agrupamiento con Louvain y Leiden se mantiene razonablemente si el ruido solo afecta a los pesos (SLIC fijo): el promedio dentro de cada superpíxel amortigua el ruido, y las diferencias que quedan reflejan sobre todo la inestabilidad de la optimización, menor en Leiden. No se mantiene cuando el ruido también altera la teselación (SLIC variable): el ARI cae a ≈ 0.26–0.29 con \(\sigma=50\) en ambas imágenes y con ambos métodos. El punto débil del flujo es SLIC, no el algoritmo de comunidades. Un suavizado previo (por ejemplo, un filtro gaussiano o de mediana antes de SLIC) sería la extensión natural para mejorar la robustez.
  5. Modularidad no es calidad visual. En el fondo uniforme del Agnus Dei se obtienen modularidades altas con comunidades que no corresponden a ninguna estructura de la imagen.

8 Referencias

  • Achanta, R., Shaji, A., Smith, K., Lucchi, A., Fua, P. y Süsstrunk, S. (2012). SLIC superpixels compared to state-of-the-art superpixel methods. IEEE TPAMI, 34(11), 2274–2282.
  • Blondel, V. D., Guillaume, J.-L., Lambiotte, R. y Lefebvre, E. (2008). Fast unfolding of communities in large networks. Journal of Statistical Mechanics, P10008.
  • Traag, V. A., Waltman, L. y van Eck, N. J. (2019). From Louvain to Leiden: guaranteeing well-connected communities. Scientific Reports, 9, 5233.
  • Hubert, L. y Arabie, P. (1985). Comparing partitions. Journal of Classification, 2, 193–218.
  • Sosa, J. Análisis de imágenes mediante redes. Notas de clase, RPubs.
  • Imágenes: fotografía © Rodrigo Dávila (uso académico); Agnus Dei, Francisco de Zurbarán, Museo Nacional del Prado (dominio público).
  • Anthropic. (2026). Claude (versión Opus 5.5) [Modelo de lenguaje de gran tamaño]. https://claude.ai
LS0tDQp0aXRsZTogIlJlbGF0b3LDrWE6IEFuw6FsaXNpcyBkZSBpbcOhZ2VuZXMgbWVkaWFudGUgcmVkZXMiDQphdXRob3I6ICJcbioqSnVhbiBGZWxpcGUgTW9yZW5vIFJvZHJpZ3VleioqICBcblxuTWFlc3Ryw61hIGVuIENpZW5jaWFzIC0gRXN0YWTDrXN0aWNhDQogIFwgXG5cblVuaXZlcnNpZGFkIE5hY2lvbmFsIGRlIENvbG9tYmlhXG4iDQpkYXRlOiAiXG4qKkN1cnNvOioqIEFuw6FsaXNpcyBFc3RhZMOtc3RpY28gZGUgUmVkZXMgIFxuXG4qKlByb2Zlc29yOioqIEp1YW4gU29zYSwgUGhEDQogIFwgXG5cbioqMTcgZGUgb2N0dWJyZSBkZSAyMDI2KipcbiINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0aGVtZTogY29zbW8NCiAgICBkZl9wcmludDoga2FibGUNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIGZpZy5hbGlnbiA9ICJjZW50ZXIiLCBjYWNoZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIGRwaSA9IDE1MCwgZmlnLnJldGluYSA9IDIsIG91dC53aWR0aCA9ICIxMDAlIikNCmBgYA0KDQoNCg0KDQojIEludHJvZHVjY2nDs24NCg0KVW4gKipww614ZWwqKiAoZGUgKnBpY3R1cmUgZWxlbWVudCopIGVzIGxhIHVuaWRhZCBtw6FzIHBlcXVlw7FhIGRlIHVuYSBpbWFnZW4gZGlnaXRhbDogdW4gY3VhZHJpdG8gZGUgdW4gc29sbyBjb2xvci4gDQoNClVuYSAqKmltYWdlbioqIGRpZ2l0YWwgZXMgdW4gYXJyZWdsbyBkZSAqKmludGVuc2lkYWRlcyoqIGN1eW9zIHZhbG9yZXMgcmVwcmVzZW50YW4gbG9zIG5pdmVsZXMgZGUgY29sb3IgZGUgY2FkYSBww614ZWwsIGxhICoqaW1hZ2VuKiogdGFtYmnDqW4gc2UgcHVlZGUgdmVyIGNvbW8gdW5hIGN1YWRyw61jdWxhIGRlIHDDrXhlbGVzIG9yZ2FuaXphZGEgZW4gZmlsYXMgeSBjb2x1bW5hcyBxdWUgIHNlIHJlcHJlc2VudGEgY29tbyB1biBhcnJlZ2xvIGRlICRuX3IgXHRpbWVzIG5fYyBcdGltZXMgMyQgbsO6bWVyb3MgKGZpbGFzLCBjb2x1bW5hcyB5IGxvcyB0cmVzIGNhbmFsZXMgZGUgY29sb3IpLg0KDQpVbmEgKipyZWQgZGUgYWR5YWNlbmNpYSBkZSByZWdpb25lcyoqIChSQUcsICpyZWdpb24gYWRqYWNlbmN5IGdyYXBoKikgZGVzY3JpYmUgbGEgcmVsYWNpw7NuIGVudHJlIHN1cyBwYXJ0ZXM6IA0KDQoqIGNhZGEgKip2w6lydGljZSoqIGVzIHVuICpzdXBlcnDDrXhlbCosIGVzIGRlY2lyLCB1bmEgcmVnacOzbiBwZXF1ZcOxYSwgY29uZXhhIHkgZGUgY29sb3IgYXByb3hpbWFkYW1lbnRlIGhvbW9nw6luZW87DQoqIGNhZGEgKiphcmlzdGEqKiB1bmUgZG9zIHN1cGVycMOteGVsZXMgcXVlIHNlIHRvY2FuIChjb21wYXJ0ZW4gYWwgbWVub3MgdW4gbGFkbyBkZSBww614ZWwsIGFkeWFjZW5jaWEgZGUgNCB2ZWNpbm9zKTsNCiogY2FkYSBhcmlzdGEgbGxldmEgdW4gKipwZXNvIGRlIHNpbWlsaXR1ZCB2aXN1YWwqKiBxdWUgaW5kaWNhIHF1w6kgdGFuIHBhcmVjaWRvcyBzb24gbG9zIGNvbG9yZXMgZGUgbGFzIGRvcyByZWdpb25lcy4NCg0KRWwgb2JqZXRpdm8gbm8gZXMgcmVjb25vY2VyIG9iamV0b3Mgc2lubyAqKmNhcmFjdGVyaXphciBsYSBlc3RydWN0dXJhIHZpc3VhbCoqIGRlIGxhIGltYWdlbi4gTGFzIGNvbXVuaWRhZGVzIGRlIGxhIHJlZCBhZ3J1cGFuIHJlZ2lvbmVzIHZpc3VhbG1lbnRlIHJlbGFjaW9uYWRhcywgcXVlIHB1ZWRlbiBjb2luY2lkaXIgbyBubyBjb24gb2JqZXRvcyByZWFsZXMuDQoNCg0KDQojIyBTdXBlcnDDrXhlbGVzIGNvbiBTTElDDQoNClNMSUMgKCpTaW1wbGUgTGluZWFyIEl0ZXJhdGl2ZSBDbHVzdGVyaW5nKikgZXMgdW4gJGskLW1lZGlhcyBsb2NhbCBlbiBlbCBlc3BhY2lvIChjb2xvciwgcG9zaWNpw7NuKS4gU2UgZmlqYSBlbCBuw7ptZXJvIGRlc2VhZG8gZGUgc3VwZXJww614ZWxlcyAkSyQsIHNlIGluaWNpYWxpemFuIGNlbnRyb3MgZW4gdW5hIHJlamlsbGEgZGUgcGFzbyAkUz1cc3FydHtOL0t9JCAoJE4kID0gbsO6bWVybyBkZSBww614ZWxlcykgeSBjYWRhIHDDrXhlbCBzZSBhc2lnbmEgYWwgY2VudHJvIGNlcmNhbm8gcXVlIG1pbmltaXphDQoNCiQkDQpEPVxzcXJ0e2RfY14yK1xsZWZ0KFxmcmFje219e1N9XCxkX3NccmlnaHQpXjJ9LA0KJCQNCg0KZG9uZGUgJGRfYyQgZXMgbGEgZGlzdGFuY2lhIGRlIGNvbG9yLCAkZF9zJCBsYSBkaXN0YW5jaWEgZXNwYWNpYWwgeSAkbSQgbGEgKmNvbXBhY3RuZXNzKi4gVmFsb3JlcyBncmFuZGVzIGRlICRtJCBwcm9kdWNlbiByZWdpb25lcyByZWd1bGFyZXMgKGNhc2kgY3VhZHJhZGFzKTsgdmFsb3JlcyBwZXF1ZcOxb3MgaGFjZW4gcXVlIGxhcyByZWdpb25lcyBzaWdhbiBtZWpvciBsb3MgYm9yZGVzIGRlIGNvbG9yLg0KDQojIyBFc3BhY2lvcyBkZSBjb2xvcjogUkdCIHkgQ0lFTEFCDQoNCkxvcyBjb2xvcmVzIGRlIHVuYSBpbWFnZW4gcHVlZGVuIGRlc2NyaWJpcnNlIGVuIGRpc3RpbnRvcyAqKmVzcGFjaW9zIGRlIGNvbG9yKiouIExhIGltYWdlbiBzZSBsZWUgZW4gUkdCLCBwZXJvIGxhcyBkaXN0YW5jaWFzIGVudHJlIGNvbG9yZXMgc2UgY2FsY3VsYW4gZW4gQ0lFTEFCLiBMYSByYXrDs24gZXMgcXVlIGVuIFJHQiBsYSBkaXN0YW5jaWEgZXVjbGlkaWFuYSBubyBjb3JyZXNwb25kZSBhIGxhIGRpZmVyZW5jaWEgZGUgY29sb3IgcXVlIHBlcmNpYmUgdW5hIHBlcnNvbmEsIHkgZW4gQ0lFTEFCIHPDrSwgZGUgZm9ybWEgYXByb3hpbWFkYS4NCg0KYGBge3IgZmlnLXJnYiwgZWNobz1GQUxTRSwgb3V0LndpZHRoPSI1NSUiLCBmaWcuY2FwPSJSdWVkYSBkZSB0b25vcyBjb24gc3VzIGNvb3JkZW5hZGFzIFJHQi4gQ2FkYSB0b25vIHB1cm8gdGllbmUgdW4gw6FuZ3VsbyAoMMKwIHJvam8sIDEyMMKwIHZlcmRlLCAyNDDCsCBhenVsKSB5IHVuYSB0ZXJuYSAoUiwgRywgQikgZW4gbGEgZXNjYWxhIDDigJMyNTUuIn0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJydWVkYV9yZ2IucG5nIikNCmBgYA0KDQoqKlJHQioqIGVzIHVuIG1vZGVsbyAqKmFkaXRpdm8qKjogY2FkYSBww614ZWwgc2UgZGVzY3JpYmUgcG9yIGxhIGludGVuc2lkYWQgZGUgdHJlcyBsdWNlcyBwcmltYXJpYXMsIHJvam8gKFIpLCB2ZXJkZSAoRykgeSBhenVsIChCKSwgY2FkYSB1bmEgZW50cmUgMCB5IDI1NS4gRWwgbmVncm8gZXMgKDAsIDAsIDApLCBlbCBibGFuY28gKDI1NSwgMjU1LCAyNTUpIHkgbG9zIGNvbG9yZXMgaW50ZXJtZWRpb3Mgc2Ugb2J0aWVuZW4gc3VtYW5kbyBsdWNlczogcm9qbyArIHZlcmRlID0gYW1hcmlsbG8gKDI1NSwgMjU1LCAwKSwgdmVyZGUgKyBhenVsID0gY2lhbiAoMCwgMjU1LCAyNTUpIHkgcm9qbyArIGF6dWwgPSBtYWdlbnRhICgyNTUsIDAsIDI1NSksIGNvbW8gc2UgdmUgZW4gbGEgcnVlZGEuIEVzIGVsIGZvcm1hdG8gbmF0dXJhbCBkZSBsYXMgY8OhbWFyYXMgeSBsYXMgcGFudGFsbGFzLCBwZXJvIHRpZW5lIGRvcyBwcm9ibGVtYXMgcGFyYSBtZWRpciBkaWZlcmVuY2lhcyBkZSBjb2xvcjoNCg0KKiAqKk5vIGVzIHBlcmNlcHR1YWxtZW50ZSB1bmlmb3JtZS4qKiBMYSBtaXNtYSBkaXN0YW5jaWEgZXVjbGlkaWFuYSBlbiBSR0IgcHVlZGUgY29ycmVzcG9uZGVyIGEgZGlmZXJlbmNpYXMgbXV5IHZpc2libGVzIGVuIHVuYSB6b25hIGRlbCBlc3BhY2lvIHkgY2FzaSBpbXBlcmNlcHRpYmxlcyBlbiBvdHJhLiBFbiBsYSBydWVkYSwgcG9yIGVqZW1wbG8sIGxhIGZyYW5qYSBxdWUgZWwgb2pvIHZlIGNvbW8gInZlcmRlIiBvY3VwYSB1biByYW5nbyBkZSDDoW5ndWxvcyBtdWNobyBtw6FzIGFtcGxpbyBxdWUgbGEgZGVsIGFtYXJpbGxvIG8gZWwgY2lhbjogbXVjaG9zIHZhbG9yZXMgUkdCIGRpc3RpbnRvcyBzZSBwZXJjaWJlbiBjYXNpIGlndWFsZXMuDQoqICoqTWV6Y2xhIGx1bWlub3NpZGFkIHkgY29sb3IuKiogT3NjdXJlY2VyIHVuIGNvbG9yIHJlZHVjZSBsb3MgdHJlcyBjYW5hbGVzIGEgbGEgdmV6LCBhc8OtIHF1ZSB1bmEgbWlzbWEgc3VwZXJmaWNpZSBiYWpvIGx1eiB5IGJham8gc29tYnJhIHF1ZWRhIGxlam9zIGVuIFJHQiBhdW5xdWUgc3UgY29sb3Igc2VhIGVsIG1pc21vLg0KDQpgYGB7ciBmaWctY2llbGFiLCBlY2hvPUZBTFNFLCBvdXQud2lkdGg9IjU1JSIsIGZpZy5jYXA9IlJlcHJlc2VudGFjacOzbiBkZWwgZXNwYWNpbyBDSUVMQUIuIEVsIGVqZSB2ZXJ0aWNhbCBlcyBsYSBsdW1pbm9zaWRhZCBMKiAobmVncm8gYWJham8sIGJsYW5jbyBhcnJpYmEpOyBlbCBwbGFubyBob3Jpem9udGFsIGNvbnRpZW5lIGxvcyBlamVzIGRlIGNvbG9yIGEqICh2ZXJkZSBhIHJvam8pIHkgYiogKGF6dWwgYSBhbWFyaWxsbykuIn0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJlc3BhY2lvX2NpZWxhYi5wbmciKQ0KYGBgDQoNCioqQ0lFTEFCKiogKG8gTFwqYVwqYlwqLCBkZWZpbmlkbyBwb3IgbGEgQ29taXNpw7NuIEludGVybmFjaW9uYWwgZGUgbGEgSWx1bWluYWNpw7NuIGVuIDE5NzYpIHNlcGFyYSBsYSBsdW1pbm9zaWRhZCBkZWwgY29sb3IgeSBvcmdhbml6YSBsb3MgY29sb3JlcyBjb21vIGxvIGhhY2UgbGEgdmlzacOzbiBodW1hbmEsIG1lZGlhbnRlIHBhcmVzIGRlIGNvbG9yZXMgb3B1ZXN0b3M6DQoNCiogKipMXCoqKiBlcyBsYSBsdW1pbm9zaWRhZCwgZGUgMCAobmVncm8pIGEgMTAwIChibGFuY28pOiBlbCBlamUgdmVydGljYWwgZGUgbGEgZXNmZXJhLg0KKiAqKmFcKioqIHZhIGRlbCB2ZXJkZSAodmFsb3JlcyBuZWdhdGl2b3MpIGFsIHJvam8gKHBvc2l0aXZvcykuDQoqICoqYlwqKiogdmEgZGVsIGF6dWwgKG5lZ2F0aXZvcykgYWwgYW1hcmlsbG8gKHBvc2l0aXZvcykuDQoNCkVuIGVsIHBsYW5vIGhvcml6b250YWwsIGVsIMOhbmd1bG8gaW5kaWNhIGVsIHRvbm8geSBsYSBkaXN0YW5jaWEgYWwgZWplIGNlbnRyYWwgaW5kaWNhIGxhIHNhdHVyYWNpw7NuOiBsb3MgZ3Jpc2VzIGVzdMOhbiBzb2JyZSBlbCBlamUgeSBsb3MgY29sb3JlcyBtw6FzIHZpdm9zLCBlbiBlbCBib3JkZS4gQ0lFTEFCIHNlIGRpc2XDscOzIHBhcmEgcXVlIGxhIGRpc3RhbmNpYSBldWNsaWRpYW5hIGVudHJlIGRvcyBjb2xvcmVzLCBsbGFtYWRhICRcRGVsdGEgRSQsIHNlYSBhcHJveGltYWRhbWVudGUgcHJvcG9yY2lvbmFsIGEgbGEgZGlmZXJlbmNpYSBwZXJjaWJpZGE6IHVuICRcRGVsdGEgRVxhcHByb3ggMnssfTMkIGVzLCBlbiBwcm9tZWRpbywgbGEgbWVub3IgZGlmZXJlbmNpYSBxdWUgdW5hIHBlcnNvbmEgZGlzdGluZ3VlLCB5IGRpZmVyZW5jaWFzIG1heW9yZXMgcXVlIDEwIHNlIHBlcmNpYmVuIGNvbW8gY29sb3JlcyBjbGFyYW1lbnRlIGRpc3RpbnRvcy4gUG9yIGVzbyBlcyBlbCBlc3BhY2lvIGFkZWN1YWRvIHBhcmEgZGVmaW5pciAkZF97aWp9JCB5LCBjb24gZWxsYSwgZWwgcGVzbyBkZSBzaW1pbGl0dWQgdmlzdWFsLg0KDQpMYSBjb252ZXJzacOzbiBkZSBSR0IgYSBDSUVMQUIgKGZ1bmNpw7NuIGByZ2JfdG9fbGFiYCkgdGllbmUgdHJlcyBwYXNvczoNCg0KMS4gKipMaW5lYWxpemFjacOzbi4qKiBMb3MgdmFsb3JlcyBSR0IgZGUgdW5hIGltYWdlbiBkaWdpdGFsIChzUkdCKSBlc3TDoW4gY29kaWZpY2Fkb3MgY29uIHVuYSBjb3JyZWNjacOzbiBnYW1tYS4gU2UgcGFzYW4gYSAkWzAsMV0kIHkgc2UgZGVzaGFjZSBlc2EgY29ycmVjY2nDs24gcGFyYSBvYnRlbmVyIGludGVuc2lkYWRlcyBkZSBsdXogcHJvcG9yY2lvbmFsZXMgYSBsYSBlbmVyZ8OtYSBmw61zaWNhLg0KMi4gKipQYXNvIGEgWFlaLioqIFVuYSBtYXRyaXogbGluZWFsIGZpamEgdHJhbnNmb3JtYSBsYXMgaW50ZW5zaWRhZGVzIFJHQiBlbiBsYXMgY29vcmRlbmFkYXMgWFlaIGRlIGxhIENJRSwgcXVlIGRlc2NyaWJlbiBlbCBjb2xvciBzZWfDum4gbGEgcmVzcHVlc3RhIGRlIGxvcyB0cmVzIHRpcG9zIGRlIGNvbm9zIGRlbCBvam8uIFNlIGRpdmlkZW4gcG9yIGVsIGJsYW5jbyBkZSByZWZlcmVuY2lhIEQ2NSAobHV6IGRlIGTDrWEpLg0KMy4gKipQYXNvIGEgTFwqYVwqYlwqLioqIFNlIGFwbGljYSB1bmEgcmHDrXogY8O6YmljYSAoY29uIHVuIHRyYW1vIGxpbmVhbCBjZXJjYSBkZSBjZXJvKSwgcXVlIGltaXRhIGxhIHJlc3B1ZXN0YSBubyBsaW5lYWwgZGVsIG9qbyBhIGxhIGludGVuc2lkYWQsIHkgc2UgY29tYmluYW4gbG9zIHJlc3VsdGFkb3M6IExcKiBzYWxlIGRlIFksIGFcKiBkZSBsYSBkaWZlcmVuY2lhIGVudHJlIFggZSBZIHkgYlwqIGRlIGxhIGRpZmVyZW5jaWEgZW50cmUgWSB5IFouDQoNCiMjIEF0cmlidXRvcywgZGlzdGFuY2lhIGRlIGNvbG9yIHkgcGVzb3MNCg0KUGFyYSBjYWRhIHN1cGVycMOteGVsICRpJCBzZSBjYWxjdWxhIHN1IMOhcmVhLCBzdSBjZW50cm9pZGUgeSBzdSBjb2xvciBtZWRpbyBlbiBDSUVMQUIsICQoXGJhciBMX2ksXGJhciBhX2ksXGJhciBiX2kpJC4gQ0lFTEFCIHNlIHVzYSBwb3JxdWUgbGFzIGRpc3RhbmNpYXMgZXVjbGlkaWFuYXMgZW4gZXNlIGVzcGFjaW8gc2UgYXByb3hpbWFuIGEgZGlmZXJlbmNpYXMgZGUgY29sb3IgcGVyY2liaWRhcy4gTGEgZGlzdGFuY2lhIGVudHJlIHJlZ2lvbmVzIHZlY2luYXMgZXMNCg0KJCQNCmRfe2lqfT1cc3FydHsoXGJhciBMX2ktXGJhciBMX2opXjIrKFxiYXIgYV9pLVxiYXIgYV9qKV4yKyhcYmFyIGJfaS1cYmFyIGJfaileMn0sDQokJA0KDQp5IGxhICoqc2ltaWxpdHVkIHZpc3VhbCoqIGVzDQoNCiQkDQp3X3tpan09XGV4cFwhXGxlZnQoLVxmcmFje2Rfe2lqfX17aH1ccmlnaHQpLFxxcXVhZCBoPjAsXHFxdWFkIHdfe2lqfVxpbigwLDFdLg0KJCQNCg0KRWwgcGFyw6FtZXRybyAkaCQgZXMgdW5hIGVzY2FsYSBkZSBjb2xvcjogaW5kaWNhIGN1w6FudGFzIHVuaWRhZGVzIGRlICRcRGVsdGEgRSQgaGFjZW4gY2FlciBsYSBzaW1pbGl0dWQgYSAkZV57LTF9XGFwcHJveCAwLjM3JC4gUGFyYSBtZWRpZGFzIGJhc2FkYXMgZW4gY2FtaW5vcyAoaW50ZXJtZWRpYWNpw7NuKSBzZSB1c2EgZWwgY29zdG8gJFxlbGxfe2lqfT0xL3dfe2lqfSQuIEFkZW3DoXMgZGUgJHdfe2lqfSQsIGNhZGEgYXJpc3RhIGd1YXJkYSBsYSBsb25naXR1ZCBkZSBmcm9udGVyYSAkYl97aWp9JCAobsO6bWVybyBkZSBsYWRvcyBkZSBww614ZWwgY29tcGFydGlkb3MpLg0KDQpMYSBmdW5jacOzbiBleHBvbmVuY2lhbCBubyBlcyBsYSDDum5pY2Egb3BjacOzbjogcHVlZGUgdXNhcnNlIGN1YWxxdWllciBzaW1pbGl0dWQgcXVlIGRlY3JlemNhIGNvbiBsYSBkaXN0YW5jaWEgZGUgY29sb3IsIHBvciBlamVtcGxvIHVuIG7DumNsZW8gZ2F1c3NpYW5vICRcZXhwKC1kX3tpan1eMi8yaF4yKSQuIENvbiBsYSBlbGVnaWRhLCBzaSBkb3Mgc3VwZXJww614ZWxlcyB0aWVuZW4gZWwgbWlzbW8gY29sb3IgbWVkaW8gZW4gQ0lFTEFCLCAkZF97aWp9PTAkIHkgJHdfe2lqfT0xJDsgYSBtZWRpZGEgcXVlICRkX3tpan0kIGNyZWNlLCAkd197aWp9JCBkZWNyZWNlIGhhY2lhIDAgc2luIGFsY2FuemFybG8gKGFzw61udG90YSBlbiAwKS4gUG9yIGVzbyBsb3MgcGVzb3Mgc29uICoqaW50ZW5zaWRhZGVzKio6IHVuIHZhbG9yIGdyYW5kZSBpbmRpY2EgcmVnaW9uZXMgcGFyZWNpZGFzLCB5IHBhcmEgbGFzIG1lZGlkYXMgYmFzYWRhcyBlbiBjYW1pbm9zIHNlIHVzYSBlbCBjb3N0byAkXGVsbF97aWp9PTEvd197aWp9JC4NCg0KDQojIyBGaWx0cmFkbyBwb3IgdW1icmFsICRcdGF1JA0KDQpDb21vICR3X3tpan0+MCQgcGFyYSB0b2RvIHBhciBkZSBzdXBlcnDDrXhlbGVzIHZlY2lub3MsIGxhIHJlZCBkZSByZWdpb25lcyBlcyBjb25leGEgeSBjb25zZXJ2YSBhcmlzdGFzIGluY2x1c28gZW50cmUgcmVnaW9uZXMgZGUgY29sb3JlcyBtdXkgZGlzdGludG9zLiBQYXJhIGVzdHVkaWFyIGxhIGVzdHJ1Y3R1cmEgZGUgbGEgcmVkIHNlIGNvbnNlcnZhbiBzb2xvIGxhcyBhcmlzdGFzIGNvbiAkd197aWp9XGdlXHRhdSQuIENvbW8gJHdfe2lqfSQgZXMgbW9uw7N0b25hIGVuICRkX3tpan0kLCBlc3RvIGVxdWl2YWxlIGENCg0KJCQNCndfe2lqfVxnZVx0YXUgXGlmZiBkX3tpan1cbGUgLWhcbG5cdGF1IC4NCiQkDQoNCkFsIGF1bWVudGFyICRcdGF1JCBzZSBlbGltaW5hbiBsYXMgY29uZXhpb25lcyBlbnRyZSByZWdpb25lcyBkZSBjb2xvcmVzIGRpc3RpbnRvcyB5IGxhIHJlZCBzZSBmcmFnbWVudGEgZW4gY29tcG9uZW50ZXMuDQoNClBvciByZWZlcmVuY2lhIHNlIHV0aWxpemEgJFx0YXU9MC41JC4NCg0KIyMgQ29tdW5pZGFkZXM6IExvdXZhaW4geSBMZWlkZW4NCg0KQW1ib3MgYWxnb3JpdG1vcyBtYXhpbWl6YW4gbGEgKiptb2R1bGFyaWRhZCBwb25kZXJhZGEqKg0KDQokJA0KUT1cZnJhY3sxfXsyV31cc3VtX3tpLGp9XGxlZnQod197aWp9LVxmcmFje3NfaSBzX2p9ezJXfVxyaWdodClcZGVsdGEoY19pLGNfaiksDQokJA0KDQpkb25kZSAkc19pPVxzdW1faiB3X3tpan0kIGVzIGxhIGZ1ZXJ6YSBkZWwgdsOpcnRpY2UgJGkkIHkgJFckIGxhIHN1bWEgZGUgcGVzb3MuICoqTG91dmFpbioqIGFsdGVybmEgbW92aW1pZW50b3MgbG9jYWxlcyBkZSB2w6lydGljZXMgeSBhZ3JlZ2FjacOzbiBkZSBjb211bmlkYWRlcy4gKipMZWlkZW4qKiBhw7FhZGUgdW5hIGZhc2UgZGUgcmVmaW5hbWllbnRvIHF1ZSBnYXJhbnRpemEgY29tdW5pZGFkZXMgaW50ZXJuYW1lbnRlIGNvbmVjdGFkYXMsIHkgc3VlbGUgYWxjYW56YXIgbW9kdWxhcmlkYWRlcyBpZ3VhbGVzIG8gbWF5b3Jlcy4gUHJveWVjdGFuZG8gbGEgY29tdW5pZGFkIGRlIGNhZGEgc3VwZXJww614ZWwgc29icmUgc3VzIHDDrXhlbGVzIHNlIG9idGllbmUgdW5hICoqc2VnbWVudGFjacOzbioqIGRlIGxhIGltYWdlbi4gVW5hIG1vZHVsYXJpZGFkIGFsdGEgbm8gZ2FyYW50aXphIHVuYSBzZWdtZW50YWNpw7NuICJidWVuYSI6ICRRJCBlcyB1bmEgbWVkaWRhIGludGVybmEgZGUgbGEgcmVkLCBubyBkZSBsYSBjYWxpZGFkIHZpc3VhbC4NCg0KIyMgRXZhbHVhY2nDs24geSByb2J1c3Rleg0KDQpTZSBkaXN0aW5ndWUgZXZhbHVhY2nDs24gaW50ZXJuYSAobW9kdWxhcmlkYWQpIHkgZXh0ZXJuYSAoQVJJLCBJb1UsIEYxIGRlIGZyb250ZXJhcywgcXVlIHJlcXVpZXJlbiB1bmEgc2VnbWVudGFjacOzbiBkZSByZWZlcmVuY2lhKSwgeSBzZSBwcm9wb25lIGVzdHVkaWFyIGxhIHNlbnNpYmlsaWRhZCBhICRLJCwgJG0kLCAkaCQgeSAkXHRhdSQsIHkgbGEgcm9idXN0ZXogZnJlbnRlIGEgcnVpZG8sIHN1YXZpemFkbyBvIGNhbWJpb3MgZGUgaWx1bWluYWNpw7NuLiBMb3MgZXhwZXJpbWVudG9zIGRlIGVzdGEgcmVsYXRvcsOtYSBzZSB1YmljYW4gcHJlY2lzYW1lbnRlIGVuIGVzZSDDumx0aW1vIGJsb3F1ZS4NCg0KIyBJbXBsZW1lbnRhY2nDs24NCg0KTGFzIHNpZ3VpZW50ZXMgZnVuY2lvbmVzIG5vcyB2YW4gYSBwZXJtaXRpciBsYSBjb25zdHJ1Y2Npw7NuIGRlbCBhbsOhbGlzaXM6IHByZXBhcmFjacOzbiBkZSBsYSBpbWFnZW4sIFNMSUMsIGdyYWZvIGRlIHJlZ2lvbmVzIGNvbiBkaXN0YW5jaWFzIENJRUxBQiB5IHBlc29zICR3X3tpan0kLCBmaWx0cmFkbyBwb3IgJFx0YXUkLCBkZXRlY2Npw7NuIGRlIGNvbXVuaWRhZGVzIHkgcHJveWVjY2nDs24gYSBww614ZWxlcy4NCg0KYGBge3IgcGFxdWV0ZXN9DQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoew0KICBsaWJyYXJ5KE9wZW5JbWFnZVIpDQogIGxpYnJhcnkoaWdyYXBoKQ0KICBsaWJyYXJ5KGRwbHlyKQ0KICBsaWJyYXJ5KHRpZHlyKQ0KICBsaWJyYXJ5KGdncGxvdDIpDQogIGxpYnJhcnkoa25pdHIpDQp9KQ0Kc2V0LnNlZWQoMjAyNikNCmBgYA0KDQojIyBGdW5jaW9uZXMgZGUgZ3LDoWZpY29zDQoNClNlIGRlZmluZW4gcHJpbWVybyBwYXJhIHBvZGVyIG1vc3RyYXIgZWwgcmVzdWx0YWRvIGRlIGNhZGEgcGFzbyBlbiBsYXMgc2VjY2lvbmVzIHNpZ3VpZW50ZXMuDQoNCmBgYHtyIGZ1bmNpb25lcy1ncmFmaWNvc30NCmJvdW5kYXJ5X21hc2sgPC0gZnVuY3Rpb24ocGFydCkgew0KICBiIDwtIG1hdHJpeChGQUxTRSwgbnJvdyhwYXJ0KSwgbmNvbChwYXJ0KSkNCiAgYlssIC1uY29sKHBhcnQpXSA8LSBiWywgLW5jb2wocGFydCldIHwgcGFydFssIC1uY29sKHBhcnQpXSAhPSBwYXJ0WywgLTFdDQogIGJbLW5yb3cocGFydCksIF0gPC0gYlstbnJvdyhwYXJ0KSwgXSB8IHBhcnRbLW5yb3cocGFydCksIF0gIT0gcGFydFstMSwgXQ0KICBiDQp9DQoNCiMgQ2FkYSBww614ZWwgc2UgcGludGEgY29uIGVsIGNvbG9yIG1lZGlvIGRlIHN1IHJlZ2nDs24vY29tdW5pZGFkDQptZWFuX2NvbG9yX2ltYWdlIDwtIGZ1bmN0aW9uKGltZywgcGFydCkgew0KICBvdXQgPC0gaW1nDQogIGZvciAoayBpbiAxOjMpIHsNCiAgICBjaCA8LSBpbWdbLCAsIGtdOyBtdSA8LSB0YXBwbHkoY2gsIHBhcnQsIG1lYW4pDQogICAgb3V0WywgLCBrXSA8LSBtYXRyaXgobXVbYXMuY2hhcmFjdGVyKHBhcnQpXSwgbnJvdyhwYXJ0KSkNCiAgfQ0KICBvdXQNCn0NCg0KcGxvdF9wYW5lbCA8LSBmdW5jdGlvbihpbWcsIHBhcnQgPSBOVUxMLCBtYWluID0gIiIsIGZpbGwgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICBib3JkZXJfY29sID0gYygwLCAwLCAwKSwgaW50ZXJwb2xhdGUgPSBpcy5udWxsKHBhcnQpKSB7DQogIG5yIDwtIGRpbShpbWcpWzFdOyBuYyA8LSBkaW0oaW1nKVsyXQ0KICByYXMgPC0gaWYgKCFpcy5udWxsKHBhcnQpICYmIGZpbGwpIG1lYW5fY29sb3JfaW1hZ2UoaW1nLCBwYXJ0KSBlbHNlIGltZw0KICByYXMgPC0gcmFzIC8gMjU1DQogIGlmICghaXMubnVsbChwYXJ0KSkgew0KICAgIGIgPC0gYm91bmRhcnlfbWFzayhwYXJ0KQ0KICAgIGZvciAoayBpbiAxOjMpIHsgdG1wIDwtIHJhc1ssICwga107IHRtcFtiXSA8LSBib3JkZXJfY29sW2tdOyByYXNbLCAsIGtdIDwtIHRtcCB9DQogIH0NCiAgcGxvdC5uZXcoKQ0KICBwbG90LndpbmRvdyhjKDAsIG5jKSwgYygwLCBuciksIGFzcCA9IDEsIHhheHMgPSAiaSIsIHlheHMgPSAiaSIpDQogIHJhc3RlckltYWdlKGFzLnJhc3RlcihyYXMpLCAwLCAwLCBuYywgbnIsIGludGVycG9sYXRlID0gaW50ZXJwb2xhdGUpDQogIHRpdGxlKG1haW4sIGNleC5tYWluID0gMC44NSwgZm9udC5tYWluID0gMSwgbGluZSA9IDAuMykNCn0NCg0KIyBSZWQgZGUgY2VudHJvaWRlcyBzb2JyZSBsYSBpbWFnZW47IGdyb3NvciB5IG9wYWNpZGFkIHByb3BvcmNpb25hbGVzIGEgd19pag0KcGxvdF9ncmFwaF9vdmVybGF5IDwtIGZ1bmN0aW9uKHJlcywgbWFpbiA9ICIiKSB7DQogIGcgPC0gcmVzJGdyYXBoOyBpbWcgPC0gcmVzJGltZzsgbnIgPC0gZGltKGltZylbMV0NCiAgcGxvdF9wYW5lbChpbWcsIG1haW4gPSBtYWluKQ0KICBlbCA8LSBlbmRzKGcsIEUoZyksIG5hbWVzID0gRkFMU0UpDQogIHggPC0gVihnKSRjb2wgLSAwLjU7IHkgPC0gbnIgLSBWKGcpJHJvdyArIDAuNQ0KICBzZWdtZW50cyh4W2VsWywgMV1dLCB5W2VsWywgMV1dLCB4W2VsWywgMl1dLCB5W2VsWywgMl1dLA0KICAgICAgICAgICBsd2QgPSAwLjQgKyAyLjIgKiBFKGcpJHcsDQogICAgICAgICAgIGNvbCA9IHJnYigxLCAxLCAxLCBhbHBoYSA9IDAuMjUgKyAwLjc1ICogRShnKSR3KSkNCiAgcG9pbnRzKHgsIHksIHBjaCA9IDE5LCBjZXggPSAwLjM1LCBjb2wgPSAiI0ZGRDQwMCIpDQp9DQpgYGANCg0KYGBge3IgaW1hZ2VuLWVqZW1wbG99DQojIEltYWdlbiB1c2FkYSBwYXJhIGlsdXN0cmFyIGNhZGEgcGFzbyBkZSBsYSBpbXBsZW1lbnRhY2nDs24NCnJ1dGFfZWogPC0gInBhaXNhamVfcmRhdmlsYS5qcGciDQpgYGANCg0KIyMgUHJlcGFyYWNpw7NuIGRlIGxhIGltYWdlbiB5IHJ1aWRvDQoNCmBgYHtyIGZ1bmNpb25lcy1pbWFnZW59DQojIExlZSB1bmEgaW1hZ2VuLCBsYSBkZWphIGVuIFJHQiBjb24gdmFsb3JlcyBlbiBbMCwgMjU1XSB5IGxhIHJlZHVjZSBkZSB0YW1hw7FvDQpwcmVwYXJlX3JnYiA8LSBmdW5jdGlvbihwYXRoLCBtYXhfc2lkZSA9IDI0MCkgew0KICBpbWcgPC0gT3BlbkltYWdlUjo6cmVhZEltYWdlKHBhdGgpDQogIGlmIChsZW5ndGgoZGltKGltZykpID09IDIpIGltZyA8LSBhcnJheShpbWcsIGMoZGltKGltZyksIDMpKQ0KICBpZiAoZGltKGltZylbM10gPT0gNCkgaW1nIDwtIGltZ1ssICwgMTozXQ0KICBpZiAobWF4KGltZykgPD0gMSkgaW1nIDwtIGltZyAqIDI1NQ0KICBuciA8LSBkaW0oaW1nKVsxXTsgbmMgPC0gZGltKGltZylbMl0NCiAgcyA8LSBtYXhfc2lkZSAvIG1heChuciwgbmMpDQogIGlmIChzIDwgMSkgew0KICAgIGltZyA8LSBPcGVuSW1hZ2VSOjpyZXNpemVJbWFnZShpbWcsIHJvdW5kKG5yICogcyksIHJvdW5kKG5jICogcyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJiaWxpbmVhciIpDQogICAgaWYgKG1heChpbWcpIDw9IDEpIGltZyA8LSBpbWcgKiAyNTUNCiAgfQ0KICBwbWluKHBtYXgoaW1nLCAwKSwgMjU1KQ0KfQ0KDQojIFJ1aWRvIGdhdXNzaWFubyBhZGl0aXZvIE4oMCwgc2lnbWFeMikgZW4gY2FkYSBjYW5hbCAoZXNjYWxhIDAtMjU1KQ0KYWRkX25vaXNlIDwtIGZ1bmN0aW9uKGltZywgc2lnbWEsIHNlZWQgPSAxKSB7DQogIGlmIChzaWdtYSA9PSAwKSByZXR1cm4oaW1nKQ0KICBzZXQuc2VlZChzZWVkKQ0KICBvdXQgPC0gaW1nICsgYXJyYXkocm5vcm0obGVuZ3RoKGltZyksIDAsIHNpZ21hKSwgZGltKGltZykpDQogIHBtaW4ocG1heChvdXQsIDApLCAyNTUpDQp9DQoNCiMgc1JHQiAoMC0yNTUpIC0+IENJRUxBQiAoaWx1bWluYW50ZSBENjUpDQpyZ2JfdG9fbGFiIDwtIGZ1bmN0aW9uKHJnYikgew0KICB2IDwtIHJnYiAvIDI1NQ0KICB2IDwtIGlmZWxzZSh2IDw9IDAuMDQwNDUsIHYgLyAxMi45MiwgKCh2ICsgMC4wNTUpIC8gMS4wNTUpXjIuNCkNCiAgTSA8LSBtYXRyaXgoYygwLjQxMjQ1NjQsIDAuMzU3NTc2MSwgMC4xODA0Mzc1LA0KICAgICAgICAgICAgICAgIDAuMjEyNjcyOSwgMC43MTUxNTIyLCAwLjA3MjE3NTAsDQogICAgICAgICAgICAgICAgMC4wMTkzMzM5LCAwLjExOTE5MjAsIDAuOTUwMzA0MSksIDMsIGJ5cm93ID0gVFJVRSkNCiAgeHl6IDwtIHYgJSolIHQoTSkNCiAgeHl6IDwtIHN3ZWVwKHh5eiwgMiwgYygwLjk1MDQ3LCAxLCAxLjA4ODgzKSwgIi8iKQ0KICBmIDwtIGlmZWxzZSh4eXogPiAoNiAvIDI5KV4zLCB4eXpeKDEgLyAzKSwgeHl6IC8gKDMgKiAoNiAvIDI5KV4yKSArIDQgLyAyOSkNCiAgY2JpbmQoTCA9IDExNiAqIGZbLCAyXSAtIDE2LA0KICAgICAgICBhID0gNTAwICogKGZbLCAxXSAtIGZbLCAyXSksDQogICAgICAgIGIgPSAyMDAgKiAoZlssIDJdIC0gZlssIDNdKSkNCn0NCmBgYA0KDQpgYGB7ciBlai1pbWFnZW4sIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0zLjZ9DQppbWdfZWogPC0gcHJlcGFyZV9yZ2IocnV0YV9laiwgbWF4X3NpZGUgPSA0MDApDQpsYWJfZWogPC0gcmdiX3RvX2xhYihtYXRyaXgoaW1nX2VqLCBuY29sID0gMykpDQpMX2VqICAgPC0gbWF0cml4KGxhYl9lalssICJMIl0sIG5yb3coaW1nX2VqKSwgbmNvbChpbWdfZWopKSAgICMgbHVtaW5hbmNpYSBMKiAoMC0xMDApDQoNCm9wIDwtIHBhcihtZnJvdyA9IGMoMSwgMyksIG1hciA9IGMoMC4zLCAwLjMsIDEuNiwgMC4zKSkNCnBsb3RfcGFuZWwoaW1nX2VqLCBtYWluID0gc3ByaW50ZigiSW1hZ2VuIHByZXBhcmFkYSAoJWQgeCAlZCBweCkiLCBucm93KGltZ19laiksIG5jb2woaW1nX2VqKSkpDQpwbG90X3BhbmVsKGFkZF9ub2lzZShpbWdfZWosIDI1KSwgbWFpbiA9ICJDb24gcnVpZG8gZ2F1c3NpYW5vLCBzaWdtYSA9IDI1IikNCnBsb3RfcGFuZWwoYXJyYXkoTF9laiAvIDEwMCAqIDI1NSwgYyhkaW0oTF9laiksIDMpKSwgbWFpbiA9ICJDYW5hbCBMKiBkZSBDSUVMQUIiKQ0KcGFyKG9wKQ0KYGBgDQoNCkNvbW8gaWx1c3RyYWNpw7NuIHNlIHVzYSBsYSBmb3RvZ3JhZsOtYSBkZWwgcGFpc2FqZSwgcmVkdWNpZGEgYSA0MDAgcMOteGVsZXMgZW4gc3UgbGFkbyBtYXlvciBjb21vIGVuIGVsIHJlc3RvIGRlbCBhbsOhbGlzaXMuIEVsIHJ1aWRvIGdhdXNzaWFubyBzZSBzdW1hIGNhbmFsIHBvciBjYW5hbCB5IHNlIHJlY29ydGEgYSBbMCwgMjU1XS4gRWwgdGVyY2VyIHBhbmVsIG11ZXN0cmEgZWwgY2FuYWwgTFwqIGRlIENJRUxBQjogc29sbyBsYSBsdW1pbm9zaWRhZCwgc2luIGluZm9ybWFjacOzbiBkZSBjb2xvci4gTGFzIHpvbmFzIGlsdW1pbmFkYXMgZGUgbGEgZmFjaGFkYSB5IGVsIGNpZWxvIHRpZW5lbiBMXCogYWx0byB5IGxvcyDDoXJib2xlcyB5IGxhIHbDrWEsIExcKiBiYWpvOyBsb3MgY2FuYWxlcyBhXCogeSBiXCogKG5vIG1vc3RyYWRvcykgY29udGllbmVuIGVsIGNvbG9yLg0KDQojIyBTdXBlcnDDrXhlbGVzIHkgZ3JhZm8gZGUgcmVnaW9uZXMNCg0KYGBge3IgZnVuY2lvbmVzLWdyYWZvfQ0KIyBTTElDOiBkZXZ1ZWx2ZSB1bmEgbWF0cml6IGRlIGV0aXF1ZXRhcyAxLCAuLi4sIEsnDQpydW5fc2xpYyA8LSBmdW5jdGlvbihpbWcsIG5fc3VwZXJwaXhlbHMgPSAxODAsIGNvbXBhY3RuZXNzID0gMjApIHsNCiAgcyA8LSBPcGVuSW1hZ2VSOjpzdXBlcnBpeGVscyhpbWcsIG1ldGhvZCA9ICJzbGljIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdXBlcnBpeGVsID0gbl9zdXBlcnBpeGVscywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21wYWN0bmVzcyA9IGNvbXBhY3RuZXNzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybl9zbGljX2RhdGEgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm5fbGFiZWxzID0gVFJVRSwgdmVyYm9zZSA9IEZBTFNFKQ0KICBsYWIgPC0gcyRsYWJlbHMNCiAgbWF0cml4KGFzLmludGVnZXIoZmFjdG9yKGxhYikpLCBucm93KGxhYiksIG5jb2wobGFiKSkNCn0NCg0KIyBQYXJlcyBkZSByZWdpb25lcyB2ZWNpbmFzICg0IHZlY2lub3MpIHkgbG9uZ2l0dWQgZGUgZnJvbnRlcmEgYl9pag0KcmVnaW9uX2VkZ2VzIDwtIGZ1bmN0aW9uKGxhYikgew0KICBoeiA8LSBjYmluZChhcy52ZWN0b3IobGFiWywgLW5jb2wobGFiKV0pLCBhcy52ZWN0b3IobGFiWywgLTFdKSkNCiAgdnQgPC0gY2JpbmQoYXMudmVjdG9yKGxhYlstbnJvdyhsYWIpLCBdKSwgYXMudmVjdG9yKGxhYlstMSwgXSkpDQogIHAgIDwtIHJiaW5kKGh6LCB2dCkNCiAgcCAgPC0gcFtwWywgMV0gIT0gcFssIDJdLCAsIGRyb3AgPSBGQUxTRV0NCiAgZGF0YS5mcmFtZShmcm9tID0gcG1pbihwWywgMV0sIHBbLCAyXSksIHRvID0gcG1heChwWywgMV0sIHBbLCAyXSkpIHw+DQogICAgY291bnQoZnJvbSwgdG8sIG5hbWUgPSAiYm9yZGVyIikNCn0NCg0KIyBDb25zdHJ1eWUgbGEgcmVkIGRlIHJlZ2lvbmVzLiBTaSAnbGFiZWxzJyBzZSBkYSwgc2UgdXNhIGVzYSBwYXJ0aWNpw7NuIGZpamENCmltYWdlX3RvX2dyYXBoIDwtIGZ1bmN0aW9uKGltZywgbGFiZWxzID0gTlVMTCwgbl9zdXBlcnBpeGVscyA9IDE4MCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbXBhY3RuZXNzID0gMjAsIGggPSAyMCkgew0KICBpZiAoaXMubnVsbChsYWJlbHMpKSBsYWJlbHMgPC0gcnVuX3NsaWMoaW1nLCBuX3N1cGVycGl4ZWxzLCBjb21wYWN0bmVzcykNCiAgbnIgPC0gbnJvdyhsYWJlbHMpOyBuYyA8LSBuY29sKGxhYmVscykNCiAgcHggIDwtIG1hdHJpeChpbWcsIG5jb2wgPSAzKSAgICAgICAgICAgICAgICAgIyBww614ZWxlcyB4IGNhbmFsZXMNCiAgbGFiX3B4IDwtIHJnYl90b19sYWIocHgpDQogIGlkICA8LSBhcy52ZWN0b3IobGFiZWxzKQ0KICByciAgPC0gYXMudmVjdG9yKHJvdyhsYWJlbHMpKTsgY2MgPC0gYXMudmVjdG9yKGNvbChsYWJlbHMpKQ0KICBLICAgPC0gbWF4KGlkKQ0KICBhdHRyX3YgPC0gZGF0YS5mcmFtZSgNCiAgICBuYW1lID0gYXMuY2hhcmFjdGVyKDE6SyksDQogICAgYXJlYSA9IHRhYnVsYXRlKGlkLCBLKSwNCiAgICByb3cgID0gdGFwcGx5KHJyLCBpZCwgbWVhbiksIGNvbCA9IHRhcHBseShjYywgaWQsIG1lYW4pLA0KICAgIFIgPSB0YXBwbHkocHhbLCAxXSwgaWQsIG1lYW4pLCBHID0gdGFwcGx5KHB4WywgMl0sIGlkLCBtZWFuKSwNCiAgICBCID0gdGFwcGx5KHB4WywgM10sIGlkLCBtZWFuKSwNCiAgICBMID0gdGFwcGx5KGxhYl9weFssIDFdLCBpZCwgbWVhbiksIGEgPSB0YXBwbHkobGFiX3B4WywgMl0sIGlkLCBtZWFuKSwNCiAgICBiID0gdGFwcGx5KGxhYl9weFssIDNdLCBpZCwgbWVhbikpDQogIGUgPC0gcmVnaW9uX2VkZ2VzKGxhYmVscykNCiAgZSRkIDwtIHNxcnQoKGF0dHJfdiRMW2UkZnJvbV0gLSBhdHRyX3YkTFtlJHRvXSleMiArDQogICAgICAgICAgICAgIChhdHRyX3YkYVtlJGZyb21dIC0gYXR0cl92JGFbZSR0b10pXjIgKw0KICAgICAgICAgICAgICAoYXR0cl92JGJbZSRmcm9tXSAtIGF0dHJfdiRiW2UkdG9dKV4yKQ0KICBlJHcgICAgPC0gZXhwKC1lJGQgLyBoKQ0KICBlJGNvc3QgPC0gMSAvIGUkdw0KICBlJGZyb20gPC0gYXMuY2hhcmFjdGVyKGUkZnJvbSk7IGUkdG8gPC0gYXMuY2hhcmFjdGVyKGUkdG8pDQogIGcgPC0gZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKGUsIGRpcmVjdGVkID0gRkFMU0UsIHZlcnRpY2VzID0gYXR0cl92KQ0KICBnJGggPC0gaA0KICBsaXN0KGdyYXBoID0gZywgbGFiZWxzID0gbGFiZWxzLCBpbWcgPSBpbWcpDQp9DQoNCiMgUmVjYWxjdWxhIGxvcyBwZXNvcyBjb24gb3RyYSBlc2NhbGEgaCAobm8gY2FtYmlhIGxhIHRvcG9sb2fDrWEpDQpzZXRfaCA8LSBmdW5jdGlvbihnLCBoKSB7DQogIEUoZykkdyA8LSBleHAoLUUoZykkZCAvIGgpOyBFKGcpJGNvc3QgPC0gMSAvIEUoZykkdzsgZyRoIDwtIGg7IGcNCn0NCg0KIyBDb25zZXJ2YSBzb2xvIGxhcyBhcmlzdGFzIGNvbiB3X2lqID49IHRhdQ0KZmlsdGVyX2dyYXBoIDwtIGZ1bmN0aW9uKGcsIHRhdSkgZGVsZXRlX2VkZ2VzKGcsIEUoZylbdyA8IHRhdV0pDQpgYGANCg0KYGBge3IgZWotZ3JhZm8sIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD03fQ0KcmVzX2VqIDwtIGltYWdlX3RvX2dyYXBoKGltZ19laiwgbl9zdXBlcnBpeGVscyA9IDE4MCwgY29tcGFjdG5lc3MgPSAyMCwgaCA9IDIwKQ0KZ19laiAgIDwtIHJlc19laiRncmFwaA0KDQpvcCA8LSBwYXIobWZyb3cgPSBjKDIsIDIpLCBtYXIgPSBjKDAuMywgMC4zLCAxLjYsIDAuMykpDQpwbG90X3BhbmVsKGltZ19laiwgcmVzX2VqJGxhYmVscywgZmlsbCA9IEZBTFNFLCBib3JkZXJfY29sID0gYygxLCAxLCAwKSwNCiAgICAgICAgICAgbWFpbiA9IHNwcmludGYoIlNMSUM6ICVkIHN1cGVycMOteGVsZXMiLCB2Y291bnQoZ19laikpKQ0KcGxvdF9wYW5lbChpbWdfZWosIHJlc19laiRsYWJlbHMsIG1haW4gPSAiQ29sb3IgbWVkaW8gZGUgY2FkYSBzdXBlcnDDrXhlbCIpDQpwbG90X2dyYXBoX292ZXJsYXkocmVzX2VqLCBtYWluID0gc3ByaW50ZigiR3JhZm8gZGUgcmVnaW9uZXMgKCVkIGFyaXN0YXMpIiwgZWNvdW50KGdfZWopKSkNCnBsb3RfZ3JhcGhfb3ZlcmxheShsaXN0KGdyYXBoID0gZmlsdGVyX2dyYXBoKGdfZWosIDAuNSksIGltZyA9IGltZ19laiksDQogICAgICAgICAgICAgICAgICAgbWFpbiA9IHNwcmludGYoIkdyYWZvIGZpbHRyYWRvLCB0YXUgPSAwLDUgKCVkIGFyaXN0YXMpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlY291bnQoZmlsdGVyX2dyYXBoKGdfZWosIDAuNSkpKSkNCnBhcihvcCkNCmBgYA0KDQpgYGB7ciBlai1wZXNvcywgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTMuNX0NCm9wIDwtIHBhcihtZnJvdyA9IGMoMSwgMiksIG1hciA9IGMoNC4yLCA0LjIsIDIsIDEpKQ0KaGlzdChFKGdfZWopJGQsIGJyZWFrcyA9IDMwLCBjb2wgPSAiIzI0NzFBMyIsIGJvcmRlciA9ICJ3aGl0ZSIsDQogICAgIG1haW4gPSAiRGlzdGFuY2lhIGRlIGNvbG9yIGVudHJlIHZlY2lub3MiLCB4bGFiID0gZXhwcmVzc2lvbihkW2lqXSB+ICIoQ0lFTEFCKSIpLCB5bGFiID0gIkFyaXN0YXMiKQ0KaGlzdChFKGdfZWopJHcsIGJyZWFrcyA9IDMwLCBjb2wgPSAiI0MwMzkyQiIsIGJvcmRlciA9ICJ3aGl0ZSIsDQogICAgIG1haW4gPSAiUGVzb3MgZGUgbGFzIGFyaXN0YXMgKGggPSAyMCkiLCB4bGFiID0gZXhwcmVzc2lvbih3W2lqXSA9PSBleHAoLWRbaWpdIC8gaCkpLCB5bGFiID0gIkFyaXN0YXMiKQ0KYWJsaW5lKHYgPSAwLjUsIGx0eSA9IDIpDQpwYXIob3ApDQpgYGANCg0KQ2FkYSBzdXBlcnDDrXhlbCBlcyB1biB2w6lydGljZSB5IGRvcyBzdXBlcnDDrXhlbGVzIHZlY2lub3MgKHF1ZSBjb21wYXJ0ZW4gZnJvbnRlcmEpIGVzdMOhbiB1bmlkb3MgcG9yIHVuYSBhcmlzdGEuIEVuIGVsIGdyYWZvIHNvYnJlIGxhIGltYWdlbiwgbGFzIGzDrW5lYXMgbcOhcyBncnVlc2FzIHkgb3BhY2FzIHNvbiBsYXMgZGUgbWF5b3IgcGVzbyAkd197aWp9JCwgZXMgZGVjaXIsIGxhcyBkZSByZWdpb25lcyBjb24gY29sb3IgcGFyZWNpZG8uIEVsIGZpbHRyYWRvIHBvciAkXHRhdSQgZWxpbWluYSBsYXMgYXJpc3RhcyBlbnRyZSByZWdpb25lcyBkZSBjb2xvciBtdXkgZGlzdGludG8sIHF1ZSBzb24gbGFzIHF1ZSBjcnV6YW4gbG9zIGJvcmRlcyBkZSBsb3Mgb2JqZXRvcy4NCg0KIyMgQ29tdW5pZGFkZXMgeSBzZWdtZW50YWNpw7NuDQoNCmBgYHtyIGZ1bmNpb25lcy1jb211bmlkYWRlc30NCmZpbmRfY29tbXVuaXRpZXMgPC0gZnVuY3Rpb24oZywgbWV0aG9kID0gYygibG91dmFpbiIsICJsZWlkZW4iKSwgc2VlZCA9IDEyMykgew0KICBtZXRob2QgPC0gbWF0Y2guYXJnKG1ldGhvZCkNCiAgaWYgKGVjb3VudChnKSA9PSAwKSByZXR1cm4obGlzdChtZW1iZXJzaGlwID0gc2VxX2xlbih2Y291bnQoZykpLCBRID0gMCkpDQogIHNldC5zZWVkKHNlZWQpDQogIGNsIDwtIHN3aXRjaChtZXRob2QsDQogICAgbG91dmFpbiA9IGNsdXN0ZXJfbG91dmFpbihnLCB3ZWlnaHRzID0gRShnKSR3KSwNCiAgICBsZWlkZW4gID0gY2x1c3Rlcl9sZWlkZW4oZywgb2JqZWN0aXZlX2Z1bmN0aW9uID0gIm1vZHVsYXJpdHkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHRzID0gRShnKSR3LCBuX2l0ZXJhdGlvbnMgPSAtMSkpDQogIG0gPC0gYXMuaW50ZWdlcihtZW1iZXJzaGlwKGNsKSkNCiAgbGlzdChtZW1iZXJzaGlwID0gbSwgUSA9IG1vZHVsYXJpdHkoZywgbSwgd2VpZ2h0cyA9IEUoZykkdykpDQp9DQoNCiMgUHJveWVjdGEgbGEgY29tdW5pZGFkIGRlIGNhZGEgc3VwZXJww614ZWwgc29icmUgc3VzIHDDrXhlbGVzDQpwaXhlbF9wYXJ0aXRpb24gPC0gZnVuY3Rpb24obGFiZWxzLCBtZW1iZXJzaGlwKSB7DQogIG1hdHJpeChtZW1iZXJzaGlwW2xhYmVsc10sIG5yb3cobGFiZWxzKSwgbmNvbChsYWJlbHMpKQ0KfQ0KDQojIMONbmRpY2UgZGUgUmFuZCBhanVzdGFkbyBlbnRyZSBkb3Mgc2VnbWVudGFjaW9uZXMgYSBuaXZlbCBkZSBww614ZWwNCmFyaSA8LSBmdW5jdGlvbihwMSwgcDIpIGlncmFwaDo6Y29tcGFyZShhcy52ZWN0b3IocDEpLCBhcy52ZWN0b3IocDIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJhZGp1c3RlZC5yYW5kIikNCmBgYA0KDQpgYGB7ciBlai1jb211bmlkYWRlcywgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTMuNn0NCmx2X2VqIDwtIGZpbmRfY29tbXVuaXRpZXMoZ19laiwgImxvdXZhaW4iKQ0KbGRfZWogPC0gZmluZF9jb21tdW5pdGllcyhnX2VqLCAibGVpZGVuIikNCnNlZ19sdiA8LSBwaXhlbF9wYXJ0aXRpb24ocmVzX2VqJGxhYmVscywgbHZfZWokbWVtYmVyc2hpcCkNCnNlZ19sZCA8LSBwaXhlbF9wYXJ0aXRpb24ocmVzX2VqJGxhYmVscywgbGRfZWokbWVtYmVyc2hpcCkNCg0Kb3AgPC0gcGFyKG1mcm93ID0gYygxLCAzKSwgbWFyID0gYygwLjMsIDAuMywgMS42LCAwLjMpKQ0KcGxvdF9wYW5lbChpbWdfZWosIG1haW4gPSAiSW1hZ2VuIG9yaWdpbmFsIikNCnBsb3RfcGFuZWwoaW1nX2VqLCBzZWdfbHYsIG1haW4gPSBzcHJpbnRmKCJMb3V2YWluOiAlZCBjb211bmlkYWRlcywgUSA9ICUuM2YiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4KGx2X2VqJG1lbWJlcnNoaXApLCBsdl9laiRRKSkNCnBsb3RfcGFuZWwoaW1nX2VqLCBzZWdfbGQsIG1haW4gPSBzcHJpbnRmKCJMZWlkZW46ICVkIGNvbXVuaWRhZGVzLCBRID0gJS4zZiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXgobGRfZWokbWVtYmVyc2hpcCksIGxkX2VqJFEpKQ0KcGFyKG9wKQ0KYGBgDQoNCkNhZGEgY29tdW5pZGFkIGRlbCBncmFmbyBkZSByZWdpb25lcyBzZSBwcm95ZWN0YSBzb2JyZSBsb3MgcMOteGVsZXMgZGUgc3VzIHN1cGVycMOteGVsZXMgeSBzZSBwaW50YSBjb24gc3UgY29sb3IgbWVkaW8uIEVsIMOtbmRpY2UgZGUgUmFuZCBhanVzdGFkbyBlbnRyZSBsYXMgZG9zIHNlZ21lbnRhY2lvbmVzIGVzIGByIHJvdW5kKGFyaShzZWdfbHYsIHNlZ19sZCksIDMpYCAoMSA9IGlkw6ludGljYXMsIGNlcmNhIGRlIDAgPSBubyBtw6FzIHBhcmVjaWRhcyBxdWUgYWwgYXphcikuDQoNCiMjIEZ1bmNpb25lcyBkZSBleHBlcmltZW50b3MNCg0KYGBge3IgZnVuY2lvbmVzLWV4cGVyaW1lbnRvc30NCiMgUmVzdW1lbiBlc3RydWN0dXJhbCBkZSBsYSByZWQgKHBvbmRlcmFkYSBwb3IgdykNCm5ldF9zdW1tYXJ5IDwtIGZ1bmN0aW9uKGcpIHsNCiAgY29tcCA8LSBjb21wb25lbnRzKGcpDQogIGRhdGEuZnJhbWUodmVydGljZXMgPSB2Y291bnQoZyksIGFyaXN0YXMgPSBlY291bnQoZyksDQogICAgICAgICAgICAgZGVuc2lkYWQgPSByb3VuZChlZGdlX2RlbnNpdHkoZyksIDQpLA0KICAgICAgICAgICAgIGdyYWRvX21lZGlvID0gcm91bmQobWVhbihkZWdyZWUoZykpLCAyKSwNCiAgICAgICAgICAgICB3X21lZGlhID0gcm91bmQobWVhbihFKGcpJHcpLCAzKSwNCiAgICAgICAgICAgICB0cmFuc2l0aXZpZGFkID0gcm91bmQodHJhbnNpdGl2aXR5KGcsIHR5cGUgPSAiZ2xvYmFsIiksIDMpLA0KICAgICAgICAgICAgIGNvbXBvbmVudGVzID0gY29tcCRubywNCiAgICAgICAgICAgICBhaXNsYWRvcyA9IHN1bShkZWdyZWUoZykgPT0gMCkpDQp9DQoNCiMgU2VnbWVudGFjaW9uZXMgTG91dmFpbiB5IExlaWRlbiBwYXJhIHZhcmlvcyBoIChncmFmbyBjb21wbGV0bykNCmV4cGVyaW1lbnRfaCA8LSBmdW5jdGlvbihyZXMsIGhzKSB7DQogIGxhcHBseShocywgZnVuY3Rpb24oaCkgew0KICAgIGcgPC0gc2V0X2gocmVzJGdyYXBoLCBoKQ0KICAgIGx2IDwtIGZpbmRfY29tbXVuaXRpZXMoZywgImxvdXZhaW4iKTsgbGQgPC0gZmluZF9jb21tdW5pdGllcyhnLCAibGVpZGVuIikNCiAgICBsaXN0KGggPSBoLCBnID0gZywgbHYgPSBsdiwgbGQgPSBsZCwNCiAgICAgICAgIHRhYiA9IGRhdGEuZnJhbWUoaCA9IGgsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHdfbWVkaWEgPSByb3VuZChtZWFuKEUoZykkdyksIDMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB3X21lZGlhbmEgPSByb3VuZChtZWRpYW4oRShnKSR3KSwgMyksDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbV9sb3V2YWluID0gbWF4KGx2JG1lbWJlcnNoaXApLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBRX2xvdXZhaW4gPSByb3VuZChsdiRRLCAzKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29tX2xlaWRlbiA9IG1heChsZCRtZW1iZXJzaGlwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgUV9sZWlkZW4gPSByb3VuZChsZCRRLCAzKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgQVJJX2xvdXZhaW5fbGVpZGVuID0gcm91bmQoYXJpKGx2JG1lbWJlcnNoaXAsIGxkJG1lbWJlcnNoaXApLCAzKSkpDQogIH0pDQp9DQoNCiMgU2VnbWVudGFjaW9uZXMgTG91dmFpbiB5IExlaWRlbiBzb2JyZSBlbCBncmFmbyBmaWx0cmFkbyBHX3RhdQ0KZXhwZXJpbWVudF90YXUgPC0gZnVuY3Rpb24ocmVzLCB0YXVzLCBoID0gMjAsIG1pbl9hcmVhID0gMCkgew0KICBnMCA8LSBzZXRfaChyZXMkZ3JhcGgsIGgpDQogIGxhcHBseSh0YXVzLCBmdW5jdGlvbih0YXUpIHsNCiAgICBnIDwtIGZpbHRlcl9ncmFwaChnMCwgdGF1KQ0KICAgIGx2IDwtIGZpbmRfY29tbXVuaXRpZXMoZywgImxvdXZhaW4iKTsgbGQgPC0gZmluZF9jb21tdW5pdGllcyhnLCAibGVpZGVuIikNCiAgICBjb21wIDwtIGNvbXBvbmVudHMoZykNCiAgICBsaXN0KHRhdSA9IHRhdSwgZyA9IGcsIGx2ID0gbHYsIGxkID0gbGQsDQogICAgICAgICB0YWIgPSBkYXRhLmZyYW1lKHRhdSA9IHRhdSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZF9tYXggPSBpZmVsc2UodGF1ID4gMCwgcm91bmQoLWggKiBsb2codGF1KSwgMiksIEluZiksDQogICAgICAgICAgICAgICAgICAgICAgICAgIGFyaXN0YXMgPSBlY291bnQoZyksDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbXBvbmVudGVzID0gY29tcCRubywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgYWlzbGFkb3MgPSBzdW0oZGVncmVlKGcpID09IDApLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBjb21fbG91dmFpbiA9IG1heChsdiRtZW1iZXJzaGlwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgUV9sb3V2YWluID0gcm91bmQobHYkUSwgMyksDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbV9sZWlkZW4gPSBtYXgobGQkbWVtYmVyc2hpcCksDQogICAgICAgICAgICAgICAgICAgICAgICAgIFFfbGVpZGVuID0gcm91bmQobGQkUSwgMyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgQVJJX2xvdXZhaW5fbGVpZGVuID0gcm91bmQoYXJpKGx2JG1lbWJlcnNoaXAsIGxkJG1lbWJlcnNoaXApLCAzKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KICAgICAgICAgDQogICAgICAgICApDQogICAgDQogIH0pDQp9DQoNCiMgUm9idXN0ZXogZnJlbnRlIGEgcnVpZG8gZ2F1c3NpYW5vLg0KIyAtIFNMSUMgZmlqbzogbG9zIHN1cGVycMOteGVsZXMgc29uIGxvcyBkZSBsYSBpbWFnZW4gbGltcGlhOyBlbCBydWlkbyBzb2xvIGNhbWJpYQ0KIyAgIGxvcyBjb2xvcmVzIG1lZGlvcyAoeSBwb3IgdGFudG8gZF9paiB5IHdfaWopLg0KIyAtIFNMSUMgdmFyaWFibGU6IFNMSUMgc2UgcmVjYWxjdWxhIHNvYnJlIGxhIGltYWdlbiBydWlkb3NhLg0KIyBMYSByZWZlcmVuY2lhIGVzIGxhIHNlZ21lbnRhY2nDs24gZGUgbGEgaW1hZ2VuIGxpbXBpYSBjb24gZWwgbWlzbW8gbcOpdG9kby4NCmV4cGVyaW1lbnRfbm9pc2UgPC0gZnVuY3Rpb24ocmVzLCBzaWdtYXMsIHJlcHMgPSA1LCBoID0gMjAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fc3VwZXJwaXhlbHMgPSAxODAsIGNvbXBhY3RuZXNzID0gMjApIHsNCiAgcmVmIDwtIGxpc3QobG91dmFpbiA9IHBpeGVsX3BhcnRpdGlvbihyZXMkbGFiZWxzLCBmaW5kX2NvbW11bml0aWVzKHJlcyRncmFwaCwgImxvdXZhaW4iKSRtZW1iZXJzaGlwKSwNCiAgICAgICAgICAgICAgbGVpZGVuICA9IHBpeGVsX3BhcnRpdGlvbihyZXMkbGFiZWxzLCBmaW5kX2NvbW11bml0aWVzKHJlcyRncmFwaCwgImxlaWRlbiIpJG1lbWJlcnNoaXApKQ0KICBvdXQgPC0gbGlzdCgpDQogIGZvciAocyBpbiBzaWdtYXMpIGZvciAociBpbiBzZXFfbGVuKHJlcHMpKSB7DQogICAgbm9pc3kgPC0gYWRkX25vaXNlKHJlcyRpbWcsIHMsIHNlZWQgPSAxMDAgKiByICsgcykNCiAgICBmaXhlZCA8LSBpbWFnZV90b19ncmFwaChub2lzeSwgbGFiZWxzID0gcmVzJGxhYmVscywgaCA9IGgpDQogICAgdmFyaWEgPC0gaW1hZ2VfdG9fZ3JhcGgobm9pc3ksIG5fc3VwZXJwaXhlbHMgPSBuX3N1cGVycGl4ZWxzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbXBhY3RuZXNzID0gY29tcGFjdG5lc3MsIGggPSBoKQ0KICAgIGZvciAoc2wgaW4gYygiZmlqbyIsICJ2YXJpYWJsZSIpKSB7DQogICAgICByciA8LSBpZiAoc2wgPT0gImZpam8iKSBmaXhlZCBlbHNlIHZhcmlhDQogICAgICBmb3IgKG0gaW4gYygibG91dmFpbiIsICJsZWlkZW4iKSkgew0KICAgICAgICBjbSA8LSBmaW5kX2NvbW11bml0aWVzKHJyJGdyYXBoLCBtKQ0KICAgICAgICBwcCA8LSBwaXhlbF9wYXJ0aXRpb24ocnIkbGFiZWxzLCBjbSRtZW1iZXJzaGlwKQ0KICAgICAgICBvdXRbW2xlbmd0aChvdXQpICsgMV1dIDwtIGRhdGEuZnJhbWUoDQogICAgICAgICAgc2lnbWEgPSBzLCByZXAgPSByLCBzbGljID0gc2wsIG1ldG9kbyA9IG0sDQogICAgICAgICAgY29tdW5pZGFkZXMgPSBtYXgoY20kbWVtYmVyc2hpcCksIFEgPSBjbSRRLA0KICAgICAgICAgIEFSSSA9IGFyaShwcCwgcmVmW1ttXV0pKQ0KICAgICAgfQ0KICAgIH0NCiAgfQ0KICBiaW5kX3Jvd3Mob3V0KQ0KfQ0KDQojIFVuYSByZWFsaXphY2nDs24gcG9yIHNpZ21hIHBhcmEgbW9zdHJhciBsYXMgc2VnbWVudGFjaW9uZXMgKFNMSUMgdmFyaWFibGUpDQpub2lzZV9leGFtcGxlcyA8LSBmdW5jdGlvbihyZXMsIHNpZ21hcywgaCA9IDIwKSB7DQogIGxhcHBseShzaWdtYXMsIGZ1bmN0aW9uKHMpIHsNCiAgICBub2lzeSA8LSBhZGRfbm9pc2UocmVzJGltZywgcywgc2VlZCA9IDEwMCArIHMpDQogICAgcnIgPC0gaW1hZ2VfdG9fZ3JhcGgobm9pc3ksIGggPSBoKQ0KICAgIGxpc3Qoc2lnbWEgPSBzLCBpbWcgPSBub2lzeSwgcmVzID0gcnIsDQogICAgICAgICBsdiA9IHBpeGVsX3BhcnRpdGlvbihyciRsYWJlbHMsIGZpbmRfY29tbXVuaXRpZXMocnIkZ3JhcGgsICJsb3V2YWluIikkbWVtYmVyc2hpcCksDQogICAgICAgICBsZCA9IHBpeGVsX3BhcnRpdGlvbihyciRsYWJlbHMsIGZpbmRfY29tbXVuaXRpZXMocnIkZ3JhcGgsICJsZWlkZW4iKSRtZW1iZXJzaGlwKSkNCiAgfSkNCn0NCg0KIyBHcsOhZmljb3MgZGUgbG9zIGV4cGVyaW1lbnRvcw0KcGxvdF9leHBfZ3JpZCA8LSBmdW5jdGlvbihyZXMsIGV4LCBrZXksIGxhYikgew0KICBvcCA8LSBwYXIobWZyb3cgPSBjKDIsIGxlbmd0aChleCkpLCBtYXIgPSBjKDAuMywgMC4zLCAxLjYsIDAuMykpDQogIGZvciAoZSBpbiBleCkgcGxvdF9wYW5lbChyZXMkaW1nLCBwaXhlbF9wYXJ0aXRpb24ocmVzJGxhYmVscywgZSRsdiRtZW1iZXJzaGlwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwcmludGYoIkxvdXZhaW4sICVzID0gJXMgKCVkIGNvbS4pIiwgbGFiLCBlW1trZXldXSwgbWF4KGUkbHYkbWVtYmVyc2hpcCkpKQ0KICBmb3IgKGUgaW4gZXgpIHBsb3RfcGFuZWwocmVzJGltZywgcGl4ZWxfcGFydGl0aW9uKHJlcyRsYWJlbHMsIGUkbGQkbWVtYmVyc2hpcCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKCJMZWlkZW4sICVzID0gJXMgKCVkIGNvbS4pIiwgbGFiLCBlW1trZXldXSwgbWF4KGUkbGQkbWVtYmVyc2hpcCkpKQ0KICBwYXIob3ApDQp9DQoNCnBsb3Rfbm9pc2VfZ3JpZCA8LSBmdW5jdGlvbihuZSkgew0KICBvcCA8LSBwYXIobWZyb3cgPSBjKDMsIGxlbmd0aChuZSkpLCBtYXIgPSBjKDAuMywgMC4zLCAxLjYsIDAuMykpDQogIGZvciAoZSBpbiBuZSkgcGxvdF9wYW5lbChlJGltZywgbWFpbiA9IHNwcmludGYoIkltYWdlbiwgc2lnbWEgPSAlcyIsIGUkc2lnbWEpKQ0KICBmb3IgKGUgaW4gbmUpIHBsb3RfcGFuZWwoZSRpbWcsIGUkbHYsIHNwcmludGYoIkxvdXZhaW4gKCVkIGNvbS4pIiwgbWF4KGUkbHYpKSkNCiAgZm9yIChlIGluIG5lKSBwbG90X3BhbmVsKGUkaW1nLCBlJGxkLCBzcHJpbnRmKCJMZWlkZW4gKCVkIGNvbS4pIiwgbWF4KGUkbGQpKSkNCiAgcGFyKG9wKQ0KfQ0KDQpwbG90X25vaXNlX2FyaSA8LSBmdW5jdGlvbihueiwgdGl0dWxvKSB7DQogIG56IHw+DQogICAgZ3JvdXBfYnkoc2lnbWEsIHNsaWMsIG1ldG9kbykgfD4NCiAgICBzdW1tYXJpc2UobWVkaWEgPSBtZWFuKEFSSSksIHNkID0gc2QoQVJJKSwgLmdyb3VwcyA9ICJkcm9wIikgfD4NCiAgICBnZ3Bsb3QoYWVzKHNpZ21hLCBtZWRpYSwgY29sb3VyID0gbWV0b2RvLCBsaW5ldHlwZSA9IHNsaWMpKSArDQogICAgZ2VvbV9saW5lKGxpbmV3aWR0aCA9IDAuOCkgKyBnZW9tX3BvaW50KHNpemUgPSAyKSArDQogICAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IG1lZGlhIC0gc2QsIHltYXggPSBtZWRpYSArIHNkKSwgd2lkdGggPSAxLjUpICsNCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMobGVpZGVuID0gIiNDMDM5MkIiLCBsb3V2YWluID0gIiMyNDcxQTMiKSkgKw0KICAgIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCAxKSkgKw0KICAgIGxhYnMoeCA9IGV4cHJlc3Npb24oc2lnbWEgfiAiZGVsIHJ1aWRvIChlc2NhbGEgMC0yNTUpIiksDQogICAgICAgICB5ID0gIkFSSSByZXNwZWN0byBhIGxhIGltYWdlbiBsaW1waWEiLA0KICAgICAgICAgY29sb3VyID0gIk3DqXRvZG8iLCBsaW5ldHlwZSA9ICJTTElDIiwgdGl0bGUgPSB0aXR1bG8pICsNCiAgICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEyKQ0KfQ0KDQpzdW1tYXJ5X25vaXNlIDwtIGZ1bmN0aW9uKG56KSB7DQogIG56IHw+DQogICAgZ3JvdXBfYnkoc2lnbWEsIHNsaWMsIG1ldG9kbykgfD4NCiAgICBzdW1tYXJpc2UoY29tdW5pZGFkZXMgPSByb3VuZChtZWFuKGNvbXVuaWRhZGVzKSwgMSksDQogICAgICAgICAgICAgIFEgPSByb3VuZChtZWFuKFEpLCAzKSwNCiAgICAgICAgICAgICAgQVJJX21lZGlhID0gcm91bmQobWVhbihBUkkpLCAzKSwNCiAgICAgICAgICAgICAgQVJJX3NkID0gcm91bmQoc2QoQVJJKSwgMyksIC5ncm91cHMgPSAiZHJvcCIpIHw+DQogICAgYXJyYW5nZShzbGljLCBtZXRvZG8sIHNpZ21hKQ0KfQ0KYGBgDQoNCiMgSW3DoWdlbmVzIGRlIHBydWViYQ0KDQoNCiogKipQYWlzYWplIGFycXVpdGVjdMOzbmljbyoqOiBlZGlmaWNpbyBlbiBjb25jcmV0byBjb24gZmFjaGFkYSBpbHVtaW5hZGEsIHpvbmFzIHZlcmRlcywgdsOtYSBjb24gY2hhcmNvcyB5IGNpZWxvIG51YmxhZG8uICpGb3RvZ3JhZsOtYSDCqSBSb2RyaWdvIETDoXZpbGEqOyBzZSB1c2EgYXF1w60gw7puaWNhbWVudGUgY29uIGZpbmVzIGFjYWTDqW1pY29zLg0KKiAqKkFnbnVzIERlaSoqLCBGcmFuY2lzY28gZGUgWnVyYmFyw6FuLCBjLiAxNjM14oCTMTY0MCwgTXVzZW8gZGVsIFByYWRvLiBPYnJhIGRlIGRvbWluaW8gcMO6YmxpY28uIEVzIHVuYSBpbWFnZW4gZGUgYmFqbyBjb250cmFzdGUgY3JvbcOhdGljbzogdW4gY29yZGVybyBjbGFybyBzb2JyZSB1biBmb25kbyBvc2N1cm8gY2FzaSB1bmlmb3JtZSwgY29uIGlsdW1pbmFjacOzbiB0ZW5lYnJpc3RhLg0KDQpQYXJhIGVsIGFuw6FsaXNpcyBsYXMgaW3DoWdlbmVzIHNlIHJlZHVjZW4gYSA0MDAgcMOteGVsZXMgZW4gc3UgbGFkbyBtYXlvciwgcGFyYSBxdWUgU0xJQyB5IGxvcyBhbGdvcml0bW9zIGRlIHJlZCBzZWFuIHLDoXBpZG9zOyBsYXMgaW3DoWdlbmVzIG9yaWdpbmFsZXMgc2UgbXVlc3RyYW4gZW4gYWx0YSByZXNvbHVjacOzbi4NCg0KYGBge3IgY2FyZ2F9DQpwYWlzYWplIDwtIHByZXBhcmVfcmdiKCJwYWlzYWplX3JkYXZpbGEuanBnIiwgbWF4X3NpZGUgPSA0MDApDQpjb3JkZXJvIDwtIHByZXBhcmVfcmdiKCJhZ251c19kZWlfenVyYmFyYW4uanBnIiwgbWF4X3NpZGUgPSA0MDApDQojIFZlcnNpb25lcyBlbiBhbHRhIHJlc29sdWNpw7NuLCBzb2xvIHBhcmEgbW9zdHJhciBsYXMgaW3DoWdlbmVzIG9yaWdpbmFsZXMNCnBhaXNhamVfaGQgPC0gcHJlcGFyZV9yZ2IoInBhaXNhamVfcmRhdmlsYS5qcGciLCBtYXhfc2lkZSA9IDEwMDApDQpjb3JkZXJvX2hkIDwtIHByZXBhcmVfcmdiKCJhZ251c19kZWlfenVyYmFyYW4uanBnIiwgbWF4X3NpZGUgPSAxMDAwKQ0KcmJpbmQocGFpc2FqZSA9IGRpbShwYWlzYWplKSwgYWdudXNfZGVpID0gZGltKGNvcmRlcm8pKQ0KYGBgDQoNClBhcsOhbWV0cm9zIGNvbXVuZXMgOiAkSz0xODAkIHN1cGVycMOteGVsZXMgc29saWNpdGFkb3MsICpjb21wYWN0bmVzcyogJG09MjAkIHkgJGg9MjAkIGNvbW8gdmFsb3IgZGUgcmVmZXJlbmNpYS4NCg0KYGBge3IgcGFyYW1ldHJvc30NCksgPC0gMTgwOyBtIDwtIDIwOyBoMCA8LSAyMA0KaHMgICAgIDwtIGMoNSwgMTAsIDIwLCA0MCwgODApICAgICAgICMgZXNjYWxhcyBkZSBjb2xvciBwYXJhIHdfaWoNCnRhdXMgICA8LSBjKDAsIDAuMywgMC41LCAwLjcpICAgICAgICAjIHVtYnJhbGVzIGRlIGZpbHRyYWRvDQpzaWdtYXMgPC0gYygwLCAxMCwgMjUsIDUwKSAgICAgICAgICAgIyBkZXN2aWFjacOzbiBlc3TDoW5kYXIgZGVsIHJ1aWRvDQpyZXBzICAgPC0gNSAgICAgICAgICAgICAgICAgICAgICAgICAgIyByw6lwbGljYXMgZGUgcnVpZG8gcG9yIHNpZ21hDQpgYGANCg0KIyBQYWlzYWplDQoNCkxhIGZvdG9ncmFmw61hIGRlIFJvZHJpZ28gRMOhdmlsYSBtdWVzdHJhIGVsIG51ZXZvIGVkaWZpY2lvIGRlIGxhIEZhY3VsdGFkIGRlIEVuZmVybWVyw61hIGRlIGxhIFVuaXZlcnNpZGFkIE5hY2lvbmFsIGRlIENvbG9tYmlhLCBzZWRlIEJvZ290w6EsIGFsIGF0YXJkZWNlci4NCg0KRW4gcHJpbWVyIHBsYW5vIGFwYXJlY2VuIHpvbmFzIHZlcmRlcyBkZWwgY2FtcHVzLCB1bmEgdsOtYSBow7ptZWRhIGNvbiBjaGFyY29zIHF1ZSByZWZsZWphbiBsYSBsdXosIMOhcmJvbGVzIHkgYWxndW5vcyBwZWF0b25lcywgeSBhbCBmb25kbyBzZSBhbGNhbnphbiBhIHZlciBsb3MgZWRpZmljaW9zIGRlIGxhZHJpbGxvIGNhcmFjdGVyw61zdGljb3MgZGUgbGEgQ2l1ZGFkIFVuaXZlcnNpdGFyaWEuDQoNCiMjIFJlZCBkZSByZWdpb25lcw0KDQpgYGB7ciBwYWlzYWplLXJlZH0NCnJlc19wIDwtIGltYWdlX3RvX2dyYXBoKHBhaXNhamUsIG5fc3VwZXJwaXhlbHMgPSBLLCBjb21wYWN0bmVzcyA9IG0sIGggPSBoMCkNCm5ldF9zdW1tYXJ5KHJlc19wJGdyYXBoKQ0KYGBgDQoNCmBgYHtyIHBhaXNhamUtcmVkLWZpZywgZmlnLndpZHRoPTExLCBmaWcuaGVpZ2h0PTMuMn0NCnBhcihtZnJvdyA9IGMoMSwgMyksIG1hciA9IGMoMC4zLCAwLjMsIDEuNiwgMC4zKSkNCnBsb3RfcGFuZWwocGFpc2FqZV9oZCwgbWFpbiA9ICJJbWFnZW4gb3JpZ2luYWwiKQ0KcGxvdF9wYW5lbChwYWlzYWplLCByZXNfcCRsYWJlbHMsICJTdXBlcnDDrXhlbGVzIFNMSUMgKGNvbG9yIG1lZGlvKSIpDQpwbG90X2dyYXBoX292ZXJsYXkocmVzX3AsICJSZWQgZGUgcmVnaW9uZXMgKGdyb3NvciB+IHdfaWosIGggPSAyMCkiKQ0KYGBgDQoNClNMSUMgZGV2dWVsdmUgYHIgdmNvdW50KHJlc19wJGdyYXBoKWAgc3VwZXJww614ZWxlcyAoZWwgbsO6bWVybyBmaW5hbCBubyBjb2luY2lkZSBleGFjdGFtZW50ZSBjb24gbG9zIGByIEtgIHNvbGljaXRhZG9zIHBvcnF1ZSBTTElDIGFqdXN0YSBsYSByZWppbGxhIGluaWNpYWwgYSBsYXMgcHJvcG9yY2lvbmVzIGRlIGxhIGltYWdlbikgeSBsYSByZWQgdGllbmUgYHIgZWNvdW50KHJlc19wJGdyYXBoKWAgYXJpc3Rhcy4gRXMgdW5hIHJlZCBlc3BhY2lhbCwgY2FzaSBwbGFuYSwgY29uIGdyYWRvIG1lZGlvIGRlIGByIHJvdW5kKG1lYW4oZGVncmVlKHJlc19wJGdyYXBoKSksIDIpYCwgY29tbyBzZSBlc3BlcmEgZGUgdW5hIHRlc2VsYWNpw7NuIGRlIGxhIGltYWdlbi4gRW4gbGEgc3VwZXJwb3NpY2nDs24gc2UgdmUgcXVlIGxhcyBhcmlzdGFzIGdydWVzYXMgKGFsdGEgc2ltaWxpdHVkKSBzZSBjb25jZW50cmFuIGVuIGVsIGNpZWxvIHkgZW4gZWwgY8Opc3BlZCwgeSBxdWUgbGFzIHF1ZSBjcnV6YW4gbGEgZmFjaGFkYSBpbHVtaW5hZGEgc29uIGRlbGdhZGFzLg0KDQojIyBTZW5zaWJpbGlkYWQgYSAkaCQgZW4gbGEgc2ltaWxpdHVkIHZpc3VhbA0KDQpMYSBlc2NhbGEgJGgkIGZpamEgY3XDoW50byAiY2FzdGlnYSIgbGEgc2ltaWxpdHVkIHVuYSBkaWZlcmVuY2lhIGRlIGNvbG9yLiBDb24gJGgkIHBlcXVlw7FvLCBzb2xvIGxhcyByZWdpb25lcyBjYXNpIGlkw6ludGljYXMgdGllbmVuIHBlc28gYWx0byB5IGxhIG1heW9yw61hIGRlIGFyaXN0YXMgdmFsZW4gY2FzaSBjZXJvLiBDb24gJGgkIGdyYW5kZSwgdG9kb3MgbG9zIHBlc29zIHNlIGFjZXJjYW4gYSAxIHkgbGEgcmVkIHNlIHZ1ZWx2ZSBjYXNpIG5vIHBvbmRlcmFkYS4NCg0KYGBge3IgY3VydmEtaCwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9My40fQ0KY3VydmEgPC0gZXhwYW5kLmdyaWQoZCA9IHNlcSgwLCA4MCwgYnkgPSAwLjUpLCBoID0gaHMpIHw+DQogIG11dGF0ZSh3ID0gZXhwKC1kIC8gaCksIGggPSBmYWN0b3IoaCkpDQpkaXN0X3AgPC0gZGF0YS5mcmFtZShkID0gRShyZXNfcCRncmFwaCkkZCkNCmdncGxvdChjdXJ2YSwgYWVzKGQsIHcsIGNvbG91ciA9IGgpKSArDQogIGdlb21fcnVnKGRhdGEgPSBkaXN0X3AsIGFlcyh4ID0gZCksIGluaGVyaXQuYWVzID0gRkFMU0UsIGFscGhhID0gMC4yNSkgKw0KICBnZW9tX2xpbmUobGluZXdpZHRoID0gMC45KSArDQogIGxhYnMoeCA9IGV4cHJlc3Npb24oZFtpal0gfiAiKCIgKiBEZWx0YSAqIEUgKiAiLCBDSUVMQUIpIiksDQogICAgICAgeSA9IGV4cHJlc3Npb24od1tpal0gPT0gZXhwKC1kW2lqXSAvIGgpKSwgY29sb3VyID0gImgiLA0KICAgICAgIHRpdGxlID0gIlNpbWlsaXR1ZCBlbiBmdW5jacOzbiBkZSBsYSBkaXN0YW5jaWEgZGUgY29sb3IgKG1hcmNhczogZF9paiBkZWwgcGFpc2FqZSkiKSArDQogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTIpDQpgYGANCg0KYGBge3IgcGFpc2FqZS1ofQ0KZXhfaF9wIDwtIGV4cGVyaW1lbnRfaChyZXNfcCwgaHMpDQpiaW5kX3Jvd3MobGFwcGx5KGV4X2hfcCwgYFtbYCwgInRhYiIpKQ0KYGBgDQoNCmBgYHtyIHBhaXNhamUtaC1maWcsIGZpZy53aWR0aD0xMywgZmlnLmhlaWdodD00LjZ9DQpwbG90X2V4cF9ncmlkKHJlc19wLCBleF9oX3AsICJoIiwgImgiKQ0KYGBgDQoNCioqTGVjdHVyYS4qKiBMYSBtb2R1bGFyaWRhZCBlcyBpbnZhcmlhbnRlIGEgbXVsdGlwbGljYXIgdG9kb3MgbG9zIHBlc29zIHBvciB1bmEgY29uc3RhbnRlLCBkZSBtb2RvIHF1ZSAkaCQgbm8gYWN0w7phIGNvbW8gdW4gc2ltcGxlIGZhY3RvciBkZSBlc2NhbGE6IGNhbWJpYSBlbCAqKmNvbnRyYXN0ZSByZWxhdGl2byoqIGVudHJlIGFyaXN0YXMgZGUgY29sb3JlcyBwYXJlY2lkb3MgeSBkaXN0aW50b3MuIEVuIGVsIHBhaXNhamU6DQoNCiogQ29uICRoPTUkIGxhIG1lZGlhbmEgZGUgJHdfe2lqfSQgZXMgYXBlbmFzIGByIGV4X2hfcFtbMV1dJHRhYiR3X21lZGlhbmFgOiBzb2xvIGxhcyByZWdpb25lcyBjYXNpIGlkw6ludGljYXMgcXVlZGFuIGZ1ZXJ0ZW1lbnRlIHVuaWRhcyB5IGxhIHNlZ21lbnRhY2nDs24gc2lndWUgbG9zIGNhbWJpb3MgZGUgY29sb3IuIFNlcGFyYSBsb3MgZG9zIHByYWRvcyBkZSBjw6lzcGVkLCBsYSB2w61hIGNvbiBjaGFyY29zLCBsYSBmYWNoYWRhLCBsb3Mgw6FyYm9sZXMgeSBlbCBjaWVsbyBlbiBibG9xdWVzIGRpc3RpbnRvcyAoMTIgY29tdW5pZGFkZXMgY29uIGFtYm9zIG3DqXRvZG9zKS4NCiogQWwgYXVtZW50YXIgJGgkLCBsb3MgcGVzb3MgdGllbmRlbiBhIDEgKG1lZGlhbmEgYHIgZXhfaF9wW1s1XV0kdGFiJHdfbWVkaWFuYWAgY29uICRoPTgwJCkgeSBsYSBwYXJ0aWNpw7NuIHNlIGFjZXJjYSBhIGxhIGRlIGxhIHJlZCBubyBwb25kZXJhZGE6IGxhcyBjb211bmlkYWRlcyBzZSBjb252aWVydGVuIGVuIGJsb3F1ZXMgZXNwYWNpYWxlcyBjb21wYWN0b3MuIENvbiAkaD00MCQgZWwgcHJhZG8gZGUgbGEgaXpxdWllcmRhIHNlIHVuZSBjb24gbG9zIMOhcmJvbGVzIHkgbGEgcGFydGUgYmFqYSBkZSBsYSBmYWNoYWRhIGVuIHVuIGdyYW4gYmxvcXVlIHZlcmRlOyBjb24gJGg9ODAkIG9jdXJyZSBsbyBtaXNtbyBhIGxhIGRlcmVjaGEgeSBsYSB2w61hIHNlIHVuZSBjb24gZWwgY2VudHJvIGRlIGxhIGZhY2hhZGEuIEVsIGNpZWxvIHkgbGEgZmFjaGFkYSBzZSBjb3J0YW4gZW4gYmxvcXVlcyBwb3IgcmF6b25lcyBnZW9tw6l0cmljYXMsIG5vIGRlIGNvbG9yLg0KKiBMYSBtb2R1bGFyaWRhZCBlcyBtw6F4aW1hIGVuICRoPTEwJCBjb24gTG91dmFpbiAoMC43MTgpIHkgZW4gJGg9MjAkIGNvbiBMZWlkZW4gKDAuNzI0KSwgeSBlbCBuw7ptZXJvIGRlIGNvbXVuaWRhZGVzIGJhamEgZGUgMTIgYSA44oCTOS4gRWwgYWN1ZXJkbyBlbnRyZSBMb3V2YWluIHkgTGVpZGVuIGVzIGNhc2kgcGVyZmVjdG8gY29uICRoPTUkIChBUkkgPSAwLjk5KSB5IGNhZSBhIDAuNTUgY29uICRoPTgwJDogY3VhbmRvIGxvcyBwZXNvcyBzZSBpZ3VhbGFuIGFwYXJlY2VuIG11Y2hhcyBwYXJ0aWNpb25lcyBkZSBtb2R1bGFyaWRhZCBwYXJlY2lkYSBxdWUgZGlmaWVyZW4gZW4gY29ydGVzIHBvY28gbWFyY2Fkb3MuDQoNClZhbG9yZXMgaW50ZXJtZWRpb3MgKCRoXGFwcHJveCAxMCTigJMkMjAkKSBlcXVpbGlicmFuIGNvbG9yIHkgY29udGlnw7xpZGFkLg0KDQojIyBTZWdtZW50YWNpb25lcyBwYXJhIHZhcmlvcyB2YWxvcmVzIGRlICRcdGF1JA0KDQpGaWphZG8gJGg9MjAkLCBzZSBmaWx0cmEgbGEgcmVkIGNvbiAkd197aWp9XGdlXHRhdSQgeSBzZSBhcGxpY2FuIExvdXZhaW4geSBMZWlkZW4gKipzb2JyZSBsYSByZWQgZmlsdHJhZGEqKiAkR19cdGF1JC4gTGEgY29sdW1uYSBgZF9tYXhgIGVzIGxhIG3DoXhpbWEgZGlzdGFuY2lhIGRlIGNvbG9yIHBlcm1pdGlkYSwgJC1oXGxuXHRhdSQuDQoNCmBgYHtyIHBhaXNhamUtdGF1fQ0KZXhfdF9wIDwtIGV4cGVyaW1lbnRfdGF1KHJlc19wLCB0YXVzLCBoID0gaDApDQpiaW5kX3Jvd3MobGFwcGx5KGV4X3RfcCwgYFtbYCwgInRhYiIpKQ0KYGBgDQoNCmBgYHtyIHBhaXNhamUtdGF1LWZpZywgZmlnLndpZHRoPTExLCBmaWcuaGVpZ2h0PTQuNn0NCnBsb3RfZXhwX2dyaWQocmVzX3AsIGV4X3RfcCwgInRhdSIsICJ0YXUiKQ0KYGBgDQoNCioqTGVjdHVyYS4qKiBDb24gJFx0YXU9MCQgc2UgdXNhIGxhIHJlZCBjb21wbGV0YS4gQWwgc3ViaXIgZWwgdW1icmFsIHNlIGNvcnRhbiBsYXMgYXJpc3RhcyBxdWUgY3J1emFuIGJvcmRlcyBkZSBjb2xvciwgYXPDrSBxdWUgbmluZ8O6biBhbGdvcml0bW8gcHVlZGUgdW5pciByZWdpb25lcyBkZSBjb21wb25lbnRlcyBkaXN0aW50b3M6IGNhZGEgY29tcG9uZW50ZSBkZSAkR19cdGF1JCBlcywgY29tbyBtw61uaW1vLCB1bmEgY29tdW5pZGFkLiBFbiBlbCBwYWlzYWplIGxhIHJlZCBwYXNhIGRlIDEgY29tcG9uZW50ZSBhIDE3LCAzOSB5IDc5IChjb24gJFx0YXU9MC43JCwgNjAgZGUgZWxsb3Mgc29uIHN1cGVycMOteGVsZXMgYWlzbGFkb3MpLCB5IGVsIG7Dum1lcm8gZGUgY29tdW5pZGFkZXMgc3ViZSBkZSAxMCBhIDI0LCA0MyB5IDgy4oCTODQuDQoNCiogQ29uICRcdGF1PTAuMyQgKCRkX3tpan1cbGUgMjQkKSBhcGFyZWNlbiBjb21vIGNvbXVuaWRhZGVzIHByb3BpYXMgbGFzIHZlbnRhbmFzIGlsdW1pbmFkYXMsIHVuYSB2ZW50YW5hIG9zY3VyYSBkZSBsYSB0b3JyZSwgbG9zIGNoYXJjb3MgeSBlbCBib3JkZSBkZWwgcHJhZG8sIHF1ZSBsYSByZWQgY29tcGxldGEgYWJzb3Jiw61hIGVuIGJsb3F1ZXMgbcOhcyBncmFuZGVzLg0KKiBDb24gJFx0YXU9MC41JCBlbWVyZ2UgbGEgZXN0cnVjdHVyYSB2ZXJ0aWNhbCBkZSBsYSBmYWNoYWRhIChwYXJhc29sZXMgeSB2ZW50YW5hbGVzKSB5IGxhIHNlZ21lbnRhY2nDs24gZW1waWV6YSBhIHBhcmVjZXIgdW4gbWFwYSBkZSBib3JkZXMuDQoqIENvbiAkXHRhdT0wLjckICgkZF97aWp9XGxlIDcuMSQpIHNlIGFjZXJjYSBhbCBuaXZlbCBkZSBzdXBlcnDDrXhlbDogZWwgZmlsdHJhZG8sIG3DoXMgcXVlIGxvcyBhbGdvcml0bW9zIGRlIGNvbXVuaWRhZGVzLCBkZXRlcm1pbmEgbGEgcGFydGljacOzbi4NCg0KTGEgbW9kdWxhcmlkYWQgc3ViZSBoYXN0YSAkXHRhdT0wLjUkLCBwb3JxdWUgbGEgcmVkIGZpbHRyYWRhIHlhIGVzdMOhICJwcmUtc2VnbWVudGFkYSIsIHkgYmFqYSBjb24gJFx0YXU9MC43JCwgY3VhbmRvIHF1ZWRhbiBkZW1hc2lhZGFzIGNvbXVuaWRhZGVzIHRyaXZpYWxlcy4gRGVzZGUgJFx0YXU9MC4zJCBMb3V2YWluIHkgTGVpZGVuIGRhbiBwcsOhY3RpY2FtZW50ZSBsYSBtaXNtYSBwYXJ0aWNpw7NuOiBjYXNpIG5vIHF1ZWRhIG1hcmdlbiBkZSBkZWNpc2nDs24gZGVudHJvIGRlIGNvbXBvbmVudGVzIHRhbiBwZXF1ZcOxYXMuDQoNCiMjIFJvYnVzdGV6IGZyZW50ZSBhIHJ1aWRvIGdhdXNzaWFubw0KDQpTZSBzdW1hIHJ1aWRvICRcdmFyZXBzaWxvblxzaW0gTigwLFxzaWdtYV4yKSQgYSBjYWRhIGNhbmFsIChlc2NhbGEgMOKAkzI1NSkgeSBzZSBjb21wYXJhIGxhIHNlZ21lbnRhY2nDs24gcnVpZG9zYSBjb24gbGEgZGUgbGEgaW1hZ2VuIGxpbXBpYSBtZWRpYW50ZSBlbCAqKsOtbmRpY2UgZGUgUmFuZCBhanVzdGFkbyAoQVJJKSoqIGEgbml2ZWwgZGUgcMOteGVsICgkMSQgPSBtaXNtYSBwYXJ0aWNpw7NuLCAkXGFwcHJveCAwJCA9IGNvaW5jaWRlbmNpYSBhbCBhemFyKS4gU2UgY29uc2lkZXJhbiBkb3MgZXNjZW5hcmlvczoNCg0KKiAqKlNMSUMgZmlqbzoqKiBzZSBjb25zZXJ2YW4gbG9zIHN1cGVycMOteGVsZXMgZGUgbGEgaW1hZ2VuIGxpbXBpYTsgZWwgcnVpZG8gc29sbyBhbHRlcmEgbG9zIGNvbG9yZXMgbWVkaW9zIHkgcG9yIHRhbnRvIGxvcyBwZXNvcy4NCiogKipTTElDIHZhcmlhYmxlOioqIFNMSUMgc2UgcmVjYWxjdWxhIHNvYnJlIGxhIGltYWdlbiBydWlkb3NhOyBlbCBydWlkbyBhZmVjdGEgdGFtYmnDqW4gbGEgdGVzZWxhY2nDs24uDQoNCmBgYHtyIHBhaXNhamUtcnVpZG8tZWosIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD02LjZ9DQpuZV9wIDwtIG5vaXNlX2V4YW1wbGVzKHJlc19wLCBzaWdtYXMsIGggPSBoMCkNCnBsb3Rfbm9pc2VfZ3JpZChuZV9wKQ0KYGBgDQoNCmBgYHtyIHBhaXNhamUtcnVpZG99DQpuel9wIDwtIGV4cGVyaW1lbnRfbm9pc2UocmVzX3AsIHNpZ21hcywgcmVwcyA9IHJlcHMsIGggPSBoMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBuX3N1cGVycGl4ZWxzID0gSywgY29tcGFjdG5lc3MgPSBtKQ0Kc3VtbWFyeV9ub2lzZShuel9wKQ0KYGBgDQoNCmBgYHtyIHBhaXNhamUtcnVpZG8tZmlnLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQ0KcGxvdF9ub2lzZV9hcmkobnpfcCwgIlBhaXNhamU6IGVzdGFiaWxpZGFkIGRlIGxhIHNlZ21lbnRhY2nDs24gZnJlbnRlIGFsIHJ1aWRvIikNCmBgYA0KDQoqKkxlY3R1cmEuKiogTG9zIGRvcyBlc2NlbmFyaW9zIHNlIGNvbXBvcnRhbiBkZSBmb3JtYSBtdXkgZGlzdGludGEuDQoNCiogKipTTElDIGZpam8uKiogRWwgQVJJIHNlIG1hbnRpZW5lIGFsdG8sIGVudHJlIDAuODEgeSAwLjk0IChMZWlkZW4gMC44NuKAkzAuOTQsIExvdXZhaW4gMC44MeKAkzAuODMpLCBzaW4gdW5hIHRlbmRlbmNpYSBjbGFyYSBjb24gJFxzaWdtYSQ6IGVsIGRlIExlaWRlbiBpbmNsdXNvIHBhc2EgZGUgMC44NiBjb24gJFxzaWdtYT0yNSQgYSAwLjkxIGNvbiAkXHNpZ21hPTUwJC4gRWwgbsO6bWVybyBkZSBjb211bmlkYWRlcyAoOS424oCTMTApIHkgbGEgbW9kdWxhcmlkYWQgKDAuNzHigJMwLjcyKSBjYXNpIG5vIGNhbWJpYW4uIEVsIHJ1aWRvIHNlIHByb21lZGlhIGRlbnRybyBkZSBjYWRhIHN1cGVycMOteGVsIChsYSBkZXN2aWFjacOzbiBkZWwgY29sb3IgbWVkaW8gY2FlIGNvbW8gJFxzaWdtYS9cc3FydHtcdGV4dHvDoXJlYX19JCksIGRlIG1vZG8gcXVlIGxvcyBwZXNvcyBhcGVuYXMgY2FtYmlhbi4gTGFzIGRpZmVyZW5jaWFzIHF1ZSBhcGFyZWNlbiBzZSBkZWJlbiBzb2JyZSB0b2RvIGEgbGEgaW5lc3RhYmlsaWRhZCBkZSBsYSBtYXhpbWl6YWNpw7NuIGRlIGxhIG1vZHVsYXJpZGFkOiBoYXkgdmFyaWFzIHBhcnRpY2lvbmVzIGNhc2kgaWd1YWwgZGUgYnVlbmFzIChwb3IgZWplbXBsbywgZMOzbmRlIGNvcnRhciBsYSBmYWNoYWRhKSB5IHVuYSBwZXF1ZcOxYSBwZXJ0dXJiYWNpw7NuIGRlIGxvcyBwZXNvcyBiYXN0YSBwYXJhIHBhc2FyIGRlIHVuYSBhIG90cmEuDQoqICoqU0xJQyB2YXJpYWJsZS4qKiBBcXXDrSBzw60gaGF5IHVuIGRldGVyaW9ybyBjbGFybzogZWwgQVJJIGJhamEgYSDiiYggMC41OeKAkzAuNjAgY29uICRcc2lnbWE9MTAkLCDiiYggMC40MeKAkzAuNDIgY29uICRcc2lnbWE9MjUkIHkg4omIIDAuMjjigJMwLjI5IGNvbiAkXHNpZ21hPTUwJDsgZWwgbsO6bWVybyBkZSBjb211bmlkYWRlcyBjYWUgZGUgMTAgYSB1bmFzIDUgeSBsYSBtb2R1bGFyaWRhZCBkZSAwLjcyIGEgMC41OC4gRWwgcnVpZG8gYWZlY3RhIGxhIHRlc2VsYWNpw7NuOiBlbCB0w6lybWlubyBkZSBjb2xvciBkZSBTTElDIHNlIHZ1ZWx2ZSBwb2NvIGluZm9ybWF0aXZvLCBsb3Mgc3VwZXJww614ZWxlcyBkZWphbiBkZSBzZWd1aXIgbG9zIGJvcmRlcyB5IGxhIHJlZCBoZXJlZGEgcmVnaW9uZXMgcXVlIG1lemNsYW4gb2JqZXRvcy4gQWRlbcOhcywgZWwgcmVjb3J0ZSBhICRbMCwyNTVdJCBzZXNnYSBsb3MgcMOteGVsZXMgb3NjdXJvcyBoYWNpYSBhcnJpYmEgKHBhcmEgdW4gcMOteGVsIG5lZ3JvLCBsYSBtZWRpYSBkZSAkXG1heCgwLFx2YXJlcHNpbG9uKSQgZXMgJFxzaWdtYS9cc3FydHsyXHBpfVxhcHByb3ggMC40XHNpZ21hJCksIGxvIHF1ZSByZWR1Y2UgZWwgY29udHJhc3RlLiBFbiBsYXMgZmlndXJhcywgY29uICRcc2lnbWE9MjUkIGxhIGZhY2hhZGEgc2UgbWV6Y2xhIGNvbiBsYSB2w61hIHkgY29uIGxvcyDDoXJib2xlcyBkZSBsYSBpenF1aWVyZGEsIHkgY29uICRcc2lnbWE9NTAkIGxhcyBjb211bmlkYWRlcyBzb24gYmxvcXVlcyBjYXNpIHZlcnRpY2FsZXMgZW4gbG9zIHF1ZSBlbCBjaWVsbyBhcGVuYXMgc2UgZGlzdGluZ3VlLg0KDQoNCiMgQWdudXMgRGVpDQoNCipBZ251cyBEZWkqIChjLiAxNjM14oCTMTY0MCwgTXVzZW8gZGVsIFByYWRvKSBlcyB1bmEgZGUgbGFzIG9icmFzIG3DoXMgY29ub2NpZGFzIGRlIEZyYW5jaXNjbyBkZSBadXJiYXLDoW4uIE11ZXN0cmEgdW4gY29yZGVybyBibGFuY28gdGVuZGlkbyBkZSBsYWRvIHNvYnJlIHVuYSByZXBpc2EgZGUgcGllZHJhLCBjb24gbGFzIHBhdGFzIGF0YWRhcyB5IGxhIGNhYmV6YSBhcG95YWRhIGNvbiBtYW5zZWR1bWJyZSwgcmVjb3J0YWRvIGNvbnRyYSB1biBmb25kbyBuZWdybyBjYXNpIGFic29sdXRvLiBMYSBlc2NlbmEgZnVuY2lvbmEgY29tbyB1biBib2RlZ8OzbiByZWxpZ2lvc286IGVsIGNvcmRlcm8gc2ltYm9saXphIGEgQ3Jpc3RvIGNvbW8gdsOtY3RpbWEgZGVsIHNhY3JpZmljaW8sIGlkZWEgcXVlIHJlZnVlcnphbiBlbCB0ZW51ZSBoYWxvIHNvYnJlIHN1IGNhYmV6YSB5IGxhIGluc2NyaXBjacOzbiBsYXRpbmEgKlRhbXF1YW0gYWdudXMqICjigJxjb21vIHVuIGNvcmRlcm/igJ0pLCBxdWUgYWx1ZGUgYWwgcGFzYWplIGRlIElzYcOtYXMgc29icmUgZWwgY29yZGVybyBsbGV2YWRvIGFsIG1hdGFkZXJvLiANCg0KWnVyYmFyw6FuIGVtcGxlYSB1biB0ZW5lYnJpc21vIGludGVuc286IHVuYSBsdXogbGF0ZXJhbCBtb2RlbGEgY29uIGVub3JtZSBkZXRhbGxlIGxhIHRleHR1cmEgZGUgbGEgbGFuYSwgbWllbnRyYXMgZWwgcmVzdG8gZGVsIGN1YWRybyBzZSBodW5kZSBlbiBsYSBwZW51bWJyYS4gRXNhIHBhbGV0YSByZWR1Y2lkYSBkZSBibGFuY29zIGPDoWxpZG9zLCBvY3JlcyB5IG5lZ3JvcywgY29uIHVuIMO6bmljbyBjb250cmFzdGUgZG9taW5hbnRlIGVudHJlIGxhIGZpZ3VyYSB5IGVsIGZvbmRvLCBsYSBoYWNlIGVzcGVjaWFsbWVudGUgaW50ZXJlc2FudGUgcGFyYSBlbCBhbsOhbGlzaXMgcG9yIHJlZGVzLiBTdSBlc3RydWN0dXJhIGNyb23DoXRpY2EgZXMgbXVjaG8gbcOhcyBzaW1wbGUgcXVlIGxhIGRlIHVuYSBmb3RvZ3JhZsOtYSwgcGVybyBlbCBjbGFyb3NjdXJvIGRlbnRybyBkZWwgY29yZGVybyBnZW5lcmEgdmFyaWFjaW9uZXMgc3V0aWxlcyBkZSBjb2xvci4NCg0KIyMgUmVkIGRlIHJlZ2lvbmVzDQoNCmBgYHtyIGNvcmRlcm8tcmVkfQ0KcmVzX2MgPC0gaW1hZ2VfdG9fZ3JhcGgoY29yZGVybywgbl9zdXBlcnBpeGVscyA9IEssIGNvbXBhY3RuZXNzID0gbSwgaCA9IGgwKQ0KbmV0X3N1bW1hcnkocmVzX2MkZ3JhcGgpDQpgYGANCg0KYGBge3IgY29yZGVyby1yZWQtZmlnLCBmaWcud2lkdGg9MTEsIGZpZy5oZWlnaHQ9My42fQ0KcGFyKG1mcm93ID0gYygxLCAzKSwgbWFyID0gYygwLjMsIDAuMywgMS42LCAwLjMpKQ0KcGxvdF9wYW5lbChjb3JkZXJvX2hkLCBtYWluID0gIkFnbnVzIERlaSAoWnVyYmFyw6FuKSIpDQpwbG90X3BhbmVsKGNvcmRlcm8sIHJlc19jJGxhYmVscywgIlN1cGVycMOteGVsZXMgU0xJQyAoY29sb3IgbWVkaW8pIikNCnBsb3RfZ3JhcGhfb3ZlcmxheShyZXNfYywgIlJlZCBkZSByZWdpb25lcyAoZ3Jvc29yIH4gd19paiwgaCA9IDIwKSIpDQpgYGANCg0KU0xJQyBkZXZ1ZWx2ZSBgciB2Y291bnQocmVzX2MkZ3JhcGgpYCBzdXBlcnDDrXhlbGVzIHkgYHIgZWNvdW50KHJlc19jJGdyYXBoKWAgYXJpc3Rhcy4gQSBkaWZlcmVuY2lhIGRlbCBwYWlzYWplLCBlc3RhIHBpbnR1cmEgdGllbmUgdW5hIHBhbGV0YSBtdXkgcmVkdWNpZGEgKGJsYW5jb3MgY8OhbGlkb3MsIG9jcmVzIHkgbmVncm9zKSwgZGUgbW9kbyBxdWUgbXVjaGFzIGFyaXN0YXMgZGVsIGZvbmRvIHRpZW5lbiAkZF97aWp9JCBwZXF1ZcOxbyB5IHBlc28gY2VyY2FubyBhIDE6IGxhIHNpbWlsaXR1ZCBtZWRpYSBlcyBgciByb3VuZChtZWFuKEUocmVzX2MkZ3JhcGgpJHcpLCAzKWAsIGZyZW50ZSBhIGByIHJvdW5kKG1lYW4oRShyZXNfcCRncmFwaCkkdyksIDMpYCBlbiBlbCBwYWlzYWplLg0KDQojIyBTZW5zaWJpbGlkYWQgYSAkaCQNCg0KYGBge3IgY29yZGVyby1ofQ0KZXhfaF9jIDwtIGV4cGVyaW1lbnRfaChyZXNfYywgaHMpDQpiaW5kX3Jvd3MobGFwcGx5KGV4X2hfYywgYFtbYCwgInRhYiIpKQ0KYGBgDQoNCmBgYHtyIGNvcmRlcm8taC1maWcsIGZpZy53aWR0aD0xMywgZmlnLmhlaWdodD01LjR9DQpwbG90X2V4cF9ncmlkKHJlc19jLCBleF9oX2MsICJoIiwgImgiKQ0KYGBgDQoNCioqTGVjdHVyYS4qKiBQYXJhICRoXGxlIDIwJCBhbWJvcyBhbGdvcml0bW9zIHNlcGFyYW4gY29uIGNsYXJpZGFkIGVsIGNvcmRlcm8sIGxhIHJlcGlzYSBvY3JlIHkgZWwgZm9uZG8gb3NjdXJvLCB5IGRpdmlkZW4gZWwgY29yZGVybyBlbiB0cmVzIG8gY3VhdHJvIHpvbmFzIGRlIGx1eiB5IHNvbWJyYS4gUGFyYSAkaFxnZSA0MCQgZWwgY29udHJhc3RlIGZpZ3VyYS1mb25kbyBkZWphIGRlIGRvbWluYXI6IGxhIGNhYmV6YSB5IGxhIHBhcnRlIGRlbGFudGVyYSBkZWwgY29yZGVybyBzZSB1bmVuIGNvbiBsYSByZXBpc2EgeSBsYXMgY29tdW5pZGFkZXMgcGFzYW4gYSBzZXIgYmxvcXVlcyBlc3BhY2lhbGVzLCBhdW5xdWUgZWwgZm9uZG8gb3NjdXJvIHNlIG1hbnRpZW5lIGFwYXJ0ZS4gRWwgbsO6bWVybyBkZSBjb211bmlkYWRlcyBjYXNpIG5vIGNhbWJpYSAoOeKAkzExKSwgcGVybyBsYSBtb2R1bGFyaWRhZCBiYWphIGRlIGZvcm1hIG1vbsOzdG9uYSAoMC43OSDihpIgMC42OSkuIEVsIGFjdWVyZG8gTG91dmFpbi1MZWlkZW4gZXMgbcOheGltbyBjb24gJGg9MTAkIChBUkkgPSAwLjk2KSB5IGJhamEgZW4gYW1ib3MgZXh0cmVtb3M6IDAuNzUgY29uICRoPTUkLCBjdWFuZG8gY2FzaSB0b2RvcyBsb3MgcGVzb3Mgc29uIHBlcXVlw7FvcyB5IGxhIHBhcnRpY2nDs24gZGVwZW5kZSBkZSB1bmFzIHBvY2FzIGFyaXN0YXMgZnVlcnRlcywgeSAwLjg2IHkgMC43NyBjb24gJGg9NDAkIHkgJGg9ODAkLCBjdWFuZG8gbG9zIHBlc29zIHNlIGlndWFsYW4geSBhcGFyZWNlbiBtdWNoYXMgcGFydGljaW9uZXMgZXF1aXZhbGVudGVzLCBkZSBtb2RvIHF1ZSBsYSBtb2R1bGFyaWRhZCB5YSBzb2xvIGVuY3VlbnRyYSBlc3RydWN0dXJhIGdlb23DqXRyaWNhLg0KDQojIyBTZWdtZW50YWNpb25lcyBwYXJhIHZhcmlvcyB2YWxvcmVzIGRlICRcdGF1JA0KDQpgYGB7ciBjb3JkZXJvLXRhdX0NCmV4X3RfYyA8LSBleHBlcmltZW50X3RhdShyZXNfYywgdGF1cywgaCA9IGgwKQ0KYmluZF9yb3dzKGxhcHBseShleF90X2MsIGBbW2AsICJ0YWIiKSkNCmBgYA0KDQpgYGB7ciBjb3JkZXJvLXRhdS1maWcsIGZpZy53aWR0aD0xMSwgZmlnLmhlaWdodD01LjR9DQpwbG90X2V4cF9ncmlkKHJlc19jLCBleF90X2MsICJ0YXUiLCAidGF1IikNCmBgYA0KDQoqKkxlY3R1cmEuKiogRWwgcGF0csOzbiBlcyBvcHVlc3RvIGFsIGRlbCBwYWlzYWplLiBDb24gJFx0YXU9MC4zJCBsYSByZWQgc2lndWUgc2llbmRvIGNvbmV4YSAoMSBjb21wb25lbnRlKSB5IGxhIHNlZ21lbnRhY2nDs24gY2FtYmlhIHBvY28sIHBvcnF1ZSBsYXMgdHJhbnNpY2lvbmVzIGRlIGNvbG9yIGRlIGxhIHBpbnR1cmEgc29uIGdyYWR1YWxlcy4gQ29uICRcdGF1PTAuNSQgaGF5IDcgY29tcG9uZW50ZXMgeSBlbXBpZXphbiBhIHNlcGFyYXJzZSBsYXMgcGF0YXMgeSB6b25hcyBkZSBzb21icmEgZGVsIGNvcmRlcm8uIENvbiAkXHRhdT0wLjckIGhheSAzMSBjb21wb25lbnRlcyB5IDM0IGNvbXVuaWRhZGVzLCBwZXJvIGxhIGZyYWdtZW50YWNpw7NuIG9jdXJyZSAqKmRlbnRybyBkZWwgY29yZGVybyoqOiBlbCBtb2RlbGFkbyBlbiBjbGFyb3NjdXJvIGRlIGxhIGxhbmEgZ2VuZXJhIGRpZmVyZW5jaWFzIGRlIGNvbG9yIG1heW9yZXMgcXVlIDcgdW5pZGFkZXMgJFxEZWx0YSBFJC4gRWwgZm9uZG8sIGVuIGNhbWJpbywgc2UgbWFudGllbmUgZW4gcG9jb3MgYmxvcXVlcyBncmFuZGVzIHBvcnF1ZSBlcyBvc2N1cm8geSBob21vZ8OpbmVvLiBMYSBtb2R1bGFyaWRhZCBzdWJlIGRlIDAuNzYgYSAwLjgx4oCTMC44MiBjb24gJFx0YXU9MC41JCB5IHNlIG1hbnRpZW5lIGNvbiAkXHRhdT0wLjckOyBjb21vIGVuIGVsIHBhaXNhamUsIExvdXZhaW4geSBMZWlkZW4gY29pbmNpZGVuIGNhc2kgcG9yIGNvbXBsZXRvIGNvbiB1bWJyYWxlcyBhbHRvcy4NCg0KIyMgUm9idXN0ZXogZnJlbnRlIGEgcnVpZG8gZ2F1c3NpYW5vDQoNCmBgYHtyIGNvcmRlcm8tcnVpZG8tZWosIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD03LjZ9DQpuZV9jIDwtIG5vaXNlX2V4YW1wbGVzKHJlc19jLCBzaWdtYXMsIGggPSBoMCkNCnBsb3Rfbm9pc2VfZ3JpZChuZV9jKQ0KYGBgDQoNCmBgYHtyIGNvcmRlcm8tcnVpZG99DQpuel9jIDwtIGV4cGVyaW1lbnRfbm9pc2UocmVzX2MsIHNpZ21hcywgcmVwcyA9IHJlcHMsIGggPSBoMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBuX3N1cGVycGl4ZWxzID0gSywgY29tcGFjdG5lc3MgPSBtKQ0Kc3VtbWFyeV9ub2lzZShuel9jKQ0KYGBgDQoNCmBgYHtyIGNvcmRlcm8tcnVpZG8tZmlnLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQ0KcGxvdF9ub2lzZV9hcmkobnpfYywgIkFnbnVzIERlaTogZXN0YWJpbGlkYWQgZGUgbGEgc2VnbWVudGFjacOzbiBmcmVudGUgYWwgcnVpZG8iKQ0KYGBgDQoNCioqTGVjdHVyYS4qKg0KDQoqICoqU0xJQyBmaWpvLioqIEFtYm9zIG3DqXRvZG9zIHNvbiBlc3RhYmxlcywgTGVpZGVuIHVuIHBvY28gbcOhczogQVJJIOKJiCAwLjky4oCTMC45MyBjb24gJFxzaWdtYVxsZSAyNSQgeSAwLjg0IGNvbiAkXHNpZ21hPTUwJCwgZnJlbnRlIGEgMC44OOKAkzAuODkgeSAwLjgxIGNvbiBMb3V2YWluLiBDb21vIGVuIGVsIHBhaXNhamUsIGxhIGRpZmVyZW5jaWEgc2UgZGViZSBzb2JyZSB0b2RvIGEgcXVlIExvdXZhaW4gY2FtYmlhIGRlIHNvbHVjacOzbiBlbnRyZSBwYXJ0aWNpb25lcyBjYXNpIGVxdWl2YWxlbnRlcy4NCiogKipTTElDIHZhcmlhYmxlLioqIEVsIEFSSSBjYWUgYSDiiYggMC42OeKAkzAuNzYsIDAuNDTigJMwLjQ1IHkgMC4yNiBwYXJhICRcc2lnbWE9MTAsIDI1LCA1MCQ6IGFsZ28gbWVqb3IgcXVlIGVuIGVsIHBhaXNhamUgY29uIHJ1aWRvIGxldmUgeSBwcsOhY3RpY2FtZW50ZSBpZ3VhbCBjb24gcnVpZG8gZnVlcnRlLiBFbiBsYXMgZmlndXJhcywgY29uICRcc2lnbWE9MTAkIGVsIGNvcmRlcm8gYcO6biBzZSBzZXBhcmEgZGVsIGZvbmRvLCBwZXJvIGxhIHJlcGlzYSBzZSB1bmUgY29uIGVsIGNvcmRlcm87IGNvbiAkXHNpZ21hPTI1JCBzdSBzaWx1ZXRhIHNlIHBpZXJkZTsgY29uICRcc2lnbWE9NTAkIGxhcyBjb211bmlkYWRlcyBzb24gZnJhbmphcyB2ZXJ0aWNhbGVzIHNpbiByZWxhY2nDs24gY29uIGxhIGVzY2VuYS4NCiogRWwgcmVjb3J0ZSBhICRbMCwyNTVdJCBwZXNhIG3DoXMgYXF1w606IGVsIGZvbmRvIGVzIGNhc2kgbmVncm8sIGFzw60gcXVlIGVsIHJ1aWRvIHJlY29ydGFkbyBsbyBhY2xhcmEgZGUgZm9ybWEgc2lzdGVtw6F0aWNhIHkgcmVkdWNlIGp1c3RvIGVsIGNvbnRyYXN0ZSBmaWd1cmEtZm9uZG8gcXVlIHNvc3RlbsOtYSBsYSBzZWdtZW50YWNpw7NuLg0KDQojIENvbXBhcmFjacOzbiBlbnRyZSBpbcOhZ2VuZXMNCioqTGVjdHVyYS4qKiBMYXMgZG9zIHJlZGVzIHRpZW5lbiB0YW1hw7FvIHkgZXN0cnVjdHVyYSBjYXNpIGlndWFsZXMgKDE1MiB5IDE2MSB2w6lydGljZXMsIGdyYWRvIG1lZGlvIOKJiCA1LjQsIHRyYW5zaXRpdmlkYWQg4omIIDAuNDApOiBsYSB0b3BvbG9nw61hIGxhIGltcG9uZSBTTElDLCBubyBsYSBpbWFnZW4uIExvIHF1ZSBsYXMgZGlzdGluZ3VlIHNvbiBsb3MgcGVzb3MuIEVuIGVsICpBZ251cyBEZWkqIGxhIG1lZGlhbmEgZGUgJGRfe2lqfSQgZXMgNi40MiwgZnJlbnRlIGEgMTYuMzIgZW4gZWwgcGFpc2FqZSwgeSBlbCA2Ni40ICUgZGUgbGFzIGFyaXN0YXMgdGllbmUgJHdfe2lqfVxnZSAwLjUkLCBmcmVudGUgYWwgNDYuMiAlOiBsYSBwYWxldGEgcmVkdWNpZGEgZGUgbGEgcGludHVyYSBwcm9kdWNlIHVuYSByZWQgbXVjaG8gbcOhcyBob21vZ8OpbmVhLiBQZXNlIGEgZXN0bywgbGEgcm9idXN0ZXogZnJlbnRlIGEgcnVpZG8gZnVlcnRlICgkXHNpZ21hPTUwJCkgZXMgbXV5IHBhcmVjaWRhIGVuIGFtYmFzOiBjb24gU0xJQyBmaWpvIGVsIEFSSSBlc3TDoSBlbnRyZSAwLjgxIHkgMC45MSwgeSBjb24gU0xJQyB2YXJpYWJsZSBjYWUgYSAwLjI24oCTMC4yOSwgbG8gcXVlIGNvbmZpcm1hIHF1ZSBlbCBmYWN0b3IgZGV0ZXJtaW5hbnRlIGVzIGxhIHRlc2VsYWNpw7NuIHkgbm8gZWwgY29udGVuaWRvIGRlIGxhIGltYWdlbi4NCmBgYHtyIGNvbXBhcmFjaW9ufQ0KY29tcCA8LSByYmluZChjYmluZChpbWFnZW4gPSAiUGFpc2FqZSIsIG5ldF9zdW1tYXJ5KHJlc19wJGdyYXBoKSksDQogICAgICAgICAgICAgIGNiaW5kKGltYWdlbiA9ICJBZ251cyBEZWkiLCBuZXRfc3VtbWFyeShyZXNfYyRncmFwaCkpKQ0KY29tcA0KDQpkaXN0X2JvdGggPC0gcmJpbmQoZGF0YS5mcmFtZShpbWFnZW4gPSAiUGFpc2FqZSIsIGQgPSBFKHJlc19wJGdyYXBoKSRkKSwNCiAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKGltYWdlbiA9ICJBZ251cyBEZWkiLCBkID0gRShyZXNfYyRncmFwaCkkZCkpDQpkaXN0X2JvdGggfD4NCiAgZ3JvdXBfYnkoaW1hZ2VuKSB8Pg0KICBzdW1tYXJpc2UoZF9tZWRpYW5hID0gcm91bmQobWVkaWFuKGQpLCAyKSwgZF9tZWRpYSA9IHJvdW5kKG1lYW4oZCksIDIpLA0KICAgICAgICAgICAgcHJvcF93X21heW9yXzAuNSA9IHJvdW5kKG1lYW4oZXhwKC1kIC8gaDApID49IDAuNSksIDMpKQ0KYGBgDQoNCmBgYHtyIGNvbXBhcmFjaW9uLWZpZywgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9My42fQ0KZ2dwbG90KGRpc3RfYm90aCwgYWVzKGV4cCgtZCAvIGgwKSwgZmlsbCA9IGltYWdlbikpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDMwLCBhbHBoYSA9IDAuNiwgcG9zaXRpb24gPSAiaWRlbnRpdHkiKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIkFnbnVzIERlaSIgPSAiIzdENjYwOCIsICJQYWlzYWplIiA9ICIjMjQ3MUEzIikpICsNCiAgbGFicyh4ID0gZXhwcmVzc2lvbih3W2lqXSB+ICIoaCA9IDIwKSIpLCB5ID0gIk7Dum1lcm8gZGUgYXJpc3RhcyIsIGZpbGwgPSBOVUxMLA0KICAgICAgIHRpdGxlID0gIkRpc3RyaWJ1Y2nDs24gZGUgc2ltaWxpdHVkZXMgdmlzdWFsZXMiKSArDQogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTIpDQpgYGANCg0KYGBge3IgY29tcGFyYWNpb24tcnVpZG99DQpiaW5kX3Jvd3MobXV0YXRlKG56X3AsIGltYWdlbiA9ICJQYWlzYWplIiksIG11dGF0ZShuel9jLCBpbWFnZW4gPSAiQWdudXMgRGVpIikpIHw+DQogIGZpbHRlcihzaWdtYSA9PSBtYXgoc2lnbWFzKSkgfD4NCiAgZ3JvdXBfYnkoaW1hZ2VuLCBzbGljLCBtZXRvZG8pIHw+DQogIHN1bW1hcmlzZShBUklfbWVkaWEgPSByb3VuZChtZWFuKEFSSSksIDMpLCAuZ3JvdXBzID0gImRyb3AiKQ0KYGBgDQoNCiMgQ29uY2x1c2lvbmVzDQoNCjEuICoqRWwgcGFyw6FtZXRybyAkaCQgY29udHJvbGEgZWwgYmFsYW5jZSBlbnRyZSBjb2xvciB5IGdlb21ldHLDrWEuKiogVmFsb3JlcyBwZXF1ZcOxb3MgaGFjZW4gcXVlIGxhcyBjb211bmlkYWRlcyBzaWdhbiBsb3MgYm9yZGVzIGRlIGNvbG9yOyB2YWxvcmVzIGdyYW5kZXMgYXBsYW5hbiBsb3MgcGVzb3MgeSBsYSBwYXJ0aWNpw7NuIHNlIGFwcm94aW1hIGEgbGEgZGUgbGEgcmVkIG5vIHBvbmRlcmFkYSwgcXVlIHNvbG8gcmVmbGVqYSBsYSBlc3RydWN0dXJhIGVzcGFjaWFsIGRlIGxhIHRlc2VsYWNpw7NuLg0KMi4gKipFbCB1bWJyYWwgJFx0YXUkIGltcG9uZSBmcm9udGVyYXMgZHVyYXMuKiogRmlsdHJhciBhbnRlcyBkZSBkZXRlY3RhciBjb211bmlkYWRlcyBnYXJhbnRpemEgcXVlIGxhcyByZWdpb25lcyBkZSBjb2xvciBtdXkgZGlzdGludG8gbnVuY2EgcXVlZGVuIGp1bnRhcywgcGVybyBwcm9kdWNlIG11Y2hhcyBjb211bmlkYWRlcyBkaW1pbnV0YXMgKHN1cGVycMOteGVsZXMgYWlzbGFkb3MpIGN1YW5kbyAkXHRhdSQgZXMgYWx0by4gRW4gbGEgcHLDoWN0aWNhIGNvbnZpZW5lIHVzYXIgJFx0YXUkIG1vZGVyYWRvcyBvIGZ1c2lvbmFyIGRlc3B1w6lzIGxhcyBjb211bmlkYWRlcyBwZXF1ZcOxYXMuDQozLiAqKkxvdXZhaW4geSBMZWlkZW4gZGFuIHBhcnRpY2lvbmVzIHBhcmVjaWRhcyoqLCBjb24gTGVpZGVuIHNpZW1wcmUgaWd1YWwgbyBsaWdlcmFtZW50ZSBzdXBlcmlvciBlbiBtb2R1bGFyaWRhZCB5IGNvbiBsYSBnYXJhbnTDrWEgZGUgY29tdW5pZGFkZXMgY29uZXhhcy4gU3VzIGRpZmVyZW5jaWFzIHNlIGNvbmNlbnRyYW4gZW4gem9uYXMgaG9tb2fDqW5lYXMsIGRvbmRlIGhheSBtdWNoYXMgcGFydGljaW9uZXMgY2FzaSBlcXVpdmFsZW50ZXMgKHBvciBlamVtcGxvLCBlbCBmb25kbyBkZWwgKkFnbnVzIERlaSogY29uICRoPTgwJCBlbCBBUkkgZW50cmUgYW1ib3MgZXMgMC41NSBlbiBlbCBwYWlzYWplIHkgMC43NyBlbiBlbCBBZ251cyBEZWkpLCB5IGRlc2FwYXJlY2VuIGN1YW5kbyBlbCBmaWx0cmFkbyBwb3IgJFx0YXUkIHlhIGZpamEgbGEgZXN0cnVjdHVyYS4NCjQuICoqUm9idXN0ZXogZnJlbnRlIGFsIHJ1aWRvLioqIEVsIGFncnVwYW1pZW50byBjb24gTG91dmFpbiB5IExlaWRlbiAqKnNlIG1hbnRpZW5lIHJhem9uYWJsZW1lbnRlIHNpIGVsIHJ1aWRvIHNvbG8gYWZlY3RhIGEgbG9zIHBlc29zKiogKFNMSUMgZmlqbyk6IGVsIHByb21lZGlvIGRlbnRybyBkZSBjYWRhIHN1cGVycMOteGVsIGFtb3J0aWd1YSBlbCBydWlkbywgeSBsYXMgZGlmZXJlbmNpYXMgcXVlIHF1ZWRhbiByZWZsZWphbiBzb2JyZSB0b2RvIGxhIGluZXN0YWJpbGlkYWQgZGUgbGEgb3B0aW1pemFjacOzbiwgbWVub3IgZW4gTGVpZGVuLiAqKk5vIHNlIG1hbnRpZW5lIGN1YW5kbyBlbCBydWlkbyB0YW1iacOpbiBhbHRlcmEgbGEgdGVzZWxhY2nDs24qKiAoU0xJQyB2YXJpYWJsZSk6IGVsIEFSSSBjYWUgYSDiiYggMC4yNuKAkzAuMjkgY29uICRcc2lnbWE9NTAkIGVuIGFtYmFzIGltw6FnZW5lcyB5IGNvbiBhbWJvcyBtw6l0b2Rvcy4gRWwgcHVudG8gZMOpYmlsIGRlbCBmbHVqbyBlcyBTTElDLCBubyBlbCBhbGdvcml0bW8gZGUgY29tdW5pZGFkZXMuIFVuIHN1YXZpemFkbyBwcmV2aW8gKHBvciBlamVtcGxvLCB1biBmaWx0cm8gZ2F1c3NpYW5vIG8gZGUgbWVkaWFuYSBhbnRlcyBkZSBTTElDKSBzZXLDrWEgbGEgZXh0ZW5zacOzbiBuYXR1cmFsIHBhcmEgbWVqb3JhciBsYSByb2J1c3Rlei4NCjUuICoqTW9kdWxhcmlkYWQgbm8gZXMgY2FsaWRhZCB2aXN1YWwuKiogRW4gZWwgZm9uZG8gdW5pZm9ybWUgZGVsICpBZ251cyBEZWkqIHNlIG9idGllbmVuIG1vZHVsYXJpZGFkZXMgYWx0YXMgY29uIGNvbXVuaWRhZGVzIHF1ZSBubyBjb3JyZXNwb25kZW4gYSBuaW5ndW5hIGVzdHJ1Y3R1cmEgZGUgbGEgaW1hZ2VuLg0KDQojIFJlZmVyZW5jaWFzDQoNCiogQWNoYW50YSwgUi4sIFNoYWppLCBBLiwgU21pdGgsIEsuLCBMdWNjaGksIEEuLCBGdWEsIFAuIHkgU8O8c3N0cnVuaywgUy4gKDIwMTIpLiBTTElDIHN1cGVycGl4ZWxzIGNvbXBhcmVkIHRvIHN0YXRlLW9mLXRoZS1hcnQgc3VwZXJwaXhlbCBtZXRob2RzLiAqSUVFRSBUUEFNSSosIDM0KDExKSwgMjI3NOKAkzIyODIuDQoqIEJsb25kZWwsIFYuIEQuLCBHdWlsbGF1bWUsIEouLUwuLCBMYW1iaW90dGUsIFIuIHkgTGVmZWJ2cmUsIEUuICgyMDA4KS4gRmFzdCB1bmZvbGRpbmcgb2YgY29tbXVuaXRpZXMgaW4gbGFyZ2UgbmV0d29ya3MuICpKb3VybmFsIG9mIFN0YXRpc3RpY2FsIE1lY2hhbmljcyosIFAxMDAwOC4NCiogVHJhYWcsIFYuIEEuLCBXYWx0bWFuLCBMLiB5IHZhbiBFY2ssIE4uIEouICgyMDE5KS4gRnJvbSBMb3V2YWluIHRvIExlaWRlbjogZ3VhcmFudGVlaW5nIHdlbGwtY29ubmVjdGVkIGNvbW11bml0aWVzLiAqU2NpZW50aWZpYyBSZXBvcnRzKiwgOSwgNTIzMy4NCiogSHViZXJ0LCBMLiB5IEFyYWJpZSwgUC4gKDE5ODUpLiBDb21wYXJpbmcgcGFydGl0aW9ucy4gKkpvdXJuYWwgb2YgQ2xhc3NpZmljYXRpb24qLCAyLCAxOTPigJMyMTguDQoqIFNvc2EsIEouICpBbsOhbGlzaXMgZGUgaW3DoWdlbmVzIG1lZGlhbnRlIHJlZGVzKi4gTm90YXMgZGUgY2xhc2UsIFJQdWJzLg0KKiBJbcOhZ2VuZXM6IGZvdG9ncmFmw61hIMKpIFJvZHJpZ28gRMOhdmlsYSAodXNvIGFjYWTDqW1pY28pOyAqQWdudXMgRGVpKiwgRnJhbmNpc2NvIGRlIFp1cmJhcsOhbiwgTXVzZW8gTmFjaW9uYWwgZGVsIFByYWRvIChkb21pbmlvIHDDumJsaWNvKS4NCiogQW50aHJvcGljLiAoMjAyNikuICpDbGF1ZGUqICh2ZXJzacOzbiBPcHVzIDUuNSkgW01vZGVsbyBkZSBsZW5ndWFqZSBkZSBncmFuIHRhbWHDsW9dLiBodHRwczovL2NsYXVkZS5haQ==