@jax-data-science/sci-image-visualizer 0.16.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.
Files changed (40) hide show
  1. package/esm2022/lib/contracts/display-types.mjs +2 -1
  2. package/esm2022/lib/implementations/napari-js/napari-loading-state.mjs +1 -1
  3. package/esm2022/lib/implementations/napari-js/napari-spatial-2d-scene.mjs +10 -8
  4. package/esm2022/lib/implementations/napari-js/napari-spatial-3d-scene.mjs +25 -7
  5. package/esm2022/lib/implementations/napari-js/napari-spatial-3d-transcripts.mjs +269 -0
  6. package/esm2022/lib/implementations/napari-js/napari-spatial-encoding.mjs +11 -4
  7. package/esm2022/lib/implementations/napari-js/napari-spatial-tiles.mjs +6 -7
  8. package/esm2022/lib/implementations/napari-js/napari-volume-region-meshes.mjs +33 -8
  9. package/esm2022/lib/implementations/napari-js/napari-volume-scene.mjs +6 -2
  10. package/esm2022/lib/implementations/napari-js/spatial-tiles/cell-layers.mjs +10 -6
  11. package/esm2022/lib/implementations/napari-js/spatial-tiles/transcript-layers.mjs +9 -5
  12. package/esm2022/lib/spatial/spatial-controls.facade.mjs +7 -4
  13. package/esm2022/lib/spatial/spatial-encoding.mjs +18 -1
  14. package/esm2022/lib/spatial-controls/spatial-charts/spatial-charts.component.mjs +6 -2
  15. package/esm2022/lib/spatial-controls/spatial-controls.component.mjs +7 -3
  16. package/esm2022/lib/spatial-controls/spatial-gene-style/spatial-gene-style.component.mjs +7 -3
  17. package/esm2022/lib/spatial-controls/spatial-gene-tree/spatial-gene-tree.component.mjs +21 -6
  18. package/esm2022/lib/spatial-controls/spatial-groups-panel/spatial-groups-panel.component.mjs +61 -8
  19. package/esm2022/lib/spatial-controls/spatial-observations-panel/spatial-observations-panel.component.mjs +3 -3
  20. package/fesm2022/{jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-CYsPzX3J.mjs → jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DHrHO2ay.mjs} +533 -113
  21. package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DHrHO2ay.mjs.map +1 -0
  22. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-DaZVXq1s.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-CKEUwNta.mjs} +2 -2
  23. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-DaZVXq1s.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-CKEUwNta.mjs.map} +1 -1
  24. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +1 -1
  25. package/lib/contracts/display-types.d.ts +9 -2
  26. package/lib/implementations/napari-js/napari-loading-state.d.ts +1 -1
  27. package/lib/implementations/napari-js/napari-spatial-2d-scene.d.ts +3 -3
  28. package/lib/implementations/napari-js/napari-spatial-3d-scene.d.ts +2 -0
  29. package/lib/implementations/napari-js/napari-spatial-3d-transcripts.d.ts +62 -0
  30. package/lib/implementations/napari-js/napari-spatial-encoding.d.ts +9 -2
  31. package/lib/implementations/napari-js/napari-spatial-tiles.d.ts +5 -6
  32. package/lib/implementations/napari-js/napari-volume-region-meshes.d.ts +16 -3
  33. package/lib/implementations/napari-js/spatial-tiles/cell-layers.d.ts +1 -1
  34. package/lib/spatial/spatial-encoding.d.ts +10 -0
  35. package/lib/spatial-controls/spatial-controls.component.d.ts +2 -0
  36. package/lib/spatial-controls/spatial-gene-style/spatial-gene-style.component.d.ts +3 -1
  37. package/lib/spatial-controls/spatial-gene-tree/spatial-gene-tree.component.d.ts +7 -0
  38. package/lib/spatial-controls/spatial-groups-panel/spatial-groups-panel.component.d.ts +14 -3
  39. package/package.json +2 -2
  40. package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-CYsPzX3J.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-CYsPzX3J.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-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-DaZVXq1s.mjs.map
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-DaZVXq1s.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-CYsPzX3J.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-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 also
134
- * stand in for cells too small on screen to outline. */
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
@@ -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: when the user wants them, and not while cell outlines are on
65
- * screen — then a cell IS its outline, and a circle on top of it is noise. Zoomed out past the
66
- * outline threshold, the circles stand in for the cells.
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
  /**
@@ -103,6 +103,8 @@ export declare class Spatial3dScene extends SpatialSceneBase implements NapariSc
103
103
  * regions were drawn on is that volume's stack (see {@link placeRegionMeshes}).
104
104
  */
105
105
  private readonly regionMeshes;
