Introduction
Gray Level Co-Occurrence Matrix (GLCM) (Haralick et al. 1973) texture is a powerful image feature for image analysis. The glcm package provides a easy-to-use function to calculate such textural features for RasterLayer objects in R.
This notebook explores how useful could be textural metrics for identification of crop stress from Landsat 5 images.
Texture metrics background
The following table shows commom texture metrics:
In the above table, Pi,j is the probability of values i and j occurring in adjacent pixels in the original image within the window defining the neighbourhood. i and j are the labels of the columns and rows (respectively) of the GLCM. Because of the construction of the GLCM, i refers to the digital number (DN) value of a target pixel, and j is the DN value of its immediate neighbour (rook’s case). In the GLCM Cor equation, μ is the mean and σ the standard deviation, both as defined by the equations for GLCM Mean and GLCM Var.
Load libraries & read data
Let’s load the glcm and raster libraries
##install.packages(c("glcm", "rgdal", "raster"))
library(glcm)
library(raster)
Now, let’s add two Landsat 5 color compositions over a USA farm from July 13, 2005. These images have been downloaded from this link: NASA Landsat page
green <- stack("./greenmap.JPG")
plot(green)

In the above figure, four of the fields are shown in R-based default color. The bottom left is a bare field. The top left is a spring wheat field. The top right two fields are sugar beets, and the bottom right is a confectionary sunflower field.
What object is green?
r
r green
class : RasterStack
dimensions : 942, 988, 930696, 3 (nrow, ncol, ncell, nlayers)
resolution : 1, 1 (x, y)
extent : 0, 988, 0, 942 (xmin, xmax, ymin, ymax)
crs : NA
names : greenmap.1, greenmap.2, greenmap.3
min values : 0, 0, 0
max values : 255, 255, 255
r
r plotRGB(green, 3, 2, 1, stretch=‘lin’, axes=TRUE, main=321)

Let’s try another library for reading the same color composition:
##install.packages("imager")
library(imager)
ngreen <- load.image("./greenmap.JPG")
plot(ngreen, main="CIMG Natural Color Composition")

r
r ngreen
Image. Width: 988 pix Height: 942 pix Depth: 1 Colour channels: 3
Converting the RGB image into a grayscale
(ggreen <- grayscale(ngreen, method = "XYZ", drop = TRUE))
Image. Width: 988 pix Height: 942 pix Depth: 1 Colour channels: 1
source("conversions.R")
rgreen <- as.raster.cimg(ggreen)
class(rgreen)
[1] "raster"
## plotting RGB converted to grayscale
plot(rgreen)

As there is no direct conversion from Cimg format into RasterLayer format, we have to take a long path: (i) save the Cimg image as jpg; and (ii) read the jpg as raster layer:
imager::save.image(ggreen,"./RGBgrayscale.jpeg")
(rgreen <- raster("./RGBgrayscale.jpeg"))
class : RasterLayer
dimensions : 942, 988, 930696 (nrow, ncol, ncell)
resolution : 1, 1 (x, y)
extent : 0, 988, 0, 942 (xmin, xmax, ymin, ymax)
crs : NA
source : /cloud/project/RGBgrayscale.jpeg
names : RGBgrayscale
values : 0, 255 (min, max)
plot(rgreen, col = gray.colors(10, start = 0.3, end = 0.9, gamma = 2.2, alpha = NULL),
axes=TRUE, main= "RGB321 as grayscale")

False color composition
Now, let’s load an infrared color composition RGB 4-3-2 (the near infrared, red, and green bands), a composite combination used for vegetation where darker patches show stressed plants.
nred <- load.image("./redmap.JPG")
plot(nred, main="CIMG False Color Composition")

Converting the RGB432 composition into a grayscale image
(gred <- grayscale(nred, method = "XYZ", drop = TRUE))
Image. Width: 992 pix Height: 937 pix Depth: 1 Colour channels: 1
imager::save.image(gred,"./NIRgrayscale.jpeg")
(rred <- raster("./NIRgrayscale.jpeg"))
class : RasterLayer
dimensions : 937, 992, 929504 (nrow, ncol, ncell)
resolution : 1, 1 (x, y)
extent : 0, 992, 0, 937 (xmin, xmax, ymin, ymax)
crs : NA
source : /cloud/project/NIRgrayscale.jpeg
names : NIRgrayscale
values : 0, 255 (min, max)
Reading the RGB432 composition as a raster stack
Let’s read the same color composition using the raster package:
red <- stack("./redmap.JPG")
plot(red)

The ground truth
truth <- load.image("./Colored.JPG")
plot(truth, main="The ground truth")

The colored image shows just the near infrared band processed so that the yellow, green and teal colors show stressed plants and the blue and purple show healthier plants. Focusing on the sugar beet field, the magenta color shows areas with excess nitrogen and the lighter green zones are sugar beets under stress.
Textural metrics for green band
Now, we can compute textural metrics using the true color composition:
Greenglcm <- glcm(green[[2]],
window = c(9,9),
shift = c(1,1),
statistics = c("mean", "variance", "homogeneity", "contrast",
"dissimilarity", "entropy", "second_moment", "correlation")
)
Greenglcm
class : RasterStack
dimensions : 942, 988, 930696, 8 (nrow, ncol, ncell, nlayers)
resolution : 1, 1 (x, y)
extent : 0, 988, 0, 942 (xmin, xmax, ymin, ymax)
crs : NA
names : glcm_mean, glcm_variance, glcm_homogeneity, glcm_contrast, glcm_dissimilarity, glcm_entropy, glcm_second_moment, glcm_correlation
min values : 0.18750000, 33.75601034, 0.33384539, 0.00000000, 0.00000000, 0.00000000, 0.05380277, -Inf
max values : 1.000000, 961.119602, 1.000000, 125.074074, 7.185185, 3.054695, 1.000000, Inf
graphics::par(mfrow = c(3, 3))
plot(Greenglcm)

Textural metrics for RGB321 converted to grayscale image
RGBglcm <- glcm(rgreen,
window = c(9,9),
shift = c(1,1),
statistics = c("mean", "variance", "homogeneity", "contrast",
"dissimilarity", "entropy", "second_moment", "correlation")
)
RGBglcm
class : RasterStack
dimensions : 942, 988, 930696, 8 (nrow, ncol, ncell, nlayers)
resolution : 1, 1 (x, y)
extent : 0, 988, 0, 942 (xmin, xmax, ymin, ymax)
crs : NA
names : glcm_mean, glcm_variance, glcm_homogeneity, glcm_contrast, glcm_dissimilarity, glcm_entropy, glcm_second_moment, glcm_correlation
min values : 0.18036265, 31.35315691, 0.21610246, 0.00000000, 0.00000000, 0.00000000, 0.02301478, -Inf
max values : 1.000000, 961.119602, 1.000000, 117.111111, 7.407407, 3.882937, 1.000000, Inf
graphics::par(mfrow = c(3, 3))
plot(RGBglcm)

Textural metrics for infrared band
Now, let’s do the computation using the first band (NIR) of the infrared color composite:
IRglcm <- glcm(red[[1]],
window = c(9,9),
shift = c(1,1),
statistics = c("mean", "variance", "homogeneity", "contrast",
"dissimilarity", "entropy", "second_moment", "correlation")
)
plot(IRglcm, main="Texture for IR band")

Textural metrics for RGB432 converted to grayscale band
GIRglcm <- glcm(rred,
window = c(9,9),
shift = c(1,1),
statistics = c("mean", "variance", "homogeneity", "contrast",
"dissimilarity", "entropy", "second_moment", "correlation")
)
plot(GIRglcm, main= "Texture for grayscaled RGB432")

