ome / napari-ome-zarr

A napari plugin for zarr backed OME-NGFF images
https://www.napari-hub.org/plugins/napari-ome-zarr
BSD 3-Clause "New" or "Revised" License
26 stars 21 forks source link

Napari colormaps within OME-Zarr Metadata #29

Open camFoltz opened 2 years ago

camFoltz commented 2 years ago

I am noticing that the OME-Zarr specifications have an option to have a "color" properties within the channel metadata. We use this property do define the colormap for when napari-ome-zarr opens up the dataset. However, it seems that doing this creates a ton of new colormaps within Napari with the title unnamed, and doesn't actually use a napari colormap.

Is there a way to define metadata such that we use napari's inherent colormaps? I see that the _reader.py looks for a metadata key called colormap which gets converted to a vispy colormap, but I am not entirely sure how to include this in the ome metadata within the zarr file or if this vispy colormap will correspond to one of napari's inherent maps and not create a new 'unnamed' one.

Let me know if I can clarify anything here. Thanks for the help

aeisenbarth commented 2 years ago

I have the same issue. We load many OME-Zarr files which specify a channel's color like cm = [[0, 0, 0], [1.0, 0.0, 0.0]]. For each of them, a new Vispy Colormap instance is created. Since they are unnamed, Napari treated all of them as separate, unique color maps.

A first, obvious fix was to give the colormap a name (name=str(cm)), but Vispy's colormaps have no name attribute.

After importing Napari's Colormap which has a name attribute, Napari matches all the Colormap instances by name and displays a single one in the user interface. Still, this is not the right approach: The UI only displays a single colormap because of key collision, but layers keep their separate Colormap instances (with same names).

We would need to lookup whether there already exists a suitable colormap instance (like from Napari's AVAILABLE_COLORMAPS) and re-use it or create a new one and register it.

will-moore commented 2 years ago

It could be possible to compare color values coming from OME-NGFF metadata with those in existing napari colormaps and if there's a match, then use the napari colormap name instead: https://napari.org/stable/howtos/layers/image.html#working-with-colormaps It might also be permissible to use the name of the colormap in the omero channel color string (as we do in OMERO itself for lookup tables).

aeisenbarth commented 2 years ago

When we first look up existing colormap instances, we can avoid creating new identical ones.

If the OME-Zarr colormap is a name (string), one can try to look it up from Napari colormaps (how to handle it if not found?). If it is instead a tuple of two RGB colors, create a new Napari colormap. Since Napari will register it, we can look it up next time.

from vispy.color import Colormap as VispyColormap
from napari.utils.colormaps.colormap import Colormap as NapariColormap
from napari.utils.colormaps import AVAILABLE_COLORMAPS

cms = metadata.get("colormap", [])
for idx, cm in enumerate(cms):
    if not isinstance(cm, (VispyColormap, NapariColormap)):
        if isinstance(cm, str):
            cms[idx] = AVAILABLE_COLORMAPS.get(cm)
            # TODO: How to handle this?  We have a colormap name not know in Napari,
            # are there cases when we can still create a color map?
        else:
            colormap_name = f"color {cm}"
            colormap = AVAILABLE_COLORMAPS.get(colormap_name)
            if colormap is None:
                colormap = NapariColormap(cm, name=colormap_name)
            cms[idx] = colormap
mezwick commented 3 days ago

Hi.

I've come across this issue myself and wasn't clear if there is a fix, or if this issue is due to me writing metadata incorrectly. Was hoping you could help.

I have written my high plex multichannel data to OME-ZARR format with ome-zarr-py package write_image function. I specify channel metadata under OMERO metadata, including each channel color to be "FFFFFF".

On loading to napari via napari-ome-zarr, channel names and render settings load correctly. But each channel spawns its own version of 'unnamed colormap [##]'

image

Looking for a way to have every channel just load with the napari 'gray' colormap?