@jax-data-science/sci-image-visualizer 0.2.8 → 0.2.10

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/index.d.ts CHANGED
@@ -22,10 +22,13 @@ export * from './lib/contracts/display-types';
22
22
  export * from './lib/contracts/sam.contract';
23
23
  export * from './lib/contracts/cell-segmenter.contract';
24
24
  export { CellposeSegmenterService } from './lib/toolbar/segmentation/cellpose-segmenter.service';
25
+ export * from './lib/contracts/instance-segmenter.contract';
26
+ export { YoloSegmenterService } from './lib/toolbar/segmentation/yolo-segmenter.service';
25
27
  export { ProcessingImage } from './lib/processing/processing-image';
26
28
  export { cropImage, CropImageOptions } from './lib/processing/image-crop';
27
29
  export { ImageConverterService } from './lib/processing/image-converter.service';
28
30
  export { SAM_MODELS, DEFAULT_SAM_MODEL_ID, getSamModel, isSamModelReady, setSamModelUrls, setDefaultSamModel, getDefaultSamModelId, } from './lib/toolbar/segmentation/sam-model-registry';
31
+ export { YOLO_MODELS, DEFAULT_YOLO_MODEL_ID, getYoloModel, isYoloModelReady, setYoloModelUrls, setDefaultYoloModel, getDefaultYoloModelId, YoloModelDef, } from './lib/toolbar/segmentation/yolo-model-registry';
29
32
  export { setOrtWasmBase, getOrtWasmBase } from './lib/toolbar/segmentation/ort-runtime-config';
30
33
  export * from './lib/models/region';
31
34
  export * from './lib/models/class-preset';
