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:
- Specim IQ white reference card.
- Labsphere 50% reflectance standard
- Cubert grey reflectance standard
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
| 70 |
Band070 |
598.60 |
| 53 |
Band053 |
548.55 |
| 19 |
Band019 |
449.35 |
CIR
| 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==