Supervised Classification
setwd("C:/Users/USS/Desktop/Pedro/2019 - 2020/Percepcion remota/P5") # Cambiar el directorio de trabajo
getwd()
[1] "C:/Users/USS/Desktop/Pedro/2019 - 2020/Percepcion remota/P5"
library(raster)
Reference data
nlcd <- brick("C:/Users/USS/Desktop/Pedro/2019 - 2020/Percepcion remota/P5/rsdata/rs/nlcd-L1.tif")
names(nlcd) <- c("nlcd2001","nlcd2011")
nlcd
class : RasterBrick
dimensions : 1230, 1877, 2308710, 2 (nrow, ncol, ncell, nlayers)
resolution : 0.0002694946, 0.0002694946 (x, y)
extent : -121.9258, -121.42, 37.85402, 38.1855 (xmin, xmax, ymin, ymax)
crs : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
source : C:/Users/USS/Desktop/Pedro/2019 - 2020/Percepcion remota/P5/rsdata/rs/nlcd-L1.tif
names : nlcd2001, nlcd2011
min values : 1, 1
max values : 9, 9
head(nlcd)
nlcd2001 nlcd2011
[1,] 9 9
[2,] 9 9
[3,] 9 9
[4,] 9 9
[5,] 9 9
[6,] 9 9
[7,] 9 9
[8,] 9 9
[9,] 9 8
[10,] 9 8
[11,] 9 9
[12,] 9 9
[13,] 9 9
[14,] 9 9
[15,] 9 9
[16,] 9 9
[17,] 9 9
[18,] 9 8
[19,] 9 8
[20,] 9 8
# Los nombres de clase y colores para plotear
nlcdclass <- c("Water", "Developed", "Barren", "Forest", "Shrubland", "Herbaceous", "Planted/Cultivated", "Wetlands")
classdf <- data.frame(classvalue1 = c(1,2,3,4,5,7,8,9), classnames1 = nlcdclass)
classdf
# Códigos hexadecimales de colores
classcolor <- c("#5475A8", "#B50000", "#D2CDC0", "#38814E", "#AF963C", "#D1D182", "#FBF65D", "#C8E6F8")
classcolor
[1] "#5475A8" "#B50000" "#D2CDC0" "#38814E" "#AF963C" "#D1D182" "#FBF65D" "#C8E6F8"
# Ahora ratificamos (RAT = "Tabla de atributos de ráster") el ncld2011 (definimos RasterLayer como una variable categórica). Esto es útil para trazar.
nlcd2011 <- nlcd[[2]]
nlcd2011
class : RasterLayer
band : 2 (of 2 bands)
dimensions : 1230, 1877, 2308710 (nrow, ncol, ncell)
resolution : 0.0002694946, 0.0002694946 (x, y)
extent : -121.9258, -121.42, 37.85402, 38.1855 (xmin, xmax, ymin, ymax)
crs : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
source : C:/Users/USS/Desktop/Pedro/2019 - 2020/Percepcion remota/P5/rsdata/rs/nlcd-L1.tif
names : nlcd2011
values : 1, 9 (min, max)
head(nlcd2011)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
1 9 9 9 9 9 9 9 9 8 8 8 8 9 8 8 8 9 7 7 7
2 9 9 9 9 9 9 9 8 8 8 8 8 8 8 8 9 7 7 7 7
3 9 9 9 9 9 9 9 8 8 8 8 8 8 8 9 9 9 9 9 9
4 9 9 9 9 9 9 9 9 9 8 8 8 8 8 9 9 9 9 9 9
5 7 9 9 9 9 9 9 9 9 9 9 9 8 8 9 9 9 9 9 9
6 9 7 7 7 9 9 9 9 4 9 9 9 9 9 9 9 9 9 9 9
7 7 7 7 7 7 9 9 4 4 4 4 9 9 9 9 9 9 9 9 9
8 9 9 7 7 7 9 9 4 4 4 9 9 9 9 9 9 9 9 9 9
9 1 9 7 7 7 9 9 9 4 9 9 9 9 9 9 9 9 9 9 9
10 1 1 1 7 7 9 9 9 9 9 9 4 9 9 9 9 9 9 9 9
# Con ratify() se puede ver la "Tabla de atributos de ráster" (RAT) como un marco de datos en un nuevo espacio denominado attributes
nlcd2011 <- ratify(nlcd2011)
# Podemos apreciar que ahora el objeto nlcd2011 tiene el apartado "attributes"
nlcd2011
class : RasterLayer
band : 2 (of 2 bands)
dimensions : 1230, 1877, 2308710 (nrow, ncol, ncell)
resolution : 0.0002694946, 0.0002694946 (x, y)
extent : -121.9258, -121.42, 37.85402, 38.1855 (xmin, xmax, ymin, ymax)
crs : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
source : C:/Users/USS/Desktop/Pedro/2019 - 2020/Percepcion remota/P5/rsdata/rs/nlcd-L1.tif
names : nlcd2011
values : 1, 9 (min, max)
attributes :
NA
# Lo que buscamos es asociar los niveles de "nlcd2011" con las categorias (landcover), para lograrlo creamos un objeto "rat". Introducimos en el objeto rat los niveles (valores que pueden tener los pixeles) de nlcd2011
rat <- levels(nlcd2011)[[1]]
rat
# Adjudicamos a cada ID (nivel de nlcd2011) una categoria (landcover) con el objeto nlcdclass
rat$landcover <- nlcdclass
rat
levels(nlcd2011) <- rat
levels(nlcd2011)
[[1]]
NA
Generate sample sites
# Cargue las ubicaciones de los sitios de entrenamiento
# Configure el generador de números aleatorios para reproducir los resultados
set.seed(99)
# Muestreo
samp2011 <- sampleStratified(nlcd2011, size = 200, na.rm = TRUE, sp = TRUE)
samp2011
class : SpatialPointsDataFrame
features : 1600
extent : -121.9257, -121.4225, 37.85415, 38.18536 (xmin, xmax, ymin, ymax)
crs : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
variables : 2
names : cell, nlcd2011
min values : 413, 1
max values : 2307837, 9
table(samp2011$nlcd2011)
1 2 3 4 5 7 8 9
200 200 200 200 200 200 200 200
# Tracemos los sitios de capacitación sobre nlcd2011 RasterLayer para visualizar la distribución de las ubicaciones de muestreo.
library(rasterVis)
plt <- levelplot(nlcd2011, col.regions = classcolor, main = 'Distribution of Training Sites')
print(plt + layer(sp.points(samp2011, pch = 3, cex = 0.5, col = 1)))

