@jax-data-science/sci-image-visualizer 0.2.16 → 0.2.18

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
@@ -25,11 +25,15 @@ export { CellposeSegmenterService } from './lib/toolbar/segmentation/cellpose-se
25
25
  export * from './lib/contracts/instance-segmenter.contract';
26
26
  export { YoloSegmenterService } from './lib/toolbar/segmentation/yolo-segmenter.service';
27
27
  export { YoloDetectToolService, YOLO_REGION_SOURCE } from './lib/toolbar/segmentation/yolo-detect-tool.service';
28
+ export * from './lib/contracts/semantic-segmenter.contract';
29
+ export { RetinalSegmenterService } from './lib/toolbar/segmentation/retinal-segmenter.service';
30
+ export { RetinalLayerToolService, RETINAL_REGION_SOURCE, } from './lib/toolbar/segmentation/retinal-layer-tool.service';
28
31
  export { ProcessingImage } from './lib/processing/processing-image';
29
32
  export { cropImage, CropImageOptions } from './lib/processing/image-crop';
30
33
  export { ImageConverterService } from './lib/processing/image-converter.service';
31
34
  export { SAM_MODELS, DEFAULT_SAM_MODEL_ID, getSamModel, isSamModelReady, setSamModelUrls, setDefaultSamModel, getDefaultSamModelId, } from './lib/toolbar/segmentation/sam-model-registry';
32
35
  export { YOLO_MODELS, DEFAULT_YOLO_MODEL_ID, getYoloModel, isYoloModelReady, setYoloModelUrls, setDefaultYoloModel, getDefaultYoloModelId, YoloModelDef, } from './lib/toolbar/segmentation/yolo-model-registry';
36
+ export { RETINAL_MODELS, DEFAULT_RETINAL_MODEL_ID, getRetinalModel, isRetinalModelReady, readyRetinalModels, setRetinalModelUrls, setDefaultRetinalModel, getDefaultRetinalModelId, RetinalModelDef, } from './lib/toolbar/segmentation/retinal-model-registry';
33
37
  export { setOrtWasmBase, getOrtWasmBase } from './lib/toolbar/segmentation/ort-runtime-config';
34
38
  export * from './lib/models/region';
35
39
  export * from './lib/models/class-preset';
