@jax-data-science/sci-image-visualizer 0.10.0 → 0.11.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 (41) hide show
  1. package/esm2022/index.mjs +4 -1
  2. package/esm2022/lib/contracts/ports/region-io.port.mjs +1 -1
  3. package/esm2022/lib/contracts/region-editor-api.contract.mjs +1 -1
  4. package/esm2022/lib/implementations/osd/openseadragon-viewer-options.mjs +18 -2
  5. package/esm2022/lib/implementations/osd/openseadragon-visualizer.service.mjs +9 -1
  6. package/esm2022/lib/implementations/osd/osd-tile-loading.mjs +71 -0
  7. package/esm2022/lib/implementations/osd/osd-viewer-like.mjs +1 -1
  8. package/esm2022/lib/implementations/osd/slice-cache.mjs +35 -6
  9. package/esm2022/lib/region-editor/region-editor.component.mjs +147 -4
  10. package/esm2022/lib/region-editor/region-persistence.service.mjs +9 -1
  11. package/esm2022/lib/region-import/label-classes.mjs +149 -0
  12. package/esm2022/lib/region-import/label-volume.mjs +194 -0
  13. package/esm2022/lib/region-import/region-import.mjs +71 -0
  14. package/esm2022/lib/routing-visualizer.service.mjs +93 -1
  15. package/esm2022/lib/store/region-source-scope.mjs +19 -4
  16. package/esm2022/lib/store/region-store.service.mjs +64 -14
  17. package/esm2022/lib/visualizer.component.mjs +28 -2
  18. 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-BELcx0jv.mjs} +1049 -188
  19. package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BELcx0jv.mjs.map +1 -0
  20. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-z1IN-F7w.mjs → jax-data-science-sci-image-visualizer-onnx-sam-session-DEDSm_NL.mjs} +2 -2
  21. package/fesm2022/{jax-data-science-sci-image-visualizer-onnx-sam-session-z1IN-F7w.mjs.map → jax-data-science-sci-image-visualizer-onnx-sam-session-DEDSm_NL.mjs.map} +1 -1
  22. package/fesm2022/jax-data-science-sci-image-visualizer.mjs +1 -1
  23. package/index.d.ts +6 -0
  24. package/lib/contracts/ports/region-io.port.d.ts +24 -0
  25. package/lib/contracts/region-editor-api.contract.d.ts +19 -1
  26. package/lib/implementations/osd/openseadragon-viewer-options.d.ts +2 -0
  27. package/lib/implementations/osd/openseadragon-visualizer.service.d.ts +2 -0
  28. package/lib/implementations/osd/osd-tile-loading.d.ts +43 -0
  29. package/lib/implementations/osd/osd-viewer-like.d.ts +14 -0
  30. package/lib/implementations/osd/slice-cache.d.ts +13 -3
  31. package/lib/region-editor/region-editor.component.d.ts +26 -0
  32. package/lib/region-editor/region-persistence.service.d.ts +5 -1
  33. package/lib/region-import/label-classes.d.ts +39 -0
  34. package/lib/region-import/label-volume.d.ts +31 -0
  35. package/lib/region-import/region-import.d.ts +46 -0
  36. package/lib/routing-visualizer.service.d.ts +30 -0
  37. package/lib/store/region-source-scope.d.ts +8 -2
  38. package/lib/store/region-store.service.d.ts +15 -5
  39. package/lib/visualizer.component.d.ts +9 -0
  40. package/package.json +1 -1
  41. package/fesm2022/jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-BKQhpbHv.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { LruCache } from 'napari-js';
2
2
  import * as ort from 'onnxruntime-web';
3
- import { g as getOrtWasmBase, f as fetchModel, r as runEncoder, a as runDecoder } from './jax-data-science-sci-image-visualizer-jax-data-science-sci-image-visualizer-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-BELcx0jv.mjs';
4
4
  import '@angular/core';
5
5
  import '@angular/common/http';
6
6
  import 'rxjs';
@@ -235,4 +235,4 @@ class OnnxSamSession {
235
235
  }
236
236
 
237
237
  export { OnnxSamSession };
