This script compares 4 options to calculate reflectance from radiance images acquired by hyperspectral scanning systems, in particular Specim FX17 and FX10 mounted on desktop-size Lumo Scanner stands:

Options 3 and 4 account for heterogeneous illumination along columns of the captured row, while options 2 and 4 account for dark noise.

#Directories
defaultW <- getOption("warn")
options(warn=-1)
RSpectDir <- "/home/alobo/owncloudRSpect/RSpect"
imadir <- "../FX17_DeceptionIsland31_2018-04-20_07-46-53/FX17/capture"
vdir <- "../FX17_DeceptionIsland31_2018-04-20_07-46-53/QGIS"
#R packages
library(terra, quietly=TRUE)
library(tidyterra, quietly=TRUE)
library(reshape2, quietly=TRUE)
library(ggplot2, quietly=TRUE)
#Required information
load(file.path(RSpectDir,"FX17bands.rda")) #central wavelength of FX17 spectral bands 
load(file.path(RSpectDir,"FX17weirds.rda"))#systematic abnormal values of our FX17 instrument
load(file.path(RSpectDir,"RefMCAA01.rda")) #certified reflectance spectra of WCS-MC-010 

Introduction

Keep in mind that these images are in fact stacks of 1 row x 650 column hyperspectral pixels, and that the lighting system does not provide a uniform illumination. The Radiance White image corresponds to a narrow white stick. An example:

  options(warn=-1)
  terraOptions(progress=0)
  fnameimaWref    <- list.files(imadir,patt=glob2rx("WHITEREF*.raw"))[1] 
  fnameimadarkref <- list.files(imadir,patt=glob2rx("DARKREF*.raw"))[1]
  fnameimaRad     <- list.files(imadir,patt=glob2rx("FX17*.raw"))[1]
  WIma <- rast(file.path(imadir,fnameimaWref))
  names(WIma) <- paste0("B",1:nlyr(WIma))
  suf <- unlist(strsplit(fnameimaWref,"WHITEREF_FX17_"))[2]
  suf <- unlist(strsplit(suf,".raw"))[1]
  fnameimaWref2   <- paste0("WHITEREF_BIG_FX17_",suf)
  WIma2 <- rast(file.path(imadir,fnameimaWref2))
  names(WIma2) <- paste0("B",1:nlyr(WIma))
  defbands=c(170, 14, 86)

  a <- subset(WIma2, subset=defbands)
  delme <- crop(a,ext(WIma))
  as1 <- stretch(delme,smin=0, smax=4096)
  as2 <- stretch(delme,minq=0.02, maxq=0.98)
  crs(as2) <- ""
  ggW2st <- ggplot() + 
    geom_spatraster_rgb(data = as2) +
    coord_fixed(ratio = 1) +
    theme_void() +  theme(plot.title = element_text(hjust = 0.125), plot.margin=unit(c(0,0,0,0),"mm")) +
    ggtitle(paste("Radiance White image \n(Bands ", paste(defbands,collapse=", "), "with 2% stretching)"))
  rm(a, as1, as2,delme)
  print(ggW2st)

options(warn=-1)
WIma2.df  <- as.data.frame(mean(WIma2[1,,,drop=FALSE]),  cell=TRUE,na.rm=FALSE)
meamWIma2.df <- data.frame(Column=WIma2.df$cell,
                             Radiance=c(WIma2.df$mean))
ggW1 <- ggplot(data=meamWIma2.df) +
    geom_point( aes(x=Column, y=Radiance),size=0.5) +
    xlab("Column") + ylab("Mean Band Radiance") +
    theme(legend.position = c(0.75, 0.15)) + theme(plot.title = element_text(hjust = 0.5)) +
    scale_size_identity() 
    #ggtitle("Radiance of White Reference")
  print(ggW1)

Reflectance of White image

Option 1

Radiance image divided by the global average spectra of the radiance white image

options(warn=-1)
ReflWIma1 <- WIma2/unlist(global(WIma2,mean,na.rm=TRUE))

Option 2

Radiance image divided by the global average spectra of the radiance white image, and subtracting the average spectra of the Dark image

options(warn=-1)
DarkIma   <- rast(file.path(imadir,fnameimadarkref))
d <- global(DarkIma, mean, na.rm=TRUE)
ReflWIma2 <- (WIma2-unlist(d))/(unlist(global(WIma2,mean,na.rm=TRUE))-unlist(d))

Option 3

Radiance image divided by the radiance value of the corresponding column in the white image

options(warn=-1)
ReflWIma3 <- WIma2/WIma2

Option 4

Radiance image divided by the radiance value of the corresponding column in the white image, with noise subtracted. (Obviously, for the case of the White image, options 3 and 4 are identical).

options(warn=-1)
ReflWIma4 <- (WIma2-unlist(d))/(WIma2-unlist(d))

Comparison

options(warn=-1)
ReflWIma1.df  <- as.data.frame(mean(ReflWIma1[1,,,drop=FALSE]),  cell=TRUE,na.rm=FALSE)
ReflWIma2.df  <- as.data.frame(mean(ReflWIma2[1,,,drop=FALSE]),  cell=TRUE,na.rm=FALSE)
ReflWIma3.df  <- as.data.frame(mean(ReflWIma3[1,,,drop=FALSE]),  cell=TRUE,na.rm=FALSE)
ReflWIma4.df  <- as.data.frame(mean(ReflWIma4[1,,,drop=FALSE]),  cell=TRUE,na.rm=FALSE)
meamReflWImas.df <- data.frame(Column=ReflWIma1.df$cell,
                             Source=rep(paste0("Option ", 1:4), rep(ncol(ReflWIma1), 4)),
                             Reflectance=c(ReflWIma1.df$mean,ReflWIma2.df$mean,
                                           ReflWIma3.df$mean,ReflWIma4.df$mean))
ggW1_W4 <- ggplot(data=meamReflWImas.df) +
    geom_point( aes(x=Column, y=Reflectance, color=Source),size=0.5) +
    xlab("Column") + ylab("Mean Reflectance across bands") +
    theme(legend.position = c(0.75, 0.15)) + theme(plot.title = element_text(hjust = 0.5)) +
    scale_size_identity() +
    ggtitle("Reflectance of White Reference")
  print(ggW1_W4)

Note that, in this rather extreme case, the illumination heterogeneity will likely imply a severe error in the calculated reflectance image if lighting differences along the white reference bar are not taken into account (options 1 and 2).

Reflectance of standard reference

In this section we use the radiance image of a Labsphere diffuse reflectance standard (WCS-MC-020), and show the reflectance spectra following all four options compared to the certified spectrum. We crop the object from the same scanned radiance dataset as above, for which we use an interactively digitized vector object.

