Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load single slices at higher resolution #163

Merged
merged 23 commits into from
Nov 27, 2023
Merged

Load single slices at higher resolution #163

merged 23 commits into from
Nov 27, 2023

Conversation

frasercl
Copy link
Collaborator

@frasercl frasercl commented Nov 6, 2023

Resolves #157: If a 3d volume is loaded, the user switches to z-slice mode, and a higher resolution level is available to load, reload at that higher resolution.

This is accomplished using the loadDims method on IVolumeLoader (unused until now), which loads the dimensions of a volume without loading any data. I made the following changes to make loadDims a good solution to this issue:

  • loadDims returns an array of VolumeDims objects for each available scale level. I added the property canLoad: boolean to this type to indicate whether a level can be loaded given the specifications.
  • Because loadDims is async, updateRequiredData must now be async too. This is a bummer, so to compensate, I tried to ensure that loadDims rarely or never is async in practice.

I also got rid of the scene property of LoadSpec, given that the only loader that supports scenes right now (OmeZarrLoader) must be bound to a single scene on construction.

@frasercl frasercl requested a review from a team as a code owner November 6, 2023 21:32
@frasercl frasercl requested review from toloudis, meganrm, blairlyons, interim17, ShrimpCryptid and ascibisz and removed request for a team November 6, 2023 21:32
@frasercl frasercl changed the title Fix/load hires slice Load single slices at higher resolution Nov 6, 2023
src/Volume.ts Outdated Show resolved Hide resolved
// shape: [t, c, z, y, x]
shape: number[] = [0, 0, 0, 0, 0];
// spacing: [t, c, z, y, x]; generally expect 1 for non-spatial dimensions
spacing: number[] = [1, 1, 1, 1, 1];
spaceUnit = "μm";
timeUnit = "s";
canLoad = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest an explanatory comment here, something like:

// dims are small enough to fit within our preferred memory footprint (see `pickLevelToLoad`)

Copy link
Contributor

Choose a reason for hiding this comment

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

wondering if this class is even the right place to store this...