Comparison
A comparison plot for Green GLCM Main vs RGB321 GLCM Main:
graphics::par(mfrow = c(1, 3))
plot(Greenglcm[[1]], main="Main Texture - Green Band")
plot(RGBglcm[[1]], main="Main Texture - RGB321")
plot(truth, main="Ground Truth")

A comparison plot for IR GLCM Main vs RGB432 GLCM Main:
graphics::par(mfrow = c(1, 3))
plot(IRglcm[[1]], main="Main Texture - IR Band")
plot(GIRglcm[[1]], main="Main Texture - RGB432")
plot(truth, main="Ground Truth")

A different visualization
###install.packages("RColorBrewer")
library(RColorBrewer)
my.palette <- brewer.pal(n = 7, name = "Spectral")
graphics::par(mfrow = c(1, 3))
plot(IRglcm[[1]], col = my.palette, main="Main Texture - IR Band")
plot(GIRglcm[[1]], col = my.palette, main="Main Texture - RGB432")
plot(truth, main="Ground Truth")

Conclusion
Multispectral textural metrics computed using grayscaled RGB natural color composites seem to improve discrimination of vegetation stress. However, when using RGB false color composites, multispectral textural metrics don’t seem to provide much information than the one provided by the single IR band.
LS0tCnRpdGxlOiAiR0xDTSB0ZXh0dXJhbCBtZXRyaWNzIGZvciByZW1vdGUgc2Vuc2luZyBpbWFnZSBhbmFseXNpcyIKYXV0aG9yOiAiSXZhbiBMaXphcmF6byIKZGF0ZTogICAwNy4xMC4yMDE5Cm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMjIyBJbnRyb2R1Y3Rpb24KCkdyYXkgTGV2ZWwgQ28tT2NjdXJyZW5jZSBNYXRyaXggKEdMQ00pIChIYXJhbGljayBldCBhbC4gMTk3MykgdGV4dHVyZSBpcyBhIHBvd2VyZnVsIGltYWdlIGZlYXR1cmUgZm9yIGltYWdlIGFuYWx5c2lzLiBUaGUgZ2xjbSBwYWNrYWdlIHByb3ZpZGVzIGEgZWFzeS10by11c2UgZnVuY3Rpb24gdG8gY2FsY3VsYXRlIHN1Y2ggdGV4dHVyYWwgZmVhdHVyZXMgZm9yIFJhc3RlckxheWVyIG9iamVjdHMgaW4gUi4KClRoaXMgbm90ZWJvb2sgZXhwbG9yZXMgaG93IHVzZWZ1bCBjb3VsZCBiZSB0ZXh0dXJhbCBtZXRyaWNzIGZvciBpZGVudGlmaWNhdGlvbiBvZiBjcm9wIHN0cmVzcyBmcm9tIExhbmRzYXQgNSBpbWFnZXMuCgojIyMgVGV4dHVyZSBtZXRyaWNzIGJhY2tncm91bmQKClRoZSBmb2xsb3dpbmcgdGFibGUgc2hvd3MgY29tbW9tIHRleHR1cmUgbWV0cmljczoKCiFbQ29tbW9uIHRleHR1cmUgbWV0cmljcyBhY2NvcmRpbmcgdG8gSGFsbC1CZXllciAoMjAxNyldKC4vZmlndXJlcy9UZXh0dXJlX21ldHJpY3MucG5nKQoKSW4gdGhlIGFib3ZlIHRhYmxlLCAqUGksaiogaXMgdGhlIHByb2JhYmlsaXR5IG9mIHZhbHVlcyAqaSogYW5kICpqKiBvY2N1cnJpbmcgaW4gYWRqYWNlbnQgcGl4ZWxzIGluIHRoZSBvcmlnaW5hbCBpbWFnZSB3aXRoaW4gdGhlIHdpbmRvdyBkZWZpbmluZyB0aGUgbmVpZ2hib3VyaG9vZC4gKmkqIGFuZCAqaiogYXJlIHRoZSBsYWJlbHMgb2YgdGhlIGNvbHVtbnMgYW5kIHJvd3MgKHJlc3BlY3RpdmVseSkgb2YgdGhlIEdMQ00uIEJlY2F1c2Ugb2YgdGhlIGNvbnN0cnVjdGlvbiBvZiB0aGUgR0xDTSwgKmkqIHJlZmVycyB0byB0aGUgZGlnaXRhbCBudW1iZXIgKEROKSB2YWx1ZSBvZiBhIHRhcmdldCBwaXhlbCwgYW5kICpqKiBpcyB0aGUgRE4gdmFsdWUgb2YgaXRzIGltbWVkaWF0ZSBuZWlnaGJvdXIgKHJvb2vigJlzIGNhc2UpLiBJbiB0aGUgR0xDTSAqQ29yKiBlcXVhdGlvbiwgKs68KiBpcyB0aGUgbWVhbiBhbmQgKs+DKiB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uLCBib3RoIGFzIGRlZmluZWQgYnkgdGhlIGVxdWF0aW9ucyBmb3IgR0xDTSAqTWVhbiogYW5kIEdMQ00gKlZhciouCgojIyMgTG9hZCBsaWJyYXJpZXMgJiByZWFkIGRhdGEKCkxldCdzIGxvYWQgdGhlICpnbGNtKiBhbmQgKnJhc3RlciogbGlicmFyaWVzCgpgYGB7cn0KIyNpbnN0YWxsLnBhY2thZ2VzKGMoImdsY20iLCAicmdkYWwiLCAicmFzdGVyIikpCmxpYnJhcnkoZ2xjbSkKbGlicmFyeShyYXN0ZXIpCmBgYAoKTm93LCBsZXQncyBhZGQgdHdvIExhbmRzYXQgNSBjb2xvciBjb21wb3NpdGlvbnMgb3ZlciBhIFVTQSBmYXJtIGZyb20gSnVseSAxMywgMjAwNS4gVGhlc2UgaW1hZ2VzIGhhdmUgYmVlbiBkb3dubG9hZGVkIGZyb20gdGhpcyBsaW5rOiBbTkFTQSBMYW5kc2F0IHBhZ2VdKGh0dHBzOi8vd3d3Lm5hc2EuZ292L21pc3Npb25fcGFnZXMvbGFuZHNhdC9uZXdzL3N3ZWV0LXNwb3QuaHRtbCkKCgoKYGBge3J9CmdyZWVuIDwtIHN0YWNrKCIuL2dyZWVubWFwLkpQRyIpCnBsb3QoZ3JlZW4pCmBgYAoKSW4gdGhlIGFib3ZlIGZpZ3VyZSwgZm91ciBvZiB0aGUgZmllbGRzIGFyZSBzaG93biBpbiBSLWJhc2VkIGRlZmF1bHQgY29sb3IuIFRoZSBib3R0b20gbGVmdCBpcyBhIGJhcmUgZmllbGQuIFRoZSB0b3AgbGVmdCBpcyBhIHNwcmluZyB3aGVhdCBmaWVsZC4gVGhlIHRvcCByaWdodCB0d28gZmllbGRzIGFyZSBzdWdhciBiZWV0cywgYW5kIHRoZSBib3R0b20gcmlnaHQgaXMgYSBjb25mZWN0aW9uYXJ5IHN1bmZsb3dlciBmaWVsZC4KCldoYXQgb2JqZWN0IGlzICpncmVlbio/ICAKCmBgYHtyfQpncmVlbgpgYGAKCmBgYHtyfQpwbG90UkdCKGdyZWVuLCAzLCAyLCAxLCBzdHJldGNoPSdsaW4nLCBheGVzPVRSVUUsIG1haW49IlJHQjMyMSIpCmBgYAoKCkxldCdzIHRyeSBhbm90aGVyIGxpYnJhcnkgZm9yIHJlYWRpbmcgdGhlIHNhbWUgY29sb3IgY29tcG9zaXRpb246CgpgYGB7ciBtZXNzYWdlPUZBTFNFfQojI2luc3RhbGwucGFja2FnZXMoImltYWdlciIpCmxpYnJhcnkoaW1hZ2VyKQpgYGAKCmBgYHtyfQpuZ3JlZW4gPC0gbG9hZC5pbWFnZSgiLi9ncmVlbm1hcC5KUEciKQpwbG90KG5ncmVlbiwgbWFpbj0iQ0lNRyBOYXR1cmFsIENvbG9yIENvbXBvc2l0aW9uIikKYGBgCgpgYGB7cn0KbmdyZWVuCmBgYAoKIyMjIENvbnZlcnRpbmcgdGhlIFJHQiBpbWFnZSBpbnRvIGEgZ3JheXNjYWxlCgpgYGB7cn0KKGdncmVlbiA8LSBncmF5c2NhbGUobmdyZWVuLCBtZXRob2QgPSAiWFlaIiwgZHJvcCA9IFRSVUUpKQpgYGAKCmBgYHtyfQpzb3VyY2UoImNvbnZlcnNpb25zLlIiKQpgYGAKCgoKYGBge3J9CnJncmVlbiA8LSBhcy5yYXN0ZXIuY2ltZyhnZ3JlZW4pCmBgYAoKYGBge3J9CmNsYXNzKHJncmVlbikKYGBgCgoKYGBge3J9CiMjIHBsb3R0aW5nIFJHQiBjb252ZXJ0ZWQgdG8gZ3JheXNjYWxlCnBsb3QocmdyZWVuKQpgYGAKCkFzIHRoZXJlIGlzIG5vIGRpcmVjdCBjb252ZXJzaW9uIGZyb20gKkNpbWcqIGZvcm1hdCBpbnRvICpSYXN0ZXJMYXllciogZm9ybWF0LCB3ZSBoYXZlIHRvIHRha2UgYSBsb25nIHBhdGg6IChpKSBzYXZlIHRoZSBDaW1nIGltYWdlIGFzIGpwZzsgYW5kIChpaSkgcmVhZCB0aGUganBnIGFzIHJhc3RlciBsYXllcjoKCmBgYHtyfQppbWFnZXI6OnNhdmUuaW1hZ2UoZ2dyZWVuLCIuL1JHQmdyYXlzY2FsZS5qcGVnIikKYGBgCgpgYGB7cn0KKHJncmVlbiA8LSByYXN0ZXIoIi4vUkdCZ3JheXNjYWxlLmpwZWciKSkKYGBgCgpgYGB7cn0KcGxvdChyZ3JlZW4sIGNvbCA9IGdyYXkuY29sb3JzKDEwLCBzdGFydCA9IDAuMywgZW5kID0gMC45LCBnYW1tYSA9IDIuMiwgYWxwaGEgPSBOVUxMKSwKICAgICBheGVzPVRSVUUsIG1haW49ICJSR0IzMjEgYXMgZ3JheXNjYWxlIikKYGBgCgoKCiMjIyBGYWxzZSBjb2xvciBjb21wb3NpdGlvbgoKTm93LCBsZXQncyBsb2FkIGFuIGluZnJhcmVkIGNvbG9yIGNvbXBvc2l0aW9uICBSR0IgNC0zLTIgKHRoZSBuZWFyIGluZnJhcmVkLCByZWQsIGFuZCBncmVlbiBiYW5kcyksIGEgY29tcG9zaXRlIGNvbWJpbmF0aW9uIHVzZWQgZm9yIHZlZ2V0YXRpb24gd2hlcmUgZGFya2VyIHBhdGNoZXMgc2hvdyBzdHJlc3NlZCBwbGFudHMuCgoKYGBge3J9Cm5yZWQgPC0gbG9hZC5pbWFnZSgiLi9yZWRtYXAuSlBHIikKcGxvdChucmVkLCBtYWluPSJDSU1HIEZhbHNlIENvbG9yIENvbXBvc2l0aW9uIikKYGBgCgojIyMgQ29udmVydGluZyB0aGUgUkdCNDMyIGNvbXBvc2l0aW9uIGludG8gYSBncmF5c2NhbGUgaW1hZ2UKCmBgYHtyfQooZ3JlZCA8LSBncmF5c2NhbGUobnJlZCwgbWV0aG9kID0gIlhZWiIsIGRyb3AgPSBUUlVFKSkKYGBgCgpgYGB7cn0KaW1hZ2VyOjpzYXZlLmltYWdlKGdyZWQsIi4vTklSZ3JheXNjYWxlLmpwZWciKQpgYGAKCmBgYHtyfQoocnJlZCA8LSByYXN0ZXIoIi4vTklSZ3JheXNjYWxlLmpwZWciKSkKYGBgCgoKCiMjIyBSZWFkaW5nIHRoZSBSR0I0MzIgY29tcG9zaXRpb24gYXMgYSByYXN0ZXIgc3RhY2sKCkxldCdzIHJlYWQgdGhlIHNhbWUgY29sb3IgY29tcG9zaXRpb24gdXNpbmcgdGhlICpyYXN0ZXIqIHBhY2thZ2U6CgpgYGB7cn0KcmVkIDwtIHN0YWNrKCIuL3JlZG1hcC5KUEciKQpwbG90KHJlZCkKYGBgCgojIyMgVGhlIGdyb3VuZCB0cnV0aAoKYGBge3J9CnRydXRoIDwtIGxvYWQuaW1hZ2UoIi4vQ29sb3JlZC5KUEciKQpwbG90KHRydXRoLCBtYWluPSJUaGUgZ3JvdW5kIHRydXRoIikKYGBgCgpUaGUgY29sb3JlZCBpbWFnZSBzaG93cyBqdXN0IHRoZSBuZWFyIGluZnJhcmVkIGJhbmQgcHJvY2Vzc2VkIHNvIHRoYXQgdGhlIHllbGxvdywgZ3JlZW4gYW5kIHRlYWwgY29sb3JzIHNob3cgc3RyZXNzZWQgcGxhbnRzIGFuZCB0aGUgYmx1ZSBhbmQgcHVycGxlIHNob3cgaGVhbHRoaWVyIHBsYW50cy4gRm9jdXNpbmcgb24gdGhlIHN1Z2FyIGJlZXQgZmllbGQsIHRoZSBtYWdlbnRhIGNvbG9yIHNob3dzIGFyZWFzIHdpdGggZXhjZXNzIG5pdHJvZ2VuIGFuZCB0aGUgbGlnaHRlciBncmVlbiB6b25lcyBhcmUgc3VnYXIgYmVldHMgdW5kZXIgc3RyZXNzLgoKCgojIyMgVGV4dHVyYWwgbWV0cmljcyBmb3IgZ3JlZW4gYmFuZAoKCk5vdywgd2UgY2FuIGNvbXB1dGUgdGV4dHVyYWwgbWV0cmljcyB1c2luZyB0aGUgdHJ1ZSBjb2xvciBjb21wb3NpdGlvbjoKCmBgYHtyfQpHcmVlbmdsY20gPC0gZ2xjbShncmVlbltbMl1dLCAKICAgICAgICAgICAgICB3aW5kb3cgPSBjKDksOSksIAogICAgICAgICAgICAgIHNoaWZ0ID0gYygxLDEpLCAKICAgICAgICAgICAgICBzdGF0aXN0aWNzID0gYygibWVhbiIsICJ2YXJpYW5jZSIsICJob21vZ2VuZWl0eSIsICJjb250cmFzdCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJkaXNzaW1pbGFyaXR5IiwgImVudHJvcHkiLCAic2Vjb25kX21vbWVudCIsICJjb3JyZWxhdGlvbiIpCiAgICAgICAgICAgICAgKQpgYGAKCmBgYHtyfQpHcmVlbmdsY20KYGBgCgoKCmBgYHtyfQpncmFwaGljczo6cGFyKG1mcm93ID0gYygzLCAzKSkKcGxvdChHcmVlbmdsY20pCmBgYAoKCiMjIyBUZXh0dXJhbCBtZXRyaWNzIGZvciBSR0IzMjEgY29udmVydGVkIHRvIGdyYXlzY2FsZSBpbWFnZQoKYGBge3J9ClJHQmdsY20gPC0gZ2xjbShyZ3JlZW4sIAogICAgICAgICAgICAgIHdpbmRvdyA9IGMoOSw5KSwgCiAgICAgICAgICAgICAgc2hpZnQgPSBjKDEsMSksIAogICAgICAgICAgICAgIHN0YXRpc3RpY3MgPSBjKCJtZWFuIiwgInZhcmlhbmNlIiwgImhvbW9nZW5laXR5IiwgImNvbnRyYXN0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImRpc3NpbWlsYXJpdHkiLCAiZW50cm9weSIsICJzZWNvbmRfbW9tZW50IiwgImNvcnJlbGF0aW9uIikKICAgICAgICAgICAgICApCmBgYAoKYGBge3J9ClJHQmdsY20KYGBgCgpgYGB7cn0KZ3JhcGhpY3M6OnBhcihtZnJvdyA9IGMoMywgMykpCnBsb3QoUkdCZ2xjbSkKYGBgCgoKCiMjIyBUZXh0dXJhbCBtZXRyaWNzIGZvciBpbmZyYXJlZCBiYW5kCgoKTm93LCBsZXQncyBkbyB0aGUgY29tcHV0YXRpb24gdXNpbmcgdGhlIGZpcnN0IGJhbmQgKE5JUikgb2YgdGhlIGluZnJhcmVkIGNvbG9yIGNvbXBvc2l0ZToKCgpgYGB7cn0KSVJnbGNtIDwtIGdsY20ocmVkW1sxXV0sIAogICAgICAgICAgICAgIHdpbmRvdyA9IGMoOSw5KSwgCiAgICAgICAgICAgICAgc2hpZnQgPSBjKDEsMSksIAogICAgICAgICAgICAgIHN0YXRpc3RpY3MgPSBjKCJtZWFuIiwgInZhcmlhbmNlIiwgImhvbW9nZW5laXR5IiwgImNvbnRyYXN0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImRpc3NpbWlsYXJpdHkiLCAiZW50cm9weSIsICJzZWNvbmRfbW9tZW50IiwgImNvcnJlbGF0aW9uIikKICAgICAgICAgICAgICApCmBgYAoKYGBge3J9CnBsb3QoSVJnbGNtLCBtYWluPSJUZXh0dXJlIGZvciBJUiBiYW5kIikKYGBgCgojIyMgVGV4dHVyYWwgbWV0cmljcyBmb3IgUkdCNDMyIGNvbnZlcnRlZCB0byBncmF5c2NhbGUgYmFuZAoKYGBge3J9CkdJUmdsY20gPC0gZ2xjbShycmVkLCAKICAgICAgICAgICAgICB3aW5kb3cgPSBjKDksOSksIAogICAgICAgICAgICAgIHNoaWZ0ID0gYygxLDEpLCAKICAgICAgICAgICAgICBzdGF0aXN0aWNzID0gYygibWVhbiIsICJ2YXJpYW5jZSIsICJob21vZ2VuZWl0eSIsICJjb250cmFzdCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJkaXNzaW1pbGFyaXR5IiwgImVudHJvcHkiLCAic2Vjb25kX21vbWVudCIsICJjb3JyZWxhdGlvbiIpCiAgICAgICAgICAgICAgKQpgYGAKCmBgYHtyfQpwbG90KEdJUmdsY20sIG1haW49ICJUZXh0dXJlIGZvciBncmF5c2NhbGVkIFJHQjQzMiIpCmBgYAoKCiMjIyBDb21wYXJpc29uCgpBIGNvbXBhcmlzb24gcGxvdCBmb3IgR3JlZW4gKkdMQ00gTWFpbiogdnMgICBSR0IzMjEgKkdMQ00gTWFpbio6CgpgYGB7cn0KZ3JhcGhpY3M6OnBhcihtZnJvdyA9IGMoMSwgMykpCnBsb3QoR3JlZW5nbGNtW1sxXV0sIG1haW49Ik1haW4gVGV4dHVyZSAtIEdyZWVuIEJhbmQiKQpwbG90KFJHQmdsY21bWzFdXSwgbWFpbj0iTWFpbiBUZXh0dXJlIC0gUkdCMzIxIikKcGxvdCh0cnV0aCwgbWFpbj0iR3JvdW5kIFRydXRoIikKYGBgCgoKQSBjb21wYXJpc29uIHBsb3QgZm9yIElSICpHTENNIE1haW4qIHZzICAgUkdCNDMyICpHTENNIE1haW4qOgoKYGBge3J9CmdyYXBoaWNzOjpwYXIobWZyb3cgPSBjKDEsIDMpKQpwbG90KElSZ2xjbVtbMV1dLCBtYWluPSJNYWluIFRleHR1cmUgLSBJUiBCYW5kIikKcGxvdChHSVJnbGNtW1sxXV0sIG1haW49Ik1haW4gVGV4dHVyZSAtIFJHQjQzMiIpCnBsb3QodHJ1dGgsIG1haW49Ikdyb3VuZCBUcnV0aCIpCmBgYAoKIyMjIEEgZGlmZmVyZW50IHZpc3VhbGl6YXRpb24KCmBgYHtyfQojIyNpbnN0YWxsLnBhY2thZ2VzKCJSQ29sb3JCcmV3ZXIiKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKYGBgCgoKCmBgYHtyfQpteS5wYWxldHRlIDwtIGJyZXdlci5wYWwobiA9IDcsIG5hbWUgPSAiU3BlY3RyYWwiKQpncmFwaGljczo6cGFyKG1mcm93ID0gYygxLCAzKSkKcGxvdChJUmdsY21bWzFdXSwgY29sID0gbXkucGFsZXR0ZSwgbWFpbj0iTWFpbiBUZXh0dXJlIC0gSVIgQmFuZCIpCnBsb3QoR0lSZ2xjbVtbMV1dLCBjb2wgPSBteS5wYWxldHRlLCBtYWluPSJNYWluIFRleHR1cmUgLSBSR0I0MzIiKQpwbG90KHRydXRoLCBtYWluPSJHcm91bmQgVHJ1dGgiKQpgYGAKCgojIyMgQ29uY2x1c2lvbgoKTXVsdGlzcGVjdHJhbCAgdGV4dHVyYWwgbWV0cmljcyBjb21wdXRlZCB1c2luZyBncmF5c2NhbGVkIFJHQiBuYXR1cmFsIGNvbG9yIGNvbXBvc2l0ZXMgc2VlbSB0byBpbXByb3ZlIGRpc2NyaW1pbmF0aW9uIG9mIHZlZ2V0YXRpb24gc3RyZXNzLiAgSG93ZXZlciwgd2hlbiB1c2luZyBSR0IgZmFsc2UgY29sb3IgY29tcG9zaXRlcywgbXVsdGlzcGVjdHJhbCB0ZXh0dXJhbCBtZXRyaWNzIGRvbid0IHNlZW0gdG8gcHJvdmlkZSBtdWNoIGluZm9ybWF0aW9uIHRoYW4gdGhlIG9uZSBwcm92aWRlZCBieSB0aGUgc2luZ2xlIElSIGJhbmQuCgoK