Entre las aplicaciones más importantes de los datos obtenidos de imágenes satelitales es la generación de mapas de cobertura y uso de suelo, las cuales pueden ser obtenidos a partir de técnicas de clasificacion de imágenes.
Las técnicas de clasificación de imagénes se basan en agrupar pixeles que poseen regiones del espectro similares y que pueden asociarse con diferentes clases de coberturas del suelo. La clasificación no supervisada (UC) permite identificar entre las diferentes coberturas basados en la clasificación de las imágenes digitales mediante el uso de bandas dentro de una categoría espectral identificada, en pixeles seleccionados, para luego ser agrupados por su similaridad. Esta técnica de agrupación comprime un conjunto grande de datos en una escala de pixel con un mínimo de trabajo por parte del analista.
Existen varios algoritmos para realizar una clasificación no supervisada, estos algoritmos permiten evaluar el número adecuado de agrupaciones, las cuales permitirán obtener la información requerida. K-means, identifica el centroide y su valor para cada grupo dividiendo el espacio entre las celdas, identificando a cada observación como una única agrupación. El algoritmo CLARA, no toma en cuenta todo el conjunto de datos, y elige aleatoriamente una pequeña muetsra.
El siguiente tutorial realiza es un ejemplo de clasificación no supervisada utilizando 2 algoritmos: K-means y CLARA, El ejemplo esta basado en el script realizado por João Gonçalves, y se puede encontrar en el siguiente link https://www.r-exercises.com/2018/02/28/advanced-techniques-with-raster-data-part-1-unsupervised-classification/
Preparación de datos: Lo primero que realizamos es transformar los niveles digitales a valores de reflectancia utilizando la funcion >landsat8<. Despues de cortar las bandas con la zona de estudio visualizamos en diferentes combinaciones de bandas. Visualmente se puede identificar coberturas como: Páramo, vegetación arbustiva, bosque nativo, infraestructura, pastizales, cultivos, vegetación herbacea.
## cargar librerias
library(raster)
library(rgdal)
library(rgdal)
library(ggplot2)
library(sp)
library(rgeos)
library(landsat8)
library(cluster)
#Ubicacion de carpeta
setwd("D:/maestria/percepcion remota avanzada/taller1/datos")
getwd()
[1] "D:/maestria/percepcion remota avanzada/taller1/datos"
band1 <- raster("LC08_L1TP_010062_20161120_20170318_01_T1_B1.TIF")
band2 <- raster("LC08_L1TP_010062_20161120_20170318_01_T1_B2.TIF")
band3 <- raster("LC08_L1TP_010062_20161120_20170318_01_T1_B3.TIF")
band4 <- raster("LC08_L1TP_010062_20161120_20170318_01_T1_B4.TIF")
band5 <- raster("LC08_L1TP_010062_20161120_20170318_01_T1_B5.TIF")
band6 <- raster("LC08_L1TP_010062_20161120_20170318_01_T1_B6.TIF")
band7 <- raster("LC08_L1TP_010062_20161120_20170318_01_T1_B7.TIF")
paute <- readOGR("paute.shp")
OGR data source with driver: ESRI Shapefile
Source: "paute.shp", layer: "paute"
with 1 features
It has 6 fields
Integer64 fields read as strings: DPA_VALOR
#transformar a valores de reflectancia
#valores de reflectancia reflconvS(x, Mp, Ap, sunelev)
#SUN_ELEVATION = 61.31854934
#REFLECTANCE_MULT_BAND_1 = 2.0000E-05
#REFLECTANCE_ADD_BAND_1 = -0.100000
band1.dn<- as(band1, 'SpatialGridDataFrame')
band2.dn<- as(band2, 'SpatialGridDataFrame')
band3.dn<- as(band3, 'SpatialGridDataFrame')
band4.dn<- as(band4, 'SpatialGridDataFrame')
band5.dn<- as(band5, 'SpatialGridDataFrame')
band6.dn<- as(band6, 'SpatialGridDataFrame')
band7.dn<- as(band7, 'SpatialGridDataFrame')
b1.ref <- reflconvS(band1.dn,2.0000E-05 ,-0.100000 , 61.31854934)
b2.ref <- reflconvS(band2.dn,2.0000E-05 ,-0.100000 , 61.31854934)
b3.ref <- reflconvS(band3.dn,2.0000E-05 ,-0.100000 , 61.31854934)
b4.ref <- reflconvS(band4.dn,2.0000E-05 ,-0.100000 , 61.31854934)
b5.ref <- reflconvS(band5.dn,2.0000E-05 ,-0.100000 , 61.31854934)
b6.ref <- reflconvS(band6.dn,2.0000E-05 ,-0.100000 , 61.31854934)
b7.ref <- reflconvS(band7.dn,2.0000E-05 ,-0.100000 , 61.31854934)
b1.ref <- as(b1.ref,'RasterLayer')
b2.ref <- as(b2.ref,'RasterLayer')
b3.ref <- as(b3.ref,'RasterLayer')
b4.ref <- as(b4.ref,'RasterLayer')
b5.ref <- as(b5.ref,'RasterLayer')
b6.ref <- as(b6.ref,'RasterLayer')
b7.ref <- as(b7.ref,'RasterLayer')
#unimos las bandas
#cambiamos los nombres a las bandas
bandas <- brick(b1.ref,b2.ref,b3.ref,b4.ref,b5.ref,b6.ref,b7.ref)
names(bandas) <- paste("Banda",1:7,sep="")
plot(bandas)

#cortar con la zona de estudio
paute17n<-spTransform(paute, CRS=crs(band1))
bandas <- crop(bandas, extent(paute17n))
bandas <- mask(bandas,paute17n)
plotRGB(bandas, r=4, g=3, b=2, stretch="hist",scale=2000) #Color natural

plotRGB(bandas, r=5, g=4, b=3, stretch="hist",scale=2000) #color infrarrojo

plotRGB(bandas, r=5, g=6, b=2, stretch="hist",scale=2000) #vegetacion saludable

