satijalab / seurat

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

Add a compatibility layer for `VisiumV1` and `VisiumV2` classes #9012

Closed ppm1337 closed 1 week ago

ppm1337 commented 3 weeks ago

Seurat is a widely-adopted package for single-cell and ST analysis and is used by many different 3rd-party packages. Due to the recent change in Seurat version 5.1 to use the new VisiumV2 class by default for any Visium data (also non-Visium HD), it is now impossible to run many 3rd-party packages that make use of image data (e.g., no coordinates slot anymore, different data.frame structure when calling GetTissueCoordinates, ...). This would be less of a hassle if you could easily convert the different image classes or choose the class upon creation of the SeuratObject through Load10X_Spatial. Instead, the use of the new class is hardcoded in Read10X_Image and there is no conversion function (e.g., as.VisiumV1). To my knowledge, the only workaround to this problem is to downgrade the Seurat version and create the SeuratObject from scratch, which is quite cumbersome.

Furthermore, to avoid problems like this in the future (i.e., to not update to a new Seurat version), I would appreciate the labelling of breaking changes as such in the release notes. The ones for 5.1 just stated:

Added VisiumV2 class, inherits SeuratObject::FOV, returned by Load10X_Spatial

without indicating that this is a breaking change and that the user has no option anymore to use VisiumV1 with 5.1.

dcollins15 commented 2 weeks ago

Thanks for reaching out.

tl;dr: there should be a patch coming soon but you can find a workaround below.

In addition to rolling out support for Visium HD, Seurat v5.1.0 took steps towards unifying our framework for both image-based and sequencing-based spatial datasets. The changes between VisiumV1 and VisiumV2 enable the latter to be used with functions like BuildNicheAssay by extending the FOV class.

Whenever possible, we recommend that third-party tools rely on the getter and setter methods provided by Seurat/SeuratObject rather than accessing slots directly. While GetTissueCoordinates returns dataframes with varying colnames, the structure (X, Y positions in the first two columns) should remain consistent across all implementations of SpatialImage.

In the case of hdWGCNA (which I assume is one of the third-party packages in question), instead of relying on the presence of a ”row” and ”col” columns in their object’s metadata here, it might be better to do something like:

coords <- GetTissueCoordinates(cur_seurat)
coords <- coords[, 1:2]
colnames(coords) <- c("row", "col")

so that the ConstructMetaspots function works with any type of SpatialImage currently implemented by Seurat/SeuratObject.

In other words, this change was not intended to be breaking, but I understand the frustration caused by having a minor version change affect your workflow. We place high importance on maintaining backward compatibility and I am sorry for any headache this might have caused.

Your suggestion for a conversion method is well received — we considered it but I initially thought that back-converting the classes would be more straightforward and I didn’t think that an out-of-the-box solution would be needed. We’ll release a patch as soon as practical. In the meantime, you can use the following workaround (ignoring my advice about getters/setters for the sake of a more explicit conversion):

library(Seurat)

path_to_visium <- file.path("/path/to/visium")
# this path may need to be adjusted depending on the dataset
path_to_coordinates <- file.path(path_to_visium, "spatial/tissue_positions_list.csv")

image_name <- "visium"
object <- Load10X_Spatial(path_to_visium, slice = image_name)
visium_v2 <- object[[image_name]]

# re-read the tissue coordinates
coordinates <- Read10X_Coordinates(path_to_coordinates, filter.matrix = TRUE)

visium_v1 <- new(
  Class = "VisiumV1",
  assay = visium_v2@assay,
  key = visium_v2@key,
  coordinates = coordinates,
  scale.factors = visium_v2@scale.factors,
  image = visium_v2@image
)

# `Radius` is now calculated on-the-fly, but we can populate the static slot
# in case it's depended on
visium_v1@spot.radius <- Radius(visium_v1)

# align the order of counts and coordinates
visium_v1 <- visium_v1[Cells(object)]

object[[image_name]] <- visium_v1

We appreciate your feedback and can certainly make an effort to better communicate the nature of any significant changes in future releases 👍

ppm1337 commented 2 weeks ago

Thanks for this very detailed reply. The proposed workaround is highly appreciated. Looking forward to the next patch release!