@jax-data-science/sci-image-visualizer 0.15.0 → 0.17.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/lib/contracts/display-types.mjs +2 -1
- package/esm2022/lib/implementations/napari-js/napari-loading-state.mjs +1 -1
- package/esm2022/lib/implementations/napari-js/napari-scene.mjs +1 -1
- package/esm2022/lib/implementations/napari-js/napari-spatial-2d-scene.mjs +10 -8
- package/esm2022/lib/implementations/napari-js/napari-spatial-3d-scene.mjs +226 -39
- package/esm2022/lib/implementations/napari-js/napari-spatial-3d-transcripts.mjs +269 -0
- package/esm2022/lib/implementations/napari-js/napari-spatial-encoding.mjs +11 -4
- package/esm2022/lib/implementations/napari-js/napari-spatial-tiles.mjs +6 -7
- package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +2 -1
- package/esm2022/lib/implementations/napari-js/napari-volume-region-meshes.mjs +33 -8
- package/esm2022/lib/implementations/napari-js/napari-volume-scene.mjs +6 -2
- package/esm2022/lib/implementations/napari-js/spatial-tiles/cell-layers.mjs +10 -6
- package/esm2022/lib/implementations/napari-js/spatial-tiles/transcript-layers.mjs +9 -5
- package/esm2022/lib/spatial/spatial-controls.facade.mjs +7 -4
- package/esm2022/lib/spatial/spatial-encoding.mjs +18 -1
- package/esm2022/lib/spatial-controls/spatial-charts/spatial-charts.component.mjs +6 -2
- package/esm2022/lib/spatial-controls/spatial-controls.component.mjs +7 -3
- package/esm2022/lib/spatial-controls/spatial-gene-style/spatial-gene-style.component.mjs +7 -3
- package/esm2022/lib/spatial-controls/spatial-gene-tree/spatial-gene-tree.component.mjs +67 -19
- package/esm2022/lib/spatial-controls/spatial-groups-panel/spatial-groups-panel.component.mjs +61 -8
- package/esm2022/lib/spatial-controls/spatial-observations-panel/spatial-observations-panel.component.mjs +3 -3
- package/esm2022/lib/toolbar/toolbar.component.mjs +13 -4
- package/esm2022/lib/toolbar/view-controls/view-controls.component.mjs +7 -7
- package/fesm2022/{jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-qUeMuBb0.mjs → jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DHrHO2ay.mjs} +795 -165
- package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DHrHO2ay.mjs.map +1 -0
- package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-U1ODcfYD.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-CKEUwNta.mjs} +2 -2
- package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-U1ODcfYD.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-CKEUwNta.mjs.map} +1 -1
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +1 -1
- package/lib/contracts/display-types.d.ts +9 -2
- package/lib/implementations/napari-js/napari-loading-state.d.ts +1 -1
- package/lib/implementations/napari-js/napari-scene.d.ts +2 -0
- package/lib/implementations/napari-js/napari-spatial-2d-scene.d.ts +3 -3
- package/lib/implementations/napari-js/napari-spatial-3d-scene.d.ts +82 -9
- package/lib/implementations/napari-js/napari-spatial-3d-transcripts.d.ts +62 -0
- package/lib/implementations/napari-js/napari-spatial-encoding.d.ts +9 -2
- package/lib/implementations/napari-js/napari-spatial-tiles.d.ts +5 -6
- package/lib/implementations/napari-js/napari-volume-region-meshes.d.ts +16 -3
- package/lib/implementations/napari-js/spatial-tiles/cell-layers.d.ts +1 -1
- package/lib/spatial/spatial-encoding.d.ts +10 -0
- package/lib/spatial-controls/spatial-controls.component.d.ts +2 -0
- package/lib/spatial-controls/spatial-gene-style/spatial-gene-style.component.d.ts +3 -1
- package/lib/spatial-controls/spatial-gene-tree/spatial-gene-tree.component.d.ts +25 -0
- package/lib/spatial-controls/spatial-groups-panel/spatial-groups-panel.component.d.ts +14 -3
- package/lib/toolbar/toolbar.component.d.ts +6 -0
- package/lib/toolbar/view-controls/view-controls.component.d.ts +3 -3
- package/package.json +2 -2
- package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-qUeMuBb0.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-DHrHO2ay.mjs';
|
|
4
4
|
import '@angular/core';
|
|
5
5
|
import '@angular/common/http';
|
|
6
6
|
import 'rxjs';
|
|
@@ -238,4 +238,4 @@ class OnnxSamSession {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
export { OnnxSamSession };
|
|
241
|
-
//# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-
|
|
241
|
+
//# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-CKEUwNta.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jax-data-science-sci-image-visualizer-onnx-sam-session-U1ODcfYD.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-CKEUwNta.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 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, n as DEFAULT_VISIBLE_CHANNELS, H as HexColorPickerComponent, I as IMAGE_STATE_PORT, o as INSTANCE_SEGMENTER, p as ImageConverterService, L as LUT_COLORS, M as MISSING_COLOR, q as MultiPolygon, N as NAPARI_DECIMATE_OPTIONS, s as NAPARI_DEFAULT_DECIMATE, t as NO_CATEGORY, u as NO_OBSERVATION, P as PLOT_MODE_CONTEXT, v as PLOT_MODE_SESSION, w as PLOT_TYPE_CONTRIBUTIONS, x as PLOT_TYPE_DESCRIPTORS, y as PREFERENCES_PORT, z as PlotType, B as Polygon, E as ProcessingImage, R as REGION_EDITOR_API, F as REGION_IO_PORT, G as REGION_SOURCE_MIN_CHARS, J as Rectangle, K as Region, O as RegionEditorComponent, Q as RoutingVisualizerService, S as SAM_MODELS, T as SEMANTIC_SEGMENTER, U as SPATIAL_DATA_PORT, V as SPATIAL_WIRE_VERSION, W as ShapeSelection, X as SpatialChartsComponent, Y as SpatialControlsComponent, Z as SpatialDataHttpService, _ as SpatialSelectionStore, $ as SupersededError, a0 as TILE_ACCESS_PORT, a1 as TOOLBAR_TOOLS, a2 as VISUALIZER, a3 as VIZ_ALERT_TOAST_KEY, a4 as VIZ_CONFIG, a5 as VIZ_TOAST_KEY, a6 as ViewerFeature, a7 as VisualizationModule, a8 as VisualizerComponent, a9 as assertManifestVersion, aa as benefitsFromGrouping, ab as buildOmicsTraces, ac as capabilitiesOf, ad as clearSamModelCache, ae as contrastWindow, af as contributedPlotTypeOption, ag as countMask, ah as cropImage, ai as datasetFromManifest, aj as decodeColumn, ak as decodeCoords, al as decodeFeatureVector, am as decodePolygons, an as decodeRadius, ao as defaultChannelVisible, ap as defaultPresetSet, aq as dialogToolContributions, ar as emptySelection, as as encodeCategorical, at as encodeContinuous, au as expandZips, av as findColumnMeta, aw as getDefaultSamModelId, g as getOrtWasmBase, ax as getPlotTypeDescriptor, ay as getSamModel, az as hasZarrMetadata, aA as hydrateBounds, aB as importFile, aC as isBuiltinPlotType, aD as isCanvasToolId, aE as isCategoricalColumn, aF as isContinuousColumn, aG as isDialogToolContribution, aH as isEmptyPresetSet, aI as isLabelVolumeName, aJ as isLittleEndian, aK as isNapari3d, aL as isNapariIsosurface, aM as isNapariScatter, aN as isNapariScatter3d, aO as isNapariSurface, aP as isNapariVolume, aQ as isSamModelReady, aR as isSpatialOmics, aS as isSpatialOmics3d, aT as isThreeDimensional, aU as isZipName, aV as labelClasses, aW as labelSliceGeoJson, aX as lutFor, aY as markerDiameters, aZ as maskToIndices, a_ as mutedFromSelection, a$ as omicsLayout, b0 as openAnnotations, b1 as openGeoJsonRegionSource, b2 as openHttpRegionSource, b3 as parsePresetSet, b4 as planRegionImport, b5 as pointInRing, b6 as provideVisualization, b7 as readAnnotationSlices, b8 as readLabelVolume, b9 as readOmeZarrLabels, ba as readZip, bb as rendererOwnsWheel, bc as resolveCategoryColors, bd as selectByCategory, be as selectInRegions, bf as setDefaultSamModel, bg as setOrtWasmBase, bh as setSamModelUrls, bi as sortToolContributions, bj as visibleToolContributions } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-
|
|
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, n as DEFAULT_VISIBLE_CHANNELS, H as HexColorPickerComponent, I as IMAGE_STATE_PORT, o as INSTANCE_SEGMENTER, p as ImageConverterService, L as LUT_COLORS, M as MISSING_COLOR, q as MultiPolygon, N as NAPARI_DECIMATE_OPTIONS, s as NAPARI_DEFAULT_DECIMATE, t as NO_CATEGORY, u as NO_OBSERVATION, P as PLOT_MODE_CONTEXT, v as PLOT_MODE_SESSION, w as PLOT_TYPE_CONTRIBUTIONS, x as PLOT_TYPE_DESCRIPTORS, y as PREFERENCES_PORT, z as PlotType, B as Polygon, E as ProcessingImage, R as REGION_EDITOR_API, F as REGION_IO_PORT, G as REGION_SOURCE_MIN_CHARS, J as Rectangle, K as Region, O as RegionEditorComponent, Q as RoutingVisualizerService, S as SAM_MODELS, T as SEMANTIC_SEGMENTER, U as SPATIAL_DATA_PORT, V as SPATIAL_WIRE_VERSION, W as ShapeSelection, X as SpatialChartsComponent, Y as SpatialControlsComponent, Z as SpatialDataHttpService, _ as SpatialSelectionStore, $ as SupersededError, a0 as TILE_ACCESS_PORT, a1 as TOOLBAR_TOOLS, a2 as VISUALIZER, a3 as VIZ_ALERT_TOAST_KEY, a4 as VIZ_CONFIG, a5 as VIZ_TOAST_KEY, a6 as ViewerFeature, a7 as VisualizationModule, a8 as VisualizerComponent, a9 as assertManifestVersion, aa as benefitsFromGrouping, ab as buildOmicsTraces, ac as capabilitiesOf, ad as clearSamModelCache, ae as contrastWindow, af as contributedPlotTypeOption, ag as countMask, ah as cropImage, ai as datasetFromManifest, aj as decodeColumn, ak as decodeCoords, al as decodeFeatureVector, am as decodePolygons, an as decodeRadius, ao as defaultChannelVisible, ap as defaultPresetSet, aq as dialogToolContributions, ar as emptySelection, as as encodeCategorical, at as encodeContinuous, au as expandZips, av as findColumnMeta, aw as getDefaultSamModelId, g as getOrtWasmBase, ax as getPlotTypeDescriptor, ay as getSamModel, az as hasZarrMetadata, aA as hydrateBounds, aB as importFile, aC as isBuiltinPlotType, aD as isCanvasToolId, aE as isCategoricalColumn, aF as isContinuousColumn, aG as isDialogToolContribution, aH as isEmptyPresetSet, aI as isLabelVolumeName, aJ as isLittleEndian, aK as isNapari3d, aL as isNapariIsosurface, aM as isNapariScatter, aN as isNapariScatter3d, aO as isNapariSurface, aP as isNapariVolume, aQ as isSamModelReady, aR as isSpatialOmics, aS as isSpatialOmics3d, aT as isThreeDimensional, aU as isZipName, aV as labelClasses, aW as labelSliceGeoJson, aX as lutFor, aY as markerDiameters, aZ as maskToIndices, a_ as mutedFromSelection, a$ as omicsLayout, b0 as openAnnotations, b1 as openGeoJsonRegionSource, b2 as openHttpRegionSource, b3 as parsePresetSet, b4 as planRegionImport, b5 as pointInRing, b6 as provideVisualization, b7 as readAnnotationSlices, b8 as readLabelVolume, b9 as readOmeZarrLabels, ba as readZip, bb as rendererOwnsWheel, bc as resolveCategoryColors, bd as selectByCategory, be as selectInRegions, bf as setDefaultSamModel, bg as setOrtWasmBase, bh as setSamModelUrls, bi as sortToolContributions, bj as visibleToolContributions } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DHrHO2ay.mjs';
|
|
2
2
|
import '@angular/core';
|
|
3
3
|
import '@angular/common/http';
|
|
4
4
|
import 'napari-js';
|
|
@@ -130,8 +130,8 @@ export interface SpatialViewState {
|
|
|
130
130
|
* setting for a backdrop and a useless one for the measurement.
|
|
131
131
|
*/
|
|
132
132
|
volumeOpacity: number;
|
|
133
|
-
/** Draw the observation markers — the 3D cloud, or the 2D circles. In 2D they
|
|
134
|
-
*
|
|
133
|
+
/** Draw the observation markers — the 3D cloud, or the 2D circles. In 2D they are drawn at
|
|
134
|
+
* every zoom, never under a couple of screen pixels, with any cell outlines over them. */
|
|
135
135
|
showPoints: boolean;
|
|
136
136
|
/**
|
|
137
137
|
* Restrict the cloud to ONE imaged section, by index into the dataset's sampled
|
|
@@ -267,6 +267,13 @@ export interface SpatialViewState {
|
|
|
267
267
|
transcriptHiddenGenes: string[];
|
|
268
268
|
/** Per-gene marker colour (`#rrggbb`); genes not listed take the palette by position. */
|
|
269
269
|
transcriptGeneColors: Record<string, string>;
|
|
270
|
+
/**
|
|
271
|
+
* Colours the user chose for groups of a categorical column (`#rrggbb`), by column name and
|
|
272
|
+
* then category: they override the column's own and the palette's, wherever that grouping
|
|
273
|
+
* colours anything (cells, markers, transcripts by cell type, legends, charts). Groups not
|
|
274
|
+
* listed keep their colour. Cleared when the dataset changes.
|
|
275
|
+
*/
|
|
276
|
+
groupColors: Record<string, Record<string, string>>;
|
|
270
277
|
/** Named gene groups (e.g. marker genes per cell type) for the selected-genes tree. */
|
|
271
278
|
transcriptGeneGroups: {
|
|
272
279
|
name: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** What the service itself counts on the badge; the spatial tile layers report their own names. */
|
|
2
|
-
export type LoadingSource = 'Image' | 'Observations';
|
|
2
|
+
export type LoadingSource = 'Image' | 'Observations' | 'Transcripts';
|
|
3
3
|
/**
|
|
4
4
|
* "x reloading…" at the bottom of the canvas, aggregated over everything that loads into one
|
|
5
5
|
* scene: image tiles (the tile client), observation colourings (the spatial scenes) and the
|
|
@@ -71,6 +71,8 @@ export interface NapariSettings {
|
|
|
71
71
|
surfaceWireframe: boolean;
|
|
72
72
|
/** The volume's user Z-height factor (1 = the volume's natural proportions). */
|
|
73
73
|
volumeZScale: number;
|
|
74
|
+
/** The 3D spatial view's Z stretch (1 = the data's true proportions). */
|
|
75
|
+
spatialZScale: number;
|
|
74
76
|
}
|
|
75
77
|
/** The stack-loading flag and progress the host shows while frames load. */
|
|
76
78
|
export interface StackProgress {
|
|
@@ -61,9 +61,9 @@ export declare class Spatial2dScene extends SpatialSceneBase implements NapariSc
|
|
|
61
61
|
*/
|
|
62
62
|
protected pickObservation(clientX: number, clientY: number, slackPx: number): number;
|
|
63
63
|
/**
|
|
64
|
-
* Whether the markers are drawn:
|
|
65
|
-
*
|
|
66
|
-
*
|
|
64
|
+
* Whether the markers are drawn: whenever the user wants them, at every zoom. Cell outlines come
|
|
65
|
+
* and go with the zoom (their level of detail follows it) and are drawn over the markers; the
|
|
66
|
+
* observations themselves never disappear with them.
|
|
67
67
|
*/
|
|
68
68
|
private pointsVisible;
|
|
69
69
|
/**
|
|
@@ -1,17 +1,44 @@
|
|
|
1
|
+
import type { AxisLabelSpec } from 'napari-js/overlays';
|
|
1
2
|
import { IHistogram } from '../../contracts/channel-histogram-api.contract';
|
|
3
|
+
import { ISurface3dControls } from '../../contracts/visualizer.contract';
|
|
2
4
|
import { SpatialDataset, SpatialObservations } from '../../contracts/spatial-dataset.contract';
|
|
3
5
|
import { SpatialViewState } from '../../contracts/display-types';
|
|
4
6
|
import { SpatialSelectionMask } from '../../spatial/spatial-selection';
|
|
5
7
|
import type { IImageInfo } from '../../contracts/image.contract';
|
|
6
8
|
import { RegionMesh3D } from './napari-volume-region-meshes';
|
|
7
9
|
import { Spatial3dScalars } from './napari-spatial-encoding';
|
|
10
|
+
import { Box3 } from './napari-axes-gizmo';
|
|
8
11
|
import { SpatialSceneBase, SpatialSession } from './napari-spatial-scene';
|
|
9
12
|
import type { NapariScene, SceneContext } from './napari-scene';
|
|
13
|
+
/** The least Z stretch: a tenth of the true depth. */
|
|
14
|
+
export declare const SPATIAL_Z_SCALE_MIN = 0.1;
|
|
15
|
+
/**
|
|
16
|
+
* The most a box's Z may be stretched: until it is twice as deep as it is wide, so even a section
|
|
17
|
+
* microns thick and millimetres wide can be stood up into a block — but never less than the
|
|
18
|
+
* Volume view's 10x, nor more than {@link SPATIAL_Z_SCALE_CEILING}.
|
|
19
|
+
*/
|
|
20
|
+
export declare function spatialZScaleMax(box: Box3): number;
|
|
21
|
+
/**
|
|
22
|
+
* The Z handle's drag (px) per doubling: the Volume view's 180 px, less when the range is wider,
|
|
23
|
+
* so going from the true depth to the most stretch is about the same drag (~540 px) either way.
|
|
24
|
+
*/
|
|
25
|
+
export declare function spatialZPixelsPerDouble(maxScale: number): number;
|
|
26
|
+
/**
|
|
27
|
+
* The X/Y/Z axis-end labels for a box (as drawn: Z stretched by `zScale`), its true lengths in µm
|
|
28
|
+
* when the dataset declares `micronsPerUnit`, else in its own units. A stretched Z says by how much,
|
|
29
|
+
* so its length is never read off the drawing.
|
|
30
|
+
*/
|
|
31
|
+
export declare function spatialAxesLabels(drawn: Box3, zScale: number, micronsPerUnit?: number): AxisLabelSpec[];
|
|
10
32
|
/**
|
|
11
33
|
* SPATIAL_OMICS_3D (review Appendix B, cluster K): observations as a 3D point cloud under the
|
|
12
34
|
* orbit camera, with the dataset's reference volume around it, per-cluster density volumes and the
|
|
13
35
|
* 3D gene map.
|
|
14
36
|
*
|
|
37
|
+
* Everything sits in one box centred on the world origin (see {@link ensureFrame}): the reference
|
|
38
|
+
* volume's, else the density grid's. The Volume view's axes gizmo and Z-height handle are drawn on
|
|
39
|
+
* it, and the handle stretches Z: the cloud through its `scale3d`, every volume through its voxel
|
|
40
|
+
* size, so nothing is re-uploaded while dragging (see {@link setZScale}).
|
|
41
|
+
*
|
|
15
42
|
* Thinner than the 2D scene: there is no tissue image to render (a registered volume like the
|
|
16
43
|
* Allen CCF has no single reference plane), so no readback and no navigator; the 3D scale bar
|
|
17
44
|
* follows the dataset (see {@link installScaleBar}). The region tools work in SCREEN space through
|
|
@@ -46,6 +73,14 @@ export declare class Spatial3dScene extends SpatialSceneBase implements NapariSc
|
|
|
46
73
|
private geneMapKey;
|
|
47
74
|
/** Offset applied to observation coordinates to sit them in the volume's box. */
|
|
48
75
|
private origin;
|
|
76
|
+
/** The box everything sits in, before the Z stretch, centred on the origin (see ensureFrame). */
|
|
77
|
+
private box;
|
|
78
|
+
private boxKey;
|
|
79
|
+
/** The axes gizmo on {@link box} and the handle that stretches its Z (see setZScale). */
|
|
80
|
+
private gizmo;
|
|
81
|
+
private zHandle;
|
|
82
|
+
/** Each volume layer's own voxel size, before the Z stretch. */
|
|
83
|
+
private readonly baseVoxels;
|
|
49
84
|
/**
|
|
50
85
|
* napari-js's depth-aware pick over the cloud, and the projection the ROI selection reads. It
|
|
51
86
|
* owns the caching policy: re-project lazily, on the first pick after the camera, the canvas or
|
|
@@ -68,6 +103,8 @@ export declare class Spatial3dScene extends SpatialSceneBase implements NapariSc
|
|
|
68
103
|
* regions were drawn on is that volume's stack (see {@link placeRegionMeshes}).
|
|
69
104
|
*/
|
|
70
105
|
private readonly regionMeshes;
|
|
106
|
+
/** The transcripts of the genes the Transcripts panel draws, at their own z (see Spatial3dTranscripts). */
|
|
107
|
+
private readonly transcripts3d;
|
|
71
108
|
/** Whether the reference volume lets every region surface through (see setRegionsThroughVolume). */
|
|
72
109
|
private regionsThrough;
|
|
73
110
|
/** The store's invert setting, which the density volumes follow (no other subscription of
|
|
@@ -106,6 +143,25 @@ export declare class Spatial3dScene extends SpatialSceneBase implements NapariSc
|
|
|
106
143
|
setZ(): void;
|
|
107
144
|
/** A cloud has no intensity distribution for the histogram pane. */
|
|
108
145
|
histogram(): IHistogram | null;
|
|
146
|
+
/** The toolbar's 3D controls, as the Volume view's: drag mode, re-framing and the axes. */
|
|
147
|
+
surface3dControls(): ISurface3dControls | null;
|
|
148
|
+
/**
|
|
149
|
+
* Stretch Z by `factor` (1 = the data's true proportions), as the Z handle drags it: the cloud
|
|
150
|
+
* through its `scale3d`, each volume through its voxel size, the axes and the region surfaces
|
|
151
|
+
* with them — all about the world origin, which the box is centred on, so they stay aligned.
|
|
152
|
+
* Nothing is re-uploaded. Kept in the settings, so a re-plot keeps it.
|
|
153
|
+
*/
|
|
154
|
+
setZScale(factor: number): void;
|
|
155
|
+
/** The Z stretch in effect: the setting, within what this box allows. */
|
|
156
|
+
zScale(): number;
|
|
157
|
+
private applyZScale;
|
|
158
|
+
/** `box` with its Z stretched as drawn. */
|
|
159
|
+
private drawnBox;
|
|
160
|
+
/**
|
|
161
|
+
* Add a volume layer with its Z stretched as the rest of the scene is, remembering its own
|
|
162
|
+
* voxel size so a later stretch can be applied to it.
|
|
163
|
+
*/
|
|
164
|
+
private addVolume;
|
|
109
165
|
/**
|
|
110
166
|
* Every observation's position on the canvas under the current 3D camera (for the ROI
|
|
111
167
|
* selection), indexed by observation with NaN for anything not drawn. Null before the cloud
|
|
@@ -224,18 +280,35 @@ export declare class Spatial3dScene extends SpatialSceneBase implements NapariSc
|
|
|
224
280
|
*/
|
|
225
281
|
private densityGroups;
|
|
226
282
|
/**
|
|
227
|
-
* Add (or keep) the dataset's reference volume
|
|
228
|
-
*
|
|
283
|
+
* Add (or keep) the dataset's reference volume. The offset that sits the observations inside
|
|
284
|
+
* it is {@link ensureFrame}'s.
|
|
229
285
|
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* the volume's own frame with its near corner at the coordinate origin. So the two only line
|
|
233
|
-
* up if the POINTS move — by half the box — which is what {@link origin} is.
|
|
234
|
-
*
|
|
235
|
-
* A failed or absent volume is not fatal: the cloud renders on its own, at its own
|
|
236
|
-
* coordinates, and the camera frames the points instead.
|
|
286
|
+
* A failed or absent volume is not fatal: the cloud renders on its own, in the same box, and
|
|
287
|
+
* the camera frames the points instead.
|
|
237
288
|
*/
|
|
238
289
|
private ensureVolume;
|
|
290
|
+
/**
|
|
291
|
+
* The box everything sits in, centred on the world origin, and the offset that sits the
|
|
292
|
+
* observations in it; with the axes gizmo and the Z handle on it.
|
|
293
|
+
*
|
|
294
|
+
* `VolumeLayer` has no translate: napari-js maps a volume's unit cube to a world box centred on
|
|
295
|
+
* the origin, sized `dims x voxelSize`. The observations, by contract, are in the reference
|
|
296
|
+
* volume's frame with its near corner at the coordinate origin, and the density grid is measured
|
|
297
|
+
* from the coordinate origin the same way (`densityGrid`), so its box is the reference volume's
|
|
298
|
+
* when there is one. So the POINTS move by half that box — {@link origin} — and every volume, the
|
|
299
|
+
* reference, the densities and the gene map, lines up with them and with each other. Without a
|
|
300
|
+
* reference volume this centres the cloud too, which the axes and the Z stretch need: both work
|
|
301
|
+
* about the origin.
|
|
302
|
+
*/
|
|
303
|
+
private ensureFrame;
|
|
304
|
+
/**
|
|
305
|
+
* The Volume view's axes gizmo and Z-height handle, on {@link box}. The handle floats past the
|
|
306
|
+
* top of the Z axis (the box's min-XY corner, where the blue axis and its label are), by a share
|
|
307
|
+
* of the box's largest side rather than of its depth, so it clears the label even while a thin
|
|
308
|
+
* section is drawn flat.
|
|
309
|
+
*/
|
|
310
|
+
private installAxes;
|
|
311
|
+
private removeAxes;
|
|
239
312
|
/** Drop every layer of the cloud (the dataset went away, or has no z). */
|
|
240
313
|
private removeLayers;
|
|
241
314
|
/**
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Points3DLayer, Viewer } from 'napari-js';
|
|
2
|
+
import { type SpatialDataPort } from '../../contracts/ports/spatial-data.port';
|
|
3
|
+
import type { SpatialViewState } from '../../contracts/display-types';
|
|
4
|
+
import { type SpatialDataset } from '../../contracts/spatial-dataset.contract';
|
|
5
|
+
/** Most transcripts drawn in the 3D view: past it, every k-th one is drawn (and the console says so). */
|
|
6
|
+
export declare const TRANSCRIPTS_3D_MAX = 2000000;
|
|
7
|
+
/** Most level-0 tiles read for the 3D view: a dataset needing more is too large to draw whole. */
|
|
8
|
+
export declare const TRANSCRIPTS_3D_MAX_TILES = 4096;
|
|
9
|
+
/** A transcript's marker in the 3D view, in screen pixels at a transcript scale of 1. */
|
|
10
|
+
export declare const TRANSCRIPT_3D_BASE_SIZE = 2;
|
|
11
|
+
/** What the 3D transcripts need of the scene: where the cloud sits and how Z is stretched. */
|
|
12
|
+
export interface Transcripts3dFrame {
|
|
13
|
+
/** Offset applied to observation coordinates to sit them in the scene's box. */
|
|
14
|
+
origin: readonly [number, number, number];
|
|
15
|
+
zScale: number;
|
|
16
|
+
}
|
|
17
|
+
/** The genes a view draws in 3D, as the tile query takes them, or null when it draws none. */
|
|
18
|
+
export declare function transcripts3dGenes(dataset: SpatialDataset, view: SpatialViewState): {
|
|
19
|
+
query: string[];
|
|
20
|
+
allGenes: boolean;
|
|
21
|
+
} | null;
|
|
22
|
+
/**
|
|
23
|
+
* Each loaded transcript's colour, packed RGBA, as the 2D markers colour them by gene: a gene's
|
|
24
|
+
* chosen colour, else the palette's by its slot (the selection's order, or with "All genes" the
|
|
25
|
+
* dataset's gene code).
|
|
26
|
+
*/
|
|
27
|
+
export declare function transcripts3dColors(view: SpatialViewState, names: readonly string[] | null, gene: Uint16Array, count: number): Float32Array;
|
|
28
|
+
/**
|
|
29
|
+
* How the panel's "Colour by" applies in 3D, as it does to the 2D markers (`colorViewFor`): by
|
|
30
|
+
* cell type when the dataset has one (else by gene), by cluster for the selected genes (with "All
|
|
31
|
+
* genes" a cluster is its gene), by gene otherwise.
|
|
32
|
+
*/
|
|
33
|
+
export declare function transcripts3dColorBy(dataset: SpatialDataset, view: SpatialViewState, allGenes: boolean): 'cellType' | 'cluster' | 'gene';
|
|
34
|
+
/**
|
|
35
|
+
* The transcripts of the 3D spatial view: every molecule of the genes the Transcripts panel draws
|
|
36
|
+
* (its selected genes, or "All genes"), at its own x, y and z, in the cloud's box and Z stretch.
|
|
37
|
+
*
|
|
38
|
+
* The 2D view reads transcripts by level of detail for the camera; a 3D orbit has no such window,
|
|
39
|
+
* so this reads the whole level 0 once per gene selection (a few hundred thousand molecules for a
|
|
40
|
+
* MERFISH section) and only restyles after that. Drawn only for a dataset whose transcripts carry z.
|
|
41
|
+
*/
|
|
42
|
+
export declare class Spatial3dTranscripts {
|
|
43
|
+
private readonly viewer;
|
|
44
|
+
private readonly begin;
|
|
45
|
+
private layer;
|
|
46
|
+
/** What {@link layer} was loaded from (dataset, genes, quality), and styled with. */
|
|
47
|
+
private loadKey;
|
|
48
|
+
private styleKey;
|
|
49
|
+
private loaded;
|
|
50
|
+
private readonly loads;
|
|
51
|
+
constructor(viewer: Viewer, begin: (label: 'Transcripts') => () => void);
|
|
52
|
+
/** The layer drawn, if any (for tests and the scene's Z stretch). */
|
|
53
|
+
get drawn(): Points3DLayer | null;
|
|
54
|
+
/** Draw what `view` asks for, loading the molecules when the genes changed. */
|
|
55
|
+
sync(dataset: SpatialDataset, view: SpatialViewState, port: SpatialDataPort | null, frame: Transcripts3dFrame): Promise<void>;
|
|
56
|
+
/** Follow the scene's Z stretch (nothing is re-uploaded). */
|
|
57
|
+
setZScale(s: number): void;
|
|
58
|
+
/** Take the layer down and forget what it showed. */
|
|
59
|
+
remove(): void;
|
|
60
|
+
private clearLayer;
|
|
61
|
+
private draw;
|
|
62
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { Colormap } from 'napari-js';
|
|
|
2
2
|
import { Rgb } from '../../contracts/colormap-lut';
|
|
3
3
|
import { CategoricalColumn } from '../../contracts/spatial-dataset.contract';
|
|
4
4
|
import { SpatialViewState } from '../../contracts/display-types';
|
|
5
|
-
import { type RGBA } from '../../spatial/spatial-encoding';
|
|
5
|
+
import { GroupColorOverrides, type RGBA } from '../../spatial/spatial-encoding';
|
|
6
6
|
import { SpatialSelectionMask } from '../../spatial/spatial-selection';
|
|
7
7
|
/**
|
|
8
8
|
* The spatial scenes' pure colour and grouping math: what the 2D markers, the 3D cloud and the
|
|
@@ -16,6 +16,13 @@ export declare const SPATIAL_FALLBACK_RADIUS = 4;
|
|
|
16
16
|
* 40 µm/px template, so drawing them to scale would put every cell a fifth of a pixel wide
|
|
17
17
|
* and the section would come up empty. The point-size control scales up from this floor. */
|
|
18
18
|
export declare const SPATIAL_SLICE_MIN_DIAMETER_PX = 1.5;
|
|
19
|
+
/**
|
|
20
|
+
* Smallest drawn marker diameter in the 2D view, in SCREEN pixels, at any zoom. A marker's size is
|
|
21
|
+
* the cell's own (data units), so zoomed out over a whole Cytos plate or Xenium section every cell
|
|
22
|
+
* fell below a pixel and the observations vanished; with the floor they stay a dense field of
|
|
23
|
+
* dots, coloured as they are, at every zoom (the GPU applies it per frame: no rebuild on zoom).
|
|
24
|
+
*/
|
|
25
|
+
export declare const SPATIAL_MARKER_MIN_SCREEN_PX = 2;
|
|
19
26
|
/** Longest side of the gene-map raster, in field pixels. A gene map is a smooth
|
|
20
27
|
* field read as territory, so it gains nothing from matching a 2 Gpx slide's
|
|
21
28
|
* resolution — and the estimate costs one pass over this many pixels. */
|
|
@@ -116,4 +123,4 @@ export declare function totalDensityGroup(selection: SpatialSelectionMask): Dens
|
|
|
116
123
|
* {@link DENSITY_MAX_CLUSTERS}, each with its legend colour — restricted to the selection when
|
|
117
124
|
* there is one, so "select a region, check the box" answers which clusters live there.
|
|
118
125
|
*/
|
|
119
|
-
export declare function rankDensityGroups(columnName: string, column: CategoricalColumn, count: number, selection: SpatialSelectionMask): DensityGroup[];
|
|
126
|
+
export declare function rankDensityGroups(columnName: string, column: CategoricalColumn, count: number, selection: SpatialSelectionMask, groupColors?: GroupColorOverrides | null): DensityGroup[];
|
|
@@ -48,11 +48,10 @@ export interface SpatialTileHost {
|
|
|
48
48
|
* -----------
|
|
49
49
|
* Cell fill, cell outline, nucleus outline, the transcript density, then transcripts on top
|
|
50
50
|
* ({@link TILE_LAYER_ORDER}); a plan restores the order once, after all its groups are in
|
|
51
|
-
* place. The service's observation markers sit under all of them
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* the order after the service re-adds its markers.
|
|
51
|
+
* place. The service's observation markers sit under all of them, drawn at every zoom: once
|
|
52
|
+
* cells are big enough to outline, their outlines go over their markers. The density goes
|
|
53
|
+
* OVER the markers on purpose: under 10^5 dots it would only show in the gaps between cells.
|
|
54
|
+
* {@link afterObservations} restores the order after the service re-adds its markers.
|
|
56
55
|
*
|
|
57
56
|
* ONE LAYER PER GROUP
|
|
58
57
|
* -------------------
|
|
@@ -95,7 +94,7 @@ export declare class NapariSpatialTileLayers {
|
|
|
95
94
|
hoverAt(wx: number, wy: number, radiusWorld: number, onDetails: (lines: string[]) => void): string[] | null;
|
|
96
95
|
/** Whether `layer` is one of the overlays drawn here (density, cells, transcripts). */
|
|
97
96
|
owns(layer: Layer): boolean;
|
|
98
|
-
/** True while outlines are on screen
|
|
97
|
+
/** True while outlines are on screen. */
|
|
99
98
|
get outlinesShown(): boolean;
|
|
100
99
|
/** The density window in use and the densest bin, for the panel's threshold control. */
|
|
101
100
|
get densityStats(): DensityStats | null;
|
|
@@ -16,6 +16,12 @@ export interface VolumeFrame {
|
|
|
16
16
|
size: [number, number, number];
|
|
17
17
|
/** The world box (centred on the origin), Z as currently stretched. */
|
|
18
18
|
world: [number, number, number];
|
|
19
|
+
/**
|
|
20
|
+
* How much of {@link world}'s depth is the view's Z stretch (default 1). The region surfaces keep
|
|
21
|
+
* their geometry in the unstretched box and take the stretch as a GPU scale (`scale3d`), so
|
|
22
|
+
* dragging the Z handle re-uploads no mesh.
|
|
23
|
+
*/
|
|
24
|
+
zScale?: number;
|
|
19
25
|
}
|
|
20
26
|
/** Opacity of a region's surface in the volume. */
|
|
21
27
|
export declare const REGION_MESH_OPACITY = 0.55;
|
|
@@ -26,8 +32,10 @@ export declare const REGION_MESH_DIMMED_OPACITY = 0.12;
|
|
|
26
32
|
/**
|
|
27
33
|
* The region reconstructions shown in a volume view: one translucent surface per region, in its
|
|
28
34
|
* colour, placed in the volume's world box (pixel x, y and slice z mapped onto it the way the
|
|
29
|
-
* volume maps its voxels)
|
|
30
|
-
*
|
|
35
|
+
* volume maps its voxels). The box's Z stretch is the surfaces' `scale3d` (a uniform), so a stretch
|
|
36
|
+
* alone changes no geometry; a new box re-places them. A surface is updated in place (`setData`,
|
|
37
|
+
* `color`, `scale3d`) rather than added again, and picked by napari-js (`pickSurface`), which
|
|
38
|
+
* picks it where it is drawn.
|
|
31
39
|
*
|
|
32
40
|
* Highlighting is focus + context: the highlighted surfaces turn near opaque and the others fade
|
|
33
41
|
* to a ghost, so the selection stands out while staying in place among its neighbours.
|
|
@@ -43,8 +51,13 @@ export declare class VolumeRegionMeshes {
|
|
|
43
51
|
constructor(viewer: Viewer);
|
|
44
52
|
/** Show exactly `meshes` (adding, keeping or removing surfaces). */
|
|
45
53
|
set(meshes: readonly RegionMesh3D[]): void;
|
|
46
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Follow the volume's frame. A new box (it opened, another volume) re-places every surface; a Z
|
|
56
|
+
* stretch of the same box only sets their `scale3d`, so nothing is re-uploaded while dragging.
|
|
57
|
+
*/
|
|
47
58
|
setFrame(frame: VolumeFrame): void;
|
|
59
|
+
/** The surfaces' GPU scale: the frame's Z stretch. */
|
|
60
|
+
private scale3d;
|
|
48
61
|
/** Highlight the surfaces of these keys (none: every surface as usual). */
|
|
49
62
|
setHighlight(keys: Iterable<string>): void;
|
|
50
63
|
/** Hide the surfaces of these keys (they are kept, so showing them again is instant). */
|
|
@@ -40,7 +40,7 @@ export declare class CellLayers {
|
|
|
40
40
|
* value, and a selection or opacity change recolours with the vector unchanged (SPATIAL-12). */
|
|
41
41
|
private readonly contrastWindows;
|
|
42
42
|
constructor(port: SpatialDataPort, groups: OrderedLayerGroups<TileGroup>, lookup: CategoricalLookup, host: CellLayersHost);
|
|
43
|
-
/** True while outlines are on screen
|
|
43
|
+
/** True while outlines are on screen. */
|
|
44
44
|
get outlinesShown(): boolean;
|
|
45
45
|
/** The viewer went away (its layers with it): the outlines are no longer shown. */
|
|
46
46
|
detached(): void;
|
|
@@ -73,6 +73,16 @@ export declare function parseHex(hex: string): Rgb;
|
|
|
73
73
|
* 40-cluster column still gets stable, distinct-ish colours.
|
|
74
74
|
*/
|
|
75
75
|
export declare function resolveCategoryColors(meta: CategoricalColumnMeta, palette?: string[]): string[];
|
|
76
|
+
/** The user's group colours: by column name, then category (see `SpatialViewState.groupColors`). */
|
|
77
|
+
export type GroupColorOverrides = Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
78
|
+
/**
|
|
79
|
+
* Categorical column `column`'s colours ({@link resolveCategoryColors}) with the user's choices
|
|
80
|
+
* for its groups over them. Every place a grouping is coloured goes through this, so a group
|
|
81
|
+
* recoloured in the list is that colour on the cells, the markers, the legend and the charts.
|
|
82
|
+
*/
|
|
83
|
+
export declare function categoryColorsFor(column: string, meta: CategoricalColumnMeta, overrides?: GroupColorOverrides | null): string[];
|
|
84
|
+
/** What a cache keyed on a grouping's colours must include of the user's choices for it. */
|
|
85
|
+
export declare function groupColorsKey(column: string | null, overrides?: GroupColorOverrides | null): string;
|
|
76
86
|
/** Categorical column → flat per-point RGBA (length `4 * codes.length`). */
|
|
77
87
|
export declare function encodeCategorical(codes: Uint16Array, opts: CategoricalEncodingOptions): Float32Array;
|
|
78
88
|
/** Continuous column / gene vector → flat per-point RGBA. */
|
|
@@ -139,6 +139,8 @@ export declare class SpatialControlsComponent implements OnInit {
|
|
|
139
139
|
protected get hasCells(): boolean;
|
|
140
140
|
/** Transcripts on offer: tiles to draw, or a density map. */
|
|
141
141
|
protected get hasTranscripts(): boolean;
|
|
142
|
+
/** Whether the 3D cloud can draw the transcripts too: they carry z (a MERFISH volume's do). */
|
|
143
|
+
protected get hasTranscripts3d(): boolean;
|
|
142
144
|
protected onShowImage(on: boolean): void;
|
|
143
145
|
protected onShowAnnotations(on: boolean): void;
|
|
144
146
|
/** Which collapsible sections are open. Per dialog instance, not persisted. */
|
|
@@ -17,6 +17,8 @@ export declare class SpatialGeneStyleComponent {
|
|
|
17
17
|
glyph: TranscriptGlyphName;
|
|
18
18
|
/** A note under the icons, e.g. where they show; none when null. */
|
|
19
19
|
iconHint: string | null;
|
|
20
|
+
/** False for a colour alone (a cell group, or every gene drawn with one icon): no icon grid. */
|
|
21
|
+
showIcons: boolean;
|
|
20
22
|
readonly colorChange: EventEmitter<string>;
|
|
21
23
|
readonly glyphChange: EventEmitter<TranscriptGlyphName>;
|
|
22
24
|
/** Back to the gene's default icon and colour. */
|
|
@@ -26,5 +28,5 @@ export declare class SpatialGeneStyleComponent {
|
|
|
26
28
|
/** A typed hex colour, accepted as `#rrggbb` or `rrggbb`; anything else is ignored. */
|
|
27
29
|
protected onHex(text: string): void;
|
|
28
30
|
static ɵfac: i0.ɵɵFactoryDeclaration<SpatialGeneStyleComponent, never>;
|
|
29
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<SpatialGeneStyleComponent, "spatial-gene-style", never, { "gene": { "alias": "gene"; "required": false; }; "what": { "alias": "what"; "required": false; }; "color": { "alias": "color"; "required": false; }; "glyph": { "alias": "glyph"; "required": false; }; "iconHint": { "alias": "iconHint"; "required": false; }; }, { "colorChange": "colorChange"; "glyphChange": "glyphChange"; "resetStyle": "resetStyle"; }, never, never, true, never>;
|
|
31
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SpatialGeneStyleComponent, "spatial-gene-style", never, { "gene": { "alias": "gene"; "required": false; }; "what": { "alias": "what"; "required": false; }; "color": { "alias": "color"; "required": false; }; "glyph": { "alias": "glyph"; "required": false; }; "iconHint": { "alias": "iconHint"; "required": false; }; "showIcons": { "alias": "showIcons"; "required": false; }; }, { "colorChange": "colorChange"; "glyphChange": "glyphChange"; "resetStyle": "resetStyle"; }, never, never, true, never>;
|
|
30
32
|
}
|
|
@@ -4,6 +4,8 @@ import type { SpatialDataset } from '../../contracts/spatial-dataset.contract';
|
|
|
4
4
|
import { SpatialViewState, TranscriptGlyphName } from '../../contracts/display-types';
|
|
5
5
|
import { GeneTreeNode } from '../../spatial/spatial-panel-model';
|
|
6
6
|
import * as i0 from "@angular/core";
|
|
7
|
+
/** How the gene rows are ordered: by name, or by their transcripts in view, most first. */
|
|
8
|
+
export type GeneSort = 'name' | 'count';
|
|
7
9
|
/** One gene row as the template draws it, derived once per input change. */
|
|
8
10
|
interface GeneRowView {
|
|
9
11
|
shown: boolean;
|
|
@@ -19,6 +21,8 @@ interface AllGeneRowView {
|
|
|
19
21
|
shown: boolean;
|
|
20
22
|
/** Its colour when coloured by gene, or null when the dataset's code order is unknown. */
|
|
21
23
|
swatch: string | null;
|
|
24
|
+
/** Its code in the dataset's gene list (what its palette colour is picked by). */
|
|
25
|
+
code: number;
|
|
22
26
|
count: number | null;
|
|
23
27
|
}
|
|
24
28
|
/**
|
|
@@ -29,6 +33,11 @@ interface AllGeneRowView {
|
|
|
29
33
|
* expands to every gene of the dataset, each with its eye when the dataset lists its genes
|
|
30
34
|
* in code order (`transcriptTiles.genes`), filtered by name past a few hundred.
|
|
31
35
|
*
|
|
36
|
+
* Both lists are ordered by name, or by their transcripts in view, most first (the
|
|
37
|
+
* sort switch, offered once the renderer reports counts). The all-genes list is sorted
|
|
38
|
+
* before it is cut to its first rows, so by count those are the genes with the most
|
|
39
|
+
* transcripts in view.
|
|
40
|
+
*
|
|
32
41
|
* Writes only through `controls.setViewState`.
|
|
33
42
|
*/
|
|
34
43
|
export declare class SpatialGeneTreeComponent implements OnChanges {
|
|
@@ -47,6 +56,10 @@ export declare class SpatialGeneTreeComponent implements OnChanges {
|
|
|
47
56
|
/** Swatches in the picker: the categorical palette genes are coloured from by default. */
|
|
48
57
|
/** The tree, rebuilt when the view changes — never per change-detection pass. */
|
|
49
58
|
protected geneTree: GeneTreeNode[];
|
|
59
|
+
/** {@link geneTree} with each node's genes in {@link geneSort} order: what the template draws. */
|
|
60
|
+
protected sortedTree: GeneTreeNode[];
|
|
61
|
+
/** The rows' order. By count only while the renderer reports counts (see {@link sortShown}). */
|
|
62
|
+
protected geneSort: GeneSort;
|
|
50
63
|
/** Per gene: its eye, swatch, glyph and count — rebuilt with the inputs (and when the
|
|
51
64
|
* cells' group colours arrive), so the template does no per-row lookups per check. */
|
|
52
65
|
protected rows: Map<string, GeneRowView>;
|
|
@@ -78,6 +91,13 @@ export declare class SpatialGeneTreeComponent implements OnChanges {
|
|
|
78
91
|
constructor(zone: NgZone, cdr: ChangeDetectorRef);
|
|
79
92
|
ngOnChanges(changes: SimpleChanges): void;
|
|
80
93
|
private rebuildRows;
|
|
94
|
+
/** Whether the rows are ordered by count: chosen, and counts to order by. */
|
|
95
|
+
protected get byCount(): boolean;
|
|
96
|
+
/** The sort switch is offered once there are counts to sort by. */
|
|
97
|
+
protected get sortShown(): boolean;
|
|
98
|
+
protected onGeneSort(sort: GeneSort): void;
|
|
99
|
+
/** `genes` in the rows' order: by count, most first (ties by name), else by name. */
|
|
100
|
+
private sorted;
|
|
81
101
|
/**
|
|
82
102
|
* The all-genes list's rows: the genes matching the filter, up to the cap. Built only while
|
|
83
103
|
* the list is open: the counts in view change with every pan, and the list can be thousands.
|
|
@@ -113,6 +133,11 @@ export declare class SpatialGeneTreeComponent implements OnChanges {
|
|
|
113
133
|
/** The colour gene `slot` is drawn in when transcripts are coloured by gene. */
|
|
114
134
|
geneColor(slot: number): string;
|
|
115
135
|
protected geneColorOf(gene: string): string;
|
|
136
|
+
/**
|
|
137
|
+
* The colour the open picker shows: with "All genes", the gene's colour among every gene (its
|
|
138
|
+
* chosen one, else the palette's by its code), as its row and its markers have it.
|
|
139
|
+
*/
|
|
140
|
+
protected styleColorOf(gene: string): string;
|
|
116
141
|
/** In Cluster colouring, a gene's swatch is its cluster's colour, as its markers are. */
|
|
117
142
|
protected geneSwatchOf(gene: string): string;
|
|
118
143
|
protected geneCountOf(gene: string): number | null;
|