Skip to content

Zarr layers - #532

Open
will-moore wants to merge 30 commits into
ome:masterfrom
will-moore:zarr_layers
Open

Zarr layers#532
will-moore wants to merge 30 commits into
ome:masterfrom
will-moore:zarr_layers

Conversation

@will-moore

@will-moore will-moore commented May 23, 2026

Copy link
Copy Markdown
Member

This PR loads OME-Zarr labels images as a layer in iviewer, rendering with ome-zarr.js. Functionality is based on pathviewer as shown in https://www.youtube.com/watch?v=E32MDqACoAw

NB: needs ome/omero-py#495 to get externalInfo on shapes.
Also needs ome/omero-web#682 (with ome/omero-py#489) for loading File Annotations (tables) on ROIs.

Set-up

webatlas example

We're using image, labels and anndata from https://cellatlas.io/studies/webatlas/dataset/161/vitessce

I downloaded the morphology.ome.tiff and segmentation.tiff used there, converted to ome.zarr (NGFF converter)
and uploaded to https://livingobjects.ebi.ac.uk/idr/webatlas/dataset_161/morphology.ome.zarr/

Import the image, using omero-cli-zarr...

$ omero zarr import https://livingobjects.ebi.ac.uk/idr/webatlas/dataset_161/morphology.ome.zarr/

Draw e.g. a Rectangle on the image and save. Use the Shape ID to set externalInfo on the Shape:

$ omero obj ext-info-set Rectangle:6891 3 com.glencoesoftware.ngff:multiscales https://livingobjects.ebi.ac.uk/idr/webatlas/dataset_161/morphology.ome.zarr/0/labels/segmentation/

Add an OMERO.table onto the ROI, downloading anndata to csv (script below)

$ python anndata_to_csv.py
anndata_to_csv.py
# https://anndata.readthedocs.io/en/latest/tutorials/notebooks/%7Bread%2Cwrite%7D_dispatched.html
import dask.array as da
import zarr
from anndata.io import read_elem
from anndata.experimental import read_dispatched

url = "https://cellgeni.cog.sanger.ac.uk/xenium/brain/healthy/xenium_1_3_0_brain-healthy-anndata.zarr/"

def read_dask(store):
    f = zarr.open(store, mode="r")

    def callback(func, elem_name: str, elem, iospec):
        if iospec.encoding_type in (
            "dataframe",
            "csr_matrix",
            "csc_matrix",
            "awkward-array",
        ):
            # Preventing recursing inside of these types
            return read_elem(elem)
        elif iospec.encoding_type == "array":
            return da.from_zarr(elem)
        else:
            return func(elem)

    return read_dispatched(f, callback=callback)

adata_dask = read_dask(url)
df = adata_dask.to_df()

# the 'graphclust' column contains a number (1 -15) which is the cluster number that the cell belongs to. This is a categorical variable.
# we want to convert this to a string "Cluster 1", "Cluster 2", etc.
adata_dask.obs["graphclust"] = adata_dask.obs["graphclust"].apply(lambda x: f"Cluster {x}")

# print number of rows and columns in the dataframes
print("Observations dataframe shape:", adata_dask.obs.shape)
print("Genes dataframe shape:", df.shape)

adata_dask.obs.to_csv("Observations3.csv", index=True)
df.to_csv("Genes3.csv", index=True)

Then, manually edit the csv files "Observations.csv" and "Genes.csv" to rename the _id column to object.
Then upload...

$ python csv_to_omero.py [ROI_ID] Genes.csv
$ python csv_to_omero.py [ROI_ID] Observations.csv
csv_to_omero.py
import argparse
import pandas
import omero2pandas
from omero.cli import cli_login

parser = argparse.ArgumentParser(description="Upload CSV to OMERO")
parser.add_argument("roi_id", type=int, help="ID of the ROI")
parser.add_argument("csv_file", type=str, help="Path to the CSV file")
args = parser.parse_args()
csv_file = args.csv_file

