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:

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

Bandnb Band Wavelength
138 138 Band138 998
42 42 Band042 614
26 26 Band026 550
5 5 Band005 466

IQ bands

Band Wavelength
203 Band203 1000.49
75 Band075 613.38
53 Band053 548.55
25 Band025 466.77

FX10 bands

Bandnb Band Wavelength fwhm
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