src/Volume.ts Outdated
const minScale = this.loadSpec.multiscaleLevel ?? 0;
// Loaders should cache loaded dimensions so that this call blocks no more than once per valid `LoadSpec`.
const dims = await this.loader?.loadDims(this.loadSpecRequired);
if (dims && currentScale > minScale && dims[currentScale - 1].canLoad) {
Copy link
Contributor

@toloudis toloudis Nov 7, 2023

Choose a reason for hiding this comment

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

I wonder if the canLoad could be a dynamic check here? I don't think it's too slow to call a function like pickLevelToLoad if it doesn't require network fetches but I forget how frequently this gets called.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I guess I felt like the decision of which level to load was the loader's responsibility, since pickLevelToLoad and the MAX_ATLAS_DIMENSION constant live in the same file as OMEZarrLoader. I kind of agree though that this ought to be treated more like a global limit. Do we make that official by moving those items into VolumeLoaderUtils and importing/calling pickLevelToLoad here? This is as opposed to calling it internally in loadDims, which is what is done now.

pickLevelToLoad itself doesn't require network fetches, it just takes dimensions and performs a bit of logic. Those dimensions, however, need to be fetched. This is the problem I referred to in the PR description and in the comment on line 251 above: loaders need to be smart enough to ensure that providing dimensions rarely requires a fetch in practice. The behavior in this PR is:

  • In OMEZarrLoader, all dims are fetched before loader construction and loadDims will never block
  • In TiffLoader, metadata is fetched on the first call to loadDims or loadVolumeData and cached (so if the volume already has data, loadVolumeData has been called and loadDims won't block in practice)
  • In JsonImageInfoLoader, metadata is fetched on the first call to loadDims or loadVolumeData per timestep and cached. In practice, if this function calls loadVolumeData after calling loadDims, the first loadDims call will have cached the metadata that the loadVolumeData would have required so checking dims first adds no additional work.

Moving the pickLevelToLoad call won't change these rules on when this function has to wait for the network.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yea I basically agree with everything you said here.

  • pickleveltoload can be a utils-like shared function, or even a IVolumeLoader method that returns 0 by default and only does something nontrivial in omezarrloader.
  • all dims could be fetched at or close to loader creation time. Then they will just be available and no call to loadDims would have to fetch anything (though they could if they wanted?)

Co-authored-by: toloudis <toloudis@users.noreply.github.com>
Comment on lines +113 to +119
private async getJsonImageInfo(time: number): Promise<JsonImageInfo> {
const cachedInfo = this.jsonInfo[time];
if (cachedInfo) {
return cachedInfo;
}

const response = await fetch(this.urls[time]);
Copy link
Contributor

Choose a reason for hiding this comment

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

I like this optimization!

Copy link
Contributor

@ShrimpCryptid ShrimpCryptid left a comment

Choose a reason for hiding this comment

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

Agree with Dan that I'm not totally sold on VolumeDims tracking whether it can be loaded? It seems like that should be tracked by a system external to it, but I'm fine to talk about tradeoffs too.

src/Volume.ts Outdated Show resolved Hide resolved
@ShrimpCryptid
Copy link
Contributor

I tried running this to test it, and I ran into an error?

Repro:

  1. Run the project (npm run start)
  2. Switch to OME Zarr (NucMorph)
  3. Switch to Z viewer
Uncaught Runtime Errors:
ERROR
chunk request cancelled
    at handleError (http://localhost:9020/volume-viewer-ui.bundle.js:25109:58)
    at http://localhost:9020/volume-viewer-ui.bundle.js:25132:7
ERROR
chunk request cancelled
    at handleError (http://localhost:9020/volume-viewer-ui.bundle.js:25109:58)
    at http://localhost:9020/volume-viewer-ui.bundle.js:25132:7
ERROR
chunk request cancelled
    at handleError (http://localhost:9020/volume-viewer-ui.bundle.js:25109:58)
    at http://localhost:9020/volume-viewer-ui.bundle.js:25132:7

It's possible this is related to the Vast outage today. Will check again later.

@ShrimpCryptid
Copy link
Contributor

ShrimpCryptid commented Nov 10, 2023

I tried running this to test it, and I ran into an error?

Repro:

  1. Run the project (npm run start)
  2. Switch to OME Zarr (NucMorph)
  3. Switch to Z viewer
Uncaught Runtime Errors:
ERROR
chunk request cancelled
    at handleError (http://localhost:9020/volume-viewer-ui.bundle.js:25109:58)
    at http://localhost:9020/volume-viewer-ui.bundle.js:25132:7
ERROR
chunk request cancelled
    at handleError (http://localhost:9020/volume-viewer-ui.bundle.js:25109:58)
    at http://localhost:9020/volume-viewer-ui.bundle.js:25132:7
ERROR
chunk request cancelled
    at handleError (http://localhost:9020/volume-viewer-ui.bundle.js:25109:58)
    at http://localhost:9020/volume-viewer-ui.bundle.js:25132:7

It's possible this is related to the Vast outage today. Will check again later.

Still getting this bug today.

This is all I'm getting from the console:

index.ts:872 currentVol with name AICS-12_881 is loaded
index.ts:872 currentVol with name P13-C4 is loaded
localhost/:1 Uncaught (in promise) chunk request cancelled
index.ts:872 currentVol with name P13-C4 is loaded

@toloudis
Copy link
Contributor

toloudis commented Nov 10, 2023

If I have any coding time today I'll try to take a look at this error message. This is an important change to get in soon.

@toloudis
Copy link
Contributor

toloudis commented Nov 13, 2023

The error here is indeed an uncaught promise exception that occurs when we cancel a request.
This pull request : gzuidhof/zarr.js#145 actually seems to fix the issue. (also see gzuidhof/zarr.js#140) The problem seems to be that our promises get stuck in the zarr.js plumbing because of their internal logic in between our proxy store's SmartStoreWrapper.getRaw and the resulting multiple calls to the true store's getItem.

You can see some hints of this by changing
return this.requestQueue.addRequest(key, () => this.getAndCacheItem(item, key, opts));
to

return this.requestQueue
        .addRequest(key, () => this.getAndCacheItem(item, key, opts))
        .catch((e) => {
          if (e !== CHUNK_REQUEST_CANCEL_REASON) {
            throw e; // this will be unhandled!!!
          }
          // if cancelled, we still need to return an arraybuffer of the expected size,
          // or else handle this one - returning this buffer still breaks things.
          return new ArrayBuffer(0);
        });

Another way of saying this is:
We call getRaw to fetch some data. The getRaw call is converted into many getItem calls by zarr.js's internals. We are overriding getItem in our SmartStoreWrapper. Each getItem becomes a cancellable request promise generated by our RequestQueue. These getItem requests are per-chunk, and are not propagated back out of the SmartStoreWrapper when getRaw finishes.

Co-authored-by: Peyton Lee <peyton.lee@alleninstitute.org>
@toloudis toloudis merged commit 2279053 into main Nov 27, 2023
3 checks passed
@toloudis toloudis deleted the fix/load-hires-slice branch November 27, 2023 19:00
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.

Reload slice if it will load at a higher resolution level
3 participants