@jax-data-science/sci-image-visualizer 0.12.1 → 0.14.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.
Files changed (78) hide show
  1. package/esm2022/lib/contracts/channel-histogram-api.contract.mjs +19 -1
  2. package/esm2022/lib/contracts/display-types.mjs +7 -1
  3. package/esm2022/lib/contracts/image.contract.mjs +1 -1
  4. package/esm2022/lib/contracts/region-editor-api.contract.mjs +1 -1
  5. package/esm2022/lib/contracts/spatial-dataset.contract.mjs +1 -1
  6. package/esm2022/lib/contracts/visualizer.contract.mjs +1 -1
  7. package/esm2022/lib/geometry/reconstruct.mjs +11 -1
  8. package/esm2022/lib/implementations/napari-js/napari-image-2d-scene.mjs +17 -2
  9. package/esm2022/lib/implementations/napari-js/napari-spatial-2d-scene.mjs +88 -10
  10. package/esm2022/lib/implementations/napari-js/napari-spatial-3d-scene.mjs +62 -13
  11. package/esm2022/lib/implementations/napari-js/napari-spatial-encoding.mjs +21 -1
  12. package/esm2022/lib/implementations/napari-js/napari-spatial-scene.mjs +5 -1
  13. package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +9 -1
  14. package/esm2022/lib/implementations/napari-js/spatial-tiles/categorical-lookup.mjs +26 -3
  15. package/esm2022/lib/implementations/napari-js/spatial-tiles/cell-layers.mjs +3 -2
  16. package/esm2022/lib/implementations/napari-js/spatial-tiles/density-layer.mjs +20 -4
  17. package/esm2022/lib/implementations/napari-js/spatial-tiles/transcript-hover.mjs +4 -2
  18. package/esm2022/lib/implementations/napari-js/spatial-tiles/transcript-jobs.mjs +4 -2
  19. package/esm2022/lib/implementations/napari-js/spatial-tiles/transcript-layers.mjs +42 -15
  20. package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +3 -1
  21. package/esm2022/lib/region-editor/region-editor.component.mjs +13 -1
  22. package/esm2022/lib/routing-visualizer.service.mjs +4 -1
  23. package/esm2022/lib/spatial/lod.mjs +6 -3
  24. package/esm2022/lib/spatial/spatial-controls.facade.mjs +78 -23
  25. package/esm2022/lib/spatial/spatial-gene-filter.mjs +138 -0
  26. package/esm2022/lib/spatial/spatial-selection.mjs +70 -1
  27. package/esm2022/lib/spatial/spatial-tile-merge.mjs +42 -1
  28. package/esm2022/lib/spatial-controls/spatial-cells-panel/spatial-cells-panel.component.mjs +3 -3
  29. package/esm2022/lib/spatial-controls/spatial-controls.component.mjs +54 -11
  30. package/esm2022/lib/spatial-controls/spatial-gene-style/spatial-gene-style.component.mjs +66 -0
  31. package/esm2022/lib/spatial-controls/spatial-gene-tree/spatial-gene-tree.component.mjs +106 -26
  32. package/esm2022/lib/spatial-controls/spatial-groups-panel/spatial-groups-panel.component.mjs +3 -3
  33. package/esm2022/lib/spatial-controls/spatial-key/spatial-key.component.mjs +3 -3
  34. package/esm2022/lib/spatial-controls/spatial-marker-genes-form/spatial-marker-genes-form.component.mjs +3 -3
  35. package/esm2022/lib/spatial-controls/spatial-observations-panel/spatial-observations-panel.component.mjs +110 -8
  36. package/esm2022/lib/spatial-controls/spatial-transcripts-panel/spatial-transcripts-panel.component.mjs +41 -9
  37. package/esm2022/lib/store/region-selection.mjs +21 -4
  38. package/esm2022/lib/store/region-store.service.mjs +22 -3
  39. package/esm2022/lib/store/spatial-selection.service.mjs +13 -1
  40. package/esm2022/lib/store/visualizer-store.service.mjs +50 -1
  41. package/esm2022/lib/toolbar/toolbar-help-dialog/toolbar-help-dialog.component.mjs +3 -3
  42. package/fesm2022/{jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DnPlIP2F.mjs → jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DvFp1jNH.mjs} +1777 -793
  43. package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DvFp1jNH.mjs.map +1 -0
  44. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-Bs987_-E.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-D7lSUAM1.mjs} +2 -2
  45. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-Bs987_-E.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-D7lSUAM1.mjs.map} +1 -1
  46. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +2 -2
  47. package/lib/contracts/channel-histogram-api.contract.d.ts +14 -0
  48. package/lib/contracts/display-types.d.ts +27 -0
  49. package/lib/contracts/image.contract.d.ts +2 -0
  50. package/lib/contracts/region-editor-api.contract.d.ts +3 -0
  51. package/lib/contracts/spatial-dataset.contract.d.ts +7 -0
  52. package/lib/contracts/visualizer.contract.d.ts +28 -2
  53. package/lib/geometry/reconstruct.d.ts +6 -0
  54. package/lib/implementations/napari-js/napari-image-2d-scene.d.ts +7 -0
  55. package/lib/implementations/napari-js/napari-spatial-2d-scene.d.ts +13 -2
  56. package/lib/implementations/napari-js/napari-spatial-encoding.d.ts +5 -0
  57. package/lib/implementations/napari-js/napari-spatial-scene.d.ts +4 -0
  58. package/lib/implementations/napari-js/napari-visualizer.service.d.ts +5 -0
  59. package/lib/implementations/napari-js/spatial-tiles/categorical-lookup.d.ts +6 -1
  60. package/lib/implementations/napari-js/spatial-tiles/transcript-hover.d.ts +4 -0
  61. package/lib/implementations/napari-js/spatial-tiles/transcript-layers.d.ts +3 -1
  62. package/lib/routing-visualizer.service.d.ts +1 -0
  63. package/lib/spatial/lod.d.ts +3 -1
  64. package/lib/spatial/spatial-controls.facade.d.ts +19 -2
  65. package/lib/spatial/spatial-gene-filter.d.ts +44 -0
  66. package/lib/spatial/spatial-selection.d.ts +16 -1
  67. package/lib/spatial/spatial-tile-merge.d.ts +17 -0
  68. package/lib/spatial-controls/spatial-controls.component.d.ts +14 -3
  69. package/lib/spatial-controls/spatial-gene-style/spatial-gene-style.component.d.ts +30 -0
  70. package/lib/spatial-controls/spatial-gene-tree/spatial-gene-tree.component.d.ts +39 -7
  71. package/lib/spatial-controls/spatial-observations-panel/spatial-observations-panel.component.d.ts +33 -1
  72. package/lib/spatial-controls/spatial-transcripts-panel/spatial-transcripts-panel.component.d.ts +13 -1
  73. package/lib/store/region-selection.d.ts +14 -3
  74. package/lib/store/region-store.service.d.ts +14 -1
  75. package/lib/store/spatial-selection.service.d.ts +8 -0
  76. package/lib/store/visualizer-store.service.d.ts +13 -0
  77. package/package.json +1 -1
  78. package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DnPlIP2F.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-DnPlIP2F.mjs';
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-DvFp1jNH.mjs';
4
4
  import '@angular/core';
