@jax-data-science/sci-image-visualizer 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/esm2022/index.mjs +7 -1
  2. package/esm2022/lib/contracts/image.contract.mjs +1 -1
  3. package/esm2022/lib/contracts/ports/region-io.port.mjs +1 -1
  4. package/esm2022/lib/contracts/region-editor-api.contract.mjs +1 -1
  5. package/esm2022/lib/implementations/napari-js/napari-spatial-3d-scene.mjs +70 -2
  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/osd/openseadragon-viewer-options.mjs +18 -2
  9. package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +9 -1
  10. package/esm2022/lib/implementations/osd/osd-tile-loading.mjs +71 -0
  11. package/esm2022/lib/implementations/osd/osd-viewer-like.mjs +1 -1
  12. package/esm2022/lib/implementations/osd/slice-cache.mjs +35 -6
  13. package/esm2022/lib/region-editor/region-editor.component.mjs +152 -4
  14. package/esm2022/lib/region-editor/region-persistence.service.mjs +9 -1
  15. package/esm2022/lib/region-import/label-classes.mjs +149 -0
  16. package/esm2022/lib/region-import/label-volume.mjs +225 -0
  17. package/esm2022/lib/region-import/ome-zarr-labels.mjs +253 -0
  18. package/esm2022/lib/region-import/open-annotations.mjs +289 -0
  19. package/esm2022/lib/region-import/region-import.mjs +71 -0
  20. package/esm2022/lib/region-import/zarr-codecs.mjs +167 -0
  21. package/esm2022/lib/region-import/zip.mjs +157 -0
  22. package/esm2022/lib/routing-visualizer.service.mjs +112 -7
  23. package/esm2022/lib/store/region-source-scope.mjs +19 -4
  24. package/esm2022/lib/store/region-store.service.mjs +64 -14
  25. package/esm2022/lib/visualizer/region-load.mjs +16 -1
  26. package/esm2022/lib/visualizer.component.mjs +28 -2
  27. package/fesm2022/{jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BKQhpbHv.mjs → jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-a68Hw5rw.mjs} +2057 -196
  28. package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-a68Hw5rw.mjs.map +1 -0
  29. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-z1IN-F7w.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-CvadfzjS.mjs} +4 -2
  30. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-z1IN-F7w.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-CvadfzjS.mjs.map} +1 -1
  31. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +3 -1
  32. package/fesm2022/jax-data-science-sci-image-visualizer.mjs.map +1 -1
  33. package/index.d.ts +12 -0
  34. package/lib/contracts/image.contract.d.ts +7 -0
  35. package/lib/contracts/ports/region-io.port.d.ts +24 -0
  36. package/lib/contracts/region-editor-api.contract.d.ts +19 -1
  37. package/lib/implementations/napari-js/napari-spatial-3d-scene.d.ts +40 -1
  38. package/lib/implementations/napari-js/napari-volume-region-meshes.d.ts +5 -0
  39. package/lib/implementations/osd/openseadragon-viewer-options.d.ts +2 -0
  40. package/lib/implementations/osd/openseadragon-visualizer.service.d.ts +2 -0
  41. package/lib/implementations/osd/osd-tile-loading.d.ts +43 -0
  42. package/lib/implementations/osd/osd-viewer-like.d.ts +14 -0
  43. package/lib/implementations/osd/slice-cache.d.ts +13 -3
  44. package/lib/region-editor/region-editor.component.d.ts +27 -0
  45. package/lib/region-editor/region-persistence.service.d.ts +5 -1
  46. package/lib/region-import/label-classes.d.ts +39 -0
  47. package/lib/region-import/label-volume.d.ts +35 -0
  48. package/lib/region-import/ome-zarr-labels.d.ts +36 -0
  49. package/lib/region-import/open-annotations.d.ts +73 -0
  50. package/lib/region-import/region-import.d.ts +46 -0
  51. package/lib/region-import/zarr-codecs.d.ts +14 -0
  52. package/lib/region-import/zip.d.ts +31 -0
  53. package/lib/routing-visualizer.service.d.ts +39 -1
  54. package/lib/store/region-source-scope.d.ts +8 -2
  55. package/lib/store/region-store.service.d.ts +15 -5
  56. package/lib/visualizer.component.d.ts +9 -0
  57. package/package.json +2 -1
  58. package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BKQhpbHv.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-BKQhpbHv.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-a68Hw5rw.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-z1IN-F7w.mjs.map
240
+ //# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-CvadfzjS.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"jax-data-science-sci-image-visualizer-onnx-sam-session-z1IN-F7w.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-CvadfzjS.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 isBuiltinPlotType, ax as isCanvasToolId, ay as isCategoricalColumn, az as isContinuousColumn, aA as isDialogToolContribution, aB as isEmptyPresetSet, aC as isLittleEndian, aD as isNapari3d, aE as isNapariIsosurface, aF as isNapariScatter, aG as isNapariScatter3d, aH as isNapariSurface, aI as isNapariVolume, aJ as isSamModelReady, aK as isSpatialOmics, aL as isSpatialOmics3d, aM as isThreeDimensional, aN as lutFor, aO as markerDiameters, aP as maskToIndices, aQ as mutedFromSelection, aR as omicsLayout, aS as openGeoJsonRegionSource, aT as openHttpRegionSource, aU as parsePresetSet, aV as pointInRing, aW as provideVisualization, aX as rendererOwnsWheel, aY as resolveCategoryColors, aZ as selectByCategory, a_ as selectInRegions, a$ as setDefaultSamModel, b0 as setOrtWasmBase, b1 as setSamModelUrls, b2 as sortToolContributions, b3 as visibleToolContributions } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BKQhpbHv.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-a68Hw5rw.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
@@ -50,6 +50,18 @@ export { openGeoJsonRegionSource } from './lib/region-source/worker-region-sourc
50
50
  export { openHttpRegionSource } from './lib/region-source/http-region-source';
51
51
  export type { HttpRegionSourceOptions, WireTileFeature } from './lib/region-source/http-region-source';
52
52
  export { REGION_SOURCE_MIN_CHARS } from './lib/region-source/region-source';
53
+ export { isLabelVolumeName, readLabelVolume } from './lib/region-import/label-volume';
54
+ export type { LabelVolume } from './lib/region-import/label-volume';
55
+ export { labelClasses, labelSliceGeoJson } from './lib/region-import/label-classes';
56
+ export type { LabelClasses } from './lib/region-import/label-classes';
57
+ export { importFile, planRegionImport } from './lib/region-import/region-import';
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';
53
65
  export type { RegionSource, RegionSourceView, SourceFeature } from './lib/region-source/region-source';
54
66
  export type { RegionIndexPage, RegionIndexQuery, RegionIndexRow, RegionSourceInfo, RegionTile, RegionTileKey, TileFeature, } from './lib/region-source/region-tiles';
55
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
@@ -25,6 +25,30 @@ export interface RegionIoPort {
25
25
  z: number;
26
26
  geoJsonStr: string;
27
27
  }[]): Observable<void>;
28
+ /**
29
+ * "Import regions from server": let the user pick region files or a region set in the host's
30
+ * storage (the current project's bucket, say) and hand them over. Emits null when the user
31
+ * cancels. Without it, the Region Editor shows no server import.
32
+ */
33
+ pickServerRegions?(): Observable<ServerRegionPick | null>;
28
34
  }
35
+ /**
36
+ * Regions picked on the host's server (see {@link RegionIoPort.pickServerRegions}): files it
37
+ * downloaded, imported as the Region Editor imports chosen files (one GeoJSON; a label volume
38
+ * `.nii` / `.nii.gz` / `.nrrd` with its ontology JSON; a folder's per-slice GeoJSON files with its
39
+ * `regionset.json`), or a region set its server serves by level of detail (jit-service's
40
+ * `/regions/{id}/`), read through the host's `HttpClient` without downloading it.
41
+ */
42
+ export type ServerRegionPick = {
43
+ kind: 'files';
44
+ files: Array<{
45
+ name: string;
46
+ data: Blob | ArrayBuffer | Uint8Array | string;
47
+ }>;
48
+ } | {
49
+ kind: 'source';
50
+ url: string;
51
+ name?: string;
52
+ };
29
53
  /** DI token for the host's {@link RegionIoPort}. Required by the region editor. */
