@jax-data-science/sci-image-visualizer 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/channel-histogram/channel-histogram.component.mjs +39 -3
- package/esm2022/lib/channel-histogram/channel-tree.mjs +65 -0
- package/esm2022/lib/contracts/channel-histogram-api.contract.mjs +1 -1
- package/esm2022/lib/contracts/image.contract.mjs +1 -1
- package/esm2022/lib/implementations/napari-js/napari-scene.mjs +1 -1
- package/esm2022/lib/implementations/napari-js/napari-spatial-3d-scene.mjs +202 -33
- package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +2 -1
- package/esm2022/lib/implementations/napari-js/spatial-tiles/cell-layers.mjs +5 -8
- package/esm2022/lib/spatial/lod.mjs +19 -1
- package/esm2022/lib/spatial-controls/spatial-cells-panel/spatial-cells-panel.component.mjs +47 -10
- package/esm2022/lib/spatial-controls/spatial-gene-tree/spatial-gene-tree.component.mjs +49 -16
- package/esm2022/lib/store/visualizer-store.service.mjs +20 -3
- package/esm2022/lib/toolbar/toolbar.component.mjs +13 -4
- package/esm2022/lib/toolbar/view-controls/view-controls.component.mjs +7 -7
- package/fesm2022/{jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DvFp1jNH.mjs → jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-CYsPzX3J.mjs} +462 -83
- package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-CYsPzX3J.mjs.map +1 -0
- package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-D7lSUAM1.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-DaZVXq1s.mjs} +3 -2
- package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-D7lSUAM1.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-DaZVXq1s.mjs.map} +1 -1
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs +2 -1
- package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
- package/lib/channel-histogram/channel-histogram.component.d.ts +14 -0
- package/lib/channel-histogram/channel-tree.d.ts +30 -0
- package/lib/contracts/channel-histogram-api.contract.d.ts +2 -0
- package/lib/contracts/image.contract.d.ts +6 -0
- package/lib/implementations/napari-js/napari-scene.d.ts +2 -0
- package/lib/implementations/napari-js/napari-spatial-3d-scene.d.ts +80 -9
- package/lib/spatial/lod.d.ts +11 -1
- package/lib/spatial-controls/spatial-cells-panel/spatial-cells-panel.component.d.ts +18 -1
- package/lib/spatial-controls/spatial-gene-tree/spatial-gene-tree.component.d.ts +18 -0
- package/lib/store/visualizer-store.service.d.ts +7 -1
- package/lib/toolbar/toolbar.component.d.ts +6 -0
- package/lib/toolbar/view-controls/view-controls.component.d.ts +3 -3
- package/package.json +2 -2
- package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DvFp1jNH.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LruCache } from 'napari-js';
|
|
2
2
|
import * as ort from 'onnxruntime-web';
|
|
3
|
-
import { g as getOrtWasmBase, f as fetchModel, r as runEncoder, a as runDecoder } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-
|
|
3
|
+
import { g as getOrtWasmBase, f as fetchModel, r as runEncoder, a as runDecoder } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-CYsPzX3J.mjs';
|
|
4
4
|
import '@angular/core';
|
|
5
5
|
import '@angular/common/http';
|
|
6
6
|
import 'rxjs';
|
|
@@ -38,6 +38,7 @@ import 'primeng/selectbutton';
|
|
|
38
38
|
import 'primeng/treeselect';
|
|
39
39
|
import 'plotly.js-dist-min';
|
|
40
40
|
import 'primeng/multiselect';
|
|
41
|
+
import 'primeng/inputswitch';
|
|
41
42
|
import 'image-js';
|
|
42
43
|
import 'buffer';
|
|
43
44
|
import 'openseadragon';
|
|
@@ -237,4 +238,4 @@ class OnnxSamSession {
|
|
|
237
238
|
}
|
|
238
239
|
|
|
239
240
|
export { OnnxSamSession };
|
|
240
|
-
//# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-
|
|
241
|
+
//# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-DaZVXq1s.mjs.map
|
|
@@ -1 +1 @@
|
|
|
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
|
+
{"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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiBA;;;;;AAKG;MACU,YAAY,CAAA;AAAzB,IAAA,WAAA,GAAA;QACU,IAAA,CAAA,GAAG,GAAG,CAAC;AACE,QAAA,IAAA,CAAA,OAAO,GAAG,IAAI,GAAG,EAAmB;IAmCvD;;AAhCE,IAAA,IAAI,IAAI,GAAA;AACN,QAAA,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI;IAC1B;;AAGA,IAAA,IAAI,CAAC,UAAuC,EAAA;AAC1C,QAAA,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG;QACrB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,MAAM,KAAI;AAC3D,YAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AACvD,QAAA,CAAC,CAAC;AACF,QAAA,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE;IACxB;;AAGA,IAAA,MAAM,CAAC,KAAkB,EAAA;AACvB,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;AACpC,QAAA,IAAI,CAAC,CAAC;YAAE;AACR,QAAA,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE;YAC7B,CAAC,CAAC,UAAU,GAAG,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC;YACnC;QACF;QACA,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAC7B,QAAA,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;AAAE,YAAA,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,mBAAmB,CAAC,CAAC;;AAC9E,YAAA,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;IACvB;;AAGA,IAAA,SAAS,CAAC,GAAU,EAAA;AAClB,QAAA,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;AAC/C,QAAA,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;QACpB,KAAK,MAAM,CAAC,IAAI,KAAK;AAAE,YAAA,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;IACtC;AACD;;ACpDD;;;;;;;;;;;;;;;AAeG;MACU,cAAc,CAAA;AAA3B,IAAA,WAAA,GAAA;QACU,IAAA,CAAA,IAAI,GAA+B,IAAI;QACvC,IAAA,CAAA,MAAM,GAAG,KAAK;QACd,IAAA,CAAA,SAAS,GAAG,IAAI;;QAGhB,IAAA,CAAA,MAAM,GAAkB,IAAI;AACnB,QAAA,IAAA,CAAA,KAAK,GAAG,IAAI,YAAY,EAAE;;QAGnC,IAAA,CAAA,OAAO,GAAgC,IAAI;QAC3C,IAAA,CAAA,OAAO,GAAgC,IAAI;QAC3C,IAAA,CAAA,SAAS,GAAG,CAAC;;AAEJ,QAAA,IAAA,CAAA,UAAU,GAAG,IAAI,QAAQ,CAAwB,CAAC,CAAC;IAiItE;AA/HE,IAAA,MAAM,SAAS,CAAC,KAAkB,EAAE,UAAuC,EAAA;QACzE,IAAI,CAAC,KAAK,CAAC,UAAU,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE;YAC1C,MAAM,IAAI,KAAK,CAAC,CAAA,WAAA,EAAc,KAAK,CAAC,EAAE,CAAA,8BAAA,CAAgC,CAAC;QACzE;AACA,QAAA,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS;QAChC,MAAM,MAAM,GAAG,OAAO,SAAS,KAAK,WAAW,IAAI,KAAK,IAAI,SAAS;QACrE,MAAM,GAAG,GAAa,KAAK,CAAC,gBAAgB,KAAK,MAAM,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACxF,MAAM,UAAU,GAAG,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;;;AAGzC,QAAA,IAAI,CAAC,IAAI,GAAG,CAAC,UAAU,IAAI,KAAK,CAAC,SAAS,GAAG,QAAQ,GAAG,QAAQ;AAEhE,QAAA,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE;YAC1B,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,GAAG,cAAc,EAAE;AACzC,YAAA,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC;AAC7E,YAAA,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAC;AAC5E,YAAA,IAAI,CAAC,OAAO,GAAG,MAAM,GAAG,CAAC,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,kBAAkB,EAAE,GAAG,EAAE,CAAC;YACrF,IAAI,CAAC,OAAO,GAAG,MAAM,GAAG,CAAC,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,kBAAkB,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5F;aAAO;YACL,MAAM,IAAI,CAAC,IAAI,CACb;AACE,gBAAA,IAAI,EAAE,MAAM;gBACZ,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,SAAS,EAAE,cAAc,EAAE;gBAC3B,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;AACzC,aAAA,EACD,EAAE,EACF,UAAU,CACX;QACH;AACA,QAAA,IAAI,CAAC,MAAM,GAAG,IAAI;IACpB;IAEA,QAAQ,GAAA;QACN,OAAO,IAAI,CAAC,MAAM;IACpB;IAEA,MAAM,KAAK,CAAC,KAAiE,EAAA;AAC3E,QAAA,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE;YAC1B,MAAM,CAAC,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,OAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC;AAChG,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE;YAC9B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;YAC7B,OAAO;AACL,gBAAA,IAAI,EAAE,IAAI,YAAY,CAAC,CAAC,CAAC;gBACzB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,KAAK;aACN;QACH;;AAEA,QAAA,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM;AAChC,QAAA,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;QAC1G,OAAO;AACL,YAAA,IAAI,EAAE,IAAI,YAAY,CAAC,CAAC,CAAC;AACzB,YAAA,IAAI,EAAE,GAAG,CAAC,MAAM,CAAa;AAC7B,YAAA,KAAK,EAAE,GAAG,CAAC,OAAO,CAAW;AAC7B,YAAA,UAAU,EAAE,GAAG,CAAC,YAAY,CAAW;AACvC,YAAA,WAAW,EAAE,GAAG,CAAC,aAAa,CAAW;AACzC,YAAA,KAAK,EAAE,GAAG,CAAC,OAAO,CAAW;SAC9B;IACH;AAEA,IAAA,MAAM,MAAM,CAAC,SAAuB,EAAE,MAAiB,EAAA;AACrD,QAAA,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE;AAC1B,YAAA,MAAM,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,KAAe,CAAC;AACxD,YAAA,IAAI,CAAC,CAAC;AAAE,gBAAA,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC;YACtE,OAAO,UAAU,CAAC,IAAI,CAAC,OAAQ,EAAE,CAAC,EAAE,MAAM,CAAC;QAC7C;QACA,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;QAC/E,OAAO;YACL,IAAI,EAAE,IAAI,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAgB,CAAC;AAClD,YAAA,KAAK,EAAE,GAAG,CAAC,OAAO,CAAW;AAC7B,YAAA,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAW;AAC/B,YAAA,GAAG,EAAE,GAAG,CAAC,KAAK,CAAW;SAC1B;IACH;AAEA;AAC4E;IAC5E,OAAO,GAAA;QACL,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;AAClD,QAAA,IAAI,CAAC,OAAO,EAAE,OAAO,IAAI;AACzB,QAAA,IAAI,CAAC,OAAO,EAAE,OAAO,IAAI;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI;AAClC,QAAA,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE;AACvB,QAAA,IAAI,CAAC,MAAM,GAAG,KAAK;AACnB,QAAA,IAAI,CAAC,IAAI,GAAG,IAAI;IAClB;;IAGQ,YAAY,GAAA;QAClB,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC,MAAM;QACnC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,mBAAmB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAC5F,QAAA,MAAM,CAAC,SAAS,GAAG,CAAC,EAA6B,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC;AAChF,QAAA,MAAM,CAAC,OAAO,GAAG,CAAC,EAAc,KAAI;;;AAGlC,YAAA,IAAI,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,OAAO,IAAI,qBAAqB,CAAC,CAAC;AAC/D,YAAA,IAAI,CAAC,MAAM,GAAG,KAAK;AACnB,YAAA,IAAI,CAAC,IAAI,GAAG,IAAI;AAClB,QAAA,CAAC;AACD,QAAA,IAAI,CAAC,MAAM,GAAG,MAAM;AACpB,QAAA,OAAO,MAAM;IACf;;AAGQ,IAAA,UAAU,CAAC,GAAU,EAAA;AAC3B,QAAA,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE;AACxB,QAAA,IAAI,CAAC,MAAM,GAAG,IAAI;AAClB,QAAA,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC;IAC3B;AAEQ,IAAA,IAAI,CACV,GAA4B,EAC5B,QAAA,GAA2B,EAAE,EAC7B,UAAgC,EAAA;AAEhC,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE;AAClC,QAAA,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC;AACnD,QAAA,MAAM,CAAC,WAAW,CAAC,EAAE,GAAG,GAAG,EAAE,EAAE,EAAE,EAAE,QAAQ,CAAC;AAC5C,QAAA,OAAO,OAAO;IAChB;AACD;;;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as ALL_GENES, b as ALL_TOOLBAR_TOOLS, c as ANNOTATION_IMAGE_MAX_DIM, C as CANVAS_TOOL_IDS, d as CELL_SEGMENTER, e as CHANNEL_HISTOGRAM_API, h as CellposeSegmenterService, i as ChannelHistogramComponent, D as DEFAULT_CATEGORICAL_PALETTE, j as DEFAULT_FALLBACK_PALETTE, k as DEFAULT_MUTED_OPACITY, l as DEFAULT_SAM_MODEL_ID, m as DEFAULT_SPATIAL_VIEW, n as DEFAULT_VISIBLE_CHANNELS, H as HexColorPickerComponent, I as IMAGE_STATE_PORT, o as INSTANCE_SEGMENTER, p as ImageConverterService, L as LUT_COLORS, M as MISSING_COLOR, q as MultiPolygon, N as NAPARI_DECIMATE_OPTIONS, s as NAPARI_DEFAULT_DECIMATE, t as NO_CATEGORY, u as NO_OBSERVATION, P as PLOT_MODE_CONTEXT, v as PLOT_MODE_SESSION, w as PLOT_TYPE_CONTRIBUTIONS, x as PLOT_TYPE_DESCRIPTORS, y as PREFERENCES_PORT, z as PlotType, B as Polygon, E as ProcessingImage, R as REGION_EDITOR_API, F as REGION_IO_PORT, G as REGION_SOURCE_MIN_CHARS, J as Rectangle, K as Region, O as RegionEditorComponent, Q as RoutingVisualizerService, S as SAM_MODELS, T as SEMANTIC_SEGMENTER, U as SPATIAL_DATA_PORT, V as SPATIAL_WIRE_VERSION, W as ShapeSelection, X as SpatialChartsComponent, Y as SpatialControlsComponent, Z as SpatialDataHttpService, _ as SpatialSelectionStore, $ as SupersededError, a0 as TILE_ACCESS_PORT, a1 as TOOLBAR_TOOLS, a2 as VISUALIZER, a3 as VIZ_ALERT_TOAST_KEY, a4 as VIZ_CONFIG, a5 as VIZ_TOAST_KEY, a6 as ViewerFeature, a7 as VisualizationModule, a8 as VisualizerComponent, a9 as assertManifestVersion, aa as benefitsFromGrouping, ab as buildOmicsTraces, ac as capabilitiesOf, ad as clearSamModelCache, ae as contrastWindow, af as contributedPlotTypeOption, ag as countMask, ah as cropImage, ai as datasetFromManifest, aj as decodeColumn, ak as decodeCoords, al as decodeFeatureVector, am as decodePolygons, an as decodeRadius, ao as defaultChannelVisible, ap as defaultPresetSet, aq as dialogToolContributions, ar as emptySelection, as as encodeCategorical, at as encodeContinuous, au as expandZips, av as findColumnMeta, aw as getDefaultSamModelId, g as getOrtWasmBase, ax as getPlotTypeDescriptor, ay as getSamModel, az as hasZarrMetadata, aA as hydrateBounds, aB as importFile, aC as isBuiltinPlotType, aD as isCanvasToolId, aE as isCategoricalColumn, aF as isContinuousColumn, aG as isDialogToolContribution, aH as isEmptyPresetSet, aI as isLabelVolumeName, aJ as isLittleEndian, aK as isNapari3d, aL as isNapariIsosurface, aM as isNapariScatter, aN as isNapariScatter3d, aO as isNapariSurface, aP as isNapariVolume, aQ as isSamModelReady, aR as isSpatialOmics, aS as isSpatialOmics3d, aT as isThreeDimensional, aU as isZipName, aV as labelClasses, aW as labelSliceGeoJson, aX as lutFor, aY as markerDiameters, aZ as maskToIndices, a_ as mutedFromSelection, a$ as omicsLayout, b0 as openAnnotations, b1 as openGeoJsonRegionSource, b2 as openHttpRegionSource, b3 as parsePresetSet, b4 as planRegionImport, b5 as pointInRing, b6 as provideVisualization, b7 as readAnnotationSlices, b8 as readLabelVolume, b9 as readOmeZarrLabels, ba as readZip, bb as rendererOwnsWheel, bc as resolveCategoryColors, bd as selectByCategory, be as selectInRegions, bf as setDefaultSamModel, bg as setOrtWasmBase, bh as setSamModelUrls, bi as sortToolContributions, bj as visibleToolContributions } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-
|
|
1
|
+
export { A as ALL_GENES, b as ALL_TOOLBAR_TOOLS, c as ANNOTATION_IMAGE_MAX_DIM, C as CANVAS_TOOL_IDS, d as CELL_SEGMENTER, e as CHANNEL_HISTOGRAM_API, h as CellposeSegmenterService, i as ChannelHistogramComponent, D as DEFAULT_CATEGORICAL_PALETTE, j as DEFAULT_FALLBACK_PALETTE, k as DEFAULT_MUTED_OPACITY, l as DEFAULT_SAM_MODEL_ID, m as DEFAULT_SPATIAL_VIEW, n as DEFAULT_VISIBLE_CHANNELS, H as HexColorPickerComponent, I as IMAGE_STATE_PORT, o as INSTANCE_SEGMENTER, p as ImageConverterService, L as LUT_COLORS, M as MISSING_COLOR, q as MultiPolygon, N as NAPARI_DECIMATE_OPTIONS, s as NAPARI_DEFAULT_DECIMATE, t as NO_CATEGORY, u as NO_OBSERVATION, P as PLOT_MODE_CONTEXT, v as PLOT_MODE_SESSION, w as PLOT_TYPE_CONTRIBUTIONS, x as PLOT_TYPE_DESCRIPTORS, y as PREFERENCES_PORT, z as PlotType, B as Polygon, E as ProcessingImage, R as REGION_EDITOR_API, F as REGION_IO_PORT, G as REGION_SOURCE_MIN_CHARS, J as Rectangle, K as Region, O as RegionEditorComponent, Q as RoutingVisualizerService, S as SAM_MODELS, T as SEMANTIC_SEGMENTER, U as SPATIAL_DATA_PORT, V as SPATIAL_WIRE_VERSION, W as ShapeSelection, X as SpatialChartsComponent, Y as SpatialControlsComponent, Z as SpatialDataHttpService, _ as SpatialSelectionStore, $ as SupersededError, a0 as TILE_ACCESS_PORT, a1 as TOOLBAR_TOOLS, a2 as VISUALIZER, a3 as VIZ_ALERT_TOAST_KEY, a4 as VIZ_CONFIG, a5 as VIZ_TOAST_KEY, a6 as ViewerFeature, a7 as VisualizationModule, a8 as VisualizerComponent, a9 as assertManifestVersion, aa as benefitsFromGrouping, ab as buildOmicsTraces, ac as capabilitiesOf, ad as clearSamModelCache, ae as contrastWindow, af as contributedPlotTypeOption, ag as countMask, ah as cropImage, ai as datasetFromManifest, aj as decodeColumn, ak as decodeCoords, al as decodeFeatureVector, am as decodePolygons, an as decodeRadius, ao as defaultChannelVisible, ap as defaultPresetSet, aq as dialogToolContributions, ar as emptySelection, as as encodeCategorical, at as encodeContinuous, au as expandZips, av as findColumnMeta, aw as getDefaultSamModelId, g as getOrtWasmBase, ax as getPlotTypeDescriptor, ay as getSamModel, az as hasZarrMetadata, aA as hydrateBounds, aB as importFile, aC as isBuiltinPlotType, aD as isCanvasToolId, aE as isCategoricalColumn, aF as isContinuousColumn, aG as isDialogToolContribution, aH as isEmptyPresetSet, aI as isLabelVolumeName, aJ as isLittleEndian, aK as isNapari3d, aL as isNapariIsosurface, aM as isNapariScatter, aN as isNapariScatter3d, aO as isNapariSurface, aP as isNapariVolume, aQ as isSamModelReady, aR as isSpatialOmics, aS as isSpatialOmics3d, aT as isThreeDimensional, aU as isZipName, aV as labelClasses, aW as labelSliceGeoJson, aX as lutFor, aY as markerDiameters, aZ as maskToIndices, a_ as mutedFromSelection, a$ as omicsLayout, b0 as openAnnotations, b1 as openGeoJsonRegionSource, b2 as openHttpRegionSource, b3 as parsePresetSet, b4 as planRegionImport, b5 as pointInRing, b6 as provideVisualization, b7 as readAnnotationSlices, b8 as readLabelVolume, b9 as readOmeZarrLabels, ba as readZip, bb as rendererOwnsWheel, bc as resolveCategoryColors, bd as selectByCategory, be as selectInRegions, bf as setDefaultSamModel, bg as setOrtWasmBase, bh as setSamModelUrls, bi as sortToolContributions, bj as visibleToolContributions } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-CYsPzX3J.mjs';
|
|
2
2
|
import '@angular/core';
|
|
3
3
|
import '@angular/common/http';
|
|
4
4
|
import 'napari-js';
|
|
@@ -38,6 +38,7 @@ import 'primeng/selectbutton';
|
|
|
38
38
|
import 'primeng/treeselect';
|
|
39
39
|
import 'plotly.js-dist-min';
|
|
40
40
|
import 'primeng/multiselect';
|
|
41
|
+
import 'primeng/inputswitch';
|
|
41
42
|
import 'image-js';
|
|
42
43
|
import 'buffer';
|
|
43
44
|
import 'openseadragon';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jax-data-science-sci-image-visualizer.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"jax-data-science-sci-image-visualizer.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EventEmitter, OnDestroy, OnInit } from '@angular/core';
|
|
2
2
|
import { TreeNode } from 'primeng/api';
|
|
3
|
+
import { ChannelGroupRow, ChannelTreeRow } from './channel-tree';
|
|
3
4
|
import { IChannelHistogramApi, IChannelState, IHistogram } from '../contracts/channel-histogram-api.contract';
|
|
4
5
|
import * as i0 from "@angular/core";
|
|
5
6
|
/**
|
|
@@ -27,7 +28,11 @@ export declare class ChannelHistogramComponent implements OnInit, OnDestroy {
|
|
|
27
28
|
color: string;
|
|
28
29
|
}[];
|
|
29
30
|
protected channels: IChannelState[];
|
|
31
|
+
/** The channels as the table lists them: in their groups when the image groups them, else flat. */
|
|
32
|
+
protected rows: ChannelTreeRow[];
|
|
30
33
|
protected selected: IChannelState | null;
|
|
34
|
+
/** The groups folded shut, by key; kept across emissions and images with the same groups. */
|
|
35
|
+
private readonly collapsed;
|
|
31
36
|
protected invert: boolean;
|
|
32
37
|
protected logScale: boolean;
|
|
33
38
|
/** Bounded retries while the (async) histogram sampling resolves. */
|
|
@@ -80,6 +85,15 @@ export declare class ChannelHistogramComponent implements OnInit, OnDestroy {
|
|
|
80
85
|
* dragging, since each change resets the timer). */
|
|
81
86
|
private markBusy;
|
|
82
87
|
protected onVisibleToggle(ch: IChannelState, value: boolean): void;
|
|
88
|
+
/** The table row of the selected channel, for its highlight. */
|
|
89
|
+
protected get selectedRow(): ChannelTreeRow | null;
|
|
90
|
+
/** A row was selected: a channel's selects the channel (a group's row cannot be). */
|
|
91
|
+
protected onRowSelect(row: ChannelTreeRow): void;
|
|
92
|
+
/** Fold a group shut, or open it. */
|
|
93
|
+
protected toggleGroup(row: ChannelGroupRow): void;
|
|
94
|
+
/** Show or hide every channel of a group. */
|
|
95
|
+
protected onGroupVisibleToggle(row: ChannelGroupRow, value: boolean): void;
|
|
96
|
+
protected trackRow(_: number, row: ChannelTreeRow): string;
|
|
83
97
|
protected onColorChange(ch: IChannelState, color: string): void;
|
|
84
98
|
protected onColormap(node: TreeNode): void;
|
|
85
99
|
protected onInvert(value: boolean): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { IChannelState } from '../contracts/channel-histogram-api.contract';
|
|
2
|
+
/** A group of channels in the Channels pane: its label, how many channels it holds, how many show. */
|
|
3
|
+
export interface ChannelGroupRow {
|
|
4
|
+
kind: 'group';
|
|
5
|
+
/** The group's path, its labels joined: stable across emissions, so it keys the row and its folding. */
|
|
6
|
+
key: string;
|
|
7
|
+
label: string;
|
|
8
|
+
depth: number;
|
|
9
|
+
/** The channels below it, at any depth, by index. */
|
|
10
|
+
channels: number[];
|
|
11
|
+
/** How many of them are visible. */
|
|
12
|
+
shown: number;
|
|
13
|
+
expanded: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** A channel in the Channels pane, below its groups. */
|
|
16
|
+
export interface ChannelRow {
|
|
17
|
+
kind: 'channel';
|
|
18
|
+
key: string;
|
|
19
|
+
depth: number;
|
|
20
|
+
channel: IChannelState;
|
|
21
|
+
}
|
|
22
|
+
export type ChannelTreeRow = ChannelGroupRow | ChannelRow;
|
|
23
|
+
/** Whether any channel says which group it is in. */
|
|
24
|
+
export declare function hasChannelGroups(channels: readonly IChannelState[]): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The Channels pane's rows: each group (as the image's metadata groups its channels, outermost
|
|
27
|
+
* first) followed by what is in it, in channel order — a group where its first channel is — and
|
|
28
|
+
* the channels in no group where they are. A group in `collapsed` (by key) hides what is in it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function channelTreeRows(channels: readonly IChannelState[], collapsed?: ReadonlySet<string>): ChannelTreeRow[];
|
|
@@ -17,6 +17,8 @@ export interface IChannelState {
|
|
|
17
17
|
max: number;
|
|
18
18
|
gamma: number;
|
|
19
19
|
visible: boolean;
|
|
20
|
+
/** The groups the channel is in, outermost first (`IChannelInfo.group`); absent when it is in none. */
|
|
21
|
+
group?: string[];
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
22
24
|
* How many channels of a multichannel image start visible. Each visible channel is drawn
|
|
@@ -23,6 +23,12 @@ export interface IChannelInfo {
|
|
|
23
23
|
maxAllowed?: number;
|
|
24
24
|
/** Whether the channel starts visible; else the first `DEFAULT_VISIBLE_CHANNELS` do. */
|
|
25
25
|
visible?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* The groups the channel is in, outermost first, as the image's metadata describes them (an
|
|
28
|
+
* instrument panel's kind of target, then the panel: `['Protein IF', 'Cell Signaling']`); the
|
|
29
|
+
* Channels pane lists the channels as a tree of them. Absent or empty for a channel in none.
|
|
30
|
+
*/
|
|
31
|
+
group?: string[] | null;
|
|
26
32
|
}
|
|
27
33
|
/** Per-series image metadata (physical size + dimensions). */
|
|
28
34
|
export interface IImageMetadata {
|
|
@@ -71,6 +71,8 @@ export interface NapariSettings {
|
|
|
71
71
|
surfaceWireframe: boolean;
|
|
72
72
|
/** The volume's user Z-height factor (1 = the volume's natural proportions). */
|
|
73
73
|
volumeZScale: number;
|
|
74
|
+
/** The 3D spatial view's Z stretch (1 = the data's true proportions). */
|
|
75
|
+
spatialZScale: number;
|
|
74
76
|
}
|
|
75
77
|
/** The stack-loading flag and progress the host shows while frames load. */
|
|
76
78
|
export interface StackProgress {
|
|
@@ -1,17 +1,44 @@
|
|
|
1
|
+
import type { AxisLabelSpec } from 'napari-js/overlays';
|
|
1
2
|
import { IHistogram } from '../../contracts/channel-histogram-api.contract';
|
|
3
|
+
import { ISurface3dControls } from '../../contracts/visualizer.contract';
|
|
2
4
|
import { SpatialDataset, SpatialObservations } from '../../contracts/spatial-dataset.contract';
|
|
3
5
|
import { SpatialViewState } from '../../contracts/display-types';
|
|
4
6
|
import { SpatialSelectionMask } from '../../spatial/spatial-selection';
|
|
5
7
|
import type { IImageInfo } from '../../contracts/image.contract';
|
|
6
8
|
import { RegionMesh3D } from './napari-volume-region-meshes';
|
|
7
9
|
import { Spatial3dScalars } from './napari-spatial-encoding';
|
|
10
|
+
import { Box3 } from './napari-axes-gizmo';
|
|
8
11
|
import { SpatialSceneBase, SpatialSession } from './napari-spatial-scene';
|
|
9
12
|
import type { NapariScene, SceneContext } from './napari-scene';
|
|
13
|
+
/** The least Z stretch: a tenth of the true depth. */
|
|
14
|
+
export declare const SPATIAL_Z_SCALE_MIN = 0.1;
|
|
15
|
+
/**
|
|
16
|
+
* The most a box's Z may be stretched: until it is twice as deep as it is wide, so even a section
|
|
17
|
+
* microns thick and millimetres wide can be stood up into a block — but never less than the
|
|
18
|
+
* Volume view's 10x, nor more than {@link SPATIAL_Z_SCALE_CEILING}.
|
|
19
|
+
*/
|
|
20
|
+
export declare function spatialZScaleMax(box: Box3): number;
|
|
21
|
+
/**
|
|
22
|
+
* The Z handle's drag (px) per doubling: the Volume view's 180 px, less when the range is wider,
|
|
23
|
+
* so going from the true depth to the most stretch is about the same drag (~540 px) either way.
|
|
24
|
+
*/
|
|
25
|
+
export declare function spatialZPixelsPerDouble(maxScale: number): number;
|
|
26
|
+
/**
|
|
27
|
+
* The X/Y/Z axis-end labels for a box (as drawn: Z stretched by `zScale`), its true lengths in µm
|
|
28
|
+
* when the dataset declares `micronsPerUnit`, else in its own units. A stretched Z says by how much,
|
|
29
|
+
* so its length is never read off the drawing.
|
|
30
|
+
*/
|
|
31
|
+
export declare function spatialAxesLabels(drawn: Box3, zScale: number, micronsPerUnit?: number): AxisLabelSpec[];
|
|
10
32
|
/**
|
|
11
33
|
* SPATIAL_OMICS_3D (review Appendix B, cluster K): observations as a 3D point cloud under the
|
|
12
34
|
* orbit camera, with the dataset's reference volume around it, per-cluster density volumes and the
|
|
13
35
|
* 3D gene map.
|
|
14
36
|
*
|
|
37
|
+
* Everything sits in one box centred on the world origin (see {@link ensureFrame}): the reference
|
|
38
|
+
* volume's, else the density grid's. The Volume view's axes gizmo and Z-height handle are drawn on
|
|
39
|
+
* it, and the handle stretches Z: the cloud through its `scale3d`, every volume through its voxel
|
|
40
|
+
* size, so nothing is re-uploaded while dragging (see {@link setZScale}).
|
|
41
|
+
*
|
|
15
42
|
* Thinner than the 2D scene: there is no tissue image to render (a registered volume like the
|
|
16
43
|
* Allen CCF has no single reference plane), so no readback and no navigator; the 3D scale bar
|
|
17
44
|
* follows the dataset (see {@link installScaleBar}). The region tools work in SCREEN space through
|
|
@@ -46,6 +73,14 @@ export declare class Spatial3dScene extends SpatialSceneBase implements NapariSc
|
|
|
46
73
|
private geneMapKey;
|
|
47
74
|
/** Offset applied to observation coordinates to sit them in the volume's box. */
|
|
48
75
|
private origin;
|
|
76
|
+
/** The box everything sits in, before the Z stretch, centred on the origin (see ensureFrame). */
|
|
77
|
+
private box;
|
|
78
|
+
private boxKey;
|
|
79
|
+
/** The axes gizmo on {@link box} and the handle that stretches its Z (see setZScale). */
|
|
80
|
+
private gizmo;
|
|
81
|
+
private zHandle;
|
|
82
|
+
/** Each volume layer's own voxel size, before the Z stretch. */
|
|
83
|
+
private readonly baseVoxels;
|
|
49
84
|
/**
|
|
50
85
|
* napari-js's depth-aware pick over the cloud, and the projection the ROI selection reads. It
|
|
51
86
|
* owns the caching policy: re-project lazily, on the first pick after the camera, the canvas or
|
|
@@ -106,6 +141,25 @@ export declare class Spatial3dScene extends SpatialSceneBase implements NapariSc
|
|
|
106
141
|
setZ(): void;
|
|
107
142
|
/** A cloud has no intensity distribution for the histogram pane. */
|
|
108
143
|
histogram(): IHistogram | null;
|
|
144
|
+
/** The toolbar's 3D controls, as the Volume view's: drag mode, re-framing and the axes. */
|
|
145
|
+
surface3dControls(): ISurface3dControls | null;
|
|
146
|
+
/**
|
|
147
|
+
* Stretch Z by `factor` (1 = the data's true proportions), as the Z handle drags it: the cloud
|
|
148
|
+
* through its `scale3d`, each volume through its voxel size, the axes and the region surfaces
|
|
149
|
+
* with them — all about the world origin, which the box is centred on, so they stay aligned.
|
|
150
|
+
* Nothing is re-uploaded. Kept in the settings, so a re-plot keeps it.
|
|
151
|
+
*/
|
|
152
|
+
setZScale(factor: number): void;
|
|
153
|
+
/** The Z stretch in effect: the setting, within what this box allows. */
|
|
154
|
+
zScale(): number;
|
|
155
|
+
private applyZScale;
|
|
156
|
+
/** `box` with its Z stretched as drawn. */
|
|
157
|
+
private drawnBox;
|
|
158
|
+
/**
|
|
159
|
+
* Add a volume layer with its Z stretched as the rest of the scene is, remembering its own
|
|
160
|
+
* voxel size so a later stretch can be applied to it.
|
|
161
|
+
*/
|
|
162
|
+
private addVolume;
|
|
109
163
|
/**
|
|
110
164
|
* Every observation's position on the canvas under the current 3D camera (for the ROI
|
|
111
165
|
* selection), indexed by observation with NaN for anything not drawn. Null before the cloud
|
|
@@ -224,18 +278,35 @@ export declare class Spatial3dScene extends SpatialSceneBase implements NapariSc
|
|
|
224
278
|
*/
|
|
225
279
|
private densityGroups;
|
|
226
280
|
/**
|
|
227
|
-
* Add (or keep) the dataset's reference volume
|
|
228
|
-
*
|
|
281
|
+
* Add (or keep) the dataset's reference volume. The offset that sits the observations inside
|
|
282
|
+
* it is {@link ensureFrame}'s.
|
|
229
283
|
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* the volume's own frame with its near corner at the coordinate origin. So the two only line
|
|
233
|
-
* up if the POINTS move — by half the box — which is what {@link origin} is.
|
|
234
|
-
*
|
|
235
|
-
* A failed or absent volume is not fatal: the cloud renders on its own, at its own
|
|
236
|
-
* coordinates, and the camera frames the points instead.
|
|
284
|
+
* A failed or absent volume is not fatal: the cloud renders on its own, in the same box, and
|
|
285
|
+
* the camera frames the points instead.
|
|
237
286
|
*/
|
|
238
287
|
private ensureVolume;
|
|
288
|
+
/**
|
|
289
|
+
* The box everything sits in, centred on the world origin, and the offset that sits the
|
|
290
|
+
* observations in it; with the axes gizmo and the Z handle on it.
|
|
291
|
+
*
|
|
292
|
+
* `VolumeLayer` has no translate: napari-js maps a volume's unit cube to a world box centred on
|
|
293
|
+
* the origin, sized `dims x voxelSize`. The observations, by contract, are in the reference
|
|
294
|
+
* volume's frame with its near corner at the coordinate origin, and the density grid is measured
|
|
295
|
+
* from the coordinate origin the same way (`densityGrid`), so its box is the reference volume's
|
|
296
|
+
* when there is one. So the POINTS move by half that box — {@link origin} — and every volume, the
|
|
297
|
+
* reference, the densities and the gene map, lines up with them and with each other. Without a
|
|
298
|
+
* reference volume this centres the cloud too, which the axes and the Z stretch need: both work
|
|
299
|
+
* about the origin.
|
|
300
|
+
*/
|
|
301
|
+
private ensureFrame;
|
|
302
|
+
/**
|
|
303
|
+
* The Volume view's axes gizmo and Z-height handle, on {@link box}. The handle floats past the
|
|
304
|
+
* top of the Z axis (the box's min-XY corner, where the blue axis and its label are), by a share
|
|
305
|
+
* of the box's largest side rather than of its depth, so it clears the label even while a thin
|
|
306
|
+
* section is drawn flat.
|
|
307
|
+
*/
|
|
308
|
+
private installAxes;
|
|
309
|
+
private removeAxes;
|
|
239
310
|
/** Drop every layer of the cloud (the dataset went away, or has no z). */
|
|
240
311
|
private removeLayers;
|
|
241
312
|
/**
|
package/lib/spatial/lod.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* `zoom · scale`. For a Xenium bundle the data unit is the micron and the world unit the
|
|
13
13
|
* morphology pixel.
|
|
14
14
|
*/
|
|
15
|
-
import type { SpatialBounds, SpatialDataset, SpatialImageRef, SpatialTileLevel } from '../contracts/spatial-dataset.contract';
|
|
15
|
+
import type { SpatialBounds, SpatialDataset, SpatialImageRef, SpatialPolygonTilesMeta, SpatialTileLevel } from '../contracts/spatial-dataset.contract';
|
|
16
16
|
import type { SpatialViewState } from '../contracts/display-types';
|
|
17
17
|
/** A rectangle in observation coordinates. */
|
|
18
18
|
export interface DataRect {
|
|
@@ -29,6 +29,16 @@ export interface TileKey {
|
|
|
29
29
|
}
|
|
30
30
|
/** Whether cell outlines are drawn: the user's choice, else on when the dataset has any. */
|
|
31
31
|
export declare function cellsShown(dataset: Pick<SpatialDataset, 'polygonTiles' | 'polygons'> | null, view: Pick<SpatialViewState, 'showCells'>): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* The boundary sets drawn for the view's `cellSet`: that set when the dataset has it, else the
|
|
34
|
+
* dataset's default, else its first; for "both", that one with the other set beside it. Null when
|
|
35
|
+
* the dataset has no set. The renderer and the Cells panel's switches both read it, so a set kept
|
|
36
|
+
* from another dataset shows as what is actually drawn.
|
|
37
|
+
*/
|
|
38
|
+
export declare function drawnCellSets(tiled: Pick<SpatialPolygonTilesMeta, 'sets' | 'defaultSet'> | null | undefined, cellSet: string | null | undefined): {
|
|
39
|
+
set: string;
|
|
40
|
+
nucleusSet?: string;
|
|
41
|
+
} | null;
|
|
32
42
|
/** A column holding a hand-curated cell-type annotation, by naming convention. */
|
|
33
43
|
export declare function isCuratedColumn(name: string): boolean;
|
|
34
44
|
/**
|
|
@@ -39,6 +39,15 @@ export declare class SpatialCellsPanelComponent implements OnChanges {
|
|
|
39
39
|
* its buttons whenever the array identity changes — which made them impossible to click.
|
|
40
40
|
*/
|
|
41
41
|
protected cellSetOptions: PanelOption<string>[];
|
|
42
|
+
/**
|
|
43
|
+
* The boundary sets, the cells' first, each with a switch when there are at most two (as
|
|
44
|
+
* CytoCanvas's "Cell Boundaries" and "Nuclear Boundaries"): either, both or neither drawn.
|
|
45
|
+
* More sets keep the one-of choice.
|
|
46
|
+
*/
|
|
47
|
+
protected boundarySets: {
|
|
48
|
+
name: string;
|
|
49
|
+
label: string;
|
|
50
|
+
}[];
|
|
42
51
|
/** Cell colour modes on offer (Xenium Explorer's "Cell Color"). */
|
|
43
52
|
protected cellColorOptions: PanelOption<SpatialViewState['cellColorMode']>[];
|
|
44
53
|
/** The density colour bar, rebuilt only when its colormap changes (a 256-entry LUT). */
|
|
@@ -47,12 +56,20 @@ export declare class SpatialCellsPanelComponent implements OnChanges {
|
|
|
47
56
|
ngOnChanges(changes: SimpleChanges): void;
|
|
48
57
|
/** Whether outlines are on — the explicit choice, or automatically for data that has them. */
|
|
49
58
|
protected get cellsOn(): boolean;
|
|
50
|
-
/** The boundary set drawn
|
|
59
|
+
/** The boundary set drawn, or "both", as the renderer resolves the view's choice: a set this
|
|
60
|
+
* dataset has, else its default, else its first. */
|
|
51
61
|
protected get activeCellSet(): string | null;
|
|
52
62
|
/** Fill opacity as a 0–100 number, for the box beside the slider. */
|
|
53
63
|
protected get cellOpacityPercent(): number;
|
|
54
64
|
protected onShowCells(on: boolean): void;
|
|
55
65
|
protected onCellSet(set: string): void;
|
|
66
|
+
/** Whether boundary set `name` is drawn: outlines on, and that set or both. */
|
|
67
|
+
protected boundaryOn(name: string): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* A set's switch: on draws it with the other when that one is drawn ("both"), else alone;
|
|
70
|
+
* off leaves the other drawn, or no outlines at all.
|
|
71
|
+
*/
|
|
72
|
+
protected onBoundary(name: string, on: boolean): void;
|
|
56
73
|
protected onCellDraw(draw: SpatialViewState['cellDraw']): void;
|
|
57
74
|
protected onCellOpacity(value: number | undefined): void;
|
|
58
75
|
protected onCellOpacityPercent(v: number | null): void;
|
|
@@ -4,6 +4,8 @@ import type { SpatialDataset } from '../../contracts/spatial-dataset.contract';
|
|
|
4
4
|
import { SpatialViewState, TranscriptGlyphName } from '../../contracts/display-types';
|
|
5
5
|
import { GeneTreeNode } from '../../spatial/spatial-panel-model';
|
|
6
6
|
import * as i0 from "@angular/core";
|
|
7
|
+
/** How the gene rows are ordered: by name, or by their transcripts in view, most first. */
|
|
8
|
+
export type GeneSort = 'name' | 'count';
|
|
7
9
|
/** One gene row as the template draws it, derived once per input change. */
|
|
8
10
|
interface GeneRowView {
|
|
9
11
|
shown: boolean;
|
|
@@ -29,6 +31,11 @@ interface AllGeneRowView {
|
|
|
29
31
|
* expands to every gene of the dataset, each with its eye when the dataset lists its genes
|
|
30
32
|
* in code order (`transcriptTiles.genes`), filtered by name past a few hundred.
|
|
31
33
|
*
|
|
34
|
+
* Both lists are ordered by name, or by their transcripts in view, most first (the
|
|
35
|
+
* sort switch, offered once the renderer reports counts). The all-genes list is sorted
|
|
36
|
+
* before it is cut to its first rows, so by count those are the genes with the most
|
|
37
|
+
* transcripts in view.
|
|
38
|
+
*
|
|
32
39
|
* Writes only through `controls.setViewState`.
|
|
33
40
|
*/
|
|
34
41
|
export declare class SpatialGeneTreeComponent implements OnChanges {
|
|
@@ -47,6 +54,10 @@ export declare class SpatialGeneTreeComponent implements OnChanges {
|
|
|
47
54
|
/** Swatches in the picker: the categorical palette genes are coloured from by default. */
|
|
48
55
|
/** The tree, rebuilt when the view changes — never per change-detection pass. */
|
|
49
56
|
protected geneTree: GeneTreeNode[];
|
|
57
|
+
/** {@link geneTree} with each node's genes in {@link geneSort} order: what the template draws. */
|
|
58
|
+
protected sortedTree: GeneTreeNode[];
|
|
59
|
+
/** The rows' order. By count only while the renderer reports counts (see {@link sortShown}). */
|
|
60
|
+
protected geneSort: GeneSort;
|
|
50
61
|
/** Per gene: its eye, swatch, glyph and count — rebuilt with the inputs (and when the
|
|
51
62
|
* cells' group colours arrive), so the template does no per-row lookups per check. */
|
|
52
63
|
protected rows: Map<string, GeneRowView>;
|
|
@@ -78,6 +89,13 @@ export declare class SpatialGeneTreeComponent implements OnChanges {
|
|
|
78
89
|
constructor(zone: NgZone, cdr: ChangeDetectorRef);
|
|
79
90
|
ngOnChanges(changes: SimpleChanges): void;
|
|
80
91
|
private rebuildRows;
|
|
92
|
+
/** Whether the rows are ordered by count: chosen, and counts to order by. */
|
|
93
|
+
protected get byCount(): boolean;
|
|
94
|
+
/** The sort switch is offered once there are counts to sort by. */
|
|
95
|
+
protected get sortShown(): boolean;
|
|
96
|
+
protected onGeneSort(sort: GeneSort): void;
|
|
97
|
+
/** `genes` in the rows' order: by count, most first (ties by name), else by name. */
|
|
98
|
+
private sorted;
|
|
81
99
|
/**
|
|
82
100
|
* The all-genes list's rows: the genes matching the filter, up to the cap. Built only while
|
|
83
101
|
* the list is open: the counts in view change with every pan, and the list can be thousands.
|
|
@@ -33,6 +33,10 @@ export declare class VisualizerStore {
|
|
|
33
33
|
private defaultChannelStates;
|
|
34
34
|
/** The image the current channel states were derived for (see setImageMeta). */
|
|
35
35
|
private channelImageKey;
|
|
36
|
+
/** The channel info the host gave with the image (see setImageMeta), apart from what a tile
|
|
37
|
+
* descriptor added since: each adoption merges against this, so a later descriptor can change
|
|
38
|
+
* or clear what an earlier one put in. */
|
|
39
|
+
private hostChannelInfo;
|
|
36
40
|
private readonly grayscale$;
|
|
37
41
|
private readonly invert$;
|
|
38
42
|
private presetSet;
|
|
@@ -82,11 +86,13 @@ export declare class VisualizerStore {
|
|
|
82
86
|
* "Actin" or "Cell-Membrane"), where a host's metadata (jit-ui's `/metadata`) carries none, so
|
|
83
87
|
* the Channels pane showed "Channel 1", "Channel 2"… The host's own names and tints win; only a
|
|
84
88
|
* channel still showing its derived default takes the descriptor's, so an edit made in the pane
|
|
85
|
-
* stays. A no-op when the channel counts differ, or for RGB.
|
|
89
|
+
* stays. A no-op when the channel counts differ, or for RGB. Each adoption replaces the last
|
|
90
|
+
* one's names, tints and groups: only the host's own count as overrides.
|
|
86
91
|
*/
|
|
87
92
|
adoptChannelInfo(info: ReadonlyArray<{
|
|
88
93
|
name?: string;
|
|
89
94
|
color?: string;
|
|
95
|
+
group?: string[] | null;
|
|
90
96
|
}> | null | undefined): void;
|
|
91
97
|
/**
|
|
92
98
|
* Publish the current image's metadata and derive its channels.
|
|
@@ -216,6 +216,12 @@ export declare class ToolbarComponent {
|
|
|
216
216
|
protected get isNapariMode(): boolean;
|
|
217
217
|
/** ISOSURFACE (Plotly or napari-js WebGPU, either resolution) shows the isovalue range slider. */
|
|
218
218
|
protected get isIsosurfaceMode(): boolean;
|
|
219
|
+
/**
|
|
220
|
+
* The views that draw the 3D axes gizmo (an `Axes3dGizmo`): napari-js's volume, isosurface and
|
|
221
|
+
* surface, and the 3D spatial cloud. Not every napari-js view: its 2D image and scatter and its
|
|
222
|
+
* 3D scatter draw none, and a toggle there would do nothing.
|
|
223
|
+
*/
|
|
224
|
+
protected get showsAxesToggle(): boolean;
|
|
219
225
|
/** The napari-js WebGPU surface — shows the wireframe toggle. */
|
|
220
226
|
protected get isNapariSurfaceMode(): boolean;
|
|
221
227
|
/** Any napari-js WebGPU 3D type (volume/isosurface/surface) — shows the Resolution control. */
|
|
@@ -16,12 +16,12 @@ export declare class ViewControlsComponent {
|
|
|
16
16
|
activeDragMode: string | null;
|
|
17
17
|
/** The 3D camera mode (`turntable` / `orbit` / …). */
|
|
18
18
|
activeSurface3dMode: string;
|
|
19
|
-
/** A napari-js WebGPU view (axes toggle, napari camera). */
|
|
20
|
-
isNapariMode: boolean;
|
|
21
19
|
/** The napari-js surface (shows the wireframe toggle). */
|
|
22
20
|
isNapariSurfaceMode: boolean;
|
|
23
21
|
/** A napari-js 3D view (shows the Resolution dropdown). */
|
|
24
22
|
isNapari3dMode: boolean;
|
|
23
|
+
/** Whether the view draws the 3D axes gizmo (shows its toggle). */
|
|
24
|
+
axesToggle: boolean;
|
|
25
25
|
/** The napari 3D axes gizmo is shown. */
|
|
26
26
|
axesVisible: boolean;
|
|
27
27
|
/** The napari surface wireframe is on. */
|
|
@@ -50,5 +50,5 @@ export declare class ViewControlsComponent {
|
|
|
50
50
|
value: number;
|
|
51
51
|
}[];
|
|
52
52
|
static ɵfac: i0.ɵɵFactoryDeclaration<ViewControlsComponent, never>;
|
|
53
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<ViewControlsComponent, "toolbar-view-controls", never, { "isHeatmap": { "alias": "isHeatmap"; "required": false; }; "zoomTools": { "alias": "zoomTools"; "required": false; }; "isImageView": { "alias": "isImageView"; "required": false; }; "activeDragMode": { "alias": "activeDragMode"; "required": false; }; "activeSurface3dMode": { "alias": "activeSurface3dMode"; "required": false; }; "
|
|
53
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<ViewControlsComponent, "toolbar-view-controls", never, { "isHeatmap": { "alias": "isHeatmap"; "required": false; }; "zoomTools": { "alias": "zoomTools"; "required": false; }; "isImageView": { "alias": "isImageView"; "required": false; }; "activeDragMode": { "alias": "activeDragMode"; "required": false; }; "activeSurface3dMode": { "alias": "activeSurface3dMode"; "required": false; }; "isNapariSurfaceMode": { "alias": "isNapariSurfaceMode"; "required": false; }; "isNapari3dMode": { "alias": "isNapari3dMode"; "required": false; }; "axesToggle": { "alias": "axesToggle"; "required": false; }; "axesVisible": { "alias": "axesVisible"; "required": false; }; "wireframeActive": { "alias": "wireframeActive"; "required": false; }; "resolutionScale": { "alias": "resolutionScale"; "required": false; }; }, { "toggleDragMode": "toggleDragMode"; "zoomIn": "zoomIn"; "zoomOut": "zoomOut"; "toggleSurface3dMode": "toggleSurface3dMode"; "resetSurfaceCamera": "resetSurfaceCamera"; "toggleAxes": "toggleAxes"; "toggleWireframe": "toggleWireframe"; "selectResolution": "selectResolution"; }, never, never, true, never>;
|
|
54
54
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jax-data-science/sci-image-visualizer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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.
|
|
43
|
+
"napari-js": "^0.19.0",
|
|
44
44
|
"openseadragon": "^6.0.2",
|
|
45
45
|
"plotly.js-dist-min": "^3.0.1",
|
|
46
46
|
"tslib": "^2.3.0"
|