Realizamos el agrupamiento de los datos de las bandas con los algoritmos K-mean y KLARA, para los 8 clústeres.
bandas_df <- values(bandas)
# Chequear los NA´s en los datos
idx <- complete.cases(bandas_df)
# Iniciar los datasets ráster que contendrán todas las soluciones de clúster
# 2 grupos / 8 clusteres
bandasKM <- raster(bandas[[1]])
bandasCLARA <- raster(bandas[[1]])
for(nClust in 2:8){
cat("-> Clustering data for nClust =",nClust,"......")
# Realizar clustering K-means
km <- kmeans(bandas_df[idx,], centers = nClust, iter.max = 50)
# Realizar clara clustering usando distancia Manhattan
cla <- clara(bandas_df[idx, ], k = nClust, metric = "manhattan")
# Crear un vector entero temporal para mantener los números del clúster
kmClust <- vector(mode = "integer", length = ncell(bandas))
claClust <- vector(mode = "integer", length = ncell(bandas))
# Generar un vector de agrupamiento temporal para K-means
kmClust[!idx] <- NA
kmClust[idx] <- km$cluster
# Generar un vector de agrupamiento temporal para CLARA
claClust[!idx] <- NA
claClust[idx] <- cla$clustering
# Crear un ráster temporal para mantener la nueva solución de clúster
# K-means
tmpbandasKM <- raster(bandas[[1]])
# CLARA
tmpbandasCLARA <- raster(bandas[[1]])
# Establecer valores ráster con el vector deL clúster
# K-means
values(tmpbandasKM) <- kmClust
# CLARA
values(tmpbandasCLARA) <- claClust
# Unir los rásteres temporales en los finales
if(nClust==2){
bandasKM <- tmpbandasKM
bandasCLARA <- tmpbandasCLARA
}else{
bandasKM <- stack(bandasKM, tmpbandasKM)
bandasCLARA <- stack(bandasCLARA, tmpbandasCLARA)
}
cat(" hecho =)\n\n")
}
-> Clustering data for nClust = 2 ...... hecho =)
-> Clustering data for nClust = 3 ...... hecho =)
-> Clustering data for nClust = 4 ...... hecho =)
-> Clustering data for nClust = 5 ...... hecho =)
-> Clustering data for nClust = 6 ...... hecho =)
-> Clustering data for nClust = 7 ...... hecho =)
-> Clustering data for nClust = 8 ...... hecho =)
writeRaster(bandasKM, "kmeans.tif", overwrite=TRUE)
writeRaster(bandasCLARA, "Clara.tif", overwrite=TRUE)
Para evaluar el rendimiento de cada solución de clúster se utilizó el índice silhouette, el cual es un método de interpretar y validar las agrupaciones de datos, proporciona una representación gráfica que representa cómo objeto agrupado se encuentra dentro de su grupo, además el valor de la silueta es una medida que muestra que tan similar es un objeto para su propio clúster en comparación con otros.
Este indice varía de -1 a +1, donde un valor alto indica que el objeto está bien adaptado a su propio clúster y no a los vecinos. Cuando la mayoria de pixeles tienen un valor alto, entonces la configuración de agrupamiento se considera apropiada. Si muchos puntos tienen un valor bajo o negativo, es necesario evaluar el numero de clusters utilizados. Para realizar este analisis el prgrama R cuenta con el paquete >clusterCrit<.
En este ejemplo tomamos las siguientes especificaciones
Se utilizó un muestreo aleatorio estratificado debido a que su calculo es un proceso bastante lento por el numero de observaciones.
Se asumió que la muestra es robusta y representativa
Se usó una sola muestra de celdas
clCritCLARA
$silhouette
[1] 0.2998882
En la tabla podemos observar que los valores de indice silhouette para las dos clasificaciones no supervisadas
knitr::kable(clustPerfSI, digits = 3, align = "c",
col.names = c("#clusters","Avg. Silhouette (k-means)","Avg. Silhouette (CLARA)"))
2 |
0.373 |
0.321 |
3 |
0.379 |
0.243 |
4 |
0.332 |
0.328 |
5 |
0.330 |
0.261 |
6 |
0.310 |
0.269 |
7 |
0.301 |
0.243 |
8 |
0.305 |
0.300 |
Comparamos los dos metodos de clasificación
plot(clustPerfSI[,1], clustPerfSI[,2],
xlim = c(1,13), ylim = range(clustPerfSI[,2:3]), type = "n",
ylab="Avg. Silhouette Index", xlab="# of clusters",
main="Silhouette index by # of clusters")
# Plot Avg Silhouette values across # of clusters for K-means
lines(clustPerfSI[,1], clustPerfSI[,2], col="red")
# Plot Avg Silhouette values across # of clusters for CLARA
lines(clustPerfSI[,1], clustPerfSI[,3], col="blue")
# Grid lines
abline(v = 1:13, lty=2, col="light grey")
abline(h = seq(0.30,0.44,0.02), lty=2, col="light grey")
legend("topright", legend=c("K-means","CLARA"), col=c("red","blue"), lty=1, lwd=1)

Ploteamos las mejores clasificaciones de cada modelo
plot(bandasKM[[2]])
title("K-mean solution clasifiacion")

plot(bandasCLARA[[2]])
title("CLARA solution clasifiacion")

