WSI Visualization in LazySlide#

LazySlide implements different plotting functions to showcase different parts of WSIData. In this tutorial, we will explore how to visualize whole slide images (WSI) using LazySlide. You will learn how to display:

  • Tissue contours

  • Tile grids

  • Feature projections

  • Pathological annotations

WSI visualization is crucial for interpreting complex tissue structures, identifying pathological regions, interpreting deep-learning features and facilitating efficient analysis in digital pathology workflows. This guide will help you understand the capabilities of LazySlide for easy and interactive WSI visualization.

import lazyslide as zs

wsi = zs.datasets.gtex_artery()
wsi
WSI: /home/runner/.cache/huggingface/hub/datasets--RendeiroLab--LazySlide-data/snapshots/9644d886889040fa10e757d912f249bbf936a979/GTEX-1117F-0526.svs
Reader: openslide
Dimensions: 19958×19919 (h×w), 3 Pyramids
Pixel physical size: 0.49 MPP (20X)
SpatialData object, with associated Zarr store: /home/runner/.cache/huggingface/hub/datasets--RendeiroLab--LazySlide-data/snapshots/9644d886889040fa10e757d912f249bbf936a979/GTEX-1117F-0526.zarr
├── Images
│     └── 'wsi_thumbnail': DataArray[cyx] (3, 1996, 1992)
├── Shapes
│     ├── 'annotations': GeoDataFrame shape: (14, 5) (2D shapes)
│     ├── 'dl-tissue': GeoDataFrame shape: (2, 2) (2D shapes)
│     ├── 'tiles': GeoDataFrame shape: (253, 3) (2D shapes)
│     └── 'tissues': GeoDataFrame shape: (2, 2) (2D shapes)
└── Tables
      ├── 'resnet50_tiles': AnnData (253, 2048)
      └── 'uni2_tiles': AnnData (253, 1536)
with coordinate systems:
    ▸ 'global', with elements:
        wsi_thumbnail (Images), annotations (Shapes), dl-tissue (Shapes), tiles (Shapes), tissues (Shapes)

Visualize tissue#

As we showed in previous tutorials, you can detect the tissue pieces with pp.find_tissues. Once you have the tissues detected, you can plot them. You will see both colored contours and id of tissues.

zs.pl.tissue(wsi)
../_images/16f1c39c1d3cde1aed48948dd951a6317973e79c2e08fc07f15c5b862e170fda.png

If you want to zoom in to specific tissue, you can specify the tissue id.

zs.pl.tissue(wsi, tissue_id=0)
../_images/e7ab5ec9526acae560048d6f01f2249faeb8d1ef0bef098aa85baa0ab529e301.png

You can see the tissue contour in green and holes in blue. Apart from showing a tissue piece, you can add a zoom view to highlight a specific region when plotting the tissue by using the zoom parameter.

It takes four values: zoom=[xmin, xmax, ymin, ymax] that define a rectangular region.

How zoom values work:

  • Values between 0-1: Interpreted as fractions of the total image size

    • zoom=[0.6, 0.9, 0.3, 0.6] means from 60% to 90% horizontally and 30% to 60% vertically

  • Values > 1: Interpreted as absolute pixel coordinates

    • zoom=[1000, 2000, 500, 1500] means from pixel 1000 to 2000 horizontally and pixel 500 to 1500 vertically

[!TIP] Look at the corners of any plot - you’ll see pixel coordinates displayed there. These numbers help you determine the exact pixel ranges for your zoom regions, making it easy to navigate to specific areas of interest in large WSI files.

zs.pl.tissue(wsi, tissue_id=0, zoom=[0.6, 0.9, 0.3, 0.6])
../_images/35c55e5276fdcc9f1a857a32ad42c29bfb1e74793a926d15c1aa1234c47c09d4.png

You can also render all individual tissue pieces at once.

zs.pl.tissue(wsi, tissue_id="all")
../_images/d044f0e0c9147d11fa849d1d74c412d68cb127edbdd7172e5ee80fd918d5efae.png

Visualize tiles#

