This script extracts reflectance spectra of 3 objects from a VISNIR hyperspectral image acquired with a demo unit of the Specim IQ system in HSILab premises on 20180918:

defaultW <- getOption("warn")
options(warn=-1)
#Paths
RSpectDir       <- "/home/alobo/owncloudRSpect/RSpect"
datadir         <- "/home/alobo/WORK/HSI_Lab/Tests_Hypercams/SpecimIQ/SpecimIQTest_2018-09-18/2018-10-18_013"
imadir          <- file.path(datadir,"results")
vecdir          <- file.path(datadir,"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

1. Read images and polygons

1.1 RGB image

A conventional RGB image in png format is automatically recorded by a dedicated CCD in Specim IQ. By some reason, this image is rotated vs. the hyperspectral one, so the code rotates it.

options(warn=-1)
r <- rast(file.path(imadir,"RGBBACKGROUND_2018-10-18_013.png"))
r
class       : SpatRaster 
dimensions  : 645, 645, 4  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 645, 0, 645  (xmin, xmax, ymin, ymax)
coord. ref. :  
source      : RGBBACKGROUND_2018-10-18_013.png 
names       : RGBBACK~8_013_1, RGBBACK~8_013_2, RGBBACK~8_013_3, RGBBACK~8_013_4 
#plotRGB(r, stretch="lin")
plotRGB(flip(t(r)))

1.2 Reflectance image

The reflectance image was calculated by the Specim IQ system itself using the average value of the White reference

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. :  
source      : REFLECTANCE_2018-10-18_013.dat 
names       :     397.32,   400.20,     403.09,     405.97,     408.85,     411.74, ... 
min values  : 0.09090909, 0.100000, 0.07894737, 0.07692308, 0.05797102, 0.04444445, ... 
max values  : 1.22727275, 1.172414, 1.18421054, 1.17647064, 1.17391300, 1.19780219, ... 

We display both RGB and CIR color composites with the following spectral bands:

RGB

Band Wavelength
70 Band070 598.60
53 Band053 548.55
19 Band019 449.35

CIR

Band Wavelength
203 Band203 1000.49
70 Band070 598.60
53 Band053 548.55
  crs(s) <- ""
  #suppressMessages(
  ggRad <- ggplot() + 
  geom_spatraster_rgb(data = stretch(subset(s, subset=defbands), minq=0.02, maxq=0.85)) +
    coord_fixed(ratio = 1) +
    theme_void() + theme(plot.title = element_text(hjust = 0.5)) +
    ggtitle(paste0("\nBands ", paste(defbands,collapse=", ")))
  ggRad2 <- ggplot() + 
    geom_spatraster_rgb(data = stretch(subset(s, subset=defbands2), minq=0.02, maxq=0.85)) +
    coord_fixed(ratio = 1) +
    theme_void() + theme(plot.title = element_text(hjust = 0.5)) +
    ggtitle(paste0("\nBands ", paste(defbands2,collapse=", ")))
  #)
  grid.arrange(ggRad, ggRad2, ncol=2)

1.3 Polygon vector

Significant parts of each object were interactively digitized as polygons in QGIS and used as input here.

options(warn=-1)
nompoly <- "013detailed.shp"
p <- vect(file.path(vecdir,nompoly))
p
 class       : SpatVector 
 geometry    : polygons 
 dimensions  : 15, 4  (geometries, attributes)
 extent      : 121.7782, 495.9103, -354.6019, -131.6486  (xmin, xmax, ymin, ymax)
 source      : 013detailed.shp
 coord. ref. : ETRS89 / UTM zone 31N (EPSG:25831) 
#knitr::kable(p)

2. Make consistent geometry and display

In case the CRS is not defined (and this is the unfortunate case of Specim hdr files), R and QGIS assume, by default, different geometry for the same tiff. As a consequence, polygons digitized in QGIS do not overlay the raster image in R. The code must account for this fact, making a consistent geometry for raster and vector layer, which we confirm in the display:

crs(s) <- crs(p)
ext(s) <- c(0,512,-512,0)
plotRGB(stretch(s[[defbands2]], smin=0.0, smax=0.85), ext=c(50,400,-360,-100))
#plotRGB(stretch(s[[defbands]], smin=0.0, smax=0.4), ext=c(50,400,-360,-100))
lines(p)
text(p,2,cex=0.9,font=2)

#lines(p,ext=c(50,400,-360,-100)) # "ext" is not a graphical parameter
#lines(ext(c(50,400,-360,-100)))

3. Extract spectra corresponding to polygons

Using the polygons as templates, we extract the reflectance values from the hyperspectral image.

options(warn=-1)
refldat.m  <- terra::extract(s, p, fun=mean, na.rm=TRUE)
refldat.sd <- terra::extract(s, p, fun=sd, na.rm=TRUE)

refldatm <- as.data.frame(t(refldat.m[,-1]))
colnames(refldatm) <- values(p)$Name
refldatm$Band <- IQbands$Band
refldatm$Wavelength <- IQbands$Wavelength
#head(refldatm)
refldatsd <- as.data.frame(t(refldat.sd[,-1]))
colnames(refldatsd) <- values(p)$Name
#head(refldatsd)

refldat <- melt(refldatm,id.vars = c("Band", "Wavelength"))
#head(refldat)
names(refldat)[3:4] <- c("Sample", "Reflectance")

a <- melt(refldatsd,id.vars=NULL)
#head(a)
refldat$sd <- a$value
refldat$State <- mapvalues(refldat$Sample, from=values(p)$Name, to=values(p)$State)
#head(refldat)

#gg1 <- plothcavspect(datainRef,title="")
#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=24)
#scales:::show_col(micolor)
micolor <- c("White"="grey50", 
             "h3"=micolor[6],
             "h4"=micolor[7],
             "h5"=micolor[8],
             "h6"=micolor[9],
             "h7"=micolor[10],
             "h1"=micolor[4],
             "h2"=micolor[5],
             "d5"=micolor[1],
             "d6"=micolor[2],
             "d7"=micolor[3],
             "d1"=micolor[21],
             "d2"=micolor[22],
             "d3"=micolor[23],
             "d4"=micolor[24])

gg1 <- ggplot(data=refldat) +
  geom_point(aes(x=Wavelength, y=Reflectance, color=Sample),size=0.5) +
  geom_line(aes(x=Wavelength, y=Reflectance, color=Sample)) +
  #geom_errorbar(aes(x=Wavelength,ymin=Reflectance-sd, ymax=Reflectance+sd, color=Sample),
  #              width=.01,
  #              position=position_dodge(0.05)) +
  geom_text(aes(x=1010, y=Reflectance,label=Sample)) +
  xlab("Wavelength (nm)") +
  theme(legend.position = "none") +
  scale_color_manual(values=micolor) +
  ggtitle("Lobularia amplissima")
#gg1 + xlim(c(450, 1000)) + ylim(c(0,0.9)) + 
#  facet_wrap(~State) + geom_dl(aes(x=Wavelength, y=Reflectance, label = Sample), method = list(dl.combine("first.points", "last.points"), cex = 0.7)) 
gg2 <- gg1 + xlim(c(450, 1000)) + ylim(c(0,0.9)) + facet_wrap(~State) +
  geom_dl(aes(x=Wavelength, y=Reflectance, label = Sample), method = list(dl.combine("first.points", "last.points"), cex = 0.7))
ggplotly(gg2,
         tooltip=c("Band","Wavelength",Reflectance="Reflectance","Sample"),
         height=600, width=800)
NA
LS0tCnRpdGxlOiAiU3BlY2ltIElRIHRlc3Qgd2l0aCBMb2JhcmlhIGFtcGxpc3NpbWEgKDIwMTgwOTE4KSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGZpZ19jYXB0aW9uOiBUUlVFCi0tLQoKPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KICAudGFibGUge3dpZHRoOiAyNSU7fQo8L3N0eWxlPgoKKiBBZ3VzdGluLkxvYm9AZ2VvM2Jjbi5jc2ljLmVzCiogMjAyMjEwMjAKClRoaXMgc2NyaXB0IGV4dHJhY3RzIHJlZmxlY3RhbmNlIHNwZWN0cmEgb2YgMyBvYmplY3RzIGZyb20gYSBWSVNOSVIgaHlwZXJzcGVjdHJhbCBpbWFnZSBhY3F1aXJlZCB3aXRoIGEgZGVtbyB1bml0IG9mIHRoZSBTcGVjaW0gSVEgc3lzdGVtIGluIEhTSUxhYiBwcmVtaXNlcyBvbiAyMDE4MDkxODoKCiogU3BlY2ltIElRIHdoaXRlIHJlZmVyZW5jZSBjYXJkLgoqIExvYmFyaWEgYW1wbGlzc2ltYSAoaHlkcmF0ZWQpCiogTG9iYXJpYSBhbXBsaXNzaW1hIChkcnkpCiAgKiBQcm92aWRlZCBieSBqb3NlaWduYWNpby5nYXJjaWFAZWh1LmV1cwoKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmRlZmF1bHRXIDwtIGdldE9wdGlvbigid2FybiIpCm9wdGlvbnMod2Fybj0tMSkKI1BhdGhzClJTcGVjdERpciAgICAgICA8LSAiL2hvbWUvYWxvYm8vb3duY2xvdWRSU3BlY3QvUlNwZWN0IgpkYXRhZGlyICAgICAgICAgPC0gIi9ob21lL2Fsb2JvL1dPUksvSFNJX0xhYi9UZXN0c19IeXBlcmNhbXMvU3BlY2ltSVEvU3BlY2ltSVFUZXN0XzIwMTgtMDktMTgvMjAxOC0xMC0xOF8wMTMiCmltYWRpciAgICAgICAgICA8LSBmaWxlLnBhdGgoZGF0YWRpciwicmVzdWx0cyIpCnZlY2RpciAgICAgICAgICA8LSBmaWxlLnBhdGgoZGF0YWRpciwiUUdJUy9Qb2x5Z29ucyIpCiNsaWJyYXJpZXMKbGlicmFyeShyZXNoYXBlMiwgcXVpZXRseSA9IFRSVUUpCmxpYnJhcnkocGx5ciwgcXVpZXRseSA9IFRSVUUpCmxpYnJhcnkoZ2dwbG90MiwgcXVpZXRseSA9IFRSVUUpCmxpYnJhcnkocGxvdGx5LCBxdWlldGx5ID0gVFJVRSkKbGlicmFyeSh0ZXJyYSwgcXVpZXRseSA9IFRSVUUpCmxpYnJhcnkodGlkeXRlcnJhLCBxdWlldGx5ID0gVFJVRSkKbGlicmFyeShncmlkRXh0cmEsIHF1aWV0bHkgPSBUUlVFKQpsaWJyYXJ5KGRpcmVjdGxhYmVscywgcXVpZXRseSA9IFRSVUUpCgojUmVxdWlyZWQgaW5mb3JtYXRpb24KbG9hZChmaWxlLnBhdGgoUlNwZWN0RGlyLCJJUWJhbmRzLnJkYSIpKSAjQmFuZHMgU3BlY2ltIElRIHN5c3RlbQpgYGAKCiMjIDEuIFJlYWQgaW1hZ2VzIGFuZCBwb2x5Z29ucwojIyMgMS4xIFJHQiBpbWFnZQpBIGNvbnZlbnRpb25hbCBSR0IgaW1hZ2UgaW4gcG5nIGZvcm1hdCBpcyBhdXRvbWF0aWNhbGx5IHJlY29yZGVkIGJ5IGEgZGVkaWNhdGVkIENDRCBpbiBTcGVjaW0gSVEuIEJ5IHNvbWUgcmVhc29uLCB0aGlzIGltYWdlIGlzIHJvdGF0ZWQgdnMuIHRoZSBoeXBlcnNwZWN0cmFsIG9uZSwgc28gdGhlIGNvZGUgcm90YXRlcyBpdC4KYGBge3IgfQpvcHRpb25zKHdhcm49LTEpCnIgPC0gcmFzdChmaWxlLnBhdGgoaW1hZGlyLCJSR0JCQUNLR1JPVU5EXzIwMTgtMTAtMThfMDEzLnBuZyIpKQpyCiNwbG90UkdCKHIsIHN0cmV0Y2g9ImxpbiIpCnBsb3RSR0IoZmxpcCh0KHIpKSkKYGBgCgojIyMgMS4yIFJlZmxlY3RhbmNlIGltYWdlClRoZSByZWZsZWN0YW5jZSBpbWFnZSB3YXMgY2FsY3VsYXRlZCBieSB0aGUgU3BlY2ltIElRIHN5c3RlbSBpdHNlbGYgdXNpbmcgdGhlIGF2ZXJhZ2UgdmFsdWUgb2YgdGhlIFdoaXRlIHJlZmVyZW5jZSAKYGBge3IgZWNobz1PRkZ9Cm9wdGlvbnMod2Fybj0tMSkKbm9tcmVmIDwtIGxpc3QuZmlsZXMoaW1hZGlyLHBhdHQ9J2RhdCcpWzFdCnMgPC0gcmFzdChmaWxlLnBhdGgoaW1hZGlyLG5vbXJlZikpCnMKYGBgCldlIGRpc3BsYXkgYm90aCBSR0IgYW5kIENJUiBjb2xvciBjb21wb3NpdGVzIHdpdGggdGhlIGZvbGxvd2luZyBzcGVjdHJhbCBiYW5kczoKYGBge3IgZWNobz1PRkYsIHJlc3VsdHM9ImFzaXMifQogIGRlZmJhbmRzIDwtIGMoNzAsNTMsMTkpCiAgZGVmYmFuZHMyIDwtIGMoMjAzLDcwLDUzKQogIGNhdCgiUkdCIikKICBrbml0cjo6a2FibGUoSVFiYW5kc1tkZWZiYW5kcyxdKQogIGNhdCgiQ0lSIikKICBrbml0cjo6a2FibGUoSVFiYW5kc1tkZWZiYW5kczIsXSkKYGBgCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQogIGNycyhzKSA8LSAiIgogICNzdXBwcmVzc01lc3NhZ2VzKAogIGdnUmFkIDwtIGdncGxvdCgpICsgCiAgZ2VvbV9zcGF0cmFzdGVyX3JnYihkYXRhID0gc3RyZXRjaChzdWJzZXQocywgc3Vic2V0PWRlZmJhbmRzKSwgbWlucT0wLjAyLCBtYXhxPTAuODUpKSArCiAgICBjb29yZF9maXhlZChyYXRpbyA9IDEpICsKICAgIHRoZW1lX3ZvaWQoKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgICBnZ3RpdGxlKHBhc3RlMCgiXG5CYW5kcyAiLCBwYXN0ZShkZWZiYW5kcyxjb2xsYXBzZT0iLCAiKSkpCiAgZ2dSYWQyIDwtIGdncGxvdCgpICsgCiAgICBnZW9tX3NwYXRyYXN0ZXJfcmdiKGRhdGEgPSBzdHJldGNoKHN1YnNldChzLCBzdWJzZXQ9ZGVmYmFuZHMyKSwgbWlucT0wLjAyLCBtYXhxPTAuODUpKSArCiAgICBjb29yZF9maXhlZChyYXRpbyA9IDEpICsKICAgIHRoZW1lX3ZvaWQoKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgICBnZ3RpdGxlKHBhc3RlMCgiXG5CYW5kcyAiLCBwYXN0ZShkZWZiYW5kczIsY29sbGFwc2U9IiwgIikpKQogICMpCiAgZ3JpZC5hcnJhbmdlKGdnUmFkLCBnZ1JhZDIsIG5jb2w9MikKYGBgCgojIyMgMS4zIFBvbHlnb24gdmVjdG9yClNpZ25pZmljYW50IHBhcnRzIG9mIGVhY2ggb2JqZWN0IHdlcmUgaW50ZXJhY3RpdmVseSBkaWdpdGl6ZWQgYXMgcG9seWdvbnMgaW4gUUdJUyBhbmQgdXNlZCBhcyBpbnB1dCBoZXJlLgpgYGB7ciB9Cm9wdGlvbnMod2Fybj0tMSkKbm9tcG9seSA8LSAiMDEzZGV0YWlsZWQuc2hwIgpwIDwtIHZlY3QoZmlsZS5wYXRoKHZlY2Rpcixub21wb2x5KSkKcAoja25pdHI6OmthYmxlKHApCmBgYAoKIyMgMi4gTWFrZSBjb25zaXN0ZW50IGdlb21ldHJ5IGFuZCBkaXNwbGF5CkluIGNhc2UgdGhlIENSUyBpcyBub3QgZGVmaW5lZCAoYW5kIHRoaXMgaXMgdGhlIHVuZm9ydHVuYXRlIGNhc2Ugb2YgU3BlY2ltIGhkciBmaWxlcyksIFIgYW5kIFFHSVMgYXNzdW1lLCBieSBkZWZhdWx0LCBkaWZmZXJlbnQgZ2VvbWV0cnkgZm9yIHRoZSBzYW1lIHRpZmYuIEFzIGEgY29uc2VxdWVuY2UsIHBvbHlnb25zIGRpZ2l0aXplZCBpbiBRR0lTIGRvIG5vdCBvdmVybGF5IHRoZSByYXN0ZXIgaW1hZ2UgaW4gUi4KVGhlIGNvZGUgbXVzdCBhY2NvdW50IGZvciB0aGlzIGZhY3QsIG1ha2luZyBhIGNvbnNpc3RlbnQgZ2VvbWV0cnkgZm9yIHJhc3RlciBhbmQgdmVjdG9yIGxheWVyLCB3aGljaCB3ZSBjb25maXJtIGluIHRoZSBkaXNwbGF5OgpgYGB7ciB9CmNycyhzKSA8LSBjcnMocCkKZXh0KHMpIDwtIGMoMCw1MTIsLTUxMiwwKQpwbG90UkdCKHN0cmV0Y2goc1tbZGVmYmFuZHMyXV0sIHNtaW49MC4wLCBzbWF4PTAuODUpLCBleHQ9Yyg1MCw0MDAsLTM2MCwtMTAwKSkKI3Bsb3RSR0Ioc3RyZXRjaChzW1tkZWZiYW5kc11dLCBzbWluPTAuMCwgc21heD0wLjQpLCBleHQ9Yyg1MCw0MDAsLTM2MCwtMTAwKSkKbGluZXMocCkKdGV4dChwLDIsY2V4PTAuOSxmb250PTIpCiNsaW5lcyhwLGV4dD1jKDUwLDQwMCwtMzYwLC0xMDApKSAjICJleHQiIGlzIG5vdCBhIGdyYXBoaWNhbCBwYXJhbWV0ZXIKI2xpbmVzKGV4dChjKDUwLDQwMCwtMzYwLC0xMDApKSkKYGBgCgojIyAzLiBFeHRyYWN0IHNwZWN0cmEgY29ycmVzcG9uZGluZyB0byBwb2x5Z29ucwpVc2luZyB0aGUgcG9seWdvbnMgYXMgdGVtcGxhdGVzLCB3ZSBleHRyYWN0IHRoZSByZWZsZWN0YW5jZSB2YWx1ZXMgZnJvbSB0aGUgaHlwZXJzcGVjdHJhbCBpbWFnZS4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm9wdGlvbnMod2Fybj0tMSkKcmVmbGRhdC5tICA8LSB0ZXJyYTo6ZXh0cmFjdChzLCBwLCBmdW49bWVhbiwgbmEucm09VFJVRSkKcmVmbGRhdC5zZCA8LSB0ZXJyYTo6ZXh0cmFjdChzLCBwLCBmdW49c2QsIG5hLnJtPVRSVUUpCgpyZWZsZGF0bSA8LSBhcy5kYXRhLmZyYW1lKHQocmVmbGRhdC5tWywtMV0pKQpjb2xuYW1lcyhyZWZsZGF0bSkgPC0gdmFsdWVzKHApJE5hbWUKcmVmbGRhdG0kQmFuZCA8LSBJUWJhbmRzJEJhbmQKcmVmbGRhdG0kV2F2ZWxlbmd0aCA8LSBJUWJhbmRzJFdhdmVsZW5ndGgKI2hlYWQocmVmbGRhdG0pCnJlZmxkYXRzZCA8LSBhcy5kYXRhLmZyYW1lKHQocmVmbGRhdC5zZFssLTFdKSkKY29sbmFtZXMocmVmbGRhdHNkKSA8LSB2YWx1ZXMocCkkTmFtZQojaGVhZChyZWZsZGF0c2QpCgpyZWZsZGF0IDwtIG1lbHQocmVmbGRhdG0saWQudmFycyA9IGMoIkJhbmQiLCAiV2F2ZWxlbmd0aCIpKQojaGVhZChyZWZsZGF0KQpuYW1lcyhyZWZsZGF0KVszOjRdIDwtIGMoIlNhbXBsZSIsICJSZWZsZWN0YW5jZSIpCgphIDwtIG1lbHQocmVmbGRhdHNkLGlkLnZhcnM9TlVMTCkKI2hlYWQoYSkKcmVmbGRhdCRzZCA8LSBhJHZhbHVlCnJlZmxkYXQkU3RhdGUgPC0gbWFwdmFsdWVzKHJlZmxkYXQkU2FtcGxlLCBmcm9tPXZhbHVlcyhwKSROYW1lLCB0bz12YWx1ZXMocCkkU3RhdGUpCiNoZWFkKHJlZmxkYXQpCgojZ2cxIDwtIHBsb3RoY2F2c3BlY3QoZGF0YWluUmVmLHRpdGxlPSIiKQojRGVmYXVsdCBjb2xvcnMKZ2dwbG90Q29sb3VycyA8LSBmdW5jdGlvbihuID0gNiwgaCA9IGMoMCwgMzYwKSArIDE1KXsKICBpZiAoKGRpZmYoaCkgJSUgMzYwKSA8IDEpIGhbMl0gPC0gaFsyXSAtIDM2MC9uCiAgaGNsKGggPSAoc2VxKGhbMV0sIGhbMl0sIGxlbmd0aCA9IG4pKSwgYyA9IDEwMCwgbCA9IDY1KQp9CgptaWNvbG9yIDwtIGdncGxvdENvbG91cnMobj0yNCkKI3NjYWxlczo6OnNob3dfY29sKG1pY29sb3IpCm1pY29sb3IgPC0gYygiV2hpdGUiPSJncmV5NTAiLCAKICAgICAgICAgICAgICJoMyI9bWljb2xvcls2XSwKICAgICAgICAgICAgICJoNCI9bWljb2xvcls3XSwKICAgICAgICAgICAgICJoNSI9bWljb2xvcls4XSwKICAgICAgICAgICAgICJoNiI9bWljb2xvcls5XSwKICAgICAgICAgICAgICJoNyI9bWljb2xvclsxMF0sCiAgICAgICAgICAgICAiaDEiPW1pY29sb3JbNF0sCiAgICAgICAgICAgICAiaDIiPW1pY29sb3JbNV0sCiAgICAgICAgICAgICAiZDUiPW1pY29sb3JbMV0sCiAgICAgICAgICAgICAiZDYiPW1pY29sb3JbMl0sCiAgICAgICAgICAgICAiZDciPW1pY29sb3JbM10sCiAgICAgICAgICAgICAiZDEiPW1pY29sb3JbMjFdLAogICAgICAgICAgICAgImQyIj1taWNvbG9yWzIyXSwKICAgICAgICAgICAgICJkMyI9bWljb2xvclsyM10sCiAgICAgICAgICAgICAiZDQiPW1pY29sb3JbMjRdKQoKZ2cxIDwtIGdncGxvdChkYXRhPXJlZmxkYXQpICsKICBnZW9tX3BvaW50KGFlcyh4PVdhdmVsZW5ndGgsIHk9UmVmbGVjdGFuY2UsIGNvbG9yPVNhbXBsZSksc2l6ZT0wLjUpICsKICBnZW9tX2xpbmUoYWVzKHg9V2F2ZWxlbmd0aCwgeT1SZWZsZWN0YW5jZSwgY29sb3I9U2FtcGxlKSkgKwogICNnZW9tX2Vycm9yYmFyKGFlcyh4PVdhdmVsZW5ndGgseW1pbj1SZWZsZWN0YW5jZS1zZCwgeW1heD1SZWZsZWN0YW5jZStzZCwgY29sb3I9U2FtcGxlKSwKICAjICAgICAgICAgICAgICB3aWR0aD0uMDEsCiAgIyAgICAgICAgICAgICAgcG9zaXRpb249cG9zaXRpb25fZG9kZ2UoMC4wNSkpICsKICBnZW9tX3RleHQoYWVzKHg9MTAxMCwgeT1SZWZsZWN0YW5jZSxsYWJlbD1TYW1wbGUpKSArCiAgeGxhYigiV2F2ZWxlbmd0aCAobm0pIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9bWljb2xvcikgKwogIGdndGl0bGUoIkxvYnVsYXJpYSBhbXBsaXNzaW1hIikKI2dnMSArIHhsaW0oYyg0NTAsIDEwMDApKSArIHlsaW0oYygwLDAuOSkpICsgCiMgIGZhY2V0X3dyYXAoflN0YXRlKSArIGdlb21fZGwoYWVzKHg9V2F2ZWxlbmd0aCwgeT1SZWZsZWN0YW5jZSwgbGFiZWwgPSBTYW1wbGUpLCBtZXRob2QgPSBsaXN0KGRsLmNvbWJpbmUoImZpcnN0LnBvaW50cyIsICJsYXN0LnBvaW50cyIpLCBjZXggPSAwLjcpKSAKZ2cyIDwtIGdnMSArIHhsaW0oYyg0NTAsIDEwMDApKSArIHlsaW0oYygwLDAuOSkpICsgZmFjZXRfd3JhcCh+U3RhdGUpICsKICBnZW9tX2RsKGFlcyh4PVdhdmVsZW5ndGgsIHk9UmVmbGVjdGFuY2UsIGxhYmVsID0gU2FtcGxlKSwgbWV0aG9kID0gbGlzdChkbC5jb21iaW5lKCJmaXJzdC5wb2ludHMiLCAibGFzdC5wb2ludHMiKSwgY2V4ID0gMC43KSkKZ2dwbG90bHkoZ2cyLAogICAgICAgICB0b29sdGlwPWMoIkJhbmQiLCJXYXZlbGVuZ3RoIixSZWZsZWN0YW5jZT0iUmVmbGVjdGFuY2UiLCJTYW1wbGUiKSwKICAgICAgICAgaGVpZ2h0PTYwMCwgd2lkdGg9ODAwKQoKYGBgCgo=