Image augmentation and overfitting: An R version

Hello! Welcome to the seventh R code walkthrough of the session Machine Learning Foundations where the awesome Laurence Moroney,a Developer Advocate at Google working on Artificial Intelligence, takes us through the fundamentals of building machine learned models using TensorFlow.

In this episode, Episode 6, Laurence Moroney gets us started with yet another exciting application of Machine Learning. Here, we look at how one can use image augmentation as a technique to artificially extend datasets to provide new information for the training of a neural network. This can potentially help with overfitting issues!

Like the previous R Notebooks, this Notebook tries to replicate the Python Notebook used for this episode.

Before we begin, I highly recommend that you go through Episode 7 first then you can come back and implement these concepts using R. In this episode, Laurence Moroney does a really exemplary job in explaining the various transformations done by image augmentation. You should definitely check that out first. I will try and highlight some of the concepts said and add some of my own for the sake of completeness of this post but I highly recommend you listen from him first.


Let’s start by loading the libraries required for this session.

We’ll be requiring some packages in the EBImage, Tidyverse and Keras(a framework for defining a neural network as a set of Sequential layers). You can have them installed as follows:

For the Tidyverse, install the complete tidyverse with:

suppressMessages(install.packages("tidyverse"))


EBImage is an R package distributed as part of the Bioconductor project. To install the package, start R and enter:

install.packages("BiocManager")
BiocManager::install("EBImage")


The Keras R interface uses the TensorFlow backend engine by default. An elegant doucumentation for the installation of both the core Keras library as well as the TensorFlow backend can be found on the R interface to Keras website.


Let’s take a step back: Overfitting

Looking back, we have had quite a run with Convolutional Neural Networks, haven’t we? We started with building a classifier for clothing then built a more real-life horses&humans classifier and also had a look at a cats vs dogs classifier. One thing was common though: the concept of overfitting. So, how does overfitting come about? Overfitting is caused by having limited data to train on such that the Neural Network becomes too familiar with that particular dataset rendering it unable to generalize new data. This can be validated by the fact that our previous models performed extremely well in classifying the training set but not so good at classifying data it had not been exposed to such as that in the testing set. Theoretically, given infinite data, a model would be exposed to every possible aspect of the data distribution at hand and overfitting would probably not occur.

So how do we fix this? :drum rolls Data Augmentation. Let’s demystify this using an example. Consider the images below, with the left feline being an image in the training set and the feline on the right being an image in the test set.

Image source: Machine Learning Foundations Ep #7 - Image augmentation and overfitting

Image source: Machine Learning Foundations Ep #7 - Image augmentation and overfitting


Computer vision works by extracting features from an image and then associating them with a given label. So features such as the pointy ears at the top, in the image on the left, might be indicative of a cat. If for instance we trained a model only on images such as that on the left, it may fail to correctly classify the image on the right as a cat. This is because it will be looking for triangular shapes oriented upwards near the top of the image, features which lack in the image on the right.

What if during training, the image on the left could be rotated such that the orientation of the ears in both cats match as shown below?

Image source: Machine Learning Foundations Ep #7 - Image augmentation and overfitting

Image source: Machine Learning Foundations Ep #7 - Image augmentation and overfitting


Then, the probability of the model classifying the image on the right as a ‘cat’ is higher.

Simply put, data augmentation is a technique used to increase the diversity of the training set by applying random (but realistic) transformations such as image rotation, shearing and zooming. Such transformations help to expose the model to more aspects of the data and in turn helping the model to generalize better. In this episode, the effects of some these transformations on images and how they help minimize overfitting have been neatly illustrated, so you should definitely check out 😉.


A little sanity check on our data

First things first, let’s download the dataset used for this episode. It is noteworthy that this particular dataset is a filtered version of the original and contains about 2,000 images. For this reason, the model is more susceptible to overfitting. However, this dataset will be a good test bed to investigate the impact of image augmentation in reducing overfitting.

base_dir <- list.dirs(path = "C:/Users/keras/Documents/cats_and_dogs_filtered", recursive = T)

sapply(base_dir, function(dir){length(list.files(dir))})
##                 C:/Users/keras/Documents/cats_and_dogs_filtered 
##                                                               3 
##           C:/Users/keras/Documents/cats_and_dogs_filtered/train 
##                                                               2 
##      C:/Users/keras/Documents/cats_and_dogs_filtered/train/cats 
##                                                            1000 
##      C:/Users/keras/Documents/cats_and_dogs_filtered/train/dogs 
##                                                            1000 
##      C:/Users/keras/Documents/cats_and_dogs_filtered/validation 
##                                                               2 
## C:/Users/keras/Documents/cats_and_dogs_filtered/validation/cats 
##                                                             500 
## C:/Users/keras/Documents/cats_and_dogs_filtered/validation/dogs 
##                                                             500
# Awesome. Seems the hard work has already been done for us. 
# The data is split into `Training` and `Validation` directories
# and each of these contain the `cats` and `dogs` sub-directories.
# There are 2000 images for training and 1000 images for validation

train_dir <-  file.path("C:/Users/keras/Documents/cats_and_dogs_filtered/train")
validation_dir <-  file.path("C:/Users/keras/Documents/cats_and_dogs_filtered/validation")
train_cats_dir <- list.dirs(train_dir, recursive = F)[1]
train_dogs_dir <- list.dirs(train_dir, recursive = F)[2]
test_cats_dir <- list.dirs(validation_dir, recursive = F)[1]
# as in the previous notebooks, let's display some of the furry creatures
library(EBImage)
library(dplyr)

# listing the files in the train_cats_dir and train_dogs_dir
cats_disp <- list.files(path = train_cats_dir, full.names = T) %>%
  sample(size = 4, replace = F)
dogs_disp <- list.files(path = train_dogs_dir, full.names = T) %>%
  sample(size = 4, replace = F)

img_disp <- sample(c(cats_disp,dogs_disp))

# resizing the images since readImage {EBImage} requires all images
# to have same dimension and color mode

for(i in seq_along(img_disp)){
  readImage(img_disp[i]) %>% 
    resize(w = 300, h = 300) %>% 
    writeImage(img_disp[i])
  
}
  

EBImage::display(
  readImage(img_disp),
  method = 'raster',
  all = T,
  nx = 4,
  spacing = c(0,0)
)


Building a model that does not use image augmentation

Very quickly, from the previous sessions: Convolutional Neural networks use filters/kernels to process images and extract features such that after learning a certain pattern a convnet can recognize it anywhere. The convolutional layers learn the features and pass these to the dense layers which map the learned features to the given labels.


The pooling layer serves to progressively reduce the spatial size of the representation to reduce the number of parameters, memory footprint and amount of computation in the network. Max pooling consists of extracting windows of size 2by2 from the input feature maps and outputting the max value of each channel. Pooling hence reduces the amount of irrelevant information in an image while maintaining the features that are detected.

In case you need to brush up on the concepts of CNN, Episode 3 would be a good place to start.

So, let’s set these concepts in motion, shall we?

Instantiating a Convolution

We’ll reuse the same general structure we’ve been using: the convnet will be a stack of alternated layer_conv_2d (with relu activation) and layer_max_pooling_2d stages. You will notice that as we go deeper, we increase the number of filters. This is because convolutions can learn spatial hierarchies of patterns. A first convolution layer will learn small local patterns such as edges, a second convolution layer will learn larger patterns made of the features of the first layers, and so on. This allows convnets to efficiently learn increasingly complex and abstract visual concepts.

library(keras)
## 
## Attaching package: 'keras'
## The following object is masked from 'package:EBImage':
## 
##     normalize
model <- keras_model_sequential() %>%
  # adding the first convolution layer with 16 3by3 filters
  # we add an additional dimension in the input shape since convolutions operate over 3D tensors
  # the input shape tells the network that the first layer should expect
  # images of 150 by 150 pixels with a color depth of 3 ie RGB images
  layer_conv_2d(input_shape = c(150, 150, 3), filters = 32, kernel_size = c(3, 3), activation = 'relu' ) %>%
  # adding a max pooling layer which halves the dimensions
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  # adding a second convolution layer with 64 filters
  layer_conv_2d(filters = 64, kernel_size = c(3, 3), activation = 'relu') %>%
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  # adding a second convolution layer with 128 filters
  layer_conv_2d(filters = 128, kernel_size = c(3, 3), activation = 'relu') %>%
  layer_max_pooling_2d(pool_size = c(2, 2))


Adding a classifier to the convnet

Convolutional layers learn the features and pass these to the dense layers which map the learned features to the given labels. Therefore, the next step is to feed the last output tensor into a densely connected classifier network like those we’re already familiar with: a stack of dense layers. These classifiers process vectors, which are 1D, however, the current output is a 3D tensor. First we have to flatten the 3D outputs to 1D, and then add a few dense layers on top.

Note that because we are facing a two-class classification problem, i.e. a binary classification problem, we can end our network with a sigmoid activation, so that the output of our network will be a single scalar between 0 and 1, encoding the probability that the current image is class 1 (as opposed to class 0). For more information about Keras activation functions, kindly visit the Keras website.

model <- model %>%
  layer_flatten() %>%
  layer_dense(units = 512, activation = 'relu') %>%
  layer_dense(units = 1,activation = 'sigmoid')
  

# Let’s look at how the dimensions of the feature maps change with every successive layer:

model %>% summary()
## Model: "sequential"
## ________________________________________________________________________________
## Layer (type)                        Output Shape                    Param #     
## ================================================================================
## conv2d (Conv2D)                     (None, 148, 148, 32)            896         
## ________________________________________________________________________________
## max_pooling2d (MaxPooling2D)        (None, 74, 74, 32)              0           
## ________________________________________________________________________________
## conv2d_1 (Conv2D)                   (None, 72, 72, 64)              18496       
## ________________________________________________________________________________
## max_pooling2d_1 (MaxPooling2D)      (None, 36, 36, 64)              0           
## ________________________________________________________________________________
## conv2d_2 (Conv2D)                   (None, 34, 34, 128)             73856       
## ________________________________________________________________________________
## max_pooling2d_2 (MaxPooling2D)      (None, 17, 17, 128)             0           
## ________________________________________________________________________________
## flatten (Flatten)                   (None, 36992)                   0           
## ________________________________________________________________________________
## dense (Dense)                       (None, 512)                     18940416    
## ________________________________________________________________________________
## dense_1 (Dense)                     (None, 1)                       513         
## ================================================================================
## Total params: 19,034,177
## Trainable params: 19,034,177
## Non-trainable params: 0
## ________________________________________________________________________________


Compile: Configuring a Keras model for training

model %>%
  compile(
    loss = 'binary_crossentropy',
    optimizer = optimizer_rmsprop(lr = 0.0001),
    metrics = 'accuracy'
  )

Binary_ Crossentropy loss Computes the cross-entropy loss between true labels and predicted labels. Typically used when there are only two label classes.(For a refresher on loss metrics, see the Machine Learning Crash Course and the Keras documentation)


Data preprocessing

So how will we apply these random transformations to our images in a bid to achieve image augmentation? Good thought! Thankfully Keras has utilities to turn image files on disk into batches of pre-processed tensors. We’ll use the image_data_generator to generate batches of tensor image data with real-time data augmentation. We’ll implement image augmentation in the next example. For now, we’ll only use the image generator to:

  • Normalize the pixel values to the [0, 1] interval.
  • Autolabel the images of cats and dogs automatically based on the subdirectory name: - ImageGenerator will label the images appropriately for you, reducing a coding step. Sounds neat, right?
# normalizing the data by multiplying by a rescaling factor
train_datagen <- image_data_generator(rescale = 1/255)


# Flow training images in batches of 20 using train_datagen generator
train_generator <- flow_images_from_directory(
  # target directory
  directory = train_dir,
  # training data generator
  generator = train_datagen,
  # resizing the images to the same dimensions expected by our NN
  target_size = c(150, 150),
  # 20 images at a time to be fed into the NN
  batch_size = 20,
  # Since we use binary_crossentropy loss, we need binary label arrays
  class_mode = 'binary'
)

Let’s do the same for the validation set

# normalizing the data by multiplying by a rescaling factort
test_datagen <- image_data_generator(rescale = 1/255)


# Flow training images in batches of 20 using test_datagen generator
validation_generator <- flow_images_from_directory(
  # target directory
  directory = validation_dir,
  # training data generator
  generator = test_datagen,
  # resizing the images to the same dimensions expected by our NN
  target_size = c(150, 150),
  # 20 images at a time to be fed into the NN
  batch_size = 20,
  # Since we use binary_crossentropy loss, we need binary label arrays
  class_mode = 'binary'
)


Training the Neural Network

Training simply means learning (determining) good values for all the weights and the bias from labeled examples. It does so by ‘learning’ the relationship between the train_images and train_labels arrays.

Let’s fit the model to the data using a generator. You do so using the fit_generator {keras} function, the equivalent for fit for data generators like this one. It expects as its first argument a generator that will yield batches of inputs and targets indefinitely. Because the data is being generated endlessly, the model needs to know how many samples to draw from the generator before declaring an epoch over. This is the role of the steps_per_epoch argument. It defines the total number of steps (batches of samples) to yield from generator before declaring one epoch finished and starting the next epoch. It should typically be equal to the number of samples in your dataset divided by the batch size.

validation_steps describes the total number of steps (batches of samples) to yield from generator before stopping at the end of every epoch. It tells the network how many batches to draw from the validation generator for evaluation.

An epoch finishes when steps_per_epoch batches have been seen by the model.


Fitting the model using a batch generator

Let’s train for 100 epochs – this may take some minutes to run.

The Loss and Accuracy are a great indication of progress of training. It’s making a guess as to the classification of the training data, and then measuring it against the known label, calculating the result. Accuracy is the portion of correct guesses.

history <- model %>% fit_generator(
  generator = train_generator,
  # Total number of steps (batches of samples) to yield
  #before declaring one epoch finished and starting the next epoch.
  steps_per_epoch = 100, # 2000/20
  # An epoch is an iteration over the entire data provided
  epochs = 100,
  validation_data = validation_generator,
  validation_steps = 50 # 1000/20
  
  
)

# It’s good practice to always save your models after training.

model %>% save_model_hdf5("cats_and_dogs_filtered.h5") 

# plotting the loss and accuracy over the training and validation data
# during training
plot(history)
## `geom_smooth()` using formula 'y ~ x'

# A summary of how the model performed
history
## 
## Final epoch (plot to see history):
##         loss: 0.000000006926
##     accuracy: 1
##     val_loss: 3.458
## val_accuracy: 0.741

The Training Accuracy is close to 100%, and the validation accuracy is in the 70%-80% range. This is a great example of overfitting: the fact that machine learning models tend to perform worse on new data they have never ‘seen’ before than on their training data. It occurs when the network ends up learning representations that are specific to the training data and doesn’t generalize to data outside of the training set.

Let’s see if we can do better to avoid overfitting – and one simple method is to tweak the images a bit by applying random transformations such as rotation, shearing, zooming etc. That’s what image augmentation is all about.

We’ll use the image_data_generator to generate batches of tensor image data with real-time data augmentation.

