Skip to content

Construct Volume from loaders; load volume meta at loader creation - #499

Open
frasercl wants to merge 29 commits into
refactor/remove-threadable-loaderfrom
refactor/sync-meta-load
Open

frasercl wants to merge 29 commits into
refactor/remove-threadable-loaderfrom
refactor/sync-meta-load

Conversation

@frasercl

@frasercl frasercl commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Review time: medium-large (30-35min)

Problem

This PR takes another step towards making Volume the primary client-facing interface for initiating volume data loads, rather than VolumeLoader, by altering the Volume constructor to require a VolumeLoader.

To enable this, all VolumeLoader methods that previously initiated a load of metadata rather than actual data are now synchronous. This has the effect of moving metadata loading to loader creation. Most loaders therefore now follow the OmeZarrLoader pattern of having a private constructor and an async static method that "opens" the loader. Once a loader is open, it must have all the data it needs to construct metadata objects immediately.

Solution

The loader interface change described above was pretty trivial on most VolumeLoader implementers, but two required more intensive changes:

  • OMEZarrLoader is responsible for computing the extent of a data request and picking a scale level. The metadata that this PR requires it to load in advance is more complex, and the code that loaded the relevant metadata was relatively entangled with the code that converted it into the required types.
  • WorkerLoader is a handle to a VolumeLoader running on a WebWorker, so everything it does involves message passing and is therefore async. This PR enables WorkerLoader to hold onto metadata on the main thread, so it can return results from metadata methods synchronously.

@frasercl
frasercl added this pull request to stack #500 October 14, 2026 01:52
Comment thread public/index.ts Outdated
Comment on lines +1110 to +1116
const channels: number[] = [];
myState.channelGui.forEach(({ enabled }, idx) => {
if (enabled) {
channels.push(idx);
}
});
loader.loadVolumeData(volume, { ...volume.loadSpec, channels }, onChannelDataArrived);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ultimate aim of this stack of PRs is to make this code, which initiates data loading, a call on volume with a simpler signature, rather than a call to loader. This PR doesn't get all the way there and in fact makes this procedure temporarily a bit more complicated; the next PR in this stack is set to clean it up.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Crop preservation, volume-state initialization, and demo-loading regressions remain unresolved.

5 open findings
What changed in this PR

Moves metadata loading to loader creation, allowing Volume to construct synchronously from an initialized loader.

Changes:

  • Replaces asynchronous metadata methods with synchronous getters.
  • Transfers worker-loader metadata to the main thread.
  • Updates volume construction, demo usage, and tests.
File Description
src/​workers/​VolumeLoadWorker.ts Returns metadata when creating loaders.
src/​workers/​VolumeLoaderContext.ts Provides synchronous metadata access.
src/​workers/​types.ts Updates worker messages and metadata types.
src/​VolumeDrawable.ts Reads names through image info.
src/​Volume.ts Constructs from loaders and adds loader replacement.
src/​test/​zarr_utils.test.ts Updates scale-helper input.
src/​test/​volume.test.ts Adapts construction tests and mocks.
src/​test/​TripleSliceVolume.test.ts Uses loader-backed test volumes.
src/​loaders/​zarr_utils/​utils.ts Generalizes scale-helper parameter type.
src/​loaders/​VolumeLoader.ts Revises the metadata interface.
src/​loaders/​TiffLoader.ts Loads metadata during asynchronous creation.
src/​loaders/​RawArrayLoader.ts Makes metadata getters synchronous.
src/​loaders/​OpenCellLoader.ts Makes metadata getters synchronous.
src/​loaders/​OmeZarrLoader.ts Separates metadata extraction and conversion.
src/​loaders/​JsonImageInfoLoader.ts Preloads JSON metadata and exports converters.
src/​loaders/​index.ts Uses asynchronous loader factories.
src/​ImageInfo.ts Adds metadata accessors.
public/​types.ts Makes demo loaders optional.
public/​index.ts Adapts demo construction and loading.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/Volume.ts Outdated
Comment thread src/Volume.ts Outdated
Comment thread src/loaders/OmeZarrLoader.ts
Comment thread public/index.ts Outdated
Comment thread public/index.ts Outdated
frasercl and others added 3 commits October 14, 2026 16:38
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Metadata initialization, load-spec propagation, and shared translation mutation need correction before approval.

1 open finding
5 resolved since last review
Previously missed (2)

In code that hasn't changed since last review

Medium severity Pass loadSpec to volume construction

public/​index.ts:1108

The supplied loadSpec is no longer passed to volume construction. A non-default time or scale-level constraint is used by getDims above but discarded for the actual volume and data load, unlike the previous createVolume(loadSpec, ...) call. Pass it as the constructor's second argument.

Medium severity Prevent mutation of cached transform translation

src/​loaders/​JsonImageInfoLoader.ts:74

jsonToImageInfo now runs against WorkerLoader's cached main-thread metadata, but tr aliases json.transform.translation and is rescaled in place. Creating another volume from the same loader and time point therefore rescales the translation shared by an already-created volume. Previously, worker responses isolated the returned arrays. Copy the translation before rescaling so conversion neither changes cached metadata nor shares this array between volumes.

🧠 Review effort: Balanced

Comment thread src/Volume.ts Outdated
@frasercl
frasercl force-pushed the refactor/sync-meta-load branch from d81c5d0 to f723181 Compare October 15, 2026 18:25
@frasercl
frasercl marked this pull request as ready for review October 16, 2026 22:05
@frasercl
frasercl requested a review from a team as a code owner October 16, 2026 22:06
@ShrimpCryptid
ShrimpCryptid self-requested a review October 19, 2026 17:36
@@ -1,3 +1,3 @@
import { Box3, Vector2, Vector3 } from "three";

import { CImageInfo, type ImageInfo } from "../ImageInfo.js";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some unused imports here

Comment on lines +130 to +134
private constructor(
private urls: string[],
public jsonInfo: JsonImageInfo[],
private cache?: VolumeCache
) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First time seeing parameter properties, neat!

Comment on lines +97 to +107
function createTestLoader(): VolumeLoader {
return {
getDims: vi.fn(() => imageInfo.multiscaleLevelDims),
getImageInfo: vi.fn((loadSpec) => ({ imageInfo, loadSpec })),
loadVolumeData: vi.fn(),
setPrefetchPriority: vi.fn(),
syncMultichannelLoading: vi.fn(),
loadRawChannelData: vi.fn(),
updateFetchOptions: vi.fn(),
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this repeated in enough places to justify making a shared utility?

Comment thread public/index.ts
@@ -1146,12 +1102,19 @@ async function createLoader(data: TestDataSpec): Promise<VolumeLoader[]> {
}

async function loadVolume(name: string, loadSpec: LoadSpec, loader: VolumeLoader): Promise<void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this still need to be async?

Comment thread src/ImageInfo.ts
z: imageInfo.physicalPixelSize.z + imageInfo.spatialUnit,
};
metadata["Multiresolution levels"] = rawImageInfo.multiscaleLevelDims;
// TODO decide???? combined or not?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we ever decide on whatever this was about?

Comment thread src/loaders/TiffLoader.ts

constructor(url: string[]) {
private constructor(
private url: string[],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be stored as a remapped URL?

meta = { type: VolumeFileFormat.JSON, info: (loader as JsonImageInfoLoader).jsonInfo };
} else {
meta = {
type: undefined,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should fileType be stored here?

Comment on lines +423 to +424
// We ensured most info (dims, chunks, etc.) matched between sources earlier, so we can just use the first source.
// const source0 = meta.sources[0];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants