Skip to content

Commit

Permalink
Allow swapping y and x in st_any_pred
Browse files Browse the repository at this point in the history
  • Loading branch information
lambdamoses committed Nov 13, 2024
1 parent 44e4dc1 commit e03a36d
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 26 deletions.
43 changes: 32 additions & 11 deletions R/geometry_operation.R
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,19 @@
#' @param pred A geometric binary predicate function, such as
#' \code{\link{st_intersects}}. It should return an object of class
#' \code{sgbp}, for sparse predicates.
#' @param yx Whether to do \code{pred(y, x)} instead of \code{pred(x, y)}. For
#' symmetric predicates, the results should be the same. When x has a large
#' number of geometries and y has few, \code{pred(y, x)} is much faster than
#' \code{pred(x, y)} for \code{st_intersects}, \code{st_disjoint}, and
#' \code{st_is_within_distance}.
#' @param sparse If \code{TRUE}, returns numeric indices rather than logical
#' vector. Defaults to \code{FALSE} for backward compatibility, though the
#' default in \code{st_intersects} is \code{TRUE}.
#' @param ... Arguments passed to \code{pred}.
#' @return For \code{st_any_*}, a logical vector indicating whether each
#' geometry in \code{x} intersects (or other predicates such as is covered by)
#' anything in \code{y}. Simplified from the \code{sgbp} results which
#' anything in \code{y} or a numeric vector of indices of \code{TRUE} when
#' \code{sparse = TRUE}. Simplified from the \code{sgbp} results which
#' indicate which item in \code{y} each item in \code{x} intersects, which
#' might not always be relevant. For \code{st_n_*}, an integer vector
#' indicating the number of geometries in y returns TRUE for each geometry in
Expand All @@ -36,18 +46,27 @@
#' st_any_intersects(pts, pol)
#' st_n_pred(pts, pol, pred = st_disjoint)
#' st_n_intersects(pts, pol)
st_any_pred <- function(x, y, pred) lengths(pred(x, y)) > 0L
# TODO: put the item with more geometries in the x position. This way is much faster.
# If swapping positions, it shouldn't be hard to recover it if the pred is symmetric
st_any_pred <- function(x, y, pred, yx = FALSE, sparse = FALSE, ...) {
if (length(x) > length(y)*10 && yx) {
res <- unlist(unclass(pred(y, x, ...)))
res <- sort(unique(res))
if (!sparse) res <- which(seq_along(x) %in% res)
} else {
res <- lengths(pred(x, y, ...)) > 0L
if (sparse) res <- which(res)
}
res
}

#' @rdname st_any_pred
#' @export
st_any_intersects <- function(x, y) st_any_pred(x, y, st_intersects)
st_any_intersects <- function(x, y, yx = FALSE, sparse = FALSE)
st_any_pred(x, y, st_intersects, yx = yx, sparse = sparse)

#' @rdname st_any_pred
#' @export
st_n_pred <- function(x, y, pred) lengths(pred(x, y))

st_n_pred <- function(x, y, pred, ...) lengths(pred(x, y, ...))
# Not doing yx here since this is usually not used when length(y) << length(x)
#' @rdname st_any_pred
#' @export
st_n_intersects <- function(x, y) st_n_pred(x, y, st_intersects)
Expand Down Expand Up @@ -145,7 +164,8 @@ st_n_intersects <- function(x, y) st_n_pred(x, y, st_intersects)
#' (i.e. \code{colGeometry}) and an annotation geometry for each sample. For
#' example, whether each Visium spot intersects with the tissue boundary in each
#' sample.
#'
#'
#' @inheritParams st_any_pred
#' @param sfe An SFE object.
#' @param colGeometryName Name of column geometry for the predicate.
#' @param annotGeometryName Name of annotation geometry for the predicate.
Expand All @@ -171,12 +191,13 @@ st_n_intersects <- function(x, y) st_n_pred(x, y, st_intersects)
#' # How many nuclei are there in each Visium spot
#' n_nuclei <- annotNPred(sfe, "spotPoly", annotGeometryName = "nuclei")
annotPred <- function(sfe, colGeometryName = 1L, annotGeometryName = 1L,
sample_id = "all", pred = st_intersects) {
sample_id = "all", pred = st_intersects, yx = FALSE) {
sample_id <- .check_sample_id(sfe, sample_id, one = FALSE)
ag <- annotGeometry(sfe, type = annotGeometryName, sample_id = sample_id)
.annot_fun(sfe, ag,
colGeometryName = colGeometryName,
samples_use = sample_id, fun = st_any_pred, pred = pred
samples_use = sample_id, fun = st_any_pred, pred = pred,
yx = yx, sparse = FALSE
)
}

