@jax-data-science/sci-image-visualizer 0.11.0 → 0.12.1

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 (38) hide show
  1. package/esm2022/index.mjs +4 -1
  2. package/esm2022/lib/contracts/image.contract.mjs +1 -1
  3. package/esm2022/lib/implementations/napari-js/napari-region-overlay.mjs +18 -6
  4. package/esm2022/lib/implementations/napari-js/napari-spatial-3d-scene.mjs +70 -2
  5. package/esm2022/lib/implementations/napari-js/napari-tool-bridge.mjs +29 -14
  6. package/esm2022/lib/implementations/napari-js/napari-visualizer.service.mjs +2 -2
  7. package/esm2022/lib/implementations/napari-js/napari-volume-region-meshes.mjs +11 -1
  8. package/esm2022/lib/implementations/napari-js/region-overlay/region-svg-renderer.mjs +9 -4
  9. package/esm2022/lib/region-editor/region-editor.component.mjs +46 -41
  10. package/esm2022/lib/region-import/label-volume.mjs +35 -4
  11. package/esm2022/lib/region-import/ome-zarr-labels.mjs +253 -0
  12. package/esm2022/lib/region-import/open-annotations.mjs +289 -0
  13. package/esm2022/lib/region-import/zarr-codecs.mjs +167 -0
  14. package/esm2022/lib/region-import/zip.mjs +157 -0
  15. package/esm2022/lib/routing-visualizer.service.mjs +20 -7
  16. package/esm2022/lib/visualizer/region-load.mjs +16 -1
  17. package/fesm2022/{jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BELcx0jv.mjs → jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DnPlIP2F.mjs} +1103 -71
  18. package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DnPlIP2F.mjs.map +1 -0
  19. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-DEDSm_NL.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-Bs987_-E.mjs} +4 -2
  20. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-DEDSm_NL.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-Bs987_-E.mjs.map} +1 -1
  21. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +3 -1
  22. package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
  23. package/index.d.ts +6 -0
  24. package/lib/contracts/image.contract.d.ts +7 -0
  25. package/lib/implementations/napari-js/napari-region-overlay.d.ts +13 -1
  26. package/lib/implementations/napari-js/napari-spatial-3d-scene.d.ts +40 -1
  27. package/lib/implementations/napari-js/napari-tool-bridge.d.ts +11 -0
  28. package/lib/implementations/napari-js/napari-volume-region-meshes.d.ts +5 -0
  29. package/lib/implementations/napari-js/region-overlay/region-svg-renderer.d.ts +6 -1
  30. package/lib/region-editor/region-editor.component.d.ts +5 -4
  31. package/lib/region-import/label-volume.d.ts +4 -0
  32. package/lib/region-import/ome-zarr-labels.d.ts +36 -0
  33. package/lib/region-import/open-annotations.d.ts +73 -0
  34. package/lib/region-import/zarr-codecs.d.ts +14 -0
  35. package/lib/region-import/zip.d.ts +31 -0
  36. package/lib/routing-visualizer.service.d.ts +9 -1
  37. package/package.json +2 -1
  38. package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BELcx0jv.mjs.map +0 -1
@@ -1,11 +1,13 @@
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-BELcx0jv.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-DnPlIP2F.mjs';
4
4
  import '@angular/core';
5
5
  import '@angular/common/http';
6
6
  import 'rxjs';
7
7
  import 'rxjs/operators';
8
8
  import 'napari-js/geometry';
9
+ import 'fast-png';
10
+ import 'fzstd';
9
11
  import '@angular/core/rxjs-interop';
10
12
  import '@angular/common';
11
13
  import '@angular/forms';
@@ -235,4 +237,4 @@ class OnnxSamSession {
235
237
  }
236
238
 
237
239
  export { OnnxSamSession };