Apart from the pl.tissues function, you can also use pl.tiles to visualize the tiles (after we have performed tiling on the WSI with pp.tile_tissues).

zs.pl.tiles(wsi)
../_images/9717f0cb60e9c863fcf72990438865a004695a063afa84b48fd8891737938792.png

By default, it will only display the tile grid on the tissues since the target object for the tiling is the detected tissue.

If not clear enough, you can also zoom in.

zs.pl.tiles(wsi, tissue_id=0, zoom=[0.6, 0.9, 0.3, 0.6])
../_images/63a8592b4eaf88ff656143ac22f8553052d64848d4fc99ed94a55460c0cb2d8d.png

Furthermore, we can project many features onto the tile visualization. These features are not informative of the tissue itself, but of the quality of the images. They can be used downstream for filtering out low-quality tiles.

wsi["tiles"].head()
tile_id tissue_id geometry
0 0 0 POLYGON ((4052 16394, 4052 16653, 3793 16653, ...
1 1 0 POLYGON ((4052 16653, 4052 16912, 3793 16912, ...
2 2 0 POLYGON ((4311 15617, 4311 15876, 4052 15876, ...
3 3 0 POLYGON ((4311 15876, 4311 16135, 4052 16135, ...
4 4 0 POLYGON ((4311 16135, 4311 16394, 4052 16394, ...
zs.tl.tile_prediction(wsi, "pathprofilerqc", device="cpu")

zs.pl.tiles(wsi, tissue_id=0, color=["diagnostic_quality", "focus_issue"], smooth=True)
../_images/057fad29363e005214aedebbe8c774c4f6fb10c6b1d4bf8bdb335525f8af0871.png
zs.pl.tiles(
    wsi, tissue_id=0, color="diagnostic_quality", zoom=[0.6, 0.9, 0.3, 0.6], smooth=True
)
../_images/5e62119e46c516eab06c8eab13b0585a6583967b83487522e667d088e1161e1e.png

We can also project deep-learning features onto the tiles of the tissue. In this example we have extracted features from the ResNet model. ResNet, short for Residual Network, is a widely used neural network architecture that uses “skip connections” to allow the training of much deeper and more accurate models, primarily for image recognition tasks.

zs.pl.tiles(wsi, tissue_id=0, feature_key="resnet50", color=["1", "100"], smooth=True)
../_images/6025b9e446815a3bab393bb81e33535a98604f17ac0f86e8fda5af091b3457eb.png

Visualize annotations#

If you have imported pathology annotations, they can also be visualized. These annotations are usually made by pathologists, using external tools like QuPath. They are very valuable, as they are made by experts and represent the ground truth. Below are some example annotations imported for showcasing. We can visualize them with zs.pl.annotations.

wsi["annotations"].head()
tissue_id id objectType name geometry
0 0 f56c25e5-ce21-4d42-bcda-b67b9fd98870 annotation sclerosis POLYGON ((4631.5 16627, 4629 16628, 4594 16628...
1 0 c799bbe2-5a08-467a-9e5d-6376b718cee8 annotation sclerosis POLYGON ((4528 16331, 4527.67 16331.17, 4527 1...
2 0 bcc06960-003b-43f0-9e88-496579dffd34 annotation sclerosis POLYGON ((5598.73 17098.36, 5596 17102, 5594.8...
3 0 e3ba7093-e973-424a-9c77-66caa03569fb annotation sclerosis POLYGON ((5964 17053, 5956 17055, 5954.2 17056...
4 0 370d0dc5-f5d1-40d9-bce6-d47f63fc4cbc annotation sclerosis POLYGON ((5649 16849, 5652 16848, 5655 16848, ...
zs.pl.annotations(wsi, "annotations", tissue_id=0)
../_images/c757f09b01f399aca505f82503480b7a9f9de52e0f196c9ef3d990352b3d54d3.png

On top of that, you can also add labels onto the image.

zs.pl.annotations(
    wsi, "annotations", tissue_id=0, label="name", zoom=[0.6, 0.9, 0.3, 0.6]
)
../_images/d47844f6ecf48f0e3dd01bf1471b95fa8616c0413d9e175701b60dfa64d10982.png

Declarative visualization in LazySlide#

Since WSIData extends from SpatialData, you may use spatialdata-plot to visualize the WSI. However, LazySlide implements a super fast and efficient plotting system to help visualize WSI from macro structures to single cells.

We start with an empty viewer, you will need to choose what to add on top of the visualization.

v = zs.pl.WSIViewer(wsi)
v.show()
<Axes: >
../_images/23ada9e3470982cc435f75d0d21525a061e735705bd8a89d47e1ce43c5255ad6.png

In most situation, we need the slide image as the background.

v.add_image()
v.show()
<Axes: >
../_images/ac4ac29f8056f90fa6b35195142fad9b6ef827b65672454dda622576bd61e6dd.png
v.add_contours("tissues")
v.show()
<Axes: >
../_images/b61ab7de64dda86802fd20b9719cd868612926f18d99b54985bfb71344e24d09.png
v.add_polygons("annotations")
v.show()
<Axes: >
../_images/bf58b85d108bac1225c6ccff216b67658de1497e242869eeaddf0af5c8aa125d.png
v.set_tissue_id(0)
v.show()
<Axes: >
../_images/afbb928a8a8fb1477c5f7d330fbce3bdd8d47744f3c0a078f760e535489f709b.png
v.add_zoom(0.6, 0.9, 0.3, 0.6)
v.add_scalebar()
v.mark_origin()
v.show()
<Axes: >
../_images/24af7bade8e9ee09cf8e537c9fc662b0fb93ac8fd1ae26c3bbddb9059a86e6bf.png

To summarize, you will have the following code

v = zs.pl.WSIViewer(wsi)
v.add_image()
v.add_contours("tissues")
v.add_polygons("annotations")
v.set_tissue_id(0)
v.add_zoom(0.6, 0.9, 0.3, 0.6)
v.add_scalebar()
v.mark_origin()
<lazyslide.plotting._wsi_viewer.WSIViewer at 0x7f35740f0e00>

If we don’t call the .show() method, nothing will happen. The plotting will be lazily evaluated.

Now let’s call the .show() to render the final image.

v.show()
<Axes: >
../_images/24af7bade8e9ee09cf8e537c9fc662b0fb93ac8fd1ae26c3bbddb9059a86e6bf.png

You can control if an element shoud be displayed in the zoom view.

Here we disabled the display of yellow annotations in the zoom view.

v = zs.pl.WSIViewer(wsi)
v.add_image()
v.add_polygons("annotations", in_zoom=False)
v.set_tissue_id(0)
v.add_zoom(0.6, 0.9, 0.3, 0.6)
v.show()
<Axes: >
../_images/0647e3ae9af29d3e1beb9a259873dd97bd5ccd958a642580cea69ad7442a1f10.png

Efficient debugging of visualization#

The progressive adding components on top of the images gives you finer control on what to visualize.

However, it’s not efficient to debug, once you add a component, you cannot delete it.

If you have a large image and you create a new viewer, every time that you want to modify some tiny details, you waste your time on re-computing the image rendering process.

Luckily, we have a solution for this. When you add a component, simply set cache=False, and it will only be rendered in the next rendering (only next time you use show).

v = zs.pl.WSIViewer(wsi)
v.add_image()
v.add_polygons("annotations", cache=False)
v.set_tissue_id(0)
v.show()
<Axes: >
../_images/92ed6f5da99ff40bc81ee388b818d1da1b78c277e03366c7c25c9f5aa53ee77a.png

If we called the .show() again, the annotation will disappeared.

v.show()
<Axes: >
../_images/c8efe3ef8dabec4a2d5a43004fa10db01373b08ec3a29397b100428d23a1d988.png

In this way, you can easily change color without recomputing the image rendering process.

You may not notice the difference in this example, but if you have a huge WSI. This can make significant difference.

v.add_polygons("annotations", color="#604FDD", cache=False)
v.show()
<Axes: >
../_images/21e503b7c4cbc784bee6ebafa92db8423106ba14ae6a52dd4f10ae81e2743546.png