Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# torchstain

[![License](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![tests](https://github.com/EIDOSLAB/torchstain/workflows/tests/badge.svg)](https://github.com/EIDOSLAB/torchstain/actions)
[![Full Tests](https://github.com/EIDOSLAB/torchstain/actions/workflows/tests_full.yml/badge.svg)](https://github.com/EIDOSLAB/torchstain/actions/workflows/tests_full.yml)
[![Pip Downloads](https://img.shields.io/pypi/dm/torchstain?label=pip%20downloads&logo=python)](https://pypi.org/project/torchstain/)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7692014.svg)](https://doi.org/10.5281/zenodo.7692014)

Expand Down Expand Up @@ -79,7 +79,7 @@ Runtimes using the Macenko algorithm using different backends. Metrics were calc
- [1] Macenko, Marc et al. "A method for normalizing histology slides for quantitative analysis." 2009 IEEE International Symposium on Biomedical Imaging: From Nano to Macro. IEEE, 2009.
- [2] Reinhard, Erik et al. "Color transfer between images." IEEE Computer Graphics and Applications. IEEE, 2001.
- [3] Roy, Santanu et al. "Modified Reinhard Algorithm for Color Normalization of Colorectal Cancer Histopathology Images". 2021 29th European Signal Processing Conference (EUSIPCO), IEEE, 2021.
- [4] Ivanov, Desislav et al. "Multi-target stain normalization for histology slides". arXiv (preprint). 2024.
- [4] Ivanov, Desislav et al. "Multi-target stain normalization for histology slides". 2nd International Workshop on Medical Optical Imaging and Virtual Microscopy Image Analysis (MOVI 2024), MICCAI. 2024.

## Citing

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

setup(
name='torchstain',
version='1.3.0',
version='1.4.0',
description='Stain normalization tools for histological analysis and computational pathology',
long_description=README,
long_description_content_type='text/markdown',
Expand Down
1 change: 1 addition & 0 deletions torchstain/base/normalizers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .he_normalizer import HENormalizer
from .macenko import MacenkoNormalizer
from .multitarget import MultiMacenkoNormalizer
from .reinhard import ReinhardNormalizer
14 changes: 9 additions & 5 deletions torchstain/base/normalizers/multitarget.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
def MultiMacenkoNormalizer(backend='torch', **kwargs):
if backend == 'torch':
from torchstain.torch.normalizers.multitarget import MultiMacenkoNormalizer
return MultiMacenkoNormalizer(**kwargs)
def MultiMacenkoNormalizer(backend="torch", **kwargs):
if backend == "numpy":
raise NotImplementedError("MultiMacenkoNormalizer is not implemented for NumPy backend")
elif backend == "torch":
from torchstain.torch.normalizers import TorchMultiMacenkoNormalizer
return TorchMultiMacenkoNormalizer(**kwargs)
elif backend == "tensorflow":
raise NotImplementedError("MultiMacenkoNormalizer is not implemented for TensorFlow backend")
else:
raise Exception(f'Unsupported backend {backend}')
raise Exception(f"Unsupported backend {backend}")
2 changes: 1 addition & 1 deletion torchstain/torch/normalizers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from torchstain.torch.normalizers.macenko import TorchMacenkoNormalizer
from torchstain.torch.normalizers.multitarget import MultiMacenkoNormalizer
from torchstain.torch.normalizers.multitarget import TorchMultiMacenkoNormalizer
from torchstain.torch.normalizers.reinhard import TorchReinhardNormalizer
17 changes: 9 additions & 8 deletions torchstain/torch/normalizers/multitarget.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import torch
from torchstain.torch.utils import cov, percentile

"""
Implementation of the multi-target normalizer from the paper: https://arxiv.org/pdf/2406.02077
"""
class MultiMacenkoNormalizer:
def __init__(self, norm_mode='avg-post'):
class TorchMultiMacenkoNormalizer:
def __init__(self, norm_mode="avg-post"):
self.norm_mode = norm_mode
self.HERef = torch.tensor([[0.5626, 0.2159],
[0.7201, 0.8012],
[0.4062, 0.5581]])
self.maxCRef = torch.tensor([1.9705, 1.0308])
self.updated_lstsq = hasattr(torch.linalg, 'lstsq')
self.updated_lstsq = hasattr(torch.linalg, "lstsq")

def __convert_rgb2od(self, I, Io, beta):
I = I.permute(1, 2, 0)
Expand Down Expand Up @@ -59,15 +60,15 @@ def __compute_matrices_single(self, I, Io, alpha, beta):
return HE, C, maxC

def fit(self, Is, Io=240, alpha=1, beta=0.15):
if self.norm_mode == 'avg-post':
if self.norm_mode == "avg-post":
HEs, _, maxCs = zip(*(
self.__compute_matrices_single(I, Io, alpha, beta)
for I in Is
))

self.HERef = torch.stack(HEs).mean(dim=0)
self.maxCRef = torch.stack(maxCs).mean(dim=0)
elif self.norm_mode == 'concat':
elif self.norm_mode == "concat":
ODs, ODhats = zip(*(
self.__convert_rgb2od(I, Io, beta)
for I in Is
Expand All @@ -83,7 +84,7 @@ def fit(self, Is, Io=240, alpha=1, beta=0.15):
maxCs = torch.stack([percentile(C[0, :], 99), percentile(C[1, :], 99)])
self.HERef = HE
self.maxCRef = maxCs
elif self.norm_mode == 'avg-pre':
elif self.norm_mode == "avg-pre":
ODs, ODhats = zip(*(
self.__convert_rgb2od(I, Io, beta)
for I in Is
Expand All @@ -100,7 +101,7 @@ def fit(self, Is, Io=240, alpha=1, beta=0.15):
maxCs = torch.stack([percentile(C[0, :], 99), percentile(C[1, :], 99)])
self.HERef = HE
self.maxCRef = maxCs
elif self.norm_mode == 'fixed-single' or self.norm_mode == 'stochastic-single':
elif self.norm_mode == "fixed-single" or self.norm_mode == "stochastic-single":
# single img
self.HERef, _, self.maxCRef = self.__compute_matrices_single(Is[0], Io, alpha, beta)
else:
Expand All @@ -127,4 +128,4 @@ def normalize(self, I, Io=240, alpha=1, beta=0.15, stains=True):
E[E > 255] = 255
E = E.T.reshape(h, w, c).int()

return Inorm, H, E
return Inorm, H, E