UNPKG

24.9 kBJavaScriptView Raw
1/**
2 * @license
3 * Copyright 2018 Google LLC
4 *
5 * Use of this source code is governed by an MIT-style
6 * license that can be found in the LICENSE file or at
7 * https://opensource.org/licenses/MIT.
8 * =============================================================================
9 */
10import { CallbackConstructorRegistry } from './base_callbacks';
11import { Input, } from './engine/input_layer';
12import { LayersModel } from './engine/training';
13import { loadLayersModelInternal, Sequential } from './models';
14// TODO(cais): Add doc string to all the public static functions in this
15// class; include exectuable JavaScript code snippets where applicable
16// (b/74074458).
17// LayersModel and related factory methods.
18/**
19 * A model is a data structure that consists of `Layers` and defines inputs
20 * and outputs.
21 *
22 * The key difference between `tf.model` and `tf.sequential` is that
23 * `tf.model` is more generic, supporting an arbitrary graph (without
24 * cycles) of layers. `tf.sequential` is less generic and supports only a linear
25 * stack of layers.
26 *
27 * When creating a `tf.LayersModel`, specify its input(s) and output(s). Layers
28 * are used to wire input(s) to output(s).
29 *
30 * For example, the following code snippet defines a model consisting of
31 * two `dense` layers, with 10 and 4 units, respectively.
32 *
33 * ```js
34 * // Define input, which has a size of 5 (not including batch dimension).
35 * const input = tf.input({shape: [5]});
36 *
37 * // First dense layer uses relu activation.
38 * const denseLayer1 = tf.layers.dense({units: 10, activation: 'relu'});
39 * // Second dense layer uses softmax activation.
40 * const denseLayer2 = tf.layers.dense({units: 4, activation: 'softmax'});
41 *
42 * // Obtain the output symbolic tensor by applying the layers on the input.
43 * const output = denseLayer2.apply(denseLayer1.apply(input));
44 *
45 * // Create the model based on the inputs.
46 * const model = tf.model({inputs: input, outputs: output});
47 *
48 * // The model can be used for training, evaluation and prediction.
49 * // For example, the following line runs prediction with the model on
50 * // some fake data.
51 * model.predict(tf.ones([2, 5])).print();
52 * ```
53 * See also:
54 * `tf.sequential`, `tf.loadLayersModel`.
55 *
56 * @doc {heading: 'Models', subheading: 'Creation'}
57 */
58export function model(args) {
59 return new LayersModel(args);
60}
61/**
62 * Creates a `tf.Sequential` model. A sequential model is any model where the
63 * outputs of one layer are the inputs to the next layer, i.e. the model
64 * topology is a simple 'stack' of layers, with no branching or skipping.
65 *
66 * This means that the first layer passed to a `tf.Sequential` model should have
67 * a defined input shape. What that means is that it should have received an
68 * `inputShape` or `batchInputShape` argument, or for some type of layers
69 * (recurrent, Dense...) an `inputDim` argument.
70 *
71 * The key difference between `tf.model` and `tf.sequential` is that
72 * `tf.sequential` is less generic, supporting only a linear stack of layers.
73 * `tf.model` is more generic and supports an arbitrary graph (without
74 * cycles) of layers.
75 *
76 * Examples:
77 *
78 * ```js
79 * const model = tf.sequential();
80 *
81 * // First layer must have an input shape defined.
82 * model.add(tf.layers.dense({units: 32, inputShape: [50]}));
83 * // Afterwards, TF.js does automatic shape inference.
84 * model.add(tf.layers.dense({units: 4}));
85 *
86 * // Inspect the inferred shape of the model's output, which equals
87 * // `[null, 4]`. The 1st dimension is the undetermined batch dimension; the
88 * // 2nd is the output size of the model's last layer.
89 * console.log(JSON.stringify(model.outputs[0].shape));
90 * ```
91 *
92 * It is also possible to specify a batch size (with potentially undetermined
93 * batch dimension, denoted by "null") for the first layer using the
94 * `batchInputShape` key. The following example is equivalent to the above:
95 *
96 * ```js
97 * const model = tf.sequential();
98 *
99 * // First layer must have a defined input shape
100 * model.add(tf.layers.dense({units: 32, batchInputShape: [null, 50]}));
101 * // Afterwards, TF.js does automatic shape inference.
102 * model.add(tf.layers.dense({units: 4}));
103 *
104 * // Inspect the inferred shape of the model's output.
105 * console.log(JSON.stringify(model.outputs[0].shape));
106 * ```
107 *
108 * You can also use an `Array` of already-constructed `Layer`s to create
109 * a `tf.Sequential` model:
110 *
111 * ```js
112 * const model = tf.sequential({
113 * layers: [tf.layers.dense({units: 32, inputShape: [50]}),
114 * tf.layers.dense({units: 4})]
115 * });
116 * console.log(JSON.stringify(model.outputs[0].shape));
117 * ```
118 *
119 * @doc {heading: 'Models', subheading: 'Creation'}
120 */
121export function sequential(config) {
122 return new Sequential(config);
123}
124/**
125 * Load a model composed of Layer objects, including its topology and optionally
126 * weights. See the Tutorial named "How to import a Keras Model" for usage
127 * examples.
128 *
129 * This method is applicable to:
130 *
131 * 1. Models created with the `tf.layers.*`, `tf.sequential`, and
132 * `tf.model` APIs of TensorFlow.js and later saved with the
133 * `tf.LayersModel.save` method.
134 * 2. Models converted from Keras or TensorFlow tf.keras using the
135 * [tensorflowjs_converter](https://github.com/tensorflow/tfjs/tree/master/tfjs-converter).
136 *
137 * This mode is *not* applicable to TensorFlow `SavedModel`s or their converted
138 * forms. For those models, use `tf.loadGraphModel`.
139 *
140 * Example 1. Load a model from an HTTP server.
141 *
142 * ```js
143 * const model = await tf.loadLayersModel(
144 * 'https://storage.googleapis.com/tfjs-models/tfjs/iris_v1/model.json');
145 * model.summary();
146 * ```
147 *
148 * Example 2: Save `model`'s topology and weights to browser [local
149 * storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage);
150 * then load it back.
151 *
152 * ```js
153 * const model = tf.sequential(
154 * {layers: [tf.layers.dense({units: 1, inputShape: [3]})]});
155 * console.log('Prediction from original model:');
156 * model.predict(tf.ones([1, 3])).print();
157 *
158 * const saveResults = await model.save('localstorage://my-model-1');
159 *
160 * const loadedModel = await tf.loadLayersModel('localstorage://my-model-1');
161 * console.log('Prediction from loaded model:');
162 * loadedModel.predict(tf.ones([1, 3])).print();
163 * ```
164 *
165 * Example 3. Saving `model`'s topology and weights to browser
166 * [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API);
167 * then load it back.
168 *
169 * ```js
170 * const model = tf.sequential(
171 * {layers: [tf.layers.dense({units: 1, inputShape: [3]})]});
172 * console.log('Prediction from original model:');
173 * model.predict(tf.ones([1, 3])).print();
174 *
175 * const saveResults = await model.save('indexeddb://my-model-1');
176 *
177 * const loadedModel = await tf.loadLayersModel('indexeddb://my-model-1');
178 * console.log('Prediction from loaded model:');
179 * loadedModel.predict(tf.ones([1, 3])).print();
180 * ```
181 *
182 * Example 4. Load a model from user-selected files from HTML
183 * [file input
184 * elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file).
185 *
186 * ```js
187 * // Note: this code snippet will not work without the HTML elements in the
188 * // page
189 * const jsonUpload = document.getElementById('json-upload');
190 * const weightsUpload = document.getElementById('weights-upload');
191 *
192 * const model = await tf.loadLayersModel(
193 * tf.io.browserFiles([jsonUpload.files[0], weightsUpload.files[0]]));
194 * ```
195 *
196 * @param pathOrIOHandler Can be either of the two formats
197 * 1. A string path to the `ModelAndWeightsConfig` JSON describing
198 * the model in the canonical TensorFlow.js format. For file://
199 * (tfjs-node-only), http:// and https:// schemas, the path can be
200 * either absolute or relative.
201 * 2. An `tf.io.IOHandler` object that loads model artifacts with its `load`
202 * method.
203 * @param options Optional configuration arguments for the model loading,
204 * including:
205 * - `strict`: Require that the provided weights exactly match those required
206 * by the layers. Default true. Passing false means that both extra
207 * weights and missing weights will be silently ignored.
208 * - `onProgress`: A function of the signature `(fraction: number) => void',
209 * that can be used as the progress callback for the model loading.
210 * @returns A `Promise` of `tf.LayersModel`, with the topology and weights
211 * loaded.
212 *
213 * @doc {heading: 'Models', subheading: 'Loading'}
214 */
215export function loadLayersModel(pathOrIOHandler, options) {
216 if (options == null) {
217 options = {};
218 }
219 return loadLayersModelInternal(pathOrIOHandler, options);
220}
221/**
222 * Used to instantiate an input to a model as a `tf.SymbolicTensor`.
223 *
224 * Users should call the `input` factory function for
225 * consistency with other generator functions.
226 *
227 * Example:
228 *
229 * ```js
230 * // Defines a simple logistic regression model with 32 dimensional input
231 * // and 3 dimensional output.
232 * const x = tf.input({shape: [32]});
233 * const y = tf.layers.dense({units: 3, activation: 'softmax'}).apply(x);
234 * const model = tf.model({inputs: x, outputs: y});
235 * model.predict(tf.ones([2, 32])).print();
236 * ```
237 *
238 * Note: `input` is only necessary when using `model`. When using
239 * `sequential`, specify `inputShape` for the first layer or use `inputLayer`
240 * as the first layer.
241 *
242 * @doc {heading: 'Models', subheading: 'Inputs'}
243 */
244export function input(config) {
245 return Input(config);
246}
247export function registerCallbackConstructor(verbosityLevel, callbackConstructor) {
248 CallbackConstructorRegistry.registerCallbackConstructor(verbosityLevel, callbackConstructor);
249}
250//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhwb3J0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3RmanMtbGF5ZXJzL3NyYy9leHBvcnRzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7OztHQVFHO0FBUUgsT0FBTyxFQUEwQiwyQkFBMkIsRUFBQyxNQUFNLGtCQUFrQixDQUFDO0FBRXRGLE9BQU8sRUFBQyxLQUFLLEdBQWUsTUFBTSxzQkFBc0IsQ0FBQztBQUV6RCxPQUFPLEVBQUMsV0FBVyxFQUFDLE1BQU0sbUJBQW1CLENBQUM7QUFDOUMsT0FBTyxFQUFDLHVCQUF1QixFQUFFLFVBQVUsRUFBaUIsTUFBTSxVQUFVLENBQUM7QUFFN0Usd0VBQXdFO0FBQ3hFLHdFQUF3RTtBQUN4RSxrQkFBa0I7QUFFbEIsMkNBQTJDO0FBRTNDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F1Q0c7QUFDSCxNQUFNLFVBQVUsS0FBSyxDQUFDLElBQW1CO0lBQ3ZDLE9BQU8sSUFBSSxXQUFXLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDL0IsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTJERztBQUNILE1BQU0sVUFBVSxVQUFVLENBQUMsTUFBdUI7SUFDaEQsT0FBTyxJQUFJLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQztBQUNoQyxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTBGRztBQUNILE1BQU0sVUFBVSxlQUFlLENBQzNCLGVBQW9DLEVBQ3BDLE9BQXdCO0lBQzFCLElBQUksT0FBTyxJQUFJLElBQUksRUFBRTtRQUNuQixPQUFPLEdBQUcsRUFBRSxDQUFDO0tBQ2Q7SUFDRCxPQUFPLHVCQUF1QixDQUFDLGVBQWUsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUMzRCxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FzQkc7QUFDSCxNQUFNLFVBQVUsS0FBSyxDQUFDLE1BQW1CO0lBQ3ZDLE9BQU8sS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBQ3ZCLENBQUM7QUFFRCxNQUFNLFVBQVUsMkJBQTJCLENBQ3ZDLGNBQXNCLEVBQ3RCLG1CQUE0QztJQUM5QywyQkFBMkIsQ0FBQywyQkFBMkIsQ0FDbkQsY0FBYyxFQUFFLG1CQUFtQixDQUFDLENBQUM7QUFDM0MsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCAyMDE4IEdvb2dsZSBMTENcbiAqXG4gKiBVc2Ugb2YgdGhpcyBzb3VyY2UgY29kZSBpcyBnb3Zlcm5lZCBieSBhbiBNSVQtc3R5bGVcbiAqIGxpY2Vuc2UgdGhhdCBjYW4gYmUgZm91bmQgaW4gdGhlIExJQ0VOU0UgZmlsZSBvciBhdFxuICogaHR0cHM6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9NSVQuXG4gKiA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuICovXG5cbi8qKlxuICogRXhwb3J0ZWQgZnVuY3Rpb25zLlxuICovXG5cbmltcG9ydCB7aW99IGZyb20gJ0B0ZW5zb3JmbG93L3RmanMtY29yZSc7XG5cbmltcG9ydCB7QmFzZUNhbGxiYWNrQ29uc3RydWN0b3IsIENhbGxiYWNrQ29uc3RydWN0b3JSZWdpc3RyeX0gZnJvbSAnLi9iYXNlX2NhbGxiYWNrcyc7XG5pbXBvcnQge0NvbnRhaW5lckFyZ3N9IGZyb20gJy4vZW5naW5lL2NvbnRhaW5lcic7XG5pbXBvcnQge0lucHV0LCBJbnB1dENvbmZpZyx9IGZyb20gJy4vZW5naW5lL2lucHV0X2xheWVyJztcbmltcG9ydCB7U3ltYm9saWNUZW5zb3J9IGZyb20gJy4vZW5naW5lL3RvcG9sb2d5JztcbmltcG9ydCB7TGF5ZXJzTW9kZWx9IGZyb20gJy4vZW5naW5lL3RyYWluaW5nJztcbmltcG9ydCB7bG9hZExheWVyc01vZGVsSW50ZXJuYWwsIFNlcXVlbnRpYWwsIFNlcXVlbnRpYWxBcmdzfSBmcm9tICcuL21vZGVscyc7XG5cbi8vIFRPRE8oY2Fpcyk6IEFkZCBkb2Mgc3RyaW5nIHRvIGFsbCB0aGUgcHVibGljIHN0YXRpYyBmdW5jdGlvbnMgaW4gdGhpc1xuLy8gICBjbGFzczsgaW5jbHVkZSBleGVjdHVhYmxlIEphdmFTY3JpcHQgY29kZSBzbmlwcGV0cyB3aGVyZSBhcHBsaWNhYmxlXG4vLyAgIChiLzc0MDc0NDU4KS5cblxuLy8gTGF5ZXJzTW9kZWwgYW5kIHJlbGF0ZWQgZmFjdG9yeSBtZXRob2RzLlxuXG4vKipcbiAqIEEgbW9kZWwgaXMgYSBkYXRhIHN0cnVjdHVyZSB0aGF0IGNvbnNpc3RzIG9mIGBMYXllcnNgIGFuZCBkZWZpbmVzIGlucHV0c1xuICogYW5kIG91dHB1dHMuXG4gKlxuICogVGhlIGtleSBkaWZmZXJlbmNlIGJldHdlZW4gYHRmLm1vZGVsYCBhbmQgYHRmLnNlcXVlbnRpYWxgIGlzIHRoYXRcbiAqIGB0Zi5tb2RlbGAgaXMgbW9yZSBnZW5lcmljLCBzdXBwb3J0aW5nIGFuIGFyYml0cmFyeSBncmFwaCAod2l0aG91dFxuICogY3ljbGVzKSBvZiBsYXllcnMuIGB0Zi5zZXF1ZW50aWFsYCBpcyBsZXNzIGdlbmVyaWMgYW5kIHN1cHBvcnRzIG9ubHkgYSBsaW5lYXJcbiAqIHN0YWNrIG9mIGxheWVycy5cbiAqXG4gKiBXaGVuIGNyZWF0aW5nIGEgYHRmLkxheWVyc01vZGVsYCwgc3BlY2lmeSBpdHMgaW5wdXQocykgYW5kIG91dHB1dChzKS4gTGF5ZXJzXG4gKiBhcmUgdXNlZCB0byB3aXJlIGlucHV0KHMpIHRvIG91dHB1dChzKS5cbiAqXG4gKiBGb3IgZXhhbXBsZSwgdGhlIGZvbGxvd2luZyBjb2RlIHNuaXBwZXQgZGVmaW5lcyBhIG1vZGVsIGNvbnNpc3Rpbmcgb2ZcbiAqIHR3byBgZGVuc2VgIGxheWVycywgd2l0aCAxMCBhbmQgNCB1bml0cywgcmVzcGVjdGl2ZWx5LlxuICpcbiAqIGBgYGpzXG4gKiAvLyBEZWZpbmUgaW5wdXQsIHdoaWNoIGhhcyBhIHNpemUgb2YgNSAobm90IGluY2x1ZGluZyBiYXRjaCBkaW1lbnNpb24pLlxuICogY29uc3QgaW5wdXQgPSB0Zi5pbnB1dCh7c2hhcGU6IFs1XX0pO1xuICpcbiAqIC8vIEZpcnN0IGRlbnNlIGxheWVyIHVzZXMgcmVsdSBhY3RpdmF0aW9uLlxuICogY29uc3QgZGVuc2VMYXllcjEgPSB0Zi5sYXllcnMuZGVuc2Uoe3VuaXRzOiAxMCwgYWN0aXZhdGlvbjogJ3JlbHUnfSk7XG4gKiAvLyBTZWNvbmQgZGVuc2UgbGF5ZXIgdXNlcyBzb2Z0bWF4IGFjdGl2YXRpb24uXG4gKiBjb25zdCBkZW5zZUxheWVyMiA9IHRmLmxheWVycy5kZW5zZSh7dW5pdHM6IDQsIGFjdGl2YXRpb246ICdzb2Z0bWF4J30pO1xuICpcbiAqIC8vIE9idGFpbiB0aGUgb3V0cHV0IHN5bWJvbGljIHRlbnNvciBieSBhcHBseWluZyB0aGUgbGF5ZXJzIG9uIHRoZSBpbnB1dC5cbiAqIGNvbnN0IG91dHB1dCA9IGRlbnNlTGF5ZXIyLmFwcGx5KGRlbnNlTGF5ZXIxLmFwcGx5KGlucHV0KSk7XG4gKlxuICogLy8gQ3JlYXRlIHRoZSBtb2RlbCBiYXNlZCBvbiB0aGUgaW5wdXRzLlxuICogY29uc3QgbW9kZWwgPSB0Zi5tb2RlbCh7aW5wdXRzOiBpbnB1dCwgb3V0cHV0czogb3V0cHV0fSk7XG4gKlxuICogLy8gVGhlIG1vZGVsIGNhbiBiZSB1c2VkIGZvciB0cmFpbmluZywgZXZhbHVhdGlvbiBhbmQgcHJlZGljdGlvbi5cbiAqIC8vIEZvciBleGFtcGxlLCB0aGUgZm9sbG93aW5nIGxpbmUgcnVucyBwcmVkaWN0aW9uIHdpdGggdGhlIG1vZGVsIG9uXG4gKiAvLyBzb21lIGZha2UgZGF0YS5cbiAqIG1vZGVsLnByZWRpY3QodGYub25lcyhbMiwgNV0pKS5wcmludCgpO1xuICogYGBgXG4gKiBTZWUgYWxzbzpcbiAqICAgYHRmLnNlcXVlbnRpYWxgLCBgdGYubG9hZExheWVyc01vZGVsYC5cbiAqXG4gKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0NyZWF0aW9uJ31cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG1vZGVsKGFyZ3M6IENvbnRhaW5lckFyZ3MpOiBMYXllcnNNb2RlbCB7XG4gIHJldHVybiBuZXcgTGF5ZXJzTW9kZWwoYXJncyk7XG59XG5cbi8qKlxuICogQ3JlYXRlcyBhIGB0Zi5TZXF1ZW50aWFsYCBtb2RlbC4gIEEgc2VxdWVudGlhbCBtb2RlbCBpcyBhbnkgbW9kZWwgd2hlcmUgdGhlXG4gKiBvdXRwdXRzIG9mIG9uZSBsYXllciBhcmUgdGhlIGlucHV0cyB0byB0aGUgbmV4dCBsYXllciwgaS5lLiB0aGUgbW9kZWxcbiAqIHRvcG9sb2d5IGlzIGEgc2ltcGxlICdzdGFjaycgb2YgbGF5ZXJzLCB3aXRoIG5vIGJyYW5jaGluZyBvciBza2lwcGluZy5cbiAqXG4gKiBUaGlzIG1lYW5zIHRoYXQgdGhlIGZpcnN0IGxheWVyIHBhc3NlZCB0byBhIGB0Zi5TZXF1ZW50aWFsYCBtb2RlbCBzaG91bGQgaGF2ZVxuICogYSBkZWZpbmVkIGlucHV0IHNoYXBlLiBXaGF0IHRoYXQgbWVhbnMgaXMgdGhhdCBpdCBzaG91bGQgaGF2ZSByZWNlaXZlZCBhblxuICogYGlucHV0U2hhcGVgIG9yIGBiYXRjaElucHV0U2hhcGVgIGFyZ3VtZW50LCBvciBmb3Igc29tZSB0eXBlIG9mIGxheWVyc1xuICogKHJlY3VycmVudCwgRGVuc2UuLi4pIGFuIGBpbnB1dERpbWAgYXJndW1lbnQuXG4gKlxuICogVGhlIGtleSBkaWZmZXJlbmNlIGJldHdlZW4gYHRmLm1vZGVsYCBhbmQgYHRmLnNlcXVlbnRpYWxgIGlzIHRoYXRcbiAqIGB0Zi5zZXF1ZW50aWFsYCBpcyBsZXNzIGdlbmVyaWMsIHN1cHBvcnRpbmcgb25seSBhIGxpbmVhciBzdGFjayBvZiBsYXllcnMuXG4gKiBgdGYubW9kZWxgIGlzIG1vcmUgZ2VuZXJpYyBhbmQgc3VwcG9ydHMgYW4gYXJiaXRyYXJ5IGdyYXBoICh3aXRob3V0XG4gKiBjeWNsZXMpIG9mIGxheWVycy5cbiAqXG4gKiBFeGFtcGxlczpcbiAqXG4gKiBgYGBqc1xuICogY29uc3QgbW9kZWwgPSB0Zi5zZXF1ZW50aWFsKCk7XG4gKlxuICogLy8gRmlyc3QgbGF5ZXIgbXVzdCBoYXZlIGFuIGlucHV0IHNoYXBlIGRlZmluZWQuXG4gKiBtb2RlbC5hZGQodGYubGF5ZXJzLmRlbnNlKHt1bml0czogMzIsIGlucHV0U2hhcGU6IFs1MF19KSk7XG4gKiAvLyBBZnRlcndhcmRzLCBURi5qcyBkb2VzIGF1dG9tYXRpYyBzaGFwZSBpbmZlcmVuY2UuXG4gKiBtb2RlbC5hZGQodGYubGF5ZXJzLmRlbnNlKHt1bml0czogNH0pKTtcbiAqXG4gKiAvLyBJbnNwZWN0IHRoZSBpbmZlcnJlZCBzaGFwZSBvZiB0aGUgbW9kZWwncyBvdXRwdXQsIHdoaWNoIGVxdWFsc1xuICogLy8gYFtudWxsLCA0XWAuIFRoZSAxc3QgZGltZW5zaW9uIGlzIHRoZSB1bmRldGVybWluZWQgYmF0Y2ggZGltZW5zaW9uOyB0aGVcbiAqIC8vIDJuZCBpcyB0aGUgb3V0cHV0IHNpemUgb2YgdGhlIG1vZGVsJ3MgbGFzdCBsYXllci5cbiAqIGNvbnNvbGUubG9nKEpTT04uc3RyaW5naWZ5KG1vZGVsLm91dHB1dHNbMF0uc2hhcGUpKTtcbiAqIGBgYFxuICpcbiAqIEl0IGlzIGFsc28gcG9zc2libGUgdG8gc3BlY2lmeSBhIGJhdGNoIHNpemUgKHdpdGggcG90ZW50aWFsbHkgdW5kZXRlcm1pbmVkXG4gKiBiYXRjaCBkaW1lbnNpb24sIGRlbm90ZWQgYnkgXCJudWxsXCIpIGZvciB0aGUgZmlyc3QgbGF5ZXIgdXNpbmcgdGhlXG4gKiBgYmF0Y2hJbnB1dFNoYXBlYCBrZXkuIFRoZSBmb2xsb3dpbmcgZXhhbXBsZSBpcyBlcXVpdmFsZW50IHRvIHRoZSBhYm92ZTpcbiAqXG4gKiBgYGBqc1xuICogY29uc3QgbW9kZWwgPSB0Zi5zZXF1ZW50aWFsKCk7XG4gKlxuICogLy8gRmlyc3QgbGF5ZXIgbXVzdCBoYXZlIGEgZGVmaW5lZCBpbnB1dCBzaGFwZVxuICogbW9kZWwuYWRkKHRmLmxheWVycy5kZW5zZSh7dW5pdHM6IDMyLCBiYXRjaElucHV0U2hhcGU6IFtudWxsLCA1MF19KSk7XG4gKiAvLyBBZnRlcndhcmRzLCBURi5qcyBkb2VzIGF1dG9tYXRpYyBzaGFwZSBpbmZlcmVuY2UuXG4gKiBtb2RlbC5hZGQodGYubGF5ZXJzLmRlbnNlKHt1bml0czogNH0pKTtcbiAqXG4gKiAvLyBJbnNwZWN0IHRoZSBpbmZlcnJlZCBzaGFwZSBvZiB0aGUgbW9kZWwncyBvdXRwdXQuXG4gKiBjb25zb2xlLmxvZyhKU09OLnN0cmluZ2lmeShtb2RlbC5vdXRwdXRzWzBdLnNoYXBlKSk7XG4gKiBgYGBcbiAqXG4gKiBZb3UgY2FuIGFsc28gdXNlIGFuIGBBcnJheWAgb2YgYWxyZWFkeS1jb25zdHJ1Y3RlZCBgTGF5ZXJgcyB0byBjcmVhdGVcbiAqIGEgYHRmLlNlcXVlbnRpYWxgIG1vZGVsOlxuICpcbiAqIGBgYGpzXG4gKiBjb25zdCBtb2RlbCA9IHRmLnNlcXVlbnRpYWwoe1xuICogICBsYXllcnM6IFt0Zi5sYXllcnMuZGVuc2Uoe3VuaXRzOiAzMiwgaW5wdXRTaGFwZTogWzUwXX0pLFxuICogICAgICAgICAgICB0Zi5sYXllcnMuZGVuc2Uoe3VuaXRzOiA0fSldXG4gKiB9KTtcbiAqIGNvbnNvbGUubG9nKEpTT04uc3RyaW5naWZ5KG1vZGVsLm91dHB1dHNbMF0uc2hhcGUpKTtcbiAqIGBgYFxuICpcbiAqIEBkb2Mge2hlYWRpbmc6ICdNb2RlbHMnLCBzdWJoZWFkaW5nOiAnQ3JlYXRpb24nfVxuICovXG5leHBvcnQgZnVuY3Rpb24gc2VxdWVudGlhbChjb25maWc/OiBTZXF1ZW50aWFsQXJncyk6IFNlcXVlbnRpYWwge1xuICByZXR1cm4gbmV3IFNlcXVlbnRpYWwoY29uZmlnKTtcbn1cblxuLyoqXG4gKiBMb2FkIGEgbW9kZWwgY29tcG9zZWQgb2YgTGF5ZXIgb2JqZWN0cywgaW5jbHVkaW5nIGl0cyB0b3BvbG9neSBhbmQgb3B0aW9uYWxseVxuICogd2VpZ2h0cy4gU2VlIHRoZSBUdXRvcmlhbCBuYW1lZCBcIkhvdyB0byBpbXBvcnQgYSBLZXJhcyBNb2RlbFwiIGZvciB1c2FnZVxuICogZXhhbXBsZXMuXG4gKlxuICogVGhpcyBtZXRob2QgaXMgYXBwbGljYWJsZSB0bzpcbiAqXG4gKiAxLiBNb2RlbHMgY3JlYXRlZCB3aXRoIHRoZSBgdGYubGF5ZXJzLipgLCBgdGYuc2VxdWVudGlhbGAsIGFuZFxuICogYHRmLm1vZGVsYCBBUElzIG9mIFRlbnNvckZsb3cuanMgYW5kIGxhdGVyIHNhdmVkIHdpdGggdGhlXG4gKiBgdGYuTGF5ZXJzTW9kZWwuc2F2ZWAgbWV0aG9kLlxuICogMi4gTW9kZWxzIGNvbnZlcnRlZCBmcm9tIEtlcmFzIG9yIFRlbnNvckZsb3cgdGYua2VyYXMgdXNpbmcgdGhlXG4gKiBbdGVuc29yZmxvd2pzX2NvbnZlcnRlcl0oaHR0cHM6Ly9naXRodWIuY29tL3RlbnNvcmZsb3cvdGZqcy90cmVlL21hc3Rlci90ZmpzLWNvbnZlcnRlcikuXG4gKlxuICogVGhpcyBtb2RlIGlzICpub3QqIGFwcGxpY2FibGUgdG8gVGVuc29yRmxvdyBgU2F2ZWRNb2RlbGBzIG9yIHRoZWlyIGNvbnZlcnRlZFxuICogZm9ybXMuIEZvciB0aG9zZSBtb2RlbHMsIHVzZSBgdGYubG9hZEdyYXBoTW9kZWxgLlxuICpcbiAqIEV4YW1wbGUgMS4gTG9hZCBhIG1vZGVsIGZyb20gYW4gSFRUUCBzZXJ2ZXIuXG4gKlxuICogYGBganNcbiAqIGNvbnN0IG1vZGVsID0gYXdhaXQgdGYubG9hZExheWVyc01vZGVsKFxuICogICAgICdodHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb20vdGZqcy1tb2RlbHMvdGZqcy9pcmlzX3YxL21vZGVsLmpzb24nKTtcbiAqIG1vZGVsLnN1bW1hcnkoKTtcbiAqIGBgYFxuICpcbiAqIEV4YW1wbGUgMjogU2F2ZSBgbW9kZWxgJ3MgdG9wb2xvZ3kgYW5kIHdlaWdodHMgdG8gYnJvd3NlciBbbG9jYWxcbiAqIHN0b3JhZ2VdKGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9XaW5kb3cvbG9jYWxTdG9yYWdlKTtcbiAqIHRoZW4gbG9hZCBpdCBiYWNrLlxuICpcbiAqIGBgYGpzXG4gKiBjb25zdCBtb2RlbCA9IHRmLnNlcXVlbnRpYWwoXG4gKiAgICAge2xheWVyczogW3RmLmxheWVycy5kZW5zZSh7dW5pdHM6IDEsIGlucHV0U2hhcGU6IFszXX0pXX0pO1xuICogY29uc29sZS5sb2coJ1ByZWRpY3Rpb24gZnJvbSBvcmlnaW5hbCBtb2RlbDonKTtcbiAqIG1vZGVsLnByZWRpY3QodGYub25lcyhbMSwgM10pKS5wcmludCgpO1xuICpcbiAqIGNvbnN0IHNhdmVSZXN1bHRzID0gYXdhaXQgbW9kZWwuc2F2ZSgnbG9jYWxzdG9yYWdlOi8vbXktbW9kZWwtMScpO1xuICpcbiAqIGNvbnN0IGxvYWRlZE1vZGVsID0gYXdhaXQgdGYubG9hZExheWVyc01vZGVsKCdsb2NhbHN0b3JhZ2U6Ly9teS1tb2RlbC0xJyk7XG4gKiBjb25zb2xlLmxvZygnUHJlZGljdGlvbiBmcm9tIGxvYWRlZCBtb2RlbDonKTtcbiAqIGxvYWRlZE1vZGVsLnByZWRpY3QodGYub25lcyhbMSwgM10pKS5wcmludCgpO1xuICogYGBgXG4gKlxuICogRXhhbXBsZSAzLiBTYXZpbmcgYG1vZGVsYCdzIHRvcG9sb2d5IGFuZCB3ZWlnaHRzIHRvIGJyb3dzZXJcbiAqIFtJbmRleGVkREJdKGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9JbmRleGVkREJfQVBJKTtcbiAqIHRoZW4gbG9hZCBpdCBiYWNrLlxuICpcbiAqIGBgYGpzXG4gKiBjb25zdCBtb2RlbCA9IHRmLnNlcXVlbnRpYWwoXG4gKiAgICAge2xheWVyczogW3RmLmxheWVycy5kZW5zZSh7dW5pdHM6IDEsIGlucHV0U2hhcGU6IFszXX0pXX0pO1xuICogY29uc29sZS5sb2coJ1ByZWRpY3Rpb24gZnJvbSBvcmlnaW5hbCBtb2RlbDonKTtcbiAqIG1vZGVsLnByZWRpY3QodGYub25lcyhbMSwgM10pKS5wcmludCgpO1xuICpcbiAqIGNvbnN0IHNhdmVSZXN1bHRzID0gYXdhaXQgbW9kZWwuc2F2ZSgnaW5kZXhlZGRiOi8vbXktbW9kZWwtMScpO1xuICpcbiAqIGNvbnN0IGxvYWRlZE1vZGVsID0gYXdhaXQgdGYubG9hZExheWVyc01vZGVsKCdpbmRleGVkZGI6Ly9teS1tb2RlbC0xJyk7XG4gKiBjb25zb2xlLmxvZygnUHJlZGljdGlvbiBmcm9tIGxvYWRlZCBtb2RlbDonKTtcbiAqIGxvYWRlZE1vZGVsLnByZWRpY3QodGYub25lcyhbMSwgM10pKS5wcmludCgpO1xuICogYGBgXG4gKlxuICogRXhhbXBsZSA0LiBMb2FkIGEgbW9kZWwgZnJvbSB1c2VyLXNlbGVjdGVkIGZpbGVzIGZyb20gSFRNTFxuICogW2ZpbGUgaW5wdXRcbiAqIGVsZW1lbnRzXShodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9IVE1ML0VsZW1lbnQvaW5wdXQvZmlsZSkuXG4gKlxuICogYGBganNcbiAqIC8vIE5vdGU6IHRoaXMgY29kZSBzbmlwcGV0IHdpbGwgbm90IHdvcmsgd2l0aG91dCB0aGUgSFRNTCBlbGVtZW50cyBpbiB0aGVcbiAqIC8vICAgcGFnZVxuICogY29uc3QganNvblVwbG9hZCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdqc29uLXVwbG9hZCcpO1xuICogY29uc3Qgd2VpZ2h0c1VwbG9hZCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd3ZWlnaHRzLXVwbG9hZCcpO1xuICpcbiAqIGNvbnN0IG1vZGVsID0gYXdhaXQgdGYubG9hZExheWVyc01vZGVsKFxuICogICAgIHRmLmlvLmJyb3dzZXJGaWxlcyhbanNvblVwbG9hZC5maWxlc1swXSwgd2VpZ2h0c1VwbG9hZC5maWxlc1swXV0pKTtcbiAqIGBgYFxuICpcbiAqIEBwYXJhbSBwYXRoT3JJT0hhbmRsZXIgQ2FuIGJlIGVpdGhlciBvZiB0aGUgdHdvIGZvcm1hdHNcbiAqICAgMS4gQSBzdHJpbmcgcGF0aCB0byB0aGUgYE1vZGVsQW5kV2VpZ2h0c0NvbmZpZ2AgSlNPTiBkZXNjcmliaW5nXG4gKiAgICAgIHRoZSBtb2RlbCBpbiB0aGUgY2Fub25pY2FsIFRlbnNvckZsb3cuanMgZm9ybWF0LiBGb3IgZmlsZTovL1xuICogICAgICAodGZqcy1ub2RlLW9ubHkpLCBodHRwOi8vIGFuZCBodHRwczovLyBzY2hlbWFzLCB0aGUgcGF0aCBjYW4gYmVcbiAqICAgICAgZWl0aGVyIGFic29sdXRlIG9yIHJlbGF0aXZlLlxuICogICAyLiBBbiBgdGYuaW8uSU9IYW5kbGVyYCBvYmplY3QgdGhhdCBsb2FkcyBtb2RlbCBhcnRpZmFjdHMgd2l0aCBpdHMgYGxvYWRgXG4gKiAgICAgIG1ldGhvZC5cbiAqIEBwYXJhbSBvcHRpb25zIE9wdGlvbmFsIGNvbmZpZ3VyYXRpb24gYXJndW1lbnRzIGZvciB0aGUgbW9kZWwgbG9hZGluZyxcbiAqICAgaW5jbHVkaW5nOlxuICogICAtIGBzdHJpY3RgOiBSZXF1aXJlIHRoYXQgdGhlIHByb3ZpZGVkIHdlaWdodHMgZXhhY3RseSBtYXRjaCB0aG9zZSByZXF1aXJlZFxuICogICAgIGJ5IHRoZSBsYXllcnMuICBEZWZhdWx0IHRydWUuICBQYXNzaW5nIGZhbHNlIG1lYW5zIHRoYXQgYm90aCBleHRyYVxuICogICAgIHdlaWdodHMgYW5kIG1pc3Npbmcgd2VpZ2h0cyB3aWxsIGJlIHNpbGVudGx5IGlnbm9yZWQuXG4gKiAgIC0gYG9uUHJvZ3Jlc3NgOiBBIGZ1bmN0aW9uIG9mIHRoZSBzaWduYXR1cmUgYChmcmFjdGlvbjogbnVtYmVyKSA9PiB2b2lkJyxcbiAqICAgICB0aGF0IGNhbiBiZSB1c2VkIGFzIHRoZSBwcm9ncmVzcyBjYWxsYmFjayBmb3IgdGhlIG1vZGVsIGxvYWRpbmcuXG4gKiBAcmV0dXJucyBBIGBQcm9taXNlYCBvZiBgdGYuTGF5ZXJzTW9kZWxgLCB3aXRoIHRoZSB0b3BvbG9neSBhbmQgd2VpZ2h0c1xuICogICAgIGxvYWRlZC5cbiAqXG4gKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0xvYWRpbmcnfVxuICovXG5leHBvcnQgZnVuY3Rpb24gbG9hZExheWVyc01vZGVsKFxuICAgIHBhdGhPcklPSGFuZGxlcjogc3RyaW5nfGlvLklPSGFuZGxlcixcbiAgICBvcHRpb25zPzogaW8uTG9hZE9wdGlvbnMpOiBQcm9taXNlPExheWVyc01vZGVsPiB7XG4gIGlmIChvcHRpb25zID09IG51bGwpIHtcbiAgICBvcHRpb25zID0ge307XG4gIH1cbiAgcmV0dXJuIGxvYWRMYXllcnNNb2RlbEludGVybmFsKHBhdGhPcklPSGFuZGxlciwgb3B0aW9ucyk7XG59XG5cbi8qKlxuICogVXNlZCB0byBpbnN0YW50aWF0ZSBhbiBpbnB1dCB0byBhIG1vZGVsIGFzIGEgYHRmLlN5bWJvbGljVGVuc29yYC5cbiAqXG4gKiBVc2VycyBzaG91bGQgY2FsbCB0aGUgYGlucHV0YCBmYWN0b3J5IGZ1bmN0aW9uIGZvclxuICogY29uc2lzdGVuY3kgd2l0aCBvdGhlciBnZW5lcmF0b3IgZnVuY3Rpb25zLlxuICpcbiAqIEV4YW1wbGU6XG4gKlxuICogYGBganNcbiAqIC8vIERlZmluZXMgYSBzaW1wbGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB3aXRoIDMyIGRpbWVuc2lvbmFsIGlucHV0XG4gKiAvLyBhbmQgMyBkaW1lbnNpb25hbCBvdXRwdXQuXG4gKiBjb25zdCB4ID0gdGYuaW5wdXQoe3NoYXBlOiBbMzJdfSk7XG4gKiBjb25zdCB5ID0gdGYubGF5ZXJzLmRlbnNlKHt1bml0czogMywgYWN0aXZhdGlvbjogJ3NvZnRtYXgnfSkuYXBwbHkoeCk7XG4gKiBjb25zdCBtb2RlbCA9IHRmLm1vZGVsKHtpbnB1dHM6IHgsIG91dHB1dHM6IHl9KTtcbiAqIG1vZGVsLnByZWRpY3QodGYub25lcyhbMiwgMzJdKSkucHJpbnQoKTtcbiAqIGBgYFxuICpcbiAqIE5vdGU6IGBpbnB1dGAgaXMgb25seSBuZWNlc3Nhcnkgd2hlbiB1c2luZyBgbW9kZWxgLiBXaGVuIHVzaW5nXG4gKiBgc2VxdWVudGlhbGAsIHNwZWNpZnkgYGlucHV0U2hhcGVgIGZvciB0aGUgZmlyc3QgbGF5ZXIgb3IgdXNlIGBpbnB1dExheWVyYFxuICogYXMgdGhlIGZpcnN0IGxheWVyLlxuICpcbiAqIEBkb2Mge2hlYWRpbmc6ICdNb2RlbHMnLCBzdWJoZWFkaW5nOiAnSW5wdXRzJ31cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlucHV0KGNvbmZpZzogSW5wdXRDb25maWcpOiBTeW1ib2xpY1RlbnNvciB7XG4gIHJldHVybiBJbnB1dChjb25maWcpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcmVnaXN0ZXJDYWxsYmFja0NvbnN0cnVjdG9yKFxuICAgIHZlcmJvc2l0eUxldmVsOiBudW1iZXIsXG4gICAgY2FsbGJhY2tDb25zdHJ1Y3RvcjogQmFzZUNhbGxiYWNrQ29uc3RydWN0b3IpOiB2b2lkIHtcbiAgQ2FsbGJhY2tDb25zdHJ1Y3RvclJlZ2lzdHJ5LnJlZ2lzdGVyQ2FsbGJhY2tDb25zdHJ1Y3RvcihcbiAgICAgIHZlcmJvc2l0eUxldmVsLCBjYWxsYmFja0NvbnN0cnVjdG9yKTtcbn1cbiJdfQ==
\No newline at end of file