This script extracts and compares reflectance spectra of a standard
reflectance target (Labsphere WCS-MC-020) from VISNIR hyperspectral
images acquired with three VISNIR hyperespectral imaging systems in
HSILab premises:
- Cubert Firefleye S185 SE
- Specim IQ
- Specim FX10
defaultW <- getOption("warn")
options(warn=-1)
#Paths
RSpectDir <- "/home/alobo/owncloudRSpect/RSpect"
datadirC <- "/home/alobo/WORK/HSI_Lab/Tests_Hypercams/LlumASD-KAISER_2019-01-23/Cubert_KAISER2-3_2019-01-23"
imadirC <- file.path(datadirC,"cue")
vecdirC <- file.path(datadirC,"QGIS/Polygons")
datadirIQ <- "/home/alobo/WORK/HSI_Lab/Tests_Hypercams/SpecimIQ/20220914/018"
imadirIQ <- file.path(datadirIQ,"results")
vecdirIQ <- file.path(datadirIQ,"QGIS/Polygons")
datadirFX10 <- "/home/alobo/WORK/HSI_Lab/Tests_Hypercams/SpecimFX10/FX10_Sant_Finx_big_2019-06-11_14-01-10/FX10/"
imadirFX10 <- file.path(datadirFX10,"capture")
vecdirFX10 <- file.path(datadirFX10,"QGIS/Polygons")
#libraries
library(reshape2, quietly = TRUE)
library(plyr, quietly = TRUE)
library(ggplot2, quietly = TRUE)
library(plotly, quietly = TRUE)
library(terra, quietly = TRUE)
library(tidyterra, quietly = TRUE)
library(gridExtra, quietly = TRUE)
library(directlabels, quietly = TRUE)
#Required information
load(file.path(RSpectDir,"IQbands.rda")) #Bands Specim IQ system
load(file.path(RSpectDir,"Cubertbands.rda")) #Bands Cubert system
load(file.path(RSpectDir,"FX10bands.rda")) #Bands Cubert system
load(file.path(RSpectDir,"RefMCAA01.rda"))#Labsphere WCS-MC-020
1. Input images
1.1 Cubert Reflectance image
We use the *.cue reflectance image that was creted by Cubert’s
program to export cub files that are written by Cubert software. We use
the original image, with no pansharpening.
Warning: [rast] unknown extent
class : SpatRaster
dimensions : 50, 50, 138 (nrow, ncol, nlyr)
resolution : 20, 20 (x, y)
extent : 0, 1000, -1000, 0 (xmin, xmax, ymin, ymax)
coord. ref. :
source(s) : memory
names : 450 nm, 454 nm, 458 nm, 462 nm, 466 nm, 470 nm, ...
min values : 0.0043, 0.0088, 0.0027, 0.0011, 0.0236, 0.0505, ...
max values : 0.9410, 6.5491, 6.5532, 1.0084, 0.9868, 0.9702, ...
1.2 Specim IQ Reflectance image
The reflectance image was calculated by the Specim IQ system itself
using the average value of the White reference The original hdr file was
manualy edited to include the mapinfo field as CRS information: map
info = {Arbitrary, 1, 1, 0 ,512,1, 1, 0, North}
class : SpatRaster
dimensions : 512, 512, 204 (nrow, ncol, nlyr)
resolution : 1, 1 (x, y)
extent : 0, 512, 0, 512 (xmin, xmax, ymin, ymax)
coord. ref. : Arbitrary
source : REFLECTANCE_018.dat
names : 397.32, 400.20, 403.09, 405.97, 408.85, 411.74, ...
min values : 0.1724138, 0.1351351, 0.09803922, 0.07894737, 0.05504587, 0.03921569, ...
max values : 1.2142857, 1.2162162, 1.15384614, 1.11842108, 1.11009169, 1.08496737, ...
We display all 3 RGB color composites with similar spectral
bands:
1.3 Specim FX10 Reflectance image
The reflectance image was calculated by the Specim IQ system itself
using the average value of the White reference The original hdr file was
manualy edited to include the mapinfo field as CRS information: map
info = {Arbitrary, 1, 1, 0 ,512,1, 1, 0, North}
class : SpatRaster
dimensions : 1173, 1024, 448 (nrow, ncol, nlyr)
resolution : 1, 1 (x, y)
extent : 0, 1024, -149, 1024 (xmin, xmax, ymin, ymax)
coord. ref. : Arbitrary
source : REFL_FX10_Sant_Finx_big_2019-06-11_14-01-10.raw
names : REFL_~-10_1, REFL_~-10_2, REFL_~-10_3, REFL_~-10_4, 402.24, 403.55, ...
min values : -0.36, -0.3913043, -0.28, -0.2222222, -0.2222222, -0.25, ...
max values : 2.00, 1.3333334, 1.50, 1.3750000, 1.3870968, 1.32, ...
1.4 Display reflectance images
We display all 3 RGB color composites with similar spectral
bands:
Cubert bands
| 138 |
138 |
Band138 |
998 |
| 42 |
42 |
Band042 |
614 |
| 26 |
26 |
Band026 |
550 |
| 5 |
5 |
Band005 |
466 |
IQ bands
| 203 |
Band203 |
1000.49 |
| 75 |
Band075 |
613.38 |
| 53 |
Band053 |
548.55 |
| 25 |
Band025 |
466.77 |
FX10 bands
| 445 |
445 |
Band445 |
1000.29 |
1.41 |
| 165 |
165 |
Band165 |
614.55 |
1.35 |
| 116 |
116 |
Band116 |
548.91 |
1.33 |
| 54 |
54 |
Band054 |
466.64 |
1.32 |
crs(s1) <- crs(s2) <- crs(s3) <- "" #geom_spatraster error otherwise
ggRad <- ggplot() +
geom_spatraster_rgb(data = stretch(subset(s1, subset=defbandsC), minq=0.0, maxq=1)) +
coord_fixed(ratio = 1) +
theme_void() + theme(plot.title = element_text(hjust = 0.5)) +
ggtitle(paste0("Cubert S185 SE \nBands ", paste(Cubertbands$Wavelength[defbandsC],collapse=", ")))
ggRad2 <- ggplot() +
geom_spatraster_rgb(data = stretch(subset(s2, subset=defbandsIQ), minq=0.0, maxq=1)) +
coord_fixed(ratio = 1) +
theme_void() + theme(plot.title = element_text(hjust = 0.5)) +
ggtitle(paste0("Specim IQ \nBands ", paste(IQbands$Wavelength[defbandsIQ],collapse=", ")))
ggRad3 <- ggplot() +
geom_spatraster_rgb(data = stretch(subset(s3, subset=defbandsFX10), minq=0.0, maxq=0.95)) +
coord_fixed(ratio = 1) +
theme_void() + theme(plot.title = element_text(hjust = 0.5)) +
ggtitle(paste0("Specim FX10 \nBands ", paste(FX10bands$Wavelength[defbandsFX10],collapse=", ")))
grid.arrange(ggRad, ggRad2, ggRad3, ncol=3)