30
54
  export declare const REGION_IO_PORT: InjectionToken<RegionIoPort>;
@@ -3,7 +3,7 @@ import { Observable } from 'rxjs';
3
3
  import { Region } from '../models/region';
4
4
  import { ClassPreset, PresetSet } from '../models/class-preset';
5
5
  import { IImageMetadata } from './image.contract';
6
- import type { RegionSourceView } from '../region-source/region-source';
6
+ import type { RegionSource, RegionSourceView } from '../region-source/region-source';
7
7
  import type { RegionIndexPage, RegionIndexQuery } from '../region-source/region-tiles';
8
8
  import type { RegionStyle } from '../region-overlay/region-style';
9
9
  /**
@@ -87,6 +87,24 @@ export interface IRegionEditorApi {
87
87
  * and should be imported as regions; rejects when it is not a GeoJSON FeatureCollection.
88
88
  */
89
89
  importRegionSource?(geoJsonStr: string): Promise<boolean>;
90
+ /**
91
+ * A ticket for an import about to read or decode its files: an import method given a stale one
92
+ * (another import took a ticket since, or another image was plotted) changes nothing.
93
+ */
94
+ regionImportTicket?(): number;
95
+ /**
96
+ * Replace the regions and region sources with one source per slice (an imported label volume,
97
+ * or a folder of per-slice region files): openers by slice index, each opened the first time its
98
+ * slice is shown. A single image takes slice 0's. Returns how many slices have one, or null
99
+ * (changing nothing) when `ticket` is stale. A save writes every imported slice.
100
+ */
101
+ importSliceRegionSources?(openers: ReadonlyMap<number, () => Promise<RegionSource>>, ticket?: number): number | null;
102
+ /**
103
+ * Replace them with one imported source (a region set a server serves): over every slice of a
104
+ * stack when z-indexed, else on the slice shown. Resolves false, changing nothing, when the
105
+ * import went stale while the source opened.
106
+ */
107
+ importRegionSourceOpener?(open: () => Promise<RegionSource>, ticket?: number): Promise<boolean>;
90
108
  /**
91
109
  * Drop the slice's region source, for regions imported in its place (a small file replacing a
92
110
  * large one). The other slices keep theirs.
@@ -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;
@@ -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;
@@ -23,6 +23,8 @@ export interface ViewerOptionsInput {
23
23
  sliceCount: number;
24
24
  /** How many slices the slice cache keeps resident (see SliceCache.maxSlices). */
25
25
  maxSlices: number;
26
+ /** Tiled images per slice: the channel count when channels are drawn per channel, else 1. */
27
+ channelImages?: number;
26
28
  }
27
29
  /**
28
30
  * The options object handed to the OpenSeadragon factory. Pure: every value
@@ -53,6 +53,8 @@ export declare class OpenSeadragonVisualizerService extends BaseStoreVisualizer
53
53
  private viewer;
54
54
  private overlay;
55
55
  private scaleBar;
56
+ /** "Image reloading…" while the view fetches its tiles. */
57
+ private tileLoading;
56
58
  private descriptor;
57
59
  /** Base64 RawFileInfo + current z-slice, kept so setZIndex can rebuild the
58
60
  * tile source and swap slices live (stack navigation). */
