%matplotlib inline

Process a high-resolution image

This example shows how to use squidpy.im.process with tiling.

The function can be applied to any method (e.g., smoothing, conversion to grayscale) or layer of a high-resolution image layer of squidpy.im.ImageContainer.

By default, squidpy.im.process processes the entire input image at once. In the case of high-resolution tissue slides however, the images might be too big to fit in memory and cannot be processed at once. In that case you can use the argument chunks to tile the image in crops of shape chunks, process each crop, and re-assemble the resulting image. Note that you can also use squidpy.im.segment in this manner.

Note that depending on the processing function used, there might be border effects occurring at the edges of the crops. Since Squidpy is backed by dask, and internally chunking is done using dask.array.map_overlap, dealing with these border effects is easy. Just specify the depth and boundary arguments in the apply_kwargs upon the call to squidpy.im.process. For more information, please refer to the documentation of dask.array.map_overlap.

For the build in processing functions, [gray]{.title-ref} and [smooth]{.title-ref}, the border effects are already automatically taken care of, so it is not necessary to specify depth and boundary. For squidpy.im.segment, the default depth is 30, which already takes care of most severe border effects.

See also

  • examples_image_compute_smooth.

  • examples_image_compute_gray.

  • examples_image_compute_segment_fluo.

import numpy as np
from scipy.ndimage import gaussian_filter

import matplotlib.pyplot as plt

import squidpy as sq

Built-in processing functions

# load the H&E stained tissue image
img = sq.datasets.visium_hne_image()

We will process the image by tiling it in crops of shape chunks = (1000, 1000).

sq.im.process(img, layer="image", method="gray", chunks=1000)

Now we can look at the result on a cropped part of the image.

crop = img.crop_corner(4000, 4000, size=2000)

fig, axes = plt.subplots(1, 2)
crop.show("image", ax=axes[0])
_ = axes[0].set_title("original")
crop.show("image_gray", cmap="gray", ax=axes[1])
_ = axes[1].set_title("grayscale")
../../../_images/784304119a86a54b41f41fdad13d05ab9ef37d7e299a06b428084bf46dc4ea28.png

Custom processing functions

Here, we use a custom processing function (here scipy.ndimage.gaussian_filter) with chunking to showcase the depth and boundary arguments.

Lets use a simple image and choose the chunk size in such a way to clearly see the differences between using overlapping crops and non-overlapping crops.

arr = np.zeros((20, 20))
arr[10:] = 1
img = sq.im.ImageContainer(arr, layer="image")

# smooth the image using `depth` 0 and 1
sq.im.process(
    img,
    layer="image",
    method=gaussian_filter,
    layer_added="smooth_depth0",
    chunks=10,
    sigma=1,
    apply_kwargs={"depth": 0},
)
sq.im.process(
    img,
    layer="image",
    method=gaussian_filter,
    layer_added="smooth_depth1",
    chunks=10,
    sigma=1,
    apply_kwargs={"depth": 1, "boundary": "reflect"},
)

Plot the difference in results. Using overlapping blocks with depth = 1 removes the artifacts at the borders between chunks.

fig, axes = plt.subplots(1, 3)
img.show("image", ax=axes[0])
_ = axes[0].set_title("original")
img.show("smooth_depth0", ax=axes[1])
_ = axes[1].set_title("non-overlapping crops")
img.show("smooth_depth1", ax=axes[2])
_ = axes[2].set_title("overlapping crops")
../../../_images/cf6a41f27b59b03a888b0df7ed2b586e346feb781fced12549eeccc995ccf84d.png