@jax-data-science/sci-image-visualizer 0.2.18 → 0.3.0
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 +2 -9
- package/esm2022/lib/contracts/instance-segmenter.contract.mjs +10 -10
- package/esm2022/lib/contracts/semantic-segmenter.contract.mjs +10 -9
- package/esm2022/lib/contracts/toolbar-tool.contract.mjs +64 -0
- package/esm2022/lib/toolbar/model-info.mjs +32 -0
- package/esm2022/lib/toolbar/toolbar.component.mjs +82 -55
- package/esm2022/lib/visualizer.component.mjs +131 -138
- package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-iPNL2JyC.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs} +2 -2
- package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-iPNL2JyC.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-ci1q0xnJ.mjs.map} +1 -1
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +710 -1565
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
- package/index.d.ts +1 -6
- package/lib/contracts/instance-segmenter.contract.d.ts +7 -3
- package/lib/contracts/semantic-segmenter.contract.d.ts +7 -2
- package/lib/contracts/toolbar-tool.contract.d.ts +190 -0
- package/lib/toolbar/model-info.d.ts +17 -0
- package/lib/toolbar/toolbar.component.d.ts +53 -30
- package/lib/visualizer.component.d.ts +49 -58
- package/package.json +2 -4
- package/src/lib/assets/cell-detection.svg +37 -0
- package/src/lib/assets/cells.svg +1 -1
- package/src/lib/assets/sam-box-prompt.svg +61 -0
- package/src/lib/assets/sam-point-prompt.svg +91 -0
- package/esm2022/lib/toolbar/segmentation/retinal-layer-tool.service.mjs +0 -197
- package/esm2022/lib/toolbar/segmentation/retinal-model-registry.mjs +0 -119
- package/esm2022/lib/toolbar/segmentation/retinal-segmenter.service.mjs +0 -172
- package/esm2022/lib/toolbar/segmentation/yolo-detect-tool.service.mjs +0 -208
- package/esm2022/lib/toolbar/segmentation/yolo-model-registry.mjs +0 -129
- package/esm2022/lib/toolbar/segmentation/yolo-segmenter.service.mjs +0 -172
- package/lib/toolbar/segmentation/retinal-layer-tool.service.d.ts +0 -49
- package/lib/toolbar/segmentation/retinal-model-registry.d.ts +0 -71
- package/lib/toolbar/segmentation/retinal-segmenter.service.d.ts +0 -60
- package/lib/toolbar/segmentation/yolo-detect-tool.service.d.ts +0 -51
- package/lib/toolbar/segmentation/yolo-model-registry.d.ts +0 -59
- package/lib/toolbar/segmentation/yolo-segmenter.service.d.ts +0 -50
|
@@ -1,172 +0,0 @@
|
|
|
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
|
-
// A run cancelled mid-download should stop the download, not just the
|
|
41
|
-
// inference queued behind it.
|
|
42
|
-
opts.signal);
|
|
43
|
-
progress?.onStatus?.('Running inference…');
|
|
44
|
-
const result = await seg.segment({ data: image.data, width: image.width, height: image.height }, {
|
|
45
|
-
...(opts.classThreshold !== undefined ? { classThreshold: opts.classThreshold } : {}),
|
|
46
|
-
...(opts.classFilter ? { classFilter: opts.classFilter } : {}),
|
|
47
|
-
...(opts.simplifyTolerance !== undefined ? { simplifyTolerance: opts.simplifyTolerance } : {}),
|
|
48
|
-
...(opts.minArea !== undefined ? { minArea: opts.minArea } : {}),
|
|
49
|
-
...(opts.signal ? { signal: opts.signal } : {}),
|
|
50
|
-
// The library narrates its own phases (per patch, then tracing) because
|
|
51
|
-
// only it knows which one it is in.
|
|
52
|
-
onStatus: (s) => progress?.onStatus?.(s),
|
|
53
|
-
});
|
|
54
|
-
const regions = result.regions.map((r) => ({
|
|
55
|
-
exterior: r.exterior.map(([x, y]) => [x, y]),
|
|
56
|
-
holes: r.holes.map((h) => h.map(([x, y]) => [x, y])),
|
|
57
|
-
classId: r.classId,
|
|
58
|
-
className: r.className,
|
|
59
|
-
area: r.area,
|
|
60
|
-
}));
|
|
61
|
-
return {
|
|
62
|
-
regions,
|
|
63
|
-
classAreas: result.classAreas,
|
|
64
|
-
classNames: [...result.classNames],
|
|
65
|
-
width: result.width,
|
|
66
|
-
height: result.height,
|
|
67
|
-
unassignedFraction: result.unassignedFraction,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* The shared segmenter for a model id, created on first use and reused
|
|
72
|
-
* thereafter (deduped across concurrent callers). Exposed so a host's
|
|
73
|
-
* processing pipeline can run on the same loaded session rather than paying
|
|
74
|
-
* the ~590 MB load twice.
|
|
75
|
-
*/
|
|
76
|
-
getModel(modelId = getDefaultRetinalModelId(), onProgress, onStatus,
|
|
77
|
-
/**
|
|
78
|
-
* Abort the load. This model is ~590 MB, so a cancel has to reach the
|
|
79
|
-
* *download* — aborting only the inference that follows leaves the transfer
|
|
80
|
-
* running and the UI stuck in "Cancelling" until it completes.
|
|
81
|
-
*
|
|
82
|
-
* Deliberately not applied to a load already in flight: that promise may be
|
|
83
|
-
* shared with another caller (the toolbar tool and the pipeline both use
|
|
84
|
-
* this service), and one caller's cancel must not tear the model out from
|
|
85
|
-
* under the other. A second caller joining an in-flight load simply waits.
|
|
86
|
-
*/
|
|
87
|
-
signal) {
|
|
88
|
-
const existing = this.instances.get(modelId);
|
|
89
|
-
if (existing)
|
|
90
|
-
return Promise.resolve(existing);
|
|
91
|
-
const inFlight = this.loading.get(modelId);
|
|
92
|
-
if (inFlight)
|
|
93
|
-
return inFlight;
|
|
94
|
-
const def = this.requireModel(modelId);
|
|
95
|
-
const load = (async () => {
|
|
96
|
-
// Lazy: keep jax-ai-js + its ORT runtime out of the initial bundle.
|
|
97
|
-
const { RetinalSegmenter, configureOrt } = await import('jax-ai-js');
|
|
98
|
-
configureOrt({ wasmPaths: getOrtWasmBase() });
|
|
99
|
-
const seg = await RetinalSegmenter.fromPretrained({
|
|
100
|
-
modelUrl: def.modelUrl,
|
|
101
|
-
...(def.metaUrl ? { metaUrl: def.metaUrl } : {}),
|
|
102
|
-
...(signal ? { signal } : {}),
|
|
103
|
-
onProgress: (loaded, total) => onProgress?.(loaded, total),
|
|
104
|
-
onStatus: (s) => onStatus?.(s),
|
|
105
|
-
});
|
|
106
|
-
this.instances.set(modelId, seg);
|
|
107
|
-
return seg;
|
|
108
|
-
})();
|
|
109
|
-
this.loading.set(modelId, load);
|
|
110
|
-
// A failed load must not poison the cache — the next caller should retry
|
|
111
|
-
// rather than inherit the rejected promise forever.
|
|
112
|
-
load.catch(() => this.loading.delete(modelId));
|
|
113
|
-
return load;
|
|
114
|
-
}
|
|
115
|
-
/** Whether a model is already loaded (warm). */
|
|
116
|
-
isLoaded(modelId = getDefaultRetinalModelId()) {
|
|
117
|
-
return this.instances.has(modelId);
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Release one model, or all of them. Safe to call on a model never loaded.
|
|
121
|
-
*
|
|
122
|
-
* In-flight loads are torn down too: dropping a pending promise and returning
|
|
123
|
-
* would let the load finish afterwards, re-populate {@link instances}, and
|
|
124
|
-
* strand a live ORT session that nothing owns — disposal would report success
|
|
125
|
-
* while leaking. At 590 MB that leak is not survivable.
|
|
126
|
-
*/
|
|
127
|
-
async dispose(modelId) {
|
|
128
|
-
const ids = modelId
|
|
129
|
-
? [modelId]
|
|
130
|
-
: [...new Set([...this.instances.keys(), ...this.loading.keys()])];
|
|
131
|
-
await Promise.all(ids.map((id) => this.disposeOne(id)));
|
|
132
|
-
}
|
|
133
|
-
async disposeOne(id) {
|
|
134
|
-
const pending = this.loading.get(id);
|
|
135
|
-
const loaded = this.instances.get(id);
|
|
136
|
-
this.instances.delete(id);
|
|
137
|
-
this.loading.delete(id);
|
|
138
|
-
if (loaded)
|
|
139
|
-
await loaded.dispose();
|
|
140
|
-
if (!pending)
|
|
141
|
-
return;
|
|
142
|
-
// A pending load assigns instances[id] before its promise resolves, so by
|
|
143
|
-
// the time this await returns the entry is back. Clear it only if it still
|
|
144
|
-
// points at what *this* load produced — a newer load started meanwhile owns
|
|
145
|
-
// its own entry and must not be evicted.
|
|
146
|
-
const seg = await pending.catch(() => null);
|
|
147
|
-
if (!seg || seg === loaded)
|
|
148
|
-
return;
|
|
149
|
-
if (this.instances.get(id) === seg)
|
|
150
|
-
this.instances.delete(id);
|
|
151
|
-
await seg.dispose();
|
|
152
|
-
}
|
|
153
|
-
requireModel(modelId) {
|
|
154
|
-
const def = getRetinalModel(modelId);
|
|
155
|
-
if (!def)
|
|
156
|
-
throw new Error(`Unknown retinal model id "${modelId}".`);
|
|
157
|
-
if (!def.modelUrl.trim()) {
|
|
158
|
-
// Prefer the registry's specific explanation over "not configured" — the
|
|
159
|
-
// ResUNet-a entries are disabled for a documented accuracy reason, and a
|
|
160
|
-
// user who picked one deserves to be told which.
|
|
161
|
-
throw new Error(def.unavailableReason ?? `Retinal model "${modelId}" has no modelUrl configured.`);
|
|
162
|
-
}
|
|
163
|
-
return def;
|
|
164
|
-
}
|
|
165
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: RetinalSegmenterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
166
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: RetinalSegmenterService, providedIn: 'root' }); }
|
|
167
|
-
}
|
|
168
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: RetinalSegmenterService, decorators: [{
|
|
169
|
-
type: Injectable,
|
|
170
|
-
args: [{ providedIn: 'root' }]
|
|
171
|
-
}] });
|
|
172
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0aW5hbC1zZWdtZW50ZXIuc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9saWIvdG9vbGJhci9zZWdtZW50YXRpb24vcmV0aW5hbC1zZWdtZW50ZXIuc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRTNDLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUN0RCxPQUFPLEVBQ0wsd0JBQXdCLEVBQ3hCLGVBQWUsR0FFaEIsTUFBTSwwQkFBMEIsQ0FBQzs7QUFXbEM7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFFSCxNQUFNLE9BQU8sdUJBQXVCO0lBRHBDO1FBRW1CLGNBQVMsR0FBRyxJQUFJLEdBQUcsRUFBNEIsQ0FBQztRQUNoRCxZQUFPLEdBQUcsSUFBSSxHQUFHLEVBQXFDLENBQUM7S0FtS3pFO0lBaktDLEtBQUssQ0FBQyxlQUFlLENBQ25CLEtBQWlFLEVBQ2pFLE9BQStCLEVBQUUsRUFDakMsUUFBa0M7UUFFbEMsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLE9BQU8sSUFBSSx3QkFBd0IsRUFBRSxDQUFDO1FBQzNELE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLENBQUM7UUFFdkMsSUFBSSxpQkFBaUIsR0FBRyxLQUFLLENBQUM7UUFDOUIsTUFBTSxHQUFHLEdBQUcsTUFBTSxJQUFJLENBQUMsUUFBUSxDQUM3QixPQUFPLEVBQ1AsQ0FBQyxNQUFNLEVBQUUsS0FBSyxFQUFFLEVBQUU7WUFDaEIsSUFBSSxLQUFLO2dCQUFFLFFBQVEsRUFBRSxVQUFVLEVBQUUsQ0FBQyxNQUFNLEdBQUcsS0FBSyxDQUFDLENBQUM7WUFDbEQsUUFBUSxFQUFFLE9BQU8sRUFBRSxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsQ0FBQztZQUNuQyxJQUFJLE1BQU0sR0FBRyxDQUFD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NkIsT0FBTyxJQUFJLENBQUMsQ0FBQztRQUNwRSxJQUFJLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDO1lBQ3pCLHlFQUF5RTtZQUN6RSx5RUFBeUU7WUFDekUsaURBQWlEO1lBQ2pELE1BQU0sSUFBSSxLQUFLLENBQUMsR0FBRyxDQUFDLGlCQUFpQixJQUFJLGtCQUFrQixPQUFPLCtCQUErQixDQUFDLENBQUM7UUFDckcsQ0FBQztRQUNELE9BQU8sR0FBRyxDQUFDO0lBQ2IsQ0FBQzs4R0FwS1UsdUJBQXVCO2tIQUF2Qix1QkFBdUIsY0FEVixNQUFNOzsyRkFDbkIsdUJBQXVCO2tCQURuQyxVQUFVO21CQUFDLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGFibGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuaW1wb3J0IHsgZ2V0T3J0V2FzbUJhc2UgfSBmcm9tICcuL29ydC1ydW50aW1lLWNvbmZpZyc7XG5pbXBvcnQge1xuICBnZXREZWZhdWx0UmV0aW5hbE1vZGVsSWQsXG4gIGdldFJldGluYWxNb2RlbCxcbiAgdHlwZSBSZXRpbmFsTW9kZWxEZWYsXG59IGZyb20gJy4vcmV0aW5hbC1tb2RlbC1yZWdpc3RyeSc7XG5cbmltcG9ydCB0eXBlIHtcbiAgSVNlbWFudGljU2VnbWVudGVyLFxuICBTZW1hbnRpY1JlZ2lvbixcbiAgU2VtYW50aWNTZWdtZW50YXRpb24sXG4gIFNlbWFudGljU2VnbWVudE9wdGlvbnMsXG4gIFNlbWFudGljU2VnbWVudFByb2dyZXNzLFxufSBmcm9tICcuLi8uLi9jb250cmFjdHMvc2VtYW50aWMtc2VnbWVudGVyLmNvbnRyYWN0JztcbmltcG9ydCB0eXBlIHsgUmV0aW5hbFNlZ21lbnRlciB9IGZyb20gJ2pheC1haS1qcyc7XG5cbi8qKlxuICogRGVmYXVsdCBpbi1saWJyYXJ5IHtAbGluayBJU2VtYW50aWNTZWdtZW50ZXJ9LCBiYWNrZWQgYnkgamF4LWFpLWpzLlxuICpcbiAqIGBqYXgtYWktanNgIGlzIGEgcmVndWxhciBkZXBlbmRlbmN5IGJ1dCBpcyAqKmxhenktaW1wb3J0ZWQqKiBvbiBmaXJzdCB1c2UsIHNvXG4gKiBhcHBzIHRoYXQgbmV2ZXIgc2VnbWVudCByZXRpbmFsIGxheWVycyBwYXkgbm90aGluZyBmb3IgaXQg4oCUIG9yIGZvclxuICogb25ueHJ1bnRpbWUtd2ViIOKAlCBpbiB0aGVpciBpbml0aWFsIGJ1bmRsZS4gTWlycm9yc1xuICoge0BsaW5rIFlvbG9TZWdtZW50ZXJTZXJ2aWNlfSwgaW5jbHVkaW5nIGl0cyBwZXItbW9kZWwtaWQgY2FjaGUuXG4gKlxuICogV0VCR1BVIE9OTFksIEFORCBBIEhPU1QgTVVTVCBCRSBSRUFEWSBGT1IgVEhBVFxuICogVW5saWtlIHRoZSBZT0xPIGFuZCBDZWxscG9zZSBzZXJ2aWNlcywgdGhlcmUgaXMgbm8gV0FTTSBmYWxsYmFjayB0byBkZWdyYWRlXG4gKiB0bzogT1JULVdlYidzIFdBU00gRVAgZGllcyB3aXRoIGBzdGQ6OmJhZF9hbGxvY2Agb24gdGhlc2UgZ3JhcGhzIGF0IDUxMsKyLCBhdFxuICogZXZlcnkgcHJlY2lzaW9uLiBqYXgtYWktanMgdGhyb3dzIGBXZWJHcHVSZXF1aXJlZEVycm9yYCByYXRoZXIgdGhhbiB0cnlpbmcsXG4gKiBhbmQgaXQgaXMgcmUtdGhyb3duIGhlcmUgdW5jaGFuZ2VkIHNvIGEgaG9zdCBjYW4gc2hvdyBhIHJlYWxcbiAqIHVuc3VwcG9ydGVkLWJyb3dzZXIgbWVzc2FnZSBpbnN0ZWFkIG9mIGEgZ2VuZXJpYyBmYWlsdXJlLlxuICovXG5ASW5qZWN0YWJsZSh7IHByb3ZpZGVkSW46ICdyb290JyB9KVxuZXhwb3J0IGNsYXNzIFJldGluYWxTZWdtZW50ZXJTZXJ2aWNlIGltcGxlbWVudHMgSVNlbWFudGljU2VnbWVudGVyIHtcbiAgcHJpdmF0ZSByZWFkb25seSBpbnN0YW5jZXMgPSBuZXcgTWFwPHN0cmluZywgUmV0aW5hbFNlZ21lbnRlcj4oKTtcbiAgcHJpdmF0ZSByZWFkb25seSBsb2FkaW5nID0gbmV3IE1hcDxzdHJpbmcsIFByb21pc2U8UmV0aW5hbFNlZ21lbnRlcj4+KCk7XG5cbiAgYXN5bmMgc2VnbWVudFNlbWFudGljKFxuICAgIGltYWdlOiB7IGRhdGE6IFVpbnQ4Q2xhbXBlZEFycmF5OyB3aWR0aDogbnVtYmVyOyBoZWlnaHQ6IG51bWJlciB9LFxuICAgIG9wdHM6IFNlbWFudGljU2VnbWVudE9wdGlvbnMgPSB7fSxcbiAgICBwcm9ncmVzcz86IFNlbWFudGljU2VnbWVudFByb2dyZXNzLFxuICApOiBQcm9taXNlPFNlbWFudGljU2VnbWVudGF0aW9uPiB7XG4gICAgY29uc3QgbW9kZWxJZCA9IG9wdHMubW9kZWxJZCA/PyBnZXREZWZhdWx0UmV0aW5hbE1vZGVsSWQoKTtcbiAgICBjb25zdCBkZWYgPSB0aGlzLnJlcXVpcmVNb2RlbChtb2RlbElkKTtcblxuICAgIGxldCBhbm5vdW5jZWREb3dubG9hZCA9IGZhbHNlO1xuICAgIGNvbnN0IHNlZyA9IGF3YWl0IHRoaXMuZ2V0TW9kZWwoXG4gICAgICBtb2RlbElkLFxuICAgICAgKGxvYWRlZCwgdG90YWwpID0+IHtcbiAgICAgICAgaWYgKHRvdGFsKSBwcm9ncmVzcz8ub25Qcm9ncmVzcz8uKGxvYWRlZCAvIHRvdGFsKTtcbiAgICAgICAgcHJvZ3Jlc3M/Lm9uQnl0ZXM/Lihsb2FkZWQsIHRvdGFsKTtcbiAgICAgICAgaWYgKGxvYWRlZCA+IDAgJiYgIWFubm91bmNlZERvd25sb2FkKSB7XG4gICAgICAgICAgYW5ub3VuY2VkRG93bmxvYWQgPSB0cnVlO1xuICAgICAgICAgIC8vIFRoZSBzaXplIGlzIGluIHRoZSBtZXNzYWdlIGJlY2F1c2UgaXQgaXMgfjU5MCBNQiDigJQgYSB1c2VyIHdob1xuICAgICAgICAgIC8vIGNsaWNrZWQgYSBidXR0b24gZGVzZXJ2ZXMgdG8ga25vdyB0aGF0IGJlZm9yZSBpdCBmaW5pc2hlcy5cbiAgICAgICAgICBwcm9ncmVzcz8ub25TdGF0dXM/LihgRG93bmxvYWRpbmcgJHtkZWYubGFiZWx9IG1vZGVsICh+JHtkZWYuc2l6ZU1ifSBNQinigKZgKTtcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIChzdGF0dXMpID0+IHByb2dyZXNzPy5vblN0YXR1cz8uKHN0YXR1cyksXG4gICAgICAvLyBBIHJ1biBjYW5jZWxsZWQgbWlkLWRvd25sb2FkIHNob3VsZCBzdG9wIHRoZSBkb3dubG9hZCwgbm90IGp1c3QgdGhlXG4gICAgICAvLyBpbmZlcmVuY2UgcXVldWVkIGJlaGluZCBpdC5cbiAgICAgIG9wdHMuc2lnbmFsLFxuICAgICk7XG5cbiAgICBwcm9ncmVzcz8ub25TdGF0dXM/LignUnVubmluZyBpbmZlcmVuY2XigKYnKTtcbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBzZWcuc2VnbWVudChcbiAgICAgIHsgZGF0YTogaW1hZ2UuZGF0YSwgd2lkdGg6IGltYWdlLndpZHRoLCBoZWlnaHQ6IGltYWdlLmhlaWdodCB9LFxuICAgICAge1xuICAgICAgICAuLi4ob3B0cy5jbGFzc1RocmVzaG9sZCAhPT0gdW5kZWZpbmVkID8geyBjbGFzc1RocmVzaG9sZDogb3B0cy5jbGFzc1RocmVzaG9sZCB9IDoge30pLFxuICAgICAgICAuLi4ob3B0cy5jbGFzc0ZpbHRlciA/IHsgY2xhc3NGaWx0ZXI6IG9wdHMuY2xhc3NGaWx0ZXIgfSA6IHt9KSxcbiAgICAgICAgLi4uKG9wdHMuc2ltcGxpZnlUb2xlcmFuY2UgIT09IHVuZGVmaW5lZCA/IHsgc2ltcGxpZnlUb2xlcmFuY2U6IG9wdHMuc2ltcGxpZnlUb2xlcmFuY2UgfSA6IHt9KSxcbiAgICAgICAgLi4uKG9wdHMubWluQXJlYSAhPT0gdW5kZWZpbmVkID8geyBtaW5BcmVhOiBvcHRzLm1pbkFyZWEgfSA6IHt9KSxcbiAgICAgICAgLi4uKG9wdHMuc2lnbmFsID8geyBzaWduYWw6IG9wdHMuc2lnbmFsIH0gOiB7fSksXG4gICAgICAgIC8vIFRoZSBsaWJyYXJ5IG5hcnJhdGVzIGl0cyBvd24gcGhhc2VzIChwZXIgcGF0Y2gsIHRoZW4gdHJhY2luZykgYmVjYXVzZVxuICAgICAgICAvLyBvbmx5IGl0IGtub3dzIHdoaWNoIG9uZSBpdCBpcyBpbi5cbiAgICAgICAgb25TdGF0dXM6IChzKSA9PiBwcm9ncmVzcz8ub25TdGF0dXM/LihzKSxcbiAgICAgIH0sXG4gICAgKTtcblxuICAgIGNvbnN0IHJlZ2lvbnM6IFNlbWFudGljUmVnaW9uW10gPSByZXN1bHQucmVnaW9ucy5tYXAoKHIpID0+ICh7XG4gICAgICBleHRlcmlvcjogci5leHRlcmlvci5tYXAoKFt4LCB5XSk6IFtudW1iZXIsIG51bWJlcl0gPT4gW3gsIHldKSxcbiAgICAgIGhvbGVzOiByLmhvbGVzLm1hcCgoaCkgPT4gaC5tYXAoKFt4LCB5XSk6IFtudW1iZXIsIG51bWJlcl0gPT4gW3gsIHldKSksXG4gICAgICBjbGFzc0lkOiByLmNsYXNzSWQsXG4gICAgICBjbGFzc05hbWU6IHIuY2xhc3NOYW1lLFxuICAgICAgYXJlYTogci5hcmVhLFxuICAgIH0pKTtcblxuICAgIHJldHVybiB7XG4gICAgICByZWdpb25zLFxuICAgICAgY2xhc3NBcmVhczogcmVzdWx0LmNsYXNzQXJlYXMsXG4gICAgICBjbGFzc05hbWVzOiBbLi4ucmVzdWx0LmNsYXNzTmFtZXNdLFxuICAgICAgd2lkdGg6IHJlc3VsdC53aWR0aCxcbiAgICAgIGhlaWdodDogcmVzdWx0LmhlaWdodCxcbiAgICAgIHVuYXNzaWduZWRGcmFjdGlvbjogcmVzdWx0LnVuYXNzaWduZWRGcmFjdGlvbixcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIFRoZSBzaGFyZWQgc2VnbWVudGVyIGZvciBhIG1vZGVsIGlkLCBjcmVhdGVkIG9uIGZpcnN0IHVzZSBhbmQgcmV1c2VkXG4gICAqIHRoZXJlYWZ0ZXIgKGRlZHVwZWQgYWNyb3NzIGNvbmN1cnJlbnQgY2FsbGVycykuIEV4cG9zZWQgc28gYSBob3N0J3NcbiAgICogcHJvY2Vzc2luZyBwaXBlbGluZSBjYW4gcnVuIG9uIHRoZSBzYW1lIGxvYWRlZCBzZXNzaW9uIHJhdGhlciB0aGFuIHBheWluZ1xuICAgKiB0aGUgfjU5MCBNQiBsb2FkIHR3aWNlLlxuICAgKi9cbiAgZ2V0TW9kZWwoXG4gICAgbW9kZWxJZDogc3RyaW5nID0gZ2V0RGVmYXVsdFJldGluYWxNb2RlbElkKCksXG4gICAgb25Qcm9ncmVzcz86IChsb2FkZWQ6IG51bWJlciwgdG90YWw6IG51bWJlciB8IG51bGwpID0+IHZvaWQsXG4gICAgb25TdGF0dXM/OiAoc3RhdHVzOiBzdHJpbmcpID0+IHZvaWQsXG4gICAgLyoqXG4gICAgICogQWJvcnQgdGhlIGxvYWQuIFRoaXMgbW9kZWwgaXMgfjU5MCBNQiwgc28gYSBjYW5jZWwgaGFzIHRvIHJlYWNoIHRoZVxuICAgICAqICpkb3dubG9hZCog4oCUIGFib3J0aW5nIG9ubHkgdGhlIGluZmVyZW5jZSB0aGF0IGZvbGxvd3MgbGVhdmVzIHRoZSB0cmFuc2ZlclxuICAgICAqIHJ1bm5pbmcgYW5kIHRoZSBVSSBzdHVjayBpbiBcIkNhbmNlbGxpbmdcIiB1bnRpbCBpdCBjb21wbGV0ZXMuXG4gICAgICpcbiAgICAgKiBEZWxpYmVyYXRlbHkgbm90IGFwcGxpZWQgdG8gYSBsb2FkIGFscmVhZHkgaW4gZmxpZ2h0OiB0aGF0IHByb21pc2UgbWF5IGJlXG4gICAgICogc2hhcmVkIHdpdGggYW5vdGhlciBjYWxsZXIgKHRoZSB0b29sYmFyIHRvb2wgYW5kIHRoZSBwaXBlbGluZSBib3RoIHVzZVxuICAgICAqIHRoaXMgc2VydmljZSksIGFuZCBvbmUgY2FsbGVyJ3MgY2FuY2VsIG11c3Qgbm90IHRlYXIgdGhlIG1vZGVsIG91dCBmcm9tXG4gICAgICogdW5kZXIgdGhlIG90aGVyLiBBIHNlY29uZCBjYWxsZXIgam9pbmluZyBhbiBpbi1mbGlnaHQgbG9hZCBzaW1wbHkgd2FpdHMuXG4gICAgICovXG4gICAgc2lnbmFsPzogQWJvcnRTaWduYWwsXG4gICk6IFByb21pc2U8UmV0aW5hbFNlZ21lbnRlcj4ge1xuICAgIGNvbnN0IGV4aXN0aW5nID0gdGhpcy5pbnN0YW5jZXMuZ2V0KG1vZGVsSWQpO1xuICAgIGlmIChleGlzdGluZykgcmV0dXJuIFByb21pc2UucmVzb2x2ZShleGlzdGluZyk7XG5cbiAgICBjb25zdCBpbkZsaWdodCA9IHRoaXMubG9hZGluZy5nZXQobW9kZWxJZCk7XG4gICAgaWYgKGluRmxpZ2h0KSByZXR1cm4gaW5GbGlnaHQ7XG5cbiAgICBjb25zdCBkZWYgPSB0aGlzLnJlcXVpcmVNb2RlbChtb2RlbElkKTtcbiAgICBjb25zdCBsb2FkID0gKGFzeW5jICgpID0+IHtcbiAgICAgIC8vIExhenk6IGtlZXAgamF4LWFpLWpzICsgaXRzIE9SVCBydW50aW1lIG91dCBvZiB0aGUgaW5pdGlhbCBidW5kbGUuXG4gICAgICBjb25zdCB7IFJldGluYWxTZWdtZW50ZXIsIGNvbmZpZ3VyZU9ydCB9ID0gYXdhaXQgaW1wb3J0KCdqYXgtYWktanMnKTtcbiAgICAgIGNvbmZpZ3VyZU9ydCh7IHdhc21QYXRoczogZ2V0T3J0V2FzbUJhc2UoKSB9KTtcbiAgICAgIGNvbnN0IHNlZyA9IGF3YWl0IFJldGluYWxTZWdtZW50ZXIuZnJvbVByZXRyYWluZWQoe1xuICAgICAgICBtb2RlbFVybDogZGVmLm1vZGVsVXJsLFxuICAgICAgICAuLi4oZGVmLm1ldGFVcmwgPyB7IG1ldGFVcmw6IGRlZi5tZXRhVXJsIH0gOiB7fSksXG4gICAgICAgIC4uLihzaWduYWwgPyB7IHNpZ25hbCB9IDoge30pLFxuICAgICAgICBvblByb2dyZXNzOiAobG9hZGVkLCB0b3RhbCkgPT4gb25Qcm9ncmVzcz8uKGxvYWRlZCwgdG90YWwpLFxuICAgICAgICBvblN0YXR1czogKHMpID0+IG9uU3RhdHVzPy4ocyksXG4gICAgICB9KTtcbiAgICAgIHRoaXMuaW5zdGFuY2VzLnNldChtb2RlbElkLCBzZWcpO1xuICAgICAgcmV0dXJuIHNlZztcbiAgICB9KSgpO1xuXG4gICAgdGhpcy5sb2FkaW5nLnNldChtb2RlbElkLCBsb2FkKTtcbiAgICAvLyBBIGZhaWxlZCBsb2FkIG11c3Qgbm90IHBvaXNvbiB0aGUgY2FjaGUg4oCUIHRoZSBuZXh0IGNhbGxlciBzaG91bGQgcmV0cnlcbiAgICAvLyByYXRoZXIgdGhhbiBpbmhlcml0IHRoZSByZWplY3RlZCBwcm9taXNlIGZvcmV2ZXIuXG4gICAgbG9hZC5jYXRjaCgoKSA9PiB0aGlzLmxvYWRpbmcuZGVsZXRlKG1vZGVsSWQpKTtcbiAgICByZXR1cm4gbG9hZDtcbiAgfVxuXG4gIC8qKiBXaGV0aGVyIGEgbW9kZWwgaXMgYWxyZWFkeSBsb2FkZWQgKHdhcm0pLiAqL1xuICBpc0xvYWRlZChtb2RlbElkOiBzdHJpbmcgPSBnZXREZWZhdWx0UmV0aW5hbE1vZGVsSWQoKSk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLmluc3RhbmNlcy5oYXMobW9kZWxJZCk7XG4gIH1cblxuICAvKipcbiAgICogUmVsZWFzZSBvbmUgbW9kZWwsIG9yIGFsbCBvZiB0aGVtLiBTYWZlIHRvIGNhbGwgb24gYSBtb2RlbCBuZXZlciBsb2FkZWQuXG4gICAqXG4gICAqIEluLWZsaWdodCBsb2FkcyBhcmUgdG9ybiBkb3duIHRvbzogZHJvcHBpbmcgYSBwZW5kaW5nIHByb21pc2UgYW5kIHJldHVybmluZ1xuICAgKiB3b3VsZCBsZXQgdGhlIGxvYWQgZmluaXNoIGFmdGVyd2FyZHMsIHJlLXBvcHVsYXRlIHtAbGluayBpbnN0YW5jZXN9LCBhbmRcbiAgICogc3RyYW5kIGEgbGl2ZSBPUlQgc2Vzc2lvbiB0aGF0IG5vdGhpbmcgb3ducyDigJQgZGlzcG9zYWwgd291bGQgcmVwb3J0IHN1Y2Nlc3NcbiAgICogd2hpbGUgbGVha2luZy4gQXQgNTkwIE1CIHRoYXQgbGVhayBpcyBub3Qgc3Vydml2YWJsZS5cbiAgICovXG4gIGFzeW5jIGRpc3Bvc2UobW9kZWxJZD86IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGlkcyA9IG1vZGVsSWRcbiAgICAgID8gW21vZGVsSWRdXG4gICAgICA6IFsuLi5uZXcgU2V0KFsuLi50aGlzLmluc3RhbmNlcy5rZXlzKCksIC4uLnRoaXMubG9hZGluZy5rZXlzKCldKV07XG4gICAgYXdhaXQgUHJvbWlzZS5hbGwoaWRzLm1hcCgoaWQpID0+IHRoaXMuZGlzcG9zZU9uZShpZCkpKTtcbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgZGlzcG9zZU9uZShpZDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgcGVuZGluZyA9IHRoaXMubG9hZGluZy5nZXQoaWQpO1xuICAgIGNvbnN0IGxvYWRlZCA9IHRoaXMuaW5zdGFuY2VzLmdldChpZCk7XG4gICAgdGhpcy5pbnN0YW5jZXMuZGVsZXRlKGlkKTtcbiAgICB0aGlzLmxvYWRpbmcuZGVsZXRlKGlkKTtcblxuICAgIGlmIChsb2FkZWQpIGF3YWl0IGxvYWRlZC5kaXNwb3NlKCk7XG4gICAgaWYgKCFwZW5kaW5nKSByZXR1cm47XG5cbiAgICAvLyBBIHBlbmRpbmcgbG9hZCBhc3NpZ25zIGluc3RhbmNlc1tpZF0gYmVmb3JlIGl0cyBwcm9taXNlIHJlc29sdmVzLCBzbyBieVxuICAgIC8vIHRoZSB0aW1lIHRoaXMgYXdhaXQgcmV0dXJucyB0aGUgZW50cnkgaXMgYmFjay4gQ2xlYXIgaXQgb25seSBpZiBpdCBzdGlsbFxuICAgIC8vIHBvaW50cyBhdCB3aGF0ICp0aGlzKiBsb2FkIHByb2R1Y2VkIOKAlCBhIG5ld2VyIGxvYWQgc3RhcnRlZCBtZWFud2hpbGUgb3duc1xuICAgIC8vIGl0cyBvd24gZW50cnkgYW5kIG11c3Qgbm90IGJlIGV2aWN0ZWQuXG4gICAgY29uc3Qgc2VnID0gYXdhaXQgcGVuZGluZy5jYXRjaCgoKSA9PiBudWxsKTtcbiAgICBpZiAoIXNlZyB8fCBzZWcgPT09IGxvYWRlZCkgcmV0dXJuO1xuICAgIGlmICh0aGlzLmluc3RhbmNlcy5nZXQoaWQpID09PSBzZWcpIHRoaXMuaW5zdGFuY2VzLmRlbGV0ZShpZCk7XG4gICAgYXdhaXQgc2VnLmRpc3Bvc2UoKTtcbiAgfVxuXG4gIHByaXZhdGUgcmVxdWlyZU1vZGVsKG1vZGVsSWQ6IHN0cmluZyk6IFJldGluYWxNb2RlbERlZiB7XG4gICAgY29uc3QgZGVmID0gZ2V0UmV0aW5hbE1vZGVsKG1vZGVsSWQpO1xuICAgIGlmICghZGVmKSB0aHJvdyBuZXcgRXJyb3IoYFVua25vd24gcmV0aW5hbCBtb2RlbCBpZCBcIiR7bW9kZWxJZH1cIi5gKTtcbiAgICBpZiAoIWRlZi5tb2RlbFVybC50cmltKCkpIHtcbiAgICAgIC8vIFByZWZlciB0aGUgcmVnaXN0cnkncyBzcGVjaWZpYyBleHBsYW5hdGlvbiBvdmVyIFwibm90IGNvbmZpZ3VyZWRcIiDigJQgdGhlXG4gICAgICAvLyBSZXNVTmV0LWEgZW50cmllcyBhcmUgZGlzYWJsZWQgZm9yIGEgZG9jdW1lbnRlZCBhY2N1cmFjeSByZWFzb24sIGFuZCBhXG4gICAgICAvLyB1c2VyIHdobyBwaWNrZWQgb25lIGRlc2VydmVzIHRvIGJlIHRvbGQgd2hpY2guXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoZGVmLnVuYXZhaWxhYmxlUmVhc29uID8/IGBSZXRpbmFsIG1vZGVsIFwiJHttb2RlbElkfVwiIGhhcyBubyBtb2RlbFVybCBjb25maWd1cmVkLmApO1xuICAgIH1cbiAgICByZXR1cm4gZGVmO1xuICB9XG59XG4iXX0=
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { Inject, Injectable, Optional } from '@angular/core';
|
|
2
|
-
import { BehaviorSubject, firstValueFrom } from 'rxjs';
|
|
3
|
-
import { Region, Polygon } from '../../models/region';
|
|
4
|
-
import { TILE_ACCESS_PORT } from '../../contracts/ports/tile-access.port';
|
|
5
|
-
import * as i0 from "@angular/core";
|
|
6
|
-
/**
|
|
7
|
-
* YOLO object detection over the current view.
|
|
8
|
-
*
|
|
9
|
-
* The complement to {@link CellSegmentToolService}: cellpose auto-segments cells
|
|
10
|
-
* inside a box you draw, whereas a detector is meant to find objects across a
|
|
11
|
-
* field. So this takes no prompt — it runs on whatever is on screen and turns
|
|
12
|
-
* every detection into a region.
|
|
13
|
-
*
|
|
14
|
-
* Detections come back in the displayed image's pixels; they are mapped onto
|
|
15
|
-
* the full-resolution image through the viewer's own source rect, so the regions
|
|
16
|
-
* land where the objects actually are rather than at view-local coordinates.
|
|
17
|
-
*
|
|
18
|
-
* Exposes the same `status$` / `busy$` / `progress$` surface as the cellpose
|
|
19
|
-
* tool so the host's shared segmentation toast drives it unchanged.
|
|
20
|
-
*/
|
|
21
|
-
/** Marks regions this tool produced, so a re-run can replace only its own. */
|
|
22
|
-
export const YOLO_REGION_SOURCE = 'yolo';
|
|
23
|
-
export class YoloDetectToolService {
|
|
24
|
-
constructor(
|
|
25
|
-
/** Server-side crop, so detection can run at the model's scale rather than
|
|
26
|
-
* the viewer's. Optional: without it the tool falls back to the displayed
|
|
27
|
-
* pixels, which is correct but scale-dependent. */
|
|
28
|
-
tileAccess = null) {
|
|
29
|
-
this.tileAccess = tileAccess;
|
|
30
|
-
this.status$ = new BehaviorSubject('');
|
|
31
|
-
this.busy$ = new BehaviorSubject(false);
|
|
32
|
-
this.progress$ = new BehaviorSubject(-1);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Detect in the current view and append the results as regions.
|
|
36
|
-
*
|
|
37
|
-
* @returns the number of regions added.
|
|
38
|
-
*/
|
|
39
|
-
async detectInView(viz, segmenter, opts = {}) {
|
|
40
|
-
const pixels = viz.getDisplayedPixelData?.();
|
|
41
|
-
if (!pixels || pixels.width === 0 || pixels.height === 0) {
|
|
42
|
-
this.status$.next('No image on screen to detect in.');
|
|
43
|
-
return 0;
|
|
44
|
-
}
|
|
45
|
-
this.busy$.next(true);
|
|
46
|
-
this.progress$.next(-1);
|
|
47
|
-
try {
|
|
48
|
-
const rect = viz.getDisplayedSourceRect?.() ?? null;
|
|
49
|
-
const target = Number(opts.downsamplingFactor ?? 0) || 0;
|
|
50
|
-
const viewFactor = rect && pixels.width > 0 ? rect.width / pixels.width : 1;
|
|
51
|
-
// Re-crop at the model's scale when the view is coarser than it wants.
|
|
52
|
-
// The image is tile-backed, so that detail exists server-side even though
|
|
53
|
-
// the displayed pixels have discarded it.
|
|
54
|
-
let source = { data: pixels.data, width: pixels.width, height: pixels.height };
|
|
55
|
-
if (rect && target > 0 && target < viewFactor * 0.98) {
|
|
56
|
-
this.status$.next('Fetching image at model resolution…');
|
|
57
|
-
const hi = await this.fetchCropAtScale(rect, target);
|
|
58
|
-
if (hi)
|
|
59
|
-
source = hi;
|
|
60
|
-
}
|
|
61
|
-
const result = await segmenter.segmentInstances(source, opts, {
|
|
62
|
-
onProgress: (f) => this.progress$.next(f),
|
|
63
|
-
onStatus: (s) => this.status$.next(s),
|
|
64
|
-
});
|
|
65
|
-
// View pixels -> full-resolution image pixels. Without this every region
|
|
66
|
-
// would sit at view-local coordinates, i.e. the wrong place on the slide
|
|
67
|
-
// for any zoomed or panned view.
|
|
68
|
-
const offsetX = rect ? rect.x : 0;
|
|
69
|
-
const offsetY = rect ? rect.y : 0;
|
|
70
|
-
// Against the pixels actually inferred on, which may be the re-crop.
|
|
71
|
-
const ratioX = rect && source.width > 0 ? rect.width / source.width : 1;
|
|
72
|
-
const ratioY = rect && source.height > 0 ? rect.height / source.height : 1;
|
|
73
|
-
// View pixels -> full-resolution image pixels, for any ring.
|
|
74
|
-
const mapRing = (ring) => ring.map(([x, y]) => [Math.round(offsetX + x * ratioX), Math.round(offsetY + y * ratioY)]);
|
|
75
|
-
const added = [];
|
|
76
|
-
for (const det of result.detections) {
|
|
77
|
-
if (det.polygons.length > 0) {
|
|
78
|
-
for (const poly of det.polygons) {
|
|
79
|
-
if (poly.exterior.length < 3)
|
|
80
|
-
continue;
|
|
81
|
-
const exterior = mapRing(poly.exterior);
|
|
82
|
-
added.push(this.makeRegion(exterior.map(([x]) => x), exterior.map(([, y]) => y), det.className,
|
|
83
|
-
// A mask with a void through it is a donut, and committing only
|
|
84
|
-
// the exterior fills it in silently — which reads as a detection
|
|
85
|
-
// that over-covers rather than as lost geometry. Each polygon
|
|
86
|
-
// already owns its own holes, so there is no ambiguity about
|
|
87
|
-
// which exterior a ring belongs to even when one mask splits
|
|
88
|
-
// into several polygons. Rings too small to bound anything after
|
|
89
|
-
// simplification are dropped rather than emitted.
|
|
90
|
-
poly.holes.filter((h) => h.length >= 3).map(mapRing)));
|
|
91
|
-
}
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
// Detection mode yields no outline; the box is still the result, and
|
|
95
|
-
// matches what the server writes as a "detection" feature.
|
|
96
|
-
const [x1, y1, x2, y2] = det.box;
|
|
97
|
-
if (x2 - x1 <= 0 || y2 - y1 <= 0)
|
|
98
|
-
continue;
|
|
99
|
-
added.push(this.makeRegion([x1, x2, x2, x1].map((x) => Math.round(offsetX + x * ratioX)), [y1, y1, y2, y2].map((y) => Math.round(offsetY + y * ratioY)), det.className));
|
|
100
|
-
}
|
|
101
|
-
if (added.length === 0) {
|
|
102
|
-
this.status$.next('No objects detected.');
|
|
103
|
-
return 0;
|
|
104
|
-
}
|
|
105
|
-
// Replace this tool's own previous output, keep everything else. Appending
|
|
106
|
-
// unconditionally made every tuning run stack onto the last, so the
|
|
107
|
-
// viewer accumulated overlapping results from parameters no longer in
|
|
108
|
-
// effect. Hand-drawn regions and other tools' output carry no `source`
|
|
109
|
-
// and are untouched.
|
|
110
|
-
const kept = (viz.getRegions() ?? []).filter((r) => r.source !== YOLO_REGION_SOURCE);
|
|
111
|
-
viz.setRegions([...kept, ...added]);
|
|
112
|
-
this.status$.next(`Added ${added.length} region(s).`);
|
|
113
|
-
return added.length;
|
|
114
|
-
}
|
|
115
|
-
catch (err) {
|
|
116
|
-
this.status$.next(err instanceof Error ? err.message : 'YOLO detection failed.');
|
|
117
|
-
return 0;
|
|
118
|
-
}
|
|
119
|
-
finally {
|
|
120
|
-
this.busy$.next(false);
|
|
121
|
-
this.progress$.next(-1);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Re-crop `rect` at `1/target` of full resolution. Returns null on any
|
|
126
|
-
* failure, so the caller falls back to the displayed pixels — a coarser crop
|
|
127
|
-
* beats a failed run.
|
|
128
|
-
*/
|
|
129
|
-
async fetchCropAtScale(rect, target) {
|
|
130
|
-
if (!this.tileAccess)
|
|
131
|
-
return null;
|
|
132
|
-
let w = Math.round(rect.width / target);
|
|
133
|
-
let h = Math.round(rect.height / target);
|
|
134
|
-
// A factor of 1 over a whole-slide ROI would ask for hundreds of megapixels.
|
|
135
|
-
const MAX_PIXELS = 16_777_216; // 4096²
|
|
136
|
-
const pixels = w * h;
|
|
137
|
-
if (pixels > MAX_PIXELS) {
|
|
138
|
-
const k = Math.sqrt(MAX_PIXELS / pixels);
|
|
139
|
-
w = Math.round(w * k);
|
|
140
|
-
h = Math.round(h * k);
|
|
141
|
-
}
|
|
142
|
-
if (w < 64 || h < 64)
|
|
143
|
-
return null;
|
|
144
|
-
try {
|
|
145
|
-
const roi = {
|
|
146
|
-
x: Math.round(rect.x),
|
|
147
|
-
y: Math.round(rect.y),
|
|
148
|
-
width: Math.round(rect.width),
|
|
149
|
-
height: Math.round(rect.height),
|
|
150
|
-
};
|
|
151
|
-
const screen = { x: 0, y: 0, width: w, height: h };
|
|
152
|
-
const bytes = await firstValueFrom(this.tileAccess.zoomOnRegion(roi, screen, 0));
|
|
153
|
-
if (!bytes || bytes.byteLength === 0)
|
|
154
|
-
return null;
|
|
155
|
-
const bitmap = await createImageBitmap(new Blob([bytes]));
|
|
156
|
-
const canvas = document.createElement('canvas');
|
|
157
|
-
canvas.width = bitmap.width;
|
|
158
|
-
canvas.height = bitmap.height;
|
|
159
|
-
const ctx = canvas.getContext('2d');
|
|
160
|
-
if (!ctx)
|
|
161
|
-
return null;
|
|
162
|
-
ctx.drawImage(bitmap, 0, 0);
|
|
163
|
-
// Read the dimensions BEFORE close(): closing an ImageBitmap zeroes its
|
|
164
|
-
// width/height, so returning them afterwards yields a 0x0 image with a
|
|
165
|
-
// full pixel buffer — inference then runs on nothing and finds nothing.
|
|
166
|
-
const width = bitmap.width;
|
|
167
|
-
const height = bitmap.height;
|
|
168
|
-
const out = ctx.getImageData(0, 0, width, height);
|
|
169
|
-
bitmap.close?.();
|
|
170
|
-
return { data: out.data, width, height };
|
|
171
|
-
}
|
|
172
|
-
catch {
|
|
173
|
-
return null;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
makeRegion(xs, ys, className, holes = []) {
|
|
177
|
-
const poly = new Polygon();
|
|
178
|
-
poly.npoints = xs.length;
|
|
179
|
-
poly.xpoints = xs;
|
|
180
|
-
poly.ypoints = ys;
|
|
181
|
-
poly.coordinates = xs.map((x, i) => [x, ys[i]]);
|
|
182
|
-
poly.closed = true;
|
|
183
|
-
// Same closed-ring convention as the exterior: no repeated closing point.
|
|
184
|
-
// Left unset when empty so a solid region does not carry an empty array
|
|
185
|
-
// through export and round-tripping.
|
|
186
|
-
if (holes.length)
|
|
187
|
-
poly.holes = holes;
|
|
188
|
-
const region = new Region();
|
|
189
|
-
region.bounds = poly;
|
|
190
|
-
region.source = YOLO_REGION_SOURCE;
|
|
191
|
-
// The class name drives preset colour resolution; leaving `color` unset lets
|
|
192
|
-
// an existing preset for this class win.
|
|
193
|
-
region.label = className;
|
|
194
|
-
return region;
|
|
195
|
-
}
|
|
196
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: YoloDetectToolService, deps: [{ token: TILE_ACCESS_PORT, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
197
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: YoloDetectToolService, providedIn: 'root' }); }
|
|
198
|
-
}
|
|
199
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: YoloDetectToolService, decorators: [{
|
|
200
|
-
type: Injectable,
|
|
201
|
-
args: [{ providedIn: 'root' }]
|
|
202
|
-
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
203
|
-
type: Optional
|
|
204
|
-
}, {
|
|
205
|
-
type: Inject,
|
|
206
|
-
args: [TILE_ACCESS_PORT]
|
|
207
|
-
}] }] });
|
|
208
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoieW9sby1kZXRlY3QtdG9vbC5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL2xpYi90b29sYmFyL3NlZ21lbnRhdGlvbi95b2xvLWRldGVjdC10b29sLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQzdELE9BQU8sRUFBRSxlQUFlLEVBQUUsY0FBYyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBRXZELE9BQU8sRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFhLE1BQU0scUJBQXFCLENBQUM7QUFDakUsT0FBTyxFQUFFLGdCQUFnQixFQUF1QixNQUFNLHdDQUF3QyxDQUFDOztBQUkvRjs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUNILDhFQUE4RTtBQUM5RSxNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyxNQUFNLENBQUM7QUFHekMsTUFBTSxPQUFPLHFCQUFxQjtJQUtoQztJQUNFOzt3REFFb0Q7SUFDTixhQUFvQyxJQUFJO1FBQXhDLGVBQVUsR0FBVixVQUFVLENBQThCO1FBUi9FLFlBQU8sR0FBRyxJQUFJLGVBQWUsQ0FBUyxFQUFFLENBQUMsQ0FBQztRQUMxQyxVQUFLLEdBQUcsSUFBSSxlQUFlLENBQVUsS0FBSyxDQUFDLENBQUM7UUFDNUMsY0FBUyxHQUFHLElBQUksZUFBZSxDQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFPbEQsQ0FBQztJQUVKOzs7O09BSUc7SUFDSCxLQUFLLENBQUMsWUFBWSxDQUNoQixHQUFnQixFQUNoQixTQUE2QixFQUM3QixPQUErQixFQUFFO1FBRWpDLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxxQkFBcUIsRUFBRSxFQUFFLENBQUM7UUFDN0MsSUFBSSxDQUFDLE1BQU0sSUFBSSxNQUFNLENBQUMsS0FBSyxLQUFLLENBQUMsSUFBSSxNQUFNLENBQUMsTUFB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LENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ2hELElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDO1FBQ25CLDBFQUEwRTtRQUMxRSx3RUFBd0U7UUFDeEUscUNBQXFDO1FBQ3JDLElBQUksS0FBSyxDQUFDLE1BQU07WUFBRSxJQUFJLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQztRQUVyQyxNQUFNLE1BQU0sR0FBRyxJQUFJLE1BQU0sRUFBRSxDQUFDO1FBQzVCLE1BQU0sQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDO1FBQ3JCLE1BQU0sQ0FBQyxNQUFNLEdBQUcsa0JBQWtCLENBQUM7UUFDbkMsNkVBQTZFO1FBQzdFLHlDQUF5QztRQUN6QyxNQUFNLENBQUMsS0FBSyxHQUFHLFNBQVMsQ0FBQztRQUN6QixPQUFPLE1BQU0sQ0FBQztJQUNoQixDQUFDOzhHQXBNVSxxQkFBcUIsa0JBU1YsZ0JBQWdCO2tIQVQzQixxQkFBcUIsY0FEUixNQUFNOzsyRkFDbkIscUJBQXFCO2tCQURqQyxVQUFVO21CQUFDLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRTs7MEJBVTdCLFFBQVE7OzBCQUFJLE1BQU07MkJBQUMsZ0JBQWdCIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0LCBJbmplY3RhYmxlLCBPcHRpb25hbCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQmVoYXZpb3JTdWJqZWN0LCBmaXJzdFZhbHVlRnJvbSB9IGZyb20gJ3J4anMnO1xuXG5pbXBvcnQgeyBSZWdpb24sIFBvbHlnb24sIFJlY3RhbmdsZSB9IGZyb20gJy4uLy4uL21vZGVscy9yZWdpb24nO1xuaW1wb3J0IHsgVElMRV9BQ0NFU1NfUE9SVCwgdHlwZSBUaWxlQWNjZXNzUG9ydCB9IGZyb20gJy4uLy4uL2NvbnRyYWN0cy9wb3J0cy90aWxlLWFjY2Vzcy5wb3J0JztcbmltcG9ydCB0eXBlIHsgSVZpc3VhbGl6ZXIgfSBmcm9tICcuLi8uLi9jb250cmFjdHMvdmlzdWFsaXplci5jb250cmFjdCc7XG5pbXBvcnQgdHlwZSB7IElJbnN0YW5jZVNlZ21lbnRlciwgSW5zdGFuY2VTZWdtZW50T3B0aW9ucyB9IGZyb20gJy4uLy4uL2NvbnRyYWN0cy9pbnN0YW5jZS1zZWdtZW50ZXIuY29udHJhY3QnO1xuXG4vKipcbiAqIFlPTE8gb2JqZWN0IGRldGVjdGlvbiBvdmVyIHRoZSBjdXJyZW50IHZpZXcuXG4gKlxuICogVGhlIGNvbXBsZW1lbnQgdG8ge0BsaW5rIENlbGxTZWdtZW50VG9vbFNlcnZpY2V9OiBjZWxscG9zZSBhdXRvLXNlZ21lbnRzIGNlbGxzXG4gKiBpbnNpZGUgYSBib3ggeW91IGRyYXcsIHdoZXJlYXMgYSBkZXRlY3RvciBpcyBtZWFudCB0byBmaW5kIG9iamVjdHMgYWNyb3NzIGFcbiAqIGZpZWxkLiBTbyB0aGlzIHRha2VzIG5vIHByb21wdCDigJQgaXQgcnVucyBvbiB3aGF0ZXZlciBpcyBvbiBzY3JlZW4gYW5kIHR1cm5zXG4gKiBldmVyeSBkZXRlY3Rpb24gaW50byBhIHJlZ2lvbi5cbiAqXG4gKiBEZXRlY3Rpb25zIGNvbWUgYmFjayBpbiB0aGUgZGlzcGxheWVkIGltYWdlJ3MgcGl4ZWxzOyB0aGV5IGFyZSBtYXBwZWQgb250b1xuICogdGhlIGZ1bGwtcmVzb2x1dGlvbiBpbWFnZSB0aHJvdWdoIHRoZSB2aWV3ZXIncyBvd24gc291cmNlIHJlY3QsIHNvIHRoZSByZWdpb25zXG4gKiBsYW5kIHdoZXJlIHRoZSBvYmplY3RzIGFjdHVhbGx5IGFyZSByYXRoZXIgdGhhbiBhdCB2aWV3LWxvY2FsIGNvb3JkaW5hdGVzLlxuICpcbiAqIEV4cG9zZXMgdGhlIHNhbWUgYHN0YXR1cyRgIC8gYGJ1c3kkYCAvIGBwcm9ncmVzcyRgIHN1cmZhY2UgYXMgdGhlIGNlbGxwb3NlXG4gKiB0b29sIHNvIHRoZSBob3N0J3Mgc2hhcmVkIHNlZ21lbnRhdGlvbiB0b2FzdCBkcml2ZXMgaXQgdW5jaGFuZ2VkLlxuICovXG4vKiogTWFya3MgcmVnaW9ucyB0aGlzIHRvb2wgcHJvZHVjZWQsIHNvIGEgcmUtcnVuIGNhbiByZXBsYWNlIG9ubHkgaXRzIG93bi4gKi9cbmV4cG9ydCBjb25zdCBZT0xPX1JFR0lPTl9TT1VSQ0UgPSAneW9sbyc7XG5cbkBJbmplY3RhYmxlKHsgcHJvdmlkZWRJbjogJ3Jvb3QnIH0pXG5leHBvcnQgY2xhc3MgWW9sb0RldGVjdFRvb2xTZXJ2aWNlIHtcbiAgcmVhZG9ubHkgc3RhdHVzJCA9IG5ldyBCZWhhdmlvclN1YmplY3Q8c3RyaW5nPignJyk7XG4gIHJlYWRvbmx5IGJ1c3kkID0gbmV3IEJlaGF2aW9yU3ViamVjdDxib29sZWFuPihmYWxzZSk7XG4gIHJlYWRvbmx5IHByb2dyZXNzJCA9IG5ldyBCZWhhdmlvclN1YmplY3Q8bnVtYmVyPigtMSk7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgLyoqIFNlcnZlci1zaWRlIGNyb3AsIHNvIGRldGVjdGlvbiBjYW4gcnVuIGF0IHRoZSBtb2RlbCdzIHNjYWxlIHJhdGhlciB0aGFuXG4gICAgICogIHRoZSB2aWV3ZXIncy4gT3B0aW9uYWw6IHdpdGhvdXQgaXQgdGhlIHRvb2wgZmFsbHMgYmFjayB0byB0aGUgZGlzcGxheWVkXG4gICAgICogIHBpeGVscywgd2hpY2ggaXMgY29ycmVjdCBidXQgc2NhbGUtZGVwZW5kZW50LiAqL1xuICAgIEBPcHRpb25hbCgpIEBJbmplY3QoVElMRV9BQ0NFU1NfUE9SVCkgcHJpdmF0ZSB0aWxlQWNjZXNzOiBUaWxlQWNjZXNzUG9ydCB8IG51bGwgPSBudWxsLFxuICApIHt9XG5cbiAgLyoqXG4gICAqIERldGVjdCBpbiB0aGUgY3VycmVudCB2aWV3IGFuZCBhcHBlbmQgdGhlIHJlc3VsdHMgYXMgcmVnaW9ucy5cbiAgICpcbiAgICogQHJldHVybnMgdGhlIG51bWJlciBvZiByZWdpb25zIGFkZGVkLlxuICAgKi9cbiAgYXN5bmMgZGV0ZWN0SW5WaWV3KFxuICAgIHZpejogSVZpc3VhbGl6ZXIsXG4gICAgc2VnbWVudGVyOiBJSW5zdGFuY2VTZWdtZW50ZXIsXG4gICAgb3B0czogSW5zdGFuY2VTZWdtZW50T3B0aW9ucyA9IHt9LFxuICApOiBQcm9taXNlPG51bWJlcj4ge1xuICAgIGNvbnN0IHBpeGVscyA9IHZpei5nZXREaXNwbGF5ZWRQaXhlbERhdGE/LigpO1xuICAgIGlmICghcGl4ZWxzIHx8IHBpeGVscy53aWR0aCA9PT0gMCB8fCBwaXhlbHMuaGVpZ2h0ID09PSAwKSB7XG4gICAgICB0aGlzLnN0YXR1cyQubmV4dCgnTm8gaW1hZ2Ugb24gc2NyZWVuIHRvIGRldGVjdCBpbi4nKTtcbiAgICAgIHJldHVybiAwO1xuICAgIH1cblxuICAgIHRoaXMuYnVzeSQubmV4dCh0cnVlKTtcbiAgICB0aGlzLnByb2dyZXNzJC5uZXh0KC0xKTtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVjdCA9IHZpei5nZXREaXNwbGF5ZWRTb3VyY2VSZWN0Py4oKSA/PyBudWxsO1xuICAgICAgY29uc3QgdGFyZ2V0ID0gTnVtYmVyKG9wdHMuZG93bnNhbXBsaW5nRmFjdG9yID8/IDApIHx8IDA7XG4gICAgICBjb25zdCB2aWV3RmFjdG9yID0gcmVjdCAmJiBwaXhlbHMud2lkdGggPiAwID8gcmVjdC53aWR0aCAvIHBpeGVscy53aWR0aCA6IDE7XG5cbiAgICAgIC8vIFJlLWNyb3AgYXQgdGhlIG1vZGVsJ3Mgc2NhbGUgd2hlbiB0aGUgdmlldyBpcyBjb2Fyc2VyIHRoYW4gaXQgd2FudHMuXG4gICAgICAvLyBUaGUgaW1hZ2UgaXMgdGlsZS1iYWNrZWQsIHNvIHRoYXQgZGV0YWlsIGV4aXN0cyBzZXJ2ZXItc2lkZSBldmVuIHRob3VnaFxuICAgICAgLy8gdGhlIGRpc3BsYXllZCBwaXhlbHMgaGF2ZSBkaXNjYXJkZWQgaXQuXG4gICAgICBsZXQgc291cmNlID0geyBkYXRhOiBwaXhlbHMuZGF0YSwgd2lkdGg6IHBpeGVscy53aWR0aCwgaGVpZ2h0OiBwaXhlbHMuaGVpZ2h0IH07XG4gICAgICBpZiAocmVjdCAmJiB0YXJnZXQgPiAwICYmIHRhcmdldCA8IHZpZXdGYWN0b3IgKiAwLjk4KSB7XG4gICAgICAgIHRoaXMuc3RhdHVzJC5uZXh0KCdGZXRjaGluZyBpbWFnZSBhdCBtb2RlbCByZXNvbHV0aW9u4oCmJyk7XG4gICAgICAgIGNvbnN0IGhpID0gYXdhaXQgdGhpcy5mZXRjaENyb3BBdFNjYWxlKHJlY3QsIHRhcmdldCk7XG4gICAgICAgIGlmIChoaSkgc291cmNlID0gaGk7XG4gICAgICB9XG5cbiAgICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHNlZ21lbnRlci5zZWdtZW50SW5zdGFuY2VzKHNvdXJjZSwgb3B0cywge1xuICAgICAgICBvblByb2dyZXNzOiAoZikgPT4gdGhpcy5wcm9ncmVzcyQubmV4dChmKSxcbiAgICAgICAgb25TdGF0dXM6IChzKSA9PiB0aGlzLnN0YXR1cyQubmV4dChzKSxcbiAgICAgIH0pO1xuXG4gICAgICAvLyBWaWV3IHBpeGVscyAtPiBmdWxsLXJlc29sdXRpb24gaW1hZ2UgcGl4ZWxzLiBXaXRob3V0IHRoaXMgZXZlcnkgcmVnaW9uXG4gICAgICAvLyB3b3VsZCBzaXQgYXQgdmlldy1sb2NhbCBjb29yZGluYXRlcywgaS5lLiB0aGUgd3JvbmcgcGxhY2Ugb24gdGhlIHNsaWRlXG4gICAgICAvLyBmb3IgYW55IHpvb21lZCBvciBwYW5uZWQgdmlldy5cbiAgICAgIGNvbnN0IG9mZnNldFggPSByZWN0ID8gcmVjdC54IDogMDtcbiAgICAgIGNvbnN0IG9mZnNldFkgPSByZWN0ID8gcmVjdC55IDogMDtcbiAgICAgIC8vIEFnYWluc3QgdGhlIHBpeGVscyBhY3R1YWxseSBpbmZlcnJlZCBvbiwgd2hpY2ggbWF5IGJlIHRoZSByZS1jcm9wLlxuICAgICAgY29uc3QgcmF0aW9YID0gcmVjdCAmJiBzb3VyY2Uud2lkdGggPiAwID8gcmVjdC53aWR0aCAvIHNvdXJjZS53aWR0aCA6IDE7XG4gICAgICBjb25zdCByYXRpb1kgPSByZWN0ICYmIHNvdXJjZS5oZWlnaHQgPiAwID8gcmVjdC5oZWlnaHQgLyBzb3VyY2UuaGVpZ2h0IDogMTtcblxuICAgICAgLy8gVmlldyBwaXhlbHMgLT4gZnVsbC1yZXNvbHV0aW9uIGltYWdlIHBpeGVscywgZm9yIGFueSByaW5nLlxuICAgICAgY29uc3QgbWFwUmluZyA9IChyaW5nOiBBcnJheTxbbnVtYmVyLCBudW1iZXJdPik6IG51bWJlcltdW10gPT5cbiAgICAgICAgcmluZy5tYXAoKFt4LCB5XSkgPT4gW01hdGgucm91bmQob2Zmc2V0WCArIHggKiByYXRpb1gpLCBNYXRoLnJvdW5kKG9mZnNldFkgKyB5ICogcmF0aW9ZKV0pO1xuXG4gICAgICBjb25zdCBhZGRlZDogUmVnaW9uW10gPSBbXTtcbiAgICAgIGZvciAoY29uc3QgZGV0IG9mIHJlc3VsdC5kZXRlY3Rpb25zKSB7XG4gICAgICAgIGlmIChkZXQucG9seWdvbnMubGVuZ3RoID4gMCkge1xuICAgICAgICAgIGZvciAoY29uc3QgcG9seSBvZiBkZXQucG9seWdvbnMpIHtcbiAgICAgICAgICAgIGlmIChwb2x5LmV4dGVyaW9yLmxlbmd0aCA8IDMpIGNvbnRpbnVlO1xuICAgICAgICAgICAgY29uc3QgZXh0ZXJpb3IgPSBtYXBSaW5nKHBvbHkuZXh0ZXJpb3IpO1xuICAgICAgICAgICAgYWRkZWQucHVzaChcbiAgICAgICAgICAgICAgdGhpcy5tYWtlUmVnaW9uKFxuICAgICAgICAgICAgICAgIGV4dGVyaW9yLm1hcCgoW3hdKSA9PiB4KSxcbiAgICAgICAgICAgICAgICBleHRlcmlvci5tYXAoKFssIHldKSA9PiB5KSxcbiAgICAgICAgICAgICAgICBkZXQuY2xhc3NOYW1lLFxuICAgICAgICAgICAgICAgIC8vIEEgbWFzayB3aXRoIGEgdm9pZCB0aHJvdWdoIGl0IGlzIGEgZG9udXQsIGFuZCBjb21taXR0aW5nIG9ubHlcbiAgICAgICAgICAgICAgICAvLyB0aGUgZXh0ZXJpb3IgZmlsbHMgaXQgaW4gc2lsZW50bHkg4oCUIHdoaWNoIHJlYWRzIGFzIGEgZGV0ZWN0aW9uXG4gICAgICAgICAgICAgICAgLy8gdGhhdCBvdmVyLWNvdmVycyByYXRoZXIgdGhhbiBhcyBsb3N0IGdlb21ldHJ5LiBFYWNoIHBvbHlnb25cbiAgICAgICAgICAgICAgICAvLyBhbHJlYWR5IG93bnMgaXRzIG93biBob2xlcywgc28gdGhlcmUgaXMgbm8gYW1iaWd1aXR5IGFib3V0XG4gICAgICAgICAgICAgICAgLy8gd2hpY2ggZXh0ZXJpb3IgYSByaW5nIGJlbG9uZ3MgdG8gZXZlbiB3aGVuIG9uZSBtYXNrIHNwbGl0c1xuICAgICAgICAgICAgICAgIC8vIGludG8gc2V2ZXJhbCBwb2x5Z29ucy4gUmluZ3MgdG9vIHNtYWxsIHRvIGJvdW5kIGFueXRoaW5nIGFmdGVyXG4gICAgICAgICAgICAgICAgLy8gc2ltcGxpZmljYXRpb24gYXJlIGRyb3BwZWQgcmF0aGVyIHRoYW4gZW1pdHRlZC5cbiAgICAgICAgICAgICAgICBwb2x5LmhvbGVzLmZpbHRlcigoaCkgPT4gaC5sZW5ndGggPj0gMykubWFwKG1hcFJpbmcpLFxuICAgICAgICAgICAgICApLFxuICAgICAgICAgICAgKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgLy8gRGV0ZWN0aW9uIG1vZGUgeWllbGRzIG5vIG91dGxpbmU7IHRoZSBib3ggaXMgc3RpbGwgdGhlIHJlc3VsdCwgYW5kXG4gICAgICAgIC8vIG1hdGNoZXMgd2hhdCB0aGUgc2VydmVyIHdyaXRlcyBhcyBhIFwiZGV0ZWN0aW9uXCIgZmVhdHVyZS5cbiAgICAgICAgY29uc3QgW3gxLCB5MSwgeDIsIHkyXSA9IGRldC5ib3g7XG4gICAgICAgIGlmICh4MiAtIHgxIDw9IDAgfHwgeTIgLSB5MSA8PSAwKSBjb250aW51ZTtcbiAgICAgICAgYWRkZWQucHVzaChcbiAgICAgICAgICB0aGlzLm1ha2VSZWdpb24oXG4gICAgICAgICAgICBbeDEsIHgyLCB4MiwgeDFdLm1hcCgoeCkgPT4gTWF0aC5yb3VuZChvZmZzZXRYICsgeCAqIHJhdGlvWCkpLFxuICAgICAgICAgICAgW3kxLCB5MSwgeTIsIHkyXS5tYXAoKHkpID0+IE1hdGgucm91bmQob2Zmc2V0WSArIHkgKiByYXRpb1kpKSxcbiAgICAgICAgICAgIGRldC5jbGFzc05hbWUsXG4gICAgICAgICAgKSxcbiAgICAgICAgKTtcbiAgICAgIH1cblxuICAgICAgaWYgKGFkZGVkLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICB0aGlzLnN0YXR1cyQubmV4dCgnTm8gb2JqZWN0cyBkZXRlY3RlZC4nKTtcbiAgICAgICAgcmV0dXJuIDA7XG4gICAgICB9XG5cbiAgICAgIC8vIFJlcGxhY2UgdGhpcyB0b29sJ3Mgb3duIHByZXZpb3VzIG91dHB1dCwga2VlcCBldmVyeXRoaW5nIGVsc2UuIEFwcGVuZGluZ1xuICAgICAgLy8gdW5jb25kaXRpb25hbGx5IG1hZGUgZXZlcnkgdHVuaW5nIHJ1biBzdGFjayBvbnRvIHRoZSBsYXN0LCBzbyB0aGVcbiAgICAgIC8vIHZpZXdlciBhY2N1bXVsYXRlZCBvdmVybGFwcGluZyByZXN1bHRzIGZyb20gcGFyYW1ldGVycyBubyBsb25nZXIgaW5cbiAgICAgIC8vIGVmZmVjdC4gSGFuZC1kcmF3biByZWdpb25zIGFuZCBvdGhlciB0b29scycgb3V0cHV0IGNhcnJ5IG5vIGBzb3VyY2VgXG4gICAgICAvLyBhbmQgYXJlIHVudG91Y2hlZC5cbiAgICAgIGNvbnN0IGtlcHQgPSAodml6LmdldFJlZ2lvbnMoKSA/PyBbXSkuZmlsdGVyKChyKSA9PiByLnNvdXJjZSAhPT0gWU9MT19SRUdJT05fU09VUkNFKTtcbiAgICAgIHZpei5zZXRSZWdpb25zKFsuLi5rZXB0LCAuLi5hZGRlZF0pO1xuICAgICAgdGhpcy5zdGF0dXMkLm5leHQoYEFkZGVkICR7YWRkZWQubGVuZ3RofSByZWdpb24ocykuYCk7XG4gICAgICByZXR1cm4gYWRkZWQubGVuZ3RoO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgdGhpcy5zdGF0dXMkLm5leHQoZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6ICdZT0xPIGRldGVjdGlvbiBmYWlsZWQuJyk7XG4gICAgICByZXR1cm4gMDtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgdGhpcy5idXN5JC5uZXh0KGZhbHNlKTtcbiAgICAgIHRoaXMucHJvZ3Jlc3MkLm5leHQoLTEpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSZS1jcm9wIGByZWN0YCBhdCBgMS90YXJnZXRgIG9mIGZ1bGwgcmVzb2x1dGlvbi4gUmV0dXJucyBudWxsIG9uIGFueVxuICAgKiBmYWlsdXJlLCBzbyB0aGUgY2FsbGVyIGZhbGxzIGJhY2sgdG8gdGhlIGRpc3BsYXllZCBwaXhlbHMg4oCUIGEgY29hcnNlciBjcm9wXG4gICAqIGJlYXRzIGEgZmFpbGVkIHJ1bi5cbiAgICovXG4gIHByaXZhdGUgYXN5bmMgZmV0Y2hDcm9wQXRTY2FsZShcbiAgICByZWN0OiB7IHg6IG51bWJlcjsgeTogbnVtYmVyOyB3aWR0aDogbnVtYmVyOyBoZWlnaHQ6IG51bWJlciB9LFxuICAgIHRhcmdldDogbnVtYmVyLFxuICApOiBQcm9taXNlPHsgZGF0YTogVWludDhDbGFtcGVkQXJyYXk7IHdpZHRoOiBudW1iZXI7IGhlaWdodDogbnVtYmVyIH0gfCBudWxsPiB7XG4gICAgaWYgKCF0aGlzLnRpbGVBY2Nlc3MpIHJldHVybiBudWxsO1xuICAgIGxldCB3ID0gTWF0aC5yb3VuZChyZWN0LndpZHRoIC8gdGFyZ2V0KTtcbiAgICBsZXQgaCA9IE1hdGgucm91bmQocmVjdC5oZWlnaHQgLyB0YXJnZXQpO1xuXG4gICAgLy8gQSBmYWN0b3Igb2YgMSBvdmVyIGEgd2hvbGUtc2xpZGUgUk9JIHdvdWxkIGFzayBmb3IgaHVuZHJlZHMgb2YgbWVnYXBpeGVscy5cbiAgICBjb25zdCBNQVhfUElYRUxTID0gMTZfNzc3XzIxNjsgLy8gNDA5NsKyXG4gICAgY29uc3QgcGl4ZWxzID0gdyAqIGg7XG4gICAgaWYgKHBpeGVscyA+IE1BWF9QSVhFTFMpIHtcbiAgICAgIGNvbnN0IGsgPSBNYXRoLnNxcnQoTUFYX1BJWEVMUyAvIHBpeGVscyk7XG4gICAgICB3ID0gTWF0aC5yb3VuZCh3ICogayk7XG4gICAgICBoID0gTWF0aC5yb3VuZChoICogayk7XG4gICAgfVxuICAgIGlmICh3IDwgNjQgfHwgaCA8IDY0KSByZXR1cm4gbnVsbDtcblxuICAgIHRyeSB7XG4gICAgICBjb25zdCByb2kgPSB7XG4gICAgICAgIHg6IE1hdGgucm91bmQocmVjdC54KSxcbiAgICAgICAgeTogTWF0aC5yb3VuZChyZWN0LnkpLFxuICAgICAgICB3aWR0aDogTWF0aC5yb3VuZChyZWN0LndpZHRoKSxcbiAgICAgICAgaGVpZ2h0OiBNYXRoLnJvdW5kKHJlY3QuaGVpZ2h0KSxcbiAgICAgIH0gYXMgUmVjdGFuZ2xlO1xuICAgICAgY29uc3Qgc2NyZWVuID0geyB4OiAwLCB5OiAwLCB3aWR0aDogdywgaGVpZ2h0OiBoIH0gYXMgUmVjdGFuZ2xlO1xuICAgICAgY29uc3QgYnl0ZXMgPSBhd2FpdCBmaXJzdFZhbHVlRnJvbSh0aGlzLnRpbGVBY2Nlc3Muem9vbU9uUmVnaW9uKHJvaSwgc2NyZWVuLCAwKSk7XG4gICAgICBpZiAoIWJ5dGVzIHx8IGJ5dGVzLmJ5dGVMZW5ndGggPT09IDApIHJldHVybiBudWxsO1xuXG4gICAgICBjb25zdCBiaXRtYXAgPSBhd2FpdCBjcmVhdGVJbWFnZUJpdG1hcChuZXcgQmxvYihbYnl0ZXNdKSk7XG4gICAgICBjb25zdCBjYW52YXMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdjYW52YXMnKTtcbiAgICAgIGNhbnZhcy53aWR0aCA9IGJpdG1hcC53aWR0aDtcbiAgICAgIGNhbnZhcy5oZWlnaHQgPSBiaXRtYXAuaGVpZ2h0O1xuICAgICAgY29uc3QgY3R4ID0gY2FudmFzLmdldENvbnRleHQoJzJkJyk7XG4gICAgICBpZiAoIWN0eCkgcmV0dXJuIG51bGw7XG4gICAgICBjdHguZHJhd0ltYWdlKGJpdG1hcCwgMCwgMCk7XG4gICAgICAvLyBSZWFkIHRoZSBkaW1lbnNpb25zIEJFRk9SRSBjbG9zZSgpOiBjbG9zaW5nIGFuIEltYWdlQml0bWFwIHplcm9lcyBpdHNcbiAgICAgIC8vIHdpZHRoL2hlaWdodCwgc28gcmV0dXJuaW5nIHRoZW0gYWZ0ZXJ3YXJkcyB5aWVsZHMgYSAweDAgaW1hZ2Ugd2l0aCBhXG4gICAgICAvLyBmdWxsIHBpeGVsIGJ1ZmZlciDigJQgaW5mZXJlbmNlIHRoZW4gcnVucyBvbiBub3RoaW5nIGFuZCBmaW5kcyBub3RoaW5nLlxuICAgICAgY29uc3Qgd2lkdGggPSBiaXRtYXAud2lkdGg7XG4gICAgICBjb25zdCBoZWlnaHQgPSBiaXRtYXAuaGVpZ2h0O1xuICAgICAgY29uc3Qgb3V0ID0gY3R4LmdldEltYWdlRGF0YSgwLCAwLCB3aWR0aCwgaGVpZ2h0KTtcbiAgICAgIGJpdG1hcC5jbG9zZT8uKCk7XG4gICAgICByZXR1cm4geyBkYXRhOiBvdXQuZGF0YSwgd2lkdGgsIGhlaWdodCB9O1xuICAgIH0gY2F0Y2gge1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBtYWtlUmVnaW9uKHhzOiBudW1iZXJbXSwgeXM6IG51bWJlcltdLCBjbGFzc05hbWU6IHN0cmluZywgaG9sZXM6IG51bWJlcltdW11bXSA9IFtdKTogUmVnaW9uIHtcbiAgICBjb25zdCBwb2x5ID0gbmV3IFBvbHlnb24oKTtcbiAgICBwb2x5Lm5wb2ludHMgPSB4cy5sZW5ndGg7XG4gICAgcG9seS54cG9pbnRzID0geHM7XG4gICAgcG9seS55cG9pbnRzID0geXM7XG4gICAgcG9seS5jb29yZGluYXRlcyA9IHhzLm1hcCgoeCwgaSkgPT4gW3gsIHlzW2ldXSk7XG4gICAgcG9seS5jbG9zZWQgPSB0cnVlO1xuICAgIC8vIFNhbWUgY2xvc2VkLXJpbmcgY29udmVudGlvbiBhcyB0aGUgZXh0ZXJpb3I6IG5vIHJlcGVhdGVkIGNsb3NpbmcgcG9pbnQuXG4gICAgLy8gTGVmdCB1bnNldCB3aGVuIGVtcHR5IHNvIGEgc29saWQgcmVnaW9uIGRvZXMgbm90IGNhcnJ5IGFuIGVtcHR5IGFycmF5XG4gICAgLy8gdGhyb3VnaCBleHBvcnQgYW5kIHJvdW5kLXRyaXBwaW5nLlxuICAgIGlmIChob2xlcy5sZW5ndGgpIHBvbHkuaG9sZXMgPSBob2xlcztcblxuICAgIGNvbnN0IHJlZ2lvbiA9IG5ldyBSZWdpb24oKTtcbiAgICByZWdpb24uYm91bmRzID0gcG9seTtcbiAgICByZWdpb24uc291cmNlID0gWU9MT19SRUdJT05fU09VUkNFO1xuICAgIC8vIFRoZSBjbGFzcyBuYW1lIGRyaXZlcyBwcmVzZXQgY29sb3VyIHJlc29sdXRpb247IGxlYXZpbmcgYGNvbG9yYCB1bnNldCBsZXRzXG4gICAgLy8gYW4gZXhpc3RpbmcgcHJlc2V0IGZvciB0aGlzIGNsYXNzIHdpbi5cbiAgICByZWdpb24ubGFiZWwgPSBjbGFzc05hbWU7XG4gICAgcmV0dXJuIHJlZ2lvbjtcbiAgfVxufVxuIl19
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Registry of YOLOv8-seg instance-segmentation models.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors {@link SAM_MODELS}: public HF-hosted ONNX by default so the tool works
|
|
5
|
-
* out of the box in any host, repointable at private hosting via
|
|
6
|
-
* {@link setYoloModelUrls}.
|
|
7
|
-
*
|
|
8
|
-
* Each entry carries the per-model *defaults* the server-side worker uses for
|
|
9
|
-
* that checkpoint. These are properties of how the model was trained (object
|
|
10
|
-
* scale, expected crowding), not UI preferences — the embryo models want heavy
|
|
11
|
-
* 60% tile overlap and loose merge thresholds because their objects are large
|
|
12
|
-
* and touch, while the eye models were trained on sparse, downsampled fields.
|
|
13
|
-
* Keeping them here means a host that just picks a model gets sane behaviour
|
|
14
|
-
* without having to know any of that.
|
|
15
|
-
*
|
|
16
|
-
* Weights point at the `model.fp16w.onnx` variant deliberately. The `.fp16`
|
|
17
|
-
* variant accumulates in half precision on WebGPU and, at the x-scale these
|
|
18
|
-
* models use, produces materially wrong boxes; `fp16w` stores weights in fp16
|
|
19
|
-
* and computes in fp32, so it is half the download at identical accuracy.
|
|
20
|
-
*
|
|
21
|
-
* Export tooling: the sibling `browser-onnx-tools` project.
|
|
22
|
-
*/
|
|
23
|
-
const HF = 'https://huggingface.co/Ballon999';
|
|
24
|
-
const EYE_DEFAULTS = {
|
|
25
|
-
confidence: 0.6,
|
|
26
|
-
iouThreshold: 0.5,
|
|
27
|
-
mergeThreshold: 0.3,
|
|
28
|
-
overlapX: 0,
|
|
29
|
-
overlapY: 0,
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* The retina is an elongated band spanning the whole field, so it crosses tile
|
|
33
|
-
* seams wherever they fall.
|
|
34
|
-
*
|
|
35
|
-
* With no overlap the tiles butt together at fixed boundaries: a structure
|
|
36
|
-
* sitting across a seam is cut in half, each tile sees only a slice, and the
|
|
37
|
-
* slice often fails to clear the confidence threshold — so the object vanishes
|
|
38
|
-
* exactly where the seam lands. Observed as multi-thousand-pixel holes in the
|
|
39
|
-
* middle of an otherwise continuous retina, which closed once overlap was
|
|
40
|
-
* raised.
|
|
41
|
-
*
|
|
42
|
-
* An object of size `s` is guaranteed to sit wholly inside some tile only when
|
|
43
|
-
* the overlap is at least `s / tileSize`; at 60% that covers anything up to
|
|
44
|
-
* ~307px of a 512px tile. The server's own table uses 0 here and has the same
|
|
45
|
-
* blind spot — this deliberately diverges from it.
|
|
46
|
-
*/
|
|
47
|
-
const RETINA_DEFAULTS = {
|
|
48
|
-
confidence: 0.6,
|
|
49
|
-
iouThreshold: 0.5,
|
|
50
|
-
// Cross-tile merging uses intersection-over-*smaller*, so at 0.3 a fragment is
|
|
51
|
-
// discarded once 30% of its own area is covered by a larger box — even when
|
|
52
|
-
// most of it lies outside. On a band detected as a chain of overlapping
|
|
53
|
-
// fragments that deletes the middle ones. Measured on one slide: 0.3 gave two
|
|
54
|
-
// boxes covering 75% of the band with a 3150px hole through it; 0.8 gave four
|
|
55
|
-
// boxes covering 100% with no hole, at the cost of two overlapping pairs.
|
|
56
|
-
// Matches the value jit-ui already sends the server for this checkpoint.
|
|
57
|
-
mergeThreshold: 0.8,
|
|
58
|
-
overlapX: 60,
|
|
59
|
-
overlapY: 60,
|
|
60
|
-
};
|
|
61
|
-
// Embryos are large, touch each other, and are imaged at native scale — hence
|
|
62
|
-
// the heavy overlap and the far looser merge thresholds.
|
|
63
|
-
const EMBRYO_DEFAULTS = {
|
|
64
|
-
confidence: 0.6,
|
|
65
|
-
iouThreshold: 0.8,
|
|
66
|
-
mergeThreshold: 0.8,
|
|
67
|
-
overlapX: 60,
|
|
68
|
-
overlapY: 60,
|
|
69
|
-
};
|
|
70
|
-
export const YOLO_MODELS = [
|
|
71
|
-
{
|
|
72
|
-
id: 'yolov8x-seg-opticnerve',
|
|
73
|
-
label: 'Optic nerve',
|
|
74
|
-
modelUrl: `${HF}/yolov8x-seg-opticnerve-onnx/resolve/main/model.fp16w.onnx`,
|
|
75
|
-
defaults: { ...EYE_DEFAULTS },
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
id: 'yolov8x-seg-retina',
|
|
79
|
-
label: 'Retina',
|
|
80
|
-
modelUrl: `${HF}/yolov8x-seg-retina-onnx/resolve/main/model.fp16w.onnx`,
|
|
81
|
-
defaults: { ...RETINA_DEFAULTS },
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
id: 'yolov8x-seg-embryo-m2',
|
|
85
|
-
label: 'Embryo (M2)',
|
|
86
|
-
modelUrl: `${HF}/yolov8x-seg-embryo-m2-onnx/resolve/main/model.fp16w.onnx`,
|
|
87
|
-
defaults: { ...EMBRYO_DEFAULTS },
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
id: 'yolov8x-seg-embryo-m3',
|
|
91
|
-
label: 'Embryo (M3)',
|
|
92
|
-
modelUrl: `${HF}/yolov8x-seg-embryo-m3-onnx/resolve/main/model.fp16w.onnx`,
|
|
93
|
-
defaults: { ...EMBRYO_DEFAULTS },
|
|
94
|
-
},
|
|
95
|
-
];
|
|
96
|
-
export const DEFAULT_YOLO_MODEL_ID = 'yolov8x-seg-opticnerve';
|
|
97
|
-
let defaultModelId = DEFAULT_YOLO_MODEL_ID;
|
|
98
|
-
/** Look up a model definition by id. Returns undefined for an unknown id. */
|
|
99
|
-
export function getYoloModel(id) {
|
|
100
|
-
return YOLO_MODELS.find((m) => m.id === id);
|
|
101
|
-
}
|
|
102
|
-
/** Whether a model has a usable weights URL configured. */
|
|
103
|
-
export function isYoloModelReady(id) {
|
|
104
|
-
const m = getYoloModel(id);
|
|
105
|
-
return !!m && m.modelUrl.trim().length > 0;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Repoint a model at different hosting (e.g. a GCS bucket or a same-origin
|
|
109
|
-
* proxy). Call once at app init, before the first run. An empty `modelUrl`
|
|
110
|
-
* disables the model.
|
|
111
|
-
*/
|
|
112
|
-
export function setYoloModelUrls(id, urls) {
|
|
113
|
-
const m = getYoloModel(id);
|
|
114
|
-
if (!m)
|
|
115
|
-
return;
|
|
116
|
-
if (urls.modelUrl !== undefined)
|
|
117
|
-
m.modelUrl = urls.modelUrl;
|
|
118
|
-
if (urls.metaUrl !== undefined)
|
|
119
|
-
m.metaUrl = urls.metaUrl;
|
|
120
|
-
}
|
|
121
|
-
/** Change which model runs when a caller does not name one. */
|
|
122
|
-
export function setDefaultYoloModel(id) {
|
|
123
|
-
if (getYoloModel(id))
|
|
124
|
-
defaultModelId = id;
|
|
125
|
-
}
|
|
126
|
-
export function getDefaultYoloModelId() {
|
|
127
|
-
return defaultModelId;
|
|
128
|
-
}
|
|
129
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoieW9sby1tb2RlbC1yZWdpc3RyeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9saWIvdG9vbGJhci9zZWdtZW50YXRpb24veW9sby1tb2RlbC1yZWdpc3RyeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBcUJHO0FBc0JILE1BQU0sRUFBRSxHQUFHLGtDQUFrQyxDQUFDO0FBRTlDLE1BQU0sWUFBWSxHQUFHO0lBQ25CLFVBQVUsRUFBRSxHQUFHO0lBQ2YsWUFBWSxFQUFFLEdBQUc7SUFDakIsY0FBYyxFQUFFLEdBQUc7SUFDbkIsUUFBUSxFQUFFLENBQUM7SUFDWCxRQUFRLEVBQUUsQ0FBQztDQUNaLENBQUM7QUFFRjs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFDSCxNQUFNLGVBQWUsR0FBRztJQUN0QixVQUFVLEVBQUUsR0FBRztJQUNmLFlBQVksRUFBRSxHQUFHO0lBQ2pCLCtFQUErRTtJQUMvRSw0RUFBNEU7SUFDNUUsd0VBQXdFO0lBQ3hFLDhFQUE4RTtJQUM5RSw4RUFBOEU7SUFDOUUsMEVBQTBFO0lBQzFFLHlFQUF5RTtJQUN6RSxjQUFjLEVBQUUsR0FBRztJQUNuQixRQUFRLEVBQUUsRUFBRTtJQUNaLFFBQVEsRUFBRSxFQUFFO0NBQ2IsQ0FBQztBQUVGLDhFQUE4RTtBQUM5RSx5REFBeUQ7QUFDekQsTUFBTSxlQUFlLEdBQUc7SUFDdEIsVUFBVSxFQUFFLEdBQUc7SUFDZixZQUFZLEVBQUUsR0FBRztJQUNqQixjQUFjLEVBQUUsR0FBRztJQUNuQixRQUFRLEVBQUUsRUFBRTtJQUNaLFFBQVEsRUFBRSxFQUFFO0NBQ2IsQ0FBQztBQUVGLE1BQU0sQ0FBQyxNQUFNLFdBQVcsR0FBbUI7SUFDekM7UUFDRSxFQUFFLEVBQUUsd0JBQXdCO1FBQzVCLEtBQUssRUFBRSxhQUFhO1FBQ3BCLFFBQVEsRUFBRSxHQUFHLEVBQUUsNERBQTREO1FBQzNFLFFBQVEsRUFBRSxFQUFFLEdBQUcsWUFBWSxFQUFFO0tBQzlCO0lBQ0Q7UUFDRSxFQUFFLEVBQUUsb0JBQW9CO1FBQ3hCLEtBQUssRUFBRSxRQUFRO1FBQ2YsUUFBUSxFQUFFLEdBQUcsRUFBRSx3REFBd0Q7UUFDdkUsUUFBUSxFQUFFLEVBQUUsR0FBRyxlQUFlLEVBQUU7S0FDakM7SUFDRDtRQUNFLEVBQUUsRUFBRSx1QkFBdUI7UUFDM0IsS0FBSyxFQUFFLGFBQWE7UUFDcEIsUUFBUSxFQUFFLEdBQUcsRUFBRSwyREFBMkQ7UUFDMUUsUUFBUSxFQUFFLEVBQUUsR0FBRyxlQUFlLEVBQUU7S0FDakM7SUFDRDtRQUNFLEVBQUUsRUFBRSx1QkFBdUI7UUFDM0IsS0FBSyxFQUFFLGFBQWE7UUFDcEIsUUFBUSxFQUFFLEdBQUcsRUFBRSwyREFBMkQ7UUFDMUUsUUFBUSxFQUFFLEVBQUUsR0FBRyxlQUFlLEVBQUU7S0FDakM7Q0FDRixDQUFDO0FBRUYsTUFBTSxDQUFDLE1BQU0scUJBQXFCLEdBQUcsd0JBQXdCLENBQUM7QUFFOUQsSUFBSSxjQUFjLEdBQUcscUJBQXFCLENBQUM7QUFFM0MsNkVBQTZFO0FBQzdFLE1BQU0sVUFBVSxZQUFZLENBQUMsRUFBVTtJQUNyQyxPQUFPLFdBQVcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUM7QUFDOUMsQ0FBQztBQUVELDJEQUEyRDtBQUMzRCxNQUFNLFVBQVUsZ0JBQWdCLENBQUMsRUFBVTtJQUN6QyxNQUFNLENBQUMsR0FBRyxZQUFZLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDM0IsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQztBQUM3QyxDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSxnQkFBZ0IsQ0FBQyxFQUFVLEVBQUUsSUFBNkM7SUFDeEYsTUFBTSxDQUFDLEdBQUcsWUFBWSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQzNCLElBQUksQ0FBQyxDQUFDO1FBQUUsT0FBTztJQUNmLElBQUksSUFBSSxDQUFDLFFBQVEsS0FBSyxTQUFTO1FBQUUsQ0FBQyxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDO0lBQzVELElBQUksSUFBSSxDQUFDLE9BQU8sS0FBSyxTQUFTO1FBQUUsQ0FBQyxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDO0FBQzNELENBQUM7QUFFRCwrREFBK0Q7QUFDL0QsTUFBTSxVQUFVLG1CQUFtQixDQUFDLEVBQVU7SUFDNUMsSUFBSSxZQUFZLENBQUMsRUFBRSxDQUFDO1FBQUUsY0FBYyxHQUFHLEVBQUUsQ0FBQztBQUM1QyxDQUFDO0FBRUQsTUFBTSxVQUFVLHFCQUFxQjtJQUNuQyxPQUFPLGNBQWMsQ0FBQztBQUN4QixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBSZWdpc3RyeSBvZiBZT0xPdjgtc2VnIGluc3RhbmNlLXNlZ21lbnRhdGlvbiBtb2RlbHMuXG4gKlxuICogTWlycm9ycyB7QGxpbmsgU0FNX01PREVMU306IHB1YmxpYyBIRi1ob3N0ZWQgT05OWCBieSBkZWZhdWx0IHNvIHRoZSB0b29sIHdvcmtzXG4gKiBvdXQgb2YgdGhlIGJveCBpbiBhbnkgaG9zdCwgcmVwb2ludGFibGUgYXQgcHJpdmF0ZSBob3N0aW5nIHZpYVxuICoge0BsaW5rIHNldFlvbG9Nb2RlbFVybHN9LlxuICpcbiAqIEVhY2ggZW50cnkgY2FycmllcyB0aGUgcGVyLW1vZGVsICpkZWZhdWx0cyogdGhlIHNlcnZlci1zaWRlIHdvcmtlciB1c2VzIGZvclxuICogdGhhdCBjaGVja3BvaW50LiBUaGVzZSBhcmUgcHJvcGVydGllcyBvZiBob3cgdGhlIG1vZGVsIHdhcyB0cmFpbmVkIChvYmplY3RcbiAqIHNjYWxlLCBleHBlY3RlZCBjcm93ZGluZyksIG5vdCBVSSBwcmVmZXJlbmNlcyDigJQgdGhlIGVtYnJ5byBtb2RlbHMgd2FudCBoZWF2eVxuICogNjAlIHRpbGUgb3ZlcmxhcCBhbmQgbG9vc2UgbWVyZ2UgdGhyZXNob2xkcyBiZWNhdXNlIHRoZWlyIG9iamVjdHMgYXJlIGxhcmdlXG4gKiBhbmQgdG91Y2gsIHdoaWxlIHRoZSBleWUgbW9kZWxzIHdlcmUgdHJhaW5lZCBvbiBzcGFyc2UsIGRvd25zYW1wbGVkIGZpZWxkcy5cbiAqIEtlZXBpbmcgdGhlbSBoZXJlIG1lYW5zIGEgaG9zdCB0aGF0IGp1c3QgcGlja3MgYSBtb2RlbCBnZXRzIHNhbmUgYmVoYXZpb3VyXG4gKiB3aXRob3V0IGhhdmluZyB0byBrbm93IGFueSBvZiB0aGF0LlxuICpcbiAqIFdlaWdodHMgcG9pbnQgYXQgdGhlIGBtb2RlbC5mcDE2dy5vbm54YCB2YXJpYW50IGRlbGliZXJhdGVseS4gVGhlIGAuZnAxNmBcbiAqIHZhcmlhbnQgYWNjdW11bGF0ZXMgaW4gaGFsZiBwcmVjaXNpb24gb24gV2ViR1BVIGFuZCwgYXQgdGhlIHgtc2NhbGUgdGhlc2VcbiAqIG1vZGVscyB1c2UsIHByb2R1Y2VzIG1hdGVyaWFsbHkgd3JvbmcgYm94ZXM7IGBmcDE2d2Agc3RvcmVzIHdlaWdodHMgaW4gZnAxNlxuICogYW5kIGNvbXB1dGVzIGluIGZwMzIsIHNvIGl0IGlzIGhhbGYgdGhlIGRvd25sb2FkIGF0IGlkZW50aWNhbCBhY2N1cmFjeS5cbiAqXG4gKiBFeHBvcnQgdG9vbGluZzogdGhlIHNpYmxpbmcgYGJyb3dzZXItb25ueC10b29sc2AgcHJvamVjdC5cbiAqL1xuXG5leHBvcnQgaW50ZXJmYWNlIFlvbG9Nb2RlbERlZiB7XG4gIGlkOiBzdHJpbmc7XG4gIGxhYmVsOiBzdHJpbmc7XG4gIC8qKiBVUkwgb2YgdGhlIGAub25ueGAgd2VpZ2h0cy4gRW1wdHkgZGlzYWJsZXMgdGhlIG1vZGVsLiAqL1xuICBtb2RlbFVybDogc3RyaW5nO1xuICAvKipcbiAgICogVVJMIG9mIHRoZSBgbW9kZWwuanNvbmAgc2lkZWNhci4gT21pdCB0byB1c2UgYG1vZGVsLmpzb25gIGFsb25nc2lkZSB0aGVcbiAgICogd2VpZ2h0cywgd2hpY2ggaXMgaG93IGV2ZXJ5IHB1Ymxpc2hlZCByZXBvIGlzIGxhaWQgb3V0LlxuICAgKi9cbiAgbWV0YVVybD86IHN0cmluZztcbiAgLyoqIFBlci1tb2RlbCBkZWZhdWx0cywgZnJvbSB0aGUgc2VydmVyIHdvcmtlcidzIG1vZGVsIHRhYmxlLiAqL1xuICBkZWZhdWx0czoge1xuICAgIGNvbmZpZGVuY2U6IG51bWJlcjtcbiAgICBpb3VUaHJlc2hvbGQ6IG51bWJlcjtcbiAgICBtZXJnZVRocmVzaG9sZDogbnVtYmVyO1xuICAgIG92ZXJsYXBYOiBudW1iZXI7XG4gICAgb3ZlcmxhcFk6IG51bWJlcjtcbiAgfTtcbn1cblxuY29uc3QgSEYgPSAnaHR0cHM6Ly9odWdnaW5nZmFjZS5jby9CYWxsb245OTknO1xuXG5jb25zdCBFWUVfREVGQVVMVFMgPSB7XG4gIGNvbmZpZGVuY2U6IDAuNixcbiAgaW91VGhyZXNob2xkOiAwLjUsXG4gIG1lcmdlVGhyZXNob2xkOiAwLjMsXG4gIG92ZXJsYXBYOiAwLFxuICBvdmVybGFwWTogMCxcbn07XG5cbi8qKlxuICogVGhlIHJldGluYSBpcyBhbiBlbG9uZ2F0ZWQgYmFuZCBzcGFubmluZyB0aGUgd2hvbGUgZmllbGQsIHNvIGl0IGNyb3NzZXMgdGlsZVxuICogc2VhbXMgd2hlcmV2ZXIgdGhleSBmYWxsLlxuICpcbiAqIFdpdGggbm8gb3ZlcmxhcCB0aGUgdGlsZXMgYnV0dCB0b2dldGhlciBhdCBmaXhlZCBib3VuZGFyaWVzOiBhIHN0cnVjdHVyZVxuICogc2l0dGluZyBhY3Jvc3MgYSBzZWFtIGlzIGN1dCBpbiBoYWxmLCBlYWNoIHRpbGUgc2VlcyBvbmx5IGEgc2xpY2UsIGFuZCB0aGVcbiAqIHNsaWNlIG9mdGVuIGZhaWxzIHRvIGNsZWFyIHRoZSBjb25maWRlbmNlIHRocmVzaG9sZCDigJQgc28gdGhlIG9iamVjdCB2YW5pc2hlc1xuICogZXhhY3RseSB3aGVyZSB0aGUgc2VhbSBsYW5kcy4gT2JzZXJ2ZWQgYXMgbXVsdGktdGhvdXNhbmQtcGl4ZWwgaG9sZXMgaW4gdGhlXG4gKiBtaWRkbGUgb2YgYW4gb3RoZXJ3aXNlIGNvbnRpbnVvdXMgcmV0aW5hLCB3aGljaCBjbG9zZWQgb25jZSBvdmVybGFwIHdhc1xuICogcmFpc2VkLlxuICpcbiAqIEFuIG9iamVjdCBvZiBzaXplIGBzYCBpcyBndWFyYW50ZWVkIHRvIHNpdCB3aG9sbHkgaW5zaWRlIHNvbWUgdGlsZSBvbmx5IHdoZW5cbiAqIHRoZSBvdmVybGFwIGlzIGF0IGxlYXN0IGBzIC8gdGlsZVNpemVgOyBhdCA2MCUgdGhhdCBjb3ZlcnMgYW55dGhpbmcgdXAgdG9cbiAqIH4zMDdweCBvZiBhIDUxMnB4IHRpbGUuIFRoZSBzZXJ2ZXIncyBvd24gdGFibGUgdXNlcyAwIGhlcmUgYW5kIGhhcyB0aGUgc2FtZVxuICogYmxpbmQgc3BvdCDigJQgdGhpcyBkZWxpYmVyYXRlbHkgZGl2ZXJnZXMgZnJvbSBpdC5cbiAqL1xuY29uc3QgUkVUSU5BX0RFRkFVTFRTID0ge1xuICBjb25maWRlbmNlOiAwLjYsXG4gIGlvdVRocmVzaG9sZDogMC41LFxuICAvLyBDcm9zcy10aWxlIG1lcmdpbmcgdXNlcyBpbnRlcnNlY3Rpb24tb3Zlci0qc21hbGxlciosIHNvIGF0IDAuMyBhIGZyYWdtZW50IGlzXG4gIC8vIGRpc2NhcmRlZCBvbmNlIDMwJSBvZiBpdHMgb3duIGFyZWEgaXMgY292ZXJlZCBieSBhIGxhcmdlciBib3gg4oCUIGV2ZW4gd2hlblxuICAvLyBtb3N0IG9mIGl0IGxpZXMgb3V0c2lkZS4gT24gYSBiYW5kIGRldGVjdGVkIGFzIGEgY2hhaW4gb2Ygb3ZlcmxhcHBpbmdcbiAgLy8gZnJhZ21lbnRzIHRoYXQgZGVsZXRlcyB0aGUgbWlkZGxlIG9uZXMuIE1lYXN1cmVkIG9uIG9uZSBzbGlkZTogMC4zIGdhdmUgdHdvXG4gIC8vIGJveGVzIGNvdmVyaW5nIDc1JSBvZiB0aGUgYmFuZCB3aXRoIGEgMzE1MHB4IGhvbGUgdGhyb3VnaCBpdDsgMC44IGdhdmUgZm91clxuICAvLyBib3hlcyBjb3ZlcmluZyAxMDAlIHdpdGggbm8gaG9sZSwgYXQgdGhlIGNvc3Qgb2YgdHdvIG92ZXJsYXBwaW5nIHBhaXJzLlxuICAvLyBNYXRjaGVzIHRoZSB2YWx1ZSBqaXQtdWkgYWxyZWFkeSBzZW5kcyB0aGUgc2VydmVyIGZvciB0aGlzIGNoZWNrcG9pbnQuXG4gIG1lcmdlVGhyZXNob2xkOiAwLjgsXG4gIG92ZXJsYXBYOiA2MCxcbiAgb3ZlcmxhcFk6IDYwLFxufTtcblxuLy8gRW1icnlvcyBhcmUgbGFyZ2UsIHRvdWNoIGVhY2ggb3RoZXIsIGFuZCBhcmUgaW1hZ2VkIGF0IG5hdGl2ZSBzY2FsZSDigJQgaGVuY2Vcbi8vIHRoZSBoZWF2eSBvdmVybGFwIGFuZCB0aGUgZmFyIGxvb3NlciBtZXJnZSB0aHJlc2hvbGRzLlxuY29uc3QgRU1CUllPX0RFRkFVTFRTID0ge1xuICBjb25maWRlbmNlOiAwLjYsXG4gIGlvdVRocmVzaG9sZDogMC44LFxuICBtZXJnZVRocmVzaG9sZDogMC44LFxuICBvdmVybGFwWDogNjAsXG4gIG92ZXJsYXBZOiA2MCxcbn07XG5cbmV4cG9ydCBjb25zdCBZT0xPX01PREVMUzogWW9sb01vZGVsRGVmW10gPSBbXG4gIHtcbiAgICBpZDogJ3lvbG92OHgtc2VnLW9wdGljbmVydmUnLFxuICAgIGxhYmVsOiAnT3B0aWMgbmVydmUnLFxuICAgIG1vZGVsVXJsOiBgJHtIRn0veW9sb3Y4eC1zZWctb3B0aWNuZXJ2ZS1vbm54L3Jlc29sdmUvbWFpbi9tb2RlbC5mcDE2dy5vbm54YCxcbiAgICBkZWZhdWx0czogeyAuLi5FWUVfREVGQVVMVFMgfSxcbiAgfSxcbiAge1xuICAgIGlkOiAneW9sb3Y4eC1zZWctcmV0aW5hJyxcbiAgICBsYWJlbDogJ1JldGluYScsXG4gICAgbW9kZWxVcmw6IGAke0hGfS95b2xvdjh4LXNlZy1yZXRpbmEtb25ueC9yZXNvbHZlL21haW4vbW9kZWwuZnAxNncub25ueGAsXG4gICAgZGVmYXVsdHM6IHsgLi4uUkVUSU5BX0RFRkFVTFRTIH0sXG4gIH0sXG4gIHtcbiAgICBpZDogJ3lvbG92OHgtc2VnLWVtYnJ5by1tMicsXG4gICAgbGFiZWw6ICdFbWJyeW8gKE0yKScsXG4gICAgbW9kZWxVcmw6IGAke0hGfS95b2xvdjh4LXNlZy1lbWJyeW8tbTItb25ueC9yZXNvbHZlL21haW4vbW9kZWwuZnAxNncub25ueGAsXG4gICAgZGVmYXVsdHM6IHsgLi4uRU1CUllPX0RFRkFVTFRTIH0sXG4gIH0sXG4gIHtcbiAgICBpZDogJ3lvbG92OHgtc2VnLWVtYnJ5by1tMycsXG4gICAgbGFiZWw6ICdFbWJyeW8gKE0zKScsXG4gICAgbW9kZWxVcmw6IGAke0hGfS95b2xvdjh4LXNlZy1lbWJyeW8tbTMtb25ueC9yZXNvbHZlL21haW4vbW9kZWwuZnAxNncub25ueGAsXG4gICAgZGVmYXVsdHM6IHsgLi4uRU1CUllPX0RFRkFVTFRTIH0sXG4gIH0sXG5dO1xuXG5leHBvcnQgY29uc3QgREVGQVVMVF9ZT0xPX01PREVMX0lEID0gJ3lvbG92OHgtc2VnLW9wdGljbmVydmUnO1xuXG5sZXQgZGVmYXVsdE1vZGVsSWQgPSBERUZBVUxUX1lPTE9fTU9ERUxfSUQ7XG5cbi8qKiBMb29rIHVwIGEgbW9kZWwgZGVmaW5pdGlvbiBieSBpZC4gUmV0dXJucyB1bmRlZmluZWQgZm9yIGFuIHVua25vd24gaWQuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0WW9sb01vZGVsKGlkOiBzdHJpbmcpOiBZb2xvTW9kZWxEZWYgfCB1bmRlZmluZWQge1xuICByZXR1cm4gWU9MT19NT0RFTFMuZmluZCgobSkgPT4gbS5pZCA9PT0gaWQpO1xufVxuXG4vKiogV2hldGhlciBhIG1vZGVsIGhhcyBhIHVzYWJsZSB3ZWlnaHRzIFVSTCBjb25maWd1cmVkLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlzWW9sb01vZGVsUmVhZHkoaWQ6IHN0cmluZyk6IGJvb2xlYW4ge1xuICBjb25zdCBtID0gZ2V0WW9sb01vZGVsKGlkKTtcbiAgcmV0dXJuICEhbSAmJiBtLm1vZGVsVXJsLnRyaW0oKS5sZW5ndGggPiAwO1xufVxuXG4vKipcbiAqIFJlcG9pbnQgYSBtb2RlbCBhdCBkaWZmZXJlbnQgaG9zdGluZyAoZS5nLiBhIEdDUyBidWNrZXQgb3IgYSBzYW1lLW9yaWdpblxuICogcHJveHkpLiBDYWxsIG9uY2UgYXQgYXBwIGluaXQsIGJlZm9yZSB0aGUgZmlyc3QgcnVuLiBBbiBlbXB0eSBgbW9kZWxVcmxgXG4gKiBkaXNhYmxlcyB0aGUgbW9kZWwuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZXRZb2xvTW9kZWxVcmxzKGlkOiBzdHJpbmcsIHVybHM6IHsgbW9kZWxVcmw/OiBzdHJpbmc7IG1ldGFVcmw/OiBzdHJpbmcgfSk6IHZvaWQge1xuICBjb25zdCBtID0gZ2V0WW9sb01vZGVsKGlkKTtcbiAgaWYgKCFtKSByZXR1cm47XG4gIGlmICh1cmxzLm1vZGVsVXJsICE9PSB1bmRlZmluZWQpIG0ubW9kZWxVcmwgPSB1cmxzLm1vZGVsVXJsO1xuICBpZiAodXJscy5tZXRhVXJsICE9PSB1bmRlZmluZWQpIG0ubWV0YVVybCA9IHVybHMubWV0YVVybDtcbn1cblxuLyoqIENoYW5nZSB3aGljaCBtb2RlbCBydW5zIHdoZW4gYSBjYWxsZXIgZG9lcyBub3QgbmFtZSBvbmUuICovXG5leHBvcnQgZnVuY3Rpb24gc2V0RGVmYXVsdFlvbG9Nb2RlbChpZDogc3RyaW5nKTogdm9pZCB7XG4gIGlmIChnZXRZb2xvTW9kZWwoaWQpKSBkZWZhdWx0TW9kZWxJZCA9IGlkO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RGVmYXVsdFlvbG9Nb2RlbElkKCk6IHN0cmluZyB7XG4gIHJldHVybiBkZWZhdWx0TW9kZWxJZDtcbn1cbiJdfQ==
|