JEFworks-Lab / STdeconvolve

Reference-free cell-type deconvolution of multi-cellular spatially resolved transcriptomics data
http://jef.works/STdeconvolve/
102 stars 11 forks source link

Visium image is flipped - mirror symmetry #39

Closed cathalgking closed 1 year ago

cathalgking commented 1 year ago

I am following the Visium tutorial and the plots that are being generated with my Visium data are being flipped around (mirror-image) for each plot. When I read them into the Spatial Experiment object, they are orientated the correct way. How can I correct this? I am following the exact code in the visium tutorial

bmill3r commented 1 year ago

Hi @cathalgking,

This should just be a matter of transforming the spatial coordinates of the spots. For example, looking at:

vizAllTopics(deconProp, pos, r=0.4)

pos is a data.frame that contains the x and y coordinates of the spots. If you wanted to flip the spots with respect to the x axis, you multiply the x coordinates by -1: pos$x <- pos$x * -1

Another suggestion looking at your plot: you can increase the size of your spots by increasing the r argument. For example, r = 0.5, assuming that the center of spot is a distance of 1 away from each other, then the spots should essentially be touching edges. r basically controls the radius of the scatterpie circles.

Another suggestion is to use the argument lwd. This controls the thickness of the strokes, or boundaries of the scatterpies. I would suggest setting this to lwd = 0 so that the colors of the different cell type proportions stand out better.

Hope this helps, Brendan

bmill3r commented 1 year ago

Hi @cathalgking,

Looking at the code you shared, I wonder if the issue is:

