seurat icon indicating copy to clipboard operation
seurat copied to clipboard

SpatialPlot: rotate/flip slides

Open lima1 opened this issue 4 years ago • 6 comments

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

lima1 avatar Mar 09 '20 18:03 lima1

Hi Markus,

Thanks for reporting. This is a bug in SpatialPlot as it should support coord_flip

mojaveazure avatar Mar 13 '20 16:03 mojaveazure

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

ckm2016 avatar Feb 02 '21 05:02 ckm2016

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

ktrns avatar Jul 01 '21 07:07 ktrns

I third this request for easy ways to rotate/flip images so my brain slices are all in the same orientation.

jdrnevich avatar Sep 14 '21 02:09 jdrnevich

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, ]

JPingLin avatar Sep 26 '21 18:09 JPingLin

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!

ark2173 avatar Sep 15 '22 16:09 ark2173

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

AmhedVargas avatar Apr 19 '23 09:04 AmhedVargas

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 : )

JPingLin avatar Apr 19 '23 15:04 JPingLin

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.

longmanz avatar Jul 07 '23 20:07 longmanz

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!

george-hall-ucl avatar Jan 05 '24 11:01 george-hall-ucl

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.

Rplot

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)
}

ElyasMo avatar Jun 26 '24 11:06 ElyasMo

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

george-hall-ucl avatar Jul 04 '24 08:07 george-hall-ucl