romeoromeo is a minimal R package that provides tools to read, validate, and write multiscale images and labels (regions, segmentation masks, etc.) stored as OME-Zarr files.
The package uses the Rarr package to manipulate images stored as Zarr datasets and OME-Zarr metadata while the ZarrArray package is used to lazily read larger-than-memory images.
romeo
realizes these Zarr-backed images (or labels) as objects of an
ome_zarr class where a number of methods are available to
manipulate these images as traditional arrays. These are, for example,
subsetting or slicing the images using the [ operator which
is applied to all levels of the multiscale OME-Zarr object (i.e. image
pyramids).
OME-Zarr is a cloud-friendly data format for storing large bioimaging datasets, such as microscopy images. It combines:
In essence, an OME-Zarr file is a collection of data arrays with XYZCT dimensions (X, Y, and Z for space, C for channels and T for time) representing an image pyramid, combined with metadata (which lives in the attributes property of Zarr arrays) that describes the properties of these arrays, such as scales, annotations and coordinate spaces (Figure 1).
There exists multiple OME-Zarr formats each having its own OME-NGFF specifications (Versions 0.4, 0.5, 0.6, etc.) and Zarr formats (Versions 2 or 3). Currently, romeo provides utilities for manipulating OME-Zarr datasets using NGFF versions 0.4 and 0.5, as well as limited support for the transitional version 0.5-dev-spatialdata. The current released version of the OME-Zarr specification is 0.5. See https://ngff.openmicroscopy.org/specifications for more information.
{
"multiscales": [
{
"version": "0.4",
"name": "example",
"axes": [
{"name": "t", "type": "time", "unit": "millisecond"},
{"name": "c", "type": "channel"},
{"name": "z", "type": "space", "unit": "micrometer"},
{"name": "y", "type": "space", "unit": "micrometer"},
{"name": "x", "type": "space", "unit": "micrometer"}
],
"datasets": [
{
"path": "0",
"coordinateTransformations": [{
// the voxel size for the first scale level (0.5 micrometer)
"type": "scale",
"scale": [1.0, 1.0, 0.5, 0.5, 0.5]
}]
},
{
"path": "1",
"coordinateTransformations": [{
// the voxel size for the second scale level (downscaled by a factor of 2 on x, y and z dimensions)
"type": "scale",
"scale": [1.0, 1.0, 1.0, 1.0, 1.0]
}]
},
{
"path": "2",
"coordinateTransformations": [{
// the voxel size for the third scale level (downscaled by a factor of 4 on x, y and z dimensions)
"type": "scale",
"scale": [1.0, 1.0, 2.0, 2.0, 2.0]
}]
}
],
"coordinateTransformations": [{
// the time unit (0.1 milliseconds), which is the same for each scale
"type": "scale",
"scale": [0.1, 1.0, 1.0, 1.0, 1.0]
}],
...
You can install the development version of romeo like so:
This is a basic example which shows you how to read an OME-Zarr
image. By default, data are read lazily using ZarrArray.
Here, the ome_read() function first validates that the
attributes of the OME-Zarr image comply with the relevant OME-NGFF
specifications (version 0.4 in this example). If valid, it proceeds to
read the data as a multi-scale ome_zarr object.
library(romeo)
library(utils)
omezarrzip <- system.file(
"extdata",
"test_ngff_image_v04.ome.zarr.zip",
package = "romeo"
)
td <- withr::local_tempfile(fileext = ".ome.zarr")
dir.create(td)
unzip(omezarrzip, exdir = td)
x <- ome_read(td)
plot(x, 1)We can extract each layer of the image pyramid using [[
method. Each layer of the image pyramid is a ZarrArray
object.
## [1] "ZarrMatrix" "ZarrArray" "DelayedMatrix"
## [4] "DelayedArray" "DelayedUnaryIsoOp" "DelayedUnaryOp"
## [7] "DelayedOp" "Array" "RectangularData"
Alternatively, the data can be read into memory as below using the
lazy argument:
## [1] "matrix" "array"
## [3] "structure" "matrix_OR_array_OR_table_OR_numeric"
## [5] "vector" "vector_OR_factor"
## [7] "vector_OR_Vector"
In this case, each layer of the image pyramid is a traditional array stored in memory.
Labels of an image (or image pyramid) are pixel-level annotations that are used to annotate regions within the image (pathology annotations, segmentation masks etc.) where each pixel stores an integer value corresponding of each label.
Labels of OME-Zarr images are often found nested within the zarr
group of the image, at the same level of the Zarr hierarchy as the
resolution levels for the original image. Each label has its own group
under the labels group.
omezarrzip <- system.file(
"extdata",
"test_ngff_image_v04.ome.zarr.zip",
package = "romeo"
)
td <- withr::local_tempfile(fileext = ".ome.zarr")
dir.create(td)
unzip(omezarrzip, exdir = td)
list.files(td)## [1] "labels" "s0" "s1" "s2" "s3" "s4"
## [1] "blobs"
Once located the path of the label pyramid within the OME-Zarr file,
we can use romeo again
to read these labels as ome_zarr objects.
For remote OME-Zarr files, you can an S3 URL to
ome_read() to read the data directly from the S3 bucket
without downloading it first:
For more advanced cases where authentication is required to access
the data, you must manually create an s3_client object
using the paws package and
pass it to ome_read().
Slicing (or subsetting) of images are performed using the
[ operator where indices correspond to each available XYZCT
dimensions.
## [1] "c" "y" "x"
## Only the first frame of the image stack is displayed.
## To display all frames use 'all = TRUE'.
romeo provides extensive utilities for writing OME-Zarr images compatible with multiple OME-NGFF specifications.
Here, ome_write() function accepts objects of
Image class (from EBImage
package) as input.
library(EBImage)
img_file <- system.file("extdata", "example_RGB.png", package = "romeo")
img <- readImage(img_file)
img## Image
## colorMode : Color
## storage.mode : double
## dim : 480 320 3
## frames.total : 3
## frames.render: 1
##
## imageData(object)[1:5,1:6,1]
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] 0.4196078 0.4196078 0.4235294 0.4235294 0.4235294 0.4235294
## [2,] 0.4196078 0.4196078 0.4235294 0.4235294 0.4235294 0.4235294
## [3,] 0.4196078 0.4196078 0.4235294 0.4235294 0.4235294 0.4235294
## [4,] 0.4196078 0.4196078 0.4235294 0.4235294 0.4235294 0.4235294
## [5,] 0.4235294 0.4235294 0.4274510 0.4274510 0.4274510 0.4274510
Currently, romeo
supports the two most recent OME-NGFF specs, Versions 0.4 and 0.5,
corresponding to Zarr formats v2 and v3, respectively. When writing the
pyramid, we define the version, e.g. 0.4,
and also the image axes, e.g. c("x", "y", "c"), whose order
and length should match the Image object.
ome_img <- ome_write(
img,
path = tempfile(fileext = ".ome.zarr"),
axes = c("x", "y", "c"),
version = "0.4",
storage_options = list(chunk_dim = c(64, 64, 1))
)
plot(ome_img, 1)## Only the first frame of the image stack is displayed.
## To display all frames use 'all = TRUE'.
Users can also define their own scaling factors to write image
pyramids. For a scalefactors vector with length three, the
resulting pyramid will contain four scales. Each scale factor in the
vector defines the scale factor of the layer relative to the previous
layer.
OME-Zarr label pyramids can be generated in the same way. We first create our own label data using EBImage.
# read the first frame of the Image object
nuc <- readImage(system.file("images", "nuclei.tif", package = "EBImage"))
nuc <- getFrames(nuc)[[1]]
# threshold using otsu's method
nuc_th <- nuc > otsu(nuc)
nuc_th## Image
## colorMode : Grayscale
## storage.mode : logical
## dim : 510 510
## frames.total : 1
## frames.render: 1
##
## imageData(object)[1:5,1:6]
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] FALSE FALSE FALSE FALSE FALSE FALSE
## [2,] FALSE FALSE FALSE FALSE FALSE FALSE
## [3,] FALSE FALSE FALSE FALSE FALSE FALSE
## [4,] FALSE FALSE FALSE FALSE FALSE FALSE
## [5,] FALSE FALSE FALSE FALSE FALSE FALSE
We can now write the label pyramid. The arguments are similar to
those used for writing image pyramids, but when
type = "label" is specified, OME-NGFF
label specifications is used to write the Zarr attributes.
ome_nuc_th <- ome_write(
nuc_th,
path = tempfile(fileext = ".ome.zarr"),
version = "0.4",
scalefactors = c(2, 2, 3),
storage_options = list(chunk_dim = c(64, 64)),
type = "label"
)
plot(ome_nuc_th, 3)If the path already includes an image pyramid, then we should define
a name (e.g. blobs) for the label pyramid associated with
the image.
td <- tempfile(fileext = ".ome.zarr")
ome_nuc <- ome_write(
nuc,
path = td,
version = "0.4",
storage_options = list(chunk_dim = c(64, 64))
)
ome_nuc_th <- ome_write(
nuc_th,
path = td,
version = "0.4",
scalefactors = c(2, 2, 3),
storage_options = list(chunk_dim = c(64, 64)),
type = "label",
label_name = "blobs"
)## An image pyramid was found at '/tmp/Rtmp8obNcY/fileee54dffd901.ome.zarr', writing labels to 'labels/blobs'
We can now visualize both the image and its corresponding labels side by side.
Additional metadata information about labels can also be provided
using the label_metadata argument, e.g. colors, properties
etc. See OME-NGFF
image-label specifications for more information.
ome_nuc_th <- ome_write(
nuc_th,
path = tempfile(fileext = ".ome.zarr"),
version = "0.4",
scalefactors = c(2, 2, 3),
storage_options = list(chunk_dim = c(64, 64)),
type = "label",
label_name = "blobs",
label_metadata = list(
colors = list(
list(`label-value` = 1, rgba = list(255, 255, 255, 255)),
list(`label-value` = 2, rgba = list(0, 255, 255, 128))
),
properties = list(
list(`label-value` = 1, class = "A"),
list(`label-value` = 2, class = "B")
)
)
)## R version 4.6.1 (2026-06-24)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 26.04 LTS
##
## Matrix products: default
## BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so; LAPACK version 3.12.0
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
## [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
##
## time zone: Etc/UTC
## tzcode source: system (glibc)
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] EBImage_4.55.1 romeo_0.99.1 BiocStyle_2.41.0
##
## loaded via a namespace (and not attached):
## [1] rappdirs_0.3.4 sass_0.4.10 generics_0.1.4
## [4] tiff_0.1-12 xml2_1.6.0 SparseArray_1.13.2
## [7] bitops_1.0-9 jpeg_0.1-11 lattice_0.22-9
## [10] jsonvalidate_1.5.0 paws.common_0.8.10 digest_0.6.39
## [13] magrittr_2.0.5 evaluate_1.0.5 grid_4.6.1
## [16] fftwtools_0.9-11 fastmap_1.2.0 Matrix_1.7-5
## [19] R.oo_1.27.1 jsonlite_2.0.0 R.utils_2.13.0
## [22] Rarr_2.1.20 BiocManager_1.30.27 httr2_1.2.3
## [25] jquerylib_0.1.4 abind_1.4-8 cli_3.6.6
## [28] rlang_1.2.0 crayon_1.5.3 XVector_0.53.0
## [31] R.methodsS3_1.8.2 ZarrArray_1.1.0 withr_3.0.3
## [34] DelayedArray_0.39.3 cachem_1.1.0 yaml_2.3.12
## [37] otel_0.2.0 S4Arrays_1.13.0 tools_4.6.1
## [40] locfit_1.5-9.12 grumpy_0.1.1.9000 BiocGenerics_0.59.8
## [43] curl_7.1.0 buildtools_1.0.0 R6_2.6.1
## [46] png_0.1-9 matrixStats_1.5.0 stats4_4.6.1
## [49] lifecycle_1.0.5 V8_8.2.0 S4Vectors_0.51.5
## [52] htmlwidgets_1.6.4 IRanges_2.47.2 bslib_0.11.0
## [55] glue_1.8.1 Rcpp_1.1.1-1.1 xfun_0.59
## [58] MatrixGenerics_1.25.0 sys_3.4.3 paws.storage_0.10.0
## [61] knitr_1.51 htmltools_0.5.9 rmarkdown_2.31
## [64] maketools_1.3.2 compiler_4.6.1 RCurl_1.98-1.19