@@ -0,0 +1,43 @@
1
+ /** What it needs of the viewer. */
2
+ interface LoadingViewer {
3
+ element: HTMLElement;
4
+ world: {
5
+ getItemCount(): number;
6
+ getItemAt(i: number): unknown;
7
+ addHandler(event: 'add-item' | 'remove-item', handler: (e: {
8
+ item: unknown;
9
+ }) => void): unknown;
10
+ removeHandler(event: 'add-item' | 'remove-item', handler: (e: {
11
+ item: unknown;
12
+ }) => void): unknown;
13
+ };
14
+ }
15
+ /**
16
+ * "Image reloading…" over the OpenSeadragon view while it fetches the tiles of what it shows, as
17
+ * napari's views show it for their image, cells and transcripts (the same badge). Driven by each
18
+ * world item's own `fully-loaded-change` (an image, a stack slice, a channel), so it covers every
19
+ * tile source alike, and shown only after a short delay, so tiles served from cache never flash it.
20
+ *
21
+ * Only items on screen count (opacity above 0): a hidden channel loads nothing by design (it is
22
+ * not preloaded), so it never becomes fully loaded, and a slice cached or loading in the
23
+ * background is not what the view shows. An item is weighed again when its opacity changes.
24
+ */
25
+ export declare class OsdTileLoading {
26
+ private readonly viewer;
27
+ private readonly state;
28
+ /** Each item still loading, with the function that un-counts it. */
29
+ private readonly loading;
30
+ private readonly watched;
31
+ private readonly onAdd;
32
+ private readonly onRemove;
33
+ constructor(viewer: LoadingViewer);
34
+ /** The badge's text (shown, or about to be). */
35
+ get text(): string;
36
+ destroy(): void;
37
+ private watch;
38
+ private unwatch;
39
+ /** Whether the item is on screen and still fetching its tiles. */
40
+ private isLoading;
41
+ private set;
42
+ }
43
+ export {};
@@ -48,6 +48,18 @@ export interface OsdViewerLike {
48
48
  /** OSD's own pan/zoom (drag + wheel); region tools switch it off. */
49
49
  setMouseNavEnabled(enabled: boolean): unknown;
50
50
  isMouseNavEnabled(): boolean;
51
+ /** OSD's tile request queue: the jobs not started yet (see OsdQueuedTileJob). */
52
+ imageLoader?: {
53
+ jobQueue?: unknown[];
54
+ };
55
+ }
56
+ /** A tile request waiting in OSD's queue: `abort` (before it starts) only marks the tile as
57
+ * not loading, so it is requested again when it is needed. */
58
+ export interface OsdQueuedTileJob {
59
+ tile?: {
60
+ tiledImage?: unknown;
61
+ };
62
+ abort?: () => void;
51
63
  }
52
64
  /** A point in any OSD space (viewer-element px, viewport units or image px —
53
65
  * the method it comes from says which). */
