diff --git a/NAMESPACE b/NAMESPACE index b8e181f..fda2907 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,6 +4,7 @@ S3method(as.raster,ImageArray) export(BFArray) export(BFPath) export(ImageArray) +export(OZPath) export(createImageArray) export(getImageInfo) export(writeImageArray) @@ -48,14 +49,20 @@ importFrom(EBImage, rotate ) importFrom(HDF5Array,writeHDF5Array) -importFrom(Rarr,write_zarr_group) +importFrom(Rarr, + write_zarr_group, + zarr_overview +) importFrom(S4Arrays,as.array.Array) importFrom(S4Vectors, SimpleList, coolcat, new2 ) -importFrom(ZarrArray,writeZarrArray) +importFrom(ZarrArray, + ZarrArray, + writeZarrArray +) importFrom(grDevices, as.raster, rgb diff --git a/R/AllClasses.R b/R/AllClasses.R index 4ad05c2..f06ab7c 100644 --- a/R/AllClasses.R +++ b/R/AllClasses.R @@ -6,18 +6,41 @@ NULL # magick classes setOldClass("magick-image") setOldClass("bitmap") -setClassUnion(c("magick_class"), - c("magick-image", "bitmap")) # array and matrix classes -setClassUnion("matrix_array_Array", +setClassUnion("matrix_array_Array", c("matrix", "array", "Array")) +#' @title ImageList class +#' +#' @description +#' A \code{SimpleList} holding the levels of an \code{ImageArray} object. +#' +#' @keywords internal +#' @noRd .ImageList <- setClass( Class="ImageList", contains="SimpleList", prototype=prototype(elementType="matrix_array_Array")) +#' @title ImageArray class +#' +#' @description +#' An S4 container for a multi-resolution (pyramidal) image, holding the +#' pyramid levels together with their axis names and scales. Objects are +#' created with \code{\link{ImageArray}}. +#' +#' @slot levels an \code{ImageList} of pyramid levels, ordered from the +#' highest to the lowest resolution +#' @slot axes a character vector of axis names, a subset of +#' \code{c("c", "y", "x", "z", "t")}, of the same length as the number of +#' dimensions of every level +#' @slot scales a list of named numeric vectors, one per level, where names +#' are a subset of \code{axes} and values are the scales of these axes. See +#' \url{https://ngff.openmicroscopy.org/} for more information. +#' +#' @keywords internal +#' @noRd .ImageArray <- setClass( Class = "ImageArray", slots = c( @@ -27,15 +50,45 @@ setClassUnion("matrix_array_Array", ) ) +#' @title BFPath class +#' +#' @description +#' A pointer to a single series and resolutions of an image file read with +#' \code{RBioFormats}, e.g. an OME-TIFF or a QPTIFF. Objects are created with +#' \code{\link{BFPath}}. +#' +#' @slot filepath the path to the image read by \code{RBioFormats} +#' @slot series the series ID of the pyramidal image, a single integer +#' typically starting from 1 +#' @slot resolution the resolution IDs of the pyramidal image, integers +#' typically starting from 1 +#' +#' @keywords internal +#' @noRd .BFPath <- setClass( Class = "BFPath", slots = c( - filepath = "character", + filepath = "character", series = "numeric", resolution = "numeric" ) ) +#' @title BFArraySeed class +#' +#' @description +#' The \code{DelayedArray} seed backing a \code{BFArray}. It extends +#' \code{BFPath}, hence it also carries the \code{filepath}, \code{series} and +#' \code{resolution} slots pointing to the image on disk. +#' +#' @slot axes a character vector of upper case axis names of the selected +#' series and resolution. +#' @slot dim the dimension lengths of the selected series and resolution, +#' matching \code{axes} +#' @slot type the storage type reported to \code{DelayedArray}. +#' +#' @keywords internal +#' @noRd .BFArraySeed <- setClass( "BFArraySeed", contains = c("Array", "BFPath"), @@ -46,8 +99,39 @@ setClassUnion("matrix_array_Array", ) ) +#' @title BFArray class +#' +#' A \code{DelayedArray} giving lazy access to a single series and resolution +#' of an image read with \code{RBioFormats}. Pixels are only read from disk +#' when the array is realized. Objects are created with \code{\link{BFArray}}. +#' +#' @slot seed a \code{BFArraySeed} object +#' +#' @keywords internal +#' @noRd .BFArray <- setClass( Class = "BFArray", contains = c("DelayedArray"), slots = c(seed = "BFArraySeed") -) \ No newline at end of file +) + +#' @title OZPath class +#' +#' @description +#' A pointer to the resolutions of an OME-Zarr store. Objects are created with +#' \code{\link{OZPath}}. +#' +#' @slot filepath the path to the OME-Zarr store +#' @slot resolution the names of the resolution directories of the pyramidal +#' image, e.g. "s0". Subdirectories of the store that are not zarr arrays, +#' such as the OME-NGFF "labels" group, are never resolutions +#' +#' @keywords internal +#' @noRd +.OZPath <- setClass( + Class = "OZPath", + slots = c( + filepath = "character", + resolution = "character" + ) +) diff --git a/R/ImageArray.R b/R/ImageArray.R index 2d65a6f..89197be 100644 --- a/R/ImageArray.R +++ b/R/ImageArray.R @@ -203,17 +203,145 @@ setMethod("scales", "ImageArray", function(object) object@scales) # Create/Write #### #### +#' ImageArray +#' +#' creates an object of ImageArray class +#' +#' @param image a path to a file, a single array or a list of arrays +#' containing the pixel intensities of an image. +#' @eval paste0("@param axes a character vector of axes names for images. +#' Should be a subset of ", deparse(.AXES)) +#' @param n.levels the number of levels of the pyramidal image, +#' typical an integer starting from 1. Will be ignored if \code{scales} is +#' provided +#' @param max.pixel.threshold the maximum width +#' and height pixel dimension that the lowest level of the image pyramid +#' should have, thus the image will be downscaled two folds until both width +#' and height is below the threshold. Default is 700 pixels. +#' Will be ignored if \code{n.levels} is provided +#' @param axes a character vector of axis names, a subset of +#' \code{c("c", "y", "x", "z", "t")}, of the same length as the number of +#' dimensions of every level +#' @param scales a list of named numeric vectors where names are a +#' subset of \code{axes} and values are associated with scales +#' of these axes. See \url{https://ngff.openmicroscopy.org/} for more +#' information. When provided, \code{axes}, \code{n.levels} and +#' \code{max.pixel.threshold} will be overridden. +#' @param engine the package to use for each image layer: either +#' \code{EBImage} or \code{magick-image} +#' @param series the series IDs of the pyramidal image, +#' typical an integer starting from 1. +#' @param resolution the resolution IDs of the pyramidal image, +#' typical an integer starting from 1. +#' @param verbose verbose +#' +#' @name ImageArray +#' @rdname ImageArray +#' +#' @aliases +#' createImageArray +#' createImageArray,ImageArray-method +#' +#' @importFrom methods new +#' @importFrom DelayedArray DelayedArray +#' +#' @export +#' @return An ImageArray object +#' +#' @examples +#' # get image +#' library(EBImage) +#' img.file <- system.file("images", "sample.png", package="EBImage") +#' +#' # create ImageArray +#' imgarray <- ImageArray(img.file, n.levels = 3) +#' imgarray_raster <- as.raster(imgarray, max.pixel.size = 300) +#' plot(imgarray_raster) +ImageArray <- function( + image, + axes = NULL, + n.levels = NULL, + max.pixel.threshold = 700, + scales = NULL, + engine = "EBImage", + series = NULL, + resolution = NULL, + verbose = FALSE +) { + + # overwrite axes if scales are given + if(!is.null(scales)) + axes <- .get_axes_from_scales(scales) + + # create ImageArray from file path + if (inherits(image, "character")) { + if (grepl(".ome.tiff$|.ome.tif$|.qptiff$|.qptif$", image)) { + image <- BFPath(image, series, resolution) + } else if (grepl(".ome.zarr", image)) { + image <- OZPath(image, resolution) + } else { + image <- read_image(image, engine = engine) + } + # read arrays as magick or EBImage + } else if(is.array(image)){ + axes <- .check_axes(image, axes = axes, engine = engine) + image <- read_image(image, axes = axes, engine = engine) + } + + # read image list + image <- createImageList( + image, + axes = axes, + scales = scales, + n.levels = n.levels, + max.pixel.threshold = max.pixel.threshold, + verbose = verbose + ) + + # save levels as SimpleList + image$levels <- S4Vectors:::new_SimpleList_from_list("ImageList", + image$levels) + + # get scales from list if not provided + if(is.null(scales)) + scales <- .get_scales_from_levels(image$levels, image$axes) + + # create class + S4Vectors::new2( + "ImageArray", + levels = image$levels, + axes = image$axes, + scales = scales + ) +} + +#' @describeIn ImageArray deprecated function +#' @export +createImageArray <- function( + image, + n.levels = NULL, + series = NULL, + resolution = NULL, + max.pixel.threshold = max.pixel.threshold, + engine = "EBImage", + verbose = FALSE +) { + warning("'createImageArray' function is deprecated. ", + "Please use 'ImageArray' instead!") + ImageArray(image, + n.levels = NULL, + max.pixel.threshold = max.pixel.threshold, + engine = "EBImage", + series = NULL, + resolution = NULL, + verbose = FALSE) +} + #' createListFromBFPath #' #' creates an object of BFArray class #' -#' @param image a BFPath object -#' @param series the number of series if the image supposed to be -#' pyramidal, or the the series IDs of the pyramidal image, -#' typical an integer starting from 1 -#' @param resolution the resolution IDs of the pyramidal -#' image, typical an integer starting from 1 -#' @param verbose verbose +#' @inheritParams ImageArray #' #' @noRd createListFromBFPath <- function( @@ -234,19 +362,41 @@ createListFromBFPath <- function( axes = tolower(axes(image_list[[1]])))) } +#' createListFromOZPath +#' +#' creates an object from OZPath object +#' +#' @inheritParams ImageArray +#' +#' @importFrom ZarrArray ZarrArray +#' +#' @noRd +createListFromOZPath <- function( + image, + axes = NULL, + scales = NULL, + n.levels = NULL, + max.pixel.threshold, + verbose = FALSE +) { + # make list + image_list <- lapply(resolution(image), function(res) { + ZarrArray::ZarrArray(file.path(path(image), res)) + }) + + # axes + axes <- .check_axes(image_list[[1]], axes = axes) + + # return + return(list(levels = image_list, + axes = axes)) +} + #' createListFromMagick #' #' creates an object of ImageArray class from magick image #' -#' @param image the image -#' @param n.levels the number of levels of the pyramidal image, -#' typical an integer starting from 1 -#' @param max.pixel.threshold the maximum width -#' and height pixel dimension that the lowest level of the image pyramid -#' should have, thus the image will be downscaled two folds until both width -#' and height is below the threshold. Default is 700 pixels. -#' If \code{n.levels} is provided, this parameter will be ignored. -#' @param verbose verbose +#' @inheritParams ImageArray #' #' @importFrom magick image_read #' @importFrom magick image_info @@ -320,15 +470,7 @@ createListFromMagick <- function( #' #' creates an object of ImageArray class from magick image #' -#' @param image the image -#' @param n.levels the number of levels of the pyramidal image, -#' typical an integer starting from 1 -#' @param max.pixel.threshold the maximum width -#' and height pixel dimension that the lowest level of the image pyramid -#' should have, thus the image will be downscaled two folds until both width -#' and height is below the threshold. Default is 700 pixels. -#' If \code{n.levels} is provided, this parameter will be ignored. -#' @param verbose verbose +#' @inheritParams ImageArray #' #' @importFrom EBImage readImage #' @importFrom EBImage resize @@ -382,6 +524,13 @@ createListFromEBImage <- function( return(list(levels = image_list, axes = axes)) } +#' createListFromList +#' +#' creates an object of ImageArray class from a list of arrays +#' +#' @inheritParams ImageArray +#' +#' @noRd createListFromList <- function(image, axes = NULL, scales = NULL, @@ -412,7 +561,10 @@ createListFromList <- function(image, } #' @noRd -setMethod("createImageList", "magick_class", createListFromMagick) +setMethod("createImageList", "bitmap", createListFromMagick) + +#' @noRd +setMethod("createImageList", "magick-image", createListFromMagick) #' @noRd setMethod("createImageList", "Image", createListFromEBImage) @@ -421,137 +573,10 @@ setMethod("createImageList", "Image", createListFromEBImage) setMethod("createImageList", "BFPath", createListFromBFPath) #' @noRd -setMethod("createImageList", "list", createListFromList) +setMethod("createImageList", "OZPath", createListFromOZPath) -#' ImageArray -#' -#' creates an object of ImageArray class -#' -#' @param image a path to a file, a single array or a list of arrays -#' containing the pixel intensities of an image. -#' @eval paste0("@param axes a character vector of axes names for images. -#' Should be a subset of ", deparse(.AXES)) -#' @param n.levels the number of levels of the pyramidal image, -#' typical an integer starting from 1. Will be ignored if \code{scales} is -#' provided -#' @param max.pixel.threshold the maximum width -#' and height pixel dimension that the lowest level of the image pyramid -#' should have, thus the image will be downscaled two folds until both width -#' and height is below the threshold. Default is 700 pixels. -#' Will be ignored if \code{n.levels} is provided -#' @param scales a list of named numeric vectors where names are a -#' subset of \code{axes} and values are associated with scales -#' of these axes. See \url{https://ngff.openmicroscopy.org/} for more -#' information. When provided, \code{axes}, \code{n.levels} and -#' \code{max.pixel.threshold} will be overridden. -#' @param engine the package to use for each image layer: either -#' \code{EBImage} or \code{magick-image} -#' @param series the series IDs of the pyramidal image, -#' typical an integer starting from 1. -#' @param resolution the resolution IDs of the pyramidal image, -#' typical an integer starting from 1. -#' @param verbose verbose -#' -#' @name ImageArray -#' @rdname ImageArray -#' -#' @aliases -#' createImageArray -#' createImageArray,ImageArray-method -#' -#' @importFrom methods new -#' @importFrom DelayedArray DelayedArray -#' -#' @export -#' @return An ImageArray object -#' -#' @examples -#' # get image -#' library(EBImage) -#' img.file <- system.file("images", "sample.png", package="EBImage") -#' -#' # create ImageArray -#' imgarray <- ImageArray(img.file, n.levels = 3) -#' imgarray_raster <- as.raster(imgarray, max.pixel.size = 300) -#' plot(imgarray_raster) -ImageArray <- function( - image, - axes = NULL, - n.levels = NULL, - max.pixel.threshold = 700, - scales = NULL, - engine = "EBImage", - series = NULL, - resolution = NULL, - verbose = FALSE -) { - - # overwrite axes if scales are given - if(!is.null(scales)) - axes <- .get_axes_from_scales(scales) - - # create ImageArray from file path - if (inherits(image, "character")) { - if (grepl(".ome.tiff$|.ome.tif$|.qptiff$|.qptif$", image)) { - image <- BFPath(image, series, resolution) - } else { - image <- read_image(image, engine = engine) - } - - # read arrays as magick or EBImage - } else if(is.array(image)){ - axes <- .check_axes(image, axes = axes, engine = engine) - image <- read_image(image, axes = axes, engine = engine) - } - - # read image list - image <- createImageList( - image, - axes = axes, - scales = scales, - n.levels = n.levels, - max.pixel.threshold = max.pixel.threshold, - verbose = verbose - ) - - # construct ImageArray object - image$levels <- S4Vectors:::new_SimpleList_from_list("ImageList", - image$levels) - - # get scales from list if not provided - if(is.null(scales)) - scales <- .get_scales_from_levels(image$levels, image$axes) - - # create class - S4Vectors::new2( - "ImageArray", - levels = image$levels, - axes = image$axes, - scales = scales - ) -} - -#' @describeIn ImageArray deprecated function -#' @export -createImageArray <- function( - image, - n.levels = NULL, - series = NULL, - resolution = NULL, - max.pixel.threshold = max.pixel.threshold, - engine = "EBImage", - verbose = FALSE -) { - warning("'createImageArray' function is deprecated. ", - "Please use 'ImageArray' instead!") - ImageArray(image, - n.levels = NULL, - max.pixel.threshold = max.pixel.threshold, - engine = "EBImage", - series = NULL, - resolution = NULL, - verbose = FALSE) -} +#' @noRd +setMethod("createImageList", "list", createListFromList) #' writeImageArray #' diff --git a/R/omezarr.R b/R/omezarr.R new file mode 100644 index 0000000..bd9a96a --- /dev/null +++ b/R/omezarr.R @@ -0,0 +1,132 @@ +### - - - - - - - - - - - - - - - - - - +### OZPath +### + +#' the shape of a zarr array +#' +#' Returns the shape of the zarr array at \code{path}, or NULL if \code{path} +#' is not a zarr array, e.g. it is a zarr group or a plain directory. +#' +#' @param path the path to a candidate zarr array +#' +#' @importFrom Rarr zarr_overview +#' @noRd +.zarr_array_dim <- function(path) { + + # no zarr metadata file at all, so not an array + zarr_meta <- c(".zarray", "zarr.json") + if (!any(file.exists(file.path(path, zarr_meta)))) + return(NULL) + + # zarr_overview() errors on groups, both the v2 .zgroup and the + # v3 zarr.json with a "group" node_type + meta <- tryCatch( + Rarr::zarr_overview(path, as_data_frame = TRUE), + error = function(e) NULL + ) + if (is.null(meta) || nrow(meta) != 1L) + return(NULL) + + as.integer(meta$dim[[1]]) +} + +#' OZPath constructor method +#' +#' A function for creating objects of OZPath class +#' +#' @param filepath the path to the OME-Zarr store +#' @param resolution the resolution IDs of the pyramidal image, either the +#' names of the resolution directories, e.g. "s0", or integers starting from 1 +#' indexing into them. Subdirectories of the store that are not zarr arrays, +#' such as the OME-NGFF "labels" group, are never resolutions. +#' +#' @name OZPath +#' @rdname OZPath +#' +#' @aliases +#' resolution +#' resolution,OZPath-method +#' path,OZPath-method +#' +#' @importFrom S4Vectors new2 +#' +#' @export +#' @return An OZPath object +#' +#' @examples +#' # get image +#' library(utils) +#' omezarrzip <- system.file("extdata", +#' "test_ngff_image_v04.ome.zarr.zip", +#' package = "ImageArray") +#' dir.create(omezarr <- tempfile()) +#' unzip(omezarrzip, exdir = omezarr) +#' oz <- OZPath(omezarr, resolution = 2) +#' resolution(oz) +#' path(oz) +OZPath <- function(filepath, resolution = NULL) { + + # check if zarr exists + zarr_meta <- c(".zgroup", "zarr.json") + if(!any(file.exists(file.path(filepath, zarr_meta)))) + stop(filepath, " is not a zarr store!") + + # keep only the subdirectories that are zarr arrays, dropping zarr groups + # such as the OME-NGFF "labels" group + candidates <- list.dirs(filepath, recursive = FALSE, full.names = FALSE) + dims <- lapply(file.path(filepath, candidates), .zarr_array_dim) + is_array <- !vapply(dims, is.null, logical(1)) + res_list <- candidates[is_array] + dims <- dims[is_array] + if(!length(res_list)) + stop("no zarr arrays found in ", filepath) + + # get/check resolutions + if (is.null(resolution)) { + index <- seq_along(res_list) + } else if (is.character(resolution)) { + if(!all(resolution %in% res_list)) stop("resolutions not found") + index <- match(resolution, res_list) + } else { + if(!all(resolution %in% seq_along(res_list))) stop("resolutions not found") + index <- as.integer(resolution) + } + resolution <- res_list[index] + + # all resolutions of a pyramid should have the same number of dimensions, + # their shapes of course differ across the pyramid + ndim <- lengths(dims[index]) + if(length(unique(ndim)) > 1) + stop("resolutions have differing dimensionality: ", + paste0(resolution, " (", ndim, "D)", collapse = ", ")) + + S4Vectors::new2( + "OZPath", + filepath = filepath, + resolution = resolution + ) +} + +### - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +### resolution() and path() getters +### + +#' @describeIn OZPath resolution metadata of OZPath object +#' @exportMethod resolution +setMethod("resolution", "OZPath", function(x) x@resolution) + +#' @describeIn OZPath path method for OZPath object +#' @exportMethod path +setMethod("path", "OZPath", function(object, ...) object@filepath) + +#' @importFrom S4Vectors coolcat +#' @noRd +setMethod( + f = "show", + signature = c("OZPath"), + definition = function(object) { + cat(class(x = object), "Object", "\n") + cat("Path:", path(object), "\n") + cat("Resolutions:", paste(resolution(object), collapse = ","), "\n") + } +) \ No newline at end of file diff --git a/inst/extdata/bird.png b/inst/extdata/bird.png deleted file mode 100644 index 8b044fd..0000000 Binary files a/inst/extdata/bird.png and /dev/null differ diff --git a/inst/extdata/sample.png b/inst/extdata/sample.png deleted file mode 100644 index dacd7e5..0000000 Binary files a/inst/extdata/sample.png and /dev/null differ diff --git a/inst/extdata/test_ngff_image_v04.ome.zarr.zip b/inst/extdata/test_ngff_image_v04.ome.zarr.zip new file mode 100644 index 0000000..4b75ca3 Binary files /dev/null and b/inst/extdata/test_ngff_image_v04.ome.zarr.zip differ diff --git a/man/ImageArray.Rd b/man/ImageArray.Rd index 888d838..a5c0b15 100644 --- a/man/ImageArray.Rd +++ b/man/ImageArray.Rd @@ -22,8 +22,9 @@ ImageArray( \item{image}{a path to a file, a single array or a list of arrays containing the pixel intensities of an image.} -\item{axes}{a character vector of axes names for images. -Should be a subset of c("c", "y", "x", "z", "t")} +\item{axes}{a character vector of axis names, a subset of +\code{c("c", "y", "x", "z", "t")}, of the same length as the number of +dimensions of every level} \item{n.levels}{the number of levels of the pyramidal image, typical an integer starting from 1. Will be ignored if \code{scales} is diff --git a/man/OZPath.Rd b/man/OZPath.Rd new file mode 100644 index 0000000..6dc8e7b --- /dev/null +++ b/man/OZPath.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/omezarr.R +\name{OZPath} +\alias{OZPath} +\alias{resolution} +\alias{resolution,OZPath-method} +\alias{path,OZPath-method} +\title{OZPath constructor method} +\usage{ +OZPath(filepath, resolution = NULL) +} +\arguments{ +\item{filepath}{the path to the OME-Zarr store} + +\item{resolution}{the resolution IDs of the pyramidal image, either the +names of the resolution directories, e.g. "s0", or integers starting from 1 +indexing into them. Subdirectories of the store that are not zarr arrays, +such as the OME-NGFF "labels" group, are never resolutions.} +} +\value{ +An OZPath object +} +\description{ +A function for creating objects of OZPath class +} +\examples{ +# get image +library(utils) +omezarrzip <- system.file("extdata", + "test_ngff_image_v04.ome.zarr.zip", + package = "ImageArray") +dir.create(omezarr <- tempfile()) +unzip(omezarrzip, exdir = omezarr) +oz <- OZPath(omezarr, resolution = 2) +resolution(oz) +path(oz) +} diff --git a/tests/testthat/test-omezarr.R b/tests/testthat/test-omezarr.R new file mode 100644 index 0000000..2894407 --- /dev/null +++ b/tests/testthat/test-omezarr.R @@ -0,0 +1,81 @@ +library(utils) + +# image file +omezarrzip <- system.file("extdata", + "test_ngff_image_v04.ome.zarr.zip", + package = "ImageArray") +dir.create(omezarr <- tempfile(fileext = ".ome.zarr")) +unzip(omezarrzip, exdir = omezarr) + +test_that("OZPath", { + + # create OZPath object, the "labels" group is not a resolution + oz <- OZPath(omezarr) + expect_equal(path(oz), omezarr) + expect_equal(resolution(oz), c("s0", "s1", "s2", "s3", "s4")) + + # resolutions by index and by name + expect_equal(resolution(OZPath(omezarr, resolution = 2)), "s1") + expect_equal(resolution(OZPath(omezarr, resolution = 1:2)), c("s0", "s1")) + expect_equal(resolution(OZPath(omezarr, resolution = "s0")), "s0") + + # faulty resolutions + expect_error(OZPath(omezarr, resolution = "labels"), + "resolutions not found") + expect_error(OZPath(omezarr, resolution = "s9"), + "resolutions not found") + expect_error(OZPath(omezarr, resolution = 99), + "resolutions not found") + + # faulty store + dir.create(notzarr <- tempfile()) + expect_error(OZPath(notzarr), "is not a zarr store") +}) + +test_that("OZPath resolutions have the same dimensionality", { + + # copy the store and make one resolution three dimensional + dir.create(badzarr <- tempfile()) + file.copy(list.files(omezarr, full.names = TRUE, all.files = TRUE, + no.. = TRUE), + badzarr, recursive = TRUE) + zarray <- readLines(file.path(badzarr, "s4", ".zarray")) + zarray <- sub('"shape": [', '"shape": [1,', + paste(zarray, collapse = ""), fixed = TRUE) + zarray <- sub('"chunks": [', '"chunks": [1,', zarray, fixed = TRUE) + writeLines(zarray, file.path(badzarr, "s4", ".zarray")) + + # the 3D level is only rejected when it is among the resolutions + expect_error(OZPath(badzarr), "differing dimensionality") + expect_error(OZPath(badzarr, resolution = c("s3", "s4")), + "differing dimensionality") + expect_equal(resolution(OZPath(badzarr, resolution = 1:4)), + c("s0", "s1", "s2", "s3")) +}) + +test_that("OME-ZARR based ImageArray", { + + # construct imagearray + img <- ImageArray(omezarr, resolution = 1:2) + expect_equal(length(img), 2) + img <- ImageArray(omezarr, resolution = c("s0", "s1")) + expect_equal(length(img), 2) + img <- ImageArray(omezarr, resolution = c("s0", "s2")) + expect_equal(length(img), 2) + img <- ImageArray(omezarr) + expect_equal(length(img), 5) + + # single channel modulate + img_modulated <- negate(img) + orig <- realize(img[1:10, 1:10]) + newmat <- realize(img_modulated[1:10, 1:10]) + expect_equal(unique(c(orig+newmat)), 255) + + # get image info + expect_equal(getImageInfo(img), data.frame(width = 512, height = 512)) + + # construct imagearray + bfa.raster <- as.raster(img) + plot(bfa.raster) +}) +