@jax-data-science/sci-image-visualizer 0.10.0 → 0.11.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 +4 -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/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 +147 -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 +194 -0
- package/esm2022/lib/region-import/region-import.mjs +71 -0
- package/esm2022/lib/routing-visualizer.service.mjs +93 -1
- package/esm2022/lib/store/region-source-scope.mjs +19 -4
- package/esm2022/lib/store/region-store.service.mjs +64 -14
- 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-BELcx0jv.mjs} +1049 -188
- package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BELcx0jv.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-DEDSm_NL.mjs} +2 -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-DEDSm_NL.mjs.map} +1 -1
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +1 -1
- package/index.d.ts +6 -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/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 +26 -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 +31 -0
- package/lib/region-import/region-import.d.ts +46 -0
- package/lib/routing-visualizer.service.d.ts +30 -0
- 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 +1 -1
- package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BKQhpbHv.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
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-BELcx0jv.mjs';
|
|
4
4
|
import '@angular/core';
|
|
5
5
|
import '@angular/common/http';
|
|
6
6
|
import 'rxjs';
|
|
@@ -235,4 +235,4 @@ class OnnxSamSession {
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
export { OnnxSamSession };
|
|
238
|
-
//# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-
|
|
238
|
+
//# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-DEDSm_NL.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-DEDSm_NL.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, c as CELL_SEGMENTER, d as CHANNEL_HISTOGRAM_API, e as CellposeSegmenterService, h as ChannelHistogramComponent, D as DEFAULT_CATEGORICAL_PALETTE, i as DEFAULT_FALLBACK_PALETTE, j as DEFAULT_MUTED_OPACITY, k as DEFAULT_SAM_MODEL_ID, l as DEFAULT_SPATIAL_VIEW, H as HexColorPickerComponent, I as IMAGE_STATE_PORT, m as INSTANCE_SEGMENTER, n as ImageConverterService, L as LUT_COLORS, M as MISSING_COLOR, o as MultiPolygon, N as NAPARI_DECIMATE_OPTIONS, p as NAPARI_DEFAULT_DECIMATE, q as NO_CATEGORY, s as NO_OBSERVATION, P as PLOT_MODE_CONTEXT, t as PLOT_MODE_SESSION, u as PLOT_TYPE_CONTRIBUTIONS, v as PLOT_TYPE_DESCRIPTORS, w as PREFERENCES_PORT, x as PlotType, y as Polygon, z as ProcessingImage, R as REGION_EDITOR_API, B as REGION_IO_PORT, E as REGION_SOURCE_MIN_CHARS, F as Rectangle, G as Region, J as RegionEditorComponent, K as RoutingVisualizerService, S as SAM_MODELS, O as SEMANTIC_SEGMENTER, Q as SPATIAL_DATA_PORT, T as SPATIAL_WIRE_VERSION, U as ShapeSelection, V as SpatialChartsComponent, W as SpatialControlsComponent, X as SpatialDataHttpService, Y as SpatialSelectionStore, Z as SupersededError, _ as TILE_ACCESS_PORT, $ as TOOLBAR_TOOLS, a0 as VISUALIZER, a1 as VIZ_ALERT_TOAST_KEY, a2 as VIZ_CONFIG, a3 as VIZ_TOAST_KEY, a4 as ViewerFeature, a5 as VisualizationModule, a6 as VisualizerComponent, a7 as assertManifestVersion, a8 as benefitsFromGrouping, a9 as buildOmicsTraces, aa as capabilitiesOf, ab as clearSamModelCache, ac as contrastWindow, ad as contributedPlotTypeOption, ae as countMask, af as cropImage, ag as datasetFromManifest, ah as decodeColumn, ai as decodeCoords, aj as decodeFeatureVector, ak as decodePolygons, al as decodeRadius, am as defaultPresetSet, an as dialogToolContributions, ao as emptySelection, ap as encodeCategorical, aq as encodeContinuous, ar as findColumnMeta, as as getDefaultSamModelId, g as getOrtWasmBase, at as getPlotTypeDescriptor, au as getSamModel, av as hydrateBounds, aw as
|
|
1
|
+
export { A as ALL_GENES, b as ALL_TOOLBAR_TOOLS, C as CANVAS_TOOL_IDS, c as CELL_SEGMENTER, d as CHANNEL_HISTOGRAM_API, e as CellposeSegmenterService, h as ChannelHistogramComponent, D as DEFAULT_CATEGORICAL_PALETTE, i as DEFAULT_FALLBACK_PALETTE, j as DEFAULT_MUTED_OPACITY, k as DEFAULT_SAM_MODEL_ID, l as DEFAULT_SPATIAL_VIEW, H as HexColorPickerComponent, I as IMAGE_STATE_PORT, m as INSTANCE_SEGMENTER, n as ImageConverterService, L as LUT_COLORS, M as MISSING_COLOR, o as MultiPolygon, N as NAPARI_DECIMATE_OPTIONS, p as NAPARI_DEFAULT_DECIMATE, q as NO_CATEGORY, s as NO_OBSERVATION, P as PLOT_MODE_CONTEXT, t as PLOT_MODE_SESSION, u as PLOT_TYPE_CONTRIBUTIONS, v as PLOT_TYPE_DESCRIPTORS, w as PREFERENCES_PORT, x as PlotType, y as Polygon, z as ProcessingImage, R as REGION_EDITOR_API, B as REGION_IO_PORT, E as REGION_SOURCE_MIN_CHARS, F as Rectangle, G as Region, J as RegionEditorComponent, K as RoutingVisualizerService, S as SAM_MODELS, O as SEMANTIC_SEGMENTER, Q as SPATIAL_DATA_PORT, T as SPATIAL_WIRE_VERSION, U as ShapeSelection, V as SpatialChartsComponent, W as SpatialControlsComponent, X as SpatialDataHttpService, Y as SpatialSelectionStore, Z as SupersededError, _ as TILE_ACCESS_PORT, $ as TOOLBAR_TOOLS, a0 as VISUALIZER, a1 as VIZ_ALERT_TOAST_KEY, a2 as VIZ_CONFIG, a3 as VIZ_TOAST_KEY, a4 as ViewerFeature, a5 as VisualizationModule, a6 as VisualizerComponent, a7 as assertManifestVersion, a8 as benefitsFromGrouping, a9 as buildOmicsTraces, aa as capabilitiesOf, ab as clearSamModelCache, ac as contrastWindow, ad as contributedPlotTypeOption, ae as countMask, af as cropImage, ag as datasetFromManifest, ah as decodeColumn, ai as decodeCoords, aj as decodeFeatureVector, ak as decodePolygons, al as decodeRadius, am as defaultPresetSet, an as dialogToolContributions, ao as emptySelection, ap as encodeCategorical, aq as encodeContinuous, ar as findColumnMeta, as as getDefaultSamModelId, g as getOrtWasmBase, at as getPlotTypeDescriptor, au as getSamModel, av as hydrateBounds, aw as importFile, ax as isBuiltinPlotType, ay as isCanvasToolId, az as isCategoricalColumn, aA as isContinuousColumn, aB as isDialogToolContribution, aC as isEmptyPresetSet, aD as isLabelVolumeName, aE as isLittleEndian, aF as isNapari3d, aG as isNapariIsosurface, aH as isNapariScatter, aI as isNapariScatter3d, aJ as isNapariSurface, aK as isNapariVolume, aL as isSamModelReady, aM as isSpatialOmics, aN as isSpatialOmics3d, aO as isThreeDimensional, aP as labelClasses, aQ as labelSliceGeoJson, aR as lutFor, aS as markerDiameters, aT as maskToIndices, aU as mutedFromSelection, aV as omicsLayout, aW as openGeoJsonRegionSource, aX as openHttpRegionSource, aY as parsePresetSet, aZ as planRegionImport, a_ as pointInRing, a$ as provideVisualization, b0 as readLabelVolume, b1 as rendererOwnsWheel, b2 as resolveCategoryColors, b3 as selectByCategory, b4 as selectInRegions, b5 as setDefaultSamModel, b6 as setOrtWasmBase, b7 as setSamModelUrls, b8 as sortToolContributions, b9 as visibleToolContributions } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BELcx0jv.mjs';
|
|
2
2
|
import '@angular/core';
|
|
3
3
|
import '@angular/common/http';
|
|
4
4
|
import 'napari-js';
|
package/index.d.ts
CHANGED
|
@@ -50,6 +50,12 @@ 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';
|
|
53
59
|
export type { RegionSource, RegionSourceView, SourceFeature } from './lib/region-source/region-source';
|
|
54
60
|
export type { RegionIndexPage, RegionIndexQuery, RegionIndexRow, RegionSourceInfo, RegionTile, RegionTileKey, TileFeature, } from './lib/region-source/region-tiles';
|
|
55
61
|
export { VisualizationModule } from './lib/visualization.module';
|
|
@@ -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.
|
|
@@ -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,31 @@ 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` (see planRegionImport): one GeoJSON file as before; a label volume as one
|
|
364
|
+
* region source per slice, traced from it the first time the slice is shown, its classes named
|
|
365
|
+
* (and treed) by the ontology chosen with it; a folder as one source per slice file. Either
|
|
366
|
+
* replaces the regions and sources of every slice.
|
|
367
|
+
*/
|
|
368
|
+
private importRegionFiles;
|
|
369
|
+
/** A region set the host's server serves (jit-service's /regions/{id}/), read through its HttpClient. */
|
|
370
|
+
private importServedRegionSet;
|
|
371
|
+
/** An import that finished after another image was shown (or another import began): dropped. */
|
|
372
|
+
private importStale;
|
|
373
|
+
/** The table and selection after the regions were replaced by imported sources. */
|
|
374
|
+
private afterSourceImport;
|
|
375
|
+
/** Warn when an imported volume's slices are not the image's size: its outlines would not fit. */
|
|
376
|
+
private warnIfSizeDiffers;
|
|
377
|
+
private importFailed;
|
|
352
378
|
/**
|
|
353
379
|
* Replace the regions with an imported GeoJSON file's, and the slice's region source with them.
|
|
354
380
|
* 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,31 @@
|
|
|
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
|
+
/** Slice `z`'s labels, row-major (`height` rows of `width`). */
|
|
24
|
+
sliceLabels(z: number): Uint32Array;
|
|
25
|
+
}
|
|
26
|
+
/** Decompress gzip bytes (default: the platform's `DecompressionStream`). */
|
|
27
|
+
export type Gunzip = (bytes: Uint8Array) => Promise<Uint8Array>;
|
|
28
|
+
/** Whether a file name is a label volume this reads. */
|
|
29
|
+
export declare function isLabelVolumeName(name: string): boolean;
|
|
30
|
+
/** Read the label volume in `bytes`, named `name` (`.nrrd`, `.nii`, `.nii.gz`). */
|
|
31
|
+
export declare function readLabelVolume(name: string, bytes: Uint8Array, gunzip?: Gunzip): Promise<LabelVolume>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** A file to import regions from: one the user chose, or one the host's server sent. */
|
|
2
|
+
export interface RegionImportFile {
|
|
3
|
+
name: string;
|
|
4
|
+
text(): Promise<string>;
|
|
5
|
+
bytes(): Promise<Uint8Array>;
|
|
6
|
+
}
|
|
7
|
+
/** A file's contents as the host may hand them over (see `ServerRegionPick`). */
|
|
8
|
+
export type RegionImportData = Blob | ArrayBuffer | Uint8Array | string;
|
|
9
|
+
/** `data`, named `name`, as an import file. A `File`'s own name is used when `name` is omitted. */
|
|
10
|
+
export declare function importFile(data: RegionImportData, name?: string): RegionImportFile;
|
|
11
|
+
/** What a set of chosen files imports as. */
|
|
12
|
+
export type RegionImportPlan =
|
|
13
|
+
/** A label volume, its classes named by an ontology (or a region set description) beside it. */
|
|
14
|
+
{
|
|
15
|
+
kind: 'volume';
|
|
16
|
+
volume: RegionImportFile;
|
|
17
|
+
ontology: RegionImportFile | null;
|
|
18
|
+
}
|
|
19
|
+
/** A folder of per-slice region files, in slice order, with the set's `regionset.json`. */
|
|
20
|
+
| {
|
|
21
|
+
kind: 'folder';
|
|
22
|
+
slices: RegionImportFile[];
|
|
23
|
+
regionSet: RegionImportFile | null;
|
|
24
|
+
}
|
|
25
|
+
/** One region file. */
|
|
26
|
+
| {
|
|
27
|
+
kind: 'single';
|
|
28
|
+
file: RegionImportFile;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* What `files` import as: a label volume (`.nii`, `.nii.gz`, `.nrrd`) with the ontology JSON chosen
|
|
32
|
+
* with it (a `regionset.json`, else an Allen `structure_graph*.json`, else any `.json`); several
|
|
33
|
+
* region files (a folder of per-slice `.geojson`, in natural name order, its `regionset.json`
|
|
34
|
+
* apart); or one region file (`.geojson` or `.json`). Null when none of them is one.
|
|
35
|
+
*/
|
|
36
|
+
export declare function planRegionImport(files: readonly RegionImportFile[]): RegionImportPlan | null;
|
|
37
|
+
/** "slice_2" before "slice_10": runs of digits compared as numbers. */
|
|
38
|
+
export declare function naturalCompare(a: string, b: string): number;
|
|
39
|
+
/**
|
|
40
|
+
* A folder slice's GeoJSON with the set's class tree and style (its `regionset.json`) as the
|
|
41
|
+
* collection's own members, unless the file has its own: what a served folder's slice carries.
|
|
42
|
+
*/
|
|
43
|
+
export declare function folderSliceGeoJson(file: RegionImportFile, regionSet: {
|
|
44
|
+
classHierarchy?: unknown;
|
|
45
|
+
style?: unknown;
|
|
46
|
+
} | null): Promise<string>;
|
|
@@ -114,6 +114,8 @@ export declare class RoutingVisualizerService implements IVisualizer, IRegionEdi
|
|
|
114
114
|
private readonly sectionSubs;
|
|
115
115
|
/** The image info and screen height of the last plot (what the slice pane mounts). */
|
|
116
116
|
private plottedInfo;
|
|
117
|
+
/** The current region import (see regionImportTicket): its id and the image it was taken for. */
|
|
118
|
+
private importTicket;
|
|
117
119
|
private plottedHeight;
|
|
118
120
|
constructor(plotly: PlotlyService, osd: OpenSeadragonVisualizerService, napari: NapariVisualizerService, store: VisualizerStore, config: VizConfig, regionStore: RegionStore, selectionStore: SpatialSelectionStore, intensity: IntensityProfileService, spatialData?: SpatialDataPort | null);
|
|
119
121
|
/** A class's colour: its classification colour, else its region source's, else a default. */
|
|
@@ -265,6 +267,34 @@ export declare class RoutingVisualizerService implements IVisualizer, IRegionEdi
|
|
|
265
267
|
attachRegionSource(source: RegionSource | RegionSourceOpener, slices?: RegionSourceSlices): void;
|
|
266
268
|
importRegionSource(geoJsonStr: string): Promise<boolean>;
|
|
267
269
|
dropRegionSource(): Promise<void>;
|
|
270
|
+
/**
|
|
271
|
+
* A ticket for a region import that is about to read or decode its files: the import methods
|
|
272
|
+
* given it change nothing once it is stale — another import took a ticket since, another image
|
|
273
|
+
* was plotted, or the service was destroyed — so an import that finishes late never lands on an
|
|
274
|
+
* image it was not started for.
|
|
275
|
+
*/
|
|
276
|
+
regionImportTicket(): number;
|
|
277
|
+
/** Whether `ticket` is still the current import's, on the image it was taken for. */
|
|
278
|
+
private importTicketValid;
|
|
279
|
+
private importImageKey;
|
|
280
|
+
/**
|
|
281
|
+
* Replace the image's regions and region sources with one source per slice, as an imported label
|
|
282
|
+
* volume or folder of per-slice region files gives them: `openers` by slice index, each opened
|
|
283
|
+
* the first time its slice is shown. A single image takes slice 0's. Returns how many slices
|
|
284
|
+
* have a source, or null (changing nothing) when `ticket` is stale (see
|
|
285
|
+
* {@link regionImportTicket}). The sources are marked imported, so a save writes every slice.
|
|
286
|
+
*/
|
|
287
|
+
importSliceRegionSources(openers: ReadonlyMap<number, RegionSourceOpener>, ticket?: number): number | null;
|
|
288
|
+
/**
|
|
289
|
+
* Replace the image's regions and region sources with one imported source (a region set the
|
|
290
|
+
* host's server serves): over every slice of a stack when it is z-indexed, else on the slice
|
|
291
|
+
* shown, as {@link applyImageRois} lays out an image's own. Resolves false, changing nothing
|
|
292
|
+
* (and disposing the source), when the import went stale while it opened: another import, or
|
|
293
|
+
* another image (see {@link regionImportTicket}; one is taken here when `ticket` is not given).
|
|
294
|
+
*/
|
|
295
|
+
importRegionSourceOpener(open: RegionSourceOpener, ticket?: number): Promise<boolean>;
|
|
296
|
+
/** Empty every slice of a stack (its regions and undo history) for regions imported over it. */
|
|
297
|
+
private enterImportedStack;
|
|
268
298
|
getRegionSourceView$(): Observable<RegionSourceView | null>;
|
|
269
299
|
getRegionSourceIndex(query: Omit<RegionIndexQuery, 'exclude'>): Promise<RegionIndexPage>;
|
|
270
300
|
selectSourceFeatures(ids: readonly string[], additive?: boolean): Promise<number[]>;
|
|
@@ -31,8 +31,12 @@ export declare class RegionSourceScope {
|
|
|
31
31
|
private readonly openOrder;
|
|
32
32
|
/** The entry of the slice on screen: kept open. */
|
|
33
33
|
private shown;
|
|
34
|
-
/**
|
|
35
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Attach `open` to `image` for `slices`, replacing (and closing) what was there. `imported` names
|
|
36
|
+
* the slices it brings that a save must write (see {@link importedSlices}): for a source of one
|
|
37
|
+
* slice, that slice when non-empty; for a `'planes'` source, the slices its planes cover.
|
|
38
|
+
*/
|
|
39
|
+
attach(image: string, open: RegionSourceOpener, slices: RegionSourceSlices, imported?: readonly number[]): void;
|
|
36
40
|
/** Drop every source of `image` (closing the open ones) and what was taken from them. */
|
|
37
41
|
detachImage(image: string): void;
|
|
38
42
|
/** Drop the source of one slice of `image`. */
|
|
@@ -56,6 +60,8 @@ export declare class RegionSourceScope {
|
|
|
56
60
|
taken(image: string | undefined, slice: number): ReadonlySet<string>;
|
|
57
61
|
/** Every id taken from any slice of `image` (a `'planes'` source's ids are unique across them). */
|
|
58
62
|
allTaken(image: string | undefined): string[];
|
|
63
|
+
/** The slices of `image` whose source was imported (see {@link attach}), in order. */
|
|
64
|
+
importedSlices(image: string | undefined): number[];
|
|
59
65
|
/** The slices of `image` with anything taken from their source. */
|
|
60
66
|
slicesWithTaken(image: string | undefined): number[];
|
|
61
67
|
/** Replace the ids taken from `slice`'s source (copy-on-write: `ids` is kept as given). */
|
|
@@ -338,8 +338,13 @@ export declare class RegionStore implements IRegionStore, IRegionEditApi, IRegio
|
|
|
338
338
|
* source was closed to save memory). `slices` says which slice of the current image it
|
|
339
339
|
* serves: one (default: the slice shown; plane 0 of the source), or `'planes'`, every slice z
|
|
340
340
|
* from plane z of a z-indexed source. Replaces the source those slices had.
|
|
341
|
+
*
|
|
342
|
+
* `imported` marks a source brought in over the image (an imported volume, folder or served set)
|
|
343
|
+
* rather than the image's own regions: it is on no file yet, so a save writes its slices though
|
|
344
|
+
* nothing was taken from it — `true` for a source of one slice, the slices its planes cover for a
|
|
345
|
+
* `'planes'` source. The image's own sources are not marked: loading them changes nothing to save.
|
|
341
346
|
*/
|
|
342
|
-
attachRegionSource(source: RegionSource | RegionSourceOpener, slices?: RegionSourceSlices): void;
|
|
347
|
+
attachRegionSource(source: RegionSource | RegionSourceOpener, slices?: RegionSourceSlices, imported?: boolean | readonly number[]): void;
|
|
343
348
|
/** Drop the current image's region sources (closing them) and what was taken from them. */
|
|
344
349
|
detachRegionSources(): void;
|
|
345
350
|
/**
|
|
@@ -439,10 +444,15 @@ export declare class RegionStore implements IRegionStore, IRegionEditApi, IRegio
|
|
|
439
444
|
*/
|
|
440
445
|
getRegionSourceIndex(query: Omit<RegionIndexQuery, 'exclude'>): Promise<RegionIndexPage>;
|
|
441
446
|
/**
|
|
442
|
-
* `regions` as a GeoJSON FeatureCollection string merged with the region
|
|
443
|
-
* the
|
|
444
|
-
* {@link getGeoJsonString}.
|
|
445
|
-
*
|
|
447
|
+
* `regions` as a GeoJSON FeatureCollection string merged with the region sources behind them:
|
|
448
|
+
* the sources' features that were not taken (or deleted), then `regions`. Without a source,
|
|
449
|
+
* {@link getGeoJsonString}.
|
|
450
|
+
*
|
|
451
|
+
* `slice` names one slice-file of a folder stack (a per-slice-file save): its source's features,
|
|
452
|
+
* on the file's own plane — of a z-indexed source over every slice, only that slice's plane.
|
|
453
|
+
* Without it, the export is the image's: on a stack saved as one file, every slice's source
|
|
454
|
+
* (each feature on its slice's plane, `geometry.plane.z`) and a z-indexed source's every plane;
|
|
455
|
+
* otherwise the source of the slice shown.
|
|
446
456
|
*/
|
|
447
457
|
getGeoJsonWithSource(regions: Region[], slice?: number): Promise<string>;
|
|
448
458
|
/**
|