Skip to content

Commit

Permalink
Merge pull request #64 from andreped/fix/multitarget
Browse files Browse the repository at this point in the history
Fixed bug in multitarget macenko during import
  • Loading branch information
carloalbertobarbano authored Jan 13, 2025
2 parents 5ccc187 + a52c85c commit 6c88302
Show file tree
Hide file tree
Showing 6 changed files with 23 additions and 17 deletions.
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

0 comments on commit 6c88302

Please sign in to comment.