ggplot2::geom_rect(data = data.frame(pos)

It looks like you are using pos2 for the plotting function, but using pos to position the rectangular plot border so maybe this is why.

Can you clarify a bit more on what you mean by not being able to "see the correlation on the upper and lower ends of the Visium image"?

Thanks, Brendan

bmill3r commented 1 year ago

Hi @cathalgking,

Each scatterpie corresponds to one of the spots in the Visium dataset. Differences between the number of spots could be due to differences in filtering workflows performed by STdeconvolve and the workflow used to obtain the original Visium cluster you show. Looking at the STdeconvolve and Visium images, my suspicion is that the missing scatterpie in the bottom-right of the STdeconvolve image may actually be the "gap" between spots in the upper-left of the Visium plot. If one were to rotate the STdeconvolve plot such that the bottom-right became the upper-left, then I think the two images might match. But to check, from the original Visium analysis, I believe you should have the spot cluster IDs, and so you could plot these using the coordinates of the scatterpies in STdeconvolve and see how the clusters correspond spatially between the two images. Note that the scatterpies correspond to the original Visium spots and therefore they should have the same indices as those in the Visium data, allowing you to match the cluster IDs.

With respect to your last question, you can certainly use a single cell reference of your choosing to help with the deconvolved cell type annotation. There several tutorials and additional issues posted by other users wanting to employ similar strategies I encourage you to check out.

Hope this helps, Brendan

BartBryant commented 1 year ago

Hi Brendan,

Thanks for developing STdeconvolve, look forward to using it.

I also have a similar issue with my Visium data as the coordinates from my 'pos' object do not match the spatial image (see screenshot)

Screenshot 2023-06-27 at 2 56 23 PM

I was able to follow your suggestion above for 'cathalgking' where i multiplied my y-coordinates by -1, but the image is still off. It is almost like the 'image' needs to be flipped on a z-axis 180 degrees (let me know if not clear). (see screenshot)

I feel like the answer is simple, but can't get my head around it.

Thanks in advance, Bart

bmill3r commented 1 year ago

Hi @BartBryant,

Yes, I see what you mean. I think in general this difference in because of differences in the origin that R matrices use vs images. What if you swap the x and y coordinates in the original STdeconvolve image? It might give you the rotation you need to have the two images matching. If that doesn't work, other manipulations could be reversing the order of the x and/or y coordinates, or some combination.

Hope this helps, Brendan

JEFworks commented 1 year ago

Dear @BartBryant,

Thanks for sharing the helpful visual. I believe this may be helpful: https://math.stackexchange.com/questions/1330161/how-to-rotate-points-through-90-degree

It looks like your original coordinates need to be rotated clockwise 90 degrees.

Some R code below:

# To rotate clockwise, replace (𝑥,𝑦) with (𝑦,−𝑥).
pos.rc <- data.frame(x = pos$y, y = -pos$x)

# Replace (𝑥,𝑦) with (−𝑦,𝑥). That will rotate 90 degrees counterclockwise about the origin.
pos.rcc <- data.frame(x = -pos$y, y = pos$x)

A more general solution to rotate the points by any angle can be achieved using a rotation matrix: https://en.wikipedia.org/wiki/Rotation_matrix

Hope that helps, Jean

BartBryant commented 1 year ago

Thanks Brendan and Jean, that worked! since this worked, can you give some R code to enable me to use the 'overlay' argument in vizAllTopics()?

Screenshot 2023-06-30 at 12 51 25 PM
cathalgking commented 1 year ago

@BartBryant Hi Bart. I am having this issue with the 'pos' co-ordinates and I believe it is the same as yours. What steps did you take to correct your plot? Did you use both Jean's and Brendan's suggestion above or just one of them? thanks

BartBryant commented 1 year ago

@cathalgking hi Cathal, the code I used to fix the 'flipped-image' issue was a combination of both.

Firtst, I switched the 'x' and 'y' coordinates from Bmill3r "swap the x and y coordinates" this was done with the line of code below (also screenshot of code)

now to change column names

colnames(pos)<-c("y","x")

Screenshot 2023-08-29 at 1 53 54 PM

which was done before running STdeconvolve

also, did the rotating option in the 'vizAllTopis()' function with code below on the 'pos' object until the STdeconvolve output matched my H&E image

either 'x' vizAllTopics(theta = deconProp, pos = pos, r = 10.3, lwd = 0, showLegend = TRUE, plotTitle = NA)

pos$x <- pos$x * -1

or 'y' pos$y <- pos$y * -1 vizAllTopics(theta = deconProp, pos = pos, r = 10.3, lwd = 0, showLegend = TRUE, plotTitle = NA)

also below is screenshot to show the code

Screenshot 2023-08-29 at 1 54 15 PM
BartBryant commented 1 year ago

Hi @bmill3r I got my 'visium image' flipped and now would like to do the overlay option in the 'vizAllTopiscs()' function, can you give an example of how to do this action? Thanks, Bart

JEFworks commented 1 year ago

Hi Cathal,

Thanks for the following on Github.

It appears that perhaps everyone could benefit from a review of coordinate systems. I've built on the following tutorial to provide an example (https://jef.works/STdeconvolve/visium_10x.html) if you would also like the follow along:

## download data
f <- "~/Desktop/visiumTutorial/"
if(!file.exists(f)){
  dir.create(f)
}
if(!file.exists(paste0(f, "V1_Adult_Mouse_Brain_filtered_feature_bc_matrix.tar.gz"))){
  tar_gz_file <- "http://cf.10xgenomics.com/samples/spatial-exp/1.1.0/V1_Adult_Mouse_Brain/V1_Adult_Mouse_Brain_filtered_feature_bc_matrix.tar.gz"
  download.file(tar_gz_file, 
                destfile = paste0(f, "V1_Adult_Mouse_Brain_filtered_feature_bc_matrix.tar.gz"), 
                method = "auto")
}
untar(tarfile = paste0(f, "V1_Adult_Mouse_Brain_filtered_feature_bc_matrix.tar.gz"), 
      exdir = f)
if(!file.exists(paste0(f, "V1_Adult_Mouse_Brain_spatial.tar.gz"))){
  spatial_imaging_data <- "http://cf.10xgenomics.com/samples/spatial-exp/1.1.0/V1_Adult_Mouse_Brain/V1_Adult_Mouse_Brain_spatial.tar.gz"
  download.file(spatial_imaging_data, 
                destfile = paste0(f, "V1_Adult_Mouse_Brain_spatial.tar.gz"), 
                method = "auto")
}
untar(tarfile = paste0(f, "V1_Adult_Mouse_Brain_spatial.tar.gz"), 
      exdir = f)

Let's plot the associated H&E image also.

img <- png::readPNG(paste0(f, 'spatial/tissue_lowres_image.png'))
## now we can plot with the image
x <- grid::rasterGrob(img,
                      interpolate = FALSE,
                      width = grid::unit(1, "npc"),
                      height = grid::unit(1, "npc"))
library(ggplot2)
plt <- ggplot() +
  annotation_custom(
    grob = x,
    xmin = 0,
    xmax = ncol(x$raster),
    ymin = 0,
    ymax = nrow(x$raster)) + 
  coord_fixed(
    xlim = c(0, ncol(x$raster)),
    ylim = c(0, nrow(x$raster))) + 
  theme_void()
plt 

image

Note, when we read in Visium spot positions, the coordinates of the spots are described as pixel row and pixel column in the image. Note in an image, we think of (0,0) as the first row and the first column, which corresponds to the top left of an image.

## read in data
se <- SpatialExperiment::read10xVisium(samples = f,
                                       type = "sparse",
                                       data = "filtered")
## just use pos for this
pos <- SpatialExperiment::spatialCoords(se)
colnames(pos)
## note pixel coordinates are in matrix coordinates of row col
plot(pos)

image

However, when we plot points outside of images, we often think in cartesian coordinates. image (http://edspi31415.blogspot.com/2018/02/hp-prime-pixel-plot-how-to-change.html) So (0,0) in cartesian coordinates would be the bottom left.

## when we plot in cartesian coordinate, we think of x,y
## with x is being the horizontal axis 
## and y being the vertical axis 
pos2 <- data.frame(x = pos[,2], y = pos[,1])
plot(pos2)

image

Pixel coordinates (some times also called matrix coordinates or image coordinates) differ from cartesian coordinates. So our plotting spot positions in pixel coordinates on a cartesian plane makes everything looked flipped relative to the original image. We can flip about the y-axis by multiply by -1 as you have done. But this makes our y-positions negative!

## in matrices we consider 0,0 as the top left
## in cartesian, we consider 0,0 as the bottom left
## to make the coordinates agree, we can to flip about the y-axis
## by multiplying by -1 
pos3 <- data.frame(x = pos[,2], y = -pos[,1])
plot(pos3)

image

Again, note the negative axis values. If you plotted these, again looking at the cartesian coordinate system, the points will be all the way to the bottom. Another way to look at it:

plot(pos3, 
     xlim=c(-dim(img)[1]/scalefactor, dim(img)[1]/scalefactor), 
     ylim=c(-dim(img)[2]/scalefactor, dim(img)[2]/scalefactor))
abline(h = 0)
abline(v = 0)

image

Therefore, we need to not only flip the positions about the y-axis, but also shift the positions up by the size of the image.

## from scalefactors_json.json for lowres image 
scalefactor <- 0.051033426
pos4 <- data.frame(x = pos[,2], y = -pos[,1] + dim(img)[1]/scalefactor)
plot(pos3, 
     xlim=c(-dim(img)[1]/scalefactor, dim(img)[1]/scalefactor), 
     ylim=c(-dim(img)[2]/scalefactor, dim(img)[2]/scalefactor))
abline(h = 0)
abline(v = 0)

image

Now that the original spot positions in pixel coordinates relative to the image have been transformed into cartesian coordinates, we can visualize the everything together.

## modification on vizAllTopics
## add scatterpies on top
r = 45*scalefactor
lwd = 0
theta_ordered_pos <- merge(data.frame(deconProp),
                           data.frame(pos4*scalefactor), by=0)
rownames(theta_ordered_pos) <- theta_ordered_pos[,"Row.names"]
topicColumns <- colnames(theta_ordered_pos)[2:(dim(theta_ordered_pos)[2]-2)]
groups <- rep("0", dim(theta_ordered_pos)[1])
theta_ordered_pos$Pixel.Groups <- groups
plt2 <- plt + 
  scatterpie::geom_scatterpie(aes(x = x, y = y, 
                              group = Row.names, 
                              r = r, 
                              color = Pixel.Groups), 
                              lwd = lwd, 
                              data = theta_ordered_pos, 
                              cols = topicColumns)
plt2

image

Please see if you can take what you've learned here to solve your specific issue.

Best of luck with your continuing research pursuits, Prof. Fan

cathalgking commented 1 year ago

Thanks for that @JEFworks I got the image to orient the correct way when I plot(pos) as shown below. However, when I run through the steps to process the data (fitLDA etc.) and visualize all cell-types in theta at once with vizAllTopics it then re-orients back to the incorrect way, even though plotting the pos dataframe at that stage still is oriented the correct way. I start with:

sample <- file.path("/PATH/D1/outs")
# read in
D1_spe <- read10xVisium(samples = sample, type = "sparse", images = "hires", data = "filtered")
D1_spe
# View image in SPE object
plotVisium(D1_spe) + ggtitle("Plot D")
#
pos <- SpatialExperiment::spatialCoords(D1_spe)
colnames(pos)
plot(pos) # incorrect orientation (flipped about the x axis)
##
colnames(pos)<-c("y","x")
pos <- as.data.frame(pos)
## flip wrt the y
pos$x <- pos$x * -1
plot(pos) # correct orientation

plot(pos) finally is the correct orientation when put next to the original image. Screen Shot 2023-09-01 at 3 36 51 pm

Then, process data with the below:

cd <- D1_spe@assays@data@listData$counts
D1_spe <- restrictCorpus(cd, removeAbove=1.0, removeBelow = 0.05, nTopOD = 1000)
ldas <- fitLDA(t(as.matrix(D1_spe)), Ks = c(3))
optLDA <- optimalModel(models = ldas, opt = 3)
results <- getBetaTheta(optLDA, perc.filt = 0.05, betaScale = 1000)
deconProp <- results$theta
deconGexp <- results$beta

Re-plot pos to make sure it has not moved. plot(pos) Screen Shot 2023-09-01 at 3 43 03 pm Finally, use vizAllTopics to show scatterpies.

# plot
vizAllTopics(theta = deconProp,
                   pos = pos,
                   r = 95,
                   lwd = 0,
                   showLegend = TRUE,
                   plotTitle = NA)

image

Now it has (apparently) rotated 90 degrees anti-clock-wise but also flipped across the x-axis. This must mean that it is something to do with the deconProp dataframe (?) because pos still plots in the correct orientation (even after running vizAllTopics). I was looking through the processing steps (fitLDA, etc) and I do not see anywhere that the orientation would have changed. Have you seen this issue before?

bmill3r commented 1 year ago

Hi @cathalgking,

I have not seen this before but looking at the output of the plot(pos), I am noticing that the axis labels are such that y is on the x-axis and x is on the y-axis. I wonder if the base R plot() function will take the first column as x, and the second column as y regardless of what the column names are. Conversely, in ggplot2 the columns to be used as the x and y coordinates are set in the aes attribute of the plotting function. Because of this, try transforming pos as Jean does in her example:

pos2 <- data.frame(x = pos[,2], y = -pos[,1] + dim(img)[1]/scalefactor)

This way, it ensures that the second column of pos (which would be "y") is column x, but it is also the first column in the dataframe. Then, I think it should plot correctly whether you use base R plot() or ggplot2.

To check:

plot(pos2)

ggplot(data = pos2) + geom_point(aes(x = x, y = y), size = 1)

Let me know if this helps, Brendan

bmill3r commented 1 year ago

Hi @BartBryant

Sorry for missing your post:

Hi @bmill3r I got my 'visium image' flipped and now would like to do the overlay option in the 'vizAllTopiscs()' function, can you give an example of how to do this action? Thanks, Bart

Please take a look at Jean's example for reorienting the data, in which she also plots the scatterpies on top of the rasterized H&E image for an example of overlaying the data on the image.

Thanks, Brendan

cathalgking commented 1 year ago

Got this sorted. Thanks.