238
- //# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-z1IN-F7w.mjs.map
238
+ //# sourceMappingURL=jax-data-science-sci-image-visualizer-onnx-sam-session-DEDSm_NL.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-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,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 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';
2
2
  import '@angular/core';
3
3
  import '@angular/common/http';
4
4
  import 'napari-js';
package/index.d.ts CHANGED
@@ -50,6 +50,12 @@ 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';
53
59
  export type { RegionSource, RegionSourceView, SourceFeature } from './lib/region-source/region-source';
54
60
  export type { RegionIndexPage, RegionIndexQuery, RegionIndexRow, RegionSourceInfo, RegionTile, RegionTileKey, TileFeature, } from './lib/region-source/region-tiles';
55
61
  export { VisualizationModule } from './lib/visualization.module';
@@ -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.
@@ -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,31 @@ 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` (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.
367
+ */
368
+ private importRegionFiles;
369
+ /** A region set the host's server serves (jit-service's /regions/{id}/), read through its HttpClient. */
370
+ private importServedRegionSet;
371
+ /** An import that finished after another image was shown (or another import began): dropped. */
372
+ private importStale;
373
+ /** The table and selection after the regions were replaced by imported sources. */
374
+ private afterSourceImport;
375
+ /** Warn when an imported volume's slices are not the image's size: its outlines would not fit. */
376
+ private warnIfSizeDiffers;
377
+ private importFailed;
352
378
  /**
353
379
  * Replace the regions with an imported GeoJSON file's, and the slice's region source with them.
354
380
  * 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,31 @@
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
+ /** Slice `z`'s labels, row-major (`height` rows of `width`). */
24
+ sliceLabels(z: number): Uint32Array;
25
+ }
26
+ /** Decompress gzip bytes (default: the platform's `DecompressionStream`). */
27
+ export type Gunzip = (bytes: Uint8Array) => Promise<Uint8Array>;
28
+ /** Whether a file name is a label volume this reads. */
29
+ export declare function isLabelVolumeName(name: string): boolean;
30
+ /** Read the label volume in `bytes`, named `name` (`.nrrd`, `.nii`, `.nii.gz`). */
31
+ export declare function readLabelVolume(name: string, bytes: Uint8Array, gunzip?: Gunzip): Promise<LabelVolume>;
@@ -0,0 +1,46 @@
1
+ /** A file to import regions from: one the user chose, or one the host's server sent. */
2
+ export interface RegionImportFile {
3
+ name: string;
4
+ text(): Promise<string>;
5
+ bytes(): Promise<Uint8Array>;
6
+ }
7
+ /** A file's contents as the host may hand them over (see `ServerRegionPick`). */
8
+ export type RegionImportData = Blob | ArrayBuffer | Uint8Array | string;
9
+ /** `data`, named `name`, as an import file. A `File`'s own name is used when `name` is omitted. */
10
+ export declare function importFile(data: RegionImportData, name?: string): RegionImportFile;
11
+ /** What a set of chosen files imports as. */
12
+ export type RegionImportPlan =
13
+ /** A label volume, its classes named by an ontology (or a region set description) beside it. */
14
+ {
15
+ kind: 'volume';
16
+ volume: RegionImportFile;
17
+ ontology: RegionImportFile | null;
18
+ }
19
+ /** A folder of per-slice region files, in slice order, with the set's `regionset.json`. */
20
+ | {
21
+ kind: 'folder';
22
+ slices: RegionImportFile[];
23
+ regionSet: RegionImportFile | null;
24
+ }
25
+ /** One region file. */
26
+ | {
27
+ kind: 'single';
28
+ file: RegionImportFile;
29
+ };
30
+ /**
31
+ * What `files` import as: a label volume (`.nii`, `.nii.gz`, `.nrrd`) with the ontology JSON chosen
32
+ * with it (a `regionset.json`, else an Allen `structure_graph*.json`, else any `.json`); several
33
+ * region files (a folder of per-slice `.geojson`, in natural name order, its `regionset.json`
34
+ * apart); or one region file (`.geojson` or `.json`). Null when none of them is one.
35
+ */
36
+ export declare function planRegionImport(files: readonly RegionImportFile[]): RegionImportPlan | null;
37
+ /** "slice_2" before "slice_10": runs of digits compared as numbers. */
38
+ export declare function naturalCompare(a: string, b: string): number;
39
+ /**
40
+ * A folder slice's GeoJSON with the set's class tree and style (its `regionset.json`) as the
41
+ * collection's own members, unless the file has its own: what a served folder's slice carries.
42
+ */
43
+ export declare function folderSliceGeoJson(file: RegionImportFile, regionSet: {
44
+ classHierarchy?: unknown;
45
+ style?: unknown;
46
+ } | null): Promise<string>;
@@ -114,6 +114,8 @@ export declare class RoutingVisualizerService implements IVisualizer, IRegionEdi
114
114
  private readonly sectionSubs;
115
115
  /** The image info and screen height of the last plot (what the slice pane mounts). */
116
116
  private plottedInfo;
117
+ /** The current region import (see regionImportTicket): its id and the image it was taken for. */
118
+ private importTicket;
117
119
  private plottedHeight;
118
120
  constructor(plotly: PlotlyService, osd: OpenSeadragonVisualizerService, napari: NapariVisualizerService, store: VisualizerStore, config: VizConfig, regionStore: RegionStore, selectionStore: SpatialSelectionStore, intensity: IntensityProfileService, spatialData?: SpatialDataPort | null);
119
121
  /** A class's colour: its classification colour, else its region source's, else a default. */
@@ -265,6 +267,34 @@ export declare class RoutingVisualizerService implements IVisualizer, IRegionEdi
265
267
  attachRegionSource(source: RegionSource | RegionSourceOpener, slices?: RegionSourceSlices): void;
266
268
  importRegionSource(geoJsonStr: string): Promise<boolean>;
267
269
  dropRegionSource(): Promise<void>;
270
+ /**
271
+ * A ticket for a region import that is about to read or decode its files: the import methods
272
+ * given it change nothing once it is stale — another import took a ticket since, another image
273
+ * was plotted, or the service was destroyed — so an import that finishes late never lands on an
274
+ * image it was not started for.
275
+ */
276
+ regionImportTicket(): number;
277
+ /** Whether `ticket` is still the current import's, on the image it was taken for. */
278
+ private importTicketValid;
279
+ private importImageKey;
280
+ /**
281
+ * Replace the image's regions and region sources with one source per slice, as an imported label
282
+ * volume or folder of per-slice region files gives them: `openers` by slice index, each opened
283
+ * the first time its slice is shown. A single image takes slice 0's. Returns how many slices
284
+ * have a source, or null (changing nothing) when `ticket` is stale (see
285
+ * {@link regionImportTicket}). The sources are marked imported, so a save writes every slice.
286
+ */
287
+ importSliceRegionSources(openers: ReadonlyMap<number, RegionSourceOpener>, ticket?: number): number | null;
288
+ /**
289
+ * Replace the image's regions and region sources with one imported source (a region set the
290
+ * host's server serves): over every slice of a stack when it is z-indexed, else on the slice
291
+ * shown, as {@link applyImageRois} lays out an image's own. Resolves false, changing nothing
292
+ * (and disposing the source), when the import went stale while it opened: another import, or
293
+ * another image (see {@link regionImportTicket}; one is taken here when `ticket` is not given).
294
+ */
295
+ importRegionSourceOpener(open: RegionSourceOpener, ticket?: number): Promise<boolean>;
296
+ /** Empty every slice of a stack (its regions and undo history) for regions imported over it. */
297
+ private enterImportedStack;
268
298
  getRegionSourceView$(): Observable<RegionSourceView | null>;
269
299
  getRegionSourceIndex(query: Omit<RegionIndexQuery, 'exclude'>): Promise<RegionIndexPage>;
270
300
  selectSourceFeatures(ids: readonly string[], additive?: boolean): Promise<number[]>;
@@ -31,8 +31,12 @@ export declare class RegionSourceScope {
31
31
  private readonly openOrder;
32
32
  /** The entry of the slice on screen: kept open. */
33
33
  private shown;
34
- /** Attach `open` to `image` for `slices`, replacing (and closing) what was there. */
35
- attach(image: string, open: RegionSourceOpener, slices: RegionSourceSlices): void;
34
+ /**
35
+ * Attach `open` to `image` for `slices`, replacing (and closing) what was there. `imported` names
36
+ * the slices it brings that a save must write (see {@link importedSlices}): for a source of one
37
+ * slice, that slice when non-empty; for a `'planes'` source, the slices its planes cover.
38
+ */
39
+ attach(image: string, open: RegionSourceOpener, slices: RegionSourceSlices, imported?: readonly number[]): void;
36
40
  /** Drop every source of `image` (closing the open ones) and what was taken from them. */
37
41
  detachImage(image: string): void;
38
42
  /** Drop the source of one slice of `image`. */
@@ -56,6 +60,8 @@ export declare class RegionSourceScope {
56
60
  taken(image: string | undefined, slice: number): ReadonlySet<string>;
57
61
  /** Every id taken from any slice of `image` (a `'planes'` source's ids are unique across them). */
58
62
  allTaken(image: string | undefined): string[];
63
+ /** The slices of `image` whose source was imported (see {@link attach}), in order. */
64
+ importedSlices(image: string | undefined): number[];
59
65
  /** The slices of `image` with anything taken from their source. */
60
66
  slicesWithTaken(image: string | undefined): number[];
61
67
  /** Replace the ids taken from `slice`'s source (copy-on-write: `ids` is kept as given). */
@@ -338,8 +338,13 @@ export declare class RegionStore implements IRegionStore, IRegionEditApi, IRegio
338
338
  * source was closed to save memory). `slices` says which slice of the current image it
339
339
  * serves: one (default: the slice shown; plane 0 of the source), or `'planes'`, every slice z
340
340
  * from plane z of a z-indexed source. Replaces the source those slices had.
341
+ *
342
+ * `imported` marks a source brought in over the image (an imported volume, folder or served set)
343
+ * rather than the image's own regions: it is on no file yet, so a save writes its slices though
344
+ * nothing was taken from it — `true` for a source of one slice, the slices its planes cover for a
345
+ * `'planes'` source. The image's own sources are not marked: loading them changes nothing to save.
341
346
  */
342
- attachRegionSource(source: RegionSource | RegionSourceOpener, slices?: RegionSourceSlices): void;
347
+ attachRegionSource(source: RegionSource | RegionSourceOpener, slices?: RegionSourceSlices, imported?: boolean | readonly number[]): void;
343
348
  /** Drop the current image's region sources (closing them) and what was taken from them. */
344
349
  detachRegionSources(): void;
345
350
  /**
@@ -439,10 +444,15 @@ export declare class RegionStore implements IRegionStore, IRegionEditApi, IRegio
439
444
  */
440
445
  getRegionSourceIndex(query: Omit<RegionIndexQuery, 'exclude'>): Promise<RegionIndexPage>;
441
446
  /**
442
- * `regions` as a GeoJSON FeatureCollection string merged with the region source behind them:
443
- * the source's features that were not taken (or deleted), then `regions`. Without a source,
444
- * {@link getGeoJsonString}. `slice` names a folder stack's slice (default the one shown);
445
- * on a z-indexed stack's source (`'planes'`) the export spans every slice.
447
+ * `regions` as a GeoJSON FeatureCollection string merged with the region sources behind them:
448
+ * the sources' features that were not taken (or deleted), then `regions`. Without a source,
449
+ * {@link getGeoJsonString}.
450
+ *
451
+ * `slice` names one slice-file of a folder stack (a per-slice-file save): its source's features,
452
+ * on the file's own plane — of a z-indexed source over every slice, only that slice's plane.
453
+ * Without it, the export is the image's: on a stack saved as one file, every slice's source
454
+ * (each feature on its slice's plane, `geometry.plane.z`) and a z-indexed source's every plane;
455
+ * otherwise the source of the slice shown.
446
456
  */
447
457
  getGeoJsonWithSource(regions: Region[], slice?: number): Promise<string>;
448
458
  /**