238
- //# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-DEDSm_NL.mjs.map
240
+ //# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-Bs987_-E.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"jax-data-science-sci-image-visualizer-onnx-sam-session-DEDSm_NL.mjs","sources":["../../src/lib/toolbar/segmentation/pending-calls.ts","../../src/lib/toolbar/segmentation/onnx-sam-session.ts"],"sourcesContent":["/** A reply from the SAM worker: progress for, or the result of, call `id`. */\nexport interface WorkerReply {\n id: number;\n type: string;\n /** Present on `type: 'progress'` replies. */\n fraction?: number;\n /** Present on `type: 'error'` replies. */\n error?: string;\n [key: string]: unknown;\n}\n\ninterface Pending {\n resolve: (reply: WorkerReply) => void;\n reject: (err: Error) => void;\n onProgress?: (fraction: number) => void;\n}\n\n/**\n * Request/response bookkeeping for a worker RPC: each call gets an id and a\n * promise that its reply settles. Every exit path (dispose, a worker crash)\n * must {@link rejectAll}, or awaiting callers never reach their `finally` and a\n * tool's busy flag sticks forever (RT-8).\n */\nexport class PendingCalls {\n private seq = 0;\n private readonly pending = new Map<number, Pending>();\n\n /** Calls still awaiting a reply. */\n get size(): number {\n return this.pending.size;\n }\n\n /** Register a new call; post the message with the returned id. */\n open(onProgress?: (fraction: number) => void): { id: number; promise: Promise<WorkerReply> } {\n const id = ++this.seq;\n const promise = new Promise<WorkerReply>((resolve, reject) => {\n this.pending.set(id, { resolve, reject, onProgress });\n });\n return { id, promise };\n }\n\n /** Route a worker reply to its call: progress, error or result. */\n handle(reply: WorkerReply): void {\n const p = this.pending.get(reply.id);\n if (!p) return;\n if (reply.type === 'progress') {\n p.onProgress?.(reply.fraction ?? 0);\n return;\n }\n this.pending.delete(reply.id);\n if (reply.type === 'error') p.reject(new Error(reply.error ?? 'SAM worker error.'));\n else p.resolve(reply);\n }\n\n /** Reject every pending call with `err` and forget them. */\n rejectAll(err: Error): void {\n const calls = Array.from(this.pending.values());\n this.pending.clear();\n for (const p of calls) p.reject(err);\n }\n}\n","import { LruCache } from 'napari-js';\nimport * as ort from 'onnxruntime-web';\n\nimport { ISamSession, SamEmbedding, SamMaskResult, SamModelDef, SamPrompt } from '../../contracts/sam.contract';\nimport { fetchModel, runEncoder, runDecoder, type CoreEmbedding } from './sam-onnx-core';\nimport { getOrtWasmBase } from './ort-runtime-config';\nimport { PendingCalls, WorkerReply } from './pending-calls';\n\n/**\n * onnxruntime-web implementation of {@link ISamSession} (jit-ui#90), with two\n * execution modes chosen per model:\n *\n * - **in-process** (main thread) — for small WASM models flagged `inProcess`\n * (e.g. micro-sam ViT-T). Fast load + inference with no Web Worker spawn /\n * second ORT runtime / message round-trips; the brief encode runs on the main\n * thread (fine for a tiny model).\n * - **worker** — for WebGPU models (e.g. ViT-B, patho-sam fp16) whose heavy\n * encode would otherwise freeze the tab. Runs in {@link ./onnx-sam.worker} so\n * the UI stays responsive (the spinner animates).\n *\n * Both modes share their inference math via {@link ./sam-onnx-core}. Lazy-imported\n * by the SAM tools so onnxruntime-web / the worker never load in unit tests or\n * the initial bundle.\n */\nexport class OnnxSamSession implements ISamSession {\n private mode: 'inproc' | 'worker' | null = null;\n private loaded = false;\n private inputSize = 1024;\n\n // ── worker mode ──\n private worker: Worker | null = null;\n private readonly calls = new PendingCalls();\n\n // ── in-process mode ──\n private encoder: ort.InferenceSession | null = null;\n private decoder: ort.InferenceSession | null = null;\n private nextToken = 1;\n /** The few most recently used embeddings, by token (one per recently viewed image). */\n private readonly embeddings = new LruCache<CoreEmbedding, number>(3);\n\n async loadModel(model: SamModelDef, onProgress?: (fraction: number) => void): Promise<void> {\n if (!model.encoderUrl || !model.decoderUrl) {\n throw new Error(`SAM model \"${model.id}\" has no ONNX URLs configured.`);\n }\n this.inputSize = model.inputSize;\n const hasGpu = typeof navigator !== 'undefined' && 'gpu' in navigator;\n const eps: string[] = model.encoderProviders ?? (hasGpu ? ['webgpu', 'wasm'] : ['wasm']);\n const usesWebGpu = eps.includes('webgpu');\n // WebGPU must run in the worker (else it freezes the main thread); small\n // WASM models opt into the faster in-process path via `inProcess`.\n this.mode = !usesWebGpu && model.inProcess ? 'inproc' : 'worker';\n\n if (this.mode === 'inproc') {\n ort.env.wasm.wasmPaths = getOrtWasmBase();\n const encBuf = await fetchModel(model.encoderUrl, onProgress, model.revision);\n const decBuf = await fetchModel(model.decoderUrl, undefined, model.revision);\n this.encoder = await ort.InferenceSession.create(encBuf, { executionProviders: eps });\n this.decoder = await ort.InferenceSession.create(decBuf, { executionProviders: ['wasm'] });\n } else {\n await this.call(\n {\n type: 'load',\n encoderUrl: model.encoderUrl,\n decoderUrl: model.decoderUrl,\n revision: model.revision,\n wasmPaths: getOrtWasmBase(),\n inputSize: model.inputSize,\n encoderProviders: model.encoderProviders,\n },\n [],\n onProgress,\n );\n }\n this.loaded = true;\n }\n\n isLoaded(): boolean {\n return this.loaded;\n }\n\n async embed(image: { data: Uint8ClampedArray; width: number; height: number }): Promise<SamEmbedding> {\n if (this.mode === 'inproc') {\n const e = await runEncoder(this.encoder!, image.data, image.width, image.height, this.inputSize);\n const token = this.nextToken++;\n this.embeddings.set(token, e);\n return {\n data: new Float32Array(0),\n dims: e.dims,\n scale: e.scale,\n imageWidth: e.imageWidth,\n imageHeight: e.imageHeight,\n token,\n };\n }\n // worker: transfer the RGBA buffer (callers pass a fresh frame buffer).\n const buffer = image.data.buffer;\n const res = await this.call({ type: 'embed', width: image.width, height: image.height, buffer }, [buffer]);\n return {\n data: new Float32Array(0),\n dims: res['dims'] as number[],\n scale: res['scale'] as number,\n imageWidth: res['imageWidth'] as number,\n imageHeight: res['imageHeight'] as number,\n token: res['token'] as number,\n };\n }\n\n async decode(embedding: SamEmbedding, prompt: SamPrompt): Promise<SamMaskResult> {\n if (this.mode === 'inproc') {\n const e = this.embeddings.get(embedding.token as number);\n if (!e) throw new Error('SAM embedding expired; re-encode the image.');\n return runDecoder(this.decoder!, e, prompt);\n }\n const res = await this.call({ type: 'decode', token: embedding.token, prompt });\n return {\n mask: new Uint8Array(res['buffer'] as ArrayBuffer),\n width: res['width'] as number,\n height: res['height'] as number,\n iou: res['iou'] as number,\n };\n }\n\n /** Release the worker / ORT sessions. Calls still in flight reject (RT-8), so\n * their callers' `finally` blocks run (e.g. a tool's busy flag clears). */\n dispose(): void {\n this.dropWorker(new Error('SAM session disposed'));\n this.encoder?.release?.();\n this.decoder?.release?.();\n this.encoder = this.decoder = null;\n this.embeddings.clear();\n this.loaded = false;\n this.mode = null;\n }\n\n // ── worker plumbing ──────────────────────────────────────────────────────\n private ensureWorker(): Worker {\n if (this.worker) return this.worker;\n const worker = new Worker(new URL('./onnx-sam.worker', import.meta.url), { type: 'module' });\n worker.onmessage = (ev: MessageEvent<WorkerReply>) => this.calls.handle(ev.data);\n worker.onerror = (ev: ErrorEvent) => {\n // A crashed worker can't serve the loaded model any more: drop it, so the\n // next run reloads instead of posting to a dead worker.\n this.dropWorker(new Error(ev.message || 'SAM worker crashed.'));\n this.loaded = false;\n this.mode = null;\n };\n this.worker = worker;\n return worker;\n }\n\n /** Terminate the worker (if any) and reject every call still waiting on it. */\n private dropWorker(err: Error): void {\n this.worker?.terminate();\n this.worker = null;\n this.calls.rejectAll(err);\n }\n\n private call(\n msg: Record<string, unknown>,\n transfer: Transferable[] = [],\n onProgress?: (f: number) => void,\n ): Promise<WorkerReply> {\n const worker = this.ensureWorker();\n const { id, promise } = this.calls.open(onProgress);\n worker.postMessage({ ...msg, id }, transfer);\n return promise;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"jax-data-science-sci-image-visualizer-onnx-sam-session-Bs987_-E.mjs","sources":["../../src/lib/toolbar/segmentation/pending-calls.ts","../../src/lib/toolbar/segmentation/onnx-sam-session.ts"],"sourcesContent":["/** A reply from the SAM worker: progress for, or the result of, call `id`. */\nexport interface WorkerReply {\n id: number;\n type: string;\n /** Present on `type: 'progress'` replies. */\n fraction?: number;\n /** Present on `type: 'error'` replies. */\n error?: string;\n [key: string]: unknown;\n}\n\ninterface Pending {\n resolve: (reply: WorkerReply) => void;\n reject: (err: Error) => void;\n onProgress?: (fraction: number) => void;\n}\n\n/**\n * Request/response bookkeeping for a worker RPC: each call gets an id and a\n * promise that its reply settles. Every exit path (dispose, a worker crash)\n * must {@link rejectAll}, or awaiting callers never reach their `finally` and a\n * tool's busy flag sticks forever (RT-8).\n */\nexport class PendingCalls {\n private seq = 0;\n private readonly pending = new Map<number, Pending>();\n\n /** Calls still awaiting a reply. */\n get size(): number {\n return this.pending.size;\n }\n\n /** Register a new call; post the message with the returned id. */\n open(onProgress?: (fraction: number) => void): { id: number; promise: Promise<WorkerReply> } {\n const id = ++this.seq;\n const promise = new Promise<WorkerReply>((resolve, reject) => {\n this.pending.set(id, { resolve, reject, onProgress });\n });\n return { id, promise };\n }\n\n /** Route a worker reply to its call: progress, error or result. */\n handle(reply: WorkerReply): void {\n const p = this.pending.get(reply.id);\n if (!p) return;\n if (reply.type === 'progress') {\n p.onProgress?.(reply.fraction ?? 0);\n return;\n }\n this.pending.delete(reply.id);\n if (reply.type === 'error') p.reject(new Error(reply.error ?? 'SAM worker error.'));\n else p.resolve(reply);\n }\n\n /** Reject every pending call with `err` and forget them. */\n rejectAll(err: Error): void {\n const calls = Array.from(this.pending.values());\n this.pending.clear();\n for (const p of calls) p.reject(err);\n }\n}\n","import { LruCache } from 'napari-js';\nimport * as ort from 'onnxruntime-web';\n\nimport { ISamSession, SamEmbedding, SamMaskResult, SamModelDef, SamPrompt } from '../../contracts/sam.contract';\nimport { fetchModel, runEncoder, runDecoder, type CoreEmbedding } from './sam-onnx-core';\nimport { getOrtWasmBase } from './ort-runtime-config';\nimport { PendingCalls, WorkerReply } from './pending-calls';\n\n/**\n * onnxruntime-web implementation of {@link ISamSession} (jit-ui#90), with two\n * execution modes chosen per model:\n *\n * - **in-process** (main thread) — for small WASM models flagged `inProcess`\n * (e.g. micro-sam ViT-T). Fast load + inference with no Web Worker spawn /\n * second ORT runtime / message round-trips; the brief encode runs on the main\n * thread (fine for a tiny model).\n * - **worker** — for WebGPU models (e.g. ViT-B, patho-sam fp16) whose heavy\n * encode would otherwise freeze the tab. Runs in {@link ./onnx-sam.worker} so\n * the UI stays responsive (the spinner animates).\n *\n * Both modes share their inference math via {@link ./sam-onnx-core}. Lazy-imported\n * by the SAM tools so onnxruntime-web / the worker never load in unit tests or\n * the initial bundle.\n */\nexport class OnnxSamSession implements ISamSession {\n private mode: 'inproc' | 'worker' | null = null;\n private loaded = false;\n private inputSize = 1024;\n\n // ── worker mode ──\n private worker: Worker | null = null;\n private readonly calls = new PendingCalls();\n\n // ── in-process mode ──\n private encoder: ort.InferenceSession | null = null;\n private decoder: ort.InferenceSession | null = null;\n private nextToken = 1;\n /** The few most recently used embeddings, by token (one per recently viewed image). */\n private readonly embeddings = new LruCache<CoreEmbedding, number>(3);\n\n async loadModel(model: SamModelDef, onProgress?: (fraction: number) => void): Promise<void> {\n if (!model.encoderUrl || !model.decoderUrl) {\n throw new Error(`SAM model \"${model.id}\" has no ONNX URLs configured.`);\n }\n this.inputSize = model.inputSize;\n const hasGpu = typeof navigator !== 'undefined' && 'gpu' in navigator;\n const eps: string[] = model.encoderProviders ?? (hasGpu ? ['webgpu', 'wasm'] : ['wasm']);\n const usesWebGpu = eps.includes('webgpu');\n // WebGPU must run in the worker (else it freezes the main thread); small\n // WASM models opt into the faster in-process path via `inProcess`.\n this.mode = !usesWebGpu && model.inProcess ? 'inproc' : 'worker';\n\n if (this.mode === 'inproc') {\n ort.env.wasm.wasmPaths = getOrtWasmBase();\n const encBuf = await fetchModel(model.encoderUrl, onProgress, model.revision);\n const decBuf = await fetchModel(model.decoderUrl, undefined, model.revision);\n this.encoder = await ort.InferenceSession.create(encBuf, { executionProviders: eps });\n this.decoder = await ort.InferenceSession.create(decBuf, { executionProviders: ['wasm'] });\n } else {\n await this.call(\n {\n type: 'load',\n encoderUrl: model.encoderUrl,\n decoderUrl: model.decoderUrl,\n revision: model.revision,\n wasmPaths: getOrtWasmBase(),\n inputSize: model.inputSize,\n encoderProviders: model.encoderProviders,\n },\n [],\n onProgress,\n );\n }\n this.loaded = true;\n }\n\n isLoaded(): boolean {\n return this.loaded;\n }\n\n async embed(image: { data: Uint8ClampedArray; width: number; height: number }): Promise<SamEmbedding> {\n if (this.mode === 'inproc') {\n const e = await runEncoder(this.encoder!, image.data, image.width, image.height, this.inputSize);\n const token = this.nextToken++;\n this.embeddings.set(token, e);\n return {\n data: new Float32Array(0),\n dims: e.dims,\n scale: e.scale,\n imageWidth: e.imageWidth,\n imageHeight: e.imageHeight,\n token,\n };\n }\n // worker: transfer the RGBA buffer (callers pass a fresh frame buffer).\n const buffer = image.data.buffer;\n const res = await this.call({ type: 'embed', width: image.width, height: image.height, buffer }, [buffer]);\n return {\n data: new Float32Array(0),\n dims: res['dims'] as number[],\n scale: res['scale'] as number,\n imageWidth: res['imageWidth'] as number,\n imageHeight: res['imageHeight'] as number,\n token: res['token'] as number,\n };\n }\n\n async decode(embedding: SamEmbedding, prompt: SamPrompt): Promise<SamMaskResult> {\n if (this.mode === 'inproc') {\n const e = this.embeddings.get(embedding.token as number);\n if (!e) throw new Error('SAM embedding expired; re-encode the image.');\n return runDecoder(this.decoder!, e, prompt);\n }\n const res = await this.call({ type: 'decode', token: embedding.token, prompt });\n return {\n mask: new Uint8Array(res['buffer'] as ArrayBuffer),\n width: res['width'] as number,\n height: res['height'] as number,\n iou: res['iou'] as number,\n };\n }\n\n /** Release the worker / ORT sessions. Calls still in flight reject (RT-8), so\n * their callers' `finally` blocks run (e.g. a tool's busy flag clears). */\n dispose(): void {\n this.dropWorker(new Error('SAM session disposed'));\n this.encoder?.release?.();\n this.decoder?.release?.();\n this.encoder = this.decoder = null;\n this.embeddings.clear();\n this.loaded = false;\n this.mode = null;\n }\n\n // ── worker plumbing ──────────────────────────────────────────────────────\n private ensureWorker(): Worker {\n if (this.worker) return this.worker;\n const worker = new Worker(new URL('./onnx-sam.worker', import.meta.url), { type: 'module' });\n worker.onmessage = (ev: MessageEvent<WorkerReply>) => this.calls.handle(ev.data);\n worker.onerror = (ev: ErrorEvent) => {\n // A crashed worker can't serve the loaded model any more: drop it, so the\n // next run reloads instead of posting to a dead worker.\n this.dropWorker(new Error(ev.message || 'SAM worker crashed.'));\n this.loaded = false;\n this.mode = null;\n };\n this.worker = worker;\n return worker;\n }\n\n /** Terminate the worker (if any) and reject every call still waiting on it. */\n private dropWorker(err: Error): void {\n this.worker?.terminate();\n this.worker = null;\n this.calls.rejectAll(err);\n }\n\n private call(\n msg: Record<string, unknown>,\n transfer: Transferable[] = [],\n onProgress?: (f: number) => void,\n ): Promise<WorkerReply> {\n const worker = this.ensureWorker();\n const { id, promise } = this.calls.open(onProgress);\n worker.postMessage({ ...msg, id }, transfer);\n return promise;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- export { A as ALL_GENES, b as ALL_TOOLBAR_TOOLS, C as CANVAS_TOOL_IDS, c as CELL_SEGMENTER, d as CHANNEL_HISTOGRAM_API, e as CellposeSegmenterService, h as ChannelHistogramComponent, D as DEFAULT_CATEGORICAL_PALETTE, i as DEFAULT_FALLBACK_PALETTE, j as DEFAULT_MUTED_OPACITY, k as DEFAULT_SAM_MODEL_ID, l as DEFAULT_SPATIAL_VIEW, H as HexColorPickerComponent, I as IMAGE_STATE_PORT, m as INSTANCE_SEGMENTER, n as ImageConverterService, L as LUT_COLORS, M as MISSING_COLOR, o as MultiPolygon, N as NAPARI_DECIMATE_OPTIONS, p as NAPARI_DEFAULT_DECIMATE, q as NO_CATEGORY, s as NO_OBSERVATION, P as PLOT_MODE_CONTEXT, t as PLOT_MODE_SESSION, u as PLOT_TYPE_CONTRIBUTIONS, v as PLOT_TYPE_DESCRIPTORS, w as PREFERENCES_PORT, x as PlotType, y as Polygon, z as ProcessingImage, R as REGION_EDITOR_API, B as REGION_IO_PORT, E as REGION_SOURCE_MIN_CHARS, F as Rectangle, G as Region, J as RegionEditorComponent, K as RoutingVisualizerService, S as SAM_MODELS, O as SEMANTIC_SEGMENTER, Q as SPATIAL_DATA_PORT, T as SPATIAL_WIRE_VERSION, U as ShapeSelection, V as SpatialChartsComponent, W as SpatialControlsComponent, X as SpatialDataHttpService, Y as SpatialSelectionStore, Z as SupersededError, _ as TILE_ACCESS_PORT, $ as TOOLBAR_TOOLS, a0 as VISUALIZER, a1 as VIZ_ALERT_TOAST_KEY, a2 as VIZ_CONFIG, a3 as VIZ_TOAST_KEY, a4 as ViewerFeature, a5 as VisualizationModule, a6 as VisualizerComponent, a7 as assertManifestVersion, a8 as benefitsFromGrouping, a9 as buildOmicsTraces, aa as capabilitiesOf, ab as clearSamModelCache, ac as contrastWindow, ad as contributedPlotTypeOption, ae as countMask, af as cropImage, ag as datasetFromManifest, ah as decodeColumn, ai as decodeCoords, aj as decodeFeatureVector, ak as decodePolygons, al as decodeRadius, am as defaultPresetSet, an as dialogToolContributions, ao as emptySelection, ap as encodeCategorical, aq as encodeContinuous, ar as findColumnMeta, as as getDefaultSamModelId, g as getOrtWasmBase, at as getPlotTypeDescriptor, au as getSamModel, av as hydrateBounds, aw as importFile, ax as isBuiltinPlotType, ay as isCanvasToolId, az as isCategoricalColumn, aA as isContinuousColumn, aB as isDialogToolContribution, aC as isEmptyPresetSet, aD as isLabelVolumeName, aE as isLittleEndian, aF as isNapari3d, aG as isNapariIsosurface, aH as isNapariScatter, aI as isNapariScatter3d, aJ as isNapariSurface, aK as isNapariVolume, aL as isSamModelReady, aM as isSpatialOmics, aN as isSpatialOmics3d, aO as isThreeDimensional, aP as labelClasses, aQ as labelSliceGeoJson, aR as lutFor, aS as markerDiameters, aT as maskToIndices, aU as mutedFromSelection, aV as omicsLayout, aW as openGeoJsonRegionSource, aX as openHttpRegionSource, aY as parsePresetSet, aZ as planRegionImport, a_ as pointInRing, a$ as provideVisualization, b0 as readLabelVolume, b1 as rendererOwnsWheel, b2 as resolveCategoryColors, b3 as selectByCategory, b4 as selectInRegions, b5 as setDefaultSamModel, b6 as setOrtWasmBase, b7 as setSamModelUrls, b8 as sortToolContributions, b9 as visibleToolContributions } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BELcx0jv.mjs';
1
+ export { A as ALL_GENES, b as ALL_TOOLBAR_TOOLS, c as ANNOTATION_IMAGE_MAX_DIM, C as CANVAS_TOOL_IDS, d as CELL_SEGMENTER, e as CHANNEL_HISTOGRAM_API, h as CellposeSegmenterService, i as ChannelHistogramComponent, D as DEFAULT_CATEGORICAL_PALETTE, j as DEFAULT_FALLBACK_PALETTE, k as DEFAULT_MUTED_OPACITY, l as DEFAULT_SAM_MODEL_ID, m as DEFAULT_SPATIAL_VIEW, H as HexColorPickerComponent, I as IMAGE_STATE_PORT, n as INSTANCE_SEGMENTER, o as ImageConverterService, L as LUT_COLORS, M as MISSING_COLOR, p as MultiPolygon, N as NAPARI_DECIMATE_OPTIONS, q as NAPARI_DEFAULT_DECIMATE, s as NO_CATEGORY, t as NO_OBSERVATION, P as PLOT_MODE_CONTEXT, u as PLOT_MODE_SESSION, v as PLOT_TYPE_CONTRIBUTIONS, w as PLOT_TYPE_DESCRIPTORS, x as PREFERENCES_PORT, y as PlotType, z as Polygon, B as ProcessingImage, R as REGION_EDITOR_API, E as REGION_IO_PORT, F as REGION_SOURCE_MIN_CHARS, G as Rectangle, J as Region, K as RegionEditorComponent, O as RoutingVisualizerService, S as SAM_MODELS, Q as SEMANTIC_SEGMENTER, T as SPATIAL_DATA_PORT, U as SPATIAL_WIRE_VERSION, V as ShapeSelection, W as SpatialChartsComponent, X as SpatialControlsComponent, Y as SpatialDataHttpService, Z as SpatialSelectionStore, _ as SupersededError, $ as TILE_ACCESS_PORT, a0 as TOOLBAR_TOOLS, a1 as VISUALIZER, a2 as VIZ_ALERT_TOAST_KEY, a3 as VIZ_CONFIG, a4 as VIZ_TOAST_KEY, a5 as ViewerFeature, a6 as VisualizationModule, a7 as VisualizerComponent, a8 as assertManifestVersion, a9 as benefitsFromGrouping, aa as buildOmicsTraces, ab as capabilitiesOf, ac as clearSamModelCache, ad as contrastWindow, ae as contributedPlotTypeOption, af as countMask, ag as cropImage, ah as datasetFromManifest, ai as decodeColumn, aj as decodeCoords, ak as decodeFeatureVector, al as decodePolygons, am as decodeRadius, an as defaultPresetSet, ao as dialogToolContributions, ap as emptySelection, aq as encodeCategorical, ar as encodeContinuous, as as expandZips, at as findColumnMeta, au as getDefaultSamModelId, g as getOrtWasmBase, av as getPlotTypeDescriptor, aw as getSamModel, ax as hasZarrMetadata, ay as hydrateBounds, az as importFile, aA as isBuiltinPlotType, aB as isCanvasToolId, aC as isCategoricalColumn, aD as isContinuousColumn, aE as isDialogToolContribution, aF as isEmptyPresetSet, aG as isLabelVolumeName, aH as isLittleEndian, aI as isNapari3d, aJ as isNapariIsosurface, aK as isNapariScatter, aL as isNapariScatter3d, aM as isNapariSurface, aN as isNapariVolume, aO as isSamModelReady, aP as isSpatialOmics, aQ as isSpatialOmics3d, aR as isThreeDimensional, aS as isZipName, aT as labelClasses, aU as labelSliceGeoJson, aV as lutFor, aW as markerDiameters, aX as maskToIndices, aY as mutedFromSelection, aZ as omicsLayout, a_ as openAnnotations, a$ as openGeoJsonRegionSource, b0 as openHttpRegionSource, b1 as parsePresetSet, b2 as planRegionImport, b3 as pointInRing, b4 as provideVisualization, b5 as readAnnotationSlices, b6 as readLabelVolume, b7 as readOmeZarrLabels, b8 as readZip, b9 as rendererOwnsWheel, ba as resolveCategoryColors, bb as selectByCategory, bc as selectInRegions, bd as setDefaultSamModel, be as setOrtWasmBase, bf as setSamModelUrls, bg as sortToolContributions, bh as visibleToolContributions } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-DnPlIP2F.mjs';
2
2
  import '@angular/core';