options(warn=-1)
fnameimaRad <- list.files(imadir,patt=glob2rx("FX17*.raw"))[1]
RadIma <- rast(file.path(imadir,fnameimaRad))
names(RadIma) <- paste0("B",1:nlyr(RadIma))
v <- vect(file.path(vdir,"DI31.shp"))[6,]
ext(RadIma)[3:4] <- c(-ext(RadIma)[4],ext(RadIma)[3])#adapt to v geometry

a <- subset(RadIma, subset=defbands)
delme <- crop(a,ext(RadIma))
as1 <- stretch(delme,smin=0, smax=4096)
as2 <- stretch(delme,minq=0.02, maxq=0.98)
plotRGB(as2, ext=ext(v) + c(10,10,10,10) )
plot(v,add=TRUE)

rm(a, delme, as1, as2)

We calculate and plot Reflectance according to all four options and compare them to the certified spectrum:

options(warn=-1)
ext(RadIma)[3:4] <- c(ext(RadIma)[4],-ext(RadIma)[3])#recover raster geometry
ReflIma1 <- RadIma/unlist(global(WIma2,mean,na.rm=TRUE))
#DarkIma   <- rast(file.path(imadir,fnameimadarkref))
#d <- global(DarkIma, mean, na.rm=TRUE)
ReflIma2 <- (RadIma-unlist(d))/(unlist(global(WIma2,mean,na.rm=TRUE))-unlist(d))
ReflIma3 <- RadIma/WIma2
ReflIma4 <- (RadIma-unlist(d))/(WIma2-unlist(d))
#Extract values and calculate
ext(ReflIma1)[3:4] <- c(-ext(ReflIma1)[4],ext(ReflIma1)[3])#adapt to v geometry
ext(ReflIma2)[3:4] <- c(-ext(ReflIma2)[4],ext(ReflIma2)[3])#adapt to v geometry
ext(ReflIma3)[3:4] <- c(-ext(ReflIma3)[4],ext(ReflIma3)[3])#adapt to v geometry
ext(ReflIma4)[3:4] <- c(-ext(ReflIma4)[4],ext(ReflIma4)[3])#adapt to v geometry

refl1dat.m  <- terra::extract(ReflIma1, v, fun=mean, na.rm=TRUE)
refl1dat.sd <- terra::extract(ReflIma1, v, fun=sd, na.rm=TRUE)
refl2dat.m  <- terra::extract(ReflIma2, v, fun=mean, na.rm=TRUE)
refl2dat.sd <- terra::extract(ReflIma2, v, fun=sd, na.rm=TRUE)
refl3dat.m  <- terra::extract(ReflIma3, v, fun=mean, na.rm=TRUE)
refl3dat.sd <- terra::extract(ReflIma3, v, fun=sd, na.rm=TRUE)
refl4dat.m  <- terra::extract(ReflIma4, v, fun=mean, na.rm=TRUE)
refl4dat.sd <- terra::extract(ReflIma4, v, fun=sd, na.rm=TRUE)

refl1dat <- data.frame(Reflectance=t(refl1dat.m[-1]),sd=t(refl1dat.sd[-1]))
refl1dat$Bandnb <- FX17bands$Bandnb
refl1dat$Wavelength <- FX17bands$Wavelength
refl1dat$source <- "Option 1"
#head(refl1dat)

refl2dat <- data.frame(Reflectance=t(refl2dat.m[-1]),sd=t(refl2dat.sd[-1]))
refl2dat$Bandnb <- FX17bands$Bandnb
refl2dat$Wavelength <- FX17bands$Wavelength
refl2dat$source <- "Option 2"
#head(refl2dat)

refl3dat <- data.frame(Reflectance=t(refl3dat.m[-1]),sd=t(refl3dat.sd[-1]))
refl3dat$Bandnb <- FX17bands$Bandnb
refl3dat$Wavelength <- FX17bands$Wavelength
refl3dat$source <- "Option 3"
#head(refl3dat)

refl4dat <- data.frame(Reflectance=t(refl4dat.m[-1]),sd=t(refl4dat.sd[-1]))
refl4dat$Bandnb <- FX17bands$Bandnb
refl4dat$Wavelength <- FX17bands$Wavelength
refl4dat$source <- "Option 4"
#head(refl4dat)

refldat <- rbind(refl1dat, refl2dat, refl3dat, refl4dat)
#head(refldat)
a <- data.frame(Reflectance=RefMCAA01$Reflectance, sd=0.0, 
                 Bandnb=NA, Wavelength=RefMCAA01$Wavelength, source="Certified")
refldat <- rbind(refldat, a)
rm(a)
#micolor <- c( "orange", "red", "blue", "cyan")
#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)
micolor <- c("Option 1"=micolor[1], "Option 2"=micolor[2], 
             "Option 3"=micolor[3], "Option 4"=micolor[4], 
             "Certified"= "black" )
ggplot(data=refldat) +
  #geom_line(data=RefMCAA01, aes(x=Wavelength, y=Reflectance), color="black") +
  #geom_point(data=RefMCAA01, aes(x=Wavelength, y=Reflectance), color="black",size=.25) +
  geom_point(aes(x=Wavelength, y=Reflectance, color=source),size=1) +
  geom_line(aes(x=Wavelength, y=Reflectance, color=source)) +
  geom_errorbar(aes(x=Wavelength,ymin=Reflectance-sd, ymax=Reflectance+sd, color=source),
                width=.1,
                position=position_dodge(0.05)) +
  geom_rect(aes(xmin = 1450, xmax = 1600, ymin = 0.4, ymax =0.9),col="red",alpha=0) +
  xlim(range(FX17bands$Wavelength)) + xlab("Wavelength (nm)")+
  theme(legend.position = c(0.91, 0.23)) +
  scale_color_manual(values=micolor) +
  ggtitle("Reference WCS-MC-020 spectra")


ggplot(data=refldat) +
  #geom_line(data=RefMCAA01, aes(x=Wavelength, y=Reflectance), color="black") +
  #geom_point(data=RefMCAA01, aes(x=Wavelength, y=Reflectance), color="black",size=.5) +
  geom_point(aes(x=Wavelength, y=Reflectance, color=source),size=1) +
  geom_line(aes(x=Wavelength, y=Reflectance, color=source)) +
  geom_errorbar(aes(x=Wavelength,ymin=Reflectance-sd, ymax=Reflectance+sd, color=source),
                width=.1,
                position=position_dodge(0.05)) +
  #geom_rect(aes(xmin = 1450, xmax = 1600, ymin = 0.4, ymax =0.9),col="red",alpha=0) +
  xlim(c(1450,1600)) + ylim(c(0.4, 0.9)) + xlab("Wavelength (nm)") +
  theme(legend.position = c(0.85, 0.22)) +
  scale_color_manual(values=micolor) +
  ggtitle("Reference WCS-MC-020 spectra (Inset)")

