seurat
seurat copied to clipboard
SpatialPlot: rotate/flip slides
Hi,
this might be a very niche problem, but I have datasets in which I'd like to rotate some samples by 90 degrees to align them better.
I could not figure out how to do that in ggplot, background and dots never properly aligned in my attempts.
It is easy to rotate the @image
in case this is indeed not currently possible with ggplot. Is this something where you would accept a PR?
Thanks again, Markus
Hi Markus,
Thanks for reporting. This is a bug in SpatialPlot
as it should support coord_flip
Hi Mojaveazure,
I wonder if is there's any update on this issue? I tried scale_x/y_reverse() and coord_flip() and the underlying images disappear. Using Seurat 4.0.3 Also I guess these functions are more for mirroring rather than rotating anyway. Do you have any advice on how to achieve clockwise/counter-clockwise rotations with correct image display at the moment?
Thanks, Simon
Same here. I would like to rotate an image, so both of my images have the same orientation and are easier to compare.
Any update by any chance?
Thanks a lot! Katrin
I third this request for easy ways to rotate/flip images so my brain slices are all in the same orientation.
Here is my workaround code, not very clean but it works for me! Step1: identify your uploaded image dimension used for spaceranger (not the ones in the spaceranger output image folder, the resolution is different): X.pixel by Y.pixel Step2: identify how you'd like your image to be rotated or flipped: Right.90.degree, Left.90.degree, V.flip, H.flip, Rotate.180.dgree, or a combination of these options. Step3: Rotate/flip RGB image array stored at: seurat.visium@images$slice1@image and put it back Step4: Rotate/flip tissue-spot index stored at: seurat.visium@images$slice1@coordinates and put it back
Example: V.flip
library(Seurat)
library(dplyr)
seurat.visium <- readRDS("your.path.to.visium.rds")
## step1 c(X, Y), original dimension
img.dim <- c(4500, 5000)
## step2: want to flip image vertically
## step3: flip image array displayed in the output
# get the original image array
ori.array <- seurat.visium@images$slice1@image
# to store new transformed matrix of each color
new.mx <- c()
# transform the image array
for (rgb in 1:3){
each.mx <- ori.array[,,rgb]
each.mx.trans <- each.mx %>%
.[dim(.)[1]:1, ] %>%
t(.)
new.mx <- c(new.mx, list(each.mx.trans))
}
# construct new rgb image array
new.X.dim <- dim(each.mx.trans)[1]
new.Y.dim <- dim(each.mx.trans)[2]
new.array <- array(c(new.mx[[1]],
new.mx[[2]],
new.mx[[3]]),
dim = c(new.X.dim, new.Y.dim, 3))
#swap old image with new image
seurat.visium@images$slice1@image <- new.array
#check if the image display as desired
SpatialDimPlot(seurat.visium, pt.size.factor = 0)
## step4: change thetissue pixel-spot index
img.index <- seurat.visium@images$slice1@coordinates
#swap index
seurat.visium@images$slice1@coordinates$imagerow <- img.dim[2]-img.index$imagerow
#check if the spot match the new image
SpatialDimPlot(seurat.visium, pt.size.factor = 1)
similar logic for other direction
foo <- matrix(1:16, 4)
fooL90 <- t(foo) %>%
.[dim(.)[1]:1, ]
fooR90 <- foo %>%
.[dim(.)[1]:1, ] %>%
t(.)
foo180 <- foo %>%
.[, dim(.)[2]:1] %>%
.[dim(.)[1]:1, ]
fooHf <- foo %>%
.[, dim(.)[2]:1]
fooVf <- foo %>%
.[dim(.)[1]:1, ]
Hi! I'd like to bring this back up. coord_flip() still does not rotate the underlying image which would be a really great option to have!
Here is my workaround code, not very clean but it works for me! ...
Hey @JPingLin , I liked your implementation so I just made a helper function based on it and it seems to be working just fine. Here is my code:
##Load libraries
library(Seurat)
options(Seurat.object.assay.version = "v5")
library(SeuratData)
library(ggplot2)
library(patchwork)
library(dplyr)
options(future.globals.maxSize = 1e+09)
##Make helper functions
rotimat=function(foo,rotation){
if(!is.matrix(foo)){
cat("Input is not a matrix")
return(foo)
}
if(!(rotation %in% c("180","Hf","Vf"))){
cat("Rotation should be either L90, R90, 180, Hf or Vf\n")
return(foo)
}
if(rotation == "180"){
foo <- foo %>%
.[, dim(.)[2]:1] %>%
.[dim(.)[1]:1, ]
}
if(rotation == "Hf"){
foo <- foo %>%
.[, dim(.)[2]:1]
}
if(rotation == "Vf"){
foo <- foo %>%
.[dim(.)[1]:1, ]
}
return(foo)
}
rotateSeuratImage = function(seuratVisumObject, slide = "slice1",rotation="Vf"){
if(!(rotation %in% c("180","Hf","Vf"))){
cat("Rotation should be either 180, Hf or Vf\n")
return(NULL)
}else{
seurat.visium = seuratVisumObject
ori.array = (seurat.visium@images)[[slide]]@image
img.dim = dim(ori.array)[1:2]/(seurat.visium@images)[[slide]]@scale.factors$lowres
new.mx <- c()
# transform the image array
for (rgb in 1:3){
each.mx <- ori.array[,,rgb]
each.mx.trans <- rotimat(each.mx,rotation)
new.mx <- c(new.mx, list(each.mx.trans))
}
# construct new rgb image array
new.X.dim <- dim(each.mx.trans)[1]
new.Y.dim <- dim(each.mx.trans)[2]
new.array <- array(c(new.mx[[1]],
new.mx[[2]],
new.mx[[3]]),
dim = c(new.X.dim, new.Y.dim, 3))
#swap old image with new image
seurat.visium@images[[slide]]@image <- new.array
## step4: change the tissue pixel-spot index
img.index <- (seurat.visium@images)[[slide]]@coordinates
#swap index
if(rotation == "Hf"){
seurat.visium@images[[slide]]@coordinates$imagecol <- img.dim[2]-img.index$imagecol
}
if(rotation == "Vf"){
seurat.visium@images[[slide]]@coordinates$imagerow <- img.dim[1]-img.index$imagerow
}
if(rotation == "180"){
seurat.visium@images[[slide]]@coordinates$imagerow <- img.dim[1]-img.index$imagerow
seurat.visium@images[[slide]]@coordinates$imagecol <- img.dim[2]-img.index$imagecol
}
return(seurat.visium)
}
}
##Test them
##Analysis test
bb=Load10X_Spatial(".",filename = "Visium_FFPE_Mouse_Brain_filtered_feature_bc_matrix.h5")
bb2=rotateSeuratImage(bb) #Vf
bb3=rotateSeuratImage(bb,rotation = "Hf")
bb4=rotateSeuratImage(bb3) ##Applying a Vf to an Hf makes a 180 degrees rotation
bb5=rotateSeuratImage(bb,rotation = "180")
pdf("test_rotation.pdf",12,12)
SpatialDimPlot(bb, pt.size.factor = 1)
SpatialDimPlot(bb2, pt.size.factor = 1)
SpatialDimPlot(bb3, pt.size.factor = 1)
SpatialDimPlot(bb4, pt.size.factor = 1)
SpatialDimPlot(bb5, pt.size.factor = 1)
SpatialFeaturePlot(bb, features = "nCount_Spatial") + theme(legend.position = "right")
SpatialFeaturePlot(bb2, features = "nCount_Spatial") + theme(legend.position = "right")
SpatialFeaturePlot(bb3, features = "nCount_Spatial") + theme(legend.position = "right")
SpatialFeaturePlot(bb4, features = "nCount_Spatial") + theme(legend.position = "right")
SpatialFeaturePlot(bb5, features = "nCount_Spatial") + theme(legend.position = "right")
dev.off()
Please note that the original pixels of the full res image can be obtained by dividing the size of the lowres image by its scaling factor. As for the 90 degrees transposes, I just needed vertical flips on my data but should be feasible to add them to this code.
Cheers and thanks again for your code @JPingLin,
Amhed
Please note that the original pixels of the full res image can be obtained by dividing the size of the lowres image by its scaling factor. As for the 90 degrees transposes, I just needed vertical flips on my data but should be feasible to add them to this code.
Cheers and thanks again for your code @JPingLin,
Amhed
Thank you @AmhedVargas for the kind words & cool wrapper (so much cleaner now)! Very happy that the code is useful; such an encouragement for making baby contributions to the business even with a wet lab background : )
Hi all,
Many thanks to @JPingLin and @AmhedVargas for the workaround codes. This is indeed a bug and we still cannot get the SpatialPlot compatible with + coord_flip()
. But we will be adding a new parameter into SpatialPlot() (SpatialFeaturePlot() and SpatialDimPlot), to allow users to specify a flip_angle
parameter to change the orientation of the plot. This is mostly built upon @AmhedVargas's code above, and we hope to fix it in our next release.
Before that, if users want to flip the orientation, you may follow AmhedVargas's code above (https://github.com/satijalab/seurat/issues/2702#issuecomment-1514447946). We have modified the codes to further allow "R90" and "L90" flip if desired.
# flip_angle %in% c(180, "R90", "L90", "Hf", "Vf")
rotimat=function(foo,rotation){
if(!is.matrix(foo)){
cat("Input is not a matrix")
return(foo)
}
if(!(rotation %in% c("180","Hf","Vf", "R90", "L90"))){
cat("Rotation should be either L90, R90, 180, Hf or Vf\n")
return(foo)
}
if(rotation == "180"){
foo <- foo %>%
.[, dim(.)[2]:1] %>%
.[dim(.)[1]:1, ]
}
if(rotation == "Hf"){
foo <- foo %>%
.[, dim(.)[2]:1]
}
if(rotation == "Vf"){
foo <- foo %>%
.[dim(.)[1]:1, ]
}
if(rotation == "L90"){
foo = t(foo)
foo <- foo %>%
.[dim(.)[1]:1, ]
}
if(rotation == "R90"){
foo = t(foo)
foo <- foo %>%
.[, dim(.)[2]:1]
}
return(foo)
}
rotateSeuratImage = function(seuratVisumObject, slide = "slice1", rotation="Vf"){
if(!(rotation %in% c("180","Hf","Vf", "L90", "R90"))){
cat("Rotation should be either 180, L90, R90, Hf or Vf\n")
return(NULL)
}else{
seurat.visium = seuratVisumObject
ori.array = (seurat.visium@images)[[slide]]@image
img.dim = dim(ori.array)[1:2]/(seurat.visium@images)[[slide]]@scale.factors$lowres
new.mx <- c()
# transform the image array
for (rgb_idx in 1:3){
each.mx <- ori.array[,,rgb_idx]
each.mx.trans <- rotimat(each.mx, rotation)
new.mx <- c(new.mx, list(each.mx.trans))
}
# construct new rgb image array
new.X.dim <- dim(each.mx.trans)[1]
new.Y.dim <- dim(each.mx.trans)[2]
new.array <- array(c(new.mx[[1]],
new.mx[[2]],
new.mx[[3]]),
dim = c(new.X.dim, new.Y.dim, 3))
#swap old image with new image
seurat.visium@images[[slide]]@image <- new.array
## step4: change the tissue pixel-spot index
img.index <- (seurat.visium@images)[[slide]]@coordinates
#swap index
if(rotation == "Hf"){
seurat.visium@images[[slide]]@coordinates$imagecol <- img.dim[2]-img.index$imagecol
}
if(rotation == "Vf"){
seurat.visium@images[[slide]]@coordinates$imagerow <- img.dim[1]-img.index$imagerow
}
if(rotation == "180"){
seurat.visium@images[[slide]]@coordinates$imagerow <- img.dim[1]-img.index$imagerow
seurat.visium@images[[slide]]@coordinates$imagecol <- img.dim[2]-img.index$imagecol
}
if(rotation == "L90"){
seurat.visium@images[[slide]]@coordinates$imagerow <- img.dim[2]-img.index$imagecol
seurat.visium@images[[slide]]@coordinates$imagecol <- img.index$imagerow
}
if(rotation == "R90"){
seurat.visium@images[[slide]]@coordinates$imagerow <- img.index$imagecol
seurat.visium@images[[slide]]@coordinates$imagecol <- img.dim[1]-img.index$imagerow
}
return(seurat.visium)
}
}
## test the codes:
InstallData("stxBrain")
bb <- LoadData("stxBrain", type = "anterior1")
bb2=rotateSeuratImage(bb,rotation = "L90", slide = "anterior1")
bb3=rotateSeuratImage(bb,rotation = "Hf", slide = "anterior1")
bb4=rotateSeuratImage(bb,rotation = "R90", slide = "anterior1")
bb5=rotateSeuratImage(bb,rotation = "180", slide = "anterior1")
SpatialDimPlot(bb, pt.size.factor = 1)
SpatialDimPlot(bb2, pt.size.factor = 1)
SpatialDimPlot(bb3, pt.size.factor = 1)
SpatialDimPlot(bb4, pt.size.factor = 1)
SpatialDimPlot(bb5, pt.size.factor = 1)
I will close this issue for now, and will update it once the bug is fixed in the next release. Again, thank you all for reporting this bug and providing workaround.
Hi all,
In my case, I am happy with simply rotating the image in e.g. SpatialFeaturePlot
(I don't need to rotate the underlying datapoints). I've come up with this solution that allows you to rotate the image by any angle (i.e. we're no longer limited to multiples of 90 degrees).
library(ggplot2)
library(cowplot)
library(grid)
rotate_image <- function(p, rot_angle) {
gt <- ggplot_gtable(ggplot_build(p))
panel_idx <- which(gt$layout$name == "panel")
rot_vp <- viewport(angle = rot_angle)
gt[["grobs"]][[panel_idx]] <- editGrob(gt[["grobs"]][[panel_idx]], vp = rot_vp)
p_rot <- ggdraw() + draw_grob(gt)
return(p_rot)
}
Hope this helps!
Another useful script from you @george-hall-ucl after SpatialFeaturePlotBlend. One question. When one tend to plot the spatial Seurat object, it happens that the margins are very close to the image. So, if you have a tissue section which its shape is along the diagonal axis, after rotating the spots of tissue section would be cropped, cause they cannot fit into the frame. In this case, changing the margins in the ggplot object would be no help. Can you think of a solution for that?
This is actually a problem in Seurat itself as well. So, if you have a very small tissue section, you need to increase the size of spots more than 40 units in pt.size.factor. This again cause cropped spots and changing the margins of ggplot would not recover the lost spots.
A suggestion could be this:
rotate_image <- function(p, rot_angle, padding = padding) {
# Create a blank plot with padding
blank_plot <- ggplot() +
theme_void() +
theme(plot.margin = margin(t = padding, r = padding, b = padding, l = padding, unit = "cm"))
# Combine the blank plot and the original plot to add padding
combined_plot <- plot_grid(blank_plot, p, blank_plot, ncol = 3, rel_widths = c(padding, 1, padding))
combined_plot <- plot_grid(blank_plot, combined_plot, blank_plot, nrow = 3, rel_heights = c(padding, 1, padding))
# Convert the combined plot to a grob
gt <- ggplot_gtable(ggplot_build(combined_plot))
panel_idx <- which(gt$layout$name == "panel")
# Create a viewport with the rotation angle
rot_vp <- viewport(angle = rot_angle)
# Edit the panel grob to include the rotation viewport
gt[["grobs"]][[panel_idx]] <- editGrob(gt[["grobs"]][[panel_idx]], vp = rot_vp)
# Draw the rotated plot
p_rot <- ggdraw() + draw_grob(gt)
return(p_rot)
}
Hi @ElyasMo,
Glad you find it useful! This isn't a problem I've experienced, but your solution looks good to me 😀 Thanks for contributing!
Thanks, George