From cef68869616ab538e3434b824ab284a482f1b015 Mon Sep 17 00:00:00 2001 From: Artur-man Date: Fri, 4 Sep 2026 01:18:31 +0200 Subject: [PATCH 1/8] fix axes metadata for points/shapes --- R/sdAttrs.R | 5 ++--- tests/testthat/test-sdattrs.R | 11 +++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/R/sdAttrs.R b/R/sdAttrs.R index 5ea5bae8..a8c30a5e 100644 --- a/R/sdAttrs.R +++ b/R/sdAttrs.R @@ -114,10 +114,9 @@ SpatialDataAttrs <- \(x, type=c("image", "label", "frame"), switch(match.arg(type), # xyzt for points/shapes frame={ - ax <- list(x, y) + ax <- list(x$name, y$name) if (dim > 2) { - ax <- c(ax, list(z)) - if (dim > 3) ax <- c(ax, list(t)) + ax <- c(ax, list(z$name)) } }, # tczyx for images/labels diff --git a/tests/testthat/test-sdattrs.R b/tests/testthat/test-sdattrs.R index 97a6a780..bce4bedd 100644 --- a/tests/testthat/test-sdattrs.R +++ b/tests/testthat/test-sdattrs.R @@ -97,12 +97,15 @@ test_that("SpatialDataAttrs()", { expect_equal(sum(y == "space"), ifelse(d == 2, 2, 3)) } # 3-4D shape/point - for (d in seq(2, 4)) { + for (d in seq(2, 3)) { x <- SpatialDataAttrs(type="frame", dim=d) - y <- axes(x, "type") + y <- axes(x) expect_length(y, d) + xy <- c("x", "y") + expect_equal(unlist(y), if(d == 2) xy else c(xy, "z")) expect_null(channels(x)) - expect_equal(sum(y == "time"), ifelse(d == 4, 1, 0)) - expect_equal(sum(y == "space"), ifelse(d == 2, 2, 3)) + # TODO: should we return x itself, regardless of requested name? + expect_error(axes(x, "name")) + expect_error(axes(x, "type")) } }) From 98fda7734cd6af661ef962a0f5c55534f738f210 Mon Sep 17 00:00:00 2001 From: Artur-man Date: Fri, 4 Sep 2026 02:02:01 +0200 Subject: [PATCH 2/8] add utilities to check point/shape/label/image versions --- R/crop.R | 4 ++-- R/sdAttrs.R | 30 +++++++++++++++++------------- R/sdFrame.R | 4 ++-- R/utils.R | 10 ++++++++++ man/SpatialDataAttrs.Rd | 20 ++++++++++---------- man/SpatialDataFrame.Rd | 4 ++-- tests/testthat/test-sdattrs.R | 23 +++++++++++++++++++++-- 7 files changed, 64 insertions(+), 31 deletions(-) diff --git a/R/crop.R b/R/crop.R index 2b77e003..b4cfb8fb 100644 --- a/R/crop.R +++ b/R/crop.R @@ -141,9 +141,9 @@ NULL ct[[type]] <- .adapt(data, type) } # update input axes from 'cyx' to 'xy' - ct$input$axes <- .default_ax(type="frame") + ct$input$axes <- .default_ax(type="shape") # create temporary shape & transform back - md <- SpatialDataAttrs(type="frame", trans=list(ct)) + md <- SpatialDataAttrs(type="shape", trans=list(ct)) z <- SpatialDataShape(df, meta=md) z <- transform(z, 1, rev=TRUE) # extract coordinates & return range diff --git a/R/sdAttrs.R b/R/sdAttrs.R index a8c30a5e..5a607649 100644 --- a/R/sdAttrs.R +++ b/R/sdAttrs.R @@ -10,17 +10,17 @@ #' #' @param x element or list extracted from a OME-NGFF compliant .zattrs file. #' @param name character string for extraction (see ?base::`$`). -#' @param type character string; either "array" (image/label) or "frame" (point/shape). -#' @param label flag; when \code{type="frame"}, should attributes be for a label? +#' @param type character string; either "image", "label", "point" or "shape" #' @param trans list of coordinate transformations; defaults to identity only. #' @param value character string (for one \code{region} and \code{_key}s), #' or vector (for many \code{region}s, \code{instances} and \code{regions}). -#' @param ver character string; specifies the OME version to comply with. +#' @param ver character string; specifies the SpatialData version to comply with. #' @param dim scalar integer in 2-4; #' number of dimensions: 2 = XY, 3 adds Z, 4 adds T (time); #' when \code{type="image"}, C (channel) will be added (for any \code{dim}). #' @param nch scalar integer; how many channels should there be? -#' (ignored unless \code{type="frame"} and \code{label=FALSE}). +#' (ignored unless \code{type="shape"} or \code{type="point"}, and +#' \code{label=FALSE}). #' @param ... additional attributes (e.g., version, feature_key). #' #' @details @@ -29,8 +29,8 @@ #' \code{SingleCellExperiment}: \code{region}, \code{region/instance_key}. #' #' When missing \code{x}, \code{SpatialDataAttrs} will generate a valid object -#' with default axes (array: cyx, frame: xy) and transformations (identify) -#' according to the specified type. +#' with default axes (image/label: cyx, point/shape: xy) and transformations +#' (identify) according to the specified type. #' #' @return character string #' @@ -55,27 +55,29 @@ #' CTdata(z, "scale") #' #' # constructor -#' SpatialDataAttrs(type="frame") +#' SpatialDataAttrs(type="point") +#' SpatialDataAttrs(type="shape") #' SpatialDataAttrs(type="image", nch=7) #' SpatialDataAttrs(type="label", dim=3) #' #' @export -SpatialDataAttrs <- \(x, type=c("image", "label", "frame"), - trans=NULL, ver="0.3", dim=2, nch=3, ...) +SpatialDataAttrs <- \(x, type=c("image", "label", "point", "shape"), + trans=NULL, ver=NULL, dim=2, nch=3, ...) { stopifnot( length(dim) == 1, is.numeric(dim), dim %in% seq(2, 4), length(nch) == 1, is.numeric(nch), round(nch) == nch, nch > 0) if (!missing(x)) return(.SpatialDataAttrs(x)) type <- match.arg(type) - ver <- .val_ome_ver(ver) + if(is.null(ver)) ver <- if(type == "point") "0.2" else "0.3" + ver <- .val_sd_ver(ver, type) ax <- .default_ax(type, dim) # transformations: ct <- trans %||% .default_ct(ax) # datasets: ds <- .default_ds(.ax_names(ax)) # .zattrs list: - if (type != "frame") { + if (!type %in% c("point", "shape")) { # default structure res <- list() if(type != "label") @@ -105,13 +107,15 @@ SpatialDataAttrs <- \(x, type=c("image", "label", "frame"), } # Internal helper to generate OME-NGFF axes -.default_ax <- \(type=c("image", "label", "frame"), dim=2) { +.default_ax <- \(type=c("image", "label", "point", "shape"), dim=2) { c <- list(name="c", type="channel") t <- list(name="t", type="time") z <- list(name="z", type="space") y <- list(name="y", type="space") x <- list(name="x", type="space") - switch(match.arg(type), + type <- match.arg(type) + type <- if(type %in% c("point", "shape")) "frame" else type + switch(type, # xyzt for points/shapes frame={ ax <- list(x$name, y$name) diff --git a/R/sdFrame.R b/R/sdFrame.R index 5f24f14c..5e1caa86 100644 --- a/R/sdFrame.R +++ b/R/sdFrame.R @@ -123,7 +123,7 @@ NULL #' @importFrom methods is #' @importFrom sf st_geometry_type #' @importFrom S4Vectors metadata<- -SpatialDataPoint <- \(data=NULL, meta=SpatialDataAttrs(type="frame"), metadata=list(), ik=NULL, fk=NULL, ...) { +SpatialDataPoint <- \(data=NULL, meta=SpatialDataAttrs(type="point"), metadata=list(), ik=NULL, fk=NULL, ...) { data <- .df_to_sf(data, "POINT") if (isTRUE(nrow(data) > 0L)) { gt <- tryCatch(unique(st_geometry_type(data)), error=\(.) "n/a") @@ -153,7 +153,7 @@ SpatialDataPoint <- \(data=NULL, meta=SpatialDataAttrs(type="frame"), metadata=l #' @rdname SpatialDataFrame #' @importFrom methods is #' @importFrom S4Vectors metadata<- -SpatialDataShape <- \(data=NULL, meta=SpatialDataAttrs(type="frame"), metadata=list(), ...) { +SpatialDataShape <- \(data=NULL, meta=SpatialDataAttrs(type="shape"), metadata=list(), ...) { data <- .df_to_sf(data, "POLYGON") if (!is(data, "duckspatial_df")) data <- .duck(data, "sdShape") diff --git a/R/utils.R b/R/utils.R index 30627fb1..7f518610 100644 --- a/R/utils.R +++ b/R/utils.R @@ -174,6 +174,16 @@ return(v) } +# validate SpatialData version +.val_sd_ver <- \(v, type) { + ok <- length(v) == 1 && + is.character(v) && + v %in% sprintf("0.%d", seq_len(if(type == "point") 2 else 3)) + if (!ok) stop("invalid SpatialData 'version'; expected '0.x' where x is 1-3 ", + "for image/label/shape and 1-2 for point") + return(v) +} + # multiscales ---- # internal helper to get the 'active' metadata level diff --git a/man/SpatialDataAttrs.Rd b/man/SpatialDataAttrs.Rd index 4638d8cd..6887eaf0 100644 --- a/man/SpatialDataAttrs.Rd +++ b/man/SpatialDataAttrs.Rd @@ -38,9 +38,9 @@ \usage{ SpatialDataAttrs( x, - type = c("image", "label", "frame"), + type = c("image", "label", "point", "shape"), trans = NULL, - ver = "0.3", + ver = NULL, dim = 2, nch = 3, ... @@ -89,18 +89,19 @@ SpatialDataAttrs( \arguments{ \item{x}{element or list extracted from a OME-NGFF compliant .zattrs file.} -\item{type}{character string; either "array" (image/label) or "frame" (point/shape).} +\item{type}{character string; either "image", "label", "point" or "shape"} \item{trans}{list of coordinate transformations; defaults to identity only.} -\item{ver}{character string; specifies the OME version to comply with.} +\item{ver}{character string; specifies the SpatialData version to comply with.} \item{dim}{scalar integer in 2-4; number of dimensions: 2 = XY, 3 adds Z, 4 adds T (time); when \code{type="image"}, C (channel) will be added (for any \code{dim}).} \item{nch}{scalar integer; how many channels should there be? -(ignored unless \code{type="frame"} and \code{label=FALSE}).} +(ignored unless \code{type="shape"} or \code{type="point"}, and +\code{label=FALSE}).} \item{...}{additional attributes (e.g., version, feature_key).} @@ -108,8 +109,6 @@ when \code{type="image"}, C (channel) will be added (for any \code{dim}).} \item{value}{character string (for one \code{region} and \code{_key}s), or vector (for many \code{region}s, \code{instances} and \code{regions}).} - -\item{label}{flag; when \code{type="frame"}, should attributes be for a label?} } \value{ character string @@ -123,8 +122,8 @@ When \code{x} is a spatial element, the following applies: \code{SingleCellExperiment}: \code{region}, \code{region/instance_key}. When missing \code{x}, \code{SpatialDataAttrs} will generate a valid object -with default axes (array: cyx, frame: xy) and transformations (identify) -according to the specified type. +with default axes (image/label: cyx, point/shape: xy) and transformations +(identify) according to the specified type. } \examples{ x <- file.path("extdata", "blobs.zarr") @@ -147,7 +146,8 @@ CTtype(z) CTdata(z, "scale") # constructor -SpatialDataAttrs(type="frame") +SpatialDataAttrs(type="point") +SpatialDataAttrs(type="shape") SpatialDataAttrs(type="image", nch=7) SpatialDataAttrs(type="label", dim=3) diff --git a/man/SpatialDataFrame.Rd b/man/SpatialDataFrame.Rd index 32709981..c3de2dfe 100644 --- a/man/SpatialDataFrame.Rd +++ b/man/SpatialDataFrame.Rd @@ -23,7 +23,7 @@ \usage{ SpatialDataPoint( data = NULL, - meta = SpatialDataAttrs(type = "frame"), + meta = SpatialDataAttrs(type = "point"), metadata = list(), ik = NULL, fk = NULL, @@ -32,7 +32,7 @@ SpatialDataPoint( SpatialDataShape( data = NULL, - meta = SpatialDataAttrs(type = "frame"), + meta = SpatialDataAttrs(type = "shape"), metadata = list(), ... ) diff --git a/tests/testthat/test-sdattrs.R b/tests/testthat/test-sdattrs.R index bce4bedd..e3b89a45 100644 --- a/tests/testthat/test-sdattrs.R +++ b/tests/testthat/test-sdattrs.R @@ -61,6 +61,23 @@ test_that(".val_ome_ver()", { expect_length(x, 1) expect_identical(x, v) }) +test_that(".val_sd_ver()", { + # invalid + expect_error(.val_sd_ver(1)) + expect_error(.val_sd_ver(TRUE)) + expect_error(.val_sd_ver("0.0")) + expect_error(.val_sd_ver("0.30")) + expect_error(.val_sd_ver(c("0.3", "0.4"))) + expect_error(.val_sd_ver(v <- "0.3-x")) + expect_error(.val_sd_ver("0.3", "point")) + # valid + expect_silent(x <- .val_sd_ver(v <- "0.3", "image")) + expect_silent(x <- .val_sd_ver(v <- "0.3", "label")) + expect_silent(x <- .val_sd_ver(v <- "0.3", "shape")) + expect_type(x, "character") + expect_length(x, 1) + expect_identical(x, v) +}) test_that("SpatialDataAttrs()", { # invalid expect_error(SpatialDataAttrs(nch=0)) @@ -98,7 +115,8 @@ test_that("SpatialDataAttrs()", { } # 3-4D shape/point for (d in seq(2, 3)) { - x <- SpatialDataAttrs(type="frame", dim=d) + for(t in c("shape", "point")){ + x <- SpatialDataAttrs(type=t, dim=d) y <- axes(x) expect_length(y, d) xy <- c("x", "y") @@ -106,6 +124,7 @@ test_that("SpatialDataAttrs()", { expect_null(channels(x)) # TODO: should we return x itself, regardless of requested name? expect_error(axes(x, "name")) - expect_error(axes(x, "type")) + expect_error(axes(x, "type")) + } } }) From 131c08a903ba392708823432f3ba61df81f91d8d Mon Sep 17 00:00:00 2001 From: Artur-man Date: Fri, 4 Sep 2026 09:51:12 +0200 Subject: [PATCH 3/8] more fixes and tests --- R/sdAttrs.R | 16 +++++++++------- R/utils.R | 3 ++- man/SpatialDataAttrs.Rd | 7 ++++--- tests/testthat/test-sdattrs.R | 5 +++-- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/R/sdAttrs.R b/R/sdAttrs.R index 5a607649..f33d5541 100644 --- a/R/sdAttrs.R +++ b/R/sdAttrs.R @@ -16,8 +16,9 @@ #' or vector (for many \code{region}s, \code{instances} and \code{regions}). #' @param ver character string; specifies the SpatialData version to comply with. #' @param dim scalar integer in 2-4; -#' number of dimensions: 2 = XY, 3 adds Z, 4 adds T (time); -#' when \code{type="image"}, C (channel) will be added (for any \code{dim}). +#' number of dimensions: 2 = XY, 3 adds Z, 4 adds T (time) for image and +#' label; when \code{type="image"}, C (channel) will be added (for any +#' \code{dim}). #' @param nch scalar integer; how many channels should there be? #' (ignored unless \code{type="shape"} or \code{type="point"}, and #' \code{label=FALSE}). @@ -29,7 +30,7 @@ #' \code{SingleCellExperiment}: \code{region}, \code{region/instance_key}. #' #' When missing \code{x}, \code{SpatialDataAttrs} will generate a valid object -#' with default axes (image/label: cyx, point/shape: xy) and transformations +#' with default axes (image: cyx, label:yx, point/shape: xy) and transformations #' (identify) according to the specified type. #' #' @return character string @@ -64,10 +65,11 @@ SpatialDataAttrs <- \(x, type=c("image", "label", "point", "shape"), trans=NULL, ver=NULL, dim=2, nch=3, ...) { + if (!missing(x)) return(.SpatialDataAttrs(x)) stopifnot( - length(dim) == 1, is.numeric(dim), dim %in% seq(2, 4), + length(dim) == 1, is.numeric(dim), + dim %in% seq(2, if(type == "point") 3 else 4), length(nch) == 1, is.numeric(nch), round(nch) == nch, nch > 0) - if (!missing(x)) return(.SpatialDataAttrs(x)) type <- match.arg(type) if(is.null(ver)) ver <- if(type == "point") "0.2" else "0.3" ver <- .val_sd_ver(ver, type) @@ -114,10 +116,10 @@ SpatialDataAttrs <- \(x, type=c("image", "label", "point", "shape"), y <- list(name="y", type="space") x <- list(name="x", type="space") type <- match.arg(type) - type <- if(type %in% c("point", "shape")) "frame" else type switch(type, # xyzt for points/shapes - frame={ + point=, + shape={ ax <- list(x$name, y$name) if (dim > 2) { ax <- c(ax, list(z$name)) diff --git a/R/utils.R b/R/utils.R index 7f518610..d1850267 100644 --- a/R/utils.R +++ b/R/utils.R @@ -175,7 +175,8 @@ } # validate SpatialData version -.val_sd_ver <- \(v, type) { +.val_sd_ver <- \(v, type=c("image", "label", "point", "shape")) { + type <- match.arg(type) ok <- length(v) == 1 && is.character(v) && v %in% sprintf("0.%d", seq_len(if(type == "point") 2 else 3)) diff --git a/man/SpatialDataAttrs.Rd b/man/SpatialDataAttrs.Rd index 6887eaf0..ce41fe96 100644 --- a/man/SpatialDataAttrs.Rd +++ b/man/SpatialDataAttrs.Rd @@ -96,8 +96,9 @@ SpatialDataAttrs( \item{ver}{character string; specifies the SpatialData version to comply with.} \item{dim}{scalar integer in 2-4; -number of dimensions: 2 = XY, 3 adds Z, 4 adds T (time); -when \code{type="image"}, C (channel) will be added (for any \code{dim}).} +number of dimensions: 2 = XY, 3 adds Z, 4 adds T (time) for image and +label; when \code{type="image"}, C (channel) will be added (for any +\code{dim}).} \item{nch}{scalar integer; how many channels should there be? (ignored unless \code{type="shape"} or \code{type="point"}, and @@ -122,7 +123,7 @@ When \code{x} is a spatial element, the following applies: \code{SingleCellExperiment}: \code{region}, \code{region/instance_key}. When missing \code{x}, \code{SpatialDataAttrs} will generate a valid object -with default axes (image/label: cyx, point/shape: xy) and transformations +with default axes (image: cyx, label:yx, point/shape: xy) and transformations (identify) according to the specified type. } \examples{ diff --git a/tests/testthat/test-sdattrs.R b/tests/testthat/test-sdattrs.R index e3b89a45..032a2a37 100644 --- a/tests/testthat/test-sdattrs.R +++ b/tests/testthat/test-sdattrs.R @@ -84,6 +84,7 @@ test_that("SpatialDataAttrs()", { expect_error(SpatialDataAttrs(dim=7)) expect_error(SpatialDataAttrs(ver="0.0")) expect_error(SpatialDataAttrs(type="bad")) + expect_error(SpatialDataAttrs(type = "point", dim=4)) # 2-4D image nms <- c("c", "t", "z", "y", "x") for (d in seq(2, 4)) { @@ -115,8 +116,8 @@ test_that("SpatialDataAttrs()", { } # 3-4D shape/point for (d in seq(2, 3)) { - for(t in c("shape", "point")){ - x <- SpatialDataAttrs(type=t, dim=d) + for(typ in c("shape", "point")){ + x <- SpatialDataAttrs(type=typ, dim=d) y <- axes(x) expect_length(y, d) xy <- c("x", "y") From ff673c70092aeb2e3bf28c801343c3674570e151 Mon Sep 17 00:00:00 2001 From: Artur-man Date: Fri, 4 Sep 2026 11:07:45 +0200 Subject: [PATCH 4/8] more checks --- R/sdAttrs.R | 4 ++-- R/utils.R | 4 ++-- tests/testthat/test-sdattrs.R | 30 +++++++++++++++--------------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/R/sdAttrs.R b/R/sdAttrs.R index f33d5541..75aa565c 100644 --- a/R/sdAttrs.R +++ b/R/sdAttrs.R @@ -66,11 +66,11 @@ SpatialDataAttrs <- \(x, type=c("image", "label", "point", "shape"), trans=NULL, ver=NULL, dim=2, nch=3, ...) { if (!missing(x)) return(.SpatialDataAttrs(x)) + type <- match.arg(type) stopifnot( length(dim) == 1, is.numeric(dim), - dim %in% seq(2, if(type == "point") 3 else 4), + dim %in% seq(2, if(type %in% c("point", "shape")) 3 else 4), length(nch) == 1, is.numeric(nch), round(nch) == nch, nch > 0) - type <- match.arg(type) if(is.null(ver)) ver <- if(type == "point") "0.2" else "0.3" ver <- .val_sd_ver(ver, type) ax <- .default_ax(type, dim) diff --git a/R/utils.R b/R/utils.R index d1850267..e234c338 100644 --- a/R/utils.R +++ b/R/utils.R @@ -175,8 +175,8 @@ } # validate SpatialData version -.val_sd_ver <- \(v, type=c("image", "label", "point", "shape")) { - type <- match.arg(type) +.val_sd_ver <- \(v, type) { + type <- match.arg(type, c("image", "label", "point", "shape")) ok <- length(v) == 1 && is.character(v) && v %in% sprintf("0.%d", seq_len(if(type == "point") 2 else 3)) diff --git a/tests/testthat/test-sdattrs.R b/tests/testthat/test-sdattrs.R index 032a2a37..04ada484 100644 --- a/tests/testthat/test-sdattrs.R +++ b/tests/testthat/test-sdattrs.R @@ -62,21 +62,20 @@ test_that(".val_ome_ver()", { expect_identical(x, v) }) test_that(".val_sd_ver()", { - # invalid - expect_error(.val_sd_ver(1)) - expect_error(.val_sd_ver(TRUE)) - expect_error(.val_sd_ver("0.0")) - expect_error(.val_sd_ver("0.30")) - expect_error(.val_sd_ver(c("0.3", "0.4"))) - expect_error(.val_sd_ver(v <- "0.3-x")) - expect_error(.val_sd_ver("0.3", "point")) - # valid - expect_silent(x <- .val_sd_ver(v <- "0.3", "image")) - expect_silent(x <- .val_sd_ver(v <- "0.3", "label")) - expect_silent(x <- .val_sd_ver(v <- "0.3", "shape")) - expect_type(x, "character") - expect_length(x, 1) - expect_identical(x, v) + # invalid + expect_error(.val_sd_ver(1)) + expect_error(.val_sd_ver(TRUE)) + expect_error(.val_sd_ver("0.0")) + expect_error(.val_sd_ver("0.30")) + expect_error(.val_sd_ver(c("0.3", "0.4"))) + expect_error(.val_sd_ver("0.3", "point")) + # valid + expect_silent(x <- .val_sd_ver(v <- "0.3", "image")) + expect_silent(x <- .val_sd_ver(v <- "0.3", "label")) + expect_silent(x <- .val_sd_ver(v <- "0.3", "shape")) + expect_type(x, "character") + expect_length(x, 1) + expect_identical(x, v) }) test_that("SpatialDataAttrs()", { # invalid @@ -85,6 +84,7 @@ test_that("SpatialDataAttrs()", { expect_error(SpatialDataAttrs(ver="0.0")) expect_error(SpatialDataAttrs(type="bad")) expect_error(SpatialDataAttrs(type = "point", dim=4)) + expect_error(SpatialDataAttrs(type = "shape", dim=4)) # 2-4D image nms <- c("c", "t", "z", "y", "x") for (d in seq(2, 4)) { From 6191a1557c5a0a1e833e4a59af56adc099e50bd4 Mon Sep 17 00:00:00 2001 From: Artur-man Date: Sat, 5 Sep 2026 09:36:46 +0200 Subject: [PATCH 5/8] fix axes ct naming issues --- R/CTutils.R | 7 ++++++- R/sdAttrs.R | 19 +++++++++++++------ tests/testthat/test-ctutils.R | 12 ++++++++++++ tests/testthat/test-sdattrs.R | 31 ++++++++++++++++++++++--------- 4 files changed, 53 insertions(+), 16 deletions(-) diff --git a/R/CTutils.R b/R/CTutils.R index a185485f..6b55d555 100644 --- a/R/CTutils.R +++ b/R/CTutils.R @@ -59,7 +59,12 @@ setMethod("axes", "SpatialDataAttrs", \(x, y=NULL, ...) { if (is.null(x)) stop("couldn't find 'axes'") if (is.null(y)) return(x) y <- match.arg(y, c("name", "type", "unit")) - vapply(x, `[[`, character(1), y) + # shape/point axes have no type and unit, return axes names as given + if(y == "name" && is.null(names(x[[1]]))){ + unlist(x) + } else { + vapply(x, `[[`, character(1), y) + } }) # CTlist/data/type/name() ---- diff --git a/R/sdAttrs.R b/R/sdAttrs.R index 75aa565c..b356c814 100644 --- a/R/sdAttrs.R +++ b/R/sdAttrs.R @@ -102,7 +102,10 @@ SpatialDataAttrs <- \(x, type=c("image", "label", "point", "shape"), if (ver == "0.3") res <- list(ome=res) } else { # points/shapes - res <- list(axes=ax, coordinateTransformations=ct) + res <- list( + axes=.ax_names(ax), # point and shape take only names + coordinateTransformations=ct + ) } res$spatialdata_attrs <- list(version=ver) SpatialDataAttrs(res) @@ -120,9 +123,9 @@ SpatialDataAttrs <- \(x, type=c("image", "label", "point", "shape"), # xyzt for points/shapes point=, shape={ - ax <- list(x$name, y$name) + ax <- list(x, y) if (dim > 2) { - ax <- c(ax, list(z$name)) + ax <- c(ax, list(z)) } }, # tczyx for images/labels @@ -143,13 +146,17 @@ SpatialDataAttrs <- \(x, type=c("image", "label", "point", "shape"), if (is.character(ax[[1]])) { unlist(ax) } else { - vapply(ax, \(.) .$name, character(1)) + lapply(ax, \(.) .$name) } } # Internal helper to generate coordinate transformations .default_ct <- \(axes, name="global", type="identity", data=NULL) { - ct <- list(input=axes, output=list(name=name), type=type) + ct <- list(input=list(axes=axes, + name=paste(.ax_names(axes), collapse = "")), + output=list(axes=axes, + name=name), + type=type) if (!is.null(data)) ct[[type]] <- data list(ct) } @@ -163,7 +170,7 @@ SpatialDataAttrs <- \(x, type=c("image", "label", "point", "shape"), coordinateTransformations = list( list( scale = lapply( - axes, + unlist(axes), \(.) if(. == "c") 1 else s), type = "scale" ) diff --git a/tests/testthat/test-ctutils.R b/tests/testthat/test-ctutils.R index 239817d1..eed38d95 100644 --- a/tests/testthat/test-ctutils.R +++ b/tests/testthat/test-ctutils.R @@ -21,6 +21,18 @@ test_that("axes", { expect_length(z, d) expect_in(z, c("time","channel","space")) } + es <- list(shape(x), point(x)) + for (e in es) { + z <- axes(e) + d <- length(dim(e)) + expect_type(z, "list") + expect_length(z, d) + expect_error(axes(e, "bad")) + # name + # TODO: should "name" only return itself for frames, or return error + expect_silent(z <- axes(e, "name")) + expect_type(z, "character") + } }) .CTtype <- c( diff --git a/tests/testthat/test-sdattrs.R b/tests/testthat/test-sdattrs.R index 04ada484..3e75e086 100644 --- a/tests/testthat/test-sdattrs.R +++ b/tests/testthat/test-sdattrs.R @@ -63,16 +63,18 @@ test_that(".val_ome_ver()", { }) test_that(".val_sd_ver()", { # invalid - expect_error(.val_sd_ver(1)) - expect_error(.val_sd_ver(TRUE)) - expect_error(.val_sd_ver("0.0")) - expect_error(.val_sd_ver("0.30")) - expect_error(.val_sd_ver(c("0.3", "0.4"))) + expect_error(.val_sd_ver(0.3), 'argument "type" is missing') + expect_error(.val_sd_ver(1, "image")) + expect_error(.val_sd_ver(TRUE, "image")) + expect_error(.val_sd_ver("0.0", "image")) + expect_error(.val_sd_ver("0.30", "image")) + expect_error(.val_sd_ver(c("0.3", "0.4"), "image")) expect_error(.val_sd_ver("0.3", "point")) # valid expect_silent(x <- .val_sd_ver(v <- "0.3", "image")) expect_silent(x <- .val_sd_ver(v <- "0.3", "label")) expect_silent(x <- .val_sd_ver(v <- "0.3", "shape")) + expect_silent(x <- .val_sd_ver(v <- "0.2", "point")) expect_type(x, "character") expect_length(x, 1) expect_identical(x, v) @@ -105,6 +107,8 @@ test_that("SpatialDataAttrs()", { expect_length(y, 7) expect_type(y, "character") expect_all_true(!duplicated(y)) + # version + expect_equal(x$spatialdata_attrs$version, "0.3") } # 2-4D label for (d in seq(2, 4)) { @@ -113,19 +117,28 @@ test_that("SpatialDataAttrs()", { expect_length(y, d) expect_equal(sum(y == "time"), ifelse(d == 4, 1, 0)) expect_equal(sum(y == "space"), ifelse(d == 2, 2, 3)) + # version + expect_equal(x$spatialdata_attrs$version, "0.3") } # 3-4D shape/point + nms <- c("x", "y", "z") for (d in seq(2, 3)) { for(typ in c("shape", "point")){ x <- SpatialDataAttrs(type=typ, dim=d) + ok <- if (d == 2) nms[-3] else nms y <- axes(x) expect_length(y, d) - xy <- c("x", "y") - expect_equal(unlist(y), if(d == 2) xy else c(xy, "z")) + expect_equal(unlist(y), ok) expect_null(channels(x)) - # TODO: should we return x itself, regardless of requested name? - expect_error(axes(x, "name")) + # axes name + y <- axes(x, "name") + expect_length(y, d) + expect_type(y, "character") + expect_identical(y, ok) expect_error(axes(x, "type")) + # version + expect_equal(x$spatialdata_attrs$version, + if(typ == "point") "0.2" else "0.3") } } }) From 7985b4829cd9d2a9b9f8832a0d16d4d6c6255dd6 Mon Sep 17 00:00:00 2001 From: Artur-man Date: Sat, 5 Sep 2026 11:57:34 +0200 Subject: [PATCH 6/8] small fixes --- R/sdAttrs.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/sdAttrs.R b/R/sdAttrs.R index b356c814..c427cb35 100644 --- a/R/sdAttrs.R +++ b/R/sdAttrs.R @@ -146,7 +146,7 @@ SpatialDataAttrs <- \(x, type=c("image", "label", "point", "shape"), if (is.character(ax[[1]])) { unlist(ax) } else { - lapply(ax, \(.) .$name) + vapply(ax, \(.) .$name, character(1)) } } @@ -170,7 +170,7 @@ SpatialDataAttrs <- \(x, type=c("image", "label", "point", "shape"), coordinateTransformations = list( list( scale = lapply( - unlist(axes), + axes, \(.) if(. == "c") 1 else s), type = "scale" ) From 98e863ee7b66b74d3bdba69d1c0492b28d0485e3 Mon Sep 17 00:00:00 2001 From: Artur-man Date: Sat, 5 Sep 2026 18:06:47 +0200 Subject: [PATCH 7/8] more fixes --- R/sdAttrs.R | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/R/sdAttrs.R b/R/sdAttrs.R index c427cb35..66d55eb8 100644 --- a/R/sdAttrs.R +++ b/R/sdAttrs.R @@ -20,9 +20,8 @@ #' label; when \code{type="image"}, C (channel) will be added (for any #' \code{dim}). #' @param nch scalar integer; how many channels should there be? -#' (ignored unless \code{type="shape"} or \code{type="point"}, and -#' \code{label=FALSE}). -#' @param ... additional attributes (e.g., version, feature_key). +#' (ignored if \code{type="label"}, \code{type="shape"}, or +#' \code{type="point"}). #' #' @details #' When \code{x} is a spatial element, the following applies: @@ -30,10 +29,10 @@ #' \code{SingleCellExperiment}: \code{region}, \code{region/instance_key}. #' #' When missing \code{x}, \code{SpatialDataAttrs} will generate a valid object -#' with default axes (image: cyx, label:yx, point/shape: xy) and transformations -#' (identify) according to the specified type. +#' with default axes (image: cyx, label: yx, point/shape: xy) and +#' transformations (identify) according to the specified type. #' -#' @return character string +#' @return SpatialDataAttrs object #' #' @examples #' x <- file.path("extdata", "blobs.zarr") @@ -63,7 +62,7 @@ #' #' @export SpatialDataAttrs <- \(x, type=c("image", "label", "point", "shape"), - trans=NULL, ver=NULL, dim=2, nch=3, ...) + trans=NULL, ver=NULL, dim=2, nch=3) { if (!missing(x)) return(.SpatialDataAttrs(x)) type <- match.arg(type) @@ -76,10 +75,10 @@ SpatialDataAttrs <- \(x, type=c("image", "label", "point", "shape"), ax <- .default_ax(type, dim) # transformations: ct <- trans %||% .default_ct(ax) - # datasets: - ds <- .default_ds(.ax_names(ax)) - # .zattrs list: + # zarr attributes list: if (!type %in% c("point", "shape")) { + # datasets: + ds <- .default_ds(.ax_names(ax)) # default structure res <- list() if(type != "label") @@ -118,8 +117,7 @@ SpatialDataAttrs <- \(x, type=c("image", "label", "point", "shape"), z <- list(name="z", type="space") y <- list(name="y", type="space") x <- list(name="x", type="space") - type <- match.arg(type) - switch(type, + switch(match.arg(type), # xyzt for points/shapes point=, shape={ From e424e04037525cd0cc430ccfc1e47e347753b2e5 Mon Sep 17 00:00:00 2001 From: Artur-man Date: Sat, 5 Sep 2026 19:47:38 +0200 Subject: [PATCH 8/8] fix some documentation --- R/sdAttrs.R | 3 ++- man/SpatialDataAttrs.Rd | 18 ++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/R/sdAttrs.R b/R/sdAttrs.R index 66d55eb8..be1ccf68 100644 --- a/R/sdAttrs.R +++ b/R/sdAttrs.R @@ -14,7 +14,8 @@ #' @param trans list of coordinate transformations; defaults to identity only. #' @param value character string (for one \code{region} and \code{_key}s), #' or vector (for many \code{region}s, \code{instances} and \code{regions}). -#' @param ver character string; specifies the SpatialData version to comply with. +#' @param ver character string; specifies the version of the SpatialData +#' element to comply with. #' @param dim scalar integer in 2-4; #' number of dimensions: 2 = XY, 3 adds Z, 4 adds T (time) for image and #' label; when \code{type="image"}, C (channel) will be added (for any diff --git a/man/SpatialDataAttrs.Rd b/man/SpatialDataAttrs.Rd index ce41fe96..d15032c5 100644 --- a/man/SpatialDataAttrs.Rd +++ b/man/SpatialDataAttrs.Rd @@ -42,8 +42,7 @@ SpatialDataAttrs( trans = NULL, ver = NULL, dim = 2, - nch = 3, - ... + nch = 3 ) \S4method{$}{SpatialDataAttrs}(x, name) @@ -93,7 +92,8 @@ SpatialDataAttrs( \item{trans}{list of coordinate transformations; defaults to identity only.} -\item{ver}{character string; specifies the SpatialData version to comply with.} +\item{ver}{character string; specifies the version of the SpatialData +element to comply with.} \item{dim}{scalar integer in 2-4; number of dimensions: 2 = XY, 3 adds Z, 4 adds T (time) for image and @@ -101,10 +101,8 @@ label; when \code{type="image"}, C (channel) will be added (for any \code{dim}).} \item{nch}{scalar integer; how many channels should there be? -(ignored unless \code{type="shape"} or \code{type="point"}, and -\code{label=FALSE}).} - -\item{...}{additional attributes (e.g., version, feature_key).} +(ignored if \code{type="label"}, \code{type="shape"}, or +\code{type="point"}).} \item{name}{character string for extraction (see ?base::`$`).} @@ -112,7 +110,7 @@ label; when \code{type="image"}, C (channel) will be added (for any or vector (for many \code{region}s, \code{instances} and \code{regions}).} } \value{ -character string +SpatialDataAttrs object } \description{ The `SpatialDataAttrs` class @@ -123,8 +121,8 @@ When \code{x} is a spatial element, the following applies: \code{SingleCellExperiment}: \code{region}, \code{region/instance_key}. When missing \code{x}, \code{SpatialDataAttrs} will generate a valid object -with default axes (image: cyx, label:yx, point/shape: xy) and transformations -(identify) according to the specified type. +with default axes (image: cyx, label: yx, point/shape: xy) and +transformations (identify) according to the specified type. } \examples{ x <- file.path("extdata", "blobs.zarr")