Improving Computer Vision Accuracy using Convolutions: An R Version (co-authors: Ian, Sam, Eric)

A high level overview of Convolution and Pooling

A convolution is a filter that passes over an image, processing it, and extracting features that show a commonolatity in the image such that if an image has certain features, it belongs to a particular class. At its heart, convolution is really simple. It involves scanning every pixel in the image, looking at it’s neighboring pixels, multiplying these pixels by their corresponding weight in a filter and then summing this all up to obtain a new pixel value. This can be shown as below:

## [1] Image source: https://colab.research.google.com/github/lmoroney/mlday-tokyo/blob/master/Lab3-What-Are-Convolutions.ipynb#scrollTo=xF0FPplsgHNh

The previous Dense Neural Network that we created simply learned from the raw pixels what made up a sweater or what made up a boot. This in itself is quite a limitation.

Ultimately the goal of trying to understand what an item is, isn’t just matching the raw pixels to labels like we did in the previous exercises.

What if we could extract features from the image instead and when an image has some specific features, it belongs to a particular class. This is the heart of Convolution Neural Networks do.

This key characteristic gives convnets two interesting properties:

  • The patterns they learn are translation-invariant: This means that after learning a certain pattern, a covnet can recognize it anywhere else in the image as opposed to a DNN which would have to learn the pattern a new if it appeared at a new location. For this reason, covnets require few training samples.

  • They 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. For this reason, covnets can learn increasingly complex and abstract features.