@@ -70,6 +82,8 @@ export interface OsdTiledImageLike {
70
82
  imageToViewportRectangle(r: OpenSeadragon.Rect): OpenSeadragon.Rect;
71
83
  viewportToImageRectangle(r: OsdRectLike): OsdRectLike;
72
84
  setOpacity(opacity: number): void;
85
+ /** Load tiles even while hidden (opacity 0); without it a hidden item loads nothing. */
86
+ setPreload?(preload: boolean): void;
73
87
  /** False while any of the image's tiles are still loading. */
74
88
  getFullyLoaded(): boolean;
75
89
  /** Re-run the tile pipeline (restore + re-process) for this image's tiles;
@@ -106,14 +106,22 @@ export declare class SliceCache {
106
106
  /**
107
107
  * Add slice z's channel group: one additively-composited ('lighter' = Fiji
108
108
  * "Composite") TiledImage per channel, hidden (opacity 0) until revealed.
109
- * Each is preloaded so the background loader can pre-fill every slice.
109
+ * The visible channels' are preloaded, so the background loader can pre-fill
110
+ * every slice; a hidden channel's loads nothing until it is shown (a 22-channel
111
+ * plate fetched and decoded every channel's tiles, overflowing the tile cache:
112
+ * tiles evicted mid-conversion failed to decode).
110
113
  * Cached so a revisit is an instant opacity toggle — no re-fetch, no
111
114
  * white-flicker. Mirrors addSlice for the composite path.
112
115
  */
113
116
  addChannelSlice(z: number): void;
114
117
  /** Reveal slice z's channel group (each visible channel → opacity 1) and
115
- * hide every other cached slice's group. Instant; no fetch/decode. */
118
+ * hide every other cached slice's group. Instant; no fetch/decode. Only the
119
+ * visible channels keep loading (preload): a hidden channel's tiles are not
120
+ * fetched or decoded until it is shown. */
116
121
  revealChannelSlice(z: number): void;
122
+ /** Drop the tile requests still waiting in OSD's queue for `items` (channels just hidden), so
123
+ * the visible channels' tiles are not stuck behind them. A request already sent completes. */
124
+ private dropQueuedTiles;
117
125
  /** Show the given composite slice (opacity 1) and hide all other cached slices. */
118
126
  private showOnlySlice;
119
127
  /** Re-tint after a display change: invalidate ONLY the visible slice's
@@ -154,7 +162,9 @@ export declare class SliceCache {
154
162
  private sliceCacheHas;
155
163
  /** The cached TiledImage(s) for slice z (0–1 for composite, N for multichannel). */
156
164
  private sliceGroup;
157
- /** Whether slice z's tiles are still streaming (any cached image not fully loaded). */
165
+ /** Whether slice z's tiles are still streaming (any cached image not fully loaded). A hidden
166
+ * channel loads nothing, so it never becomes fully loaded: only the visible channels count, or
167
+ * the background loader would wait on it forever. */
158
168
  private sliceTilesLoading;
159
169
  /** Remove slice z's cached image(s) from the world and the active cache. */
160
170
  private removeCachedSlice;
@@ -26,6 +26,8 @@ export declare class RegionEditorComponent implements OnInit, OnDestroy {
26
26
  */
27
27
  set active(value: boolean);
28
28
  private isActive;
29
+ /** The host's HttpClient: a served region set's requests go through it (its auth). */
30
+ private readonly http;
29
31
  protected regions: Region[];
30
32
  protected selectedRegions: Region[];
31
33
  protected showShapeLabel: boolean;
@@ -348,7 +350,32 @@ export declare class RegionEditorComponent implements OnInit, OnDestroy {
348
350
  protected importPresets(event: Event): void;
349
351
  /** Validate and apply an imported annotation-classes JSON file. */
350
352
  private applyImportedPresets;
353
+ /** The files chosen in the import inputs: one GeoJSON, a label volume with its ontology, or a
354
+ * folder of per-slice GeoJSON files. */
351
355
  protected importRois(event: Event): void;
356
+ /** Whether the host can pick regions on its server ("Import regions from server"). */
357
+ protected get canImportFromServer(): boolean;
358
+ /** An import from the server is in flight. */
359
+ protected importing: boolean;
360
+ /** "Import regions from server": the host picks files or a served region set; they import as chosen files do. */
361
+ protected importFromServer(): void;
362
+ /**
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.
368
+ */
369
+ private importRegionFiles;
370
+ /** A region set the host's server serves (jit-service's /regions/{id}/), read through its HttpClient. */
371
+ private importServedRegionSet;
372
+ /** An import that finished after another image was shown (or another import began): dropped. */
373
+ private importStale;
374
+ /** The table and selection after the regions were replaced by imported sources. */
375
+ private afterSourceImport;
376
+ /** Warn when an imported volume's slices are not the image's size: its outlines would not fit. */
377
+ private warnIfSizeDiffers;
378
+ private importFailed;
352
379
  /**
353
380
  * Replace the regions with an imported GeoJSON file's, and the slice's region source with them.
354
381
  * A large file becomes the slice's region source instead (drawn by level of detail; listed above
@@ -1,7 +1,7 @@
1
1
  import { Observable } from 'rxjs';
2
2
  import { Region } from '../models/region';
3
3
  import { IRegionEditorApi } from '../contracts/region-editor-api.contract';
4
- import { RegionIoPort } from '../contracts/ports/region-io.port';
4
+ import { RegionIoPort, ServerRegionPick } from '../contracts/ports/region-io.port';
5
5
  import * as i0 from "@angular/core";
6
6
  /** One slice-file's serialized regions (folder stack, jit-ui#93). */
7
7
  export interface SliceGeoJson {
@@ -67,6 +67,10 @@ export declare class RegionPersistenceService {
67
67
  * in it.
68
68
  */
69
69
  sliceGeoJsons(): Promise<SliceGeoJson[]>;
70
+ /** Whether the host can pick regions on its server (see RegionIoPort.pickServerRegions). */
71
+ canPickServerRegions(): boolean;
72
+ /** Let the user pick regions on the host's server; null when cancelled or unsupported. */
73
+ pickServerRegions(): Observable<ServerRegionPick | null>;
70
74
  /** Write per-slice geojsons through the host port. */
71
75
  saveSlices(slices: SliceGeoJson[]): Observable<void>;
72
76
  /** Read the file chosen in a file input's change event as text, then call
@@ -0,0 +1,39 @@
1
+ import type { RegionClassNode, RegionSourceStyle } from '../region-source/region-tiles';
2
+ import type { LabelVolume } from './label-volume';
3
+ /**
4
+ * The classes of a label volume's values: their names and colours and, when the volume comes
5
+ * with an ontology, their tree. Pure.
6
+ */
7
+ export interface LabelClasses {
8
+ /** The class of label `value` (> 0); one without an ontology entry is `label <value>`. */
9
+ classOf(value: number): {
10
+ name: string;
11
+ color: string;
12
+ title?: string;
13
+ };
14
+ /** The class tree, when the ontology has one. */
15
+ hierarchy?: RegionClassNode[];
16
+ /** How the set wants to be drawn (an atlas's filled look), when the ontology says. */
17
+ style?: RegionSourceStyle;
18
+ }
19
+ /** The look the Allen atlas region sets carry (see the brain-atlas example's GeoJSON). */
20
+ export declare const ATLAS_STYLE: RegionSourceStyle;
21
+ /**
22
+ * The classes of a label volume from `ontology`, a parsed JSON document:
23
+ *
24
+ * - an **Allen structure graph** (`structure_graph_1.json`: `{ msg: [root] }`, or a node or a
25
+ * list of nodes with `id`, `acronym`, `name`, `color_hex_triplet`, `graph_order` and
26
+ * `children`): label = structure id, class = acronym, its tree the ontology's, drawn as the
27
+ * atlas region sets are;
28
+ * - a **region set description** (a `regionset.json`: `classHierarchy`, optional `style`), whose
29
+ * classes name their label as `value` (or `id`), or with a `labels` map of value → class name.
30
+ *
31
+ * Without one (or with another document), each label is its own class `label <value>`.
32
+ */
33
+ export declare function labelClasses(ontology?: unknown): LabelClasses;
34
+ /**
35
+ * Slice `z` of `volume` as a GeoJSON FeatureCollection string: one feature per label on the
36
+ * slice, its every piece (with holes) traced along pixel edges, in image pixels with y down;
37
+ * classified by `classes`, which also give the collection its class tree and style.
38
+ */
39
+ export declare function labelSliceGeoJson(volume: LabelVolume, z: number, classes: LabelClasses): string;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * A label volume (a NIfTI-1 `.nii` / `.nii.gz` or a NRRD `.nrrd` of integer structure ids, an
3
+ * atlas annotation say) read in the browser, sliced as the server opens the matching image:
4
+ *
5
+ * - **NRRD**: a 3D volume is cut along its FIRST axis, each section its third axis wide and its
6
+ * second axis high, as jit-service opens a NRRD volume (`AtlasNrrdReader`: the Allen CCF's 528
7
+ * coronal sections of 456 × 320, anterior first). A 2D NRRD is one slice.
8
+ * - **NIfTI**: along its third axis, each slice its first axis wide and its second axis high, as
9
+ * Bio-Formats reads one (no row flip).
10
+ *
11
+ * Only what label volumes use: a single file (NRRD with attached data, NIfTI-1 `n+1`), integer
12
+ * or integer-valued float samples, either byte order, raw or gzip. Anything else throws rather
13
+ * than be read approximately. Pure: usable from workers.
14
+ */
15
+ /** A label volume, sliced (see the module comment). Label 0 is background. */
16
+ export interface LabelVolume {
17
+ format: 'nrrd' | 'nifti';
18
+ /** A slice's width and height, in voxels (the image's pixels). */
19
+ width: number;
20
+ height: number;
21
+ /** Number of slices. */
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];
27
+ /** Slice `z`'s labels, row-major (`height` rows of `width`). */
28
+ sliceLabels(z: number): Uint32Array;
29
+ }
30
+ /** Decompress gzip bytes (default: the platform's `DecompressionStream`). */
31
+ export type Gunzip = (bytes: Uint8Array) => Promise<Uint8Array>;
32
+ /** Whether a file name is a label volume this reads. */
33
+ export declare function isLabelVolumeName(name: string): boolean;
34
+ /** Read the label volume in `bytes`, named `name` (`.nrrd`, `.nii`, `.nii.gz`). */
35
+ export declare function readLabelVolume(name: string, bytes: Uint8Array, gunzip?: Gunzip): Promise<LabelVolume>;
@@ -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>;