RESULTADOS
La tabla y la gráfica nos muestra que el algoritmo K-means supera ampliamente el rendimiento de clasificación a CLARA. Sin embargo los dos clasificadores no muestran resultados idoneos de clasificación, obteniendo valores menores a 0.38 de indice de silueta. por lo cual es importante determinar cual es el número de cluster ideal para la clasificación de la zona de estudio. Ademas tambien se deberia evaluar el tamaño de la muestra aleatorias estratificadas por clúster, en este caso utilizamos un valor de 2000, sin embargo se podria aumentar para analizar los resulatdos. Entre las razones del bajo rendimiento de Clara, puede deberse a que este algoritmo trabaja con subconjunto de datos y podria ser menos capaz de encontrar mejores centros de los cluster.
DISCUSIONEs
Al ser este ejercicio un analisis preliminar de clasificación, es indispensable evaluar varios parametros en los clasificadores, entre ellos el numero de clusters y el numero de muestras aleatorias, para obtener mejores resultados.
LS0tDQp0aXRsZTogIkNsYXNpZmljYWNp824gbm8gc3VwZXJ2aXNhZGEiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KRW50cmUgbGFzIGFwbGljYWNpb25lcyBt4XMgaW1wb3J0YW50ZXMgZGUgbG9zIGRhdG9zIG9idGVuaWRvcyBkZSBpbeFnZW5lcyBzYXRlbGl0YWxlcyBlcyBsYSBnZW5lcmFjafNuIGRlIG1hcGFzIGRlIGNvYmVydHVyYSB5IHVzbyBkZSBzdWVsbywgbGFzIGN1YWxlcyBwdWVkZW4gc2VyIG9idGVuaWRvcyBhIHBhcnRpciBkZSB06WNuaWNhcyBkZSBjbGFzaWZpY2FjaW9uIGRlIGlt4WdlbmVzLiANCg0KTGFzIHTpY25pY2FzIGRlIGNsYXNpZmljYWNp824gZGUgaW1hZ+luZXMgc2UgYmFzYW4gZW4gYWdydXBhciBwaXhlbGVzIHF1ZSBwb3NlZW4gcmVnaW9uZXMgZGVsIGVzcGVjdHJvIHNpbWlsYXJlcyB5IHF1ZSBwdWVkZW4gYXNvY2lhcnNlIGNvbiBkaWZlcmVudGVzIGNsYXNlcyBkZSBjb2JlcnR1cmFzIGRlbCBzdWVsby4gTGEgY2xhc2lmaWNhY2nzbiBubyBzdXBlcnZpc2FkYSAoVUMpIHBlcm1pdGUgaWRlbnRpZmljYXIgZW50cmUgbGFzIGRpZmVyZW50ZXMgY29iZXJ0dXJhcyBiYXNhZG9zIGVuIGxhIGNsYXNpZmljYWNp824gZGUgbGFzIGlt4WdlbmVzIGRpZ2l0YWxlcyBtZWRpYW50ZSBlbCB1c28gZGUgYmFuZGFzIGRlbnRybyBkZSB1bmEgY2F0ZWdvcu1hIGVzcGVjdHJhbCBpZGVudGlmaWNhZGEsIGVuIHBpeGVsZXMgc2VsZWNjaW9uYWRvcywgcGFyYSBsdWVnbyBzZXIgYWdydXBhZG9zIHBvciBzdSBzaW1pbGFyaWRhZC4gRXN0YSB06WNuaWNhIGRlIGFncnVwYWNp824gY29tcHJpbWUgdW4gY29uanVudG8gZ3JhbmRlIGRlIGRhdG9zIGVuIHVuYSBlc2NhbGEgZGUgcGl4ZWwgY29uIHVuIG3tbmltbyBkZSB0cmFiYWpvIHBvciBwYXJ0ZSBkZWwgYW5hbGlzdGEuDQoNCkV4aXN0ZW4gdmFyaW9zIGFsZ29yaXRtb3MgcGFyYSByZWFsaXphciB1bmEgY2xhc2lmaWNhY2nzbiBubyBzdXBlcnZpc2FkYSwgZXN0b3MgYWxnb3JpdG1vcyBwZXJtaXRlbiBldmFsdWFyIGVsIG76bWVybyBhZGVjdWFkbyBkZSBhZ3J1cGFjaW9uZXMsIGxhcyBjdWFsZXMgcGVybWl0aXLhbiBvYnRlbmVyIGxhIGluZm9ybWFjafNuIHJlcXVlcmlkYS4gSy1tZWFucywgaWRlbnRpZmljYSBlbCBjZW50cm9pZGUgeSBzdSB2YWxvciBwYXJhIGNhZGEgZ3J1cG8gZGl2aWRpZW5kbyBlbCBlc3BhY2lvIGVudHJlIGxhcyBjZWxkYXMsIGlkZW50aWZpY2FuZG8gYSBjYWRhIG9ic2VydmFjafNuIGNvbW8gdW5hIPpuaWNhIGFncnVwYWNp824uIEVsIGFsZ29yaXRtbyBDTEFSQSwgbm8gdG9tYSBlbiBjdWVudGEgdG9kbyBlbCBjb25qdW50byBkZSBkYXRvcywgeSBlbGlnZSBhbGVhdG9yaWFtZW50ZSB1bmEgcGVxdWXxYSBtdWV0c3JhLiANCg0KRWwgc2lndWllbnRlIHR1dG9yaWFsIHJlYWxpemEgZXMgdW4gZWplbXBsbyBkZSBjbGFzaWZpY2FjafNuIG5vIHN1cGVydmlzYWRhIHV0aWxpemFuZG8gMiBhbGdvcml0bW9zOiBLLW1lYW5zIHkgQ0xBUkEsIEVsIGVqZW1wbG8gZXN0YSBiYXNhZG8gZW4gZWwgc2NyaXB0IHJlYWxpemFkbyBwb3IgIEpv428gR29u52FsdmVzLCB5IHNlIHB1ZWRlIGVuY29udHJhciBlbiBlbCBzaWd1aWVudGUgbGluayBodHRwczovL3d3dy5yLWV4ZXJjaXNlcy5jb20vMjAxOC8wMi8yOC9hZHZhbmNlZC10ZWNobmlxdWVzLXdpdGgtcmFzdGVyLWRhdGEtcGFydC0xLXVuc3VwZXJ2aXNlZC1jbGFzc2lmaWNhdGlvbi8NCg0KDQoNClByZXBhcmFjafNuIGRlIGRhdG9zOiBMbyBwcmltZXJvIHF1ZSByZWFsaXphbW9zIGVzIHRyYW5zZm9ybWFyIGxvcyBuaXZlbGVzIGRpZ2l0YWxlcyBhIHZhbG9yZXMgZGUgcmVmbGVjdGFuY2lhIHV0aWxpemFuZG8gbGEgZnVuY2lvbiAgPmxhbmRzYXQ4PC4gRGVzcHVlcyBkZSBjb3J0YXIgbGFzIGJhbmRhcyBjb24gbGEgem9uYSBkZSBlc3R1ZGlvIHZpc3VhbGl6YW1vcyBlbiBkaWZlcmVudGVzIGNvbWJpbmFjaW9uZXMgZGUgYmFuZGFzLiBWaXN1YWxtZW50ZSBzZSBwdWVkZSBpZGVudGlmaWNhciBjb2JlcnR1cmFzIGNvbW86IFDhcmFtbywgdmVnZXRhY2nzbiBhcmJ1c3RpdmEsIGJvc3F1ZSBuYXRpdm8sIGluZnJhZXN0cnVjdHVyYSwgcGFzdGl6YWxlcywgY3VsdGl2b3MsIHZlZ2V0YWNp824gaGVyYmFjZWEuIA0KDQpgYGB7cn0NCiMjIGNhcmdhciBsaWJyZXJpYXMNCmxpYnJhcnkocmFzdGVyKQ0KbGlicmFyeShyZ2RhbCkNCmxpYnJhcnkocmdkYWwpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHNwKQ0KbGlicmFyeShyZ2VvcykNCmxpYnJhcnkobGFuZHNhdDgpDQpsaWJyYXJ5KGNsdXN0ZXIpDQoNCiNVYmljYWNpb24gZGUgY2FycGV0YQ0Kc2V0d2QoIkQ6L21hZXN0cmlhL3BlcmNlcGNpb24gcmVtb3RhIGF2YW56YWRhL3RhbGxlcjEvZGF0b3MiKQ0KZ2V0d2QoKQ0KDQpiYW5kMSA8LSByYXN0ZXIoIkxDMDhfTDFUUF8wMTAwNjJfMjAxNjExMjBfMjAxNzAzMThfMDFfVDFfQjEuVElGIikNCmJhbmQyIDwtIHJhc3RlcigiTEMwOF9MMVRQXzAxMDA2Ml8yMDE2MTEyMF8yMDE3MDMxOF8wMV9UMV9CMi5USUYiKQ0KYmFuZDMgPC0gcmFzdGVyKCJMQzA4X0wxVFBfMDEwMDYyXzIwMTYxMTIwXzIwMTcwMzE4XzAxX1QxX0IzLlRJRiIpDQpiYW5kNCA8LSByYXN0ZXIoIkxDMDhfTDFUUF8wMTAwNjJfMjAxNjExMjBfMjAxNzAzMThfMDFfVDFfQjQuVElGIikNCmJhbmQ1IDwtIHJhc3RlcigiTEMwOF9MMVRQXzAxMDA2Ml8yMDE2MTEyMF8yMDE3MDMxOF8wMV9UMV9CNS5USUYiKQ0KYmFuZDYgPC0gcmFzdGVyKCJMQzA4X0wxVFBfMDEwMDYyXzIwMTYxMTIwXzIwMTcwMzE4XzAxX1QxX0I2LlRJRiIpDQpiYW5kNyA8LSByYXN0ZXIoIkxDMDhfTDFUUF8wMTAwNjJfMjAxNjExMjBfMjAxNzAzMThfMDFfVDFfQjcuVElGIikNCg0KcGF1dGUgPC0gcmVhZE9HUigicGF1dGUuc2hwIikNCg0KDQojdHJhbnNmb3JtYXIgYSB2YWxvcmVzIGRlICByZWZsZWN0YW5jaWEgDQojdmFsb3JlcyBkZSByZWZsZWN0YW5jaWEgICAgICByZWZsY29udlMoeCwgTXAsIEFwLCBzdW5lbGV2KQ0KI1NVTl9FTEVWQVRJT04gPSA2MS4zMTg1NDkzNCANCiNSRUZMRUNUQU5DRV9NVUxUX0JBTkRfMSA9IDIuMDAwMEUtMDUNCiNSRUZMRUNUQU5DRV9BRERfQkFORF8xID0gLTAuMTAwMDAwDQoNCmJhbmQxLmRuPC0gYXMoYmFuZDEsICdTcGF0aWFsR3JpZERhdGFGcmFtZScpDQpiYW5kMi5kbjwtIGFzKGJhbmQyLCAnU3BhdGlhbEdyaWREYXRhRnJhbWUnKQ0KYmFuZDMuZG48LSBhcyhiYW5kMywgJ1NwYXRpYWxHcmlkRGF0YUZyYW1lJykNCmJhbmQ0LmRuPC0gYXMoYmFuZDQsICdTcGF0aWFsR3JpZERhdGFGcmFtZScpDQpiYW5kNS5kbjwtIGFzKGJhbmQ1LCAnU3BhdGlhbEdyaWREYXRhRnJhbWUnKQ0KYmFuZDYuZG48LSBhcyhiYW5kNiwgJ1NwYXRpYWxHcmlkRGF0YUZyYW1lJykNCmJhbmQ3LmRuPC0gYXMoYmFuZDcsICdTcGF0aWFsR3JpZERhdGFGcmFtZScpDQoNCmIxLnJlZiA8LSByZWZsY29udlMoYmFuZDEuZG4sMi4wMDAwRS0wNSAsLTAuMTAwMDAwICwgNjEuMzE4NTQ5MzQpDQpiMi5yZWYgPC0gcmVmbGNvbnZTKGJhbmQyLmRuLDIuMDAwMEUtMDUgLC0wLjEwMDAwMCAsIDYxLjMxODU0OTM0KQ0KYjMucmVmIDwtIHJlZmxjb252UyhiYW5kMy5kbiwyLjAwMDBFLTA1ICwtMC4xMDAwMDAgLCA2MS4zMTg1NDkzNCkNCmI0LnJlZiA8LSByZWZsY29udlMoYmFuZDQuZG4sMi4wMDAwRS0wNSAsLTAuMTAwMDAwICwgNjEuMzE4NTQ5MzQpDQpiNS5yZWYgPC0gcmVmbGNvbnZTKGJhbmQ1LmRuLDIuMDAwMEUtMDUgLC0wLjEwMDAwMCAsIDYxLjMxODU0OTM0KQ0KYjYucmVmIDwtIHJlZmxjb252UyhiYW5kNi5kbiwyLjAwMDBFLTA1ICwtMC4xMDAwMDAgLCA2MS4zMTg1NDkzNCkNCmI3LnJlZiA8LSByZWZsY29udlMoYmFuZDcuZG4sMi4wMDAwRS0wNSAsLTAuMTAwMDAwICwgNjEuMzE4NTQ5MzQpDQoNCmIxLnJlZiA8LSBhcyhiMS5yZWYsJ1Jhc3RlckxheWVyJykNCmIyLnJlZiA8LSBhcyhiMi5yZWYsJ1Jhc3RlckxheWVyJykNCmIzLnJlZiA8LSBhcyhiMy5yZWYsJ1Jhc3RlckxheWVyJykNCmI0LnJlZiA8LSBhcyhiNC5yZWYsJ1Jhc3RlckxheWVyJykNCmI1LnJlZiA8LSBhcyhiNS5yZWYsJ1Jhc3RlckxheWVyJykNCmI2LnJlZiA8LSBhcyhiNi5yZWYsJ1Jhc3RlckxheWVyJykNCmI3LnJlZiA8LSBhcyhiNy5yZWYsJ1Jhc3RlckxheWVyJykNCg0KI3VuaW1vcyBsYXMgYmFuZGFzDQojY2FtYmlhbW9zIGxvcyBub21icmVzIGEgbGFzIGJhbmRhcw0KDQpiYW5kYXMgPC0gYnJpY2soYjEucmVmLGIyLnJlZixiMy5yZWYsYjQucmVmLGI1LnJlZixiNi5yZWYsYjcucmVmKQ0KbmFtZXMoYmFuZGFzKSA8LSBwYXN0ZSgiQmFuZGEiLDE6NyxzZXA9IiIpIA0KcGxvdChiYW5kYXMpDQoNCiNjb3J0YXIgY29uIGxhIHpvbmEgZGUgZXN0dWRpbw0KcGF1dGUxN248LXNwVHJhbnNmb3JtKHBhdXRlLCBDUlM9Y3JzKGJhbmQxKSkNCmJhbmRhcyA8LSBjcm9wKGJhbmRhcywgZXh0ZW50KHBhdXRlMTduKSkNCmJhbmRhcyA8LSBtYXNrKGJhbmRhcyxwYXV0ZTE3bikNCnBsb3RSR0IoYmFuZGFzLCByPTQsIGc9MywgYj0yLCBzdHJldGNoPSJoaXN0IixzY2FsZT0yMDAwKSAjQ29sb3IgbmF0dXJhbA0KcGxvdFJHQihiYW5kYXMsIHI9NSwgZz00LCBiPTMsIHN0cmV0Y2g9Imhpc3QiLHNjYWxlPTIwMDApICNjb2xvciBpbmZyYXJyb2pvDQpwbG90UkdCKGJhbmRhcywgcj01LCBnPTYsIGI9Miwgc3RyZXRjaD0iaGlzdCIsc2NhbGU9MjAwMCkgI3ZlZ2V0YWNpb24gc2FsdWRhYmxlDQpgYGANCg0KUmVhbGl6YW1vcyBlbCBhZ3J1cGFtaWVudG8gZGUgbG9zIGRhdG9zIGRlIGxhcyBiYW5kYXMgY29uIGxvcyBhbGdvcml0bW9zIEstbWVhbiB5IEtMQVJBLCBwYXJhIGxvcyA4IGNs+nN0ZXJlcy4NCg0KYGBge3J9DQpiYW5kYXNfZGYgPC0gdmFsdWVzKGJhbmRhcykNCg0KIyBDaGVxdWVhciBsb3MgTkG0cyBlbiBsb3MgZGF0b3MNCmlkeCA8LSBjb21wbGV0ZS5jYXNlcyhiYW5kYXNfZGYpDQoNCiMgSW5pY2lhciBsb3MgZGF0YXNldHMgcuFzdGVyIHF1ZSBjb250ZW5kcuFuIHRvZGFzIGxhcyBzb2x1Y2lvbmVzIGRlIGNs+nN0ZXINCiMgMiBncnVwb3MgLyAgOCBjbHVzdGVyZXMNCmJhbmRhc0tNIDwtIHJhc3RlcihiYW5kYXNbWzFdXSkNCmJhbmRhc0NMQVJBIDwtIHJhc3RlcihiYW5kYXNbWzFdXSkNCg0KDQpmb3IobkNsdXN0IGluIDI6OCl7DQogIA0KICBjYXQoIi0+IENsdXN0ZXJpbmcgZGF0YSBmb3IgbkNsdXN0ID0iLG5DbHVzdCwiLi4uLi4uIikNCiAgDQogICMgUmVhbGl6YXIgY2x1c3RlcmluZyBLLW1lYW5zDQogIGttIDwtIGttZWFucyhiYW5kYXNfZGZbaWR4LF0sIGNlbnRlcnMgPSBuQ2x1c3QsIGl0ZXIubWF4ID0gNTApDQogIA0KICAjIFJlYWxpemFyIGNsYXJhIGNsdXN0ZXJpbmcgdXNhbmRvIGRpc3RhbmNpYSBNYW5oYXR0YW4gDQogIGNsYSA8LSBjbGFyYShiYW5kYXNfZGZbaWR4LCBdLCBrID0gbkNsdXN0LCBtZXRyaWMgPSAibWFuaGF0dGFuIikNCiAgDQogICMgQ3JlYXIgdW4gdmVjdG9yIGVudGVybyB0ZW1wb3JhbCBwYXJhIG1hbnRlbmVyIGxvcyBu+m1lcm9zIGRlbCBjbPpzdGVyDQogIGttQ2x1c3QgPC0gdmVjdG9yKG1vZGUgPSAiaW50ZWdlciIsIGxlbmd0aCA9IG5jZWxsKGJhbmRhcykpDQogIGNsYUNsdXN0IDwtIHZlY3Rvcihtb2RlID0gImludGVnZXIiLCBsZW5ndGggPSBuY2VsbChiYW5kYXMpKQ0KICANCiAgIyBHZW5lcmFyIHVuIHZlY3RvciBkZSBhZ3J1cGFtaWVudG8gdGVtcG9yYWwgcGFyYSBLLW1lYW5zDQogIGttQ2x1c3RbIWlkeF0gPC0gTkENCiAga21DbHVzdFtpZHhdIDwtIGttJGNsdXN0ZXINCiAgDQogICMgR2VuZXJhciB1biB2ZWN0b3IgZGUgYWdydXBhbWllbnRvIHRlbXBvcmFsIHBhcmEgQ0xBUkENCiAgY2xhQ2x1c3RbIWlkeF0gPC0gTkENCiAgY2xhQ2x1c3RbaWR4XSA8LSBjbGEkY2x1c3RlcmluZw0KICANCiAgIyBDcmVhciB1biBy4XN0ZXIgdGVtcG9yYWwgcGFyYSBtYW50ZW5lciBsYSBudWV2YSBzb2x1Y2nzbiBkZSBjbPpzdGVyDQogICMgSy1tZWFucw0KICB0bXBiYW5kYXNLTSA8LSByYXN0ZXIoYmFuZGFzW1sxXV0pDQogICMgQ0xBUkENCiAgdG1wYmFuZGFzQ0xBUkEgPC0gcmFzdGVyKGJhbmRhc1tbMV1dKQ0KICANCiAgIyBFc3RhYmxlY2VyIHZhbG9yZXMgcuFzdGVyIGNvbiBlbCB2ZWN0b3IgZGVMIGNs+nN0ZXINCiAgIyBLLW1lYW5zDQogIHZhbHVlcyh0bXBiYW5kYXNLTSkgPC0ga21DbHVzdA0KICAjIENMQVJBDQogIHZhbHVlcyh0bXBiYW5kYXNDTEFSQSkgPC0gY2xhQ2x1c3QNCiAgDQogICMgVW5pciBsb3MgcuFzdGVyZXMgdGVtcG9yYWxlcyBlbiBsb3MgZmluYWxlcw0KICBpZihuQ2x1c3Q9PTIpew0KICAgIGJhbmRhc0tNICAgIDwtIHRtcGJhbmRhc0tNDQogICAgYmFuZGFzQ0xBUkEgPC0gdG1wYmFuZGFzQ0xBUkENCiAgfWVsc2V7DQogICAgYmFuZGFzS00gICAgPC0gc3RhY2soYmFuZGFzS00sIHRtcGJhbmRhc0tNKQ0KICAgIGJhbmRhc0NMQVJBIDwtIHN0YWNrKGJhbmRhc0NMQVJBLCB0bXBiYW5kYXNDTEFSQSkNCiAgfQ0KICANCiAgY2F0KCIgaGVjaG8gPSlcblxuIikNCn0NCg0Kd3JpdGVSYXN0ZXIoYmFuZGFzS00sICJrbWVhbnMudGlmIiwgb3ZlcndyaXRlPVRSVUUpDQp3cml0ZVJhc3RlcihiYW5kYXNDTEFSQSwgIkNsYXJhLnRpZiIsIG92ZXJ3cml0ZT1UUlVFKQ0KYGBgDQoNCg0KUGFyYSBldmFsdWFyIGVsIHJlbmRpbWllbnRvIGRlIGNhZGEgc29sdWNp824gZGUgY2z6c3RlciBzZSB1dGlsaXrzIGVsIO1uZGljZSBzaWxob3VldHRlLCBlbCBjdWFsIGVzIHVuIG3pdG9kbyBkZSBpbnRlcnByZXRhciB5IHZhbGlkYXIgbGFzIGFncnVwYWNpb25lcyBkZSBkYXRvcywgcHJvcG9yY2lvbmEgdW5hIHJlcHJlc2VudGFjafNuIGdy4WZpY2EgcXVlIHJlcHJlc2VudGEgY/NtbyAgb2JqZXRvIGFncnVwYWRvIHNlIGVuY3VlbnRyYSBkZW50cm8gZGUgc3UgZ3J1cG8sIGFkZW3hcyBlbCB2YWxvciBkZSBsYSBzaWx1ZXRhIGVzIHVuYSBtZWRpZGEgcXVlIG11ZXN0cmEgcXVlIHRhbiBzaW1pbGFyIGVzIHVuIG9iamV0byBwYXJhIHN1IHByb3BpbyBjbPpzdGVyIGVuIGNvbXBhcmFjafNuIGNvbiBvdHJvcy4NCg0KRXN0ZSBpbmRpY2UgdmFy7WEgZGUgLTEgYSArMSwgZG9uZGUgdW4gdmFsb3IgYWx0byBpbmRpY2EgcXVlIGVsIG9iamV0byBlc3ThIGJpZW4gYWRhcHRhZG8gYSBzdSBwcm9waW8gY2z6c3RlciB5IG5vIGEgbG9zIHZlY2lub3MuIA0KQ3VhbmRvIGxhIG1heW9yaWEgZGUgcGl4ZWxlcyB0aWVuZW4gdW4gdmFsb3IgYWx0bywgZW50b25jZXMgbGEgY29uZmlndXJhY2nzbiBkZSBhZ3J1cGFtaWVudG8gc2UgY29uc2lkZXJhIGFwcm9waWFkYS4gU2kgbXVjaG9zIHB1bnRvcyB0aWVuZW4gdW4gdmFsb3IgYmFqbyBvIG5lZ2F0aXZvLCBlcyBuZWNlc2FyaW8gZXZhbHVhciBlbCBudW1lcm8gZGUgY2x1c3RlcnMgdXRpbGl6YWRvcy4gUGFyYSByZWFsaXphciBlc3RlIGFuYWxpc2lzIGVsIHByZ3JhbWEgUiBjdWVudGEgY29uIGVsIHBhcXVldGUgPmNsdXN0ZXJDcml0PC4NCg0KRW4gZXN0ZSBlamVtcGxvIHRvbWFtb3MgbGFzIHNpZ3VpZW50ZXMgZXNwZWNpZmljYWNpb25lcw0KDQogIC0gU2UgdXRpbGl68yB1biBtdWVzdHJlbyBhbGVhdG9yaW8gZXN0cmF0aWZpY2FkbyBkZWJpZG8gYSBxdWUgc3UgY2FsY3VsbyBlcyB1biBwcm9jZXNvIGJhc3RhbnRlIGxlbnRvIHBvciBlbCBudW1lcm8gZGUgb2JzZXJ2YWNpb25lcy4NCiAgDQogIC0gU2UgYXN1bWnzIHF1ZSBsYSBtdWVzdHJhIGVzIHJvYnVzdGEgeSByZXByZXNlbnRhdGl2YQ0KDQogIC0gU2UgdXPzIHVuYSBzb2xhIG11ZXN0cmEgZGUgY2VsZGFzDQogIA0KYGBge3J9DQpsaWJyYXJ5KGNsdXN0ZXJDcml0KQ0KDQpjbHVzdFBlcmZTSSA8LSBkYXRhLmZyYW1lKG5DbHVzdCA9IDI6OCwgU0lfS00gPSBOQSwgU0lfQ0xBUkEgPSBOQSkNCg0KZm9yKGkgaW4gMTpubGF5ZXJzKGJhbmRhc0tNKSkgeyANCiAgDQogIGNhdCgiLT4gRXZhbHVhdGluZyBjbHVzdGVyaW5nIHBlcmZvcm1hbmNlIGZvciBuQ2x1c3QgPSIsKDI6OClbaV0sIi4uLi4uLiIpDQogIA0KICAjIEV4dHJhZXIgbXVlc3RyYXMgYWxlYXRvcmlhcyBlc3RyYXRpZmljYWRhcyBwb3IgY2z6c3Rlcg0KICBjZWxsSWR4X2JhbmRhc0tNIDwtIHNhbXBsZVN0cmF0aWZpZWQoYmFuZGFzS01bW2ldXSwgc2l6ZSA9IDIwMDApDQogIGNlbGxJZHhfYmFuZGFzQ0xBUkEgPC0gc2FtcGxlU3RyYXRpZmllZChiYW5kYXNDTEFSQVtbaV1dLCBzaXplID0gMjAwMCkNCiAgDQogICMgT2J0ZW5lciB2YWxvcmVzIGRlIGNlbGRhIGRlIGxhIE11ZXN0cmEgYWxlYXRvcmlhIGVzdHJhdGlmaWNhZGEgZGUgbGEgcuFzdGVyIA0KICBiYW5kYXNERlN0UlNfS00gPC0gYmFuZGFzX2RmW2NlbGxJZHhfYmFuZGFzS01bLDFdLCBdDQogIGJhbmRhc0RGU3RSU19DTEFSQSA8LSBiYW5kYXNfZGZbY2VsbElkeF9iYW5kYXNDTEFSQVssMV0sIF0NCiAgDQogIHdyaXRlUmFzdGVyKGJhbmRhc0tNLCJLbWVhbnMudGlmIiwgb3ZlcndyaXRlPVRSVUUpDQogIHdyaXRlUmFzdGVyKGJhbmRhc0NMQVJBLCJjbGFyYS50aWYiLCBvdmVyd3JpdGU9VFJVRSkNCiAgDQogIGJhbmRhc0RGU3RSU19LTVtdIDwtIHNhcHBseShiYW5kYXNERlN0UlNfS00sIGFzLm51bWVyaWMpDQogIGJhbmRhc0RGU3RSU19DTEFSQVtdIDwtIHNhcHBseShiYW5kYXNERlN0UlNfQ0xBUkEsIGFzLm51bWVyaWMpDQogIA0KICAjIENhbGN1bGFyIGVsIO1uZGljZSBkZSBTaWxob3VldHRlIGJhc2FkbyBlbiBtdWVzdHJhcyBwYXJhOg0KICAjICAgIA0KICAjIEstbWVhbnMNCiAgY2xDcml0S00gPC0gaW50Q3JpdGVyaWEodHJhaiA9IGJhbmRhc0RGU3RSU19LTSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHBhcnQgPSBhcy5pbnRlZ2VyKGNlbGxJZHhfYmFuZGFzS01bLDJdKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNyaXQgPSAiU2lsaG91ZXR0ZSIpDQogICMgYW5kIENMQVJBDQogIGNsQ3JpdENMQVJBIDwtIGludENyaXRlcmlhKHRyYWogPSBiYW5kYXNERlN0UlNfQ0xBUkEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJ0ID0gYXMuaW50ZWdlcihjZWxsSWR4X2JhbmRhc0NMQVJBWywyXSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjcml0ID0gIlNpbGhvdWV0dGUiKQ0KDQogIGNsdXN0UGVyZlNJW2ksICJTSV9LTSJdICAgIDwtIGNsQ3JpdEtNW1sxXV1bMV0NCiAgY2x1c3RQZXJmU0lbaSwgIlNJX0NMQVJBIl0gPC0gY2xDcml0Q0xBUkFbWzFdXVsxXQ0KICANCiAgY2F0KCIgaGVjaG8gPSlcblxuIikNCiAgDQp9DQoNCndyaXRlLmNzdihjbHVzdFBlcmZTSSwgZmlsZSA9ICIuL2RhdGEtcmF3L2NsdXN0UGVyZlNJLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQ0KYGBgDQpFbiBsYSB0YWJsYSBwb2RlbW9zIG9ic2VydmFyIHF1ZSBsb3MgdmFsb3JlcyBkZSBpbmRpY2Ugc2lsaG91ZXR0ZSBwYXJhIGxhcyBkb3MgY2xhc2lmaWNhY2lvbmVzIG5vIHN1cGVydmlzYWRhcyANCg0KYGBge3J9DQprbml0cjo6a2FibGUoY2x1c3RQZXJmU0ksIGRpZ2l0cyA9IDMsIGFsaWduID0gImMiLCANCiAgICAgICAgICAgICBjb2wubmFtZXMgPSBjKCIjY2x1c3RlcnMiLCJBdmcuIFNpbGhvdWV0dGUgKGstbWVhbnMpIiwiQXZnLiBTaWxob3VldHRlIChDTEFSQSkiKSkNCg0KYGBgDQoNCkNvbXBhcmFtb3MgbG9zIGRvcyBtZXRvZG9zIGRlIGNsYXNpZmljYWNp824NCmBgYHtyfQ0KcGxvdChjbHVzdFBlcmZTSVssMV0sIGNsdXN0UGVyZlNJWywyXSwgDQogICAgIHhsaW0gPSBjKDEsMTMpLCB5bGltID0gcmFuZ2UoY2x1c3RQZXJmU0lbLDI6M10pLCB0eXBlID0gIm4iLCANCiAgICAgeWxhYj0iQXZnLiBTaWxob3VldHRlIEluZGV4IiwgeGxhYj0iIyBvZiBjbHVzdGVycyIsDQogICAgIG1haW49IlNpbGhvdWV0dGUgaW5kZXggYnkgIyBvZiBjbHVzdGVycyIpDQoNCiMgUGxvdCBBdmcgU2lsaG91ZXR0ZSB2YWx1ZXMgYWNyb3NzICMgb2YgY2x1c3RlcnMgZm9yIEstbWVhbnMNCmxpbmVzKGNsdXN0UGVyZlNJWywxXSwgY2x1c3RQZXJmU0lbLDJdLCBjb2w9InJlZCIpDQojIFBsb3QgQXZnIFNpbGhvdWV0dGUgdmFsdWVzIGFjcm9zcyAjIG9mIGNsdXN0ZXJzIGZvciBDTEFSQQ0KbGluZXMoY2x1c3RQZXJmU0lbLDFdLCBjbHVzdFBlcmZTSVssM10sIGNvbD0iYmx1ZSIpDQoNCiMgR3JpZCBsaW5lcw0KYWJsaW5lKHYgPSAxOjEzLCBsdHk9MiwgY29sPSJsaWdodCBncmV5IikNCmFibGluZShoID0gc2VxKDAuMzAsMC40NCwwLjAyKSwgbHR5PTIsIGNvbD0ibGlnaHQgZ3JleSIpDQoNCmxlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQ9YygiSy1tZWFucyIsIkNMQVJBIiksIGNvbD1jKCJyZWQiLCJibHVlIiksIGx0eT0xLCBsd2Q9MSkNCmBgYA0KDQoNClBsb3RlYW1vcyBsYXMgbWVqb3JlcyBjbGFzaWZpY2FjaW9uZXMgZGUgY2FkYSBtb2RlbG8NCg0KDQpgYGB7cn0NCnBsb3QoYmFuZGFzS01bWzJdXSkNCnRpdGxlKCJLLW1lYW4gc29sdXRpb24gY2xhc2lmaWFjaW9uIikNCg0KcGxvdChiYW5kYXNDTEFSQVtbMl1dKQ0KdGl0bGUoIkNMQVJBIHNvbHV0aW9uIGNsYXNpZmlhY2lvbiIpDQoNCg0KYGBgDQoNCg0KDQojI1JFU1VMVEFET1MNCkxhIHRhYmxhIHkgbGEgZ3LhZmljYSBub3MgbXVlc3RyYSBxdWUgZWwgYWxnb3JpdG1vIEstbWVhbnMgc3VwZXJhIGFtcGxpYW1lbnRlIGVsIHJlbmRpbWllbnRvIGRlIGNsYXNpZmljYWNp824gYSBDTEFSQS4gU2luIGVtYmFyZ28gbG9zIGRvcyBjbGFzaWZpY2Fkb3JlcyBubyBtdWVzdHJhbiByZXN1bHRhZG9zIGlkb25lb3MgZGUgY2xhc2lmaWNhY2nzbiwgb2J0ZW5pZW5kbyB2YWxvcmVzIG1lbm9yZXMgYSAwLjM4IGRlIGluZGljZSBkZSBzaWx1ZXRhLiBwb3IgbG8gY3VhbCBlcyBpbXBvcnRhbnRlIGRldGVybWluYXIgY3VhbCBlcyBlbCBu+m1lcm8gZGUgY2x1c3RlciBpZGVhbCBwYXJhIGxhIGNsYXNpZmljYWNp824gZGUgbGEgem9uYSBkZSBlc3R1ZGlvLg0KQWRlbWFzIHRhbWJpZW4gc2UgZGViZXJpYSBldmFsdWFyIGVsIHRhbWHxbyBkZSBsYSBtdWVzdHJhIGFsZWF0b3JpYXMgZXN0cmF0aWZpY2FkYXMgcG9yIGNs+nN0ZXIsIGVuIGVzdGUgY2FzbyB1dGlsaXphbW9zIHVuIHZhbG9yIGRlIDIwMDAsIHNpbiBlbWJhcmdvIHNlIHBvZHJpYSBhdW1lbnRhciBwYXJhIGFuYWxpemFyIGxvcyByZXN1bGF0ZG9zLiBFbnRyZSBsYXMgcmF6b25lcyBkZWwgYmFqbyByZW5kaW1pZW50byBkZSBDbGFyYSwgcHVlZGUgZGViZXJzZSBhIHF1ZSBlc3RlIGFsZ29yaXRtbyB0cmFiYWphIGNvbiBzdWJjb25qdW50byBkZSBkYXRvcyB5IHBvZHJpYSBzZXIgbWVub3MgY2FwYXogZGUgZW5jb250cmFyIG1lam9yZXMgY2VudHJvcyBkZSBsb3MgY2x1c3Rlci4NCg0KIyNESVNDVVNJT05Fcw0KDQpBbCBzZXIgZXN0ZSBlamVyY2ljaW8gdW4gYW5hbGlzaXMgcHJlbGltaW5hciBkZSBjbGFzaWZpY2FjafNuLCBlcyBpbmRpc3BlbnNhYmxlIGV2YWx1YXIgdmFyaW9zIHBhcmFtZXRyb3MgZW4gbG9zIGNsYXNpZmljYWRvcmVzLCBlbnRyZSBlbGxvcyBlbCBudW1lcm8gZGUgY2x1c3RlcnMgeSBlbCBudW1lcm8gZGUgbXVlc3RyYXMgYWxlYXRvcmlhcywgcGFyYSBvYnRlbmVyIG1lam9yZXMgcmVzdWx0YWRvcy4NCg0KDQo=