@jax-data-science/sci-image-visualizer 0.2.16 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/index.mjs +6 -1
- package/esm2022/lib/contracts/semantic-segmenter.contract.mjs +11 -0
- package/esm2022/lib/toolbar/segmentation/retinal-layer-tool.service.mjs +197 -0
- package/esm2022/lib/toolbar/segmentation/retinal-model-registry.mjs +119 -0
- package/esm2022/lib/toolbar/segmentation/retinal-segmenter.service.mjs +157 -0
- package/esm2022/lib/toolbar/toolbar.component.mjs +31 -3
- package/esm2022/lib/visualizer.component.mjs +86 -22
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +579 -18
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
- package/index.d.ts +4 -0
- package/lib/contracts/semantic-segmenter.contract.d.ts +105 -0
- package/lib/toolbar/segmentation/retinal-layer-tool.service.d.ts +49 -0
- package/lib/toolbar/segmentation/retinal-model-registry.d.ts +71 -0
- package/lib/toolbar/segmentation/retinal-segmenter.service.d.ts +49 -0
- package/lib/toolbar/toolbar.component.d.ts +15 -1
- package/lib/visualizer.component.d.ts +38 -2
- package/package.json +2 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { getOrtWasmBase } from './ort-runtime-config';
|
|
3
|
+
import { getDefaultRetinalModelId, getRetinalModel, } from './retinal-model-registry';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
/**
|
|
6
|
+
* Default in-library {@link ISemanticSegmenter}, backed by jax-ai-js.
|
|
7
|
+
*
|
|
8
|
+
* `jax-ai-js` is a regular dependency but is **lazy-imported** on first use, so
|
|
9
|
+
* apps that never segment retinal layers pay nothing for it — or for
|
|
10
|
+
* onnxruntime-web — in their initial bundle. Mirrors
|
|
11
|
+
* {@link YoloSegmenterService}, including its per-model-id cache.
|
|
12
|
+
*
|
|
13
|
+
* WEBGPU ONLY, AND A HOST MUST BE READY FOR THAT
|
|
14
|
+
* Unlike the YOLO and Cellpose services, there is no WASM fallback to degrade
|
|
15
|
+
* to: ORT-Web's WASM EP dies with `std::bad_alloc` on these graphs at 512², at
|
|
16
|
+
* every precision. jax-ai-js throws `WebGpuRequiredError` rather than trying,
|
|
17
|
+
* and it is re-thrown here unchanged so a host can show a real
|
|
18
|
+
* unsupported-browser message instead of a generic failure.
|
|
19
|
+
*/
|
|
20
|
+
export class RetinalSegmenterService {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.instances = new Map();
|
|
23
|
+
this.loading = new Map();
|
|
24
|
+
}
|
|
25
|
+
async segmentSemantic(image, opts = {}, progress) {
|
|
26
|
+
const modelId = opts.modelId ?? getDefaultRetinalModelId();
|
|
27
|
+
const def = this.requireModel(modelId);
|
|
28
|
+
let announcedDownload = false;
|
|
29
|
+
const seg = await this.getModel(modelId, (loaded, total) => {
|
|
30
|
+
if (total)
|
|
31
|
+
progress?.onProgress?.(loaded / total);
|
|
32
|
+
progress?.onBytes?.(loaded, total);
|
|
33
|
+
if (loaded > 0 && !announcedDownload) {
|
|
34
|
+
announcedDownload = true;
|
|
35
|
+
// The size is in the message because it is ~590 MB — a user who
|
|
36
|
+
// clicked a button deserves to know that before it finishes.
|
|
37
|
+
progress?.onStatus?.(`Downloading ${def.label} model (~${def.sizeMb} MB)…`);
|
|
38
|
+
}
|
|
39
|
+
}, (status) => progress?.onStatus?.(status));
|
|
40
|
+
progress?.onStatus?.('Running inference…');
|
|
41
|
+
const result = await seg.segment({ data: image.data, width: image.width, height: image.height }, {
|
|
42
|
+
...(opts.classThreshold !== undefined ? { classThreshold: opts.classThreshold } : {}),
|
|
43
|
+
...(opts.classFilter ? { classFilter: opts.classFilter } : {}),
|
|
44
|
+
...(opts.simplifyTolerance !== undefined ? { simplifyTolerance: opts.simplifyTolerance } : {}),
|
|
45
|
+
...(opts.minArea !== undefined ? { minArea: opts.minArea } : {}),
|
|
46
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
47
|
+
// The library narrates its own phases (per patch, then tracing) because
|
|
48
|
+
// only it knows which one it is in.
|
|
49
|
+
onStatus: (s) => progress?.onStatus?.(s),
|
|
50
|
+
});
|
|
51
|
+
const regions = result.regions.map((r) => ({
|
|
52
|
+
exterior: r.exterior.map(([x, y]) => [x, y]),
|
|
53
|
+
holes: r.holes.map((h) => h.map(([x, y]) => [x, y])),
|
|
54
|
+
classId: r.classId,
|
|
55
|
+
className: r.className,
|
|
56
|
+
area: r.area,
|
|
57
|
+
}));
|
|
58
|
+
return {
|
|
59
|
+
regions,
|
|
60
|
+
classAreas: result.classAreas,
|
|
61
|
+
classNames: [...result.classNames],
|
|
62
|
+
width: result.width,
|
|
63
|
+
height: result.height,
|
|
64
|
+
unassignedFraction: result.unassignedFraction,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The shared segmenter for a model id, created on first use and reused
|
|
69
|
+
* thereafter (deduped across concurrent callers). Exposed so a host's
|
|
70
|
+
* processing pipeline can run on the same loaded session rather than paying
|
|
71
|
+
* the ~590 MB load twice.
|
|
72
|
+
*/
|
|
73
|
+
getModel(modelId = getDefaultRetinalModelId(), onProgress, onStatus) {
|
|
74
|
+
const existing = this.instances.get(modelId);
|
|
75
|
+
if (existing)
|
|
76
|
+
return Promise.resolve(existing);
|
|
77
|
+
const inFlight = this.loading.get(modelId);
|
|
78
|
+
if (inFlight)
|
|
79
|
+
return inFlight;
|
|
80
|
+
const def = this.requireModel(modelId);
|
|
81
|
+
const load = (async () => {
|
|
82
|
+
// Lazy: keep jax-ai-js + its ORT runtime out of the initial bundle.
|
|
83
|
+
const { RetinalSegmenter, configureOrt } = await import('jax-ai-js');
|
|
84
|
+
configureOrt({ wasmPaths: getOrtWasmBase() });
|
|
85
|
+
const seg = await RetinalSegmenter.fromPretrained({
|
|
86
|
+
modelUrl: def.modelUrl,
|
|
87
|
+
...(def.metaUrl ? { metaUrl: def.metaUrl } : {}),
|
|
88
|
+
onProgress: (loaded, total) => onProgress?.(loaded, total),
|
|
89
|
+
onStatus: (s) => onStatus?.(s),
|
|
90
|
+
});
|
|
91
|
+
this.instances.set(modelId, seg);
|
|
92
|
+
return seg;
|
|
93
|
+
})();
|
|
94
|
+
this.loading.set(modelId, load);
|
|
95
|
+
// A failed load must not poison the cache — the next caller should retry
|
|
96
|
+
// rather than inherit the rejected promise forever.
|
|
97
|
+
load.catch(() => this.loading.delete(modelId));
|
|
98
|
+
return load;
|
|
99
|
+
}
|
|
100
|
+
/** Whether a model is already loaded (warm). */
|
|
101
|
+
isLoaded(modelId = getDefaultRetinalModelId()) {
|
|
102
|
+
return this.instances.has(modelId);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Release one model, or all of them. Safe to call on a model never loaded.
|
|
106
|
+
*
|
|
107
|
+
* In-flight loads are torn down too: dropping a pending promise and returning
|
|
108
|
+
* would let the load finish afterwards, re-populate {@link instances}, and
|
|
109
|
+
* strand a live ORT session that nothing owns — disposal would report success
|
|
110
|
+
* while leaking. At 590 MB that leak is not survivable.
|
|
111
|
+
*/
|
|
112
|
+
async dispose(modelId) {
|
|
113
|
+
const ids = modelId
|
|
114
|
+
? [modelId]
|
|
115
|
+
: [...new Set([...this.instances.keys(), ...this.loading.keys()])];
|
|
116
|
+
await Promise.all(ids.map((id) => this.disposeOne(id)));
|
|
117
|
+
}
|
|
118
|
+
async disposeOne(id) {
|
|
119
|
+
const pending = this.loading.get(id);
|
|
120
|
+
const loaded = this.instances.get(id);
|
|
121
|
+
this.instances.delete(id);
|
|
122
|
+
this.loading.delete(id);
|
|
123
|
+
if (loaded)
|
|
124
|
+
await loaded.dispose();
|
|
125
|
+
if (!pending)
|
|
126
|
+
return;
|
|
127
|
+
// A pending load assigns instances[id] before its promise resolves, so by
|
|
128
|
+
// the time this await returns the entry is back. Clear it only if it still
|
|
129
|
+
// points at what *this* load produced — a newer load started meanwhile owns
|
|
130
|
+
// its own entry and must not be evicted.
|
|
131
|
+
const seg = await pending.catch(() => null);
|
|
132
|
+
if (!seg || seg === loaded)
|
|
133
|
+
return;
|
|
134
|
+
if (this.instances.get(id) === seg)
|
|
135
|
+
this.instances.delete(id);
|
|
136
|
+
await seg.dispose();
|
|
137
|
+
}
|
|
138
|
+
requireModel(modelId) {
|
|
139
|
+
const def = getRetinalModel(modelId);
|
|
140
|
+
if (!def)
|
|
141
|
+
throw new Error(`Unknown retinal model id "${modelId}".`);
|
|
142
|
+
if (!def.modelUrl.trim()) {
|
|
143
|
+
// Prefer the registry's specific explanation over "not configured" — the
|
|
144
|
+
// ResUNet-a entries are disabled for a documented accuracy reason, and a
|
|
145
|
+
// user who picked one deserves to be told which.
|
|
146
|
+
throw new Error(def.unavailableReason ?? `Retinal model "${modelId}" has no modelUrl configured.`);
|
|
147
|
+
}
|
|
148
|
+
return def;
|
|
149
|
+
}
|
|
150
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: RetinalSegmenterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
151
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: RetinalSegmenterService, providedIn: 'root' }); }
|
|
152
|
+
}
|
|
153
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: RetinalSegmenterService, decorators: [{
|
|
154
|
+
type: Injectable,
|
|
155
|
+
args: [{ providedIn: 'root' }]
|
|
156
|
+
}] });
|
|
157
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0aW5hbC1zZWdtZW50ZXIuc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9saWIvdG9vbGJhci9zZWdtZW50YXRpb24vcmV0aW5hbC1zZWdtZW50ZXIuc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRTNDLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUN0RCxPQUFPLEVBQ0wsd0JBQXdCLEVBQ3hCLGVBQWUsR0FFaEIsTUFBTSwwQkFBMEIsQ0FBQzs7QUFXbEM7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFFSCxNQUFNLE9BQU8sdUJBQXVCO0lBRHBDO1FBRW1CLGNBQVMsR0FBRyxJQUFJLEdBQUcsRUFBNEIsQ0FBQztRQUNoRCxZQUFPLEdBQUcsSUFBSSxHQUFHLEVBQXFDLENBQUM7S0FvSnpFO0lBbEpDLEtBQUssQ0FBQyxlQUFlLENBQ25CLEtBQWlFLEVBQ2pFLE9BQStCLEVBQUUsRUFDakMsUUFBa0M7UUFFbEMsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLE9BQU8sSUFBSSx3QkFBd0IsRUFBRSxDQUFDO1FBQzNELE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLENBQUM7UUFFdkMsSUFBSSxpQkFBaUIsR0FBRyxLQUFLLENBQUM7UUFDOUIsTUFBTSxHQUFHLEdBQUcsTUFBTSxJQUFJLENBQUMsUUFBUSxDQUM3QixPQUFPLEVBQ1AsQ0FBQyxNQUFNLEVBQUUsS0FBSyxFQUFFLEVBQUU7WUFDaEIsSUFBSSxLQUFLO2dCQUFFLFFBQVEsRUFBRSxVQUFVLEVBQUUsQ0FBQyxNQUFNLEdBQUcsS0FBSyxDQUFDLENBQUM7WUFDbEQsUUFBUSxFQUFFLE9BQU8sRUFBRSxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsQ0FBQztZQUNuQyxJQUFJLE1BQU0sR0FBRyxDQUFD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LGlCQUFpQixJQUFJLGtCQUFrQixPQUFPLCtCQUErQixDQUFDLENBQUM7UUFDckcsQ0FBQztRQUNELE9BQU8sR0FBRyxDQUFDO0lBQ2IsQ0FBQzs4R0FySlUsdUJBQXVCO2tIQUF2Qix1QkFBdUIsY0FEVixNQUFNOzsyRkFDbkIsdUJBQXVCO2tCQURuQyxVQUFVO21CQUFDLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGFibGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuaW1wb3J0IHsgZ2V0T3J0V2FzbUJhc2UgfSBmcm9tICcuL29ydC1ydW50aW1lLWNvbmZpZyc7XG5pbXBvcnQge1xuICBnZXREZWZhdWx0UmV0aW5hbE1vZGVsSWQsXG4gIGdldFJldGluYWxNb2RlbCxcbiAgdHlwZSBSZXRpbmFsTW9kZWxEZWYsXG59IGZyb20gJy4vcmV0aW5hbC1tb2RlbC1yZWdpc3RyeSc7XG5cbmltcG9ydCB0eXBlIHtcbiAgSVNlbWFudGljU2VnbWVudGVyLFxuICBTZW1hbnRpY1JlZ2lvbixcbiAgU2VtYW50aWNTZWdtZW50YXRpb24sXG4gIFNlbWFudGljU2VnbWVudE9wdGlvbnMsXG4gIFNlbWFudGljU2VnbWVudFByb2dyZXNzLFxufSBmcm9tICcuLi8uLi9jb250cmFjdHMvc2VtYW50aWMtc2VnbWVudGVyLmNvbnRyYWN0JztcbmltcG9ydCB0eXBlIHsgUmV0aW5hbFNlZ21lbnRlciB9IGZyb20gJ2pheC1haS1qcyc7XG5cbi8qKlxuICogRGVmYXVsdCBpbi1saWJyYXJ5IHtAbGluayBJU2VtYW50aWNTZWdtZW50ZXJ9LCBiYWNrZWQgYnkgamF4LWFpLWpzLlxuICpcbiAqIGBqYXgtYWktanNgIGlzIGEgcmVndWxhciBkZXBlbmRlbmN5IGJ1dCBpcyAqKmxhenktaW1wb3J0ZWQqKiBvbiBmaXJzdCB1c2UsIHNvXG4gKiBhcHBzIHRoYXQgbmV2ZXIgc2VnbWVudCByZXRpbmFsIGxheWVycyBwYXkgbm90aGluZyBmb3IgaXQg4oCUIG9yIGZvclxuICogb25ueHJ1bnRpbWUtd2ViIOKAlCBpbiB0aGVpciBpbml0aWFsIGJ1bmRsZS4gTWlycm9yc1xuICoge0BsaW5rIFlvbG9TZWdtZW50ZXJTZXJ2aWNlfSwgaW5jbHVkaW5nIGl0cyBwZXItbW9kZWwtaWQgY2FjaGUuXG4gKlxuICogV0VCR1BVIE9OTFksIEFORCBBIEhPU1QgTVVTVCBCRSBSRUFEWSBGT1IgVEhBVFxuICogVW5saWtlIHRoZSBZT0xPIGFuZCBDZWxscG9zZSBzZXJ2aWNlcywgdGhlcmUgaXMgbm8gV0FTTSBmYWxsYmFjayB0byBkZWdyYWRlXG4gKiB0bzogT1JULVdlYidzIFdBU00gRVAgZGllcyB3aXRoIGBzdGQ6OmJhZF9hbGxvY2Agb24gdGhlc2UgZ3JhcGhzIGF0IDUxMsKyLCBhdFxuICogZXZlcnkgcHJlY2lzaW9uLiBqYXgtYWktanMgdGhyb3dzIGBXZWJHcHVSZXF1aXJlZEVycm9yYCByYXRoZXIgdGhhbiB0cnlpbmcsXG4gKiBhbmQgaXQgaXMgcmUtdGhyb3duIGhlcmUgdW5jaGFuZ2VkIHNvIGEgaG9zdCBjYW4gc2hvdyBhIHJlYWxcbiAqIHVuc3VwcG9ydGVkLWJyb3dzZXIgbWVzc2FnZSBpbnN0ZWFkIG9mIGEgZ2VuZXJpYyBmYWlsdXJlLlxuICovXG5ASW5qZWN0YWJsZSh7IHByb3ZpZGVkSW46ICdyb290JyB9KVxuZXhwb3J0IGNsYXNzIFJldGluYWxTZWdtZW50ZXJTZXJ2aWNlIGltcGxlbWVudHMgSVNlbWFudGljU2VnbWVudGVyIHtcbiAgcHJpdmF0ZSByZWFkb25seSBpbnN0YW5jZXMgPSBuZXcgTWFwPHN0cmluZywgUmV0aW5hbFNlZ21lbnRlcj4oKTtcbiAgcHJpdmF0ZSByZWFkb25seSBsb2FkaW5nID0gbmV3IE1hcDxzdHJpbmcsIFByb21pc2U8UmV0aW5hbFNlZ21lbnRlcj4+KCk7XG5cbiAgYXN5bmMgc2VnbWVudFNlbWFudGljKFxuICAgIGltYWdlOiB7IGRhdGE6IFVpbnQ4Q2xhbXBlZEFycmF5OyB3aWR0aDogbnVtYmVyOyBoZWlnaHQ6IG51bWJlciB9LFxuICAgIG9wdHM6IFNlbWFudGljU2VnbWVudE9wdGlvbnMgPSB7fSxcbiAgICBwcm9ncmVzcz86IFNlbWFudGljU2VnbWVudFByb2dyZXNzLFxuICApOiBQcm9taXNlPFNlbWFudGljU2VnbWVudGF0aW9uPiB7XG4gICAgY29uc3QgbW9kZWxJZCA9IG9wdHMubW9kZWxJZCA/PyBnZXREZWZhdWx0UmV0aW5hbE1vZGVsSWQoKTtcbiAgICBjb25zdCBkZWYgPSB0aGlzLnJlcXVpcmVNb2RlbChtb2RlbElkKTtcblxuICAgIGxldCBhbm5vdW5jZWREb3dubG9hZCA9IGZhbHNlO1xuICAgIGNvbnN0IHNlZyA9IGF3YWl0IHRoaXMuZ2V0TW9kZWwoXG4gICAgICBtb2RlbElkLFxuICAgICAgKGxvYWRlZCwgdG90YWwpID0+IHtcbiAgICAgICAgaWYgKHRvdGFsKSBwcm9ncmVzcz8ub25Qcm9ncmVzcz8uKGxvYWRlZCAvIHRvdGFsKTtcbiAgICAgICAgcHJvZ3Jlc3M/Lm9uQnl0ZXM/Lihsb2FkZWQsIHRvdGFsKTtcbiAgICAgICAgaWYgKGxvYWRlZCA+IDAgJiYgIWFubm91bmNlZERvd25sb2FkKSB7XG4gICAgICAgICAgYW5ub3VuY2VkRG93bmxvYWQgPSB0cnVlO1xuICAgICAgICAgIC8vIFRoZSBzaXplIGlzIGluIHRoZSBtZXNzYWdlIGJlY2F1c2UgaXQgaXMgfjU5MCBNQiDigJQgYSB1c2VyIHdob1xuICAgICAgICAgIC8vIGNsaWNrZWQgYSBidXR0b24gZGVzZXJ2ZXMgdG8ga25vdyB0aGF0IGJlZm9yZSBpdCBmaW5pc2hlcy5cbiAgICAgICAgICBwcm9ncmVzcz8ub25TdGF0dXM/LihgRG93bmxvYWRpbmcgJHtkZWYubGFiZWx9IG1vZGVsICh+JHtkZWYuc2l6ZU1ifSBNQinigKZgKTtcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIChzdGF0dXMpID0+IHByb2dyZXNzPy5vblN0YXR1cz8uKHN0YXR1cyksXG4gICAgKTtcblxuICAgIHByb2dyZXNzPy5vblN0YXR1cz8uKCdSdW5uaW5nIGluZmVyZW5jZeKApicpO1xuICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHNlZy5zZWdtZW50KFxuICAgICAgeyBkYXRhOiBpbWFnZS5kYXRhLCB3aWR0aDogaW1hZ2Uud2lkdGgsIGhlaWdodDogaW1hZ2UuaGVpZ2h0IH0sXG4gICAgICB7XG4gICAgICAgIC4uLihvcHRzLmNsYXNzVGhyZXNob2xkICE9PSB1bmRlZmluZWQgPyB7IGNsYXNzVGhyZXNob2xkOiBvcHRzLmNsYXNzVGhyZXNob2xkIH0gOiB7fSksXG4gICAgICAgIC4uLihvcHRzLmNsYXNzRmlsdGVyID8geyBjbGFzc0ZpbHRlcjogb3B0cy5jbGFzc0ZpbHRlciB9IDoge30pLFxuICAgICAgICAuLi4ob3B0cy5zaW1wbGlmeVRvbGVyYW5jZSAhPT0gdW5kZWZpbmVkID8geyBzaW1wbGlmeVRvbGVyYW5jZTogb3B0cy5zaW1wbGlmeVRvbGVyYW5jZSB9IDoge30pLFxuICAgICAgICAuLi4ob3B0cy5taW5BcmVhICE9PSB1bmRlZmluZWQgPyB7IG1pbkFyZWE6IG9wdHMubWluQXJlYSB9IDoge30pLFxuICAgICAgICAuLi4ob3B0cy5zaWduYWwgPyB7IHNpZ25hbDogb3B0cy5zaWduYWwgfSA6IHt9KSxcbiAgICAgICAgLy8gVGhlIGxpYnJhcnkgbmFycmF0ZXMgaXRzIG93biBwaGFzZXMgKHBlciBwYXRjaCwgdGhlbiB0cmFjaW5nKSBiZWNhdXNlXG4gICAgICAgIC8vIG9ubHkgaXQga25vd3Mgd2hpY2ggb25lIGl0IGlzIGluLlxuICAgICAgICBvblN0YXR1czogKHMpID0+IHByb2dyZXNzPy5vblN0YXR1cz8uKHMpLFxuICAgICAgfSxcbiAgICApO1xuXG4gICAgY29uc3QgcmVnaW9uczogU2VtYW50aWNSZWdpb25bXSA9IHJlc3VsdC5yZWdpb25zLm1hcCgocikgPT4gKHtcbiAgICAgIGV4dGVyaW9yOiByLmV4dGVyaW9yLm1hcCgoW3gsIHldKTogW251bWJlciwgbnVtYmVyXSA9PiBbeCwgeV0pLFxuICAgICAgaG9sZXM6IHIuaG9sZXMubWFwKChoKSA9PiBoLm1hcCgoW3gsIHldKTogW251bWJlciwgbnVtYmVyXSA9PiBbeCwgeV0pKSxcbiAgICAgIGNsYXNzSWQ6IHIuY2xhc3NJZCxcbiAgICAgIGNsYXNzTmFtZTogci5jbGFzc05hbWUsXG4gICAgICBhcmVhOiByLmFyZWEsXG4gICAgfSkpO1xuXG4gICAgcmV0dXJuIHtcbiAgICAgIHJlZ2lvbnMsXG4gICAgICBjbGFzc0FyZWFzOiByZXN1bHQuY2xhc3NBcmVhcyxcbiAgICAgIGNsYXNzTmFtZXM6IFsuLi5yZXN1bHQuY2xhc3NOYW1lc10sXG4gICAgICB3aWR0aDogcmVzdWx0LndpZHRoLFxuICAgICAgaGVpZ2h0OiByZXN1bHQuaGVpZ2h0LFxuICAgICAgdW5hc3NpZ25lZEZyYWN0aW9uOiByZXN1bHQudW5hc3NpZ25lZEZyYWN0aW9uLFxuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogVGhlIHNoYXJlZCBzZWdtZW50ZXIgZm9yIGEgbW9kZWwgaWQsIGNyZWF0ZWQgb24gZmlyc3QgdXNlIGFuZCByZXVzZWRcbiAgICogdGhlcmVhZnRlciAoZGVkdXBlZCBhY3Jvc3MgY29uY3VycmVudCBjYWxsZXJzKS4gRXhwb3NlZCBzbyBhIGhvc3Qnc1xuICAgKiBwcm9jZXNzaW5nIHBpcGVsaW5lIGNhbiBydW4gb24gdGhlIHNhbWUgbG9hZGVkIHNlc3Npb24gcmF0aGVyIHRoYW4gcGF5aW5nXG4gICAqIHRoZSB+NTkwIE1CIGxvYWQgdHdpY2UuXG4gICAqL1xuICBnZXRNb2RlbChcbiAgICBtb2RlbElkOiBzdHJpbmcgPSBnZXREZWZhdWx0UmV0aW5hbE1vZGVsSWQoKSxcbiAgICBvblByb2dyZXNzPzogKGxvYWRlZDogbnVtYmVyLCB0b3RhbDogbnVtYmVyIHwgbnVsbCkgPT4gdm9pZCxcbiAgICBvblN0YXR1cz86IChzdGF0dXM6IHN0cmluZykgPT4gdm9pZCxcbiAgKTogUHJvbWlzZTxSZXRpbmFsU2VnbWVudGVyPiB7XG4gICAgY29uc3QgZXhpc3RpbmcgPSB0aGlzLmluc3RhbmNlcy5nZXQobW9kZWxJZCk7XG4gICAgaWYgKGV4aXN0aW5nKSByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKGV4aXN0aW5nKTtcblxuICAgIGNvbnN0IGluRmxpZ2h0ID0gdGhpcy5sb2FkaW5nLmdldChtb2RlbElkKTtcbiAgICBpZiAoaW5GbGlnaHQpIHJldHVybiBpbkZsaWdodDtcblxuICAgIGNvbnN0IGRlZiA9IHRoaXMucmVxdWlyZU1vZGVsKG1vZGVsSWQpO1xuICAgIGNvbnN0IGxvYWQgPSAoYXN5bmMgKCkgPT4ge1xuICAgICAgLy8gTGF6eToga2VlcCBqYXgtYWktanMgKyBpdHMgT1JUIHJ1bnRpbWUgb3V0IG9mIHRoZSBpbml0aWFsIGJ1bmRsZS5cbiAgICAgIGNvbnN0IHsgUmV0aW5hbFNlZ21lbnRlciwgY29uZmlndXJlT3J0IH0gPSBhd2FpdCBpbXBvcnQoJ2pheC1haS1qcycpO1xuICAgICAgY29uZmlndXJlT3J0KHsgd2FzbVBhdGhzOiBnZXRPcnRXYXNtQmFzZSgpIH0pO1xuICAgICAgY29uc3Qgc2VnID0gYXdhaXQgUmV0aW5hbFNlZ21lbnRlci5mcm9tUHJldHJhaW5lZCh7XG4gICAgICAgIG1vZGVsVXJsOiBkZWYubW9kZWxVcmwsXG4gICAgICAgIC4uLihkZWYubWV0YVVybCA/IHsgbWV0YVVybDogZGVmLm1ldGFVcmwgfSA6IHt9KSxcbiAgICAgICAgb25Qcm9ncmVzczogKGxvYWRlZCwgdG90YWwpID0+IG9uUHJvZ3Jlc3M/Lihsb2FkZWQsIHRvdGFsKSxcbiAgICAgICAgb25TdGF0dXM6IChzKSA9PiBvblN0YXR1cz8uKHMpLFxuICAgICAgfSk7XG4gICAgICB0aGlzLmluc3RhbmNlcy5zZXQobW9kZWxJZCwgc2VnKTtcbiAgICAgIHJldHVybiBzZWc7XG4gICAgfSkoKTtcblxuICAgIHRoaXMubG9hZGluZy5zZXQobW9kZWxJZCwgbG9hZCk7XG4gICAgLy8gQSBmYWlsZWQgbG9hZCBtdXN0IG5vdCBwb2lzb24gdGhlIGNhY2hlIOKAlCB0aGUgbmV4dCBjYWxsZXIgc2hvdWxkIHJldHJ5XG4gICAgLy8gcmF0aGVyIHRoYW4gaW5oZXJpdCB0aGUgcmVqZWN0ZWQgcHJvbWlzZSBmb3JldmVyLlxuICAgIGxvYWQuY2F0Y2goKCkgPT4gdGhpcy5sb2FkaW5nLmRlbGV0ZShtb2RlbElkKSk7XG4gICAgcmV0dXJuIGxvYWQ7XG4gIH1cblxuICAvKiogV2hldGhlciBhIG1vZGVsIGlzIGFscmVhZHkgbG9hZGVkICh3YXJtKS4gKi9cbiAgaXNMb2FkZWQobW9kZWxJZDogc3RyaW5nID0gZ2V0RGVmYXVsdFJldGluYWxNb2RlbElkKCkpOiBib29sZWFuIHtcbiAgICByZXR1cm4gdGhpcy5pbnN0YW5jZXMuaGFzKG1vZGVsSWQpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlbGVhc2Ugb25lIG1vZGVsLCBvciBhbGwgb2YgdGhlbS4gU2FmZSB0byBjYWxsIG9uIGEgbW9kZWwgbmV2ZXIgbG9hZGVkLlxuICAgKlxuICAgKiBJbi1mbGlnaHQgbG9hZHMgYXJlIHRvcm4gZG93biB0b286IGRyb3BwaW5nIGEgcGVuZGluZyBwcm9taXNlIGFuZCByZXR1cm5pbmdcbiAgICogd291bGQgbGV0IHRoZSBsb2FkIGZpbmlzaCBhZnRlcndhcmRzLCByZS1wb3B1bGF0ZSB7QGxpbmsgaW5zdGFuY2VzfSwgYW5kXG4gICAqIHN0cmFuZCBhIGxpdmUgT1JUIHNlc3Npb24gdGhhdCBub3RoaW5nIG93bnMg4oCUIGRpc3Bvc2FsIHdvdWxkIHJlcG9ydCBzdWNjZXNzXG4gICAqIHdoaWxlIGxlYWtpbmcuIEF0IDU5MCBNQiB0aGF0IGxlYWsgaXMgbm90IHN1cnZpdmFibGUuXG4gICAqL1xuICBhc3luYyBkaXNwb3NlKG1vZGVsSWQ/OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCBpZHMgPSBtb2RlbElkXG4gICAgICA/IFttb2RlbElkXVxuICAgICAgOiBbLi4ubmV3IFNldChbLi4udGhpcy5pbnN0YW5jZXMua2V5cygpLCAuLi50aGlzLmxvYWRpbmcua2V5cygpXSldO1xuICAgIGF3YWl0IFByb21pc2UuYWxsKGlkcy5tYXAoKGlkKSA9PiB0aGlzLmRpc3Bvc2VPbmUoaWQpKSk7XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIGRpc3Bvc2VPbmUoaWQ6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHBlbmRpbmcgPSB0aGlzLmxvYWRpbmcuZ2V0KGlkKTtcbiAgICBjb25zdCBsb2FkZWQgPSB0aGlzLmluc3RhbmNlcy5nZXQoaWQpO1xuICAgIHRoaXMuaW5zdGFuY2VzLmRlbGV0ZShpZCk7XG4gICAgdGhpcy5sb2FkaW5nLmRlbGV0ZShpZCk7XG5cbiAgICBpZiAobG9hZGVkKSBhd2FpdCBsb2FkZWQuZGlzcG9zZSgpO1xuICAgIGlmICghcGVuZGluZykgcmV0dXJuO1xuXG4gICAgLy8gQSBwZW5kaW5nIGxvYWQgYXNzaWducyBpbnN0YW5jZXNbaWRdIGJlZm9yZSBpdHMgcHJvbWlzZSByZXNvbHZlcywgc28gYnlcbiAgICAvLyB0aGUgdGltZSB0aGlzIGF3YWl0IHJldHVybnMgdGhlIGVudHJ5IGlzIGJhY2suIENsZWFyIGl0IG9ubHkgaWYgaXQgc3RpbGxcbiAgICAvLyBwb2ludHMgYXQgd2hhdCAqdGhpcyogbG9hZCBwcm9kdWNlZCDigJQgYSBuZXdlciBsb2FkIHN0YXJ0ZWQgbWVhbndoaWxlIG93bnNcbiAgICAvLyBpdHMgb3duIGVudHJ5IGFuZCBtdXN0IG5vdCBiZSBldmljdGVkLlxuICAgIGNvbnN0IHNlZyA9IGF3YWl0IHBlbmRpbmcuY2F0Y2goKCkgPT4gbnVsbCk7XG4gICAgaWYgKCFzZWcgfHwgc2VnID09PSBsb2FkZWQpIHJldHVybjtcbiAgICBpZiAodGhpcy5pbnN0YW5jZXMuZ2V0KGlkKSA9PT0gc2VnKSB0aGlzLmluc3RhbmNlcy5kZWxldGUoaWQpO1xuICAgIGF3YWl0IHNlZy5kaXNwb3NlKCk7XG4gIH1cblxuICBwcml2YXRlIHJlcXVpcmVNb2RlbChtb2RlbElkOiBzdHJpbmcpOiBSZXRpbmFsTW9kZWxEZWYge1xuICAgIGNvbnN0IGRlZiA9IGdldFJldGluYWxNb2RlbChtb2RlbElkKTtcbiAgICBpZiAoIWRlZikgdGhyb3cgbmV3IEVycm9yKGBVbmtub3duIHJldGluYWwgbW9kZWwgaWQgXCIke21vZGVsSWR9XCIuYCk7XG4gICAgaWYgKCFkZWYubW9kZWxVcmwudHJpbSgpKSB7XG4gICAgICAvLyBQcmVmZXIgdGhlIHJlZ2lzdHJ5J3Mgc3BlY2lmaWMgZXhwbGFuYXRpb24gb3ZlciBcIm5vdCBjb25maWd1cmVkXCIg4oCUIHRoZVxuICAgICAgLy8gUmVzVU5ldC1hIGVudHJpZXMgYXJlIGRpc2FibGVkIGZvciBhIGRvY3VtZW50ZWQgYWNjdXJhY3kgcmVhc29uLCBhbmQgYVxuICAgICAgLy8gdXNlciB3aG8gcGlja2VkIG9uZSBkZXNlcnZlcyB0byBiZSB0b2xkIHdoaWNoLlxuICAgICAgdGhyb3cgbmV3IEVycm9yKGRlZi51bmF2YWlsYWJsZVJlYXNvbiA/PyBgUmV0aW5hbCBtb2RlbCBcIiR7bW9kZWxJZH1cIiBoYXMgbm8gbW9kZWxVcmwgY29uZmlndXJlZC5gKTtcbiAgICB9XG4gICAgcmV0dXJuIGRlZjtcbiAgfVxufVxuIl19
|