Train the classifier
library(rpart)
# Formamos el modelo. La formula que usaremos es tipo ~ ., la cual expresa que intentaremos clasificar classvalue usando a todas las demás variables como predictoras.
cart <- rpart(as.factor(classvalue)~., data=sampdata, method = 'class', minsplit = 5)
cart
n= 1600
node), split, n, loss, yval, (yprob)
* denotes terminal node
1) root 1600 1400 1 (0.12 0.12 0.12 0.12 0.12 0.12 0.12 0.12)
2) SWIR1< 0.07371388 207 28 1 (0.86 0.0097 0.034 0 0 0 0 0.092) *
3) SWIR1>=0.07371388 1393 1193 4 (0.015 0.14 0.14 0.14 0.14 0.14 0.14 0.13)
6) blue>=0.1180971 595 425 2 (0.0067 0.29 0.28 0.012 0.035 0.27 0.067 0.039)
12) SWIR1< 0.1966332 146 57 2 (0.021 0.61 0.25 0.014 0.021 0.0068 0.0068 0.075) *
13) SWIR1>=0.1966332 449 287 7 (0.0022 0.18 0.29 0.011 0.04 0.36 0.087 0.027)
26) blue>=0.1506717 54 22 3 (0 0.3 0.59 0 0 0.093 0.019 0) *
27) blue< 0.1506717 395 238 7 (0.0025 0.16 0.25 0.013 0.046 0.4 0.096 0.03)
54) SWIR1< 0.2871708 302 213 7 (0.0033 0.2 0.28 0.017 0.056 0.29 0.11 0.036)
108) green>=0.1254639 182 110 3 (0.0055 0.26 0.4 0.011 0.016 0.19 0.088 0.027) *
109) green< 0.1254639 120 66 7 (0 0.092 0.12 0.025 0.12 0.45 0.15 0.05) *
55) SWIR1>=0.2871708 93 25 7 (0 0.065 0.14 0 0.011 0.73 0.043 0.011) *
7) blue< 0.1180971 798 605 4 (0.021 0.035 0.033 0.24 0.22 0.046 0.2 0.2)
14) NIR< 0.2540509 666 475 4 (0.012 0.03 0.033 0.29 0.26 0.047 0.15 0.18)
28) NIR>=0.2062028 302 180 4 (0 0.033 0.017 0.4 0.13 0.073 0.17 0.17) *
29) NIR< 0.2062028 364 232 5 (0.022 0.027 0.047 0.19 0.36 0.025 0.14 0.19)
58) blue< 0.09249049 51 17 5 (0 0 0 0.31 0.67 0 0 0.02) *
59) blue>=0.09249049 313 215 5 (0.026 0.032 0.054 0.17 0.31 0.029 0.16 0.21)
118) SWIR2>=0.08096182 232 141 5 (0.013 0.026 0.06 0.17 0.39 0.039 0.19 0.11) *
119) SWIR2< 0.08096182 81 40 9 (0.062 0.049 0.037 0.17 0.086 0 0.086 0.51) *
15) NIR>=0.2540509 132 74 8 (0.068 0.061 0.03 0.015 0.053 0.045 0.44 0.29)
30) SWIR1>=0.1437143 112 55 8 (0.062 0.062 0.036 0.018 0.062 0.054 0.51 0.2) *
31) SWIR1< 0.1437143 20 4 9 (0.1 0.05 0 0 0 0 0.05 0.8) *
# Ploteamos (model.class)
# Trazar el árbol de clasificación formado.
par(xpd = FALSE, mar = c(0,1,1,0))
plot(cart, uniform = TRUE, main="Classification Tree", )
text(cart, cex = 0.8)

