@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.
@@ -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