From e312323ef5c6c1b42f946a11cb941a36e5b84b5a Mon Sep 17 00:00:00 2001 From: Cameron Fraser Date: Thu, 24 Sep 2026 13:08:28 -0700 Subject: [PATCH 1/4] bones of an adapter --- src/data_manager/AdapterLoader.ts | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/data_manager/AdapterLoader.ts diff --git a/src/data_manager/AdapterLoader.ts b/src/data_manager/AdapterLoader.ts new file mode 100644 index 00000000..856070fa --- /dev/null +++ b/src/data_manager/AdapterLoader.ts @@ -0,0 +1,59 @@ +import type { ImageInfo } from "../ImageInfo.js"; +import type { LoadedVolumeInfo, LoadSpec, RawChannelDataCallback } from "../loaders/IVolumeLoader.js"; +import { ThreadableVolumeLoader } from "../loaders/IVolumeLoader.js"; +import { VolumeDims } from "../VolumeDims.js"; +import DataManager, { IDataSubscriber } from "./DataManager.js"; +import type { Chunk, ChunkId } from "./types.js"; + +export default class AdapterLoader extends ThreadableVolumeLoader implements IDataSubscriber { + constructor( + private manager: DataManager, + private sourceId: number + ) { + super(); + manager.subscribeToSource(this, sourceId); + } + + loadDims(loadSpec: LoadSpec): Promise { + const result = this.manager.getSourceDims(this.sourceId); + if (result === undefined) { + throw new Error("AdapterLoader: source does not exist"); + } + return Promise.resolve(result); + } + + createImageInfo(loadSpec: LoadSpec): Promise { + const imageInfo: ImageInfo = { + name: undefined, + atlasTileDims: [], + subregionSize: [], + subregionOffset: [], + numChannelsPerSource: [], + channelNames: [], + channelColors: [], + multiscaleLevelDims: [], + multiscaleLevel: 0, + transform: { + translation: [], + rotation: [], + scale: [], + }, + userData: {}, + }; + } + + loadRawChannelData( + imageInfo: ImageInfo, + loadSpec: LoadSpec, + onUpdateVolumeMetadata: (imageInfo?: ImageInfo, loadSpec?: LoadSpec) => void, + onData: RawChannelDataCallback + ): Promise { + throw new Error("Method not implemented."); + } + + onChunkLoaded(id: ChunkId, chunk: Chunk) {} + + destroy() { + this.manager.unsubscribeFromSource(this, this.sourceId); + } +} From 7df929382f5bb71729acc01cfd41e113740f9864 Mon Sep 17 00:00:00 2001 From: Cameron Fraser Date: Thu, 24 Sep 2026 17:32:59 -0700 Subject: [PATCH 2/4] messy progress on `AdapterLoader.createImageInfo` --- src/data_manager/AdapterLoader.ts | 59 ++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/src/data_manager/AdapterLoader.ts b/src/data_manager/AdapterLoader.ts index 856070fa..c6878890 100644 --- a/src/data_manager/AdapterLoader.ts +++ b/src/data_manager/AdapterLoader.ts @@ -1,45 +1,70 @@ +import { Box3, Vector3 } from "three"; + import type { ImageInfo } from "../ImageInfo.js"; import type { LoadedVolumeInfo, LoadSpec, RawChannelDataCallback } from "../loaders/IVolumeLoader.js"; import { ThreadableVolumeLoader } from "../loaders/IVolumeLoader.js"; import { VolumeDims } from "../VolumeDims.js"; import DataManager, { IDataSubscriber } from "./DataManager.js"; +import { ExtVolumeDims, VolumeMetadata } from "./sources/ChunkSource.js"; import type { Chunk, ChunkId } from "./types.js"; +import { pickLevelToLoad } from "../loaders/VolumeLoaderUtils.js"; export default class AdapterLoader extends ThreadableVolumeLoader implements IDataSubscriber { + private dims: ExtVolumeDims[]; + private meta: VolumeMetadata; + constructor( private manager: DataManager, private sourceId: number ) { super(); + const dims = manager.getSourceDims(sourceId); + const meta = manager.getSourceMeta(sourceId); + + if (dims === undefined || meta === undefined) { + throw new Error(`AdapterLoader: source ${this.sourceId} does not exist`); + } + + this.dims = dims; + this.meta = meta; + manager.subscribeToSource(this, sourceId); } + private roundExtentToChunks(loadSpec: LoadSpec, level: number): LoadSpec { + const { shape, chunkShape } = this.dims[level]; + const [, , sz, sy, sx] = shape; + const [, , cz, cy, cx] = chunkShape; + const chunksPerDim = new Vector3(sx / cx, sy / cy, sz / cz); + + const min = loadSpec.subregion.min.clone().multiply(chunksPerDim).floor().divide(chunksPerDim); + const max = loadSpec.subregion.max.clone().multiply(chunksPerDim).ceil().divide(chunksPerDim).min(new Vector3(1)); + return { ...loadSpec, subregion: new Box3(min, max) }; + } + loadDims(loadSpec: LoadSpec): Promise { - const result = this.manager.getSourceDims(this.sourceId); - if (result === undefined) { - throw new Error("AdapterLoader: source does not exist"); - } - return Promise.resolve(result); + return Promise.resolve(this.dims); } createImageInfo(loadSpec: LoadSpec): Promise { + const shapesZYX = this.dims.map(({ shape }) => [shape[2], shape[3], shape[4]] as [number, number, number]); + // TODO this picks the optimal level to load assuming we will fetch exactly `LoadSpec.subregion`, but this loader + // extends `subregion` to the nearest chunk boundaries + const multiscaleLevel = pickLevelToLoad(loadSpec, shapesZYX); + const adjustedLoadSpec = this.roundExtentToChunks(loadSpec, multiscaleLevel); + const imageInfo: ImageInfo = { - name: undefined, + ...this.meta, + multiscaleLevelDims: this.dims, + multiscaleLevel, atlasTileDims: [], subregionSize: [], subregionOffset: [], - numChannelsPerSource: [], - channelNames: [], - channelColors: [], - multiscaleLevelDims: [], - multiscaleLevel: 0, - transform: { - translation: [], - rotation: [], - scale: [], - }, - userData: {}, + // no multi-source support, yet + numChannelsPerSource: [this.dims[multiscaleLevel].chunkShape[1]], }; + + return Promise.resolve({ imageInfo, loadSpec: adjustedLoadSpec }); } loadRawChannelData( From 7381df616d3bd002f5707cfe4816e0c7aa38048e Mon Sep 17 00:00:00 2001 From: Cameron Fraser Date: Tue, 6 Oct 2026 16:37:06 -0700 Subject: [PATCH 3/4] fix import and some types --- src/data_manager/AdapterLoader.ts | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/data_manager/AdapterLoader.ts b/src/data_manager/AdapterLoader.ts index c6878890..a193faed 100644 --- a/src/data_manager/AdapterLoader.ts +++ b/src/data_manager/AdapterLoader.ts @@ -1,20 +1,20 @@ -import { Box3, Vector3 } from "three"; - import type { ImageInfo } from "../ImageInfo.js"; import type { LoadedVolumeInfo, LoadSpec, RawChannelDataCallback } from "../loaders/IVolumeLoader.js"; import { ThreadableVolumeLoader } from "../loaders/IVolumeLoader.js"; import { VolumeDims } from "../VolumeDims.js"; -import DataManager, { IDataSubscriber } from "./DataManager.js"; -import { ExtVolumeDims, VolumeMetadata } from "./sources/ChunkSource.js"; +import DataManager, { IDataSubscriber } from "./data_manager.js"; +import { ExtVolumeDims, VolumeMetadata } from "./sources/chunk_source.js"; import type { Chunk, ChunkId } from "./types.js"; import { pickLevelToLoad } from "../loaders/VolumeLoaderUtils.js"; -export default class AdapterLoader extends ThreadableVolumeLoader implements IDataSubscriber { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export default class AdapterLoader extends ThreadableVolumeLoader implements IDataSubscriber { private dims: ExtVolumeDims[]; private meta: VolumeMetadata; constructor( - private manager: DataManager, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private manager: DataManager, private sourceId: number ) { super(); @@ -35,11 +35,20 @@ export default class AdapterLoader extends ThreadableVolumeLoader implements IDa const { shape, chunkShape } = this.dims[level]; const [, , sz, sy, sx] = shape; const [, , cz, cy, cx] = chunkShape; - const chunksPerDim = new Vector3(sx / cx, sy / cy, sz / cz); + const chunksX = sx / cx; + const chunksY = sy / cy; + const chunksZ = sz / cz; + + const { + min: [minX, minY, minZ], + max: [maxX, maxY, maxZ], + } = loadSpec.subregion; + const roundMin = (val: number, chunks: number): number => Math.floor(val * chunks) / chunks; + const roundMax = (val: number, chunks: number): number => Math.min(1, Math.ceil(val * chunks) / chunks); + const min: [number, number, number] = [roundMin(minX, chunksX), roundMin(minY, chunksY), roundMin(minZ, chunksZ)]; + const max: [number, number, number] = [roundMax(maxX, chunksX), roundMax(maxY, chunksY), roundMax(maxZ, chunksZ)]; - const min = loadSpec.subregion.min.clone().multiply(chunksPerDim).floor().divide(chunksPerDim); - const max = loadSpec.subregion.max.clone().multiply(chunksPerDim).ceil().divide(chunksPerDim).min(new Vector3(1)); - return { ...loadSpec, subregion: new Box3(min, max) }; + return { ...loadSpec, subregion: { min, max } }; } loadDims(loadSpec: LoadSpec): Promise { From 9bbde845c4dcae7f7a90e2d4108604c0a06c89ec Mon Sep 17 00:00:00 2001 From: Cameron Fraser Date: Tue, 6 Oct 2026 16:37:24 -0700 Subject: [PATCH 4/4] rename with underscores again --- src/data_manager/{AdapterLoader.ts => adapter_loader.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/data_manager/{AdapterLoader.ts => adapter_loader.ts} (100%) diff --git a/src/data_manager/AdapterLoader.ts b/src/data_manager/adapter_loader.ts similarity index 100% rename from src/data_manager/AdapterLoader.ts rename to src/data_manager/adapter_loader.ts