2. Polygon vectors
Relevant parts of each reference target were interactively digitized
as polygons in QGIS and used to select spectra from the images.
options(warn=-1)
nompolyC <- "KAISER2-3.shp"
p1 <- vect(file.path(vecdirC,nompolyC))
#values(p)$Name <- c("White", "Ochre", "Ochre_800",
# "Red", "Red_800",
# "GreenTerra", "GreenTerra_800")
values(p1)$Name <- c("WCS-MC-020", "White")
nompolyIQ <- "018.shp"
p2 <- vect(file.path(vecdirIQ,nompolyIQ))
#values(p2)
nompolyFX10 <- "FX10_Sant_Finx_big_2019-06-11_14-01-10.shp"
p3 <- vect(file.path(vecdirFX10,nompolyFX10))
crs(s1) <- crs(p1)
plotRGB(stretch(s1[[defbandsC]], smin=0.0, smax=1),
main=Cubertbands[defbandsC,])
#lines(p1, xlim=c(50,200))
lines(p1)
text(p1,2,cex=0.9,font=2)

crs(s2) <- crs(p2)
plotRGB(stretch(s2[[defbandsIQ]], smin=0.0, smax=1),
main=IQbands[defbandsIQ,])
#lines(p2, xlim=c(50,200))
lines(p2)
text(p2,2,cex=0.9,font=2)

crs(s3) <- crs(p3)
plotRGB(stretch(s3[[defbandsFX10]], smin=0.0, smax=1),
main=IQbands[defbandsFX10,])
#lines(p2, xlim=c(50,200))
lines(p3)
text(p3,2,cex=0.9,font=2)