5
5
  import '@angular/common/http';
6
6
  import 'rxjs';
@@ -237,4 +237,4 @@ class OnnxSamSession {
237
237
  }
238
238
 
239
239
  export { OnnxSamSession };
240
- //# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-Bs987_-E.mjs.map
240
+ //# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-D7lSUAM1.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"jax-data-science-sci-image-visualizer-onnx-sam-session-Bs987_-E.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-D7lSUAM1.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,11 +1,11 @@
1
- export { A as ALL_GENES, b as ALL_TOOLBAR_TOOLS, c as ANNOTATION_IMAGE_MAX_DIM, C as CANVAS_TOOL_IDS, d as CELL_SEGMENTER, e as CHANNEL_HISTOGRAM_API, h as CellposeSegmenterService, i as ChannelHistogramComponent, D as DEFAULT_CATEGORICAL_PALETTE, j as DEFAULT_FALLBACK_PALETTE, k as DEFAULT_MUTED_OPACITY, l as DEFAULT_SAM_MODEL_ID, m as DEFAULT_SPATIAL_VIEW, H as HexColorPickerComponent, I as IMAGE_STATE_PORT, n as INSTANCE_SEGMENTER, o as ImageConverterService, L as LUT_COLORS, M as MISSING_COLOR, p as MultiPolygon, N as NAPARI_DECIMATE_OPTIONS, q as NAPARI_DEFAULT_DECIMATE, s as NO_CATEGORY, t as NO_OBSERVATION, P as PLOT_MODE_CONTEXT, u as PLOT_MODE_SESSION, v as PLOT_TYPE_CONTRIBUTIONS, w as PLOT_TYPE_DESCRIPTORS, x as PREFERENCES_PORT, y as PlotType, z as Polygon, B as ProcessingImage, R as REGION_EDITOR_API, E as REGION_IO_PORT, F as REGION_SOURCE_MIN_CHARS, G as Rectangle, J as Region, K as RegionEditorComponent, O as RoutingVisualizerService, S as SAM_MODELS, Q as SEMANTIC_SEGMENTER, T as SPATIAL_DATA_PORT, U as SPATIAL_WIRE_VERSION, V as ShapeSelection, W as SpatialChartsComponent, X as SpatialControlsComponent, Y as SpatialDataHttpService, Z as SpatialSelectionStore, _ as SupersededError, $ as TILE_ACCESS_PORT, a0 as TOOLBAR_TOOLS, a1 as VISUALIZER, a2 as VIZ_ALERT_TOAST_KEY, a3 as VIZ_CONFIG, a4 as VIZ_TOAST_KEY, a5 as ViewerFeature, a6 as VisualizationModule, a7 as VisualizerComponent, a8 as assertManifestVersion, a9 as benefitsFromGrouping, aa as buildOmicsTraces, ab as capabilitiesOf, ac as clearSamModelCache, ad as contrastWindow, ae as contributedPlotTypeOption, af as countMask, ag as cropImage, ah as datasetFromManifest, ai as decodeColumn, aj as decodeCoords, ak as decodeFeatureVector, al as decodePolygons, am as decodeRadius, an as defaultPresetSet, ao as dialogToolContributions, ap as emptySelection, aq as encodeCategorical, ar as encodeContinuous, as as expandZips, at as findColumnMeta, au as getDefaultSamModelId, g as getOrtWasmBase, av as getPlotTypeDescriptor, aw as getSamModel, ax as hasZarrMetadata, ay as hydrateBounds, az as importFile, aA as isBuiltinPlotType, aB as isCanvasToolId, aC as isCategoricalColumn, aD as isContinuousColumn, aE as isDialogToolContribution, aF as isEmptyPresetSet, aG as isLabelVolumeName, aH as isLittleEndian, aI as isNapari3d, aJ as isNapariIsosurface, aK as isNapariScatter, aL as isNapariScatter3d, aM as isNapariSurface, aN as isNapariVolume, aO as isSamModelReady, aP as isSpatialOmics, aQ as isSpatialOmics3d, aR as isThreeDimensional, aS as isZipName, aT as labelClasses, aU as labelSliceGeoJson, aV as lutFor, aW as markerDiameters, aX as maskToIndices, aY as mutedFromSelection, aZ as omicsLayout, a_ as openAnnotations, a$ as openGeoJsonRegionSource, b0 as openHttpRegionSource, b1 as parsePresetSet, b2 as planRegionImport, b3 as pointInRing, b4 as provideVisualization, b5 as readAnnotationSlices, b6 as readLabelVolume, b7 as readOmeZarrLabels, b8 as readZip, b9 as rendererOwnsWheel, ba as resolveCategoryColors, bb as selectByCategory, bc as selectInRegions, bd as setDefaultSamModel, be as setOrtWasmBase, bf as setSamModelUrls, bg as sortToolContributions, bh as visibleToolContributions } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DnPlIP2F.mjs';
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-DvFp1jNH.mjs';
2
2
  import '@angular/core';
