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.
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
- Option 1. Dark noise not taken into account. Illumination
heterogeneity not taken into account.
- Option 2. Dark noise taken into account. Illumination
heterogeneity not taken into account.
- Option 3. Dark noise not taken into account. Illumination
heterogeneity taken into account.
- Option 4. Dark noise taken into account. Illumination
heterogeneity taken into account.
From these graphics, we can conclude:
- 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.
- 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.
- 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.
- 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==