3. Object spectra
Using the polygons as templates, we extract the reflectance values
from the hyperspectral images.
3.1 Cubert
options(warn=-1)
refldat1.m <- terra::extract(s1, p1, fun=mean, na.rm=TRUE)
refldat1.sd <- terra::extract(s1, p1, fun=sd, na.rm=TRUE)
refldatm1 <- as.data.frame(t(refldat1.m[,-1]))
colnames(refldatm1) <- values(p1)$Name
refldatm1$Band <- Cubertbands$Band
refldatm1$Wavelength <- Cubertbands$Wavelength
#head(refldatm)
refldatsd1 <- as.data.frame(t(refldat1.sd[,-1]))
colnames(refldatsd1) <- values(p1)$Name
#head(refldatsd)
refldat1 <- melt(refldatm1,id.vars = c("Band", "Wavelength"))
#head(refldat)
names(refldat1)[3:4] <- c("Sample", "Reflectance")
a <- melt(refldatsd1,id.vars=NULL)
#head(a)
refldat1$sd <- a$value
refldat1$Source = "Cubert S185SE"
#head(refldat1)
3.2 Specim IQ
options(warn=-1)
refldat2.m <- terra::extract(s2, p2, fun=mean, na.rm=TRUE)
refldat2.sd <- terra::extract(s2, p2, fun=sd, na.rm=TRUE)
refldatm2 <- as.data.frame(t(refldat2.m[,-1]))
colnames(refldatm2) <- values(p2)$Name
refldatm2$Band <- IQbands$Band
refldatm2$Wavelength <- IQbands$Wavelength
#head(refldatm)
refldatsd2 <- as.data.frame(t(refldat2.sd[,-1]))
colnames(refldatsd2) <- values(p2)$Name
#head(refldatsd)
refldat2 <- melt(refldatm2,id.vars = c("Band", "Wavelength"))
#head(refldat2)
names(refldat2)[3:4] <- c("Sample", "Reflectance")
a <- melt(refldatsd2,id.vars=NULL)
#head(a)
refldat2$sd <- a$value
refldat2$Source = "Specim IQ"
#head(refldat2)
3.3 Specim FX10
options(warn=-1)
refldat3.m <- terra::extract(s3, p3, fun=mean, na.rm=TRUE)
refldat3.sd <- terra::extract(s3, p3, fun=sd, na.rm=TRUE)
refldatm3 <- as.data.frame(t(refldat3.m[,-1]))
colnames(refldatm3) <- values(p3)$Name
refldatm3$Band <- FX10bands$Band
refldatm3$Wavelength <- FX10bands$Wavelength
#head(refldatm)
refldatsd3 <- as.data.frame(t(refldat3.sd[,-1]))
colnames(refldatsd3) <- values(p3)$Name
#head(refldatsd)
refldat3 <- melt(refldatm3,id.vars = c("Band", "Wavelength"))
#head(refldat2)
names(refldat3)[3:4] <- c("Sample", "Reflectance")
a <- melt(refldatsd3,id.vars=NULL)
#head(a)
refldat3$sd <- a$value
refldat3$Source = "Specim FX10"
#head(refldat2)
3.4 Combine
We combine all 3 datasets and organize the certified spectra of the
Labsphere WCS MC-020 target in an equivalent dataset
refldat <- rbind(refldat1, refldat2, refldat3)
refldat$Reflectance[refldat$Reflectance>5] <- NA
a <- data.frame( Band=NA, Wavelength=RefMCAA01$Wavelength, Sample="WCS-MC-020",
Reflectance=RefMCAA01$Reflectance, sd=0.0, Source="Certified")
4. Plot reflectance spectra
We plot the spectra, including the certified spectra of the Labsphere
WCS MC-020 target:
options(warn=-1)
#Custom colors from Default colors
#Default colors
ggplotColours <- function(n = 6, h = c(0, 360) + 15){
if ((diff(h) %% 360) < 1) h[2] <- h[2] - 360/n
hcl(h = (seq(h[1], h[2], length = n)), c = 100, l = 65)
}
micolor <- ggplotColours(n=4)
#scales:::show_col(micolor)
#https://www.nceas.ucsb.edu/sites/default/files/2020-04/colorPaletteCheatsheet.pdf
micolor <- c("Certified"="black", "Cubert S185SE"= micolor[4],
"Specim FX10"=micolor[2], "Specim IQ" = micolor[3])
gg1 <- ggplot() +
#geom_line(data=a, aes(x=Wavelength, y=Reflectance), color="black", size=0.5) +
geom_point(aes(x=Wavelength, y=Reflectance, color=Source),size=0.5) +
geom_line(aes(x=Wavelength, y=Reflectance, color=Source)) +
geom_errorbar(aes(x=Wavelength,ymin=Reflectance-sd, ymax=Reflectance+sd, color=Source),
width=.001, alpha=0.5,
position=position_dodge(0.05)) +
xlab("Wavelength (nm)") +
xlim(c(400, 1000)) +
scale_color_manual(values=micolor) +
theme(legend.position = c(0.85, 0.22))
gg2 <- gg1 %+% rbind(refldat[refldat$Sample=="WCS-MC-020",],a) +
ggtitle("Comparison of HSI systems on Labsphere WCS-MC-020 target")
gg3 <- gg1 %+% refldat[refldat$Sample=="White",] +
ggtitle("Comparison of HSI systems on White target")
#gg1 + facet_wrap(~Sample,nrow=2)
ggplotly(gg2 + facet_wrap(~Sample,nrow=2) ,
tooltip=c("Band","Wavelength",Reflectance="Reflectance","Source"),
height=600, width=1200)
ggplotly(gg3 + facet_wrap(~Sample,nrow=2) ,
tooltip=c("Band","Wavelength",Reflectance="Reflectance","Source"),
height=600, width=1200)
LS0tCnRpdGxlOiAiQSBjb21wYXJpc29uIG9mIFZJU05JUiBIU0kgc3lzdGVtcyIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGZpZ19jYXB0aW9uOiBUUlVFCi0tLQoKYGBgez1odG1sfQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogIC50YWJsZSB7d2lkdGg6IDI1JTt9Cjwvc3R5bGU+CmBgYAotICAgW0FndXN0aW4uTG9ib1xAZ2VvM2Jjbi5jc2ljLmVzXShtYWlsdG86QWd1c3Rpbi5Mb2JvQGdlbzNiY24uY3NpYy5lcyl7LmVtYWlsfQotICAgMjAyMjExMDgKClRoaXMgc2NyaXB0IGV4dHJhY3RzIGFuZCBjb21wYXJlcyByZWZsZWN0YW5jZSBzcGVjdHJhIG9mIGEgc3RhbmRhcmQgcmVmbGVjdGFuY2UgdGFyZ2V0IChMYWJzcGhlcmUgV0NTLU1DLTAyMCkgZnJvbSBWSVNOSVIgaHlwZXJzcGVjdHJhbCBpbWFnZXMgYWNxdWlyZWQgd2l0aCB0aHJlZSBWSVNOSVIgaHlwZXJlc3BlY3RyYWwgaW1hZ2luZyBzeXN0ZW1zIGluIEhTSUxhYiBwcmVtaXNlczoKCiogQ3ViZXJ0IEZpcmVmbGV5ZSBTMTg1IFNFIAoqIFNwZWNpbSBJUSAgCiogU3BlY2ltIEZYMTAgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpkZWZhdWx0VyA8LSBnZXRPcHRpb24oIndhcm4iKQpvcHRpb25zKHdhcm49LTEpCiNQYXRocwpSU3BlY3REaXIgICAgICAgPC0gIi9ob21lL2Fsb2JvL293bmNsb3VkUlNwZWN0L1JTcGVjdCIKZGF0YWRpckMgICAgICAgICA8LSAiL2hvbWUvYWxvYm8vV09SSy9IU0lfTGFiL1Rlc3RzX0h5cGVyY2Ftcy9MbHVtQVNELUtBSVNFUl8yMDE5LTAxLTIzL0N1YmVydF9LQUlTRVIyLTNfMjAxOS0wMS0yMyIKaW1hZGlyQyAgICAgICAgICA8LSBmaWxlLnBhdGgoZGF0YWRpckMsImN1ZSIpCnZlY2RpckMgICAgICAgICAgPC0gZmlsZS5wYXRoKGRhdGFkaXJDLCJRR0lTL1BvbHlnb25zIikKZGF0YWRpcklRICAgICAgICAgPC0gIi9ob21lL2Fsb2JvL1dPUksvSFNJX0xhYi9UZXN0c19IeXBlcmNhbXMvU3BlY2ltSVEvMjAyMjA5MTQvMDE4IgppbWFkaXJJUSAgICAgICAgICA8LSBmaWxlLnBhdGgoZGF0YWRpcklRLCJyZXN1bHRzIikKdmVjZGlySVEgICAgICAgICAgPC0gZmlsZS5wYXRoKGRhdGFkaXJJUSwiUUdJUy9Qb2x5Z29ucyIpCmRhdGFkaXJGWDEwICAgICAgICAgPC0gIi9ob21lL2Fsb2JvL1dPUksvSFNJX0xhYi9UZXN0c19IeXBlcmNhbXMvU3BlY2ltRlgxMC9GWDEwX1NhbnRfRmlueF9iaWdfMjAxOS0wNi0xMV8xNC0wMS0xMC9GWDEwLyIKaW1hZGlyRlgxMCAgICAgICAgICA8LSBmaWxlLnBhdGgoZGF0YWRpckZYMTAsImNhcHR1cmUiKQp2ZWNkaXJGWDEwICAgICAgICAgIDwtIGZpbGUucGF0aChkYXRhZGlyRlgxMCwiUUdJUy9Qb2x5Z29ucyIpCgojbGlicmFyaWVzCmxpYnJhcnkocmVzaGFwZTIsIHF1aWV0bHkgPSBUUlVFKQpsaWJyYXJ5KHBseXIsIHF1aWV0bHkgPSBUUlVFKQpsaWJyYXJ5KGdncGxvdDIsIHF1aWV0bHkgPSBUUlVFKQpsaWJyYXJ5KHBsb3RseSwgcXVpZXRseSA9IFRSVUUpCmxpYnJhcnkodGVycmEsIHF1aWV0bHkgPSBUUlVFKQpsaWJyYXJ5KHRpZHl0ZXJyYSwgcXVpZXRseSA9IFRSVUUpCmxpYnJhcnkoZ3JpZEV4dHJhLCBxdWlldGx5ID0gVFJVRSkKbGlicmFyeShkaXJlY3RsYWJlbHMsIHF1aWV0bHkgPSBUUlVFKQoKI1JlcXVpcmVkIGluZm9ybWF0aW9uCmxvYWQoZmlsZS5wYXRoKFJTcGVjdERpciwiSVFiYW5kcy5yZGEiKSkgI0JhbmRzIFNwZWNpbSBJUSBzeXN0ZW0KbG9hZChmaWxlLnBhdGgoUlNwZWN0RGlyLCJDdWJlcnRiYW5kcy5yZGEiKSkgI0JhbmRzIEN1YmVydCBzeXN0ZW0KbG9hZChmaWxlLnBhdGgoUlNwZWN0RGlyLCJGWDEwYmFuZHMucmRhIikpICNCYW5kcyBDdWJlcnQgc3lzdGVtCmxvYWQoZmlsZS5wYXRoKFJTcGVjdERpciwiUmVmTUNBQTAxLnJkYSIpKSNMYWJzcGhlcmUgV0NTLU1DLTAyMApgYGAKCiMjIDEuIElucHV0IGltYWdlcwojIyMgMS4xIEN1YmVydCBSZWZsZWN0YW5jZSBpbWFnZQpXZSB1c2UgdGhlICouY3VlIHJlZmxlY3RhbmNlIGltYWdlIHRoYXQgd2FzIGNyZXRlZCBieSBDdWJlcnQncyBwcm9ncmFtIHRvIGV4cG9ydCBjdWIgZmlsZXMgdGhhdCBhcmUgd3JpdHRlbiBieSBDdWJlcnQgc29mdHdhcmUuIFdlIHVzZSB0aGUgb3JpZ2luYWwgaW1hZ2UsIHdpdGggbm8gcGFuc2hhcnBlbmluZy4gCgpgYGB7ciBlY2hvPU9GRn0Kb3B0aW9ucyh3YXJuPS0xKQojbm9tcmVmQyA8LSAiS0FJU0VSMi0zMDAxUzBfUkVGcGFuc2hhcnAudGlmIgpub21yZWZDIDwtIktBSVNFUjItMzAwMVMwX1JFRi5jdWUiCnMxIDwtIHJhc3QoZmlsZS5wYXRoKGltYWRpckMsbm9tcmVmQykpCnMxIDwtIHMxLzEwMDAwICNyZWZsIGN1ZSB2YWx1ZXMgYXJlICoxMDAwMApleHQoczEpIDwtIGMoMCwxMDAwLC0xMDAwLDApICN0byBtYXRjaCBwYW5zaGFycGVuZWQgaW1hZ2UgYW5kIHBvbHlnb25zCnMxCmBgYAoKIyMjIDEuMiBTcGVjaW0gSVEgUmVmbGVjdGFuY2UgaW1hZ2UKVGhlIHJlZmxlY3RhbmNlIGltYWdlIHdhcyBjYWxjdWxhdGVkIGJ5IHRoZSBTcGVjaW0gSVEgc3lzdGVtIGl0c2VsZiB1c2luZyB0aGUgYXZlcmFnZSB2YWx1ZSBvZiB0aGUgV2hpdGUgcmVmZXJlbmNlClRoZSBvcmlnaW5hbCBoZHIgZmlsZSB3YXMgbWFudWFseSBlZGl0ZWQgdG8gaW5jbHVkZSB0aGUgbWFwaW5mbyBmaWVsZCBhcyBDUlMgaW5mb3JtYXRpb246CiptYXAgaW5mbyA9IHtBcmJpdHJhcnksIDEsIDEsIDAgLDUxMiwxLCAxLCAwLCBOb3J0aH0qCgpgYGB7ciBlY2hvPU9GRn0Kb3B0aW9ucyh3YXJuPS0xKQpub21yZWZJUSA8LSAiUkVGTEVDVEFOQ0VfMDE4LmRhdCIKczIgPC0gcmFzdChmaWxlLnBhdGgoaW1hZGlySVEsbm9tcmVmSVEpKQpzMgpgYGAKCldlIGRpc3BsYXkgYWxsIDMgUkdCIGNvbG9yIGNvbXBvc2l0ZXMgd2l0aCBzaW1pbGFyIHNwZWN0cmFsIGJhbmRzOgoKIyMjIDEuMyBTcGVjaW0gRlgxMCBSZWZsZWN0YW5jZSBpbWFnZQpUaGUgcmVmbGVjdGFuY2UgaW1hZ2Ugd2FzIGNhbGN1bGF0ZWQgYnkgdGhlIFNwZWNpbSBJUSBzeXN0ZW0gaXRzZWxmIHVzaW5nIHRoZSBhdmVyYWdlIHZhbHVlIG9mIHRoZSBXaGl0ZSByZWZlcmVuY2UKVGhlIG9yaWdpbmFsIGhkciBmaWxlIHdhcyBtYW51YWx5IGVkaXRlZCB0byBpbmNsdWRlIHRoZSBtYXBpbmZvIGZpZWxkIGFzIENSUyBpbmZvcm1hdGlvbjoKKm1hcCBpbmZvID0ge0FyYml0cmFyeSwgMSwgMSwgMCAsNTEyLDEsIDEsIDAsIE5vcnRofSoKCmBgYHtyIGVjaG89T0ZGfQpvcHRpb25zKHdhcm49LTEpCm5vbXJlZkZYMTAgPC0gIlJFRkxfRlgxMF9TYW50X0ZpbnhfYmlnXzIwMTktMDYtMTFfMTQtMDEtMTAucmF3IgpzMyA8LSByYXN0KGZpbGUucGF0aChpbWFkaXJGWDEwLG5vbXJlZkZYMTApKQpzMwpgYGAKIyMjIDEuNCBEaXNwbGF5IHJlZmxlY3RhbmNlIGltYWdlcwpXZSBkaXNwbGF5IGFsbCAzIFJHQiBjb2xvciBjb21wb3NpdGVzIHdpdGggc2ltaWxhciBzcGVjdHJhbCBiYW5kczoKCmBgYHtyIGVjaG89T0ZGLCByZXN1bHRzPSJhc2lzIn0KI3N0YW5kYXJkIHNSR0I6IDYxMiwgNTQ5LCA0NjUKICBkZWZiYW5kc0lRIDwtIGMoNzUsNTMsMjUpICAgI3NSR0IgYmFuZHMgSVEKICBkZWZiYW5kczJJUSA8LSBjKDIwMyw3NSw1MykgI0NJUiBiYW5kcyBJUQogIGRlZmJhbmRzRlgxMCA8LSBjKDE2NSwxMTYsNTQpICAgI3NSR0IgYmFuZHMgRlgxMAogIGRlZmJhbmRzMkZYMTAgPC0gYyg0NDUsIDE2NSwxMTYpICNDSVIgYmFuZHMgRlgxMAogIGRlZmJhbmRzQyA8LSBjKDQyLDI2LDUpICAgICAjc1JHQiBiYW5kcyBDdWJlcnQKICBkZWZiYW5kczJDIDwtIGMoMTM4LDQyLDI2KSAgI0NJUiBiYW5kcyBDdWJlcnQKCiAgY2F0KCJDdWJlcnQgYmFuZHMiKQogIGtuaXRyOjprYWJsZShDdWJlcnRiYW5kc1tjKGRlZmJhbmRzMkNbMV0sZGVmYmFuZHNDKSxdKQogIGNhdCgiSVEgYmFuZHMiKQogIGtuaXRyOjprYWJsZShJUWJhbmRzW2MoZGVmYmFuZHMySVFbMV0sZGVmYmFuZHNJUSksXSkKICBjYXQoIkZYMTAgYmFuZHMiKQogIGtuaXRyOjprYWJsZShGWDEwYmFuZHNbYyhkZWZiYW5kczJGWDEwWzFdLGRlZmJhbmRzRlgxMCksXSkKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQogIGNycyhzMSkgPC0gY3JzKHMyKSA8LSBjcnMoczMpIDwtICIiICNnZW9tX3NwYXRyYXN0ZXIgZXJyb3Igb3RoZXJ3aXNlCiAgZ2dSYWQgPC0gZ2dwbG90KCkgKyAKICBnZW9tX3NwYXRyYXN0ZXJfcmdiKGRhdGEgPSBzdHJldGNoKHN1YnNldChzMSwgc3Vic2V0PWRlZmJhbmRzQyksIG1pbnE9MC4wLCBtYXhxPTEpKSArCiAgICBjb29yZF9maXhlZChyYXRpbyA9IDEpICsKICAgIHRoZW1lX3ZvaWQoKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgICBnZ3RpdGxlKHBhc3RlMCgiQ3ViZXJ0IFMxODUgU0UgXG5CYW5kcyAiLCBwYXN0ZShDdWJlcnRiYW5kcyRXYXZlbGVuZ3RoW2RlZmJhbmRzQ10sY29sbGFwc2U9IiwgIikpKQogIGdnUmFkMiA8LSBnZ3Bsb3QoKSArIAogICAgZ2VvbV9zcGF0cmFzdGVyX3JnYihkYXRhID0gc3RyZXRjaChzdWJzZXQoczIsIHN1YnNldD1kZWZiYW5kc0lRKSwgbWlucT0wLjAsIG1heHE9MSkpICsKICAgIGNvb3JkX2ZpeGVkKHJhdGlvID0gMSkgKwogICAgdGhlbWVfdm9pZCgpICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpICsKICAgICBnZ3RpdGxlKHBhc3RlMCgiU3BlY2ltIElRIFxuQmFuZHMgIiwgcGFzdGUoSVFiYW5kcyRXYXZlbGVuZ3RoW2RlZmJhbmRzSVFdLGNvbGxhcHNlPSIsICIpKSkKICAgZ2dSYWQzIDwtIGdncGxvdCgpICsgCiAgICBnZW9tX3NwYXRyYXN0ZXJfcmdiKGRhdGEgPSBzdHJldGNoKHN1YnNldChzMywgc3Vic2V0PWRlZmJhbmRzRlgxMCksIG1pbnE9MC4wLCBtYXhxPTAuOTUpKSArCiAgICBjb29yZF9maXhlZChyYXRpbyA9IDEpICsKICAgIHRoZW1lX3ZvaWQoKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgICAgZ2d0aXRsZShwYXN0ZTAoIlNwZWNpbSBGWDEwIFxuQmFuZHMgIiwgcGFzdGUoRlgxMGJhbmRzJFdhdmVsZW5ndGhbZGVmYmFuZHNGWDEwXSxjb2xsYXBzZT0iLCAiKSkpCgogIGdyaWQuYXJyYW5nZShnZ1JhZCwgZ2dSYWQyLCBnZ1JhZDMsIG5jb2w9MykKYGBgCgojIyAyLiBQb2x5Z29uIHZlY3RvcnMKClJlbGV2YW50IHBhcnRzIG9mIGVhY2ggcmVmZXJlbmNlIHRhcmdldCB3ZXJlIGludGVyYWN0aXZlbHkgZGlnaXRpemVkIGFzIHBvbHlnb25zIGluIFFHSVMgYW5kIHVzZWQgdG8gc2VsZWN0IHNwZWN0cmEgZnJvbSB0aGUgaW1hZ2VzLgoKYGBge3IgfQpvcHRpb25zKHdhcm49LTEpCm5vbXBvbHlDIDwtICJLQUlTRVIyLTMuc2hwIgpwMSA8LSB2ZWN0KGZpbGUucGF0aCh2ZWNkaXJDLG5vbXBvbHlDKSkKI3ZhbHVlcyhwKSROYW1lIDwtIGMoIldoaXRlIiwgIk9jaHJlIiwgIk9jaHJlXzgwMCIsCiMgICAgICAgICAgICAgICAgICAgICJSZWQiLCAiUmVkXzgwMCIsCiMgICAgICAgICAgICAgICAgICAgICJHcmVlblRlcnJhIiwgIkdyZWVuVGVycmFfODAwIikKdmFsdWVzKHAxKSROYW1lIDwtIGMoIldDUy1NQy0wMjAiLCAiV2hpdGUiKQpub21wb2x5SVEgPC0gIjAxOC5zaHAiCnAyIDwtIHZlY3QoZmlsZS5wYXRoKHZlY2RpcklRLG5vbXBvbHlJUSkpCiN2YWx1ZXMocDIpCm5vbXBvbHlGWDEwIDwtICJGWDEwX1NhbnRfRmlueF9iaWdfMjAxOS0wNi0xMV8xNC0wMS0xMC5zaHAiCnAzIDwtIHZlY3QoZmlsZS5wYXRoKHZlY2RpckZYMTAsbm9tcG9seUZYMTApKQpgYGAKCmBgYHtyIH0KY3JzKHMxKSA8LSBjcnMocDEpCnBsb3RSR0Ioc3RyZXRjaChzMVtbZGVmYmFuZHNDXV0sIHNtaW49MC4wLCBzbWF4PTEpLAogICAgICAgIG1haW49Q3ViZXJ0YmFuZHNbZGVmYmFuZHNDLF0pCiNsaW5lcyhwMSwgeGxpbT1jKDUwLDIwMCkpCmxpbmVzKHAxKQp0ZXh0KHAxLDIsY2V4PTAuOSxmb250PTIpCmBgYApgYGB7ciB9CmNycyhzMikgPC0gY3JzKHAyKQpwbG90UkdCKHN0cmV0Y2goczJbW2RlZmJhbmRzSVFdXSwgc21pbj0wLjAsIHNtYXg9MSksCiAgICAgICAgbWFpbj1JUWJhbmRzW2RlZmJhbmRzSVEsXSkKI2xpbmVzKHAyLCB4bGltPWMoNTAsMjAwKSkKbGluZXMocDIpCnRleHQocDIsMixjZXg9MC45LGZvbnQ9MikKCmBgYAoKYGBge3IgfQpjcnMoczMpIDwtIGNycyhwMykKcGxvdFJHQihzdHJldGNoKHMzW1tkZWZiYW5kc0ZYMTBdXSwgc21pbj0wLjAsIHNtYXg9MSksCiAgICAgICAgbWFpbj1JUWJhbmRzW2RlZmJhbmRzRlgxMCxdKQojbGluZXMocDIsIHhsaW09Yyg1MCwyMDApKQpsaW5lcyhwMykKdGV4dChwMywyLGNleD0wLjksZm9udD0yKQpgYGAKCiMjIDMuIE9iamVjdCBzcGVjdHJhCgpVc2luZyB0aGUgcG9seWdvbnMgYXMgdGVtcGxhdGVzLCB3ZSBleHRyYWN0IHRoZSByZWZsZWN0YW5jZSB2YWx1ZXMgZnJvbSB0aGUgaHlwZXJzcGVjdHJhbCBpbWFnZXMuCgojIyMgMy4xIEN1YmVydCAKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kb3B0aW9ucyh3YXJuPS0xKQpyZWZsZGF0MS5tICA8LSB0ZXJyYTo6ZXh0cmFjdChzMSwgcDEsIGZ1bj1tZWFuLCBuYS5ybT1UUlVFKQpyZWZsZGF0MS5zZCA8LSB0ZXJyYTo6ZXh0cmFjdChzMSwgcDEsIGZ1bj1zZCwgbmEucm09VFJVRSkKCnJlZmxkYXRtMSA8LSBhcy5kYXRhLmZyYW1lKHQocmVmbGRhdDEubVssLTFdKSkKY29sbmFtZXMocmVmbGRhdG0xKSA8LSB2YWx1ZXMocDEpJE5hbWUKcmVmbGRhdG0xJEJhbmQgPC0gQ3ViZXJ0YmFuZHMkQmFuZApyZWZsZGF0bTEkV2F2ZWxlbmd0aCA8LSBDdWJlcnRiYW5kcyRXYXZlbGVuZ3RoCiNoZWFkKHJlZmxkYXRtKQpyZWZsZGF0c2QxIDwtIGFzLmRhdGEuZnJhbWUodChyZWZsZGF0MS5zZFssLTFdKSkKY29sbmFtZXMocmVmbGRhdHNkMSkgPC0gdmFsdWVzKHAxKSROYW1lCiNoZWFkKHJlZmxkYXRzZCkKCnJlZmxkYXQxIDwtIG1lbHQocmVmbGRhdG0xLGlkLnZhcnMgPSBjKCJCYW5kIiwgIldhdmVsZW5ndGgiKSkKI2hlYWQocmVmbGRhdCkKbmFtZXMocmVmbGRhdDEpWzM6NF0gPC0gYygiU2FtcGxlIiwgIlJlZmxlY3RhbmNlIikKYSA8LSBtZWx0KHJlZmxkYXRzZDEsaWQudmFycz1OVUxMKQojaGVhZChhKQpyZWZsZGF0MSRzZCA8LSBhJHZhbHVlCnJlZmxkYXQxJFNvdXJjZSA9ICJDdWJlcnQgUzE4NVNFIgojaGVhZChyZWZsZGF0MSkKYGBgCiMjIyAzLjIgU3BlY2ltIElRIApgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpvcHRpb25zKHdhcm49LTEpCnJlZmxkYXQyLm0gIDwtIHRlcnJhOjpleHRyYWN0KHMyLCBwMiwgZnVuPW1lYW4sIG5hLnJtPVRSVUUpCnJlZmxkYXQyLnNkIDwtIHRlcnJhOjpleHRyYWN0KHMyLCBwMiwgZnVuPXNkLCBuYS5ybT1UUlVFKQoKcmVmbGRhdG0yIDwtIGFzLmRhdGEuZnJhbWUodChyZWZsZGF0Mi5tWywtMV0pKQpjb2xuYW1lcyhyZWZsZGF0bTIpIDwtIHZhbHVlcyhwMikkTmFtZQpyZWZsZGF0bTIkQmFuZCA8LSBJUWJhbmRzJEJhbmQKcmVmbGRhdG0yJFdhdmVsZW5ndGggPC0gSVFiYW5kcyRXYXZlbGVuZ3RoCiNoZWFkKHJlZmxkYXRtKQpyZWZsZGF0c2QyIDwtIGFzLmRhdGEuZnJhbWUodChyZWZsZGF0Mi5zZFssLTFdKSkKY29sbmFtZXMocmVmbGRhdHNkMikgPC0gdmFsdWVzKHAyKSROYW1lCiNoZWFkKHJlZmxkYXRzZCkKCnJlZmxkYXQyIDwtIG1lbHQocmVmbGRhdG0yLGlkLnZhcnMgPSBjKCJCYW5kIiwgIldhdmVsZW5ndGgiKSkKI2hlYWQocmVmbGRhdDIpCm5hbWVzKHJlZmxkYXQyKVszOjRdIDwtIGMoIlNhbXBsZSIsICJSZWZsZWN0YW5jZSIpCmEgPC0gbWVsdChyZWZsZGF0c2QyLGlkLnZhcnM9TlVMTCkKI2hlYWQoYSkKcmVmbGRhdDIkc2QgPC0gYSR2YWx1ZQpyZWZsZGF0MiRTb3VyY2UgPSAiU3BlY2ltIElRIgojaGVhZChyZWZsZGF0MikKYGBgCgojIyMgMy4zIFNwZWNpbSBGWDEwCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm9wdGlvbnMod2Fybj0tMSkKcmVmbGRhdDMubSAgPC0gdGVycmE6OmV4dHJhY3QoczMsIHAzLCBmdW49bWVhbiwgbmEucm09VFJVRSkKcmVmbGRhdDMuc2QgPC0gdGVycmE6OmV4dHJhY3QoczMsIHAzLCBmdW49c2QsIG5hLnJtPVRSVUUpCgpyZWZsZGF0bTMgPC0gYXMuZGF0YS5mcmFtZSh0KHJlZmxkYXQzLm1bLC0xXSkpCmNvbG5hbWVzKHJlZmxkYXRtMykgPC0gdmFsdWVzKHAzKSROYW1lCnJlZmxkYXRtMyRCYW5kIDwtIEZYMTBiYW5kcyRCYW5kCnJlZmxkYXRtMyRXYXZlbGVuZ3RoIDwtIEZYMTBiYW5kcyRXYXZlbGVuZ3RoCiNoZWFkKHJlZmxkYXRtKQpyZWZsZGF0c2QzIDwtIGFzLmRhdGEuZnJhbWUodChyZWZsZGF0My5zZFssLTFdKSkKY29sbmFtZXMocmVmbGRhdHNkMykgPC0gdmFsdWVzKHAzKSROYW1lCiNoZWFkKHJlZmxkYXRzZCkKCnJlZmxkYXQzIDwtIG1lbHQocmVmbGRhdG0zLGlkLnZhcnMgPSBjKCJCYW5kIiwgIldhdmVsZW5ndGgiKSkKI2hlYWQocmVmbGRhdDIpCm5hbWVzKHJlZmxkYXQzKVszOjRdIDwtIGMoIlNhbXBsZSIsICJSZWZsZWN0YW5jZSIpCmEgPC0gbWVsdChyZWZsZGF0c2QzLGlkLnZhcnM9TlVMTCkKI2hlYWQoYSkKcmVmbGRhdDMkc2QgPC0gYSR2YWx1ZQpyZWZsZGF0MyRTb3VyY2UgPSAiU3BlY2ltIEZYMTAiCiNoZWFkKHJlZmxkYXQyKQpgYGAKCiMjIyAzLjQgQ29tYmluZQpXZSBjb21iaW5lIGFsbCAzIGRhdGFzZXRzIGFuZCBvcmdhbml6ZSB0aGUgY2VydGlmaWVkIHNwZWN0cmEgb2YgdGhlIExhYnNwaGVyZSBXQ1MgTUMtMDIwIHRhcmdldCBpbiBhbiBlcXVpdmFsZW50IGRhdGFzZXQKYGBge3IgfQpyZWZsZGF0IDwtIHJiaW5kKHJlZmxkYXQxLCByZWZsZGF0MiwgcmVmbGRhdDMpCnJlZmxkYXQkUmVmbGVjdGFuY2VbcmVmbGRhdCRSZWZsZWN0YW5jZT41XSA8LSBOQQphIDwtIGRhdGEuZnJhbWUoIEJhbmQ9TkEsIFdhdmVsZW5ndGg9UmVmTUNBQTAxJFdhdmVsZW5ndGgsIFNhbXBsZT0iV0NTLU1DLTAyMCIsIAogICAgICAgICAgICAgICAgIFJlZmxlY3RhbmNlPVJlZk1DQUEwMSRSZWZsZWN0YW5jZSwgc2Q9MC4wLCBTb3VyY2U9IkNlcnRpZmllZCIpCmBgYAojIyMgNC4gUGxvdCByZWZsZWN0YW5jZSBzcGVjdHJhCldlIHBsb3QgdGhlIHNwZWN0cmEsIGluY2x1ZGluZyB0aGUgY2VydGlmaWVkIHNwZWN0cmEgb2YgdGhlIExhYnNwaGVyZSBXQ1MgTUMtMDIwIHRhcmdldDoKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gOH0Kb3B0aW9ucyh3YXJuPS0xKQojQ3VzdG9tIGNvbG9ycyBmcm9tIERlZmF1bHQgY29sb3JzCiNEZWZhdWx0IGNvbG9ycwpnZ3Bsb3RDb2xvdXJzIDwtIGZ1bmN0aW9uKG4gPSA2LCBoID0gYygwLCAzNjApICsgMTUpewogIGlmICgoZGlmZihoKSAlJSAzNjApIDwgMSkgaFsyXSA8LSBoWzJdIC0gMzYwL24KICBoY2woaCA9IChzZXEoaFsxXSwgaFsyXSwgbGVuZ3RoID0gbikpLCBjID0gMTAwLCBsID0gNjUpCn0KCm1pY29sb3IgPC0gZ2dwbG90Q29sb3VycyhuPTQpCiNzY2FsZXM6OjpzaG93X2NvbChtaWNvbG9yKQojaHR0cHM6Ly93d3cubmNlYXMudWNzYi5lZHUvc2l0ZXMvZGVmYXVsdC9maWxlcy8yMDIwLTA0L2NvbG9yUGFsZXR0ZUNoZWF0c2hlZXQucGRmCm1pY29sb3IgPC0gYygiQ2VydGlmaWVkIj0iYmxhY2siLCAiQ3ViZXJ0IFMxODVTRSI9IG1pY29sb3JbNF0sIAogICAgICAgICAgICAgIlNwZWNpbSBGWDEwIj1taWNvbG9yWzJdLCAiU3BlY2ltIElRIiA9IG1pY29sb3JbM10pCgpnZzEgPC0gZ2dwbG90KCkgKwogICNnZW9tX2xpbmUoZGF0YT1hLCBhZXMoeD1XYXZlbGVuZ3RoLCB5PVJlZmxlY3RhbmNlKSwgY29sb3I9ImJsYWNrIiwgc2l6ZT0wLjUpICsKICBnZW9tX3BvaW50KGFlcyh4PVdhdmVsZW5ndGgsIHk9UmVmbGVjdGFuY2UsIGNvbG9yPVNvdXJjZSksc2l6ZT0wLjUpICsKICBnZW9tX2xpbmUoYWVzKHg9V2F2ZWxlbmd0aCwgeT1SZWZsZWN0YW5jZSwgY29sb3I9U291cmNlKSkgKwogIGdlb21fZXJyb3JiYXIoYWVzKHg9V2F2ZWxlbmd0aCx5bWluPVJlZmxlY3RhbmNlLXNkLCB5bWF4PVJlZmxlY3RhbmNlK3NkLCBjb2xvcj1Tb3VyY2UpLAogICAgICAgICAgICAgICAgd2lkdGg9LjAwMSwgYWxwaGE9MC41LAogICAgICAgICAgICAgICAgcG9zaXRpb249cG9zaXRpb25fZG9kZ2UoMC4wNSkpICsKICB4bGFiKCJXYXZlbGVuZ3RoIChubSkiKSArCiAgeGxpbShjKDQwMCwgMTAwMCkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPW1pY29sb3IpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuODUsIDAuMjIpKSAKCmdnMiA8LSBnZzEgJSslIHJiaW5kKHJlZmxkYXRbcmVmbGRhdCRTYW1wbGU9PSJXQ1MtTUMtMDIwIixdLGEpICsKICBnZ3RpdGxlKCJDb21wYXJpc29uIG9mIEhTSSBzeXN0ZW1zIG9uIExhYnNwaGVyZSBXQ1MtTUMtMDIwIHRhcmdldCIpCgpnZzMgPC0gZ2cxICUrJSByZWZsZGF0W3JlZmxkYXQkU2FtcGxlPT0iV2hpdGUiLF0gKwogIGdndGl0bGUoIkNvbXBhcmlzb24gb2YgSFNJIHN5c3RlbXMgb24gV2hpdGUgdGFyZ2V0IikKCiNnZzEgKyBmYWNldF93cmFwKH5TYW1wbGUsbnJvdz0yKSAKCmdncGxvdGx5KGdnMiArIGZhY2V0X3dyYXAoflNhbXBsZSxucm93PTIpICwKICAgICAgICAgdG9vbHRpcD1jKCJCYW5kIiwiV2F2ZWxlbmd0aCIsUmVmbGVjdGFuY2U9IlJlZmxlY3RhbmNlIiwiU291cmNlIiksCiAgICAgICAgIGhlaWdodD02MDAsIHdpZHRoPTEyMDApCmdncGxvdGx5KGdnMyArIGZhY2V0X3dyYXAoflNhbXBsZSxucm93PTIpICwKICAgICAgICAgdG9vbHRpcD1jKCJCYW5kIiwiV2F2ZWxlbmd0aCIsUmVmbGVjdGFuY2U9IlJlZmxlY3RhbmNlIiwiU291cmNlIiksCiAgICAgICAgIGhlaWdodD02MDAsIHdpZHRoPTEyMDApCmBgYAoK