3
3
  import '@angular/common/http';
4
4
  import 'napari-js';
@@ -6,6 +6,8 @@ import 'rxjs';
6
6
  import 'rxjs/operators';
7
7
  import 'onnxruntime-web';
8
8
  import 'napari-js/geometry';
9
+ import 'fast-png';
10
+ import 'fzstd';
9
11
  import '@angular/core/rxjs-interop';
10
12
  import '@angular/common';
11
13
  import '@angular/forms';
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/index.d.ts CHANGED
@@ -56,6 +56,12 @@ export { labelClasses, labelSliceGeoJson } from './lib/region-import/label-class
56
56
  export type { LabelClasses } from './lib/region-import/label-classes';
57
57
  export { importFile, planRegionImport } from './lib/region-import/region-import';
58
58
  export type { RegionImportFile, RegionImportPlan } from './lib/region-import/region-import';
59
+ export { ANNOTATION_IMAGE_MAX_DIM, openAnnotations, readAnnotationSlices, } from './lib/region-import/open-annotations';
60
+ export type { AnnotationDecoders, AnnotationSlices, OpenedAnnotations, } from './lib/region-import/open-annotations';
61
+ export { hasZarrMetadata, readOmeZarrLabels } from './lib/region-import/ome-zarr-labels';
62
+ export type { LabelStack, OmeZarrLabels } from './lib/region-import/ome-zarr-labels';
63
+ export { expandZips, isZipName, readZip } from './lib/region-import/zip';
64
+ export type { ZipEntry } from './lib/region-import/zip';
59
65
  export type { RegionSource, RegionSourceView, SourceFeature } from './lib/region-source/region-source';