3
3
  import '@angular/common/http';
4
4
  import 'napari-js';
5
5
  import 'rxjs';
6
6
  import 'rxjs/operators';
7
- import 'onnxruntime-web';
8
7
  import 'napari-js/geometry';
8
+ import 'onnxruntime-web';
9
9
  import 'fast-png';
10
10
  import 'fzstd';
11
11
  import '@angular/core/rxjs-interop';
@@ -18,6 +18,20 @@ export interface IChannelState {
18
18
  gamma: number;
19
19
  visible: boolean;
20
20
  }
21
+ /**
22
+ * How many channels of a multichannel image start visible. Each visible channel is drawn
23
+ * from tiles of its own, so a 22-channel plate showed every channel at once and asked the
24
+ * server for 22 tiles per position on every pan and zoom. Channels past these start hidden
25
+ * (one click shows them), and a hidden channel fetches no tiles.
26
+ */
27
+ export declare const DEFAULT_VISIBLE_CHANNELS = 4;
28
+ /**
29
+ * Whether channel `index` of `count` starts visible: as the image says when its
30
+ * `channelInfo` does, else every channel of an image with at most
31
+ * {@link DEFAULT_VISIBLE_CHANNELS}, and the first {@link DEFAULT_VISIBLE_CHANNELS} of one with more.
32
+ * Only a boolean says: a descriptor's `null` (a JSON field left empty) leaves the default.
33
+ */
34
+ export declare function defaultChannelVisible(index: number, count: number, specified?: boolean | null): boolean;
21
35
  /** The standard LUT pseudo-colours (mirrors Fiji's Merge Channels palette) for
22
36
  * quick per-channel assignment. */
