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)))

Extract values for sites

# Cargamos los datos Landsat

landsat5 <- stack("C:/Users/USS/Desktop/Pedro/2019 - 2020/Percepcion remota/P5/rsdata/rs/centralvalley-2011LT5.tif")
names(landsat5) <- c('blue', 'green', 'red', 'NIR', 'SWIR1', 'SWIR2')

landsat5
class      : RasterStack 
dimensions : 1230, 1877, 2308710, 6  (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 
names      : blue, green, red, NIR, SWIR1, SWIR2 
# Extrae los valores de capa para las ubicaciones

sampvals <- extract(landsat5, samp2011, df = TRUE)
sampvals
# sampvals ya no tiene la información espacial. Para mantener la información espacial, utilice el argumento "sp = TRUE" en la función "extract".

# Quitamos la columna ID

sampvals <- sampvals[, -1]
sampvals
# Se combina la información de la clase con los valores extraídos mediante un data.frame

sampdata <- data.frame(classvalue = samp2011@data$nlcd2011, sampvals)
sampdata

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")

Question 1:Plot “nlcd2011” and “pr2011” side-by-side and comment about the accuracy of the prediction (e.g. mixing between cultivated crops, pasture, grassland and shrubs).

# Quitamos los ejes y la leyenda de colores para una mejor visualización. En gráficos lattice no funciona la funcion par().

library(gridExtra)
plot_nlcd2011 <- levelplot(nlcd2011,
                    maxpixels = 1e6, 
                    xlab= NULL,
                    ylab= NULL,
                    main='nlcd2011',
                    colorkey = FALSE,
                    scales=list(draw=FALSE),
                    col.regions = classcolor,
                    labels = FALSE)
plot_pr2011 <- levelplot(pr2011,
                    maxpixels = 1e6, 
                    xlab= NULL,
                    ylab= NULL,
                    main='pr2011 (predicción)',
                    colorkey = FALSE,
                    scales=list(draw=FALSE),
                    col.regions = classcolor,
                    labels = FALSE)
grid.arrange(plot_nlcd2011, plot_pr2011, ncol=2)

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