tfjs-evolution 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +72 -0
- package/esm2022/lib/components/display-panel/display-panel.component.mjs +34 -23
- package/esm2022/lib/models/custom-mobilenet.mjs +182 -0
- package/esm2022/lib/models/teachable-evolution.mjs +424 -0
- package/esm2022/lib/utils/class.mjs +2 -0
- package/esm2022/lib/utils/tf.mjs +29 -0
- package/esm2022/lib/utils/util.mjs +165 -0
- package/fesm2022/tfjs-evolution.mjs +826 -22
- package/fesm2022/tfjs-evolution.mjs.map +1 -1
- package/lib/components/display-panel/display-panel.component.d.ts +5 -1
- package/lib/models/custom-mobilenet.d.ts +63 -0
- package/lib/models/teachable-evolution.d.ts +101 -0
- package/lib/utils/class.d.ts +5 -0
- package/lib/utils/tf.d.ts +9 -0
- package/lib/utils/util.d.ts +43 -0
- package/package.json +1 -1
@@ -0,0 +1,165 @@
|
|
1
|
+
import * as tf from '@tensorflow/tfjs';
|
2
|
+
export class Util {
|
3
|
+
/**
|
4
|
+
* Receives an image and normalizes it between -1 and 1.
|
5
|
+
* Returns a batched image (1 - element batch) of shape [1, w, h, c]
|
6
|
+
* @param rasterElement the element with pixels to convert to a Tensor
|
7
|
+
* @param grayscale optinal flag that changes the crop to [1, w, h, 1]
|
8
|
+
*/
|
9
|
+
capture(rasterElement, grayscale) {
|
10
|
+
return tf.tidy(() => {
|
11
|
+
const pixels = tf.browser.fromPixels(rasterElement);
|
12
|
+
// // crop the image so we're using the center square
|
13
|
+
const cropped = this.cropTensor(pixels, grayscale);
|
14
|
+
// // Expand the outer most dimension so we have a batch size of 1
|
15
|
+
const batchedImage = cropped.expandDims(0);
|
16
|
+
// // Normalize the image between -1 and a1. The image comes in between 0-255
|
17
|
+
// // so we divide by 127 and subtract 1.
|
18
|
+
return batchedImage.toFloat().div(tf.scalar(127)).sub(tf.scalar(1));
|
19
|
+
});
|
20
|
+
}
|
21
|
+
cropTensor(img, grayscaleModel, grayscaleInput) {
|
22
|
+
const size = Math.min(img.shape[0], img.shape[1]);
|
23
|
+
const centerHeight = img.shape[0] / 2;
|
24
|
+
const beginHeight = centerHeight - (size / 2);
|
25
|
+
const centerWidth = img.shape[1] / 2;
|
26
|
+
const beginWidth = centerWidth - (size / 2);
|
27
|
+
if (grayscaleModel && !grayscaleInput) {
|
28
|
+
//cropped rgb data
|
29
|
+
let grayscale_cropped = img.slice([beginHeight, beginWidth, 0], [size, size, 3]);
|
30
|
+
grayscale_cropped = grayscale_cropped.reshape([size * size, 1, 3]);
|
31
|
+
const rgb_weights = [0.2989, 0.5870, 0.1140];
|
32
|
+
grayscale_cropped = tf.mul(grayscale_cropped, rgb_weights);
|
33
|
+
grayscale_cropped = grayscale_cropped.reshape([size, size, 3]);
|
34
|
+
grayscale_cropped = tf.sum(grayscale_cropped, -1);
|
35
|
+
grayscale_cropped = tf.expandDims(grayscale_cropped, -1);
|
36
|
+
return grayscale_cropped;
|
37
|
+
}
|
38
|
+
return img.slice([beginHeight, beginWidth, 0], [size, size, 3]);
|
39
|
+
}
|
40
|
+
/**
|
41
|
+
* This function will make a copy of a model on the weight level
|
42
|
+
* This is an attempt to avoid influencing the new mode when the old one
|
43
|
+
* is eliminated.
|
44
|
+
*
|
45
|
+
* @param originalModel - the model to be copied
|
46
|
+
* @param recipient - the new model
|
47
|
+
*/
|
48
|
+
async copyModel_v3(originalModel, recipient) {
|
49
|
+
originalModel.layers.forEach((layer, index) => {
|
50
|
+
recipient.layers[index].setWeights(layer.getWeights());
|
51
|
+
});
|
52
|
+
// originalModel.dispose();
|
53
|
+
}
|
54
|
+
/**
|
55
|
+
* This function will make a copy of a TFJS model, as so it would be possible
|
56
|
+
* to erase the original.
|
57
|
+
* @param model - model to be copied
|
58
|
+
* @returns - copy of the model
|
59
|
+
*/
|
60
|
+
async copyModel_v2(originalModel) {
|
61
|
+
// Serialize the original model
|
62
|
+
const modelTopology = originalModel.toJSON();
|
63
|
+
// Load the serialized model into a new model
|
64
|
+
const copiedModel = await tf.loadLayersModel(tf.io.fromMemory(modelTopology, undefined, undefined));
|
65
|
+
// Compile the copied model with the same settings as the original
|
66
|
+
copiedModel.compile({
|
67
|
+
loss: originalModel.loss,
|
68
|
+
optimizer: originalModel.optimizer
|
69
|
+
});
|
70
|
+
return copiedModel;
|
71
|
+
}
|
72
|
+
/**
|
73
|
+
* This function will make a copy of a TFJS model, as so it would be possible
|
74
|
+
* to erase the original.
|
75
|
+
* @param model - model to be copied
|
76
|
+
* @returns - copy of the model
|
77
|
+
*/
|
78
|
+
copyModel(model) {
|
79
|
+
const copy = tf.sequential();
|
80
|
+
`
|
81
|
+
`;
|
82
|
+
model.layers.forEach(layer => {
|
83
|
+
const aux = layer;
|
84
|
+
// layer.dispose();
|
85
|
+
copy.add(aux);
|
86
|
+
});
|
87
|
+
copy.compile({
|
88
|
+
loss: model.loss,
|
89
|
+
optimizer: model.optimizer
|
90
|
+
});
|
91
|
+
return copy;
|
92
|
+
}
|
93
|
+
removeElementByIndex(arr, index) {
|
94
|
+
// Check if the index is within bounds
|
95
|
+
if (index >= 0 && index < arr.length) {
|
96
|
+
// Remove the element at the specified index
|
97
|
+
arr.splice(index, 1);
|
98
|
+
}
|
99
|
+
return arr;
|
100
|
+
}
|
101
|
+
removeElement(arr, element) {
|
102
|
+
// Remove all occurrences of the specified element from the array
|
103
|
+
return arr.filter((item) => item !== element);
|
104
|
+
}
|
105
|
+
clean_array_of_tensors(tensors) {
|
106
|
+
tensors.forEach((elem, index) => {
|
107
|
+
// if(!index_selection.includes(index))
|
108
|
+
elem.dispose();
|
109
|
+
});
|
110
|
+
}
|
111
|
+
getClassNameBySignature(classes, signature) {
|
112
|
+
const class_name = classes.find(p => {
|
113
|
+
let match = true;
|
114
|
+
p.signature?.forEach((elem, index) => {
|
115
|
+
if (elem !== signature[index])
|
116
|
+
match = false;
|
117
|
+
});
|
118
|
+
return match;
|
119
|
+
});
|
120
|
+
return class_name ? class_name.name : "not found";
|
121
|
+
}
|
122
|
+
identityMatrix(n) {
|
123
|
+
return Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => (i === j ? 1 : 0)));
|
124
|
+
}
|
125
|
+
indexOfMax(arr) {
|
126
|
+
if (arr.length === 0) {
|
127
|
+
return -1; // Return -1 for an empty array
|
128
|
+
}
|
129
|
+
let max = arr[0];
|
130
|
+
let maxIndex = 0;
|
131
|
+
for (let i = 1; i < arr.length; i++) {
|
132
|
+
if (arr[i] > max) {
|
133
|
+
maxIndex = i;
|
134
|
+
max = arr[i];
|
135
|
+
}
|
136
|
+
}
|
137
|
+
return maxIndex;
|
138
|
+
}
|
139
|
+
suffle(array1, array2) {
|
140
|
+
// Shuffle the order of elements
|
141
|
+
for (let i = array1.length - 1; i > 0; i--) {
|
142
|
+
const j = Math.floor(Math.random() * (i + 1));
|
143
|
+
// Swap elements in both arrays
|
144
|
+
[array1[i], array1[j]] = [array1[j], array1[i]];
|
145
|
+
[array2[i], array2[j]] = [array2[j], array2[i]];
|
146
|
+
}
|
147
|
+
}
|
148
|
+
sortByValuePreservingIndex(arr1, arr2) {
|
149
|
+
// console.log("Vector for organizing: ", arr1)
|
150
|
+
// arr2[0].summary()
|
151
|
+
// Create an array of objects with value, index from arr1, and original index
|
152
|
+
const pairingArray = arr1.map((value, index) => ({
|
153
|
+
value,
|
154
|
+
index,
|
155
|
+
originalIndex: index,
|
156
|
+
elementFromArr2: arr2[index], // Preserve the corresponding element from arr2
|
157
|
+
}));
|
158
|
+
// Sort the pairing array by value (largest to smallest)
|
159
|
+
pairingArray.sort((a, b) => b.value - a.value);
|
160
|
+
// Extract the sorted elements from arr2 based on the original index
|
161
|
+
const sortedElementsFromArr2 = pairingArray.map(pair => pair.elementFromArr2);
|
162
|
+
return sortedElementsFromArr2;
|
163
|
+
}
|
164
|
+
}
|
165
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3Byb2plY3RzL3RmanMtZXZvbHV0aW9uL3NyYy9saWIvdXRpbHMvdXRpbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSxPQUFPLEtBQUssRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBR3ZDLE1BQU0sT0FBTyxJQUFJO0lBRWpCOzs7OztPQUtHO0lBQ0gsT0FBTyxDQUFDLGFBQXNFLEVBQUUsU0FBbUI7UUFDL0YsT0FBTyxFQUFFLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRTtZQUNoQixNQUFNLE1BQU0sR0FBRyxFQUFFLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxhQUFhLENBQUMsQ0FBQztZQUVwRCxxREFBcUQ7WUFDckQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxNQUFNLEVBQUUsU0FBUyxDQUFDLENBQUM7WUFFbkQsa0VBQWtFO1lBQ2xFLE1BQU0sWUFBWSxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFFM0MsNkVBQTZFO1lBQzdFLHlDQUF5QztZQUN6QyxPQUFPLFlBQVksQ0FBQyxPQUFPLEVBQUUsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDeEUsQ0FBQyxDQUFDLENBQUM7SUFDUCxDQUFDO0lBR0QsVUFBVSxDQUFFLEdBQWdCLEVBQUUsY0FBd0IsRUFBRSxjQUF3QjtRQUM1RSxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ2xELE1BQU0sWUFBWSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3RDLE1BQU0sV0FBVyxHQUFHLFlBQVksR0FBRyxDQUFDLElBQUksR0FBRyxDQUFDLENBQUMsQ0FBQztRQUM5QyxNQUFNLFdBQVcsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNyQyxNQUFNLFVBQVUsR0FBRyxXQUFXLEdBQUcsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxDQUFDLENBQUM7UUFFNUMsSUFBSSxjQUFjLElBQUksQ0FBQyxjQUFjLEVBQUU7WUFDbkMsa0JBQWtCO1lBQ2xCLElBQUksaUJBQWlCLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLFdBQVcsRUFBRSxVQUFVLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFFakYsaUJBQWlCLEdBQUcsaUJBQWlCLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxHQUFHLElBQUksRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQTtZQUNsRSxNQUFNLFdBQVcsR0FBRyxDQUFDLE1BQU0sRUFBRSxNQUFNLEVBQUUsTUFBTSxDQUFDLENBQUE7WUFDNUMsaUJBQWlCLEdBQUcsRUFBRSxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsRUFBRSxXQUFXLENBQUMsQ0FBQTtZQUMxRCxpQkFBaUIsR0FBRyxpQkFBaUIsQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFFL0QsaUJBQWlCLEdBQUcsRUFBRSxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFBO1lBQ2pELGlCQUFpQixHQUFHLEVBQUUsQ0FBQyxVQUFVLENBQUMsaUJBQWlCLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQTtZQUV4RCxPQUFPLGlCQUFpQixDQUFDO1NBQzVCO1FBQ0QsT0FBTyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsV0FBVyxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLElBQUksRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNwRSxDQUFDO0lBS0Q7Ozs7Ozs7T0FPRztJQUNILEtBQUssQ0FBQyxZQUFZLENBQUUsYUFBNEIsRUFBRSxTQUF3QjtRQUVsRSxhQUFhLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLEtBQUssRUFBRSxLQUFLLEVBQUMsRUFBRTtZQUN6QyxTQUFTLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsVUFBVSxFQUFFLENBQUMsQ0FBQTtRQUMxRCxDQUFDLENBQUMsQ0FBQTtRQUVOLDJCQUEyQjtJQUMvQixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSCxLQUFLLENBQUMsWUFBWSxDQUFFLGFBQTRCO1FBQ3hDLCtCQUErQjtRQUMvQixNQUFNLGFBQWEsR0FBRyxhQUFhLENBQUMsTUFBTSxFQUFFLENBQUM7UUFFN0MsNkNBQTZDO1FBQzdDLE1BQU0sV0FBVyxHQUFHLE1BQU0sRUFBRSxDQUFDLGVBQWUsQ0FDeEMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsYUFBYSxFQUFFLFNBQVMsRUFBRSxTQUFTLENBQUMsQ0FDeEQsQ0FBQztRQUVGLGtFQUFrRTtRQUNsRSxXQUFXLENBQUMsT0FBTyxDQUFDO1lBQ2hCLElBQUksRUFBRSxhQUFhLENBQUMsSUFBSTtZQUN4QixTQUFTLEVBQUUsYUFBYSxDQUFDLFNBQVM7U0FDckMsQ0FBQyxDQUFDO1FBRUgsT0FBTyxXQUFXLENBQUM7SUFDM0IsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0gsU0FBUyxDQUFFLEtBQW9CO1FBRXZCLE1BQU0sSUFBSSxHQUFHLEVBQUUsQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUFBO1NBQzVCLENBQUE7UUFDRCxLQUFLLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRTtZQUN6QixNQUFNLEdBQUcsR0FBRSxLQUFLLENBQUM7WUFDakIsbUJBQW1CO1lBQ25CLElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDbEIsQ0FBQyxDQUFDLENBQUM7UUFDSCxJQUFJLENBQUMsT0FBTyxDQUFDO1lBQ1QsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO1lBQ2hCLFNBQVMsRUFBRSxLQUFLLENBQUMsU0FBUztTQUM3QixDQUFDLENBQUM7UUFDSCxPQUFPLElBQUksQ0FBQztJQUNoQixDQUFDO0lBR0Qsb0JBQW9CLENBQUMsR0FBUSxFQUFFLEtBQWE7UUFDeEMsc0NBQXNDO1FBQ3RDLElBQUksS0FBSyxJQUFJLENBQUMsSUFBSSxLQUFLLEdBQUcsR0FBRyxDQUFDLE1BQU0sRUFBRTtZQUNsQyw0Q0FBNEM7WUFDNUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUM7U0FDeEI7UUFDRCxPQUFPLEdBQUcsQ0FBQztJQUNmLENBQUM7SUFFRCxhQUFhLENBQUMsR0FBUSxFQUFFLE9BQVk7UUFDaEMsaUVBQWlFO1FBQ2pFLE9BQU8sR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLElBQVMsRUFBRSxFQUFFLENBQUMsSUFBSSxLQUFLLE9BQU8sQ0FBQyxDQUFDO0lBQ3ZELENBQUM7SUFFTCxzQkFBc0IsQ0FBQyxPQUF3QjtRQUUzQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxFQUFFLEtBQUssRUFBQyxFQUFFO1lBQzNCLHlDQUF5QztZQUNyQyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUE7UUFDbEIsQ0FBQyxDQUFDLENBQUM7SUFFWCxDQUFDO0lBRUEsdUJBQXVCLENBQUMsT0FBZ0IsRUFBRSxTQUFtQjtRQUV0RCxNQUFNLFVBQVUsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFO1lBQ2hDLElBQUksS0FBSyxHQUFHLElBQUksQ0FBQztZQUNqQixDQUFDLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBRSxDQUFDLElBQUksRUFBQyxLQUFLLEVBQUMsRUFBRTtnQkFDaEMsSUFBRyxJQUFJLEtBQUcsU0FBUyxDQUFDLEtBQUssQ0FBQztvQkFDdEIsS0FBSyxHQUFDLEtBQUssQ0FBQztZQUNwQixDQUFDLENBQUMsQ0FBQTtZQUVGLE9BQU8sS0FBSyxDQUFDO1FBQ2pCLENBQUMsQ0FBQyxDQUFDO1FBRUgsT0FBTyxVQUFVLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLFdBQVcsQ0FBQztJQUMxRCxDQUFDO0lBRUQsY0FBYyxDQUFDLENBQVM7UUFDaEIsT0FBTyxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUUsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDM0csQ0FBQztJQUdELFVBQVUsQ0FBQyxHQUFhO1FBQ2hCLElBQUksR0FBRyxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUU7WUFDbEIsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLCtCQUErQjtTQUM3QztRQUVELElBQUksR0FBRyxHQUFHLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNqQixJQUFJLFFBQVEsR0FBRyxDQUFDLENBQUM7UUFFakIsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUU7WUFDakMsSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsR0FBRyxFQUFFO2dCQUNkLFFBQVEsR0FBRyxDQUFDLENBQUM7Z0JBQ2IsR0FBRyxHQUFHLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQzthQUNoQjtTQUNKO1FBRUQsT0FBTyxRQUFRLENBQUM7SUFDcEIsQ0FBQztJQUdMLE1BQU0sQ0FBQyxNQUFVLEVBQUUsTUFBVztRQUN0QixnQ0FBZ0M7UUFDcEMsS0FBSyxJQUFJLENBQUMsR0FBRyxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFO1lBQzVDLE1BQU0sQ0FBQyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxHQUFHLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFFOUMsK0JBQStCO1lBQy9CLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ2hELENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1NBQzdDO0lBR0gsQ0FBQztJQUVELDBCQUEwQixDQUN0QixJQUFjLEVBQ2QsSUFBVztRQUdYLCtDQUErQztRQUMvQyxvQkFBb0I7UUFFcEIsNkVBQTZFO1FBQzdFLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxLQUFLLEVBQUUsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1lBQzdDLEtBQUs7WUFDTCxLQUFLO1lBQ0wsYUFBYSxFQUFFLEtBQUs7WUFDcEIsZUFBZSxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSwrQ0FBK0M7U0FDaEYsQ0FBQyxDQUFDLENBQUM7UUFFSix3REFBd0Q7UUFDeEQsWUFBWSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLEdBQUcsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBSS9DLG9FQUFvRTtRQUNwRSxNQUFNLHNCQUFzQixHQUFHLFlBQVksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsZUFBZSxDQUFDLENBQUM7UUFFOUUsT0FBTyxzQkFBc0IsQ0FBQztJQUNsQyxDQUFDO0NBRUoiLCJzb3VyY2VzQ29udGVudCI6WyJcclxuaW1wb3J0IHtDbGFzc30gZnJvbSBcIi4vY2xhc3NcIlxyXG5pbXBvcnQgKiBhcyB0ZiBmcm9tICdAdGVuc29yZmxvdy90ZmpzJztcclxuXHJcblxyXG5leHBvcnQgY2xhc3MgVXRpbCB7XHJcblxyXG4vKipcclxuICogUmVjZWl2ZXMgYW4gaW1hZ2UgYW5kIG5vcm1hbGl6ZXMgaXQgYmV0d2VlbiAtMSBhbmQgMS5cclxuICogUmV0dXJucyBhIGJhdGNoZWQgaW1hZ2UgKDEgLSBlbGVtZW50IGJhdGNoKSBvZiBzaGFwZSBbMSwgdywgaCwgY11cclxuICogQHBhcmFtIHJhc3RlckVsZW1lbnQgdGhlIGVsZW1lbnQgd2l0aCBwaXhlbHMgdG8gY29udmVydCB0byBhIFRlbnNvclxyXG4gKiBAcGFyYW0gZ3JheXNjYWxlIG9wdGluYWwgZmxhZyB0aGF0IGNoYW5nZXMgdGhlIGNyb3AgdG8gWzEsIHcsIGgsIDFdXHJcbiAqL1xyXG5jYXB0dXJlKHJhc3RlckVsZW1lbnQ6IEhUTUxJbWFnZUVsZW1lbnQgfCBIVE1MVmlkZW9FbGVtZW50IHwgSFRNTENhbnZhc0VsZW1lbnQsIGdyYXlzY2FsZT86IGJvb2xlYW4pIHtcclxuICAgIHJldHVybiB0Zi50aWR5KCgpID0+IHtcclxuICAgICAgICBjb25zdCBwaXhlbHMgPSB0Zi5icm93c2VyLmZyb21QaXhlbHMocmFzdGVyRWxlbWVudCk7XHJcblxyXG4gICAgICAgIC8vIC8vIGNyb3AgdGhlIGltYWdlIHNvIHdlJ3JlIHVzaW5nIHRoZSBjZW50ZXIgc3F1YXJlXHJcbiAgICAgICAgY29uc3QgY3JvcHBlZCA9IHRoaXMuY3JvcFRlbnNvcihwaXhlbHMsIGdyYXlzY2FsZSk7XHJcblxyXG4gICAgICAgIC8vIC8vIEV4cGFuZCB0aGUgb3V0ZXIgbW9zdCBkaW1lbnNpb24gc28gd2UgaGF2ZSBhIGJhdGNoIHNpemUgb2YgMVxyXG4gICAgICAgIGNvbnN0IGJhdGNoZWRJbWFnZSA9IGNyb3BwZWQuZXhwYW5kRGltcygwKTtcclxuXHJcbiAgICAgICAgLy8gLy8gTm9ybWFsaXplIHRoZSBpbWFnZSBiZXR3ZWVuIC0xIGFuZCBhMS4gVGhlIGltYWdlIGNvbWVzIGluIGJldHdlZW4gMC0yNTVcclxuICAgICAgICAvLyAvLyBzbyB3ZSBkaXZpZGUgYnkgMTI3IGFuZCBzdWJ0cmFjdCAxLlxyXG4gICAgICAgIHJldHVybiBiYXRjaGVkSW1hZ2UudG9GbG9hdCgpLmRpdih0Zi5zY2FsYXIoMTI3KSkuc3ViKHRmLnNjYWxhcigxKSk7ICAgIFxyXG4gICAgfSk7XHJcbn0gICAgXHJcblxyXG5cclxuY3JvcFRlbnNvciggaW1nOiB0Zi5UZW5zb3IzRCwgZ3JheXNjYWxlTW9kZWw/OiBib29sZWFuLCBncmF5c2NhbGVJbnB1dD86IGJvb2xlYW4gKSA6IHRmLlRlbnNvcjNEIHtcclxuICAgIGNvbnN0IHNpemUgPSBNYXRoLm1pbihpbWcuc2hhcGVbMF0sIGltZy5zaGFwZVsxXSk7XHJcbiAgICBjb25zdCBjZW50ZXJIZWlnaHQgPSBpbWcuc2hhcGVbMF0gLyAyO1xyXG4gICAgY29uc3QgYmVnaW5IZWlnaHQgPSBjZW50ZXJIZWlnaHQgLSAoc2l6ZSAvIDIpO1xyXG4gICAgY29uc3QgY2VudGVyV2lkdGggPSBpbWcuc2hhcGVbMV0gLyAyO1xyXG4gICAgY29uc3QgYmVnaW5XaWR0aCA9IGNlbnRlcldpZHRoIC0gKHNpemUgLyAyKTtcclxuICAgIFxyXG4gICAgaWYgKGdyYXlzY2FsZU1vZGVsICYmICFncmF5c2NhbGVJbnB1dCkge1xyXG4gICAgICAgIC8vY3JvcHBlZCByZ2IgZGF0YVxyXG4gICAgICAgIGxldCBncmF5c2NhbGVfY3JvcHBlZCA9IGltZy5zbGljZShbYmVnaW5IZWlnaHQsIGJlZ2luV2lkdGgsIDBdLCBbc2l6ZSwgc2l6ZSwgM10pO1xyXG4gICAgICAgIFxyXG4gICAgICAgIGdyYXlzY2FsZV9jcm9wcGVkID0gZ3JheXNjYWxlX2Nyb3BwZWQucmVzaGFwZShbc2l6ZSAqIHNpemUsIDEsIDNdKVxyXG4gICAgICAgIGNvbnN0IHJnYl93ZWlnaHRzID0gWzAuMjk4OSwgMC41ODcwLCAwLjExNDBdXHJcbiAgICAgICAgZ3JheXNjYWxlX2Nyb3BwZWQgPSB0Zi5tdWwoZ3JheXNjYWxlX2Nyb3BwZWQsIHJnYl93ZWlnaHRzKVxyXG4gICAgICAgIGdyYXlzY2FsZV9jcm9wcGVkID0gZ3JheXNjYWxlX2Nyb3BwZWQucmVzaGFwZShbc2l6ZSwgc2l6ZSwgM10pO1xyXG4gICAgXHJcbiAgICAgICAgZ3JheXNjYWxlX2Nyb3BwZWQgPSB0Zi5zdW0oZ3JheXNjYWxlX2Nyb3BwZWQsIC0xKVxyXG4gICAgICAgIGdyYXlzY2FsZV9jcm9wcGVkID0gdGYuZXhwYW5kRGltcyhncmF5c2NhbGVfY3JvcHBlZCwgLTEpXHJcblxyXG4gICAgICAgIHJldHVybiBncmF5c2NhbGVfY3JvcHBlZDtcclxuICAgIH1cclxuICAgIHJldHVybiBpbWcuc2xpY2UoW2JlZ2luSGVpZ2h0LCBiZWdpbldpZHRoLCAwXSwgW3NpemUsIHNpemUsIDNdKTtcclxufVxyXG5cclxuXHJcblxyXG5cclxuLyoqXHJcbiAqIFRoaXMgZnVuY3Rpb24gd2lsbCBtYWtlIGEgY29weSBvZiBhIG1vZGVsIG9uIHRoZSB3ZWlnaHQgbGV2ZWxcclxuICogVGhpcyBpcyBhbiBhdHRlbXB0IHRvIGF2b2lkIGluZmx1ZW5jaW5nIHRoZSBuZXcgbW9kZSB3aGVuIHRoZSBvbGQgb25lXHJcbiAqIGlzIGVsaW1pbmF0ZWQuIFxyXG4gKiBcclxuICogQHBhcmFtIG9yaWdpbmFsTW9kZWwgLSB0aGUgbW9kZWwgdG8gYmUgY29waWVkIFxyXG4gKiBAcGFyYW0gcmVjaXBpZW50IC0gdGhlIG5ldyBtb2RlbFxyXG4gKi9cclxuYXN5bmMgY29weU1vZGVsX3YzIChvcmlnaW5hbE1vZGVsOiB0Zi5TZXF1ZW50aWFsLCByZWNpcGllbnQ6IHRmLlNlcXVlbnRpYWwpICB7XHJcblxyXG4gICAgICAgIG9yaWdpbmFsTW9kZWwubGF5ZXJzLmZvckVhY2goKGxheWVyLCBpbmRleCk9PntcclxuICAgICAgICAgICAgcmVjaXBpZW50LmxheWVyc1tpbmRleF0uc2V0V2VpZ2h0cyhsYXllci5nZXRXZWlnaHRzKCkpXHJcbiAgICAgICAgfSlcclxuXHJcbiAgICAvLyBvcmlnaW5hbE1vZGVsLmRpc3Bvc2UoKTtcclxufVxyXG4gICAgXHJcbi8qKlxyXG4gKiBUaGlzIGZ1bmN0aW9uIHdpbGwgbWFrZSBhIGNvcHkgb2YgYSBURkpTIG1vZGVsLCBhcyBzbyBpdCB3b3VsZCBiZSBwb3NzaWJsZSBcclxuICogdG8gZXJhc2UgdGhlIG9yaWdpbmFsLlxyXG4gKiBAcGFyYW0gbW9kZWwgLSBtb2RlbCB0byBiZSBjb3BpZWRcclxuICogQHJldHVybnMgLSBjb3B5IG9mIHRoZSBtb2RlbFxyXG4gKi8gIFxyXG5hc3luYyBjb3B5TW9kZWxfdjIgKG9yaWdpbmFsTW9kZWw6IHRmLlNlcXVlbnRpYWwpICB7XHJcbiAgICAgICAgLy8gU2VyaWFsaXplIHRoZSBvcmlnaW5hbCBtb2RlbFxyXG4gICAgICAgIGNvbnN0IG1vZGVsVG9wb2xvZ3kgPSBvcmlnaW5hbE1vZGVsLnRvSlNPTigpO1xyXG5cclxuICAgICAgICAvLyBMb2FkIHRoZSBzZXJpYWxpemVkIG1vZGVsIGludG8gYSBuZXcgbW9kZWxcclxuICAgICAgICBjb25zdCBjb3BpZWRNb2RlbCA9IGF3YWl0IHRmLmxvYWRMYXllcnNNb2RlbChcclxuICAgICAgICAgICAgdGYuaW8uZnJvbU1lbW9yeShtb2RlbFRvcG9sb2d5LCB1bmRlZmluZWQsIHVuZGVmaW5lZClcclxuICAgICAgICApO1xyXG4gICAgXHJcbiAgICAgICAgLy8gQ29tcGlsZSB0aGUgY29waWVkIG1vZGVsIHdpdGggdGhlIHNhbWUgc2V0dGluZ3MgYXMgdGhlIG9yaWdpbmFsXHJcbiAgICAgICAgY29waWVkTW9kZWwuY29tcGlsZSh7XHJcbiAgICAgICAgICAgIGxvc3M6IG9yaWdpbmFsTW9kZWwubG9zcyxcclxuICAgICAgICAgICAgb3B0aW1pemVyOiBvcmlnaW5hbE1vZGVsLm9wdGltaXplclxyXG4gICAgICAgIH0pO1xyXG4gICAgXHJcbiAgICAgICAgcmV0dXJuIGNvcGllZE1vZGVsO1xyXG59XHJcblxyXG4vKipcclxuICogVGhpcyBmdW5jdGlvbiB3aWxsIG1ha2UgYSBjb3B5IG9mIGEgVEZKUyBtb2RlbCwgYXMgc28gaXQgd291bGQgYmUgcG9zc2libGUgXHJcbiAqIHRvIGVyYXNlIHRoZSBvcmlnaW5hbC5cclxuICogQHBhcmFtIG1vZGVsIC0gbW9kZWwgdG8gYmUgY29waWVkXHJcbiAqIEByZXR1cm5zIC0gY29weSBvZiB0aGUgbW9kZWxcclxuICovICBcclxuY29weU1vZGVsIChtb2RlbDogdGYuU2VxdWVudGlhbCkgIHtcclxuICAgIFxyXG4gICAgICAgIGNvbnN0IGNvcHkgPSB0Zi5zZXF1ZW50aWFsKCk7YFxyXG4gICAgICAgIGBcclxuICAgICAgICBtb2RlbC5sYXllcnMuZm9yRWFjaChsYXllciA9PiB7XHJcbiAgICAgICAgICAgIGNvbnN0IGF1eCA9bGF5ZXI7XHJcbiAgICAgICAgICAgIC8vIGxheWVyLmRpc3Bvc2UoKTtcclxuICAgICAgICAgICAgY29weS5hZGQoYXV4KTtcclxuICAgICAgICB9KTtcclxuICAgICAgICBjb3B5LmNvbXBpbGUoe1xyXG4gICAgICAgICAgICBsb3NzOiBtb2RlbC5sb3NzLFxyXG4gICAgICAgICAgICBvcHRpbWl6ZXI6IG1vZGVsLm9wdGltaXplclxyXG4gICAgICAgIH0pO1xyXG4gICAgICAgIHJldHVybiBjb3B5O1xyXG4gICAgfVxyXG5cclxuXHJcbiAgICByZW1vdmVFbGVtZW50QnlJbmRleChhcnI6IGFueSwgaW5kZXg6IG51bWJlcik6IG51bWJlcltdIHtcclxuICAgICAgICAvLyBDaGVjayBpZiB0aGUgaW5kZXggaXMgd2l0aGluIGJvdW5kc1xyXG4gICAgICAgIGlmIChpbmRleCA+PSAwICYmIGluZGV4IDwgYXJyLmxlbmd0aCkge1xyXG4gICAgICAgICAgICAvLyBSZW1vdmUgdGhlIGVsZW1lbnQgYXQgdGhlIHNwZWNpZmllZCBpbmRleFxyXG4gICAgICAgICAgICBhcnIuc3BsaWNlKGluZGV4LCAxKTsgICAgICAgICAgICBcclxuICAgICAgICB9XHJcbiAgICAgICAgcmV0dXJuIGFycjtcclxuICAgIH1cclxuXHJcbiAgICByZW1vdmVFbGVtZW50KGFycjogYW55LCBlbGVtZW50OiBhbnkpOiBudW1iZXJbXSB7XHJcbiAgICAgICAgLy8gUmVtb3ZlIGFsbCBvY2N1cnJlbmNlcyBvZiB0aGUgc3BlY2lmaWVkIGVsZW1lbnQgZnJvbSB0aGUgYXJyYXlcclxuICAgICAgICByZXR1cm4gYXJyLmZpbHRlcigoaXRlbTogYW55KSA9PiBpdGVtICE9PSBlbGVtZW50KTtcclxuICAgIH1cclxuXHJcbmNsZWFuX2FycmF5X29mX3RlbnNvcnModGVuc29yczogdGYuU2VxdWVudGlhbFtdKSB7XHJcblxyXG4gICAgdGVuc29ycy5mb3JFYWNoKChlbGVtLCBpbmRleCk9PnsgXHJcbiAgICAgICAgLy8gaWYoIWluZGV4X3NlbGVjdGlvbi5pbmNsdWRlcyhpbmRleCkpICBcclxuICAgICAgICAgICAgZWxlbS5kaXNwb3NlKCkgXHJcbiAgICAgICAgfSk7XHJcblxyXG59XHJcblxyXG4gZ2V0Q2xhc3NOYW1lQnlTaWduYXR1cmUoY2xhc3NlczogQ2xhc3NbXSwgc2lnbmF0dXJlOiBudW1iZXJbXSkgeyAgICAgICAgICAgXHJcbiAgICBcclxuICAgICAgICBjb25zdCBjbGFzc19uYW1lID0gY2xhc3Nlcy5maW5kKHAgPT4ge1xyXG4gICAgICAgICAgICBsZXQgbWF0Y2ggPSB0cnVlO1xyXG4gICAgICAgICAgICBwLnNpZ25hdHVyZT8uZm9yRWFjaCAoKGVsZW0saW5kZXgpPT57IFxyXG4gICAgICAgICAgICAgICAgaWYoZWxlbSE9PXNpZ25hdHVyZVtpbmRleF0pXHJcbiAgICAgICAgICAgICAgICAgICAgbWF0Y2g9ZmFsc2U7ICAgICAgICAgICAgICAgIFxyXG4gICAgICAgICAgICB9KVxyXG5cclxuICAgICAgICAgICAgcmV0dXJuIG1hdGNoO1xyXG4gICAgICAgIH0pO1xyXG5cclxuICAgICAgICByZXR1cm4gY2xhc3NfbmFtZSA/IGNsYXNzX25hbWUubmFtZSA6IFwibm90IGZvdW5kXCI7XHJcbn1cclxuXHJcbmlkZW50aXR5TWF0cml4KG46IG51bWJlcik6IG51bWJlcltdW10ge1xyXG4gICAgICAgIHJldHVybiBBcnJheS5mcm9tKHsgbGVuZ3RoOiBuIH0sIChfLCBpKSA9PiBBcnJheS5mcm9tKHsgbGVuZ3RoOiBuIH0sIChfLCBqKSA9PiAoaSA9PT0gaiA/IDEgOiAwKSkpO1xyXG59ICBcclxuXHJcblxyXG5pbmRleE9mTWF4KGFycjogbnVtYmVyW10pOiBudW1iZXIge1xyXG4gICAgICAgIGlmIChhcnIubGVuZ3RoID09PSAwKSB7XHJcbiAgICAgICAgICAgIHJldHVybiAtMTsgLy8gUmV0dXJuIC0xIGZvciBhbiBlbXB0eSBhcnJheVxyXG4gICAgICAgIH1cclxuICAgIFxyXG4gICAgICAgIGxldCBtYXggPSBhcnJbMF07XHJcbiAgICAgICAgbGV0IG1heEluZGV4ID0gMDtcclxuICAgIFxyXG4gICAgICAgIGZvciAobGV0IGkgPSAxOyBpIDwgYXJyLmxlbmd0aDsgaSsrKSB7XHJcbiAgICAgICAgICAgIGlmIChhcnJbaV0gPiBtYXgpIHtcclxuICAgICAgICAgICAgICAgIG1heEluZGV4ID0gaTtcclxuICAgICAgICAgICAgICAgIG1heCA9IGFycltpXTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgIH1cclxuICAgIFxyXG4gICAgICAgIHJldHVybiBtYXhJbmRleDtcclxuICAgIH1cclxuXHJcblxyXG5zdWZmbGUoYXJyYXkxOmFueSwgYXJyYXkyOiBhbnkpe1xyXG4gICAgICAgIC8vIFNodWZmbGUgdGhlIG9yZGVyIG9mIGVsZW1lbnRzXHJcbiAgICBmb3IgKGxldCBpID0gYXJyYXkxLmxlbmd0aCAtIDE7IGkgPiAwOyBpLS0pIHtcclxuICAgIGNvbnN0IGogPSBNYXRoLmZsb29yKE1hdGgucmFuZG9tKCkgKiAoaSArIDEpKTtcclxuXHJcbiAgICAvLyBTd2FwIGVsZW1lbnRzIGluIGJvdGggYXJyYXlzXHJcbiAgICBbYXJyYXkxW2ldLCBhcnJheTFbal1dID0gW2FycmF5MVtqXSwgYXJyYXkxW2ldXTtcclxuICAgIFthcnJheTJbaV0sIGFycmF5MltqXV0gPSBbYXJyYXkyW2pdLCBhcnJheTJbaV1dO1xyXG4gICAgICB9XHJcbiAgICBcclxuICAgIFxyXG4gICAgfVxyXG5cclxuICAgIHNvcnRCeVZhbHVlUHJlc2VydmluZ0luZGV4PFQ+KFxyXG4gICAgICAgIGFycjE6IG51bWJlcltdLFxyXG4gICAgICAgIGFycjI6IGFueVtdXHJcbiAgICApOiBUW10ge1xyXG5cclxuICAgICAgICAvLyBjb25zb2xlLmxvZyhcIlZlY3RvciBmb3Igb3JnYW5pemluZzogXCIsIGFycjEpXHJcbiAgICAgICAgLy8gYXJyMlswXS5zdW1tYXJ5KClcclxuXHJcbiAgICAgICAgLy8gQ3JlYXRlIGFuIGFycmF5IG9mIG9iamVjdHMgd2l0aCB2YWx1ZSwgaW5kZXggZnJvbSBhcnIxLCBhbmQgb3JpZ2luYWwgaW5kZXhcclxuICAgICAgICBjb25zdCBwYWlyaW5nQXJyYXkgPSBhcnIxLm1hcCgodmFsdWUsIGluZGV4KSA9PiAoe1xyXG4gICAgICAgICAgICB2YWx1ZSxcclxuICAgICAgICAgICAgaW5kZXgsXHJcbiAgICAgICAgICAgIG9yaWdpbmFsSW5kZXg6IGluZGV4LFxyXG4gICAgICAgICAgICBlbGVtZW50RnJvbUFycjI6IGFycjJbaW5kZXhdLCAvLyBQcmVzZXJ2ZSB0aGUgY29ycmVzcG9uZGluZyBlbGVtZW50IGZyb20gYXJyMlxyXG4gICAgICAgIH0pKTtcclxuICAgIFxyXG4gICAgICAgIC8vIFNvcnQgdGhlIHBhaXJpbmcgYXJyYXkgYnkgdmFsdWUgKGxhcmdlc3QgdG8gc21hbGxlc3QpXHJcbiAgICAgICAgcGFpcmluZ0FycmF5LnNvcnQoKGEsIGIpID0+IGIudmFsdWUgLSBhLnZhbHVlKTtcclxuXHJcbiAgICAgICAgXHJcbiAgICBcclxuICAgICAgICAvLyBFeHRyYWN0IHRoZSBzb3J0ZWQgZWxlbWVudHMgZnJvbSBhcnIyIGJhc2VkIG9uIHRoZSBvcmlnaW5hbCBpbmRleFxyXG4gICAgICAgIGNvbnN0IHNvcnRlZEVsZW1lbnRzRnJvbUFycjIgPSBwYWlyaW5nQXJyYXkubWFwKHBhaXIgPT4gcGFpci5lbGVtZW50RnJvbUFycjIpO1xyXG4gICAgXHJcbiAgICAgICAgcmV0dXJuIHNvcnRlZEVsZW1lbnRzRnJvbUFycjI7XHJcbiAgICB9ICAgIFxyXG4gICAgXHJcbn1cclxuXHJcbiJdfQ==
|