23
37
  export declare const LUT_COLORS: ReadonlyArray<{
@@ -225,6 +225,26 @@ export interface SpatialViewState {
225
225
  * not drawn, nor the transcripts inside them. Cleared when the group column changes.
226
226
  */
227
227
  hiddenGroups: string[];
228
+ /**
229
+ * Genes whose observations are drawn: with any set, only the observations that express
230
+ * them (a value above 0) are drawn — the markers, the cell outlines and the transcripts
231
+ * inside them, in 2D and in the 3D cloud — and the rest are hidden. Empty draws every
232
+ * observation. Cleared when the dataset changes.
233
+ */
234
+ observationGenes: string[];
235
+ /** Whether an observation must express any of {@link observationGenes}, or all of them. */
236
+ observationGenesMatch: 'any' | 'all';
237
+ /**
238
+ * Colour (and mark) the filtered observations by their gene: each drawn observation in the
239
+ * colour and icon of the picked gene it expresses most (relative to that gene's highest
240
+ * value), in place of {@link colorBy}. Off, {@link colorBy} colours them as before.
241
+ */
242
+ observationGenesColorBy: boolean;
243
+ /** `#rrggbb` per gene of {@link observationGenes}; a gene left out takes the palette's colour
244
+ * for its place in the list. */
245
+ observationGeneColors: Record<string, string>;
246
+ /** The icon per gene of {@link observationGenes} (2D); a gene left out is drawn as a circle. */
247
+ observationGeneGlyphs: Record<string, TranscriptGlyphName>;
228
248
  /** Draw the tissue image (the Images section's switch). */
229
249
  showImage: boolean;
230
250
  /** Draw the annotation regions (the Annotations section's switch). Selecting from them
@@ -258,6 +278,13 @@ export interface SpatialViewState {
258
278
  * marker per transcript once those fit {@link transcriptBudget}.
259
279
  */
260
280
  transcriptAllGenes: boolean;
281
+ /**
282
+ * Genes of "All genes" switched off with their eye toggle — its own list, so hiding a gene
283
+ * there leaves the selection's {@link transcriptHiddenGenes} alone, and the other way round.
284
+ * Led by `'*'` (`ALL_GENES`), it lists the genes still shown instead: `['*']` hides every
285
+ * gene, `['*', 'KRT5']` shows KRT5 alone, without naming the thousands of others.
286
+ */
287
+ transcriptAllGenesHidden: string[];
261
288
  /**
262
289
  * Most transcript markers drawn at once. The level of detail is chosen to stay under it,
263
290
  * which is what keeps pan and zoom responsive however dense the data is.
@@ -21,6 +21,8 @@ export interface IChannelInfo {
21
21
  minAllowed?: number;
22
22
  /** Pixel-type maximum (e.g. 65535 for unsigned 16-bit). Native-unit ceiling. */
23
23
  maxAllowed?: number;
24
+ /** Whether the channel starts visible; else the first `DEFAULT_VISIBLE_CHANNELS` do. */
25
+ visible?: boolean;
24
26
  }
25
27
  /** Per-series image metadata (physical size + dimensions). */
26
28
  export interface IImageMetadata {
@@ -177,6 +177,9 @@ export interface IRegionEditorApi {
177
177
  setClassesHidden?(classes: readonly string[], hidden: boolean): void;
178
178
  /** Select every region of these classes on the slice, the source's included (taken). */
179
179
  selectRegionClasses?(classes: readonly string[], additive?: boolean): Promise<void>;
180
+ /** The classes the selection was picked as by {@link selectRegionClasses}; [] once anything
181
+ * else is selected (a row, a region drawn or clicked). */
182
+ getPickedRegionClasses$?(): Observable<readonly string[]>;
180
183
  /** {@link getGeoJsonString} merged with the region source behind the regions (see
181
184
  * `RegionStore.getGeoJsonWithSource`); `slice` names a folder stack's slice. */
182
185
  getGeoJsonStringWithSource?(regions: Region[], slice?: number): Promise<string>;
@@ -278,6 +278,13 @@ export interface SpatialTranscriptTilesMeta {
278
278
  count?: number;
279
279
  /** Real genes (control probes excluded). Names come from the feature search. */
280
280
  geneCount: number;
281
+ /**
282
+ * The genes an all-gene tile's `gene` codes index, in code order (the port's
283
+ * `TranscriptTileQuery.genes` with `[ALL_GENES]`); an empty name is a code those tiles never
284
+ * carry (a control probe). Optional: without it "All genes" still draws, but cannot hide one
285
+ * of its genes.
286
+ */
287
+ genes?: string[];
281
288
  hasZ: boolean;
282
289
  levels: (SpatialTileLevel & {
283
290
  /** True when an entry stands for several transcripts (see {@link SpatialTranscriptTile.weight}). */
@@ -305,6 +305,12 @@ export interface SpatialCategoricalView {
305
305
  /** Per-observation category index, or `NO_CATEGORY`. */
306
306
  codes: Uint16Array;
307
307
  }
308
+ /**
309
+ * How "Select from ROIs" selects: inside the volumes of the region classes picked
310
+ * in the Region Editor (Spatial omics 3D, the regions on the dataset's reference
311
+ * volume shown), or by the drawn regions.
312
+ */
313
+ export type RoiSelectionMode = 'class-volumes' | 'regions';
308
314
  /**
309
315
  * Controls for the SPATIAL_OMICS plot type: what the observation markers are
310
316
  * coloured by, and how they are drawn. Capability-gated like
@@ -384,6 +390,9 @@ export interface ISpatialControls {
384
390
  hi: number;
385
391
  max: number;
386
392
  } | null>;
393
+ /** True while the observation gene filter could not be applied (an expression read failed):
394
+ * the observations then still show the last filter that could be. */
395
+ getGeneFilterFailed$?(): Observable<boolean>;
387
396
  /**
388
397
  * The z positions of the sections the dataset was imaged at, ascending — or
389
398
  * null when its z is continuous rather than sectioned (so there are no sections
@@ -399,9 +408,26 @@ export interface ISpatialControls {
399
408
  /**
400
409
  * Select every observation inside the currently-drawn regions (their union).
401
410
  * Reuses the existing ROI tools — rectangle, polygon, freehand, wand, brush —
402
- * so there is no separate marquee to learn. Returns how many were selected.
411
+ * so there is no separate marquee to learn. In the 3D cloud, the region classes
412
+ * picked in the Region Editor (see {@link getPickedRegionClasses$}) select the
413
+ * observations inside their volumes instead, when the regions sit on the
414
+ * dataset's reference volume; without a pick, the lassos drawn over the cloud.
415
+ * Resolves how many were selected — or null when it was superseded while the
416
+ * classes' outlines were read (a newer selection, a Clear, another dataset, view
417
+ * or pick), in which case nothing was published.
418
+ */
419
+ selectFromRegions(): Promise<number | null>;
420
+ /**
421
+ * How {@link selectFromRegions} would select now: inside the picked classes'
422
+ * volumes, or by the drawn regions. The rule it runs by, so a panel describing
423
+ * the button can never disagree with what it does.
424
+ */
425
+ roiSelectionMode?(): RoiSelectionMode;
426
+ /**
427
+ * The region classes the region selection was picked as (a class tree node with
428
+ * its subclasses, a surface clicked in 3D); [] for any other selection.
403
429
  */
404
- selectFromRegions(): number;
430
+ getPickedRegionClasses$?(): Observable<readonly string[]>;
405
431
  /** Select every observation in one category of a categorical column — the
406
432
  * legend click. Rejects for an unknown or continuous column. */
407
433
  selectCategory(column: string, categoryIndex: number): Promise<number>;
@@ -28,6 +28,12 @@ export interface ReconstructOptions {
28
28
  * {@link loftLines}). Empty when no slice has an area and no two slices have lines.
29
29
  */
30
30
  export declare function reconstruct(slices: readonly SliceOutlines[], opts?: ReconstructOptions): Mesh;
31
+ /**
32
+ * Fill one slice's outlines into `mask`, a `width × height` grid of image px (pixel i spanning
33
+ * [i, i + 1)), as the reconstruction fills them: each feature even-odd, the features as their
34
+ * union, sampled at pixel centres. Cells already set stay set. Open lines enclose nothing.
35
+ */
36
+ export declare function fillOutlines(mask: Uint8Array, width: number, height: number, outlines: SliceOutlines): void;
31
37
  /**
32
38
  * The area inside `rings` (interleaved `x, y`), even-odd: a ring inside an odd number of others is
33
39
  * a hole. Exact for rings that do not cross (a polygon set's exteriors and holes).
@@ -101,6 +101,13 @@ export declare class Image2dScene implements NapariScene {
101
101
  /** Channel states + grayscale colormap → live-apply to the rendered layers (no re-fetch; only z
102
102
  * changes re-fetch), and recolour the navigator thumbnail. */
103
103
  private followDisplayState;
104
+ /**
105
+ * Put the current channel states back on the rendered layers: each channel's visibility, window,
106
+ * gamma and tint, and which of an RGB image's layers draw it. For a caller that changed the
107
+ * layers' visibility wholesale (the spatial view showing its tissue again), so the channels
108
+ * the user or the defaults switched off stay off. Nothing to do before the image is drawn.
109
+ */
110
+ reapplyDisplayState(): void;
104
111
  /** Apply the current channel states / colormap to the live layers (no re-fetch), delegating the
105
112
  * per-channel layer mutations to the {@link MultiChannelImageView}. */
106
113
  private applyDisplayState;
@@ -1,7 +1,7 @@
1
1
  import type { ImageLayer, Viewer } from 'napari-js';
2
2
  import { IHistogram } from '../../contracts/channel-histogram-api.contract';
3
3
  import { SpatialDataset } from '../../contracts/spatial-dataset.contract';
4
- import { SpatialViewState } from '../../contracts/display-types';
4
+ import { SpatialViewState, TranscriptGlyphName } from '../../contracts/display-types';
5
5
  import { SpatialSelectionMask } from '../../spatial/spatial-selection';
6
6
  import { NapariSpatialTileLayers } from './napari-spatial-tiles';
7
7
  import { Image2dScene } from './napari-image-2d-scene';
@@ -157,7 +157,18 @@ export declare class Spatial2dScene extends SpatialSceneBase implements NapariSc
157
157
  * selected to colour by. Categorical columns use the column's own palette; continuous columns
158
158
  * and gene vectors go through the active colormap with a percentile-clipped window.
159
159
  */
160
- private faceColors;
160
+ /** The observations the view's gene filter keeps (1 = drawn), or null when it has none.
161
+ * Rejects when an expression read fails. */
162
+ private filterMask;
163
+ /** The markers' colours (and icons, by gene) before the gene filter hides any of them. */
164
+ private markerStyle;
165
+ /** {@link faceColors} before the gene filter: what the colour source and the selection make of them. */
166
+ private sourceColors;
161
167
  /** Continuous values → RGBA through the active colormap and a clipped window. */
162
168
  encodeContinuous(values: Float32Array, view: SpatialViewState, muted?: Uint8Array | null): Float32Array;
163
169
  }
170
+ /** Per-observation napari-js symbol codes: each observation's gene's icon (`codes` index
171
+ * `glyphs`), a disc where it has none. */
172
+ export declare function glyphSymbols(codes: Uint16Array, glyphs: readonly TranscriptGlyphName[]): Uint8Array;
173
+ /** The symbols of the drawn subset (all of them when `indices` is undefined). */
174
+ export declare function gatherSymbols(symbols: Uint8Array | null, indices?: Uint32Array): Uint8Array | null;
@@ -94,6 +94,11 @@ export declare function encodeSpatialContinuous(values: Float32Array, view: Spat
94
94
  export type PointColors = Float32Array | RGBA;
95
95
  /** Whether a colour input is one RGBA per point rather than one broadcast RGBA. */
96
96
  export declare function isPerPoint(colors: PointColors): colors is Float32Array;
97
+ /**
98
+ * `colors` with every observation `shown` leaves at 0 made transparent: the observations a gene
99
+ * filter leaves out. A broadcast colour becomes per-point, since the points now differ.
100
+ */
101
+ export declare function hideUnshown(colors: PointColors, shown: Uint8Array): Float32Array;
97
102
  /** Per-point colours for the drawn subset. A broadcast tuple stays broadcast —
98
103
  * it is one colour for every point either way. */
99
104
  export declare function gatherColors(colors: PointColors, indices?: Uint32Array): PointColors;
@@ -26,6 +26,8 @@ export interface SpatialTileSinks {
26
26
  max: number;
27
27
  }): void;
28
28
  loadingChanged(layers: string[]): void;
29
+ /** Whether the observation gene filter could be applied; see {@link SpatialSession.reportGeneFilter}. */
30
+ geneFilterFailed?(failed: boolean): void;
29
31
  }
30
32
  /** The 2D scene the tile layers currently draw into. */
31
33
  export interface SpatialTileOwner {
@@ -64,6 +66,8 @@ export declare class SpatialSession {
64
66
  private tileLayers;
65
67
  private tileOwner;
66
68
  constructor(port: SpatialDataPort | null, selection: SpatialSelectionStore | null, sinks: SpatialTileSinks);
69
+ /** Tell the panel whether the observation gene filter could be applied this rebuild. */
70
+ reportGeneFilter(failed: boolean): void;
67
71
  /** The tile layers, if they were ever built. */
68
72
  get tiles(): NapariSpatialTileLayers | null;
69
73
  /**
@@ -80,6 +80,8 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
80
80
  hi: number;
81
81
  max: number;
82
82
  } | null>;
83
+ /** The observation gene filter could not be applied (an expression read failed). */
84
+ readonly geneFilterFailed$: BehaviorSubject<boolean>;
83
85
  /** "x reloading…" at the bottom of the canvas: the tile layers' loads and the observations'. */
84
86
  private readonly badge;
85
87
  /** The store's display state (colormap, reverse, invert) and the colormaps derived from it. */
@@ -150,6 +152,9 @@ export declare class NapariVisualizerService extends BaseStoreVisualizer impleme
150
152
  /** Observations projected to canvas pixels under the 3D camera (the spatial 3D cloud), indexed
151
153
  * by observation with NaN for any not drawn; null when no cloud is mounted. */
152
154
  getSpatialScreenProjection(obs: SpatialObservations): Float32Array | null;
155
+ /** The ids of the regions drawn over the 3D cloud in screen space (its lassos); [] in 2D. Every
156
+ * other region in the store is in image space, and means nothing at canvas pixels. */
157
+ getSpatialLassoIds(): number[];
153
158
  private fitCameraSoon;
154
159
  /** @deprecated Plotly-specific; host re-drives plot() from its image stream. */
155
160
  reloadAndPlot(): void;
@@ -21,6 +21,11 @@ export declare class CategoricalLookup {
21
21
  constructor(port: SpatialDataPort);
22
22
  /** Codes of the column `name`, or null when it is not categorical. */
23
23
  codes(name: string): Promise<CategoricalCodes | null>;
24
- /** Codes of the group column that are switched off, or null when none are. */
24
+ /**
25
+ * The observations not drawn: those of the groups switched off, and those the gene filter
26
+ * leaves out. Null when nothing is hidden.
27
+ */
25
28
  hiddenCodes(dataset: SpatialDataset, view: SpatialViewState): Promise<HiddenCodes | null>;
29
+ /** Codes of the group column that are switched off, or null when none are. */
30
+ private hiddenGroupCodes;
26
31
  }
@@ -62,6 +62,10 @@ export interface DrawnTranscripts {
62
62
  merged: SpatialTranscriptTile;
63
63
  /** The layer the entries are drawn in, one marker per entry in the same order. */
64
64
  layer: PointsLayer;
65
+ /**
66
+ * The genes the entries' `gene` codes index: the selection's, or for every gene's individual
67
+ * transcripts the dataset's gene list (`transcriptTiles.genes`; empty without one).
68
+ */
65
69
  genes: string[];
66
70
  /** For a grouped gene selection: each entry's group, and the groups' names. */
67
71
  entryGroup?: Int32Array;
@@ -6,7 +6,7 @@ import { TranscriptGlyph } from '../../../spatial/glyphs';
6
6
  import type { CategoricalLookup } from './categorical-lookup';
7
7
  import type { OrderedLayerGroups, TileGroup } from './layer-groups';
8
8
  import type { PlanContext } from './plan-context';
9
- import type { TranscriptHover } from './transcript-hover';
9
+ import type { TranscriptHover, TranscriptKind } from './transcript-hover';
10
10
  import { type TranscriptJobPlanner } from './transcript-jobs';
11
11
  /**
12
12
  * The napari-js point symbol each transcript icon is drawn as. Every icon has a counterpart of
@@ -14,6 +14,8 @@ import { type TranscriptJobPlanner } from './transcript-jobs';
14
14
  * differs.
15
15
  */
16
16
  export declare const GLYPH_SYMBOLS: Readonly<Record<TranscriptGlyph, PointSymbol>>;
17
+ /** The view the markers are coloured by, for what was loaded. */
18
+ export declare function colorViewFor(view: SpatialViewState, kind: TranscriptKind, clustered: boolean, hasCellTypes: boolean): SpatialViewState;
17
19
  /**
18
20
  * The transcript markers of the 2D spatial view: runs the planner's job, drops hidden groups
19
21
  * and genes, groups a selection under the marker budget, colours the entries and draws them as
@@ -361,6 +361,7 @@ export declare class RoutingVisualizerService implements IVisualizer, IRegionEdi
361
361
  getHiddenClasses$(): Observable<ReadonlySet<string>>;
362
362
  setClassesHidden(classes: readonly string[], hidden: boolean): void;
363
363
  selectRegionClasses(classes: readonly string[], additive?: boolean): Promise<void>;
364
+ getPickedRegionClasses$(): Observable<readonly string[]>;
364
365
  getGeoJsonStringWithSource(regions: Region[], slice?: number): Promise<string>;
365
366
  enterStackMode(slices: Map<number, Region[]>, initialZ?: number, saveLayout?: 'combined' | 'per-slice-file'): void;
366
367
  exitStackMode(): void;
@@ -35,7 +35,9 @@ export declare function isCuratedColumn(name: string): boolean;
35
35
  * The categorical column that names each cell's type: the view's choice when it is a
36
36
  * column of this dataset, else the first categorical column that is not a curated
37
37
  * annotation (a pipeline's clustering is the default; curation is opted into), else
38
- * any categorical column.
38
+ * any categorical column. A column of one category, such as the `region` a SpatialData
39
+ * table links its cells to their shapes with, names no type and is only used when chosen;
40
+ * a dataset with nothing else has no cell types (null).
39
41
  */
40
42
  export declare function cellTypeColumnFor(dataset: SpatialDataset, view: Pick<SpatialViewState, 'cellTypeColumn'>): string | null;
41
43
  /** A stable string id for a tile key, `level/gx/gy` — usable as a Map key. */
@@ -24,8 +24,16 @@ export interface SpatialRendererFeeds {
24
24
  max: number;
25
25
  } | null>;
26
26
  };
27
+ readonly geneFilterFailed$?: {
28
+ asObservable(): Observable<boolean>;
29
+ };
27
30
  /** Observations projected to canvas pixels by the 3D camera, or null when the cloud is not mounted. */
28
31
  getSpatialScreenProjection(obs: SpatialObservations): Float32Array | null;
32
+ /** The ids of the regions drawn over the 3D cloud in screen space (its lassos). */
33
+ getSpatialLassoIds(): number[];
34
+ /** Image px per voxel of the volume the region surfaces sit in; null when the plotted image is
35
+ * not a volume's stack (the regions are then not in the reference volume's grid). */
36
+ regionVoxelStep(): number | null;
29
37
  }
30
38
  /** What the facade works over: the shared stores, the renderer feeds and the router's view state. */
31
39
  export interface SpatialControlsContext {
@@ -53,12 +61,21 @@ export declare class SpatialControlsFacade {
53
61
  private readonly ctx;
54
62
  /** The controls; one object for the facade's life, so consumers can hold it. */
55
63
  readonly controls: ISpatialControls;
56
- /** Latest dataset, mirrored so `selectFromRegions()` can answer synchronously — it
57
- * runs from a button click and must not await a round-trip. */
64
+ /** Latest dataset, mirrored so `selectFromRegions()` answers from the dataset on
65
+ * screen — it runs from a button click and must not await a round-trip. */
58
66
  private dataset;
59
67
  private readonly sub;
68
+ /** Bumped per class-volume selection (and on dispose): only the latest may publish. */
69
+ private roiRequest;
60
70
  constructor(port: SpatialDataPort, ctx: SpatialControlsContext);
61
71
  /** Release the port subscription (the router's injector is being destroyed). */
62
72
  dispose(): void;
73
+ /**
74
+ * How "Select from ROIs" selects now — the one rule its executor and the panel's description
75
+ * share: inside the picked classes' volumes in the 3D cloud, when the dataset has a reference
76
+ * volume (and no tissue image) and the plotted image is that volume's stack, so the regions sit
77
+ * in its grid; by the drawn regions otherwise.
78
+ */
79
+ private roiSelectionMode;
63
80
  private build;
64
81
  }
@@ -0,0 +1,44 @@
1
+ import type { SpatialViewState, TranscriptGlyphName } from '../contracts/display-types';
2
+ /** One gene's value for every observation: the port's `getFeatureVector`. */
3
+ export type FeatureVectorSource = (gene: string) => Promise<Float32Array>;
4
+ type GeneFilterView = Pick<SpatialViewState, 'observationGenes' | 'observationGenesMatch'>;
5
+ /** Whether the view draws only the observations expressing some genes. */
6
+ export declare function hasGeneFilter(view: Partial<GeneFilterView>): boolean;
7
+ /** What decides the gene filter, for the layers' cache keys: '' when there is none. */
8
+ export declare function geneFilterKey(view: Partial<GeneFilterView>): string;
9
+ /**
10
+ * 1 for each of `count` observations that expresses the genes — a value above 0 in any of
11
+ * `vectors`, or in all of them — else 0. A missing value (NaN) is not expressed.
12
+ */
13
+ export declare function expressingMask(vectors: readonly Float32Array[], count: number, match: 'any' | 'all'): Uint8Array;
14
+ /**
15
+ * The view's gene filter as a per-observation mask (1 = drawn), or null when it has none. Every
16
+ * layer asks on every rebuild, so the mask of the same vectors is built once.
17
+ */
18
+ export declare function observationGeneMask(view: GeneFilterView, count: number, read: FeatureVectorSource): Promise<Uint8Array | null>;
19
+ /** The colour a gene of the observation filter is drawn in: the view's own, else the palette's
20
+ * for its place in the list. */
21
+ export declare function observationGeneColor(view: Pick<SpatialViewState, 'observationGenes' | 'observationGeneColors'>, gene: string): string;
22
+ /** The icon a gene of the observation filter is drawn as (2D): the view's own, else a circle. */
23
+ export declare function observationGeneGlyph(view: Pick<SpatialViewState, 'observationGeneGlyphs'>, gene: string): TranscriptGlyphName;
24
+ /** Whether the filtered observations are coloured by their gene (rather than by `colorBy`). */
25
+ export declare function colorsByObservationGene(view: Pick<SpatialViewState, 'observationGenes' | 'observationGenesColorBy'>): boolean;
26
+ /**
27
+ * Each observation's gene among `vectors`: the index of the one it expresses most relative to
28
+ * that gene's own highest value — so a gene expressed at low counts everywhere is not always
29
+ * outvoted by a highly expressed one — or {@link NO_CATEGORY} where it expresses none.
30
+ */
31
+ export declare function dominantGenes(vectors: readonly Float32Array[], count: number): Uint16Array;
32
+ /**
33
+ * How the filtered observations are drawn, by gene: each observation's gene (an index into the
34
+ * view's genes, see {@link dominantGenes}) with the genes' colours and icons. Null when the view
35
+ * does not colour by them.
36
+ */
37
+ export declare function observationGeneStyle(view: GeneFilterView & Pick<SpatialViewState, 'observationGenesColorBy' | 'observationGeneColors' | 'observationGeneGlyphs'>, count: number, read: FeatureVectorSource): Promise<{
38
+ codes: Uint16Array;
39
+ colors: string[];
40
+ glyphs: TranscriptGlyphName[];
41
+ } | null>;
42
+ /** What decides how the filtered observations are drawn by gene, for cache keys. */
43
+ export declare function observationGeneStyleKey(view: Pick<SpatialViewState, 'observationGenes' | 'observationGenesColorBy' | 'observationGeneColors' | 'observationGeneGlyphs'>): string;
44
+ export {};
@@ -1,6 +1,7 @@
1
1
  import { Region } from '../models/region';
2
+ import { SliceOutlines } from '../geometry/reconstruct';
2
3
  import { RingBounds as Bounds } from '../geometry/ring';
3
- import { SpatialImageRef, SpatialObservations, SpatialSelectionMask } from '../contracts/spatial-dataset.contract';
4
+ import { SpatialImageRef, SpatialObservations, SpatialSelectionMask, SpatialVolumeMeta } from '../contracts/spatial-dataset.contract';
4
5
  /**
5
6
  * Which observations fall inside a set of drawn regions.
6
7
  *
@@ -95,6 +96,20 @@ export declare function selectInRegions(observations: SpatialObservations, image
95
96
  * never selected.
96
97
  */
97
98
  export declare function selectInRegionsProjected(screen: Float32Array, count: number, regions: readonly Region[]): SpatialSelectionMask;
99
+ /**
100
+ * Observations inside the VOLUME a stack of outlines encloses: a region class (an
101
+ * atlas structure with its substructures) through every section of the dataset's
102
+ * reference volume, as its 3D reconstruction shows it.
103
+ *
104
+ * The outlines are in the volume's own grid — x, y in voxels, z the slice — which
105
+ * is where regions imported onto the volume's stack sit. An observation is in voxel
106
+ * `(⌊x / vx⌋, ⌊y / vy⌋, ⌊z / vz⌋)` (see `volumeImageRef`), and selected when its
107
+ * slice's outlines cover that voxel, filled as the reconstruction fills them. One
108
+ * slice is rasterised at a time, so a structure through the whole brain costs one
109
+ * slice's mask, not the volume's. An observation outside the volume, or without a
110
+ * z, is never selected.
111
+ */
112
+ export declare function selectInClassVolumes(observations: SpatialObservations, volume: SpatialVolumeMeta, slices: readonly SliceOutlines[]): SpatialSelectionMask;
98
113
  /**
99
114
  * Whether two selections hold exactly the same observations.
100
115
  *
@@ -15,6 +15,23 @@ export declare function filterRings(rings: SpatialPolygonTile, hidden: {
15
15
  } | null): SpatialPolygonTile;
16
16
  /** Slots (indices into the selected genes) switched off with their eye toggle. */
17
17
  export declare function hiddenGeneSlots(view: SpatialViewState): Uint8Array | null;
18
+ /**
19
+ * Whether "All genes" is switched off as a whole with the gene tree's root eye: its hidden
20
+ * list is `[ALL_GENES]` (every gene hidden, none excepted). Hiding every gene by name would
21
+ * need the gene list, which a dataset need not have.
22
+ */
23
+ export declare function allGenesHidden(view: Pick<SpatialViewState, 'transcriptAllGenes' | 'transcriptAllGenesHidden'>): boolean;
24
+ /** Whether an "All genes" hidden list hides every gene, none excepted. */
25
+ export declare function everyGeneOff(off: readonly string[]): boolean;
26
+ /** Whether a gene of "All genes" is drawn, by its hidden list (see `transcriptAllGenesHidden`). */
27
+ export declare function allGeneShownBy(off: readonly string[]): (gene: string) => boolean;
28
+ /** "All genes"'s hidden list with `gene` shown (`on`) or hidden, in the list's own form. */
29
+ export declare function toggleAllGeneHidden(off: readonly string[], gene: string, on: boolean): string[];
30
+ /**
31
+ * Codes (indices into `genes`, the dataset's all-gene code order) switched off with their eye
32
+ * toggle while every gene is drawn; null when none is, or without the gene list.
33
+ */
34
+ export declare function hiddenAllGeneCodes(view: Pick<SpatialViewState, 'transcriptAllGenesHidden'>, genes: readonly string[] | undefined): Uint8Array | null;
18
35
  /** Entries in a hidden group's cell, or of a hidden gene, removed (with their sizes). */
19
36
  export declare function filterTranscripts(t: SpatialTranscriptTile, px: Float32Array, hidden: {
20
37
  codes: Uint16Array;