106
+ /** The transcripts of the genes the Transcripts panel draws, at their own z (see Spatial3dTranscripts). */
107
+ private readonly transcripts3d;
106
108
  /** Whether the reference volume lets every region surface through (see setRegionsThroughVolume). */
107
109
  private regionsThrough;
108
110
  /** The store's invert setting, which the density volumes follow (no other subscription of
@@ -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; while outlines are drawn the
52
- * markers are hidden (a cell is its outline then, not a dot), so the cells never have to
53
- * be ordered against them. The density goes OVER the markers on purpose: under 10^5
54
- * dots it would only show in the gaps between cells. {@link afterObservations} restores
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 — the service hides its dots then. */
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) and re-placed when the volume's Z is stretched. A surface is updated in
30
- * place (`setData`, `color`) rather than added again, and picked by napari-js (`pickSurface`).
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
- /** Follow the volume's frame (it opened, or its Z was stretched): re-place every surface. */
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 — the service hides its dots then. */
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
  }
@@ -21,6 +21,8 @@ interface AllGeneRowView {
21
21
  shown: boolean;
22
22
  /** Its colour when coloured by gene, or null when the dataset's code order is unknown. */
23
23
  swatch: string | null;
24
+ /** Its code in the dataset's gene list (what its palette colour is picked by). */
25
+ code: number;
24
26
  count: number | null;
25
27
  }
26
28
  /**
@@ -131,6 +133,11 @@ export declare class SpatialGeneTreeComponent implements OnChanges {
131
133
  /** The colour gene `slot` is drawn in when transcripts are coloured by gene. */
132
134
  geneColor(slot: number): string;
133
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;
134
141
  /** In Cluster colouring, a gene's swatch is its cluster's colour, as its markers are. */
135
142
  protected geneSwatchOf(gene: string): string;
136
143
  protected geneCountOf(gene: string): number | null;
@@ -7,10 +7,10 @@ import * as i0 from "@angular/core";
7
7
  /**
8
8
  * The cells' grouping, as Xenium Explorer's Groups section: the group picker (columns under
9
9
  * their section heading, a k-means family once, with its k beside it), CSV import of a new
10
- * grouping, and the grouping's categories with their colours, cell counts and show/hide
11
- * switches.
10
+ * grouping, and the grouping's categories with their colours (each one's swatch opens a colour
11
+ * picker), cell counts and show/hide switches.
12
12
  *
13
- * Writes only through `controls.setViewState` (`cellTypeColumn`, `hiddenGroups`).
13
+ * Writes only through `controls.setViewState` (`cellTypeColumn`, `hiddenGroups`, `groupColors`).
14
14
  */
15
15
  export declare class SpatialGroupsPanelComponent implements OnChanges {
16
16
  private readonly zone;
@@ -42,6 +42,8 @@ export declare class SpatialGroupsPanelComponent implements OnChanges {
42
42
  private readonly groupRowsLoad;
43
43
  protected groupImportError: string | null;
44
44
  protected groupImporting: boolean;
45
+ /** The group the open colour picker edits. */
46
+ protected styleGroup: string | null;
45
47
  constructor(zone: NgZone, cdr: ChangeDetectorRef);
46
48
  ngOnChanges(changes: SimpleChanges): void;
47
49
  /** The categorical column the cells are grouped by. */
@@ -57,6 +59,15 @@ export declare class SpatialGroupsPanelComponent implements OnChanges {
57
59
  protected get allGroupsShown(): boolean;
58
60
  protected onGroupShown(label: string, on: boolean): void;
59
61
  protected onAllGroupsShown(on: boolean): void;
62
+ protected openGroupStyle(event: Event, label: string, panel: {
63
+ toggle(e: Event): void;
64
+ hide(): void;
65
+ }): void;
66
+ /** The colour group `label` is drawn in now (the user's, else the column's or the palette's). */
67
+ protected groupColorOf(label: string): string;
68
+ protected onGroupColor(label: string, hex: string): void;
69
+ /** Back to the group's own colour (the column's, else the palette's). */
70
+ protected resetGroupColor(label: string): void;
60
71
  protected get canImportGroups(): boolean;
61
72
  /** '+': a CSV/TSV of `cell_id` and group, named after the file. */
62
73
  protected onImportGroupsFile(input: HTMLInputElement): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jax-data-science/sci-image-visualizer",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "engines": {
5
5
  "node": ">=20.9.0"
6
6
  },
@@ -40,7 +40,7 @@
40
40
  "cellpose-js": "^0.6.0",
41
41
  "fast-png": "^6.4.0",
42
42
  "fzstd": "^0.1.1",
43
- "napari-js": "^0.19.0",
43
+ "napari-js": "^0.20.0",
44
44
  "openseadragon": "^6.0.2",
45
45
  "plotly.js-dist-min": "^3.0.1",
46
46
  "tslib": "^2.3.0"