with cli_login() as cli:
    table_name = csv_file.split("/")[-1].split(".")[0]  # get the file name without extension to use as table name
    my_data = pandas.read_csv(csv_file)
    ann_id = omero2pandas.upload_table(my_data, table_name, parent_id=args.roi_id, parent_type="ROI", omero_connector=cli._client)

idr0079 example

$ omero zarr import https://livingobjects.ebi.ac.uk/idr/zarr/v0.4/idr0079A/idr0079_images.zarr/2/

# Draw & save a Rectange, then:
$ omero obj ext-info-set Rectangle:ID 3 com.glencoesoftware.ngff:multiscales https://livingobjects.ebi.ac.uk/idr/zarr/v0.4/idr0079A/idr0079_images.zarr/2/labels/0/

Download as CSV from https://idr.openmicroscopy.org/webclient/omero_table/41585282/
Rename Cell_ID column to object.

$ python csv_to_omero.py [ROI_ID] idr0079_table.csv

To test

  • In iviewer, click the "labels" tab - labels layer will load and all labels will be initially rendered the same color (cyan)
  • Possible to change the color (color-picker) or choose "Auto" and to change Opacity, and toggle layer visibility.
  • If OMERO.tables are linked to the ROI, you can choose to add a "data layer" based on the Table (chosen from the drop-down select widget)
  • Once the table layer has loaded, you can choose criteria based on numerical columns to filter the labels by - then choose "Apply" to filter.
Screenshot 2026-06-10 at 16 06 41

@will-moore

will-moore commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

Discussed alignment of this PR with standards that Pathviewer uses:

  • OMERO.tables use an object or Object (long) column to store the label values
  • Pathviewer expects all label images to have a single Channel
  • ExternalInfo is set on the Shape and OMERO.table is linked to the ROI
  • Supports multiple ROIs (zarr label images) for a single OMERO image
  • OMERO.tables are annotations on the corresponding ROI (not on the OMERO Image) which associates one or more OMERO.tables to a specific zarr label image
  • The zarr label image extent (x, y, width, height) is specified by the bounding box of the Mask Shape. May not cover the whole image and resolutions may differ etc)
  • OMERO.table may have columns 'bbox_min_x', 'bbox_min_y', 'bbox_max_x', 'bbox_max_y' to specify bounding box of the specific label. Allows you to pan the image to show a label, and also to query the OMERO.table for labels within certain viewport/region.
  • Histograms for particular table columns can be calculated when OMERO.tables are created, and stored in table metadata (not sure how?). These are used for the histogram shown when using "color by a parameter" - even when you may not load a complete column (e.g. if filtering by some value).

@will-moore
will-moore marked this pull request as ready for review July 2, 2026 21:32
@snoopycrimecop

Copy link
Copy Markdown
Member

Conflicting PR. Removed from build OMERO-plugins-push#1. See the console output for more details.
Possible conflicts:

--conflicts

@imagesc-bot

Copy link
Copy Markdown

This pull request has been mentioned on Image.sc Forum. There might be relevant details there:

https://forum.image.sc/t/omero-and-ome-zarr-support-overview-as-of-july-2026/121787/1

@snoopycrimecop

snoopycrimecop commented Jul 9, 2026

Copy link
Copy Markdown
Member

Conflicting PR. Removed from build OMERO-plugins-push#3. See the console output for more details.
Possible conflicts:

--conflicts Conflict resolved in build OMERO-plugins-push#9. See the console output for more details.

@snoopycrimecop

Copy link
Copy Markdown
Member

Conflicting PR. Removed from build OMERO-plugins-push#13. See the console output for more details.
Possible conflicts:

--conflicts

@snoopycrimecop

Copy link
Copy Markdown
Member

Conflicting PR. Removed from build OMERO-plugins-push#17. See the console output for more details.
Possible conflicts:

--conflicts

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