@@ -0,0 +1,105 @@
1
+ import { InjectionToken } from '@angular/core';
2
+ /**
3
+ * Port for a **semantic** segmenter — one that assigns every pixel a class from
4
+ * a fixed set, with no notion of separate objects.
5
+ *
6
+ * The third sibling of {@link ICellSegmenter} and {@link IInstanceSegmenter},
7
+ * and genuinely a different shape from both. Cellpose separates touching
8
+ * instances of one class; YOLO returns overlapping classified objects; this
9
+ * returns *layers* — regions that tile the image, never overlap, and where two
10
+ * disconnected patches of the same class are the same finding rather than two
11
+ * objects. There is no confidence per object and no NMS, because there are no
12
+ * objects.
13
+ *
14
+ * Results are delivered as polygon rings in **image pixels**, so a caller can
15
+ * build {@link Region}s directly — the same currency the other two ports use.
16
+ */
17
+ /** A closed ring; the last point is NOT a repeat of the first. */
18
+ export type SemanticRing = Array<[number, number]>;
19
+ /** One connected component of one class, with any interior voids. */
20
+ export interface SemanticRegion {
21
+ exterior: SemanticRing;
22
+ holes: SemanticRing[];
23
+ classId: number;
24
+ className: string;
25
+ /** Area in pixels, holes already subtracted. */
26
+ area: number;
27
+ }
28
+ export interface SemanticSegmentation {
29
+ regions: SemanticRegion[];
30
+ /** Total pixels assigned to each class, indexed by class id. */
31
+ classAreas: number[];
32
+ /** Class names indexed by class id, from the model's own metadata. */
33
+ classNames: string[];
34
+ width: number;
35
+ height: number;
36
+ /**
37
+ * Fraction of pixels that met no class's threshold.
38
+ *
39
+ * Worth surfacing rather than discarding: these models are thresholded per
40
+ * class rather than argmaxed, so a high value means the model was unsure
41
+ * everywhere — which is the signature of wrong preprocessing (wrong input
42
+ * scale, wrong greyscale) long before the polygons look obviously wrong.
43
+ */
44
+ unassignedFraction: number;
45
+ }
46
+ export interface SemanticSegmentOptions {
47
+ /** Which registered model to run. Defaults to the registry's default. */
48
+ modelId?: string;
49
+ /**
50
+ * Scale to run at, as a divisor of full resolution — the same meaning the
51
+ * server gives it.
52
+ *
53
+ * Matters as much here as for a detector: these checkpoints were trained on
54
+ * 40x or 20x patches, and a layer imaged at the wrong scale does not look
55
+ * like the layer the model learned.
56
+ */
57
+ downsamplingFactor?: number;
58
+ /**
59
+ * Minimum probability for a pixel to be assigned to a class, default 0.5.
60
+ *
61
+ * Not a plain argmax. The reference worker thresholds each class channel
62
+ * independently at 0.5; because the model's last layer is a softmax at most
63
+ * one class can clear that, so this is argmax *plus a confidence floor* —
64
+ * pixels where the winner is weak are left unassigned rather than forced into
65
+ * the best of a bad set. Lowering it toward 0.25 approaches a true argmax and
66
+ * visibly fattens every layer.
67
+ */
68
+ classThreshold?: number;
69
+ /** Keep only these class ids; omit for everything except background. */
70
+ classFilter?: number[];
71
+ /** Douglas-Peucker tolerance in pixels for outline simplification; 0 disables. */
72
+ simplifyTolerance?: number;
73
+ /** Drop regions whose area is below this, in square pixels. */
74
+ minArea?: number;
75
+ /**
76
+ * Cooperative cancellation, checked at patch boundaries — so it takes effect
77
+ * within roughly one patch rather than instantly.
78
+ */
79
+ signal?: AbortSignal;
80
+ }
81
+ /** Progress/status callbacks (all optional). Mirrors {@link InstanceSegmentProgress}. */
82
+ export interface SemanticSegmentProgress {
83
+ /** Model-download progress, 0..1 (first run only). */
84
+ onProgress?: (fraction: number) => void;
85
+ /**
86
+ * Raw download byte counts, for hosts that render a size rather than a bare
87
+ * percentage. `total` is null when the response carried no Content-Length.
88
+ */
89
+ onBytes?: (loaded: number, total: number | null) => void;
90
+ /** Human-readable phase, e.g. 'Segmenting patch 3/8…'. */
91
+ onStatus?: (status: string) => void;
92
+ }
93
+ export interface ISemanticSegmenter {
94
+ /** Assign every pixel a class and return the resulting regions. */
95
+ segmentSemantic(image: {
96
+ data: Uint8ClampedArray;
97
+ width: number;
98
+ height: number;
99
+ }, opts?: SemanticSegmentOptions, progress?: SemanticSegmentProgress): Promise<SemanticSegmentation>;
100
+ }
101
+ /**
102
+ * DI token for the semantic segmenter, defaulting to the in-library
103
+ * jax-ai-js-backed implementation. Hosts override it to supply their own.
104
+ */
105
+ export declare const SEMANTIC_SEGMENTER: InjectionToken<ISemanticSegmenter>;
@@ -0,0 +1,49 @@
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
+ }
@@ -0,0 +1,71 @@
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;
@@ -0,0 +1,60 @@
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
+ }
@@ -72,6 +72,12 @@ export declare class ToolbarComponent implements OnChanges {
72
72
  label: string;
73
73
  }[];
74
74
  yoloModelId: string;
75
+ /** Retinal-layer checkpoints offered by the host's registry, and the active one. */
76
+ retinalModels: {
77
+ id: string;
78
+ label: string;
79
+ }[];
80
+ retinalModelId: string;
75
81
  selectPlotType: EventEmitter<PlotType>;
