Skip to content

Commit dfce8f8

Browse files
authored
synch shapes when replacing table(s) [#183]
2 parents 2c533ac + e5671e6 commit dfce8f8

10 files changed

Lines changed: 103 additions & 31 deletions

File tree

NAMESPACE

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ export(readTable)
2828
export(select)
2929
exportClasses(SpatialData)
3030
exportMethods("$")
31+
exportMethods("$<-")
3132
exportMethods("[")
3233
exportMethods("[[")
3334
exportMethods("[[<-")
35+
exportMethods("element<-")
3436
exportMethods("feature_key<-")
3537
exportMethods("image<-")
3638
exportMethods("imageNames<-")
@@ -111,7 +113,6 @@ exportMethods(tables)
111113
exportMethods(transform)
112114
exportMethods(translation)
113115
import(geoarrow)
114-
importClassesFrom(S4Arrays,Array)
115116
importClassesFrom(S4Vectors,DFrame)
116117
importFrom(BiocGenerics,as.data.frame)
117118
importFrom(BiocGenerics,colnames)

R/AllClasses.R

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,6 @@
22
Class="SpatialDataAttrs",
33
contains="list")
44

5-
#' @importFrom methods setClassUnion
6-
#' @importClassesFrom S4Arrays Array
7-
setClassUnion(
8-
"array_OR_df",
9-
c("Array", "array", "data.frame"))
10-
115
.SpatialDataImage <- setClass(
126
Class="SpatialDataImage",
137
contains=c("Annotated"),

R/AllGenerics.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ setGeneric("meta<-", \(x, ..., value) standardGeneric("meta<-"))
9494

9595
setGeneric("layer", \(x, i, ...) standardGeneric("layer"))
9696
setGeneric("element", \(x, i, ...) standardGeneric("element"))
97+
setGeneric("element<-", \(x, i, value) standardGeneric("element<-"))
9798
setGeneric("elements", \(x, i, ...) standardGeneric("elements"))
9899

99100
setGeneric("query", \(x, ...) standardGeneric("query"))

R/combine.R

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,21 @@ setMethod("combine", c("SpatialData", "SpatialData"), \(x, y, ...) {
2828
idx <- rep.int(c(1, 2), vapply(old, length, integer(1)))
2929
new <- split(make.unique(unlist(old)), idx)
3030
for (i in c(1, 2)) {
31+
# get input element names
3132
z <- get(c("x", "y")[i])
32-
layer_nms <- setdiff(rownames(z), "tables")
33-
old_nms <- unlist(colnames(z)[layer_nms])
33+
old_nms <- unlist(colnames(z)[.ls])
34+
3435
# find new names for these elements
3536
j <- match(old_nms, old[[i]])
3637
new_nms <- new[[i]][j]
3738

3839
# rename elements
39-
for (l in layer_nms) {
40+
for (l in .ls) {
4041
j <- match(names(z[[l]]), old[[i]])
4142
names(z[[l]]) <- new[[i]][j]
4243
}
4344
# sync tables
44-
z <- .sync_tables(z, old_nms, new_nms)
45+
z <- .sync_tables_sdattrs(z, old_nms, new_nms)
4546

4647
# rename tables themselves
4748
j <- match(tableNames(z), old[[i]])

R/crop.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ setMethod("crop", "SpatialDataFrame", \(x, y, j=1, ...) {
223223
fd <- st_sf(geometry=st_as_sfc(st_bbox(unlist(y))))
224224
}
225225
df <- data(transform(x, j))
226+
fd <- data(SpatialDataShape(fd))
226227
ok <- ddbs_intersects(df, fd, sparse=TRUE)
227228
id_x <- NULL # R CMD check
228229
x[pull(ok, id_x), ]

R/methods.R

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
#' @rdname SpatialData
77
setMethod("$", "SpatialData", \(x, name) attr(x, name))
88

9+
#' @exportMethod $<-
10+
#' @rdname SpatialData
11+
setReplaceMethod("$", "SpatialData", \(x, name, value) `[[<-`(x, i=name, value=value))
12+
913
#' @export
1014
#' @rdname SpatialData
1115
#' @importFrom methods callNextMethod
@@ -16,9 +20,7 @@ setMethod("[[", c("SpatialData", "numeric"), \(x, i, ...) {
1620

1721
#' @rdname SpatialData
1822
#' @export
19-
setMethod("[[", c("SpatialData", "character"), \(x, i, ...) {
20-
attr(x, grep(i, names(attributes(x)), value=TRUE))
21-
})
23+
setMethod("[[", c("SpatialData", "character"), \(x, i, ...) attr(x, i))
2224

2325
# data/meta ----
2426

@@ -142,6 +144,12 @@ setMethod("element", c("SpatialData", "missing"), \(x, i) element(x, 1))
142144
setMethod("element", c("SpatialData", "ANY"), \(x, i)
143145
stop("invalid 'i'; should be a string specifying an element in 'x'"))
144146

147+
#' @rdname SpatialData
148+
#' @export
149+
setReplaceMethod("element",
150+
c("SpatialData", "character"),
151+
\(x, i, value) { x[[layer(x, i)]][[i]] <- value; x })
152+
145153
# get all ----
146154

147155
#' @export
@@ -173,7 +181,9 @@ all <- paste0(one, "s")
173181
#' @exportMethod imageNames labelNames pointNames shapeNames tableNames
174182
NULL
175183

176-
f <- \(.) setMethod(paste0(., "Names"), "SpatialData", \(x) names(x[[.]]))
184+
f <- \(.) setMethod(
185+
paste0(., "Names"), "SpatialData",
186+
\(x) names(x[[paste0(., "s")]]))
177187
for (. in one) eval(f(.), parent.env(environment()))
178188

179189
# set nms ----
@@ -190,7 +200,7 @@ f <- \(.) setReplaceMethod(
190200
old <- names(x[[paste0(., "s")]])
191201
new <- names(x[[paste0(., "s")]]) <- value
192202
if (. == "table") return(x)
193-
.sync_tables(x, old, new)
203+
.sync_tables_sdattrs(x, old, new)
194204
})
195205
for (. in one) eval(f(.), parent.env(environment()))
196206

@@ -226,7 +236,7 @@ for (. in one) eval(f(.), parent.env(environment()))
226236
#' @importFrom methods setReplaceMethod
227237
#' @export
228238
setReplaceMethod("[[", c("SpatialData", "numeric"),
229-
\(x, i, value) { attr(x, .LAYERS[i]) <- value; return(x) })
239+
\(x, i, value) { x[[.LAYERS[i]]] <- value; x })
230240

231241
#' @rdname SpatialData
232242
#' @export
@@ -237,12 +247,17 @@ setReplaceMethod("[[", c("SpatialData", "character"),
237247
old <- names(attr(x, l))
238248
new <- names(value)
239249
if (length(old) == length(new) && any(old != new))
240-
x <- .sync_tables(x, old, new)
250+
x <- .sync_tables_sdattrs(x, old, new)
241251
}
242252
attr(x, l) <- value
243-
if (l != "tables")
253+
if (l != "tables") {
244254
x <- .sync_tables_on_drop(x)
245-
return(x)
255+
} else {
256+
for (t in tableNames(x)) {
257+
x <- .sync_shapes_on_drop(x, t)
258+
}
259+
}
260+
x
246261
})
247262

248263
# |_value=list ----
@@ -258,7 +273,7 @@ f <- \(.) setReplaceMethod(.,
258273
old <- names(attr(x, .))
259274
new <- names(value)
260275
if (length(old) == length(new) && any(old != new))
261-
x <- .sync_tables(x, old, new)
276+
x <- .sync_tables_sdattrs(x, old, new)
262277
}
263278
attr(x, .) <- value
264279
if (. != "tables")
@@ -286,14 +301,11 @@ f <- \(.) setReplaceMethod(.,
286301
y <- attr(x, paste0(., "s"))
287302
y[[i]] <- value
288303
attr(x, paste0(., "s")) <- y
304+
if (. == "table") x <- .sync_shapes_on_drop(x, i)
289305
return(x)
290306
})
291307
for (. in one) eval(f(.), parent.env(environment()))
292308

293-
# TODO: something like table(x)$cluster_id <- doesn't work atm...
294-
# not sure how to get around without defining all the possible
295-
# SCE replacement methods :/
296-
297309
# _i=numeric ----
298310

299311
#' @name SpatialData

R/utils.R

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
return(x)
2020
}
2121

22-
.sync_tables <- \(x, old, new) {
22+
.sync_tables_sdattrs <- \(x, old, new) {
2323
if (!length(ts <- tables(x))) return(x)
2424
for (i in seq_along(ts)) {
2525
t <- ts[[i]]
@@ -44,6 +44,23 @@
4444
return(x)
4545
}
4646

47+
.sync_shapes_on_drop <- \(x, i) {
48+
# skip when there aren't any shapes
49+
if (!length(shapes(x))) return(x)
50+
t <- SpatialData::table(x, i)
51+
for (j in region(t)) {
52+
# skip non-shape elements
53+
if (layer(x, j) != "shapes") next
54+
# get element 'y' annotated by table 't'
55+
y <- element(x, j)
56+
# match instances between them
57+
y <- y[match(instances(t), instances(y), nomatch=0)]
58+
# return matching shape instances
59+
shape(x, j) <- y
60+
}
61+
return(x)
62+
}
63+
4764
.sync_tables_on_drop <- \(x) {
4865
if (!length(ts <- tables(x))) return(x)
4966
all_nms <- unlist(colnames(x)[.ls])

man/SpatialData.Rd

Lines changed: 9 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-methods.R

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ test_that("element()", {
4141
})
4242
})
4343

44+
test_that("element<-()", {
45+
i <- vapply(colnames(x), \(.) .[1], character(1))
46+
for (. in i) {
47+
y <- x; element(y, .) <- element(x, .)
48+
expect_identical(element(y, .), element(x, .))
49+
y <- x; element(y, .) <- NULL
50+
expect_error(element(y, .))
51+
}
52+
})
53+
4454
test_that("get all", {
4555
for (f in paste0(fun, "s"))
4656
expect_is(get(f)(x), "list")

tests/testthat/test-tables.R

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,40 @@ x <- file.path("extdata", "blobs.zarr")
44
x <- system.file(x, package="SpatialData")
55
x <- readSpatialData(x)
66

7-
se <- SpatialData::table(x)
8-
md <- int_metadata(se)
7+
t <- SpatialData::table(x)
8+
md <- int_metadata(t)
99
md <- md$spatialdata_attrs
1010
i <- md[[rk <- md$region_key]]
1111

12+
test_that("table<-", {
13+
# labels aren't affected
14+
y <- x
15+
i <- region(t)
16+
table(y) <- t[, -1]
17+
expect_identical(element(x, i), element(y, i))
18+
# shapes are synchronized
19+
i <- shapeNames(x)[1]
20+
y <- shape(x, i)
21+
m <- 77; n <- length(y)
22+
u <- matrix(m*n, m, n)
23+
u <- SingleCellExperiment(u)
24+
a <- setTable(x, i, u, name="x")
25+
v <- element(a, "x")[-33, -3]
26+
f <- \(a, b) {
27+
a <- element(a, "x")
28+
b <- element(b, "x")
29+
expect_equal(dim(a), c(m,n))
30+
expect_equal(dim(b), c(m-1,n-1))
31+
expect_identical(instances(a), instances(y))
32+
expect_identical(instances(b), instances(y)[-3])
33+
}
34+
b <- a; b$tables$x <- v; f(a, b)
35+
b <- a; table(b, "x") <- v; f(a, b)
36+
b <- a; b$tables <- list(x=v); f(a, b)
37+
b <- a; tables(b) <- list(x=v); f(a, b)
38+
b <- a; table(b, grep("x", tableNames(b))) <- v; f(a, b)
39+
})
40+
1241
test_that("hasTable()", {
1342
# TRUE
1443
i <- region(SpatialData::table(x))

0 commit comments

Comments
 (0)