@jax-data-science/sci-image-visualizer 0.10.0 → 0.12.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 +7 -1
- package/esm2022/lib/contracts/image.contract.mjs +1 -1
- package/esm2022/lib/contracts/ports/region-io.port.mjs +1 -1
- package/esm2022/lib/contracts/region-editor-api.contract.mjs +1 -1
- package/esm2022/lib/implementations/napari-js/napari-spatial-3d-scene.mjs +70 -2
- package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +2 -2
- package/esm2022/lib/implementations/napari-js/napari-volume-region-meshes.mjs +11 -1
- package/esm2022/lib/implementations/osd/openseadragon-viewer-options.mjs +18 -2
- package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +9 -1
- package/esm2022/lib/implementations/osd/osd-tile-loading.mjs +71 -0
- package/esm2022/lib/implementations/osd/osd-viewer-like.mjs +1 -1
- package/esm2022/lib/implementations/osd/slice-cache.mjs +35 -6
- package/esm2022/lib/region-editor/region-editor.component.mjs +152 -4
- package/esm2022/lib/region-editor/region-persistence.service.mjs +9 -1
- package/esm2022/lib/region-import/label-classes.mjs +149 -0
- package/esm2022/lib/region-import/label-volume.mjs +225 -0
- package/esm2022/lib/region-import/ome-zarr-labels.mjs +253 -0
- package/esm2022/lib/region-import/open-annotations.mjs +289 -0
- package/esm2022/lib/region-import/region-import.mjs +71 -0
- package/esm2022/lib/region-import/zarr-codecs.mjs +167 -0
- package/esm2022/lib/region-import/zip.mjs +157 -0
- package/esm2022/lib/routing-visualizer.service.mjs +112 -7
- package/esm2022/lib/store/region-source-scope.mjs +19 -4
- package/esm2022/lib/store/region-store.service.mjs +64 -14
- package/esm2022/lib/visualizer/region-load.mjs +16 -1
- package/esm2022/lib/visualizer.component.mjs +28 -2
- package/fesm2022/{jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BKQhpbHv.mjs → jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-a68Hw5rw.mjs} +2057 -196
- package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-a68Hw5rw.mjs.map +1 -0
- package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-z1IN-F7w.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-CvadfzjS.mjs} +4 -2
- package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-z1IN-F7w.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-CvadfzjS.mjs.map} +1 -1
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +3 -1
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
- package/index.d.ts +12 -0
- package/lib/contracts/image.contract.d.ts +7 -0
- package/lib/contracts/ports/region-io.port.d.ts +24 -0
- package/lib/contracts/region-editor-api.contract.d.ts +19 -1
- package/lib/implementations/napari-js/napari-spatial-3d-scene.d.ts +40 -1
- package/lib/implementations/napari-js/napari-volume-region-meshes.d.ts +5 -0
- package/lib/implementations/osd/openseadragon-viewer-options.d.ts +2 -0
- package/lib/implementations/osd/openseadragon-visualizer.service.d.ts +2 -0
- package/lib/implementations/osd/osd-tile-loading.d.ts +43 -0
- package/lib/implementations/osd/osd-viewer-like.d.ts +14 -0
- package/lib/implementations/osd/slice-cache.d.ts +13 -3
- package/lib/region-editor/region-editor.component.d.ts +27 -0
- package/lib/region-editor/region-persistence.service.d.ts +5 -1
- package/lib/region-import/label-classes.d.ts +39 -0
- package/lib/region-import/label-volume.d.ts +35 -0
- package/lib/region-import/ome-zarr-labels.d.ts +36 -0
- package/lib/region-import/open-annotations.d.ts +73 -0
- package/lib/region-import/region-import.d.ts +46 -0
- package/lib/region-import/zarr-codecs.d.ts +14 -0
- package/lib/region-import/zip.d.ts +31 -0
- package/lib/routing-visualizer.service.d.ts +39 -1
- package/lib/store/region-source-scope.d.ts +8 -2
- package/lib/store/region-store.service.d.ts +15 -5
- package/lib/visualizer.component.d.ts +9 -0
- package/package.json +2 -1
- package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BKQhpbHv.mjs.map +0 -1
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { LruCache } from 'napari-js';
|
|
2
2
|
import * as ort from 'onnxruntime-web';
|
|
3
|
-
import { g as getOrtWasmBase, f as fetchModel, r as runEncoder, a as runDecoder } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-
|
|
3
|
+
import { g as getOrtWasmBase, f as fetchModel, r as runEncoder, a as runDecoder } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-a68Hw5rw.mjs';
|
|
4
4
|
import '@angular/core';
|
|
5
5
|
import '@angular/common/http';
|
|
6
6
|
import 'rxjs';
|
|
7
7
|
import 'rxjs/operators';
|
|
8
8
|
import 'napari-js/geometry';
|
|
9
|
+
import 'fast-png';
|
|
10
|
+
import 'fzstd';
|
|
9
11
|
import '@angular/core/rxjs-interop';
|
|
10
12
|
import '@angular/common';
|
|
11
13
|
import '@angular/forms';
|
|
@@ -235,4 +237,4 @@ class OnnxSamSession {
|
|
|
235
237
|
}
|
|
236
238
|
|
|
237
239
|
export { OnnxSamSession };
|
|
238
|
-
//# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-
|
|
240
|
+
//# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-CvadfzjS.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jax-data-science-sci-image-visualizer-onnx-sam-session-z1IN-F7w.mjs","sources":["../../src/lib/toolbar/segmentation/pending-calls.ts","../../src/lib/toolbar/segmentation/onnx-sam-session.ts"],"sourcesContent":["/** A reply from the SAM worker: progress for, or the result of, call `id`. */\nexport interface WorkerReply {\n id: number;\n type: string;\n /** Present on `type: 'progress'` replies. */\n fraction?: number;\n /** Present on `type: 'error'` replies. */\n error?: string;\n [key: string]: unknown;\n}\n\ninterface Pending {\n resolve: (reply: WorkerReply) => void;\n reject: (err: Error) => void;\n onProgress?: (fraction: number) => void;\n}\n\n/**\n * Request/response bookkeeping for a worker RPC: each call gets an id and a\n * promise that its reply settles. Every exit path (dispose, a worker crash)\n * must {@link rejectAll}, or awaiting callers never reach their `finally` and a\n * tool's busy flag sticks forever (RT-8).\n */\nexport class PendingCalls {\n private seq = 0;\n private readonly pending = new Map<number, Pending>();\n\n /** Calls still awaiting a reply. */\n get size(): number {\n return this.pending.size;\n }\n\n /** Register a new call; post the message with the returned id. */\n open(onProgress?: (fraction: number) => void): { id: number; promise: Promise<WorkerReply> } {\n const id = ++this.seq;\n const promise = new Promise<WorkerReply>((resolve, reject) => {\n this.pending.set(id, { resolve, reject, onProgress });\n });\n return { id, promise };\n }\n\n /** Route a worker reply to its call: progress, error or result. */\n handle(reply: WorkerReply): void {\n const p = this.pending.get(reply.id);\n if (!p) return;\n if (reply.type === 'progress') {\n p.onProgress?.(reply.fraction ?? 0);\n return;\n }\n this.pending.delete(reply.id);\n if (reply.type === 'error') p.reject(new Error(reply.error ?? 'SAM worker error.'));\n else p.resolve(reply);\n }\n\n /** Reject every pending call with `err` and forget them. */\n rejectAll(err: Error): void {\n const calls = Array.from(this.pending.values());\n this.pending.clear();\n for (const p of calls) p.reject(err);\n }\n}\n","import { LruCache } from 'napari-js';\nimport * as ort from 'onnxruntime-web';\n\nimport { ISamSession, SamEmbedding, SamMaskResult, SamModelDef, SamPrompt } from '../../contracts/sam.contract';\nimport { fetchModel, runEncoder, runDecoder, type CoreEmbedding } from './sam-onnx-core';\nimport { getOrtWasmBase } from './ort-runtime-config';\nimport { PendingCalls, WorkerReply } from './pending-calls';\n\n/**\n * onnxruntime-web implementation of {@link ISamSession} (jit-ui#90), with two\n * execution modes chosen per model:\n *\n * - **in-process** (main thread) — for small WASM models flagged `inProcess`\n * (e.g. micro-sam ViT-T). Fast load + inference with no Web Worker spawn /\n * second ORT runtime / message round-trips; the brief encode runs on the main\n * thread (fine for a tiny model).\n * - **worker** — for WebGPU models (e.g. ViT-B, patho-sam fp16) whose heavy\n * encode would otherwise freeze the tab. Runs in {@link ./onnx-sam.worker} so\n * the UI stays responsive (the spinner animates).\n *\n * Both modes share their inference math via {@link ./sam-onnx-core}. Lazy-imported\n * by the SAM tools so onnxruntime-web / the worker never load in unit tests or\n * the initial bundle.\n */\nexport class OnnxSamSession implements ISamSession {\n private mode: 'inproc' | 'worker' | null = null;\n private loaded = false;\n private inputSize = 1024;\n\n // ── worker mode ──\n private worker: Worker | null = null;\n private readonly calls = new PendingCalls();\n\n // ── in-process mode ──\n private encoder: ort.InferenceSession | null = null;\n private decoder: ort.InferenceSession | null = null;\n private nextToken = 1;\n /** The few most recently used embeddings, by token (one per recently viewed image). */\n private readonly embeddings = new LruCache<CoreEmbedding, number>(3);\n\n async loadModel(model: SamModelDef, onProgress?: (fraction: number) => void): Promise<void> {\n if (!model.encoderUrl || !model.decoderUrl) {\n throw new Error(`SAM model \"${model.id}\" has no ONNX URLs configured.`);\n }\n this.inputSize = model.inputSize;\n const hasGpu = typeof navigator !== 'undefined' && 'gpu' in navigator;\n const eps: string[] = model.encoderProviders ?? (hasGpu ? ['webgpu', 'wasm'] : ['wasm']);\n const usesWebGpu = eps.includes('webgpu');\n // WebGPU must run in the worker (else it freezes the main thread); small\n // WASM models opt into the faster in-process path via `inProcess`.\n this.mode = !usesWebGpu && model.inProcess ? 'inproc' : 'worker';\n\n if (this.mode === 'inproc') {\n ort.env.wasm.wasmPaths = getOrtWasmBase();\n const encBuf = await fetchModel(model.encoderUrl, onProgress, model.revision);\n const decBuf = await fetchModel(model.decoderUrl, undefined, model.revision);\n this.encoder = await ort.InferenceSession.create(encBuf, { executionProviders: eps });\n this.decoder = await ort.InferenceSession.create(decBuf, { executionProviders: ['wasm'] });\n } else {\n await this.call(\n {\n type: 'load',\n encoderUrl: model.encoderUrl,\n decoderUrl: model.decoderUrl,\n revision: model.revision,\n wasmPaths: getOrtWasmBase(),\n inputSize: model.inputSize,\n encoderProviders: model.encoderProviders,\n },\n [],\n onProgress,\n );\n }\n this.loaded = true;\n }\n\n isLoaded(): boolean {\n return this.loaded;\n }\n\n async embed(image: { data: Uint8ClampedArray; width: number; height: number }): Promise<SamEmbedding> {\n if (this.mode === 'inproc') {\n const e = await runEncoder(this.encoder!, image.data, image.width, image.height, this.inputSize);\n const token = this.nextToken++;\n this.embeddings.set(token, e);\n return {\n data: new Float32Array(0),\n dims: e.dims,\n scale: e.scale,\n imageWidth: e.imageWidth,\n imageHeight: e.imageHeight,\n token,\n };\n }\n // worker: transfer the RGBA buffer (callers pass a fresh frame buffer).\n const buffer = image.data.buffer;\n const res = await this.call({ type: 'embed', width: image.width, height: image.height, buffer }, [buffer]);\n return {\n data: new Float32Array(0),\n dims: res['dims'] as number[],\n scale: res['scale'] as number,\n imageWidth: res['imageWidth'] as number,\n imageHeight: res['imageHeight'] as number,\n token: res['token'] as number,\n };\n }\n\n async decode(embedding: SamEmbedding, prompt: SamPrompt): Promise<SamMaskResult> {\n if (this.mode === 'inproc') {\n const e = this.embeddings.get(embedding.token as number);\n if (!e) throw new Error('SAM embedding expired; re-encode the image.');\n return runDecoder(this.decoder!, e, prompt);\n }\n const res = await this.call({ type: 'decode', token: embedding.token, prompt });\n return {\n mask: new Uint8Array(res['buffer'] as ArrayBuffer),\n width: res['width'] as number,\n height: res['height'] as number,\n iou: res['iou'] as number,\n };\n }\n\n /** Release the worker / ORT sessions. Calls still in flight reject (RT-8), so\n * their callers' `finally` blocks run (e.g. a tool's busy flag clears). */\n dispose(): void {\n this.dropWorker(new Error('SAM session disposed'));\n this.encoder?.release?.();\n this.decoder?.release?.();\n this.encoder = this.decoder = null;\n this.embeddings.clear();\n this.loaded = false;\n this.mode = null;\n }\n\n // ── worker plumbing ──────────────────────────────────────────────────────\n private ensureWorker(): Worker {\n if (this.worker) return this.worker;\n const worker = new Worker(new URL('./onnx-sam.worker', import.meta.url), { type: 'module' });\n worker.onmessage = (ev: MessageEvent<WorkerReply>) => this.calls.handle(ev.data);\n worker.onerror = (ev: ErrorEvent) => {\n // A crashed worker can't serve the loaded model any more: drop it, so the\n // next run reloads instead of posting to a dead worker.\n this.dropWorker(new Error(ev.message || 'SAM worker crashed.'));\n this.loaded = false;\n this.mode = null;\n };\n this.worker = worker;\n return worker;\n }\n\n /** Terminate the worker (if any) and reject every call still waiting on it. */\n private dropWorker(err: Error): void {\n this.worker?.terminate();\n this.worker = null;\n this.calls.rejectAll(err);\n }\n\n private call(\n msg: Record<string, unknown>,\n transfer: Transferable[] = [],\n onProgress?: (f: number) => void,\n ): Promise<WorkerReply> {\n const worker = this.ensureWorker();\n const { id, promise } = this.calls.open(onProgress);\n worker.postMessage({ ...msg, id }, transfer);\n return promise;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"jax-data-science-sci-image-visualizer-onnx-sam-session-CvadfzjS.mjs","sources":["../../src/lib/toolbar/segmentation/pending-calls.ts","../../src/lib/toolbar/segmentation/onnx-sam-session.ts"],"sourcesContent":["/** A reply from the SAM worker: progress for, or the result of, call `id`. */\nexport interface WorkerReply {\n id: number;\n type: string;\n /** Present on `type: 'progress'` replies. */\n fraction?: number;\n /** Present on `type: 'error'` replies. */\n error?: string;\n [key: string]: unknown;\n}\n\ninterface Pending {\n resolve: (reply: WorkerReply) => void;\n reject: (err: Error) => void;\n onProgress?: (fraction: number) => void;\n}\n\n/**\n * Request/response bookkeeping for a worker RPC: each call gets an id and a\n * promise that its reply settles. Every exit path (dispose, a worker crash)\n * must {@link rejectAll}, or awaiting callers never reach their `finally` and a\n * tool's busy flag sticks forever (RT-8).\n */\nexport class PendingCalls {\n private seq = 0;\n private readonly pending = new Map<number, Pending>();\n\n /** Calls still awaiting a reply. */\n get size(): number {\n return this.pending.size;\n }\n\n /** Register a new call; post the message with the returned id. */\n open(onProgress?: (fraction: number) => void): { id: number; promise: Promise<WorkerReply> } {\n const id = ++this.seq;\n const promise = new Promise<WorkerReply>((resolve, reject) => {\n this.pending.set(id, { resolve, reject, onProgress });\n });\n return { id, promise };\n }\n\n /** Route a worker reply to its call: progress, error or result. */\n handle(reply: WorkerReply): void {\n const p = this.pending.get(reply.id);\n if (!p) return;\n if (reply.type === 'progress') {\n p.onProgress?.(reply.fraction ?? 0);\n return;\n }\n this.pending.delete(reply.id);\n if (reply.type === 'error') p.reject(new Error(reply.error ?? 'SAM worker error.'));\n else p.resolve(reply);\n }\n\n /** Reject every pending call with `err` and forget them. */\n rejectAll(err: Error): void {\n const calls = Array.from(this.pending.values());\n this.pending.clear();\n for (const p of calls) p.reject(err);\n }\n}\n","import { LruCache } from 'napari-js';\nimport * as ort from 'onnxruntime-web';\n\nimport { ISamSession, SamEmbedding, SamMaskResult, SamModelDef, SamPrompt } from '../../contracts/sam.contract';\nimport { fetchModel, runEncoder, runDecoder, type CoreEmbedding } from './sam-onnx-core';\nimport { getOrtWasmBase } from './ort-runtime-config';\nimport { PendingCalls, WorkerReply } from './pending-calls';\n\n/**\n * onnxruntime-web implementation of {@link ISamSession} (jit-ui#90), with two\n * execution modes chosen per model:\n *\n * - **in-process** (main thread) — for small WASM models flagged `inProcess`\n * (e.g. micro-sam ViT-T). Fast load + inference with no Web Worker spawn /\n * second ORT runtime / message round-trips; the brief encode runs on the main\n * thread (fine for a tiny model).\n * - **worker** — for WebGPU models (e.g. ViT-B, patho-sam fp16) whose heavy\n * encode would otherwise freeze the tab. Runs in {@link ./onnx-sam.worker} so\n * the UI stays responsive (the spinner animates).\n *\n * Both modes share their inference math via {@link ./sam-onnx-core}. Lazy-imported\n * by the SAM tools so onnxruntime-web / the worker never load in unit tests or\n * the initial bundle.\n */\nexport class OnnxSamSession implements ISamSession {\n private mode: 'inproc' | 'worker' | null = null;\n private loaded = false;\n private inputSize = 1024;\n\n // ── worker mode ──\n private worker: Worker | null = null;\n private readonly calls = new PendingCalls();\n\n // ── in-process mode ──\n private encoder: ort.InferenceSession | null = null;\n private decoder: ort.InferenceSession | null = null;\n private nextToken = 1;\n /** The few most recently used embeddings, by token (one per recently viewed image). */\n private readonly embeddings = new LruCache<CoreEmbedding, number>(3);\n\n async loadModel(model: SamModelDef, onProgress?: (fraction: number) => void): Promise<void> {\n if (!model.encoderUrl || !model.decoderUrl) {\n throw new Error(`SAM model \"${model.id}\" has no ONNX URLs configured.`);\n }\n this.inputSize = model.inputSize;\n const hasGpu = typeof navigator !== 'undefined' && 'gpu' in navigator;\n const eps: string[] = model.encoderProviders ?? (hasGpu ? ['webgpu', 'wasm'] : ['wasm']);\n const usesWebGpu = eps.includes('webgpu');\n // WebGPU must run in the worker (else it freezes the main thread); small\n // WASM models opt into the faster in-process path via `inProcess`.\n this.mode = !usesWebGpu && model.inProcess ? 'inproc' : 'worker';\n\n if (this.mode === 'inproc') {\n ort.env.wasm.wasmPaths = getOrtWasmBase();\n const encBuf = await fetchModel(model.encoderUrl, onProgress, model.revision);\n const decBuf = await fetchModel(model.decoderUrl, undefined, model.revision);\n this.encoder = await ort.InferenceSession.create(encBuf, { executionProviders: eps });\n this.decoder = await ort.InferenceSession.create(decBuf, { executionProviders: ['wasm'] });\n } else {\n await this.call(\n {\n type: 'load',\n encoderUrl: model.encoderUrl,\n decoderUrl: model.decoderUrl,\n revision: model.revision,\n wasmPaths: getOrtWasmBase(),\n inputSize: model.inputSize,\n encoderProviders: model.encoderProviders,\n },\n [],\n onProgress,\n );\n }\n this.loaded = true;\n }\n\n isLoaded(): boolean {\n return this.loaded;\n }\n\n async embed(image: { data: Uint8ClampedArray; width: number; height: number }): Promise<SamEmbedding> {\n if (this.mode === 'inproc') {\n const e = await runEncoder(this.encoder!, image.data, image.width, image.height, this.inputSize);\n const token = this.nextToken++;\n this.embeddings.set(token, e);\n return {\n data: new Float32Array(0),\n dims: e.dims,\n scale: e.scale,\n imageWidth: e.imageWidth,\n imageHeight: e.imageHeight,\n token,\n };\n }\n // worker: transfer the RGBA buffer (callers pass a fresh frame buffer).\n const buffer = image.data.buffer;\n const res = await this.call({ type: 'embed', width: image.width, height: image.height, buffer }, [buffer]);\n return {\n data: new Float32Array(0),\n dims: res['dims'] as number[],\n scale: res['scale'] as number,\n imageWidth: res['imageWidth'] as number,\n imageHeight: res['imageHeight'] as number,\n token: res['token'] as number,\n };\n }\n\n async decode(embedding: SamEmbedding, prompt: SamPrompt): Promise<SamMaskResult> {\n if (this.mode === 'inproc') {\n const e = this.embeddings.get(embedding.token as number);\n if (!e) throw new Error('SAM embedding expired; re-encode the image.');\n return runDecoder(this.decoder!, e, prompt);\n }\n const res = await this.call({ type: 'decode', token: embedding.token, prompt });\n return {\n mask: new Uint8Array(res['buffer'] as ArrayBuffer),\n width: res['width'] as number,\n height: res['height'] as number,\n iou: res['iou'] as number,\n };\n }\n\n /** Release the worker / ORT sessions. Calls still in flight reject (RT-8), so\n * their callers' `finally` blocks run (e.g. a tool's busy flag clears). */\n dispose(): void {\n this.dropWorker(new Error('SAM session disposed'));\n this.encoder?.release?.();\n this.decoder?.release?.();\n this.encoder = this.decoder = null;\n this.embeddings.clear();\n this.loaded = false;\n this.mode = null;\n }\n\n // ── worker plumbing ──────────────────────────────────────────────────────\n private ensureWorker(): Worker {\n if (this.worker) return this.worker;\n const worker = new Worker(new URL('./onnx-sam.worker', import.meta.url), { type: 'module' });\n worker.onmessage = (ev: MessageEvent<WorkerReply>) => this.calls.handle(ev.data);\n worker.onerror = (ev: ErrorEvent) => {\n // A crashed worker can't serve the loaded model any more: drop it, so the\n // next run reloads instead of posting to a dead worker.\n this.dropWorker(new Error(ev.message || 'SAM worker crashed.'));\n this.loaded = false;\n this.mode = null;\n };\n this.worker = worker;\n return worker;\n }\n\n /** Terminate the worker (if any) and reject every call still waiting on it. */\n private dropWorker(err: Error): void {\n this.worker?.terminate();\n this.worker = null;\n this.calls.rejectAll(err);\n }\n\n private call(\n msg: Record<string, unknown>,\n transfer: Transferable[] = [],\n onProgress?: (f: number) => void,\n ): Promise<WorkerReply> {\n const worker = this.ensureWorker();\n const { id, promise } = this.calls.open(onProgress);\n worker.postMessage({ ...msg, id }, transfer);\n return promise;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as ALL_GENES, b as ALL_TOOLBAR_TOOLS, C as CANVAS_TOOL_IDS,
|
|
1
|
+
export { A as ALL_GENES, b as ALL_TOOLBAR_TOOLS, c as ANNOTATION_IMAGE_MAX_DIM, C as CANVAS_TOOL_IDS, d as CELL_SEGMENTER, e as CHANNEL_HISTOGRAM_API, h as CellposeSegmenterService, i as ChannelHistogramComponent, D as DEFAULT_CATEGORICAL_PALETTE, j as DEFAULT_FALLBACK_PALETTE, k as DEFAULT_MUTED_OPACITY, l as DEFAULT_SAM_MODEL_ID, m as DEFAULT_SPATIAL_VIEW, H as HexColorPickerComponent, I as IMAGE_STATE_PORT, n as INSTANCE_SEGMENTER, o as ImageConverterService, L as LUT_COLORS, M as MISSING_COLOR, p as MultiPolygon, N as NAPARI_DECIMATE_OPTIONS, q as NAPARI_DEFAULT_DECIMATE, s as NO_CATEGORY, t as NO_OBSERVATION, P as PLOT_MODE_CONTEXT, u as PLOT_MODE_SESSION, v as PLOT_TYPE_CONTRIBUTIONS, w as PLOT_TYPE_DESCRIPTORS, x as PREFERENCES_PORT, y as PlotType, z as Polygon, B as ProcessingImage, R as REGION_EDITOR_API, E as REGION_IO_PORT, F as REGION_SOURCE_MIN_CHARS, G as Rectangle, J as Region, K as RegionEditorComponent, O as RoutingVisualizerService, S as SAM_MODELS, Q as SEMANTIC_SEGMENTER, T as SPATIAL_DATA_PORT, U as SPATIAL_WIRE_VERSION, V as ShapeSelection, W as SpatialChartsComponent, X as SpatialControlsComponent, Y as SpatialDataHttpService, Z as SpatialSelectionStore, _ as SupersededError, $ as TILE_ACCESS_PORT, a0 as TOOLBAR_TOOLS, a1 as VISUALIZER, a2 as VIZ_ALERT_TOAST_KEY, a3 as VIZ_CONFIG, a4 as VIZ_TOAST_KEY, a5 as ViewerFeature, a6 as VisualizationModule, a7 as VisualizerComponent, a8 as assertManifestVersion, a9 as benefitsFromGrouping, aa as buildOmicsTraces, ab as capabilitiesOf, ac as clearSamModelCache, ad as contrastWindow, ae as contributedPlotTypeOption, af as countMask, ag as cropImage, ah as datasetFromManifest, ai as decodeColumn, aj as decodeCoords, ak as decodeFeatureVector, al as decodePolygons, am as decodeRadius, an as defaultPresetSet, ao as dialogToolContributions, ap as emptySelection, aq as encodeCategorical, ar as encodeContinuous, as as expandZips, at as findColumnMeta, au as getDefaultSamModelId, g as getOrtWasmBase, av as getPlotTypeDescriptor, aw as getSamModel, ax as hasZarrMetadata, ay as hydrateBounds, az as importFile, aA as isBuiltinPlotType, aB as isCanvasToolId, aC as isCategoricalColumn, aD as isContinuousColumn, aE as isDialogToolContribution, aF as isEmptyPresetSet, aG as isLabelVolumeName, aH as isLittleEndian, aI as isNapari3d, aJ as isNapariIsosurface, aK as isNapariScatter, aL as isNapariScatter3d, aM as isNapariSurface, aN as isNapariVolume, aO as isSamModelReady, aP as isSpatialOmics, aQ as isSpatialOmics3d, aR as isThreeDimensional, aS as isZipName, aT as labelClasses, aU as labelSliceGeoJson, aV as lutFor, aW as markerDiameters, aX as maskToIndices, aY as mutedFromSelection, aZ as omicsLayout, a_ as openAnnotations, a$ as openGeoJsonRegionSource, b0 as openHttpRegionSource, b1 as parsePresetSet, b2 as planRegionImport, b3 as pointInRing, b4 as provideVisualization, b5 as readAnnotationSlices, b6 as readLabelVolume, b7 as readOmeZarrLabels, b8 as readZip, b9 as rendererOwnsWheel, ba as resolveCategoryColors, bb as selectByCategory, bc as selectInRegions, bd as setDefaultSamModel, be as setOrtWasmBase, bf as setSamModelUrls, bg as sortToolContributions, bh as visibleToolContributions } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-a68Hw5rw.mjs';
|
|
2
2
|
import '@angular/core';
|
|
3
3
|
import '@angular/common/http';
|
|
4
4
|
import 'napari-js';
|
|
@@ -6,6 +6,8 @@ import 'rxjs';
|
|
|
6
6
|
import 'rxjs/operators';
|
|
7
7
|
import 'onnxruntime-web';
|
|
8
8
|
import 'napari-js/geometry';
|
|
9
|
+
import 'fast-png';
|
|
10
|
+
import 'fzstd';
|
|
9
11
|
import '@angular/core/rxjs-interop';
|
|
10
12
|
import '@angular/common';
|
|
11
13
|
import '@angular/forms';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jax-data-science-sci-image-visualizer.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"jax-data-science-sci-image-visualizer.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/index.d.ts
CHANGED
|
@@ -50,6 +50,18 @@ export { openGeoJsonRegionSource } from './lib/region-source/worker-region-sourc
|
|
|
50
50
|
export { openHttpRegionSource } from './lib/region-source/http-region-source';
|
|
51
51
|
export type { HttpRegionSourceOptions, WireTileFeature } from './lib/region-source/http-region-source';
|
|
52
52
|
export { REGION_SOURCE_MIN_CHARS } from './lib/region-source/region-source';
|
|
53
|
+
export { isLabelVolumeName, readLabelVolume } from './lib/region-import/label-volume';
|
|
54
|
+
export type { LabelVolume } from './lib/region-import/label-volume';
|
|
55
|
+
export { labelClasses, labelSliceGeoJson } from './lib/region-import/label-classes';
|
|
56
|
+
export type { LabelClasses } from './lib/region-import/label-classes';
|
|
57
|
+
export { importFile, planRegionImport } from './lib/region-import/region-import';
|
|
58
|
+
export type { RegionImportFile, RegionImportPlan } from './lib/region-import/region-import';
|
|
59
|
+
export { ANNOTATION_IMAGE_MAX_DIM, openAnnotations, readAnnotationSlices, } from './lib/region-import/open-annotations';
|
|
60
|
+
export type { AnnotationDecoders, AnnotationSlices, OpenedAnnotations, } from './lib/region-import/open-annotations';
|
|
61
|
+
export { hasZarrMetadata, readOmeZarrLabels } from './lib/region-import/ome-zarr-labels';
|
|
62
|
+
export type { LabelStack, OmeZarrLabels } from './lib/region-import/ome-zarr-labels';
|
|
63
|
+
export { expandZips, isZipName, readZip } from './lib/region-import/zip';
|
|
64
|
+
export type { ZipEntry } from './lib/region-import/zip';
|
|
53
65
|
export type { RegionSource, RegionSourceView, SourceFeature } from './lib/region-source/region-source';
|
|
54
66
|
export type { RegionIndexPage, RegionIndexQuery, RegionIndexRow, RegionSourceInfo, RegionTile, RegionTileKey, TileFeature, } from './lib/region-source/region-tiles';
|
|
55
67
|
export { VisualizationModule } from './lib/visualization.module';
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* `ImageInfo`/`ImageMetadata` classes are pure data holders that satisfy them
|
|
8
8
|
* structurally, so the host keeps passing its own instances unchanged.
|
|
9
9
|
*/
|
|
10
|
+
import type { RegionSource } from '../region-source/region-source';
|
|
10
11
|
/** Per-channel metadata from the server descriptor (issue #76). */
|
|
11
12
|
export interface IChannelInfo {
|
|
12
13
|
name: string;
|
|
@@ -65,6 +66,12 @@ export interface IImageInfo {
|
|
|
65
66
|
* its regions are drawn from the server's level-of-detail tiles instead of being loaded
|
|
66
67
|
* from `roiJsonStr`. On a stack, slice z shows the source's plane z. */
|
|
67
68
|
regionSourceUrl?: string;
|
|
69
|
+
/** The image's own regions, one region source per slice, opened the first time its slice is
|
|
70
|
+
* shown: what an image built from annotations alone carries (see `openAnnotations`: a label
|
|
71
|
+
* volume traced slice by slice, a folder of per-slice GeoJSON). Attached when the image lands,
|
|
72
|
+
* replacing its regions, as an imported label volume or folder is; takes precedence over
|
|
73
|
+
* `roiJsonStrs`, `roiJsonStr` and `regionSourceUrl`. Keyed by slice index. */
|
|
74
|
+
regionSlices?: ReadonlyMap<number, () => Promise<RegionSource>>;
|
|
68
75
|
/** Per-slice, per-channel plane URLs for a SERVERLESS multichannel image
|
|
69
76
|
* (`tiled: false` + `imageMeta[0].channelCount > 1`). `channelUrls[z][c]`
|
|
70
77
|
* is a complete single-band image of channel c at slice z. The OSD simple
|
|
@@ -25,6 +25,30 @@ export interface RegionIoPort {
|
|
|
25
25
|
z: number;
|
|
26
26
|
geoJsonStr: string;
|
|
27
27
|
}[]): Observable<void>;
|
|
28
|
+
/**
|
|
29
|
+
* "Import regions from server": let the user pick region files or a region set in the host's
|
|
30
|
+
* storage (the current project's bucket, say) and hand them over. Emits null when the user
|
|
31
|
+
* cancels. Without it, the Region Editor shows no server import.
|
|
32
|
+
*/
|
|
33
|
+
pickServerRegions?(): Observable<ServerRegionPick | null>;
|
|
28
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Regions picked on the host's server (see {@link RegionIoPort.pickServerRegions}): files it
|
|
37
|
+
* downloaded, imported as the Region Editor imports chosen files (one GeoJSON; a label volume
|
|
38
|
+
* `.nii` / `.nii.gz` / `.nrrd` with its ontology JSON; a folder's per-slice GeoJSON files with its
|
|
39
|
+
* `regionset.json`), or a region set its server serves by level of detail (jit-service's
|
|
40
|
+
* `/regions/{id}/`), read through the host's `HttpClient` without downloading it.
|
|
41
|
+
*/
|
|
42
|
+
export type ServerRegionPick = {
|
|
43
|
+
kind: 'files';
|
|
44
|
+
files: Array<{
|
|
45
|
+
name: string;
|
|
46
|
+
data: Blob | ArrayBuffer | Uint8Array | string;
|
|
47
|
+
}>;
|
|
48
|
+
} | {
|
|
49
|
+
kind: 'source';
|
|
50
|
+
url: string;
|
|
51
|
+
name?: string;
|
|
52
|
+
};
|
|
29
53
|
/** DI token for the host's {@link RegionIoPort}. Required by the region editor. */
|
|
30
54
|
export declare const REGION_IO_PORT: InjectionToken<RegionIoPort>;
|
|
@@ -3,7 +3,7 @@ import { Observable } from 'rxjs';
|
|
|
3
3
|
import { Region } from '../models/region';
|
|
4
4
|
import { ClassPreset, PresetSet } from '../models/class-preset';
|
|
5
5
|
import { IImageMetadata } from './image.contract';
|
|
6
|
-
import type { RegionSourceView } from '../region-source/region-source';
|
|
6
|
+
import type { RegionSource, RegionSourceView } from '../region-source/region-source';
|
|
7
7
|
import type { RegionIndexPage, RegionIndexQuery } from '../region-source/region-tiles';
|
|
8
8
|
import type { RegionStyle } from '../region-overlay/region-style';
|
|
9
9
|
/**
|
|
@@ -87,6 +87,24 @@ export interface IRegionEditorApi {
|
|
|
87
87
|
* and should be imported as regions; rejects when it is not a GeoJSON FeatureCollection.
|
|
88
88
|
*/
|
|
89
89
|
importRegionSource?(geoJsonStr: string): Promise<boolean>;
|
|
90
|
+
/**
|
|
91
|
+
* A ticket for an import about to read or decode its files: an import method given a stale one
|
|
92
|
+
* (another import took a ticket since, or another image was plotted) changes nothing.
|
|
93
|
+
*/
|
|
94
|
+
regionImportTicket?(): number;
|
|
95
|
+
/**
|
|
96
|
+
* Replace the regions and region sources with one source per slice (an imported label volume,
|
|
97
|
+
* or a folder of per-slice region files): openers by slice index, each opened the first time its
|
|
98
|
+
* slice is shown. A single image takes slice 0's. Returns how many slices have one, or null
|
|
99
|
+
* (changing nothing) when `ticket` is stale. A save writes every imported slice.
|
|
100
|
+
*/
|
|
101
|
+
importSliceRegionSources?(openers: ReadonlyMap<number, () => Promise<RegionSource>>, ticket?: number): number | null;
|
|
102
|
+
/**
|
|
103
|
+
* Replace them with one imported source (a region set a server serves): over every slice of a
|
|
104
|
+
* stack when z-indexed, else on the slice shown. Resolves false, changing nothing, when the
|
|
105
|
+
* import went stale while the source opened.
|
|
106
|
+
*/
|
|
107
|
+
importRegionSourceOpener?(open: () => Promise<RegionSource>, ticket?: number): Promise<boolean>;
|
|
90
108
|
/**
|
|
91
109
|
* Drop the slice's region source, for regions imported in its place (a small file replacing a
|
|
92
110
|
* large one). The other slices keep theirs.
|
|
@@ -2,6 +2,8 @@ import { IHistogram } from '../../contracts/channel-histogram-api.contract';
|
|
|
2
2
|
import { SpatialDataset, SpatialObservations } from '../../contracts/spatial-dataset.contract';
|
|
3
3
|
import { SpatialViewState } from '../../contracts/display-types';
|
|
4
4
|
import { SpatialSelectionMask } from '../../spatial/spatial-selection';
|
|
5
|
+
import type { IImageInfo } from '../../contracts/image.contract';
|
|
6
|
+
import { RegionMesh3D } from './napari-volume-region-meshes';
|
|
5
7
|
import { Spatial3dScalars } from './napari-spatial-encoding';
|
|
6
8
|
import { SpatialSceneBase, SpatialSession } from './napari-spatial-scene';
|
|
7
9
|
import type { NapariScene, SceneContext } from './napari-scene';
|
|
@@ -17,6 +19,7 @@ import type { NapariScene, SceneContext } from './napari-scene';
|
|
|
17
19
|
* shape while keeping the selection. The first 3D layer added frames the camera (`fit3d: 'once'`).
|
|
18
20
|
*/
|
|
19
21
|
export declare class Spatial3dScene extends SpatialSceneBase implements NapariScene {
|
|
22
|
+
private readonly info?;
|
|
20
23
|
/** The 3D point cloud. One layer: selection is a per-point alpha on it, not a second. */
|
|
21
24
|
private points;
|
|
22
25
|
private layerKey;
|
|
@@ -58,10 +61,46 @@ export declare class Spatial3dScene extends SpatialSceneBase implements NapariSc
|
|
|
58
61
|
* one; a rebuild that leaves the key alone lets it finish. */
|
|
59
62
|
private readonly densityLoads;
|
|
60
63
|
private readonly geneMapLoads;
|
|
64
|
+
/**
|
|
65
|
+
* The region reconstructions (an atlas's structures, imported onto the reference volume's image
|
|
66
|
+
* stack), drawn by the same {@link VolumeRegionMeshes} as the Volume view's, so a structure is the
|
|
67
|
+
* same surface in both. Placed in the reference volume's box, once it is there and the image the
|
|
68
|
+
* regions were drawn on is that volume's stack (see {@link placeRegionMeshes}).
|
|
69
|
+
*/
|
|
70
|
+
private readonly regionMeshes;
|
|
71
|
+
/** Whether the reference volume lets every region surface through (see setRegionsThroughVolume). */
|
|
72
|
+
private regionsThrough;
|
|
61
73
|
/** The store's invert setting, which the density volumes follow (no other subscription of
|
|
62
74
|
* this scene carries it). */
|
|
63
75
|
private invertSub;
|
|
64
|
-
|
|
76
|
+
/**
|
|
77
|
+
* @param info the image plotted with the cloud: the reference volume's stack (the 2D view's
|
|
78
|
+
* image, see showVolumeAsImage) when the dataset has a volume, which the regions are on
|
|
79
|
+
*/
|
|
80
|
+
constructor(ctx: SceneContext, session: SpatialSession, info?: IImageInfo | undefined);
|
|
81
|
+
/** Show these region reconstructions with the cloud (replacing the ones shown). */
|
|
82
|
+
setRegionMeshes(meshes: readonly RegionMesh3D[]): void;
|
|
83
|
+
setRegionHighlight(keys: readonly string[]): void;
|
|
84
|
+
setRegionHidden(keys: readonly string[]): void;
|
|
85
|
+
/** As the Volume view: the reference volume hides the surfaces behind what it shows, or none. */
|
|
86
|
+
setRegionsThroughVolume(through: boolean): void;
|
|
87
|
+
/**
|
|
88
|
+
* Place the region surfaces in the reference volume's box, as the Volume view places them in its
|
|
89
|
+
* own: image pixel (x, y) is voxel (x, y), slice k is voxel plane k, and the box is the volume's
|
|
90
|
+
* voxels times its voxel size, centred on the origin as napari centres the volume (and the cloud
|
|
91
|
+
* is offset to sit in it). Only when the plotted image IS that volume's stack: regions drawn on
|
|
92
|
+
* another image (a tissue image) have no place in it, and are not shown - nor are surfaces
|
|
93
|
+
* placed in a volume this one replaced.
|
|
94
|
+
*/
|
|
95
|
+
private placeRegionMeshes;
|
|
96
|
+
/**
|
|
97
|
+
* Image px per voxel of the volume the surfaces sit in: 1, the reference volume being the stack's
|
|
98
|
+
* own voxels, as the Volume view's is at full resolution, so both reconstruct a class alike (and
|
|
99
|
+
* share its cached surface). Null while there is no volume stack to place them in.
|
|
100
|
+
*/
|
|
101
|
+
voxelStep(): number | null;
|
|
102
|
+
/** Whether the plotted image is the reference volume's stack: its size and slice count. */
|
|
103
|
+
private isVolumeStack;
|
|
65
104
|
mount(): Promise<void>;
|
|
66
105
|
/** The observations' own z is on screen: there is no plane to step. */
|
|
67
106
|
setZ(): void;
|
|
@@ -54,6 +54,11 @@ export declare class VolumeRegionMeshes {
|
|
|
54
54
|
/** The meshes shown. */
|
|
55
55
|
get shown(): RegionMesh3D[];
|
|
56
56
|
clear(): void;
|
|
57
|
+
/**
|
|
58
|
+
* The volume the surfaces sat in is gone (or no longer theirs): take every surface down, keeping
|
|
59
|
+
* the meshes wanted, so they come back when a frame is set again.
|
|
60
|
+
*/
|
|
61
|
+
clearFrame(): void;
|
|
57
62
|
private sync;
|
|
58
63
|
private add;
|
|
59
64
|
private opacityOf;
|
|
@@ -23,6 +23,8 @@ export interface ViewerOptionsInput {
|
|
|
23
23
|
sliceCount: number;
|
|
24
24
|
/** How many slices the slice cache keeps resident (see SliceCache.maxSlices). */
|
|
25
25
|
maxSlices: number;
|
|
26
|
+
/** Tiled images per slice: the channel count when channels are drawn per channel, else 1. */
|
|
27
|
+
channelImages?: number;
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
28
30
|
* The options object handed to the OpenSeadragon factory. Pure: every value
|
|
@@ -53,6 +53,8 @@ export declare class OpenSeadragonVisualizerService extends BaseStoreVisualizer
|
|
|
53
53
|
private viewer;
|
|
54
54
|
private overlay;
|
|
55
55
|
private scaleBar;
|
|
56
|
+
/** "Image reloading…" while the view fetches its tiles. */
|
|
57
|
+
private tileLoading;
|
|
56
58
|
private descriptor;
|
|
57
59
|
/** Base64 RawFileInfo + current z-slice, kept so setZIndex can rebuild the
|
|
58
60
|
* tile source and swap slices live (stack navigation). */
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** What it needs of the viewer. */
|
|
2
|
+
interface LoadingViewer {
|
|
3
|
+
element: HTMLElement;
|
|
4
|
+
world: {
|
|
5
|
+
getItemCount(): number;
|
|
6
|
+
getItemAt(i: number): unknown;
|
|
7
|
+
addHandler(event: 'add-item' | 'remove-item', handler: (e: {
|
|
8
|
+
item: unknown;
|
|
9
|
+
}) => void): unknown;
|
|
10
|
+
removeHandler(event: 'add-item' | 'remove-item', handler: (e: {
|
|
11
|
+
item: unknown;
|
|
12
|
+
}) => void): unknown;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* "Image reloading…" over the OpenSeadragon view while it fetches the tiles of what it shows, as
|
|
17
|
+
* napari's views show it for their image, cells and transcripts (the same badge). Driven by each
|
|
18
|
+
* world item's own `fully-loaded-change` (an image, a stack slice, a channel), so it covers every
|
|
19
|
+
* tile source alike, and shown only after a short delay, so tiles served from cache never flash it.
|
|
20
|
+
*
|
|
21
|
+
* Only items on screen count (opacity above 0): a hidden channel loads nothing by design (it is
|
|
22
|
+
* not preloaded), so it never becomes fully loaded, and a slice cached or loading in the
|
|
23
|
+
* background is not what the view shows. An item is weighed again when its opacity changes.
|
|
24
|
+
*/
|
|
25
|
+
export declare class OsdTileLoading {
|
|
26
|
+
private readonly viewer;
|
|
27
|
+
private readonly state;
|
|
28
|
+
/** Each item still loading, with the function that un-counts it. */
|
|
29
|
+
private readonly loading;
|
|
30
|
+
private readonly watched;
|
|
31
|
+
private readonly onAdd;
|
|
32
|
+
private readonly onRemove;
|
|
33
|
+
constructor(viewer: LoadingViewer);
|
|
34
|
+
/** The badge's text (shown, or about to be). */
|
|
35
|
+
get text(): string;
|
|
36
|
+
destroy(): void;
|
|
37
|
+
private watch;
|
|
38
|
+
private unwatch;
|
|
39
|
+
/** Whether the item is on screen and still fetching its tiles. */
|
|
40
|
+
private isLoading;
|
|
41
|
+
private set;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
@@ -48,6 +48,18 @@ export interface OsdViewerLike {
|
|
|
48
48
|
/** OSD's own pan/zoom (drag + wheel); region tools switch it off. */
|
|
49
49
|
setMouseNavEnabled(enabled: boolean): unknown;
|
|
50
50
|
isMouseNavEnabled(): boolean;
|
|
51
|
+
/** OSD's tile request queue: the jobs not started yet (see OsdQueuedTileJob). */
|
|
52
|
+
imageLoader?: {
|
|
53
|
+
jobQueue?: unknown[];
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** A tile request waiting in OSD's queue: `abort` (before it starts) only marks the tile as
|
|
57
|
+
* not loading, so it is requested again when it is needed. */
|
|
58
|
+
export interface OsdQueuedTileJob {
|
|
59
|
+
tile?: {
|
|
60
|
+
tiledImage?: unknown;
|
|
61
|
+
};
|
|
62
|
+
abort?: () => void;
|
|
51
63
|
}
|
|
52
64
|
/** A point in any OSD space (viewer-element px, viewport units or image px —
|
|
53
65
|
* the method it comes from says which). */
|
|
@@ -70,6 +82,8 @@ export interface OsdTiledImageLike {
|
|
|
70
82
|
imageToViewportRectangle(r: OpenSeadragon.Rect): OpenSeadragon.Rect;
|
|
71
83
|
viewportToImageRectangle(r: OsdRectLike): OsdRectLike;
|
|
72
84
|
setOpacity(opacity: number): void;
|
|
85
|
+
/** Load tiles even while hidden (opacity 0); without it a hidden item loads nothing. */
|
|
86
|
+
setPreload?(preload: boolean): void;
|
|
73
87
|
/** False while any of the image's tiles are still loading. */
|
|
74
88
|
getFullyLoaded(): boolean;
|
|
75
89
|
/** Re-run the tile pipeline (restore + re-process) for this image's tiles;
|
|
@@ -106,14 +106,22 @@ export declare class SliceCache {
|
|
|
106
106
|
/**
|
|
107
107
|
* Add slice z's channel group: one additively-composited ('lighter' = Fiji
|
|
108
108
|
* "Composite") TiledImage per channel, hidden (opacity 0) until revealed.
|
|
109
|
-
*
|
|
109
|
+
* The visible channels' are preloaded, so the background loader can pre-fill
|
|
110
|
+
* every slice; a hidden channel's loads nothing until it is shown (a 22-channel
|
|
111
|
+
* plate fetched and decoded every channel's tiles, overflowing the tile cache:
|
|
112
|
+
* tiles evicted mid-conversion failed to decode).
|
|
110
113
|
* Cached so a revisit is an instant opacity toggle — no re-fetch, no
|
|
111
114
|
* white-flicker. Mirrors addSlice for the composite path.
|
|
112
115
|
*/
|
|
113
116
|
addChannelSlice(z: number): void;
|
|
114
117
|
/** Reveal slice z's channel group (each visible channel → opacity 1) and
|
|
115
|
-
* hide every other cached slice's group. Instant; no fetch/decode.
|
|
118
|
+
* hide every other cached slice's group. Instant; no fetch/decode. Only the
|
|
119
|
+
* visible channels keep loading (preload): a hidden channel's tiles are not
|
|
120
|
+
* fetched or decoded until it is shown. */
|
|
116
121
|
revealChannelSlice(z: number): void;
|
|
122
|
+
/** Drop the tile requests still waiting in OSD's queue for `items` (channels just hidden), so
|
|
123
|
+
* the visible channels' tiles are not stuck behind them. A request already sent completes. */
|
|
124
|
+
private dropQueuedTiles;
|
|
117
125
|
/** Show the given composite slice (opacity 1) and hide all other cached slices. */
|
|
118
126
|
private showOnlySlice;
|
|
119
127
|
/** Re-tint after a display change: invalidate ONLY the visible slice's
|
|
@@ -154,7 +162,9 @@ export declare class SliceCache {
|
|
|
154
162
|
private sliceCacheHas;
|
|
155
163
|
/** The cached TiledImage(s) for slice z (0–1 for composite, N for multichannel). */
|
|
156
164
|
private sliceGroup;
|
|
157
|
-
/** Whether slice z's tiles are still streaming (any cached image not fully loaded).
|
|
165
|
+
/** Whether slice z's tiles are still streaming (any cached image not fully loaded). A hidden
|
|
166
|
+
* channel loads nothing, so it never becomes fully loaded: only the visible channels count, or
|
|
167
|
+
* the background loader would wait on it forever. */
|
|
158
168
|
private sliceTilesLoading;
|
|
159
169
|
/** Remove slice z's cached image(s) from the world and the active cache. */
|
|
160
170
|
private removeCachedSlice;
|
|
@@ -26,6 +26,8 @@ export declare class RegionEditorComponent implements OnInit, OnDestroy {
|
|
|
26
26
|
*/
|
|
27
27
|
set active(value: boolean);
|
|
28
28
|
private isActive;
|
|
29
|
+
/** The host's HttpClient: a served region set's requests go through it (its auth). */
|
|
30
|
+
private readonly http;
|
|
29
31
|
protected regions: Region[];
|
|
30
32
|
protected selectedRegions: Region[];
|
|
31
33
|
protected showShapeLabel: boolean;
|
|
@@ -348,7 +350,32 @@ export declare class RegionEditorComponent implements OnInit, OnDestroy {
|
|
|
348
350
|
protected importPresets(event: Event): void;
|
|
349
351
|
/** Validate and apply an imported annotation-classes JSON file. */
|
|
350
352
|
private applyImportedPresets;
|
|
353
|
+
/** The files chosen in the import inputs: one GeoJSON, a label volume with its ontology, or a
|
|
354
|
+
* folder of per-slice GeoJSON files. */
|
|
351
355
|
protected importRois(event: Event): void;
|
|
356
|
+
/** Whether the host can pick regions on its server ("Import regions from server"). */
|
|
357
|
+
protected get canImportFromServer(): boolean;
|
|
358
|
+
/** An import from the server is in flight. */
|
|
359
|
+
protected importing: boolean;
|
|
360
|
+
/** "Import regions from server": the host picks files or a served region set; they import as chosen files do. */
|
|
361
|
+
protected importFromServer(): void;
|
|
362
|
+
/**
|
|
363
|
+
* Import `files`: one GeoJSON file as before (its regions, editable); anything else (see
|
|
364
|
+
* readAnnotationSlices) as one region source per slice, replacing the regions and sources of
|
|
365
|
+
* every slice: a label volume or an OME-Zarr label image traced the first time a slice is shown,
|
|
366
|
+
* its classes named (and treed) by its ontology; a folder as one source per slice file; a
|
|
367
|
+
* z-indexed GeoJSON one per plane; a zip of any of these.
|
|
368
|
+
*/
|
|
369
|
+
private importRegionFiles;
|
|
370
|
+
/** A region set the host's server serves (jit-service's /regions/{id}/), read through its HttpClient. */
|
|
371
|
+
private importServedRegionSet;
|
|
372
|
+
/** An import that finished after another image was shown (or another import began): dropped. */
|
|
373
|
+
private importStale;
|
|
374
|
+
/** The table and selection after the regions were replaced by imported sources. */
|
|
375
|
+
private afterSourceImport;
|
|
376
|
+
/** Warn when an imported volume's slices are not the image's size: its outlines would not fit. */
|
|
377
|
+
private warnIfSizeDiffers;
|
|
378
|
+
private importFailed;
|
|
352
379
|
/**
|
|
353
380
|
* Replace the regions with an imported GeoJSON file's, and the slice's region source with them.
|
|
354
381
|
* A large file becomes the slice's region source instead (drawn by level of detail; listed above
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Observable } from 'rxjs';
|
|
2
2
|
import { Region } from '../models/region';
|
|
3
3
|
import { IRegionEditorApi } from '../contracts/region-editor-api.contract';
|
|
4
|
-
import { RegionIoPort } from '../contracts/ports/region-io.port';
|
|
4
|
+
import { RegionIoPort, ServerRegionPick } from '../contracts/ports/region-io.port';
|
|
5
5
|
import * as i0 from "@angular/core";
|
|
6
6
|
/** One slice-file's serialized regions (folder stack, jit-ui#93). */
|
|
7
7
|
export interface SliceGeoJson {
|
|
@@ -67,6 +67,10 @@ export declare class RegionPersistenceService {
|
|
|
67
67
|
* in it.
|
|
68
68
|
*/
|
|
69
69
|
sliceGeoJsons(): Promise<SliceGeoJson[]>;
|
|
70
|
+
/** Whether the host can pick regions on its server (see RegionIoPort.pickServerRegions). */
|
|
71
|
+
canPickServerRegions(): boolean;
|
|
72
|
+
/** Let the user pick regions on the host's server; null when cancelled or unsupported. */
|
|
73
|
+
pickServerRegions(): Observable<ServerRegionPick | null>;
|
|
70
74
|
/** Write per-slice geojsons through the host port. */
|
|
71
75
|
saveSlices(slices: SliceGeoJson[]): Observable<void>;
|
|
72
76
|
/** Read the file chosen in a file input's change event as text, then call
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { RegionClassNode, RegionSourceStyle } from '../region-source/region-tiles';
|
|
2
|
+
import type { LabelVolume } from './label-volume';
|
|
3
|
+
/**
|
|
4
|
+
* The classes of a label volume's values: their names and colours and, when the volume comes
|
|
5
|
+
* with an ontology, their tree. Pure.
|
|
6
|
+
*/
|
|
7
|
+
export interface LabelClasses {
|
|
8
|
+
/** The class of label `value` (> 0); one without an ontology entry is `label <value>`. */
|
|
9
|
+
classOf(value: number): {
|
|
10
|
+
name: string;
|
|
11
|
+
color: string;
|
|
12
|
+
title?: string;
|
|
13
|
+
};
|
|
14
|
+
/** The class tree, when the ontology has one. */
|
|
15
|
+
hierarchy?: RegionClassNode[];
|
|
16
|
+
/** How the set wants to be drawn (an atlas's filled look), when the ontology says. */
|
|
17
|
+
style?: RegionSourceStyle;
|
|
18
|
+
}
|
|
19
|
+
/** The look the Allen atlas region sets carry (see the brain-atlas example's GeoJSON). */
|
|
20
|
+
export declare const ATLAS_STYLE: RegionSourceStyle;
|
|
21
|
+
/**
|
|
22
|
+
* The classes of a label volume from `ontology`, a parsed JSON document:
|
|
23
|
+
*
|
|
24
|
+
* - an **Allen structure graph** (`structure_graph_1.json`: `{ msg: [root] }`, or a node or a
|
|
25
|
+
* list of nodes with `id`, `acronym`, `name`, `color_hex_triplet`, `graph_order` and
|
|
26
|
+
* `children`): label = structure id, class = acronym, its tree the ontology's, drawn as the
|
|
27
|
+
* atlas region sets are;
|
|
28
|
+
* - a **region set description** (a `regionset.json`: `classHierarchy`, optional `style`), whose
|
|
29
|
+
* classes name their label as `value` (or `id`), or with a `labels` map of value → class name.
|
|
30
|
+
*
|
|
31
|
+
* Without one (or with another document), each label is its own class `label <value>`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function labelClasses(ontology?: unknown): LabelClasses;
|
|
34
|
+
/**
|
|
35
|
+
* Slice `z` of `volume` as a GeoJSON FeatureCollection string: one feature per label on the
|
|
36
|
+
* slice, its every piece (with holes) traced along pixel edges, in image pixels with y down;
|
|
37
|
+
* classified by `classes`, which also give the collection its class tree and style.
|
|
38
|
+
*/
|
|
39
|
+
export declare function labelSliceGeoJson(volume: LabelVolume, z: number, classes: LabelClasses): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A label volume (a NIfTI-1 `.nii` / `.nii.gz` or a NRRD `.nrrd` of integer structure ids, an
|
|
3
|
+
* atlas annotation say) read in the browser, sliced as the server opens the matching image:
|
|
4
|
+
*
|
|
5
|
+
* - **NRRD**: a 3D volume is cut along its FIRST axis, each section its third axis wide and its
|
|
6
|
+
* second axis high, as jit-service opens a NRRD volume (`AtlasNrrdReader`: the Allen CCF's 528
|
|
7
|
+
* coronal sections of 456 × 320, anterior first). A 2D NRRD is one slice.
|
|
8
|
+
* - **NIfTI**: along its third axis, each slice its first axis wide and its second axis high, as
|
|
9
|
+
* Bio-Formats reads one (no row flip).
|
|
10
|
+
*
|
|
11
|
+
* Only what label volumes use: a single file (NRRD with attached data, NIfTI-1 `n+1`), integer
|
|
12
|
+
* or integer-valued float samples, either byte order, raw or gzip. Anything else throws rather
|
|
13
|
+
* than be read approximately. Pure: usable from workers.
|
|
14
|
+
*/
|
|
15
|
+
/** A label volume, sliced (see the module comment). Label 0 is background. */
|
|
16
|
+
export interface LabelVolume {
|
|
17
|
+
format: 'nrrd' | 'nifti';
|
|
18
|
+
/** A slice's width and height, in voxels (the image's pixels). */
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
/** Number of slices. */
|
|
22
|
+
slices: number;
|
|
23
|
+
/** Size of a voxel along the slice's x, its y and across slices, in micrometres, when the file
|
|
24
|
+
* says (NRRD `space directions`/`spacings`, micrometres unless `space units` says otherwise;
|
|
25
|
+
* NIfTI `pixdim`, in its `xyzt_units`, millimetres when unset). */
|
|
26
|
+
spacing?: [number, number, number];
|
|
27
|
+
/** Slice `z`'s labels, row-major (`height` rows of `width`). */
|
|
28
|
+
sliceLabels(z: number): Uint32Array;
|
|
29
|
+
}
|
|
30
|
+
/** Decompress gzip bytes (default: the platform's `DecompressionStream`). */
|
|
31
|
+
export type Gunzip = (bytes: Uint8Array) => Promise<Uint8Array>;
|
|
32
|
+
/** Whether a file name is a label volume this reads. */
|
|
33
|
+
export declare function isLabelVolumeName(name: string): boolean;
|
|
34
|
+
/** Read the label volume in `bytes`, named `name` (`.nrrd`, `.nii`, `.nii.gz`). */
|
|
35
|
+
export declare function readLabelVolume(name: string, bytes: Uint8Array, gunzip?: Gunzip): Promise<LabelVolume>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A label image of an OME-Zarr store (`labels/<name>/`, NGFF 0.4 on zarr v2 or 0.5 on zarr v3),
|
|
3
|
+
* read in the browser from the store's files (a folder chosen, or a zip's members): its full
|
|
4
|
+
* resolution level, sliced along z, with the classes its `image-label` metadata names and colours.
|
|
5
|
+
*
|
|
6
|
+
* Chunks are decoded when a slice that needs them is read, so a store is never decoded whole.
|
|
7
|
+
* Only what label images use: C-order chunks of integer (or integer-valued float) samples, through
|
|
8
|
+
* the codecs {@link decodeChunk} reads, a missing chunk being the fill value. Sharded or transposed
|
|
9
|
+
* arrays, F-order and filters throw rather than be read wrongly. Pure: usable from workers.
|
|
10
|
+
*/
|
|
11
|
+
import { LabelClasses } from './label-classes';
|
|
12
|
+
import type { RegionImportFile } from './region-import';
|
|
13
|
+
import { Inflate } from './zarr-codecs';
|
|
14
|
+
/** A stack of label slices read on demand. Label 0 is background. */
|
|
15
|
+
export interface LabelStack {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
slices: number;
|
|
19
|
+
/** Size of a voxel, x, y, z, in micrometres, when the source says. */
|
|
20
|
+
spacing?: [number, number, number];
|
|
21
|
+
/** Slice `z`'s labels, row-major (`height` rows of `width`). */
|
|
22
|
+
labels(z: number): Promise<Uint32Array>;
|
|
23
|
+
}
|
|
24
|
+
/** An OME-Zarr label image: its name, its slices, its classes. */
|
|
25
|
+
export interface OmeZarrLabels {
|
|
26
|
+
name: string;
|
|
27
|
+
stack: LabelStack;
|
|
28
|
+
classes: LabelClasses;
|
|
29
|
+
}
|
|
30
|
+
/** Whether `files` hold a zarr store (any zarr metadata among them). */
|
|
31
|
+
export declare function hasZarrMetadata(files: readonly RegionImportFile[]): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* The first label image of the zarr store among `files` (the order `labels/`'s listing gives,
|
|
34
|
+
* else the first found), or null when the store has none.
|
|
35
|
+
*/
|
|
36
|
+
export declare function readOmeZarrLabels(files: readonly RegionImportFile[], inflate?: Inflate): Promise<OmeZarrLabels | null>;
|