76
82
  /** Intensity (LINE) mode: add another colored line ROI + inset trace. */
77
83
  addProfileLine: EventEmitter<void>;
@@ -117,6 +123,12 @@ export declare class ToolbarComponent implements OnChanges {
117
123
  yoloModelChange: EventEmitter<string>;
118
124
  /** Open the YOLO parameter dialog. */
119
125
  openYoloParams: EventEmitter<void>;
126
+ /** Run retinal-layer segmentation over the current view. */
127
+ segmentRetinal: EventEmitter<void>;
128
+ /** Pick the retinal-layer checkpoint. */
129
+ retinalModelChange: EventEmitter<string>;
130
+ /** Open the retinal-layer parameter dialog. */
131
+ openRetinalParams: EventEmitter<void>;
120
132
  /** SAM model picker (jit-ui#90 P1). */
121
133
  samModelChange: EventEmitter<string>;
122
134
  wandSensitivityChange: EventEmitter<number | undefined>;
@@ -147,6 +159,8 @@ export declare class ToolbarComponent implements OnChanges {
147
159
  samMenuItems: MenuItem[];
148
160
  /** Same stable-array reasoning as {@link samMenuItems}. */
149
161
  yoloMenuItems: MenuItem[];
162
+ /** Same stable-array reasoning as {@link samMenuItems}. */
163
+ retinalMenuItems: MenuItem[];
150
164
  /** Rebuild the SAM model menu when the model list or active selection
151
165
  * changes (keeps the array reference stable across other CD ticks). */
152
166
  ngOnChanges(changes: SimpleChanges): void;
@@ -177,5 +191,5 @@ export declare class ToolbarComponent implements OnChanges {
177
191
  get isIntensityCapable(): boolean;
178
192
  showHelp(): void;
179
193
  static ɵfac: i0.ɵɵFactoryDeclaration<ToolbarComponent, never>;
180
- static ɵcmp: i0.ɵɵComponentDeclaration<ToolbarComponent, "plotting-toolbar", never, { "imageInfo": { "alias": "imageInfo"; "required": false; }; "plotTypeOptions": { "alias": "plotTypeOptions"; "required": false; }; "selectedPlotType": { "alias": "selectedPlotType"; "required": false; }; "isoRange": { "alias": "isoRange"; "required": false; }; "maxIndex": { "alias": "maxIndex"; "required": false; }; "zIndex": { "alias": "zIndex"; "required": false; }; "isHeatmap": { "alias": "isHeatmap"; "required": false; }; "activeDragMode": { "alias": "activeDragMode"; "required": false; }; "activeSurface3dMode": { "alias": "activeSurface3dMode"; "required": false; }; "axesVisible": { "alias": "axesVisible"; "required": false; }; "wireframeActive": { "alias": "wireframeActive"; "required": false; }; "resolutionScale": { "alias": "resolutionScale"; "required": false; }; "wandSensitivity": { "alias": "wandSensitivity"; "required": false; }; "brushSize": { "alias": "brushSize"; "required": false; }; "vertexEraserRadius": { "alias": "vertexEraserRadius"; "required": false; }; "canUndo": { "alias": "canUndo"; "required": false; }; "canRedo": { "alias": "canRedo"; "required": false; }; "stackOptions": { "alias": "stackOptions"; "required": false; }; "tools": { "alias": "tools"; "required": false; }; "imageSmoothingEnabled": { "alias": "imageSmoothingEnabled"; "required": false; }; "samModels": { "alias": "samModels"; "required": false; }; "samModelId": { "alias": "samModelId"; "required": false; }; "yoloModels": { "alias": "yoloModels"; "required": false; }; "yoloModelId": { "alias": "yoloModelId"; "required": false; }; }, { "selectPlotType": "selectPlotType"; "addProfileLine": "addProfileLine"; "toggleImageSmoothing": "toggleImageSmoothing"; "isoRangeChange": "isoRangeChange"; "openChannelHistogram": "openChannelHistogram"; "openRegionEditor": "openRegionEditor"; "selectStackOption": "selectStackOption"; "zScrub": "zScrub"; "zSlide": "zSlide"; "zIndexInput": "zIndexInput"; "reloadAndPlot": "reloadAndPlot"; "downloadImage": "downloadImage"; "autoscaleImage": "autoscaleImage"; "toggleDragMode": "toggleDragMode"; "zoomIn": "zoomIn"; "zoomOut": "zoomOut"; "toggleSurface3dMode": "toggleSurface3dMode"; "resetSurfaceCamera": "resetSurfaceCamera"; "toggleAxes": "toggleAxes"; "toggleWireframe": "toggleWireframe"; "selectResolution": "selectResolution"; "deleteRegion": "deleteRegion"; "undoRegion": "undoRegion"; "redoRegion": "redoRegion"; "segmentRegions": "segmentRegions"; "segmentCellpose": "segmentCellpose"; "detectYolo": "detectYolo"; "yoloModelChange": "yoloModelChange"; "openYoloParams": "openYoloParams"; "samModelChange": "samModelChange"; "wandSensitivityChange": "wandSensitivityChange"; "brushSizeChange": "brushSizeChange"; "vertexEraserRadiusChange": "vertexEraserRadiusChange"; }, never, ["*"], false, never>;
194
+ static ɵcmp: i0.ɵɵComponentDeclaration<ToolbarComponent, "plotting-toolbar", never, { "imageInfo": { "alias": "imageInfo"; "required": false; }; "plotTypeOptions": { "alias": "plotTypeOptions"; "required": false; }; "selectedPlotType": { "alias": "selectedPlotType"; "required": false; }; "isoRange": { "alias": "isoRange"; "required": false; }; "maxIndex": { "alias": "maxIndex"; "required": false; }; "zIndex": { "alias": "zIndex"; "required": false; }; "isHeatmap": { "alias": "isHeatmap"; "required": false; }; "activeDragMode": { "alias": "activeDragMode"; "required": false; }; "activeSurface3dMode": { "alias": "activeSurface3dMode"; "required": false; }; "axesVisible": { "alias": "axesVisible"; "required": false; }; "wireframeActive": { "alias": "wireframeActive"; "required": false; }; "resolutionScale": { "alias": "resolutionScale"; "required": false; }; "wandSensitivity": { "alias": "wandSensitivity"; "required": false; }; "brushSize": { "alias": "brushSize"; "required": false; }; "vertexEraserRadius": { "alias": "vertexEraserRadius"; "required": false; }; "canUndo": { "alias": "canUndo"; "required": false; }; "canRedo": { "alias": "canRedo"; "required": false; }; "stackOptions": { "alias": "stackOptions"; "required": false; }; "tools": { "alias": "tools"; "required": false; }; "imageSmoothingEnabled": { "alias": "imageSmoothingEnabled"; "required": false; }; "samModels": { "alias": "samModels"; "required": false; }; "samModelId": { "alias": "samModelId"; "required": false; }; "yoloModels": { "alias": "yoloModels"; "required": false; }; "yoloModelId": { "alias": "yoloModelId"; "required": false; }; "retinalModels": { "alias": "retinalModels"; "required": false; }; "retinalModelId": { "alias": "retinalModelId"; "required": false; }; }, { "selectPlotType": "selectPlotType"; "addProfileLine": "addProfileLine"; "toggleImageSmoothing": "toggleImageSmoothing"; "isoRangeChange": "isoRangeChange"; "openChannelHistogram": "openChannelHistogram"; "openRegionEditor": "openRegionEditor"; "selectStackOption": "selectStackOption"; "zScrub": "zScrub"; "zSlide": "zSlide"; "zIndexInput": "zIndexInput"; "reloadAndPlot": "reloadAndPlot"; "downloadImage": "downloadImage"; "autoscaleImage": "autoscaleImage"; "toggleDragMode": "toggleDragMode"; "zoomIn": "zoomIn"; "zoomOut": "zoomOut"; "toggleSurface3dMode": "toggleSurface3dMode"; "resetSurfaceCamera": "resetSurfaceCamera"; "toggleAxes": "toggleAxes"; "toggleWireframe": "toggleWireframe"; "selectResolution": "selectResolution"; "deleteRegion": "deleteRegion"; "undoRegion": "undoRegion"; "redoRegion": "redoRegion"; "segmentRegions": "segmentRegions"; "segmentCellpose": "segmentCellpose"; "detectYolo": "detectYolo"; "yoloModelChange": "yoloModelChange"; "openYoloParams": "openYoloParams"; "segmentRetinal": "segmentRetinal"; "retinalModelChange": "retinalModelChange"; "openRetinalParams": "openRetinalParams"; "samModelChange": "samModelChange"; "wandSensitivityChange": "wandSensitivityChange"; "brushSizeChange": "brushSizeChange"; "vertexEraserRadiusChange": "vertexEraserRadiusChange"; }, never, ["*"], false, never>;
181
195
  }
@@ -14,6 +14,8 @@ import { SamPointToolService } from './toolbar/segmentation/sam-point-tool.servi
14
14
  import { CellSegmentToolService } from './toolbar/segmentation/cell-segment-tool.service';
15
15
  import { YoloDetectToolService } from './toolbar/segmentation/yolo-detect-tool.service';
16
16
  import type { IInstanceSegmenter, InstanceSegmentOptions } from './contracts/instance-segmenter.contract';
17
+ import { RetinalLayerToolService } from './toolbar/segmentation/retinal-layer-tool.service';
18
+ import type { ISemanticSegmenter, SemanticSegmentOptions } from './contracts/semantic-segmenter.contract';
17
19
  import { ToolbarToolVisibility } from './contracts/toolbar-config';
18
20
  import { VizConfig } from './contracts/viz-config';
19
21
  import * as i0 from "@angular/core";
@@ -28,6 +30,8 @@ export declare class VisualizerComponent implements OnInit, OnChanges, AfterView
28
30
  private cellSegmentTool;
29
31
  private yoloDetectTool;
30
32
  private instanceSegmenter;
33
+ private semanticSegmenter;
34
+ private retinalLayerTool;
31
35
  private samPointTool;
32
36
  private regionOps;
33
37
  private vizConfig?;
@@ -137,6 +141,18 @@ export declare class VisualizerComponent implements OnInit, OnChanges, AfterView
137
141
  * encode the scale and crowding each checkpoint was trained for. */
138
142
  showYoloParams: boolean;
139
143
  yoloParams: InstanceSegmentOptions;
144
+ /** Retinal-layer checkpoints with a configured URL, and the active one.
145
+ * Only VNet ships — see the registry for why the ResUNet-a entries are off. */
146
+ retinalModels: {
147
+ id: string;
148
+ label: string;
149
+ }[];
150
+ retinalModelId: string;
151
+ showRetinalParams: boolean;
152
+ retinalParams: SemanticSegmentOptions;
153
+ /** Download size of the active retinal model, surfaced in the dialog because
154
+ * ~590 MB is worth knowing before clicking Run rather than after. */
155
+ get retinalModelSizeMb(): number;
140
156
  /** SAM download/segment toast state (bound by the `sam` p-toast template). */
141
157
  samStatus: string;
142
158
  samProgress: number;
@@ -247,7 +263,7 @@ export declare class VisualizerComponent implements OnInit, OnChanges, AfterView
247
263
  private wheelListener?;
248
264
  /** Debounced z-slice scrubbing (see SliceScrubber — refactoring plan Step 7). */
249
265
  private readonly scrubber;
250
- constructor(state: ImageStatePort, plotService: IVisualizer, messageService: MessageService, ngZone: NgZone, cdr: ChangeDetectorRef, session: VisualizerStore, samTool: SamToolService, cellSegmentTool: CellSegmentToolService, yoloDetectTool: YoloDetectToolService, instanceSegmenter: IInstanceSegmenter, samPointTool: SamPointToolService, regionOps: RegionOpsService, vizConfig?: VizConfig | undefined);
266
+ constructor(state: ImageStatePort, plotService: IVisualizer, messageService: MessageService, ngZone: NgZone, cdr: ChangeDetectorRef, session: VisualizerStore, samTool: SamToolService, cellSegmentTool: CellSegmentToolService, yoloDetectTool: YoloDetectToolService, instanceSegmenter: IInstanceSegmenter, semanticSegmenter: ISemanticSegmenter, retinalLayerTool: RetinalLayerToolService, samPointTool: SamPointToolService, regionOps: RegionOpsService, vizConfig?: VizConfig | undefined);
251
267
  /**
252
268
  * Plot types offered in the selector for the current image:
253
269
  * - Outside **test mode**, only the curated default set (descriptors with a
@@ -406,6 +422,26 @@ export declare class VisualizerComponent implements OnInit, OnChanges, AfterView
406
422
  * in a field rather than being pointed at one. Reuses the shared segmentation
407
423
  * toast so progress and status read the same as the SAM/cellpose tools. */
408
424
  detectYolo(): Promise<void>;
425
+ /**
426
+ * Defaults for the retinal tool.
427
+ *
428
+ * `classThreshold` is 0.5 to match the reference worker, which thresholds each
429
+ * class channel independently rather than taking an argmax — with a softmax
430
+ * output that makes it argmax plus a confidence floor.
431
+ *
432
+ * `downsamplingFactor` is 1 rather than YOLO's 5: these checkpoints were
433
+ * trained on native 40x patches, so the model wants full resolution and
434
+ * downsampling actively hurts.
435
+ */
436
+ private defaultRetinalParams;
437
+ /** Switching checkpoint re-seeds the parameters, since the defaults belong to
438
+ * the model rather than to the tool. */
439
+ onRetinalModelChange(id: string): void;
440
+ openRetinalParams(): void;
441
+ resetRetinalParams(): void;
442
+ /** Segment retinal layers across the current view. Like YOLO this takes no
443
+ * prompt — it labels every pixel — and reuses the shared segmentation toast. */
444
+ segmentRetinal(): Promise<void>;
409
445
  /** Shared driver for the box-prompt segment tools: wires the tool's status +
410
446
  * download progress into the sticky `sam` toast, runs `op`, and reports the
411
447
  * region count. Keeps the toast open until the run settles (bar hits 100%). */
@@ -449,6 +485,6 @@ export declare class VisualizerComponent implements OnInit, OnChanges, AfterView
449
485
  onSelectPlotType(type: PlotType): void;
450
486
  /** Deactivate whatever tool is armed and clear every tool mode. */
451
487
  private deactivateActiveTool;
452
- static ɵfac: i0.ɵɵFactoryDeclaration<VisualizerComponent, [null, null, null, null, null, null, null, null, null, null, null, null, { optional: true; }]>;
488
+ static ɵfac: i0.ɵɵFactoryDeclaration<VisualizerComponent, [null, null, null, null, null, null, null, null, null, null, null, null, null, null, { optional: true; }]>;
453
489
  static ɵcmp: i0.ɵɵComponentDeclaration<VisualizerComponent, "visualizer", never, { "toolbarTools": { "alias": "toolbarTools"; "required": false; }; "testMode": { "alias": "testMode"; "required": false; }; }, { "isStackEvent": "isStackEvent"; "isGrayscaleEvent": "isGrayscaleEvent"; }, never, ["*"], false, never>;
454
490
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jax-data-science/sci-image-visualizer",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
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",
@@ -36,6 +36,7 @@
36
36
  "dependencies": {
37
37
  "cellpose-js": "^0.4.1",
38
38
  "fast-png": "^6.4.0",
39
+ "jax-ai-js": "^0.1.1",
39
40
  "napari-js": "^0.11.0",
40
41
  "openseadragon": "^6.0.2",
41
42
  "plotly.js-dist-min": "^3.0.1",