Let’s get started with an example.


Image augmentation: An example

All this time, we have been using the image_data_generator to normalize our images. We’ll update a few more arguments to it and we’ll be getting past overfitting in no time.


Setting up a data augmentation configuration via image_data_generator

datagen <- image_data_generator(
  rescale = 1/255,
  rotation_range = 40,
  width_shift_range = 0.2,
  height_shift_range = 0.2,
  shear_range = 0.2,
  zoom_range = 0.2,
  horizontal_flip = TRUE,
  fill_mode = "nearest"
)

These are just a few of the options available (for more, see the Keras documentation). Let’s quickly go over this code:

  • rotation_range is a value in degrees (0–180), a range within which to randomly rotate pictures.
  • width_shift and height_shift are ranges (as a fraction of total width or height) within which to randomly translate pictures vertically or horizontally.
  • shear_range is for randomly applying shearing transformations.
  • zoom_range is for randomly zooming inside pictures.
  • horizontal_flip is for randomly flipping half the images horizontally—relevant when there are no assumptions of horizontal asymmetry (for example, real­world pictures).
  • fill_mode is the strategy used for filling in newly created pixels, which can appear after a rotation or a width/height shift.
# choosing an image to augment
fnames <- list.files(test_cats_dir, full.names = TRUE)
img_path <- fnames[332]
# original image before augmentation
readImage(img_path) %>% resize(w = 150, h = 150) %>% display(method = 'raster')

# Ah! 😺
# loading the image and resizing it
img <- image_load(img_path, target_size = c(150, 150))

# converting PIL format to an array with shape (150, 150, 3)
img_array <- image_to_array(img)

# including a batch dimension
img_array <- array_reshape(img_array, c(1, 150, 150, 3))

# generating batches of augmented images from our img_array
augmentation_generator <- flow_images_from_data(
  img_array, # should have rank 4
  # generator used for augmentation
  generator = datagen,
  batch_size = 1
)

# Displaying some randomly augmented training images
op <- par(mfrow = c(2,2), pty = 's', mar = c(1, 0, 1, 0))
for (i in 1:4) {
aug_img <- generator_next(augmentation_generator)
plot(as.raster(aug_img[1, , , ]))
}

par(op)

Voila! Finally, there goes some of the random transformations we have been talking about. Such transformations help to expose the model to more aspects of the data and in turn helping the model to generalize better.


Getting past overfitting with data augmentation

⏲ It’s time to build a model that uses Image Augmentation during training.

We’ll reuse the same code as before, with the only exception being that we’ll update the image_data_generator to perform augmentation. Here we go:

model <- keras_model_sequential() %>%
  # adding the first convolution layer with 16 3by3 filters
  # we add an additional dimension in the input shape since convolutions operate over 3D tensors
  # the input shape tells the network that the first layer should expect
  # images of 150 by 150 pixels with a color depth of 3 ie RGB images
  layer_conv_2d(input_shape = c(150, 150, 3), filters = 32, kernel_size = c(3, 3), activation = 'relu' ) %>%
  # adding a max pooling layer which halves the dimensions
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  # adding a second convolution layer with 64 filters
  layer_conv_2d(filters = 64, kernel_size = c(3, 3), activation = 'relu') %>%
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  # adding a second convolution layer with 128 filters
  layer_conv_2d(filters = 128, kernel_size = c(3, 3), activation = 'relu') %>%
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  layer_flatten() %>%
  layer_dense(units = 512, activation = 'relu') %>%
  layer_dense(units = 1,activation = 'sigmoid')

# Compile: Configuring a Keras model for training
model %>%
  compile(
    loss = 'binary_crossentropy',
    optimizer = optimizer_rmsprop(lr = 0.0001),
    metrics = 'accuracy'
  )


# This code has changed. Now instead of the ImageGenerator just rescaling
# the image, we also rotate and do other operations
# Updated to do image augmentation

train_datagen <- image_data_generator(
  rescale = 1/255,
  rotation_range = 40,
  width_shift_range = 0.2,
  height_shift_range = 0.2,
  shear_range = 0.2,
  zoom_range = 0.2,
  horizontal_flip = TRUE,
  fill_mode = "nearest"
)

# Flow training images in batches of 20 using train_datagen generator
train_generator <- flow_images_from_directory(
  # target directory
  directory = train_dir,
  # training data generator
  generator = train_datagen,
  # resizing the images to the same dimensions expected by our NN
  target_size = c(150, 150),
  # 20 images at a time to be fed into the NN
  batch_size = 20,
  # Since we use binary_crossentropy loss, we need binary label arrays
  class_mode = 'binary'
)

# training the model
# training will take longer due to the augmentation process
history <- model %>% fit_generator(
  generator = train_generator,
  # Total number of steps (batches of samples) to yield
  #before declaring one epoch finished and starting the next epoch.
  steps_per_epoch = 100, # 2000/20
  # An epoch is an iteration over the entire data provided
  epochs = 100,
  validation_data = validation_generator,
  validation_steps = 50 # 1000/20
  
  
)

# It’s good practice to always save your models after training.

model %>% save_model_hdf5("cats_and_dogs_filtered_augmented.h5") 

# plotting the loss and accuracy over the training and validation data
# during training
plot(history)
## `geom_smooth()` using formula 'y ~ x'

# A summary of how the model performed
history
## 
## Final epoch (plot to see history):
##         loss: 0.3754
##     accuracy: 0.828
##     val_loss: 0.4739
## val_accuracy: 0.796

Thanks to image augmentation, we are no longer overfitting. The training curves are closely tracking the validation curves as shown in the plots. Although the training accuracy is not reaching 99%+, the training accuracy is really close to the validation accuracy. This is an indication that the model is doing a good job in predicting images it has never ‘seen’ before.

The use of image augmentation helped to increase the diversity of the training set and also reduce the model’s dependence on certain properties. As such, the model was exposed to more attributes, making it better at generalizing new data.


One last detour: Adding dropout

Very simply, let’s look at another commonly trick used in minimizing overfitting: dropout. Dropout works by randomly removing nodes along with all their incoming and outgoing connections from a neural network during training. It does this by setting to zero a number of output features of the layer during training. For instance if the output of a layer is the vector [0.4, 0.9, 1.5, 1.8, 0.2], after applying dropout, this vector will have a few zero entries distributed at random: [0.4, 0, 1.5, 1.8, 0]. The core idea is that introducing noise in the output values of a layer will minimise the dependence on certain neurons to detect certain features hence modulating the quantity of information that your model is allowed to store. Much more elaborate information about dropout as a way of minimising overfitting can be found in this well written and researched paper: Dropout: A Simple Way to Prevent Neural Networks from Overfitting

Dropout: A Simple Way to Prevent Neural Networks from Overfitting

Dropout: A Simple Way to Prevent Neural Networks from Overfitting


Getting past overfitting with data augmentation and drop out.

To further fight overfitting, we’ll try adding a dropout layer to a model, right before the densely connected classifier and see how our model performs. We’ll do this by specifying a dropout rate (fraction of the features that are zeroed out) in layer_dropout {keras}.

model <- keras_model_sequential() %>%
  # adding the first convolution layer with 16 3by3 filters
  # we add an additional dimension in the input shape since convolutions operate over 3D tensors
  # the input shape tells the network that the first layer should expect
  # images of 150 by 150 pixels with a color depth of 3 ie RGB images
  layer_conv_2d(input_shape = c(150, 150, 3), filters = 32, kernel_size = c(3, 3), activation = 'relu' ) %>%
  # adding a max pooling layer which halves the dimensions
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  # adding a second convolution layer with 64 filters
  layer_conv_2d(filters = 64, kernel_size = c(3, 3), activation = 'relu') %>%
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  # adding a second convolution layer with 128 filters
  layer_conv_2d(filters = 128, kernel_size = c(3, 3), activation = 'relu') %>%
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  # adding dropout
  layer_dropout(rate = 0.5) %>% 
  layer_flatten() %>%
  layer_dense(units = 512, activation = 'relu') %>%
  layer_dense(units = 1,activation = 'sigmoid')

# Compile: Configuring a Keras model for training
model %>%
  compile(
    loss = 'binary_crossentropy',
    optimizer = optimizer_rmsprop(lr = 0.0001),
    metrics = 'accuracy'
  )


# This code has changed. Now instead of the ImageGenerator just rescaling
# the image, we also rotate and do other operations
# Updated to do image augmentation

train_datagen <- image_data_generator(
  rescale = 1/255,
  rotation_range = 40,
  width_shift_range = 0.2,
  height_shift_range = 0.2,
  shear_range = 0.2,
  zoom_range = 0.2,
  horizontal_flip = TRUE,
  fill_mode = "nearest"
)

# Flow training images in batches of 20 using train_datagen generator
train_generator <- flow_images_from_directory(
  # target directory
  directory = train_dir,
  # training data generator
  generator = train_datagen,
  # resizing the images to the same dimensions expected by our NN
  target_size = c(150, 150),
  # 20 images at a time to be fed into the NN
  batch_size = 20,
  # Since we use binary_crossentropy loss, we need binary label arrays
  class_mode = 'binary'
)

# training the model
# training will take longer due to the augmentation process
history <- model %>% fit_generator(
  generator = train_generator,
  # Total number of steps (batches of samples) to yield
  #before declaring one epoch finished and starting the next epoch.
  steps_per_epoch = 100, # 2000/20
  # An epoch is an iteration over the entire data provided
  epochs = 100,
  validation_data = validation_generator,
  validation_steps = 50 # 1000/20
  
  
)

# It’s good practice to always save your models after training.

model %>% save_model_hdf5("cats_and_dogs_filtered_augmented_drop.h5") 

# plotting the loss and accuracy over the training and validation data
# during training
plot(history)
## `geom_smooth()` using formula 'y ~ x'

# A summary of how the model performed
history
## 
## Final epoch (plot to see history):
##         loss: 0.406
##     accuracy: 0.813
##     val_loss: 0.4468
## val_accuracy: 0.797

Well, that’s the performance of our model with both augmentation and dropout implemented. As before, we have combated overfitting, and that’s a step in the right direction.

By using regularization techniques even further, and by tuning the network’s parameters (such as the number of filters per convolution layer, or the number of layers in the network), you may be able to get an even better accuracy.

We’ll wrap it here. Augmentation was quite an interesting topic to learn, share and R too 😊. A big thank you to Laurence Moroney for this amazing series, you are the best!

Time to strap in for Natural Language Processing.

Till then,

Happy Learning 👩🏽‍💻 👨‍💻 👨🏾‍💻 👩‍💻 ,

Eric (R_ic), Microsoft Learn Student Ambassador.

Reference Material