Classify
# Ahora que tenemos nuestro modelo de clasificación entrenado, podemos usarlo para hacer predicciones, es decir, para clasificar todas las celdas en el raster stack Landsat 5.
# Ahora se predice los datos del subconjunto en función del modelo; la predicción para toda el área lleva más tiempo
pr2011 <- predict(landsat5, cart, type='class')
pr2011
class : RasterLayer
dimensions : 1230, 1877, 2308710 (nrow, ncol, ncell)
resolution : 0.0002694946, 0.0002694946 (x, y)
extent : -121.9258, -121.42, 37.85402, 38.1855 (xmin, xmax, ymin, ymax)
crs : +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0
source : memory
names : layer
values : 1, 9 (min, max)
attributes :
NA
# Ahora trace el resultado de la clasificación usando rasterVis. Observar que establecerá los nombres de clase para los valores de clase
pr2011 <- ratify(pr2011)
rat <- levels(pr2011)[[1]]
rat$legend <- classdf$classnames
levels(pr2011) <- rat
levelplot(pr2011, maxpixels = 1e6,
col.regions = classcolor,
scales=list(draw=FALSE),
main = "Decision Tree classification of Landsat 5")

Model evaluation
# Ahora analicemos la precisión del modelo para tener una idea de la precisión del mapa clasificado.
# Para evaluar cualquier modelo, puede usar la validación cruzada k-fold. En esta técnica, los datos utilizados para ajustar el modelo se dividen en k grupos (generalmente 5 grupos). A su vez, uno de los grupos se usará para la prueba del modelo, mientras que el resto de los datos se usará para el entrenamiento del modelo (ajuste).
library(dismo)
set.seed(99)
j <- kfold(sampdata, k = 5, by=sampdata$classvalue)
table(j)
j
1 2 3 4 5
320 320 320 320 320
# Ahora entrenamos y probamos el modelo cinco veces, cada vez calculando una matriz de confusión que almacenamos en una lista.
x <- list()
for (k in 1:5) {
train <- sampdata[j!= k, ]
test <- sampdata[j == k, ]
cart <- rpart(as.factor(classvalue)~., data=train, method = 'class', minsplit = 5)
pclass <- predict(cart, test, type='class')
# Creamos un data.frame usando la referencia y la predicción
# Cuando se desea unir una tabla a la derecha de otra se utiliza la función "cbind"
x[[k]] <- cbind(test$classvalue, as.integer(pclass))
}
# Ahora combinamos los cinco elementos de la lista en un solo data.frame, usando la función "do.call" y calcule una matriz de confusión.
# Cuando se desea unir una tabla a debajo de otra, se utiliza la función "rbind"
y <- do.call(rbind, x)
y <- data.frame(y)
colnames(y) <- c('observed', 'predicted')
conmat <- table(y)
# change the name of the classes
colnames(conmat) <- classdf$classnames
rownames(conmat) <- classdf$classnames
conmat
predicted
observed Water Developed Barren Forest Shrubland Herbaceous Planted/Cultivated Wetlands
Water 175 6 0 3 0 0 7 9
Developed 2 90 51 8 10 22 11 6
Barren 7 39 82 4 19 38 5 6
Forest 0 2 1 106 57 1 6 27
Shrubland 0 3 5 59 102 12 12 7
Herbaceous 0 9 36 10 27 109 8 1
Planted/Cultivated 0 7 11 34 42 19 69 18
Wetlands 18 10 6 36 29 5 33 63
Calcule la precisión general y la estadística “Kappa”.
Precisión general:
# number of cases
n <- sum(conmat)
n
[1] 1600
# Cantidad de casos correctamente clasificados por clase. La función "diag" nos da la diagonal de la tabla conmat, es decir, los casos en los que las clases de observed coinciden con las de predicted
diag <- diag(conmat)
diag
Water Developed Barren Forest Shrubland
175 90 82 106 102
Herbaceous Planted/Cultivated Wetlands
109 69 63
# Precisión general
OA <- sum(diag) / n
OA
[1] 0.4975
Kappa:
# Casos observados (verdaderos) por clase. La función "apply" aplica una función a todos los elementos de una matriz. Con MARGIN = 1 sumamos los elementos de cada fila.
rowsums <- apply(conmat, 1, sum)
p <- rowsums / n
p
Water Developed Barren Forest Shrubland
0.125 0.125 0.125 0.125 0.125
Herbaceous Planted/Cultivated Wetlands
0.125 0.125 0.125
# Casos predichos por clase. Con MARGIN = 2 sumamos los elementos de cada columna.
colsums <- apply(conmat, 2, sum)
q <- colsums / n
expAccuracy <- sum(p*q)
kappa <- (OA - expAccuracy) / (1 - expAccuracy)
kappa
[1] 0.4257143
Producer and user accuracy
# Producer accuracy
PA <- diag / colsums
PA
Water Developed Barren Forest Shrubland
0.8663366 0.5421687 0.4270833 0.4076923 0.3566434
Herbaceous Planted/Cultivated Wetlands
0.5291262 0.4569536 0.4598540
# User accuracy
UA <- diag / rowsums
outAcc <- data.frame(producerAccuracy = PA, userAccuracy = UA)
outAcc
LS0tDQp0aXRsZTogIlByYWN0aWNhIDUgUGVyY2VwY2lvbiByZW1vdGEiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIFN1cGVydmlzZWQgQ2xhc3NpZmljYXRpb24NCg0KYGBge3J9DQpzZXR3ZCgiQzovVXNlcnMvVVNTL0Rlc2t0b3AvUGVkcm8vMjAxOSAtIDIwMjAvUGVyY2VwY2lvbiByZW1vdGEvUDUiKSAjIENhbWJpYXIgZWwgZGlyZWN0b3JpbyBkZSB0cmFiYWpvDQpgYGANCg0KYGBge3J9DQpnZXR3ZCgpDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KHJhc3RlcikNCmBgYA0KDQojIyBSZWZlcmVuY2UgZGF0YQ0KDQpgYGB7cn0NCm5sY2QgPC0gYnJpY2soIkM6L1VzZXJzL1VTUy9EZXNrdG9wL1BlZHJvLzIwMTkgLSAyMDIwL1BlcmNlcGNpb24gcmVtb3RhL1A1L3JzZGF0YS9ycy9ubGNkLUwxLnRpZiIpDQpgYGANCg0KYGBge3J9DQpuYW1lcyhubGNkKSA8LSBjKCJubGNkMjAwMSIsIm5sY2QyMDExIikNCmBgYA0KDQpgYGB7cn0NCm5sY2QNCmBgYA0KDQpgYGB7cn0NCmhlYWQobmxjZCkNCmBgYA0KDQpgYGB7cn0NCiMgTG9zIG5vbWJyZXMgZGUgY2xhc2UgeSBjb2xvcmVzIHBhcmEgcGxvdGVhcg0KDQpubGNkY2xhc3MgPC0gYygiV2F0ZXIiLCAiRGV2ZWxvcGVkIiwgIkJhcnJlbiIsICJGb3Jlc3QiLCAiU2hydWJsYW5kIiwgIkhlcmJhY2VvdXMiLCAiUGxhbnRlZC9DdWx0aXZhdGVkIiwgIldldGxhbmRzIikNCmBgYA0KDQpgYGB7cn0NCmNsYXNzZGYgPC0gZGF0YS5mcmFtZShjbGFzc3ZhbHVlMSA9IGMoMSwyLDMsNCw1LDcsOCw5KSwgY2xhc3NuYW1lczEgPSBubGNkY2xhc3MpDQpjbGFzc2RmDQpgYGANCg0KYGBge3J9DQojIEPDs2RpZ29zIGhleGFkZWNpbWFsZXMgZGUgY29sb3Jlcw0KDQpjbGFzc2NvbG9yIDwtIGMoIiM1NDc1QTgiLCAiI0I1MDAwMCIsICIjRDJDREMwIiwgIiMzODgxNEUiLCAiI0FGOTYzQyIsICIjRDFEMTgyIiwgIiNGQkY2NUQiLCAiI0M4RTZGOCIpDQpjbGFzc2NvbG9yDQpgYGANCg0KYGBge3J9DQojIEFob3JhIHJhdGlmaWNhbW9zIChSQVQgPSAiVGFibGEgZGUgYXRyaWJ1dG9zIGRlIHLDoXN0ZXIiKSBlbCBuY2xkMjAxMSAoZGVmaW5pbW9zIFJhc3RlckxheWVyIGNvbW8gdW5hIHZhcmlhYmxlIGNhdGVnw7NyaWNhKS4gRXN0byBlcyDDunRpbCBwYXJhIHRyYXphci4NCg0KbmxjZDIwMTEgPC0gbmxjZFtbMl1dDQpubGNkMjAxMQ0KYGBgDQoNCmBgYHtyfQ0KaGVhZChubGNkMjAxMSkNCmBgYA0KDQpgYGB7cn0NCiMgQ29uIHJhdGlmeSgpIHNlIHB1ZWRlIHZlciBsYSAiVGFibGEgZGUgYXRyaWJ1dG9zIGRlIHLDoXN0ZXIiIChSQVQpIGNvbW8gdW4gbWFyY28gZGUgZGF0b3MgZW4gdW4gbnVldm8gZXNwYWNpbyBkZW5vbWluYWRvIGF0dHJpYnV0ZXMNCg0KbmxjZDIwMTEgPC0gcmF0aWZ5KG5sY2QyMDExKQ0KDQojIFBvZGVtb3MgYXByZWNpYXIgcXVlIGFob3JhIGVsIG9iamV0byBubGNkMjAxMSB0aWVuZSBlbCBhcGFydGFkbyAiYXR0cmlidXRlcyINCg0KbmxjZDIwMTENCmBgYA0KDQpgYGB7cn0NCiMgTG8gcXVlIGJ1c2NhbW9zIGVzIGFzb2NpYXIgbG9zIG5pdmVsZXMgZGUgIm5sY2QyMDExIiBjb24gbGFzIGNhdGVnb3JpYXMgKGxhbmRjb3ZlciksIHBhcmEgbG9ncmFybG8gY3JlYW1vcyB1biBvYmpldG8gInJhdCIuIEludHJvZHVjaW1vcyBlbiBlbCBvYmpldG8gcmF0IGxvcyBuaXZlbGVzICh2YWxvcmVzIHF1ZSBwdWVkZW4gdGVuZXIgbG9zIHBpeGVsZXMpIGRlIG5sY2QyMDExDQoNCnJhdCA8LSBsZXZlbHMobmxjZDIwMTEpW1sxXV0NCnJhdA0KYGBgDQoNCmBgYHtyfQ0KIyBBZGp1ZGljYW1vcyBhIGNhZGEgSUQgKG5pdmVsIGRlIG5sY2QyMDExKSB1bmEgY2F0ZWdvcmlhIChsYW5kY292ZXIpIGNvbiBlbCBvYmpldG8gbmxjZGNsYXNzDQoNCnJhdCRsYW5kY292ZXIgPC0gbmxjZGNsYXNzDQpyYXQNCmBgYA0KDQpgYGB7cn0NCmxldmVscyhubGNkMjAxMSkgPC0gcmF0DQpsZXZlbHMobmxjZDIwMTEpDQpgYGANCg0KIyMgR2VuZXJhdGUgc2FtcGxlIHNpdGVzDQoNCmBgYHtyfQ0KIyBDYXJndWUgbGFzIHViaWNhY2lvbmVzIGRlIGxvcyBzaXRpb3MgZGUgZW50cmVuYW1pZW50bw0KIyBDb25maWd1cmUgZWwgZ2VuZXJhZG9yIGRlIG7Dum1lcm9zIGFsZWF0b3Jpb3MgcGFyYSByZXByb2R1Y2lyIGxvcyByZXN1bHRhZG9zDQoNCnNldC5zZWVkKDk5KQ0KYGBgDQoNCmBgYHtyfQ0KIyBNdWVzdHJlbw0KDQpzYW1wMjAxMSA8LSBzYW1wbGVTdHJhdGlmaWVkKG5sY2QyMDExLCBzaXplID0gMjAwLCBuYS5ybSA9IFRSVUUsIHNwID0gVFJVRSkNCnNhbXAyMDExDQpgYGANCg0KYGBge3J9DQp0YWJsZShzYW1wMjAxMSRubGNkMjAxMSkNCmBgYA0KDQpgYGB7cn0NCiMgVHJhY2Vtb3MgbG9zIHNpdGlvcyBkZSBjYXBhY2l0YWNpw7NuIHNvYnJlIG5sY2QyMDExIFJhc3RlckxheWVyIHBhcmEgdmlzdWFsaXphciBsYSBkaXN0cmlidWNpw7NuIGRlIGxhcyB1YmljYWNpb25lcyBkZSBtdWVzdHJlby4NCg0KbGlicmFyeShyYXN0ZXJWaXMpDQpwbHQgPC0gbGV2ZWxwbG90KG5sY2QyMDExLCBjb2wucmVnaW9ucyA9IGNsYXNzY29sb3IsIG1haW4gPSAnRGlzdHJpYnV0aW9uIG9mIFRyYWluaW5nIFNpdGVzJykNCnByaW50KHBsdCArIGxheWVyKHNwLnBvaW50cyhzYW1wMjAxMSwgcGNoID0gMywgY2V4ID0gMC41LCBjb2wgPSAxKSkpDQpgYGANCg0KIyMgRXh0cmFjdCB2YWx1ZXMgZm9yIHNpdGVzDQoNCmBgYHtyfQ0KIyBDYXJnYW1vcyBsb3MgZGF0b3MgTGFuZHNhdA0KDQpsYW5kc2F0NSA8LSBzdGFjaygiQzovVXNlcnMvVVNTL0Rlc2t0b3AvUGVkcm8vMjAxOSAtIDIwMjAvUGVyY2VwY2lvbiByZW1vdGEvUDUvcnNkYXRhL3JzL2NlbnRyYWx2YWxsZXktMjAxMUxUNS50aWYiKQ0KbmFtZXMobGFuZHNhdDUpIDwtIGMoJ2JsdWUnLCAnZ3JlZW4nLCAncmVkJywgJ05JUicsICdTV0lSMScsICdTV0lSMicpDQoNCmxhbmRzYXQ1DQpgYGANCg0KYGBge3J9DQojIEV4dHJhZSBsb3MgdmFsb3JlcyBkZSBjYXBhIHBhcmEgbGFzIHViaWNhY2lvbmVzDQoNCnNhbXB2YWxzIDwtIGV4dHJhY3QobGFuZHNhdDUsIHNhbXAyMDExLCBkZiA9IFRSVUUpDQpzYW1wdmFscw0KYGBgDQoNCmBgYHtyfQ0KIyBzYW1wdmFscyB5YSBubyB0aWVuZSBsYSBpbmZvcm1hY2nDs24gZXNwYWNpYWwuIFBhcmEgbWFudGVuZXIgbGEgaW5mb3JtYWNpw7NuIGVzcGFjaWFsLCB1dGlsaWNlIGVsIGFyZ3VtZW50byAic3AgPSBUUlVFIiBlbiBsYSBmdW5jacOzbiAiZXh0cmFjdCIuDQoNCiMgUXVpdGFtb3MgbGEgY29sdW1uYSBJRA0KDQpzYW1wdmFscyA8LSBzYW1wdmFsc1ssIC0xXQ0Kc2FtcHZhbHMNCmBgYA0KDQpgYGB7cn0NCiMgU2UgY29tYmluYSBsYSBpbmZvcm1hY2nDs24gZGUgbGEgY2xhc2UgY29uIGxvcyB2YWxvcmVzIGV4dHJhw61kb3MgbWVkaWFudGUgdW4gZGF0YS5mcmFtZQ0KDQpzYW1wZGF0YSA8LSBkYXRhLmZyYW1lKGNsYXNzdmFsdWUgPSBzYW1wMjAxMUBkYXRhJG5sY2QyMDExLCBzYW1wdmFscykNCnNhbXBkYXRhDQpgYGANCg0KIyMgVHJhaW4gdGhlIGNsYXNzaWZpZXINCg0KYGBge3J9DQpsaWJyYXJ5KHJwYXJ0KQ0KDQojIEZvcm1hbW9zIGVsIG1vZGVsby4gTGEgZm9ybXVsYSBxdWUgdXNhcmVtb3MgZXMgdGlwbyB+IC4sIGxhIGN1YWwgZXhwcmVzYSBxdWUgaW50ZW50YXJlbW9zIGNsYXNpZmljYXIgY2xhc3N2YWx1ZSB1c2FuZG8gYSB0b2RhcyBsYXMgZGVtw6FzIHZhcmlhYmxlcyBjb21vIHByZWRpY3RvcmFzLg0KDQpjYXJ0IDwtIHJwYXJ0KGFzLmZhY3RvcihjbGFzc3ZhbHVlKX4uLCBkYXRhPXNhbXBkYXRhLCBtZXRob2QgPSAnY2xhc3MnLCBtaW5zcGxpdCA9IDUpDQpjYXJ0DQpgYGANCg0KYGBge3J9DQojIFBsb3RlYW1vcyAobW9kZWwuY2xhc3MpDQojIFRyYXphciBlbCDDoXJib2wgZGUgY2xhc2lmaWNhY2nDs24gZm9ybWFkby4NCg0KcGFyKHhwZCA9IEZBTFNFLCBtYXIgPSBjKDAsMSwxLDApKQ0KcGxvdChjYXJ0LCB1bmlmb3JtID0gVFJVRSwgbWFpbj0iQ2xhc3NpZmljYXRpb24gVHJlZSIsICkNCnRleHQoY2FydCwgY2V4ID0gMC44KQ0KYGBgDQoNCiMjIENsYXNzaWZ5DQoNCmBgYHtyfQ0KIyBBaG9yYSBxdWUgdGVuZW1vcyBudWVzdHJvIG1vZGVsbyBkZSBjbGFzaWZpY2FjacOzbiBlbnRyZW5hZG8sIHBvZGVtb3MgdXNhcmxvIHBhcmEgaGFjZXIgcHJlZGljY2lvbmVzLCBlcyBkZWNpciwgcGFyYSBjbGFzaWZpY2FyIHRvZGFzIGxhcyBjZWxkYXMgZW4gZWwgcmFzdGVyIHN0YWNrIExhbmRzYXQgNS4NCg0KIyBBaG9yYSBzZSBwcmVkaWNlIGxvcyBkYXRvcyBkZWwgc3ViY29uanVudG8gZW4gZnVuY2nDs24gZGVsIG1vZGVsbzsgbGEgcHJlZGljY2nDs24gcGFyYSB0b2RhIGVsIMOhcmVhIGxsZXZhIG3DoXMgdGllbXBvDQoNCnByMjAxMSA8LSBwcmVkaWN0KGxhbmRzYXQ1LCBjYXJ0LCB0eXBlPSdjbGFzcycpDQpwcjIwMTENCmBgYA0KDQpgYGB7cn0NCiMgQWhvcmEgdHJhY2UgZWwgcmVzdWx0YWRvIGRlIGxhIGNsYXNpZmljYWNpw7NuIHVzYW5kbyByYXN0ZXJWaXMuIE9ic2VydmFyIHF1ZSBlc3RhYmxlY2Vyw6EgbG9zIG5vbWJyZXMgZGUgY2xhc2UgcGFyYSBsb3MgdmFsb3JlcyBkZSBjbGFzZQ0KDQpwcjIwMTEgPC0gcmF0aWZ5KHByMjAxMSkNCnJhdCA8LSBsZXZlbHMocHIyMDExKVtbMV1dDQpyYXQkbGVnZW5kIDwtIGNsYXNzZGYkY2xhc3NuYW1lcw0KbGV2ZWxzKHByMjAxMSkgPC0gcmF0DQpsZXZlbHBsb3QocHIyMDExLCBtYXhwaXhlbHMgPSAxZTYsDQogICAgICAgICAgY29sLnJlZ2lvbnMgPSBjbGFzc2NvbG9yLA0KICAgICAgICAgIHNjYWxlcz1saXN0KGRyYXc9RkFMU0UpLA0KICAgICAgICAgIG1haW4gPSAiRGVjaXNpb24gVHJlZSBjbGFzc2lmaWNhdGlvbiBvZiBMYW5kc2F0IDUiKQ0KYGBgDQoNCiMjIyBRdWVzdGlvbiAxOlBsb3QgIm5sY2QyMDExIiBhbmQgInByMjAxMSIgc2lkZS1ieS1zaWRlIGFuZCBjb21tZW50IGFib3V0IHRoZSBhY2N1cmFjeSBvZiB0aGUgcHJlZGljdGlvbiAoZS5nLiBtaXhpbmcgYmV0d2VlbiBjdWx0aXZhdGVkIGNyb3BzLCBwYXN0dXJlLCBncmFzc2xhbmQgYW5kIHNocnVicykuDQoNCmBgYHtyfQ0KIyBRdWl0YW1vcyBsb3MgZWplcyB5IGxhIGxleWVuZGEgZGUgY29sb3JlcyBwYXJhIHVuYSBtZWpvciB2aXN1YWxpemFjacOzbi4gRW4gZ3LDoWZpY29zIGxhdHRpY2Ugbm8gZnVuY2lvbmEgbGEgZnVuY2lvbiBwYXIoKS4NCg0KbGlicmFyeShncmlkRXh0cmEpDQpwbG90X25sY2QyMDExIDwtIGxldmVscGxvdChubGNkMjAxMSwNCiAgICAgICAgICAgICAgICAgICAgbWF4cGl4ZWxzID0gMWU2LCANCiAgICAgICAgICAgICAgICAgICAgeGxhYj0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgeWxhYj0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgbWFpbj0nbmxjZDIwMTEnLA0KICAgICAgICAgICAgICAgICAgICBjb2xvcmtleSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICBzY2FsZXM9bGlzdChkcmF3PUZBTFNFKSwNCiAgICAgICAgICAgICAgICAgICAgY29sLnJlZ2lvbnMgPSBjbGFzc2NvbG9yLA0KICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBGQUxTRSkNCnBsb3RfcHIyMDExIDwtIGxldmVscGxvdChwcjIwMTEsDQogICAgICAgICAgICAgICAgICAgIG1heHBpeGVscyA9IDFlNiwgDQogICAgICAgICAgICAgICAgICAgIHhsYWI9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgIHlsYWI9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgIG1haW49J3ByMjAxMSAocHJlZGljY2nDs24pJywNCiAgICAgICAgICAgICAgICAgICAgY29sb3JrZXkgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgc2NhbGVzPWxpc3QoZHJhdz1GQUxTRSksDQogICAgICAgICAgICAgICAgICAgIGNvbC5yZWdpb25zID0gY2xhc3Njb2xvciwNCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gRkFMU0UpDQpncmlkLmFycmFuZ2UocGxvdF9ubGNkMjAxMSwgcGxvdF9wcjIwMTEsIG5jb2w9MikNCmBgYA0KDQojIyBNb2RlbCBldmFsdWF0aW9uDQoNCmBgYHtyfQ0KIyBBaG9yYSBhbmFsaWNlbW9zIGxhIHByZWNpc2nDs24gZGVsIG1vZGVsbyBwYXJhIHRlbmVyIHVuYSBpZGVhIGRlIGxhIHByZWNpc2nDs24gZGVsIG1hcGEgY2xhc2lmaWNhZG8uDQoNCiMgUGFyYSBldmFsdWFyIGN1YWxxdWllciBtb2RlbG8sIHB1ZWRlIHVzYXIgbGEgdmFsaWRhY2nDs24gY3J1emFkYSBrLWZvbGQuIEVuIGVzdGEgdMOpY25pY2EsIGxvcyBkYXRvcyB1dGlsaXphZG9zIHBhcmEgYWp1c3RhciBlbCBtb2RlbG8gc2UgZGl2aWRlbiBlbiBrIGdydXBvcyAoZ2VuZXJhbG1lbnRlIDUgZ3J1cG9zKS4gQSBzdSB2ZXosIHVubyBkZSBsb3MgZ3J1cG9zIHNlIHVzYXLDoSBwYXJhIGxhIHBydWViYSBkZWwgbW9kZWxvLCBtaWVudHJhcyBxdWUgZWwgcmVzdG8gZGUgbG9zIGRhdG9zIHNlIHVzYXLDoSBwYXJhIGVsIGVudHJlbmFtaWVudG8gZGVsIG1vZGVsbyAoYWp1c3RlKS4NCg0KbGlicmFyeShkaXNtbykNCnNldC5zZWVkKDk5KQ0KaiA8LSBrZm9sZChzYW1wZGF0YSwgayA9IDUsIGJ5PXNhbXBkYXRhJGNsYXNzdmFsdWUpDQp0YWJsZShqKQ0KYGBgDQoNCmBgYHtyfQ0KIyBBaG9yYSBlbnRyZW5hbW9zIHkgcHJvYmFtb3MgZWwgbW9kZWxvIGNpbmNvIHZlY2VzLCBjYWRhIHZleiBjYWxjdWxhbmRvIHVuYSBtYXRyaXogZGUgY29uZnVzacOzbiBxdWUgYWxtYWNlbmFtb3MgZW4gdW5hIGxpc3RhLg0KDQp4IDwtIGxpc3QoKQ0KZm9yIChrIGluIDE6NSkgew0KICAgIHRyYWluIDwtIHNhbXBkYXRhW2ohPSBrLCBdDQogICAgdGVzdCA8LSBzYW1wZGF0YVtqID09IGssIF0NCiAgICBjYXJ0IDwtIHJwYXJ0KGFzLmZhY3RvcihjbGFzc3ZhbHVlKX4uLCBkYXRhPXRyYWluLCBtZXRob2QgPSAnY2xhc3MnLCBtaW5zcGxpdCA9IDUpDQogICAgcGNsYXNzIDwtIHByZWRpY3QoY2FydCwgdGVzdCwgdHlwZT0nY2xhc3MnKQ0KDQojIENyZWFtb3MgdW4gZGF0YS5mcmFtZSB1c2FuZG8gbGEgcmVmZXJlbmNpYSB5IGxhIHByZWRpY2Npw7NuDQojIEN1YW5kbyBzZSBkZXNlYSB1bmlyIHVuYSB0YWJsYSBhIGxhIGRlcmVjaGEgZGUgb3RyYSBzZSB1dGlsaXphIGxhIGZ1bmNpw7NuICJjYmluZCINCiAgIA0KICAgICB4W1trXV0gPC0gY2JpbmQodGVzdCRjbGFzc3ZhbHVlLCBhcy5pbnRlZ2VyKHBjbGFzcykpDQp9DQpgYGANCg0KYGBge3J9DQojIEFob3JhIGNvbWJpbmFtb3MgbG9zIGNpbmNvIGVsZW1lbnRvcyBkZSBsYSBsaXN0YSBlbiB1biBzb2xvIGRhdGEuZnJhbWUsIHVzYW5kbyBsYSBmdW5jacOzbiAiZG8uY2FsbCIgeSBjYWxjdWxlIHVuYSBtYXRyaXogZGUgY29uZnVzacOzbi4NCiMgQ3VhbmRvIHNlIGRlc2VhIHVuaXIgdW5hIHRhYmxhIGEgZGViYWpvIGRlIG90cmEsIHNlIHV0aWxpemEgbGEgZnVuY2nDs24gInJiaW5kIg0KDQp5IDwtIGRvLmNhbGwocmJpbmQsIHgpDQp5IDwtIGRhdGEuZnJhbWUoeSkNCmNvbG5hbWVzKHkpIDwtIGMoJ29ic2VydmVkJywgJ3ByZWRpY3RlZCcpDQpjb25tYXQgPC0gdGFibGUoeSkNCiMgY2hhbmdlIHRoZSBuYW1lIG9mIHRoZSBjbGFzc2VzDQpjb2xuYW1lcyhjb25tYXQpIDwtIGNsYXNzZGYkY2xhc3NuYW1lcw0Kcm93bmFtZXMoY29ubWF0KSA8LSBjbGFzc2RmJGNsYXNzbmFtZXMNCmNvbm1hdA0KYGBgDQoNCiMjIyBDYWxjdWxlIGxhIHByZWNpc2nDs24gZ2VuZXJhbCB5IGxhIGVzdGFkw61zdGljYSAiS2FwcGEiLg0KDQojIyMjIFByZWNpc2nDs24gZ2VuZXJhbDoNCmBgYHtyfQ0KIyBudW1iZXIgb2YgY2FzZXMNCm4gPC0gc3VtKGNvbm1hdCkNCm4NCmBgYA0KDQpgYGB7cn0NCiMgQ2FudGlkYWQgZGUgY2Fzb3MgY29ycmVjdGFtZW50ZSBjbGFzaWZpY2Fkb3MgcG9yIGNsYXNlLiBMYSBmdW5jacOzbiAiZGlhZyIgbm9zIGRhIGxhIGRpYWdvbmFsIGRlIGxhIHRhYmxhIGNvbm1hdCwgZXMgZGVjaXIsIGxvcyBjYXNvcyBlbiBsb3MgcXVlIGxhcyBjbGFzZXMgZGUgb2JzZXJ2ZWQgY29pbmNpZGVuIGNvbiBsYXMgZGUgcHJlZGljdGVkDQoNCmRpYWcgPC0gZGlhZyhjb25tYXQpDQpkaWFnDQpgYGANCg0KYGBge3J9DQojIFByZWNpc2nDs24gZ2VuZXJhbA0KDQpPQSA8LSBzdW0oZGlhZykgLyBuDQpPQQ0KYGBgDQoNCiMjIyMgS2FwcGE6DQoNCmBgYHtyfQ0KIyBDYXNvcyBvYnNlcnZhZG9zICh2ZXJkYWRlcm9zKSBwb3IgY2xhc2UuIExhIGZ1bmNpw7NuICJhcHBseSIgYXBsaWNhIHVuYSBmdW5jacOzbiBhIHRvZG9zIGxvcyBlbGVtZW50b3MgZGUgdW5hIG1hdHJpei4gQ29uIE1BUkdJTiA9IDEgc3VtYW1vcyBsb3MgZWxlbWVudG9zIGRlIGNhZGEgZmlsYS4NCg0Kcm93c3VtcyA8LSBhcHBseShjb25tYXQsIDEsIHN1bSkNCnAgPC0gcm93c3VtcyAvIG4NCnANCmBgYA0KDQpgYGB7cn0NCiMgQ2Fzb3MgcHJlZGljaG9zIHBvciBjbGFzZS4gQ29uIE1BUkdJTiA9IDIgc3VtYW1vcyBsb3MgZWxlbWVudG9zIGRlIGNhZGEgY29sdW1uYS4NCg0KY29sc3VtcyA8LSBhcHBseShjb25tYXQsIDIsIHN1bSkNCnEgPC0gY29sc3VtcyAvIG4NCmV4cEFjY3VyYWN5IDwtIHN1bShwKnEpDQprYXBwYSA8LSAoT0EgLSBleHBBY2N1cmFjeSkgLyAoMSAtIGV4cEFjY3VyYWN5KQ0Ka2FwcGENCmBgYA0KDQojIyMgUHJvZHVjZXIgYW5kIHVzZXIgYWNjdXJhY3kNCg0KYGBge3J9DQojIFByb2R1Y2VyIGFjY3VyYWN5DQoNClBBIDwtIGRpYWcgLyBjb2xzdW1zDQpQQQ0KYGBgDQoNCmBgYHtyfQ0KIyBVc2VyIGFjY3VyYWN5DQoNClVBIDwtIGRpYWcgLyByb3dzdW1zDQpvdXRBY2MgPC0gZGF0YS5mcmFtZShwcm9kdWNlckFjY3VyYWN5ID0gUEEsIHVzZXJBY2N1cmFjeSA9IFVBKQ0Kb3V0QWNjDQpgYGANCg0K