If you’ve ever done image processing using a filter (like this: https://en.wikipedia.org/wiki/Kernel_(image_processing)) then convolutions will look very familiar.

In short, you take an array (usually 3x3 or 5x5) and pass it over the image. By changing the underlying pixels based on the formula within that matrix, you can do things like edge detection. So, for example, if you look at the above link, you’ll see a 3x3 that is defined for edge detection where the middle cell is 8, and all of its neighbors are -1. In this case, for each pixel, you would multiply its value by 8, then subtract the value of each neighbor. Do this for every pixel, and you’ll end up with a new image that has the edges enhanced.

That’s the concept of Convolutional Neural Networks. Add some layers to do convolution before you have the dense layers, and then the information going to the dense layers is more focussed, and possibly more accurate.

Pooling reduces the amount of irrelevant information in an image while maintaining the features that are detected. It does so by looking at a pixel and its immediate neighbours to the right, beneath and right-beneath, takes the largest hence the name Max pooling, and loads it into a new image. It thus reduces the amount of information that a model has to process while still maintaining the prominent features.

## [1] Image source: https://colab.research.google.com/github/lmoroney/mlday-tokyo/blob/master/Lab3-What-Are-Convolutions.ipynb#scrollTo=xF0FPplsgHNh

Building Convolution Neural Networks.

Gathering the Data

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

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

suppressMessages(install.packages("tidyverse"))
suppressMessages(install.packages("keras"))
suppressMessages(install_keras())

Ps: it could take a while

Once installed, let’s get rolling:

## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union

Reshaping our image arrays and normalizing them

## [1] 60000    28    28     1

Why are we adding one more dimension? That’s an important question. Convolutions operate over 3D tensors, called feature maps, with two spatial axes (height width) as well as a depth axis. For an RGB depth channels image, the dimension of the depth axis is 3, because the image has 3 color channels: red, green, and blue. For a black-and-white picture, like the Fashion MNIST dataset, the depth is 1.

Instantiating a Convolution

Next is to define your model. Now instead of the input layer at the top, we’re going to add a Convolution. The parameters are:

  1. The number of convolutions you want to generate. Purely arbitrary, but good to start with something in the order of 32

  2. The size of the Convolution, in this case a 3x3 grid

  3. The activation function to use – in this case we’ll use relu, which you might recall is the equivalent of returning x when x>0, else returning 0

We’ll follow the Convolution with a MaxPooling layer which is then designed to compress the image, while maintaining the content of the features that were highlighted by the convlution. By specifying (2,2) for the MaxPooling, the effect is to quarter the size of the image. Without going into too much detail here, the idea is that it creates a 2x2 array of pixels, and picks the biggest one, thus turning 4 pixels into 1. It repeats this across the image, and in so doing halves the number of horizontal, and halves the number of vertical pixels, effectively reducing the image by 25%. These concepts are clearly explained in Episode 3.

Adding a classifier to the covnet

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, whereas 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.

Compile: Configuring a Keras model for training

## Model: "sequential"
## ___________________________________________________________________________
## Layer (type)                     Output Shape                  Param #     
## ===========================================================================
## conv2d (Conv2D)                  (None, 26, 26, 6)             60          
## ___________________________________________________________________________
## max_pooling2d (MaxPooling2D)     (None, 13, 13, 6)             0           
## ___________________________________________________________________________
## conv2d_1 (Conv2D)                (None, 11, 11, 16)            880         
## ___________________________________________________________________________
## max_pooling2d_1 (MaxPooling2D)   (None, 5, 5, 16)              0           
## ___________________________________________________________________________
## flatten (Flatten)                (None, 400)                   0           
## ___________________________________________________________________________
## dense (Dense)                    (None, 128)                   51328       
## ___________________________________________________________________________
## dense_1 (Dense)                  (None, 10)                    1290        
## ===========================================================================
## Total params: 53,558
## Trainable params: 53,558
## Non-trainable params: 0
## ___________________________________________________________________________

From the summary, we expect output of the first convolution to be a 28x28 but we obtain a 26x26. If you have watched the episode, you probably know why. A 3 by 3 filter requiring a neighbour on all sides can’t work on the pixels around the edges of the picture. You effectively have to remove one pixel from the top,bottom left and rignt and this reduces your dimension by 2 on each axis. So a 28 by 28 becomes a 26 by 26. Also, the pooling layer clearly halves the dimensions of each axis.

Displaying the first 25 images from the testing set:

Visualizing every filter output in each convolution and pooling layer

For this step, I took a little detour from what was done in the Python Notebook but it illustrates the same concept: how the convolutions apply different filters to extract features from our input images.

##  [1]  1 18 27 35 37 42 61 65 71 76

let’s extract the first 4 convolution and pooling layers

## [1]  1 26 26  6

Next we define a function that will help us visualise the result of each filter in each of the layer activations above.

Visualizing the convolutions and pooling on our first test image

The code below saves the images in your current directory.

The output of each filter in each layer is as shown row-wise.

Transformation by second pooling layer

Great! Now there are a few things to note:

  • The first layer acts as a collection of various filters. At that stage, the output of the layer seems to retain almost all of the information present in the initial picture.

  • As you go higher, the outputs of the layers become increasingly abstract and less visually interpretable. They begin to encode higher-level concepts such as the “heel” and “vamp” of the shoe. Higher presentations carry increasingly less information about the visual contents of the image, and increasingly more information related to the class of the image.

Yes! We dit it!! 🤩 We successfully implemented convolutions and pooling to improve the accuracy of computer vision using R. Indeed R, at its core is a beautiful and elegant language, well designed for Data Science 💖.

For the guys at the back:

Reference Material

LS0tDQojdGl0bGU6ICJJbXByb3ZpbmcgQ29tcHV0ZXIgVmlzaW9uIEFjY3VyYWN5IHVzaW5nIENvbnZvbHV0aW9ucyINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0aGVtZTogY2VydWxlYW4NCiAgICBoaWdobGlnaHQ6IGJyZWV6ZWRhcmsNCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQotLS0NCg0KDQojICoqSW1wcm92aW5nIENvbXB1dGVyIFZpc2lvbiBBY2N1cmFjeSB1c2luZyBDb252b2x1dGlvbnM6KiogQW4gUiBWZXJzaW9uIChjby1hdXRob3JzOiBJYW4sIFNhbSwgRXJpYykNCg0KDQojICoqQSBoaWdoIGxldmVsIG92ZXJ2aWV3IG9mIENvbnZvbHV0aW9uIGFuZCBQb29saW5nKioNCg0KQSBgY29udm9sdXRpb25gIGlzIGEgZmlsdGVyIHRoYXQgcGFzc2VzIG92ZXIgYW4gaW1hZ2UsIHByb2Nlc3NpbmcgaXQsIGFuZCBleHRyYWN0aW5nIGZlYXR1cmVzIHRoYXQgc2hvdyBhIGNvbW1vbm9sYXRpdHkgaW4gdGhlIGltYWdlIHN1Y2ggdGhhdCBpZiBhbiBpbWFnZSBoYXMgY2VydGFpbiBmZWF0dXJlcywgaXQgYmVsb25ncyB0byBhIHBhcnRpY3VsYXIgY2xhc3MuIEF0IGl0cyBoZWFydCwgY29udm9sdXRpb24gaXMgcmVhbGx5IHNpbXBsZS4gSXQgaW52b2x2ZXMgc2Nhbm5pbmcgZXZlcnkgcGl4ZWwgaW4gdGhlIGltYWdlLCBsb29raW5nIGF0IGl0J3MgbmVpZ2hib3JpbmcgcGl4ZWxzLCBtdWx0aXBseWluZyB0aGVzZSBwaXhlbHMgYnkgdGhlaXIgY29ycmVzcG9uZGluZyB3ZWlnaHQgaW4gYSBmaWx0ZXIgYW5kIHRoZW4gc3VtbWluZyB0aGlzIGFsbCB1cCB0byBvYnRhaW4gYSBuZXcgcGl4ZWwgdmFsdWUuIFRoaXMgY2FuIGJlIHNob3duIGFzIGJlbG93Og0KDQpgYGB7cixlY2hvPUZBTFNFfQ0KbGlicmFyeShrbml0cikNCmluY2x1ZGVfZ3JhcGhpY3MoIkM6L1VzZXJzL0FETUlOL0Rlc2t0b3AvSW50b2R1Y3Rpb24gdG8gUHl0aG9uIGZvciBkYXRhIHNjaWVuY2UvUiBmb3IgZGF0YSBzY2llbmNlL2FSZHVpbm8vcjRnL2NvbnZvbHV0aW9uLkpQRyIpDQpwcmludCgiSW1hZ2Ugc291cmNlOiBodHRwczovL2NvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20vZ2l0aHViL2xtb3JvbmV5L21sZGF5LXRva3lvL2Jsb2IvbWFzdGVyL0xhYjMtV2hhdC1BcmUtQ29udm9sdXRpb25zLmlweW5iI3Njcm9sbFRvPXhGMEZQcGxzZ0hOaCIsIHF1b3RlID0gRikNCg0KYGBgDQoNCg0KDQpUaGUgcHJldmlvdXMgYERlbnNlIE5ldXJhbCBOZXR3b3JrYCB0aGF0IHdlIGNyZWF0ZWQgc2ltcGx5IGxlYXJuZWQgZnJvbSB0aGUgcmF3IHBpeGVscyB3aGF0IG1hZGUgdXAgYSBzd2VhdGVyIG9yIHdoYXQgbWFkZSB1cCBhIGJvb3QuIFRoaXMgaW4gaXRzZWxmIGlzIHF1aXRlIGEgbGltaXRhdGlvbi4gDQoNClVsdGltYXRlbHkgdGhlIGdvYWwgb2YgdHJ5aW5nIHRvIHVuZGVyc3RhbmQgd2hhdCBhbiBpdGVtIGlzLCBpc24ndCBqdXN0IG1hdGNoaW5nIHRoZSByYXcgcGl4ZWxzIHRvIGxhYmVscyBsaWtlIHdlIGRpZCBpbiB0aGUgcHJldmlvdXMgZXhlcmNpc2VzLg0KDQpXaGF0IGlmIHdlIGNvdWxkIGV4dHJhY3QgZmVhdHVyZXMgZnJvbSB0aGUgaW1hZ2UgaW5zdGVhZCBhbmQgd2hlbiBhbiBpbWFnZSBoYXMgc29tZSBzcGVjaWZpYyBmZWF0dXJlcywgaXQgYmVsb25ncyB0byBhIHBhcnRpY3VsYXIgY2xhc3MuIFRoaXMgaXMgdGhlIGhlYXJ0IG9mIENvbnZvbHV0aW9uIE5ldXJhbCBOZXR3b3JrcyBkby4NCg0KVGhpcyBrZXkgY2hhcmFjdGVyaXN0aWMgZ2l2ZXMgY29udm5ldHMgdHdvIGludGVyZXN0aW5nIHByb3BlcnRpZXM6DQoNCiogKlRoZSBwYXR0ZXJucyB0aGV5IGxlYXJuIGFyZSB0cmFuc2xhdGlvbi1pbnZhcmlhbnQ6Kg0KVGhpcyBtZWFucyB0aGF0IGFmdGVyIGxlYXJuaW5nIGEgY2VydGFpbiBwYXR0ZXJuLCBhIGNvdm5ldCBjYW4gcmVjb2duaXplIGl0IGFueXdoZXJlIGVsc2UgaW4gdGhlIGltYWdlIGFzIG9wcG9zZWQgdG8gYSBETk4gd2hpY2ggd291bGQgaGF2ZSB0byBsZWFybiB0aGUgcGF0dGVybiBhIG5ldyBpZiBpdCBhcHBlYXJlZCBhdCBhIG5ldyBsb2NhdGlvbi4gRm9yIHRoaXMgcmVhc29uLCBjb3ZuZXRzIHJlcXVpcmUgZmV3IHRyYWluaW5nIHNhbXBsZXMuDQoNCiogKlRoZXkgY2FuIGxlYXJuIHNwYXRpYWwgaGllcmFyY2hpZXMgb2YgcGF0dGVybnM6Kg0KQSBmaXJzdCBjb252b2x1dGlvbiBsYXllciB3aWxsIGxlYXJuIHNtYWxsIGxvY2FsIHBhdHRlcm5zIHN1Y2ggYXMgZWRnZXMsIGEgc2Vjb25kIGNvbnZvbHV0aW9uIGxheWVyIHdpbGwgbGVhcm4gbGFyZ2VyIHBhdHRlcm5zIG1hZGUgb2YgdGhlIGZlYXR1cmVzIG9mIHRoZSBmaXJzdCBsYXllcnMsIGFuZCBzbyBvbi4gRm9yIHRoaXMgcmVhc29uLCBjb3ZuZXRzIGNhbiBsZWFybiBpbmNyZWFzaW5nbHkgY29tcGxleCBhbmQgYWJzdHJhY3QgZmVhdHVyZXMuDQoNCg0KSWYgeW91J3ZlIGV2ZXIgZG9uZSBpbWFnZSBwcm9jZXNzaW5nIHVzaW5nIGEgZmlsdGVyIChsaWtlIHRoaXM6IGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0tlcm5lbF8oaW1hZ2VfcHJvY2Vzc2luZykpIHRoZW4gY29udm9sdXRpb25zIHdpbGwgbG9vayB2ZXJ5IGZhbWlsaWFyLg0KDQpJbiBzaG9ydCwgeW91IHRha2UgYW4gYXJyYXkgKHVzdWFsbHkgM3gzIG9yIDV4NSkgYW5kIHBhc3MgaXQgb3ZlciB0aGUgaW1hZ2UuIEJ5IGNoYW5naW5nIHRoZSB1bmRlcmx5aW5nIHBpeGVscyBiYXNlZCBvbiB0aGUgZm9ybXVsYSB3aXRoaW4gdGhhdCBtYXRyaXgsIHlvdSBjYW4gZG8gdGhpbmdzIGxpa2UgZWRnZSBkZXRlY3Rpb24uIFNvLCBmb3IgZXhhbXBsZSwgaWYgeW91IGxvb2sgYXQgdGhlIGFib3ZlIGxpbmssIHlvdSdsbCBzZWUgYSAzeDMgdGhhdCBpcyBkZWZpbmVkIGZvciBlZGdlIGRldGVjdGlvbiB3aGVyZSB0aGUgbWlkZGxlIGNlbGwgaXMgOCwgYW5kIGFsbCBvZiBpdHMgbmVpZ2hib3JzIGFyZSAtMS4gSW4gdGhpcyBjYXNlLCBmb3IgZWFjaCBwaXhlbCwgeW91IHdvdWxkIG11bHRpcGx5IGl0cyB2YWx1ZSBieSA4LCB0aGVuIHN1YnRyYWN0IHRoZSB2YWx1ZSBvZiBlYWNoIG5laWdoYm9yLiBEbyB0aGlzIGZvciBldmVyeSBwaXhlbCwgYW5kIHlvdSdsbCBlbmQgdXAgd2l0aCBhIG5ldyBpbWFnZSB0aGF0IGhhcyB0aGUgZWRnZXMgZW5oYW5jZWQuDQoNCg0KDQoqVGhhdCdzIHRoZSBjb25jZXB0IG9mIENvbnZvbHV0aW9uYWwgTmV1cmFsIE5ldHdvcmtzLiBBZGQgc29tZSBsYXllcnMgdG8gZG8gY29udm9sdXRpb24gYmVmb3JlIHlvdSBoYXZlIHRoZSBkZW5zZSBsYXllcnMsIGFuZCB0aGVuIHRoZSBpbmZvcm1hdGlvbiBnb2luZyB0byB0aGUgZGVuc2UgbGF5ZXJzIGlzIG1vcmUgZm9jdXNzZWQsIGFuZCBwb3NzaWJseSBtb3JlIGFjY3VyYXRlLioNCg0KDQpgUG9vbGluZ2AgcmVkdWNlcyB0aGUgYW1vdW50IG9mIGlycmVsZXZhbnQgaW5mb3JtYXRpb24gaW4gYW4gaW1hZ2Ugd2hpbGUgbWFpbnRhaW5pbmcgdGhlIGZlYXR1cmVzIHRoYXQgYXJlIGRldGVjdGVkLiBJdCBkb2VzIHNvIGJ5IGxvb2tpbmcgYXQgYSBwaXhlbCBhbmQgaXRzIGltbWVkaWF0ZSBuZWlnaGJvdXJzIHRvIHRoZSByaWdodCwgYmVuZWF0aCBhbmQgcmlnaHQtYmVuZWF0aCwgdGFrZXMgdGhlIGxhcmdlc3QgaGVuY2UgdGhlIG5hbWUgTWF4IHBvb2xpbmcsIGFuZCBsb2FkcyBpdCBpbnRvIGEgbmV3IGltYWdlLiBJdCB0aHVzIHJlZHVjZXMgdGhlIGFtb3VudCBvZiBpbmZvcm1hdGlvbiB0aGF0IGEgbW9kZWwgaGFzIHRvIHByb2Nlc3Mgd2hpbGUgc3RpbGwgbWFpbnRhaW5pbmcgdGhlIHByb21pbmVudCBmZWF0dXJlcy4NCg0KYGBge3IsZWNobz1GQUxTRX0NCmxpYnJhcnkoa25pdHIpDQppbmNsdWRlX2dyYXBoaWNzKCJDOi9Vc2Vycy9BRE1JTi9EZXNrdG9wL0ludG9kdWN0aW9uIHRvIFB5dGhvbiBmb3IgZGF0YSBzY2llbmNlL1IgZm9yIGRhdGEgc2NpZW5jZS9hUmR1aW5vL3I0Zy9wb29saW5nLkpQRyIpDQpwcmludCgiSW1hZ2Ugc291cmNlOiBodHRwczovL2NvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20vZ2l0aHViL2xtb3JvbmV5L21sZGF5LXRva3lvL2Jsb2IvbWFzdGVyL0xhYjMtV2hhdC1BcmUtQ29udm9sdXRpb25zLmlweW5iI3Njcm9sbFRvPXhGMEZQcGxzZ0hOaCIsIHF1b3RlID0gRikNCg0KYGBgDQoNCg0KDQojIyMgKipCdWlsZGluZyBDb252b2x1dGlvbiBOZXVyYWwgTmV0d29ya3MuKioNCg0KDQojICoqR2F0aGVyaW5nIHRoZSBEYXRhKioNCg0KTGV0J3Mgc3RhcnQgYnkgbG9hZGluZyB0aGUgbGlicmFyaWVzIHJlcXVpcmVkIGZvciB0aGlzIHNlc3Npb24uDQoNCldlJ2xsIGJlIHJlcXVpcmluZyBzb21lIHBhY2thZ2VzIGluIHRoZSBUaWR5dmVyc2UgYW5kIEtlcmFzKGEgZnJhbWV3b3JrIGZvciBkZWZpbmluZyBhIG5ldXJhbCBuZXR3b3JrIGFzIGEgc2V0IG9mIFNlcXVlbnRpYWwgbGF5ZXJzKS4gWW91IGNhbiBoYXZlIHRoZW0gaW5zdGFsbGVkIGFzIGZvbGxvd3MNCg0KYGBgDQpzdXBwcmVzc01lc3NhZ2VzKGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpKQ0Kc3VwcHJlc3NNZXNzYWdlcyhpbnN0YWxsLnBhY2thZ2VzKCJrZXJhcyIpKQ0Kc3VwcHJlc3NNZXNzYWdlcyhpbnN0YWxsX2tlcmFzKCkpDQoNClBzOiBpdCBjb3VsZCB0YWtlIGEgd2hpbGUNCmBgYA0KT25jZSBpbnN0YWxsZWQsIGxldCdzIGdldCByb2xsaW5nOg0KDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGtlcmFzKQ0KDQptbmlzdCA8LSBkYXRhc2V0X21uaXN0KCkNCg0KYyh0cmFpbmluZ19pbWFnZXMsIHRyYWluaW5nX2xhYmVscykgJTwtJSBtbmlzdCR0cmFpbg0KYyh0ZXN0X2ltYWdlcywgdGVzdF9sYWJlbHMpICU8LSUgbW5pc3QkdGVzdA0KDQojIHRoZSB0cmFpbl9pbWFnZXMgYW5kIHRyYWluX2xhYmVscyBhcnJheXMgYXJlIHRoZSB0cmFpbmluZyBzZXQNCiMgKHRoZSBkYXRhIHRoZSBtb2RlbCB1c2VzIHRvIGxlYXJuKS4gDQoNCg0KIyBUaGUgbW9kZWwgaXMgdGVzdGVkIGFnYWluc3QgdGhlIHRlc3Qgc2V0OiANCiMgdGhlIHRlc3RfaW1hZ2VzLCBhbmQgdGVzdF9sYWJlbHMgYXJyYXlzLg0KYGBgDQoNCg0KKipSZXNoYXBpbmcgb3VyIGltYWdlIGFycmF5cyBhbmQgbm9ybWFsaXppbmcgdGhlbSoqDQoNCg0KYGBge3J9DQp0cmFpbmluZ19pbWFnZXMgPC0gYXJyYXlfcmVzaGFwZSh0cmFpbmluZ19pbWFnZXMsIGMoNjAwMDAsIDI4LCAyOCwgMSkpDQoNCnRyYWluaW5nX2ltYWdlcyA8LSB0cmFpbmluZ19pbWFnZXMvMjU1DQoNCnRlc3RfaW1hZ2VzIDwtIGFycmF5X3Jlc2hhcGUodGVzdF9pbWFnZXMsIGMoMTAwMDAsIDI4LCAyOCwgMSkpDQp0ZXN0X2ltYWdlcyA8LSB0ZXN0X2ltYWdlcy8yNTUNCg0KIyB0aGUgdmFsdWVzIDYwLDAwMCBhbmQgMTAsMDAwIGFyZSBub3QgYXJiaXRyYXJ5LCB3ZSBvYnRhaW5lZCB0aGVtIHVzaW5nDQpkaW0odHJhaW5pbmdfaW1hZ2VzKQ0KDQojIDYwLDAwMCBpbWFnZXMgaW4gdGhlIHRyYWluaW5nIHNldCwNCiMgd2l0aCBlYWNoIGltYWdlIHJlcHJlc2VudGVkIGFzIDI4IHggMjggcGl4ZWxzIGFuZCAxIGRlcHRoIG9mIDEoYmluYXJ5IGltYWdlKQ0KYGBgDQoNCldoeSBhcmUgd2UgYWRkaW5nIG9uZSBtb3JlIGRpbWVuc2lvbj8gVGhhdCdzIGFuIGltcG9ydGFudCBxdWVzdGlvbi4NCkNvbnZvbHV0aW9ucyBvcGVyYXRlIG92ZXIgM0QgdGVuc29ycywgY2FsbGVkIGZlYXR1cmUgbWFwcywgd2l0aCB0d28gc3BhdGlhbCBheGVzIChoZWlnaHQgd2lkdGgpIGFzIHdlbGwgYXMgYSBkZXB0aCBheGlzLiBGb3IgYW4gUkdCIGRlcHRoIGNoYW5uZWxzIGltYWdlLCB0aGUgZGltZW5zaW9uIG9mIHRoZSBkZXB0aCBheGlzIGlzIDMsIGJlY2F1c2UgdGhlIGltYWdlIGhhcyAzIGNvbG9yIGNoYW5uZWxzOiByZWQsIGdyZWVuLCBhbmQgYmx1ZS4gRm9yIGEgYmxhY2stYW5kLXdoaXRlIHBpY3R1cmUsIGxpa2UgdGhlIEZhc2hpb24gTU5JU1QgZGF0YXNldCwgdGhlIGRlcHRoIGlzIDEuDQoNCg0KDQoNCiMgKipJbnN0YW50aWF0aW5nIGEgQ29udm9sdXRpb24qKg0KDQpOZXh0IGlzIHRvIGRlZmluZSB5b3VyIG1vZGVsLiBOb3cgaW5zdGVhZCBvZiB0aGUgaW5wdXQgbGF5ZXIgYXQgdGhlIHRvcCwgd2UncmUgZ29pbmcgdG8gYWRkIGEgQ29udm9sdXRpb24uIFRoZSBwYXJhbWV0ZXJzIGFyZToNCg0KMS4gVGhlIG51bWJlciBvZiBjb252b2x1dGlvbnMgeW91IHdhbnQgdG8gZ2VuZXJhdGUuIFB1cmVseSBhcmJpdHJhcnksIGJ1dCBnb29kIHRvIHN0YXJ0IHdpdGggc29tZXRoaW5nIGluIHRoZSBvcmRlciBvZiAzMg0KDQoyLiBUaGUgc2l6ZSBvZiB0aGUgQ29udm9sdXRpb24sIGluIHRoaXMgY2FzZSBhIDN4MyBncmlkDQoNCjMuIFRoZSBhY3RpdmF0aW9uIGZ1bmN0aW9uIHRvIHVzZSAtLSBpbiB0aGlzIGNhc2Ugd2UnbGwgdXNlIHJlbHUsIHdoaWNoIHlvdSBtaWdodCByZWNhbGwgaXMgdGhlIGVxdWl2YWxlbnQgb2YgcmV0dXJuaW5nIHggd2hlbiB4PjAsIGVsc2UgcmV0dXJuaW5nIDANCg0KV2UnbGwgZm9sbG93IHRoZSBDb252b2x1dGlvbiB3aXRoIGEgTWF4UG9vbGluZyBsYXllciB3aGljaCBpcyB0aGVuIGRlc2lnbmVkIHRvIGNvbXByZXNzIHRoZSBpbWFnZSwgd2hpbGUgbWFpbnRhaW5pbmcgdGhlIGNvbnRlbnQgb2YgdGhlIGZlYXR1cmVzIHRoYXQgd2VyZSBoaWdobGlnaHRlZCBieSB0aGUgY29udmx1dGlvbi4gQnkgc3BlY2lmeWluZyAoMiwyKSBmb3IgdGhlIE1heFBvb2xpbmcsIHRoZSBlZmZlY3QgaXMgdG8gcXVhcnRlciB0aGUgc2l6ZSBvZiB0aGUgaW1hZ2UuIFdpdGhvdXQgZ29pbmcgaW50byB0b28gbXVjaCBkZXRhaWwgaGVyZSwgdGhlIGlkZWEgaXMgdGhhdCBpdCBjcmVhdGVzIGEgMngyIGFycmF5IG9mIHBpeGVscywgYW5kIHBpY2tzIHRoZSBiaWdnZXN0IG9uZSwgdGh1cyB0dXJuaW5nIDQgcGl4ZWxzIGludG8gMS4gSXQgcmVwZWF0cyB0aGlzIGFjcm9zcyB0aGUgaW1hZ2UsIGFuZCBpbiBzbyBkb2luZyBoYWx2ZXMgdGhlIG51bWJlciBvZiBob3Jpem9udGFsLCBhbmQgaGFsdmVzIHRoZSBudW1iZXIgb2YgdmVydGljYWwgcGl4ZWxzLCBlZmZlY3RpdmVseSByZWR1Y2luZyB0aGUgaW1hZ2UgYnkgMjUlLiBUaGVzZSBjb25jZXB0cyBhcmUgY2xlYXJseSBleHBsYWluZWQgaW4gW0VwaXNvZGUgM10oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1QQ2dMbXprUk0zOCZ0PTEpLg0KDQoNCmBgYHtyfQ0KbW9kZWwgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JSANCiAgIyBhZGRpbmcgdGhlIGZpcnN0IGNvbnZvbHV0aW9uIGxheWVyIHdpdGggNjQgM2J5MyBmaWx0ZXINCiAgIyB3ZSBhZGQgYSBjb2xvciBkZXB0aCBvZiAxIHNpbmNlIGNvbnZvbHV0aW9ucyBvcGVyYXRlIG92ZXIgM0QgdGVuc29ycw0KICBsYXllcl9jb252XzJkKGlucHV0X3NoYXBlID0gYygyOCwgMjgsIDEpLCBmaWx0ZXJzID0gNiwga2VybmVsX3NpemUgPSBjKDMsMyksIGFjdGl2YXRpb24gPSAncmVsdScpICU+JSANCiAgIyBhZGRpbmcgYSBtYXggcG9vbGluZyBsYXllciB3aGljaCBoYWx2ZXMgdGhlIGRpbWVuc2lvbnMNCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLDIpKSAlPiUgDQogICMgYWRkaW5nIGEgc2Vjb25kIGNvbnZvbHV0aW9uIGxheWVyIHdoaWNoIGZpbHRlcnMgdGhlIHJlc3VsdHMNCiAgIyBmcm9tIHRoZSBwcmV2aW91cyBsYXllcg0KICBsYXllcl9jb252XzJkKGZpbHRlcnMgPSAxNiwga2VybmVsX3NpemUgPSBjKDMsMyksIGFjdGl2YXRpb24gPSAncmVsdScpICU+JSAjIGFkZGluZyBhIHBvb2xpbmcgbGF5ZXINCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLDIpKQ0KICANCmBgYA0KDQoNCg0KDQojICoqQWRkaW5nIGEgY2xhc3NpZmllciB0byB0aGUgY292bmV0KioNCkNvbnZvbHV0aW9uYWwgbGF5ZXJzIGxlYXJuIHRoZSBmZWF0dXJlcyBhbmQgcGFzcyB0aGVzZSB0byB0aGUgZGVuc2UgbGF5ZXJzIHdoaWNoIG1hcCB0aGUgbGVhcm5lZCBmZWF0dXJlcyB0byB0aGUgZ2l2ZW4gbGFiZWxzLiBUaGVyZWZvcmUsIHRoZSBuZXh0IHN0ZXAgaXMgdG8gZmVlZCB0aGUgbGFzdCBvdXRwdXQgdGVuc29yIGludG8gYSBkZW5zZWx5DQpjb25uZWN0ZWQgY2xhc3NpZmllciBuZXR3b3JrIGxpa2UgdGhvc2Ugd2XigJlyZSBhbHJlYWR5IGZhbWlsaWFyIHdpdGg6IGEgc3RhY2sgb2YgZGVuc2UgbGF5ZXJzLg0KVGhlc2UgY2xhc3NpZmllcnMgcHJvY2VzcyB2ZWN0b3JzLCB3aGljaCBhcmUgMUQsIHdoZXJlYXMgdGhlIGN1cnJlbnQgb3V0cHV0IGlzIGEgM0QgdGVuc29yLiBGaXJzdCB3ZSBoYXZlIHRvIGZsYXR0ZW4gdGhlIDNEIG91dHB1dHMgdG8gMUQsIGFuZCB0aGVuIGFkZCBhIGZldyBkZW5zZSBsYXllcnMgb24gdG9wLg0KDQpgYGB7cn0NCm1vZGVsIDwtIG1vZGVsICU+JSANCiAgIyBOb3cgZmxhdHRlbiB0aGUgb3V0cHV0Lg0KICBsYXllcl9mbGF0dGVuKCkgJT4lIA0KICAjIEFmdGVyIHRoaXMgeW91J2xsIGp1c3QgaGF2ZSB0aGUgc2FtZSBETk4gc3RydWN0dXJlIGFzIHRoZSBub24gY29udm9sdXRpb25hbCB2ZXJzaW9uDQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gJ3JlbHUnKSAlPiUgDQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTAsIGFjdGl2YXRpb24gPSAnc29mdG1heCcpDQoNCg0KYGBgDQoNCioqQ29tcGlsZToqKiBDb25maWd1cmluZyBhIEtlcmFzIG1vZGVsIGZvciB0cmFpbmluZw0KDQpgYGB7cn0NCm1vZGVsICU+JSBjb21waWxlKA0KICBsb3NzID0gJ3NwYXJzZV9jYXRlZ29yaWNhbF9jcm9zc2VudHJvcHknLA0KICBvcHRpbWl6ZXIgPSBvcHRpbWl6ZXJfYWRhbSgpLA0KICBtZXRyaWNzID0gYygnYWNjdXJhY3knKQ0KDQopDQptb2RlbCAlPiUgc3VtbWFyeSgpDQpgYGANCkZyb20gdGhlIHN1bW1hcnksIHdlIGV4cGVjdCBvdXRwdXQgb2YgdGhlIGZpcnN0IGNvbnZvbHV0aW9uIHRvIGJlIGEgMjh4MjggYnV0IHdlIG9idGFpbiBhIDI2eDI2LiBJZiB5b3UgaGF2ZSB3YXRjaGVkIHRoZSBlcGlzb2RlLCB5b3UgcHJvYmFibHkga25vdyB3aHkuIEEgMyBieSAzIGZpbHRlciByZXF1aXJpbmcgYSBuZWlnaGJvdXIgb24gYWxsIHNpZGVzIGNhbid0IHdvcmsgb24gdGhlIHBpeGVscyBhcm91bmQgdGhlIGVkZ2VzIG9mIHRoZSBwaWN0dXJlLiBZb3UgZWZmZWN0aXZlbHkgaGF2ZSB0byByZW1vdmUgb25lIHBpeGVsIGZyb20gdGhlIHRvcCxib3R0b20gbGVmdCBhbmQgcmlnbnQgYW5kIHRoaXMgcmVkdWNlcyB5b3VyIGRpbWVuc2lvbiBieSAyIG9uIGVhY2ggYXhpcy4gU28gYSAyOCBieSAyOCBiZWNvbWVzIGEgMjYgYnkgMjYuDQpBbHNvLCB0aGUgcG9vbGluZyBsYXllciBjbGVhcmx5IGhhbHZlcyB0aGUgZGltZW5zaW9ucyBvZiBlYWNoIGF4aXMuDQoNCkRpc3BsYXlpbmcgdGhlIGZpcnN0IDI1IGltYWdlcyBmcm9tIHRoZSB0ZXN0aW5nIHNldDoNCg0KDQpgYGB7cn0NCnBhcihtZnJvdyA9IGMoNSw1KSkNCnBhcihtYXI9YygwLCAwLCAxLjUsIDApLCB4YXhzPSdpJywgeWF4cz0naScpDQoNCmZvcihpIGluIDE6NTApew0KICBpbWcgPSB0ZXN0X2ltYWdlc1tpLCAsICwxXQ0KICBpbWcgPSB0KGFwcGx5KGltZywgMiwgcmV2KSkNCiAgaW1hZ2UoMToyOCwgMToyOCwgaW1nLCBjb2wgPSBncmF5KCgwOjI1NSkvMjU1KSwgeGF4dCA9ICduJywNCiAgICAgICAgeWF4dCA9ICduJykNCn0NCmBgYA0KDQoNCg0KIyAqKlZpc3VhbGl6aW5nIGV2ZXJ5IGZpbHRlciBvdXRwdXQgaW4gZWFjaCBjb252b2x1dGlvbiBhbmQgcG9vbGluZyBsYXllcioqDQoNCkZvciB0aGlzIHN0ZXAsIEkgdG9vayBhIGxpdHRsZSBkZXRvdXIgZnJvbSB3aGF0IHdhcyBkb25lIGluIHRoZSBQeXRob24gTm90ZWJvb2sgYnV0IGl0IGlsbHVzdHJhdGVzIHRoZSBzYW1lIGNvbmNlcHQ6IGhvdyB0aGUgY29udm9sdXRpb25zIGFwcGx5IGRpZmZlcmVudCBmaWx0ZXJzIHRvIGV4dHJhY3QgZmVhdHVyZXMgZnJvbSBvdXIgaW5wdXQgaW1hZ2VzLg0KDQpgYGB7cn0NCiMgbGV0J3MgdmlzdWFsaXplIHRoZSBmaWx0ZXJzIGFwcGxpZWQgYnkgdGhlIGNvbnZvbHV0aW9ucyB1c2luZyBvdXINCiMgZmlyc3QgaW1hZ2UNCg0KIyB0aGVyZSBhcmUgMTAyOCBpbWFnZXMgb2YgbnVtYmVyIDcgaW4gdGhlIHRlc3RfaW1hZ2VzLg0KIyBJIGhhdmUgdXNlZCB0aGUgZmlyc3QgNyB3aGljaCBoYXBwZW5zIHRvIGJlIHRoZSBmaXJzdCBpbmRleC4NCiMgSSB5b3Ugd2FudCB0byBmaW5kIHRoZSBvdGhlciBpbmRpY2VzL3Jvd25hbWVzIHdpdGggbnVtYmVyIDcsIGRvIHRoaXM6DQoNCm1uaXN0X3NldmVuID0gd2hpY2godGVzdF9sYWJlbHMgPT0gNykNCg0KbW5pc3Rfc2V2ZW5bMToxMF0NCg0KIyByb3cgMSwgMTgsIDI3LCAzNSwgMzcuLi4gOTk5MSBhbGwgY29udGFpbiBkaWdpdCA3DQoNCg0KaW1nX3RlbnNvciA8LSB0ZXN0X2ltYWdlc1syNywgLCAsXQ0KDQojIHNhdmluZyB0aGUgbWF0cml4IHRoYXQgY29ycmVzcG9uZHMgdG8gaW1hZ2UgNw0Kd3JpdGUuY3N2KGltZ190ZW5zb3IsICJzZXZlbl9tbmlzdC5jc3YiKQ0KDQojIGxldCdzIHZpZXcgb3VyIGZpcnN0IGltYWdlDQpwbG90KGFzLnJhc3RlcihpbWdfdGVuc29yKSkNCg0KIyBleHRyYWN0aW5nIHRoZSBvdXRwdXRzIG9mIHRoZSB0b3AgNCBsYXllcnMNCmxheWVyX291dHB1dCA8LSBsYXBwbHkobW9kZWwkbGF5ZXJzWzE6NF0sIGZ1bmN0aW9uKGxheWVyKSBsYXllciRvdXRwdXQpDQoNCg0KIyBDcmVhdGVzIGEgbW9kZWwgdGhhdCB3aWxsIHJldHVybiB0aGVzZSBvdXRwdXRzLCBnaXZlbiB0aGUgbW9kZWwgaW5wdXQNCiMgVGhpcyBtb2RlbCBoYXMgb25lIGlucHV0IGFuZCBmb3VyIG91dHB1dHM6DQojIG9uZSBvdXRwdXQgcGVyIGxheWVyIGFjdGl2YXRpb24uDQphY3RpdmF0aW9uX21vZGVsIDwtIGtlcmFzX21vZGVsKGlucHV0cyA9IG1vZGVsJGlucHV0LCBvdXRwdXRzID0gbGF5ZXJfb3V0cHV0KQ0KDQoNCiMgdXNpbmcgdGhlIG1vZGVsIHRvIHByZWRpY3QgdGhlIG91dHB1dCBvZiB0aGUgZmlsdGVycyBpbiB0aGUgcmVwZWN0aXZlIGxheWVycw0KIyB3ZSBzdGlsbCBoYXZlIHRvIHJlc2hhcGUgb3VyIGltZ190ZW5zb3Igc2luY2UgY29udm9sdXRpb25zIHdvcmsgd2l0aA0KIyAzRCBpbnB1dHMNCg0KYWN0aXZhdGlvbnMgPC0gYWN0aXZhdGlvbl9tb2RlbCAlPiUgcHJlZGljdChhcnJheV9yZXNoYXBlKGltZ190ZW5zb3IsIGMoMSwgMjgsIDI4LCAxKSkpDQoNCmBgYA0KDQoqKmxldCdzIGV4dHJhY3QgdGhlIGZpcnN0IDQgY29udm9sdXRpb24gYW5kIHBvb2xpbmcgbGF5ZXJzKioNCg0KYGBge3J9DQpmaXJzdF9sYXllcl9hY3RpdmF0aW9uIDwtIGFjdGl2YXRpb25zW1sxXV0NCnNlY29uZF9sYXllcl9hY3RpdmF0aW9uIDwtIGFjdGl2YXRpb25zW1syXV0NCnRoaXJkX2xheWVyX2FjdGl2YXRpb24gPC0gYWN0aXZhdGlvbnNbWzNdXQ0KZm91cnRoX2xheWVyX2FjdGl2YXRpb24gPC0gYWN0aXZhdGlvbnNbWzRdXQ0KDQoNCmRpbShmaXJzdF9sYXllcl9hY3RpdmF0aW9uKQ0KIyB0aGUgZmlyc3QgbGF5ZXIgaGFzIDY0IGZpbHRlcnMgYXMgd2UgZGVmaW5lZCBlYXJsaWVyDQpgYGANCg0KDQpOZXh0IHdlIGRlZmluZSBhIGZ1bmN0aW9uIHRoYXQgd2lsbCBoZWxwIHVzIHZpc3VhbGlzZSB0aGUgcmVzdWx0IG9mIGVhY2ggZmlsdGVyIGluIGVhY2ggb2YgdGhlIGxheWVyIGFjdGl2YXRpb25zIGFib3ZlLg0KDQpgYGB7cn0NCnBsb3RfY2hhbm5lbCA8LSBmdW5jdGlvbihjaGFubmVsKSB7DQogICMgcm90YXRpbmcgdGhlIGltYWdlcw0KICBpbWcgPC0gdChhcHBseShjaGFubmVsLCAyLCByZXYpKQ0KICBpbWFnZShpbWcsIGF4ZXMgPSBGQUxTRSwgYXNwPTEsY29sID0gZ3JheSgoMDoyNTUpLzI1NSkpDQp9DQpgYGANCg0KDQoqKlZpc3VhbGl6aW5nIHRoZSBjb252b2x1dGlvbnMgYW5kIHBvb2xpbmcgb24gb3VyIGZpcnN0IHRlc3QgaW1hZ2UqKg0KDQpUaGUgY29kZSBiZWxvdyBzYXZlcyB0aGUgaW1hZ2VzIGluIHlvdXIgY3VycmVudCBkaXJlY3RvcnkuDQoNCmBgYHtyfQ0KaW1hZ2VzX3Blcl9yb3cgPC0gMw0KZm9yIChpIGluIDE6Mikgew0KIGxheWVyX2FjdGl2YXRpb24gPC0gYWN0aXZhdGlvbnNbW2ldXQ0KIGxheWVyX25hbWUgPC0gbW9kZWwkbGF5ZXJzW1tpXV0kbmFtZQ0KIG5fZmVhdHVyZXMgPC0gZGltKGxheWVyX2FjdGl2YXRpb24pW1s0XV0NCiBuX2NvbHMgPC0gbl9mZWF0dXJlcyAlLyUgaW1hZ2VzX3Blcl9yb3cNCiBwbmcocGFzdGUwKCJtbmlzdF9zZXZlbl9hY3RpdmF0aW9uc18iLCBpLCAiXyIsIGxheWVyX25hbWUsICIucG5nIiksDQogd2lkdGggPSA1MDAsDQogaGVpZ2h0ID0gNTAwKQ0KIG9wIDwtIHBhcihtZnJvdyA9IGMobl9jb2xzLCBpbWFnZXNfcGVyX3JvdyksIG1haSA9IHJlcF9sZW4oMC4wMiwgNCkpDQogZm9yIChjb2wgaW4gMDoobl9jb2xzLTEpKSB7DQogZm9yIChyb3cgaW4gMDooaW1hZ2VzX3Blcl9yb3ctMSkpIHsNCiBjaGFubmVsX2ltYWdlIDwtIGxheWVyX2FjdGl2YXRpb25bMSwsLChjb2wqaW1hZ2VzX3Blcl9yb3cpICsgcm93ICsgMV0NCiBwbG90X2NoYW5uZWwoY2hhbm5lbF9pbWFnZSkNCiB9DQogfQ0KIHBhcihvcCkNCiBkZXYub2ZmKCkNCn0NCg0KaW1hZ2VzX3Blcl9yb3cgPC0gNA0KZm9yIChpIGluIDM6NCkgew0KIGxheWVyX2FjdGl2YXRpb24gPC0gYWN0aXZhdGlvbnNbW2ldXQ0KIGxheWVyX25hbWUgPC0gbW9kZWwkbGF5ZXJzW1tpXV0kbmFtZQ0KIG5fZmVhdHVyZXMgPC0gZGltKGxheWVyX2FjdGl2YXRpb24pW1s0XV0NCiBuX2NvbHMgPC0gbl9mZWF0dXJlcyAlLyUgaW1hZ2VzX3Blcl9yb3cNCiBwbmcocGFzdGUwKCJtbmlzdF9zZXZlbl9hY3RpdmF0aW9uc18iLCBpLCAiXyIsIGxheWVyX25hbWUsICIucG5nIiksDQogd2lkdGggPSA1MDAsDQogaGVpZ2h0ID0gNTAwKQ0KIG9wIDwtIHBhcihtZnJvdyA9IGMobl9jb2xzLCBpbWFnZXNfcGVyX3JvdyksIG1haSA9IHJlcF9sZW4oMC4wMiwgNCkpDQogZm9yIChjb2wgaW4gMDoobl9jb2xzLTEpKSB7DQogZm9yIChyb3cgaW4gMDooaW1hZ2VzX3Blcl9yb3ctMSkpIHsNCiBjaGFubmVsX2ltYWdlIDwtIGxheWVyX2FjdGl2YXRpb25bMSwsLChjb2wqaW1hZ2VzX3Blcl9yb3cpICsgcm93ICsgMV0NCiBwbG90X2NoYW5uZWwoY2hhbm5lbF9pbWFnZSkNCiB9DQogfQ0KIHBhcihvcCkNCiBkZXYub2ZmKCkNCn0NCg0KYGBgDQoNCg0KVGhlIG91dHB1dCBvZiBlYWNoIGZpbHRlciBpbiBlYWNoIGxheWVyIGlzIGFzIHNob3duIHJvdy13aXNlLg0KDQojIyMgVHJhbnNmb3JtYXRpb24gYnkgZmlyc3QgY29udm9sdXRpb24gbGF5ZXINCmBgYHtyfQ0Kb3AgPC0gcGFyKG1mcm93ID0gYygzLDMpLCBtYWkgPSBjKDAuMDUsIDAsIDAsIDApKQ0KZm9yIChpIGluIDE6ZGltKGZpcnN0X2xheWVyX2FjdGl2YXRpb24pWzRdKSB7DQogcGxvdF9jaGFubmVsKGZpcnN0X2xheWVyX2FjdGl2YXRpb25bMSwsLGldKQ0KIHBhc3RlMCgiZmlyc3QgbGF5ZXIiKQ0KfQ0KYGBgDQoNCg0KDQoNCg0KIyMjIFRyYW5zZm9ybWF0aW9uIGJ5IGZpcnN0IHBvb2xpbmcgbGF5ZXINCmBgYHtyfQ0Kb3AgPC0gcGFyKG1mcm93ID0gYygzLDMpLCBtYWkgPSByZXBfbGVuKDAuMDIsIDQpKQ0KZm9yIChpIGluIDE6ZGltKHNlY29uZF9sYXllcl9hY3RpdmF0aW9uKVs0XSkgew0KIHBsb3RfY2hhbm5lbChzZWNvbmRfbGF5ZXJfYWN0aXZhdGlvblsxLCwsaV0pDQogcGFzdGUwKCJzZWNvbmQgbGF5ZXIiKQ0KfQ0KI3BuZygic2Vjb25kX2xheWVyLnBuZyIsIHdpZHRoID0gMTUwLCBoZWlnaHQgPSAxNTApDQpgYGANCg0KDQoNCiMjIyBUcmFuc2Zvcm1hdGlvbiBieSBzZWNvbmQgY29udm9sdXRpb24gbGF5ZXINCmBgYHtyfQ0Kb3AgPC0gcGFyKG1mcm93ID0gYyg0LDQpLCBtYWkgPSBjKDAuMDUsIDAsIDAsIDApKQ0KZm9yIChpIGluIDE6ZGltKHRoaXJkX2xheWVyX2FjdGl2YXRpb24pWzRdKSB7DQogcGxvdF9jaGFubmVsKHRoaXJkX2xheWVyX2FjdGl2YXRpb25bMSwsLGldKQ0KIHBhc3RlMCgidGhpcmQgbGF5ZXIiKQ0KfQ0KYGBgDQoNCg0KIyMjIFRyYW5zZm9ybWF0aW9uIGJ5IHNlY29uZCBwb29saW5nIGxheWVyDQoNCmBgYHtyfQ0Kb3AgPC0gcGFyKG1mcm93ID0gYyg0LDQpLCBtYWkgPSBjKDAuMDUsIDAsIDAsIDApKQ0KZm9yIChpIGluIDE6ZGltKGZvdXJ0aF9sYXllcl9hY3RpdmF0aW9uKVs0XSkgew0KIHBsb3RfY2hhbm5lbChmb3VydGhfbGF5ZXJfYWN0aXZhdGlvblsxLCwsaV0pDQogcGFzdGUwKCJmb3VydGggbGF5ZXIiKQ0KfQ0KYGBgDQoNCg0KR3JlYXQhIE5vdyB0aGVyZSBhcmUgYSBmZXcgdGhpbmdzIHRvIG5vdGU6DQoNCiogVGhlIGZpcnN0IGxheWVyIGFjdHMgYXMgYSBjb2xsZWN0aW9uIG9mIHZhcmlvdXMgZmlsdGVycy4gQXQgdGhhdCBzdGFnZSwgdGhlIG91dHB1dCBvZiB0aGUgbGF5ZXIgc2VlbXMgdG8gcmV0YWluIGFsbW9zdCBhbGwgb2YgdGhlIGluZm9ybWF0aW9uIHByZXNlbnQgaW4gdGhlIGluaXRpYWwgcGljdHVyZS4NCg0KKiBBcyB5b3UgZ28gaGlnaGVyLCB0aGUgb3V0cHV0cyBvZiB0aGUgbGF5ZXJzIGJlY29tZSBpbmNyZWFzaW5nbHkgYWJzdHJhY3QgYW5kIGxlc3MgdmlzdWFsbHkgaW50ZXJwcmV0YWJsZS4gVGhleSBiZWdpbiB0byBlbmNvZGUgaGlnaGVyLWxldmVsIGNvbmNlcHRzIHN1Y2ggYXMgdGhlICJoZWVsIiBhbmQgInZhbXAiIG9mIHRoZSBzaG9lLg0KSGlnaGVyIHByZXNlbnRhdGlvbnMgY2FycnkgaW5jcmVhc2luZ2x5IGxlc3MgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHZpc3VhbCBjb250ZW50cyBvZiB0aGUgaW1hZ2UsIGFuZCBpbmNyZWFzaW5nbHkgbW9yZSBpbmZvcm1hdGlvbiByZWxhdGVkIHRvIHRoZSBjbGFzcyBvZiB0aGUgaW1hZ2UuDQoNCg0KDQoNCg0KDQpZZXMhIFdlIGRpdCBpdCEhIPCfpKkgV2Ugc3VjY2Vzc2Z1bGx5IGltcGxlbWVudGVkIGNvbnZvbHV0aW9ucyBhbmQgcG9vbGluZyB0byBpbXByb3ZlIHRoZSBhY2N1cmFjeSBvZiBjb21wdXRlciB2aXNpb24gdXNpbmcgUi4gSW5kZWVkIFIsIGF0IGl0cyBjb3JlIGlzIGEgYmVhdXRpZnVsIGFuZCBlbGVnYW50IGxhbmd1YWdlLCB3ZWxsIGRlc2lnbmVkIGZvciBEYXRhIFNjaWVuY2Ug8J+Sli4NCg0KDQoNCkZvciB0aGUgZ3V5cyBhdCB0aGUgYmFjazoNCg0KIyAqSG93IHRvIG1ha2UgYSByYXN0ZXIgcGxvdCBvZiB0aGUgcGl4ZWwgdmFsdWVzIHdlIGp1c3Qgc2F2ZWQgYmVmb3JlKg0KDQpgYGB7cn0NCmxpYnJhcnkocmVhZHhsKQ0Kc2V2ZW5fbW5pc3QgPC0gcmVhZC5jc3YoIkM6L1VzZXJzL0FETUlOL0Rlc2t0b3AvRGVrdXQvcHBzL1NlbmlvciBZZWFyL0FydGlmaWNpYWwgSW50ZWxsaWdlbmNlL3NldmVuX21uaXN0LmNzdiIpDQoNCiMgdGhlIGRhdGFzZXQgd2UganVzdCBsb2FkZWQgY29udGFpbnMgdGhlIHBpeGVsIHZhbHVlcyB0aGF0IG1ha2UgdXAgYSB2YXJpYXRpb24gb2YgZGlnaXQgNw0KDQojIGNvbnZlcnRpbmcgdG8gYSBtYXRyaXggYW5kIHJlbW92aW5nIHRoZSBmaXJzdCBydWRlbHkgYWRkZWQgY29sdW1uDQoNCnNldmVuX21uaXN0IDwtIGFzLm1hdHJpeChzZXZlbl9tbmlzdCkNCnNldmVuX21uaXN0IDwtIHNldmVuX21uaXN0WywtMV0NCg0KIyBwbG90dGluZyBhIHJhc3RlciBvYmplY3Qgb2YgdGhlIG1hdHJpeA0KDQpwbG90KGFzLnJhc3RlcihzZXZlbl9tbmlzdCkpDQpgYGANCg0KDQpUcnVseSwNCmBFcmljLCBJYW4sIFNhbWANCg0KDQojICoqUmVmZXJlbmNlIE1hdGVyaWFsKioNCg0KKiBNYWNoaW5lIExlYXJuaW5nIEZvdW5kYXRpb25zOiBFcCAjNCAtIFtDb2Rpbmcgd2l0aCBDb252b2x1dGlvbmFsIE5ldXJhbCBOZXR3b3Jrc10oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1kZDhINGZpTDlZYyZ0PTFzKQ0KDQoqIE1hY2hpbmUgTGVhcm5pbmcgRm91bmRhdGlvbnM6IEVwICMzIC0gW0NvbnZvbHV0aW9ucyBhbmQgcG9vbGluZ10oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1QQ2dMbXprUk0zOCZ0PTEpDQoNCiogRGVlcCBMZWFybmluZyB3aXRoIFIgYnkgRnJhbmNvaXMgQ2hvbGxldCBhbmQgSi5KLkFsbGFpcmUNCg0KKiBUaGUgW1IgaW50ZXJmYWNlIHRvIEtlcmFzXShodHRwczovL2tlcmFzLnJzdHVkaW8uY29tKSB3ZWJzaXRlDQoNCiogTGFiIDQtIFtVc2luZyBDb252b2x1dGlvbnNdKGh0dHBzOi8vY29sYWIucmVzZWFyY2guZ29vZ2xlLmNvbS9naXRodWIvbG1vcm9uZXkvbWxkYXktdG9reW8vYmxvYi9tYXN0ZXIvTGFiNC1Vc2luZy1Db252b2x1dGlvbnMuaXB5bmIjc2Nyb2xsVG89emxkRVhTc0Y4Tm96KSANCg0KKiBMYWIgMy0gW1doYXQgYXJlIENvbnZvbHV0aW9uc10oaHR0cHM6Ly9jb2xhYi5yZXNlYXJjaC5nb29nbGUuY29tL2dpdGh1Yi9sbW9yb25leS9tbGRheS10b2t5by9ibG9iL21hc3Rlci9MYWI0LVVzaW5nLUNvbnZvbHV0aW9ucy5pcHluYiNzY3JvbGxUbz16bGRFWFNzRjhOb3opIA0KDQoqIEV4ZXJjaXNlIGZvciB0aGlzIGVwaXNvZGU6IFtFeGVyY2lzZSAzXShodHRwczovL2NvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20vZ2l0aHViL2xtb3JvbmV5L2RsYWljb3Vyc2UvYmxvYi9tYXN0ZXIvRXhlcmNpc2VzL0V4ZXJjaXNlJTIwMyUyMC0lMjBDb252b2x1dGlvbnMvRXhlcmNpc2UlMjAzJTIwLSUyMFF1ZXN0aW9uLmlweW5iI3Njcm9sbFRvPXNmUVJ5YUpXQUlkZykNCg0KKiBNQVRMQUIgRGVlcCBMZWFybmluZyBieSBQaGlsIEtpbQ0KDQoNCg0K