retinanetjs
Version:
Wrapper for models built using keras-retinanet.
194 lines • 17.2 kB
JavaScript
;
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const tf = __importStar(require("@tensorflow/tfjs"));
const initializers_1 = require("@tensorflow/tfjs-layers/dist/initializers"); // tslint:disable-line
const anchors_1 = require("./anchors");
class UpsampleLike extends tf.layers.Layer {
constructor() {
super({});
}
computeOutputShape(inputShape) {
return [
inputShape[0][0],
inputShape[1][1],
inputShape[1][2],
inputShape[0][3]
];
}
call(inputs, _) {
const [source, target] = inputs;
const targetShape = target.shape;
return tf.image.resizeNearestNeighbor(source, [
targetShape[1],
targetShape[2]
]);
}
static get className() {
return 'UpsampleLike';
}
}
class PriorProbability extends initializers_1.Zeros {
}
// tslint:disable-line
/** @nocollapse */
PriorProbability.className = 'PriorProbability';
tf.serialization.registerClass(UpsampleLike); // Needed for serialization.
tf.serialization.registerClass(PriorProbability);
/**
* Represents a RetinaNet model. Rather than creating directly,
* it is intended to be created using `load()`.
*/
class RetinaNet {
constructor(model, classes, preprocessingMode, anchorParams = anchors_1.defaultAnchorParameters) {
const [height, width] = model.inputs[0].shape.slice(1, 3);
// tslint:disable-next-line
if (height === -1 || width === -1) {
throw new Error('RetinaNetJS only supports fixed input sizes.');
}
// tslint:disable-next-line
if (preprocessingMode !== 'tf' && preprocessingMode !== 'caffe') {
throw new Error('preprocessingMode must be either `tf` or `caffe`.');
}
this.width = width;
this.height = height;
this.model = model;
this.classes = classes;
this.preprocessingMode = preprocessingMode;
this.anchorParams = anchorParams;
}
/**
* Computes predictions. We currently do not support class-specific filtering.
* When non-max suppression is applied, it will be across all boxes, regardless of class.
*
* @param img The image object on which to run object detection
* @param threshold The prediction threshold
* @param nmsThreshold The non-max suppresion IoU threshold
*/
async detect(img, threshold = 0.5, nmsThreshold = 0.5) {
// Build model input from image
const [X, padX, padY] = tf.tidy(() => {
const imageTensor = !(img instanceof tf.Tensor)
? tf.browser.fromPixels(img)
: img;
return this.handleImageTensor(imageTensor);
});
// Run inference
const y = this.model.predict(X);
const [coords, classification] = tf.tidy(() => {
const [boxDeltas, classScores] = y.map(t => t.squeeze([0]));
const anchorBoxes = anchors_1.anchorsForShape(this.model.inputs[0].shape.slice(1, 3), this.anchorParams);
const mean = [0, 0, 0, 0];
const std = [0.2, 0.2, 0.2, 0.2];
const width = anchorBoxes
.slice([0, 2], [-1, 1])
.sub(anchorBoxes.slice([0, 0], [-1, 1]));
const height = anchorBoxes
.slice([0, 2], [-1, 1])
.sub(anchorBoxes.slice([0, 0], [-1, 1]));
const x1y1x2y2 = tf.concat([0, 1, 2, 3].map(i => {
return anchorBoxes.slice([0, i], [-1, 1]).add(boxDeltas
.slice([0, i], [-1, 1])
.mul(std[i])
.add(mean[i])
.mul(i % 2 === 0 ? width : height));
}), 1);
// TODO: Add support for class-specific filtering and turning nms on and off, just
// like in retinanet.
return [x1y1x2y2, classScores];
});
const selected = await tf.image.nonMaxSuppressionAsync(coords, classification.max(1, false), 300, nmsThreshold, threshold);
const detections = tf.tidy(() => {
const classificationNms = classification.gather(selected);
const coordsNms = coords
.gather(selected)
.div([
this.width - padX,
this.height - padY,
this.width - padX,
this.height - padY
]);
const x1y1x2y2ls = tf
.concat([
coordsNms,
classificationNms
.argMax(1)
.expandDims(1)
.cast('float32'),
classificationNms.max(1, true)
], 1)
.arraySync();
return x1y1x2y2ls.map(r => {
const [x1, y1, x2, y2, labelIdx, score] = r;
return { label: this.classes[labelIdx], score, x1, x2, y1, y2 };
});
});
X.dispose();
y.map(t => t.dispose());
selected.dispose();
return detections;
}
/**
* Remove the model from memory.
*/
dispose() {
this.model.dispose();
}
handleImageTensor(imageTensor) {
return tf.tidy(() => {
const inputHeight = imageTensor.shape[0];
const inputWidth = imageTensor.shape[1];
const [outputHeight, outputWidth] = [this.height, this.width];
const scale = Math.min(outputHeight / inputHeight, outputWidth / inputWidth);
const padY = outputHeight - Math.round(scale * inputHeight);
const padX = outputWidth - Math.round(scale * inputWidth);
const scaledTensor = scale === 1
? imageTensor
: imageTensor.resizeBilinear([
Math.round(scale * inputHeight),
Math.round(scale * inputWidth)
]);
const paddedTensor = padX === 0 && padY === 0
? scaledTensor
: scaledTensor.pad([[0, padY], [0, padX], [0, 0]]);
const normedTensor = this.preprocessingMode === 'tf'
? paddedTensor.sub(127.5).div(127.5)
: paddedTensor.sub(tf.tensor3d([[[103.939, 116.779, 123.68]]]));
return [normedTensor.expandDims(0).cast('float32'), padX, padY];
});
}
}
exports.RetinaNet = RetinaNet;
/**
*
* @param modelPath The path to the model or a `tf.io.IOHandler` object
* @param classes The list of detected classes
* @param preprocessingMode One of `tf` or `caffe`. Check the `preprocess_images`
* method of your backbone to see which you should use.
* @param onProgress A callback to report progress
* @param anchorParams The anchor parameters for your model
*/
async function load(modelPath, classes, preprocessingMode, onProgress, anchorParams = anchors_1.defaultAnchorParameters) {
const tfOnProgress = (progress) => onProgress(0.9 * progress, 'Downloading');
const model = await tf.loadLayersModel(modelPath, {
onProgress: tfOnProgress,
strict: false
});
const detector = new RetinaNet(model, classes, preprocessingMode, anchorParams);
if (onProgress)
onProgress(0.92, 'Building'); // tslint:disable-line
setTimeout(async () => {
await detector.detect(tf.ones([100, 100, 3]));
if (onProgress)
onProgress(1.0, 'Finished'); // tslint:disable-line
}, 100);
return detector;
}
exports.load = load;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0aW5hbmV0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2xpYi9yZXRpbmFuZXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7O0FBQUEscURBQXVDO0FBQ3ZDLDRFQUFrRSxDQUFDLHNCQUFzQjtBQUV6Rix1Q0FJbUI7QUFFbkIsTUFBTSxZQUFhLFNBQVEsRUFBRSxDQUFDLE1BQU0sQ0FBQyxLQUFLO0lBQ3hDO1FBQ0UsS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ1osQ0FBQztJQUVNLGtCQUFrQixDQUFDLFVBQXNCO1FBQzlDLE9BQU87WUFDTCxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ2hCLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDaEIsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUNoQixVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1NBQ2pCLENBQUM7SUFDSixDQUFDO0lBRU0sSUFBSSxDQUFDLE1BQXFCLEVBQUUsQ0FBUztRQUMxQyxNQUFNLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxHQUFHLE1BQU0sQ0FBQztRQUNoQyxNQUFNLFdBQVcsR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDO1FBQ2pDLE9BQU8sRUFBRSxDQUFDLEtBQUssQ0FBQyxxQkFBcUIsQ0FBQyxNQUFNLEVBQUU7WUFDNUMsV0FBVyxDQUFDLENBQUMsQ0FBQztZQUNkLFdBQVcsQ0FBQyxDQUFDLENBQUM7U0FDZixDQUFDLENBQUM7SUFDTCxDQUFDO0lBRU0sTUFBTSxLQUFLLFNBQVM7UUFDekIsT0FBTyxjQUFjLENBQUM7SUFDeEIsQ0FBQztDQUNGO0FBRUQsTUFBTSxnQkFBaUIsU0FBUSxvQkFBSzs7QUFDbEMsc0JBQXNCO0FBQ3RCLGtCQUFrQjtBQUNKLDBCQUFTLEdBQUcsa0JBQWtCLENBQUM7QUFHL0MsRUFBRSxDQUFDLGFBQWEsQ0FBQyxhQUFhLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyw0QkFBNEI7QUFDMUUsRUFBRSxDQUFDLGFBQWEsQ0FBQyxhQUFhLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztBQWVqRDs7O0dBR0c7QUFDSCxNQUFhLFNBQVM7SUFRcEIsWUFDRSxLQUFxQixFQUNyQixPQUFpQixFQUNqQixpQkFBeUIsRUFDekIsWUFBWSxHQUFHLGlDQUF1QjtRQUV0QyxNQUFNLENBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQyxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFhLENBQUM7UUFDdkUsMkJBQTJCO1FBQzNCLElBQUksTUFBTSxLQUFLLENBQUMsQ0FBQyxJQUFJLEtBQUssS0FBSyxDQUFDLENBQUMsRUFBRTtZQUNqQyxNQUFNLElBQUksS0FBSyxDQUFDLDhDQUE4QyxDQUFDLENBQUM7U0FDakU7UUFDRCwyQkFBMkI7UUFDM0IsSUFBSSxpQkFBaUIsS0FBSyxJQUFJLElBQUksaUJBQWlCLEtBQUssT0FBTyxFQUFFO1lBQy9ELE1BQU0sSUFBSSxLQUFLLENBQUMsbURBQW1ELENBQUMsQ0FBQztTQUN0RTtRQUNELElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDO1FBQ25CLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDO1FBQ3JCLElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDO1FBQ25CLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxpQkFBaUIsQ0FBQztRQUMzQyxJQUFJLENBQUMsWUFBWSxHQUFHLFlBQVksQ0FBQztJQUNuQyxDQUFDO0lBRUQ7Ozs7Ozs7T0FPRztJQUNJLEtBQUssQ0FBQyxNQUFNLENBQ2pCLEdBS29CLEVBQ3BCLFNBQVMsR0FBRyxHQUFHLEVBQ2YsWUFBWSxHQUFHLEdBQUc7UUFFbEIsK0JBQStCO1FBQy9CLE1BQU0sQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFO1lBQ25DLE1BQU0sV0FBVyxHQUFHLENBQUMsQ0FBQyxHQUFHLFlBQVksRUFBRSxDQUFDLE1BQU0sQ0FBQztnQkFDN0MsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQztnQkFDNUIsQ0FBQyxDQUFDLEdBQUcsQ0FBQztZQUNSLE9BQU8sSUFBSSxDQUFDLGlCQUFpQixDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBQzdDLENBQUMsQ0FBQyxDQUFDO1FBQ0gsZ0JBQWdCO1FBQ2hCLE1BQU0sQ0FBQyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBa0IsQ0FBQztRQUNqRCxNQUFNLENBQUMsTUFBTSxFQUFFLGNBQWMsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFO1lBQzVDLE1BQU0sQ0FBQyxTQUFTLEVBQUUsV0FBVyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDNUQsTUFBTSxXQUFXLEdBQUcseUJBQWUsQ0FDakMsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFhLEVBQ25ELElBQUksQ0FBQyxZQUFZLENBQ2xCLENBQUM7WUFFRixNQUFNLElBQUksR0FBRyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO1lBQzFCLE1BQU0sR0FBRyxHQUFHLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUM7WUFDakMsTUFBTSxLQUFLLEdBQUcsV0FBVztpQkFDdEIsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7aUJBQ3RCLEdBQUcsQ0FBQyxXQUFXLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQzNDLE1BQU0sTUFBTSxHQUFHLFdBQVc7aUJBQ3ZCLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO2lCQUN0QixHQUFHLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUUzQyxNQUFNLFFBQVEsR0FBRyxFQUFFLENBQUMsTUFBTSxDQUN4QixDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRTtnQkFDbkIsT0FBTyxXQUFXLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQzNDLFNBQVM7cUJBQ04sS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7cUJBQ3RCLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUM7cUJBQ1gsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztxQkFDWixHQUFHLENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQ3JDLENBQUM7WUFDSixDQUFDLENBQUMsRUFDRixDQUFDLENBQ2EsQ0FBQztZQUVqQixrRkFBa0Y7WUFDbEYscUJBQXFCO1lBQ3JCLE9BQU8sQ0FBQyxRQUFRLEVBQUUsV0FBVyxDQUFDLENBQUM7UUFDakMsQ0FBQyxDQUFDLENBQUM7UUFDSCxNQUFNLFFBQVEsR0FBRyxNQUFNLEVBQUUsQ0FBQyxLQUFLLENBQUMsc0JBQXNCLENBQ3BELE1BQU0sRUFDTixjQUFjLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxLQUFLLENBQUMsRUFDNUIsR0FBRyxFQUNILFlBQVksRUFDWixTQUFTLENBQ1YsQ0FBQztRQUNGLE1BQU0sVUFBVSxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFO1lBQzlCLE1BQU0saUJBQWlCLEdBQUcsY0FBYyxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQWdCLENBQUM7WUFDekUsTUFBTSxTQUFTLEdBQUcsTUFBTTtpQkFDckIsTUFBTSxDQUFDLFFBQVEsQ0FBQztpQkFDaEIsR0FBRyxDQUFDO2dCQUNILElBQUksQ0FBQyxLQUFLLEdBQUcsSUFBSTtnQkFDakIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJO2dCQUNsQixJQUFJLENBQUMsS0FBSyxHQUFHLElBQUk7Z0JBQ2pCLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSTthQUNuQixDQUFDLENBQUM7WUFDTCxNQUFNLFVBQVUsR0FBRyxFQUFFO2lCQUNsQixNQUFNLENBQ0w7Z0JBQ0UsU0FBUztnQkFDVCxpQkFBaUI7cUJBQ2QsTUFBTSxDQUFDLENBQUMsQ0FBQztxQkFDVCxVQUFVLENBQUMsQ0FBQyxDQUFDO3FCQUNiLElBQUksQ0FBQyxTQUFTLENBQUM7Z0JBQ2xCLGlCQUFpQixDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsSUFBSSxDQUFDO2FBQy9CLEVBQ0QsQ0FBQyxDQUNGO2lCQUNBLFNBQVMsRUFBZ0IsQ0FBQztZQUM3QixPQUFPLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUU7Z0JBQ3hCLE1BQU0sQ0FBQyxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsUUFBUSxFQUFFLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztnQkFDNUMsT0FBTyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxFQUFFLEtBQUssRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsQ0FBQztZQUNsRSxDQUFDLENBQUMsQ0FBQztRQUNMLENBQUMsQ0FBQyxDQUFDO1FBQ0gsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ1osQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1FBQ3hCLFFBQVEsQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUNuQixPQUFPLFVBQVUsQ0FBQztJQUNwQixDQUFDO0lBRUQ7O09BRUc7SUFDSSxPQUFPO1FBQ1osSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUN2QixDQUFDO0lBRU8saUJBQWlCLENBQ3ZCLFdBQXdCO1FBRXhCLE9BQU8sRUFBRSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUU7WUFDbEIsTUFBTSxXQUFXLEdBQUcsV0FBVyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUN6QyxNQUFNLFVBQVUsR0FBRyxXQUFXLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ3hDLE1BQU0sQ0FBQyxZQUFZLEVBQUUsV0FBVyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUM5RCxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsR0FBRyxDQUNwQixZQUFZLEdBQUcsV0FBVyxFQUMxQixXQUFXLEdBQUcsVUFBVSxDQUN6QixDQUFDO1lBQ0YsTUFBTSxJQUFJLEdBQUcsWUFBWSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxHQUFHLFdBQVcsQ0FBQyxDQUFDO1lBQzVELE1BQU0sSUFBSSxHQUFHLFdBQVcsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssR0FBRyxVQUFVLENBQUMsQ0FBQztZQUMxRCxNQUFNLFlBQVksR0FDaEIsS0FBSyxLQUFLLENBQUM7Z0JBQ1QsQ0FBQyxDQUFDLFdBQVc7Z0JBQ2IsQ0FBQyxDQUFDLFdBQVcsQ0FBQyxjQUFjLENBQUM7b0JBQ3pCLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxHQUFHLFdBQVcsQ0FBQztvQkFDL0IsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLEdBQUcsVUFBVSxDQUFDO2lCQUMvQixDQUFDLENBQUM7WUFDVCxNQUFNLFlBQVksR0FDaEIsSUFBSSxLQUFLLENBQUMsSUFBSSxJQUFJLEtBQUssQ0FBQztnQkFDdEIsQ0FBQyxDQUFDLFlBQVk7Z0JBQ2QsQ0FBQyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDdkQsTUFBTSxZQUFZLEdBQ2hCLElBQUksQ0FBQyxpQkFBaUIsS0FBSyxJQUFJO2dCQUM3QixDQUFDLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDO2dCQUNwQyxDQUFDLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUNwRSxPQUFPLENBQUMsWUFBWSxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FJN0QsQ0FBQztRQUNKLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztDQUNGO0FBOUtELDhCQThLQztBQUVEOzs7Ozs7OztHQVFHO0FBQ0ksS0FBSyxVQUFVLElBQUksQ0FDeEIsU0FBbUMsRUFDbkMsT0FBaUIsRUFDakIsaUJBQXlCLEVBQ3pCLFVBQXdELEVBQ3hELFlBQVksR0FBRyxpQ0FBdUI7SUFFdEMsTUFBTSxZQUFZLEdBQUcsQ0FBQyxRQUFnQixFQUFFLEVBQUUsQ0FDeEMsVUFBVSxDQUFDLEdBQUcsR0FBRyxRQUFRLEVBQUUsYUFBYSxDQUFDLENBQUM7SUFDNUMsTUFBTSxLQUFLLEdBQUcsTUFBTSxFQUFFLENBQUMsZUFBZSxDQUFDLFNBQVMsRUFBRTtRQUNoRCxVQUFVLEVBQUUsWUFBWTtRQUN4QixNQUFNLEVBQUUsS0FBSztLQUNkLENBQUMsQ0FBQztJQUNILE1BQU0sUUFBUSxHQUFHLElBQUksU0FBUyxDQUM1QixLQUFLLEVBQ0wsT0FBTyxFQUNQLGlCQUFpQixFQUNqQixZQUFZLENBQ2IsQ0FBQztJQUNGLElBQUksVUFBVTtRQUFFLFVBQVUsQ0FBQyxJQUFJLEVBQUUsVUFBVSxDQUFDLENBQUMsQ0FBQyxzQkFBc0I7SUFDcEUsVUFBVSxDQUFDLEtBQUssSUFBSSxFQUFFO1FBQ3BCLE1BQU0sUUFBUSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDOUMsSUFBSSxVQUFVO1lBQUUsVUFBVSxDQUFDLEdBQUcsRUFBRSxVQUFVLENBQUMsQ0FBQyxDQUFDLHNCQUFzQjtJQUNyRSxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDUixPQUFPLFFBQVEsQ0FBQztBQUNsQixDQUFDO0FBekJELG9CQXlCQyJ9