Expand Down Expand Up @@ -425,7 +446,7 @@ crop <- function(x, y = NULL, colGeometryName = 1L, sample_id = "all",
}
preds <- .annot_fun(x, y, colGeometryName,
samples_use = samples_use,
fun = st_any_pred, pred = pred
fun = st_any_pred, pred = pred, yx = TRUE
)
# Don't remove anything from other samples
other_bcs <- setdiff(colnames(x), names(preds))
Expand Down
9 changes: 8 additions & 1 deletion man/annotPred.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 17 additions & 4 deletions man/st_any_pred.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 18 additions & 10 deletions tests/testthat/test-geometry_operation.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,16 @@ pts = st_sfc(st_point(c(.5,.5)), st_point(c(1.5, 1.5)), st_point(c(2.5, 2.5)))
pol = st_polygon(list(rbind(c(0,0), c(2,0), c(2,2), c(0,2), c(0,0))))

test_that("trivial, st_any_intersects", {
o <- st_any_intersects(pts, pol)
o <- st_any_intersects(pts, pol, sparse = FALSE)
expect_equal(o, c(TRUE, TRUE, FALSE))
o <- st_any_intersects(pts, pol, sparse = TRUE)
expect_equal(o, c(1L, 2L))
})

test_that("Swap x and y for st_intersects", {
o1 <- st_any_intersects(pts, pol, sparse = TRUE)
o2 <- st_any_intersects(pts, pol, sparse = TRUE, yx = TRUE)
expect_equal(o1, o2)
})

test_that("trivial, st_n_intersects", {
Expand Down Expand Up @@ -48,21 +56,21 @@ rowGeometry(sfe_visium, "points", sample_id = "sample01", withDimnames = FALSE)
test_that("All spots in the cropped SFE objects indeed are covered by the bbox", {
sfe_cropped <- SpatialFeatureExperiment::crop(sfe_visium, bbox_use, sample_id = "all")
cg <- spotPoly(sfe_cropped, "all")
expect_true(all(st_any_pred(cg, bbox_use, pred = st_covered_by)))
expect_true(all(st_any_pred(cg, bbox_use, pred = st_covered_by, sparse = FALSE)))
expect_true(st_geometry_type(cg, by_geometry = FALSE) == "POLYGON")
rg <- rowGeometry(sfe_cropped)
expect_true(all(st_any_pred(rg, bbox_use, pred = st_covered_by)))
expect_true(all(st_any_pred(rg, bbox_use, pred = st_covered_by, sparse = FALSE)))
})

test_that("Only crop one sample out of two, with sf", {
expect_error(SpatialFeatureExperiment::crop(sfe_visium, y = bbox_sf, sample_id = "sample02"),
"No bounding boxes for samples specified.")
sfe_cropped2 <- SpatialFeatureExperiment::crop(sfe_visium, y = bbox_sf, sample_id = "sample01")
expect_true(all(st_any_pred(spotPoly(sfe_cropped2, "sample01"), bbox_use,
pred = st_covered_by
pred = st_covered_by, sparse = FALSE
)))
expect_false(all(st_any_pred(spotPoly(sfe_cropped2, "sample02"), bbox_use,
pred = st_covered_by
pred = st_covered_by, sparse = FALSE
)))
expect_equal(sum(st_any_intersects(
spotPoly(sfe_cropped2, "sample02"),
Expand Down Expand Up @@ -92,10 +100,10 @@ test_that("Using a bounding box to crop SFE objects, current way, one sample", {
"No bounding boxes for samples specified.")
sfe_cropped <- SpatialFeatureExperiment::crop(sfe_visium, y = m)
expect_true(all(st_any_pred(spotPoly(sfe_cropped, "sample01"), bbox_use,
pred = st_covered_by
pred = st_covered_by, sparse = FALSE
)))
expect_false(all(st_any_pred(spotPoly(sfe_cropped, "sample02"), bbox_use,
pred = st_covered_by
pred = st_covered_by, sparse = FALSE
)))
expect_equal(sum(st_any_intersects(
spotPoly(sfe_cropped, "sample02"),
Expand All @@ -111,10 +119,10 @@ test_that("Using a bounding box to crop SFE objects, current way, all samples",
c("sample01", "sample02")))
sfe_cropped <- SpatialFeatureExperiment::crop(sfe_visium, y = m)
expect_true(all(st_any_pred(spotPoly(sfe_cropped, "sample01"), bbox_use,
pred = st_covered_by
pred = st_covered_by, sparse = FALSE
)))
expect_true(all(st_any_pred(spotPoly(sfe_cropped, "sample02"), bbox_use2,
pred = st_covered_by
pred = st_covered_by, sparse = FALSE
)))
})

Expand Down Expand Up @@ -289,7 +297,7 @@ test_that("annotOp", {
rownames(out),
colnames(sfe_visium)[colData(sfe_visium)$sample_id == "sample01"]
)
p <- st_any_pred(out, bbox_use, st_covered_by)
p <- st_any_pred(out, bbox_use, st_covered_by, sparse = FALSE)
expect_true(all(p[c(1, 2, 5)]))
expect_false(any(p[3:4]))
})
Expand Down

0 comments on commit e03a36d

Please sign in to comment.