satijalab / seurat

R toolkit for single cell genomics
http://www.satijalab.org/seurat
Other
2.29k stars 915 forks source link

convex hull support for `Crop` on image-based spatial data #8457

Open alikhuseynov opened 9 months ago

alikhuseynov commented 9 months ago

Hi there, Partially related to this #3552 I'm wondering someone is already working on supporting convex hull like for cropping. Currently it's only possible to crop a box given x and y coordinates range. I implemented a small function for custom crop to remove any tissue area using sf package. It would be a good idea to integrate such support within SeuratObject::Crop @mojaveazure any suggestions? I could try to PR that. Thanks

mojaveazure commented 9 months ago

Hi Alik,

This sounds like you'd be interested in SeuratObject::Overlay()

Crop() was designed for simple x/y subsets, whereas Overlay() takes an FOV object x and subsets it to the region defined in another spatial object y (or to the region outside of y if invert = TRUE). Currently, Overlay() works with classes defined by sp or other FOV objects, but uses sf to perform the actual overlay (we don't use sf objects directly as sf is an incredibly heavy package due to its sysreqs)

Can you give Overlay() a whirl and see if that works for you? I'd also be happy to discuss any potential changes/improvements to Overlay() that you can think of

alikhuseynov commented 9 months ago

Hi Paul, Thanks for that. I will try out, so the spatial obj y can be just sp object representing the convex hull/polygon to overlay with? I think letting user to provide selected xy tissue coordinates (which makes the polygon) to crop out or preserve the desired region would be awesome. Attached is the example done with my convex hull crop function

crop_eg 001

example run ``` # crop to cut out selected "good" region xy_pts <- list("fov_test_1" = data.frame("x" = c(5400, 11000, 5400, 6100, 7500, 11000), "y" = c(0, 0, 1500, 2000, 4100, 4100)), "fov_test_2" = data.frame("x" = c(3700, 9000, 9000, 9000, 2000, 2300), "y" = c(1000, 1000, 9000, 4000, 5000, 3000)) ) # obj.xen_list # list of multiple Seurat objects # run per object, only 1, and 2 object samples_seq <- seq(2) start_t <- Sys.time() objs <- lapply(samples_seq, function(i) { Crop_custom(object = obj.xen_list[[i]], col_id = c("cell"), xy_pts = xy_pts[[i]], c_hull_include = TRUE, crop_molecules = TRUE, BPPARAM = BiocParallel::MulticoreParam(5, tasks = 10L, force.GC = FALSE, progressbar = TRUE)) }) end_t <- Sys.time() end_t - start_t # cropped objects objs ```
mojaveazure commented 9 months ago

Hi Alik,

Yep; currently, we've implemented Overlay() where x is an FOV (v4 spatial, no support for v3 spatial objects) object and y is an sp::SpatialPolygons object (cf. sp's constructor function). A new method to Overlay() where y is a data.frame or matrix with coordinates to handle the automatic coercion to sp::SpatialPolygons would be nice and something we'd happily take a PR for

If Overlay() doesn't cover the functionality you need, we can talk more about integrating Crop_custom() into SeuratObject. As it stands:

alikhuseynov commented 9 months ago

Sounds good, thanks for the tips! I think implementing a part to Overlay() where y is a data.frame to make a SpatialPolygons polygon would be very useful. I can play around and see how it works.

mojaveazure commented 8 months ago

I think implementing a part to Overlay() where y is a data.frame to make a SpatialPolygons polygon would be very useful

This could (and should) be done with a new S4 method for Overlay() à la

setMethod(
  f = 'Overlay',
  signature = c(x = 'ANY', y = 'data.frame'),
  definition = function(x, y, invert = FALSE, ...) {
    # validate `y` to match requirements of `sp::SpatialPolygons()`
    # note: class checking with `inherits()` is not needed as `y` is 
    # guaranteed to be a `data.frame` or a derivative of one (eg. `tibble`)
    stopifnot(.ValidCoords(y)) # or however the validation checks are structured
    # convert `y` to `SpatialPolygons using `sp::SpatialPolygons()`
    y <- sp::SpatialPolygons(y) # or however the final call is structured
    # re-trigger method dispatch for `Overlay()` to select a method where `y` is a `SpatialPolygons`
    return(Overlay(x = x, y = y, invert = invert, ...))
  }
)

I don't currently have the bandwidth to build this out, but would happily take PR that implements this

alikhuseynov commented 8 months ago

sounds good, I will take a look.