diff --git a/R/AllClasses.R b/R/AllClasses.R index 4cfa7f5..7887db6 100644 --- a/R/AllClasses.R +++ b/R/AllClasses.R @@ -16,7 +16,7 @@ setClassUnion( .LabelArray <- setClass( Class="LabelArray", contains=c("Annotated"), - slots=list(data="array_OR_df", meta="Zattrs")) + slots=list(data="list", meta="Zattrs")) # these are 'R6ClassGenerator's; # this somehow does the trick... @@ -43,6 +43,10 @@ setClassUnion( contains=c("Annotated"), slots=list(data="arrow_OR_df", meta="Zattrs")) +setClassUnion( + "sdArray", + c("ImageArray", "LabelArray")) + setClassUnion( "SpatialDataElement", c("ImageArray", "LabelArray", "PointFrame", "ShapeFrame")) diff --git a/R/ImageArray.R b/R/ImageArray.R index 88ee899..c54901e 100644 --- a/R/ImageArray.R +++ b/R/ImageArray.R @@ -88,25 +88,6 @@ setMethod("channels", "ANY", \(x, ...) stop("only 'images' have channels")) } } -#' @rdname ImageArray -#' @export -setMethod("data", "ImageArray", \(x, k=1) { - if (is.null(k)) return(x@data) - stopifnot(length(k) == 1, is.numeric(k), k > 0) - n <- length(x@data) # get number of available scales - if (is.infinite(k)) k <- n # input of Inf uses lowest - if (k <= n) return(x@data[[k]]) # return specified scale - stop("'k=", k, "' but only ", n, " resolution(s) available") -}) - -#' @rdname ImageArray -#' @export -setMethod("dim", "ImageArray", \(x) dim(data(x))) - -#' @rdname ImageArray -#' @export -setMethod("length", "ImageArray", \(x) length(data(x, NULL))) - .check_jk <- \(x, .) { if (isTRUE(x)) return() tryCatch( diff --git a/R/LabelArray.R b/R/LabelArray.R index bddbaad..64ed3f6 100644 --- a/R/LabelArray.R +++ b/R/LabelArray.R @@ -37,15 +37,22 @@ LabelArray <- function(data=array(), meta=Zattrs(), metadata=list(), ...) { } #' @rdname LabelArray -#' @export -setMethod("dim", "LabelArray", \(x) dim(data(x))) - -#' @rdname LabelArray +#' @importFrom utils head tail #' @exportMethod [ setMethod("[", "LabelArray", \(x, i, j, ..., drop=FALSE) { - # TODO: subsetting for multiscales - if (missing(i)) i <- TRUE - if (missing(j)) j <- TRUE - x@data <- data(x)[i, j, drop=FALSE] - return(x) -}) + if (missing(i)) i <- TRUE else if (isFALSE(i)) i <- 0 else .check_jk(i, "i") + if (missing(j)) j <- TRUE else if (isFALSE(j)) j <- 0 else .check_jk(j, "j") + n <- length(data(x, NULL)) + d <- dim(data(x, 1)) + x@data <- lapply(seq_len(n), \(.) { + i <- if (isTRUE(i)) seq_len(d[1]) else i + j <- if (isTRUE(j)) seq_len(d[2]) else j + ij <- lapply(list(i, j), \(ij) { + fac <- 2^(.-1) + seq(floor(head(ij, 1)/fac), + ceiling(tail(ij, 1)/fac)) + }) + data(x, .)[ij[[1]], ij[[2]], drop=FALSE] + }) + x +}) \ No newline at end of file diff --git a/R/misc.R b/R/misc.R index f208609..3ca3000 100644 --- a/R/misc.R +++ b/R/misc.R @@ -73,24 +73,15 @@ NULL setMethod("show", "SpatialData", .showSpatialData) #' @importFrom S4Vectors coolcat -.showImageArray <- function(object) { +.showsdArray <- function(object) { n.object <- length(object@data) - cat("class: ImageArray", ifelse(n.object > 1, "(MultiScale)", ""),"\n") + cat("class: ", class(object), ifelse(n.object > 1, "(MultiScale)", ""),"\n") scales <- vapply(object@data, \(x) sprintf("(%s)", paste0(dim(x), collapse=",")), character(1)) coolcat("Scales (%d): %s", scales) } #' @rdname misc -setMethod("show", "ImageArray", .showImageArray) - -#' @importFrom S4Vectors coolcat -.showLabelArray <- function(object) { - cat("class: LabelArray\n") - cat("dim:", dim(object@data)) -} - -#' @rdname misc -setMethod("show", "LabelArray", .showLabelArray) +setMethod("show", "sdArray", .showsdArray) #' @importFrom S4Vectors coolcat .showPointFrame <- function(object) { diff --git a/R/read.R b/R/read.R index cd140e8..929fd14 100644 --- a/R/read.R +++ b/R/read.R @@ -67,15 +67,20 @@ allp = c("session_info==1.0.0", "spatialdata==0.3.0", "spatialdata_io==0.1.7", #' (x <- readSpatialData(base)) NULL +readsdlayer <- function(x, ...) { + md <- fromJSON(file.path(x, ".zattrs")) + ps <- .get_multiscales_dataset_paths(md) + list(array = lapply(ps, \(.) ZarrArray(file.path(x, as.character(.)))), + md = md) +} + #' @rdname readSpatialData #' @importFrom Rarr ZarrArray #' @importFrom jsonlite fromJSON #' @export readImage <- function(x, ...) { - md <- fromJSON(file.path(x, ".zattrs")) - ps <- .get_multiscales_dataset_paths(md) - za_lys <- lapply(ps, \(.) ZarrArray(file.path(x, as.character(.)))) - ImageArray(data=za_lys, meta=Zattrs(md), ...) + lyrs <- readsdlayer(x, ...) + ImageArray(data=lyrs$array, meta=Zattrs(lyrs$md), ...) } #' @rdname readSpatialData @@ -83,9 +88,8 @@ readImage <- function(x, ...) { #' @importFrom jsonlite fromJSON #' @export readLabel <- function(x, ...) { - md <- fromJSON(file.path(x, ".zattrs")) - za <- ZarrArray(file.path(x, "0")) - LabelArray(data=za, meta=Zattrs(md)) + lyrs <- readsdlayer(x, ...) + LabelArray(data=lyrs$array, meta=Zattrs(lyrs$md), ...) } #' @rdname readSpatialData diff --git a/R/sdArray.R b/R/sdArray.R new file mode 100644 index 0000000..4d83793 --- /dev/null +++ b/R/sdArray.R @@ -0,0 +1,41 @@ +#' @name Array-methods +#' @title Methods for `ImageArray` and `LabelArray` class +#' +#' @aliases +#' data,ImageArray-method +#' data,LabelArray-method +#' dim,ImageArray-method +#' dim,LabelArray-method +#' length,ImageArray-method +#' length,LabelArray-method +#' +#' @param x \code{ImageArray} or \code{LabelArray} +#' @param k scalar index specifying which scale to extract. +#' +#' @return \code{ImageArray} +#' +#' @examples +#' # TODO +#' +#' @importFrom S4Vectors metadata<- +#' @importFrom methods new +NULL + +#' @rdname Array-methods +#' @export +setMethod("data", "sdArray", \(x, k=1) { + if (is.null(k)) return(x@data) + stopifnot(length(k) == 1, is.numeric(k), k > 0) + n <- length(x@data) # get number of available scales + if (is.infinite(k)) k <- n # input of Inf uses lowest + if (k <= n) return(x@data[[k]]) # return specified scale + stop("'k=", k, "' but only ", n, " resolution(s) available") +}) + +#' @rdname Array-methods +#' @export +setMethod("dim", "sdArray", \(x) dim(data(x))) + +#' @rdname Array-methods +#' @export +setMethod("length", "sdArray", \(x) length(data(x, NULL))) \ No newline at end of file diff --git a/man/Array-methods.Rd b/man/Array-methods.Rd new file mode 100644 index 0000000..083e08d --- /dev/null +++ b/man/Array-methods.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/sdArray.R +\name{Array-methods} +\alias{Array-methods} +\alias{data,ImageArray-method} +\alias{data,LabelArray-method} +\alias{dim,ImageArray-method} +\alias{dim,LabelArray-method} +\alias{length,ImageArray-method} +\alias{length,LabelArray-method} +\alias{data,sdArray-method} +\alias{dim,sdArray-method} +\alias{length,sdArray-method} +\title{Methods for `ImageArray` and `LabelArray` class} +\usage{ +\S4method{data}{sdArray}(x, k = 1) + +\S4method{dim}{sdArray}(x) + +\S4method{length}{sdArray}(x) +} +\arguments{ +\item{x}{\code{ImageArray} or \code{LabelArray}} + +\item{k}{scalar index specifying which scale to extract.} +} +\value{ +\code{ImageArray} +} +\description{ +Methods for `ImageArray` and `LabelArray` class +} +\examples{ +# TODO + +} diff --git a/man/ImageArray.Rd b/man/ImageArray.Rd index 28c9141..85160a3 100644 --- a/man/ImageArray.Rd +++ b/man/ImageArray.Rd @@ -5,9 +5,6 @@ \alias{channels,ImageArray-method} \alias{channels} \alias{channels,ANY-method} -\alias{data,ImageArray-method} -\alias{dim,ImageArray-method} -\alias{length,ImageArray-method} \alias{[,ImageArray,ANY,ANY,ANY-method} \title{The `ImageArray` class} \usage{ @@ -17,12 +14,6 @@ ImageArray(data = list(), meta = Zattrs(), metadata = list(), ...) \S4method{channels}{ANY}(x, ...) -\S4method{data}{ImageArray}(x, k = 1) - -\S4method{dim}{ImageArray}(x) - -\S4method{length}{ImageArray}(x) - \S4method{[}{ImageArray,ANY,ANY,ANY}(x, i, j, k, ..., drop = FALSE) } \arguments{ @@ -37,10 +28,10 @@ content describing the overall object.} \item{x}{\code{ImageArray}} -\item{k}{scalar index specifying which scale to extract.} - \item{i, j}{indices specifying elements to extract.} +\item{k}{scalar index specifying which scale to extract.} + \item{drop}{ignored.} } \value{ diff --git a/man/LabelArray.Rd b/man/LabelArray.Rd index 8465e35..44f557f 100644 --- a/man/LabelArray.Rd +++ b/man/LabelArray.Rd @@ -2,14 +2,11 @@ % Please edit documentation in R/LabelArray.R \name{LabelArray} \alias{LabelArray} -\alias{dim,LabelArray-method} \alias{[,LabelArray,ANY,ANY,ANY-method} \title{The \code{LabelArray} class} \usage{ LabelArray(data = array(), meta = Zattrs(), metadata = list(), ...) -\S4method{dim}{LabelArray}(x) - \S4method{[}{LabelArray,ANY,ANY,ANY}(x, i, j, ..., drop = FALSE) } \arguments{ diff --git a/man/SpatialData.Rd b/man/SpatialData.Rd index e760cfd..9521d85 100644 --- a/man/SpatialData.Rd +++ b/man/SpatialData.Rd @@ -50,8 +50,8 @@ \alias{element,SpatialData,ANY,numeric-method} \alias{element,SpatialData,ANY,missing-method} \alias{element,SpatialData,ANY,ANY-method} -\alias{[[<-,SpatialData,numeric,ANY-method} -\alias{[[<-,SpatialData,character,ANY-method} +\alias{[[<-,SpatialData,numeric,ANY,ANY-method} +\alias{[[<-,SpatialData,character,ANY,ANY-method} \title{The `SpatialData` class} \usage{ SpatialData(images, labels, points, shapes, tables) @@ -88,9 +88,9 @@ SpatialData(images, labels, points, shapes, tables) \S4method{element}{SpatialData,ANY,ANY}(x, i, j) -\S4method{[[}{SpatialData,numeric,ANY}(x, i) <- value +\S4method{[[}{SpatialData,numeric,ANY,ANY}(x, i) <- value -\S4method{[[}{SpatialData,character,ANY}(x, i) <- value +\S4method{[[}{SpatialData,character,ANY,ANY}(x, i) <- value } \arguments{ \item{images}{list of \code{\link{ImageArray}}s} diff --git a/man/misc.Rd b/man/misc.Rd index b63a1e8..32e489b 100644 --- a/man/misc.Rd +++ b/man/misc.Rd @@ -3,17 +3,14 @@ \name{misc} \alias{misc} \alias{show,SpatialData-method} -\alias{show,ImageArray-method} -\alias{show,LabelArray-method} +\alias{show,sdArray-method} \alias{show,PointFrame-method} \alias{show,ShapeFrame-method} \title{Miscellaneous `Miro` methods} \usage{ \S4method{show}{SpatialData}(object) -\S4method{show}{ImageArray}(object) - -\S4method{show}{LabelArray}(object) +\S4method{show}{sdArray}(object) \S4method{show}{PointFrame}(object) diff --git a/tests/testthat/test-imagearray.R b/tests/testthat/test-imagearray.R index ea212f9..ae70feb 100644 --- a/tests/testthat/test-imagearray.R +++ b/tests/testthat/test-imagearray.R @@ -30,3 +30,13 @@ test_that("data(),ImageArray", { expect_error(data(img, "")) expect_error(data(img, c(1,2))) }) + +x <- file.path("extdata", "blobs.zarr") +x <- system.file(x, package="SpatialData") +x <- readSpatialData(x, tables=FALSE) + +test_that("[,ImageArray", { + y <- image(x, i <- "blobs_image") + y <- y[,seq_len(32)] # subset to make things harder +}) + diff --git a/tests/testthat/test-labelarray.R b/tests/testthat/test-labelarray.R new file mode 100644 index 0000000..00cfca1 --- /dev/null +++ b/tests/testthat/test-labelarray.R @@ -0,0 +1,43 @@ +arr <- seq_len(12) + +test_that("LabelArray()", { + val <- sample(arr, 20*20, replace=TRUE) + mat <- array(val, dim=c(20, 20)) + # invalid + expect_error(LabelArray(mat)) + expect_error(LabelArray(mat, 1)) + expect_error(LabelArray(mat, list())) + # single scale + expect_silent(LabelArray(list())) + expect_silent(LabelArray(list(mat))) + expect_silent(LabelArray(list(mat), Zattrs())) + # multiscale + dim <- lapply(c(20, 10, 5), \(.) c(3, rep(., 2))) + lys <- lapply(dim, \(.) array(sample(arr, prod(.), replace=TRUE), dim=.)) + expect_silent(LabelArray(lys)) +}) +de +test_that("data(),LabelArray", { + dim <- lapply(c(8, 4, 2), \(.) c(3, rep(., 2))) + lys <- lapply(dim, \(.) array(0, dim=.)) + lab <- LabelArray(lys) + for (. in seq_along(lys)) + expect_identical(data(lab, .), lys[[.]]) + expect_identical(data(lab, Inf), lys[[3]]) + expect_error(data(lab, 0)) + expect_error(data(lab, -1)) + expect_error(data(lab, 99)) + expect_error(data(lab, "")) + expect_error(data(lab, c(1,2))) +}) + +x <- file.path("extdata", "blobs.zarr") +x <- system.file(x, package="SpatialData") +x <- readSpatialData(x, tables=FALSE) + +test_that("[,LabelArray", { + y <- label(x, i <- "blobs_labels") + y <- y[,seq_len(32)] # subset to make things harder + y <- label(x, i <- "blobs_multiscale_labels") + y <- y[,seq_len(32)] # subset to make things harder +}) \ No newline at end of file