Stain Normalization

Stain normalization reduces colour variation caused by differences in staining protocols, scanners, and laboratory conditions across histopathology slides.

Important

The built-in MacenkoStainNormalizer and ReinhardtStainNormalizer are reference implementations provided to demonstrate the StainNormalizer interface. Both emit a UserWarning indicating their experimental status.

For production use, we strongly recommend implementing your own normalizer adapted to your specific stain types, tissue morphology, and computational constraints.

Stain normalization comparison

Macenko normalization: heart tissue (source) matched to breast tissue (target)

Macenko Normalizer

The Macenko method uses principal component analysis (PCA) on optical density values to estimate stain vectors, then matches the stain distribution to a target image.

from glasscut import MacenkoStainNormalizer
from PIL import Image

normalizer = MacenkoStainNormalizer()

target = Image.open("target.png")
normalizer.fit(target)

source = Image.open("source.png")
normalized = normalizer.transform(source)
normalized.save("normalized.png")

Warning

The Macenko normalizer is marked as experimental. Results may vary across different tissue types and staining qualities.

Reinhardt Normalizer

The Reinhardt method matches the mean and standard deviation of each LAB colour channel on tissue regions, providing a fast approximate normalisation.

from glasscut import ReinhardtStainNormalizer
from PIL import Image

normalizer = ReinhardtStainNormalizer()

target = Image.open("target.png")
normalizer.fit(target)

source = Image.open("source.png")
normalized = normalizer.transform(source)
normalized.save("normalized.png")

Warning

The Reinhardt normalizer is marked as experimental. Results may vary across different tissue types and staining qualities.

Using with GridTiler

Stain normalizers can be used as tile transforms in the tiling pipeline:

normalizer = MacenkoStainNormalizer()
normalizer.fit(target_image)

tiler = GridTiler(
    tile_size=(512, 512),
    transforms=[normalizer.transform],
)