This script extracts reflectance spectra of 2 grey standards from a VISNIR hyperspectral image acquired with a Specim IQ system in HSILab premises on 20221109:

defaultW <- getOption("warn")
options(warn=-1)
#Paths
RSpectDir       <- "/home/alobo/owncloudRSpect/RSpect"
datadir         <- "/home/alobo/WORK/HSI_Lab/Tests_Hypercams/SpecimIQ/20221109/034"
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_034.png"))
Warning: [rast] unknown extent
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_034.png 
names       : RGBBACK~D_034_1, RGBBACK~D_034_2, RGBBACK~D_034_3, RGBBACK~D_034_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. : Arbitrary 
source      : REFLECTANCE_034.dat 
names       :    397.32,    400.20,    403.09,    405.97,    408.85,    411.74, ... 
min values  : 0.1458333, 0.1694915, 0.1578947, 0.1359223, 0.1205674, 0.1185567, ... 
max values  : 1.1458334, 1.1355932, 1.1052631, 1.1067961, 1.0845070, 1.0773196, ... 

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

Relevant parts of each object were interactively digitized as polygons in QGIS and used to select spectra from the image.

options(warn=-1)
nompoly <- "034.shp"
p <- vect(file.path(vecdir,nompoly))
#values(p)$Name <- c("White", "Ochre", "Ochre_800",
#                    "Red", "Red_800",
#                    "GreenTerra", "GreenTerra_800")
p
 class       : SpatVector 
 geometry    : polygons 
 dimensions  : 3, 3  (geometries, attributes)
 extent      : 9.067334, 502.9327, 9.403755, 497.8865  (xmin, xmax, ymin, ymax)
 source      : 034.shp
 coord. ref. : Arbitrary 
#knitr::kable(p)

2. Geometric consistency

In this example, the CRS was defined in the image by editing the original hdr file, and the digitized polygons were defined in QGIS with the same CRS. We confirm the correct overlay of vector polygons and raster image (RGB display with bands 70 (598 nm), 53 (548 nm) and 19 (449 nm)).

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=1.0), main=IQbands[defbands,])
plot(p, add=TRUE)
text(p,2,cex=0.9,font=2)

3. Object spectra

Finally, using the polygons as templates, we extract the reflectance values from the hyperspectral image and plot.

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
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")
refldat$Sample <- mapvalues(refldat$Sample, from= values(p)$Name, to= values(p)$Comment)
a <- melt(refldatsd,id.vars=NULL)
#head(a)
refldat$sd <- a$value
refldat$Treatment <- mapvalues(refldat$Sample, from=values(p)$Acronym, to=values(p)$Treatment)
refldat$Pigment <- mapvalues(refldat$Sample, from=values(p)$Acronym, to=values(p)$Pigment)
#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=36)
#scales:::show_col(micolor)
#https://www.nceas.ucsb.edu/sites/default/files/2020-04/colorPaletteCheatsheet.pdf
#micolor <- c("W"="cyan", "G50"="grey50", "GC"="grey20")

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=.001, alpha=0.5,
                position=position_dodge(0.05)) +
  ylim(c(0,1.2)) +
  xlab("Wavelength (nm)") +
  theme(legend.position = "bottom") +
  ggtitle("Grey standards")

#  gg1 + geom_dl(aes(x=Wavelength, y=Reflectance, label = Sample), method = list(dl.combine("first.points", "last.points"), cex = #0.7)) + xlim(c(300, 1100))

#+ scale_color_manual(values=micolor)

ggplotly(gg1,
         tooltip=c("Band","Wavelength",Reflectance="Reflectance","Sample"),
         height=600, width=900)