60
66
  export type { RegionIndexPage, RegionIndexQuery, RegionIndexRow, RegionSourceInfo, RegionTile, RegionTileKey, TileFeature, } from './lib/region-source/region-tiles';
61
67
  export { VisualizationModule } from './lib/visualization.module';
@@ -7,6 +7,7 @@
7
7
  * `ImageInfo`/`ImageMetadata` classes are pure data holders that satisfy them
8
8
  * structurally, so the host keeps passing its own instances unchanged.
9
9
  */
10
+ import type { RegionSource } from '../region-source/region-source';
10
11
  /** Per-channel metadata from the server descriptor (issue #76). */
11
12
  export interface IChannelInfo {
12
13
  name: string;
@@ -65,6 +66,12 @@ export interface IImageInfo {
65
66
  * its regions are drawn from the server's level-of-detail tiles instead of being loaded
66
67
  * from `roiJsonStr`. On a stack, slice z shows the source's plane z. */
67
68
  regionSourceUrl?: string;
69
+ /** The image's own regions, one region source per slice, opened the first time its slice is
70
+ * shown: what an image built from annotations alone carries (see `openAnnotations`: a label
71
+ * volume traced slice by slice, a folder of per-slice GeoJSON). Attached when the image lands,
72
+ * replacing its regions, as an imported label volume or folder is; takes precedence over
73
+ * `roiJsonStrs`, `roiJsonStr` and `regionSourceUrl`. Keyed by slice index. */
74
+ regionSlices?: ReadonlyMap<number, () => Promise<RegionSource>>;
68
75
  /** Per-slice, per-channel plane URLs for a SERVERLESS multichannel image
69
76
  * (`tiled: false` + `imageMeta[0].channelCount > 1`). `channelUrls[z][c]`
70
77
  * is a complete single-band image of channel c at slice z. The OSD simple
@@ -75,8 +75,16 @@ export declare class NapariRegionOverlay implements IRegionOverlay {
75
75
  /** In-progress manipulation (select/move modes): dragging a body, a polygon vertex, or a
76
76
  * rectangle corner. `anchor` is the fixed opposite corner for a rectangle resize. */
77
77
  private edit;
78
+ /**
79
+ * Screen space (the 3D view's lassos): the regions this overlay drew itself, by id. Only those
80
+ * are drawn, picked or edited here; every other region in the store is in image space (the
81
+ * image's own, an imported set, structures a tree click took from a source) and means nothing
82
+ * at canvas pixels. Null: every region (world is the image).
83
+ */
84
+ private readonly own;
78
85
  constructor(host: HTMLElement, viewer: OverlayViewer, store: RegionStore, opts?: {
79
86
  regionSource?: boolean;
87
+ screenSpace?: boolean;
80
88
  });
81
89
  /**
82
90
  * Whether a region tool currently owns the pointer.
@@ -115,8 +123,12 @@ export declare class NapariRegionOverlay implements IRegionOverlay {
115
123
  /** Click-to-place polygon: add a vertex, or close when clicking near the first one. */
116
124
  private handlePolygonClick;
117
125
  private readonly onDblClick;
118
- /** The currently-selected region (single selection), or null. */
126
+ /** The currently-selected region (single selection) this overlay draws, or null. */
119
127
  private selectedRegion;
128
+ /** Whether this overlay draws (and lets the pointer pick) `region`: see {@link own}. */
129
+ private draws;
130
+ /** The regions this overlay drew in screen space (the 3D view's lassos); [] in image space. */
131
+ ownRegionIds(): number[];
120
132
  /**
121
133
  * Start a select/move interaction: grab a handle (rectangle corner) or vertex of the already-
122
134
  * selected region first; otherwise select the topmost region under the cursor and drag its body.
@@ -2,6 +2,8 @@ import { IHistogram } from '../../contracts/channel-histogram-api.contract';
2
2
  import { SpatialDataset, SpatialObservations } from '../../contracts/spatial-dataset.contract';
3
3
  import { SpatialViewState } from '../../contracts/display-types';
4
4
  import { SpatialSelectionMask } from '../../spatial/spatial-selection';
5
+ import type { IImageInfo } from '../../contracts/image.contract';
6
+ import { RegionMesh3D } from './napari-volume-region-meshes';
5
7
  import { Spatial3dScalars } from './napari-spatial-encoding';
6
8
  import { SpatialSceneBase, SpatialSession } from './napari-spatial-scene';
7
9
  import type { NapariScene, SceneContext } from './napari-scene';
@@ -17,6 +19,7 @@ import type { NapariScene, SceneContext } from './napari-scene';
17
19
  * shape while keeping the selection. The first 3D layer added frames the camera (`fit3d: 'once'`).
18
20
  */
19
21
  export declare class Spatial3dScene extends SpatialSceneBase implements NapariScene {
22
+ private readonly info?;
20
23
  /** The 3D point cloud. One layer: selection is a per-point alpha on it, not a second. */
21
24
  private points;
22
25
  private layerKey;
@@ -58,10 +61,46 @@ export declare class Spatial3dScene extends SpatialSceneBase implements NapariSc
58
61
  * one; a rebuild that leaves the key alone lets it finish. */
59
62
  private readonly densityLoads;
60
63
  private readonly geneMapLoads;
64
+ /**
65
+ * The region reconstructions (an atlas's structures, imported onto the reference volume's image
66
+ * stack), drawn by the same {@link VolumeRegionMeshes} as the Volume view's, so a structure is the
67
+ * same surface in both. Placed in the reference volume's box, once it is there and the image the
68
+ * regions were drawn on is that volume's stack (see {@link placeRegionMeshes}).
69
+ */
70
+ private readonly regionMeshes;
71
+ /** Whether the reference volume lets every region surface through (see setRegionsThroughVolume). */
72
+ private regionsThrough;
61
73
  /** The store's invert setting, which the density volumes follow (no other subscription of
62
74
  * this scene carries it). */
63
75
  private invertSub;
64
- constructor(ctx: SceneContext, session: SpatialSession);
76
+ /**
77
+ * @param info the image plotted with the cloud: the reference volume's stack (the 2D view's
78
+ * image, see showVolumeAsImage) when the dataset has a volume, which the regions are on
79
+ */
80
+ constructor(ctx: SceneContext, session: SpatialSession, info?: IImageInfo | undefined);
81
+ /** Show these region reconstructions with the cloud (replacing the ones shown). */
82
+ setRegionMeshes(meshes: readonly RegionMesh3D[]): void;
83
+ setRegionHighlight(keys: readonly string[]): void;
84
+ setRegionHidden(keys: readonly string[]): void;
85
+ /** As the Volume view: the reference volume hides the surfaces behind what it shows, or none. */
86
+ setRegionsThroughVolume(through: boolean): void;
87
+ /**
88
+ * Place the region surfaces in the reference volume's box, as the Volume view places them in its
89
+ * own: image pixel (x, y) is voxel (x, y), slice k is voxel plane k, and the box is the volume's
90
+ * voxels times its voxel size, centred on the origin as napari centres the volume (and the cloud
91
+ * is offset to sit in it). Only when the plotted image IS that volume's stack: regions drawn on
92
+ * another image (a tissue image) have no place in it, and are not shown - nor are surfaces
93
+ * placed in a volume this one replaced.
94
+ */
95
+ private placeRegionMeshes;
96
+ /**
97
+ * Image px per voxel of the volume the surfaces sit in: 1, the reference volume being the stack's
98
+ * own voxels, as the Volume view's is at full resolution, so both reconstruct a class alike (and
99
+ * share its cached surface). Null while there is no volume stack to place them in.
100
+ */
101
+ voxelStep(): number | null;
102
+ /** Whether the plotted image is the reference volume's stack: its size and slice count. */
103
+ private isVolumeStack;
65
104
  mount(): Promise<void>;
66
105
  /** The observations' own z is on screen: there is no plane to step. */
67
106
  setZ(): void;
@@ -124,8 +124,19 @@ export declare class NapariToolBridge {
124
124
  * A screen-space shape stops meaning anything the moment the camera moves, so an orbit clears
125
125
  * the regions drawn in this view (and only those). The SELECTION they produced is kept: that is
126
126
  * the durable artefact, and the highlighted points stay highlighted from every angle.
127
+ *
128
+ * Every other region in the store is in image space - the image's own, an imported set,
129
+ * structures a tree click took from a region source - so the overlay neither draws nor picks it
130
+ * (it would lie flat at canvas pixels over the cloud), and an orbit leaves it alone: they show
131
+ * in 3D as their reconstructions instead.
127
132
  */
128
133
  install3dInteraction(viewer: Viewer, host: HTMLElement): void;
134
+ /**
135
+ * Remove the regions the 3D overlay drew in screen space (only those) from the store: a lasso
136
+ * means nothing once the camera it was drawn under moves or the scene goes. Not an edit of the
137
+ * user's: no undo step (NAPARI-SVC-11).
138
+ */
139
+ private clearOwnLassos;
129
140
  /**
130
141
  * The {@link OverlayViewer} the 3D region overlay draws through: "world" is the canvas, in
131
142
  * canvas-local CSS pixels. A pointer arrives in CLIENT pixels, so `canvasToWorld` subtracts the
@@ -54,6 +54,11 @@ export declare class VolumeRegionMeshes {
54
54
  /** The meshes shown. */
55
55
  get shown(): RegionMesh3D[];
56
56
  clear(): void;
57
+ /**
58
+ * The volume the surfaces sat in is gone (or no longer theirs): take every surface down, keeping
59
+ * the meshes wanted, so they come back when a frame is set again.
60
+ */
61
+ clearFrame(): void;
57
62
  private sync;
58
63
  private add;
59
64
  private opacityOf;
@@ -24,10 +24,15 @@ export interface RegionDraft {
24
24
  export declare class NapariRegionSvgRenderer {
25
25
  private readonly svg;
26
26
  private readonly store;
27
+ private readonly draws;
27
28
  private readonly renderer;
28
29
  /** Dedicated marquee rect element, updated directly during the drag (no full region redraw). */
29
30
  private marqueeEl;
30
- constructor(svg: SVGSVGElement, store: RegionStore);
31
+ /**
32
+ * @param draws which regions are this overlay's to draw (default: every one); the others are
33
+ * left out of the shapes and handles, keeping the store's indices
34
+ */
35
+ constructor(svg: SVGSVGElement, store: RegionStore, draws?: (region: Region) => boolean);
31
36
  /** Follow the camera: one transform attribute plus the screen-space elements. */
32
37
  setCamera(affine: Affine): void;
33
38
  /** Rebuild the regions (world space) and their labels. */
@@ -360,10 +360,11 @@ export declare class RegionEditorComponent implements OnInit, OnDestroy {
360
360
  /** "Import regions from server": the host picks files or a served region set; they import as chosen files do. */
361
361
  protected importFromServer(): void;
362
362
  /**
363
- * Import `files` (see planRegionImport): one GeoJSON file as before; a label volume as one
364
- * region source per slice, traced from it the first time the slice is shown, its classes named
365
- * (and treed) by the ontology chosen with it; a folder as one source per slice file. Either
366
- * replaces the regions and sources of every slice.
363
+ * Import `files`: one GeoJSON file as before (its regions, editable); anything else (see
364
+ * readAnnotationSlices) as one region source per slice, replacing the regions and sources of
365
+ * every slice: a label volume or an OME-Zarr label image traced the first time a slice is shown,
366
+ * its classes named (and treed) by its ontology; a folder as one source per slice file; a
367
+ * z-indexed GeoJSON one per plane; a zip of any of these.
367
368
  */
368
369
  private importRegionFiles;
369
370
  /** A region set the host's server serves (jit-service's /regions/{id}/), read through its HttpClient. */
@@ -20,6 +20,10 @@ export interface LabelVolume {
20
20
  height: number;
21
21
  /** Number of slices. */
22
22
  slices: number;
23
+ /** Size of a voxel along the slice's x, its y and across slices, in micrometres, when the file
24
+ * says (NRRD `space directions`/`spacings`, micrometres unless `space units` says otherwise;
25
+ * NIfTI `pixdim`, in its `xyzt_units`, millimetres when unset). */
26
+ spacing?: [number, number, number];
23
27
  /** Slice `z`'s labels, row-major (`height` rows of `width`). */
24
28
  sliceLabels(z: number): Uint32Array;
25
29
  }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * A label image of an OME-Zarr store (`labels/<name>/`, NGFF 0.4 on zarr v2 or 0.5 on zarr v3),
3
+ * read in the browser from the store's files (a folder chosen, or a zip's members): its full
4
+ * resolution level, sliced along z, with the classes its `image-label` metadata names and colours.
5
+ *
6
+ * Chunks are decoded when a slice that needs them is read, so a store is never decoded whole.
7
+ * Only what label images use: C-order chunks of integer (or integer-valued float) samples, through
8
+ * the codecs {@link decodeChunk} reads, a missing chunk being the fill value. Sharded or transposed
9
+ * arrays, F-order and filters throw rather than be read wrongly. Pure: usable from workers.
10
+ */
11
+ import { LabelClasses } from './label-classes';
12
+ import type { RegionImportFile } from './region-import';
13
+ import { Inflate } from './zarr-codecs';
14
+ /** A stack of label slices read on demand. Label 0 is background. */
15
+ export interface LabelStack {
16
+ width: number;
17
+ height: number;
18
+ slices: number;
19
+ /** Size of a voxel, x, y, z, in micrometres, when the source says. */
20
+ spacing?: [number, number, number];
21
+ /** Slice `z`'s labels, row-major (`height` rows of `width`). */
22
+ labels(z: number): Promise<Uint32Array>;
23
+ }
24
+ /** An OME-Zarr label image: its name, its slices, its classes. */
25
+ export interface OmeZarrLabels {
26
+ name: string;
27
+ stack: LabelStack;
28
+ classes: LabelClasses;
29
+ }
30
+ /** Whether `files` hold a zarr store (any zarr metadata among them). */
31
+ export declare function hasZarrMetadata(files: readonly RegionImportFile[]): boolean;
32
+ /**
33
+ * The first label image of the zarr store among `files` (the order `labels/`'s listing gives,
34
+ * else the first found), or null when the store has none.
35
+ */
36
+ export declare function readOmeZarrLabels(files: readonly RegionImportFile[], inflate?: Inflate): Promise<OmeZarrLabels | null>;
@@ -0,0 +1,73 @@
1
+ import type { IImageInfo } from '../contracts/image.contract';
2
+ import type { RegionSource } from '../region-source/region-source';
3
+ import { LabelClasses } from './label-classes';
4
+ import { Gunzip } from './label-volume';
5
+ import { LabelStack } from './ome-zarr-labels';
6
+ import { RegionImportFile } from './region-import';
7
+ import { Inflate } from './zarr-codecs';
8
+ import { InflateRaw } from './zip';
9
+ /** The longest side an image built for annotations may have: OpenSeadragon's non-tiled limit. */
10
+ export declare const ANNOTATION_IMAGE_MAX_DIM = 8192;
11
+ /** Opens a slice's region source (again, after the store closed it). */
12
+ type Opener = () => Promise<RegionSource>;
13
+ /** Decoders, injectable for tests and workers (defaults: the platform's streams). */
14
+ export interface AnnotationDecoders {
15
+ inflateRaw?: InflateRaw;
16
+ inflate?: Inflate;
17
+ gunzip?: Gunzip;
18
+ /** Open a GeoJSON text as a region source (default: in the shared region worker). */
19
+ openGeoJson?: (text: string) => Promise<RegionSource>;
20
+ /**
21
+ * Give a label image's regions the atlas look (dark outlines, labels) when its ontology names
22
+ * no style: drawn over the label image itself, outlines in their classes' own colours would not
23
+ * show. (Imported over a real image, the default look reads.)
24
+ */
25
+ outlineLabels?: boolean;
26
+ }
27
+ /** The annotations among some files, as one region source per slice. */
28
+ export interface AnnotationSlices {
29
+ kind: 'labels' | 'folder' | 'single';
30
+ /** A name for what was opened (the label image's, the folder's, the file's). */
31
+ name: string;
32
+ slices: number;
33
+ /** One opener per slice index. */
34
+ openers: Map<number, Opener>;
35
+ /** A label image's slices and classes (to draw them, see openAnnotations). */
36
+ labels?: {
37
+ stack: LabelStack;
38
+ classes: LabelClasses;
39
+ };
40
+ /** Slice width and height, when known without opening the sources (a label image's). */
41
+ size?: [number, number];
42
+ /** Voxel size x, y, z in micrometres, when the source says. */
43
+ spacing?: [number, number, number];
44
+ /** What was read, for a message ("12 slices of 456 × 320"). */
45
+ summary: string;
46
+ }
47
+ /**
48
+ * The annotations among `files` (zips expanded), as one region source per slice; null when there
49
+ * are none to read. A label image's slices are traced, and a GeoJSON parsed, when the slice is
50
+ * first shown.
51
+ */
52
+ export declare function readAnnotationSlices(chosen: readonly RegionImportFile[], decoders?: AnnotationDecoders): Promise<AnnotationSlices | null>;
53
+ /** What {@link openAnnotations} built: the image to publish, and how to release it. */
54
+ export interface OpenedAnnotations {
55
+ info: IImageInfo;
56
+ summary: string;
57
+ /** Revoke the image's blob URLs, and release the slices' sources the region store never took
58
+ * (once another image replaced it). */
59
+ dispose(): void;
60
+ }
61
+ /**
62
+ * The image `files`' annotations are shown on by themselves (see the module comment). Throws when
63
+ * there are none, or when their regions span more than {@link ANNOTATION_IMAGE_MAX_DIM} pixels
64
+ * (they are then imported onto the image they were drawn on).
65
+ */
66
+ export declare function openAnnotations(files: readonly RegionImportFile[], decoders?: AnnotationDecoders): Promise<OpenedAnnotations>;
67
+ /**
68
+ * One GeoJSON's slices: a collection whose features name planes (`geometry.plane.z`) split into
69
+ * one collection per plane, each keeping the set's class tree and style, its features moved to
70
+ * plane 0 (a slice's source shows plane 0); any other GeoJSON is one slice as it is.
71
+ */
72
+ export declare function splitByPlane(text: string): string[];
73
+ export {};
@@ -0,0 +1,14 @@
1
+ /** Inflate gzip or zlib data (default: the platform's `DecompressionStream`). */
2
+ export type Inflate = (bytes: Uint8Array, format: 'gzip' | 'deflate') => Promise<Uint8Array>;
3
+ /** Decode a Blosc frame. */
4
+ export declare function bloscDecompress(frame: Uint8Array, inflate?: Inflate): Promise<Uint8Array>;
5
+ /** Decode one LZ4 block (no frame) of `size` bytes. */
6
+ export declare function lz4BlockDecompress(src: Uint8Array, size: number): Uint8Array;
7
+ /**
8
+ * Decode a zarr chunk through `codecs`, outermost last as zarr lists them (v2: the one
9
+ * `compressor`; v3: the bytes-to-bytes codecs after `bytes`).
10
+ */
11
+ export declare function decodeChunk(bytes: Uint8Array, codecs: readonly {
12
+ id: string;
13
+ [k: string]: unknown;
14
+ }[], inflate?: Inflate): Promise<Uint8Array>;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The members of a `.zip`, read in the browser: annotations handed over as one archive (a folder
3
+ * of per-slice GeoJSON, a label volume with its ontology, an OME-Zarr store's labels).
4
+ *
5
+ * Only what such archives use: stored or deflated members, ZIP64 sizes and offsets, UTF-8 or
6
+ * legacy names. An encrypted or otherwise compressed member throws when it is read, rather than be
7
+ * read wrongly; a member's CRC-32 is checked. Members are read on demand, so an archive of many
8
+ * chunks (a zarr store) is not inflated whole. Pure: usable from workers.
9
+ */
10
+ import type { RegionImportFile } from './region-import';
11
+ /** One member of an archive. */
12
+ export interface ZipEntry {
13
+ /** Its path in the archive, '/'-separated. */
14
+ name: string;
15
+ /** Its uncompressed size, in bytes. */
16
+ size: number;
17
+ bytes(): Promise<Uint8Array>;
18
+ }
19
+ /** Inflate raw DEFLATE data (default: the platform's `DecompressionStream('deflate-raw')`). */
20
+ export type InflateRaw = (bytes: Uint8Array) => Promise<Uint8Array>;
21
+ /** Whether a file name is a zip archive. */
22
+ export declare function isZipName(name: string): boolean;
23
+ /** The members of the archive in `bytes` (directories and macOS metadata left out). */
24
+ export declare function readZip(bytes: Uint8Array, inflateRaw?: InflateRaw): ZipEntry[];
25
+ /**
26
+ * `files` with every `.zip` among them replaced by its members, named by their paths in it (a
27
+ * single top folder the archive wraps everything in is kept, as a folder pick would keep it).
28
+ */
29
+ export declare function expandZips(files: readonly RegionImportFile[], inflateRaw?: InflateRaw): Promise<RegionImportFile[]>;
30
+ /** CRC-32 (IEEE), as zip records it. */
31
+ export declare function crc32(data: Uint8Array): number;
@@ -75,8 +75,14 @@ export declare class RoutingVisualizerService implements IVisualizer, IRegionEdi
75
75
  private plottedFileName;
76
76
  /** The div the backends render into (every backend shares it); '' before the first plot. */
77
77
  private plotDiv;
78
- /** Whether a napari volume view is the current plot type. */
78
+ /**
79
+ * Whether regions can be shown in 3D on screen: a napari volume (or isosurface) view, or the 3D
80
+ * spatial cloud over a stack image (its reference volume's, see showVolumeAsImage). Both draw the
81
+ * region reconstructions the same way (VolumeRegionMeshes), in the volume's box.
82
+ */
79
83
  private readonly volumeView$;
84
+ /** Whether a napari volume (or isosurface) view is the current plot type: the slice pane's. */
85
+ private imageVolumeView;
80
86
  /** Whether the volume view's slice pane is wanted (see {@link setSliceEditor}). */
81
87
  private readonly sliceEditor$;
82
88
  /** Whether the volume view draws the region reconstructions through the volume. */
@@ -301,6 +307,8 @@ export declare class RoutingVisualizerService implements IVisualizer, IRegionEdi
301
307
  deleteSourceFeatures(ids: readonly string[]): void;
302
308
  getRegionStyle$(): Observable<RegionStyle>;
303
309
  setRegionStyle(patch: Partial<RegionStyle>): void;
310
+ /** Recompute which 3D view is on screen for `plotType` (see {@link volumeView$}). */
311
+ private updateVolumeView;
304
312
  /** Whether a napari volume (or isosurface) view is on screen: regions can be shown in 3D. */
305
313
  getVolumeView$(): Observable<boolean>;
306
314
  /** Whether the volume view's slice pane is wanted (it shows while a volume view is on screen). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jax-data-science/sci-image-visualizer",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "engines": {
5
5
  "node": ">=20.9.0"
6
6
  },
@@ -39,6 +39,7 @@
39
39
  "dependencies": {
40
40
  "cellpose-js": "^0.6.0",
41
41
  "fast-png": "^6.4.0",
42
+ "fzstd": "^0.1.1",
42
43
  "napari-js": "^0.18.0",
43
44
  "openseadragon": "^6.0.2",
44
45
  "plotly.js-dist-min": "^3.0.1",