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 | */
|
10 | import { CallbackConstructorRegistry } from './base_callbacks';
|
11 | import { Input, } from './engine/input_layer';
|
12 | import { LayersModel } from './engine/training';
|
13 | import { 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 | */
|
58 | export 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 | */
|
121 | export 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 | */
|
215 | export 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 | */
|
244 | export function input(config) {
|
245 | return Input(config);
|
246 | }
|
247 | export function registerCallbackConstructor(verbosityLevel, callbackConstructor) {
|
248 | CallbackConstructorRegistry.registerCallbackConstructor(verbosityLevel, callbackConstructor);
|
249 | }
|
250 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhwb3J0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3RmanMtbGF5ZXJzL3NyYy9leHBvcnRzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7OztHQVFHO0FBUUgsT0FBTyxFQUEwQiwyQkFBMkIsRUFBQyxNQUFNLGtCQUFrQixDQUFDO0FBRXRGLE9BQU8sRUFBQyxLQUFLLEdBQWUsTUFBTSxzQkFBc0IsQ0FBQztBQUV6RCxPQUFPLEVBQUMsV0FBVyxFQUFDLE1BQU0sbUJBQW1CLENBQUM7QUFDOUMsT0FBTyxFQUFDLHVCQUF1QixFQUFFLFVBQVUsRUFBaUIsTUFBTSxVQUFVLENBQUM7QUFFN0Usd0VBQXdFO0FBQ3hFLHdFQUF3RTtBQUN4RSxrQkFBa0I7QUFFbEIsMkNBQTJDO0FBRTNDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F1Q0c7QUFDSCxNQUFNLFVBQVUsS0FBSyxDQUFDLElBQW1CO0lBQ3ZDLE9BQU8sSUFBSSxXQUFXLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDL0IsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTJERztBQUNILE1BQU0sVUFBVSxVQUFVLENBQUMsTUFBdUI7SUFDaEQsT0FBTyxJQUFJLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQztBQUNoQyxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTBGRztBQUNILE1BQU0sVUFBVSxlQUFlLENBQzNCLGVBQW9DLEVBQ3BDLE9BQXdCO0lBQzFCLElBQUksT0FBTyxJQUFJLElBQUksRUFBRTtRQUNuQixPQUFPLEdBQUcsRUFBRSxDQUFDO0tBQ2Q7SUFDRCxPQUFPLHVCQUF1QixDQUFDLGVBQWUsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUMzRCxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FzQkc7QUFDSCxNQUFNLFVBQVUsS0FBSyxDQUFDLE1BQW1CO0lBQ3ZDLE9BQU8sS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBQ3ZCLENBQUM7QUFFRCxNQUFNLFVBQVUsMkJBQTJCLENBQ3ZDLGNBQXNCLEVBQ3RCLG1CQUE0QztJQUM5QywyQkFBMkIsQ0FBQywyQkFBMkIsQ0FDbkQsY0FBYyxFQUFFLG1CQUFtQixDQUFDLENBQUM7QUFDM0MsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCAyMDE4IEdvb2dsZSBMTENcbiAqXG4gKiBVc2Ugb2YgdGhpcyBzb3VyY2UgY29kZSBpcyBnb3Zlcm5lZCBieSBhbiBNSVQtc3R5bGVcbiAqIGxpY2Vuc2UgdGhhdCBjYW4gYmUgZm91bmQgaW4gdGhlIExJQ0VOU0UgZmlsZSBvciBhdFxuICogaHR0cHM6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9NSVQuXG4gKiA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuICovXG5cbi8qKlxuICogRXhwb3J0ZWQgZnVuY3Rpb25zLlxuICovXG5cbmltcG9ydCB7aW99IGZyb20gJ0B0ZW5zb3JmbG93L3RmanMtY29yZSc7XG5cbmltcG9ydCB7QmFzZUNhbGxiYWNrQ29uc3RydWN0b3IsIENhbGxiYWNrQ29uc3RydWN0b3JSZWdpc3RyeX0gZnJvbSAnLi9iYXNlX2NhbGxiYWNrcyc7XG5pbXBvcnQge0NvbnRhaW5lckFyZ3N9IGZyb20gJy4vZW5naW5lL2NvbnRhaW5lcic7XG5pbXBvcnQge0lucHV0LCBJbnB1dENvbmZpZyx9IGZyb20gJy4vZW5naW5lL2lucHV0X2xheWVyJztcbmltcG9ydCB7U3ltYm9saWNUZW5zb3J9IGZyb20gJy4vZW5naW5lL3RvcG9sb2d5JztcbmltcG9ydCB7TGF5ZXJzTW9kZWx9IGZyb20gJy4vZW5naW5lL3RyYWluaW5nJztcbmltcG9ydCB7bG9hZExheWVyc01vZGVsSW50ZXJuYWwsIFNlcXVlbnRpYWwsIFNlcXVlbnRpYWxBcmdzfSBmcm9tICcuL21vZGVscyc7XG5cbi8vIFRPRE8oY2Fpcyk6IEFkZCBkb2Mgc3RyaW5nIHRvIGFsbCB0aGUgcHVibGljIHN0YXRpYyBmdW5jdGlvbnMgaW4gdGhpc1xuLy8gICBjbGFzczsgaW5jbHVkZSBleGVjdHVhYmxlIEphdmFTY3JpcHQgY29kZSBzbmlwcGV0cyB3aGVyZSBhcHBsaWNhYmxlXG4vLyAgIChiLzc0MDc0NDU4KS5cblxuLy8gTGF5ZXJzTW9kZWwgYW5kIHJlbGF0ZWQgZmFjdG9yeSBtZXRob2RzLlxuXG4vKipcbiAqIEEgbW9kZWwgaXMgYSBkYXRhIHN0cnVjdHVyZSB0aGF0IGNvbnNpc3RzIG9mIGBMYXllcnNgIGFuZCBkZWZpbmVzIGlucHV0c1xuICogYW5kIG91dHB1dHMuXG4gKlxuICogVGhlIGtleSBkaWZmZXJlbmNlIGJldHdlZW4gYHRmLm1vZGVsYCBhbmQgYHRmLnNlcXVlbnRpYWxgIGlzIHRoYXRcbiAqIGB0Zi5tb2RlbGAgaXMgbW9yZSBnZW5lcmljLCBzdXBwb3J0aW5nIGFuIGFyYml0cmFyeSBncmFwaCAod2l0aG91dFxuICogY3ljbGVzKSBvZiBsYXllcnMuIGB0Zi5zZXF1ZW50aWFsYCBpcyBsZXNzIGdlbmVyaWMgYW5kIHN1cHBvcnRzIG9ubHkgYSBsaW5lYXJcbiAqIHN0YWNrIG9mIGxheWVycy5cbiAqXG4gKiBXaGVuIGNyZWF0aW5nIGEgYHRmLkxheWVyc01vZGVsYCwgc3BlY2lmeSBpdHMgaW5wdXQocykgYW5kIG91dHB1dChzKS4gTGF5ZXJzXG4gKiBhcmUgdXNlZCB0byB3aXJlIGlucHV0KHMpIHRvIG91dHB1dChzKS5cbiAqXG4gKiBGb3IgZXhhbXBsZSwgdGhlIGZvbGxvd2luZyBjb2RlIHNuaXBwZXQgZGVmaW5lcyBhIG1vZGVsIGNvbnNpc3Rpbmcgb2ZcbiAqIHR3byBgZGVuc2VgIGxheWVycywgd2l0aCAxMCBhbmQgNCB1bml0cywgcmVzcGVjdGl2ZWx5LlxuICpcbiAqIGBgYGpzXG4gKiAvLyBEZWZpbmUgaW5wdXQsIHdoaWNoIGhhcyBhIHNpemUgb2YgNSAobm90IGluY2x1ZGluZyBiYXRjaCBkaW1lbnNpb24pLlxuICogY29uc3QgaW5wdXQgPSB0Zi5pbnB1dCh7c2hhcGU6IFs1XX0pO1xuICpcbiAqIC8vIEZpcnN0IGRlbnNlIGxheWVyIHVzZXMgcmVsdSBhY3RpdmF0aW9uLlxuICogY29uc3QgZGVuc2VMYXllcjEgPSB0Zi5sYXllcnMuZGVuc2Uoe3VuaXRzOiAxMCwgYWN0aXZhdGlvbjogJ3JlbHUnfSk7XG4gKiAvLyBTZWNvbmQgZGVuc2UgbGF5ZXIgdXNlcyBzb2Z0bWF4IGFjdGl2YXRpb24uXG4gKiBjb25zdCBkZW5zZUxheWVyMiA9IHRmLmxheWVycy5kZW5zZSh7dW5pdHM6IDQsIGFjdGl2YXRpb246ICdzb2Z0bWF4J30pO1xuICpcbiAqIC8vIE9idGFpbiB0aGUgb3V0cHV0IHN5bWJvbGljIHRlbnNvciBieSBhcHBseWluZyB0aGUgbGF5ZXJzIG9uIHRoZSBpbnB1dC5cbiAqIGNvbnN0IG91dHB1dCA9IGRlbnNlTGF5ZXIyLmFwcGx5KGRlbnNlTGF5ZXIxLmFwcGx5KGlucHV0KSk7XG4gKlxuICogLy8gQ3JlYXRlIHRoZSBtb2RlbCBiYXNlZCBvbiB0aGUgaW5wdXRzLlxuICogY29uc3QgbW9kZWwgPSB0Zi5tb2RlbCh7aW5wdXRzOiBpbnB1dCwgb3V0cHV0czogb3V0cHV0fSk7XG4gKlxuICogLy8gVGhlIG1vZGVsIGNhbiBiZSB1c2VkIGZvciB0cmFpbmluZywgZXZhbHVhdGlvbiBhbmQgcHJlZGljdGlvbi5cbiAqIC8vIEZvciBleGFtcGxlLCB0aGUgZm9sbG93aW5nIGxpbmUgcnVucyBwcmVkaWN0aW9uIHdpdGggdGhlIG1vZGVsIG9uXG4gKiAvLyBzb21lIGZha2UgZGF0YS5cbiAqIG1vZGVsLnByZWRpY3QodGYub25lcyhbMiwgNV0pKS5wcmludCgpO1xuICogYGBgXG4gKiBTZWUgYWxzbzpcbiAqICAgYHRmLnNlcXVlbnRpYWxgLCBgdGYubG9hZExheWVyc01vZGVsYC5cbiAqXG4gKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0NyZWF0aW9uJ31cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG1vZGVsKGFyZ3M6IENvbnRhaW5lckFyZ3MpOiBMYXllcnNNb2RlbCB7XG4gIHJldHVybiBuZXcgTGF5ZXJzTW9kZWwoYXJncyk7XG59XG5cbi8qKlxuICogQ3JlYXRlcyBhIGB0Zi5TZXF1ZW50aWFsYCBtb2RlbC4gIEEgc2VxdWVudGlhbCBtb2RlbCBpcyBhbnkgbW9kZWwgd2hlcmUgdGhlXG4gKiBvdXRwdXRzIG9mIG9uZSBsYXllciBhcmUgdGhlIGlucHV0cyB0byB0aGUgbmV4dCBsYXllciwgaS5lLiB0aGUgbW9kZWxcbiAqIHRvcG9sb2d5IGlzIGEgc2ltcGxlICdzdGFjaycgb2YgbGF5ZXJzLCB3aXRoIG5vIGJyYW5jaGluZyBvciBza2lwcGluZy5cbiAqXG4gKiBUaGlzIG1lYW5zIHRoYXQgdGhlIGZpcnN0IGxheWVyIHBhc3NlZCB0byBhIGB0Zi5TZXF1ZW50aWFsYCBtb2RlbCBzaG91bGQgaGF2ZVxuICogYSBkZWZpbmVkIGlucHV0IHNoYXBlLiBXaGF0IHRoYXQgbWVhbnMgaXMgdGhhdCBpdCBzaG91bGQgaGF2ZSByZWNlaXZlZCBhblxuICogYGlucHV0U2hhcGVgIG9yIGBiYXRjaElucHV0U2hhcGVgIGFyZ3VtZW50LCBvciBmb3Igc29tZSB0eXBlIG9mIGxheWVyc1xuICogKHJlY3VycmVudCwgRGVuc2UuLi4pIGFuIGBpbnB1dERpbWAgYXJndW1lbnQuXG4gKlxuICogVGhlIGtleSBkaWZmZXJlbmNlIGJldHdlZW4gYHRmLm1vZGVsYCBhbmQgYHRmLnNlcXVlbnRpYWxgIGlzIHRoYXRcbiAqIGB0Zi5zZXF1ZW50aWFsYCBpcyBsZXNzIGdlbmVyaWMsIHN1cHBvcnRpbmcgb25seSBhIGxpbmVhciBzdGFjayBvZiBsYXllcnMuXG4gKiBgdGYubW9kZWxgIGlzIG1vcmUgZ2VuZXJpYyBhbmQgc3VwcG9ydHMgYW4gYXJiaXRyYXJ5IGdyYXBoICh3aXRob3V0XG4gKiBjeWNsZXMpIG9mIGxheWVycy5cbiAqXG4gKiBFeGFtcGxlczpcbiAqXG4gKiBgYGBqc1xuICogY29uc3QgbW9kZWwgPSB0Zi5zZXF1ZW50aWFsKCk7XG4gKlxuICogLy8gRmlyc3QgbGF5ZXIgbXVzdCBoYXZlIGFuIGlucHV0IHNoYXBlIGRlZmluZWQuXG4gKiBtb2RlbC5hZGQodGYubGF5ZXJzLmRlbnNlKHt1bml0czogMzIsIGlucHV0U2hhcGU6IFs1MF19KSk7XG4gKiAvLyBBZnRlcndhcmRzLCBURi5qcyBkb2VzIGF1dG9tYXRpYyBzaGFwZSBpbmZlcmVuY2UuXG4gKiBtb2RlbC5hZGQodGYubGF5ZXJzLmRlbnNlKHt1bml0czogNH0pKTtcbiAqXG4gKiAvLyBJbnNwZWN0IHRoZSBpbmZlcnJlZCBzaGFwZSBvZiB0aGUgbW9kZWwncyBvdXRwdXQsIHdoaWNoIGVxdWFsc1xuICogLy8gYFtudWxsLCA0XWAuIFRoZSAxc3QgZGltZW5zaW9uIGlzIHRoZSB1bmRldGVybWluZWQgYmF0Y2ggZGltZW5zaW9uOyB0aGVcbiAqIC8vIDJuZCBpcyB0aGUgb3V0cHV0IHNpemUgb2YgdGhlIG1vZGVsJ3MgbGFzdCBsYXllci5cbiAqIGNvbnNvbGUubG9nKEpTT04uc3RyaW5naWZ5KG1vZGVsLm91dHB1dHNbMF0uc2hhcGUpKTtcbiAqIGBgYFxuICpcbiAqIEl0IGlzIGFsc28gcG9zc2libGUgdG8gc3BlY2lmeSBhIGJhdGNoIHNpemUgKHdpdGggcG90ZW50aWFsbHkgdW5kZXRlcm1pbmVkXG4gKiBiYXRjaCBkaW1lbnNpb24sIGRlbm90ZWQgYnkgXCJudWxsXCIpIGZvciB0aGUgZmlyc3QgbGF5ZXIgdXNpbmcgdGhlXG4gKiBgYmF0Y2hJbnB1dFNoYXBlYCBrZXkuIFRoZSBmb2xsb3dpbmcgZXhhbXBsZSBpcyBlcXVpdmFsZW50IHRvIHRoZSBhYm92ZTpcbiAqXG4gKiBgYGBqc1xuICogY29uc3QgbW9kZWwgPSB0Zi5zZXF1ZW50aWFsKCk7XG4gKlxuICogLy8gRmlyc3QgbGF5ZXIgbXVzdCBoYXZlIGEgZGVmaW5lZCBpbnB1dCBzaGFwZVxuICogbW9kZWwuYWRkKHRmLmxheWVycy5kZW5zZSh7dW5pdHM6IDMyLCBiYXRjaElucHV0U2hhcGU6IFtudWxsLCA1MF19KSk7XG4gKiAvLyBBZnRlcndhcmRzLCBURi5qcyBkb2VzIGF1dG9tYXRpYyBzaGFwZSBpbmZlcmVuY2UuXG4gKiBtb2RlbC5hZGQodGYubGF5ZXJzLmRlbnNlKHt1bml0czogNH0pKTtcbiAqXG4gKiAvLyBJbnNwZWN0IHRoZSBpbmZlcnJlZCBzaGFwZSBvZiB0aGUgbW9kZWwncyBvdXRwdXQuXG4gKiBjb25zb2xlLmxvZyhKU09OLnN0cmluZ2lmeShtb2RlbC5vdXRwdXRzWzBdLnNoYXBlKSk7XG4gKiBgYGBcbiAqXG4gKiBZb3UgY2FuIGFsc28gdXNlIGFuIGBBcnJheWAgb2YgYWxyZWFkeS1jb25zdHJ1Y3RlZCBgTGF5ZXJgcyB0byBjcmVhdGVcbiAqIGEgYHRmLlNlcXVlbnRpYWxgIG1vZGVsOlxuICpcbiAqIGBgYGpzXG4gKiBjb25zdCBtb2RlbCA9IHRmLnNlcXVlbnRpYWwoe1xuICogICBsYXllcnM6IFt0Zi5sYXllcnMuZGVuc2Uoe3VuaXRzOiAzMiwgaW5wdXRTaGFwZTogWzUwXX0pLFxuICogICAgICAgICAgICB0Zi5sYXllcnMuZGVuc2Uoe3VuaXRzOiA0fSldXG4gKiB9KTtcbiAqIGNvbnNvbGUubG9nKEpTT04uc3RyaW5naWZ5KG1vZGVsLm91dHB1dHNbMF0uc2hhcGUpKTtcbiAqIGBgYFxuICpcbiAqIEBkb2Mge2hlYWRpbmc6ICdNb2RlbHMnLCBzdWJoZWFkaW5nOiAnQ3JlYXRpb24nfVxuICovXG5leHBvcnQgZnVuY3Rpb24gc2VxdWVudGlhbChjb25maWc/OiBTZXF1ZW50aWFsQXJncyk6IFNlcXVlbnRpYWwge1xuICByZXR1cm4gbmV3IFNlcXVlbnRpYWwoY29uZmlnKTtcbn1cblxuLyoqXG4gKiBMb2FkIGEgbW9kZWwgY29tcG9zZWQgb2YgTGF5ZXIgb2JqZWN0cywgaW5jbHVkaW5nIGl0cyB0b3BvbG9neSBhbmQgb3B0aW9uYWxseVxuICogd2VpZ2h0cy4gU2VlIHRoZSBUdXRvcmlhbCBuYW1lZCBcIkhvdyB0byBpbXBvcnQgYSBLZXJhcyBNb2RlbFwiIGZvciB1c2FnZVxuICogZXhhbXBsZXMuXG4gKlxuICogVGhpcyBtZXRob2QgaXMgYXBwbGljYWJsZSB0bzpcbiAqXG4gKiAxLiBNb2RlbHMgY3JlYXRlZCB3aXRoIHRoZSBgdGYubGF5ZXJzLipgLCBgdGYuc2VxdWVudGlhbGAsIGFuZFxuICogYHRmLm1vZGVsYCBBUElzIG9mIFRlbnNvckZsb3cuanMgYW5kIGxhdGVyIHNhdmVkIHdpdGggdGhlXG4gKiBgdGYuTGF5ZXJzTW9kZWwuc2F2ZWAgbWV0aG9kLlxuICogMi4gTW9kZWxzIGNvbnZlcnRlZCBmcm9tIEtlcmFzIG9yIFRlbnNvckZsb3cgdGYua2VyYXMgdXNpbmcgdGhlXG4gKiBbdGVuc29yZmxvd2pzX2NvbnZlcnRlcl0oaHR0cHM6Ly9naXRodWIuY29tL3RlbnNvcmZsb3cvdGZqcy90cmVlL21hc3Rlci90ZmpzLWNvbnZlcnRlcikuXG4gKlxuICogVGhpcyBtb2RlIGlzICpub3QqIGFwcGxpY2FibGUgdG8gVGVuc29yRmxvdyBgU2F2ZWRNb2RlbGBzIG9yIHRoZWlyIGNvbnZlcnRlZFxuICogZm9ybXMuIEZvciB0aG9zZSBtb2RlbHMsIHVzZSBgdGYubG9hZEdyYXBoTW9kZWxgLlxuICpcbiAqIEV4YW1wbGUgMS4gTG9hZCBhIG1vZGVsIGZyb20gYW4gSFRUUCBzZXJ2ZXIuXG4gKlxuICogYGBganNcbiAqIGNvbnN0IG1vZGVsID0gYXdhaXQgdGYubG9hZExheWVyc01vZGVsKFxuICogICAgICdodHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb20vdGZqcy1tb2RlbHMvdGZqcy9pcmlzX3YxL21vZGVsLmpzb24nKTtcbiAqIG1vZGVsLnN1bW1hcnkoKTtcbiAqIGBgYFxuICpcbiAqIEV4YW1wbGUgMjogU2F2ZSBgbW9kZWxgJ3MgdG9wb2xvZ3kgYW5kIHdlaWdodHMgdG8gYnJvd3NlciBbbG9jYWxcbiAqIHN0b3JhZ2VdKGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9XaW5kb3cvbG9jYWxTdG9yYWdlKTtcbiAqIHRoZW4gbG9hZCBpdCBiYWNrLlxuICpcbiAqIGBgYGpzXG4gKiBjb25zdCBtb2RlbCA9IHRmLnNlcXVlbnRpYWwoXG4gKiAgICAge2xheWVyczogW3RmLmxheWVycy5kZW5zZSh7dW5pdHM6IDEsIGlucHV0U2hhcGU6IFszXX0pXX0pO1xuICogY29uc29sZS5sb2coJ1ByZWRpY3Rpb24gZnJvbSBvcmlnaW5hbCBtb2RlbDonKTtcbiAqIG1vZGVsLnByZWRpY3QodGYub25lcyhbMSwgM10pKS5wcmludCgpO1xuICpcbiAqIGNvbnN0IHNhdmVSZXN1bHRzID0gYXdhaXQgbW9kZWwuc2F2ZSgnbG9jYWxzdG9yYWdlOi8vbXktbW9kZWwtMScpO1xuICpcbiAqIGNvbnN0IGxvYWRlZE1vZGVsID0gYXdhaXQgdGYubG9hZExheWVyc01vZGVsKCdsb2NhbHN0b3JhZ2U6Ly9teS1tb2RlbC0xJyk7XG4gKiBjb25zb2xlLmxvZygnUHJlZGljdGlvbiBmcm9tIGxvYWRlZCBtb2RlbDonKTtcbiAqIGxvYWRlZE1vZGVsLnByZWRpY3QodGYub25lcyhbMSwgM10pKS5wcmludCgpO1xuICogYGBgXG4gKlxuICogRXhhbXBsZSAzLiBTYXZpbmcgYG1vZGVsYCdzIHRvcG9sb2d5IGFuZCB3ZWlnaHRzIHRvIGJyb3dzZXJcbiAqIFtJbmRleGVkREJdKGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9JbmRleGVkREJfQVBJKTtcbiAqIHRoZW4gbG9hZCBpdCBiYWNrLlxuICpcbiAqIGBgYGpzXG4gKiBjb25zdCBtb2RlbCA9IHRmLnNlcXVlbnRpYWwoXG4gKiAgICAge2xheWVyczogW3RmLmxheWVycy5kZW5zZSh7dW5pdHM6IDEsIGlucHV0U2hhcGU6IFszXX0pXX0pO1xuICogY29uc29sZS5sb2coJ1ByZWRpY3Rpb24gZnJvbSBvcmlnaW5hbCBtb2RlbDonKTtcbiAqIG1vZGVsLnByZWRpY3QodGYub25lcyhbMSwgM10pKS5wcmludCgpO1xuICpcbiAqIGNvbnN0IHNhdmVSZXN1bHRzID0gYXdhaXQgbW9kZWwuc2F2ZSgnaW5kZXhlZGRiOi8vbXktbW9kZWwtMScpO1xuICpcbiAqIGNvbnN0IGxvYWRlZE1vZGVsID0gYXdhaXQgdGYubG9hZExheWVyc01vZGVsKCdpbmRleGVkZGI6Ly9teS1tb2RlbC0xJyk7XG4gKiBjb25zb2xlLmxvZygnUHJlZGljdGlvbiBmcm9tIGxvYWRlZCBtb2RlbDonKTtcbiAqIGxvYWRlZE1vZGVsLnByZWRpY3QodGYub25lcyhbMSwgM10pKS5wcmludCgpO1xuICogYGBgXG4gKlxuICogRXhhbXBsZSA0LiBMb2FkIGEgbW9kZWwgZnJvbSB1c2VyLXNlbGVjdGVkIGZpbGVzIGZyb20gSFRNTFxuICogW2ZpbGUgaW5wdXRcbiAqIGVsZW1lbnRzXShodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9IVE1ML0VsZW1lbnQvaW5wdXQvZmlsZSkuXG4gKlxuICogYGBganNcbiAqIC8vIE5vdGU6IHRoaXMgY29kZSBzbmlwcGV0IHdpbGwgbm90IHdvcmsgd2l0aG91dCB0aGUgSFRNTCBlbGVtZW50cyBpbiB0aGVcbiAqIC8vICAgcGFnZVxuICogY29uc3QganNvblVwbG9hZCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdqc29uLXVwbG9hZCcpO1xuICogY29uc3Qgd2VpZ2h0c1VwbG9hZCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd3ZWlnaHRzLXVwbG9hZCcpO1xuICpcbiAqIGNvbnN0IG1vZGVsID0gYXdhaXQgdGYubG9hZExheWVyc01vZGVsKFxuICogICAgIHRmLmlvLmJyb3dzZXJGaWxlcyhbanNvblVwbG9hZC5maWxlc1swXSwgd2VpZ2h0c1VwbG9hZC5maWxlc1swXV0pKTtcbiAqIGBgYFxuICpcbiAqIEBwYXJhbSBwYXRoT3JJT0hhbmRsZXIgQ2FuIGJlIGVpdGhlciBvZiB0aGUgdHdvIGZvcm1hdHNcbiAqICAgMS4gQSBzdHJpbmcgcGF0aCB0byB0aGUgYE1vZGVsQW5kV2VpZ2h0c0NvbmZpZ2AgSlNPTiBkZXNjcmliaW5nXG4gKiAgICAgIHRoZSBtb2RlbCBpbiB0aGUgY2Fub25pY2FsIFRlbnNvckZsb3cuanMgZm9ybWF0LiBGb3IgZmlsZTovL1xuICogICAgICAodGZqcy1ub2RlLW9ubHkpLCBodHRwOi8vIGFuZCBodHRwczovLyBzY2hlbWFzLCB0aGUgcGF0aCBjYW4gYmVcbiAqICAgICAgZWl0aGVyIGFic29sdXRlIG9yIHJlbGF0aXZlLlxuICogICAyLiBBbiBgdGYuaW8uSU9IYW5kbGVyYCBvYmplY3QgdGhhdCBsb2FkcyBtb2RlbCBhcnRpZmFjdHMgd2l0aCBpdHMgYGxvYWRgXG4gKiAgICAgIG1ldGhvZC5cbiAqIEBwYXJhbSBvcHRpb25zIE9wdGlvbmFsIGNvbmZpZ3VyYXRpb24gYXJndW1lbnRzIGZvciB0aGUgbW9kZWwgbG9hZGluZyxcbiAqICAgaW5jbHVkaW5nOlxuICogICAtIGBzdHJpY3RgOiBSZXF1aXJlIHRoYXQgdGhlIHByb3ZpZGVkIHdlaWdodHMgZXhhY3RseSBtYXRjaCB0aG9zZSByZXF1aXJlZFxuICogICAgIGJ5IHRoZSBsYXllcnMuICBEZWZhdWx0IHRydWUuICBQYXNzaW5nIGZhbHNlIG1lYW5zIHRoYXQgYm90aCBleHRyYVxuICogICAgIHdlaWdodHMgYW5kIG1pc3Npbmcgd2VpZ2h0cyB3aWxsIGJlIHNpbGVudGx5IGlnbm9yZWQuXG4gKiAgIC0gYG9uUHJvZ3Jlc3NgOiBBIGZ1bmN0aW9uIG9mIHRoZSBzaWduYXR1cmUgYChmcmFjdGlvbjogbnVtYmVyKSA9PiB2b2lkJyxcbiAqICAgICB0aGF0IGNhbiBiZSB1c2VkIGFzIHRoZSBwcm9ncmVzcyBjYWxsYmFjayBmb3IgdGhlIG1vZGVsIGxvYWRpbmcuXG4gKiBAcmV0dXJucyBBIGBQcm9taXNlYCBvZiBgdGYuTGF5ZXJzTW9kZWxgLCB3aXRoIHRoZSB0b3BvbG9neSBhbmQgd2VpZ2h0c1xuICogICAgIGxvYWRlZC5cbiAqXG4gKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0xvYWRpbmcnfVxuICovXG5leHBvcnQgZnVuY3Rpb24gbG9hZExheWVyc01vZGVsKFxuICAgIHBhdGhPcklPSGFuZGxlcjogc3RyaW5nfGlvLklPSGFuZGxlcixcbiAgICBvcHRpb25zPzogaW8uTG9hZE9wdGlvbnMpOiBQcm9taXNlPExheWVyc01vZGVsPiB7XG4gIGlmIChvcHRpb25zID09IG51bGwpIHtcbiAgICBvcHRpb25zID0ge307XG4gIH1cbiAgcmV0dXJuIGxvYWRMYXllcnNNb2RlbEludGVybmFsKHBhdGhPcklPSGFuZGxlciwgb3B0aW9ucyk7XG59XG5cbi8qKlxuICogVXNlZCB0byBpbnN0YW50aWF0ZSBhbiBpbnB1dCB0byBhIG1vZGVsIGFzIGEgYHRmLlN5bWJvbGljVGVuc29yYC5cbiAqXG4gKiBVc2VycyBzaG91bGQgY2FsbCB0aGUgYGlucHV0YCBmYWN0b3J5IGZ1bmN0aW9uIGZvclxuICogY29uc2lzdGVuY3kgd2l0aCBvdGhlciBnZW5lcmF0b3IgZnVuY3Rpb25zLlxuICpcbiAqIEV4YW1wbGU6XG4gKlxuICogYGBganNcbiAqIC8vIERlZmluZXMgYSBzaW1wbGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB3aXRoIDMyIGRpbWVuc2lvbmFsIGlucHV0XG4gKiAvLyBhbmQgMyBkaW1lbnNpb25hbCBvdXRwdXQuXG4gKiBjb25zdCB4ID0gdGYuaW5wdXQoe3NoYXBlOiBbMzJdfSk7XG4gKiBjb25zdCB5ID0gdGYubGF5ZXJzLmRlbnNlKHt1bml0czogMywgYWN0aXZhdGlvbjogJ3NvZnRtYXgnfSkuYXBwbHkoeCk7XG4gKiBjb25zdCBtb2RlbCA9IHRmLm1vZGVsKHtpbnB1dHM6IHgsIG91dHB1dHM6IHl9KTtcbiAqIG1vZGVsLnByZWRpY3QodGYub25lcyhbMiwgMzJdKSkucHJpbnQoKTtcbiAqIGBgYFxuICpcbiAqIE5vdGU6IGBpbnB1dGAgaXMgb25seSBuZWNlc3Nhcnkgd2hlbiB1c2luZyBgbW9kZWxgLiBXaGVuIHVzaW5nXG4gKiBgc2VxdWVudGlhbGAsIHNwZWNpZnkgYGlucHV0U2hhcGVgIGZvciB0aGUgZmlyc3QgbGF5ZXIgb3IgdXNlIGBpbnB1dExheWVyYFxuICogYXMgdGhlIGZpcnN0IGxheWVyLlxuICpcbiAqIEBkb2Mge2hlYWRpbmc6ICdNb2RlbHMnLCBzdWJoZWFkaW5nOiAnSW5wdXRzJ31cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlucHV0KGNvbmZpZzogSW5wdXRDb25maWcpOiBTeW1ib2xpY1RlbnNvciB7XG4gIHJldHVybiBJbnB1dChjb25maWcpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcmVnaXN0ZXJDYWxsYmFja0NvbnN0cnVjdG9yKFxuICAgIHZlcmJvc2l0eUxldmVsOiBudW1iZXIsXG4gICAgY2FsbGJhY2tDb25zdHJ1Y3RvcjogQmFzZUNhbGxiYWNrQ29uc3RydWN0b3IpOiB2b2lkIHtcbiAgQ2FsbGJhY2tDb25zdHJ1Y3RvclJlZ2lzdHJ5LnJlZ2lzdGVyQ2FsbGJhY2tDb25zdHJ1Y3RvcihcbiAgICAgIHZlcmJvc2l0eUxldmVsLCBjYWxsYmFja0NvbnN0cnVjdG9yKTtcbn1cbiJdfQ== |
\ | No newline at end of file |