NA
LS0tCnRpdGxlOiAiU3BlY2ltIElRIHRlc3Qgb2YgZ3JleSBzdGFuZGFyZHMiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBmaWdfY2FwdGlvbjogVFJVRQotLS0KCmBgYHs9aHRtbH0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KICAudGFibGUge3dpZHRoOiAyNSU7fQo8L3N0eWxlPgpgYGAKLSAgIFtBZ3VzdGluLkxvYm9cQGdlbzNiY24uY3NpYy5lc10obWFpbHRvOkFndXN0aW4uTG9ib0BnZW8zYmNuLmNzaWMuZXMpey5lbWFpbH0KLSAgIDIwMjIxMTE1CgpUaGlzIHNjcmlwdCBleHRyYWN0cyByZWZsZWN0YW5jZSBzcGVjdHJhIG9mIDIgZ3JleSBzdGFuZGFyZHMgZnJvbSBhIFZJU05JUiBoeXBlcnNwZWN0cmFsIGltYWdlIGFjcXVpcmVkIHdpdGggYSBTcGVjaW0gSVEgc3lzdGVtIGluIEhTSUxhYiBwcmVtaXNlcyBvbiAyMDIyMTEwOToKCi0gICBTcGVjaW0gSVEgd2hpdGUgcmVmZXJlbmNlIGNhcmQuCi0gICBMYWJzcGhlcmUgNTAlIHJlZmxlY3RhbmNlIHN0YW5kYXJkCi0gICBDdWJlcnQgZ3JleSByZWZsZWN0YW5jZSBzdGFuZGFyZAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZGVmYXVsdFcgPC0gZ2V0T3B0aW9uKCJ3YXJuIikKb3B0aW9ucyh3YXJuPS0xKQojUGF0aHMKUlNwZWN0RGlyICAgICAgIDwtICIvaG9tZS9hbG9iby9vd25jbG91ZFJTcGVjdC9SU3BlY3QiCmRhdGFkaXIgICAgICAgICA8LSAiL2hvbWUvYWxvYm8vV09SSy9IU0lfTGFiL1Rlc3RzX0h5cGVyY2Ftcy9TcGVjaW1JUS8yMDIyMTEwOS8wMzQiCmltYWRpciAgICAgICAgICA8LSBmaWxlLnBhdGgoZGF0YWRpciwicmVzdWx0cyIpCnZlY2RpciAgICAgICAgICA8LSBmaWxlLnBhdGgoZGF0YWRpciwiUUdJUy9Qb2x5Z29ucyIpCiNsaWJyYXJpZXMKbGlicmFyeShyZXNoYXBlMiwgcXVpZXRseSA9IFRSVUUpCmxpYnJhcnkocGx5ciwgcXVpZXRseSA9IFRSVUUpCmxpYnJhcnkoZ2dwbG90MiwgcXVpZXRseSA9IFRSVUUpCmxpYnJhcnkocGxvdGx5LCBxdWlldGx5ID0gVFJVRSkKbGlicmFyeSh0ZXJyYSwgcXVpZXRseSA9IFRSVUUpCmxpYnJhcnkodGlkeXRlcnJhLCBxdWlldGx5ID0gVFJVRSkKbGlicmFyeShncmlkRXh0cmEsIHF1aWV0bHkgPSBUUlVFKQpsaWJyYXJ5KGRpcmVjdGxhYmVscywgcXVpZXRseSA9IFRSVUUpCgojUmVxdWlyZWQgaW5mb3JtYXRpb24KbG9hZChmaWxlLnBhdGgoUlNwZWN0RGlyLCJJUWJhbmRzLnJkYSIpKSAjQmFuZHMgU3BlY2ltIElRIHN5c3RlbQpgYGAKCiMjIDEuIFJlYWQgaW1hZ2VzIGFuZCBwb2x5Z29ucwoKIyMjIDEuMSBSR0IgaW1hZ2UKCkEgY29udmVudGlvbmFsIFJHQiBpbWFnZSBpbiBwbmcgZm9ybWF0IGlzIGF1dG9tYXRpY2FsbHkgcmVjb3JkZWQgYnkgYSBkZWRpY2F0ZWQgQ0NEIGluIFNwZWNpbSBJUS4gQnkgc29tZSByZWFzb24sIHRoaXMgaW1hZ2UgaXMgcm90YXRlZCB2cy4gdGhlIGh5cGVyc3BlY3RyYWwgb25lLCBzbyB0aGUgY29kZSByb3RhdGVzIGl0LgoKYGBge3IgfQpvcHRpb25zKHdhcm49LTEpCnIgPC0gcmFzdChmaWxlLnBhdGgoaW1hZGlyLCJSR0JCQUNLR1JPVU5EXzAzNC5wbmciKSkKcgojcGxvdFJHQihyLCBzdHJldGNoPSJsaW4iKQpwbG90UkdCKGZsaXAodChyKSkpCmBgYAoKIyMjIDEuMiBSZWZsZWN0YW5jZSBpbWFnZQoKVGhlIHJlZmxlY3RhbmNlIGltYWdlIHdhcyBjYWxjdWxhdGVkIGJ5IHRoZSBTcGVjaW0gSVEgc3lzdGVtIGl0c2VsZiB1c2luZyB0aGUgYXZlcmFnZSB2YWx1ZSBvZiB0aGUgV2hpdGUgcmVmZXJlbmNlCgpgYGB7ciBlY2hvPU9GRn0Kb3B0aW9ucyh3YXJuPS0xKQpub21yZWYgPC0gbGlzdC5maWxlcyhpbWFkaXIscGF0dD0nZGF0JylbMV0KcyA8LSByYXN0KGZpbGUucGF0aChpbWFkaXIsbm9tcmVmKSkKcwpgYGAKCldlIGRpc3BsYXkgYm90aCBSR0IgYW5kIENJUiBjb2xvciBjb21wb3NpdGVzIHdpdGggdGhlIGZvbGxvd2luZyBzcGVjdHJhbCBiYW5kczoKCmBgYHtyIGVjaG89T0ZGLCByZXN1bHRzPSJhc2lzIn0KICBkZWZiYW5kcyA8LSBjKDcwLDUzLDE5KQogIGRlZmJhbmRzMiA8LSBjKDIwMyw3MCw1MykKICBjYXQoIlJHQiIpCiAga25pdHI6OmthYmxlKElRYmFuZHNbZGVmYmFuZHMsXSkKICBjYXQoIkNJUiIpCiAga25pdHI6OmthYmxlKElRYmFuZHNbZGVmYmFuZHMyLF0pCmBgYAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KICBjcnMocykgPC0gIiIKICAjc3VwcHJlc3NNZXNzYWdlcygKICBnZ1JhZCA8LSBnZ3Bsb3QoKSArIAogIGdlb21fc3BhdHJhc3Rlcl9yZ2IoZGF0YSA9IHN0cmV0Y2goc3Vic2V0KHMsIHN1YnNldD1kZWZiYW5kcyksIG1pbnE9MC4wMiwgbWF4cT0wLjg1KSkgKwogICAgY29vcmRfZml4ZWQocmF0aW8gPSAxKSArCiAgICB0aGVtZV92b2lkKCkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogICAgZ2d0aXRsZShwYXN0ZTAoIlxuQmFuZHMgIiwgcGFzdGUoZGVmYmFuZHMsY29sbGFwc2U9IiwgIikpKQogIGdnUmFkMiA8LSBnZ3Bsb3QoKSArIAogICAgZ2VvbV9zcGF0cmFzdGVyX3JnYihkYXRhID0gc3RyZXRjaChzdWJzZXQocywgc3Vic2V0PWRlZmJhbmRzMiksIG1pbnE9MC4wMiwgbWF4cT0wLjg1KSkgKwogICAgY29vcmRfZml4ZWQocmF0aW8gPSAxKSArCiAgICB0aGVtZV92b2lkKCkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogICAgZ2d0aXRsZShwYXN0ZTAoIlxuQmFuZHMgIiwgcGFzdGUoZGVmYmFuZHMyLGNvbGxhcHNlPSIsICIpKSkKICAjKQogIGdyaWQuYXJyYW5nZShnZ1JhZCwgZ2dSYWQyLCBuY29sPTIpCmBgYAoKIyMjIDEuMyBQb2x5Z29uIHZlY3RvcgoKUmVsZXZhbnQgcGFydHMgb2YgZWFjaCBvYmplY3Qgd2VyZSBpbnRlcmFjdGl2ZWx5IGRpZ2l0aXplZCBhcyBwb2x5Z29ucyBpbiBRR0lTIGFuZCB1c2VkIHRvIHNlbGVjdCBzcGVjdHJhIGZyb20gdGhlIGltYWdlLgoKYGBge3IgfQpvcHRpb25zKHdhcm49LTEpCm5vbXBvbHkgPC0gIjAzNC5zaHAiCnAgPC0gdmVjdChmaWxlLnBhdGgodmVjZGlyLG5vbXBvbHkpKQojdmFsdWVzKHApJE5hbWUgPC0gYygiV2hpdGUiLCAiT2NocmUiLCAiT2NocmVfODAwIiwKIyAgICAgICAgICAgICAgICAgICAgIlJlZCIsICJSZWRfODAwIiwKIyAgICAgICAgICAgICAgICAgICAgIkdyZWVuVGVycmEiLCAiR3JlZW5UZXJyYV84MDAiKQpwCiNrbml0cjo6a2FibGUocCkKYGBgCgojIyAyLiBHZW9tZXRyaWMgY29uc2lzdGVuY3kKCkluIHRoaXMgZXhhbXBsZSwgdGhlIENSUyB3YXMgZGVmaW5lZCBpbiB0aGUgaW1hZ2UgYnkgZWRpdGluZyB0aGUgb3JpZ2luYWwgaGRyIGZpbGUsIGFuZCB0aGUgZGlnaXRpemVkIHBvbHlnb25zIHdlcmUgZGVmaW5lZCBpbiBRR0lTIHdpdGggdGhlIHNhbWUgQ1JTLiBXZSBjb25maXJtIHRoZSBjb3JyZWN0IG92ZXJsYXkgb2YgdmVjdG9yIHBvbHlnb25zIGFuZCByYXN0ZXIgaW1hZ2UgKFJHQiBkaXNwbGF5IHdpdGggYmFuZHMgNzAgKDU5OCBubSksIDUzICg1NDggbm0pIGFuZCAxOSAoNDQ5IG5tKSkuCgpgYGB7ciB9CmNycyhzKSA8LSBjcnMocCkKI2V4dChzKSA8LSBjKDAsNTEyLC01MTIsMCkKI3Bsb3RSR0Ioc3RyZXRjaChzW1tkZWZiYW5kczJdXSwgc21pbj0wLjAsIHNtYXg9MC44NSksIGV4dD1jKDUwLDQwMCwtMzYwLC0xMDApKQpwbG90UkdCKHN0cmV0Y2goc1tbZGVmYmFuZHNdXSwgc21pbj0wLjAsIHNtYXg9MS4wKSwgbWFpbj1JUWJhbmRzW2RlZmJhbmRzLF0pCnBsb3QocCwgYWRkPVRSVUUpCnRleHQocCwyLGNleD0wLjksZm9udD0yKQpgYGAKCiMjIDMuIE9iamVjdCBzcGVjdHJhCgpGaW5hbGx5LCB1c2luZyB0aGUgcG9seWdvbnMgYXMgdGVtcGxhdGVzLCB3ZSBleHRyYWN0IHRoZSByZWZsZWN0YW5jZSB2YWx1ZXMgZnJvbSB0aGUgaHlwZXJzcGVjdHJhbCBpbWFnZSBhbmQgcGxvdC4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gOH0Kb3B0aW9ucyh3YXJuPS0xKQpyZWZsZGF0Lm0gIDwtIHRlcnJhOjpleHRyYWN0KHMsIHAsIGZ1bj1tZWFuLCBuYS5ybT1UUlVFKQpyZWZsZGF0LnNkIDwtIHRlcnJhOjpleHRyYWN0KHMsIHAsIGZ1bj1zZCwgbmEucm09VFJVRSkKcmVmbGRhdG0gPC0gYXMuZGF0YS5mcmFtZSh0KHJlZmxkYXQubVssLTFdKSkKI2NvbG5hbWVzKHJlZmxkYXRtKSA8LSB2YWx1ZXMocCkkTmFtZQpjb2xuYW1lcyhyZWZsZGF0bSkgPC0gdmFsdWVzKHApJE5hbWUKcmVmbGRhdG0kQmFuZCA8LSBJUWJhbmRzJEJhbmQKcmVmbGRhdG0kV2F2ZWxlbmd0aCA8LSBJUWJhbmRzJFdhdmVsZW5ndGgKI2hlYWQocmVmbGRhdG0pCnJlZmxkYXRzZCA8LSBhcy5kYXRhLmZyYW1lKHQocmVmbGRhdC5zZFssLTFdKSkKY29sbmFtZXMocmVmbGRhdHNkKSA8LSB2YWx1ZXMocCkkTmFtZQojaGVhZChyZWZsZGF0c2QpCgpyZWZsZGF0IDwtIG1lbHQocmVmbGRhdG0saWQudmFycyA9IGMoIkJhbmQiLCAiV2F2ZWxlbmd0aCIpKQojaGVhZChyZWZsZGF0KQpuYW1lcyhyZWZsZGF0KVszOjRdIDwtIGMoIlNhbXBsZSIsICJSZWZsZWN0YW5jZSIpCnJlZmxkYXQkU2FtcGxlIDwtIG1hcHZhbHVlcyhyZWZsZGF0JFNhbXBsZSwgZnJvbT0gdmFsdWVzKHApJE5hbWUsIHRvPSB2YWx1ZXMocCkkQ29tbWVudCkKYSA8LSBtZWx0KHJlZmxkYXRzZCxpZC52YXJzPU5VTEwpCiNoZWFkKGEpCnJlZmxkYXQkc2QgPC0gYSR2YWx1ZQpyZWZsZGF0JFRyZWF0bWVudCA8LSBtYXB2YWx1ZXMocmVmbGRhdCRTYW1wbGUsIGZyb209dmFsdWVzKHApJEFjcm9ueW0sIHRvPXZhbHVlcyhwKSRUcmVhdG1lbnQpCnJlZmxkYXQkUGlnbWVudCA8LSBtYXB2YWx1ZXMocmVmbGRhdCRTYW1wbGUsIGZyb209dmFsdWVzKHApJEFjcm9ueW0sIHRvPXZhbHVlcyhwKSRQaWdtZW50KQojaGVhZChyZWZsZGF0KQoKI2dnMSA8LSBwbG90aGNhdnNwZWN0KGRhdGFpblJlZix0aXRsZT0iIikKI0RlZmF1bHQgY29sb3JzCiNnZ3Bsb3RDb2xvdXJzIDwtIGZ1bmN0aW9uKG4gPSA2LCBoID0gYygwLCAzNjApICsgMTUpewojICBpZiAoKGRpZmYoaCkgJSUgMzYwKSA8IDEpIGhbMl0gPC0gaFsyXSAtIDM2MC9uCiMgaGNsKGggPSAoc2VxKGhbMV0sIGhbMl0sIGxlbmd0aCA9IG4pKSwgYyA9IDEwMCwgbCA9IDY1KQojfQoKI21pY29sb3IgPC0gZ2dwbG90Q29sb3VycyhuPTM2KQojc2NhbGVzOjo6c2hvd19jb2wobWljb2xvcikKI2h0dHBzOi8vd3d3Lm5jZWFzLnVjc2IuZWR1L3NpdGVzL2RlZmF1bHQvZmlsZXMvMjAyMC0wNC9jb2xvclBhbGV0dGVDaGVhdHNoZWV0LnBkZgojbWljb2xvciA8LSBjKCJXIj0iY3lhbiIsICJHNTAiPSJncmV5NTAiLCAiR0MiPSJncmV5MjAiKQoKZ2cxIDwtIGdncGxvdChkYXRhPXJlZmxkYXQpICsKICBnZW9tX3BvaW50KGFlcyh4PVdhdmVsZW5ndGgsIHk9UmVmbGVjdGFuY2UsIGNvbG9yPVNhbXBsZSksc2l6ZT0wLjUpICsKICBnZW9tX2xpbmUoYWVzKHg9V2F2ZWxlbmd0aCwgeT1SZWZsZWN0YW5jZSwgY29sb3I9U2FtcGxlKSkgKwogIGdlb21fZXJyb3JiYXIoYWVzKHg9V2F2ZWxlbmd0aCx5bWluPVJlZmxlY3RhbmNlLXNkLCB5bWF4PVJlZmxlY3RhbmNlK3NkLCBjb2xvcj1TYW1wbGUpLAogICAgICAgICAgICAgICAgd2lkdGg9LjAwMSwgYWxwaGE9MC41LAogICAgICAgICAgICAgICAgcG9zaXRpb249cG9zaXRpb25fZG9kZ2UoMC4wNSkpICsKICB5bGltKGMoMCwxLjIpKSArCiAgeGxhYigiV2F2ZWxlbmd0aCAobm0pIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArCiAgZ2d0aXRsZSgiR3JleSBzdGFuZGFyZHMiKQoKIyAgZ2cxICsgZ2VvbV9kbChhZXMoeD1XYXZlbGVuZ3RoLCB5PVJlZmxlY3RhbmNlLCBsYWJlbCA9IFNhbXBsZSksIG1ldGhvZCA9IGxpc3QoZGwuY29tYmluZSgiZmlyc3QucG9pbnRzIiwgImxhc3QucG9pbnRzIiksIGNleCA9ICMwLjcpKSArIHhsaW0oYygzMDAsIDExMDApKQoKIysgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1taWNvbG9yKQoKZ2dwbG90bHkoZ2cxLAogICAgICAgICB0b29sdGlwPWMoIkJhbmQiLCJXYXZlbGVuZ3RoIixSZWZsZWN0YW5jZT0iUmVmbGVjdGFuY2UiLCJTYW1wbGUiKSwKICAgICAgICAgaGVpZ2h0PTYwMCwgd2lkdGg9OTAwKQoKYGBgCg==