# Visium normalization best practices

**URL:** <https://discourse.scverse.org/t/visium-normalization-best-practices/1889>\
**Category:** squidpy\
**Created:** [November 14, 2023, 7:59am UTC](https://discourse.scverse.org/t/visium-normalization-best-practices/1889 "2023-11-14T07:59:14Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![grst](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.scverse.org/grst/32/17_2.png) [@grst](https://discourse.scverse.org/u/grst)\
**Post date:** [November 14, 2023, 7:59am UTC](https://discourse.scverse.org/t/visium-normalization-best-practices/1889/1 "2023-11-14T07:59:14Z")

</div>

Are there any recommendations on normalizing spatial transcriptomics data before visualization? In the [spatial scanpy tutorial](https://scanpy-tutorials.readthedocs.io/en/latest/spatial/basic-analysis.html), the gene expression is normalized like scRNA-seq data using `normalize_total` + `log1p`. In the [squidpy visium tutorial](https://squidpy.readthedocs.io/en/stable/notebooks/tutorials/tutorial_visium_hne.html), on the other hand, raw counts are plotted.

Personally I’m not convinced that `normalize_total` makes sense for spatial data, as

1. I’d assume there is less technical variability between spots than between droplets.
2. There are biological reasons for different spatial regions having different mRNA content (see also the example below).

But there should probably still be some normalization when comparing _between samples_. I’m wondering if something like [scran](https://bioconductor.org/packages/devel/bioc/vignettes/scran/inst/doc/scran.html) makes sense here? As far as I understood scran takes different mRNA content of cells is taken into account when computing scaling factors. Alternatively maybe just normalizing at the sample level (e.g. normalize each sample to 10k \* n\_spots\_under\_tissue) is appropriate?

* * *

### normalize total in a tumor sample

In my experience `normalize_total` seems problematic at least for the tumor slides I have, because different regions of the slide have vastely different mRNA contents. For instance, the tumor region has a much higher count density than the stromal or immune regions.

 ![image](https://canada1.discourse-cdn.com/flex035/uploads/forum11/original/1X/c8c9e8c617129b2a00f5612276e67bdffd7644dc.jpeg)  
**Fig1: log1p total counts**

 ![image](https://canada1.discourse-cdn.com/flex035/uploads/forum11/original/1X/5fcab1af79b1dc325951a96bccca2ece24f91f40.jpeg)  
**Fig2: spatial niches**

When plotting the normalized gene expression, the normalized values appear to be much higher (although sparser) in the immune region, which is misleading:

 ![image](https://canada1.discourse-cdn.com/flex035/uploads/forum11/original/1X/3ef73806beb8121a50aa192062ebb1a2fb8ea3d6.jpeg)  
**Fig3: KRAS expression, log-normalized**

Here is the same plot with just log1p transformed counts:

 ![image](https://canada1.discourse-cdn.com/flex035/uploads/forum11/original/1X/598f323fa28a1512af96d85d0beb144fa184b452.png)  
**Fig4: KRAS expression, log1p-transformed**

---

<div class="post-metadata">

**Author:** ![pakiessling](https://avatars.discourse-cdn.com/v4/letter/p/73ab20/32.png) [@pakiessling](https://discourse.scverse.org/u/pakiessling)\
**Post date:** [November 20, 2023, 5:52pm UTC](https://discourse.scverse.org/t/visium-normalization-best-practices/1889/2 "2023-11-20T17:52:16Z")

</div>

Hi @grst ,

I was wondering the same thing.  
While most publications do `normalize_total` this also seems a bit strange to me.  
I saw Seurat’s sctransform and stLearn’s SME recommended [here](https://genomemedicine.biomedcentral.com/articles/10.1186/s13073-022-01075-1#Sec14) as they do not assume that every spot / cell should contain the same count.

Not sure how to best evaluate what works well and what doesn’t though. 🤷‍♂️

---

<div class="post-metadata">

**Author:** ![cane11](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.scverse.org/cane11/32/241_2.png) [@cane11](https://discourse.scverse.org/u/cane11)\
**Post date:** [November 28, 2023, 6:28pm UTC](https://discourse.scverse.org/t/visium-normalization-best-practices/1889/3 "2023-11-28T18:28:53Z")

</div>

The normalization from Cell2location using detection efficiency behaves well in my hands. For pure display reasons and if you don’t want to train something, another round of count normalization after logarithm is helpful in my hands as proposed by Lior Pachter (don’t remember where).

---

<div class="post-metadata">

**Author:** ![grst](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.scverse.org/grst/32/17_2.png) [@grst](https://discourse.scverse.org/u/grst)\
**Post date:** [June 5, 2024, 6:40am UTC](https://discourse.scverse.org/t/visium-normalization-best-practices/1889/4 "2024-06-05T06:40:26Z")

</div>

Hi @cane11,

> [@cane11](#):
>
> The normalization from Cell2location using detection efficiency behaves well in my hands.

Thanks for the suggestion! Where do you get that from?  
Is this

X / y\_s

where X is the raw UMI counts and y\_s is the “a location/observation-specific scaling parameter which adjusts for differences in sensitivity between observations and batches” obtained using

```python
mod.samples["post_sample_means"]["detection_y_s"]

```

?

---

<div class="post-metadata">

**Author:** ![cane11](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.scverse.org/cane11/32/241_2.png) [@cane11](https://discourse.scverse.org/u/cane11)\
**Post date:** [June 11, 2024, 6:02pm UTC](https://discourse.scverse.org/t/visium-normalization-best-practices/1889/5 "2024-06-11T18:02:22Z")

</div>

Hi Gregor, this sounds correct. However, I was speaking about the per-celltype expression after deconvolution (described in the NCEM part of Cell2location - [Mapping human lymph node cell types to 10X Visium with Cell2location — cell2location documentation](https://cell2location.readthedocs.io/en/latest/notebooks/cell2location_tutorial.html#Estimate-cell-type-specific-expression-of-every-gene-in-the-spatial-data-(needed-for-NCEM)). This one still works, if there are sever differences in library size along the chip. For CELLxGENE session and similar, I had the easiest visual time doing normalize\_total-\>log1p-\>normalize\_total (called PFlog1pPF in [https://www.biorxiv.org/content/10.1101/2022.05.06.490859v1.full.pdf](https://www.biorxiv.org/content/10.1101/2022.05.06.490859v1.full.pdf)).

---

<div class="post-metadata">

**Author:** ![cane11](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.scverse.org/cane11/32/241_2.png) [@cane11](https://discourse.scverse.org/u/cane11)\
**Post date:** [June 11, 2024, 6:09pm UTC](https://discourse.scverse.org/t/visium-normalization-best-practices/1889/6 "2024-06-11T18:09:16Z")

</div>

I don’t think it’s discussed somewhere, but:

```auto
    def compute_expected(self, samples, adata_manager, ind_x=None):
        r"""Compute expected expression of each gene in each location. Useful for evaluating how well
        the model learned expression pattern of all genes in the data.
        """
        if ind_x is None:
            ind_x = np.arange(adata_manager.adata.n_obs).astype(int)
        else:
            ind_x = ind_x.astype(int)
        obs2sample = adata_manager.get_from_registry(REGISTRY_KEYS.BATCH_KEY)
        obs2sample = pd.get_dummies(obs2sample.flatten()).values[ind_x, :]
        mu = (
            np.dot(samples["w_sf"][ind_x, :], self.cell_state_mat.T) * samples["m_g"]
            + np.dot(obs2sample, samples["s_g_gene_add"])
        ) * samples["detection_y_s"][ind_x, :]
        alpha = np.dot(obs2sample, 1 / np.power(samples["alpha_g_inverse"], 2))

        return {"mu": mu, "alpha": alpha, "ind_x": ind_x}

```

removing `samples["detection_y_s"][ind_x, :]` from this code would remove the detection efficiency and generate normalized data. Similarly normalizing to the same `samples["detection_y_s"][ind_x, :]` would give you normalized counts.