LS0tDQp0aXRsZTogJyAnDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY3NzOiBzdHlsZV8yLmNzcw0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICBoaWdobGlnaHQ6IGJyZWV6ZWRhcmsNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KICAgIGluY2x1ZGVzOg0KICAgICAgYWZ0ZXJfYm9keTogZm9vdGVyLmh0bWwNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcw0KLS0tDQoNCiMgKipJbWFnZSBhdWdtZW50YXRpb24gYW5kIG92ZXJmaXR0aW5nOioqIEFuIFIgdmVyc2lvbg0KDQpIZWxsbyEgV2VsY29tZSB0byB0aGUgc2V2ZW50aCAqKlIqKiBjb2RlIHdhbGt0aHJvdWdoIG9mIHRoZSBzZXNzaW9uICoqKk1hY2hpbmUgTGVhcm5pbmcgRm91bmRhdGlvbnMqKiogd2hlcmUgdGhlIGF3ZXNvbWUgW0xhdXJlbmNlIE1vcm9uZXldKGh0dHBzOi8vd3d3LmxpbmtlZGluLmNvbS9pbi9sYXVyZW5jZS1tb3JvbmV5KSxhIERldmVsb3BlciBBZHZvY2F0ZSBhdCBHb29nbGUgd29ya2luZyBvbiBBcnRpZmljaWFsIEludGVsbGlnZW5jZSwgdGFrZXMgdXMgdGhyb3VnaCB0aGUgZnVuZGFtZW50YWxzIG9mIGJ1aWxkaW5nIG1hY2hpbmUgbGVhcm5lZCBtb2RlbHMgdXNpbmcgVGVuc29yRmxvdy4NCg0KSW4gdGhpcyBlcGlzb2RlLCBbRXBpc29kZSA2XShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PW5xN19aWUpQV2YwKSwgTGF1cmVuY2UgTW9yb25leSBnZXRzIHVzIHN0YXJ0ZWQgd2l0aCB5ZXQgYW5vdGhlciBleGNpdGluZyBhcHBsaWNhdGlvbiBvZiBNYWNoaW5lIExlYXJuaW5nLg0KSGVyZSwgd2UgbG9vayBhdCBob3cgb25lIGNhbiB1c2UgYGltYWdlIGF1Z21lbnRhdGlvbmAgYXMgYSB0ZWNobmlxdWUgdG8gYXJ0aWZpY2lhbGx5IGV4dGVuZCBkYXRhc2V0cyB0byBwcm92aWRlIG5ldyBpbmZvcm1hdGlvbiBmb3IgdGhlIHRyYWluaW5nIG9mIGEgbmV1cmFsIG5ldHdvcmsuIFRoaXMgY2FuIHBvdGVudGlhbGx5IGhlbHAgd2l0aCBvdmVyZml0dGluZyBpc3N1ZXMhDQoNCkxpa2UgdGhlIHByZXZpb3VzIFtSIE5vdGVib29rc10ocnB1YnMuZVJfaWMpLCB0aGlzIE5vdGVib29rIHRyaWVzIHRvIHJlcGxpY2F0ZSB0aGUgW1B5dGhvbiBOb3RlYm9va10oaHR0cHM6Ly9jb2xhYi5yZXNlYXJjaC5nb29nbGUuY29tL2dpdGh1Yi9sbW9yb25leS9kbGFpY291cnNlL2Jsb2IvbWFzdGVyL0NvdXJzZSUyMDIlMjAtJTIwUGFydCUyMDQlMjAtJTIwTGVzc29uJTIwMiUyMC0lMjBOb3RlYm9vayUyMChDYXRzJTIwdiUyMERvZ3MlMjBBdWdtZW50YXRpb24pLmlweW5iI3Njcm9sbFRvPWdHeENENG1HSEhqRykgdXNlZCBmb3IgdGhpcyBlcGlzb2RlLg0KDQpCZWZvcmUgd2UgYmVnaW4sIEkgaGlnaGx5IHJlY29tbWVuZCB0aGF0IHlvdSBnbyB0aHJvdWdoIFtFcGlzb2RlIDddKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9UVdkWVd3VzZPQUUpIGZpcnN0IHRoZW4geW91IGNhbiBjb21lIGJhY2sgYW5kIGltcGxlbWVudCB0aGVzZSBjb25jZXB0cyB1c2luZyBSLiBJbiB0aGlzIGVwaXNvZGUsIExhdXJlbmNlIE1vcm9uZXkgZG9lcyBhIHJlYWxseSBleGVtcGxhcnkgam9iIGluIGV4cGxhaW5pbmcgdGhlIHZhcmlvdXMgdHJhbnNmb3JtYXRpb25zIGRvbmUgYnkgaW1hZ2UgYXVnbWVudGF0aW9uLiBZb3Ugc2hvdWxkIGRlZmluaXRlbHkgY2hlY2sgdGhhdCBvdXQgZmlyc3QuIEkgd2lsbCB0cnkgYW5kIGhpZ2hsaWdodCBzb21lIG9mIHRoZSBjb25jZXB0cyBzYWlkIGFuZCBhZGQgc29tZSBvZiBteSBvd24gZm9yIHRoZSBzYWtlIG9mIGNvbXBsZXRlbmVzcyBvZiB0aGlzIHBvc3QgYnV0IEkgaGlnaGx5IHJlY29tbWVuZCB5b3UgbGlzdGVuIGZyb20gaGltIGZpcnN0Lg0KDQo8YnI+DQoNCkxldCdzIHN0YXJ0IGJ5IGxvYWRpbmcgdGhlIGxpYnJhcmllcyByZXF1aXJlZCBmb3IgdGhpcyBzZXNzaW9uLg0KDQpXZSdsbCBiZSByZXF1aXJpbmcgc29tZSBwYWNrYWdlcyBpbiB0aGUgRUJJbWFnZSwgVGlkeXZlcnNlIGFuZCBLZXJhcyhhIGZyYW1ld29yayBmb3IgZGVmaW5pbmcgYSBuZXVyYWwgbmV0d29yayBhcyBhIHNldCBvZiBTZXF1ZW50aWFsIGxheWVycykuIFlvdSBjYW4gaGF2ZSB0aGVtIGluc3RhbGxlZCBhcyBmb2xsb3dzOg0KDQpGb3IgdGhlIFtUaWR5dmVyc2VdKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvKSwgaW5zdGFsbCB0aGUgY29tcGxldGUgdGlkeXZlcnNlIHdpdGg6DQpgYGANCnN1cHByZXNzTWVzc2FnZXMoaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikpDQpgYGANCg0KPGJyPg0KDQpbRUJJbWFnZV0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzLzMuMTEvYmlvYy9odG1sL0VCSW1hZ2UuaHRtbCkgaXMgYW4gUiBwYWNrYWdlIGRpc3RyaWJ1dGVkIGFzIHBhcnQgb2YgdGhlIFtCaW9jb25kdWN0b3JdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnLykgcHJvamVjdC4gVG8gaW5zdGFsbCB0aGUgcGFja2FnZSwgc3RhcnQgUiBhbmQgZW50ZXI6DQpgYGANCmluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikNCkJpb2NNYW5hZ2VyOjppbnN0YWxsKCJFQkltYWdlIikNCmBgYA0KPGJyPg0KVGhlIEtlcmFzIFIgaW50ZXJmYWNlIHVzZXMgdGhlIFRlbnNvckZsb3cgYmFja2VuZCBlbmdpbmUgYnkgZGVmYXVsdC4gQW4gZWxlZ2FudCBkb3VjdW1lbnRhdGlvbiBmb3IgdGhlIGluc3RhbGxhdGlvbiBvZiBib3RoIHRoZSBjb3JlIEtlcmFzIGxpYnJhcnkgYXMgd2VsbCBhcyB0aGUgVGVuc29yRmxvdyBiYWNrZW5kIGNhbiBiZSBmb3VuZCBvbiB0aGUgW1IgaW50ZXJmYWNlIHRvIEtlcmFzXShodHRwczovL2tlcmFzLnJzdHVkaW8uY29tL3JlZmVyZW5jZS9pbnN0YWxsX2tlcmFzLmh0bWwpIHdlYnNpdGUuDQoNCjxicj4NCg0KIyAqKkxldCdzIHRha2UgYSBzdGVwIGJhY2s6KiogT3ZlcmZpdHRpbmcNCg0KTG9va2luZyBiYWNrLCB3ZSBoYXZlIGhhZCBxdWl0ZSBhIHJ1biB3aXRoIENvbnZvbHV0aW9uYWwgTmV1cmFsIE5ldHdvcmtzLCBoYXZlbid0IHdlPyBXZSBzdGFydGVkIHdpdGggYnVpbGRpbmcgYSBjbGFzc2lmaWVyIGZvciBjbG90aGluZyB0aGVuIGJ1aWx0IGEgbW9yZSByZWFsLWxpZmUgaG9yc2VzJmh1bWFucyBjbGFzc2lmaWVyIGFuZCBhbHNvIGhhZCBhIGxvb2sgYXQgYSBjYXRzIHZzIGRvZ3MgY2xhc3NpZmllci4gT25lIHRoaW5nIHdhcyBjb21tb24gdGhvdWdoOiB0aGUgY29uY2VwdCBvZiBgb3ZlcmZpdHRpbmdgLiBTbywgaG93IGRvZXMgb3ZlcmZpdHRpbmcgY29tZSBhYm91dD8gT3ZlcmZpdHRpbmcgaXMgY2F1c2VkIGJ5IGhhdmluZyBsaW1pdGVkIGRhdGEgdG8gdHJhaW4gb24gc3VjaCB0aGF0IHRoZSBOZXVyYWwgTmV0d29yayBiZWNvbWVzIHRvbyBmYW1pbGlhciB3aXRoIHRoYXQgcGFydGljdWxhciBkYXRhc2V0IHJlbmRlcmluZyBpdCB1bmFibGUgdG8gZ2VuZXJhbGl6ZSBuZXcgZGF0YS4gVGhpcyBjYW4gYmUgdmFsaWRhdGVkIGJ5IHRoZSBmYWN0IHRoYXQgb3VyIHByZXZpb3VzIG1vZGVscyBwZXJmb3JtZWQgZXh0cmVtZWx5IHdlbGwgaW4gY2xhc3NpZnlpbmcgdGhlIHRyYWluaW5nIHNldCBidXQgbm90IHNvIGdvb2QgYXQgY2xhc3NpZnlpbmcgZGF0YSBpdCBoYWQgbm90IGJlZW4gZXhwb3NlZCB0byBzdWNoIGFzIHRoYXQgaW4gdGhlIHRlc3Rpbmcgc2V0LiBUaGVvcmV0aWNhbGx5LCBnaXZlbiBpbmZpbml0ZSBkYXRhLCBhIG1vZGVsIHdvdWxkIGJlIGV4cG9zZWQgdG8gZXZlcnkgcG9zc2libGUgYXNwZWN0IG9mIHRoZSBkYXRhIGRpc3RyaWJ1dGlvbiBhdCBoYW5kIGFuZCBvdmVyZml0dGluZyB3b3VsZCBwcm9iYWJseSBub3Qgb2NjdXIuDQoNClNvIGhvdyBkbyB3ZSBmaXggdGhpcz8gOipkcnVtIHJvbGxzKiBgRGF0YSBBdWdtZW50YXRpb25gLiBMZXQncyBkZW15c3RpZnkgdGhpcyB1c2luZyBhbiBleGFtcGxlLg0KQ29uc2lkZXIgdGhlIGltYWdlcyBiZWxvdywgd2l0aCB0aGUgbGVmdCBmZWxpbmUgYmVpbmcgYW4gaW1hZ2UgaW4gdGhlIHRyYWluaW5nIHNldCBhbmQgdGhlIGZlbGluZSBvbiB0aGUgcmlnaHQgYmVpbmcgYW4gaW1hZ2UgaW4gdGhlIHRlc3Qgc2V0Lg0KDQpgYGB7ciwgZWNobz1GQUxTRSwgZmlnLmNhcD0gIkltYWdlIHNvdXJjZTogTWFjaGluZSBMZWFybmluZyBGb3VuZGF0aW9ucyBFcCAjNyAtIEltYWdlIGF1Z21lbnRhdGlvbiBhbmQgb3ZlcmZpdHRpbmcifQ0KDQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoew0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoRUJJbWFnZSkNCmxpYnJhcnkoZHBseXIpDQp9KQ0KDQoNCmltZ19maWxlcyA8LSBsaXN0LmZpbGVzKHBhdGggPSAiQzovVXNlcnMva2VyYXMvT25lRHJpdmUgLSBNaWNyb3NvZnQgU3R1ZGVudCBQYXJ0bmVycy9FcF83L3Jlc291cmNlcyIsIGZ1bGwubmFtZXMgPSBUUlVFICkNCnJlYWRJbWFnZShpbWdfZmlsZXNbMV0pICU+JSBkaXNwbGF5KG1ldGhvZCA9ICdyYXN0ZXInKQ0KYGBgDQo8YnI+DQoNCg0KQ29tcHV0ZXIgdmlzaW9uIHdvcmtzIGJ5IGV4dHJhY3RpbmcgZmVhdHVyZXMgZnJvbSBhbiBpbWFnZSBhbmQgdGhlbiBhc3NvY2lhdGluZyB0aGVtIHdpdGggYSBnaXZlbiBsYWJlbC4gU28gZmVhdHVyZXMgc3VjaCBhcyB0aGUgcG9pbnR5IGVhcnMgYXQgdGhlIHRvcCwgaW4gdGhlIGltYWdlIG9uIHRoZSBsZWZ0LCBtaWdodCBiZSBpbmRpY2F0aXZlIG9mIGEgY2F0LiBJZiBmb3IgaW5zdGFuY2Ugd2UgdHJhaW5lZCBhIG1vZGVsIG9ubHkgb24gaW1hZ2VzIHN1Y2ggYXMgdGhhdCBvbiB0aGUgbGVmdCwgaXQgbWF5IGZhaWwgdG8gY29ycmVjdGx5IGNsYXNzaWZ5IHRoZSBpbWFnZSBvbiB0aGUgcmlnaHQgYXMgYSBjYXQuIFRoaXMgaXMgYmVjYXVzZSBpdCB3aWxsIGJlIGxvb2tpbmcgZm9yIGB0cmlhbmd1bGFyIHNoYXBlcyBvcmllbnRlZCB1cHdhcmRzYCBuZWFyIHRoZSB0b3Agb2YgdGhlIGltYWdlLCBmZWF0dXJlcyB3aGljaCBsYWNrIGluIHRoZSBpbWFnZSBvbiB0aGUgcmlnaHQuDQoNCldoYXQgaWYgZHVyaW5nIHRyYWluaW5nLCB0aGUgaW1hZ2Ugb24gdGhlIGxlZnQgY291bGQgYmUgcm90YXRlZCBzdWNoIHRoYXQgdGhlIG9yaWVudGF0aW9uIG9mIHRoZSBlYXJzIGluIGJvdGggY2F0cyBtYXRjaCBhcyBzaG93biBiZWxvdz8gDQoNCmBgYHtyLCBlY2hvPUZBTFNFLCBmaWcuY2FwPSAiSW1hZ2Ugc291cmNlOiBNYWNoaW5lIExlYXJuaW5nIEZvdW5kYXRpb25zIEVwICM3IC0gSW1hZ2UgYXVnbWVudGF0aW9uIGFuZCBvdmVyZml0dGluZyJ9DQoNCg0KcmVhZEltYWdlKGltZ19maWxlc1syXSkgJT4lIGRpc3BsYXkobWV0aG9kID0gJ3Jhc3RlcicpDQpgYGANCg0KPGJyPg0KDQpUaGVuLCB0aGUgcHJvYmFiaWxpdHkgb2YgdGhlIG1vZGVsIGNsYXNzaWZ5aW5nIHRoZSBpbWFnZSBvbiB0aGUgcmlnaHQgYXMgYSAnY2F0JyBpcyBoaWdoZXIuDQoNClNpbXBseSBwdXQsIGBkYXRhIGF1Z21lbnRhdGlvbmAgaXMgYSB0ZWNobmlxdWUgdXNlZCB0byBpbmNyZWFzZSB0aGUgZGl2ZXJzaXR5IG9mIHRoZSB0cmFpbmluZyBzZXQgYnkgYXBwbHlpbmcgcmFuZG9tIChidXQgcmVhbGlzdGljKSB0cmFuc2Zvcm1hdGlvbnMgc3VjaCBhcyBpbWFnZSByb3RhdGlvbiwgc2hlYXJpbmcgYW5kIHpvb21pbmcuIFN1Y2ggdHJhbnNmb3JtYXRpb25zIGhlbHAgdG8gZXhwb3NlIHRoZSBtb2RlbCB0byBtb3JlIGFzcGVjdHMgb2YgdGhlIGRhdGEgYW5kIGluIHR1cm4gaGVscGluZyB0aGUgbW9kZWwgdG8gZ2VuZXJhbGl6ZSBiZXR0ZXIuDQpJbiB0aGlzIGVwaXNvZGUsIHRoZSBlZmZlY3RzIG9mIHNvbWUgdGhlc2UgdHJhbnNmb3JtYXRpb25zIG9uIGltYWdlcyBhbmQgaG93IHRoZXkgaGVscCBtaW5pbWl6ZSBvdmVyZml0dGluZyBoYXZlIGJlZW4gbmVhdGx5IGlsbHVzdHJhdGVkLCBzbyB5b3Ugc2hvdWxkIGRlZmluaXRlbHkgY2hlY2sgb3V0IPCfmIkuIA0KDQo8YnI+DQoNCiMgKipBIGxpdHRsZSBzYW5pdHkgY2hlY2sgb24gb3VyIGRhdGEqKg0KDQpGaXJzdCB0aGluZ3MgZmlyc3QsIGxldCdzIGRvd25sb2FkIHRoZSBbZGF0YXNldF0oaHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL21sZWR1LWRhdGFzZXRzL2NhdHNfYW5kX2RvZ3NfZmlsdGVyZWQuemlwKSB1c2VkIGZvciB0aGlzIGVwaXNvZGUuIEl0IGlzIG5vdGV3b3J0aHkgdGhhdCB0aGlzIHBhcnRpY3VsYXIgZGF0YXNldCBpcyBhIGZpbHRlcmVkIHZlcnNpb24gb2YgdGhlIG9yaWdpbmFsIGFuZCBjb250YWlucyBhYm91dCAyLDAwMCBpbWFnZXMuIEZvciB0aGlzIHJlYXNvbiwgdGhlIG1vZGVsIGlzIG1vcmUgc3VzY2VwdGlibGUgdG8gb3ZlcmZpdHRpbmcuIEhvd2V2ZXIsIHRoaXMgZGF0YXNldCB3aWxsIGJlIGEgZ29vZCB0ZXN0IGJlZCB0byBpbnZlc3RpZ2F0ZSB0aGUgaW1wYWN0IG9mIGltYWdlIGF1Z21lbnRhdGlvbiBpbiByZWR1Y2luZyBvdmVyZml0dGluZy4NCg0KYGBge3J9DQpiYXNlX2RpciA8LSBsaXN0LmRpcnMocGF0aCA9ICJDOi9Vc2Vycy9rZXJhcy9Eb2N1bWVudHMvY2F0c19hbmRfZG9nc19maWx0ZXJlZCIsIHJlY3Vyc2l2ZSA9IFQpDQoNCnNhcHBseShiYXNlX2RpciwgZnVuY3Rpb24oZGlyKXtsZW5ndGgobGlzdC5maWxlcyhkaXIpKX0pDQojIEF3ZXNvbWUuIFNlZW1zIHRoZSBoYXJkIHdvcmsgaGFzIGFscmVhZHkgYmVlbiBkb25lIGZvciB1cy4gDQojIFRoZSBkYXRhIGlzIHNwbGl0IGludG8gYFRyYWluaW5nYCBhbmQgYFZhbGlkYXRpb25gIGRpcmVjdG9yaWVzDQojIGFuZCBlYWNoIG9mIHRoZXNlIGNvbnRhaW4gdGhlIGBjYXRzYCBhbmQgYGRvZ3NgIHN1Yi1kaXJlY3Rvcmllcy4NCiMgVGhlcmUgYXJlIDIwMDAgaW1hZ2VzIGZvciB0cmFpbmluZyBhbmQgMTAwMCBpbWFnZXMgZm9yIHZhbGlkYXRpb24NCg0KdHJhaW5fZGlyIDwtICBmaWxlLnBhdGgoIkM6L1VzZXJzL2tlcmFzL0RvY3VtZW50cy9jYXRzX2FuZF9kb2dzX2ZpbHRlcmVkL3RyYWluIikNCnZhbGlkYXRpb25fZGlyIDwtICBmaWxlLnBhdGgoIkM6L1VzZXJzL2tlcmFzL0RvY3VtZW50cy9jYXRzX2FuZF9kb2dzX2ZpbHRlcmVkL3ZhbGlkYXRpb24iKQ0KdHJhaW5fY2F0c19kaXIgPC0gbGlzdC5kaXJzKHRyYWluX2RpciwgcmVjdXJzaXZlID0gRilbMV0NCnRyYWluX2RvZ3NfZGlyIDwtIGxpc3QuZGlycyh0cmFpbl9kaXIsIHJlY3Vyc2l2ZSA9IEYpWzJdDQp0ZXN0X2NhdHNfZGlyIDwtIGxpc3QuZGlycyh2YWxpZGF0aW9uX2RpciwgcmVjdXJzaXZlID0gRilbMV0NCmBgYA0KDQoNCmBgYHtyLCBmaWcud2lkdGg9MTF9DQojIGFzIGluIHRoZSBwcmV2aW91cyBub3RlYm9va3MsIGxldCdzIGRpc3BsYXkgc29tZSBvZiB0aGUgZnVycnkgY3JlYXR1cmVzDQpsaWJyYXJ5KEVCSW1hZ2UpDQpsaWJyYXJ5KGRwbHlyKQ0KDQojIGxpc3RpbmcgdGhlIGZpbGVzIGluIHRoZSB0cmFpbl9jYXRzX2RpciBhbmQgdHJhaW5fZG9nc19kaXINCmNhdHNfZGlzcCA8LSBsaXN0LmZpbGVzKHBhdGggPSB0cmFpbl9jYXRzX2RpciwgZnVsbC5uYW1lcyA9IFQpICU+JQ0KICBzYW1wbGUoc2l6ZSA9IDQsIHJlcGxhY2UgPSBGKQ0KZG9nc19kaXNwIDwtIGxpc3QuZmlsZXMocGF0aCA9IHRyYWluX2RvZ3NfZGlyLCBmdWxsLm5hbWVzID0gVCkgJT4lDQogIHNhbXBsZShzaXplID0gNCwgcmVwbGFjZSA9IEYpDQoNCmltZ19kaXNwIDwtIHNhbXBsZShjKGNhdHNfZGlzcCxkb2dzX2Rpc3ApKQ0KDQojIHJlc2l6aW5nIHRoZSBpbWFnZXMgc2luY2UgcmVhZEltYWdlIHtFQkltYWdlfSByZXF1aXJlcyBhbGwgaW1hZ2VzDQojIHRvIGhhdmUgc2FtZSBkaW1lbnNpb24gYW5kIGNvbG9yIG1vZGUNCg0KZm9yKGkgaW4gc2VxX2Fsb25nKGltZ19kaXNwKSl7DQogIHJlYWRJbWFnZShpbWdfZGlzcFtpXSkgJT4lIA0KICAgIHJlc2l6ZSh3ID0gMzAwLCBoID0gMzAwKSAlPiUgDQogICAgd3JpdGVJbWFnZShpbWdfZGlzcFtpXSkNCiAgDQp9DQogIA0KDQpFQkltYWdlOjpkaXNwbGF5KA0KICByZWFkSW1hZ2UoaW1nX2Rpc3ApLA0KICBtZXRob2QgPSAncmFzdGVyJywNCiAgYWxsID0gVCwNCiAgbnggPSA0LA0KICBzcGFjaW5nID0gYygwLDApDQopDQoNCmBgYA0KDQo8YnI+DQoNCiMgKipCdWlsZGluZyBhIG1vZGVsIHRoYXQgZG9lcyBub3QgdXNlIGltYWdlIGF1Z21lbnRhdGlvbioqDQoNClZlcnkgcXVpY2tseSwgZnJvbSB0aGUgcHJldmlvdXMgc2Vzc2lvbnM6DQpDb252b2x1dGlvbmFsIE5ldXJhbCBuZXR3b3JrcyB1c2UgZmlsdGVycy9rZXJuZWxzIHRvIHByb2Nlc3MgaW1hZ2VzIGFuZCBleHRyYWN0IGZlYXR1cmVzIHN1Y2ggdGhhdCBhZnRlciBsZWFybmluZyBhIGNlcnRhaW4gcGF0dGVybiBhIGNvbnZuZXQgY2FuIHJlY29nbml6ZSBpdCBhbnl3aGVyZS4gVGhlIGNvbnZvbHV0aW9uYWwgbGF5ZXJzIGxlYXJuIHRoZSBmZWF0dXJlcyBhbmQgcGFzcyB0aGVzZSB0byB0aGUgZGVuc2UgbGF5ZXJzIHdoaWNoIG1hcCB0aGUgbGVhcm5lZCBmZWF0dXJlcyB0byB0aGUgZ2l2ZW4gbGFiZWxzLg0KDQo8YnI+DQoNClRoZSBwb29saW5nIGxheWVyIHNlcnZlcyB0byBwcm9ncmVzc2l2ZWx5IHJlZHVjZSB0aGUgc3BhdGlhbCBzaXplIG9mIHRoZSByZXByZXNlbnRhdGlvbiB0byByZWR1Y2UgdGhlIG51bWJlciBvZiBwYXJhbWV0ZXJzLCBtZW1vcnkgZm9vdHByaW50IGFuZCBhbW91bnQgb2YgY29tcHV0YXRpb24gaW4gdGhlIG5ldHdvcmsuIE1heCBwb29saW5nIGNvbnNpc3RzIG9mIGV4dHJhY3Rpbmcgd2luZG93cyBvZiBzaXplIDJieTIgZnJvbSB0aGUgaW5wdXQgZmVhdHVyZSBtYXBzIGFuZCBvdXRwdXR0aW5nIHRoZSBtYXggdmFsdWUgb2YgZWFjaCBjaGFubmVsLg0KYFBvb2xpbmdgIGhlbmNlIHJlZHVjZXMgdGhlIGFtb3VudCBvZiBpcnJlbGV2YW50IGluZm9ybWF0aW9uIGluIGFuIGltYWdlIHdoaWxlIG1haW50YWluaW5nIHRoZSBmZWF0dXJlcyB0aGF0IGFyZSBkZXRlY3RlZC4NCg0KSW4gY2FzZSB5b3UgbmVlZCB0byBicnVzaCB1cCBvbiB0aGUgY29uY2VwdHMgb2YgQ05OLCBbRXBpc29kZSAzXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PVBDZ0xtemtSTTM4Jmxpc3Q9UExPVTJYTFl4bXNJSTltelEtWHh1ZzRsMm8wNEpCcmtMViZpbmRleD0zKSB3b3VsZCBiZSBhIGdvb2QgcGxhY2UgdG8gc3RhcnQuDQoNCg0KDQpTbywgbGV0J3Mgc2V0IHRoZXNlIGNvbmNlcHRzIGluIG1vdGlvbiwgc2hhbGwgd2U/DQoNCg0KIyMjICoqSW5zdGFudGlhdGluZyBhIENvbnZvbHV0aW9uKioNCldl4oCZbGwgcmV1c2UgdGhlIHNhbWUgZ2VuZXJhbCBzdHJ1Y3R1cmUgd2UndmUgYmVlbiB1c2luZzogdGhlIGNvbnZuZXQgd2lsbCBiZSBhIHN0YWNrIG9mIGFsdGVybmF0ZWQgbGF5ZXJfY29udl8yZCAod2l0aCByZWx1IGFjdGl2YXRpb24pIGFuZCBsYXllcl9tYXhfcG9vbGluZ18yZCBzdGFnZXMuIA0KWW91IHdpbGwgbm90aWNlIHRoYXQgYXMgd2UgZ28gZGVlcGVyLCB3ZSBpbmNyZWFzZSB0aGUgbnVtYmVyIG9mIGZpbHRlcnMuIFRoaXMgaXMgYmVjYXVzZSBjb252b2x1dGlvbnMgY2FuIGxlYXJuIHNwYXRpYWwgaGllcmFyY2hpZXMgb2YgcGF0dGVybnMuIEEgZmlyc3QgY29udm9sdXRpb24gbGF5ZXIgd2lsbCBsZWFybiBzbWFsbCBsb2NhbCBwYXR0ZXJucyBzdWNoIGFzIGVkZ2VzLCBhIHNlY29uZCBjb252b2x1dGlvbiBsYXllciB3aWxsIGxlYXJuIGxhcmdlciBwYXR0ZXJucyBtYWRlIG9mIHRoZSBmZWF0dXJlcyBvZiB0aGUgZmlyc3QgbGF5ZXJzLCBhbmQgc28gb24uIFRoaXMgYWxsb3dzIGNvbnZuZXRzIHRvIGVmZmljaWVudGx5IGxlYXJuIGluY3JlYXNpbmdseSBjb21wbGV4IGFuZCBhYnN0cmFjdCB2aXN1YWwgY29uY2VwdHMuDQoNCmBgYHtyfQ0KbGlicmFyeShrZXJhcykNCg0KbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JQ0KICAjIGFkZGluZyB0aGUgZmlyc3QgY29udm9sdXRpb24gbGF5ZXIgd2l0aCAxNiAzYnkzIGZpbHRlcnMNCiAgIyB3ZSBhZGQgYW4gYWRkaXRpb25hbCBkaW1lbnNpb24gaW4gdGhlIGlucHV0IHNoYXBlIHNpbmNlIGNvbnZvbHV0aW9ucyBvcGVyYXRlIG92ZXIgM0QgdGVuc29ycw0KICAjIHRoZSBpbnB1dCBzaGFwZSB0ZWxscyB0aGUgbmV0d29yayB0aGF0IHRoZSBmaXJzdCBsYXllciBzaG91bGQgZXhwZWN0DQogICMgaW1hZ2VzIG9mIDE1MCBieSAxNTAgcGl4ZWxzIHdpdGggYSBjb2xvciBkZXB0aCBvZiAzIGllIFJHQiBpbWFnZXMNCiAgbGF5ZXJfY29udl8yZChpbnB1dF9zaGFwZSA9IGMoMTUwLCAxNTAsIDMpLCBmaWx0ZXJzID0gMzIsIGtlcm5lbF9zaXplID0gYygzLCAzKSwgYWN0aXZhdGlvbiA9ICdyZWx1JyApICU+JQ0KICAjIGFkZGluZyBhIG1heCBwb29saW5nIGxheWVyIHdoaWNoIGhhbHZlcyB0aGUgZGltZW5zaW9ucw0KICBsYXllcl9tYXhfcG9vbGluZ18yZChwb29sX3NpemUgPSBjKDIsIDIpKSAlPiUNCiAgIyBhZGRpbmcgYSBzZWNvbmQgY29udm9sdXRpb24gbGF5ZXIgd2l0aCA2NCBmaWx0ZXJzDQogIGxheWVyX2NvbnZfMmQoZmlsdGVycyA9IDY0LCBrZXJuZWxfc2l6ZSA9IGMoMywgMyksIGFjdGl2YXRpb24gPSAncmVsdScpICU+JQ0KICBsYXllcl9tYXhfcG9vbGluZ18yZChwb29sX3NpemUgPSBjKDIsIDIpKSAlPiUNCiAgIyBhZGRpbmcgYSBzZWNvbmQgY29udm9sdXRpb24gbGF5ZXIgd2l0aCAxMjggZmlsdGVycw0KICBsYXllcl9jb252XzJkKGZpbHRlcnMgPSAxMjgsIGtlcm5lbF9zaXplID0gYygzLCAzKSwgYWN0aXZhdGlvbiA9ICdyZWx1JykgJT4lDQogIGxheWVyX21heF9wb29saW5nXzJkKHBvb2xfc2l6ZSA9IGMoMiwgMikpDQogIA0KICANCmBgYA0KDQo8YnI+DQoNCiMjIyAqKkFkZGluZyBhIGNsYXNzaWZpZXIgdG8gdGhlIGNvbnZuZXQqKg0KDQpDb252b2x1dGlvbmFsIGxheWVycyBsZWFybiB0aGUgZmVhdHVyZXMgYW5kIHBhc3MgdGhlc2UgdG8gdGhlIGRlbnNlIGxheWVycyB3aGljaCBtYXAgdGhlIGxlYXJuZWQgZmVhdHVyZXMgdG8gdGhlIGdpdmVuIGxhYmVscy4gVGhlcmVmb3JlLCB0aGUgbmV4dCBzdGVwIGlzIHRvIGZlZWQgdGhlIGxhc3Qgb3V0cHV0IHRlbnNvciBpbnRvIGEgZGVuc2VseSBjb25uZWN0ZWQgY2xhc3NpZmllciBuZXR3b3JrIGxpa2UgdGhvc2Ugd2XigJlyZSBhbHJlYWR5IGZhbWlsaWFyIHdpdGg6IGEgc3RhY2sgb2YgZGVuc2UgbGF5ZXJzLg0KVGhlc2UgY2xhc3NpZmllcnMgcHJvY2VzcyB2ZWN0b3JzLCB3aGljaCBhcmUgMUQsIGhvd2V2ZXIsIHRoZSBjdXJyZW50IG91dHB1dCBpcyBhIDNEIHRlbnNvci4gRmlyc3Qgd2UgaGF2ZSB0byBmbGF0dGVuIHRoZSAzRCBvdXRwdXRzIHRvIDFELCBhbmQgdGhlbiBhZGQgYSBmZXcgZGVuc2UgbGF5ZXJzIG9uIHRvcC4NCg0KTm90ZSB0aGF0IGJlY2F1c2Ugd2UgYXJlIGZhY2luZyBhIHR3by1jbGFzcyBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtLCBpLmUuIGEgYmluYXJ5IGNsYXNzaWZpY2F0aW9uIHByb2JsZW0sIHdlIGNhbiBlbmQgb3VyIG5ldHdvcmsgd2l0aCBhIFtzaWdtb2lkIGFjdGl2YXRpb25dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1NpZ21vaWRfZnVuY3Rpb24pLCBzbyB0aGF0IHRoZSBvdXRwdXQgb2Ygb3VyIG5ldHdvcmsgd2lsbCBiZSBhIHNpbmdsZSBzY2FsYXIgYmV0d2VlbiAwIGFuZCAxLCBlbmNvZGluZyB0aGUgcHJvYmFiaWxpdHkgdGhhdCB0aGUgY3VycmVudCBpbWFnZSBpcyBjbGFzcyAxIChhcyBvcHBvc2VkIHRvIGNsYXNzIDApLiBGb3IgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCBLZXJhcyBhY3RpdmF0aW9uIGZ1bmN0aW9ucywga2luZGx5IHZpc2l0IHRoZSBbS2VyYXMgd2Vic2l0ZV0oaHR0cHM6Ly9rZXJhcy5pby9hcGkvbGF5ZXJzL2FjdGl2YXRpb25zLykuDQoNCg0KYGBge3J9DQptb2RlbCA8LSBtb2RlbCAlPiUNCiAgbGF5ZXJfZmxhdHRlbigpICU+JQ0KICBsYXllcl9kZW5zZSh1bml0cyA9IDUxMiwgYWN0aXZhdGlvbiA9ICdyZWx1JykgJT4lDQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSxhY3RpdmF0aW9uID0gJ3NpZ21vaWQnKQ0KICANCg0KIyBMZXTigJlzIGxvb2sgYXQgaG93IHRoZSBkaW1lbnNpb25zIG9mIHRoZSBmZWF0dXJlIG1hcHMgY2hhbmdlIHdpdGggZXZlcnkgc3VjY2Vzc2l2ZSBsYXllcjoNCg0KbW9kZWwgJT4lIHN1bW1hcnkoKQ0KYGBgDQoNCjxicj4NCg0KKipDb21waWxlOioqIENvbmZpZ3VyaW5nIGEgS2VyYXMgbW9kZWwgZm9yIHRyYWluaW5nDQoNCmBgYHtyfQ0KbW9kZWwgJT4lDQogIGNvbXBpbGUoDQogICAgbG9zcyA9ICdiaW5hcnlfY3Jvc3NlbnRyb3B5JywNCiAgICBvcHRpbWl6ZXIgPSBvcHRpbWl6ZXJfcm1zcHJvcChsciA9IDAuMDAwMSksDQogICAgbWV0cmljcyA9ICdhY2N1cmFjeScNCiAgKQ0KYGBgDQpCaW5hcnlfIENyb3NzZW50cm9weSBsb3NzIENvbXB1dGVzIHRoZSBjcm9zcy1lbnRyb3B5IGxvc3MgYmV0d2VlbiB0cnVlIGxhYmVscyBhbmQgcHJlZGljdGVkIGxhYmVscy4gVHlwaWNhbGx5IHVzZWQgd2hlbiB0aGVyZSBhcmUgb25seSB0d28gbGFiZWwgY2xhc3Nlcy4oRm9yIGEgcmVmcmVzaGVyIG9uIGxvc3MgbWV0cmljcywgc2VlIHRoZSBbTWFjaGluZSBMZWFybmluZyBDcmFzaCBDb3Vyc2VdKGh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL21hY2hpbmUtbGVhcm5pbmcvY3Jhc2gtY291cnNlL2Rlc2NlbmRpbmctaW50by1tbC92aWRlby1sZWN0dXJlKSBhbmQgdGhlIFtLZXJhcyBkb2N1bWVudGF0aW9uXShodHRwczovL2tlcmFzLmlvL2FwaS9sb3NzZXMvcHJvYmFiaWxpc3RpY19sb3NzZXMvI2JpbmFyeV9jcm9zc2VudHJvcHktZnVuY3Rpb24pKSANCg0KPGJyPg0KDQojIyMgKipEYXRhIHByZXByb2Nlc3NpbmcqKg0KDQpTbyBob3cgd2lsbCB3ZSBhcHBseSB0aGVzZSBgcmFuZG9tIHRyYW5zZm9ybWF0aW9uc2AgdG8gb3VyIGltYWdlcyBpbiBhIGJpZCB0byBhY2hpZXZlIGltYWdlIGF1Z21lbnRhdGlvbj8gR29vZCB0aG91Z2h0IQ0KVGhhbmtmdWxseSBLZXJhcyBoYXMgdXRpbGl0aWVzIHRvIHR1cm4gaW1hZ2UgZmlsZXMgb24gZGlzayBpbnRvIGJhdGNoZXMgb2YgcHJlLXByb2Nlc3NlZCB0ZW5zb3JzLiBXZSdsbCB1c2UgdGhlIGBpbWFnZV9kYXRhX2dlbmVyYXRvcmAgdG8gZ2VuZXJhdGUgYmF0Y2hlcyBvZiB0ZW5zb3IgaW1hZ2UgZGF0YSB3aXRoIHJlYWwtdGltZSBkYXRhIGF1Z21lbnRhdGlvbi4NCldlJ2xsIGltcGxlbWVudCBpbWFnZSBhdWdtZW50YXRpb24gaW4gdGhlIG5leHQgZXhhbXBsZS4gRm9yIG5vdywgd2UnbGwgb25seSB1c2UgdGhlIGltYWdlIGdlbmVyYXRvciB0bzogDQoNCiogTm9ybWFsaXplIHRoZSBwaXhlbCB2YWx1ZXMgdG8gdGhlIFswLCAxXSBpbnRlcnZhbC4NCiogQXV0b2xhYmVsIHRoZSBpbWFnZXMgb2YgY2F0cyBhbmQgZG9ncyBhdXRvbWF0aWNhbGx5IGJhc2VkIG9uIHRoZSBzdWJkaXJlY3RvcnkgbmFtZTogLSBJbWFnZUdlbmVyYXRvciB3aWxsIGxhYmVsIHRoZSBpbWFnZXMgYXBwcm9wcmlhdGVseSBmb3IgeW91LCByZWR1Y2luZyBhIGNvZGluZyBzdGVwLiBTb3VuZHMgbmVhdCwgcmlnaHQ/DQoNCmBgYHtyfQ0KIyBub3JtYWxpemluZyB0aGUgZGF0YSBieSBtdWx0aXBseWluZyBieSBhIHJlc2NhbGluZyBmYWN0b3INCnRyYWluX2RhdGFnZW4gPC0gaW1hZ2VfZGF0YV9nZW5lcmF0b3IocmVzY2FsZSA9IDEvMjU1KQ0KDQoNCiMgRmxvdyB0cmFpbmluZyBpbWFnZXMgaW4gYmF0Y2hlcyBvZiAyMCB1c2luZyB0cmFpbl9kYXRhZ2VuIGdlbmVyYXRvcg0KdHJhaW5fZ2VuZXJhdG9yIDwtIGZsb3dfaW1hZ2VzX2Zyb21fZGlyZWN0b3J5KA0KICAjIHRhcmdldCBkaXJlY3RvcnkNCiAgZGlyZWN0b3J5ID0gdHJhaW5fZGlyLA0KICAjIHRyYWluaW5nIGRhdGEgZ2VuZXJhdG9yDQogIGdlbmVyYXRvciA9IHRyYWluX2RhdGFnZW4sDQogICMgcmVzaXppbmcgdGhlIGltYWdlcyB0byB0aGUgc2FtZSBkaW1lbnNpb25zIGV4cGVjdGVkIGJ5IG91ciBOTg0KICB0YXJnZXRfc2l6ZSA9IGMoMTUwLCAxNTApLA0KICAjIDIwIGltYWdlcyBhdCBhIHRpbWUgdG8gYmUgZmVkIGludG8gdGhlIE5ODQogIGJhdGNoX3NpemUgPSAyMCwNCiAgIyBTaW5jZSB3ZSB1c2UgYmluYXJ5X2Nyb3NzZW50cm9weSBsb3NzLCB3ZSBuZWVkIGJpbmFyeSBsYWJlbCBhcnJheXMNCiAgY2xhc3NfbW9kZSA9ICdiaW5hcnknDQopDQoNCg0KDQpgYGANCg0KDQpMZXQncyBkbyB0aGUgc2FtZSBmb3IgdGhlIHZhbGlkYXRpb24gc2V0DQpgYGB7cn0NCiMgbm9ybWFsaXppbmcgdGhlIGRhdGEgYnkgbXVsdGlwbHlpbmcgYnkgYSByZXNjYWxpbmcgZmFjdG9ydA0KdGVzdF9kYXRhZ2VuIDwtIGltYWdlX2RhdGFfZ2VuZXJhdG9yKHJlc2NhbGUgPSAxLzI1NSkNCg0KDQojIEZsb3cgdHJhaW5pbmcgaW1hZ2VzIGluIGJhdGNoZXMgb2YgMjAgdXNpbmcgdGVzdF9kYXRhZ2VuIGdlbmVyYXRvcg0KdmFsaWRhdGlvbl9nZW5lcmF0b3IgPC0gZmxvd19pbWFnZXNfZnJvbV9kaXJlY3RvcnkoDQogICMgdGFyZ2V0IGRpcmVjdG9yeQ0KICBkaXJlY3RvcnkgPSB2YWxpZGF0aW9uX2RpciwNCiAgIyB0cmFpbmluZyBkYXRhIGdlbmVyYXRvcg0KICBnZW5lcmF0b3IgPSB0ZXN0X2RhdGFnZW4sDQogICMgcmVzaXppbmcgdGhlIGltYWdlcyB0byB0aGUgc2FtZSBkaW1lbnNpb25zIGV4cGVjdGVkIGJ5IG91ciBOTg0KICB0YXJnZXRfc2l6ZSA9IGMoMTUwLCAxNTApLA0KICAjIDIwIGltYWdlcyBhdCBhIHRpbWUgdG8gYmUgZmVkIGludG8gdGhlIE5ODQogIGJhdGNoX3NpemUgPSAyMCwNCiAgIyBTaW5jZSB3ZSB1c2UgYmluYXJ5X2Nyb3NzZW50cm9weSBsb3NzLCB3ZSBuZWVkIGJpbmFyeSBsYWJlbCBhcnJheXMNCiAgY2xhc3NfbW9kZSA9ICdiaW5hcnknDQopDQoNCg0KDQpgYGANCg0KPGJyPg0KDQojIyMgKipUcmFpbmluZyB0aGUgTmV1cmFsIE5ldHdvcmsqKg0KDQpUcmFpbmluZyBzaW1wbHkgbWVhbnMgbGVhcm5pbmcgKGRldGVybWluaW5nKSBnb29kIHZhbHVlcyBmb3IgYWxsIHRoZSB3ZWlnaHRzIGFuZCB0aGUgYmlhcyBmcm9tIGxhYmVsZWQgZXhhbXBsZXMuIEl0IGRvZXMgc28gYnkgJ2xlYXJuaW5nJyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHRyYWluX2ltYWdlcyBhbmQgdHJhaW5fbGFiZWxzIGFycmF5cy4NCg0KTGV04oCZcyBmaXQgdGhlIG1vZGVsIHRvIHRoZSBkYXRhIHVzaW5nIGEgZ2VuZXJhdG9yLiBZb3UgZG8gc28gdXNpbmcgdGhlDQpgZml0X2dlbmVyYXRvciB7a2VyYXN9YCBmdW5jdGlvbiwgdGhlIGVxdWl2YWxlbnQgZm9yIGBmaXRgIGZvciBkYXRhIGdlbmVyYXRvcnMgbGlrZSB0aGlzIG9uZS4gSXQgZXhwZWN0cyBhcyBpdHMgZmlyc3QgYXJndW1lbnQgYSBnZW5lcmF0b3IgdGhhdCB3aWxsIHlpZWxkIGJhdGNoZXMgb2YgaW5wdXRzIGFuZCB0YXJnZXRzIGluZGVmaW5pdGVseS4gQmVjYXVzZSB0aGUgZGF0YSBpcyBiZWluZyBnZW5lcmF0ZWQgZW5kbGVzc2x5LCB0aGUgbW9kZWwgbmVlZHMgdG8ga25vdyBob3cgbWFueSBzYW1wbGVzIHRvIGRyYXcgZnJvbSB0aGUgZ2VuZXJhdG9yIGJlZm9yZSBkZWNsYXJpbmcgYW4gZXBvY2ggb3Zlci4gVGhpcyBpcyB0aGUgcm9sZSBvZiB0aGUgYHN0ZXBzX3Blcl9lcG9jaGAgYXJndW1lbnQuIEl0IGRlZmluZXMgdGhlIHRvdGFsIG51bWJlciBvZiBzdGVwcyAoYmF0Y2hlcyBvZiBzYW1wbGVzKSB0byB5aWVsZCBmcm9tIGdlbmVyYXRvciBiZWZvcmUgZGVjbGFyaW5nIG9uZSBlcG9jaCBmaW5pc2hlZCBhbmQgc3RhcnRpbmcgdGhlIG5leHQgZXBvY2guIEl0IHNob3VsZCAqdHlwaWNhbGx5KiBiZSBlcXVhbCB0byB0aGUgKm51bWJlciBvZiBzYW1wbGVzIGluIHlvdXIgZGF0YXNldCBkaXZpZGVkIGJ5IHRoZSBiYXRjaCBzaXplKi4NCg0KYHZhbGlkYXRpb25fc3RlcHNgIGRlc2NyaWJlcyB0aGUgdG90YWwgbnVtYmVyIG9mIHN0ZXBzIChiYXRjaGVzIG9mIHNhbXBsZXMpIHRvIHlpZWxkIGZyb20gZ2VuZXJhdG9yIGJlZm9yZSBzdG9wcGluZyBhdCB0aGUgZW5kIG9mIGV2ZXJ5IGVwb2NoLiBJdCB0ZWxscyB0aGUgbmV0d29yayBob3cgbWFueSBiYXRjaGVzIHRvIGRyYXcgZnJvbSB0aGUgdmFsaWRhdGlvbiBnZW5lcmF0b3IgZm9yIGV2YWx1YXRpb24uDQoNCmBBbiBlcG9jaCBmaW5pc2hlcyB3aGVuIHN0ZXBzX3Blcl9lcG9jaCBiYXRjaGVzIGhhdmUgYmVlbiBzZWVuIGJ5IHRoZSBtb2RlbC5gDQoNCjxicj4NCg0KICoqRml0dGluZyB0aGUgbW9kZWwgdXNpbmcgYSBiYXRjaCBnZW5lcmF0b3IqKg0KIA0KTGV0J3MgdHJhaW4gZm9yIDEwMCBlcG9jaHMgLS0gdGhpcyBtYXkgdGFrZSBzb21lIG1pbnV0ZXMgdG8gcnVuLg0KDQpUaGUgTG9zcyBhbmQgQWNjdXJhY3kgYXJlIGEgZ3JlYXQgaW5kaWNhdGlvbiBvZiBwcm9ncmVzcyBvZiB0cmFpbmluZy4gSXQncyBtYWtpbmcgYSBndWVzcyBhcyB0byB0aGUgY2xhc3NpZmljYXRpb24gb2YgdGhlIHRyYWluaW5nIGRhdGEsIGFuZCB0aGVuIG1lYXN1cmluZyBpdCBhZ2FpbnN0IHRoZSBrbm93biBsYWJlbCwgY2FsY3VsYXRpbmcgdGhlIHJlc3VsdC4gQWNjdXJhY3kgaXMgdGhlIHBvcnRpb24gb2YgY29ycmVjdCBndWVzc2VzLg0KPGJyPjxicj4NCg0KYGBge3J9DQpoaXN0b3J5IDwtIG1vZGVsICU+JSBmaXRfZ2VuZXJhdG9yKA0KICBnZW5lcmF0b3IgPSB0cmFpbl9nZW5lcmF0b3IsDQogICMgVG90YWwgbnVtYmVyIG9mIHN0ZXBzIChiYXRjaGVzIG9mIHNhbXBsZXMpIHRvIHlpZWxkDQogICNiZWZvcmUgZGVjbGFyaW5nIG9uZSBlcG9jaCBmaW5pc2hlZCBhbmQgc3RhcnRpbmcgdGhlIG5leHQgZXBvY2guDQogIHN0ZXBzX3Blcl9lcG9jaCA9IDEwMCwgIyAyMDAwLzIwDQogICMgQW4gZXBvY2ggaXMgYW4gaXRlcmF0aW9uIG92ZXIgdGhlIGVudGlyZSBkYXRhIHByb3ZpZGVkDQogIGVwb2NocyA9IDEwMCwNCiAgdmFsaWRhdGlvbl9kYXRhID0gdmFsaWRhdGlvbl9nZW5lcmF0b3IsDQogIHZhbGlkYXRpb25fc3RlcHMgPSA1MCAjIDEwMDAvMjANCiAgDQogIA0KKQ0KDQojIEl04oCZcyBnb29kIHByYWN0aWNlIHRvIGFsd2F5cyBzYXZlIHlvdXIgbW9kZWxzIGFmdGVyIHRyYWluaW5nLg0KDQptb2RlbCAlPiUgc2F2ZV9tb2RlbF9oZGY1KCJjYXRzX2FuZF9kb2dzX2ZpbHRlcmVkLmg1IikgDQoNCiMgcGxvdHRpbmcgdGhlIGxvc3MgYW5kIGFjY3VyYWN5IG92ZXIgdGhlIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIGRhdGENCiMgZHVyaW5nIHRyYWluaW5nDQpwbG90KGhpc3RvcnkpDQoNCiMgQSBzdW1tYXJ5IG9mIGhvdyB0aGUgbW9kZWwgcGVyZm9ybWVkDQpoaXN0b3J5DQoNCmBgYA0KDQpUaGUgVHJhaW5pbmcgQWNjdXJhY3kgaXMgY2xvc2UgdG8gMTAwJSwgYW5kIHRoZSB2YWxpZGF0aW9uIGFjY3VyYWN5IGlzIGluIHRoZSA3MCUtODAlIHJhbmdlLiBUaGlzIGlzIGEgZ3JlYXQgZXhhbXBsZSBvZiBvdmVyZml0dGluZzogdGhlIGZhY3QgdGhhdCBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscyB0ZW5kIHRvIHBlcmZvcm0gd29yc2Ugb24gbmV3IGRhdGEgdGhleSBoYXZlIG5ldmVyICdzZWVuJyBiZWZvcmUgdGhhbiBvbiB0aGVpciB0cmFpbmluZyBkYXRhLiBJdCBvY2N1cnMgd2hlbiB0aGUgbmV0d29yayBlbmRzIHVwIGxlYXJuaW5nIHJlcHJlc2VudGF0aW9ucyB0aGF0IGFyZSBzcGVjaWZpYyB0byB0aGUgdHJhaW5pbmcgZGF0YSBhbmQgZG9lc24ndCBnZW5lcmFsaXplIHRvIGRhdGEgb3V0c2lkZSBvZiB0aGUgdHJhaW5pbmcgc2V0Lg0KDQpMZXQncyBzZWUgaWYgd2UgY2FuIGRvIGJldHRlciB0byBhdm9pZCBvdmVyZml0dGluZyAtLSBhbmQgb25lIHNpbXBsZSBtZXRob2QgaXMgdG8gdHdlYWsgdGhlIGltYWdlcyBhIGJpdCBieSBhcHBseWluZyByYW5kb20gdHJhbnNmb3JtYXRpb25zIHN1Y2ggYXMgcm90YXRpb24sIHNoZWFyaW5nLCB6b29taW5nIGV0Yy4gVGhhdCdzIHdoYXQgYGltYWdlIGF1Z21lbnRhdGlvbmAgaXMgYWxsIGFib3V0Lg0KDQpXZSdsbCB1c2UgdGhlIGBpbWFnZV9kYXRhX2dlbmVyYXRvcmAgdG8gZ2VuZXJhdGUgYmF0Y2hlcyBvZiB0ZW5zb3IgaW1hZ2UgZGF0YSB3aXRoIHJlYWwtdGltZSBkYXRhIGF1Z21lbnRhdGlvbi4NCg0KTGV0J3MgZ2V0IHN0YXJ0ZWQgd2l0aCBhbiBleGFtcGxlLg0KDQo8YnI+DQoNCiMgKipJbWFnZSBhdWdtZW50YXRpb246KiogQW4gZXhhbXBsZQ0KDQpBbGwgdGhpcyB0aW1lLCB3ZSBoYXZlIGJlZW4gdXNpbmcgdGhlIGBpbWFnZV9kYXRhX2dlbmVyYXRvcmAgdG8gbm9ybWFsaXplIG91ciBpbWFnZXMuIFdlJ2xsIHVwZGF0ZSBhIGZldyBtb3JlIGFyZ3VtZW50cyB0byBpdCBhbmQgd2UnbGwgYmUgZ2V0dGluZyBwYXN0IGBvdmVyZml0dGluZ2AgaW4gbm8gdGltZS4NCg0KPGJyPg0KDQojIyMgKipTZXR0aW5nIHVwIGEgZGF0YSBhdWdtZW50YXRpb24gY29uZmlndXJhdGlvbiB2aWEgaW1hZ2VfZGF0YV9nZW5lcmF0b3IqKg0KDQpgYGB7cn0NCg0KZGF0YWdlbiA8LSBpbWFnZV9kYXRhX2dlbmVyYXRvcigNCiAgcmVzY2FsZSA9IDEvMjU1LA0KICByb3RhdGlvbl9yYW5nZSA9IDQwLA0KICB3aWR0aF9zaGlmdF9yYW5nZSA9IDAuMiwNCiAgaGVpZ2h0X3NoaWZ0X3JhbmdlID0gMC4yLA0KICBzaGVhcl9yYW5nZSA9IDAuMiwNCiAgem9vbV9yYW5nZSA9IDAuMiwNCiAgaG9yaXpvbnRhbF9mbGlwID0gVFJVRSwNCiAgZmlsbF9tb2RlID0gIm5lYXJlc3QiDQopDQoNCg0KYGBgDQpUaGVzZSBhcmUganVzdCBhIGZldyBvZiB0aGUgb3B0aW9ucyBhdmFpbGFibGUgKGZvciBtb3JlLCBzZWUgdGhlIFtLZXJhcyBkb2N1bWVudGF0aW9uXShodHRwczovL2tlcmFzLmlvL2FwaS9wcmVwcm9jZXNzaW5nL2ltYWdlLykpLiBMZXTigJlzIHF1aWNrbHkgZ28gb3ZlciB0aGlzIGNvZGU6DQoNCiogYHJvdGF0aW9uX3JhbmdlYCBpcyBhIHZhbHVlIGluIGRlZ3JlZXMgKDDigJMxODApLCBhIHJhbmdlIHdpdGhpbiB3aGljaCB0byByYW5kb21seSByb3RhdGUgcGljdHVyZXMuDQoqIGB3aWR0aF9zaGlmdGAgYW5kIGBoZWlnaHRfc2hpZnRgIGFyZSByYW5nZXMgKGFzIGEgZnJhY3Rpb24gb2YgdG90YWwgd2lkdGggb3INCmhlaWdodCkgd2l0aGluIHdoaWNoIHRvIHJhbmRvbWx5IHRyYW5zbGF0ZSBwaWN0dXJlcyB2ZXJ0aWNhbGx5IG9yIGhvcml6b250YWxseS4NCiogYHNoZWFyX3JhbmdlYCBpcyBmb3IgcmFuZG9tbHkgYXBwbHlpbmcgc2hlYXJpbmcgdHJhbnNmb3JtYXRpb25zLg0KKiBgem9vbV9yYW5nZWAgaXMgZm9yIHJhbmRvbWx5IHpvb21pbmcgaW5zaWRlIHBpY3R1cmVzLg0KKiBgaG9yaXpvbnRhbF9mbGlwYCBpcyBmb3IgcmFuZG9tbHkgZmxpcHBpbmcgaGFsZiB0aGUgaW1hZ2VzIGhvcml6b250YWxseeKAlHJlbGV2YW50IHdoZW4gdGhlcmUgYXJlIG5vIGFzc3VtcHRpb25zIG9mIGhvcml6b250YWwgYXN5bW1ldHJ5IChmb3IgZXhhbXBsZSwgcmVhbMKtd29ybGQgcGljdHVyZXMpLg0KKiBgZmlsbF9tb2RlYCBpcyB0aGUgc3RyYXRlZ3kgdXNlZCBmb3IgZmlsbGluZyBpbiBuZXdseSBjcmVhdGVkIHBpeGVscywgd2hpY2ggY2FuIGFwcGVhciBhZnRlciBhIHJvdGF0aW9uIG9yIGEgd2lkdGgvaGVpZ2h0IHNoaWZ0Lg0KDQpgYGB7cn0NCiMgY2hvb3NpbmcgYW4gaW1hZ2UgdG8gYXVnbWVudA0KZm5hbWVzIDwtIGxpc3QuZmlsZXModGVzdF9jYXRzX2RpciwgZnVsbC5uYW1lcyA9IFRSVUUpDQppbWdfcGF0aCA8LSBmbmFtZXNbMzMyXQ0KIyBvcmlnaW5hbCBpbWFnZSBiZWZvcmUgYXVnbWVudGF0aW9uDQpyZWFkSW1hZ2UoaW1nX3BhdGgpICU+JSByZXNpemUodyA9IDE1MCwgaCA9IDE1MCkgJT4lIGRpc3BsYXkobWV0aG9kID0gJ3Jhc3RlcicpDQojIEFoISDwn5i6DQpgYGANCg0KDQpgYGB7cn0NCiMgbG9hZGluZyB0aGUgaW1hZ2UgYW5kIHJlc2l6aW5nIGl0DQppbWcgPC0gaW1hZ2VfbG9hZChpbWdfcGF0aCwgdGFyZ2V0X3NpemUgPSBjKDE1MCwgMTUwKSkNCg0KIyBjb252ZXJ0aW5nIFBJTCBmb3JtYXQgdG8gYW4gYXJyYXkgd2l0aCBzaGFwZSAoMTUwLCAxNTAsIDMpDQppbWdfYXJyYXkgPC0gaW1hZ2VfdG9fYXJyYXkoaW1nKQ0KDQojIGluY2x1ZGluZyBhIGJhdGNoIGRpbWVuc2lvbg0KaW1nX2FycmF5IDwtIGFycmF5X3Jlc2hhcGUoaW1nX2FycmF5LCBjKDEsIDE1MCwgMTUwLCAzKSkNCg0KIyBnZW5lcmF0aW5nIGJhdGNoZXMgb2YgYXVnbWVudGVkIGltYWdlcyBmcm9tIG91ciBpbWdfYXJyYXkNCmF1Z21lbnRhdGlvbl9nZW5lcmF0b3IgPC0gZmxvd19pbWFnZXNfZnJvbV9kYXRhKA0KICBpbWdfYXJyYXksICMgc2hvdWxkIGhhdmUgcmFuayA0DQogICMgZ2VuZXJhdG9yIHVzZWQgZm9yIGF1Z21lbnRhdGlvbg0KICBnZW5lcmF0b3IgPSBkYXRhZ2VuLA0KICBiYXRjaF9zaXplID0gMQ0KKQ0KDQojIERpc3BsYXlpbmcgc29tZSByYW5kb21seSBhdWdtZW50ZWQgdHJhaW5pbmcgaW1hZ2VzDQpvcCA8LSBwYXIobWZyb3cgPSBjKDIsMiksIHB0eSA9ICdzJywgbWFyID0gYygxLCAwLCAxLCAwKSkNCmZvciAoaSBpbiAxOjQpIHsNCmF1Z19pbWcgPC0gZ2VuZXJhdG9yX25leHQoYXVnbWVudGF0aW9uX2dlbmVyYXRvcikNCnBsb3QoYXMucmFzdGVyKGF1Z19pbWdbMSwgLCAsIF0pKQ0KfQ0KcGFyKG9wKQ0KDQoNCg0KYGBgDQoNClZvaWxhISBGaW5hbGx5LCB0aGVyZSBnb2VzIHNvbWUgb2YgdGhlIHJhbmRvbSB0cmFuc2Zvcm1hdGlvbnMgd2UgaGF2ZSBiZWVuIHRhbGtpbmcgYWJvdXQuIFN1Y2ggdHJhbnNmb3JtYXRpb25zIGhlbHAgdG8gZXhwb3NlIHRoZSBtb2RlbCB0byBtb3JlIGFzcGVjdHMgb2YgdGhlIGRhdGEgYW5kIGluIHR1cm4gaGVscGluZyB0aGUgbW9kZWwgdG8gZ2VuZXJhbGl6ZSBiZXR0ZXIuDQoNCg0KPGJyPg0KDQojICoqR2V0dGluZyBwYXN0IG92ZXJmaXR0aW5nIHdpdGggZGF0YSBhdWdtZW50YXRpb24qKg0KDQrij7IgSXQncyB0aW1lIHRvIGJ1aWxkIGEgbW9kZWwgdGhhdCB1c2VzIEltYWdlIEF1Z21lbnRhdGlvbiBkdXJpbmcgdHJhaW5pbmcuIA0KDQpXZSdsbCByZXVzZSB0aGUgc2FtZSBjb2RlIGFzIGJlZm9yZSwgd2l0aCB0aGUgb25seSBleGNlcHRpb24gYmVpbmcgdGhhdCB3ZSdsbCB1cGRhdGUgdGhlIGBpbWFnZV9kYXRhX2dlbmVyYXRvcmAgdG8gcGVyZm9ybSBhdWdtZW50YXRpb24uIEhlcmUgd2UgZ286DQo8YnI+DQoNCmBgYHtyfQ0KbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JQ0KICAjIGFkZGluZyB0aGUgZmlyc3QgY29udm9sdXRpb24gbGF5ZXIgd2l0aCAxNiAzYnkzIGZpbHRlcnMNCiAgIyB3ZSBhZGQgYW4gYWRkaXRpb25hbCBkaW1lbnNpb24gaW4gdGhlIGlucHV0IHNoYXBlIHNpbmNlIGNvbnZvbHV0aW9ucyBvcGVyYXRlIG92ZXIgM0QgdGVuc29ycw0KICAjIHRoZSBpbnB1dCBzaGFwZSB0ZWxscyB0aGUgbmV0d29yayB0aGF0IHRoZSBmaXJzdCBsYXllciBzaG91bGQgZXhwZWN0DQogICMgaW1hZ2VzIG9mIDE1MCBieSAxNTAgcGl4ZWxzIHdpdGggYSBjb2xvciBkZXB0aCBvZiAzIGllIFJHQiBpbWFnZXMNCiAgbGF5ZXJfY29udl8yZChpbnB1dF9zaGFwZSA9IGMoMTUwLCAxNTAsIDMpLCBmaWx0ZXJzID0gMzIsIGtlcm5lbF9zaXplID0gYygzLCAzKSwgYWN0aXZhdGlvbiA9ICdyZWx1JyApICU+JQ0KICAjIGFkZGluZyBhIG1heCBwb29saW5nIGxheWVyIHdoaWNoIGhhbHZlcyB0aGUgZGltZW5zaW9ucw0KICBsYXllcl9tYXhfcG9vbGluZ18yZChwb29sX3NpemUgPSBjKDIsIDIpKSAlPiUNCiAgIyBhZGRpbmcgYSBzZWNvbmQgY29udm9sdXRpb24gbGF5ZXIgd2l0aCA2NCBmaWx0ZXJzDQogIGxheWVyX2NvbnZfMmQoZmlsdGVycyA9IDY0LCBrZXJuZWxfc2l6ZSA9IGMoMywgMyksIGFjdGl2YXRpb24gPSAncmVsdScpICU+JQ0KICBsYXllcl9tYXhfcG9vbGluZ18yZChwb29sX3NpemUgPSBjKDIsIDIpKSAlPiUNCiAgIyBhZGRpbmcgYSBzZWNvbmQgY29udm9sdXRpb24gbGF5ZXIgd2l0aCAxMjggZmlsdGVycw0KICBsYXllcl9jb252XzJkKGZpbHRlcnMgPSAxMjgsIGtlcm5lbF9zaXplID0gYygzLCAzKSwgYWN0aXZhdGlvbiA9ICdyZWx1JykgJT4lDQogIGxheWVyX21heF9wb29saW5nXzJkKHBvb2xfc2l6ZSA9IGMoMiwgMikpICU+JQ0KICBsYXllcl9mbGF0dGVuKCkgJT4lDQogIGxheWVyX2RlbnNlKHVuaXRzID0gNTEyLCBhY3RpdmF0aW9uID0gJ3JlbHUnKSAlPiUNCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxLGFjdGl2YXRpb24gPSAnc2lnbW9pZCcpDQoNCiMgQ29tcGlsZTogQ29uZmlndXJpbmcgYSBLZXJhcyBtb2RlbCBmb3IgdHJhaW5pbmcNCm1vZGVsICU+JQ0KICBjb21waWxlKA0KICAgIGxvc3MgPSAnYmluYXJ5X2Nyb3NzZW50cm9weScsDQogICAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAwLjAwMDEpLA0KICAgIG1ldHJpY3MgPSAnYWNjdXJhY3knDQogICkNCg0KDQojIFRoaXMgY29kZSBoYXMgY2hhbmdlZC4gTm93IGluc3RlYWQgb2YgdGhlIEltYWdlR2VuZXJhdG9yIGp1c3QgcmVzY2FsaW5nDQojIHRoZSBpbWFnZSwgd2UgYWxzbyByb3RhdGUgYW5kIGRvIG90aGVyIG9wZXJhdGlvbnMNCiMgVXBkYXRlZCB0byBkbyBpbWFnZSBhdWdtZW50YXRpb24NCg0KdHJhaW5fZGF0YWdlbiA8LSBpbWFnZV9kYXRhX2dlbmVyYXRvcigNCiAgcmVzY2FsZSA9IDEvMjU1LA0KICByb3RhdGlvbl9yYW5nZSA9IDQwLA0KICB3aWR0aF9zaGlmdF9yYW5nZSA9IDAuMiwNCiAgaGVpZ2h0X3NoaWZ0X3JhbmdlID0gMC4yLA0KICBzaGVhcl9yYW5nZSA9IDAuMiwNCiAgem9vbV9yYW5nZSA9IDAuMiwNCiAgaG9yaXpvbnRhbF9mbGlwID0gVFJVRSwNCiAgZmlsbF9tb2RlID0gIm5lYXJlc3QiDQopDQoNCiMgRmxvdyB0cmFpbmluZyBpbWFnZXMgaW4gYmF0Y2hlcyBvZiAyMCB1c2luZyB0cmFpbl9kYXRhZ2VuIGdlbmVyYXRvcg0KdHJhaW5fZ2VuZXJhdG9yIDwtIGZsb3dfaW1hZ2VzX2Zyb21fZGlyZWN0b3J5KA0KICAjIHRhcmdldCBkaXJlY3RvcnkNCiAgZGlyZWN0b3J5ID0gdHJhaW5fZGlyLA0KICAjIHRyYWluaW5nIGRhdGEgZ2VuZXJhdG9yDQogIGdlbmVyYXRvciA9IHRyYWluX2RhdGFnZW4sDQogICMgcmVzaXppbmcgdGhlIGltYWdlcyB0byB0aGUgc2FtZSBkaW1lbnNpb25zIGV4cGVjdGVkIGJ5IG91ciBOTg0KICB0YXJnZXRfc2l6ZSA9IGMoMTUwLCAxNTApLA0KICAjIDIwIGltYWdlcyBhdCBhIHRpbWUgdG8gYmUgZmVkIGludG8gdGhlIE5ODQogIGJhdGNoX3NpemUgPSAyMCwNCiAgIyBTaW5jZSB3ZSB1c2UgYmluYXJ5X2Nyb3NzZW50cm9weSBsb3NzLCB3ZSBuZWVkIGJpbmFyeSBsYWJlbCBhcnJheXMNCiAgY2xhc3NfbW9kZSA9ICdiaW5hcnknDQopDQoNCiMgdHJhaW5pbmcgdGhlIG1vZGVsDQojIHRyYWluaW5nIHdpbGwgdGFrZSBsb25nZXIgZHVlIHRvIHRoZSBhdWdtZW50YXRpb24gcHJvY2Vzcw0KaGlzdG9yeSA8LSBtb2RlbCAlPiUgZml0X2dlbmVyYXRvcigNCiAgZ2VuZXJhdG9yID0gdHJhaW5fZ2VuZXJhdG9yLA0KICAjIFRvdGFsIG51bWJlciBvZiBzdGVwcyAoYmF0Y2hlcyBvZiBzYW1wbGVzKSB0byB5aWVsZA0KICAjYmVmb3JlIGRlY2xhcmluZyBvbmUgZXBvY2ggZmluaXNoZWQgYW5kIHN0YXJ0aW5nIHRoZSBuZXh0IGVwb2NoLg0KICBzdGVwc19wZXJfZXBvY2ggPSAxMDAsICMgMjAwMC8yMA0KICAjIEFuIGVwb2NoIGlzIGFuIGl0ZXJhdGlvbiBvdmVyIHRoZSBlbnRpcmUgZGF0YSBwcm92aWRlZA0KICBlcG9jaHMgPSAxMDAsDQogIHZhbGlkYXRpb25fZGF0YSA9IHZhbGlkYXRpb25fZ2VuZXJhdG9yLA0KICB2YWxpZGF0aW9uX3N0ZXBzID0gNTAgIyAxMDAwLzIwDQogIA0KICANCikNCg0KIyBJdOKAmXMgZ29vZCBwcmFjdGljZSB0byBhbHdheXMgc2F2ZSB5b3VyIG1vZGVscyBhZnRlciB0cmFpbmluZy4NCg0KbW9kZWwgJT4lIHNhdmVfbW9kZWxfaGRmNSgiY2F0c19hbmRfZG9nc19maWx0ZXJlZF9hdWdtZW50ZWQuaDUiKSANCg0KIyBwbG90dGluZyB0aGUgbG9zcyBhbmQgYWNjdXJhY3kgb3ZlciB0aGUgdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gZGF0YQ0KIyBkdXJpbmcgdHJhaW5pbmcNCnBsb3QoaGlzdG9yeSkNCg0KIyBBIHN1bW1hcnkgb2YgaG93IHRoZSBtb2RlbCBwZXJmb3JtZWQNCmhpc3RvcnkNCg0KYGBgDQpUaGFua3MgdG8gaW1hZ2UgYXVnbWVudGF0aW9uLCB3ZSBhcmUgbm8gbG9uZ2VyIG92ZXJmaXR0aW5nLiBUaGUgdHJhaW5pbmcNCmN1cnZlcyBhcmUgY2xvc2VseSB0cmFja2luZyB0aGUgdmFsaWRhdGlvbiBjdXJ2ZXMgYXMgc2hvd24gaW4gdGhlIHBsb3RzLg0KQWx0aG91Z2ggdGhlIHRyYWluaW5nIGFjY3VyYWN5IGlzIG5vdCByZWFjaGluZyA5OSUrLCB0aGUgdHJhaW5pbmcgYWNjdXJhY3kgaXMgcmVhbGx5IGNsb3NlIHRvIHRoZSB2YWxpZGF0aW9uIGFjY3VyYWN5LiBUaGlzIGlzIGFuIGluZGljYXRpb24gdGhhdCB0aGUgbW9kZWwgaXMgZG9pbmcgYSBnb29kIGpvYiBpbiBwcmVkaWN0aW5nIGltYWdlcyBpdCBoYXMgbmV2ZXIgJ3NlZW4nIGJlZm9yZS4NCg0KVGhlIHVzZSBvZiBpbWFnZSBhdWdtZW50YXRpb24gaGVscGVkIHRvIGluY3JlYXNlIHRoZSBkaXZlcnNpdHkgb2YgdGhlIHRyYWluaW5nIHNldCBhbmQgYWxzbyByZWR1Y2UgdGhlIG1vZGVs4oCZcyBkZXBlbmRlbmNlIG9uIGNlcnRhaW4gcHJvcGVydGllcy4gQXMgc3VjaCwgdGhlIG1vZGVsIHdhcyBleHBvc2VkIHRvIG1vcmUgYXR0cmlidXRlcywgbWFraW5nIGl0IGJldHRlciBhdCBnZW5lcmFsaXppbmcgbmV3IGRhdGEuDQoNCg0KPGJyPg0KDQojICoqT25lIGxhc3QgZGV0b3VyOioqIEFkZGluZyBkcm9wb3V0IA0KDQpWZXJ5IHNpbXBseSwgbGV0J3MgbG9vayBhdCBhbm90aGVyIGNvbW1vbmx5IHRyaWNrIHVzZWQgaW4gbWluaW1pemluZyBvdmVyZml0dGluZzogYGRyb3BvdXRgLiBEcm9wb3V0IHdvcmtzIGJ5IHJhbmRvbWx5IHJlbW92aW5nIG5vZGVzIGFsb25nIHdpdGggYWxsIHRoZWlyIGluY29taW5nIGFuZCBvdXRnb2luZyBjb25uZWN0aW9ucyBmcm9tIGEgbmV1cmFsIG5ldHdvcmsgZHVyaW5nIHRyYWluaW5nLiBJdCBkb2VzIHRoaXMgYnkgc2V0dGluZyB0byB6ZXJvIGEgbnVtYmVyIG9mIG91dHB1dCBmZWF0dXJlcyBvZiB0aGUgbGF5ZXIgZHVyaW5nIHRyYWluaW5nLiBGb3IgaW5zdGFuY2UgaWYgdGhlIG91dHB1dCBvZiBhIGxheWVyIGlzIHRoZSB2ZWN0b3IgYFswLjQsIDAuOSwgMS41LCAxLjgsIDAuMl1gLCBhZnRlciBhcHBseWluZyBkcm9wb3V0LCB0aGlzIHZlY3RvciB3aWxsIGhhdmUgYSBmZXcgemVybyBlbnRyaWVzIGRpc3RyaWJ1dGVkIGF0IHJhbmRvbTogYFswLjQsIDAsIDEuNSwgMS44LCAwXWAuIFRoZSBjb3JlIGlkZWEgaXMgdGhhdCBpbnRyb2R1Y2luZyBub2lzZSBpbiB0aGUgb3V0cHV0IHZhbHVlcyBvZiBhIGxheWVyIHdpbGwgbWluaW1pc2UgdGhlIGRlcGVuZGVuY2Ugb24gY2VydGFpbiBuZXVyb25zIHRvIGRldGVjdCBjZXJ0YWluIGZlYXR1cmVzIGhlbmNlIG1vZHVsYXRpbmcgdGhlIHF1YW50aXR5IG9mIGluZm9ybWF0aW9uIHRoYXQgeW91ciBtb2RlbCBpcyBhbGxvd2VkIHRvIHN0b3JlLiBNdWNoIG1vcmUgZWxhYm9yYXRlIGluZm9ybWF0aW9uIGFib3V0IGRyb3BvdXQgYXMgYSB3YXkgb2YgbWluaW1pc2luZyBvdmVyZml0dGluZyBjYW4gYmUgZm91bmQgaW4gdGhpcyB3ZWxsIHdyaXR0ZW4gYW5kIHJlc2VhcmNoZWQgcGFwZXI6IFtEcm9wb3V0OiBBIFNpbXBsZSBXYXkgdG8gUHJldmVudCBOZXVyYWwgTmV0d29ya3MgZnJvbSBPdmVy76yBdHRpbmddKGh0dHBzOi8vZGwuYWNtLm9yZy9kb2kvZXBkZi8xMC41NTU1LzI2Mjc0MzUuMjY3MDMxMykNCg0KDQoNCmBgYHtyLCBlY2hvPUZBTFNFLCBmaWcuY2FwPSAiRHJvcG91dDogQSBTaW1wbGUgV2F5IHRvIFByZXZlbnQgTmV1cmFsIE5ldHdvcmtzIGZyb20gT3ZlcmZpdHRpbmcifQ0KDQoNCmltZ19maWxlcyA8LSBsaXN0LmZpbGVzKHBhdGggPSAiQzovVXNlcnMva2VyYXMvT25lRHJpdmUgLSBNaWNyb3NvZnQgU3R1ZGVudCBQYXJ0bmVycy9FcF83L3Jlc291cmNlcyIsIGZ1bGwubmFtZXMgPSBUUlVFICkNCnJlYWRJbWFnZShpbWdfZmlsZXNbM10pICU+JSBkaXNwbGF5KG1ldGhvZCA9ICdyYXN0ZXInKQ0KYGBgDQoNCg0KDQoNCg0KPGJyPg0KDQojIyMgKipHZXR0aW5nIHBhc3Qgb3ZlcmZpdHRpbmcgd2l0aCBkYXRhIGF1Z21lbnRhdGlvbiBhbmQgZHJvcCBvdXQuKioNCg0KVG8gZnVydGhlciBmaWdodCBvdmVyZml0dGluZywgd2XigJlsbCB0cnkgYWRkaW5nIGEgZHJvcG91dCBsYXllciB0byBhIG1vZGVsLCByaWdodCBiZWZvcmUgdGhlIGRlbnNlbHkgY29ubmVjdGVkIGNsYXNzaWZpZXIgYW5kIHNlZSBob3cgb3VyIG1vZGVsIHBlcmZvcm1zLiBXZSdsbCBkbyB0aGlzIGJ5IHNwZWNpZnlpbmcgYSBgZHJvcG91dCByYXRlYCAoZnJhY3Rpb24gb2YgdGhlIGZlYXR1cmVzIHRoYXQgYXJlIHplcm9lZCBvdXQpIGluIGBsYXllcl9kcm9wb3V0IHtrZXJhc31gLg0KDQpgYGB7cn0NCm1vZGVsIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUNCiAgIyBhZGRpbmcgdGhlIGZpcnN0IGNvbnZvbHV0aW9uIGxheWVyIHdpdGggMTYgM2J5MyBmaWx0ZXJzDQogICMgd2UgYWRkIGFuIGFkZGl0aW9uYWwgZGltZW5zaW9uIGluIHRoZSBpbnB1dCBzaGFwZSBzaW5jZSBjb252b2x1dGlvbnMgb3BlcmF0ZSBvdmVyIDNEIHRlbnNvcnMNCiAgIyB0aGUgaW5wdXQgc2hhcGUgdGVsbHMgdGhlIG5ldHdvcmsgdGhhdCB0aGUgZmlyc3QgbGF5ZXIgc2hvdWxkIGV4cGVjdA0KICAjIGltYWdlcyBvZiAxNTAgYnkgMTUwIHBpeGVscyB3aXRoIGEgY29sb3IgZGVwdGggb2YgMyBpZSBSR0IgaW1hZ2VzDQogIGxheWVyX2NvbnZfMmQoaW5wdXRfc2hhcGUgPSBjKDE1MCwgMTUwLCAzKSwgZmlsdGVycyA9IDMyLCBrZXJuZWxfc2l6ZSA9IGMoMywgMyksIGFjdGl2YXRpb24gPSAncmVsdScgKSAlPiUNCiAgIyBhZGRpbmcgYSBtYXggcG9vbGluZyBsYXllciB3aGljaCBoYWx2ZXMgdGhlIGRpbWVuc2lvbnMNCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLCAyKSkgJT4lDQogICMgYWRkaW5nIGEgc2Vjb25kIGNvbnZvbHV0aW9uIGxheWVyIHdpdGggNjQgZmlsdGVycw0KICBsYXllcl9jb252XzJkKGZpbHRlcnMgPSA2NCwga2VybmVsX3NpemUgPSBjKDMsIDMpLCBhY3RpdmF0aW9uID0gJ3JlbHUnKSAlPiUNCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLCAyKSkgJT4lDQogICMgYWRkaW5nIGEgc2Vjb25kIGNvbnZvbHV0aW9uIGxheWVyIHdpdGggMTI4IGZpbHRlcnMNCiAgbGF5ZXJfY29udl8yZChmaWx0ZXJzID0gMTI4LCBrZXJuZWxfc2l6ZSA9IGMoMywgMyksIGFjdGl2YXRpb24gPSAncmVsdScpICU+JQ0KICBsYXllcl9tYXhfcG9vbGluZ18yZChwb29sX3NpemUgPSBjKDIsIDIpKSAlPiUNCiAgIyBhZGRpbmcgZHJvcG91dA0KICBsYXllcl9kcm9wb3V0KHJhdGUgPSAwLjUpICU+JSANCiAgbGF5ZXJfZmxhdHRlbigpICU+JQ0KICBsYXllcl9kZW5zZSh1bml0cyA9IDUxMiwgYWN0aXZhdGlvbiA9ICdyZWx1JykgJT4lDQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSxhY3RpdmF0aW9uID0gJ3NpZ21vaWQnKQ0KDQojIENvbXBpbGU6IENvbmZpZ3VyaW5nIGEgS2VyYXMgbW9kZWwgZm9yIHRyYWluaW5nDQptb2RlbCAlPiUNCiAgY29tcGlsZSgNCiAgICBsb3NzID0gJ2JpbmFyeV9jcm9zc2VudHJvcHknLA0KICAgIG9wdGltaXplciA9IG9wdGltaXplcl9ybXNwcm9wKGxyID0gMC4wMDAxKSwNCiAgICBtZXRyaWNzID0gJ2FjY3VyYWN5Jw0KICApDQoNCg0KIyBUaGlzIGNvZGUgaGFzIGNoYW5nZWQuIE5vdyBpbnN0ZWFkIG9mIHRoZSBJbWFnZUdlbmVyYXRvciBqdXN0IHJlc2NhbGluZw0KIyB0aGUgaW1hZ2UsIHdlIGFsc28gcm90YXRlIGFuZCBkbyBvdGhlciBvcGVyYXRpb25zDQojIFVwZGF0ZWQgdG8gZG8gaW1hZ2UgYXVnbWVudGF0aW9uDQoNCnRyYWluX2RhdGFnZW4gPC0gaW1hZ2VfZGF0YV9nZW5lcmF0b3IoDQogIHJlc2NhbGUgPSAxLzI1NSwNCiAgcm90YXRpb25fcmFuZ2UgPSA0MCwNCiAgd2lkdGhfc2hpZnRfcmFuZ2UgPSAwLjIsDQogIGhlaWdodF9zaGlmdF9yYW5nZSA9IDAuMiwNCiAgc2hlYXJfcmFuZ2UgPSAwLjIsDQogIHpvb21fcmFuZ2UgPSAwLjIsDQogIGhvcml6b250YWxfZmxpcCA9IFRSVUUsDQogIGZpbGxfbW9kZSA9ICJuZWFyZXN0Ig0KKQ0KDQojIEZsb3cgdHJhaW5pbmcgaW1hZ2VzIGluIGJhdGNoZXMgb2YgMjAgdXNpbmcgdHJhaW5fZGF0YWdlbiBnZW5lcmF0b3INCnRyYWluX2dlbmVyYXRvciA8LSBmbG93X2ltYWdlc19mcm9tX2RpcmVjdG9yeSgNCiAgIyB0YXJnZXQgZGlyZWN0b3J5DQogIGRpcmVjdG9yeSA9IHRyYWluX2RpciwNCiAgIyB0cmFpbmluZyBkYXRhIGdlbmVyYXRvcg0KICBnZW5lcmF0b3IgPSB0cmFpbl9kYXRhZ2VuLA0KICAjIHJlc2l6aW5nIHRoZSBpbWFnZXMgdG8gdGhlIHNhbWUgZGltZW5zaW9ucyBleHBlY3RlZCBieSBvdXIgTk4NCiAgdGFyZ2V0X3NpemUgPSBjKDE1MCwgMTUwKSwNCiAgIyAyMCBpbWFnZXMgYXQgYSB0aW1lIHRvIGJlIGZlZCBpbnRvIHRoZSBOTg0KICBiYXRjaF9zaXplID0gMjAsDQogICMgU2luY2Ugd2UgdXNlIGJpbmFyeV9jcm9zc2VudHJvcHkgbG9zcywgd2UgbmVlZCBiaW5hcnkgbGFiZWwgYXJyYXlzDQogIGNsYXNzX21vZGUgPSAnYmluYXJ5Jw0KKQ0KDQojIHRyYWluaW5nIHRoZSBtb2RlbA0KIyB0cmFpbmluZyB3aWxsIHRha2UgbG9uZ2VyIGR1ZSB0byB0aGUgYXVnbWVudGF0aW9uIHByb2Nlc3MNCmhpc3RvcnkgPC0gbW9kZWwgJT4lIGZpdF9nZW5lcmF0b3IoDQogIGdlbmVyYXRvciA9IHRyYWluX2dlbmVyYXRvciwNCiAgIyBUb3RhbCBudW1iZXIgb2Ygc3RlcHMgKGJhdGNoZXMgb2Ygc2FtcGxlcykgdG8geWllbGQNCiAgI2JlZm9yZSBkZWNsYXJpbmcgb25lIGVwb2NoIGZpbmlzaGVkIGFuZCBzdGFydGluZyB0aGUgbmV4dCBlcG9jaC4NCiAgc3RlcHNfcGVyX2Vwb2NoID0gMTAwLCAjIDIwMDAvMjANCiAgIyBBbiBlcG9jaCBpcyBhbiBpdGVyYXRpb24gb3ZlciB0aGUgZW50aXJlIGRhdGEgcHJvdmlkZWQNCiAgZXBvY2hzID0gMTAwLA0KICB2YWxpZGF0aW9uX2RhdGEgPSB2YWxpZGF0aW9uX2dlbmVyYXRvciwNCiAgdmFsaWRhdGlvbl9zdGVwcyA9IDUwICMgMTAwMC8yMA0KICANCiAgDQopDQoNCiMgSXTigJlzIGdvb2QgcHJhY3RpY2UgdG8gYWx3YXlzIHNhdmUgeW91ciBtb2RlbHMgYWZ0ZXIgdHJhaW5pbmcuDQoNCm1vZGVsICU+JSBzYXZlX21vZGVsX2hkZjUoImNhdHNfYW5kX2RvZ3NfZmlsdGVyZWRfYXVnbWVudGVkX2Ryb3AuaDUiKSANCg0KIyBwbG90dGluZyB0aGUgbG9zcyBhbmQgYWNjdXJhY3kgb3ZlciB0aGUgdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gZGF0YQ0KIyBkdXJpbmcgdHJhaW5pbmcNCnBsb3QoaGlzdG9yeSkNCg0KIyBBIHN1bW1hcnkgb2YgaG93IHRoZSBtb2RlbCBwZXJmb3JtZWQNCmhpc3RvcnkNCg0KYGBgDQpXZWxsLCB0aGF0J3MgdGhlIHBlcmZvcm1hbmNlIG9mIG91ciBtb2RlbCB3aXRoIGJvdGggYXVnbWVudGF0aW9uIGFuZCBkcm9wb3V0IGltcGxlbWVudGVkLiBBcyBiZWZvcmUsIHdlIGhhdmUgY29tYmF0ZWQgb3ZlcmZpdHRpbmcsIGFuZCB0aGF0J3MgYSBzdGVwIGluIHRoZSByaWdodCBkaXJlY3Rpb24uDQoNCkJ5IHVzaW5nIHJlZ3VsYXJpemF0aW9uIHRlY2huaXF1ZXMgZXZlbiBmdXJ0aGVyLCBhbmQgYnkgdHVuaW5nIHRoZSBuZXR3b3Jr4oCZcyBwYXJhbWV0ZXJzIChzdWNoIGFzIHRoZSBudW1iZXIgb2YgZmlsdGVycyBwZXIgY29udm9sdXRpb24gbGF5ZXIsIG9yIHRoZSBudW1iZXIgb2YgbGF5ZXJzIGluIHRoZSBuZXR3b3JrKSwgeW91IG1heSBiZSBhYmxlIHRvIGdldCBhbiBldmVuIGJldHRlciBhY2N1cmFjeS4NCg0KV2UnbGwgd3JhcCBpdCBoZXJlLiBBdWdtZW50YXRpb24gd2FzIHF1aXRlIGFuIGludGVyZXN0aW5nIHRvcGljIHRvIGxlYXJuLCBzaGFyZSBhbmQgUiB0b28g8J+Yii4gQSBiaWcgdGhhbmsgeW91IHRvIExhdXJlbmNlIE1vcm9uZXkgZm9yIHRoaXMgYW1hemluZyBzZXJpZXMsIHlvdSBhcmUgdGhlIGJlc3QhDQoNClRpbWUgdG8gc3RyYXAgaW4gZm9yIE5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZy4NCg0KVGlsbCB0aGVuLCANCg0KSGFwcHkgTGVhcm5pbmcg8J+RqfCfj73igI3wn5K7IPCfkajigI3wn5K7IPCfkajwn4++4oCN8J+SuyDwn5Gp4oCN8J+SuyAsDQoNCkVyaWMgKFJfaWMpLCBNaWNyb3NvZnQgTGVhcm4gU3R1ZGVudCBBbWJhc3NhZG9yLg0KDQoNCiMgKipSZWZlcmVuY2UgTWF0ZXJpYWwqKg0KDQoqIE1hY2hpbmUgTGVhcm5pbmcgRm91bmRhdGlvbnM6IEVwICM3IC0gW0ltYWdlIGF1Z21lbnRhdGlvbiBhbmQgb3ZlcmZpdHRpbmddKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9UVdkWVd3VzZPQUUpDQoNCiogRGVlcCBMZWFybmluZyB3aXRoIFIgYnkgRnJhbmNvaXMgQ2hvbGxldCBhbmQgSi5KLkFsbGFpcmUNCg0KKiBUaGUgW1IgaW50ZXJmYWNlIHRvIEtlcmFzXShodHRwczovL3RlbnNvcmZsb3cucnN0dWRpby5jb20vbGVhcm4vcmVzb3VyY2VzLykgd2Vic2l0ZS4NCg0KKiBUaGUgW0tlcmFzIEFQSSBSZWZlcmVuY2VdKGh0dHBzOi8va2VyYXMuaW8vYXBpLykgd2Vic2l0ZQ0KDQoqIExhYiA3OiBbTGVzc29uIDIgLSBOb3RlYm9vayAoQ2F0cyB2IERvZ3MgQXVnbWVudGF0aW9uKS5pcHluYl0oaHR0cHM6Ly9jb2xhYi5yZXNlYXJjaC5nb29nbGUuY29tL2dpdGh1Yi9sbW9yb25leS9kbGFpY291cnNlL2Jsb2IvbWFzdGVyL0NvdXJzZSUyMDIlMjAtJTIwUGFydCUyMDQlMjAtJTIwTGVzc29uJTIwMiUyMC0lMjBOb3RlYm9vayUyMChDYXRzJTIwdiUyMERvZ3MlMjBBdWdtZW50YXRpb24pLmlweW5iI3Njcm9sbFRvPUJaU2xwM0RBamRZZikgDQoNCiogW0Ryb3BvdXQ6IEEgU2ltcGxlIFdheSB0byBQcmV2ZW50IE5ldXJhbCBOZXR3b3JrcyBmcm9tIE92ZXLvrIF0dGluZ10oaHR0cHM6Ly9kbC5hY20ub3JnL2RvaS9lcGRmLzEwLjU1NTUvMjYyNzQzNS4yNjcwMzEzKSBieSBOaXRpc2ggU3JpdmFzdGF2YSwgR2Vv76yAcmV5IEhpbnRvbiwgQWxleCBLcml6aGV2c2t5LCBJbHlhIFN1dHNrZXZlciwgUnVzbGFuIFNhbGFraHV0ZGlub3YuDQoNCg0K