diff --git a/.github/workflows/check-bioc.yml b/.github/workflows/check-bioc.yml index 996b6f31..da719a29 100644 --- a/.github/workflows/check-bioc.yml +++ b/.github/workflows/check-bioc.yml @@ -22,7 +22,6 @@ on: push: - pull_request: name: R-CMD-check-bioc @@ -52,9 +51,10 @@ jobs: fail-fast: false matrix: config: - - { os: ubuntu-latest, r: '4.5', bioc: 'devel', cont: "bioconductor/bioconductor_docker:devel", rspm: "https://packagemanager.rstudio.com/cran/__linux__/jammy/latest" } -# - { os: macOS-latest, r: '4.5', bioc: 'devel'} -# - { os: windows-latest, r: '4.5', bioc: 'devel'} + #- { os: ubuntu-latest, r: '4.5', bioc: '3.22', cont: "bioconductor/bioconductor_docker:RELEASE_3_22", rspm: "https://p3m.dev/cran/__linux__/noble/latest" } + - { os: ubuntu-latest, r: 'devel', bioc: 'devel', cont: "bioconductor/bioconductor_docker:devel", rspm: "https://p3m.dev/cran/__linux__/noble/latest" } + - { os: macOS-latest, r: 'devel', bioc: 'devel'} + #- { os: windows-latest, r: 'devel', bioc: 'devel'} ## Check https://github.com/r-lib/actions/tree/master/examples ## for examples using the http-user-agent env: @@ -116,13 +116,6 @@ jobs: key: ${{ env.cache-version }}-${{ runner.os }}-biocversion-devel-r-4.5-${{ hashFiles('.github/depends.Rds') }} restore-keys: ${{ env.cache-version }}-${{ runner.os }}-biocversion-devel-r-4.5- - # - name: Install Linux system dependencies - # if: runner.os == 'Linux' - # run: | - # sysreqs=$(Rscript -e 'cat("apt-get update -y && apt-get install -y", paste(gsub("apt-get install -y ", "", remotes::system_requirements("ubuntu", "20.04")), collapse = " "))') - # echo $sysreqs - # sudo -s eval "$sysreqs" - - name: Install macOS system dependencies if: matrix.config.os == 'macOS-latest' run: | @@ -130,10 +123,6 @@ jobs: brew install libxml2 echo "XML_CONFIG=/usr/local/opt/libxml2/bin/xml2-config" >> $GITHUB_ENV - ## Required to install magick as noted at - ## https://github.com/r-lib/usethis/commit/f1f1e0d10c1ebc75fd4c18fa7e2de4551fd9978f#diff-9bfee71065492f63457918efcd912cf2 - brew install imagemagick@6 - ## For textshaping, required by ragg, and required by pkgdown brew install harfbuzz fribidi @@ -171,7 +160,11 @@ jobs: ## For running the checks message(paste('****', Sys.time(), 'installing rcmdcheck and BiocCheck ****')) - install.packages(c("rcmdcheck", "BiocCheck"), repos = BiocManager::repositories()) + install.packages(c("rcmdcheck", "BiocCheck", "remotes"), repos = BiocManager::repositories()) + + ## TEMP REMOVE LATER: + ## For now install Rarr from github since ZarrArray needs >= 1.11.33 + remotes::install_github("Huber-group-EMBL/Rarr") ## Pass #1 at installing dependencies message(paste('****', Sys.time(), 'pass number 1 at installing dependencies: local dependencies ****')) @@ -183,7 +176,7 @@ jobs: run: | ## Pass #2 at installing dependencies message(paste('****', Sys.time(), 'pass number 2 at installing dependencies: any remaining dependencies ****')) - remotes::install_local(dependencies = TRUE, repos = BiocManager::repositories(), build_vignettes = TRUE, upgrade = TRUE, force = TRUE) + remotes::install_local(dependencies = TRUE, repos = BiocManager::repositories(), build_vignettes = FALSE, upgrade = TRUE, force = TRUE) shell: Rscript {0} - name: Install BiocGenerics 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 1ee9afac..57838bf6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,9 +1,9 @@ Package: SpatialData Title: Representation of Python's SpatialData in R -Depends: R (>= 4.4) -Version: 0.99.23 +Depends: R (>= 4.6) +Version: 0.99.29 Description: Interface to Python's 'SpatialData', currently including: - reticulate-based use of 'spatialdata-io' for reading of manufracturer + reticulate-based use of 'spatialdata-io' for reading of manufacturer data and writing to .zarr, on-disk representation of images/labels as 'ZarrArray's ('Rarr') and shapes/points as 'arrow' objects, and method drafts for visualization and coordinate system handling. @@ -38,27 +38,28 @@ Imports: BiocGenerics, DelayedArray, dplyr, + EBImage, geoarrow, graph, - jsonlite, Matrix, methods, + ZarrArray, Rarr, RBGL, reticulate, + anndataR, sf, S4Arrays, S4Vectors, SingleCellExperiment, SummarizedExperiment, - zellkonverter, EBImage, - stringr + stringr, + jsonlite Suggests: BiocStyle, ggnewscale, knitr, - magick, patchwork, paws, Rgraphviz, @@ -71,7 +72,6 @@ Enhances: anndataR, pizzarr Remotes: - keller-mark/pizzarr, keller-mark/anndataR@spatialdata, HelenaLC/SpatialData.data, HelenaLC/SpatialData.plot diff --git a/NAMESPACE b/NAMESPACE index 14b7aeff..661f76de 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,6 +7,7 @@ S3method(.DollarNames,Zattrs) S3method(filter,PointFrame) S3method(select,PointFrame) export(.SpatialData) +export(CTplot) export(ImageArray) export(LabelArray) export(PointFrame) @@ -17,20 +18,17 @@ export(create_zarr) export(create_zarr_group) export(do_tx_to_ext) export(mask) -export(plotCoordGraph) export(readImage) export(readLabel) export(readPoint) export(readShape) export(readSpatialData) export(readTable) -export(read_zattrs) export(writeImage) export(writeLabel) export(writePoint) export(writeShape) export(writeSpatialData) -export(write_zattrs) exportClasses(SpatialData) exportMethods("$") exportMethods("[") @@ -48,22 +46,31 @@ exportMethods("table<-") exportMethods("tables<-") exportMethods(CTdata) exportMethods(CTgraph) +exportMethods(CTlist) exportMethods(CTname) exportMethods(CTpath) exportMethods(CTtype) exportMethods(addCT) exportMethods(as.data.frame) exportMethods(axes) +exportMethods(centroids) exportMethods(channels) exportMethods(colnames) exportMethods(data) +exportMethods(data_type) exportMethods(dim) exportMethods(element) +exportMethods(extent) +exportMethods(feature_key) +exportMethods(flip) +exportMethods(flop) +exportMethods(geom_type) exportMethods(getTable) exportMethods(hasTable) exportMethods(image) exportMethods(imageNames) exportMethods(images) +exportMethods(instance_key) exportMethods(label) exportMethods(labelNames) exportMethods(labels) @@ -71,11 +78,14 @@ exportMethods(layer) exportMethods(length) exportMethods(mask) exportMethods(meta) +exportMethods(mirror) exportMethods(names) exportMethods(point) exportMethods(pointNames) exportMethods(points) exportMethods(query) +exportMethods(region) +exportMethods(region_key) exportMethods(rmvCT) exportMethods(rotate) exportMethods(rownames) @@ -89,6 +99,7 @@ exportMethods(tableNames) exportMethods(tables) exportMethods(translation) exportMethods(valTable) +import(anndataR) import(geoarrow) importClassesFrom(S4Arrays,Array) importClassesFrom(S4Vectors,DFrame) @@ -98,12 +109,16 @@ importFrom(BiocGenerics,rownames) importFrom(DelayedArray,DelayedArray) importFrom(DelayedArray,realize) importFrom(EBImage,resize) +importFrom(EBImage,rotate) +importFrom(EBImage,translate) importFrom(Matrix,rowSums) +importFrom(Matrix,sparseMatrix) importFrom(Matrix,sparseVector) +importFrom(Matrix,summary) importFrom(Matrix,t) importFrom(RBGL,sp.between) -importFrom(Rarr,ZarrArray) -importFrom(S4Arrays,as.array.Array) +importFrom(Rarr,read_zarr_attributes) +importFrom(Rarr,zarr_overview) importFrom(S4Vectors,"metadata<-") importFrom(S4Vectors,coolcat) importFrom(S4Vectors,isSequence) @@ -115,9 +130,14 @@ importFrom(SingleCellExperiment,"int_metadata<-") importFrom(SingleCellExperiment,SingleCellExperiment) importFrom(SingleCellExperiment,int_colData) importFrom(SingleCellExperiment,int_metadata) +importFrom(SummarizedExperiment,"assay<-") +importFrom(SummarizedExperiment,"assayNames<-") importFrom(SummarizedExperiment,"colData<-") importFrom(SummarizedExperiment,assay) importFrom(SummarizedExperiment,colData) +importFrom(ZarrArray,ZarrArray) +importFrom(ZarrArray,path) +importFrom(ZarrArray,type) importFrom(arrow,open_dataset) importFrom(basilisk,BasiliskEnvironment) importFrom(basilisk,basiliskRun) @@ -142,7 +162,6 @@ importFrom(graph,graphAM) importFrom(graph,nodeData) importFrom(graph,nodes) importFrom(jsonlite,fromJSON) -importFrom(jsonlite,read_json) importFrom(jsonlite,toJSON) importFrom(methods,as) importFrom(methods,callNextMethod) @@ -153,16 +172,15 @@ importFrom(methods,setReplaceMethod) importFrom(reticulate,import) importFrom(sf,"st_geometry<-") importFrom(sf,st_as_sf) +importFrom(sf,st_bbox) importFrom(sf,st_coordinates) +importFrom(sf,st_crop) importFrom(sf,st_distance) importFrom(sf,st_geometry) importFrom(sf,st_geometry_type) -importFrom(sf,st_point) -importFrom(sf,st_sfc) +importFrom(sf,st_intersects) +importFrom(sf,st_polygon) importFrom(stats,setNames) -importFrom(stringr,str_extract) -importFrom(stringr,str_remove) importFrom(utils,.DollarNames) importFrom(utils,head) importFrom(utils,tail) -importFrom(zellkonverter,AnnData2SCE) diff --git a/R/AllGenerics.R b/R/AllGenerics.R index 6e0a9488..62ae3d87 100644 --- a/R/AllGenerics.R +++ b/R/AllGenerics.R @@ -22,18 +22,6 @@ setGeneric("shapeNames", \(x, ...) standardGeneric("shapeNames")) setGeneric("pointNames", \(x, ...) standardGeneric("pointNames")) setGeneric("tableNames", \(x, ...) standardGeneric("tableNames")) -setMethod("images", "SpatialData", \(x) x$images) -setMethod("labels", "SpatialData", \(x) x$labels) -setMethod("shapes", "SpatialData", \(x) x$shapes) -setMethod("points", "SpatialData", \(x) x$points) -setMethod("tables", "SpatialData", \(x) x$tables) - -setMethod("imageNames", "SpatialData", \(x) names(images(x))) -setMethod("labelNames", "SpatialData", \(x) names(labels(x))) -setMethod("shapeNames", "SpatialData", \(x) names(shapes(x))) -setMethod("pointNames", "SpatialData", \(x) names(points(x))) -setMethod("tableNames", "SpatialData", \(x) names(tables(x))) - # set one ----- setGeneric("image<-", \(x, i, ..., value) standardGeneric("image<-")) @@ -52,7 +40,7 @@ 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")) setGeneric("CTtype", \(x, ...) standardGeneric("CTtype")) @@ -68,6 +56,17 @@ setGeneric("rotate", \(x, t, ...) standardGeneric("rotate")) setGeneric("transform", \(x, ...) standardGeneric("transform")) setGeneric("translation", \(x, t, ...) standardGeneric("translation")) +setGeneric("flip", \(x, ...) standardGeneric("flip")) +setGeneric("flop", \(x, ...) standardGeneric("flop")) +setGeneric("mirror", \(x, ...) standardGeneric("mirror")) + +# sda ---- + +setGeneric("region", \(x, ...) standardGeneric("region")) +setGeneric("region_key", \(x, ...) standardGeneric("region_key")) +setGeneric("feature_key", \(x, ...) standardGeneric("feature_key")) +setGeneric("instance_key", \(x, ...) standardGeneric("instance_key")) + # uts ---- setGeneric("layer", \(x, i, ...) standardGeneric("layer")) @@ -79,7 +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("multiscales", \(x, ...) standardGeneric("multiscales")) # tbl ---- diff --git a/R/CTgraph.R b/R/CTgraph.R new file mode 100644 index 00000000..a1a2dd40 --- /dev/null +++ b/R/CTgraph.R @@ -0,0 +1,181 @@ +#' @name CTgraph +#' @title Coord. trans. graph +#' @aliases CTgraph CTpath CTplot +#' +#' @param x \code{SpatialData}, an element, or \code{Zattrs}. +#' @param i character string; name of source node. +#' @param j character string; name of target coordinate space. +#' @param g base R graph; extracted with \code{CTgraph}. +#' @param cex scalar numeric; controls fontsize of node labels. +#' @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") +#' x <- readSpatialData(x, tables=FALSE) +#' +#' # object-wide +#' g <- CTgraph(x) +#' CTplot(g) +#' +#' # one element +#' y <- label(x) +#' g <- CTgraph(y) +#' CTplot(g) +#' +#' # retrieve transformation(s) +#' # from element to target space +#' CTpath(x, "blobs_labels", "sequence") +NULL + +#' @rdname CTgraph +#' @export +setMethod("CTgraph", "SpatialData", \(x) { + names(ls) <- ls <- setdiff(.LAYERS, "tables") + md <- lapply(ls, \(l) { + names(es) <- es <- names(x[[l]]) + lapply(es, \(e) meta(x[[l]][[e]])) + }) + .make_g(md) +}) + +#' @rdname CTgraph +#' @export +setMethod("CTgraph", "SpatialDataElement", \(x) + .make_g(list("mock"=list("self"=meta(x))))) + +#' @rdname CTgraph +#' @export +setMethod("CTgraph", "ANY", \(x) stop("'x' should be a", + " 'SpatialData' object, or a non-'table' element")) + +#' @importFrom graph graphAM nodeDataDefaults<- edgeDataDefaults<- +.init_g <- \() { + g <- graphAM(edgemode="directed") + edgeDataDefaults(g, "data") <- list() + edgeDataDefaults(g, "type") <- character() + nodeDataDefaults(g, "type") <- character() + return(g) +} + +#' @importFrom graph nodes addNode addEdge nodeData<- edgeData<- +.make_g <- \(md) { + g <- .init_g() + for (l in names(md)) for (e in names(md[[l]])) { + .md <- md[[l]][[e]] + ms <- .md$multiscales + if (!is.null(ms)) .md <- ms[[1]] + ct <- .md$coordinateTransformations + g <- addNode(e, g) + nodeData(g, e, "type") <- "element" + for (i in seq_along(ct)) { + n <- ct[[i]]$output$name + if (!n %in% nodes(g)) { + g <- addNode(n, g) + nodeData(g, n, "type") <- "space" + } + t <- ct[[i]]$type + if (t == "sequence") { + sq <- ct[[i]]$transformations + . <- e + for (j in seq_along(sq)) { + if (j == length(sq)) { + m <- n + } else { + m <- paste(e, n, j, sep="_") + g <- addNode(m, g) + nodeData(g, m, "type") <- "none" + } + t <- sq[[j]]$type + d <- sq[[j]][[t]] + g <- addEdge(., m, g) + edgeData(g, ., m, "type") <- t + edgeData(g, ., m, "data") <- list(d) + . <- m + } + } else { + g <- addEdge(e, n, g) + d <- ct[[i]][[ct[[i]]$type]] + edgeData(g, e, n, "type") <- t + edgeData(g, e, n, "data") <- list(d) + } + } + } + return(g) +} + +# path ---- + +#' @importFrom graph edgeData +#' @importFrom RBGL sp.between +.path_ij <- \(g, i, j) { + p <- sp.between(g, i, j) + p <- p[[1]]$path_detail + n <- length(p)-1 + lapply(seq_len(n), \(.) edgeData(g, p[.], p[.+1])[[1]]) +} + +#' @rdname CTgraph +#' @export +setMethod("CTpath", "SpatialData", \(x, i, j) { + g <- CTgraph(x) + .path_ij(g, i, j) +}) + +#' @rdname CTgraph +#' @export +setMethod("CTpath", "SpatialDataElement", \(x, j) { + g <- CTgraph(x) + .path_ij(g, "self", j) +}) + +#' @rdname CTgraph +#' @export +setMethod("CTpath", "ANY", \(x) stop("'x' should be a", + " 'SpatialData' object, or a non-'table' element")) + +# plot ---- + +#' @importFrom graph nodes nodes<- graph.par +#' @rdname CTgraph +#' @export +CTplot <- \(g, cex=0.5, fac=2, max=10) { + if (!requireNamespace("Rgraphviz", quietly=TRUE)) + stop("Install 'Rgraphviz' to use this function") + g2view <- g # leave 'g' alone + nodes(g2view) <- .nodefix(nodes(g2view), fac=fac, max=max) + graph.par(list(nodes=list(shape="plaintext", cex=cex))) + g2view <- Rgraphviz::layoutGraph(g2view) + Rgraphviz::renderGraph(g2view) +} + +.nodefix <- \(x, fac=2, max=10) { + fix <- nchar(x) > max + if (!any(fix)) return(x) + x[fix] <- .fixup(x[fix], fac) + x +} + +.fixup <- \(x, fac) { + xs <- strsplit(x, "") + nc <- floor(nchar(x)/fac) + vapply(seq_along(xs), \(i) { + j <- seq_len(nc[i]) + y <- c(xs[[i]][j], "-\n", xs[[i]][-j]) + paste(y, collapse="") + }, character(1)) +} diff --git a/R/CTutils.R b/R/CTutils.R new file mode 100644 index 00000000..124c0929 --- /dev/null +++ b/R/CTutils.R @@ -0,0 +1,227 @@ +#' @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 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") +#' x <- readSpatialData(x, tables=FALSE) +#' +#' # view available target coordinate systems +#' CTname(z <- meta(label(x))) +#' +#' # add +#' addCT(z, "scale", "scale", c(12, 34)) # overwrite +#' CTname(addCT(z, "new", "translation", c(12, 34))) +#' +#' # rmv +#' CTname(rmvCT(z, 2)) # by index +#' CTname(rmvCT(z, "scale")) # by name +#' CTname(rmvCT(z, "global")) # identity is protected +NULL + +# TODO: currently applying transformations only on 'data.frame's for plotting, +# not the actual data (e.g., image)... but this might be necessary for queries? + +# TODO: for all layers, implement all transformations +# (translate, scale, rotate, affine, and sequential) + +# axes() ---- + +#' @rdname CTutils +#' @export +setMethod("axes", "Zattrs", \(x, ...) { + ms <- multiscales(x) + if (!is.null(ms)) x <- ms[[1]] + if (is.null(x <- x$axes)) stop("couldn't find 'axes'") + return(x) +}) + +#' @rdname CTutils +#' @export +setMethod("axes", "SpatialDataElement", \(x, ...) axes(meta(x))) + +# CTlist/data/type/name() ---- + +#' @rdname CTutils +#' @export +setMethod("CTlist", "Zattrs", \(x, ...) { + ms <- multiscales(x) + ct <- "coordinateTransformations" + if (is.null(ms)) return(x[[ct]]) + ms[[1]][[ct]] +}) + +#' @rdname CTutils +#' @export +setMethod("CTdata", "Zattrs", \(x, i=1, ...) { + stopifnot(length(i) == 1) + if (is.character(i)) { + match.arg(i, CTname(x)) + i <- match(i, CTname(x)) + } else if (is.numeric(i)) { + stopifnot( + i == round(i), + i %in% seq_along(CTlist(x))) + } else stop("Invalid 'i'; should be a scalar character or integer") + t <- CTtype(x)[i] + if (t != "sequence") + return(CTlist(x)[[i]][[t]]) + ts <- CTlist(x)[[i]]$transformations + names(ts) <- vapply(ts, \(.) .$type, character(1)) + mapply(x=ts, i=names(ts), \(x, i) x[[i]], SIMPLIFY=FALSE) +}) + +#' @rdname CTutils +#' @export +setMethod("CTtype", "Zattrs", \(x, ...) { + vapply(CTlist(x), \(.) .$type, character(1)) +}) + +#' @rdname CTutils +#' @export +setMethod("CTname", "Zattrs", \(x, ...) { + vapply(CTlist(x), \(.) .$output$name, character(1)) +}) + +#' @rdname CTutils +#' @export +setMethod("CTlist", "SpatialDataElement", \(x, ...) CTlist(meta(x))) + +#' @rdname CTutils +#' @export +setMethod("CTdata", "SpatialDataElement", \(x, i=1, ...) CTdata(meta(x), i)) + +#' @rdname CTutils +#' @export +setMethod("CTtype", "SpatialDataElement", \(x, ...) CTtype(meta(x))) + +#' @rdname CTutils +#' @export +setMethod("CTname", "SpatialDataElement", \(x, ...) CTname(meta(x))) + +#' @rdname CTutils +#' @export +setMethod("CTname", "SpatialData", \(x, ...) { + g <- CTgraph(x) + t <- nodeData(g, nodes(g), "type") + names(t)[unlist(t) == "space"] +}) + +# rmv ---- + +#' @rdname CTutils +#' @export +setMethod("rmvCT", "SpatialDataElement", + \(x, i) { x@meta <- rmvCT(meta(x), i); x }) + +#' @rdname CTutils +#' @export +setMethod("rmvCT", "Zattrs", \(x, i) { + nms <- CTname(x) + if (is.numeric(i)) { + if (any(i > length(nms))) + stop("invalid 'i'") + i <- nms[i] + } + nan <- setdiff(i, nms) + if (length(nan)) stop( + "couldn't find 'coordTrans' of name(s) ", + paste(dQuote(nan), collapse=",")) + i <- match(i, nms) + # protect against dropping identity + i <- i[CTtype(x)[i] != "identity"] + if (!length(i)) { + warning("can't drop identity") + return(x) + } + ms <- "multiscales" + ct <- "coordinateTransformations" + if (length(i)) { + if (is.null(x[[ms]])) { + x[[ct]] <- x[[ct]][-i] + } else { + y <- x[[ms]][[1]][[ct]][-i] + x[[ms]][[1]][[ct]] <- y + } + } + return(x) +}) + +# add ---- + +#' @rdname CTutils +#' @export +setMethod("addCT", "SpatialDataElement", \(x, name, type, data) { + x@meta <- addCT(meta(x), name, type, data); x }) + +.check_ct <- \(x, type, data) { + d <- length(axes(x)) + f <- \(t) stop("invalid 'data' for transformation of 'type' ", dQuote(t)) + t <- match.arg(type, c("identity", "scale", "rotate", "translation", "affine")) + . <- switch(t, + identity=is.null(data), + translation=length(data) == d & is.numeric(data), + rotate=length(data) == 1 & is.numeric(data) & data > 0, + scale=length(data) == d & is.numeric(data) & all(data > 0), + TRUE) + if (!.) f(t) +} + +#' @rdname CTutils +#' @export +setMethod("addCT", "Zattrs", \(x, name, type="identity", data=NULL) { + stopifnot( + is.character(name), length(name) == 1, + is.character(type), length(type) == 1) + .check_ct(x, type, data) + # use existing as skeleton + old <- CTlist(x) + new <- old[[1]][c("input", "output", "type")] + new$type <- type + new$output$name <- name + new[[new$type]] <- list(data) + # append/overwrite & stash + ms <- "multiscales" + ct <- "coordinateTransformations" + i <- match(name, CTname(x)) + if (is.na(i)) { + new <- c(old, list(new)) + } else { + old[[i]] <- new + new <- old + } + if (is.null(x[[ms]])) { + x[[ct]] <- new + } else { + x[[ms]][[1]][[ct]] <- new + } + return(x) +}) diff --git a/R/ImageArray.R b/R/ImageArray.R index 7f67b987..a0e9f812 100644 --- a/R/ImageArray.R +++ b/R/ImageArray.R @@ -2,7 +2,7 @@ #' @title The `ImageArray` class #' #' @param x \code{ImageArray} -#' @param data list of \code{\link[Rarr]{ZarrArray}}s +#' @param data list of \code{\link[ZarrArray]{ZarrArray}}s #' @param meta \code{\link{Zattrs}} #' @param metadata optional list of arbitrary #' content describing the overall object. @@ -18,17 +18,18 @@ #' #' @examples #' library(SpatialData.data) -#' dir.create(td <- tempfile()) -#' pa <- SpatialData.data:::.unzip_merfish_demo(td) -#' pa <- file.path(pa, "images", "rasterized") +#' zs <- get_demo_SDdata("merfish") +#' pa <- file.path(zs, "images", "rasterized") #' (ia <- readImage(pa)) #' #' @importFrom S4Vectors metadata<- #' @importFrom methods new #' @importFrom DelayedArray DelayedArray #' @export -ImageArray <- function(data=list(), meta=Zattrs(), metadata=list(), +ImageArray <- function(data=list(), meta=Zattrs(), metadata=list(), multiscale=FALSE, axes = NULL, ...) { + if (!missing(data) && is.list(data) && length(data) == 0) + stop("'data' must not be an empty list; use ImageArray() for an empty placeholder") if(!is.list(data)){ if(multiscale){ data <- .generate_multiscale(data, axes = axes, method = "image") @@ -36,11 +37,11 @@ ImageArray <- function(data=list(), meta=Zattrs(), metadata=list(), data <- list(DelayedArray::DelayedArray(data)) } } - if(length(meta) < 1){ - meta <- .make_image_meta(data, - version = 0.4, + if(length(meta) < 1 && length(data) > 0){ + meta <- .make_image_meta(data, + version = 0.4, axes = axes) - } + } x <- .ImageArray(data=data, meta=meta, ...) metadata(x) <- metadata return(x) @@ -49,60 +50,58 @@ ImageArray <- function(data=list(), meta=Zattrs(), metadata=list(), #' @rdname ImageArray #' @aliases channels #' @export -setMethod("channels", "ImageArray", \(x, ...) meta(x)$omero$channels$label) +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 +#' @export +setMethod("channels", "ImageArray", \(x, ...) channels(meta(x))) #' @rdname ImageArray #' @export setMethod("channels", "ANY", \(x, ...) stop("only 'images' have channels")) #' @importFrom S4Vectors isSequence -.get_multiscales_dataset_paths <- function(md) { - - # validate multiscales attributes - .validate_multiscales_dataset_path(md) - - # get paths - paths <- md$multiscales$datasets[[1]]$path - paths <- suppressWarnings({as.numeric(sort(paths, decreasing=FALSE))}) - - # TODO: how to check if a vector of values here are integers - # check paths and return - # if(all(paths %% 0 == 0)){ - # if(S4Vectors::isSequence(paths)) - # return(paths) - # } - return(paths) - - # stop if not a sequence of integers - stop("ImageArray paths are ill-defined, should be e.g. 0,1,2, ..., n") +.get_multiscales_dataset_paths <- function(za) { + # validate 'multiscales' + ms <- .check_ms(za) + # get & validate 'path's + ds <- ms[[1]]$datasets + ps <- vapply(ds, \(.) .$path, character(1)) + ps <- suppressWarnings(as.numeric(sort(ps, decreasing=FALSE))) + if (length(ps)) { + qs <- seq(min(ps), max(ps)) + if (!isTRUE(all.equal(ps, qs))) + stop("ImageArray paths are ill-defined, should", + " be an integer sequence, e.g., 0,1,...,n") + } + return(ps) } -#' @noRd -.validate_multiscales_dataset_path <- function(md) { - # validate 'multiscales' - if ("multiscales" %in% names(md)) { - ms <- md[["multiscales"]] - +.check_ms <- \(za) { + # validate 'multiscales' + ms <- multiscales(za) + if (!is.null(ms)) { # validate 'datasets' - if("datasets" %in% names(ms)) { - ds <- ms[["datasets"]] - - # validate 'paths' - valid <- vapply(ds, \(ds) "path" %in% colnames(ds), logical(1)) - - if (!all(valid)) { - stop("'ImageArray' paths are ill-defined,", - " no 'path' attribute under 'multiscale-datasets'") - } - - } else { - stop("'ImageArray' paths are ill-defined,", - " no 'datasets' attribute under 'multiscale'") - } - } else { - stop("'ImageArray' paths are ill-defined,", - " no 'multiscales' attribute under '.zattrs'") - } + ds <- ms[[1]]$datasets + if (!is.null(ds)) { + # validate 'paths' + ok <- vapply(ds, \(.) !is.null(.$path), logical(1)) + if (!all(ok)) + stop("'ImageArray' paths are ill-defined,", + " no 'path' attribute under 'multiscale-datasets'") + } else stop( + "'ImageArray' paths are ill-defined,", + " no 'datasets' attribute under 'multiscale'") + } 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 c67bdfda..1da4cee2 100644 --- a/R/LabelArray.R +++ b/R/LabelArray.R @@ -14,7 +14,7 @@ #' } #' #' @param x \code{LabelArray} -#' @param data list of \code{\link[Rarr]{ZarrArray}}s +#' @param data list of \code{\link[ZarrArray]{ZarrArray}}s #' @param meta \code{\link{Zattrs}} #' @param metadata optional list of arbitrary #' content describing the overall object. @@ -28,13 +28,21 @@ #' @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 #' @export -LabelArray <- function(data=array(), meta=Zattrs(), metadata=list(), +LabelArray <- function(data=list(), meta=Zattrs(), metadata=list(), multiscale = FALSE, axes = NULL, ...) { + if (!missing(data) && is.list(data) && length(data) == 0) + stop("'data' must not be an empty list; use LabelArray() for an empty placeholder") if(!is.list(data)){ if(multiscale){ data <- .generate_multiscale(data, axes = axes, method = "label") @@ -42,11 +50,11 @@ LabelArray <- function(data=array(), meta=Zattrs(), metadata=list(), data <- list(DelayedArray::DelayedArray(data)) } } - if(length(meta) < 1){ - meta <- .make_label_meta(data, - version = 0.4, + if(length(meta) < 1 && length(data) > 0){ + meta <- .make_label_meta(data, + version = 0.4, axes = axes) - } + } x <- .LabelArray(data=data, meta=meta, ...) metadata(x) <- metadata return(x) diff --git a/R/PointFrame.R b/R/PointFrame.R index 74a6f77c..88b015f7 100644 --- a/R/PointFrame.R +++ b/R/PointFrame.R @@ -32,9 +32,8 @@ #' #' @examples #' library(SpatialData.data) -#' dir.create(tf <- tempfile()) -#' base <- SpatialData.data:::.unzip_merfish_demo(tf) -#' x <- file.path(base, "points", "single_molecule") +#' zs <- get_demo_SDdata("merfish") +#' x <- file.path(zs, "points", "single_molecule") #' (p <- readPoint(x)) #' #' head(as.data.frame(data(p))) diff --git a/R/ShapeFrame.R b/R/ShapeFrame.R index 2a9ad2b5..f6795436 100644 --- a/R/ShapeFrame.R +++ b/R/ShapeFrame.R @@ -1,5 +1,6 @@ #' @name ShapeFrame #' @title The `ShapeFrame` class +#' @aliases geom_type #' #' @param x \code{ShapeFrame} #' @param data \code{arrow}-derived table for on-disk, @@ -9,20 +10,20 @@ #' 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} #' #' @examples #' library(SpatialData.data) -#' dir.create(tf <- tempfile()) -#' base <- SpatialData.data:::.unzip_merfish_demo(tf) -#' y <- file.path(base, "shapes", "cells") +#' zs <- get_demo_SDdata("merfish") +#' +#' y <- file.path(zs, "shapes", "cells") #' (s <- readShape(y)) #' plot(sf::st_as_sf(data(s)), cex=0.2) #' -#' y <- file.path(base, "shapes", "anatomical") +#' y <- file.path(zs, "shapes", "anatomical") #' (s <- readShape(y)) #' plot(sf::st_as_sf(data(s)), cex=0.2) #' @@ -56,16 +57,25 @@ setMethod("length", "ShapeFrame", \(x) nrow(data(x))) #' @export setMethod("names", "ShapeFrame", \(x) names(data(x))) -#' @importFrom utils .DollarNames #' @export -.DollarNames.ShapeFrame <- \(x, pattern="") { +#' @rdname ShapeFrame +#' @importFrom utils .DollarNames +.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, ])) + z <- st_geometry_type(y) + return(as.character(z)) +}) + # sub ---- #' @rdname ShapeFrame diff --git a/R/SpatialData.R b/R/SpatialData.R index b27b9239..0db13141 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 ... #' @@ -31,7 +33,7 @@ #' @examples #' x <- file.path("extdata", "blobs.zarr") #' x <- system.file(x, package="SpatialData") -#' (x <- readSpatialData(x, anndataR=FALSE)) +#' (x <- readSpatialData(x, anndataR=TRUE)) #' #' # subsetting #' # layers are taken in order of appearance diff --git a/R/Zattrs.R b/R/Zattrs.R index 854443cf..eb969bec 100644 --- a/R/Zattrs.R +++ b/R/Zattrs.R @@ -1,6 +1,6 @@ #' @name Zattrs #' @title The `Zattrs` class -#' +#' #' @param x list extracted from a OME-NGFF compliant .zattrs file. #' @param name character string for extraction (see ?base::`$`). #' @@ -11,11 +11,13 @@ #' x <- system.file(x, package="SpatialData") #' x <- readSpatialData(x, tables=FALSE) #' -#' z <- meta(label(x)) -#' axes(z) -#' CTdata(z) +#' (z <- meta(label(x))) +#' #' CTname(z) #' CTtype(z) +#' CTdata(z, "scale") +#' +#' feature_key(point(x)) #' #' @export Zattrs <- \(x=list()) { @@ -33,3 +35,108 @@ Zattrs <- \(x=list()) { #' @rdname Zattrs #' @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) + cat(sprintf("axes(%d):\n", length(ax))) + if (is.character(ax[[1]])) { + cat("- name:", unlist(ax), "\n") + } else { + cat("- name:", vapply(ax, \(.) .$name, character(1)), "\n") + cat("- type:", vapply(ax, \(.) .$type, character(1)), "\n") + } + # TODO: more detailed 'sequence' display + cat(sprintf("coordTrans(%d):\n", n <- length(CTname(object)))) + g <- \(.) { + . <- paste(unlist(.), collapse=",") + if (!grepl(",", .)) return(.) + sprintf("[%s]", .) + } + f <- \(.) { + if (is.null(.)) return("") + paste0(":", g(lapply(., g))) + } + for (i in seq_len(n)) + cat(sprintf("- %s: (%s%s)\n", + CTname(object)[i], + CTtype(object)[i], + f(CTlist(object)[[i]][[CTtype(object)[i]]]))) + ms <- object$multiscales[[1]] + if (!is.null(ms)) { + ds <- ms$datasets + ps <- vapply(ds, \(.) .$path, character(1)) + coolcat("datasets(%d): %s\n", ps) + for (i in seq_along(ds)) { + ct <- ds[[i]]$coordinateTransformations[[1]] + cat(sprintf("- %s: (%s:%s)\n", + ps[i], ct$type, g(ct[[ct$type]]))) + } + } + cs <- unlist(channels(object)) + if (!is.null(cs)) coolcat("channels(%d): %s\n", cs) +} +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/coord_utils.R b/R/coord_utils.R deleted file mode 100644 index 04734da3..00000000 --- a/R/coord_utils.R +++ /dev/null @@ -1,443 +0,0 @@ -#' @name coord-utils -#' @title Coordinate transformations -#' @aliases axes CTname CTtype CTdata CTpath CTgraph 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. -#' -#' @examples -#' x <- file.path("extdata", "blobs.zarr") -#' x <- system.file(x, package="SpatialData") -#' x <- readSpatialData(x, tables=FALSE) -#' -#' # element-wise -#' g <- CTgraph(y <- image(x)) -#' graph::nodes(g) -#' CTpath(y, "global") -#' -#' # object-wide -#' g <- CTgraph(x) -#' plotCoordGraph(g) -#' -#' # retrieve transformation from element to target space -#' CTpath(x, "blobs_labels", "sequence") -#' -#' # view available coordinate transformations -#' CTdata(z <- meta(label(x))) -#' -#' # add -#' addCT(z, "scale", "scale", c(12, 34)) # can't overwrite -#' CTdata(addCT(z, "new", "translation", c(12, 34))) -#' -#' # rmv -#' CTdata(rmvCT(z, 2)) # by index -#' CTdata(rmvCT(z, "scale")) # by name -#' CTdata(rmvCT(z, 1)) # identity is protected -NULL - -# TODO: currently applying transformations only on 'data.frame's for plotting, -# not the actual data (e.g., image)... but this might be necessary for queries? - -# TODO: for all layers, implement all transformations -# (translate, scale, rotate, affine, and sequential) - -# axes() ---- - -#' @rdname coord-utils -#' @export -setMethod("axes", "Zattrs", \(x, ...) { - if (!is.null(ms <- x$multiscales)) x <- ms - if (is.null(x <- x$axes)) stop("couldn't find 'axes'") - if (is.character(x)) x else x[[1]] -}) - -#' @rdname coord-utils -#' @export -setMethod("axes", "SpatialDataElement", \(x, ...) axes(meta(x))) - -# CTdata/type/name() ---- - -#' @rdname coord-utils -#' @export -setMethod("CTdata", "Zattrs", \(x, ...) { - ms <- x$multiscales - if (!is.null(ms)) x <- ms - x <- x$coordinateTransformations - if (is.null(dim(x))) x[[1]] else x -}) - -#' @rdname coord-utils -#' @export -setMethod("CTdata", "SpatialDataElement", \(x, ...) CTdata(meta(x))) - -#' @rdname coord-utils -#' @export -setMethod("CTtype", "Zattrs", \(x, ...) CTdata(x)$type) - -#' @rdname coord-utils -#' @export -setMethod("CTtype", "SpatialDataElement", \(x, ...) CTtype(meta(x))) - -#' @rdname coord-utils -#' @export -setMethod("CTname", "Zattrs", \(x, ...) CTdata(x)$output$name) - -#' @rdname coord-utils -#' @export -setMethod("CTname", "SpatialDataElement", \(x, ...) CTname(meta(x))) - -#' @rdname coord-utils -#' @export -setMethod("CTname", "SpatialData", \(x, ...) { - g <- CTgraph(x) - t <- nodeData(g, nodes(g), "type") - names(t)[unlist(t) == "space"] -}) - -# CTgraph() ---- - -#' @rdname coord-utils -#' @export -setMethod("CTgraph", "SpatialData", \(x) { - names(ls) <- ls <- setdiff(.LAYERS, "tables") - md <- lapply(ls, \(l) { - names(es) <- es <- names(x[[l]]) - lapply(es, \(e) meta(x[[l]][[e]])) - }) - .make_g(md) -}) - -#' @rdname coord-utils -#' @export -setMethod("CTgraph", "SpatialDataElement", \(x) - .make_g(list("mock"=list("self"=meta(x))))) - -#' @rdname coord-utils -#' @export -setMethod("CTgraph", "ANY", \(x) stop("'x' should be a", - " 'SpatialData' object, or a non-'table' element")) - -#' @importFrom graph graphAM nodeDataDefaults<- edgeDataDefaults<- -.init_g <- \() { - g <- graphAM(edgemode="directed") - edgeDataDefaults(g, "data") <- list() - edgeDataDefaults(g, "type") <- character() - nodeDataDefaults(g, "type") <- character() - return(g) -} - -#' @importFrom graph nodes addNode addEdge nodeData<- edgeData<- -.make_g <- \(md) { - g <- .init_g() - for (l in names(md)) for (e in names(md[[l]])) { - .md <- md[[l]][[e]] - ms <- .md$multiscales - if (!is.null(ms)) .md <- ms - ct <- .md$coordinateTransformations - ct <- if (length(ct) == 1) ct[[1]] else ct - g <- addNode(e, g) - nodeData(g, e, "type") <- "element" - for (i in seq(nrow(ct))) { - n <- ct$output$name[i] - if (!n %in% nodes(g)) { - g <- addNode(n, g) - nodeData(g, n, "type") <- "space" - } - t <- ct$type[i] - if (t == "sequence") { - sq <- ct$transformations[i][[1]] - . <- e - for (j in seq(nrow(sq))) { - if (j == nrow(sq)) { - m <- n - } else { - m <- paste(e, n, j, sep="_") - g <- addNode(m, g) - nodeData(g, m, "type") <- "none" - } - t <- sq$type[j] - d <- sq[[t]][j] - g <- addEdge(., m, g) - edgeData(g, ., m, "type") <- t - edgeData(g, ., m, "data") <- d - . <- m - } - } else { - g <- addEdge(e, n, g) - d <- ct[[ct$type[i]]][i] - edgeData(g, e, n, "type") <- t - edgeData(g, e, n, "data") <- d - } - } - } - return(g) -} - -# CTpath() ---- - -#' @rdname coord-utils -#' @export -setMethod("CTpath", "SpatialData", \(x, i, j) { - g <- CTgraph(x) - .path_ij(g, i, j) -}) - -#' @rdname coord-utils -#' @export -setMethod("CTpath", "SpatialDataElement", \(x, j) { - g <- CTgraph(x) - .path_ij(g, "self", j) -}) - -#' @importFrom graph edgeData -#' @importFrom RBGL sp.between -.path_ij <- \(g, i, j) { - p <- sp.between(g, i, j) - p <- p[[1]]$path_detail - n <- length(p)-1 - lapply(seq_len(n), \(.) - edgeData(g, p[.], p[.+1])[[1]]) -} - -# rmv ---- - -#' @rdname coord-utils -#' @export -setMethod("rmvCT", "SpatialDataElement", - \(x, i) { x@meta <- rmvCT(meta(x), i); x }) - -#' @rdname coord-utils -#' @export -setMethod("rmvCT", "Zattrs", \(x, i) { - nms <- CTname(x) - if (is.numeric(i)) { - if (any(i > length(nms))) - stop("invalid 'i'") - i <- nms[i] - } - nan <- setdiff(i, nms) - if (length(nan)) stop( - "couln't find 'coordTrans' of name(s) ", - paste(dQuote(nan), collapse=",")) - i <- match(i, nms) - # # prevent against dropping identity - # i <- i[CTtype(x)[i] != "identity"] - ms <- "multiscales" - ct <- "coordinateTransformations" - if (length(i)) { - # utility to drop empty columns - j <- \(.) vapply(., \(.) !is.null(unlist(.)), logical(1)) - if (!is.null(x[[ms]])) { - y <- x[[ms]][[ct]][[1]][-i, ] - x[[ms]][[ct]][[1]] <- y[, j(y)] - } else { - y <- x[[ct]][-i, ] - x[[ct]] <- y[, j(y)] - } - } - return(x) -}) - -# add ---- - -#' @rdname coord-utils -#' @export -setMethod("addCT", "SpatialDataElement", \(x, name, type, data) { - x@meta <- addCT(meta(x), name, type, data); x }) - -.check_ct <- \(x, type, data) { - d <- ifelse(is.character(a <- axes(x)), length(a), nrow(a)) - f <- \(t) stop("invalid 'data' for transformation of 'type' ", dQuote(t)) - t <- match.arg(type, c("identity", "scale", "rotate", "translation", "affine")) - . <- switch(t, - identity=is.null(data), - translation=length(data) == d & is.numeric(data), - rotate=length(data) == 1 & is.numeric(data) & data > 0, - scale=length(data) == d & is.numeric(data) & all(data > 0), - TRUE) - if (!.) f(t) -} - -# make ---- - -.make_axes_meta <- function(x, unit = FALSE){ - lapply(x, \(.){ - meta <- list(names = ., - type = if(. == "c") "channel" else "space") - if(unit) - meta <- c(meta, list(unit = "unit")) - meta - }) -} - -.make_empty_ct <- function(x){ - space <- .make_axes_meta(x, unit = TRUE) - input <- list(axes = space, - name = paste(x, collapse = "")) - output <- list(axes = space, name = "global") - meta <- list( - list(input = input, - output = output, - type = "identity") - ) - meta -} - -.make_datasets <- function(x, axes){ - paths <- paste0(seq_len(length(x)) - 1) - mapply(\(p) { - list( - coordinateTransformations = list( - list( - scale = vapply(axes, \(.){ - if(. == "c") 1 else (2^as.numeric(p)) - }, numeric(1)), - type = "scale" - ) - ), - path = p - ) - }, paths, USE.NAMES = FALSE, SIMPLIFY = FALSE) -} - -#' @rdname coord-utils -#' @export -setMethod("addCT", "Zattrs", \(x, name, type="identity", data=NULL) { - stopifnot( - is.character(name), length(name) == 1, - is.character(type), length(type) == 1) - .check_ct(x, type, data) - ms <- "multiscales" - ts <- "transformations" - ct <- "coordinateTransformations" - # use existing as skeleton - fd <- (df <- CTdata(x))[1, ] - fd <- fd[, c("input", "output", "type")] - fd$type <- type - fd$output$name <- name - fd[[fd$type]] <- list(data) - # append to existing if 'name' already present - idx <- match(name, CTname(x)) - typ <- CTtype(x)[idx] - if (!is.na(typ) && typ == "identity") { - df <- df[0, ] - app <- FALSE - } else if (app <- !is.na(idx)) { - if (seq <- (typ == "sequence")) { - df <- df[idx, ][[ts]][[1]] - fd$output$name <- df$output$name[1] - } else { - df <- df[idx, ] - if (is.null(df[[ts]])) { - - } else { - df[[ts]][[1]] <- df - } - # fd$type <- type - # fd[[fd$type]] <- list(data) - } - } else { - # fd$type <- type - # fd[[fd$type]] <- list(data) - } - na <- setdiff(names(df), names(fd)) - for (. in na) fd[[.]] <- list(NULL) - na <- setdiff(names(fd), names(df)) - for (. in na) df[[.]] <- if (nrow(df) > 0) list(NULL) else list() - fd <- fd[, names(col) <- col <- names(df)] - # combine - if (app && !seq) { - # append to other - rownames(df) <- rownames(df$input) <- rownames(df$output) <- 1 - rownames(fd) <- rownames(fd$input) <- rownames(fd$output) <- 2 - } else { - # append to table or sequence - rownames(fd$input) <- rownames(fd$output) <- nrow(df)+1 - } - new <- rbind(df, fd) - if (is_ms <- !is.null(x[[ms]])) { - .x <- x[[ms]][[ct]][[1]] - } else .x <- x[[ct]] - if (app) { - .x$type[idx] <- "sequence" - .x[idx, ]$transformations[[1]] <- new - } else .x <- new - if (is_ms) x[[ms]][[ct]][[1]] <- .x else x[[ct]] <- .x - return(x) -}) - -# Dec 8 VJC -- why is this in doc comment mode? changing to plain comment -# # @importFrom EBImage resize -# setMethod("scale", "ImageArray", \(x, t, ...) { -# a <- as.array(data(x)) -# # TODO: this should be done w/o realizing -# # into memory, but EBImage needs an array? -# d <- length(dim(a)) -# if (missing(t)) -# t <- rep(1, d) -# b <- resize(aperm(a), -# w=dim(a)[d]*t[d], -# h=dim(a)[d-1]*t[d-1]) -# x@data <- aperm(b) -# x -# }) -# -# # @importFrom EBImage resize -# setMethod("translation", "ImageArray", \(x, t, ...) {}) -# setMethod("transform", "ImageArray", \(x, t) get(t$type)(x, unlist(t[[t$type]]))) - -# plotCoordGraph() ---- - -#' @name plotCoordGraph -#' @title CT graph viz. -#' -#' @description -#' given a \code{graphNEL} instance, nodes with \code{nchar>max} are -#' split and hyphenated at character position \code{floor(nchar/fac)}. -#' -#' @param g base R graph; extracted with \code{\link{CTgraph}}. -#' @param cex scalar numeric; controls fontsize of node labels. -#' -#' @examples -#' x <- file.path("extdata", "blobs.zarr") -#' x <- system.file(x, package="SpatialData") -#' x <- readSpatialData(x, tables=FALSE) -#' -#' g <- CTgraph(x) -#' plotCoordGraph(g, cex=0.6) -#' -#' @importFrom graph nodes nodes<- graph.par -#' @export -plotCoordGraph <- \(g, cex=0.6) { - if (!requireNamespace("Rgraphviz", quietly=TRUE)) - stop("To use this function, install the 'Rgraphviz'.") - g2view <- g # leave 'g' alone - nodes(g2view) <- .nodefix(nodes(g2view)) - graph.par(list(nodes=list(shape="plaintext", cex=cex))) - g2view <- Rgraphviz::layoutGraph(g2view) - Rgraphviz::renderGraph(g2view) -} - -.nodefix <- \(x, fac=2, max=10) { - fix <- nchar(x) > max - if (!any(fix)) return(x) - x[fix] <- .fixup(x[fix], fac) - x -} - -.fixup <- \(x, fac) { - xs <- strsplit(x, "") - nc <- floor(nchar(x)/fac) - vapply(seq_along(xs), \(i) { - j <- seq_len(nc[i]) - y <- c(xs[[i]][j], "-\n", xs[[i]][-j]) - paste(y, collapse="") - }, character(1)) -} 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/mask.R b/R/mask.R index 9f62a532..6b81b514 100644 --- a/R/mask.R +++ b/R/mask.R @@ -1,15 +1,21 @@ #' @name mask #' @title Masking #' -#' @description ... +#' @description +#' Masking operations serve to aggregate data across layers, e.g., +#' counting points in shapes, averaging image channels by labels, etc. +#' For added flexibility, these may be carried out directly between elements, +#' or using an input \code{SpatialData} object and specifying element names. #' #' @param x \code{\link{SpatialData}} object. #' @param i,j character string; names of elements to mask, #' specifically, \code{i} will be masked by \code{j}, #' adding a \code{table} for \code{j} in \code{x}. +#' @param how character string; statistic to use for masking. +#' @param name function use to generate the new \code{table}'s name. #' @param ... optional arguments passed to and from other methods. #' -#' @return \code{\link{SingleCellExperiment}} +#' @return Input \code{SpatialData} object \code{x} with an additional table. #' #' @examples #' library(SingleCellExperiment) @@ -17,88 +23,144 @@ #' x <- system.file(x, package="SpatialData") #' x <- readSpatialData(x, tables=FALSE) #' -#' # count points in circles -#' x <- mask(x, "blobs_points", "blobs_circles") -#' x <- mask(x, "blobs_image", "blobs_labels") -#' tables(x) +#' # count points in shapes +#' y <- mask(x, "blobs_points", "blobs_circles") +#' tail(tables(y), 1) +#' +#' # average image channels by labels +#' y <- mask(x, "blobs_image", "blobs_labels") +#' tail(tables(y), 1) #' +#' library(SpatialData.data) +#' x <- get_demo_SDdata("merfish") +#' x <- readSpatialData(x) +#' +#' # sum table counts by shapes +#' y <- mask(x, "cells", "anatomical") +#' tail(tables(y), 1) +#' #' @export NULL -# TODO: table from point + shape, image + label etc. etc. etc. +.check_ij <- \(x, .) stopifnot(length(.) == 1, is.character(.), . %in% unlist(colnames(x))) #' @rdname mask +#' @importFrom methods as +#' @importFrom SummarizedExperiment assay assay<- #' @importFrom SingleCellExperiment int_colData int_colData<- int_metadata<- #' @export -setMethod("mask", "SpatialData", \(x, i, j, ...) { - stopifnot(length(i) == 1, is.character(i), i %in% unlist(colnames(x))) - stopifnot(length(j) == 1, is.character(j), j %in% unlist(colnames(x))) - # get element types - ls <- vapply( - list(i, j), \(e) rownames(x)[vapply(colnames(x), - \(es) e %in% es, logical(1))], character(1)) - a <- element(x, ls[[1]], i) - b <- element(x, ls[[2]], j) - t <- .mask(a, b, ...) - md <- list(region=j, - region_key="region", - instance_key="instance") - int_metadata(t)$spatialdata_attrs <- md - cd <- data.frame(region=j, instance=colnames(t)) - int_colData(t) <- cbind(int_colData(t), cd) - nm <- paste0(i, "_masked_by_", j) - `table<-`(x, nm, value=t) +setMethod("mask", c("SpatialData", "ANY", "ANY"), \(x, i, j, + how=NULL, name=\(i, j) sprintf("%s_by_%s", i, j), ...) { + .check_ij(x, i); .check_ij(x, j) + #if (!is.null(how)) how <- match.arg(how, c("sum", "mean")) + ok <- is.character(name) && length(name) == 1 && !name %in% tableNames(x) + nm <- if (is.function(name)) name(i, j) else if (ok) name else stop( + "Invalid 'name'; should be a function or a ", + "character string not yet in 'tableNames(x)'") + f <- \(i) names(which(rapply(colnames(x), \(.) i %in% ., "character"))) + .i <- element(x, f(i), i) + .j <- element(x, f(j), j) + t <- tryCatch(error=\(.) NULL, getTable(x, i)) + se <- .mask(.i, .j, how=how, table=t, ...) + md <- list(region=j, region_key="region", instance_key="instance") + int_metadata(se)$spatialdata_attrs <- md + assay(se) <- as(assay(se), "dgCMatrix") + cd <- int_colData(se) + cd$region <- j + cd$instance <- colnames(se) + int_colData(se) <- cd + `table<-`(x, nm, value=se) }) -setGeneric(".mask", \(a, b, ...) standardGeneric(".mask")) +setGeneric(".mask", \(i, j, ...) standardGeneric(".mask")) +#' @noRd #' @importFrom methods as -#' @importFrom Matrix rowSums sparseVector t +#' @importFrom Matrix sparseVector +#' @importFrom SummarizedExperiment assayNames<- #' @importFrom SingleCellExperiment SingleCellExperiment -#' @importFrom sf st_as_sf st_geometry_type st_sfc st_point st_distance -setMethod(".mask", c("PointFrame", "ShapeFrame"), \(a, b) { - n <- nrow(b <- st_as_sf(data(b))) - fk <- meta(a)$spatialdata_attrs$feature_key - switch(paste(st_geometry_type(b)[1]), - POINT={ - # realize one feature at a time - is <- split(seq_len(length(a)), a[[fk]]) - ns <- lapply(is, \(.) { - # make points 'sf'-compliant - xy <- as.data.frame(a[., c("x", "y")]) - ps <- st_sfc(lapply(asplit(xy, 1), st_point)) - # for each circle, count points within radius - z <- rowSums(st_distance(b, ps) < b$radius) - # sparsify counts - sv <- sparseVector(z[i <- z > 0], which(i), n) - sm <- as(sv, "sparseMatrix") - }) - # collect intro matrix w/ dim. features x circles - ns <- t(as(do.call(cbind, ns), "dgCMatrix")) - rownames(ns) <- names(is) - colnames(ns) <- seq(ncol(ns)) - }) - SingleCellExperiment(list(counts=ns)) +setMethod(".mask", c("ImageArray", "LabelArray"), \(i, j, how=NULL, ...) { + if (is.null(how)) { how <- "mean"; message("Missing 'how'; defaulting to 'mean'") } + stopifnot(dim(i)[-1] == dim(j)) + .j <- as(data(j), "sparseVector") + .j <- as.vector(.j[ok <- .j > 0]) + mx <- apply(data(i), 1, \(.i) { + .i <- as(.i, "sparseVector") + .i <- as.vector(.i[ok]) + tapply(.i, .j, how) + }) + colnames(mx) <- channels(i) + se <- SingleCellExperiment(list(t(mx))) + assayNames(se) <- how + return(se) }) +#' @noRd #' @importFrom methods as -#' @importFrom DelayedArray realize -#' @importFrom S4Arrays as.array.Array +#' @importFrom Matrix t rowSums sparseVector sparseMatrix #' @importFrom SingleCellExperiment SingleCellExperiment -setMethod(".mask", c("ImageArray", "LabelArray"), \(a, b, fun=mean) { - # TODO: somehow rewrite w/o realizing everything - # at once (maybe w/ 'DelayedArray::blockApply'?) - .a2v <- \(.) as.vector(as.array.Array(.)) - stopifnot(dim(a)[-1] == dim(b)) - w <- .a2v(data(b)); w[w == 0] <- NA - n <- length(i <- unique(w[!is.na(w)])) - ns <- vapply(seq_len(dim(a)[1]), \(.) { - v <- .a2v(data(a, 1)[., , ]) - tapply(v, w, sum, na.rm=TRUE) - }, numeric(n)) - ns <- t(as(ns, "dgCMatrix")) - dimnames(ns) <- list(seq(dim(a)[1]), i) +#' @importFrom sf st_as_sf st_geometry_type st_distance +setMethod(".mask", c("PointFrame", "ShapeFrame"), \(i, j, how=NULL, ...) { + if (!is.null(how)) warning("Can only count when masking points; ignoring 'how'") + fun <- switch(geom_type(j), + POINT=\(i, j) rowSums(st_distance(j, i) <= j$radius), + \(i, j) vapply(st_intersects(j, i), length, integer(1))) + # realize one feature at i time + n <- nrow(j <- st_as_sf(data(j))) + is <- split(seq_len(length(i)), i[[feature_key(i)]]) + ns <- lapply(is, \(.) { + # make points 'sf'-compliant + i <- as.data.frame(i[., c("x", "y")]) + i <- st_as_sf(i, coords=c("x", "y")) + # for each shape, count intersecting points + z <- fun(i, j) + # sparsify counts + sv <- sparseVector(z[i <- z > 0], which(i), n) + sm <- as(sv, "sparseMatrix") + }) + # collect into matrix w/ dim. features x shapes + ns <- t(do.call(cbind, ns)) + rownames(ns) <- names(is) + colnames(ns) <- seq(ncol(ns)) SingleCellExperiment(list(counts=ns)) }) -setMethod(".mask", c("ANY", "ANY"), \(a, b) - stop("'mask'ing between these element types not supported.")) + +#' @noRd +#' @importFrom methods as +#' @importFrom Matrix sparseMatrix +#' @importFrom SummarizedExperiment assay +#' @importFrom SingleCellExperiment SingleCellExperiment +setMethod(".mask", c("ShapeFrame", "ShapeFrame"), \(i, j, how=NULL, table=NULL, value=NULL, assay=1, ...) { + # validity + if (is.null(table)) stop("Missing 'table'; can't mask shapes without") + ok <- is.null(value) || (is.character(value) && all(value %in% rownames(table))) + if (!ok) stop("Invalid 'value'; should be in 'rownames(table(x, i))'") + if (is.null(how)) { how <- "sum"; message("Missing 'how'; defaulting to 'sum'") } + if (is.character(how)) how <- match.arg(how, c("sum", "mean", "detected", "prop.detected")) + # grouping + js <- st_intersects(st_as_sf(data(j)), st_as_sf(data(i))) + is <- factor(integer(nrow(i)), seq(0, nrow(j))) + is[unlist(js)] <- rep(seq_along(js), lengths(js)) + ns <- tabulate(is, ni <- nlevels(is)) + # aggregation + mx <- assay(table, assay) + if (grepl("detected$", how)) mx <- mx > 0 + my <- sparseMatrix( + x=rep(1, length(is)), + i=seq_along(is), j=is, + dims=c(ncol(table), ni)) + mx <- mx %*% my + if (grepl("mean|prop", how)) mx <- t(t(mx)/ns) + # wrangling + mx <- as(mx, "dgCMatrix") + colnames(mx) <- levels(is) + mx <- list(mx); names(mx) <- how + se <- SingleCellExperiment(mx) + nm <- paste0("n_", meta(table)$region) + se[[nm]] <- ns + return(se) +}) + +#' @noRd +setMethod(".mask", c("ANY", "ANY"), \(i, j, ...) + stop("'mask'ing between these element types not yet supported")) diff --git a/R/metadata.R b/R/metadata.R index 5e488057..b338c502 100644 --- a/R/metadata.R +++ b/R/metadata.R @@ -1,4 +1,58 @@ -.make_pointshape_meta <- function(x, +# helpers ---- + +.make_axes_meta <- function(x, unit = FALSE){ + lapply(x, \(.){ + meta <- list(names = ., + type = if(. == "c") "channel" else "space") + if(unit) + meta <- c(meta, list(unit = "unit")) + meta + }) +} + +.make_empty_ct <- function(x){ + space <- .make_axes_meta(x, unit = TRUE) + input <- list(axes = space, + name = paste(x, collapse = "")) + output <- list(axes = space, name = "global") + meta <- list( + list(input = input, + output = output, + type = "identity") + ) + meta +} + +.make_datasets <- function(x, axes){ + paths <- paste0(seq_len(length(x)) - 1) + mapply(\(p) { + list( + coordinateTransformations = list( + list( + scale = vapply(axes, \(.){ + if(. == "c") 1 else (2^as.numeric(p)) + }, numeric(1)), + type = "scale" + ) + ), + path = p + ) + }, paths, USE.NAMES = FALSE, SIMPLIFY = FALSE) +} + +# metadata constructors ---- +#' @title Make point/shape metadata +#' @description Make point/shape metadata +#' @param x A points or shapes object +#' @param axes A character vector of axes names +#' @param encoding_type A string specifying the encoding type +#' @param feature_key A string specifying the feature key +#' @param instance_key A string specifying the instance key +#' @param version A string specifying the version +#' @return A list of metadata for the point/shape object +#' @importFrom jsonlite fromJSON toJSON +#' @noRd +.make_pointshape_meta <- function(x, axes = NULL, encoding_type = "ngff:points", feature_key = NULL, @@ -7,7 +61,7 @@ meta <- list() ax <- "axes" ct <- "coordinateTransformations" - sa <- "spatial_attrs" + sa <- "spatialdata_attrs" # axis # NOTE: rev dimensions since points and shapes want x, y @@ -28,10 +82,20 @@ meta[[ct]] <- .make_empty_ct(meta[[ax]]) # update json list - meta <- fromJSON(toJSON(meta, auto_unbox = TRUE), simplifyVector = TRUE) + meta <- fromJSON(toJSON(meta, auto_unbox = TRUE), simplifyVector = FALSE) Zattrs(meta) } +# TODO: make it the functions take a global option e.g. sd_zarr_version +# as an argument for the default zarr version +#' @title Make image metadata +#' @description Make image metadata +#' @param x An image object +#' @param axes A character vector of axes names +#' @param version A string specifying the version +#' @return A list of metadata for the image object +#' @importFrom jsonlite fromJSON toJSON +#' @noRd .make_image_meta <- function(x, axes = NULL, version = 0.4){ @@ -69,18 +133,26 @@ meta[[v]] <- list(version = version) # multiscales - meta <- list(multiscales = list(meta), + meta <- list(multiscales = list(meta), omero = list( - channels = lapply(seq_len(length(axes))-1, \(.) + channels = lapply(seq_len(length(axes))-1, \(.) list(label = .)) - ), + ), spatialdata_attrs = list(version = "0.1")) - + # update json list - meta <- fromJSON(toJSON(meta, auto_unbox = TRUE), simplifyVector = TRUE) + meta <- fromJSON(toJSON(meta, auto_unbox = TRUE), simplifyVector = FALSE) Zattrs(meta) } +#' @title Make label metadata +#' @description Make label metadata +#' @param x A label object +#' @param axes A character vector of axes names +#' @param version A string specifying the version +#' @return A list of metadata for the label object +#' @importFrom jsonlite fromJSON toJSON +#' @noRd .make_label_meta <- function(x, axes = NULL, version = 0.4){ @@ -120,13 +192,12 @@ spatialdata_attrs = list(version = "0.1")) # update json list - meta <- fromJSON(toJSON(meta, auto_unbox = TRUE), simplifyVector = TRUE) + meta <- fromJSON(toJSON(meta, auto_unbox = TRUE), simplifyVector = FALSE) Zattrs(meta) } -#' .get_valid_axes -#' -#' Get validated axes +#' @title Get valid axes +#' @description Get validated axes #' #' @inheritParams write_image #' @@ -210,28 +281,5 @@ return(c(nrow(x), n_col)) } -#' #' @importFrom sf st_as_sf st_geometry -#' .get_geoarrow_dim <- function(x){ -#' meta <- .get_geoarrow_metadata(x) -#' if(length(meta) > 1){ -#' if("geometry" %in% colnames(meta)){ -#' bbox <- meta$geo$columns$geometry$bbox -#' n_col <- if(length(bbox) == 4) 2 else 3 -#' } else { -#' n_col <- ncol(x) -#' } -#' } else { -#' if("geometry" %in% colnames(x)){ -#' geo <- st_geometry(st_as_sf(df)) -#' } else{ -#' stop("No geometry object is detected!") -#' } -#' } -#' return(c(nrow(x), n_col)) -#' } -#' -#' #' @importFrom jsonlite fromJSON -#' .get_geoarrow_metadata <- function(x){ -#' lapply(x$metadata, fromJSON) -#' } + diff --git a/R/methods.R b/R/methods.R index eaf7dc44..ed30acf8 100644 --- a/R/methods.R +++ b/R/methods.R @@ -43,7 +43,10 @@ setMethod("[[", c("SpatialData", "character"), \(x, i, ...) { for (. in names(j)) { .j <- j[[.]] n <- length(attr(x, .)) - if (length(.j) == 1 && is.infinite(.j)) { + if (is.character(.j)) { + if (!all(.j %in% names(attr(x, .)))) + stop("invalid 'j'") + } else if (length(.j) == 1 && is.infinite(.j)) { .j <- n } else if (any(.j > n)) { stop("invalid 'j'") @@ -217,7 +220,7 @@ NULL f <- \(.) setReplaceMethod(., c("SpatialData", "character", typ[[.]]), - \(x, i, value) { + \(x, i, value) { y <- attr(x, paste0(., "s")) y[[i]] <- value attr(x, paste0(., "s")) <- y @@ -253,7 +256,7 @@ NULL f <- \(.) setReplaceMethod(., c("SpatialData", "missing", typ[[.]]), - \(x, i, value) { + \(x, i, value) { f <- get(paste0(., "<-")) f(x=x, i=1, value=value) }) diff --git a/R/misc.R b/R/misc.R index 3ca30006..713a5b82 100644 --- a/R/misc.R +++ b/R/misc.R @@ -1,16 +1,33 @@ #' @name misc -#' @title Miscellaneous `Miro` methods -#' @description ... -#' -#' @param object \code{\link{SpatialData}} object or one of its -#' elements, i.e., an Image/LabelArray or Point/ShapeFrame. +#' @title Miscellaneous `SpatialData` methods +#' @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} #' #' @author Helena L. Crowell #' #' @examples -#' # TODO +#' zs <- file.path("extdata", "blobs.zarr") +#' zs <- system.file(zs, package="SpatialData") +#' (sd <- readSpatialData(zs, anndataR=TRUE)) +#' +#' # show element +#' image(sd) +#' label(sd) +#' point(sd) +#' shape(sd) +#' +#' # show .zattrs +#' meta(label(sd)) +#' meta(image(sd, 2)) NULL #' @importFrom RBGL sp.between @@ -55,11 +72,12 @@ NULL for (. in seq_along(t)) cat(sprintf(" - %s (%s)\n", t[.], d[.])) # spaces - cat("coordinate systems:\n") e <- c(i, l, s, p) g <- CTgraph(object) t <- nodeData(g, nodes(g), "type") - for (c in nodes(g)[t == "space"]) { + n <- sum(i <- (t == "space")) + cat(sprintf("coordinate systems(%s):\n", n)) + for (c in nodes(g)[i]) { pa <- suppressWarnings(sp.between(g, e, c)) ss <- strsplit(names(pa), ":") ss <- ss[vapply(pa, \(.) !is.na(.$length), logical(1))] diff --git a/R/query.R b/R/query.R index d75183a6..01fa1524 100644 --- a/R/query.R +++ b/R/query.R @@ -1,125 +1,184 @@ #' @name query #' @title spatial queries #' +#' @description Spatial queries serve to subset \code{SpatialData} elements +#' according to a rectangular bounding box or arbitrary polygonal shapes. +#' Queries rely on lesser-/greater-equal and \code{sf::st_intersects} for +#' spatial operations (i.e., instances that intersect the query region +#' in any way are kept). For circle shapes, radii are currently ignored +#' (i.e., a circle is kept if its centroid intersects the query region). +#' #' @param x \code{SpatialData} element. -#' @param j scalar character or integer; index or name of coordinate space. +#' @param y query specification; +#' 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. #' #' @return same as input #' #' @examples -#' x <- file.path("extdata", "blobs.zarr") -#' x <- system.file(x, package="SpatialData") -#' x <- readSpatialData(x, tables=FALSE) +#' zs <- file.path("extdata", "blobs.zarr") +#' zs <- system.file(zs, package="SpatialData") +#' sd <- readSpatialData(zs, tables=FALSE) +#' +#' # helper for visualizing point coordinates +#' .xy <- \(.) data.frame(data(.)[c("x", "y")]) +#' +#' # bounding box +#' y <- list(xmin=11, xmax=44, ymin=22, ymax=55) +#' q <- query(p <- point(sd), y) +#' +#' plot(.xy(p), asp=1) +#' points(.xy(q), col="red") +#' rect(y$xmin, y$ymin, y$xmax, y$ymax, border="blue") #' -#' image(x, "box") <- query(image(x), xmin=0, xmax=30, ymin=30, ymax=50) +#' # polygon +#' y <- rbind(c(20,10), c(50,30), c(20,50), c(30,30)) +#' q <- query(p <- point(sd), y) #' -#' image(x) -#' image(x, "box") +#' plot(.xy(p), asp=1) +#' points(.xy(q), col="red") +#' lines(rbind(y, y[1, ]), col="blue") +#' +#' # shapes that intersect the query region are kept +#' y <- rbind(c(30,45), c(40,45), c(35,50)) +#' t <- query(s <- shape(sd, 3), y) +#' +#' require(sf, quietly=TRUE) +#' df <- st_coordinates(st_as_sf(data(s))) +#' fd <- st_coordinates(st_as_sf(data(t))) +#' plot( +#' asp=1, xlim=c(15, 60), ylim=c(15, 60), +#' rbind(y, y[1, ]), type="l", col="blue") +#' foo <- by(df, df[, "L2"], \(x) points(x, type="b", col="black")) +#' foo <- by(fd, fd[, "L2"], \(x) points(x, type="b", col="red")) NULL -.check_bb <- \(args) { - m <- match(names(args), c("xmin", "xmax", "ymin", "ymax")) - if (any(is.na(m)) || !identical(sort(m), seq_len(4))) - stop("currently only supporting bounding box query;", - " please provide 'xmin/xmax/ymin/ymax' as ...") -} - #' @rdname query +#' @importFrom dplyr filter #' @export -setMethod("query", "SpatialData", \(x, j=NULL, ...) { - # check validity of dots - args <- list(...) - .check_bb(args) - # guess coordinate space - stopifnot(length(j) == 1) - j <- if (is.null(j)) { - .guess_space(x) - } else { - if (is.character(j)) { - match.arg(j, CTname(x)) - } else if (is.numeric(j)) { - stopifnot(j > 0, j == round(j)) - CTname(x)[j] - } +setMethod("query", "SpatialData", \(x, ..., i) { + # TODO: need more example data to properly implement this; + # for now, just a proof of concept using 'spatialdata_attrs' + if (missing(i)) i <- 1 + if (!length(tables(x))) + stop("There aren't any tables") + if (is.numeric(i)) { + i <- tableNames(x)[i] + } else if (is.character(i)) { + i <- match.arg(i, tableNames(x)) } - # execute query - for (l in rownames(x)) - for (e in colnames(x)[[l]]) - x[[l]][[e]] <- query(x[[l]][[e]], j, ...) - return(x) + t <- x$tables[[i]] + ns <- vapply(nm <- colnames(x), length, integer(1)) + nm <- data.frame(layer=rep.int(names(nm), ns), region=unlist(nm)) + nm <- filter(nm, ...) + i <- match(nm$layer, .LAYERS) + j <- split(nm$region, nm$layer) + x <- x[i, j] + x$tables$table <- t + return(x) }) +.check_box <- \(bb) { + xy <- c("xmin", "xmax", "ymin", "ymax") + ok <- c(is.list(bb), + length(bb) == 4, setequal(names(bb), xy), + bb$xmin <= bb$xmax, bb$ymin <= bb$ymax, + is.numeric(bb <- unlist(bb)), !is.na(bb)) + if (!all(ok)) stop( + "Invalid bounding box query; should be length-4 ", + "numeric list with names 'xmin/xmax/ymin/ymax'") +} + +.check_pol <- \(mx) { + ok <- c( + is.matrix(mx), is.numeric(mx), + nrow(mx) >= 3, ncol(mx) == 2, + !is.na(mx), is.finite(mx)) + if (!all(ok)) stop( + "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), ] + if (!all(top == bot)) + mx <- rbind(mx, top) + dup <- duplicated(as.data.frame(mx[-1, , drop=FALSE])) + if (any(dup)) stop("Invalid polygon query; found duplicated vertices") + return(mx) +} + +.query_sdArray <- \(x, y) { + if (is.matrix(y)) stop( + "Polygon query not supported for ", + "element of type 'image/labelArray'") + .check_box(y) + # protect image channels (i.e., + # only query spatial dimensions) + n <- length(d <- dim(x)) + if (n == 3) d <- d[-1] + # assure query is within bounds + y$xmin <- max(y$xmin, 0) + y$ymin <- max(y$ymin, 0) + y$ymax <- min(y$ymax, d[1]) + y$xmax <- min(y$xmax, d[2]) + # subset spatial dimensions + i <- seq(y$ymin, y$ymax) + j <- seq(y$xmin, y$xmax) + if (n == 3) { + return(x[, i, j]) + } else { + return(x[i, j]) + } +} + #' @rdname query #' @export -setMethod("query", "ImageArray", \(x, j, ...) { - qu <- list(...) - .check_bb(qu) - if (missing(j)) j <- 1 - if (is.numeric(j)) j <- CTname(x)[j] - stopifnot(length(j) == 1) - . <- grep(j, CTname(x)) - if (!length(.) || is.na(.)) stop("invalid 'j'") - # transform query into target space - ts <- CTpath(x, j) - xy <- list(c(qu$xmin, qu$xmax), c(qu$ymin, qu$ymax)) - xy <- data.frame(xy); names(xy) <- c("x", "y") - xy <- .trans_xy(xy, ts, TRUE) - xy <- lapply(xy, \(.) as.list(round(.))) - x <- x[, # crop (i.e., subset) array dimensions 2-3 - do.call(seq, xy[[2]]), - do.call(seq, xy[[1]])] - # transform array dimensions into target space - os <- data.frame(x=c(0, dim(x)[3]), y=c(0, dim(x)[2])) - os <- vapply(.trans_xy(os, ts), min, numeric(1)) - # add transformation of type translation as to - # offset origin by difference between new & old - os <- unlist(qu[c("xmin", "ymin")]) - os - if (!all(os == 0)) x <- addCT(x, - name=j, type="translation", - data=c(0, os[2], os[1])) - return(x) -}) +setMethod("query", "ImageArray", \(x, y) .query_sdArray(x, y)) #' @rdname query #' @export -setMethod("query", "LabelArray", \(x, ...) { - args <- list(...) - .check_bb(args) - d <- dim(x) - if (args$ymax > d[1]) args$ymax <- d[1] - if (args$xmax > d[2]) args$xmax <- d[2] - a <- data(x)[ - seq(args$ymin, args$ymax), - seq(args$xmin, args$xmax)] - x@data <- a - return(x) -}) +setMethod("query", "LabelArray", \(x, y) .query_sdArray(x, y)) #' @rdname query +#' @importFrom sf st_as_sf st_intersects st_polygon st_bbox st_crop #' @export -setMethod("query", "ShapeFrame", \(x, ...) { - args <- list(...) - .check_bb(args) - df <- st_as_sf(data(x)) - xy <- st_coordinates(df) - i <- - xy[, 1] >= args$xmin & - xy[, 1] <= args$xmax & - xy[, 2] >= args$ymin & - xy[, 2] <= args$ymax - x@data <- data(x)[which(i), ] +setMethod("query", "ShapeFrame", \(x, y) { + # TODO: this will drop geometries where any coordinate + # is out of bounds; keep but crop to boundary region? + if (is.matrix(y)) { + # TODO: currently ignoring 'radius' for circles (i.e., + # query based on centroids only); what does Python do? + mx <- .check_pol(y) + sf <- st_as_sf(data(x)) + ok <- st_intersects(sf, st_polygon(list(mx)), sparse=FALSE) + x@data <- x@data[which(ok), ] + return(x) + } + # note: non-spatial attributes (e.g., radius) give warnings? + .check_box(y) + sf <- st_as_sf(data(x)) + bb <- st_bbox(unlist(y)) + suppressWarnings(sf <- st_crop(sf, bb)) + x@data <- sf[names(x)] return(x) }) #' @rdname query +#' @importFrom sf st_as_sf st_polygon st_intersects +#' @importFrom dplyr collect filter #' @export -setMethod("query", "PointFrame", \(x, j, ...) { - args <- list(...) - .check_bb(args) - y <- filter(x, - x >= args$xmin, x <= args$xmax, - y >= args$ymin, y <= args$ymax) - x@data <- y@data - return(x) +setMethod("query", "PointFrame", \(x, y) { + if (is.matrix(y)) { + mx <- .check_pol(y) + xy <- st_as_sf(collect(data(x)[c("x", "y")]), coords=c("x", "y")) + ok <- st_intersects(xy, st_polygon(list(mx)), sparse=FALSE) + return(x[which(ok[, 1])]) + } else { + .check_box(bb <- y) + filter(x, + x >= bb$xmin, x <= bb$xmax, + y >= bb$ymin, y <= bb$ymax) + } }) diff --git a/R/read.R b/R/read.R index ca3b3f83..78217254 100644 --- a/R/read.R +++ b/R/read.R @@ -1,34 +1,41 @@ -# -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", -"click==8.1.8", "cloudpickle==3.1.1", "cycler==0.12.1", "dask==2024.4.1", -"dask_image==2024.5.3", "datashader==0.17.0", -"deprecated==1.2.18", "distributed==2024.4.1", -"flowio==1.3.0", "fsspec==2025.2.0", "geopandas==1.0.1", "h5py==3.12.1", -"idna==3.10", "imagecodecs==2024.12.30", "imageio==2.37.0", "jinja2==3.1.5", -"joblib==1.4.2", "kiwisolver==1.4.8", "lazy_loader==0.4", "legacy_api_wrap==1.4.1", -"llvmlite==0.44.0", "locket==1.0.0", "markupsafe==3.0.2", "matplotlib==3.10.0", -"more_itertools==10.3.0", "msgpack==1.1.0", "multipledispatch==0.6.0", -"multiscale_spatial_image==2.0.2", "natsort==8.4.0", "networkx==3.4.2", -"numba==0.61.0", "numcodecs==0.15.1", "numpy==2.1.3", "ome_types==0.5.3", -"ome_zarr==0.10.3", "packaging==24.2", "pandas==2.2.3", "param==2.2.0", -"pims==0.7", "platformdirs==4.3.6", "psutil==7.0.0", "pyarrow==19.0.0", -"pyct==0.5.0", "pydantic==2.10.6", "pydantic_compat==0.1.2", -"pydantic_core==2.27.2", "pygments==2.19.1", "pyparsing==3.2.1", -"pyproj==3.7.0", "pytz==2025.1", "readfcs==2.0.1", "requests==2.32.3", -"rich==13.9.4", "scanpy==1.11.0", "scipy==1.15.1", "setuptools==75.8.0", -"shapely==2.0.7", "six==1.17.0", "scikit-image==0.25.1", "scikit-learn==1.5.2", -"slicerator==1.1.0", "sortedcontainers==2.4.0", "spatial_image==1.1.0", -"tblib==3.0.0", "threadpoolctl==3.5.0", "tifffile==2025.1.10", -"toolz==1.0.0", "tornado==6.4.2", "tqdm==4.67.1", -"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") -# notes from VJC -- readSpatialData was modified below so -# that if anndataR = FALSE, spatialdata.read_zarr is used +# 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", +# "click==8.1.8", "cloudpickle==3.1.1", "cycler==0.12.1", "dask==2024.4.1", +# "dask_image==2024.5.3", "datashader==0.17.0", +# "deprecated==1.2.18", "distributed==2024.4.1", +# "flowio==1.3.0", "fsspec==2025.2.0", "geopandas==1.0.1", "h5py==3.12.1", +# "idna==3.10", "imagecodecs==2024.12.30", "imageio==2.37.0", "jinja2==3.1.5", +# "joblib==1.4.2", "kiwisolver==1.4.8", "lazy_loader==0.4", "legacy_api_wrap==1.4.1", +# "llvmlite==0.44.0", "locket==1.0.0", "markupsafe==3.0.2", "matplotlib==3.10.0", +# "more_itertools==10.3.0", "msgpack==1.1.0", "multipledispatch==0.6.0", +# "multiscale_spatial_image==2.0.2", "natsort==8.4.0", "networkx==3.4.2", +# "numba==0.61.0", "numcodecs==0.15.1", "numpy==2.1.3", "ome_types==0.5.3", +# "ome_zarr==0.10.3", "packaging==24.2", "pandas==2.2.3", "param==2.2.0", +# "pims==0.7", "platformdirs==4.3.6", "psutil==7.0.0", "pyarrow==19.0.0", +# "pyct==0.5.0", "pydantic==2.10.6", "pydantic_compat==0.1.2", +# "pydantic_core==2.27.2", "pygments==2.19.1", "pyparsing==3.2.1", +# "pyproj==3.7.0", "pytz==2025.1", "readfcs==2.0.1", "requests==2.32.3", +# "rich==13.9.4", "scanpy==1.11.0", "scipy==1.15.1", "setuptools==75.8.0", +# "shapely==2.0.7", "six==1.17.0", "scikit-image==0.25.1", "scikit-learn==1.5.2", +# "slicerator==1.1.0", "sortedcontainers==2.4.0", "spatial_image==1.1.0", +# "tblib==3.0.0", "threadpoolctl==3.5.0", "tifffile==2025.1.10", +# "toolz==1.0.0", "tornado==6.4.2", "tqdm==4.67.1", +# "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") + +# 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 -# transformed via zellkonverter. this gives a 10x speedup +# transformed via anndataR. This gives a 10x speedup # 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 @@ -48,9 +55,9 @@ allp = c("session_info==1.0.0", "spatialdata==0.3.0", "spatialdata_io==0.1.7", #' The default, NULL, reads all elements; alternatively, may be FALSE #' to skip a layer, or a integer vector specifying which elements to read. #' @param anndataR logical specifying whether -#' to use \code{anndataR} to read tables; defaults to FALSE in `readSpatialData`, -#' and `readTable`, -#' so that pythonic \code{spatialdata} and \code{zellkonverter} are used. +#' to use \code{anndataR} to read tables; +#' defaults to FALSE in `readSpatialData`, and `readTable`, +#' so that pythonic \code{anndata} are used. #' @param ... option arguments passed to and from other methods. #' #' @return @@ -61,100 +68,109 @@ allp = c("session_info==1.0.0", "spatialdata==0.3.0", "spatialdata_io==0.1.7", #' #' @examples #' library(SpatialData.data) -#' dir.create(tf <- tempfile()) -#' base <- SpatialData.data:::.unzip_merfish_demo(tf) -#' (x <- readSpatialData(base)) +#' zs <- get_demo_SDdata("merfish") +#' +#' # read complete Zarr store +#' (sd <- readSpatialData(zs, anndataR=TRUE)) +#' +#' # helper that gets path to first element in layer 'l' +#' fn <- \(l) list.files(file.path(zs, l), full.names=TRUE)[1] +#' +#' # read individual element +#' readImage(fn("images")) +#' readShape(fn("shapes")) +#' readPoint(fn("points")) NULL -readsdlayer <- function(x, ...) { - md <- read_zattrs(x) - ps <- .get_multiscales_dataset_paths(md) - list(array = lapply(ps, \(.) ZarrArray(file.path(x, as.character(.)))), - md = md) +#' @importFrom Rarr read_zarr_attributes +#' @importFrom ZarrArray ZarrArray +.readArray <- function(x, ...) { + md <- read_zarr_attributes(x) + ps <- .get_multiscales_dataset_paths(md) + ps <- file.path(x, as.character(ps)) + as <- lapply(ps, ZarrArray) + list(array=as, md=md) } #' @rdname readSpatialData -#' @importFrom Rarr ZarrArray #' @export readImage <- function(x, ...) { - lyrs <- readsdlayer(x, ...) - ImageArray(data=lyrs$array, meta=Zattrs(lyrs$md), ...) + l <- .readArray(x, ...) + ImageArray(data=l$array, meta=Zattrs(l$md), ...) } #' @rdname readSpatialData -#' @importFrom Rarr ZarrArray #' @export readLabel <- function(x, ...) { - lyrs <- readsdlayer(x, ...) - LabelArray(data=lyrs$array, meta=Zattrs(lyrs$md), ...) + l <- .readArray(x, ...) + LabelArray(data=l$array, meta=Zattrs(l$md), ...) } #' @rdname readSpatialData #' @importFrom arrow open_dataset +#' @importFrom Rarr read_zarr_attributes #' @export readPoint <- function(x, ...) { - md <- read_zattrs(x) + md <- read_zarr_attributes(x) pq <- list.files(x, "\\.parquet$", full.names=TRUE) PointFrame(data=open_dataset(pq), meta=Zattrs(md)) } #' @rdname readSpatialData #' @importFrom arrow open_dataset +#' @importFrom Rarr read_zarr_attributes #' @import geoarrow #' @export readShape <- function(x, ...) { - requireNamespace("geoarrow", quietly=TRUE) - md <- read_zattrs(x) # TODO: previously had read_parquet(), # but that doesn't work with geoparquet? + #requireNamespace("geoarrow", quietly=TRUE) + md <- read_zarr_attributes(x) pq <- list.files(x, "\\.parquet$", full.names=TRUE) ShapeFrame(data=open_dataset(pq), meta=Zattrs(md)) } #' @importFrom basilisk BasiliskEnvironment .env <- BasiliskEnvironment( - pkgname="SpatialData", - envname="anndata_env", - packages=c("python==3.12.0", "zarr==2.18.4"), + pkgname="SpatialData", envname="anndata_env", + packages=c( "python==3.13.0"), pip=allp) +#' @import anndataR #' @importFrom reticulate import #' @importFrom S4Vectors metadata -#' @importFrom zellkonverter AnnData2SCE #' @importFrom SingleCellExperiment int_metadata #' @importFrom basilisk basiliskStart basiliskStop basiliskRun .readTables_basilisk <- function(x) { proc <- basiliskStart(.env) on.exit(basiliskStop(proc)) basiliskRun(proc, x=x, \(x) { - # read in 'SpatialData' from .zarr store - sd <- import("spatialdata") - zs <- sd$read_zarr(x) - # return (named) list of SCEs - names(ts) <- ts <- names(zs$tables$data) - lapply(ts, \(z) { - se <- AnnData2SCE(zs$tables[z]) - nm <- "spatialdata_attrs" - md <- metadata(se)[[nm]] - int_metadata(se)[[nm]] <- md - metadata(se)[[nm]] <- NULL - se - }) + # read in 'AnnData' tables from .zarr store + sd <- import("anndata") + za <- import("zarr") + # return (named) list of SCEs + names(ts) <- ts <- list.dirs(file.path(x,"tables/"), + recursive = FALSE, + full.names = FALSE) + lapply(ts, \(z) { + zs <- sd$read_zarr(file.path(x, "tables", z)) + se <- zs$as_SingleCellExperiment() + nm <- "spatialdata_attrs" + md <- metadata(se)[[nm]] + int_metadata(se)[[nm]] <- md + metadata(se)[[nm]] <- NULL + se + }) }) } - .readTable_anndataR <- function(x) { if (!requireNamespace('anndataR', quietly=TRUE)) { - stop("To use this function, install the 'anndataR' package via\n", - "`BiocManager::install(\"keller-mark/anndataR\", ref=\"spatialdata\")`") - } - if (!requireNamespace('pizzarr', quietly=TRUE)) { - stop("To use this function, install the 'pizzarr' package via\n", - "`BiocManager::install(\"keller-mark/pizzarr\")`") + message("To make sure 'anndataR' package works as intended, ", + "install the development version via\n", + "`BiocManager::install(\"keller-mark/anndataR\", ref=\"spatialdata\")`") } suppressWarnings({ # suppress warnings related to hidden files - adata <- anndataR::read_zarr(x) - anndataR::to_SingleCellExperiment(adata) + anndataR::read_zarr(x, as="SingleCellExperiment") }) } @@ -186,24 +202,26 @@ readTable <- function(x) { #' @export readSpatialData <- function(x, images=TRUE, labels=TRUE, points=TRUE, - shapes=TRUE, tables=TRUE, anndataR=FALSE) { + shapes=TRUE, tables=TRUE, anndataR=TRUE) { if (!anndataR) tables <- FALSE # will do manually below 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))) stop("'", i, "=", opt, "', but only ", ., " elements found") if (is.character(opt) && length(. <- setdiff(opt, basename(j)))) - stop("couln't find ", i, " of name", .) + stop("couldn't find ", i, " of name", .) j <- j[opt] } f <- get(paste0("read", toupper(substr(i, 1, 1)), substr(i, 2, nchar(i)-1))) lapply(j, \(.) do.call(f, list(.))) }) - if (!anndataR) sd$tables <- .readTables_basilisk(x) + if (!anndataR && !isFALSE(tables)) sd$tables <- .readTables_basilisk(x) do.call(SpatialData, sd) } diff --git a/R/sdArray.R b/R/sdArray.R index 2eefc757..47d22cce 100644 --- a/R/sdArray.R +++ b/R/sdArray.R @@ -1,7 +1,8 @@ -#' @name Array-methods +#' @name sdArray #' @title Methods for `ImageArray` and `LabelArray` class #' #' @aliases +#' data_type #' data,ImageArray-method #' data,LabelArray-method #' dim,ImageArray-method @@ -15,13 +16,20 @@ #' @return \code{ImageArray} #' #' @examples -#' # TODO +#' library(SpatialData.data) +#' zs <- get_demo_SDdata("merfish") +#' +#' # helper that gets path to first element in layer 'l' +#' fn <- \(l) list.files(file.path(zs, l), full.names=TRUE)[1] +#' +#' # read individual element +#' (ia <- readImage(fn("images"))) #' #' @importFrom S4Vectors metadata<- #' @importFrom methods new NULL -#' @rdname Array-methods +#' @rdname sdArray #' @export setMethod("data", "sdArray", \(x, k=1) { if (is.null(k)) return(x@data) @@ -32,14 +40,30 @@ 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 sdArray +#' @importFrom S4Vectors metadata +setMethod("data_type", "sdArray", \(x) { + if (is(y <- data(x), "DelayedArray")) + data_type(y) else metadata(x)$data_type +}) + +#' @export +#' @rdname sdArray +#' @importFrom DelayedArray DelayedArray +#' @importFrom Rarr zarr_overview +#' @importFrom ZarrArray path +setMethod("data_type", "DelayedArray", \(x) zarr_overview(path(x), as_data_frame=TRUE)$data_type) + + #' .create_mip #' #' Generate a downsampled pyramid of images. @@ -87,5 +111,11 @@ setMethod("length", "sdArray", \(x) length(data(x, NULL))) perm = rev(seq_len(length(axes)))) } } + if (method == "label") { + image_list <- lapply(image_list, function(x) { + storage.mode(x) <- "integer" + x + }) + } image_list } \ No newline at end of file diff --git a/R/table_utils.R b/R/table_utils.R index b875a0e2..7e2e6e59 100644 --- a/R/table_utils.R +++ b/R/table_utils.R @@ -18,11 +18,24 @@ #' @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") #' x <- system.file(x, package="SpatialData") -#' x <- readSpatialData(x, anndataR=FALSE) +#' x <- readSpatialData(x, anndataR=TRUE) #' #' # check if element has a 'table' #' hasTable(x, "blobs_points") @@ -98,15 +111,12 @@ setMethod("hasTable", c("SpatialData", "character"), \(x, i, name=FALSE) { match.arg(i, unlist(nms[idx])) # count occurrences t <- lapply(tables(x), \(t) meta(t)$region) - n <- vapply(seq_along(t), \(.) i %in% t[[.]], numeric(1)) - nan <- all(n == 0) + ok <- vapply(t, \(.) i %in% ., logical(1)) # failure when no/many matches - if (name) { - dup <- length(unique(n)) != length(n) - if (nan) stop("no 'table' found for 'i'") - if (dup) stop("multiple 'table's found for 'i'") - return(names(t)[n == 1]) - } else return(!nan) + if (!name) return(any(ok)) + if (!any(ok)) stop("no 'table' found for 'i'") + if (sum(ok) > 1) stop("multiple 'table's found for 'i'") + return(names(t)[ok]) }) # get ---- @@ -121,15 +131,15 @@ setMethod("getTable", c("SpatialData", "ANY"), \(x, i, drop=TRUE) .invalid_i()) setMethod("getTable", c("SpatialData", "character"), \(x, i, drop=TRUE) { stopifnot(isTRUE(drop) || isFALSE(drop)) # get 'table' annotating 'i', if any - t <- table(x, hasTable(x, i, name=TRUE)) + t <- SpatialData::table(x, hasTable(x, i, name=TRUE)) # only keep observations belonging to 'i' (optional) if (drop) { rk <- meta(t)$region_key # TODO: check the replacement below, search colData as well? # t <- t[, int_colData(t)[[rk]] == i] - coldata <- - if(rk %in% names(cd <- int_colData(t))) cd[[rk]] else colData(t)[[rk]] - t <- t[, coldata == i] + int <- rk %in% names(cd <- int_colData(t)) + cd <- if (int) cd[[rk]] else t[[rk]] + t <- t[, cd == i] } return(t) }) @@ -221,8 +231,7 @@ setMethod("setTable", int_colData(sce) <- cbind(int_colData(sce), icd) md <- list(region=i, region_key=rk, instance_key=ik) int_metadata(sce)[[sda]] <- md - table(x, name) <- sce - return(x) + SpatialData::`table<-`(x, i=name, value=sce) }) # val ---- diff --git a/R/trans.R b/R/trans.R index bef7a277..af9ec51f 100644 --- a/R/trans.R +++ b/R/trans.R @@ -1,14 +1,19 @@ #' @name trans #' @rdname trans #' @title Transformations -#' @aliases scale rotate translation +#' @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 t transformation data. +#' @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}). +#' @param k scalar index specifying which scale to use; +#' \code{Inf} to use lowest available resolution; +#' 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") @@ -16,14 +21,14 @@ #' #' # image #' y <- x -#' image(y) <- scale(image(y), 1, c(1, 1, 1/3)) -#' CTpath(image(x), "global") -#' CTpath(image(y), "global") +#' image(y) <- scale(image(y), c(1, 1, 1/3)) +#' dim(image(x)) +#' dim(image(y)) #' #' # point #' y <- x #' point(y, "rot") <- rotate(point(y), 20) -#' point(y, "wide") <- scale(point(y), c(1, 1.2)) +#' point(y, "wide") <- scale(point(y), c(1.2, 1)) #' #' xy0 <- as.data.frame(point(y)) #' xy1 <- as.data.frame(point(y, "rot")) @@ -36,34 +41,99 @@ #' # shape #' y <- x #' shape(y, "rot") <- rotate(shape(y), 5) -#' shape(y, "high") <- scale(shape(y), c(1.2, 1)) +#' shape(y, "wide") <- scale(shape(y), c(1.2, 1)) #' shape(y, "left") <- translation(shape(y), c(-5, 0)) -#' -#' graph::plot(CTgraph(y)) +#' y["shapes", c("rot", "wide", "left")] NULL -# image ---- +# rotation matrix to rotate points counter-clockwise through an angle 't' +.R <- \(t) matrix(c(cos(t), sin(t), -sin(t), cos(t)), 2, 2) + +# array ---- +.mirror <- \(x, t, k=1) { + d <- length(dim(x)) == 3 + i <- if (d) c(1, 3, 2) else c(2, 1) + x@data <- list(aperm(data(x, k), i)) + rotate(x, t, k=1) +} + +#' @export #' @rdname trans +setMethod("mirror", "sdArray", \(x, t=c("v", "h"), k=1, ...) + switch(match.arg(t), v=flip, h=flop)(x, k)) + +#' @export +#' @rdname trans +setMethod("flip", "sdArray", \(x, k=1, ...) .mirror(x, -90, k)) + +#' @export +#' @rdname trans +setMethod("flop", "sdArray", \(x, k=1, ...) .mirror(x, 90, k)) + +#' @importFrom methods as +#' @importFrom S4Vectors metadata<- +.trans_a <- \(x, f, k=1) { + a <- f(aperm(as.array(data(x, k)))) + metadata(x)$data_type <- data_type(x) + x@data <- list(as(aperm(a), "SparseArray")) + return(x) +} + #' @export -setMethod("scale", c("ImageArray", "numeric"), \(x, j, t, ...) { - stopifnot(length(t) == 3, t > 0) +#' @rdname trans +#' @importFrom EBImage resize +setMethod("scale", c("sdArray", "numeric"), \(x, t, k=1, ...) { + stopifnot(length(t) == length(dim(x)), is.finite(t), t > 0) if (all(t == 1)) return(x) - if (is.numeric(j)) j <- CTname(x) - j <- match.arg(j, CTname(x)) - addCT(x, name=j, type="scale", data=t) + n <- length(d <- dim(data(x, k))) + f <- \(.) resize(., + w=d[n]*t[n], + h=d[n-1]*t[n-1]) + .trans_a(x, f, k) }) -# label ---- +#' @export +#' @rdname trans +#' @importFrom EBImage rotate +setMethod("rotate", c("sdArray", "numeric"), \(x, t, k=1,...) { + # negate angle since 'EBImage' rotates clockwise + stopifnot(length(t) == 1, is.finite(t)) + if (t %% 360 == 0) return(x) + f <- \(.) EBImage::rotate(., -t) + if (length(d <- dim(data(x, k))) == 3) d <- d[-1] + metadata(x)$wh <- lapply(rev(d), \(.) c(c(0, .) %*% .R(-t*pi/180))) + .trans_a(x, f, k) +}) -#TODO +#' @export +#' @rdname trans +#' @importFrom EBImage translate +setMethod("translation", c("sdArray", "numeric"), \(x, t, k=1, ...) { + stopifnot(length(t) == length(dim(x)), is.finite(t)) + if (all(t == 0)) return(x) + d <- dim(data(x, k)) + if (length(d) == 3) { + # protect non-spatial dim. + t <- t[-1]; d <- d[-1] + } + t <- rev(t); d <- rev(d) + metadata(x)$wh <- list( + c(t[1], t[1]+d[1]), + c(t[2], t[2]+d[2])) + #f <- \(.) translate(., t, output.dim=d+t) + #.trans_a(x, f) + x +}) # point ---- +#' @export #' @rdname trans #' @importFrom dplyr mutate -#' @export setMethod("scale", c("PointFrame", "numeric"), \(x, t, ...) { + stopifnot(is.numeric(t), length(t) == 2, t > 0, is.finite(t)) + if (all(t == 1)) return(x) y <- NULL # R CMD check x@data <- x@data |> mutate(x=x*t[1]) |> @@ -71,18 +141,19 @@ setMethod("scale", c("PointFrame", "numeric"), \(x, t, ...) { return(x) }) +#' @export #' @rdname trans #' @importFrom dplyr mutate select -#' @export setMethod("rotate", c("PointFrame", "numeric"), \(x, t, ...) { - y <- .y <- .x <- NULL # R CMD check + stopifnot(is.numeric(t), length(t) == 1, is.finite(t)) + if (t %% 360 == 0) return(x) + y <- a <- b <- c <- d <- NULL # R CMD check R <- .R(t*pi/180) x@data <- x@data |> - mutate(.x=x*R[1,1], .y=y*R[1,2]) |> - mutate(x=.x+.y) |> - mutate(.x=x*R[2,1], .y=y*R[2,2]) |> - mutate(y=.x+.y) |> - select(-.x, -.y) + mutate(a=x*R[1,1], b=y*R[1,2]) |> + mutate(c=x*R[2,1], d=y*R[2,2]) |> + mutate(x=a+b, y=c+d) |> + select(-c(a,b, c,d)) return(x) }) @@ -90,6 +161,8 @@ setMethod("rotate", c("PointFrame", "numeric"), \(x, t, ...) { #' @importFrom dplyr mutate select #' @export setMethod("translation", c("PointFrame", "numeric"), \(x, t, ...) { + stopifnot(is.numeric(t), length(t) == 2, is.finite(t)) + if (all(t == 0)) return(x) y <- NULL # R CMD check x@data <- x@data |> mutate(x=x+t[1]) |> @@ -99,11 +172,23 @@ setMethod("translation", c("PointFrame", "numeric"), \(x, t, ...) { # shape ---- +# TODO: do this w/o realizing +#' @importFrom sf st_as_sf st_geometry st_geometry<- +.trans_s <- \(x, f) { + y <- st_as_sf(data(x)) + xy <- st_coordinates(y) + xy <- data.frame(f(xy)) + xy <- st_as_sf(xy, coords=names(xy)) + st_geometry(y) <- st_geometry(xy) + x@data <- y + return(x) +} + #' @rdname trans #' @importFrom sf st_as_sf st_coordinates #' @export setMethod("scale", c("ShapeFrame", "numeric"), \(x, t, ...) { - stopifnot(is.numeric(t), length(t) == 2, t > 0, all(is.finite(t))) + stopifnot(is.numeric(t), length(t) == 2, t > 0, is.finite(t)) .trans_s(x, \(xy) sweep(xy, 2, t, `*`)) }) @@ -122,58 +207,3 @@ setMethod("translation", c("ShapeFrame", "numeric"), \(x, t, ...) { stopifnot(is.numeric(t), length(t) == 2, is.finite(t)) .trans_s(x, \(xy) sweep(xy, 2, t, `+`)) }) - -# utils ---- - -# rotation matrix to rotate points counter-clockwise through an angle 't' -.R <- function(t) matrix(c(cos(t), -sin(t), sin(t), cos(t)), 2, 2) - -# count occurrences of each coordinate space; -# return most frequent (in order of appearance) -.guess_space <- \(x) { - nms <- lapply(rownames(x), \(l) - lapply(colnames(x)[[l]], \(e) - CTname(x[[l]][[e]]))) - cnt <- table(nms <- unlist(nms)) - cnt <- cnt[unique(nms)] - names(which.max(cnt)) -} - -.trans_xy <- \(xy, ts, rev=FALSE) { - if (rev) ts <- rev(ts) - for (. in seq_along(ts)) { - t <- ts[[.]]$type - d <- ts[[.]]$data - if (length(d) == 3) - d <- d[-1] - switch(t, - identity={}, - scale={ - op <- ifelse(rev, `/`, `*`) - xy$x <- op(xy$x, d[2]) - xy$y <- op(xy$y, d[1]) - }, - rotate={ - xy <- xy %*% .R(d*pi/180) - }, - translation={ - op <- ifelse(rev, `-`, `+`) - xy$x <- op(xy$x, d[2]) - xy$y <- op(xy$y, d[1]) - }) - } - return(xy) -} - -# transform 'ShapeFrame' by realizing, -# and updating 'sf' geometry coordinates -#' @importFrom sf st_as_sf st_geometry st_geometry<- -.trans_s <- \(x, f) { - y <- st_as_sf(data(x)) - xy <- st_coordinates(y) - xy <- data.frame(f(xy)) - xy <- st_as_sf(xy, coords=names(xy)) - st_geometry(y) <- st_geometry(xy) - x@data <- y - return(x) -} \ No newline at end of file diff --git a/R/tx_to_ext.R b/R/tx_to_ext.R index 96128c7c..e4421a28 100644 --- a/R/tx_to_ext.R +++ b/R/tx_to_ext.R @@ -21,15 +21,20 @@ #' 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() -#' do_tx_to_ext( -#' srcdir=src, dest=td, -#' coordinate_system="global", -#' maintain_positioning=FALSE, -#' target_width=400.) -#' (sd <- readSpatialData(td)) +#' # TODO: for now this example converts to a zarr v3 so we comment out, +#' # check again later +#' +#' # do_tx_to_ext( +#' # srcdir=src, dest=td, +#' # coordinate_system="global", +#' # maintain_positioning=FALSE, +#' # target_width=400.) +#' # (sd <- readSpatialData(td)) #' #' @export do_tx_to_ext <- function(srcdir, dest, coordinate_system, ...) { diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 00000000..8618e2ad --- /dev/null +++ b/R/utils.R @@ -0,0 +1,128 @@ +#' @name utils +#' @rdname utils +#' @title Utilities +#' @aliases centroids extent +#' +#' @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") +#' x <- system.file(x, package="SpatialData") +#' x <- readSpatialData(x, tables=FALSE) +#' +#' centroids(label(x)) +#' centroids(shape(x)) +#' centroids(shape(x, 3), "list") +#' +#' head(centroids(point(x))) +#' xy <- centroids(point(x), "list") +#' plot(xy$gene_a, col=a <- "red") +#' points(xy$gene_b, col=b <- "blue") +#' legend("topright", legend=names(xy), col=c(a, b), pch=21) +#' +#' # object-wide +#' extent(x) +#' +#' # element-wise +#' extent(label(x)) +#' extent(point(x)) +#' extent(shape(x)) +NULL + +# centroids ---- + +#' @export +#' @rdname utils +setMethod("centroids", "ANY", \(x, ...) stop("'centroids' ", + "only supported for label, shape, and point elements")) + +#' @export +#' @rdname utils +#' @importFrom Matrix summary +setMethod("centroids", "LabelArray", \(x, + as=c("data.frame", "matrix")) { + 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))) + dimnames(xy) <- list(NULL, c("x", "y", "i")) + if (as == "matrix") return(xy) + xy <- as.data.frame(xy) + xy$i <- factor(xy$i); xy +}) + +#' @export +#' @rdname utils +#' @importFrom sf st_as_sf st_geometry_type st_coordinates +setMethod("centroids", "ShapeFrame", \(x, + as=c("data.frame", "matrix", "list")) { + as <- match.arg(as) + y <- st_as_sf(data(x)) + xy <- st_coordinates(y) + colnames(xy)[c(1, 2)] <- c("x", "y") + if (as == "matrix") return(xy) + xy <- as.data.frame(xy) + rownames(xy) <- NULL + if (ncol(xy) > 2) + for (. in seq(3, ncol(xy))) + xy[[.]] <- factor(xy[[.]], unique(xy[[.]])) + if (as == "data.frame") return(xy) + split(xy, xy[seq(3, ncol(xy))]) +}) + +#' @export +#' @rdname utils +setMethod("centroids", "PointFrame", \(x, + as=c("data.frame", "list")) { + as <- match.arg(as) + i <- feature_key(x) + xy <- data(x)[, c("x", "y", i)] + xy <- as.data.frame(xy) + if (as == "data.frame") return(xy) + lapply(split(xy, xy[[i]]), `[`, -3) +}) + +# extent ---- + +# TODO: this needs more work to consider transformations + +#' @export +#' @rdname utils +setMethod("extent", "SpatialData", \(x) { + ls <- setdiff(.LAYERS, "tables") + ex <- lapply(ls, \(.) lapply(x[[.]], extent)) + ex <- Reduce(c, ex) + names(xy) <- xy <- c("x", "y") + lapply(xy, \(z) { + d <- vapply(ex, \(.) .[[z]], numeric(2)) + c(min(d[1, ]), max(d[2, ])) + }) +}) + +#' @export +#' @rdname utils +setMethod("extent", "SpatialDataElement", \(x) { + if (is(x, "sdArray")) { + nm <- vapply(ax <- axes(x), \(.) .$name, character(1)) + xy <- vapply(ax, \(.) .$type == "space", logical(1)) + d <- dim(x); names(d) <- nm + lapply(d[xy], \(.) c(0, .)) + } else { + ax <- unlist(axes(x)) + xy <- centroids(x)[ax] + apply(xy, 2, range, simplify=FALSE) + } +}) diff --git a/R/validity.R b/R/validity.R index 5da54752..f30ed209 100644 --- a/R/validity.R +++ b/R/validity.R @@ -1,80 +1,122 @@ -# TODO: everything... - -.validateTable <- function(object) { +# https://spatialdata.scverse.org/en/latest/design_doc.html#table-table-of-annotations-for-regions +#' @importFrom SingleCellExperiment int_metadata int_colData +.validateTables <- \(object) { msg <- c() + sce <- \(.) is(., "SingleCellExperiment") for (i in seq_along(tables(object))) { - t <- table(object, i) - if (!is(t, "SingleCellExperiment")) { - msg <- c(msg, paste0("Table ", i, " is not a 'SingleCellExperiment'")) + ok <- sce(se <- table(object, i)) + if (!ok) msg <- c(msg, paste0( + i, "-th table is not a 'SingleCellExperiment'")) + if (!ok) next + md <- int_metadata(se)$spatialdata_attrs + nm <- c("region", "region_key", "instance_key") + .nm <- sprintf("'%s'", paste(nm, collapse="/")) + if (any(ok <- nm %in% names(md))) { + if (!all(ok)) msg <- c(msg, paste0( + i, "-th table missing ", .nm, "; must set all if any")) + ok <- all(vapply(md, is.character, logical(1))) + if (!ok) msg <- c(msg, paste0( + i, "-th table's ", .nm, " is not of type character")) + ok <- all(vapply(intersect(md, nm[-1]), length, integer(1)) == 1) + if (!ok) msg <- c(msg, paste0( + i, "-th table's 'region/instance_key' is not length 1")) + ok <- !is.null(int_colData(se)[[md$region_key]]) + if (!ok) msg <- c(msg, paste0( + i, "-th table missing 'region_key' column in 'int_colData'")) + 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'")) + } - # TODO: validate int_metadata$spatialdata_attrs - # md <- int_metadata(sce)[["spatialdata_attrs"]] - # if (!all(c("region_key", "instance_key") %in% names(md))) { - # msg <- c(msg, paste0("region_key/instance_key not present in ", - # i, "-th sce int_metadata")) - # } } + na <- setdiff( + unlist(lapply(tables(object), \(.) if (sce(.)) region(.))), + unlist(colnames(object)[setdiff(.LAYERS, "tables")])) # don't flip! + if (length(na)) + msg <- c(msg, paste( + "table region(s) not found in any layer:", + paste(sprintf("'%s'", na), collapse=", "))) return(msg) } -.validatePointFrame <- function(object) { - msg <- NULL - # Checks if the points have the x,y coordinates, as they are hard-coded - # in the plot functions - if (length(points(object))) { # there are some cases where the points are empty - if (!is.null(data(point(object)))) { - np <- length(points(object)) - for (i in seq_len(np)) { - dfi <- data(point(object, i)) - if (!all(c("x", "y") %in% names(dfi))) { - msg <- c(msg, paste0("'x' and 'y' missing in data point ", i)) - } - } - } +.validateImageArray <- \(object) { + msg <- c() + res <- length(object) + for (k in seq_len(res)) { + x <- data(object, k) + if (length(dim(x)) != 3) msg <- c(msg, paste( + "'ImageArray' resolution", k, "is not 3D")) + if (!type(x) %in% c("double", "integer")) msg <- c(msg, paste( + "'ImageArray' resolution", k, "is not of type double or integer")) } return(msg) } +#' @importFrom S4Vectors setValidity2 +setValidity2("ImageArray", .validateImageArray) -.validateImageArray <- function(object) { +#' @importFrom ZarrArray type +.validateLabelArray <- \(object) { msg <- c() - if (ni <- length(images(object))) { - for (i in seq_along(ni)) { - ai <- as.array(aperm(data(image(object,1))/255, perm=c(3,2,1))) - for (j in dim(ai)[3]) { - if (!all(vapply(ai[,,j], is.numeric, logical(1)))) { - msg <- c(msg, paste0("Image ", i, " channel ", j, " not numeric")) - } - } - } + res <- length(object) + for (k in seq_len(res)) { + x <- data(object, k) + if (length(dim(x)) != 2) msg <- c(msg, paste( + "'LabelArray' resolution", k, "is not 2D")) + if (type(x) != "integer") msg <- c(msg, paste( + "'LabelArray' resolution", k, "is not of type integer")) } return(msg) } +#' @importFrom S4Vectors setValidity2 +setValidity2("LabelArray", .validateLabelArray) + +.validatePointFrame <- \(object) { + msg <- c() + 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) +} +#' @importFrom S4Vectors setValidity2 +setValidity2("PointFrame", .validatePointFrame) + +.validateShapeFrame <- \(object) { + msg <- c() + if (!nrow(object)) return(msg) + if (!"geometry" %in% names(object)) msg <- c(msg, "'ShapeFrame' missing 'geometry'.") + return(msg) +} +#' @importFrom S4Vectors setValidity2 +setValidity2("ShapeFrame", .validateShapeFrame) #' @importFrom methods is .validateSpatialData <- \(x) { + msg <- c() typ <- c( images="ImageArray", labels="LabelArray", points="PointFrame", shapes="ShapeFrame", tables="SingleCellExperiment") - msg <- NULL for (. in names(typ)) if (length(x[[.]])) if (!all(vapply(x[[.]], \(y) is(y, typ[.]), logical(1)))) msg <- c(msg, sprintf("'%s' should be a list of '%s'", ., typ[.])) - msg <- c(msg, .validatePointFrame(x)) - msg <- c(msg, .validateTable(x)) - for (y in labels(x)) .validateZattrsLabelArray(y) - if (length(msg)) - return(msg) - return(TRUE) + # TODO: validate .zattrs across all layers + 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)) + msg <- c(msg, .validateTables(x)) + return(msg) } #' @importFrom S4Vectors setValidity2 setValidity2("SpatialData", .validateSpatialData) +# TODO: version-specific .zattrs validation for all layers + .validateZattrs_multiscales <- \(x, msg) { - if (is.null(ms <- x$multiscales)) + if (is.null(ms <- x$multiscales[[1]])) msg <- c(msg, "missing 'multiscales'") # MUST contain for (. in c("axes", "datasets")) @@ -96,18 +138,18 @@ setValidity2("SpatialData", .validateSpatialData) .validateZattrs_coordTrans <- \(x, msg) { if (!is.list(ct <- x$coordinateTransformations)) msg <- c(msg, "missing or non-list 'coordTrans'") - ct <- ct[[1]] - for (. in c("input", "output", "type")) - if (is.null(ct[[.]])) - msg <- c(msg, sprintf("'coordTrans' missing '%s'", .)) + for (i in seq_along(ct)) + for (j in c("input", "output", "type")) + if (is.null(ct[[i]][[j]])) + msg <- c(msg, sprintf("'coordTrans' %s missing '%s'", i, j)) return(msg) } .validateZattrsLabelArray <- \(x) { msg <- c() za <- meta(x) msg <- .validateZattrs_multiscales(za, msg) - ms <- za$multiscales + ms <- za$multiscales[[1]] msg <- .validateZattrs_axes(ms, msg) msg <- .validateZattrs_coordTrans(ms, msg) - if (length(msg)) return(msg) else return(TRUE) + return(msg) } diff --git a/R/write.R b/R/write.R index 8217abfe..ccd29f41 100644 --- a/R/write.R +++ b/R/write.R @@ -33,29 +33,45 @@ NULL #' @rdname writeSpatialData #' @export -writeSpatialData <- function(x, name, path, replace = TRUE, version = "v2", +writeSpatialData <- function(x, name, path, replace = TRUE, version = "v2", ...) { zarr.path <- .replace_zarr(name, path, replace, version) - + + # write root-level spatialdata_attrs for v3 (Python uses this to pick the read path) + if (version == "v3") + Rarr::write_zarr_attributes(zarr.path, + new.zattrs = list(spatialdata_attrs = list(version = "0.2"))) + # write points . <- lapply(pointNames(x), \(.){ - writePoint(point(x, .),., path = zarr.path, replace = replace) + writePoint(point(x, .),., path = zarr.path, replace = replace, version = version) }) - + # write shapes . <- lapply(shapeNames(x), \(.){ - writeShape(shape(x, .),., path = zarr.path, replace = replace) + writeShape(shape(x, .),., path = zarr.path, replace = replace, version = version) }) - + # write images . <- lapply(imageNames(x), \(.){ - writeImage(image(x, .),., path = zarr.path, replace = replace) + writeImage(image(x, .),., path = zarr.path, replace = replace, version = version) }) - + # write labels . <- lapply(labelNames(x), \(.){ - writeLabel(label(x, .),., path = zarr.path, replace = replace) + writeLabel(label(x, .),., path = zarr.path, replace = replace, version = version) }) + + # write labels group metadata listing all label names (required by spatialdata spec) + # v2: {"labels": [...]}, v3: {"ome": {"labels": [...]}} + lnames <- labelNames(x) + if (length(lnames) > 0L) { + labels.dir <- file.path(zarr.path, "labels") + lnames_zattrs <- if (version == "v3") + list(ome = list(labels = as.list(lnames))) else + list(labels = as.list(lnames)) + Rarr::write_zarr_attributes(labels.dir, new.zattrs = lnames_zattrs) + } } #' @rdname writeSpatialData @@ -64,9 +80,12 @@ writePoint <- function(x, name, path, replace = TRUE, version = "v2") { # if no PointFrames were written before, update zarr store zarr.group <- .make_zarr_group(x, name, file.path(path, "points"), replace, version) # write meta - write_zattrs(path = zarr.group, meta(x)) + zattrs <- as.list(meta(x)) + if (version == "v3") zattrs$spatialdata_attrs$version <- "0.2" + Rarr::write_zarr_attributes(zarr.group, new.zattrs = zattrs) # write data - arrow::write_dataset(data(x), file.path(zarr.group, "points.parquet")) + arrow::write_dataset(data(x), file.path(zarr.group, "points.parquet"), + basename_template = "part.{i}.parquet") } #' @rdname writeSpatialData @@ -75,45 +94,74 @@ writeShape <- function(x, name, path, replace = TRUE, version = "v2") { # if no ShapeFrames were written before, update zarr store zarr.group <- .make_zarr_group(x, name, file.path(path, "shapes"), replace, version) # write meta - write_zattrs(path = zarr.group, meta(x)) - # write data - arrow::write_dataset(data(x), file.path(zarr.group, "shapes.parquet")) + zattrs <- as.list(meta(x)) + if (version == "v3") zattrs$spatialdata_attrs$version <- "0.3" + Rarr::write_zarr_attributes(zarr.group, new.zattrs = zattrs) + # write data as a single parquet file (matches Python spatialdata convention) + arrow::write_parquet(data(x), file.path(zarr.group, "shapes.parquet")) } #' @rdname writeSpatialData +#' @importFrom Rarr write_zarr_array +#' @importFrom DelayedArray realize #' @export writeImage <- function(x, name, path, replace = TRUE, version = "v2") { # if no ImageArray were written before, update zarr store zarr.group <- .make_zarr_group(x, name, file.path(path, "images"), replace, version) - # write meta - write_zattrs(path = zarr.group, meta(x)) + zarr_version <- if (version == "v3") 3L else 2L + dimension_names <- .get_multiscale_axes(meta(x)) + # write meta: for v3, OME-NGFF content goes under "ome" key in attributes + zattrs <- .wrap_ome_for_v3(meta(x), version) + if (version == "v3") zattrs$spatialdata_attrs$version <- "0.3" + Rarr::write_zarr_attributes(zarr.group, new.zattrs = zattrs) # write data lapply( .get_multiscales_dataset_paths(meta(x)), \(.){ - da <- data(x, . + 1) - Rarr::write_zarr_array(realize(da), - zarr_array_path = file.path(zarr.group, .), - chunk_dim = dim(da)) + arr <- realize(data(x, . + 1)) + # Rarr reads names(dimnames(x)) to write dimension_names in v3 zarr.json + if (!is.null(dimension_names)) + dimnames(arr) <- setNames(vector("list", length(dim(arr))), dimension_names) + Rarr::write_zarr_array(arr, + zarr_array_path = file.path(zarr.group, .), + chunk_dim = dim(arr), + order = "C", + dimension_separator = "/", + zarr_version = zarr_version) + if (version == "v3") + .normalize_v3_array_metadata(file.path(zarr.group, .)) } ) } #' @rdname writeSpatialData +#' @importFrom Rarr write_zarr_array +#' @importFrom DelayedArray realize #' @export writeLabel <- function(x, name, path, replace = TRUE, version = "v2") { # if no LabelArray were written before, update zarr store zarr.group <- .make_zarr_group(x, name, file.path(path, "labels"), replace, version) - # write meta - write_zattrs(path = zarr.group, meta(x)) + zarr_version <- if (version == "v3") 3L else 2L + dimension_names <- .get_multiscale_axes(meta(x)) + # write meta: for v3, OME-NGFF content goes under "ome" key in attributes + zattrs <- .wrap_ome_for_v3(meta(x), version) + if (version == "v3") zattrs$spatialdata_attrs$version <- "0.3" + Rarr::write_zarr_attributes(zarr.group, new.zattrs = zattrs) # write data lapply( .get_multiscales_dataset_paths(meta(x)), \(.){ - da <- data(x, . + 1) - Rarr::write_zarr_array(realize(da), - zarr_array_path = file.path(zarr.group, .), - chunk_dim = dim(da)) + arr <- realize(data(x, . + 1)) + if (!is.null(dimension_names)) + dimnames(arr) <- setNames(vector("list", length(dim(arr))), dimension_names) + Rarr::write_zarr_array(arr, + zarr_array_path = file.path(zarr.group, .), + chunk_dim = dim(arr), + order = "C", + dimension_separator = "/", + zarr_version = zarr_version) + if (version == "v3") + .normalize_v3_array_metadata(file.path(zarr.group, .)) } ) -} \ No newline at end of file +} diff --git a/R/zarr_utils.R b/R/zarr_utils.R index 15f7e5b4..970dd3b1 100644 --- a/R/zarr_utils.R +++ b/R/zarr_utils.R @@ -9,23 +9,25 @@ create_zarr_group <- function(store, name, version = "v2"){ split.name <- strsplit(name, split = "\\/")[[1]] if(length(split.name) > 1){ - split.name <- vapply(seq_len(length(split.name)), - function(x) paste(split.name[seq_len(x)], collapse = "/"), - FUN.VALUE = character(1)) + split.name <- vapply(seq_len(length(split.name)), + function(x) paste(split.name[seq_len(x)], collapse = "/"), + FUN.VALUE = character(1)) split.name <- rev(tail(split.name,2)) if(!dir.exists(file.path(store,split.name[2]))) - create_zarr_group(store = store, name = split.name[2]) + create_zarr_group(store = store, name = split.name[2], version = version) } dir.create(file.path(store, split.name[1]), showWarnings = FALSE) - switch(version, + switch(version, v2 = { write("{\"zarr_format\":2}", file = file.path(store, split.name[1], ".zgroup"))}, v3 = { - stop("Currently only zarr v2 is supported!") + write( + "{\"zarr_format\":3,\"node_type\":\"group\",\"attributes\":{}}", + file = file.path(store, split.name[1], "zarr.json")) }, - stop("only zarr v2 is supported. Use version = 'v2'") + stop("version must be 'v2' or 'v3'") ) - + } #' create_zarr @@ -47,70 +49,6 @@ create_zarr <- function(name, dir, version = "v2"){ create_zarr_group(store = dir, name = name, version = version) } -#' Read the .zattrs file associated with a Zarr array or group -#' -#' @param path A character vector of length 1. This provides the -#' path to a Zarr array or group. This can either be on a local file -#' system or on S3 storage. -#' @param s3_client A list representing an S3 client. This should be produced -#' by [paws.storage::s3()]. -#' -#' @returns A list containing the .zattrs elements -#' -#' @importFrom jsonlite read_json fromJSON -#' @importFrom stringr str_extract str_remove -#' -#' @export -read_zattrs <- function(path, s3_client = NULL) { - path <- .normalize_array_path(path) - zattrs_path <- paste0(path, ".zattrs") - - if(!file.exists(zattrs_path)) - stop("The group or array does not contain attributes (.zattrs)") - - if (!is.null(s3_client)) { - stop("no s3 support!") - } else { - zattrs <- read_json(zattrs_path, simplifyVector = TRUE) - } - return(zattrs) -} - -#' Read the .zattrs file associated with a Zarr array or group -#' -#' @param path A character vector of length 1. This provides the -#' path to a Zarr array or group. -#' @param new.zattrs a list inserted to .zattrs at the \code{path}. -#' @param overwrite if TRUE, existing .zattrs elements will be overwritten by \code{new.zattrs}. -#' -#' @importFrom jsonlite toJSON -#' -#' @export -write_zattrs <- function(path, new.zattrs = list(), overwrite = TRUE){ - path <- .normalize_array_path(path) - zattrs_path <- paste0(path, ".zattrs") - - if(is.null(names(new.zattrs))) - stop("list elements should be named") - - if("" %in% names(new.zattrs)){ - message("Ignoring unnamed list elements") - new.zattrs <- new.zattrs[which(names(new.zattrs == ""))] - } - - if(file.exists(zattrs_path)){ - old.zattrs <- read_json(zattrs_path) - if(overwrite){ - old.zattrs <- old.zattrs[setdiff(names(old.zattrs), names(new.zattrs))] - } else { - new.zattrs <- new.zattrs[setdiff(names(new.zattrs), names(old.zattrs))] - } - new.zattrs <- c(old.zattrs, new.zattrs) - } - - json <- .format_json(toJSON(new.zattrs, auto_unbox = TRUE, pretty = TRUE, null = "null")) - write(x = json, file = zattrs_path) -} .replace_zarr <- function(name, path, replace, version = "v2") { @@ -129,8 +67,14 @@ write_zattrs <- function(path, new.zattrs = list(), overwrite = TRUE){ .make_zarr_group <- function(x, name, path, replace, version){ # gd <- file.path(path, "points") - if(!dir.exists(path)) + if(!dir.exists(path)) { dir.create(path) + switch(version, + v2 = write('{"zarr_format":2}', file = file.path(path, ".zgroup")), + v3 = write('{"zarr_format":3,"node_type":"group","attributes":{}}', + file = file.path(path, "zarr.json")) + ) + } ng <- file.path(path, name) if(replace){ unlink(ng, recursive = TRUE) @@ -145,46 +89,66 @@ write_zattrs <- function(path, new.zattrs = list(), overwrite = TRUE){ return(ng) } -#' Normalize a Zarr array path -#' -#' Taken from https://zarr.readthedocs.io/en/stable/spec/v2.html#logical-storage-paths -#' -#' @param path Character vector of length 1 giving the path to be normalised. -#' -#' @returns A character vector of length 1 containing the normalised path. -#' -#' @keywords Internal -.normalize_array_path <- function(path) { - ## we strip the protocol because it gets messed up by the slash removal later - if (grepl(x = path, pattern = "^((https?://)|(s3://)).*$")) { - root <- gsub(x = path, pattern = "^((https?://)|(s3://)).*$", - replacement = "\\1") - path <- gsub(x = path, pattern = "^((https?://)|(s3://))(.*$)", - replacement = "\\4") - } else { - ## Replace all backward slash ("\\") with forward slash ("/") - path <- gsub(x = path, pattern = "\\", replacement = "/", fixed = TRUE) - path <- normalizePath(path, winslash = "/", mustWork = FALSE) - root <- gsub(x = path, "(^[[:alnum:]:.]*/)(.*)", replacement = "\\1") - path <- gsub(x = path, "(^[[:alnum:]:.]*/)(.*)", replacement = "\\2") + +# For zarr v3, OME-NGFF content (multiscales, omero, image-label) must be +# nested under an "ome" key inside "attributes"; spatialdata_attrs stays at top. +# If the metadata was read from a v3 store it already has "ome", so skip wrapping. +.wrap_ome_for_v3 <- function(zattrs, version) { + if (version != "v3" || "ome" %in% names(zattrs)) return(as.list(zattrs)) + ome_keys <- setdiff(names(zattrs), "spatialdata_attrs") + ome_content <- as.list(zattrs)[ome_keys] + # Strip v2-only fields from each multiscales entry + if (!is.null(ome_content$multiscales)) { + ome_content$multiscales <- lapply(ome_content$multiscales, function(ms) { + ms[setdiff(names(ms), c("version", "metadata"))] + }) } - - ## Strip any leading "/" characters - path <- gsub(x = path, pattern = "^/", replacement = "", fixed = FALSE) - ## Strip any trailing "/" characters - path <- gsub(x = path, pattern = "/$", replacement = "", fixed = FALSE) - ## Collapse any sequence of more than one "/" character into a single "/" - path <- gsub(x = path, pattern = "//*", replacement = "/", fixed = FALSE) - ## The key prefix is then obtained by appending a single "/" character to - ## the normalized logical path. - path <- paste0(root, path, "/") - - return(path) + list( + ome = c(list(version = "0.5-dev-spatialdata"), ome_content), + spatialdata_attrs = zattrs[["spatialdata_attrs"]] + ) } -.format_json <- function(json) { - json <- gsub(x = json, pattern = "[", replacement = "[\n ", fixed = TRUE) - json <- gsub(x = json, pattern = "],", replacement = "\n ],", fixed = TRUE) - json <- gsub(x = json, pattern = ", ", replacement = ",\n ", fixed = TRUE) - return(json) -} \ No newline at end of file +.get_multiscale_axes <- function(zattrs) { + multiscales <- zattrs[["multiscales"]] + if (is.null(multiscales) && !is.null(zattrs[["ome"]])) + multiscales <- zattrs[["ome"]][["multiscales"]] + if (is.null(multiscales) || length(multiscales) == 0L) return(NULL) + axes <- multiscales[[1]][["axes"]] + if (is.null(axes) || length(axes) == 0L) return(NULL) + vapply(axes, `[[`, character(1), "name") +} + +# Post-processes Rarr-written v3 array zarr.json: +# 1. Sorts codecs to required order [array-array → array-bytes → bytes-bytes]. +# Rarr currently serialises them as [transpose, zstd, bytes] which Python rejects. +# 2. Adds "attributes": {} and "storage_transformers": [] which Python zarr expects +# but Rarr does not emit. +# dimension_names are handled upstream by setting names(dimnames()) before write_zarr_array. +.normalize_v3_array_metadata <- function(zarr_array_path) { + metadata_path <- file.path(zarr_array_path, "zarr.json") + if (!file.exists(metadata_path)) return(invisible(FALSE)) + + metadata <- jsonlite::read_json(metadata_path, simplifyVector = FALSE) + codecs <- metadata[["codecs"]] + if (!is.null(codecs) && length(codecs) > 1L) { + codec_names <- vapply(codecs, `[[`, character(1), "name") + codec_stage <- ifelse( + codec_names %in% "transpose", 1L, + ifelse(codec_names %in% c("bytes", "vlen-utf8", "vlen_utf8"), 2L, 3L) + ) + metadata[["codecs"]] <- codecs[order(codec_stage)] + } + + if (is.null(metadata[["attributes"]])) metadata[["attributes"]] <- list() + if (is.null(metadata[["storage_transformers"]])) metadata[["storage_transformers"]] <- list() + + jsonlite::write_json( + metadata, + path = metadata_path, + auto_unbox = TRUE, + pretty = 4, + null = "null" + ) + invisible(TRUE) +} diff --git a/README.md b/README.md index 4ac6b05a..89f817af 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # `SpatialData` +[](https://github.com/HelenaLC/SpatialData/actions/workflows/check-bioc.yml) + > for a demo of the class, see the [vignette](https://htmlpreview.github.io/?https://github.com/HelenaLC/SpatialData/blob/main/vignettes/SpatialData.html) > for visualization capabilites, see [`SpatialData.plot`](https://github.com/HelenaLC/SpatialData.plot) diff --git a/inst/NEWS b/inst/NEWS index d61edd0f..18c4e5df 100644 --- a/inst/NEWS +++ b/inst/NEWS @@ -1,3 +1,44 @@ +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 +- fixed and reenable broken/skipped tests +- spatialdata_attrs utilities (instance_key(), region_key(), region()) + +changes in version 0.99.27 + +- spatial queries (aka subsetting) by bounding box + (all element types) or polygons (points and shapes), + including unit tests, documentation, visual examples +- masking draft to aggregate information across layers + (image by label, point by shape, shape by shape; + the latter aggregates values in an associated table) + +changes in version 0.99.26 + +- added unit tests for existing transformations +- implemented minimal layer-wise validity checks + (Image/LabelArray and Shape/PointFrame elements) + +changes in version 0.99.25 + +- improved Zattrs show method (cf., PR #117) +- replace jsonlite::fromJSON() with Rarr::read_zarr_attributes() + for reading .zattrs & rewrite code-/test-base accordingly + +changes in version 0.99.24 + +- ZarrArray imported by Bioconductor/ZarrArray +- Rarr replaces pizzarr for importing tables via anndataR +- anndataR replaces zellkonverter +- update basilisk env to spatialdata==0.7.0 +- replace spatialdata.read with anndata.read_zarr to read tables + changes in version 0.99.22 - split off 'SpatialData.data' diff --git a/inst/extdata/blobs_v3.zarr/images/blobs_image/0/c/0/0/0 b/inst/extdata/blobs_v3.zarr/images/blobs_image/0/c/0/0/0 new file mode 100644 index 00000000..30dae706 Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/images/blobs_image/0/c/0/0/0 differ diff --git a/inst/extdata/blobs_v3.zarr/images/blobs_image/0/zarr.json b/inst/extdata/blobs_v3.zarr/images/blobs_image/0/zarr.json new file mode 100644 index 00000000..ca44cb71 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/images/blobs_image/0/zarr.json @@ -0,0 +1,49 @@ +{ + "shape": [ + 3, + 64, + 64 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 3, + 64, + 64 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "dimension_names": [ + "c", + "y", + "x" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/images/blobs_image/zarr.json b/inst/extdata/blobs_v3.zarr/images/blobs_image/zarr.json new file mode 100644 index 00000000..daa726e2 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/images/blobs_image/zarr.json @@ -0,0 +1,102 @@ +{ + "attributes": { + "ome": { + "omero": { + "channels": [ + { + "label": 0 + }, + { + "label": 1 + }, + { + "label": 2 + } + ] + }, + "version": "0.5-dev-spatialdata", + "multiscales": [ + { + "datasets": [ + { + "path": "0", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 1.0, + 1.0 + ] + } + ] + } + ], + "name": "/images/blobs_image", + "axes": [ + { + "name": "c", + "type": "channel" + }, + { + "name": "y", + "type": "space" + }, + { + "name": "x", + "type": "space" + } + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "cyx", + "axes": [ + { + "name": "c", + "type": "channel" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "c", + "type": "channel" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + } + ] + } + ] + }, + "spatialdata_attrs": { + "version": "0.3" + } + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/0/c/0/0/0 b/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/0/c/0/0/0 new file mode 100644 index 00000000..30dae706 Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/0/c/0/0/0 differ diff --git a/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/0/zarr.json b/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/0/zarr.json new file mode 100644 index 00000000..724ab4cc --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/0/zarr.json @@ -0,0 +1,44 @@ +{ + "shape": [ + 3, + 64, + 64 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 3, + 64, + 64 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/1/c/0/0/0 b/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/1/c/0/0/0 new file mode 100644 index 00000000..b685afc6 Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/1/c/0/0/0 differ diff --git a/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/1/zarr.json b/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/1/zarr.json new file mode 100644 index 00000000..48c80911 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/1/zarr.json @@ -0,0 +1,44 @@ +{ + "shape": [ + 3, + 32, + 32 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 3, + 32, + 32 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/2/c/0/0/0 b/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/2/c/0/0/0 new file mode 100644 index 00000000..48cbafdc Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/2/c/0/0/0 differ diff --git a/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/2/zarr.json b/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/2/zarr.json new file mode 100644 index 00000000..c33e2c1d --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/2/zarr.json @@ -0,0 +1,44 @@ +{ + "shape": [ + 3, + 16, + 16 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 3, + 16, + 16 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/zarr.json b/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/zarr.json new file mode 100644 index 00000000..8080f7d9 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/images/blobs_multiscale_image/zarr.json @@ -0,0 +1,128 @@ +{ + "attributes": { + "ome": { + "omero": { + "channels": [ + { + "label": 0 + }, + { + "label": 1 + }, + { + "label": 2 + } + ] + }, + "version": "0.5-dev-spatialdata", + "multiscales": [ + { + "datasets": [ + { + "path": "0", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 1.0, + 1.0 + ] + } + ] + }, + { + "path": "1", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 2.0, + 2.0 + ] + } + ] + }, + { + "path": "2", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 4.0, + 4.0 + ] + } + ] + } + ], + "name": "/images/blobs_multiscale_image", + "axes": [ + { + "name": "c", + "type": "channel" + }, + { + "name": "y", + "type": "space" + }, + { + "name": "x", + "type": "space" + } + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "cyx", + "axes": [ + { + "name": "c", + "type": "channel" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "c", + "type": "channel" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + } + ] + } + ] + }, + "spatialdata_attrs": { + "version": "0.3" + } + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/images/zarr.json b/inst/extdata/blobs_v3.zarr/images/zarr.json new file mode 100644 index 00000000..d1f97dad --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/images/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/labels/blobs_labels/0/c/0/0 b/inst/extdata/blobs_v3.zarr/labels/blobs_labels/0/c/0/0 new file mode 100644 index 00000000..e6022913 Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/labels/blobs_labels/0/c/0/0 differ diff --git a/inst/extdata/blobs_v3.zarr/labels/blobs_labels/0/zarr.json b/inst/extdata/blobs_v3.zarr/labels/blobs_labels/0/zarr.json new file mode 100644 index 00000000..dac77af5 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/labels/blobs_labels/0/zarr.json @@ -0,0 +1,46 @@ +{ + "shape": [ + 64, + 64 + ], + "data_type": "int16", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 64, + 64 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "dimension_names": [ + "y", + "x" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/labels/blobs_labels/zarr.json b/inst/extdata/blobs_v3.zarr/labels/blobs_labels/zarr.json new file mode 100644 index 00000000..f68c3f89 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/labels/blobs_labels/zarr.json @@ -0,0 +1,307 @@ +{ + "attributes": { + "ome": { + "version": "0.5-dev-spatialdata", + "multiscales": [ + { + "datasets": [ + { + "path": "0", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 1.0 + ] + } + ] + } + ], + "name": "blobs_labels", + "axes": [ + { + "name": "y", + "type": "space" + }, + { + "name": "x", + "type": "space" + } + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + }, + { + "type": "scale", + "scale": [ + 3.0, + 2.0 + ], + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "scale", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + }, + { + "type": "translation", + "translation": [ + -50.0, + 10.0 + ], + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "translation", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + }, + { + "type": "affine", + "affine": [ + [ + 20.0, + 10.0, + 30.0 + ], + [ + 50.0, + 40.0, + 60.0 + ] + ], + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "affine", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + } + }, + { + "type": "sequence", + "transformations": [ + { + "type": "scale", + "scale": [ + 3.0, + 2.0 + ], + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + }, + { + "type": "translation", + "translation": [ + -50.0, + 10.0 + ], + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + } + ], + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "sequence", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + } + ] + } + ], + "image-label": { + "version": "0.5" + } + }, + "spatialdata_attrs": { + "version": "0.3" + } + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/0/c/0/0 b/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/0/c/0/0 new file mode 100644 index 00000000..e6022913 Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/0/c/0/0 differ diff --git a/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/0/zarr.json b/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/0/zarr.json new file mode 100644 index 00000000..a573f4af --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/0/zarr.json @@ -0,0 +1,42 @@ +{ + "shape": [ + 64, + 64 + ], + "data_type": "int16", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 64, + 64 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/1/c/0/0 b/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/1/c/0/0 new file mode 100644 index 00000000..826823f1 Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/1/c/0/0 differ diff --git a/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/1/zarr.json b/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/1/zarr.json new file mode 100644 index 00000000..c5a39ce9 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/1/zarr.json @@ -0,0 +1,42 @@ +{ + "shape": [ + 32, + 32 + ], + "data_type": "int16", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 32, + 32 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/2/c/0/0 b/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/2/c/0/0 new file mode 100644 index 00000000..3d9fa2dd Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/2/c/0/0 differ diff --git a/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/2/zarr.json b/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/2/zarr.json new file mode 100644 index 00000000..3dc5e399 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/2/zarr.json @@ -0,0 +1,42 @@ +{ + "shape": [ + 16, + 16 + ], + "data_type": "int16", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 16, + 16 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/zarr.json b/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/zarr.json new file mode 100644 index 00000000..4a69969c --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/labels/blobs_multiscale_labels/zarr.json @@ -0,0 +1,100 @@ +{ + "attributes": { + "ome": { + "version": "0.5-dev-spatialdata", + "multiscales": [ + { + "datasets": [ + { + "path": "0", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 1.0 + ] + } + ] + }, + { + "path": "1", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 2.0, + 2.0 + ] + } + ] + }, + { + "path": "2", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 4.0, + 4.0 + ] + } + ] + } + ], + "name": "blobs_multiscale_labels", + "axes": [ + { + "name": "y", + "type": "space" + }, + { + "name": "x", + "type": "space" + } + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + } + ] + } + ] + }, + "spatialdata_attrs": { + "version": "0.3" + } + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/labels/zarr.json b/inst/extdata/blobs_v3.zarr/labels/zarr.json new file mode 100644 index 00000000..51290556 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/labels/zarr.json @@ -0,0 +1,12 @@ +{ + "attributes": { + "ome": { + "labels": [ + "blobs_labels", + "blobs_multiscale_labels" + ] + } + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/points/blobs_points/points.parquet/part.0.parquet b/inst/extdata/blobs_v3.zarr/points/blobs_points/points.parquet/part.0.parquet new file mode 100644 index 00000000..5a95cc62 Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/points/blobs_points/points.parquet/part.0.parquet differ diff --git a/inst/extdata/blobs_v3.zarr/points/blobs_points/zarr.json b/inst/extdata/blobs_v3.zarr/points/blobs_points/zarr.json new file mode 100644 index 00000000..889834a0 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/points/blobs_points/zarr.json @@ -0,0 +1,51 @@ +{ + "attributes": { + "encoding-type": "ngff:points", + "axes": [ + "x", + "y" + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "xy", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + } + } + ], + "spatialdata_attrs": { + "instance_key": "instance_id", + "feature_key": "genes", + "version": "0.2" + } + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/points/zarr.json b/inst/extdata/blobs_v3.zarr/points/zarr.json new file mode 100644 index 00000000..d1f97dad --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/points/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/shapes/blobs_circles/shapes.parquet b/inst/extdata/blobs_v3.zarr/shapes/blobs_circles/shapes.parquet new file mode 100644 index 00000000..0bfe0ec6 Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/shapes/blobs_circles/shapes.parquet differ diff --git a/inst/extdata/blobs_v3.zarr/shapes/blobs_circles/zarr.json b/inst/extdata/blobs_v3.zarr/shapes/blobs_circles/zarr.json new file mode 100644 index 00000000..9a1f0a06 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/shapes/blobs_circles/zarr.json @@ -0,0 +1,49 @@ +{ + "attributes": { + "encoding-type": "ngff:shapes", + "axes": [ + "x", + "y" + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "xy", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + } + } + ], + "spatialdata_attrs": { + "version": "0.3" + } + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/shapes/blobs_multipolygons/shapes.parquet b/inst/extdata/blobs_v3.zarr/shapes/blobs_multipolygons/shapes.parquet new file mode 100644 index 00000000..86952e51 Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/shapes/blobs_multipolygons/shapes.parquet differ diff --git a/inst/extdata/blobs_v3.zarr/shapes/blobs_multipolygons/zarr.json b/inst/extdata/blobs_v3.zarr/shapes/blobs_multipolygons/zarr.json new file mode 100644 index 00000000..9a1f0a06 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/shapes/blobs_multipolygons/zarr.json @@ -0,0 +1,49 @@ +{ + "attributes": { + "encoding-type": "ngff:shapes", + "axes": [ + "x", + "y" + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "xy", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + } + } + ], + "spatialdata_attrs": { + "version": "0.3" + } + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/shapes/blobs_polygons/shapes.parquet b/inst/extdata/blobs_v3.zarr/shapes/blobs_polygons/shapes.parquet new file mode 100644 index 00000000..0a1f1673 Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/shapes/blobs_polygons/shapes.parquet differ diff --git a/inst/extdata/blobs_v3.zarr/shapes/blobs_polygons/zarr.json b/inst/extdata/blobs_v3.zarr/shapes/blobs_polygons/zarr.json new file mode 100644 index 00000000..9a1f0a06 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/shapes/blobs_polygons/zarr.json @@ -0,0 +1,49 @@ +{ + "attributes": { + "encoding-type": "ngff:shapes", + "axes": [ + "x", + "y" + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "xy", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + } + } + ], + "spatialdata_attrs": { + "version": "0.3" + } + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/shapes/zarr.json b/inst/extdata/blobs_v3.zarr/shapes/zarr.json new file mode 100644 index 00000000..d1f97dad --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/shapes/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/X/data/c/0 b/inst/extdata/blobs_v3.zarr/tables/table/X/data/c/0 new file mode 100644 index 00000000..1cafb41b Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/tables/table/X/data/c/0 differ diff --git a/inst/extdata/blobs_v3.zarr/tables/table/X/data/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/X/data/zarr.json new file mode 100644 index 00000000..c9cc2cfc --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/X/data/zarr.json @@ -0,0 +1,40 @@ +{ + "shape": [ + 30 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 30 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/X/indices/c/0 b/inst/extdata/blobs_v3.zarr/tables/table/X/indices/c/0 new file mode 100644 index 00000000..4ca94ff7 Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/tables/table/X/indices/c/0 differ diff --git a/inst/extdata/blobs_v3.zarr/tables/table/X/indices/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/X/indices/zarr.json new file mode 100644 index 00000000..18266fe6 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/X/indices/zarr.json @@ -0,0 +1,40 @@ +{ + "shape": [ + 30 + ], + "data_type": "int32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 30 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/X/indptr/c/0 b/inst/extdata/blobs_v3.zarr/tables/table/X/indptr/c/0 new file mode 100644 index 00000000..dce39b80 Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/tables/table/X/indptr/c/0 differ diff --git a/inst/extdata/blobs_v3.zarr/tables/table/X/indptr/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/X/indptr/zarr.json new file mode 100644 index 00000000..31decf2b --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/X/indptr/zarr.json @@ -0,0 +1,40 @@ +{ + "shape": [ + 11 + ], + "data_type": "int32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 11 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/X/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/X/zarr.json new file mode 100644 index 00000000..f37c375c --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/X/zarr.json @@ -0,0 +1,12 @@ +{ + "attributes": { + "shape": [ + 10, + 3 + ], + "encoding-type": "csr_matrix", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/layers/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/layers/zarr.json new file mode 100644 index 00000000..05cd3eb8 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/layers/zarr.json @@ -0,0 +1,8 @@ +{ + "attributes": { + "encoding-type": "dict", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/obs/_index/c/0 b/inst/extdata/blobs_v3.zarr/tables/table/obs/_index/c/0 new file mode 100644 index 00000000..d3f50a2f Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/tables/table/obs/_index/c/0 differ diff --git a/inst/extdata/blobs_v3.zarr/tables/table/obs/_index/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/obs/_index/zarr.json new file mode 100644 index 00000000..36548d79 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/obs/_index/zarr.json @@ -0,0 +1,41 @@ +{ + "shape": [ + 10 + ], + "data_type": "string", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 10 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "", + "codecs": [ + { + "name": "vlen-utf8", + "configuration": {} + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "string-array", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/obs/instance_id/c/0 b/inst/extdata/blobs_v3.zarr/tables/table/obs/instance_id/c/0 new file mode 100644 index 00000000..8dbfad1d Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/tables/table/obs/instance_id/c/0 differ diff --git a/inst/extdata/blobs_v3.zarr/tables/table/obs/instance_id/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/obs/instance_id/zarr.json new file mode 100644 index 00000000..1073667e --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/obs/instance_id/zarr.json @@ -0,0 +1,43 @@ +{ + "shape": [ + 10 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 10 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "array", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/obs/region/categories/c/0 b/inst/extdata/blobs_v3.zarr/tables/table/obs/region/categories/c/0 new file mode 100644 index 00000000..de92311e Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/tables/table/obs/region/categories/c/0 differ diff --git a/inst/extdata/blobs_v3.zarr/tables/table/obs/region/categories/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/obs/region/categories/zarr.json new file mode 100644 index 00000000..85fc95b1 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/obs/region/categories/zarr.json @@ -0,0 +1,41 @@ +{ + "shape": [ + 1 + ], + "data_type": "string", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "", + "codecs": [ + { + "name": "vlen-utf8", + "configuration": {} + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "string-array", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/obs/region/codes/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/obs/region/codes/zarr.json new file mode 100644 index 00000000..85a8ca7d --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/obs/region/codes/zarr.json @@ -0,0 +1,40 @@ +{ + "shape": [ + 10 + ], + "data_type": "int8", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 10 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "array", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/obs/region/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/obs/region/zarr.json new file mode 100644 index 00000000..0ac12c3a --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/obs/region/zarr.json @@ -0,0 +1,9 @@ +{ + "attributes": { + "ordered": false, + "encoding-type": "categorical", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/obs/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/obs/zarr.json new file mode 100644 index 00000000..b7c5909c --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/obs/zarr.json @@ -0,0 +1,13 @@ +{ + "attributes": { + "column-order": [ + "instance_id", + "region" + ], + "_index": "_index", + "encoding-type": "dataframe", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/obsm/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/obsm/zarr.json new file mode 100644 index 00000000..05cd3eb8 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/obsm/zarr.json @@ -0,0 +1,8 @@ +{ + "attributes": { + "encoding-type": "dict", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/obsp/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/obsp/zarr.json new file mode 100644 index 00000000..05cd3eb8 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/obsp/zarr.json @@ -0,0 +1,8 @@ +{ + "attributes": { + "encoding-type": "dict", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/raw/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/raw/zarr.json new file mode 100644 index 00000000..de573f69 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/raw/zarr.json @@ -0,0 +1,36 @@ +{ + "shape": [], + "data_type": "bool", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": false, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "null", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/instance_key/c b/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/instance_key/c new file mode 100644 index 00000000..6bd76d81 Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/instance_key/c differ diff --git a/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/instance_key/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/instance_key/zarr.json new file mode 100644 index 00000000..0a9eb931 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/instance_key/zarr.json @@ -0,0 +1,37 @@ +{ + "shape": [], + "data_type": "string", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "", + "codecs": [ + { + "name": "vlen-utf8", + "configuration": {} + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "string", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/region/c b/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/region/c new file mode 100644 index 00000000..de92311e Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/region/c differ diff --git a/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/region/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/region/zarr.json new file mode 100644 index 00000000..0a9eb931 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/region/zarr.json @@ -0,0 +1,37 @@ +{ + "shape": [], + "data_type": "string", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "", + "codecs": [ + { + "name": "vlen-utf8", + "configuration": {} + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "string", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/region_key/c b/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/region_key/c new file mode 100644 index 00000000..99c3ca14 Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/region_key/c differ diff --git a/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/region_key/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/region_key/zarr.json new file mode 100644 index 00000000..0a9eb931 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/region_key/zarr.json @@ -0,0 +1,37 @@ +{ + "shape": [], + "data_type": "string", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "", + "codecs": [ + { + "name": "vlen-utf8", + "configuration": {} + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "string", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/zarr.json new file mode 100644 index 00000000..05cd3eb8 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/uns/spatialdata_attrs/zarr.json @@ -0,0 +1,8 @@ +{ + "attributes": { + "encoding-type": "dict", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/uns/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/uns/zarr.json new file mode 100644 index 00000000..05cd3eb8 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/uns/zarr.json @@ -0,0 +1,8 @@ +{ + "attributes": { + "encoding-type": "dict", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/var/_index/c/0 b/inst/extdata/blobs_v3.zarr/tables/table/var/_index/c/0 new file mode 100644 index 00000000..0d0349cc Binary files /dev/null and b/inst/extdata/blobs_v3.zarr/tables/table/var/_index/c/0 differ diff --git a/inst/extdata/blobs_v3.zarr/tables/table/var/_index/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/var/_index/zarr.json new file mode 100644 index 00000000..21ca57b6 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/var/_index/zarr.json @@ -0,0 +1,41 @@ +{ + "shape": [ + 3 + ], + "data_type": "string", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 3 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "", + "codecs": [ + { + "name": "vlen-utf8", + "configuration": {} + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "string-array", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/var/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/var/zarr.json new file mode 100644 index 00000000..390d1856 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/var/zarr.json @@ -0,0 +1,10 @@ +{ + "attributes": { + "column-order": [], + "_index": "_index", + "encoding-type": "dataframe", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/varm/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/varm/zarr.json new file mode 100644 index 00000000..05cd3eb8 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/varm/zarr.json @@ -0,0 +1,8 @@ +{ + "attributes": { + "encoding-type": "dict", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/varp/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/varp/zarr.json new file mode 100644 index 00000000..05cd3eb8 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/varp/zarr.json @@ -0,0 +1,8 @@ +{ + "attributes": { + "encoding-type": "dict", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/table/zarr.json b/inst/extdata/blobs_v3.zarr/tables/table/zarr.json new file mode 100644 index 00000000..02404661 --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/table/zarr.json @@ -0,0 +1,13 @@ +{ + "attributes": { + "encoding-type": "anndata", + "encoding-version": "0.1.0", + "spatialdata-encoding-type": "ngff:regions_table", + "region": "blobs_labels", + "region_key": "region", + "instance_key": "instance_id", + "version": "0.2" + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/tables/zarr.json b/inst/extdata/blobs_v3.zarr/tables/zarr.json new file mode 100644 index 00000000..d1f97dad --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/tables/zarr.json @@ -0,0 +1,5 @@ +{ + "attributes": {}, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file diff --git a/inst/extdata/blobs_v3.zarr/zarr.json b/inst/extdata/blobs_v3.zarr/zarr.json new file mode 100644 index 00000000..ccb8916d --- /dev/null +++ b/inst/extdata/blobs_v3.zarr/zarr.json @@ -0,0 +1,1947 @@ +{ + "attributes": { + "spatialdata_attrs": { + "version": "0.2", + "spatialdata_software_version": "0.7.2" + } + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": { + "images": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "labels": { + "attributes": { + "ome": { + "labels": [ + "blobs_labels", + "blobs_multiscale_labels" + ] + } + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "points": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "shapes": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "tables": { + "attributes": {}, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "images/blobs_image": { + "attributes": { + "ome": { + "omero": { + "channels": [ + { + "label": 0 + }, + { + "label": 1 + }, + { + "label": 2 + } + ] + }, + "version": "0.5-dev-spatialdata", + "multiscales": [ + { + "datasets": [ + { + "path": "0", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 1.0, + 1.0 + ] + } + ] + } + ], + "name": "/images/blobs_image", + "axes": [ + { + "name": "c", + "type": "channel" + }, + { + "name": "y", + "type": "space" + }, + { + "name": "x", + "type": "space" + } + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "cyx", + "axes": [ + { + "name": "c", + "type": "channel" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "c", + "type": "channel" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + } + ] + } + ] + }, + "spatialdata_attrs": { + "version": "0.3" + } + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "images/blobs_multiscale_image": { + "attributes": { + "ome": { + "omero": { + "channels": [ + { + "label": 0 + }, + { + "label": 1 + }, + { + "label": 2 + } + ] + }, + "version": "0.5-dev-spatialdata", + "multiscales": [ + { + "datasets": [ + { + "path": "0", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 1.0, + 1.0 + ] + } + ] + }, + { + "path": "1", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 2.0, + 2.0 + ] + } + ] + }, + { + "path": "2", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 4.0, + 4.0 + ] + } + ] + } + ], + "name": "/images/blobs_multiscale_image", + "axes": [ + { + "name": "c", + "type": "channel" + }, + { + "name": "y", + "type": "space" + }, + { + "name": "x", + "type": "space" + } + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "cyx", + "axes": [ + { + "name": "c", + "type": "channel" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "c", + "type": "channel" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + } + ] + } + ] + }, + "spatialdata_attrs": { + "version": "0.3" + } + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "labels/blobs_labels": { + "attributes": { + "ome": { + "version": "0.5-dev-spatialdata", + "multiscales": [ + { + "datasets": [ + { + "path": "0", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 1.0 + ] + } + ] + } + ], + "name": "blobs_labels", + "axes": [ + { + "name": "y", + "type": "space" + }, + { + "name": "x", + "type": "space" + } + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + }, + { + "type": "scale", + "scale": [ + 3.0, + 2.0 + ], + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "scale", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + }, + { + "type": "translation", + "translation": [ + -50.0, + 10.0 + ], + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "translation", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + }, + { + "type": "affine", + "affine": [ + [ + 20.0, + 10.0, + 30.0 + ], + [ + 50.0, + 40.0, + 60.0 + ] + ], + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "affine", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + } + }, + { + "type": "sequence", + "transformations": [ + { + "type": "scale", + "scale": [ + 3.0, + 2.0 + ], + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + }, + { + "type": "translation", + "translation": [ + -50.0, + 10.0 + ], + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + } + ], + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "sequence", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + } + ] + } + ], + "image-label": { + "version": "0.5" + } + }, + "spatialdata_attrs": { + "version": "0.3" + } + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "labels/blobs_multiscale_labels": { + "attributes": { + "ome": { + "version": "0.5-dev-spatialdata", + "multiscales": [ + { + "datasets": [ + { + "path": "0", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 1.0 + ] + } + ] + }, + { + "path": "1", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 2.0, + 2.0 + ] + } + ] + }, + { + "path": "2", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 4.0, + 4.0 + ] + } + ] + } + ], + "name": "blobs_multiscale_labels", + "axes": [ + { + "name": "y", + "type": "space" + }, + { + "name": "x", + "type": "space" + } + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "yx", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "y", + "type": "space", + "unit": "unit" + }, + { + "name": "x", + "type": "space", + "unit": "unit" + } + ] + } + } + ] + } + ] + }, + "spatialdata_attrs": { + "version": "0.3" + } + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "points/blobs_points": { + "attributes": { + "encoding-type": "ngff:points", + "axes": [ + "x", + "y" + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "xy", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + } + } + ], + "spatialdata_attrs": { + "instance_key": "instance_id", + "feature_key": "genes", + "version": "0.2" + } + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "shapes/blobs_circles": { + "attributes": { + "encoding-type": "ngff:shapes", + "axes": [ + "x", + "y" + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "xy", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + } + } + ], + "spatialdata_attrs": { + "version": "0.3" + } + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "shapes/blobs_multipolygons": { + "attributes": { + "encoding-type": "ngff:shapes", + "axes": [ + "x", + "y" + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "xy", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + } + } + ], + "spatialdata_attrs": { + "version": "0.3" + } + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "shapes/blobs_polygons": { + "attributes": { + "encoding-type": "ngff:shapes", + "axes": [ + "x", + "y" + ], + "coordinateTransformations": [ + { + "type": "identity", + "input": { + "name": "xy", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + }, + "output": { + "name": "global", + "axes": [ + { + "name": "x", + "type": "space", + "unit": "unit" + }, + { + "name": "y", + "type": "space", + "unit": "unit" + } + ] + } + } + ], + "spatialdata_attrs": { + "version": "0.3" + } + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "tables/table": { + "attributes": { + "encoding-type": "anndata", + "encoding-version": "0.1.0", + "spatialdata-encoding-type": "ngff:regions_table", + "region": "blobs_labels", + "region_key": "region", + "instance_key": "instance_id", + "version": "0.2" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "images/blobs_image/0": { + "shape": [ + 3, + 64, + 64 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 3, + 64, + 64 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "dimension_names": [ + "c", + "y", + "x" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "images/blobs_multiscale_image/0": { + "shape": [ + 3, + 64, + 64 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 3, + 64, + 64 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "images/blobs_multiscale_image/1": { + "shape": [ + 3, + 32, + 32 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 3, + 32, + 32 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "images/blobs_multiscale_image/2": { + "shape": [ + 3, + 16, + 16 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 3, + 16, + 16 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "labels/blobs_labels/0": { + "shape": [ + 64, + 64 + ], + "data_type": "int16", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 64, + 64 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "dimension_names": [ + "y", + "x" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "labels/blobs_multiscale_labels/0": { + "shape": [ + 64, + 64 + ], + "data_type": "int16", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 64, + 64 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "labels/blobs_multiscale_labels/1": { + "shape": [ + 32, + 32 + ], + "data_type": "int16", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 32, + 32 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "labels/blobs_multiscale_labels/2": { + "shape": [ + 16, + 16 + ], + "data_type": "int16", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 16, + 16 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "tables/table/layers": { + "attributes": { + "encoding-type": "dict", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "tables/table/obs": { + "attributes": { + "column-order": [ + "instance_id", + "region" + ], + "_index": "_index", + "encoding-type": "dataframe", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "tables/table/obsm": { + "attributes": { + "encoding-type": "dict", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "tables/table/obsp": { + "attributes": { + "encoding-type": "dict", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "tables/table/raw": { + "shape": [], + "data_type": "bool", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": false, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "null", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "tables/table/uns": { + "attributes": { + "encoding-type": "dict", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "tables/table/var": { + "attributes": { + "column-order": [], + "_index": "_index", + "encoding-type": "dataframe", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "tables/table/varm": { + "attributes": { + "encoding-type": "dict", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "tables/table/varp": { + "attributes": { + "encoding-type": "dict", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "tables/table/X": { + "attributes": { + "shape": [ + 10, + 3 + ], + "encoding-type": "csr_matrix", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "tables/table/obs/_index": { + "shape": [ + 10 + ], + "data_type": "string", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 10 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "", + "codecs": [ + { + "name": "vlen-utf8", + "configuration": {} + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "string-array", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "tables/table/obs/instance_id": { + "shape": [ + 10 + ], + "data_type": "int64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 10 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "array", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "tables/table/obs/region": { + "attributes": { + "ordered": false, + "encoding-type": "categorical", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "tables/table/uns/spatialdata_attrs": { + "attributes": { + "encoding-type": "dict", + "encoding-version": "0.1.0" + }, + "zarr_format": 3, + "consolidated_metadata": { + "kind": "inline", + "must_understand": false, + "metadata": {} + }, + "node_type": "group" + }, + "tables/table/var/_index": { + "shape": [ + 3 + ], + "data_type": "string", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 3 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "", + "codecs": [ + { + "name": "vlen-utf8", + "configuration": {} + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "string-array", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "tables/table/X/data": { + "shape": [ + 30 + ], + "data_type": "float64", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 30 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0.0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "tables/table/X/indices": { + "shape": [ + 30 + ], + "data_type": "int32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 30 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "tables/table/X/indptr": { + "shape": [ + 11 + ], + "data_type": "int32", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 11 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes", + "configuration": { + "endian": "little" + } + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "tables/table/obs/region/categories": { + "shape": [ + 1 + ], + "data_type": "string", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "", + "codecs": [ + { + "name": "vlen-utf8", + "configuration": {} + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "string-array", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "tables/table/obs/region/codes": { + "shape": [ + 10 + ], + "data_type": "int8", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 10 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "array", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "tables/table/uns/spatialdata_attrs/instance_key": { + "shape": [], + "data_type": "string", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "", + "codecs": [ + { + "name": "vlen-utf8", + "configuration": {} + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "string", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "tables/table/uns/spatialdata_attrs/region": { + "shape": [], + "data_type": "string", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "", + "codecs": [ + { + "name": "vlen-utf8", + "configuration": {} + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "string", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + }, + "tables/table/uns/spatialdata_attrs/region_key": { + "shape": [], + "data_type": "string", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": "", + "codecs": [ + { + "name": "vlen-utf8", + "configuration": {} + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": { + "encoding-type": "string", + "encoding-version": "0.2.0" + }, + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] + } + } + }, + "node_type": "group" +} \ No newline at end of file diff --git a/man/CTgraph.Rd b/man/CTgraph.Rd new file mode 100644 index 00000000..5892ad88 --- /dev/null +++ b/man/CTgraph.Rd @@ -0,0 +1,76 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/CTgraph.R +\name{CTgraph} +\alias{CTgraph} +\alias{CTpath} +\alias{CTplot} +\alias{CTgraph,SpatialData-method} +\alias{CTgraph,SpatialDataElement-method} +\alias{CTgraph,ANY-method} +\alias{CTpath,SpatialData-method} +\alias{CTpath,SpatialDataElement-method} +\alias{CTpath,ANY-method} +\title{Coord. trans. graph} +\usage{ +\S4method{CTgraph}{SpatialData}(x) + +\S4method{CTgraph}{SpatialDataElement}(x) + +\S4method{CTgraph}{ANY}(x) + +\S4method{CTpath}{SpatialData}(x, i, j) + +\S4method{CTpath}{SpatialDataElement}(x, j) + +\S4method{CTpath}{ANY}(x) + +CTplot(g, cex = 0.5, fac = 2, max = 10) +} +\arguments{ +\item{x}{\code{SpatialData}, an element, or \code{Zattrs}.} + +\item{i}{character string; name of source node.} + +\item{j}{character string; name of target coordinate space.} + +\item{g}{base R graph; extracted with \code{CTgraph}.} + +\item{cex}{scalar numeric; controls fontsize of node labels.} + +\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 +} +\examples{ +x <- file.path("extdata", "blobs.zarr") +x <- system.file(x, package="SpatialData") +x <- readSpatialData(x, tables=FALSE) + +# object-wide +g <- CTgraph(x) +CTplot(g) + +# one element +y <- label(x) +g <- CTgraph(y) +CTplot(g) + +# retrieve transformation(s) +# from element to target space +CTpath(x, "blobs_labels", "sequence") +} diff --git a/man/coord-utils.Rd b/man/CTutils.Rd similarity index 57% rename from man/coord-utils.Rd rename to man/CTutils.Rd index a321588a..a04efc4b 100644 --- a/man/coord-utils.Rd +++ b/man/CTutils.Rd @@ -1,62 +1,52 @@ % 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} \alias{CTtype} \alias{CTdata} -\alias{CTpath} -\alias{CTgraph} \alias{addCT} \alias{rmvCT} \alias{axes,Zattrs-method} \alias{axes,SpatialDataElement-method} +\alias{CTlist,Zattrs-method} \alias{CTdata,Zattrs-method} -\alias{CTdata,SpatialDataElement-method} \alias{CTtype,Zattrs-method} -\alias{CTtype,SpatialDataElement-method} \alias{CTname,Zattrs-method} +\alias{CTlist,SpatialDataElement-method} +\alias{CTdata,SpatialDataElement-method} +\alias{CTtype,SpatialDataElement-method} \alias{CTname,SpatialDataElement-method} \alias{CTname,SpatialData-method} -\alias{CTgraph,SpatialData-method} -\alias{CTgraph,SpatialDataElement-method} -\alias{CTgraph,ANY-method} -\alias{CTpath,SpatialData-method} -\alias{CTpath,SpatialDataElement-method} \alias{rmvCT,SpatialDataElement-method} \alias{rmvCT,Zattrs-method} \alias{addCT,SpatialDataElement-method} \alias{addCT,Zattrs-method} -\title{Coordinate transformations} +\title{Coord. trans. utilities} \usage{ \S4method{axes}{Zattrs}(x, ...) \S4method{axes}{SpatialDataElement}(x, ...) -\S4method{CTdata}{Zattrs}(x, ...) +\S4method{CTlist}{Zattrs}(x, ...) -\S4method{CTdata}{SpatialDataElement}(x, ...) +\S4method{CTdata}{Zattrs}(x, i = 1, ...) \S4method{CTtype}{Zattrs}(x, ...) -\S4method{CTtype}{SpatialDataElement}(x, ...) - \S4method{CTname}{Zattrs}(x, ...) -\S4method{CTname}{SpatialDataElement}(x, ...) - -\S4method{CTname}{SpatialData}(x, ...) - -\S4method{CTgraph}{SpatialData}(x) +\S4method{CTlist}{SpatialDataElement}(x, ...) -\S4method{CTgraph}{SpatialDataElement}(x) +\S4method{CTdata}{SpatialDataElement}(x, i = 1, ...) -\S4method{CTgraph}{ANY}(x) +\S4method{CTtype}{SpatialDataElement}(x, ...) -\S4method{CTpath}{SpatialData}(x, i, j) +\S4method{CTname}{SpatialDataElement}(x, ...) -\S4method{CTpath}{SpatialDataElement}(x, j) +\S4method{CTname}{SpatialData}(x, ...) \S4method{rmvCT}{SpatialDataElement}(x, i) @@ -74,8 +64,6 @@ \item{i}{for \code{CTpath}, source node label; else, string or scalar integer giving the name or index of a coordinate space.} -\item{j}{character string; name of target coordinate space.} - \item{name}{character(1); name of coordinate space} \item{type}{character(1); type of transformation} @@ -83,35 +71,41 @@ 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)} } +\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") x <- system.file(x, package="SpatialData") x <- readSpatialData(x, tables=FALSE) -# element-wise -g <- CTgraph(y <- image(x)) -graph::nodes(g) -CTpath(y, "global") - -# object-wide -g <- CTgraph(x) -plotCoordGraph(g) - -# retrieve transformation from element to target space -CTpath(x, "blobs_labels", "sequence") - -# view available coordinate transformations -CTdata(z <- meta(label(x))) +# view available target coordinate systems +CTname(z <- meta(label(x))) # add -addCT(z, "scale", "scale", c(12, 34)) # can't overwrite -CTdata(addCT(z, "new", "translation", c(12, 34))) +addCT(z, "scale", "scale", c(12, 34)) # overwrite +CTname(addCT(z, "new", "translation", c(12, 34))) # rmv -CTdata(rmvCT(z, 2)) # by index -CTdata(rmvCT(z, "scale")) # by name -CTdata(rmvCT(z, 1)) # identity is protected +CTname(rmvCT(z, 2)) # by index +CTname(rmvCT(z, "scale")) # by name +CTname(rmvCT(z, "global")) # identity is protected } diff --git a/man/ImageArray.Rd b/man/ImageArray.Rd index cfdf1669..1ed003ed 100644 --- a/man/ImageArray.Rd +++ b/man/ImageArray.Rd @@ -2,8 +2,9 @@ % Please edit documentation in R/ImageArray.R \name{ImageArray} \alias{ImageArray} -\alias{channels,ImageArray-method} +\alias{channels,Zattrs-method} \alias{channels} +\alias{channels,ImageArray-method} \alias{channels,ANY-method} \alias{[,ImageArray,ANY,ANY,ANY-method} \title{The `ImageArray` class} @@ -17,6 +18,8 @@ ImageArray( ... ) +\S4method{channels}{Zattrs}(x, ...) + \S4method{channels}{ImageArray}(x, ...) \S4method{channels}{ANY}(x, ...) @@ -24,7 +27,7 @@ ImageArray( \S4method{[}{ImageArray,ANY,ANY,ANY}(x, i, j, k, ..., drop = FALSE) } \arguments{ -\item{data}{list of \code{\link[Rarr]{ZarrArray}}s} +\item{data}{list of \code{\link[ZarrArray]{ZarrArray}}s} \item{meta}{\code{\link{Zattrs}}} @@ -54,9 +57,8 @@ The `ImageArray` class } \examples{ library(SpatialData.data) -dir.create(td <- tempfile()) -pa <- SpatialData.data:::.unzip_merfish_demo(td) -pa <- file.path(pa, "images", "rasterized") +zs <- get_demo_SDdata("merfish") +pa <- file.path(zs, "images", "rasterized") (ia <- readImage(pa)) } diff --git a/man/LabelArray.Rd b/man/LabelArray.Rd index 1ff66b5e..6a480e2b 100644 --- a/man/LabelArray.Rd +++ b/man/LabelArray.Rd @@ -6,7 +6,7 @@ \title{The \code{LabelArray} class} \usage{ LabelArray( - data = array(), + data = list(), meta = Zattrs(), metadata = list(), multiscale = FALSE, @@ -17,7 +17,7 @@ LabelArray( \S4method{[}{LabelArray,ANY,ANY,ANY}(x, i, j, ..., drop = FALSE) } \arguments{ -\item{data}{list of \code{\link[Rarr]{ZarrArray}}s} +\item{data}{list of \code{\link[ZarrArray]{ZarrArray}}s} \item{meta}{\code{\link{Zattrs}}} @@ -53,6 +53,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/PointFrame.Rd b/man/PointFrame.Rd index b2a140b0..1830a9b2 100644 --- a/man/PointFrame.Rd +++ b/man/PointFrame.Rd @@ -79,9 +79,8 @@ Currently defined methods (here, \code{x} is a \code{PointFrame}): } \examples{ library(SpatialData.data) -dir.create(tf <- tempfile()) -base <- SpatialData.data:::.unzip_merfish_demo(tf) -x <- file.path(base, "points", "single_molecule") +zs <- get_demo_SDdata("merfish") +x <- file.path(zs, "points", "single_molecule") (p <- readPoint(x)) head(as.data.frame(data(p))) 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 bb940c2d..e69d3efc 100644 --- a/man/ShapeFrame.Rd +++ b/man/ShapeFrame.Rd @@ -2,10 +2,13 @@ % Please edit documentation in R/ShapeFrame.R \name{ShapeFrame} \alias{ShapeFrame} +\alias{geom_type} \alias{dim,ShapeFrame-method} \alias{length,ShapeFrame-method} \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} @@ -20,8 +23,12 @@ ShapeFrame(data = data.frame(), meta = Zattrs(), metadata = list(), ...) \S4method{names}{ShapeFrame}(x) +\method{.DollarNames}{ShapeFrame}(x, pattern = "") + \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) @@ -47,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} @@ -57,13 +64,13 @@ The `ShapeFrame` class } \examples{ library(SpatialData.data) -dir.create(tf <- tempfile()) -base <- SpatialData.data:::.unzip_merfish_demo(tf) -y <- file.path(base, "shapes", "cells") +zs <- get_demo_SDdata("merfish") + +y <- file.path(zs, "shapes", "cells") (s <- readShape(y)) plot(sf::st_as_sf(data(s)), cex=0.2) -y <- file.path(base, "shapes", "anatomical") +y <- file.path(zs, "shapes", "anatomical") (s <- readShape(y)) plot(sf::st_as_sf(data(s)), cex=0.2) diff --git a/man/SpatialData.Rd b/man/SpatialData.Rd index e760cfd6..671a894f 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} @@ -50,8 +52,6 @@ \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} \title{The `SpatialData` class} \usage{ SpatialData(images, labels, points, shapes, tables) @@ -126,7 +126,7 @@ or NULL/\code{list()} to remove an element/layer completely.} \examples{ x <- file.path("extdata", "blobs.zarr") x <- system.file(x, package="SpatialData") -(x <- readSpatialData(x, anndataR=FALSE)) +(x <- readSpatialData(x, anndataR=TRUE)) # subsetting # layers are taken in order of appearance diff --git a/man/Zattrs.Rd b/man/Zattrs.Rd index b736e2e7..49a06fae 100644 --- a/man/Zattrs.Rd +++ b/man/Zattrs.Rd @@ -25,10 +25,12 @@ x <- file.path("extdata", "blobs.zarr") x <- system.file(x, package="SpatialData") x <- readSpatialData(x, tables=FALSE) -z <- meta(label(x)) -axes(z) -CTdata(z) +(z <- meta(label(x))) + CTname(z) CTtype(z) +CTdata(z, "scale") + +feature_key(point(x)) } 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 198d6149..47c8656c 100644 --- a/man/do_tx_to_ext.Rd +++ b/man/do_tx_to_ext.Rd @@ -17,17 +17,23 @@ 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 } \examples{ src <- system.file("extdata", "blobs.zarr", package="SpatialData") td <- tempfile() -do_tx_to_ext( - srcdir=src, dest=td, - coordinate_system="global", - maintain_positioning=FALSE, - target_width=400.) -(sd <- readSpatialData(td)) +# TODO: for now this example converts to a zarr v3 so we comment out, +# check again later + +# do_tx_to_ext( +# srcdir=src, dest=td, +# coordinate_system="global", +# maintain_positioning=FALSE, +# target_width=400.) +# (sd <- readSpatialData(td)) } diff --git a/man/dot-normalize_array_path.Rd b/man/dot-normalize_array_path.Rd deleted file mode 100644 index 8a491cab..00000000 --- a/man/dot-normalize_array_path.Rd +++ /dev/null @@ -1,18 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/zarr_utils.R -\name{.normalize_array_path} -\alias{.normalize_array_path} -\title{Normalize a Zarr array path} -\usage{ -.normalize_array_path(path) -} -\arguments{ -\item{path}{Character vector of length 1 giving the path to be normalised.} -} -\value{ -A character vector of length 1 containing the normalised path. -} -\description{ -Taken from https://zarr.readthedocs.io/en/stable/spec/v2.html#logical-storage-paths -} -\keyword{Internal} diff --git a/man/mask.Rd b/man/mask.Rd index 2caa01cf..9c248c12 100644 --- a/man/mask.Rd +++ b/man/mask.Rd @@ -5,7 +5,14 @@ \alias{mask,SpatialData-method} \title{Masking} \usage{ -\S4method{mask}{SpatialData}(x, i, j, ...) +\S4method{mask}{SpatialData}( + x, + i, + j, + how = NULL, + name = function(i, j) sprintf("\%s_by_\%s", i, j), + ... +) } \arguments{ \item{x}{\code{\link{SpatialData}} object.} @@ -14,13 +21,20 @@ specifically, \code{i} will be masked by \code{j}, adding a \code{table} for \code{j} in \code{x}.} +\item{how}{character string; statistic to use for masking.} + +\item{name}{function use to generate the new \code{table}'s name.} + \item{...}{optional arguments passed to and from other methods.} } \value{ -\code{\link{SingleCellExperiment}} +Input \code{SpatialData} object \code{x} with an additional table. } \description{ -... +Masking operations serve to aggregate data across layers, e.g., +counting points in shapes, averaging image channels by labels, etc. +For added flexibility, these may be carried out directly between elements, +or using an input \code{SpatialData} object and specifying element names. } \examples{ library(SingleCellExperiment) @@ -28,9 +42,20 @@ x <- file.path("extdata", "blobs.zarr") x <- system.file(x, package="SpatialData") x <- readSpatialData(x, tables=FALSE) -# count points in circles -x <- mask(x, "blobs_points", "blobs_circles") -x <- mask(x, "blobs_image", "blobs_labels") -tables(x) +# count points in shapes +y <- mask(x, "blobs_points", "blobs_circles") +tail(tables(y), 1) + +# average image channels by labels +y <- mask(x, "blobs_image", "blobs_labels") +tail(tables(y), 1) + +library(SpatialData.data) +x <- get_demo_SDdata("merfish") +x <- readSpatialData(x) + +# sum table counts by shapes +y <- mask(x, "cells", "anatomical") +tail(tables(y), 1) } diff --git a/man/misc.Rd b/man/misc.Rd index 32e489b2..4c5cade5 100644 --- a/man/misc.Rd +++ b/man/misc.Rd @@ -6,7 +6,7 @@ \alias{show,sdArray-method} \alias{show,PointFrame-method} \alias{show,ShapeFrame-method} -\title{Miscellaneous `Miro` methods} +\title{Miscellaneous `SpatialData` methods} \usage{ \S4method{show}{SpatialData}(object) @@ -17,17 +17,30 @@ \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{ -# TODO +zs <- file.path("extdata", "blobs.zarr") +zs <- system.file(zs, package="SpatialData") +(sd <- readSpatialData(zs, anndataR=TRUE)) + +# show element +image(sd) +label(sd) +point(sd) +shape(sd) + +# show .zattrs +meta(label(sd)) +meta(image(sd, 2)) } \author{ Helena L. Crowell diff --git a/man/plotCoordGraph.Rd b/man/plotCoordGraph.Rd deleted file mode 100644 index a4acca58..00000000 --- a/man/plotCoordGraph.Rd +++ /dev/null @@ -1,26 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/coord_utils.R -\name{plotCoordGraph} -\alias{plotCoordGraph} -\title{CT graph viz.} -\usage{ -plotCoordGraph(g, cex = 0.6) -} -\arguments{ -\item{g}{base R graph; extracted with \code{\link{CTgraph}}.} - -\item{cex}{scalar numeric; controls fontsize of node labels.} -} -\description{ -given a \code{graphNEL} instance, nodes with \code{nchar>max} are -split and hyphenated at character position \code{floor(nchar/fac)}. -} -\examples{ -x <- file.path("extdata", "blobs.zarr") -x <- system.file(x, package="SpatialData") -x <- readSpatialData(x, tables=FALSE) - -g <- CTgraph(x) -plotCoordGraph(g, cex=0.6) - -} diff --git a/man/query.Rd b/man/query.Rd index 66b8b488..4b566bcc 100644 --- a/man/query.Rd +++ b/man/query.Rd @@ -9,36 +9,72 @@ \alias{query,PointFrame-method} \title{spatial queries} \usage{ -\S4method{query}{SpatialData}(x, j = NULL, ...) +\S4method{query}{SpatialData}(x, ..., i) -\S4method{query}{ImageArray}(x, j, ...) +\S4method{query}{ImageArray}(x, y) -\S4method{query}{LabelArray}(x, ...) +\S4method{query}{LabelArray}(x, y) -\S4method{query}{ShapeFrame}(x, ...) +\S4method{query}{ShapeFrame}(x, y) -\S4method{query}{PointFrame}(x, j, ...) +\S4method{query}{PointFrame}(x, y) } \arguments{ \item{x}{\code{SpatialData} element.} -\item{j}{scalar character or integer; index or name of coordinate space.} - \item{...}{optional arguments passed to and from other methods.} + +\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'; +polygon: numeric matrix with at least 3 rows and exactly 2 columns.} } \value{ same as input } \description{ -spatial queries +Spatial queries serve to subset \code{SpatialData} elements +according to a rectangular bounding box or arbitrary polygonal shapes. +Queries rely on lesser-/greater-equal and \code{sf::st_intersects} for +spatial operations (i.e., instances that intersect the query region +in any way are kept). For circle shapes, radii are currently ignored +(i.e., a circle is kept if its centroid intersects the query region). } \examples{ -x <- file.path("extdata", "blobs.zarr") -x <- system.file(x, package="SpatialData") -x <- readSpatialData(x, tables=FALSE) +zs <- file.path("extdata", "blobs.zarr") +zs <- system.file(zs, package="SpatialData") +sd <- readSpatialData(zs, tables=FALSE) + +# helper for visualizing point coordinates +.xy <- \(.) data.frame(data(.)[c("x", "y")]) + +# bounding box +y <- list(xmin=11, xmax=44, ymin=22, ymax=55) +q <- query(p <- point(sd), y) + +plot(.xy(p), asp=1) +points(.xy(q), col="red") +rect(y$xmin, y$ymin, y$xmax, y$ymax, border="blue") + +# polygon +y <- rbind(c(20,10), c(50,30), c(20,50), c(30,30)) +q <- query(p <- point(sd), y) + +plot(.xy(p), asp=1) +points(.xy(q), col="red") +lines(rbind(y, y[1, ]), col="blue") -image(x, "box") <- query(image(x), xmin=0, xmax=30, ymin=30, ymax=50) +# shapes that intersect the query region are kept +y <- rbind(c(30,45), c(40,45), c(35,50)) +t <- query(s <- shape(sd, 3), y) -image(x) -image(x, "box") +require(sf, quietly=TRUE) +df <- st_coordinates(st_as_sf(data(s))) +fd <- st_coordinates(st_as_sf(data(t))) +plot( + asp=1, xlim=c(15, 60), ylim=c(15, 60), + rbind(y, y[1, ]), type="l", col="blue") +foo <- by(df, df[, "L2"], \(x) points(x, type="b", col="black")) +foo <- by(fd, fd[, "L2"], \(x) points(x, type="b", col="red")) } diff --git a/man/readSpatialData.Rd b/man/readSpatialData.Rd index 000ed434..812a51dc 100644 --- a/man/readSpatialData.Rd +++ b/man/readSpatialData.Rd @@ -26,7 +26,7 @@ readSpatialData( points = TRUE, shapes = TRUE, tables = TRUE, - anndataR = FALSE + anndataR = TRUE ) } \arguments{ @@ -42,9 +42,9 @@ The default, NULL, reads all elements; alternatively, may be FALSE to skip a layer, or a integer vector specifying which elements to read.} \item{anndataR}{logical specifying whether -to use \code{anndataR} to read tables; defaults to FALSE in `readSpatialData`, -and `readTable`, -so that pythonic \code{spatialdata} and \code{zellkonverter} are used.} +to use \code{anndataR} to read tables; +defaults to FALSE in `readSpatialData`, and `readTable`, +so that pythonic \code{anndata} are used.} } \value{ \itemize{ @@ -57,7 +57,16 @@ Reading `SpatialData` } \examples{ library(SpatialData.data) -dir.create(tf <- tempfile()) -base <- SpatialData.data:::.unzip_merfish_demo(tf) -(x <- readSpatialData(base)) +zs <- get_demo_SDdata("merfish") + +# read complete Zarr store +(sd <- readSpatialData(zs, anndataR=TRUE)) + +# helper that gets path to first element in layer 'l' +fn <- \(l) list.files(file.path(zs, l), full.names=TRUE)[1] + +# read individual element +readImage(fn("images")) +readShape(fn("shapes")) +readPoint(fn("points")) } diff --git a/man/read_zattrs.Rd b/man/read_zattrs.Rd deleted file mode 100644 index ccd83f36..00000000 --- a/man/read_zattrs.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/zarr_utils.R -\name{read_zattrs} -\alias{read_zattrs} -\title{Read the .zattrs file associated with a Zarr array or group} -\usage{ -read_zattrs(path, s3_client = NULL) -} -\arguments{ -\item{path}{A character vector of length 1. This provides the -path to a Zarr array or group. This can either be on a local file -system or on S3 storage.} - -\item{s3_client}{A list representing an S3 client. This should be produced -by [paws.storage::s3()].} -} -\value{ -A list containing the .zattrs elements -} -\description{ -Read the .zattrs file associated with a Zarr array or group -} diff --git a/man/Array-methods.Rd b/man/sdArray.Rd similarity index 63% rename from man/Array-methods.Rd rename to man/sdArray.Rd index 083e08df..cb1ca718 100644 --- a/man/Array-methods.Rd +++ b/man/sdArray.Rd @@ -1,7 +1,8 @@ % 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} @@ -11,6 +12,8 @@ \alias{data,sdArray-method} \alias{dim,sdArray-method} \alias{length,sdArray-method} +\alias{data_type,sdArray-method} +\alias{data_type,DelayedArray-method} \title{Methods for `ImageArray` and `LabelArray` class} \usage{ \S4method{data}{sdArray}(x, k = 1) @@ -18,6 +21,10 @@ \S4method{dim}{sdArray}(x) \S4method{length}{sdArray}(x) + +\S4method{data_type}{sdArray}(x) + +\S4method{data_type}{DelayedArray}(x) } \arguments{ \item{x}{\code{ImageArray} or \code{LabelArray}} @@ -31,6 +38,13 @@ Methods for `ImageArray` and `LabelArray` class } \examples{ -# TODO +library(SpatialData.data) +zs <- get_demo_SDdata("merfish") + +# helper that gets path to first element in layer 'l' +fn <- \(l) list.files(file.path(zs, l), full.names=TRUE)[1] + +# read individual element +(ia <- readImage(fn("images"))) } diff --git a/man/table-utils.Rd b/man/table-utils.Rd index 709cd7c4..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 } @@ -63,7 +76,7 @@ specifies which \code{assay} to use when \code{j} is a row name.} library(SingleCellExperiment) x <- file.path("extdata", "blobs.zarr") x <- system.file(x, package="SpatialData") -x <- readSpatialData(x, anndataR=FALSE) +x <- readSpatialData(x, anndataR=TRUE) # check if element has a 'table' hasTable(x, "blobs_points") diff --git a/man/trans.Rd b/man/trans.Rd index 8602b2e5..b4f9c87d 100644 --- a/man/trans.Rd +++ b/man/trans.Rd @@ -5,7 +5,15 @@ \alias{scale} \alias{rotate} \alias{translation} -\alias{scale,ImageArray,numeric-method} +\alias{flip} +\alias{flop} +\alias{mirror} +\alias{mirror,sdArray-method} +\alias{flip,sdArray-method} +\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} @@ -14,7 +22,17 @@ \alias{translation,ShapeFrame,numeric-method} \title{Transformations} \usage{ -\S4method{scale}{ImageArray,numeric}(x, j, t, ...) +\S4method{mirror}{sdArray}(x, t = c("v", "h"), k = 1, ...) + +\S4method{flip}{sdArray}(x, k = 1, ...) + +\S4method{flop}{sdArray}(x, k = 1, ...) + +\S4method{scale}{sdArray,numeric}(x, t, k = 1, ...) + +\S4method{rotate}{sdArray,numeric}(x, t, k = 1, ...) + +\S4method{translation}{sdArray,numeric}(x, t, k = 1, ...) \S4method{scale}{PointFrame,numeric}(x, t, ...) @@ -29,15 +47,21 @@ \S4method{translation}{ShapeFrame,numeric}(x, t, ...) } \arguments{ -\item{x}{\code{SpatialData} element} +\item{x}{\code{SpatialData} element.} -\item{j}{scalar character or numeric; -name or index of coordinate space.} +\item{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}).} -\item{t}{transformation data.} +\item{k}{scalar index specifying which scale to use; +\code{Inf} to use lowest available resolution; +only applies to \code{sdArray}s (images, labels).} \item{...}{option arguments passed to and from other methods.} } +\value{ +\code{SpatialData} element with transformation(s) applied. +} \description{ Transformations } @@ -48,14 +72,14 @@ x <- readSpatialData(x, tables=FALSE) # image y <- x -image(y) <- scale(image(y), 1, c(1, 1, 1/3)) -CTpath(image(x), "global") -CTpath(image(y), "global") +image(y) <- scale(image(y), c(1, 1, 1/3)) +dim(image(x)) +dim(image(y)) # point y <- x point(y, "rot") <- rotate(point(y), 20) -point(y, "wide") <- scale(point(y), c(1, 1.2)) +point(y, "wide") <- scale(point(y), c(1.2, 1)) xy0 <- as.data.frame(point(y)) xy1 <- as.data.frame(point(y, "rot")) @@ -68,8 +92,7 @@ points(xy2[, c(1, 2)], col=4) # shape y <- x shape(y, "rot") <- rotate(shape(y), 5) -shape(y, "high") <- scale(shape(y), c(1.2, 1)) +shape(y, "wide") <- scale(shape(y), c(1.2, 1)) shape(y, "left") <- translation(shape(y), c(-5, 0)) - -graph::plot(CTgraph(y)) +y["shapes", c("rot", "wide", "left")] } diff --git a/man/utils.Rd b/man/utils.Rd new file mode 100644 index 00000000..245bc2c4 --- /dev/null +++ b/man/utils.Rd @@ -0,0 +1,64 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{utils} +\alias{utils} +\alias{centroids} +\alias{extent} +\alias{centroids,ANY-method} +\alias{centroids,LabelArray-method} +\alias{centroids,ShapeFrame-method} +\alias{centroids,PointFrame-method} +\alias{extent,SpatialData-method} +\alias{extent,SpatialDataElement-method} +\title{Utilities} +\usage{ +\S4method{centroids}{ANY}(x, ...) + +\S4method{centroids}{LabelArray}(x, as = c("data.frame", "matrix")) + +\S4method{centroids}{ShapeFrame}(x, as = c("data.frame", "matrix", "list")) + +\S4method{centroids}{PointFrame}(x, as = c("data.frame", "list")) + +\S4method{extent}{SpatialData}(x) + +\S4method{extent}{SpatialDataElement}(x) +} +\arguments{ +\item{x}{a \code{SpatialData} element (any but image).} + +\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 +} +\examples{ +x <- file.path("extdata", "blobs.zarr") +x <- system.file(x, package="SpatialData") +x <- readSpatialData(x, tables=FALSE) + +centroids(label(x)) +centroids(shape(x)) +centroids(shape(x, 3), "list") + +head(centroids(point(x))) +xy <- centroids(point(x), "list") +plot(xy$gene_a, col=a <- "red") +points(xy$gene_b, col=b <- "blue") +legend("topright", legend=names(xy), col=c(a, b), pch=21) + +# object-wide +extent(x) + +# element-wise +extent(label(x)) +extent(point(x)) +extent(shape(x)) +} diff --git a/man/write_zattrs.Rd b/man/write_zattrs.Rd deleted file mode 100644 index 944db4ea..00000000 --- a/man/write_zattrs.Rd +++ /dev/null @@ -1,19 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/zarr_utils.R -\name{write_zattrs} -\alias{write_zattrs} -\title{Read the .zattrs file associated with a Zarr array or group} -\usage{ -write_zattrs(path, new.zattrs = list(), overwrite = TRUE) -} -\arguments{ -\item{path}{A character vector of length 1. This provides the -path to a Zarr array or group.} - -\item{new.zattrs}{a list inserted to .zattrs at the \code{path}.} - -\item{overwrite}{if TRUE, existing .zattrs elements will be overwritten by \code{new.zattrs}.} -} -\description{ -Read the .zattrs file associated with a Zarr array or group -} diff --git a/tests/testthat/test-PointFrame.R b/tests/testthat/test-PointFrame.R index def83fc4..1fa4470d 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", { @@ -45,7 +46,8 @@ test_that("select", { replicate(3, { n <- sample(ncol(p), 1) i <- sample(names(p), n) - y <- select(p, i); z <- data(p)[, i] + y <- select(p, all_of(i)) + z <- data(p)[, i] expect_equal(collect(data(y)), collect(z)) }) }) 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-imagearray.R b/tests/testthat/test-imagearray.R index 182c94a5..3acb046f 100644 --- a/tests/testthat/test-imagearray.R +++ b/tests/testthat/test-imagearray.R @@ -37,16 +37,6 @@ 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 -}) - test_that("create", { # create image @@ -159,4 +149,29 @@ test_that("write multiscale", { expect_identical(realize(data(imgarray, 3)), realize(data(imgarray2, 3))) expect_identical(meta(imgarray),meta(imgarray2)) -}) \ No newline at end of file +}) + +test_that("write v3 uses Python-readable codec ordering", { + td <- tempdir() + zarr.path <- file.path(td, "test_v3.zarr") + unlink(zarr.path, recursive = TRUE) + + set.seed(1) + img <- array(sample(1:255, size = 20 * 20 * 3, replace = TRUE), + dim = c(3, 20, 20)) + imgarray <- ImageArray(img, axes = c("c", "y", "x")) + sd <- SpatialData(images = list(test_image = imgarray)) + + writeSpatialData(sd, "test_v3.zarr", path = td, version = "v3") + + metadata <- jsonlite::read_json( + file.path(zarr.path, "images", "test_image", "0", "zarr.json"), + simplifyVector = FALSE + ) + codec_names <- vapply(metadata$codecs, `[[`, character(1), "name") + + expect_identical(codec_names, c("transpose", "bytes", "zstd")) + expect_equal(unname(unlist(metadata$dimension_names)), c("c", "y", "x")) + expect_equal(metadata$attributes, list()) + expect_equal(metadata$storage_transformers, list()) +}) diff --git a/tests/testthat/test-labelarray.R b/tests/testthat/test-labelarray.R index 5a5b59d8..4e420e89 100644 --- a/tests/testthat/test-labelarray.R +++ b/tests/testthat/test-labelarray.R @@ -1,7 +1,5 @@ -arr <- seq_len(12) - test_that("LabelArray()", { - val <- sample(arr, 20*20, replace=TRUE) + val <- sample(seq_len(12), 20*20, replace=TRUE) mat <- array(val, dim=c(20, 20)) expect_silent(LabelArray(mat)) expect_silent(LabelArray(mat, list())) @@ -12,14 +10,15 @@ test_that("LabelArray()", { 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=.)) + dim <- lapply(c(20, 10, 5), \(.) rep(., 2)) + lys <- lapply(dim, \(.) array(sample(seq_len(12), prod(.), replace=TRUE), dim=.)) expect_silent(LabelArray(lys)) }) + test_that("data(),LabelArray", { - dim <- lapply(c(8, 4, 2), \(.) c(3, rep(., 2))) - lys <- lapply(dim, \(.) array(0, dim=.)) + dim <- lapply(c(8, 4, 2), \(.) rep(., 2)) + lys <- lapply(dim, \(.) array(0L, dim=.)) lab <- LabelArray(lys) for (. in seq_along(lys)) expect_identical(data(lab, .), lys[[.]]) @@ -31,16 +30,6 @@ test_that("data(),LabelArray", { 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 -}) test_that("create", { diff --git a/tests/testthat/test-mask.R b/tests/testthat/test-mask.R index ad3545b9..1eebe0d0 100644 --- a/tests/testthat/test-mask.R +++ b/tests/testthat/test-mask.R @@ -1,26 +1,68 @@ library(SingleCellExperiment) x <- file.path("extdata", "blobs.zarr") x <- system.file(x, package="SpatialData") -x <- readSpatialData(x, anndataR=FALSE) +x <- readSpatialData(x, anndataR=TRUE) -test_that("mask(),ImageArray,LabelArray", { +test_that("mask,unsupported", { + nm <- list( + c(imageNames(x)[1], imageNames(x)[2]), # image,image + c(labelNames(x)[1], labelNames(x)[2]), # label,label + c(labelNames(x)[1], imageNames(x)[1]), # label,image + c(shapeNames(x)[1], pointNames(x)[1])) # shape,point + for (ij in nm) expect_error(mask(x, ij[1], ij[2])) +}) + +test_that("mask,ImageArray,LabelArray", { i <- "blobs_image" j <- "blobs_labels" - x <- SpatialData::mask(x, i, j, fun=sum) + # reproduce example data + y <- mask(x, i, j, how="sum") expect_equivalent( - assay(SpatialData::table(x, 1)), - assay(SpatialData::table(x, 2))) + assay(tables(y)[[2]]), + assay(tables(x)[[1]])) + # default to 'mean' with a message + expect_message(y <- mask(x, i, j)) + expect_silent(z <- mask(x, i, j, how="mean")) + expect_identical(y, z) }) -test_that("mask(),PointFrame,ShapeFrame", { +test_that("mask,PointFrame,ShapeFrame", { i <- "blobs_points" j <- "blobs_circles" - x <- SpatialData::mask(x, i, j) - t <- getTable(x, j) - md <- meta(point(x, i)) - md <- md$spatialdata_attrs - fk <- md$feature_key - nr <- length(unique(point(x, i)[[fk]])) + y <- mask(x, i, j) + t <- getTable(y, j) + fk <- feature_key(p <- point(x, i)) + np <- length(unique(p[[fk]])) nc <- nrow(shape(x, j)) - expect_equal(dim(t), c(nr, nc)) + expect_equal(dim(t), c(np, nc)) + # ignore 'how' with a warning + expect_warning(mask(x, i, j, how="sum")) +}) + +require(SpatialData.data, quietly=TRUE) +x <- get_demo_SDdata("merfish") +x <- readSpatialData(x) + +test_that("mask,ShapeFrame,ShapeFrame", { + i <- "cells" + j <- "anatomical" + # error without 'table' + y <- x; tables(y) <- list() + expect_error(mask(y, i, j)) + # default to 'sum' with a message + expect_message(y <- mask(x, i, j)) + expect_silent(z <- mask(x, i, j, how="sum")) + expect_identical(y, z) + old <- getTable(y, i) + new <- getTable(y, j) + expect_equal(dim(new), c(nrow(old), nrow(shape(x, j))+1)) + expect_equal(sum(assay(new)), sum(assay(old))) + expect_identical(rownames(new), rownames(old)) + expect_identical(meta(new)$region, j) + # 'value' should be a character + # vector of rownames in 'table(x, i)' + v <- sample(rownames(old), 5) + new <- getTable(mask(x, i, j, value=v), j) + expect_equal(sum(assay(new[v, ])), sum(assay(old[v, ]))) + expect_error(mask(x, i, j, value=`[<-`(v, i=1, "x"))) }) diff --git a/tests/testthat/test-methods.R b/tests/testthat/test-methods.R index 7ee02f2c..66e5c677 100644 --- a/tests/testthat/test-methods.R +++ b/tests/testthat/test-methods.R @@ -1,14 +1,14 @@ -if (FALSE) { library(SingleCellExperiment) x <- file.path("extdata", "blobs.zarr") x <- system.file(x, package="SpatialData") x <- readSpatialData(x) -sdtable = SpatialData::table # skirt ambiguity and limitations of get() -"sdtable<-" = "SpatialData::table<-" # skirt ambiguity and limitations of get() -sdtables = SpatialData::tables +# # skirt base::table ambiguity +# sdtable <- SpatialData::table +# `sdtable<-` <- `SpatialData::table<-` +# sdtables <- SpatialData::tables -fun <- c("image", "label", "shape", "point", "sdtable") +fun <- c("image", "label", "shape", "point", "table") nms <- c("blobs_image", "blobs_labels", "blobs_circles", "blobs_points", "table") typ <- c("ImageArray", "LabelArray", "ShapeFrame", "PointFrame", "SingleCellExperiment") @@ -53,19 +53,19 @@ test_that("get one", { # i=numeric mapply(f=fun, t=typ, \(f, t) expect_is(get(f)(x, i=1), t)) - # i=character -- VJC Dec 8 2024 -- ambiguity of table()? -# mapply(f=fun, t=typ, n=nms, \(f, t, n) -# expect_is(get(f)(x, i=n), t)) + # i=character + mapply(f=fun, t=typ, n=nms, \(f, t, n) + expect_is(get(f)(x, i=n), t)) # i=invalid -# for (f in fun) { -# expect_error(get(f)(x, 0)) -# expect_error(get(f)(x, ".")) -# expect_error(get(f)(x, c(1,1))) -# expect_silent(y <- get(f)(x, Inf)) -# set <- get(paste0(f, "s<-")) -# y <- set(x, list()) -# expect_error(get(f)(y, 1)) -# } + for (f in fun) { + expect_error(get(f)(x, 0)) + expect_error(get(f)(x, ".")) + expect_error(get(f)(x, c(1,1))) + expect_silent(y <- get(f)(x, Inf)) + set <- get(paste0(f, "s<-")) + y <- set(x, list()) + expect_error(get(f)(y, 1)) + } }) # set ---- @@ -169,12 +169,12 @@ test_that("[,LabelArray", { expect_equal(dim(y[FALSE,FALSE]), c(0,0)) expect_equal(dim(y[FALSE,TRUE]), c(0,ncol(y))) expect_equal(dim(y[TRUE,FALSE]), c(nrow(y),0)) - i <- logical(nrow(y)); j <- logical(ncol(y)) - n <- replicate(2, sample(seq(2, 10), 1)) - i[sample(nrow(y), n[1])] <- TRUE - j[sample(ncol(y), n[2])] <- TRUE - expect_equal(nrow(y[i,]), n[1]) - expect_equal(ncol(y[,j]), n[2]) + # i <- logical(nrow(y)); j <- logical(ncol(y)) + # n <- replicate(2, sample(seq(2, 10), 1)) + # i[sample(nrow(y), n[1])] <- TRUE + # j[sample(ncol(y), n[2])] <- TRUE + # expect_equal(nrow(y[i,]), n[1]) + # expect_equal(ncol(y[,j]), n[2]) # numeric expect_identical(y[,], y) # none expect_equal(nrow(y[1,]), 1) # no j @@ -257,4 +257,3 @@ test_that("[,SpatialData", { element(y, 1, 1), element(x, 1, .n(x)[1])) }) -} diff --git a/tests/testthat/test-query.R b/tests/testthat/test-query.R index 1c742f6c..0490f584 100644 --- a/tests/testthat/test-query.R +++ b/tests/testthat/test-query.R @@ -1,45 +1,96 @@ -library(sf) +require(sf, quietly=TRUE) x <- file.path("extdata", "blobs.zarr") x <- system.file(x, package="SpatialData") -x <- readSpatialData(x, tables=FALSE) +x <- readSpatialData(x, anndataR=TRUE) -test_that("query,...", { - # extract 1st element from every layer - lys <- lapply(seq_len(4), \(.) x[.,1][[.]][[1]]) - for (y in lys) { - # missing bounding box coordinates - expect_error(query(y, xmin=0, xmax=1, ymin=0)) - # # invalid coordinate space - # expect_error(query(y, ".", xmin=0, xmax=1, ymin=0, ymax=1)) - # expect_error(query(y, 100, xmin=0, xmax=1, ymin=0, ymax=1)) - } +test_that("query,.check_box", { + # valid + q <- list( + list(xmin=0, xmax=1, ymin=0, ymax=1), + list(xmin=-1, xmax=0, ymin=-1, ymax=0), + list(xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf)) + for (. in q) expect_silent(.check_box(.)) + # invalid + q <- list( + list(xmin=0, xmax=1, ymin=0), + list(xmin=1, xmax=0, ymin=1, ymax=0), + list(xmin=0, xmax=-1, ymin=0, ymax=-1), + list(xmin=0, xmax=1, ymin=10, ymax=NA), + list(xmin=Inf, xmax=-Inf, ymin=Inf, ymax=-Inf)) + for (. in q) expect_error(.check_box(.)) +}) + +test_that("query,.check_pol", { + # valid + q <- list( + m <- matrix(seq_len(8), 4, 2), + rbind(c(1,1), c(2,2), c(3,3)), # open + rbind(c(1,1), c(2,2), c(3,3), c(1,1))) + for (. in q) expect_silent(.check_pol(.)) + # invalid + q <- list( + matrix(seq_len(6), 2, 3), # wrong dim. + matrix(numeric(6), 3, 2), # duplicates + `[<-`(m, i=1, j=1, value=Inf), # not finite + `[<-`(m, i=1, j=1, value=NA)) # missing value + for (. in q) expect_error(.check_pol(.)) }) test_that("query,ImageArray", { d <- dim(i <- image(x)) - # neither crop nor shift - expect_identical(query(i, xmin=0, xmax=d[3], ymin=0, ymax=d[2]), i) + # unsupported query + y <- matrix(seq_len(8), 4, 2) + expect_error(query(i, y)) + # query equals dimensions + y <- list(xmin=0, xmax=d[3], ymin=0, ymax=d[2]) + expect_identical(query(i, y), i) + # order is irrelevant + y <- list(ymax=d[2], xmax=d[3], xmin=0, ymin=0) + expect_identical(query(i, y), i) + # crop but don't shift + y <- list(xmin=0, xmax=w <- d[3]/2, ymin=0, ymax=h <- d[2]/4) + expect_equal(dim(j <- query(i, y)), c(3, h, w)) + expect_identical(CTlist(i), CTlist(j)) + # crop and shift + y <- list( + xmin=dx <- 3, xmax=w <- d[3]/2, + ymin=dy <- 5, ymax=h <- d[2]/4) + expect_equal(dim(query(i, y)), c(3, 1+h-dy, 1+w-dx)) + # non-finite boundaries + y <- list(xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) + expect_silent(query(i, y)) +}) + +test_that("query,LabelArray", { + d <- dim(l <- label(x)) + # query equals dimensions + y <- list(xmin=0, xmax=d[2], ymin=0, ymax=d[1]) + expect_identical(query(l, y), l) # order is irrelevant - expect_identical(query(i, ymax=d[2], xmax=d[3], xmin=0, ymin=0), i) + y <- list(ymax=d[1], xmax=d[2], xmin=0, ymin=0) + expect_identical(query(l, y), l) # crop but don't shift - j <- query(i, xmin=0, xmax=w <- d[3]/2, ymin=0, ymax=h <- d[2]/4) - expect_equal(dim(j), c(3, h, w)) - expect_identical(CTdata(i), CTdata(j)) + y <- list(xmin=0, xmax=w <- d[2]/2, ymin=0, ymax=h <- d[1]/4) + expect_equal(dim(m <- query(l, y)), c(h, w)) + expect_identical(CTlist(l), CTlist(m)) # crop and shift - j <- query(i, xmin=1, xmax=w <- d[3]/2, ymin=2, ymax=h <- d[2]/4) - expect_equal(dim(j), c(3, 1+h-2, 1+w-1)) - expect_equal(CTtype(j), t <- "translation") - expect_equivalent(CTdata(j)[[t]][[1]], c(0, 2, 1)) + y <- list( + xmin=dx <- 3, xmax=w <- d[2]/2, + ymin=dy <- 5, ymax=h <- d[1]/4) + expect_equal(dim(query(l, y)), c(1+h-dy, 1+w-dx)) + # non-finite boundaries + y <- list(xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) + expect_silent(query(l, y)) }) -test_that("query,PointFrame", { +test_that("query-box,PointFrame", { n <- length(p <- point(x)) # this shouldn't do anything - q <- query(p, xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) + q <- query(p, list(xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf)) expect_is(data(q), "arrow_dplyr_query") expect_identical(collect(data(p)), collect(data(q))) # this should drop everything - q <- query(p, xmin=Inf, xmax=-Inf, ymin=Inf, ymax=-Inf) + q <- query(p, list(xmin=0, xmax=1e-3, ymin=0, ymax=1e-3)) expect_equal(nrow(collect(data(q))), 0) # proper query bb <- lapply(c("x", "y"), \(.) { @@ -47,22 +98,44 @@ test_that("query,PointFrame", { d <- c((d <- diff(range(v)))/4, d/2) names(d) <- paste0(., c("min", "max")) as.list(d) }) |> Reduce(f=c) - q <- do.call(query, c(list(x=p), bb)) + q <- do.call(query, c(list(x=p), list(bb))) df <- collect(data(p)) fd <- collect(data(q)) - i <- df$x >= bb$xmin & df$x <= bb$xmax & + i <- + df$x >= bb$xmin & df$x <= bb$xmax & df$y >= bb$ymin & df$y <= bb$ymax expect_identical(df[i, ], fd) }) -test_that("query,ShapeFrame", { +test_that("query-pol,PointFrame", { + n <- length(p <- point(x)) + f <- \(.) collect(data(.)) + # mock all-inclusive query + xy <- rbind(c(0,0), c(0,1e6), c(1e6,0)) + expect_identical(f(query(p, xy)), f(p)) + # sample random points & + # query tiny polygon around them + replicate(5, { + i <- sample(n, 1) + xy <- c(p[i]$x, p[i]$y) + i <- p$x == xy[1] & p$y == xy[2] + xy <- rbind( + xy+c(0, d <- 1e-6), + xy+c(-d,-d), xy+c(+d,-d)) + q <- query(p, xy) + expect_length(q, sum(i)) + expect_identical(f(q), f(p[which(i)])) + }) +}) + +test_that("query-box,ShapeFrame", { n <- length(s <- shape(x)) # mock query without any effect - t <- query(s, xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) - expect_equal(collect(data(s)), collect(data(t))) + t <- query(s, list(xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf)) + expect_equal(nrow(data(t)), nrow(data(s))) # this should drop everything - t <- query(s, xmin=Inf, xmax=-Inf, ymin=Inf, ymax=-Inf) - expect_equal(nrow(collect(data(t))), 0) + t <- query(s, list(xmin=0, xmax=1e-3, ymin=0, ymax=1e-3)) + expect_equal(nrow(t), 0) # proper query xy <- st_coordinates(st_as_sf(data(s))) xy <- data.frame(xy); names(xy) <- c("x", "y") @@ -70,6 +143,26 @@ test_that("query,ShapeFrame", { bb <- lapply(xy, \(.) c(.-1e-9, .+1e-9)) bb <- data.frame(t(unlist(bb))) names(bb) <- c("xmin", "xmax", "ymin", "ymax") - t <- do.call(query, c(list(x=s), bb)) + t <- do.call(query, c(list(x=s), list(bb))) expect_equal(s[i], t) }) + +test_that("query-pol,ShapeFrame", { + n <- length(s <- shape(x)) + # mock all-inclusive query + xy <- rbind(c(0,0), c(0,1e6), c(1e6,0)) + expect_equal(query(s, xy), s) + # sample random shapes & + # query tiny polygon around them + xy <- st_coordinates(st_as_sf(data(s))) + replicate(5, { + i <- sample(n, 1) + xy <- xy[i, ] + xy <- rbind( + xy+c(0, d <- 1e-6), + xy+c(-d,-d), xy+c(+d,-d)) + t <- query(s, xy) + expect_length(t, 1) + expect_equal(t, s[i]) + }) +}) diff --git a/tests/testthat/test-reading.R b/tests/testthat/test-read.R similarity index 86% rename from tests/testthat/test-reading.R rename to tests/testthat/test-read.R index 807d261e..57b7a4fe 100644 --- a/tests/testthat/test-reading.R +++ b/tests/testthat/test-read.R @@ -11,17 +11,13 @@ test_that("readElement()", { for (l in names(typ)) { f <- paste0(toupper(substr(l, 1, 1)), substr(l, 2, nchar(l)-1)) y <- list.files(file.path(x, l), full.names=TRUE)[1] - if (l != "tables") { - expect_is(get(paste0("read", f))(y), typ[l]) - } else { - expect_is(.readTables_basilisk(x)[[1]], typ[l]) - } + expect_is(get(paste0("read", f))(y), typ[l]) } }) test_that("readSpatialData()", { expect_is(y <- readSpatialData(x), "SpatialData") - a <- list(images=TRUE, labels=TRUE, shapes=TRUE, points=TRUE)#, tables=TRUE) + a <- list(images=TRUE, labels=TRUE, shapes=TRUE, points=TRUE, tables=FALSE) for (. in names(a)) { # setting any layer to FALSE skips it b <- c(list(x=x), a); b[[.]] <- FALSE diff --git a/tests/testthat/test-sdarray.R b/tests/testthat/test-sdarray.R new file mode 100644 index 00000000..de123f94 --- /dev/null +++ b/tests/testthat/test-sdarray.R @@ -0,0 +1,22 @@ +x <- file.path("extdata", "blobs.zarr") +x <- system.file(x, package="SpatialData") +x <- readSpatialData(x, anndataR=TRUE) + +test_that("data_type()", { + # image + za <- data(image(x)) + dt <- data_type(za) + expect_length(dt, 1) + expect_is(dt, "character") + expect_identical(dt, "float64") + expect_identical(dt, data_type(za[1,,])) + expect_identical(dt, data_type(image(x))) + # label + za <- data(label(x)) + dt <- data_type(za) + expect_length(dt, 1) + expect_is(dt, "character") + expect_identical(dt, "int16") + expect_identical(dt, data_type(head(za))) + expect_identical(dt, data_type(label(x))) +}) diff --git a/tests/testthat/test-tables.R b/tests/testthat/test-tables.R index 74e5f6b6..57147ca2 100644 --- a/tests/testthat/test-tables.R +++ b/tests/testthat/test-tables.R @@ -1,20 +1,19 @@ -oo = options()$arrow.pull_as_vector - -options(arrow.pull_as_vector=TRUE) require(SingleCellExperiment, quietly=TRUE) +oo <- options()$arrow.pull_as_vector +options(arrow.pull_as_vector=TRUE) + x <- file.path("extdata", "blobs.zarr") x <- system.file(x, package="SpatialData") -x <- readSpatialData(x, table=1, anndataR=FALSE) +x <- readSpatialData(x, anndataR=TRUE) -md <- int_metadata(SpatialData::table(x)) +se <- SpatialData::table(x) +md <- int_metadata(se) md <- md$spatialdata_attrs i <- md[[rk <- md$region_key]] -#int_colData(SpatialData::table(x))[[rk]] <- -# paste(int_colData(SpatialData::table(x))[[rk]]) test_that("hasTable()", { # TRUE - i <- md$region + i <- region(table(x)) expect_true(hasTable(x, i)) # FALSE j <- setdiff(unlist(colnames(x)), c(i, tableNames(x))) @@ -32,7 +31,6 @@ test_that("hasTable()", { expect_error(hasTable(setTable(x, i), i, name=TRUE)) # many }) -if (FALSE) { test_that("getTable()", { # invalid expect_error(getTable(x, 123)) @@ -46,21 +44,16 @@ test_that("getTable()", { expect_error(getTable(x, i, ".")) expect_error(getTable(x, i, c(TRUE, FALSE))) # alter 'region' of a couple random observations - s <- t - . <- sample(ncol(s), 2) - # TODO: check the replacement of this test below - # int_colData(s)[[rk]][.] <- "." - tmp <- as.character(colData(s)[[rk]]) - tmp[.] <- "." - colData(s)[[rk]] <- tmp - SpatialData::table(x) <- s + s <- t; y <- x + int_colData(s)[[rk]] <- paste(int_colData(s)[[rk]]) + int_colData(s)[[rk]][. <- sample(ncol(s), 2)] <- "." + SpatialData::table(y) <- s # these should be gone when 'drop=TRUE' - t1 <- getTable(x, i, drop=FALSE) - t2 <- getTable(x, i, drop=TRUE) + t1 <- getTable(y, i, drop=FALSE) + t2 <- getTable(y, i, drop=TRUE) expect_identical(t1, s) expect_identical(t2, s[, -.]) }) -} test_that("setTable(),labels", { # invalid 'i' @@ -91,8 +84,7 @@ test_that("setTable(),points/shapes", { n <- switch(., shape=length(y), point={ - md <- meta(y)$spatialdata_attrs - ik <- md$instance_key + ik <- instance_key(y) n <- length(unique(pull(data(y), ik))) }) df <- data.frame(foo=runif(n)) @@ -101,8 +93,7 @@ test_that("setTable(),points/shapes", { expect_true(hasTable(y, i)) t <- getTable(y, i) expect_identical(t$foo, df$foo) - md <- int_metadata(t)$spatialdata_attrs - expect_identical(md$region, i) + #expect_identical(region(t), i) # dots = list of functions f <- list( numbers=\(n) runif(n), @@ -112,8 +103,7 @@ test_that("setTable(),points/shapes", { expect_true(hasTable(y, i)) t <- getTable(y, i) expect_true(all(names(f) %in% names(colData(t)))) - md <- int_metadata(t)$spatialdata_attrs - expect_identical(md$region, i) + #expect_identical(region(t), i) } }) @@ -125,9 +115,9 @@ test_that("valTable()", { expect_error(valTable(x, i, sample(rownames(t), 2))) expect_error(valTable(x, i, sample(names(colData(t)), 2))) # 'colData' - df <- DataFrame(a=sample(letters, n), b=runif(n), - region = valTable(x, i, j <- "region")) - s <- t; colData(s) <- df; y <- x; SpatialData::table(y) <- s + cd <- DataFrame(a=sample(letters, n), b=runif(n)) + s <- t; colData(s) <- cd + y <- x; SpatialData::table(y) <- s expect_identical(valTable(y, i, j <- "a"), s[[j]]) expect_identical(valTable(y, i, j <- "b"), s[[j]]) expect_error(valTable(y, i, "c")) diff --git a/tests/testthat/test-trans.R b/tests/testthat/test-trans.R new file mode 100644 index 00000000..71bb6579 --- /dev/null +++ b/tests/testthat/test-trans.R @@ -0,0 +1,160 @@ +zs <- file.path("extdata", "blobs.zarr") +zs <- system.file(zs, package="SpatialData") +sd <- readSpatialData(zs, tables=FALSE) + +test_that("mirror,sdArray", { + x <- label(sd, 1)[-1,-c(1,2)] + expect_error(mirror(x, "x")) + expect_identical(mirror(x, "v"), flip(x)) + expect_identical(mirror(x, "h"), flop(x)) + # vertical reflection + y <- flip(x) + expect_identical(dim(y), dim(x)) + expect_equal(data(y)[1, ], rev(data(x)[1, ])) + expect_equal(data(y)[, 1], data(x)[, ncol(x)]) + # horizontal reflection + y <- flop(x) + expect_identical(dim(y), dim(x)) + expect_equal(data(y)[, 1], rev(data(x)[, 1])) + expect_equal(data(y)[1, ], data(x)[nrow(x), ]) +}) + +test_that("translation,imageArray", { + x <- image(sd, 1) + # identity + y <- translation(x, c(0,0,0)) + expect_identical(x, y) + expect_null(metadata(y)$wh) + # invalid + expect_error(translation(x, numeric(2))) + expect_error(translation(x, numeric(4))) + expect_error(translation(x, character(3))) + # row + t <- c(0,n <- sample(77, 1),0) + z <- translation(y <- x[,-1,-c(1,2)], t) + expect_equal(dim(z), dim(y)) + expect_is(data(z), "DelayedArray") + md <- metadata(z)$wh + expect_is(md, "list") + expect_is(unlist(md), "numeric") + expect_equal(md[[1]], c(0, dim(y)[3])) + expect_equal(md[[2]], c(n, dim(y)[2]+n)) + # col + t <- c(0,0,n <- sample(77, 1)) + z <- translation(y <- x[,-1,-c(1,2)], t) + expect_equal(dim(z), dim(y)) + expect_is(data(z), "DelayedArray") + md <- metadata(z)$wh + expect_is(md, "list") + expect_is(unlist(md), "numeric") + expect_equal(md[[1]], c(n, dim(y)[3]+n)) + expect_equal(md[[2]], c(0, dim(y)[2])) +}) + +test_that("translation,labelArray", { + x <- label(sd, 1) + # identity + y <- translation(x, c(0,0)) + expect_identical(x, y) + expect_null(metadata(y)$wh) + # invalid + expect_error(translation(x, numeric(1))) + expect_error(translation(x, numeric(3))) + expect_error(translation(x, character(2))) + # row + t <- c(n <- sample(77, 1), 0) + z <- translation(y <- x[-1,-c(1,2)], t) + expect_equal(dim(z), dim(y)) + expect_is(data(z), "DelayedArray") + md <- metadata(z)$wh + expect_is(md, "list") + expect_is(unlist(md), "numeric") + expect_equal(md[[1]], c(0, dim(y)[2])) + expect_equal(md[[2]], c(n, dim(y)[1]+n)) + # col + t <- c(0, n <- sample(77, 1)) + z <- translation(y <- x[-1,-c(1,2)], t) + expect_equal(dim(z), dim(y)) + expect_is(data(z), "DelayedArray") + md <- metadata(z)$wh + expect_is(md, "list") + expect_is(unlist(md), "numeric") + expect_equal(md[[1]], c(n, dim(y)[2]+n)) + expect_equal(md[[2]], c(0, dim(y)[1])) + # TODO: multiscale + # x <- label(sd, 2) + # t <- c(n <- nrow(x), 0) + # y <- translation(x, t) + # dx <- vapply(data(x, NULL), dim, integer(2)) + # dy <- vapply(data(y, NULL), dim, integer(2)) + # expect_equal(dx[1,], dy[1,]/2) + # expect_identical(dx[2,], dy[2,]) +}) + +test_that("translation,PointFrame", { + x <- point(sd, 1) + y <- translation(x, c(0,0)) + expect_identical(x, y) + # invalid + expect_error(translation(x, numeric(1))) + expect_error(translation(x, numeric(3))) + expect_error(translation(x, logical(2))) + expect_error(translation(x, c(Inf, Inf))) + expect_error(translation(x, character(2))) + expect_error(translation(x, NA*numeric(2))) + # valid + i <- setdiff(names(x), c("x", "y")) + f <- \() sample(33, 1)*sample(c(-1, 1), 1) + replicate(5, { + n <- f(); m <- f() + y <- translation(x, c(n,m)) + expect_equal(x$x+n, y$x) + expect_equal(x$y+m, y$y) + for (. in i) expect_identical(x[[.]], y[[.]]) + }) +}) + +test_that("scale,PointFrame", { + x <- point(sd, 1) + y <- scale(x, c(1, 1)) + expect_identical(x, y) + # invalid + expect_error(scale(x, -c(1, 1))) + expect_error(scale(x, c(1, -1))) + expect_error(scale(x, numeric(1))) + expect_error(scale(x, numeric(3))) + expect_error(scale(x, logical(2))) + expect_error(scale(x, character(2))) + expect_error(scale(x, NA*numeric(2))) + expect_error(scale(x, c(Inf, Inf))) + # valid + i <- setdiff(names(x), c("x", "y")) + f <- \() replicate(2, runif(1, 0.1, 2)) + g <- \(.) cbind(.$x, .$y) + replicate(5, { + y <- scale(x, t <- f()) + expect_equal(sweep(g(x), 2, t, `*`), g(y)) + }) +}) + +test_that("rotate,PointFrame", { + x <- point(sd, 1) + y <- rotate(x, 0) + expect_identical(x, y) + # invalid + expect_error(rotate(x, Inf)) + expect_error(rotate(x, numeric(2))) + expect_error(rotate(x, logical(1))) + expect_error(rotate(x, character(1))) + expect_error(rotate(x, NA*numeric(1))) + # valid + i <- setdiff(names(x), c("x", "y")) + f <- \() sample(777, 1)*sample(c(-1, 1), 1) + g <- \(.) cbind(.$x, .$y) + replicate(5, { + y <- rotate(x, t <- f()) + R <- .R(t*base::pi/180) + expect_equal(t(R %*% t(g(x))), g(y)) + for (. in i) expect_identical(x[[.]], y[[.]]) + }) +}) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R new file mode 100644 index 00000000..74343ff3 --- /dev/null +++ b/tests/testthat/test-utils.R @@ -0,0 +1,98 @@ +xy <- c("x", "y") +require(sf, quietly=TRUE) +x <- file.path("extdata", "blobs.zarr") +x <- system.file(x, package="SpatialData") +x <- readSpatialData(x, tables=FALSE) + +test_that("centroids,LabelArray", { + y <- label(x) + z <- centroids(y, "data.frame") + expect_is(z, "data.frame") + expect_identical(names(z), c(xy, "i")) + expect_is(z$i, "factor") + expect_is(unlist(z[xy]), "numeric") + .z <- centroids(y, "matrix") + expect_is(.z, "matrix") + z$i <- as.integer(as.character(z$i)) + expect_identical(.z, as.matrix(z)) +}) +test_that("centroids,PointFrame", { + i <- feature_key(y <- point(x)) + z <- centroids(y, "data.frame") + expect_is(z, "data.frame") + expect_identical(names(z), c(xy, i)) + expect_is(z[[i]], "factor") + expect_is(unlist(z[xy]), "integer") + .z <- centroids(y, "list") + expect_is(.z, "list") + expect_all_true(names(.z) %in% z[[i]]) + expect_length(.z, length(unique(z[[i]]))) + for (. in names(.z)) expect_identical( + .z[[.]][xy], z[z[[i]] == ., xy]) +}) +test_that("centroids,ShapeFrame", { + # circle + y <- shape(x) + z <- centroids(y, "data.frame") + expect_is(z, "data.frame") + expect_identical(names(z), xy) + expect_is(unlist(z[xy]), "numeric") + .z <- centroids(y, "matrix") + expect_identical(.z, as.matrix(z)) + # polygon + y <- shape(x, 3) + z <- centroids(y, "data.frame") + expect_is(z, "data.frame") + expect_all_true(xy %in% names(z)) + expect_is(z[[ncol(z)]], "factor") + expect_is(unlist(z[xy]), "numeric") + .z <- centroids(y, "matrix") + expect_is(.z, "matrix") + expect_identical(.z[, xy], as.matrix(z[xy])) + # multipolygon + y <- shape(x, 2) + z <- centroids(y, "data.frame") + expect_is(z, "data.frame") + expect_all_true(xy %in% names(z)) + expect_is(z[[ncol(z)]], "factor") + expect_is(unlist(z[xy]), "numeric") + .z <- centroids(y, "matrix") + expect_is(.z, "matrix") + for (. in seq(3, ncol(z))) + z[[.]] <- as.integer(as.character(z[[.]])) + expect_identical(.z, as.matrix(z)) +}) + +test_that("extent,ImageArray", { + z <- extent(y <- image(x)[,-1,-c(1,2)]) + expect_is(z, "list") + expect_is(unlist(z), "numeric") + expect_identical(names(z), rev(xy)) + expect_identical(z$y, c(0, dim(y)[2])) + expect_identical(z$x, c(0, dim(y)[3])) +}) +test_that("extent,LabelArray", { + z <- extent(y <- label(x)[,-1,-c(1,2)]) + expect_is(z, "list") + expect_is(unlist(z), "numeric") + expect_identical(names(z), rev(xy)) + expect_identical(z$y, c(0, dim(y)[1])) + expect_identical(z$x, c(0, dim(y)[2])) +}) +test_that("extent,PointFrame", { + z <- extent(y <- point(x)) + expect_is(z, "list") + expect_identical(names(z), xy) + expect_is(unlist(z), "integer") + expect_identical(z$x, range(y$x)) + expect_identical(z$y, range(y$y)) +}) +test_that("extent,ShapeFrame", { + z <- extent(y <- shape(x)) + expect_is(z, "list") + expect_identical(names(z), xy) + expect_is(unlist(z), "numeric") + mx <- st_coordinates(st_as_sf(data(y))) + expect_identical(z$x, range(mx[, 1])) + expect_identical(z$y, range(mx[, 2])) +}) diff --git a/tests/testthat/test-validity.R b/tests/testthat/test-validity.R new file mode 100644 index 00000000..e08c8393 --- /dev/null +++ b/tests/testthat/test-validity.R @@ -0,0 +1,48 @@ +require(dplyr, quietly=TRUE) +zs <- file.path("extdata", "blobs.zarr") +zs <- system.file(zs, package="SpatialData") +sd <- readSpatialData(zs, tables=FALSE) + +test_that("validity,ImageArray", { + # all resolutions should be numbers + # (note: logical gets coerced to binary) + expect_error(ImageArray(list(v <- character(1)))) + x <- image(sd,1); x@data[[1]][1,1,1] <- v; expect_error(validObject(x)) + x <- image(sd,2); x@data[[2]][1,1,1] <- v; expect_error(validObject(x)) + # there should be three dimensions (channels + spatial) + expect_error(ImageArray(list(a <- array(numeric(1), c(1,1))))) + x <- image(sd,1); x@data[[1]] <- a; expect_error(validObject(x)) + x <- image(sd,2); x@data[[2]] <- a; expect_error(validObject(x)) +}) + +test_that("validity,LabelArray", { + # all resolutions should be of type integer + for (v in list(logical(1), character(1), numeric(1))) { + expect_error(LabelArray(list(v))) + x <- label(sd,1); x@data[[1]][1,1] <- v; expect_error(validObject(x)) + x <- label(sd,2); x@data[[2]][1,1] <- v; expect_error(validObject(x)) + } + # there should be two dimensions + expect_error(LabelArray(list(a <- array(integer(1), c(1,1,1))))) + x <- label(sd,1); x@data[[1]] <- a; expect_error(validObject(x)) + x <- label(sd,2); x@data[[2]] <- a; expect_error(validObject(x)) +}) + +test_that("validity,PointFrame", { + x <- point(sd,1) + expect_error(validObject(select(x, -x))) + expect_error(validObject(select(x, -y))) + expect_silent(validObject(select(x, -c(x, y))[0,])) +}) + +test_that("validity,ShapeFrame", { + x <- shape(sd,1) + x@data <- select(data(x), -radius) + expect_silent(validObject(x)) + x <- shape(sd,1) + x@data <- filter(data(x), radius == Inf) + expect_silent(validObject(x)) + x <- shape(sd,1) + x@data <- select(data(x), -geometry) + expect_error(validObject(x)) +}) diff --git a/tests/testthat/test-zarrutils.R b/tests/testthat/test-zarrutils.R index 1d086572..6b0708dd 100644 --- a/tests/testthat/test-zarrutils.R +++ b/tests/testthat/test-zarrutils.R @@ -32,52 +32,116 @@ test_that("create zarr/group", { expect_true(dir.exists(file.path(output_zarr, "group3/subgroup1/subsubgroup1"))) expect_true(file.exists(file.path(output_zarr, "group3/subgroup1/subsubgroup1", ".zgroup"))) - # version 3 and other entries + # invalid version string dir.create(td <- tempfile()) name <- "test" - output_zarr <- file.path(td, paste0(name, ".zarr")) - expect_error(create_zarr(dir = td, name = name, version = "v4"), pattern = "only zarr v2 is supported") + expect_error(create_zarr(dir = td, name = name, version = "v4"), pattern = "version must be 'v2' or 'v3'") }) +test_that("create zarr/group v3", { -# create zarr array + dir.create(td <- tempfile()) + name <- "test.zarr" + output_zarr <- file.path(td, name) + + # open v3 zarr store + create_zarr(name = name, dir = td, version = "v3") + expect_true(dir.exists(output_zarr)) + expect_true(file.exists(file.path(output_zarr, "zarr.json"))) + expect_false(file.exists(file.path(output_zarr, ".zgroup"))) + + # check zarr.json exists and attributes are empty + expect_true(file.exists(file.path(output_zarr, "zarr.json"))) + expect_equal(Rarr::read_zarr_attributes(output_zarr), list()) + + # create a sub-group + create_zarr_group(store = output_zarr, name = "images", version = "v3") + expect_true(file.exists(file.path(output_zarr, "images", "zarr.json"))) + expect_false(file.exists(file.path(output_zarr, "images", ".zgroup"))) + + # create nested groups — parent group should also be v3 + create_zarr_group(store = output_zarr, name = "points/blobs_points", version = "v3") + expect_true(file.exists(file.path(output_zarr, "points", "zarr.json"))) + expect_true(file.exists(file.path(output_zarr, "points/blobs_points", "zarr.json"))) +}) + + +# create a v2 zarr array for the v2 zattrs tests dir.create(td <- tempfile()) path <- file.path(td, "test.zarr") x <- array(runif(n = 10), dim = c(2, 5)) Rarr::write_zarr_array( x = x, zarr_array_path = path, - chunk_dim = c(2, 5) + chunk_dim = c(2, 5), zarr_version = 2L ) test_that("read/write zattrs", { # add .zattrs to / zattrs <- list(foo = "foo", bar = "bar") - write_zattrs(path = path, new.zattrs = zattrs) + Rarr::write_zarr_attributes(path, new.zattrs = zattrs) expect_true(file.exists(file.path(path, ".zattrs"))) - + # check .zattrs - read.zattrs <- read_zattrs(path) + read.zattrs <- Rarr::read_zarr_attributes(path) expect_equal(read.zattrs, zattrs) - + # add new elements to .zattrs zattrs.new.elem <- list(foo2 = "foo") - write_zattrs(path = path, new.zattrs = zattrs.new.elem) - read.zattrs <- read_zattrs(path) + Rarr::write_zarr_attributes(path, new.zattrs = zattrs.new.elem) + read.zattrs <- Rarr::read_zarr_attributes(path) expect_equal(read.zattrs, c(zattrs,zattrs.new.elem)) - + # overwrite zattrs.new.elem <- list(foo2 = "foo2") - write_zattrs(path = path, new.zattrs = zattrs.new.elem) - read.zattrs <- read_zattrs(path) + Rarr::write_zarr_attributes(path, new.zattrs = zattrs.new.elem) + read.zattrs <- Rarr::read_zarr_attributes(path) zattrs[names(zattrs.new.elem)] <- zattrs.new.elem expect_equal(read.zattrs, c(zattrs)) - + # overwrite = FALSE zattrs.new.elem <- list(foo2 = "foo") - write_zattrs(path = path, new.zattrs = zattrs.new.elem, overwrite = FALSE) - read.zattrs <- read_zattrs(path) + Rarr::write_zarr_attributes(path, new.zattrs = zattrs.new.elem, overwrite = FALSE) + read.zattrs <- Rarr::read_zarr_attributes(path) zattrs[names(zattrs.new.elem)] <- "foo2" expect_equal(read.zattrs, c(zattrs)) - + +}) + +test_that("read/write zattrs v3", { + + # create a v3 zarr group to use as the target path + dir.create(td <- tempfile()) + grp <- file.path(td, "elem") + create_zarr_group(store = td, name = "elem", version = "v3") + + # write attributes into zarr.json + zattrs <- list(foo = "foo", bar = "bar") + Rarr::write_zarr_attributes(grp, new.zattrs = zattrs) + expect_true(file.exists(file.path(grp, "zarr.json"))) + expect_false(file.exists(file.path(grp, ".zattrs"))) + + # read back attributes from zarr.json + read.zattrs <- Rarr::read_zarr_attributes(grp) + expect_equal(read.zattrs, zattrs) + + # zarr.json must still exist (zarr_format / node_type preserved by Rarr internally) + expect_true(file.exists(file.path(grp, "zarr.json"))) + + # add new element + Rarr::write_zarr_attributes(grp, new.zattrs = list(baz = "baz")) + read.zattrs <- Rarr::read_zarr_attributes(grp) + expect_equal(read.zattrs, c(zattrs, list(baz = "baz"))) + + # overwrite existing key + Rarr::write_zarr_attributes(grp, new.zattrs = list(foo = "FOO")) + read.zattrs <- Rarr::read_zarr_attributes(grp) + expect_equal(read.zattrs$foo, "FOO") + expect_equal(read.zattrs$bar, "bar") # untouched key preserved + + # overwrite = FALSE should not overwrite existing key + Rarr::write_zarr_attributes(grp, new.zattrs = list(foo = "original"), overwrite = FALSE) + read.zattrs <- Rarr::read_zarr_attributes(grp) + expect_equal(read.zattrs$foo, "FOO") # unchanged + }) \ No newline at end of file diff --git a/tests/testthat/test-zattrs.R b/tests/testthat/test-zattrs.R index c618854e..ccfc0edb 100644 --- a/tests/testthat/test-zattrs.R +++ b/tests/testthat/test-zattrs.R @@ -1,134 +1,51 @@ -x <- file.path("extdata", "blobs.zarr") -x <- system.file(x, package="SpatialData") -x <- readSpatialData(x, anndataR=FALSE) +z <- list(v1="blobs.zarr", v3="blobs_v3.zarr") -test_that("axes", { - # image - y <- axes(image(x)) - expect_is(y, "data.frame") - expect_equal(dim(y), c(3, 2)) - # label - y <- axes(label(x)) - expect_is(y, "data.frame") - expect_equal(dim(y), c(2, 2)) - # shape - y <- axes(shape(x)) - expect_is(y, "character") - expect_length(y, 2) - # point - y <- axes(point(x)) - expect_is(y, "character") - expect_length(y, 2) -}) +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]) + }) -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]))) - # by name - i <- sample(CTname(y), 2) - expect_identical(CTname(rmvCT(y, i)), setdiff(CTname(y), i)) - # by index - i <- sample(seq_along(CTname(y)), 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 <- CTdata(z)[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("plotCoordGraph", { - f <- function(.) { - tf <- tempfile(fileext=".pdf") - on.exit(unlink(tf)) - pdf(tf); .; dev.off() - file.size(tf) - } - g <- CTgraph(x) - p <- f(plotCoordGraph(g)) - expect_is(p, "numeric") - expect_true(p > f(plot(1))) - p <- f(plotCoordGraph(g, 0.1)) - q <- f(plotCoordGraph(g, 0.9)) - expect_true(p < q) -}) +} \ No newline at end of file diff --git a/vignettes/SpatialData.Rmd b/vignettes/SpatialData.Rmd index 956096ff..8c6e879d 100644 --- a/vignettes/SpatialData.Rmd +++ b/vignettes/SpatialData.Rmd @@ -49,7 +49,7 @@ For demonstration, we read in a toy dataset that is available through the packag ```{r blobs-read} x <- file.path("extdata", "blobs.zarr") x <- system.file(x, package="SpatialData") -(x <- readSpatialData(x, anndataR=FALSE)) # VJC Dec 8, test effect of absence of anndataR +(x <- readSpatialData(x, anndataR=TRUE)) ``` `SpatialData` object behave like a list, thereby supporting flexible accession, @@ -119,9 +119,7 @@ To facilitate .zattrs handling, we provide a set of functions to access and modi - `rmv/addCT` to remove, add, or append coordinate transformations ```{r cs-methods} -z <- meta(label(x)) -axes(z) -CTdata(z) +(z <- meta(label(x))) CTname(rmvCT(z, "scale")) CTname(addCT(z, name="D'Artagnan", @@ -134,7 +132,7 @@ We can represent these are a graph as follows: ```{r cs-graph} (g <- CTgraph(x)) -plotCoordGraph(g) +CTplot(g) ``` The above representation greatly facilitates queries of the transformation(s) diff --git a/vignettes/SpatialData.html b/vignettes/SpatialData.html index cf711c6f..0f8bfcff 100644 --- a/vignettes/SpatialData.html +++ b/vignettes/SpatialData.html @@ -10,7 +10,7 @@ - +
SpatialDataSpatialData 0.99.19
+SpatialData 0.99.25
@@ -717,14 +717,11 @@library(ggplot2)
-library(ggnewscale)
-library(SpatialData)
+library(SpatialData)
library(SingleCellExperiment)
1 Introduction
@@ -733,11 +730,11 @@ 1 Introduction
.zarr files that follow OME-NGFF specs.
Each SpatialData object is composed of five layers: images, labels, shapes,
points, and tables. Each layer may contain an arbitrary number of elements.
-Images and labels are represented as ZarrArrays (Rarr).
+
Images and labels are represented as ZarrArrays (Rarr).
Points and shapes are represented as arrow objects linked
to an on-disk .parquet file. As such, all data are represented out of memory.
Element annotation as well as cross-layer summarizations (e.g., count matrices)
-are represented as SingleCellExperiment as tables.
+are represented as SingleCellExperiment as tables.
1.1 Handling
For demonstration, we read in a toy dataset that is available through the package:
@@ -792,20 +789,11 @@ 1.1 Handling
##
## See $metadata for additional Schema metadata
meta(shape(x))
-## An object of class "Zattrs"
-## [[1]]
-## [1] "x" "y"
-##
-## [[2]]
-## input.axes input.name output.axes output.name type
-## 1 c("x", ".... xy c("x", ".... global identity
-##
-## [[3]]
-## [1] "ngff:shapes"
-##
-## [[4]]
-## [[4]]$version
-## [1] "0.2"
+## class: Zattrs
+## axes(2):
+## - name: x y
+## coordTrans(1):
+## - global: (identity)
1.2 Annotations
@@ -853,14 +841,14 @@ 1.2 Annotations
y <- setTable(x, i, df)
head(colData(getTable(y, i)))
## DataFrame with 6 rows and 1 column
-## n
-## <numeric>
-## 1 0.0741157
-## 2 0.2648403
-## 3 0.5950422
-## 4 0.6883271
-## 5 0.0452319
-## 6 0.1401131
+## n
+## <numeric>
+## 1 0.00353685
+## 2 0.94029116
+## 3 0.78284891
+## 4 0.97153644
+## 5 0.81024104
+## 6 0.72704077
# ...using a list of data generating functions
f <- list(
numbers=\(n) runif(n),
@@ -871,12 +859,12 @@ 1.2 Annotations
## DataFrame with 6 rows and 2 columns
## numbers letters
## <numeric> <character>
-## 1 0.156174 m
-## 2 0.998399 m
-## 3 0.418688 d
-## 4 0.302950 k
-## 5 0.319555 x
-## 6 0.443932 l
+## 1 0.9760238 f
+## 2 0.8935270 p
+## 3 0.0271508 l
+## 4 0.6642598 a
+## 5 0.3210180 o
+## 6 0.4329136 d
1.3 Transformations
@@ -886,32 +874,26 @@ 1.3 Transformations
CTdata/name/type to access coordinate transformation components
rmv/addCT to remove, add, or append coordinate transformations
-z <- meta(label(x))
-axes(z)
-## name type
-## 1 y space
-## 2 x space
-CTdata(z)
-## input.axes input.name output.axes output.name type scale
-## 1 c("y", ".... yx c("y", ".... global identity
-## 2 c("y", ".... yx c("y", ".... scale scale 3, 2
-## 3 c("y", ".... yx c("y", ".... translation translation
-## 4 c("y", ".... yx c("x", ".... affine affine
-## 5 c("y", ".... yx c("y", ".... sequence sequence
-## translation affine transformations
-## 1
-## 2
-## 3 -50, 10
-## 4 20, 50, ....
-## 5 list(axe....
+(z <- meta(label(x)))
+## class: Zattrs
+## axes(2):
+## - name: y x
+## - type: space space
+## coordTrans(5):
+## - global: (identity)
+## - scale: (scale)
+## - translation: (translation)
+## - affine: (affine)
+## - sequence: (sequence)
+## datasets(1): 0
+## - 0: (scale:[1,1])
CTname(rmvCT(z, "scale"))
## [1] "global" "translation" "affine" "sequence"
CTname(addCT(z,
name="D'Artagnan",
type="scale",
data=c(19, 94)))
-## [1] "global" "scale" "translation" "affine" "sequence"
-## [6] "D'Artagnan"
+## [1] "D'Artagnan"
Zattrs specify an explicit relationship between elements and coordinate systems.
We can represent these are a graph as follows:
(g <- CTgraph(x))
@@ -919,7 +901,7 @@ 1.3 Transformations
## Number of Nodes = 14
## Number of Edges = 13
plotCoordGraph(g)
-
+
The above representation greatly facilitates queries of the transformation(s)
required to spatially align elements. blobs_labels, for example, requires a
sequential transformation (scaling and translation) for the sequence space:
@@ -931,7 +913,12 @@ 1.3 Transformations
invisible(CTpath(y, j))
## [[1]]
## [[1]]$data
-## [1] 3 2
+## [[1]]$data[[1]]
+## [1] 3
+##
+## [[1]]$data[[2]]
+## [1] 2
+##
##
## [[1]]$type
## [1] "scale"
@@ -939,55 +926,26 @@ 1.3 Transformations
##
## [[2]]
## [[2]]$data
-## [1] -50 10
+## [[2]]$data[[1]]
+## [1] -50
+##
+## [[2]]$data[[2]]
+## [1] 10
+##
##
## [[2]]$type
## [1] "translation"
-
-2 Datasets
-Data from a variety of technologies has been made available as SpatialData .zarr stores
-here.
-These, in turn, have been deposited in Bioconductor’s NSF Open Storage Network bucket,
-and can be retrieved with caching support using BiocFileCache.
-We can interrogate the bucket for available (zipped) .zarr archives:
-available_spd_zarr_zips()
-## [1] "mcmicro_io.zip"
-## [2] "merfish.zarr.zip"
-## [3] "mibitof.zip"
-## [4] "steinbock_io.zip"
-## [5] "visium_associated_xenium_io_aligned.zip"
-## [6] "visium_hd_3.0.0_io.zip"
-Any of the above can be retrieved (once) into some location, and read into R; for example:
-dir.create(td <- tempfile())
-pa <- unzip_spd_demo(
- zipname="merfish.zarr.zip",
- dest=td, source="biocOSN")
-(x <- readSpatialData(pa))
-## class: SpatialData
-## - images(1):
-## - rasterized (1,522,575)
-## - labels(0):
-## - points(1):
-## - single_molecule (3714642)
-## - shapes(2):
-## - anatomical (6,polygon)
-## - cells (2389,circle)
-## - tables(1):
-## - table (268,2389)
-## coordinate systems:
-## - global(4): rasterized anatomical cells single_molecule
-
-
-3 Session info
-## R version 4.4.1 Patched (2024-07-08 r86893)
-## Platform: aarch64-apple-darwin20
-## Running under: macOS Sonoma 14.2.1
+
+2 Session info
+## R version 4.6.0 alpha (2026-03-26 r89725)
+## Platform: aarch64-apple-darwin23
+## Running under: macOS Sequoia 15.6.1
##
## Matrix products: default
-## BLAS: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib
-## LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
+## BLAS: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib
+## LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
@@ -1000,49 +958,42 @@ 3 Session info
## [8] base
##
## other attached packages:
-## [1] Rarr_1.6.0 DelayedArray_0.32.0
-## [3] SparseArray_1.6.0 S4Arrays_1.6.0
-## [5] abind_1.4-8 Matrix_1.7-1
-## [7] SingleCellExperiment_1.28.0 SummarizedExperiment_1.36.0
-## [9] Biobase_2.66.0 GenomicRanges_1.58.0
-## [11] GenomeInfoDb_1.42.0 IRanges_2.40.0
-## [13] S4Vectors_0.44.0 BiocGenerics_0.52.0
-## [15] MatrixGenerics_1.18.0 matrixStats_1.4.1
-## [17] SpatialData_0.99.19 ggnewscale_0.5.0
-## [19] ggplot2_3.5.1 BiocStyle_2.34.0
+## [1] SingleCellExperiment_1.33.2 SummarizedExperiment_1.41.1
+## [3] Biobase_2.71.0 GenomicRanges_1.63.1
+## [5] Seqinfo_1.1.0 IRanges_2.45.0
+## [7] S4Vectors_0.49.0 BiocGenerics_0.57.0
+## [9] generics_0.1.4 MatrixGenerics_1.23.0
+## [11] matrixStats_1.5.0 SpatialData_0.99.25
+## [13] BiocStyle_2.39.0
##
## loaded via a namespace (and not attached):
-## [1] DBI_1.2.3 RBGL_1.82.0 anndataR_0.99.0
-## [4] rlang_1.1.4 magrittr_2.0.3 e1071_1.7-16
-## [7] compiler_4.4.1 RSQLite_2.3.8 dir.expiry_1.14.0
-## [10] paws.storage_0.7.0 png_0.1-8 vctrs_0.6.5
-## [13] stringr_1.5.1 wk_0.9.4 pkgconfig_2.0.3
-## [16] crayon_1.5.3 fastmap_1.2.0 magick_2.8.5
-## [19] dbplyr_2.5.0 XVector_0.46.0 paws.common_0.7.7
-## [22] utf8_1.2.4 rmarkdown_2.29 graph_1.84.0
-## [25] UCSC.utils_1.2.0 tinytex_0.54 purrr_1.0.2
-## [28] bit_4.5.0 xfun_0.49 zlibbioc_1.52.0
-## [31] cachem_1.1.0 jsonlite_1.8.9 blob_1.2.4
-## [34] parallel_4.4.1 R6_2.5.1 bslib_0.8.0
-## [37] stringi_1.8.4 reticulate_1.40.0 jquerylib_0.1.4
-## [40] Rcpp_1.0.13-1 bookdown_0.41 assertthat_0.2.1
-## [43] knitr_1.49 R.utils_2.12.3 tidyselect_1.2.1
-## [46] rstudioapi_0.17.1 yaml_2.3.10 zellkonverter_1.16.0
-## [49] curl_6.0.1 lattice_0.22-6 tibble_3.2.1
-## [52] basilisk.utils_1.18.0 withr_3.0.2 evaluate_1.0.1
-## [55] sf_1.0-19 units_0.8-5 proxy_0.4-27
-## [58] BiocFileCache_2.14.0 xml2_1.3.6 pillar_1.9.0
-## [61] BiocManager_1.30.25 filelock_1.0.3 KernSmooth_2.23-24
-## [64] pizzarr_0.1.0 generics_0.1.3 nanoarrow_0.6.0
-## [67] munsell_0.5.1 scales_1.3.0 class_7.3-22
-## [70] glue_1.8.0 tools_4.4.1 grid_4.4.1
-## [73] colorspace_2.1-1 paws_0.7.0 GenomeInfoDbData_1.2.13
-## [76] basilisk_1.18.0 cli_3.6.3 fansi_1.0.6
-## [79] arrow_17.0.0.1 dplyr_1.1.4 geoarrow_0.2.1
-## [82] Rgraphviz_2.50.0 gtable_0.3.6 R.methodsS3_1.8.2
-## [85] sass_0.4.9 digest_0.6.37 classInt_0.4-10
-## [88] memoise_2.0.1 htmltools_0.5.8.1 R.oo_1.27.0
-## [91] lifecycle_1.0.4 httr_1.4.7 bit64_4.5.2
+## [1] tidyselect_1.2.1 dplyr_1.2.0 filelock_1.0.3
+## [4] arrow_23.0.1.2 R.utils_2.13.0 fastmap_1.2.0
+## [7] digest_0.6.39 lifecycle_1.0.5 sf_1.1-0
+## [10] paws.storage_0.9.0 magrittr_2.0.4 compiler_4.6.0
+## [13] rlang_1.1.7 sass_0.4.10 tools_4.6.0
+## [16] yaml_2.3.12 knitr_1.51 S4Arrays_1.11.1
+## [19] bit_4.6.0 classInt_0.4-11 curl_7.0.0
+## [22] reticulate_1.45.0 DelayedArray_0.37.0 KernSmooth_2.23-26
+## [25] abind_1.4-8 withr_3.0.2 purrr_1.2.1
+## [28] R.oo_1.27.1 grid_4.6.0 e1071_1.7-17
+## [31] tinytex_0.59 cli_3.6.5 rmarkdown_2.31
+## [34] crayon_1.5.3 otel_0.2.0 rstudioapi_0.18.0
+## [37] DBI_1.3.0 cachem_1.1.0 proxy_0.4-29
+## [40] assertthat_0.2.1 parallel_4.6.0 BiocManager_1.30.27
+## [43] XVector_0.51.0 geoarrow_0.4.2 basilisk_1.23.0
+## [46] vctrs_0.7.2 Matrix_1.7-5 jsonlite_2.0.0
+## [49] dir.expiry_1.19.0 bookdown_0.46 bit64_4.6.0-1
+## [52] RBGL_1.87.0 Rgraphviz_2.55.0 magick_2.9.1
+## [55] jquerylib_0.1.4 units_1.0-1 glue_1.8.0
+## [58] ZarrArray_0.99.1 Rarr_1.11.32 tibble_3.3.1
+## [61] pillar_1.11.1 rappdirs_0.3.4 nanoarrow_0.8.0
+## [64] htmltools_0.5.9 graph_1.89.1 R6_2.6.1
+## [67] httr2_1.2.2 wk_0.9.5 evaluate_1.0.5
+## [70] lattice_0.22-9 R.methodsS3_1.8.2 png_0.1-9
+## [73] paws.common_0.8.9 bslib_0.10.0 class_7.3-23
+## [76] Rcpp_1.1.1 SparseArray_1.11.11 anndataR_1.1.2
+## [79] xfun_0.57 pkgconfig_2.0.3