@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.
Files changed (35) hide show
  1. package/esm2022/index.mjs +2 -9
  2. package/esm2022/lib/contracts/instance-segmenter.contract.mjs +10 -10
  3. package/esm2022/lib/contracts/semantic-segmenter.contract.mjs +10 -9
  4. package/esm2022/lib/contracts/toolbar-tool.contract.mjs +64 -0
  5. package/esm2022/lib/toolbar/model-info.mjs +32 -0
  6. package/esm2022/lib/toolbar/toolbar.component.mjs +82 -55
  7. package/esm2022/lib/visualizer.component.mjs +131 -138
  8. 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
  9. 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
  10. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +710 -1565
  11. package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
  12. package/index.d.ts +1 -6
  13. package/lib/contracts/instance-segmenter.contract.d.ts +7 -3
  14. package/lib/contracts/semantic-segmenter.contract.d.ts +7 -2
  15. package/lib/contracts/toolbar-tool.contract.d.ts +190 -0
  16. package/lib/toolbar/model-info.d.ts +17 -0
  17. package/lib/toolbar/toolbar.component.d.ts +53 -30
  18. package/lib/visualizer.component.d.ts +49 -58
  19. package/package.json +2 -4
  20. package/src/lib/assets/cell-detection.svg +37 -0
  21. package/src/lib/assets/cells.svg +1 -1
  22. package/src/lib/assets/sam-box-prompt.svg +61 -0
  23. package/src/lib/assets/sam-point-prompt.svg +91 -0
  24. package/esm2022/lib/toolbar/segmentation/retinal-layer-tool.service.mjs +0 -197
  25. package/esm2022/lib/toolbar/segmentation/retinal-model-registry.mjs +0 -119
  26. package/esm2022/lib/toolbar/segmentation/retinal-segmenter.service.mjs +0 -172
  27. package/esm2022/lib/toolbar/segmentation/yolo-detect-tool.service.mjs +0 -208
  28. package/esm2022/lib/toolbar/segmentation/yolo-model-registry.mjs +0 -129
  29. package/esm2022/lib/toolbar/segmentation/yolo-segmenter.service.mjs +0 -172
  30. package/lib/toolbar/segmentation/retinal-layer-tool.service.d.ts +0 -49
  31. package/lib/toolbar/segmentation/retinal-model-registry.d.ts +0 -71
  32. package/lib/toolbar/segmentation/retinal-segmenter.service.d.ts +0 -60
  33. package/lib/toolbar/segmentation/yolo-detect-tool.service.d.ts +0 -51
  34. package/lib/toolbar/segmentation/yolo-model-registry.d.ts +0 -59
  35. 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==