Viewing a volume as 2D pyramids

Hi all,

I’m working with a z-stack of large-scale images of consecutive, roughly-registered tissue sections.

What I am hoping for is the ability to view each z-slice in the stack as a zarr/dask pyramid, and move through z using a slider. Basically the pyramid pathology example, but with a volume of pyramids, though with no intention of ever rendering in 3D.

In the video example I am viewing an intermediate pyramid level from all the z-slices, from pyramids generated using:

# lazy_pyramid function is pulled from
# https://github.com/janelia-cosem/fst/blob/051e7e82ff35a5efc8372570eff9b81f24c7d5a8/fst/pyramid.py

fns = sorted(glob('HE_registered/*.tif'))
chunk = (1,6000,6000,3)

for fn in tqdm(fns):
    img = imread(fn).rechunk(chunk)
    pyramid = lazy_pyramid(array=img,
                           reduction=np.mean,
                           scale_factors=(1,2,2,1))
    
    for idx, level in enumerate(pyramid):
        pbar = ProgressBar()
        with pbar:
            zarr_fn = f'zarrs/{os.path.basename(fn).split(".")[0]}/{idx}'
            level.data.to_zarr(zarr_fn)

# For viewing all pyramid levels of a single z-slice
zdict = {}
for fn in glob('zarrs/*'):
    z = os.path.basename(fn).split('-')[-1] # Extract z index from fn
    pyramid = [da.from_zarr(f'{fn}/{idx}') for idx in range(len(glob(f'{fn}/*')))]
    zdict[z] = pyramid

# For viewing all z-slices of a single pyramid level
ldict = {i:[] for i in range(6)}  # 6 pyramid levels
for i in sorted(zdict.keys()):
    for j in range(6):
        ldict[j].append(zdict[i][j])

img = da.stack(ldict[3],axis=0)
napari.view_image(img, rgb=True, multiscale=False)

I see that there are some ongoing napari projects/milestones related to 3D rendering, but given that this use case only requires 2D rendering of the volume, I’m wondering if this functionality already exists. I’m imagining passing a list of pyramid lists, but it is unclear to me how to do this yet.

Thanks and all the best,

Erik

1 Like

I love the code example/ video. This should just work if all of your 2D data always has the same shape as you move through z for all the resolutions!!

I can look through your code tomorrow morning and make suggestions, but for now you might find some clues in this example https://github.com/napari/napari/blob/1b9a8fa16b155aabd425412fe55cbd2283377085/examples/nD_multiscale_image_non_uniform.py

You’ll basically just want to create a list of 3D arrays going from highest resolution to lowest resolution, where the first shape axis never changes and corresponds to the number of z-planes. Taking into consideration that your data is rgb, if you had 20 z slices of a 100_000 x 100_000 array with 2x downsamples you’d pass to napari a list of dask arrays that have the following shapes

[[20, 100_100, 100_000, 3], [20, 50_100, 50_000, 3], [20, 25_100, 25_000, 3] ....]

You’d then have a z-slider with 20 steps, and all the pyramid code should work as if you were just looking at one pyramid at a time, and the z-slider should work like you were just looking at one tile at a time.

If you do click the 3D rendering button we’ll just show the lowest resolution in 3D, so we shouldn’t crash, but it won’t be very helpful

Good luck and let me know how it goes, I can help more tomorrow if needed

3 Likes

That’s the ticket! Buttery smooth slider playback at each pyramid level. Since the shape doesn’t change through z, all I had to do was stack the level arrays, i.e. napari.view_image([da.stack(i) for i in ldict.values()],rgb=True,multiscale=True). I can hardly believe how easy you folks are making this. Thanks, @sofroniewn!

3 Likes

Love it! Keep the questions/ feature requests coming. We really do want to make this as easy as possible for everyone!!

1 Like