hms-dbmi / viv

Library for multiscale visualization of high-resolution multiplexed bioimaging data on the web. Directly renders Zarr and OME-TIFF.
http://avivator.gehlenborglab.org
MIT License
279 stars 42 forks source link

Better default for `maxRequests` in TileLayer #396

Open manzt opened 3 years ago

manzt commented 3 years ago

For images with small tile sizes, there is a noticeable improvement in performance when using the TileLayer, but this isn't reflected as much for images with large tiles. Why? This isn't what we measured in our benchmark and it's worth exploring.

Currently we expose maxRequests as a prop to limit the number of concurrent requests from deck.gl. To my knowledge we just use the default of 10 in Avivator, Vitessce, and Vizarr regardless of the image tile size. It occurred to me this morning that 10 requests for 256x256 tiles != 10 requests for 1024x1024 tiles.

Deck.gl will only start cancelling requests if maxRequests is exceeded. One 1024x1024 tile takes is the same spatial region as 16 256x256 tiles. For one 1024x1024 region, deck.gl can choose to cancel requests when tile sizes are 256x256. In contrast, it requires at least concurrent 10 requests for 1024x1024 regions (or the equivalent of 160 256x256 tiles) before deck.gl can cancel requests when the tilesize is 1024x1024.

Image pyramids are often less than 10 levels, and if you have a small screen, you could end up requesting 1 tile per level when zooming and never have a request cancelled.

Preferred solution

Set maxRequests to be much smaller by default. Open Viv and try setting it to 2, for example. You'll be blown away with how much better some of the examples are in Avivator. This is very simple and mimics the current API. My preference is to have a smarter default that works well in most cases.

I think the real question is what user are we optimizing for by default. If it's loading a fix viewport the fastest, having a large maxRequests makes sense. But for exploratory navigation, we want to be able to cancel as many requests as possible when a user is panning and zooming so that when they reach a region it loads quickly.

Possible alternatives

Come up with some heuristic for maxRequests that varies with tiles size. I am hesitant to suggest this alternative because it's complicated and it isn't clear to me what a new API would look like. Even maxRequests is somewhat confusing because loaderSelection influences how many requests are actually made per "request". If we go this route, I would suggest not exposing an API.

ilan-gold commented 3 years ago

I think this seems very reasonable, thanks for bringing it up.

Just spitballing/trying to contribute, but it seems that we should probably do something like powers-of-2 maybe? Like 16 <-> 256, 8 <-> 512, 4 <-> 1024 (or one power lower). This seems reasonable (or we could go one power lower) since the worst case for 1024 on an average computer screen (~1000 x 2000), for example, is that you need to load 9 tiles to fill the viewport so roughly half of that seems reasonable. We could also calculate based on the viewport size, although this can change if someone resizes the window.

ilan-gold commented 3 years ago

In fact, related to what you mention about equivalent numbers of tiles, it might even make sense to do powers of 4

ilan-gold commented 3 years ago

I guess screens are rectangular so not exactly powers of 4, but something larger than 2 is necessary for what I'm saying.

manzt commented 3 years ago

My vote is to experiment with some power-of-two tile sizes and see if anything generalizes. It's hard-coded at 10 for now for all tile sizes, so anything makes that lower for larger tiles is better than what we have currently. For that reason, for now I think a heuristic based only on tile size makes sense. We can experiment with more sophisticated things (e.g. number of selections per request, data type, current viewport) in the future.

We can keep maxRequests with no default. If provided, it overrides everything, otherwise we do something coarse. It's a complex relationship between how interaction influences number of requests and when deck.gl will decided to cancel requests.

For instance, if you've stopped zooming, you basically want maxRequests===Infinity because you want deck.gl to fill the viewport as fast as possible. But when zooming, you want to cancel as many requests from the zoom level you're not on as possible. We have to use maxRequests to optimize for the latter, but it feels like something deck.gl might be able to handle as well.