ImageArray## Loading required package: EBImage
##
## Attaching package: 'ImageArray'
## The following objects are masked from 'package:EBImage':
##
## flip, flop, rotate
## Loading required package: dbplyr
## BioFormats library version 7.3.0
##
## Attaching package: 'shiny'
## The following object is masked from 'package:BiocStyle':
##
## markdown
The ImageArray package provides a unified, memory-efficient framework for working with both pyramidal and non-pyramidal images in R by leveraging the DelayedArray package. With ImageArray you can store large images on disk (as HDF5 or Zarr), treat them as array-like objects, perform delayed/lazy operations (e.g., rotate, flip, crop) across all pyramid levels, and seamlessly integrate with other R package and workflows that use large bioimages.
You can install ImageArray from Bioconductor with:
Image pyramids are multi-resolution representations that start with a full resolution image followed by a series of down-sampled levels (e.g., half resolutions). These are common in microscopy, digital pathology and large-scale imaging because:
In our context, an ImageArray object typically contain multiple levels (each a resolution level) internally, and most operations are applied lazily (or delayed) across all levels.
We primarily use images via the EBImage package.
# make random EBImage image
f <- system.file("images", "sample.png", package = "EBImage")
img <- readImage(f)Similar to its counterparts (see writeHDF5Array in
HDF5Array),
we use the writeImageArray function to write pyramid images
to preferred locations. Here, n.levels control the amount
of levels that the pyramid image should have.
# create image array
dir.create(td <- tempfile())
output_h5 <- file.path(td, "h5test")
imgarray <- writeImageArray(img,
format = "HDF5ImageArray",
output = output_h5,
n.levels = 2,
replace = TRUE)
imgarray## ImageArray Object (x,y)
## Scales (2): (768,512) (384,256)
Each level of a pyramid can be rasterized at any time, and thus plotted.
However, the true functionality of ImageArray is to pick pyramid levels in a memory-efficient manner. Packages, applications and workflows that use ImageArray can rasterize images by defining thresholds for pixel dimensions across all levels.
Here, by using the max.pixel.size, we request ImageArray
to return a pyramid level whose both width (X) and height
(Y) are lower than, for example, 400. This approach is
particularly useful when visualizing:
with minimal memory footprint.
## [1] 256 384
Operations such as rotate, flip, flop or negate will be conducted on all levels (without loading the image in the memory) which then can be rasterized and plotted.
## ImageArray Object (x,y)
## Scales (2): (512,768) (256,384)
You can even crop images, either by using crop or
[ methods.
## ImageArray Object (x,y)
## Scales (2): (201,101) (101,51)
## ImageArray Object (x,y)
## Scales (2): (201,101) (101,51)
ImageArray is also compatible with the magick package where we retain the dimensionality of the image similar to EBImage.
# make random EBImage image
f <- system.file("images", "sample.png", package = "EBImage")
img <- magick::image_read(f)
# create image array
dir.create(td <- tempfile())
output_h5 <- file.path(td, "h5test")
imgarray <- writeImageArray(img,
format = "HDF5ImageArray",
output = output_h5,
replace = TRUE)
imgarray## ImageArray Object (c,x,y)
## Scales (2): (3,768,512) (3,384,256)
You can create ImageArray objects from OME-TIFF files
(or any file compatible with Bio-formats, see RBioFormats)
with already defined layers.
ome.tiff.file <- system.file("extdata", "xy_12bit__plant.ome.tiff",
package = "ImageArray")
read.metadata(ome.tiff.file)## ImageMetadata list of length 2
##
## series res sizeX sizeY sizeC sizeZ sizeT total
## 1 1 512 512 1 1 1 1
## 1 2 256 256 1 1 1 1
##
## globalMetadata:List of 1058
## $ xy_12bit__plant.oir inTrigger triggerNo #5 : chr "0"
## $ xy_12bit__plant.oir inTrigger triggerNo #4 : chr "1"
## $ xy_12bit__plant.oir laser name #7 : chr "LD640"
## $ xy_12bit__plant.oir inTrigger triggerNo #3 : chr "0"
## $ xy_12bit__plant.oir laser name #6 : chr "LD594"
## [list output truncated]
# define ImageArray object
imgarray <- createImageArray(ome.tiff.file, series = 1, resolution = 1:2)
imgarray## ImageArray Object (x,y,c)
## Scales (2): (512,512,1) (256,256,1)
You can again use either max.pixel.size or
min.pixel.size to control the size of the image that loaded
into the memory.
## [1] 256 256
## [1] 512 512
## [1] 512 512
The delayed pyramid scheme introduced by ImageArray
objects can also be used to generate scalable plots and interactive
Shiny application for visualizing large images without loading them into
the memory.
For this example, we will use a large H&E image used by 10x Genomics, generated after a Xenium in Situ platform run (i.e. postXenium H&E).
image_file <- paste(
"https://cf.10xgenomics.com/samples/xenium/1.0.1",
"Xenium_FFPE_Human_Breast_Cancer_Rep1",
"Xenium_FFPE_Human_Breast_Cancer_Rep1_he_image.ome.tif",
sep = "/")
library(BiocFileCache)
bfc <- BiocFileCache()
image_file <- bfcrpath(bfc, image_file)## adding rname 'https://cf.10xgenomics.com/samples/xenium/1.0.1/Xenium_FFPE_Human_Breast_Cancer_Rep1/Xenium_FFPE_Human_Breast_Cancer_Rep1_he_image.ome.tif'
Let us create an ImageArray object first with the image
above. The OME-TIFF file includes multiple resolutions that we can ask
ImageArray object to include.
## ImageArray Object (x,y,c)
## Scales (6): (30786,24241,3) (15393,12120,3) ... (1924,1515,3)
## (962,757,3)
You can visualize subsets of large images really quickly using
crop and as.raster. Again, here we use
max.pixel.size to optimize the level parsed from the
pyramid; that is, as defined below, only resolutions whose width and
height are both under size 800 would be returned, whether it is the
entire image, or a subset.
# crop image
imgarray_sub <- crop(imgarray, ind = list(16000:19000, 7000:10000))
# convert to raster
img_raster <- as.raster(imgarray_sub, max.pixel.size = 800)
dim(img_raster)## [1] 751 751
# plot with ggplot
ggplot2::ggplot(data.frame(x = 0, y = 0),
ggplot2::aes_string("x", "y")) +
ggplot2::coord_fixed(expand = FALSE,
xlim = c(0, dim(img_raster)[2]),
ylim = c(0, dim(img_raster)[1])) +
ggplot2::annotation_raster(img_raster, 0, dim(img_raster)[2],
dim(img_raster)[1], 0, interpolate = FALSE)## Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
## ℹ Please use tidy evaluation idioms with `aes()`.
## ℹ See also `vignette("ggplot2-in-packages")` for more information.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
Now let us create a shiny application where we can interactively subset and visualize the OME-TIFF image quickly.
The Shiny application lets the user to define image subsets (or slices) before visualizing without loading large images in memory.
This is because as.raster determines the layer with the
pixel dimensions of he specified subset not exceeding the defined
threshold (max.pixel.size).
if (interactive()) {
# variables
dimimg <- dim(imgarray)
max.pixel.size <- 800
# Define UI
ui <- fluidPage(
sliderInput("x_slider", label = "X", min = 1,
max = dimimg[1], value = c(16000, 19000)),
sliderInput("y_slider", label = "Y", min = 1,
max = dimimg[2], value = c(7000, 10000)),
mainPanel(
plotOutput(outputId = "scatterPlot")
)
)
# Define server logic
server <- function(input, output) {
output$scatterPlot <- renderPlot({
# crop image
indx <- seq(input$x_slider[1], input$x_slider[2])
indy <- seq(input$y_slider[1], input$y_slider[2])
imgarray_sub <- crop(imgarray, ind = list(indx, indy))
# convert to raster
img_raster <- as.raster(imgarray_sub, max.pixel.size = max.pixel.size)
# plot with ggplot
imgggplot <- ggplot2::ggplot(data.frame(x = 0, y = 0),
ggplot2::aes_string("x", "y")) +
ggplot2::coord_fixed(expand = FALSE,
xlim = c(0, dim(img_raster)[2]),
ylim = c(0, dim(img_raster)[1])) +
ggplot2::annotation_raster(img_raster, 0, dim(img_raster)[2],
dim(img_raster)[1], 0, interpolate = FALSE)
imgggplot
})
}
# Run the app
shinyApp(ui = ui, server = server)
}## 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=en_US.UTF-8
## [9] LC_ADDRESS=en_US.UTF-8 LC_TELEPHONE=en_US.UTF-8
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=en_US.UTF-8
##
## time zone: Etc/UTC
## tzcode source: system (glibc)
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] shiny_1.14.0 ggplot2_4.0.3 RBioFormats_1.13.0
## [4] BiocFileCache_3.3.0 dbplyr_2.6.0 ImageArray_0.99.6
## [7] EBImage_4.55.1 BiocStyle_2.41.0
##
## loaded via a namespace (and not attached):
## [1] tidyselect_1.2.1 grumpy_0.1.1 dplyr_1.2.1
## [4] farver_2.1.2 blob_1.3.0 S7_0.2.2
## [7] filelock_1.0.3 R.utils_2.13.0 bitops_1.0-9
## [10] fastmap_1.2.0 RCurl_1.98-1.19 promises_1.5.0
## [13] digest_0.6.39 mime_0.13 lifecycle_1.0.5
## [16] paws.storage_0.10.0 RSQLite_3.53.3 magrittr_2.0.5
## [19] compiler_4.6.1 rlang_1.2.0 sass_0.4.10
## [22] tools_4.6.1 yaml_2.3.12 knitr_1.51
## [25] S4Arrays_1.13.0 htmlwidgets_1.6.4 bit_4.6.0
## [28] curl_7.1.0 DelayedArray_0.39.3 RColorBrewer_1.1-3
## [31] abind_1.4-8 HDF5Array_1.41.0 purrr_1.2.2
## [34] withr_3.0.3 BiocGenerics_0.59.8 sys_3.4.3
## [37] R.oo_1.27.1 grid_4.6.1 stats4_4.6.1
## [40] xtable_1.8-8 Rhdf5lib_2.1.0 scales_1.4.0
## [43] cli_3.6.6 rmarkdown_2.31 crayon_1.5.3
## [46] generics_0.1.4 otel_0.2.0 DBI_1.3.0
## [49] cachem_1.1.0 rhdf5_2.57.1 BiocManager_1.30.27
## [52] XVector_0.53.0 tiff_0.1-12 matrixStats_1.5.0
## [55] vctrs_0.7.3 Matrix_1.7-5 jsonlite_2.0.0
## [58] IRanges_2.47.2 fftwtools_0.9-11 S4Vectors_0.51.5
## [61] bit64_4.8.2 jpeg_0.1-11 maketools_1.3.2
## [64] magick_2.9.1 h5mread_1.5.0 locfit_1.5-9.12
## [67] jquerylib_0.1.4 glue_1.8.1 rJava_1.0-18
## [70] gtable_0.3.6 later_1.4.8 Rarr_2.1.20
## [73] tibble_3.3.1 pillar_1.11.1 rappdirs_0.3.4
## [76] htmltools_0.5.9 rhdf5filters_1.25.0 R6_2.6.1
## [79] httr2_1.2.3 evaluate_1.0.5 lattice_0.22-9
## [82] R.methodsS3_1.8.2 png_0.1-9 memoise_2.0.1
## [85] httpuv_1.6.17 paws.common_0.8.10 bslib_0.11.0
## [88] Rcpp_1.1.1-1.1 SparseArray_1.13.2 xfun_0.59
## [91] MatrixGenerics_1.25.0 buildtools_1.0.0 pkgconfig_2.0.3