@@ -0,0 +1,124 @@
1
+ import { InjectionToken } from '@angular/core';
2
+ /**
3
+ * Port for an automatic **instance** segmenter — one that returns discrete,
4
+ * classified objects rather than a per-pixel label map.
5
+ *
6
+ * This is a sibling of {@link ICellSegmenter}, not a replacement. Cellpose-style
7
+ * segmenters produce one exclusive label per pixel, which is exactly right for
8
+ * densely packed cells; a detector like YOLO produces overlapping instances,
9
+ * each with its own class and confidence, and flattening those into a label map
10
+ * would silently discard both the overlap and the classification. The two
11
+ * outputs are genuinely different shapes, so they get different ports.
12
+ *
13
+ * Geometry is returned as polygon rings in **image pixels**, ready to become
14
+ * {@link Region}s without a second conversion step.
15
+ */
16
+ /** A closed ring; the last point is NOT a repeat of the first. */
17
+ export type InstanceRing = Array<[number, number]>;
18
+ /** One polygon with optional interior rings (donut holes). */
19
+ export interface InstancePolygon {
20
+ exterior: InstanceRing;
21
+ holes: InstanceRing[];
22
+ }
23
+ /** One detected object. */
24
+ export interface InstanceDetection {
25
+ /**
26
+ * Outline(s) of this instance. Usually one, but a mask that splits under
27
+ * thresholding yields several — they all belong to the same detection.
28
+ */
29
+ polygons: InstancePolygon[];
30
+ /** `[x1, y1, x2, y2]` in image pixels. */
31
+ box: [number, number, number, number];
32
+ /** Model confidence, 0..1. */
33
+ score: number;
34
+ classId: number;
35
+ className: string;
36
+ }
37
+ export interface InstanceSegmentation {
38
+ detections: InstanceDetection[];
39
+ width: number;
40
+ height: number;
41
+ /** Class names indexed by class id, from the model's own metadata. */
42
+ classNames: string[];
43
+ }
44
+ /**
45
+ * Tunables for one run.
46
+ *
47
+ * Three separate thresholds are in play and they are easy to conflate:
48
+ * `confidence` and `iouThreshold` act per tile, while `mergeThreshold` governs
49
+ * the cross-tile reconciliation. They come from different stages of the
50
+ * pipeline and are not interchangeable.
51
+ */
52
+ export interface InstanceSegmentOptions {
53
+ /** Which registered model to run. Defaults to the registry's default. */
54
+ modelId?: string;
55
+ /** Minimum detection confidence, per tile. */
56
+ confidence?: number;
57
+ /** NMS IoU within a single tile. */
58
+ iouThreshold?: number;
59
+ /** Intersection-over-smaller threshold for merging across tile seams. */
60
+ mergeThreshold?: number;
61
+ /** Tile overlap, percent. */
62
+ overlapX?: number;
63
+ overlapY?: number;
64
+ /** Probability at which a mask pixel counts as inside. */
65
+ maskThreshold?: number;
66
+ /** Cap on detections per tile. */
67
+ maxDetections?: number;
68
+ /** Keep only these class ids; omit for all classes the model declares. */
69
+ classFilter?: number[];
70
+ /** Douglas-Peucker tolerance in pixels for outline simplification; 0 disables. */
71
+ simplifyTolerance?: number;
72
+ /** Drop instances whose outline area is below this, in square pixels. */
73
+ minArea?: number;
74
+ /**
75
+ * Set false to detect boxes without assembling masks — substantially faster,
76
+ * since mask assembly dominates the cost of a run. Detections then carry a
77
+ * box and class but no outline, so nothing can be committed as a region.
78
+ */
79
+ withMasks?: boolean;
80
+ /**
81
+ * Abort an in-progress run.
82
+ *
83
+ * Cancellation is cooperative and checked at tile boundaries, so it takes
84
+ * effect within roughly one tile rather than instantly — a `session.run()`
85
+ * already in flight cannot be interrupted.
86
+ */
87
+ signal?: AbortSignal;
88
+ }
89
+ /**
90
+ * Progress/status callbacks (all optional).
91
+ *
92
+ * Deliberately mirrors {@link CellSegmentProgress} in shape rather than importing
93
+ * it — the two contracts stay independently versionable.
94
+ */
95
+ export interface InstanceSegmentProgress {
96
+ /** Model-download progress, 0..1 (first run only). */
97
+ onProgress?: (fraction: number) => void;
98
+ /**
99
+ * Raw download byte counts, for hosts that render a size ("142 / 287 MB")
100
+ * rather than a bare percentage. `total` is null when the response carried no
101
+ * Content-Length.
102
+ *
103
+ * A fraction alone cannot be turned back into bytes, so a host given only
104
+ * {@link onProgress} has to invent the numbers — which reads as "0 MB / 0 MB"
105
+ * the moment it does.
106
+ */
107
+ onBytes?: (loaded: number, total: number | null) => void;
108
+ /** Human-readable phase, e.g. 'Running inference (tile 3/8)…'. */
109
+ onStatus?: (status: string) => void;
110
+ }
111
+ export interface IInstanceSegmenter {
112
+ /** Detect and outline all instances in an RGBA image. */
113
+ segmentInstances(image: {
114
+ data: Uint8ClampedArray;
115
+ width: number;
116
+ height: number;
117
+ }, opts?: InstanceSegmentOptions, progress?: InstanceSegmentProgress): Promise<InstanceSegmentation>;
118
+ }
119
+ /**
120
+ * DI token for the automatic instance segmenter. Defaults to the in-library
121
+ * {@link YoloSegmenterService} (yolo-segdetect-js, lazy-loaded); override it in
122
+ * the host to swap implementations — for example to delegate to the server.
123
+ */
124
+ export declare const INSTANCE_SEGMENTER: InjectionToken<IInstanceSegmenter>;
@@ -0,0 +1,59 @@
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;
@@ -0,0 +1,50 @@
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jax-data-science/sci-image-visualizer",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Browser-based scientific image visualization for Angular — gigapixel whole-slide (OpenSeadragon), 2D/3D (Plotly) and WebGPU (napari-js) rendering, with region/annotation editing and in-browser SAM segmentation.",
5
5
  "keywords": [
6
6
  "angular",
@@ -39,7 +39,8 @@
39
39
  "napari-js": "^0.11.0",
40
40
  "openseadragon": "^6.0.2",
41
41
  "plotly.js-dist-min": "^3.0.1",
42
- "tslib": "^2.3.0"
42
+ "tslib": "^2.3.0",
43
+ "yolo-segdetect-js": "^0.1.1"
43
44
  },
44
45
  "peerDependencies": {
45
46
  "@angular/animations": "^17.3.0",