@jax-data-science/sci-image-visualizer 0.2.19 → 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 { getDefaultYoloModelId, getYoloModel } from './yolo-model-registry';
|
|
4
|
-
import * as i0 from "@angular/core";
|
|
5
|
-
/**
|
|
6
|
-
* Default in-library {@link IInstanceSegmenter}, backed by yolo-segdetect-js
|
|
7
|
-
* (YOLOv8-seg on WebGPU, falling back to WASM).
|
|
8
|
-
*
|
|
9
|
-
* `yolo-segdetect-js` is a regular dependency but is **lazy-imported** on first
|
|
10
|
-
* use, so apps that never run YOLO pay nothing for it — or for onnxruntime-web —
|
|
11
|
-
* in their initial bundle. This mirrors {@link CellposeSegmenterService}.
|
|
12
|
-
*
|
|
13
|
-
* The one structural difference from the Cellpose service: there are four YOLO
|
|
14
|
-
* checkpoints, not one, so instances are cached **per model id** rather than as
|
|
15
|
-
* a single singleton. Each entry owns its own worker and ORT session, so a host
|
|
16
|
-
* that switches models pays a second load but keeps the first one warm. Nothing
|
|
17
|
-
* evicts automatically — these are ~144 MB each, so a host that cycles through
|
|
18
|
-
* all four should call {@link dispose} for the ones it is done with.
|
|
19
|
-
*/
|
|
20
|
-
export class YoloSegmenterService {
|
|
21
|
-
constructor() {
|
|
22
|
-
this.instances = new Map();
|
|
23
|
-
this.loading = new Map();
|
|
24
|
-
}
|
|
25
|
-
async segmentInstances(image, opts = {}, progress) {
|
|
26
|
-
const modelId = opts.modelId ?? getDefaultYoloModelId();
|
|
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
|
-
progress?.onStatus?.(`Downloading ${def.label} model…`);
|
|
36
|
-
}
|
|
37
|
-
}, (status) => progress?.onStatus?.(status));
|
|
38
|
-
progress?.onStatus?.('Running inference…');
|
|
39
|
-
const result = await seg.segment({ data: image.data, width: image.width, height: image.height }, {
|
|
40
|
-
confThreshold: opts.confidence ?? def.defaults.confidence,
|
|
41
|
-
iouThreshold: opts.iouThreshold ?? def.defaults.iouThreshold,
|
|
42
|
-
threshold: opts.mergeThreshold ?? def.defaults.mergeThreshold,
|
|
43
|
-
overlapX: opts.overlapX ?? def.defaults.overlapX,
|
|
44
|
-
overlapY: opts.overlapY ?? def.defaults.overlapY,
|
|
45
|
-
...(opts.maskThreshold !== undefined ? { maskThreshold: opts.maskThreshold } : {}),
|
|
46
|
-
...(opts.maxDetections !== undefined ? { maxDetections: opts.maxDetections } : {}),
|
|
47
|
-
...(opts.classFilter ? { classFilter: opts.classFilter } : {}),
|
|
48
|
-
...(opts.withMasks !== undefined ? { withMasks: opts.withMasks } : {}),
|
|
49
|
-
...(opts.signal ? { signal: opts.signal } : {}),
|
|
50
|
-
// Only worth tracing when there are masks to trace.
|
|
51
|
-
tracePolygons: opts.withMasks !== false,
|
|
52
|
-
traceOptions: {
|
|
53
|
-
...(opts.simplifyTolerance !== undefined ? { simplifyTolerance: opts.simplifyTolerance } : {}),
|
|
54
|
-
...(opts.minArea !== undefined ? { minArea: opts.minArea } : {}),
|
|
55
|
-
},
|
|
56
|
-
// The worker narrates its own phases — shader warm-up, per tile, merge,
|
|
57
|
-
// tracing — because only it knows which one it is in. Deriving a second
|
|
58
|
-
// status from tile counts here would just fight it.
|
|
59
|
-
onStatus: (s) => progress?.onStatus?.(s),
|
|
60
|
-
});
|
|
61
|
-
// A detection without an outline is still a detection.
|
|
62
|
-
//
|
|
63
|
-
// These used to be dropped, which quietly emptied the entire result in
|
|
64
|
-
// detection mode: no masks means no polygons, so every object was
|
|
65
|
-
// discarded and the caller saw nothing at all. The server does not work
|
|
66
|
-
// that way either — it always writes a box feature and only *adds*
|
|
67
|
-
// segmentation outlines when asked. Callers get the box and decide what to
|
|
68
|
-
// do with it.
|
|
69
|
-
const detections = result.detections.map((d) => ({
|
|
70
|
-
polygons: (d.polygons ?? []).map((p) => ({
|
|
71
|
-
exterior: p.exterior.map(([x, y]) => [x, y]),
|
|
72
|
-
holes: p.holes.map((h) => h.map(([x, y]) => [x, y])),
|
|
73
|
-
})),
|
|
74
|
-
box: [d.box[0], d.box[1], d.box[2], d.box[3]],
|
|
75
|
-
score: d.score,
|
|
76
|
-
classId: d.classId,
|
|
77
|
-
className: d.className ?? `class${d.classId}`,
|
|
78
|
-
}));
|
|
79
|
-
return {
|
|
80
|
-
detections,
|
|
81
|
-
width: result.width,
|
|
82
|
-
height: result.height,
|
|
83
|
-
classNames: result.classNames,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* The shared segmenter for a model id, created on first use and reused
|
|
88
|
-
* thereafter (deduped across concurrent callers). Exposed so a host's
|
|
89
|
-
* processing pipeline can drive richer `segment()` options on the same loaded
|
|
90
|
-
* session instead of loading the model twice.
|
|
91
|
-
*/
|
|
92
|
-
getModel(modelId = getDefaultYoloModelId(), onProgress, onStatus) {
|
|
93
|
-
const existing = this.instances.get(modelId);
|
|
94
|
-
if (existing)
|
|
95
|
-
return Promise.resolve(existing);
|
|
96
|
-
const inFlight = this.loading.get(modelId);
|
|
97
|
-
if (inFlight)
|
|
98
|
-
return inFlight;
|
|
99
|
-
const def = this.requireModel(modelId);
|
|
100
|
-
const load = (async () => {
|
|
101
|
-
// Lazy: keep yolo-segdetect-js + its ORT runtime out of the initial bundle.
|
|
102
|
-
const { YoloSegmenter, configureOrt } = await import('yolo-segdetect-js');
|
|
103
|
-
configureOrt({ wasmPaths: getOrtWasmBase() });
|
|
104
|
-
const seg = await YoloSegmenter.fromPretrained(def.modelUrl, {
|
|
105
|
-
preload: true,
|
|
106
|
-
...(def.metaUrl ? { metaUrl: def.metaUrl } : {}),
|
|
107
|
-
onProgress: ({ loaded, total }) => onProgress?.(loaded, total),
|
|
108
|
-
onStatus: (s) => onStatus?.(s),
|
|
109
|
-
});
|
|
110
|
-
this.instances.set(modelId, seg);
|
|
111
|
-
return seg;
|
|
112
|
-
})();
|
|
113
|
-
this.loading.set(modelId, load);
|
|
114
|
-
// A failed load must not poison the cache — the next caller should retry
|
|
115
|
-
// rather than inherit the rejected promise forever.
|
|
116
|
-
load.catch(() => this.loading.delete(modelId));
|
|
117
|
-
return load;
|
|
118
|
-
}
|
|
119
|
-
/** Whether a model is already loaded (warm). */
|
|
120
|
-
isLoaded(modelId = getDefaultYoloModelId()) {
|
|
121
|
-
return this.instances.has(modelId);
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Release one model, or all of them. Safe to call on a model never loaded.
|
|
125
|
-
*
|
|
126
|
-
* In-flight loads are torn down too, not just warm ones. Simply dropping the
|
|
127
|
-
* pending promise and returning would let the load finish afterwards,
|
|
128
|
-
* re-populate {@link instances}, and strand a live worker and ORT session that
|
|
129
|
-
* nothing owns — disposal would have reported success while leaking. So a load
|
|
130
|
-
* that is still running is awaited and then released.
|
|
131
|
-
*/
|
|
132
|
-
async dispose(modelId) {
|
|
133
|
-
const ids = modelId ? [modelId] : [...new Set([...this.instances.keys(), ...this.loading.keys()])];
|
|
134
|
-
await Promise.all(ids.map((id) => this.disposeOne(id)));
|
|
135
|
-
}
|
|
136
|
-
async disposeOne(id) {
|
|
137
|
-
const pending = this.loading.get(id);
|
|
138
|
-
const loaded = this.instances.get(id);
|
|
139
|
-
this.instances.delete(id);
|
|
140
|
-
this.loading.delete(id);
|
|
141
|
-
if (loaded)
|
|
142
|
-
await loaded.dispose();
|
|
143
|
-
if (!pending)
|
|
144
|
-
return;
|
|
145
|
-
// A pending load assigns instances[id] before its promise resolves, so by
|
|
146
|
-
// the time this await returns the entry is back. Clear it only if it still
|
|
147
|
-
// points at what *this* load produced — a newer load started in the
|
|
148
|
-
// meantime owns its own entry and must not be evicted.
|
|
149
|
-
const seg = await pending.catch(() => null);
|
|
150
|
-
if (!seg || seg === loaded)
|
|
151
|
-
return;
|
|
152
|
-
if (this.instances.get(id) === seg)
|
|
153
|
-
this.instances.delete(id);
|
|
154
|
-
await seg.dispose();
|
|
155
|
-
}
|
|
156
|
-
requireModel(modelId) {
|
|
157
|
-
const def = getYoloModel(modelId);
|
|
158
|
-
if (!def)
|
|
159
|
-
throw new Error(`Unknown YOLO model id "${modelId}".`);
|
|
160
|
-
if (!def.modelUrl.trim()) {
|
|
161
|
-
throw new Error(`YOLO model "${modelId}" is not configured — its modelUrl is empty.`);
|
|
162
|
-
}
|
|
163
|
-
return def;
|
|
164
|
-
}
|
|
165
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: YoloSegmenterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
166
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: YoloSegmenterService, providedIn: 'root' }); }
|
|
167
|
-
}
|
|
168
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: YoloSegmenterService, decorators: [{
|
|
169
|
-
type: Injectable,
|
|
170
|
-
args: [{ providedIn: 'root' }]
|
|
171
|
-
}] });
|
|
172
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoieW9sby1zZWdtZW50ZXIuc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9saWIvdG9vbGJhci9zZWdtZW50YXRpb24veW9sby1zZWdtZW50ZXIuc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQzNDLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUN0RCxPQUFPLEVBQUUscUJBQXFCLEVBQUUsWUFBWSxFQUFxQixNQUFNLHVCQUF1QixDQUFDOztBQVcvRjs7Ozs7Ozs7Ozs7Ozs7R0FjRztBQUVILE1BQU0sT0FBTyxvQkFBb0I7SUFEakM7UUFFbUIsY0FBUyxHQUFHLElBQUksR0FBRyxFQUF5QixDQUFDO1FBQzdDLFlBQU8sR0FBRyxJQUFJLEdBQUcsRUFBa0MsQ0FBQztLQW1LdEU7SUFqS0MsS0FBSyxDQUFDLGdCQUFnQixDQUNwQixLQUFpRSxFQUNqRSxPQUErQixFQUFFLEVBQ2pDLFFBQWtDO1FBRWxDLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxPQUFPLElBQUkscUJBQXFCLEVBQUUsQ0FBQztRQUN4RCxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBRXZDLElBQUksaUJBQWlCLEdBQUcsS0FBSyxDQUFDO1FBQzlCLE1BQU0sR0FBRyxHQUFHLE1BQU0sSUFBSSxDQUFDLFFBQVEsQ0FDN0IsT0FBTyxFQUNQLENBQUMsTUFBTSxFQUFFLEtBQUssRUFBRSxFQUFFO1lBQ2hCLElBQUksS0FBSztnQkFBRSxRQUFRLEVBQUUsVUFBVSxFQUFFLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQyxDQUFDO1lBQ2xELFFBQVEsRUFBRSxPQUFPLEVBQUUsQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUM7WUFDbkMsSUFBSSxNQUFNLEdBQUcsQ0FBQyxJQUFJ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QztRQUNELE9BQU8sR0FBRyxDQUFDO0lBQ2IsQ0FBQzs4R0FwS1Usb0JBQW9CO2tIQUFwQixvQkFBb0IsY0FEUCxNQUFNOzsyRkFDbkIsb0JBQW9CO2tCQURoQyxVQUFVO21CQUFDLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGFibGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IGdldE9ydFdhc21CYXNlIH0gZnJvbSAnLi9vcnQtcnVudGltZS1jb25maWcnO1xuaW1wb3J0IHsgZ2V0RGVmYXVsdFlvbG9Nb2RlbElkLCBnZXRZb2xvTW9kZWwsIHR5cGUgWW9sb01vZGVsRGVmIH0gZnJvbSAnLi95b2xvLW1vZGVsLXJlZ2lzdHJ5JztcblxuaW1wb3J0IHR5cGUge1xuICBJSW5zdGFuY2VTZWdtZW50ZXIsXG4gIEluc3RhbmNlRGV0ZWN0aW9uLFxuICBJbnN0YW5jZVNlZ21lbnRhdGlvbixcbiAgSW5zdGFuY2VTZWdtZW50T3B0aW9ucyxcbiAgSW5zdGFuY2VTZWdtZW50UHJvZ3Jlc3MsXG59IGZyb20gJy4uLy4uL2NvbnRyYWN0cy9pbnN0YW5jZS1zZWdtZW50ZXIuY29udHJhY3QnO1xuaW1wb3J0IHR5cGUgeyBZb2xvU2VnbWVudGVyIH0gZnJvbSAneW9sby1zZWdkZXRlY3QtanMnO1xuXG4vKipcbiAqIERlZmF1bHQgaW4tbGlicmFyeSB7QGxpbmsgSUluc3RhbmNlU2VnbWVudGVyfSwgYmFja2VkIGJ5IHlvbG8tc2VnZGV0ZWN0LWpzXG4gKiAoWU9MT3Y4LXNlZyBvbiBXZWJHUFUsIGZhbGxpbmcgYmFjayB0byBXQVNNKS5cbiAqXG4gKiBgeW9sby1zZWdkZXRlY3QtanNgIGlzIGEgcmVndWxhciBkZXBlbmRlbmN5IGJ1dCBpcyAqKmxhenktaW1wb3J0ZWQqKiBvbiBmaXJzdFxuICogdXNlLCBzbyBhcHBzIHRoYXQgbmV2ZXIgcnVuIFlPTE8gcGF5IG5vdGhpbmcgZm9yIGl0IOKAlCBvciBmb3Igb25ueHJ1bnRpbWUtd2ViIOKAlFxuICogaW4gdGhlaXIgaW5pdGlhbCBidW5kbGUuIFRoaXMgbWlycm9ycyB7QGxpbmsgQ2VsbHBvc2VTZWdtZW50ZXJTZXJ2aWNlfS5cbiAqXG4gKiBUaGUgb25lIHN0cnVjdHVyYWwgZGlmZmVyZW5jZSBmcm9tIHRoZSBDZWxscG9zZSBzZXJ2aWNlOiB0aGVyZSBhcmUgZm91ciBZT0xPXG4gKiBjaGVja3BvaW50cywgbm90IG9uZSwgc28gaW5zdGFuY2VzIGFyZSBjYWNoZWQgKipwZXIgbW9kZWwgaWQqKiByYXRoZXIgdGhhbiBhc1xuICogYSBzaW5nbGUgc2luZ2xldG9uLiBFYWNoIGVudHJ5IG93bnMgaXRzIG93biB3b3JrZXIgYW5kIE9SVCBzZXNzaW9uLCBzbyBhIGhvc3RcbiAqIHRoYXQgc3dpdGNoZXMgbW9kZWxzIHBheXMgYSBzZWNvbmQgbG9hZCBidXQga2VlcHMgdGhlIGZpcnN0IG9uZSB3YXJtLiBOb3RoaW5nXG4gKiBldmljdHMgYXV0b21hdGljYWxseSDigJQgdGhlc2UgYXJlIH4xNDQgTUIgZWFjaCwgc28gYSBob3N0IHRoYXQgY3ljbGVzIHRocm91Z2hcbiAqIGFsbCBmb3VyIHNob3VsZCBjYWxsIHtAbGluayBkaXNwb3NlfSBmb3IgdGhlIG9uZXMgaXQgaXMgZG9uZSB3aXRoLlxuICovXG5ASW5qZWN0YWJsZSh7IHByb3ZpZGVkSW46ICdyb290JyB9KVxuZXhwb3J0IGNsYXNzIFlvbG9TZWdtZW50ZXJTZXJ2aWNlIGltcGxlbWVudHMgSUluc3RhbmNlU2VnbWVudGVyIHtcbiAgcHJpdmF0ZSByZWFkb25seSBpbnN0YW5jZXMgPSBuZXcgTWFwPHN0cmluZywgWW9sb1NlZ21lbnRlcj4oKTtcbiAgcHJpdmF0ZSByZWFkb25seSBsb2FkaW5nID0gbmV3IE1hcDxzdHJpbmcsIFByb21pc2U8WW9sb1NlZ21lbnRlcj4+KCk7XG5cbiAgYXN5bmMgc2VnbWVudEluc3RhbmNlcyhcbiAgICBpbWFnZTogeyBkYXRhOiBVaW50OENsYW1wZWRBcnJheTsgd2lkdGg6IG51bWJlcjsgaGVpZ2h0OiBudW1iZXIgfSxcbiAgICBvcHRzOiBJbnN0YW5jZVNlZ21lbnRPcHRpb25zID0ge30sXG4gICAgcHJvZ3Jlc3M/OiBJbnN0YW5jZVNlZ21lbnRQcm9ncmVzcyxcbiAgKTogUHJvbWlzZTxJbnN0YW5jZVNlZ21lbnRhdGlvbj4ge1xuICAgIGNvbnN0IG1vZGVsSWQgPSBvcHRzLm1vZGVsSWQgPz8gZ2V0RGVmYXVsdFlvbG9Nb2RlbElkKCk7XG4gICAgY29uc3QgZGVmID0gdGhpcy5yZXF1aXJlTW9kZWwobW9kZWxJZCk7XG5cbiAgICBsZXQgYW5ub3VuY2VkRG93bmxvYWQgPSBmYWxzZTtcbiAgICBjb25zdCBzZWcgPSBhd2FpdCB0aGlzLmdldE1vZGVsKFxuICAgICAgbW9kZWxJZCxcbiAgICAgIChsb2FkZWQsIHRvdGFsKSA9PiB7XG4gICAgICAgIGlmICh0b3RhbCkgcHJvZ3Jlc3M/Lm9uUHJvZ3Jlc3M/Lihsb2FkZWQgLyB0b3RhbCk7XG4gICAgICAgIHByb2dyZXNzPy5vbkJ5dGVzPy4obG9hZGVkLCB0b3RhbCk7XG4gICAgICAgIGlmIChsb2FkZWQgPiAwICYmICFhbm5vdW5jZWREb3dubG9hZCkge1xuICAgICAgICAgIGFubm91bmNlZERvd25sb2FkID0gdHJ1ZTtcbiAgICAgICAgICBwcm9ncmVzcz8ub25TdGF0dXM/LihgRG93bmxvYWRpbmcgJHtkZWYubGFiZWx9IG1vZGVs4oCmYCk7XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICAoc3RhdHVzKSA9PiBwcm9ncmVzcz8ub25TdGF0dXM/LihzdGF0dXMpLFxuICAgICk7XG5cbiAgICBwcm9ncmVzcz8ub25TdGF0dXM/LignUnVubmluZyBpbmZlcmVuY2XigKYnKTtcbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBzZWcuc2VnbWVudChcbiAgICAgIHsgZGF0YTogaW1hZ2UuZGF0YSwgd2lkdGg6IGltYWdlLndpZHRoLCBoZWlnaHQ6IGltYWdlLmhlaWdodCB9LFxuICAgICAge1xuICAgICAgICBjb25mVGhyZXNob2xkOiBvcHRzLmNvbmZpZGVuY2UgPz8gZGVmLmRlZmF1bHRzLmNvbmZpZGVuY2UsXG4gICAgICAgIGlvdVRocmVzaG9sZDogb3B0cy5pb3VUaHJlc2hvbGQgPz8gZGVmLmRlZmF1bHRzLmlvdVRocmVzaG9sZCxcbiAgICAgICAgdGhyZXNob2xkOiBvcHRzLm1lcmdlVGhyZXNob2xkID8/IGRlZi5kZWZhdWx0cy5tZXJnZVRocmVzaG9sZCxcbiAgICAgICAgb3ZlcmxhcFg6IG9wdHMub3ZlcmxhcFggPz8gZGVmLmRlZmF1bHRzLm92ZXJsYXBYLFxuICAgICAgICBvdmVybGFwWTogb3B0cy5vdmVybGFwWSA/PyBkZWYuZGVmYXVsdHMub3ZlcmxhcFksXG4gICAgICAgIC4uLihvcHRzLm1hc2tUaHJlc2hvbGQgIT09IHVuZGVmaW5lZCA/IHsgbWFza1RocmVzaG9sZDogb3B0cy5tYXNrVGhyZXNob2xkIH0gOiB7fSksXG4gICAgICAgIC4uLihvcHRzLm1heERldGVjdGlvbnMgIT09IHVuZGVmaW5lZCA/IHsgbWF4RGV0ZWN0aW9uczogb3B0cy5tYXhEZXRlY3Rpb25zIH0gOiB7fSksXG4gICAgICAgIC4uLihvcHRzLmNsYXNzRmlsdGVyID8geyBjbGFzc0ZpbHRlcjogb3B0cy5jbGFzc0ZpbHRlciB9IDoge30pLFxuICAgICAgICAuLi4ob3B0cy53aXRoTWFza3MgIT09IHVuZGVmaW5lZCA/IHsgd2l0aE1hc2tzOiBvcHRzLndpdGhNYXNrcyB9IDoge30pLFxuICAgICAgICAuLi4ob3B0cy5zaWduYWwgPyB7IHNpZ25hbDogb3B0cy5zaWduYWwgfSA6IHt9KSxcbiAgICAgICAgLy8gT25seSB3b3J0aCB0cmFjaW5nIHdoZW4gdGhlcmUgYXJlIG1hc2tzIHRvIHRyYWNlLlxuICAgICAgICB0cmFjZVBvbHlnb25zOiBvcHRzLndpdGhNYXNrcyAhPT0gZmFsc2UsXG4gICAgICAgIHRyYWNlT3B0aW9uczoge1xuICAgICAgICAgIC4uLihvcHRzLnNpbXBsaWZ5VG9sZXJhbmNlICE9PSB1bmRlZmluZWQgPyB7IHNpbXBsaWZ5VG9sZXJhbmNlOiBvcHRzLnNpbXBsaWZ5VG9sZXJhbmNlIH0gOiB7fSksXG4gICAgICAgICAgLi4uKG9wdHMubWluQXJlYSAhPT0gdW5kZWZpbmVkID8geyBtaW5BcmVhOiBvcHRzLm1pbkFyZWEgfSA6IHt9KSxcbiAgICAgICAgfSxcbiAgICAgICAgLy8gVGhlIHdvcmtlciBuYXJyYXRlcyBpdHMgb3duIHBoYXNlcyDigJQgc2hhZGVyIHdhcm0tdXAsIHBlciB0aWxlLCBtZXJnZSxcbiAgICAgICAgLy8gdHJhY2luZyDigJQgYmVjYXVzZSBvbmx5IGl0IGtub3dzIHdoaWNoIG9uZSBpdCBpcyBpbi4gRGVyaXZpbmcgYSBzZWNvbmRcbiAgICAgICAgLy8gc3RhdHVzIGZyb20gdGlsZSBjb3VudHMgaGVyZSB3b3VsZCBqdXN0IGZpZ2h0IGl0LlxuICAgICAgICBvblN0YXR1czogKHMpID0+IHByb2dyZXNzPy5vblN0YXR1cz8uKHMpLFxuICAgICAgfSxcbiAgICApO1xuXG4gICAgLy8gQSBkZXRlY3Rpb24gd2l0aG91dCBhbiBvdXRsaW5lIGlzIHN0aWxsIGEgZGV0ZWN0aW9uLlxuICAgIC8vXG4gICAgLy8gVGhlc2UgdXNlZCB0byBiZSBkcm9wcGVkLCB3aGljaCBxdWlldGx5IGVtcHRpZWQgdGhlIGVudGlyZSByZXN1bHQgaW5cbiAgICAvLyBkZXRlY3Rpb24gbW9kZTogbm8gbWFza3MgbWVhbnMgbm8gcG9seWdvbnMsIHNvIGV2ZXJ5IG9iamVjdCB3YXNcbiAgICAvLyBkaXNjYXJkZWQgYW5kIHRoZSBjYWxsZXIgc2F3IG5vdGhpbmcgYXQgYWxsLiBUaGUgc2VydmVyIGRvZXMgbm90IHdvcmtcbiAgICAvLyB0aGF0IHdheSBlaXRoZXIg4oCUIGl0IGFsd2F5cyB3cml0ZXMgYSBib3ggZmVhdHVyZSBhbmQgb25seSAqYWRkcypcbiAgICAvLyBzZWdtZW50YXRpb24gb3V0bGluZXMgd2hlbiBhc2tlZC4gQ2FsbGVycyBnZXQgdGhlIGJveCBhbmQgZGVjaWRlIHdoYXQgdG9cbiAgICAvLyBkbyB3aXRoIGl0LlxuICAgIGNvbnN0IGRldGVjdGlvbnM6IEluc3RhbmNlRGV0ZWN0aW9uW10gPSByZXN1bHQuZGV0ZWN0aW9ucy5tYXAoKGQpID0+ICh7XG4gICAgICBwb2x5Z29uczogKGQucG9seWdvbnMgPz8gW10pLm1hcCgocCkgPT4gKHtcbiAgICAgICAgZXh0ZXJpb3I6IHAuZXh0ZXJpb3IubWFwKChbeCwgeV0pOiBbbnVtYmVyLCBudW1iZXJdID0+IFt4LCB5XSksXG4gICAgICAgIGhvbGVzOiBwLmhvbGVzLm1hcCgoaCkgPT4gaC5tYXAoKFt4LCB5XSk6IFtudW1iZXIsIG51bWJlcl0gPT4gW3gsIHldKSksXG4gICAgICB9KSksXG4gICAgICBib3g6IFtkLmJveFswXSwgZC5ib3hbMV0sIGQuYm94WzJdLCBkLmJveFszXV0gYXMgW251bWJlciwgbnVtYmVyLCBudW1iZXIsIG51bWJlcl0sXG4gICAgICBzY29yZTogZC5zY29yZSxcbiAgICAgIGNsYXNzSWQ6IGQuY2xhc3NJZCxcbiAgICAgIGNsYXNzTmFtZTogZC5jbGFzc05hbWUgPz8gYGNsYXNzJHtkLmNsYXNzSWR9YCxcbiAgICB9KSk7XG5cbiAgICByZXR1cm4ge1xuICAgICAgZGV0ZWN0aW9ucyxcbiAgICAgIHdpZHRoOiByZXN1bHQud2lkdGgsXG4gICAgICBoZWlnaHQ6IHJlc3VsdC5oZWlnaHQsXG4gICAgICBjbGFzc05hbWVzOiByZXN1bHQuY2xhc3NOYW1lcyxcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIFRoZSBzaGFyZWQgc2VnbWVudGVyIGZvciBhIG1vZGVsIGlkLCBjcmVhdGVkIG9uIGZpcnN0IHVzZSBhbmQgcmV1c2VkXG4gICAqIHRoZXJlYWZ0ZXIgKGRlZHVwZWQgYWNyb3NzIGNvbmN1cnJlbnQgY2FsbGVycykuIEV4cG9zZWQgc28gYSBob3N0J3NcbiAgICogcHJvY2Vzc2luZyBwaXBlbGluZSBjYW4gZHJpdmUgcmljaGVyIGBzZWdtZW50KClgIG9wdGlvbnMgb24gdGhlIHNhbWUgbG9hZGVkXG4gICAqIHNlc3Npb24gaW5zdGVhZCBvZiBsb2FkaW5nIHRoZSBtb2RlbCB0d2ljZS5cbiAgICovXG4gIGdldE1vZGVsKFxuICAgIG1vZGVsSWQ6IHN0cmluZyA9IGdldERlZmF1bHRZb2xvTW9kZWxJZCgpLFxuICAgIG9uUHJvZ3Jlc3M/OiAobG9hZGVkOiBudW1iZXIsIHRvdGFsOiBudW1iZXIgfCBudWxsKSA9PiB2b2lkLFxuICAgIG9uU3RhdHVzPzogKHN0YXR1czogc3RyaW5nKSA9PiB2b2lkLFxuICApOiBQcm9taXNlPFlvbG9TZWdtZW50ZXI+IHtcbiAgICBjb25zdCBleGlzdGluZyA9IHRoaXMuaW5zdGFuY2VzLmdldChtb2RlbElkKTtcbiAgICBpZiAoZXhpc3RpbmcpIHJldHVybiBQcm9taXNlLnJlc29sdmUoZXhpc3RpbmcpO1xuXG4gICAgY29uc3QgaW5GbGlnaHQgPSB0aGlzLmxvYWRpbmcuZ2V0KG1vZGVsSWQpO1xuICAgIGlmIChpbkZsaWdodCkgcmV0dXJuIGluRmxpZ2h0O1xuXG4gICAgY29uc3QgZGVmID0gdGhpcy5yZXF1aXJlTW9kZWwobW9kZWxJZCk7XG4gICAgY29uc3QgbG9hZCA9IChhc3luYyAoKSA9PiB7XG4gICAgICAvLyBMYXp5OiBrZWVwIHlvbG8tc2VnZGV0ZWN0LWpzICsgaXRzIE9SVCBydW50aW1lIG91dCBvZiB0aGUgaW5pdGlhbCBidW5kbGUuXG4gICAgICBjb25zdCB7IFlvbG9TZWdtZW50ZXIsIGNvbmZpZ3VyZU9ydCB9ID0gYXdhaXQgaW1wb3J0KCd5b2xvLXNlZ2RldGVjdC1qcycpO1xuICAgICAgY29uZmlndXJlT3J0KHsgd2FzbVBhdGhzOiBnZXRPcnRXYXNtQmFzZSgpIH0pO1xuICAgICAgY29uc3Qgc2VnID0gYXdhaXQgWW9sb1NlZ21lbnRlci5mcm9tUHJldHJhaW5lZChkZWYubW9kZWxVcmwsIHtcbiAgICAgICAgcHJlbG9hZDogdHJ1ZSxcbiAgICAgICAgLi4uKGRlZi5tZXRhVXJsID8geyBtZXRhVXJsOiBkZWYubWV0YVVybCB9IDoge30pLFxuICAgICAgICBvblByb2dyZXNzOiAoeyBsb2FkZWQsIHRvdGFsIH0pID0+IG9uUHJvZ3Jlc3M/Lihsb2FkZWQsIHRvdGFsKSxcbiAgICAgICAgb25TdGF0dXM6IChzKSA9PiBvblN0YXR1cz8uKHMpLFxuICAgICAgfSk7XG4gICAgICB0aGlzLmluc3RhbmNlcy5zZXQobW9kZWxJZCwgc2VnKTtcbiAgICAgIHJldHVybiBzZWc7XG4gICAgfSkoKTtcblxuICAgIHRoaXMubG9hZGluZy5zZXQobW9kZWxJZCwgbG9hZCk7XG4gICAgLy8gQSBmYWlsZWQgbG9hZCBtdXN0IG5vdCBwb2lzb24gdGhlIGNhY2hlIOKAlCB0aGUgbmV4dCBjYWxsZXIgc2hvdWxkIHJldHJ5XG4gICAgLy8gcmF0aGVyIHRoYW4gaW5oZXJpdCB0aGUgcmVqZWN0ZWQgcHJvbWlzZSBmb3JldmVyLlxuICAgIGxvYWQuY2F0Y2goKCkgPT4gdGhpcy5sb2FkaW5nLmRlbGV0ZShtb2RlbElkKSk7XG4gICAgcmV0dXJuIGxvYWQ7XG4gIH1cblxuICAvKiogV2hldGhlciBhIG1vZGVsIGlzIGFscmVhZHkgbG9hZGVkICh3YXJtKS4gKi9cbiAgaXNMb2FkZWQobW9kZWxJZDogc3RyaW5nID0gZ2V0RGVmYXVsdFlvbG9Nb2RlbElkKCkpOiBib29sZWFuIHtcbiAgICByZXR1cm4gdGhpcy5pbnN0YW5jZXMuaGFzKG1vZGVsSWQpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlbGVhc2Ugb25lIG1vZGVsLCBvciBhbGwgb2YgdGhlbS4gU2FmZSB0byBjYWxsIG9uIGEgbW9kZWwgbmV2ZXIgbG9hZGVkLlxuICAgKlxuICAgKiBJbi1mbGlnaHQgbG9hZHMgYXJlIHRvcm4gZG93biB0b28sIG5vdCBqdXN0IHdhcm0gb25lcy4gU2ltcGx5IGRyb3BwaW5nIHRoZVxuICAgKiBwZW5kaW5nIHByb21pc2UgYW5kIHJldHVybmluZyB3b3VsZCBsZXQgdGhlIGxvYWQgZmluaXNoIGFmdGVyd2FyZHMsXG4gICAqIHJlLXBvcHVsYXRlIHtAbGluayBpbnN0YW5jZXN9LCBhbmQgc3RyYW5kIGEgbGl2ZSB3b3JrZXIgYW5kIE9SVCBzZXNzaW9uIHRoYXRcbiAgICogbm90aGluZyBvd25zIOKAlCBkaXNwb3NhbCB3b3VsZCBoYXZlIHJlcG9ydGVkIHN1Y2Nlc3Mgd2hpbGUgbGVha2luZy4gU28gYSBsb2FkXG4gICAqIHRoYXQgaXMgc3RpbGwgcnVubmluZyBpcyBhd2FpdGVkIGFuZCB0aGVuIHJlbGVhc2VkLlxuICAgKi9cbiAgYXN5bmMgZGlzcG9zZShtb2RlbElkPzogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgaWRzID0gbW9kZWxJZCA/IFttb2RlbElkXSA6IFsuLi5uZXcgU2V0KFsuLi50aGlzLmluc3RhbmNlcy5rZXlzKCksIC4uLnRoaXMubG9hZGluZy5rZXlzKCldKV07XG4gICAgYXdhaXQgUHJvbWlzZS5hbGwoaWRzLm1hcCgoaWQpID0+IHRoaXMuZGlzcG9zZU9uZShpZCkpKTtcbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgZGlzcG9zZU9uZShpZDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgcGVuZGluZyA9IHRoaXMubG9hZGluZy5nZXQoaWQpO1xuICAgIGNvbnN0IGxvYWRlZCA9IHRoaXMuaW5zdGFuY2VzLmdldChpZCk7XG4gICAgdGhpcy5pbnN0YW5jZXMuZGVsZXRlKGlkKTtcbiAgICB0aGlzLmxvYWRpbmcuZGVsZXRlKGlkKTtcblxuICAgIGlmIChsb2FkZWQpIGF3YWl0IGxvYWRlZC5kaXNwb3NlKCk7XG4gICAgaWYgKCFwZW5kaW5nKSByZXR1cm47XG5cbiAgICAvLyBBIHBlbmRpbmcgbG9hZCBhc3NpZ25zIGluc3RhbmNlc1tpZF0gYmVmb3JlIGl0cyBwcm9taXNlIHJlc29sdmVzLCBzbyBieVxuICAgIC8vIHRoZSB0aW1lIHRoaXMgYXdhaXQgcmV0dXJucyB0aGUgZW50cnkgaXMgYmFjay4gQ2xlYXIgaXQgb25seSBpZiBpdCBzdGlsbFxuICAgIC8vIHBvaW50cyBhdCB3aGF0ICp0aGlzKiBsb2FkIHByb2R1Y2VkIOKAlCBhIG5ld2VyIGxvYWQgc3RhcnRlZCBpbiB0aGVcbiAgICAvLyBtZWFudGltZSBvd25zIGl0cyBvd24gZW50cnkgYW5kIG11c3Qgbm90IGJlIGV2aWN0ZWQuXG4gICAgY29uc3Qgc2VnID0gYXdhaXQgcGVuZGluZy5jYXRjaCgoKSA9PiBudWxsKTtcbiAgICBpZiAoIXNlZyB8fCBzZWcgPT09IGxvYWRlZCkgcmV0dXJuO1xuICAgIGlmICh0aGlzLmluc3RhbmNlcy5nZXQoaWQpID09PSBzZWcpIHRoaXMuaW5zdGFuY2VzLmRlbGV0ZShpZCk7XG4gICAgYXdhaXQgc2VnLmRpc3Bvc2UoKTtcbiAgfVxuXG4gIHByaXZhdGUgcmVxdWlyZU1vZGVsKG1vZGVsSWQ6IHN0cmluZyk6IFlvbG9Nb2RlbERlZiB7XG4gICAgY29uc3QgZGVmID0gZ2V0WW9sb01vZGVsKG1vZGVsSWQpO1xuICAgIGlmICghZGVmKSB0aHJvdyBuZXcgRXJyb3IoYFVua25vd24gWU9MTyBtb2RlbCBpZCBcIiR7bW9kZWxJZH1cIi5gKTtcbiAgICBpZiAoIWRlZi5tb2RlbFVybC50cmltKCkpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgWU9MTyBtb2RlbCBcIiR7bW9kZWxJZH1cIiBpcyBub3QgY29uZmlndXJlZCDigJQgaXRzIG1vZGVsVXJsIGlzIGVtcHR5LmApO1xuICAgIH1cbiAgICByZXR1cm4gZGVmO1xuICB9XG59XG4iXX0=
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { BehaviorSubject } from 'rxjs';
|
|
2
|
-
import { type TileAccessPort } from '../../contracts/ports/tile-access.port';
|
|
3
|
-
import type { IVisualizer } from '../../contracts/visualizer.contract';
|
|
4
|
-
import type { ISemanticSegmenter, SemanticSegmentOptions } from '../../contracts/semantic-segmenter.contract';
|
|
5
|
-
import * as i0 from "@angular/core";
|
|
6
|
-
/** Marks regions this tool produced, so a re-run can replace only its own. */
|
|
7
|
-
export declare const RETINAL_REGION_SOURCE = "retinal-layer";
|
|
8
|
-
/**
|
|
9
|
-
* Retinal-layer segmentation over the current view.
|
|
10
|
-
*
|
|
11
|
-
* Sibling of {@link YoloDetectToolService}, and structured the same way: run on
|
|
12
|
-
* whatever is on screen, map the results onto full-image coordinates through
|
|
13
|
-
* the viewer's source rect, and commit them as regions. The difference is what
|
|
14
|
-
* comes back — layers that tile the field rather than discrete objects, so
|
|
15
|
-
* every region is one connected component of one layer and there is no
|
|
16
|
-
* per-object confidence to filter on.
|
|
17
|
-
*
|
|
18
|
-
* Exposes the same `status$` / `busy$` / `progress$` surface as the cellpose and
|
|
19
|
-
* YOLO tools so the host's shared segmentation toast drives it unchanged.
|
|
20
|
-
*/
|
|
21
|
-
export declare class RetinalLayerToolService {
|
|
22
|
-
/** Server-side crop, so segmentation can run at the model's scale rather
|
|
23
|
-
* than the viewer's. Optional: without it the tool falls back to the
|
|
24
|
-
* displayed pixels, which is correct but scale-dependent. */
|
|
25
|
-
private tileAccess;
|
|
26
|
-
readonly status$: BehaviorSubject<string>;
|
|
27
|
-
readonly busy$: BehaviorSubject<boolean>;
|
|
28
|
-
readonly progress$: BehaviorSubject<number>;
|
|
29
|
-
constructor(
|
|
30
|
-
/** Server-side crop, so segmentation can run at the model's scale rather
|
|
31
|
-
* than the viewer's. Optional: without it the tool falls back to the
|
|
32
|
-
* displayed pixels, which is correct but scale-dependent. */
|
|
33
|
-
tileAccess?: TileAccessPort | null);
|
|
34
|
-
/**
|
|
35
|
-
* Segment the current view and commit the layers as regions.
|
|
36
|
-
*
|
|
37
|
-
* @returns the number of regions added.
|
|
38
|
-
*/
|
|
39
|
-
segmentInView(viz: IVisualizer, segmenter: ISemanticSegmenter, opts?: SemanticSegmentOptions): Promise<number>;
|
|
40
|
-
/**
|
|
41
|
-
* Re-crop `rect` at `1/target` of full resolution. Returns null on any
|
|
42
|
-
* failure, so the caller falls back to the displayed pixels — a coarser crop
|
|
43
|
-
* beats a failed run.
|
|
44
|
-
*/
|
|
45
|
-
private fetchCropAtScale;
|
|
46
|
-
private makeRegion;
|
|
47
|
-
static ɵfac: i0.ɵɵFactoryDeclaration<RetinalLayerToolService, [{ optional: true; }]>;
|
|
48
|
-
static ɵprov: i0.ɵɵInjectableDeclaration<RetinalLayerToolService>;
|
|
49
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Registry of retinal-layer semantic-segmentation models.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors {@link YOLO_MODELS}: public HF-hosted ONNX by default, repointable at
|
|
5
|
-
* private hosting via {@link setRetinalModelUrls}.
|
|
6
|
-
*
|
|
7
|
-
* ONLY ONE CHECKPOINT IS ENABLED, AND THAT IS DELIBERATE
|
|
8
|
-
* The server offers four. Measured against the ground-truth masks shipped
|
|
9
|
-
* alongside them, only VNet reproduces them:
|
|
10
|
-
*
|
|
11
|
-
* | checkpoint | mIoU |
|
|
12
|
-
* |---|---|
|
|
13
|
-
* | **VNet 2D** | **0.9061** |
|
|
14
|
-
* | ResUNet-a 2D (base) | 0.3065 |
|
|
15
|
-
* | ResUNet-a 2D 20x | 0.2869 |
|
|
16
|
-
* | ResUNet-a 2D 40x | 0.2718 |
|
|
17
|
-
*
|
|
18
|
-
* The ResUNet-a numbers are not quantization damage — their ONNX exports match
|
|
19
|
-
* their own Keras originals exactly. They disagree with the *masks*, folding
|
|
20
|
-
* ground-truth class 1 into their class 2 and class 3 into background, and no
|
|
21
|
-
* relabelling rescues them (the best of all 24 class permutations reaches only
|
|
22
|
-
* 0.36). Either those masks belong to a different model generation or the class
|
|
23
|
-
* definitions moved — an open question for whoever trained them.
|
|
24
|
-
*
|
|
25
|
-
* So their weights stay unpublished (their Hub repos are private) and their
|
|
26
|
-
* entries here carry an empty `modelUrl`, which {@link isRetinalModelReady}
|
|
27
|
-
* reports as not-ready. They are listed rather than deleted so a host shows
|
|
28
|
-
* them as unavailable-in-browser instead of pretending the server's choice does
|
|
29
|
-
* not exist — and so enabling one later is a URL, not a code change.
|
|
30
|
-
*
|
|
31
|
-
* Export tooling: the sibling `browser-onnx-tools` project.
|
|
32
|
-
*/
|
|
33
|
-
export interface RetinalModelDef {
|
|
34
|
-
id: string;
|
|
35
|
-
label: string;
|
|
36
|
-
/** URL of the `.onnx` weights. **Empty disables the model.** */
|
|
37
|
-
modelUrl: string;
|
|
38
|
-
/**
|
|
39
|
-
* URL of the `model.json` sidecar. Omit to use `model.json` alongside the
|
|
40
|
-
* weights, which is how every published repo is laid out.
|
|
41
|
-
*/
|
|
42
|
-
metaUrl?: string;
|
|
43
|
-
/** Patch size the checkpoint was trained at, for callers that size reads to it. */
|
|
44
|
-
patchSize: number;
|
|
45
|
-
/** Download size of the `fp16w` weights, MB — these are large enough to warn about. */
|
|
46
|
-
sizeMb: number;
|
|
47
|
-
/** Measured mIoU against the bucket's ground-truth masks. */
|
|
48
|
-
miou: number;
|
|
49
|
-
/** Set when the model is knowingly disabled; shown to the user. */
|
|
50
|
-
unavailableReason?: string;
|
|
51
|
-
}
|
|
52
|
-
export declare const RETINAL_MODELS: RetinalModelDef[];
|
|
53
|
-
export declare const DEFAULT_RETINAL_MODEL_ID = "vnet-2d-retinal";
|
|
54
|
-
/** Look up a model definition by id. Returns undefined for an unknown id. */
|
|
55
|
-
export declare function getRetinalModel(id: string): RetinalModelDef | undefined;
|
|
56
|
-
/** Whether a model has usable weights configured. */
|
|
57
|
-
export declare function isRetinalModelReady(id: string): boolean;
|
|
58
|
-
/** Only the models a browser can actually run. */
|
|
59
|
-
export declare function readyRetinalModels(): RetinalModelDef[];
|
|
60
|
-
/**
|
|
61
|
-
* Repoint a model at different hosting (a GCS bucket, a same-origin proxy), or
|
|
62
|
-
* enable one of the disabled entries. Call once at app init, before first use.
|
|
63
|
-
* An empty `modelUrl` disables the model.
|
|
64
|
-
*/
|
|
65
|
-
export declare function setRetinalModelUrls(id: string, urls: {
|
|
66
|
-
modelUrl?: string;
|
|
67
|
-
metaUrl?: string;
|
|
68
|
-
}): void;
|
|
69
|
-
/** Change which model runs when a caller does not name one. */
|
|
70
|
-
export declare function setDefaultRetinalModel(id: string): void;
|
|
71
|
-
export declare function getDefaultRetinalModelId(): string;
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import type { ISemanticSegmenter, SemanticSegmentation, SemanticSegmentOptions, SemanticSegmentProgress } from '../../contracts/semantic-segmenter.contract';
|
|
2
|
-
import type { RetinalSegmenter } from 'jax-ai-js';
|
|
3
|
-
import * as i0 from "@angular/core";
|
|
4
|
-
/**
|
|
5
|
-
* Default in-library {@link ISemanticSegmenter}, backed by jax-ai-js.
|
|
6
|
-
*
|
|
7
|
-
* `jax-ai-js` is a regular dependency but is **lazy-imported** on first use, so
|
|
8
|
-
* apps that never segment retinal layers pay nothing for it — or for
|
|
9
|
-
* onnxruntime-web — in their initial bundle. Mirrors
|
|
10
|
-
* {@link YoloSegmenterService}, including its per-model-id cache.
|
|
11
|
-
*
|
|
12
|
-
* WEBGPU ONLY, AND A HOST MUST BE READY FOR THAT
|
|
13
|
-
* Unlike the YOLO and Cellpose services, there is no WASM fallback to degrade
|
|
14
|
-
* to: ORT-Web's WASM EP dies with `std::bad_alloc` on these graphs at 512², at
|
|
15
|
-
* every precision. jax-ai-js throws `WebGpuRequiredError` rather than trying,
|
|
16
|
-
* and it is re-thrown here unchanged so a host can show a real
|
|
17
|
-
* unsupported-browser message instead of a generic failure.
|
|
18
|
-
*/
|
|
19
|
-
export declare class RetinalSegmenterService implements ISemanticSegmenter {
|
|
20
|
-
private readonly instances;
|
|
21
|
-
private readonly loading;
|
|
22
|
-
segmentSemantic(image: {
|
|
23
|
-
data: Uint8ClampedArray;
|
|
24
|
-
width: number;
|
|
25
|
-
height: number;
|
|
26
|
-
}, opts?: SemanticSegmentOptions, progress?: SemanticSegmentProgress): Promise<SemanticSegmentation>;
|
|
27
|
-
/**
|
|
28
|
-
* The shared segmenter for a model id, created on first use and reused
|
|
29
|
-
* thereafter (deduped across concurrent callers). Exposed so a host's
|
|
30
|
-
* processing pipeline can run on the same loaded session rather than paying
|
|
31
|
-
* the ~590 MB load twice.
|
|
32
|
-
*/
|
|
33
|
-
getModel(modelId?: string, onProgress?: (loaded: number, total: number | null) => void, onStatus?: (status: string) => void,
|
|
34
|
-
/**
|
|
35
|
-
* Abort the load. This model is ~590 MB, so a cancel has to reach the
|
|
36
|
-
* *download* — aborting only the inference that follows leaves the transfer
|
|
37
|
-
* running and the UI stuck in "Cancelling" until it completes.
|
|
38
|
-
*
|
|
39
|
-
* Deliberately not applied to a load already in flight: that promise may be
|
|
40
|
-
* shared with another caller (the toolbar tool and the pipeline both use
|
|
41
|
-
* this service), and one caller's cancel must not tear the model out from
|
|
42
|
-
* under the other. A second caller joining an in-flight load simply waits.
|
|
43
|
-
*/
|
|
44
|
-
signal?: AbortSignal): Promise<RetinalSegmenter>;
|
|
45
|
-
/** Whether a model is already loaded (warm). */
|
|
46
|
-
isLoaded(modelId?: string): boolean;
|
|
47
|
-
/**
|
|
48
|
-
* Release one model, or all of them. Safe to call on a model never loaded.
|
|
49
|
-
*
|
|
50
|
-
* In-flight loads are torn down too: dropping a pending promise and returning
|
|
51
|
-
* would let the load finish afterwards, re-populate {@link instances}, and
|
|
52
|
-
* strand a live ORT session that nothing owns — disposal would report success
|
|
53
|
-
* while leaking. At 590 MB that leak is not survivable.
|
|
54
|
-
*/
|
|
55
|
-
dispose(modelId?: string): Promise<void>;
|
|
56
|
-
private disposeOne;
|
|
57
|
-
private requireModel;
|
|
58
|
-
static ɵfac: i0.ɵɵFactoryDeclaration<RetinalSegmenterService, never>;
|
|
59
|
-
static ɵprov: i0.ɵɵInjectableDeclaration<RetinalSegmenterService>;
|
|
60
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { BehaviorSubject } from 'rxjs';
|
|
2
|
-
import { type TileAccessPort } from '../../contracts/ports/tile-access.port';
|
|
3
|
-
import type { IVisualizer } from '../../contracts/visualizer.contract';
|
|
4
|
-
import type { IInstanceSegmenter, InstanceSegmentOptions } from '../../contracts/instance-segmenter.contract';
|
|
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 declare const YOLO_REGION_SOURCE = "yolo";
|
|
23
|
-
export declare class YoloDetectToolService {
|
|
24
|
-
/** Server-side crop, so detection can run at the model's scale rather than
|
|
25
|
-
* the viewer's. Optional: without it the tool falls back to the displayed
|
|
26
|
-
* pixels, which is correct but scale-dependent. */
|
|
27
|
-
private tileAccess;
|
|
28
|
-
readonly status$: BehaviorSubject<string>;
|
|
29
|
-
readonly busy$: BehaviorSubject<boolean>;
|
|
30
|
-
readonly progress$: BehaviorSubject<number>;
|
|
31
|
-
constructor(
|
|
32
|
-
/** Server-side crop, so detection can run at the model's scale rather than
|
|
33
|
-
* the viewer's. Optional: without it the tool falls back to the displayed
|
|
34
|
-
* pixels, which is correct but scale-dependent. */
|
|
35
|
-
tileAccess?: TileAccessPort | null);
|
|
36
|
-
/**
|
|
37
|
-
* Detect in the current view and append the results as regions.
|
|
38
|
-
*
|
|
39
|
-
* @returns the number of regions added.
|
|
40
|
-
*/
|
|
41
|
-
detectInView(viz: IVisualizer, segmenter: IInstanceSegmenter, opts?: InstanceSegmentOptions): Promise<number>;
|
|
42
|
-
/**
|
|
43
|
-
* Re-crop `rect` at `1/target` of full resolution. Returns null on any
|
|
44
|
-
* failure, so the caller falls back to the displayed pixels — a coarser crop
|
|
45
|
-
* beats a failed run.
|
|
46
|
-
*/
|
|
47
|
-
private fetchCropAtScale;
|
|
48
|
-
private makeRegion;
|
|
49
|
-
static ɵfac: i0.ɵɵFactoryDeclaration<YoloDetectToolService, [{ optional: true; }]>;
|
|
50
|
-
static ɵprov: i0.ɵɵInjectableDeclaration<YoloDetectToolService>;
|
|
51
|
-
}
|
|
@@ -1,59 +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
|
-
export interface YoloModelDef {
|
|
24
|
-
id: string;
|
|
25
|
-
label: string;
|
|
26
|
-
/** URL of the `.onnx` weights. Empty disables the model. */
|
|
27
|
-
modelUrl: string;
|
|
28
|
-
/**
|
|
29
|
-
* URL of the `model.json` sidecar. Omit to use `model.json` alongside the
|
|
30
|
-
* weights, which is how every published repo is laid out.
|
|
31
|
-
*/
|
|
32
|
-
metaUrl?: string;
|
|
33
|
-
/** Per-model defaults, from the server worker's model table. */
|
|
34
|
-
defaults: {
|
|
35
|
-
confidence: number;
|
|
36
|
-
iouThreshold: number;
|
|
37
|
-
mergeThreshold: number;
|
|
38
|
-
overlapX: number;
|
|
39
|
-
overlapY: number;
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
export declare const YOLO_MODELS: YoloModelDef[];
|
|
43
|
-
export declare const DEFAULT_YOLO_MODEL_ID = "yolov8x-seg-opticnerve";
|
|
44
|
-
/** Look up a model definition by id. Returns undefined for an unknown id. */
|
|
45
|
-
export declare function getYoloModel(id: string): YoloModelDef | undefined;
|
|
46
|
-
/** Whether a model has a usable weights URL configured. */
|
|
47
|
-
export declare function isYoloModelReady(id: string): boolean;
|
|
48
|
-
/**
|
|
49
|
-
* Repoint a model at different hosting (e.g. a GCS bucket or a same-origin
|
|
50
|
-
* proxy). Call once at app init, before the first run. An empty `modelUrl`
|
|
51
|
-
* disables the model.
|
|
52
|
-
*/
|
|
53
|
-
export declare function setYoloModelUrls(id: string, urls: {
|
|
54
|
-
modelUrl?: string;
|
|
55
|
-
metaUrl?: string;
|
|
56
|
-
}): void;
|
|
57
|
-
/** Change which model runs when a caller does not name one. */
|
|
58
|
-
export declare function setDefaultYoloModel(id: string): void;
|
|
59
|
-
export declare function getDefaultYoloModelId(): string;
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { IInstanceSegmenter, InstanceSegmentation, InstanceSegmentOptions, InstanceSegmentProgress } from '../../contracts/instance-segmenter.contract';
|
|
2
|
-
import type { YoloSegmenter } from 'yolo-segdetect-js';
|
|
3
|
-
import * as i0 from "@angular/core";
|
|
4
|
-
/**
|
|
5
|
-
* Default in-library {@link IInstanceSegmenter}, backed by yolo-segdetect-js
|
|
6
|
-
* (YOLOv8-seg on WebGPU, falling back to WASM).
|
|
7
|
-
*
|
|
8
|
-
* `yolo-segdetect-js` is a regular dependency but is **lazy-imported** on first
|
|
9
|
-
* use, so apps that never run YOLO pay nothing for it — or for onnxruntime-web —
|
|
10
|
-
* in their initial bundle. This mirrors {@link CellposeSegmenterService}.
|
|
11
|
-
*
|
|
12
|
-
* The one structural difference from the Cellpose service: there are four YOLO
|
|
13
|
-
* checkpoints, not one, so instances are cached **per model id** rather than as
|
|
14
|
-
* a single singleton. Each entry owns its own worker and ORT session, so a host
|
|
15
|
-
* that switches models pays a second load but keeps the first one warm. Nothing
|
|
16
|
-
* evicts automatically — these are ~144 MB each, so a host that cycles through
|
|
17
|
-
* all four should call {@link dispose} for the ones it is done with.
|
|
18
|
-
*/
|
|
19
|
-
export declare class YoloSegmenterService implements IInstanceSegmenter {
|
|
20
|
-
private readonly instances;
|
|
21
|
-
private readonly loading;
|
|
22
|
-
segmentInstances(image: {
|
|
23
|
-
data: Uint8ClampedArray;
|
|
24
|
-
width: number;
|
|
25
|
-
height: number;
|
|
26
|
-
}, opts?: InstanceSegmentOptions, progress?: InstanceSegmentProgress): Promise<InstanceSegmentation>;
|
|
27
|
-
/**
|
|
28
|
-
* The shared segmenter for a model id, created on first use and reused
|
|
29
|
-
* thereafter (deduped across concurrent callers). Exposed so a host's
|
|
30
|
-
* processing pipeline can drive richer `segment()` options on the same loaded
|
|
31
|
-
* session instead of loading the model twice.
|
|
32
|
-
*/
|
|
33
|
-
getModel(modelId?: string, onProgress?: (loaded: number, total: number | null) => void, onStatus?: (status: string) => void): Promise<YoloSegmenter>;
|
|
34
|
-
/** Whether a model is already loaded (warm). */
|
|
35
|
-
isLoaded(modelId?: string): boolean;
|
|
36
|
-
/**
|
|
37
|
-
* Release one model, or all of them. Safe to call on a model never loaded.
|
|
38
|
-
*
|
|
39
|
-
* In-flight loads are torn down too, not just warm ones. Simply dropping the
|
|
40
|
-
* pending promise and returning would let the load finish afterwards,
|
|
41
|
-
* re-populate {@link instances}, and strand a live worker and ORT session that
|
|
42
|
-
* nothing owns — disposal would have reported success while leaking. So a load
|
|
43
|
-
* that is still running is awaited and then released.
|
|
44
|
-
*/
|
|
45
|
-
dispose(modelId?: string): Promise<void>;
|
|
46
|
-
private disposeOne;
|
|
47
|
-
private requireModel;
|
|
48
|
-
static ɵfac: i0.ɵɵFactoryDeclaration<YoloSegmenterService, never>;
|
|
49
|
-
static ɵprov: i0.ɵɵInjectableDeclaration<YoloSegmenterService>;
|
|
50
|
-
}
|