diff --git a/.gitignore b/.gitignore index 7ac4af56..a110dd34 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ inst/extdata/xenium1.zarr inst/extdata/visiumhd.zarr *.Rproj *.html +R/_* diff --git a/DESCRIPTION b/DESCRIPTION index 8fbcafc8..7438d1bc 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: SpatialData Title: Representation of Python's SpatialData in R Depends: R (>= 4.6) -Version: 0.99.28 +Version: 0.99.29 Description: Interface to Python's 'SpatialData', currently including: reticulate-based use of 'spatialdata-io' for reading of manufacturer data and writing to .zarr, on-disk representation of images/labels as @@ -51,7 +51,6 @@ Imports: sf, S4Arrays, S4Vectors, - SparseArray, SingleCellExperiment, SummarizedExperiment Suggests: @@ -63,8 +62,7 @@ Suggests: Rgraphviz, SpatialData.data, SpatialData.plot, - testthat, - DT + testthat Remotes: keller-mark/anndataR@spatialdata, HelenaLC/SpatialData.data, diff --git a/R/AllGenerics.R b/R/AllGenerics.R index cc2107a1..62ae3d87 100644 --- a/R/AllGenerics.R +++ b/R/AllGenerics.R @@ -40,7 +40,6 @@ setGeneric("tables<-", \(x, value) standardGeneric("tables<-")) # trs ---- -setGeneric("axes", \(x, ...) standardGeneric("axes")) setGeneric("CTlist", \(x, ...) standardGeneric("CTlist")) setGeneric("CTdata", \(x, ...) standardGeneric("CTdata")) setGeneric("CTname", \(x, ...) standardGeneric("CTname")) @@ -79,11 +78,13 @@ setGeneric("meta", \(x, ...) standardGeneric("meta")) setGeneric("query", \(x, ...) standardGeneric("query")) setGeneric("mask", \(x, i, j, ...) standardGeneric("mask")) +setGeneric("axes", \(x, ...) standardGeneric("axes")) +setGeneric("extent", \(x, ...) standardGeneric("extent")) setGeneric("channels", \(x, ...) standardGeneric("channels")) +setGeneric("centroids", \(x, ...) standardGeneric("centroids")) setGeneric("data_type", \(x, ...) standardGeneric("data_type")) setGeneric("geom_type", \(x, ...) standardGeneric("geom_type")) -setGeneric("centroids", \(x, ...) standardGeneric("centroids")) -setGeneric("extent", \(x, ...) standardGeneric("extent")) +setGeneric("multiscales", \(x, ...) standardGeneric("multiscales")) # tbl ---- diff --git a/R/CTgraph.R b/R/CTgraph.R index ee47ebd3..a1a2dd40 100644 --- a/R/CTgraph.R +++ b/R/CTgraph.R @@ -10,6 +10,19 @@ #' @param fac,max scalar numeric; node labels with \code{nchar>max} #' are split and hyphenated at position \code{floor(nchar/fac)} #' +#' @returns +#' \itemize{ +#' \item \code{CTgraph}: +#' \code{graph::graphAM} object with nodes for each element and +#' coordinate space, and edges for each transformation (if specified) +#' \item \code{CTpath}: +#' list of transformations from \code{i} to \code{j}; +#' length > 1 if \code{type} is \code{"sequential"}, length-1 otherwise; +#' each element specifies \code{type} and \code{data} of the transformation +#' \item \code{CTplot}: +#' visualizes the element-coordinate space graph with \code{Rgraphviz} +#' } +#' #' @examples #' x <- file.path("extdata", "blobs.zarr") #' x <- system.file(x, package="SpatialData") diff --git a/R/coord_utils.R b/R/CTutils.R similarity index 79% rename from R/coord_utils.R rename to R/CTutils.R index 91a1517b..124c0929 100644 --- a/R/coord_utils.R +++ b/R/CTutils.R @@ -1,17 +1,34 @@ -#' @name coord-utils -#' @title Coordinate transformations +#' @name CTutils +#' @title Coord. trans. utilities #' @aliases axes CTlist CTname CTtype CTdata addCT rmvCT #' #' @param x \code{SpatialData}, an element, or \code{Zattrs}. #' @param i for \code{CTpath}, source node label; else, string or #' scalar integer giving the name or index of a coordinate space. -#' @param j character string; name of target coordinate space. #' @param name character(1); name of coordinate space #' @param type character(1); type of transformation #' @param data transformation data; size and shape depend on transformation and #' element type (e.g., numeric(1) for rotation, numeric(2) for scaling in 2D) #' @param ... option arguments passed to and from other methods. #' +#' @returns +#' \itemize{ +#' \item \code{CTname}: character string; +#' transformation name (e.g., "global") +#' \item \code{CTtype}: character string; +#' transformation type (e.g., "affine") +#' \item \code{CTdata}: list; +#' transformation data (e.g., scalar numeric for rotation) +#' \item \code{CTlist}: list; +#' list of transformation specifications per OME-NGFF spec +#' \item \code{add/rmvCT}: +#' \code{SpatialDataElement} or \code{Zattrs} +#' with transformation(s) added/removed +#' \item \code{axes}: list; +#' each element is a character string (name), or list +#' with axis name and type (e.g., "space" or "channel") +#' } +#' #' @examples #' x <- file.path("extdata", "blobs.zarr") #' x <- system.file(x, package="SpatialData") @@ -38,30 +55,31 @@ NULL # axes() ---- -#' @rdname coord-utils +#' @rdname CTutils #' @export setMethod("axes", "Zattrs", \(x, ...) { - if (!is.null(ms <- x$multiscales)) x <- ms[[1]] + ms <- multiscales(x) + if (!is.null(ms)) x <- ms[[1]] if (is.null(x <- x$axes)) stop("couldn't find 'axes'") return(x) }) -#' @rdname coord-utils +#' @rdname CTutils #' @export setMethod("axes", "SpatialDataElement", \(x, ...) axes(meta(x))) # CTlist/data/type/name() ---- -#' @rdname coord-utils +#' @rdname CTutils #' @export setMethod("CTlist", "Zattrs", \(x, ...) { - ms <- "multiscales" + ms <- multiscales(x) ct <- "coordinateTransformations" - if (is.null(x[[ms]])) return(x[[ct]]) - x[[ms]][[1]][[ct]] + if (is.null(ms)) return(x[[ct]]) + ms[[1]][[ct]] }) -#' @rdname coord-utils +#' @rdname CTutils #' @export setMethod("CTdata", "Zattrs", \(x, i=1, ...) { stopifnot(length(i) == 1) @@ -81,31 +99,35 @@ setMethod("CTdata", "Zattrs", \(x, i=1, ...) { mapply(x=ts, i=names(ts), \(x, i) x[[i]], SIMPLIFY=FALSE) }) -#' @rdname coord-utils +#' @rdname CTutils #' @export -setMethod("CTtype", "Zattrs", \(x, ...) vapply(CTlist(x), \(.) .$type, character(1))) +setMethod("CTtype", "Zattrs", \(x, ...) { + vapply(CTlist(x), \(.) .$type, character(1)) +}) -#' @rdname coord-utils +#' @rdname CTutils #' @export -setMethod("CTname", "Zattrs", \(x, ...) vapply(CTlist(x), \(.) .$output$name, character(1))) +setMethod("CTname", "Zattrs", \(x, ...) { + vapply(CTlist(x), \(.) .$output$name, character(1)) +}) -#' @rdname coord-utils +#' @rdname CTutils #' @export setMethod("CTlist", "SpatialDataElement", \(x, ...) CTlist(meta(x))) -#' @rdname coord-utils +#' @rdname CTutils #' @export setMethod("CTdata", "SpatialDataElement", \(x, i=1, ...) CTdata(meta(x), i)) -#' @rdname coord-utils +#' @rdname CTutils #' @export setMethod("CTtype", "SpatialDataElement", \(x, ...) CTtype(meta(x))) -#' @rdname coord-utils +#' @rdname CTutils #' @export setMethod("CTname", "SpatialDataElement", \(x, ...) CTname(meta(x))) -#' @rdname coord-utils +#' @rdname CTutils #' @export setMethod("CTname", "SpatialData", \(x, ...) { g <- CTgraph(x) @@ -115,12 +137,12 @@ setMethod("CTname", "SpatialData", \(x, ...) { # rmv ---- -#' @rdname coord-utils +#' @rdname CTutils #' @export setMethod("rmvCT", "SpatialDataElement", \(x, i) { x@meta <- rmvCT(meta(x), i); x }) -#' @rdname coord-utils +#' @rdname CTutils #' @export setMethod("rmvCT", "Zattrs", \(x, i) { nms <- CTname(x) @@ -155,7 +177,7 @@ setMethod("rmvCT", "Zattrs", \(x, i) { # add ---- -#' @rdname coord-utils +#' @rdname CTutils #' @export setMethod("addCT", "SpatialDataElement", \(x, name, type, data) { x@meta <- addCT(meta(x), name, type, data); x }) @@ -173,7 +195,7 @@ setMethod("addCT", "SpatialDataElement", \(x, name, type, data) { if (!.) f(t) } -#' @rdname coord-utils +#' @rdname CTutils #' @export setMethod("addCT", "Zattrs", \(x, name, type="identity", data=NULL) { stopifnot( diff --git a/R/ImageArray.R b/R/ImageArray.R index afc52939..58539966 100644 --- a/R/ImageArray.R +++ b/R/ImageArray.R @@ -31,7 +31,12 @@ ImageArray <- function(data=list(), meta=Zattrs(), metadata=list(), ...) { #' @rdname ImageArray #' @aliases channels #' @export -setMethod("channels", "Zattrs", \(x, ...) unlist(x$omero$channels)) +setMethod("channels", "Zattrs", \(x, ...) { + v <- x$spatialdata_attrs$version + if (!length(v)) stop("couldn't find 'version' in 'spatialdata_attrs'") + if (v == "0.3") x <- x$ome + unlist(x$omero$channels) +}) #' @rdname ImageArray #' @aliases channels @@ -43,11 +48,11 @@ setMethod("channels", "ImageArray", \(x, ...) channels(meta(x))) setMethod("channels", "ANY", \(x, ...) stop("only 'images' have channels")) #' @importFrom S4Vectors isSequence -.get_multiscales_dataset_paths <- function(md) { +.get_multiscales_dataset_paths <- function(za) { # validate 'multiscales' - .validate_multiscales_dataset_path(md) + ms <- .check_ms(za) # get & validate 'path's - ds <- md$multiscales[[1]]$datasets + ds <- ms[[1]]$datasets ps <- vapply(ds, \(.) .$path, character(1)) ps <- suppressWarnings(as.numeric(sort(ps, decreasing=FALSE))) if (length(ps)) { @@ -59,10 +64,9 @@ setMethod("channels", "ANY", \(x, ...) stop("only 'images' have channels")) return(ps) } -#' @noRd -.validate_multiscales_dataset_path <- function(md) { - # validate 'multiscales' - ms <- md$multiscales +.check_ms <- \(za) { + # validate 'multiscales' + ms <- multiscales(za) if (!is.null(ms)) { # validate 'datasets' ds <- ms[[1]]$datasets @@ -78,6 +82,7 @@ setMethod("channels", "ANY", \(x, ...) stop("only 'images' have channels")) } else stop( "'ImageArray' paths are ill-defined,", " no 'multiscales' attribute under '.zattrs'") + return(ms) } .check_jk <- \(x, .) { diff --git a/R/LabelArray.R b/R/LabelArray.R index 403f8b3f..0f544ba9 100644 --- a/R/LabelArray.R +++ b/R/LabelArray.R @@ -25,7 +25,13 @@ #' @return \code{LabelArray} #' #' @examples -#' # TODO +#' x <- file.path("extdata", "blobs.zarr") +#' x <- system.file(x, package="SpatialData") +#' x <- file.path(x, "labels", "blobs_labels") +#' +#' (y <- readLabel(x)) +#' y[1:10, 1:10] +#' meta(y) #' #' @importFrom S4Vectors metadata<- #' @importFrom methods new diff --git a/R/ShapeFrame.R b/R/ShapeFrame.R index b8e11a51..86ad2b91 100644 --- a/R/ShapeFrame.R +++ b/R/ShapeFrame.R @@ -10,7 +10,7 @@ #' content describing the overall object. #' @param name character string for extraction (see \code{?base::`$`}). #' @param i,j indices specifying elements to extract. -#' @param drop ignored. +#' @param drop,pattern ignored. #' @param ... optional arguments passed to and from other methods. #' #' @return \code{ShapeFrame} @@ -55,15 +55,15 @@ setMethod("names", "ShapeFrame", \(x) names(data(x))) #' @export #' @rdname ShapeFrame #' @importFrom utils .DollarNames -.DollarNames.ShapeFrame <- \(x, pattern="") { +.DollarNames.ShapeFrame <- \(x, pattern="") grep(pattern, names(x), value=TRUE) -} #' @rdname ShapeFrame #' @exportMethod $ setMethod("$", "ShapeFrame", \(x, name) data(x)[[name]]) #' @export +#' @rdname ShapeFrame #' @importFrom sf st_as_sf st_geometry_type setMethod("geom_type", "ShapeFrame", \(x) { y <- st_as_sf(data(x[1, ])) diff --git a/R/SpatialData.R b/R/SpatialData.R index bacbfe6f..57ceed4a 100644 --- a/R/SpatialData.R +++ b/R/SpatialData.R @@ -8,6 +8,8 @@ #' image images image<- images<- imageNames #' shape shapes shape<- shapes<- shapeNames #' table tables table<- tables<- tableNames +#' [[<-,SpatialData,character,ANY-method +#' [[<-,SpatialData,numeric,ANY-method #' #' @description ... #' diff --git a/R/Zattrs.R b/R/Zattrs.R index 9bae799b..eb969bec 100644 --- a/R/Zattrs.R +++ b/R/Zattrs.R @@ -1,8 +1,6 @@ #' @name Zattrs #' @title The `Zattrs` class #' -#' @aliases feature_key -#' #' @param x list extracted from a OME-NGFF compliant .zattrs file. #' @param name character string for extraction (see ?base::`$`). #' @@ -38,6 +36,14 @@ Zattrs <- \(x=list()) { #' @exportMethod $ setMethod("$", "Zattrs", \(x, name) x[[name]]) +# internal use only! +#' @noRd +setMethod("multiscales", "list", \(x) { + v <- x$spatialdata_attrs$version + if (!length(v)) stop("couldn't find 'version' in 'spatialdata_attrs'") + switch(v, "0.3"=x$ome$multiscales, x$multiscales) +}) + .showZattrs <- function(object) { cat("class: Zattrs\n") ax <- axes(object) @@ -80,22 +86,57 @@ setMethod("$", "Zattrs", \(x, name) x[[name]]) } setMethod("show", "Zattrs", .showZattrs) +#' @name SDattrs +#' @title \code{SpatialData} attributes +#' +#' @aliases +#' region +#' region_key +#' feature_key +#' instance_key +#' +#' @param x depends on which attributes are available; +#' specifically, \code{PointFrame} (\code{feature/instance_key}), or +#' \code{SingleCellExperiment} (\code{region}, \code{region/instance_key}), +#' +#' @return character string +#' +#' @examples +#' x <- file.path("extdata", "blobs.zarr") +#' x <- system.file(x, package="SpatialData") +#' x <- readSpatialData(x, anndataR=TRUE) +#' +#' region(table(x)) +#' region_key(table(x)) +#' +#' instance_key(point(x)) +#' fk <- feature_key(point(x)) +#' base::table(point(x)[[fk]]) +NULL + # TODO: only points can have this? #' @export +#' @rdname SDattrs setMethod("feature_key", "list", \(x) x$spatialdata_attrs$feature_key) #' @export +#' @rdname SDattrs setMethod("feature_key", "PointFrame", \(x) feature_key(meta(x))) # TODO: only tables can have this? #' @export +#' @rdname SDattrs setMethod("region_key", "SingleCellExperiment", \(x) meta(x)$region_key) #' @export +#' @rdname SDattrs setMethod("region", "SingleCellExperiment", \(x) meta(x)[[region_key(x)]]) # TODO: only tables and points can have this? #' @export +#' @rdname SDattrs setMethod("instance_key", "list", \(x) x$instance_key) #' @export +#' @rdname SDattrs setMethod("instance_key", "PointFrame", \(x) instance_key(meta(x)$spatialdata_attrs)) #' @export +#' @rdname SDattrs setMethod("instance_key", "SingleCellExperiment", \(x) instance_key(meta(x))) diff --git a/R/data.R b/R/data.R index 9c6daa62..97aa5bf2 100644 --- a/R/data.R +++ b/R/data.R @@ -4,7 +4,7 @@ #' #' @description data were retrieved on Nov. 11th, 2024, from \href{https://github.com/scverse/spatialdata-notebooks/tree/main/notebooks/developers_resources/storage_format/multiple_elements.zarr}{here}. #' -#' @return NULL +#' @returns zarr store. #' #' @examples #' x <- file.path("extdata", "blobs.zarr") diff --git a/R/misc.R b/R/misc.R index ad128704..713a5b82 100644 --- a/R/misc.R +++ b/R/misc.R @@ -1,9 +1,14 @@ #' @name misc #' @title Miscellaneous `SpatialData` methods -#' @description ... -#' -#' @param object \code{\link{SpatialData}} object or one of its -#' elements, i.e., an Image/LabelArray or Point/ShapeFrame. +#' @aliases show,SpatialData-method +#' +#' @description +#' Miscellaneous methods (e.g., \code{show}) for the +#' \code{\link{SpatialData}} class and its elements. +#' +#' @param object +#' \code{\link{SpatialData}} object or one of its elements, +#' i.e., an \code{Image/LabelArray} or \code{Point/ShapeFrame}. #' #' @return \code{NULL} #' diff --git a/R/query.R b/R/query.R index f5fa6bd3..01fa1524 100644 --- a/R/query.R +++ b/R/query.R @@ -10,8 +10,8 @@ #' #' @param x \code{SpatialData} element. #' @param y query specification; -#' bounding box: length-4 numeric list with names 'xmin/xmax/ymin/ymax' -#' (order is irrelevant); polygon: numeric matrix with ≥ 3 rows and 2 columns. +#' bounding box: length-4 numeric list with names 'xmin/xmax/ymin/ymax'; +#' polygon: numeric matrix with at least 3 rows and exactly 2 columns. #' @param i for \code{SpatialData}, index or name of table to query. #' @param ... optional arguments passed to and from other methods. #' @@ -97,8 +97,8 @@ setMethod("query", "SpatialData", \(x, ..., i) { nrow(mx) >= 3, ncol(mx) == 2, !is.na(mx), is.finite(mx)) if (!all(ok)) stop( - "Invalid polygon query; should be numeric matrix ", - "with ≥ 3 rows and 2 columns (= xy-coordinates)") + "Invalid polygon query; should be numeric matrix with at ", + "least 3 rows and exactly 2 columns (= xy-coordinates)") # ensure polygon is closed top <- mx[1, ] bot <- mx[nrow(mx), ] diff --git a/R/read.R b/R/read.R index f0dbec78..78217254 100644 --- a/R/read.R +++ b/R/read.R @@ -1,4 +1,3 @@ -# # allp = c("session_info==1.0.0", "spatialdata==0.3.0", "spatialdata_io==0.1.7", # "pillow==11.1.0", "anndata==0.11.3", "annotated_types==0.7.0", "asciitree==0.3.3", # "attr==0.3.2", "certifi==2025.01.31", "charset_normalizer==3.4.1", @@ -25,8 +24,14 @@ # "typing_extensions==4.12.2", "urllib3==2.3.0", "wrapt==1.17.2", # "xarray==2024.11.0", "xarray_dataclasses==1.9.1", "xarray_schema==0.0.3", # "zarr==2.18.4", "zict==3.0.0") -allp = c("zarr==3.1.5", "spatialdata==0.7.0", "spatialdata_io==0.6.0", - "spatialdata_plot==0.2.14", "setuptools==75.8.0") + +allp <- c( + "zarr==3.1.5", + "spatialdata==0.7.0", + "spatialdata_io==0.6.0", + "spatialdata_plot==0.2.14", + "setuptools==75.8.0") + # notes from VJC/AM -- readSpatialData was modified below so # that if anndataR = FALSE, anndata.read_zarr is used # to get the whole zarr store, and then the tables are @@ -34,7 +39,6 @@ allp = c("zarr==3.1.5", "spatialdata==0.7.0", "spatialdata_io==0.6.0", # for ingesting the visium_hd_3.0.0 example but fails on # the blobs dataset in example("table-utils") because # of matters related to metadata/hasTable behavior -# #' @name readSpatialData #' @title Reading `SpatialData` @@ -203,8 +207,10 @@ readSpatialData <- function(x, args <- as.list(environment())[.LAYERS] skip <- vapply(args, isFALSE, logical(1)) sd <- lapply(.LAYERS[!skip], \(i) { - y <- file.path(x, i) - j <- list.files(y, full.names=TRUE) + j <- list.dirs( + file.path(x, i), + recursive=FALSE, + full.names=TRUE) names(j) <- basename(j) if (!isTRUE(opt <- args[[i]])) { if (is.numeric(opt) && opt > (. <- length(j))) diff --git a/R/sdArray.R b/R/sdArray.R index ea0e4662..2d8ce7cf 100644 --- a/R/sdArray.R +++ b/R/sdArray.R @@ -1,15 +1,14 @@ -#' @name Array-methods +#' @name sdArray #' @title Methods for `ImageArray` and `LabelArray` class #' #' @aliases +#' data_type #' data,ImageArray-method #' data,LabelArray-method #' dim,ImageArray-method #' dim,LabelArray-method #' length,ImageArray-method #' length,LabelArray-method -#' data_type,ImageArray-method -#' data_type,LabelArray-method #' #' @param x \code{ImageArray} or \code{LabelArray} #' @param k scalar index specifying which scale to extract. @@ -30,7 +29,7 @@ #' @importFrom methods new NULL -#' @rdname Array-methods +#' @rdname sdArray #' @export setMethod("data", "sdArray", \(x, k=1) { if (is.null(k)) return(x@data) @@ -41,25 +40,25 @@ setMethod("data", "sdArray", \(x, k=1) { stop("'k=", k, "' but only ", n, " resolution(s) available") }) -#' @rdname Array-methods +#' @rdname sdArray #' @export setMethod("dim", "sdArray", \(x) dim(data(x))) -#' @rdname Array-methods +#' @rdname sdArray #' @export setMethod("length", "sdArray", \(x) length(data(x, NULL))) #' @export -#' @rdname Array-methods +#' @rdname sdArray #' @importFrom S4Vectors metadata setMethod("data_type", "sdArray", \(x) { if (is(y <- data(x), "DelayedArray")) data_type(y) else metadata(x)$data_type }) -#' @rdname Array-methods +#' @export +#' @rdname sdArray #' @importFrom DelayedArray DelayedArray #' @importFrom Rarr zarr_overview #' @importFrom ZarrArray path -#' @export setMethod("data_type", "DelayedArray", \(x) zarr_overview(path(x), as_data_frame=TRUE)$data_type) diff --git a/R/table_utils.R b/R/table_utils.R index 784c7c47..7e2e6e59 100644 --- a/R/table_utils.R +++ b/R/table_utils.R @@ -18,6 +18,19 @@ #' @param ... \code{data.frame} or list of data generation function(s) #' that accept an argument for the number of observations; see examples. #' +#' @returns +#' \itemize{ +#' \item \code{hasTable}: +#' logical scalar (or character string, if \code{name=TRUE}); +#' whether or not a \code{table} annotating \code{i} exists in \code{x} +#' \item \code{getTable}: +#' \code{SingleCellExperiment}; the \code{table} annotating +#' \code{i} with optional filtering of matching observations +#' \item \code{valTable}: +#' vector of values (according to \code{j}) +#' from the \code{table} annotating \code{i} +#' } +#' #' @examples #' library(SingleCellExperiment) #' x <- file.path("extdata", "blobs.zarr") diff --git a/R/trans.R b/R/trans.R index bb303385..af9ec51f 100644 --- a/R/trans.R +++ b/R/trans.R @@ -3,9 +3,7 @@ #' @title Transformations #' @aliases scale rotate translation flip flop mirror #' -#' @param x \code{SpatialData} element -#' @param j scalar character or numeric; -#' name or index of coordinate space. +#' @param x \code{SpatialData} element. #' @param t transformation data; exceptions: for \code{mirror}, controls #' whether to perform \bold{v}ertical or \bold{h}orizontal reflection; #' no data is needed for \code{flip} (\bold{v}) and \code{flop} (\bold{h}). @@ -14,6 +12,8 @@ #' only applies to \code{sdArray}s (images, labels). #' @param ... option arguments passed to and from other methods. #' +#' @returns \code{SpatialData} element with transformation(s) applied. +#' #' @examples #' x <- file.path("extdata", "blobs.zarr") #' x <- system.file(x, package="SpatialData") @@ -106,6 +106,8 @@ setMethod("rotate", c("sdArray", "numeric"), \(x, t, k=1,...) { .trans_a(x, f, k) }) +#' @export +#' @rdname trans #' @importFrom EBImage translate setMethod("translation", c("sdArray", "numeric"), \(x, t, k=1, ...) { stopifnot(length(t) == length(dim(x)), is.finite(t)) diff --git a/R/tx_to_ext.R b/R/tx_to_ext.R index f0149435..e4421a28 100644 --- a/R/tx_to_ext.R +++ b/R/tx_to_ext.R @@ -21,6 +21,8 @@ #' can include "maintain_positioning" (logical (1)) or numerics for #' target_unit_to_pixels, target_width, target_height, target_depth. #' +#' @return \code{SpatialData} object. +#' #' @examples #' src <- system.file("extdata", "blobs.zarr", package="SpatialData") #' td <- tempfile() diff --git a/R/utils.R b/R/utils.R index e2706093..8618e2ad 100644 --- a/R/utils.R +++ b/R/utils.R @@ -3,8 +3,14 @@ #' @title Utilities #' @aliases centroids extent #' -#' @param x a \code{SpatialData} element (any but image) -#' @param as determines how results will be returned +#' @param x a \code{SpatialData} element (any but image). +#' @param as character string; how results should be returned. +#' @param ... optional arguments passed to and from other methods. +#' +#' @returns +#' For \code{centroids}, a table (\code{data.frame} or \code{matrix}) +#' of spatial coordinates (if \code{as="list"}, split by instance); +#' for extend, a length-2 numeric list of x- and y-ranges. #' #' @examples #' x <- file.path("extdata", "blobs.zarr") @@ -42,11 +48,13 @@ setMethod("centroids", "ANY", \(x, ...) stop("'centroids' ", #' @importFrom Matrix summary setMethod("centroids", "LabelArray", \(x, as=c("data.frame", "matrix")) { - # TODO: should these be offset by 0.5? as <- match.arg(as) y <- data(x) y <- as(y, "dgCMatrix") i <- summary(y) + # flip dimensions so that columns=x, rows=y + # TODO: should these be offset by 0.5? + i[, c(1, 2)] <- i[, c(2, 1)]-0.5 xy <- tapply(i[, -3], i[[3]], colMeans) xy <- do.call(rbind, xy) xy <- cbind(xy, as.integer(rownames(xy))) diff --git a/R/validity.R b/R/validity.R index edd7ba70..f30ed209 100644 --- a/R/validity.R +++ b/R/validity.R @@ -26,15 +26,15 @@ ok <- !is.null(int_colData(se)[[md$instance_key]]) if (!ok) msg <- c(msg, paste0( i, "-th table missing 'instance_key' column in 'int_colData'")) - + } } na <- setdiff( unlist(lapply(tables(object), \(.) if (sce(.)) region(.))), unlist(colnames(object)[setdiff(.LAYERS, "tables")])) # don't flip! - if (length(na)) + if (length(na)) msg <- c(msg, paste( - "table region(s) not found in any layer:", + "table region(s) not found in any layer:", paste(sprintf("'%s'", na), collapse=", "))) return(msg) } @@ -72,7 +72,7 @@ setValidity2("LabelArray", .validateLabelArray) .validatePointFrame <- \(object) { msg <- c() - if (!length(object)) return(msg) + if (!length(object)) return(msg) if (!"x" %in% names(object)) msg <- c(msg, "'PointFrame' missing 'x'.") if (!"y" %in% names(object)) msg <- c(msg, "'PointFrame' missing 'y'.") return(msg) @@ -82,7 +82,7 @@ setValidity2("PointFrame", .validatePointFrame) .validateShapeFrame <- \(object) { msg <- c() - if (!nrow(object)) return(msg) + if (!nrow(object)) return(msg) if (!"geometry" %in% names(object)) msg <- c(msg, "'ShapeFrame' missing 'geometry'.") return(msg) } @@ -102,10 +102,7 @@ setValidity2("ShapeFrame", .validateShapeFrame) if (!all(vapply(x[[.]], \(y) is(y, typ[.]), logical(1)))) msg <- c(msg, sprintf("'%s' should be a list of '%s'", ., typ[.])) # TODO: validate .zattrs across all layers - for (y in labels(x)) { - msg <- c(msg, .validateLabelArray(y)) - msg <- c(msg, .validateZattrsLabelArray(y)) - } + for (y in labels(x)) msg <- c(msg, .validateLabelArray(y)) for (y in images(x)) msg <- c(msg, .validateImageArray(y)) for (y in points(x)) msg <- c(msg, .validatePointFrame(y)) for (y in shapes(x)) msg <- c(msg, .validateShapeFrame(y)) @@ -116,6 +113,8 @@ setValidity2("ShapeFrame", .validateShapeFrame) #' @importFrom S4Vectors setValidity2 setValidity2("SpatialData", .validateSpatialData) +# TODO: version-specific .zattrs validation for all layers + .validateZattrs_multiscales <- \(x, msg) { if (is.null(ms <- x$multiscales[[1]])) msg <- c(msg, "missing 'multiscales'") diff --git a/inst/NEWS b/inst/NEWS index 155ca6fa..18c4e5df 100644 --- a/inst/NEWS +++ b/inst/NEWS @@ -1,3 +1,9 @@ +changes in version 0.99.29 + +- revision of Zarr version-specific .zattrs handling +- added Zarr v3 example dataset 'inst/extdata/blobs_v3' +- reorganization of unit tests to facilitate v3-specific testing + changes in version 0.99.28 - validity checks for 'table' elements diff --git a/man/CTgraph.Rd b/man/CTgraph.Rd index 89336782..5892ad88 100644 --- a/man/CTgraph.Rd +++ b/man/CTgraph.Rd @@ -40,6 +40,19 @@ CTplot(g, cex = 0.5, fac = 2, max = 10) \item{fac, max}{scalar numeric; node labels with \code{nchar>max} are split and hyphenated at position \code{floor(nchar/fac)}} } +\value{ +\itemize{ +\item \code{CTgraph}: + \code{graph::graphAM} object with nodes for each element and + coordinate space, and edges for each transformation (if specified) +\item \code{CTpath}: + list of transformations from \code{i} to \code{j}; + length > 1 if \code{type} is \code{"sequential"}, length-1 otherwise; + each element specifies \code{type} and \code{data} of the transformation +\item \code{CTplot}: + visualizes the element-coordinate space graph with \code{Rgraphviz} +} +} \description{ Coord. trans. graph } diff --git a/man/coord-utils.Rd b/man/CTutils.Rd similarity index 76% rename from man/coord-utils.Rd rename to man/CTutils.Rd index 3bc530f8..a04efc4b 100644 --- a/man/coord-utils.Rd +++ b/man/CTutils.Rd @@ -1,7 +1,7 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/coord_utils.R -\name{coord-utils} -\alias{coord-utils} +% Please edit documentation in R/CTutils.R +\name{CTutils} +\alias{CTutils} \alias{axes} \alias{CTlist} \alias{CTname} @@ -24,7 +24,7 @@ \alias{rmvCT,Zattrs-method} \alias{addCT,SpatialDataElement-method} \alias{addCT,Zattrs-method} -\title{Coordinate transformations} +\title{Coord. trans. utilities} \usage{ \S4method{axes}{Zattrs}(x, ...) @@ -70,11 +70,27 @@ scalar integer giving the name or index of a coordinate space.} \item{data}{transformation data; size and shape depend on transformation and element type (e.g., numeric(1) for rotation, numeric(2) for scaling in 2D)} - -\item{j}{character string; name of target coordinate space.} +} +\value{ +\itemize{ +\item \code{CTname}: character string; + transformation name (e.g., "global") +\item \code{CTtype}: character string; + transformation type (e.g., "affine") +\item \code{CTdata}: list; + transformation data (e.g., scalar numeric for rotation) +\item \code{CTlist}: list; + list of transformation specifications per OME-NGFF spec +\item \code{add/rmvCT}: + \code{SpatialDataElement} or \code{Zattrs} + with transformation(s) added/removed +\item \code{axes}: list; + each element is a character string (name), or list + with axis name and type (e.g., "space" or "channel") +} } \description{ -Coordinate transformations +Coord. trans. utilities } \examples{ x <- file.path("extdata", "blobs.zarr") diff --git a/man/LabelArray.Rd b/man/LabelArray.Rd index 0c3dea1b..338f60fd 100644 --- a/man/LabelArray.Rd +++ b/man/LabelArray.Rd @@ -41,6 +41,12 @@ Currently defined methods (here, \code{x} is a \code{LabelArray}): } } \examples{ -# TODO +x <- file.path("extdata", "blobs.zarr") +x <- system.file(x, package="SpatialData") +x <- file.path(x, "labels", "blobs_labels") + +(y <- readLabel(x)) +y[1:10, 1:10] +meta(y) } diff --git a/man/SDattrs.Rd b/man/SDattrs.Rd new file mode 100644 index 00000000..c5cb65ae --- /dev/null +++ b/man/SDattrs.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/Zattrs.R +\name{SDattrs} +\alias{SDattrs} +\alias{region} +\alias{region_key} +\alias{feature_key} +\alias{instance_key} +\alias{feature_key,list-method} +\alias{feature_key,PointFrame-method} +\alias{region_key,SingleCellExperiment-method} +\alias{region,SingleCellExperiment-method} +\alias{instance_key,list-method} +\alias{instance_key,PointFrame-method} +\alias{instance_key,SingleCellExperiment-method} +\title{\code{SpatialData} attributes} +\usage{ +\S4method{feature_key}{list}(x) + +\S4method{feature_key}{PointFrame}(x) + +\S4method{region_key}{SingleCellExperiment}(x) + +\S4method{region}{SingleCellExperiment}(x) + +\S4method{instance_key}{list}(x) + +\S4method{instance_key}{PointFrame}(x) + +\S4method{instance_key}{SingleCellExperiment}(x) +} +\arguments{ +\item{x}{depends on which attributes are available; +specifically, \code{PointFrame} (\code{feature/instance_key}), or +\code{SingleCellExperiment} (\code{region}, \code{region/instance_key}),} +} +\value{ +character string +} +\description{ +\code{SpatialData} attributes +} +\examples{ +x <- file.path("extdata", "blobs.zarr") +x <- system.file(x, package="SpatialData") +x <- readSpatialData(x, anndataR=TRUE) + +region(table(x)) +region_key(table(x)) + +instance_key(point(x)) +fk <- feature_key(point(x)) +base::table(point(x)[[fk]]) +} diff --git a/man/ShapeFrame.Rd b/man/ShapeFrame.Rd index 8eaa3eb3..e69d3efc 100644 --- a/man/ShapeFrame.Rd +++ b/man/ShapeFrame.Rd @@ -8,6 +8,7 @@ \alias{names,ShapeFrame-method} \alias{.DollarNames.ShapeFrame} \alias{$,ShapeFrame-method} +\alias{geom_type,ShapeFrame-method} \alias{[,ShapeFrame,missing,ANY,ANY-method} \alias{[,ShapeFrame,ANY,missing,ANY-method} \alias{[,ShapeFrame,missing,missing,ANY-method} @@ -26,6 +27,8 @@ ShapeFrame(data = data.frame(), meta = Zattrs(), metadata = list(), ...) \S4method{$}{ShapeFrame}(x, name) +\S4method{geom_type}{ShapeFrame}(x) + \S4method{[}{ShapeFrame,missing,ANY,ANY}(x, i, j, ..., drop = TRUE) \S4method{[}{ShapeFrame,ANY,missing,ANY}(x, i, j, ..., drop = TRUE) @@ -51,7 +54,7 @@ content describing the overall object.} \item{i, j}{indices specifying elements to extract.} -\item{drop}{ignored.} +\item{drop, pattern}{ignored.} } \value{ \code{ShapeFrame} diff --git a/man/SpatialData.Rd b/man/SpatialData.Rd index 5c16f970..913adda8 100644 --- a/man/SpatialData.Rd +++ b/man/SpatialData.Rd @@ -34,6 +34,8 @@ \alias{table<-} \alias{tables<-} \alias{tableNames} +\alias{[[<-,SpatialData,character,ANY-method} +\alias{[[<-,SpatialData,numeric,ANY-method} \alias{$,SpatialData-method} \alias{[[,SpatialData,numeric,ANY-method} \alias{[[,SpatialData,character,ANY-method} diff --git a/man/Zattrs.Rd b/man/Zattrs.Rd index 06ddea16..49a06fae 100644 --- a/man/Zattrs.Rd +++ b/man/Zattrs.Rd @@ -2,7 +2,6 @@ % Please edit documentation in R/Zattrs.R \name{Zattrs} \alias{Zattrs} -\alias{feature_key} \alias{$,Zattrs-method} \title{The `Zattrs` class} \usage{ diff --git a/man/blobs.Rd b/man/blobs.Rd index 1aad2796..9cd4b024 100644 --- a/man/blobs.Rd +++ b/man/blobs.Rd @@ -3,6 +3,9 @@ \name{blobs} \alias{blobs} \title{`SpatialData` .zarr toy datasets} +\value{ +zarr store. +} \description{ data were retrieved on Nov. 11th, 2024, from \href{https://github.com/scverse/spatialdata-notebooks/tree/main/notebooks/developers_resources/storage_format/multiple_elements.zarr}{here}. } diff --git a/man/do_tx_to_ext.Rd b/man/do_tx_to_ext.Rd index da7c529f..47c8656c 100644 --- a/man/do_tx_to_ext.Rd +++ b/man/do_tx_to_ext.Rd @@ -17,6 +17,9 @@ do_tx_to_ext(srcdir, dest, coordinate_system, ...) can include "maintain_positioning" (logical (1)) or numerics for target_unit_to_pixels, target_width, target_height, target_depth.} } +\value{ +\code{SpatialData} object. +} \description{ Use Python's 'spatialdata' 'transform_to_data_extent' on a spatialdata zarr store } diff --git a/man/misc.Rd b/man/misc.Rd index a67f7ec8..4c5cade5 100644 --- a/man/misc.Rd +++ b/man/misc.Rd @@ -17,14 +17,15 @@ \S4method{show}{ShapeFrame}(object) } \arguments{ -\item{object}{\code{\link{SpatialData}} object or one of its -elements, i.e., an Image/LabelArray or Point/ShapeFrame.} +\item{object}{\code{\link{SpatialData}} object or one of its elements, +i.e., an \code{Image/LabelArray} or \code{Point/ShapeFrame}.} } \value{ \code{NULL} } \description{ -... +Miscellaneous methods (e.g., \code{show}) for the +\code{\link{SpatialData}} class and its elements. } \examples{ zs <- file.path("extdata", "blobs.zarr") diff --git a/man/query.Rd b/man/query.Rd index 83d23cf4..4b566bcc 100644 --- a/man/query.Rd +++ b/man/query.Rd @@ -27,8 +27,8 @@ \item{i}{for \code{SpatialData}, index or name of table to query.} \item{y}{query specification; -bounding box: length-4 numeric list with names 'xmin/xmax/ymin/ymax' -(order is irrelevant); polygon: numeric matrix with ≥ 3 rows and 2 columns.} +bounding box: length-4 numeric list with names 'xmin/xmax/ymin/ymax'; +polygon: numeric matrix with at least 3 rows and exactly 2 columns.} } \value{ same as input diff --git a/man/Array-methods.Rd b/man/sdArray.Rd similarity index 90% rename from man/Array-methods.Rd rename to man/sdArray.Rd index 2be1a08a..cb1ca718 100644 --- a/man/Array-methods.Rd +++ b/man/sdArray.Rd @@ -1,15 +1,14 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/sdArray.R -\name{Array-methods} -\alias{Array-methods} +\name{sdArray} +\alias{sdArray} +\alias{data_type} \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_type,ImageArray-method} -\alias{data_type,LabelArray-method} \alias{data,sdArray-method} \alias{dim,sdArray-method} \alias{length,sdArray-method} diff --git a/man/table-utils.Rd b/man/table-utils.Rd index b83b2f8c..c9f700ee 100644 --- a/man/table-utils.Rd +++ b/man/table-utils.Rd @@ -56,6 +56,19 @@ or row name to retrieve \code{assay} data.} \item{assay}{character string or scalar integer; specifies which \code{assay} to use when \code{j} is a row name.} } +\value{ +\itemize{ +\item \code{hasTable}: + logical scalar (or character string, if \code{name=TRUE}); + whether or not a \code{table} annotating \code{i} exists in \code{x} +\item \code{getTable}: + \code{SingleCellExperiment}; the \code{table} annotating + \code{i} with optional filtering of matching observations +\item \code{valTable}: + vector of values (according to \code{j}) + from the \code{table} annotating \code{i} +} +} \description{ \code{SpatialData} annotations } diff --git a/man/trans.Rd b/man/trans.Rd index 4378d4eb..b4f9c87d 100644 --- a/man/trans.Rd +++ b/man/trans.Rd @@ -13,6 +13,7 @@ \alias{flop,sdArray-method} \alias{scale,sdArray,numeric-method} \alias{rotate,sdArray,numeric-method} +\alias{translation,sdArray,numeric-method} \alias{scale,PointFrame,numeric-method} \alias{rotate,PointFrame,numeric-method} \alias{translation,PointFrame,numeric-method} @@ -31,6 +32,8 @@ \S4method{rotate}{sdArray,numeric}(x, t, k = 1, ...) +\S4method{translation}{sdArray,numeric}(x, t, k = 1, ...) + \S4method{scale}{PointFrame,numeric}(x, t, ...) \S4method{rotate}{PointFrame,numeric}(x, t, ...) @@ -44,7 +47,7 @@ \S4method{translation}{ShapeFrame,numeric}(x, t, ...) } \arguments{ -\item{x}{\code{SpatialData} element} +\item{x}{\code{SpatialData} element.} \item{t}{transformation data; exceptions: for \code{mirror}, controls whether to perform \bold{v}ertical or \bold{h}orizontal reflection; @@ -55,9 +58,9 @@ no data is needed for \code{flip} (\bold{v}) and \code{flop} (\bold{h}).} only applies to \code{sdArray}s (images, labels).} \item{...}{option arguments passed to and from other methods.} - -\item{j}{scalar character or numeric; -name or index of coordinate space.} +} +\value{ +\code{SpatialData} element with transformation(s) applied. } \description{ Transformations diff --git a/man/utils.Rd b/man/utils.Rd index cc478d2b..245bc2c4 100644 --- a/man/utils.Rd +++ b/man/utils.Rd @@ -25,9 +25,16 @@ \S4method{extent}{SpatialDataElement}(x) } \arguments{ -\item{x}{a \code{SpatialData} element (any but image)} +\item{x}{a \code{SpatialData} element (any but image).} -\item{as}{determines how results will be returned} +\item{...}{optional arguments passed to and from other methods.} + +\item{as}{character string; how results should be returned.} +} +\value{ +For \code{centroids}, a table (\code{data.frame} or \code{matrix}) +of spatial coordinates (if \code{as="list"}, split by instance); +for extend, a length-2 numeric list of x- and y-ranges. } \description{ Utilities diff --git a/tests/testthat/test-PointFrame.R b/tests/testthat/test-PointFrame.R index d9397525..c8f089a1 100644 --- a/tests/testthat/test-PointFrame.R +++ b/tests/testthat/test-PointFrame.R @@ -37,7 +37,8 @@ test_that("filter", { n <- length(p <- point(x)) expect_length(filter(p), n) expect_length(filter(p, x > Inf), 0) - expect_error(filter(p, z == 1)) + f <- \() filter(p, z == 1) + expect_error(show(f())) }) test_that("select", { diff --git a/tests/testthat/test-ctgraph.R b/tests/testthat/test-ctgraph.R new file mode 100644 index 00000000..b230d71a --- /dev/null +++ b/tests/testthat/test-ctgraph.R @@ -0,0 +1,55 @@ +x <- file.path("extdata", "blobs.zarr") +x <- system.file(x, package="SpatialData") +x <- readSpatialData(x, anndataR=TRUE) + +test_that("CTgraph", { + # invalid + expect_error(CTgraph(list())) + expect_error(CTgraph(SpatialData::table(x))) + # object-wide + g <- CTgraph(x) + expect_is(g, "graph") + # graph should contain node for + # every element & transformation + ns <- lapply(setdiff(SpatialData:::.LAYERS, "tables"), + \(l) lapply(names(x[[l]]), + \(e) c(e, CTname(x[[l]][[e]])))) + ns <- sort(unique(unlist(ns))) + expect_true(all(ns %in% sort(graph::nodes(g)))) + # element-wise + for (l in setdiff(SpatialData:::.LAYERS, "tables")) + for (e in names(x[[l]])) { + y <- x[[l]][[e]] + g <- CTgraph(y) + expect_is(g, "graph") + expect_true("self" %in% graph::nodes(g)) + } +}) + +test_that("CTpath", { + i <- "blobs_image" + y <- element(x, "images", i) + z <- CTpath(y, j <- CTname(y)) + expect_identical(CTpath(x, i, j), z) + expect_is(z, "list") + expect_length(z <- z[[1]], 2) + expect_setequal(names(z), c("type", "data")) + expect_is(z$type, "character") + expect_length(z$type, 1) +}) + +test_that("CTplot", { + f <- function(.) { + tf <- tempfile(fileext=".pdf") + on.exit(unlink(tf)) + pdf(tf); .; dev.off() + file.size(tf) + } + g <- CTgraph(x) + p <- f(CTplot(g)) + expect_is(p, "numeric") + expect_true(p > f(plot(1))) + p <- f(CTplot(g, 0.1)) + q <- f(CTplot(g, 0.9)) + expect_true(p < q) +}) diff --git a/tests/testthat/test-ctutils.R b/tests/testthat/test-ctutils.R new file mode 100644 index 00000000..4f61b212 --- /dev/null +++ b/tests/testthat/test-ctutils.R @@ -0,0 +1,128 @@ +x <- file.path("extdata", "blobs.zarr") +x <- system.file(x, package="SpatialData") +x <- readSpatialData(x, anndataR=TRUE) + +.CTtype <- c( + "identity", "scale", "rotate", + "translation", "affine", "sequence") + +test_that("CTlist", { + y <- CTlist(label(x)) + expect_is(y, "list") + expect_length(y, 5) + z <- Reduce(intersect, lapply(y, names)) + expect_setequal(z, c("input", "output", "type")) + z <- vapply(y, \(.) .$type, character(1)) + expect_true(all(z %in% .CTtype)) +}) +test_that("CTdata", { + # invalid + expect_error(CTdata(label(x), "")) + expect_error(CTdata(label(x), 99)) + expect_error(CTdata(label(x), Inf)) + expect_error(CTdata(label(x), TRUE)) + # identity + y <- CTdata(label(x), "global") + expect_null(y) + # scale + y <- CTdata(label(x), "scale") + expect_is(y, "list") + expect_length(y, 2) + expect_is(unlist(y), "numeric") + expect_true(all(unlist(y) > 0)) + # translation + y <- CTdata(label(x), "translation") + expect_is(y, "list") + expect_length(y, 2) + expect_is(unlist(y), "numeric") + # affine + y <- CTdata(label(x), "affine") + expect_is(y, "list") + expect_length(y, 2) + expect_is(unlist(y), "numeric") + expect_true(all(unlist(y) > 0)) + z <- vapply(y, length, integer(1)) + expect_true(all(z == 3)) + # sequence + y <- CTdata(label(x), "sequence") + expect_is(y, "list") + expect_length(y, 2) + expect_true(all(names(y) %in% .CTtype)) + z <- vapply(y, length, integer(1)) + expect_true(all(z == 2)) +}) +test_that("CTtype", { + y <- CTtype(label(x)) + expect_is(y, "character") + expect_length(y, 5) + expect_true(all(y %in% .CTtype)) +}) +test_that("CTname,element", { + y <- CTname(label(x)) + expect_is(y, "character") + expect_length(y, 5) + expect_true(all(nchar(y) > 0)) + expect_true(!any(duplicated(y))) +}) +test_that("CTname,object", { + y <- CTname(x) + expect_is(y, "character") + expect_true(!any(duplicated(y))) + y <- CTname(image(x)) + z <- CTname(meta(image(x))) + expect_is(y, "character") + expect_length(y, 1) + expect_identical(y, z) +}) + +test_that("rmvCT", { + y <- label(x) + # invalid index/name + expect_error(rmvCT(y, 100)) + expect_error(rmvCT(y, ".")) + expect_error(rmvCT(y, c(".", CTname(y)[1]))) + # identity is kept with a warning + expect_warning(z <- rmvCT(y, "global")) + expect_identical(CTname(z), CTname(y)) + # by name + i <- sample(setdiff(CTname(y), "global"), 2) + expect_identical(CTname(rmvCT(y, i)), setdiff(CTname(y), i)) + # by index + i <- sample(which(CTtype(y) != "identity"), 2) + expect_identical(CTname(rmvCT(y, i)), CTname(y)[-i]) +}) + +test_that("addCT", { + # get 1st element from each layer + ls <- setdiff(SpatialData:::.LAYERS, "tables") + es <- lapply(ls, \(.) x[.,1][[.]][[1]]) + .check_data <- \(z, x) { + expect_true("." %in% CTname(z)) + ct <- CTlist(z)[[which(CTname(z) == ".")]] + expect_identical(ct[[t]][[1]], x) + } + for (y in es) { + t <- "identity" + expect_error(addCT(y, ".", t, 12345)) + expect_silent(z <- addCT(y, ".", t, v <- NULL)) + .check_data(z, v) + t <- "rotate" + expect_error(addCT(y, ".", t, -12345)) # negative + expect_error(addCT(y, ".", t, c(1,1))) # too many + expect_error(addCT(y, ".", t, ".")) # not a number + expect_silent(z <- addCT(y, ".", t, v <- 1)) + .check_data(z, v) + t <- "scale" + d <- ifelse(is(y, "ImageArray"), 3, 2) + expect_error(addCT(y, ".", t, numeric(d))) # zeroes + expect_error(addCT(y, ".", t, 1+numeric(d+1))) # too many + expect_error(addCT(y, ".", t, character(d))) # not a number + expect_silent(z <- addCT(y, ".", t, v <- 1+numeric(d))) + .check_data(z, v) + t <- "translation" + expect_error(addCT(y, ".", t, numeric(d+1))) # too many + expect_error(addCT(y, ".", t, character(d))) # not a number + expect_silent(z <- addCT(y, ".", t, v <- numeric(d))) + .check_data(z, v) + } +}) diff --git a/tests/testthat/test-reading.R b/tests/testthat/test-read.R similarity index 100% rename from tests/testthat/test-reading.R rename to tests/testthat/test-read.R diff --git a/tests/testthat/test-zattrs.R b/tests/testthat/test-zattrs.R index 8321fd26..ccfc0edb 100644 --- a/tests/testthat/test-zattrs.R +++ b/tests/testthat/test-zattrs.R @@ -1,204 +1,51 @@ -x <- file.path("extdata", "blobs.zarr") -x <- system.file(x, package="SpatialData") -x <- readSpatialData(x, anndataR=TRUE) - -test_that("axes", { - # image - y <- axes(image(x)) - expect_is(y, "list") - expect_length(y, 3) - # label - y <- axes(label(x)) - expect_is(y, "list") - expect_length(y, 2) - # shape - y <- axes(shape(x)) - expect_is(y, "list") - expect_length(y, 2) - expect_equal(unlist(y), c("x", "y")) - # point - y <- axes(point(x)) - expect_is(y, "list") - expect_length(y, 2) - expect_equal(unlist(y), c("x", "y")) - # missing - y <- image(x) - y@meta$multiscales[[1]]$axes <- NULL - expect_error(axes(y)) -}) - -.CTtype <- c("identity", "scale", "rotate", "translation", "affine", "sequence") - -test_that("CTlist", { - y <- CTlist(label(x)) - expect_is(y, "list") - expect_length(y, 5) - z <- Reduce(intersect, lapply(y, names)) - expect_setequal(z, c("input", "output", "type")) - z <- vapply(y, \(.) .$type, character(1)) - expect_true(all(z %in% .CTtype)) -}) -test_that("CTdata", { - # invalid - expect_error(CTdata(label(x), "")) - expect_error(CTdata(label(x), 99)) - expect_error(CTdata(label(x), Inf)) - expect_error(CTdata(label(x), TRUE)) - # identity - y <- CTdata(label(x), "global") - expect_null(y) - # scale - y <- CTdata(label(x), "scale") - expect_is(y, "list") - expect_length(y, 2) - expect_is(unlist(y), "numeric") - expect_true(all(unlist(y) > 0)) - # translation - y <- CTdata(label(x), "translation") - expect_is(y, "list") - expect_length(y, 2) - expect_is(unlist(y), "numeric") - # affine - y <- CTdata(label(x), "affine") - expect_is(y, "list") - expect_length(y, 2) - expect_is(unlist(y), "numeric") - expect_true(all(unlist(y) > 0)) - z <- vapply(y, length, integer(1)) - expect_true(all(z == 3)) - # sequence - y <- CTdata(label(x), "sequence") - expect_is(y, "list") - expect_length(y, 2) - expect_true(all(names(y) %in% .CTtype)) - z <- vapply(y, length, integer(1)) - expect_true(all(z == 2)) -}) -test_that("CTtype", { - y <- CTtype(label(x)) - expect_is(y, "character") - expect_length(y, 5) - expect_true(all(y %in% .CTtype)) -}) -test_that("CTname", { - y <- CTname(label(x)) - expect_is(y, "character") - expect_length(y, 5) - expect_true(all(nchar(y) > 0)) - expect_true(!any(duplicated(y))) -}) - -test_that("rmvCT", { - y <- label(x) - # invalid index/name - expect_error(rmvCT(y, 100)) - expect_error(rmvCT(y, ".")) - expect_error(rmvCT(y, c(".", CTname(y)[1]))) - # identity is kept with a warning - expect_warning(z <- rmvCT(y, "global")) - expect_identical(CTname(z), CTname(y)) - # by name - i <- sample(setdiff(CTname(y), "global"), 2) - expect_identical(CTname(rmvCT(y, i)), setdiff(CTname(y), i)) - # by index - i <- sample(which(CTtype(y) != "identity"), 2) - expect_identical(CTname(rmvCT(y, i)), CTname(y)[-i]) -}) - -test_that("addCT", { - # get 1st element from each layer - ls <- setdiff(SpatialData:::.LAYERS, "tables") - es <- lapply(ls, \(.) x[.,1][[.]][[1]]) - .check_data <- \(z, x) { - expect_true("." %in% CTname(z)) - ct <- CTlist(z)[[which(CTname(z) == ".")]] - expect_identical(ct[[t]][[1]], x) - } - for (y in es) { - t <- "identity" - expect_error(addCT(y, ".", t, 12345)) - expect_silent(z <- addCT(y, ".", t, v <- NULL)) - .check_data(z, v) - t <- "rotate" - expect_error(addCT(y, ".", t, -12345)) # negative - expect_error(addCT(y, ".", t, c(1,1))) # too many - expect_error(addCT(y, ".", t, ".")) # not a number - expect_silent(z <- addCT(y, ".", t, v <- 1)) - .check_data(z, v) - t <- "scale" - d <- ifelse(is(y, "ImageArray"), 3, 2) - expect_error(addCT(y, ".", t, numeric(d))) # zeroes - expect_error(addCT(y, ".", t, 1+numeric(d+1))) # too many - expect_error(addCT(y, ".", t, character(d))) # not a number - expect_silent(z <- addCT(y, ".", t, v <- 1+numeric(d))) - .check_data(z, v) - t <- "translation" - expect_error(addCT(y, ".", t, numeric(d+1))) # too many - expect_error(addCT(y, ".", t, character(d))) # not a number - expect_silent(z <- addCT(y, ".", t, v <- numeric(d))) - .check_data(z, v) - } -}) - -test_that("CTname", { - y <- CTname(x) - expect_is(y, "character") - expect_true(!any(duplicated(y))) - y <- CTname(image(x)) - z <- CTname(meta(image(x))) - expect_is(y, "character") - expect_length(y, 1) - expect_identical(y, z) -}) - -test_that("CTgraph", { - # invalid - expect_error(CTgraph(list())) - expect_error(CTgraph(SpatialData::table(x))) - # object-wide - g <- CTgraph(x) - expect_is(g, "graph") - # graph should contain node for - # every element & transformation - ns <- lapply(setdiff(SpatialData:::.LAYERS, "tables"), - \(l) lapply(names(x[[l]]), - \(e) c(e, CTname(x[[l]][[e]])))) - ns <- sort(unique(unlist(ns))) - expect_true(all(ns %in% sort(graph::nodes(g)))) - # element-wise - for (l in setdiff(SpatialData:::.LAYERS, "tables")) - for (e in names(x[[l]])) { - y <- x[[l]][[e]] - g <- CTgraph(y) - expect_is(g, "graph") - expect_true("self" %in% graph::nodes(g)) - } -}) - -test_that("CTpath", { - i <- "blobs_image" - y <- element(x, "images", i) - z <- CTpath(y, j <- CTname(y)) - expect_identical(CTpath(x, i, j), z) - expect_is(z, "list") - expect_length(z <- z[[1]], 2) - expect_setequal(names(z), c("type", "data")) - expect_is(z$type, "character") - expect_length(z$type, 1) -}) - -test_that("CTplot", { - f <- function(.) { - tf <- tempfile(fileext=".pdf") - on.exit(unlink(tf)) - pdf(tf); .; dev.off() - file.size(tf) - } - g <- CTgraph(x) - p <- f(CTplot(g)) - expect_is(p, "numeric") - expect_true(p > f(plot(1))) - p <- f(CTplot(g, 0.1)) - q <- f(CTplot(g, 0.9)) - expect_true(p < q) -}) +z <- list(v1="blobs.zarr", v3="blobs_v3.zarr") + +for (v in names(z)) { + + x <- file.path("extdata", z[[v]]) + x <- system.file(x, package="SpatialData") + x <- readSpatialData(x, anndataR=TRUE) + + test_that(paste0(v, "-multiscales"), { + y <- meta(image(x)) + z <- multiscales(y) + expect_is(z, "list") + expect_length(z, 1) + y$spatialdata_attrs <- NULL + expect_error(multiscales(y)) + }) + + test_that(paste0(v, "-axes"), { + # image + y <- axes(image(x)) + expect_is(y, "list") + expect_length(y, 3) + # label + y <- axes(label(x)) + expect_is(y, "list") + expect_length(y, 2) + # shape + y <- axes(shape(x)) + expect_is(y, "list") + expect_length(y, 2) + expect_equal(unlist(y), c("x", "y")) + # point + y <- axes(point(x)) + expect_is(y, "list") + expect_length(y, 2) + expect_equal(unlist(y), c("x", "y")) + # missing + y <- image(x) + switch(v, + "v3"=y@meta$ome$multiscales[[1]]$axes <- NULL, + y@meta$multiscales[[1]]$axes <- NULL) + expect_error(axes(y)) + }) + + test_that(paste0(v, "-channels"), { + expect_error(channels(label(x))) + expect_silent(z <- channels(y <- image(x))) + expect_length(z, dim(y)[1]) + }) + +} \ No newline at end of file