Reminder: For the calculation of the Reflectance image

From these graphics, we can conclude:

  1. The standard deviation is higher in the first two options, which indicates that the variability introduced by the heterogeneous illumination has an impact even within a relatively small object.
  2. As the White image has lower values at the extremes, the global average is low and thus the calculated Reflectance is higher for the first two options.
  3. Taking into account both the illumination heterogeneity and the dark noise, Option 4 is the closest to the certified spectrum. Note this is the option we are regularly using to calculate Reflectance from images acquired by FX17 + Lumo scanner in HSILab.
  4. Note that the band width (fwhm) of the FX17 instrument implies that narrow absorption features in the certified spectrum appear smoother in FX17 spectra.
LS0tCnRpdGxlOiAiT3B0aW9ucyB0byBjYWxjdWxhdGUgUmVmbGVjdGFuY2UiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBmaWdfY2FwdGlvbjogVFJVRQotLS0KCiogQWd1c3Rpbi5Mb2JvQGdlbzNiY24uY3NpYy5lcwoqIDIwMjIwOTI1CgpUaGlzIHNjcmlwdCBjb21wYXJlcyA0IG9wdGlvbnMgdG8gY2FsY3VsYXRlIHJlZmxlY3RhbmNlIGZyb20gcmFkaWFuY2UgaW1hZ2VzIGFjcXVpcmVkIGJ5IGh5cGVyc3BlY3RyYWwgc2Nhbm5pbmcgc3lzdGVtcywgaW4gcGFydGljdWxhciBTcGVjaW0gRlgxNyBhbmQgRlgxMCBtb3VudGVkIG9uIGRlc2t0b3Atc2l6ZSBMdW1vIFNjYW5uZXIgc3RhbmRzOgoKKiBPcHRpb24gMS4gUmFkaWFuY2UgaW1hZ2UgZGl2aWRlZCBieSB0aGUgZ2xvYmFsIGF2ZXJhZ2Ugc3BlY3RyYSBvZiB0aGUgcmFkaWFuY2Ugd2hpdGUgaW1hZ2UgIAoqUmVmbChpLGosaykgPSBSYWQoaSxqLGspL1JhZChrKSogIAoqIE9wdGlvbiAyLiBBcyAoMSksIGJ1dCBzdWJ0cmFjdGluZyB0aGUgYXZlcmFnZSBzcGVjdHJhIG9mIHRoZSBEYXJrIGltYWdlICAKKlJlZmwoaSxqLGspID0gKFJhZChpLGosayktRGFyayhrKSkvKFJhZChrKS1EYXJrKGspKSogIAoqIE9wdGlvbiAzLiBSYWRpYW5jZSBpbWFnZSBkaXZpZGVkIGJ5IHRoZSByYWRpYW5jZSB2YWx1ZSBvZiB0aGUgY29ycmVzcG9uZGluZyBjb2x1bW4gaW4gdGhlIHdoaXRlIGltYWdlICAKKlJlZmwoaSxqLGspID0gUmFkKGksaixrKS9SYWQoaixrKSogIAoqIE9wdGlvbiA0LiBBcyAoMyksIGJ1dCBzdWJ0cmFjdGluZyB0aGUgYXZlcmFnZSBzcGVjdHJhIG9mIHRoZSBEYXJrIGltYWdlICAKKlJlZmwoaSxqLGspID0gKFJhZChpLGosayktRGFyayhrKSkvKFJhZChqLGspLURhcmsoaykpKiAgCgpPcHRpb25zIDMgYW5kIDQgYWNjb3VudCBmb3IgaGV0ZXJvZ2VuZW91cyBpbGx1bWluYXRpb24gYWxvbmcgY29sdW1ucyBvZiB0aGUgY2FwdHVyZWQgcm93LCB3aGlsZSBvcHRpb25zIDIgYW5kIDQgYWNjb3VudCBmb3IgZGFyayBub2lzZS4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiNEaXJlY3RvcmllcwpkZWZhdWx0VyA8LSBnZXRPcHRpb24oIndhcm4iKQpvcHRpb25zKHdhcm49LTEpClJTcGVjdERpciA8LSAiL2hvbWUvYWxvYm8vb3duY2xvdWRSU3BlY3QvUlNwZWN0IgppbWFkaXIgPC0gIi4uL0ZYMTdfRGVjZXB0aW9uSXNsYW5kMzFfMjAxOC0wNC0yMF8wNy00Ni01My9GWDE3L2NhcHR1cmUiCnZkaXIgPC0gIi4uL0ZYMTdfRGVjZXB0aW9uSXNsYW5kMzFfMjAxOC0wNC0yMF8wNy00Ni01My9RR0lTIgojUiBwYWNrYWdlcwpsaWJyYXJ5KHRlcnJhLCBxdWlldGx5PVRSVUUpCmxpYnJhcnkodGlkeXRlcnJhLCBxdWlldGx5PVRSVUUpCmxpYnJhcnkocmVzaGFwZTIsIHF1aWV0bHk9VFJVRSkKbGlicmFyeShnZ3Bsb3QyLCBxdWlldGx5PVRSVUUpCiNSZXF1aXJlZCBpbmZvcm1hdGlvbgpsb2FkKGZpbGUucGF0aChSU3BlY3REaXIsIkZYMTdiYW5kcy5yZGEiKSkgI2NlbnRyYWwgd2F2ZWxlbmd0aCBvZiBGWDE3IHNwZWN0cmFsIGJhbmRzIApsb2FkKGZpbGUucGF0aChSU3BlY3REaXIsIkZYMTd3ZWlyZHMucmRhIikpI3N5c3RlbWF0aWMgYWJub3JtYWwgdmFsdWVzIG9mIG91ciBGWDE3IGluc3RydW1lbnQKbG9hZChmaWxlLnBhdGgoUlNwZWN0RGlyLCJSZWZNQ0FBMDEucmRhIikpICNjZXJ0aWZpZWQgcmVmbGVjdGFuY2Ugc3BlY3RyYSBvZiBXQ1MtTUMtMDEwIAoKYGBgCgojIyBJbnRyb2R1Y3Rpb24KS2VlcCBpbiBtaW5kIHRoYXQgdGhlc2UgaW1hZ2VzIGFyZSBpbiBmYWN0IHN0YWNrcyBvZiAxIHJvdyB4IDY1MCBjb2x1bW4gaHlwZXJzcGVjdHJhbCBwaXhlbHMsIGFuZCB0aGF0IHRoZSBsaWdodGluZyBzeXN0ZW0gZG9lcyBub3QgcHJvdmlkZSBhIHVuaWZvcm0gaWxsdW1pbmF0aW9uLiBUaGUgUmFkaWFuY2UgV2hpdGUgaW1hZ2UgY29ycmVzcG9uZHMgdG8gYSBuYXJyb3cgd2hpdGUgc3RpY2suIEFuIGV4YW1wbGU6CgpgYGB7ciBmaWcuYXNwID0gMC40LCBmaWcud2lkdGggPSA2LCBmaWcuY2FwID0gIkxpZmUgZXhwZWN0YW5jeSBmcm9tIDE5NTIifQogIG9wdGlvbnMod2Fybj0tMSkKICB0ZXJyYU9wdGlvbnMocHJvZ3Jlc3M9MCkKICBmbmFtZWltYVdyZWYgICAgPC0gbGlzdC5maWxlcyhpbWFkaXIscGF0dD1nbG9iMnJ4KCJXSElURVJFRioucmF3IikpWzFdIAogIGZuYW1laW1hZGFya3JlZiA8LSBsaXN0LmZpbGVzKGltYWRpcixwYXR0PWdsb2IycngoIkRBUktSRUYqLnJhdyIpKVsxXQogIGZuYW1laW1hUmFkICAgICA8LSBsaXN0LmZpbGVzKGltYWRpcixwYXR0PWdsb2IycngoIkZYMTcqLnJhdyIpKVsxXQogIFdJbWEgPC0gcmFzdChmaWxlLnBhdGgoaW1hZGlyLGZuYW1laW1hV3JlZikpCiAgbmFtZXMoV0ltYSkgPC0gcGFzdGUwKCJCIiwxOm5seXIoV0ltYSkpCiAgc3VmIDwtIHVubGlzdChzdHJzcGxpdChmbmFtZWltYVdyZWYsIldISVRFUkVGX0ZYMTdfIikpWzJdCiAgc3VmIDwtIHVubGlzdChzdHJzcGxpdChzdWYsIi5yYXciKSlbMV0KICBmbmFtZWltYVdyZWYyICAgPC0gcGFzdGUwKCJXSElURVJFRl9CSUdfRlgxN18iLHN1ZikKICBXSW1hMiA8LSByYXN0KGZpbGUucGF0aChpbWFkaXIsZm5hbWVpbWFXcmVmMikpCiAgbmFtZXMoV0ltYTIpIDwtIHBhc3RlMCgiQiIsMTpubHlyKFdJbWEpKQogIGRlZmJhbmRzPWMoMTcwLCAxNCwgODYpCgogIGEgPC0gc3Vic2V0KFdJbWEyLCBzdWJzZXQ9ZGVmYmFuZHMpCiAgZGVsbWUgPC0gY3JvcChhLGV4dChXSW1hKSkKICBhczEgPC0gc3RyZXRjaChkZWxtZSxzbWluPTAsIHNtYXg9NDA5NikKICBhczIgPC0gc3RyZXRjaChkZWxtZSxtaW5xPTAuMDIsIG1heHE9MC45OCkKICBjcnMoYXMyKSA8LSAiIgogIGdnVzJzdCA8LSBnZ3Bsb3QoKSArIAogICAgZ2VvbV9zcGF0cmFzdGVyX3JnYihkYXRhID0gYXMyKSArCiAgICBjb29yZF9maXhlZChyYXRpbyA9IDEpICsKICAgIHRoZW1lX3ZvaWQoKSArICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC4xMjUpLCBwbG90Lm1hcmdpbj11bml0KGMoMCwwLDAsMCksIm1tIikpICsKICAgIGdndGl0bGUocGFzdGUoIlJhZGlhbmNlIFdoaXRlIGltYWdlIFxuKEJhbmRzICIsIHBhc3RlKGRlZmJhbmRzLGNvbGxhcHNlPSIsICIpLCAid2l0aCAyJSBzdHJldGNoaW5nKSIpKQogIHJtKGEsIGFzMSwgYXMyLGRlbG1lKQogIHByaW50KGdnVzJzdCkKYGBgCgpgYGB7ciBmaWcuYXNwID0gMC4zLCBmaWcud2lkdGggPSA2fQpvcHRpb25zKHdhcm49LTEpCldJbWEyLmRmICA8LSBhcy5kYXRhLmZyYW1lKG1lYW4oV0ltYTJbMSwsLGRyb3A9RkFMU0VdKSwgIGNlbGw9VFJVRSxuYS5ybT1GQUxTRSkKbWVhbVdJbWEyLmRmIDwtIGRhdGEuZnJhbWUoQ29sdW1uPVdJbWEyLmRmJGNlbGwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUmFkaWFuY2U9YyhXSW1hMi5kZiRtZWFuKSkKZ2dXMSA8LSBnZ3Bsb3QoZGF0YT1tZWFtV0ltYTIuZGYpICsKICAgIGdlb21fcG9pbnQoIGFlcyh4PUNvbHVtbiwgeT1SYWRpYW5jZSksc2l6ZT0wLjUpICsKICAgIHhsYWIoIkNvbHVtbiIpICsgeWxhYigiTWVhbiBCYW5kIFJhZGlhbmNlIikgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjc1LCAwLjE1KSkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogICAgc2NhbGVfc2l6ZV9pZGVudGl0eSgpIAogICAgI2dndGl0bGUoIlJhZGlhbmNlIG9mIFdoaXRlIFJlZmVyZW5jZSIpCiAgcHJpbnQoZ2dXMSkKYGBgCiAKIAojIyBSZWZsZWN0YW5jZSBvZiBXaGl0ZSBpbWFnZQoKIyMjIE9wdGlvbiAxIApSYWRpYW5jZSBpbWFnZSBkaXZpZGVkIGJ5IHRoZSBnbG9iYWwgYXZlcmFnZSBzcGVjdHJhIG9mIHRoZSByYWRpYW5jZSB3aGl0ZSBpbWFnZQoKYGBge3J9Cm9wdGlvbnMod2Fybj0tMSkKUmVmbFdJbWExIDwtIFdJbWEyL3VubGlzdChnbG9iYWwoV0ltYTIsbWVhbixuYS5ybT1UUlVFKSkKYGBgCgojIyMgT3B0aW9uIDIgClJhZGlhbmNlIGltYWdlIGRpdmlkZWQgYnkgdGhlIGdsb2JhbCBhdmVyYWdlIHNwZWN0cmEgb2YgdGhlIHJhZGlhbmNlIHdoaXRlIGltYWdlLCBhbmQgc3VidHJhY3RpbmcgdGhlIGF2ZXJhZ2Ugc3BlY3RyYSBvZiB0aGUgRGFyayBpbWFnZQoKYGBge3J9Cm9wdGlvbnMod2Fybj0tMSkKRGFya0ltYSAgIDwtIHJhc3QoZmlsZS5wYXRoKGltYWRpcixmbmFtZWltYWRhcmtyZWYpKQpkIDwtIGdsb2JhbChEYXJrSW1hLCBtZWFuLCBuYS5ybT1UUlVFKQpSZWZsV0ltYTIgPC0gKFdJbWEyLXVubGlzdChkKSkvKHVubGlzdChnbG9iYWwoV0ltYTIsbWVhbixuYS5ybT1UUlVFKSktdW5saXN0KGQpKQpgYGAKIyMjIE9wdGlvbiAzIApSYWRpYW5jZSBpbWFnZSBkaXZpZGVkIGJ5IHRoZSByYWRpYW5jZSB2YWx1ZSBvZiB0aGUgY29ycmVzcG9uZGluZyBjb2x1bW4gaW4gdGhlIHdoaXRlIGltYWdlCmBgYHtyfQpvcHRpb25zKHdhcm49LTEpClJlZmxXSW1hMyA8LSBXSW1hMi9XSW1hMgpgYGAKIyMjIE9wdGlvbiA0IApSYWRpYW5jZSBpbWFnZSBkaXZpZGVkIGJ5IHRoZSByYWRpYW5jZSB2YWx1ZSBvZiB0aGUgY29ycmVzcG9uZGluZyBjb2x1bW4gaW4gdGhlIHdoaXRlIGltYWdlLCB3aXRoIG5vaXNlIHN1YnRyYWN0ZWQuCihPYnZpb3VzbHksIGZvciB0aGUgY2FzZSBvZiB0aGUgV2hpdGUgaW1hZ2UsIG9wdGlvbnMgMyBhbmQgNCBhcmUgaWRlbnRpY2FsKS4KYGBge3J9Cm9wdGlvbnMod2Fybj0tMSkKUmVmbFdJbWE0IDwtIChXSW1hMi11bmxpc3QoZCkpLyhXSW1hMi11bmxpc3QoZCkpCmBgYAoKIyMjIENvbXBhcmlzb24KCmBgYHtyfQpvcHRpb25zKHdhcm49LTEpClJlZmxXSW1hMS5kZiAgPC0gYXMuZGF0YS5mcmFtZShtZWFuKFJlZmxXSW1hMVsxLCwsZHJvcD1GQUxTRV0pLCAgY2VsbD1UUlVFLG5hLnJtPUZBTFNFKQpSZWZsV0ltYTIuZGYgIDwtIGFzLmRhdGEuZnJhbWUobWVhbihSZWZsV0ltYTJbMSwsLGRyb3A9RkFMU0VdKSwgIGNlbGw9VFJVRSxuYS5ybT1GQUxTRSkKUmVmbFdJbWEzLmRmICA8LSBhcy5kYXRhLmZyYW1lKG1lYW4oUmVmbFdJbWEzWzEsLCxkcm9wPUZBTFNFXSksICBjZWxsPVRSVUUsbmEucm09RkFMU0UpClJlZmxXSW1hNC5kZiAgPC0gYXMuZGF0YS5mcmFtZShtZWFuKFJlZmxXSW1hNFsxLCwsZHJvcD1GQUxTRV0pLCAgY2VsbD1UUlVFLG5hLnJtPUZBTFNFKQptZWFtUmVmbFdJbWFzLmRmIDwtIGRhdGEuZnJhbWUoQ29sdW1uPVJlZmxXSW1hMS5kZiRjZWxsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNvdXJjZT1yZXAocGFzdGUwKCJPcHRpb24gIiwgMTo0KSwgcmVwKG5jb2woUmVmbFdJbWExKSwgNCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlZmxlY3RhbmNlPWMoUmVmbFdJbWExLmRmJG1lYW4sUmVmbFdJbWEyLmRmJG1lYW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSZWZsV0ltYTMuZGYkbWVhbixSZWZsV0ltYTQuZGYkbWVhbikpCmdnVzFfVzQgPC0gZ2dwbG90KGRhdGE9bWVhbVJlZmxXSW1hcy5kZikgKwogICAgZ2VvbV9wb2ludCggYWVzKHg9Q29sdW1uLCB5PVJlZmxlY3RhbmNlLCBjb2xvcj1Tb3VyY2UpLHNpemU9MC41KSArCiAgICB4bGFiKCJDb2x1bW4iKSArIHlsYWIoIk1lYW4gUmVmbGVjdGFuY2UgYWNyb3NzIGJhbmRzIikgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjc1LCAwLjE1KSkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogICAgc2NhbGVfc2l6ZV9pZGVudGl0eSgpICsKICAgIGdndGl0bGUoIlJlZmxlY3RhbmNlIG9mIFdoaXRlIFJlZmVyZW5jZSIpCiAgcHJpbnQoZ2dXMV9XNCkKYGBgCgoKTm90ZSB0aGF0LCBpbiB0aGlzIHJhdGhlciBleHRyZW1lIGNhc2UsIHRoZSBpbGx1bWluYXRpb24gaGV0ZXJvZ2VuZWl0eSB3aWxsIGxpa2VseSBpbXBseSBhIHNldmVyZSBlcnJvciBpbiB0aGUgY2FsY3VsYXRlZCByZWZsZWN0YW5jZSBpbWFnZSBpZiBsaWdodGluZyBkaWZmZXJlbmNlcyBhbG9uZyB0aGUgd2hpdGUgcmVmZXJlbmNlIGJhciBhcmUgbm90IHRha2VuIGludG8gYWNjb3VudCAob3B0aW9ucyAxIGFuZCAyKS4KCiMjIFJlZmxlY3RhbmNlIG9mIHN0YW5kYXJkIHJlZmVyZW5jZQpJbiB0aGlzIHNlY3Rpb24gd2UgdXNlIHRoZSByYWRpYW5jZSBpbWFnZSBvZiBhIExhYnNwaGVyZSBkaWZmdXNlIHJlZmxlY3RhbmNlIHN0YW5kYXJkIChXQ1MtTUMtMDIwKSwgYW5kIHNob3cgdGhlIHJlZmxlY3RhbmNlIHNwZWN0cmEgZm9sbG93aW5nIGFsbCBmb3VyIG9wdGlvbnMgY29tcGFyZWQgdG8gdGhlIGNlcnRpZmllZCBzcGVjdHJ1bS4gV2UgY3JvcCB0aGUgb2JqZWN0IGZyb20gdGhlIHNhbWUgc2Nhbm5lZCByYWRpYW5jZSBkYXRhc2V0IGFzIGFib3ZlLCBmb3Igd2hpY2ggd2UgdXNlIGFuIGludGVyYWN0aXZlbHkgZGlnaXRpemVkIHZlY3RvciBvYmplY3QuCgpgYGB7ciBmaWcuYXNwID0gMSwgZmlnLndpZHRoID0gMn0Kb3B0aW9ucyh3YXJuPS0xKQpmbmFtZWltYVJhZCA8LSBsaXN0LmZpbGVzKGltYWRpcixwYXR0PWdsb2IycngoIkZYMTcqLnJhdyIpKVsxXQpSYWRJbWEgPC0gcmFzdChmaWxlLnBhdGgoaW1hZGlyLGZuYW1laW1hUmFkKSkKbmFtZXMoUmFkSW1hKSA8LSBwYXN0ZTAoIkIiLDE6bmx5cihSYWRJbWEpKQp2IDwtIHZlY3QoZmlsZS5wYXRoKHZkaXIsIkRJMzEuc2hwIikpWzYsXQpleHQoUmFkSW1hKVszOjRdIDwtIGMoLWV4dChSYWRJbWEpWzRdLGV4dChSYWRJbWEpWzNdKSNhZGFwdCB0byB2IGdlb21ldHJ5CgphIDwtIHN1YnNldChSYWRJbWEsIHN1YnNldD1kZWZiYW5kcykKZGVsbWUgPC0gY3JvcChhLGV4dChSYWRJbWEpKQphczEgPC0gc3RyZXRjaChkZWxtZSxzbWluPTAsIHNtYXg9NDA5NikKYXMyIDwtIHN0cmV0Y2goZGVsbWUsbWlucT0wLjAyLCBtYXhxPTAuOTgpCnBsb3RSR0IoYXMyLCBleHQ9ZXh0KHYpICsgYygxMCwxMCwxMCwxMCkgKQpwbG90KHYsYWRkPVRSVUUpCnJtKGEsIGRlbG1lLCBhczEsIGFzMikKYGBgCgpXZSBjYWxjdWxhdGUgYW5kIHBsb3QgUmVmbGVjdGFuY2UgYWNjb3JkaW5nIHRvIGFsbCBmb3VyIG9wdGlvbnMgYW5kIGNvbXBhcmUgdGhlbSB0byB0aGUgY2VydGlmaWVkIHNwZWN0cnVtOiAKYGBge3J9Cm9wdGlvbnMod2Fybj0tMSkKZXh0KFJhZEltYSlbMzo0XSA8LSBjKGV4dChSYWRJbWEpWzRdLC1leHQoUmFkSW1hKVszXSkjcmVjb3ZlciByYXN0ZXIgZ2VvbWV0cnkKUmVmbEltYTEgPC0gUmFkSW1hL3VubGlzdChnbG9iYWwoV0ltYTIsbWVhbixuYS5ybT1UUlVFKSkKI0RhcmtJbWEgICA8LSByYXN0KGZpbGUucGF0aChpbWFkaXIsZm5hbWVpbWFkYXJrcmVmKSkKI2QgPC0gZ2xvYmFsKERhcmtJbWEsIG1lYW4sIG5hLnJtPVRSVUUpClJlZmxJbWEyIDwtIChSYWRJbWEtdW5saXN0KGQpKS8odW5saXN0KGdsb2JhbChXSW1hMixtZWFuLG5hLnJtPVRSVUUpKS11bmxpc3QoZCkpClJlZmxJbWEzIDwtIFJhZEltYS9XSW1hMgpSZWZsSW1hNCA8LSAoUmFkSW1hLXVubGlzdChkKSkvKFdJbWEyLXVubGlzdChkKSkKI0V4dHJhY3QgdmFsdWVzIGFuZCBjYWxjdWxhdGUKZXh0KFJlZmxJbWExKVszOjRdIDwtIGMoLWV4dChSZWZsSW1hMSlbNF0sZXh0KFJlZmxJbWExKVszXSkjYWRhcHQgdG8gdiBnZW9tZXRyeQpleHQoUmVmbEltYTIpWzM6NF0gPC0gYygtZXh0KFJlZmxJbWEyKVs0XSxleHQoUmVmbEltYTIpWzNdKSNhZGFwdCB0byB2IGdlb21ldHJ5CmV4dChSZWZsSW1hMylbMzo0XSA8LSBjKC1leHQoUmVmbEltYTMpWzRdLGV4dChSZWZsSW1hMylbM10pI2FkYXB0IHRvIHYgZ2VvbWV0cnkKZXh0KFJlZmxJbWE0KVszOjRdIDwtIGMoLWV4dChSZWZsSW1hNClbNF0sZXh0KFJlZmxJbWE0KVszXSkjYWRhcHQgdG8gdiBnZW9tZXRyeQoKcmVmbDFkYXQubSAgPC0gdGVycmE6OmV4dHJhY3QoUmVmbEltYTEsIHYsIGZ1bj1tZWFuLCBuYS5ybT1UUlVFKQpyZWZsMWRhdC5zZCA8LSB0ZXJyYTo6ZXh0cmFjdChSZWZsSW1hMSwgdiwgZnVuPXNkLCBuYS5ybT1UUlVFKQpyZWZsMmRhdC5tICA8LSB0ZXJyYTo6ZXh0cmFjdChSZWZsSW1hMiwgdiwgZnVuPW1lYW4sIG5hLnJtPVRSVUUpCnJlZmwyZGF0LnNkIDwtIHRlcnJhOjpleHRyYWN0KFJlZmxJbWEyLCB2LCBmdW49c2QsIG5hLnJtPVRSVUUpCnJlZmwzZGF0Lm0gIDwtIHRlcnJhOjpleHRyYWN0KFJlZmxJbWEzLCB2LCBmdW49bWVhbiwgbmEucm09VFJVRSkKcmVmbDNkYXQuc2QgPC0gdGVycmE6OmV4dHJhY3QoUmVmbEltYTMsIHYsIGZ1bj1zZCwgbmEucm09VFJVRSkKcmVmbDRkYXQubSAgPC0gdGVycmE6OmV4dHJhY3QoUmVmbEltYTQsIHYsIGZ1bj1tZWFuLCBuYS5ybT1UUlVFKQpyZWZsNGRhdC5zZCA8LSB0ZXJyYTo6ZXh0cmFjdChSZWZsSW1hNCwgdiwgZnVuPXNkLCBuYS5ybT1UUlVFKQoKcmVmbDFkYXQgPC0gZGF0YS5mcmFtZShSZWZsZWN0YW5jZT10KHJlZmwxZGF0Lm1bLTFdKSxzZD10KHJlZmwxZGF0LnNkWy0xXSkpCnJlZmwxZGF0JEJhbmRuYiA8LSBGWDE3YmFuZHMkQmFuZG5iCnJlZmwxZGF0JFdhdmVsZW5ndGggPC0gRlgxN2JhbmRzJFdhdmVsZW5ndGgKcmVmbDFkYXQkc291cmNlIDwtICJPcHRpb24gMSIKI2hlYWQocmVmbDFkYXQpCgpyZWZsMmRhdCA8LSBkYXRhLmZyYW1lKFJlZmxlY3RhbmNlPXQocmVmbDJkYXQubVstMV0pLHNkPXQocmVmbDJkYXQuc2RbLTFdKSkKcmVmbDJkYXQkQmFuZG5iIDwtIEZYMTdiYW5kcyRCYW5kbmIKcmVmbDJkYXQkV2F2ZWxlbmd0aCA8LSBGWDE3YmFuZHMkV2F2ZWxlbmd0aApyZWZsMmRhdCRzb3VyY2UgPC0gIk9wdGlvbiAyIgojaGVhZChyZWZsMmRhdCkKCnJlZmwzZGF0IDwtIGRhdGEuZnJhbWUoUmVmbGVjdGFuY2U9dChyZWZsM2RhdC5tWy0xXSksc2Q9dChyZWZsM2RhdC5zZFstMV0pKQpyZWZsM2RhdCRCYW5kbmIgPC0gRlgxN2JhbmRzJEJhbmRuYgpyZWZsM2RhdCRXYXZlbGVuZ3RoIDwtIEZYMTdiYW5kcyRXYXZlbGVuZ3RoCnJlZmwzZGF0JHNvdXJjZSA8LSAiT3B0aW9uIDMiCiNoZWFkKHJlZmwzZGF0KQoKcmVmbDRkYXQgPC0gZGF0YS5mcmFtZShSZWZsZWN0YW5jZT10KHJlZmw0ZGF0Lm1bLTFdKSxzZD10KHJlZmw0ZGF0LnNkWy0xXSkpCnJlZmw0ZGF0JEJhbmRuYiA8LSBGWDE3YmFuZHMkQmFuZG5iCnJlZmw0ZGF0JFdhdmVsZW5ndGggPC0gRlgxN2JhbmRzJFdhdmVsZW5ndGgKcmVmbDRkYXQkc291cmNlIDwtICJPcHRpb24gNCIKI2hlYWQocmVmbDRkYXQpCgpyZWZsZGF0IDwtIHJiaW5kKHJlZmwxZGF0LCByZWZsMmRhdCwgcmVmbDNkYXQsIHJlZmw0ZGF0KQojaGVhZChyZWZsZGF0KQphIDwtIGRhdGEuZnJhbWUoUmVmbGVjdGFuY2U9UmVmTUNBQTAxJFJlZmxlY3RhbmNlLCBzZD0wLjAsIAogICAgICAgICAgICAgICAgIEJhbmRuYj1OQSwgV2F2ZWxlbmd0aD1SZWZNQ0FBMDEkV2F2ZWxlbmd0aCwgc291cmNlPSJDZXJ0aWZpZWQiKQpyZWZsZGF0IDwtIHJiaW5kKHJlZmxkYXQsIGEpCnJtKGEpCiNtaWNvbG9yIDwtIGMoICJvcmFuZ2UiLCAicmVkIiwgImJsdWUiLCAiY3lhbiIpCiNEZWZhdWx0IGNvbG9ycwpnZ3Bsb3RDb2xvdXJzIDwtIGZ1bmN0aW9uKG4gPSA2LCBoID0gYygwLCAzNjApICsgMTUpewogIGlmICgoZGlmZihoKSAlJSAzNjApIDwgMSkgaFsyXSA8LSBoWzJdIC0gMzYwL24KICBoY2woaCA9IChzZXEoaFsxXSwgaFsyXSwgbGVuZ3RoID0gbikpLCBjID0gMTAwLCBsID0gNjUpCn0KCm1pY29sb3IgPC0gZ2dwbG90Q29sb3VycyhuPTQpCm1pY29sb3IgPC0gYygiT3B0aW9uIDEiPW1pY29sb3JbMV0sICJPcHRpb24gMiI9bWljb2xvclsyXSwgCiAgICAgICAgICAgICAiT3B0aW9uIDMiPW1pY29sb3JbM10sICJPcHRpb24gNCI9bWljb2xvcls0XSwgCiAgICAgICAgICAgICAiQ2VydGlmaWVkIj0gImJsYWNrIiApCmdncGxvdChkYXRhPXJlZmxkYXQpICsKICAjZ2VvbV9saW5lKGRhdGE9UmVmTUNBQTAxLCBhZXMoeD1XYXZlbGVuZ3RoLCB5PVJlZmxlY3RhbmNlKSwgY29sb3I9ImJsYWNrIikgKwogICNnZW9tX3BvaW50KGRhdGE9UmVmTUNBQTAxLCBhZXMoeD1XYXZlbGVuZ3RoLCB5PVJlZmxlY3RhbmNlKSwgY29sb3I9ImJsYWNrIixzaXplPS4yNSkgKwogIGdlb21fcG9pbnQoYWVzKHg9V2F2ZWxlbmd0aCwgeT1SZWZsZWN0YW5jZSwgY29sb3I9c291cmNlKSxzaXplPTEpICsKICBnZW9tX2xpbmUoYWVzKHg9V2F2ZWxlbmd0aCwgeT1SZWZsZWN0YW5jZSwgY29sb3I9c291cmNlKSkgKwogIGdlb21fZXJyb3JiYXIoYWVzKHg9V2F2ZWxlbmd0aCx5bWluPVJlZmxlY3RhbmNlLXNkLCB5bWF4PVJlZmxlY3RhbmNlK3NkLCBjb2xvcj1zb3VyY2UpLAogICAgICAgICAgICAgICAgd2lkdGg9LjEsCiAgICAgICAgICAgICAgICBwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSgwLjA1KSkgKwogIGdlb21fcmVjdChhZXMoeG1pbiA9IDE0NTAsIHhtYXggPSAxNjAwLCB5bWluID0gMC40LCB5bWF4ID0wLjkpLGNvbD0icmVkIixhbHBoYT0wKSArCiAgeGxpbShyYW5nZShGWDE3YmFuZHMkV2F2ZWxlbmd0aCkpICsgeGxhYigiV2F2ZWxlbmd0aCAobm0pIikrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjkxLCAwLjIzKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9bWljb2xvcikgKwogIGdndGl0bGUoIlJlZmVyZW5jZSBXQ1MtTUMtMDIwIHNwZWN0cmEiKQoKZ2dwbG90KGRhdGE9cmVmbGRhdCkgKwogICNnZW9tX2xpbmUoZGF0YT1SZWZNQ0FBMDEsIGFlcyh4PVdhdmVsZW5ndGgsIHk9UmVmbGVjdGFuY2UpLCBjb2xvcj0iYmxhY2siKSArCiAgI2dlb21fcG9pbnQoZGF0YT1SZWZNQ0FBMDEsIGFlcyh4PVdhdmVsZW5ndGgsIHk9UmVmbGVjdGFuY2UpLCBjb2xvcj0iYmxhY2siLHNpemU9LjUpICsKICBnZW9tX3BvaW50KGFlcyh4PVdhdmVsZW5ndGgsIHk9UmVmbGVjdGFuY2UsIGNvbG9yPXNvdXJjZSksc2l6ZT0xKSArCiAgZ2VvbV9saW5lKGFlcyh4PVdhdmVsZW5ndGgsIHk9UmVmbGVjdGFuY2UsIGNvbG9yPXNvdXJjZSkpICsKICBnZW9tX2Vycm9yYmFyKGFlcyh4PVdhdmVsZW5ndGgseW1pbj1SZWZsZWN0YW5jZS1zZCwgeW1heD1SZWZsZWN0YW5jZStzZCwgY29sb3I9c291cmNlKSwKICAgICAgICAgICAgICAgIHdpZHRoPS4xLAogICAgICAgICAgICAgICAgcG9zaXRpb249cG9zaXRpb25fZG9kZ2UoMC4wNSkpICsKICAjZ2VvbV9yZWN0KGFlcyh4bWluID0gMTQ1MCwgeG1heCA9IDE2MDAsIHltaW4gPSAwLjQsIHltYXggPTAuOSksY29sPSJyZWQiLGFscGhhPTApICsKICB4bGltKGMoMTQ1MCwxNjAwKSkgKyB5bGltKGMoMC40LCAwLjkpKSArIHhsYWIoIldhdmVsZW5ndGggKG5tKSIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuODUsIDAuMjIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1taWNvbG9yKSArCiAgZ2d0aXRsZSgiUmVmZXJlbmNlIFdDUy1NQy0wMjAgc3BlY3RyYSAoSW5zZXQpIikKCgpnZ3Bsb3QoZGF0YT1yZWZsZGF0KSArCiAgZ2VvbV9wb2ludChhZXMoeD1XYXZlbGVuZ3RoLCB5PVJlZmxlY3RhbmNlLCBjb2xvcj1zb3VyY2UpLHNpemU9MSkgKwogIGZhY2V0X3dyYXAofnNvdXJjZSkKYGBgCipSZW1pbmRlcjogRm9yIHRoZSBjYWxjdWxhdGlvbiBvZiB0aGUgUmVmbGVjdGFuY2UgaW1hZ2UqCgoqICpPcHRpb24gMS4gRGFyayBub2lzZSBub3QgdGFrZW4gaW50byBhY2NvdW50LiBJbGx1bWluYXRpb24gaGV0ZXJvZ2VuZWl0eSBub3QgdGFrZW4gaW50byBhY2NvdW50LioKKiAqT3B0aW9uIDIuIERhcmsgbm9pc2UgdGFrZW4gaW50byBhY2NvdW50LiBJbGx1bWluYXRpb24gaGV0ZXJvZ2VuZWl0eSBub3QgdGFrZW4gaW50byBhY2NvdW50LioKKiAqT3B0aW9uIDMuIERhcmsgbm9pc2Ugbm90IHRha2VuIGludG8gYWNjb3VudC4gSWxsdW1pbmF0aW9uIGhldGVyb2dlbmVpdHkgdGFrZW4gaW50byBhY2NvdW50LioKKiAqT3B0aW9uIDQuIERhcmsgbm9pc2UgdGFrZW4gaW50byBhY2NvdW50LiBJbGx1bWluYXRpb24gaGV0ZXJvZ2VuZWl0eSB0YWtlbiBpbnRvIGFjY291bnQuKgoKRnJvbSB0aGVzZSBncmFwaGljcywgd2UgY2FuIGNvbmNsdWRlOgoKMS4gVGhlIHN0YW5kYXJkIGRldmlhdGlvbiBpcyBoaWdoZXIgaW4gdGhlIGZpcnN0IHR3byBvcHRpb25zLCB3aGljaCBpbmRpY2F0ZXMgdGhhdCB0aGUgdmFyaWFiaWxpdHkgaW50cm9kdWNlZCBieSB0aGUgaGV0ZXJvZ2VuZW91cyBpbGx1bWluYXRpb24gaGFzIGFuIGltcGFjdCBldmVuIHdpdGhpbiBhIHJlbGF0aXZlbHkgc21hbGwgb2JqZWN0LgoyLiBBcyB0aGUgV2hpdGUgaW1hZ2UgaGFzIGxvd2VyIHZhbHVlcyBhdCB0aGUgZXh0cmVtZXMsIHRoZSBnbG9iYWwgYXZlcmFnZSBpcyBsb3cgYW5kIHRodXMgdGhlIGNhbGN1bGF0ZWQgUmVmbGVjdGFuY2UgaXMgaGlnaGVyIGZvciB0aGUgZmlyc3QgdHdvIG9wdGlvbnMuCjMuIFRha2luZyBpbnRvIGFjY291bnQgYm90aCB0aGUgaWxsdW1pbmF0aW9uIGhldGVyb2dlbmVpdHkgYW5kIHRoZSBkYXJrIG5vaXNlLCBPcHRpb24gNCBpcyB0aGUgY2xvc2VzdCB0byB0aGUgY2VydGlmaWVkIHNwZWN0cnVtLiAqKk5vdGUgdGhpcyBpcyB0aGUgb3B0aW9uIHdlIGFyZSByZWd1bGFybHkgdXNpbmcgdG8gY2FsY3VsYXRlIFJlZmxlY3RhbmNlIGZyb20gaW1hZ2VzIGFjcXVpcmVkIGJ5ICBGWDE3ICsgTHVtbyBzY2FubmVyIGluIEhTSUxhYi4qKgo0LiBOb3RlIHRoYXQgdGhlIGJhbmQgd2lkdGggKGZ3aG0pIG9mIHRoZSBGWDE3IGluc3RydW1lbnQgaW1wbGllcyB0aGF0IG5hcnJvdyBhYnNvcnB0aW9uIGZlYXR1cmVzIGluIHRoZSBjZXJ0aWZpZWQgc3BlY3RydW0gYXBwZWFyIHNtb290aGVyIGluIEZYMTcgc3BlY3RyYS4gCg==