From a7194b701b78f2dd530f9d623c162a23ee42c932 Mon Sep 17 00:00:00 2001 From: Carlo Alberto Barbano <carlo.alberto.barbano@outlook.com> Date: Thu, 9 Jan 2025 00:29:33 +0100 Subject: [PATCH 1/5] Bump version to 1.4.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a8b3628..040a55e 100644 --- a/setup.py +++ b/setup.py @@ -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', From eeda0ec4d429294b28192bb08e9dcd6005f5b9f3 Mon Sep 17 00:00:00 2001 From: Carlo Alberto Barbano <carlo.alberto.barbano@outlook.com> Date: Thu, 9 Jan 2025 11:08:47 +0100 Subject: [PATCH 2/5] Update README.md Update citation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 00f2d4e..4e9b62a 100644 --- a/README.md +++ b/README.md @@ -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 From 7b2620e625178c6c9a95ed0ff9970bd9097a8f17 Mon Sep 17 00:00:00 2001 From: Carlo Alberto Barbano <carlo.alberto.barbano@outlook.com> Date: Thu, 9 Jan 2025 11:12:12 +0100 Subject: [PATCH 3/5] Update README.md [no ci] fix tests badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e9b62a..3e6dec7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # torchstain [](https://opensource.org/licenses/MIT) -[](https://github.com/EIDOSLAB/torchstain/actions) +[](https://github.com/EIDOSLAB/torchstain/actions/workflows/tests_full.yml) [](https://pypi.org/project/torchstain/) [](https://doi.org/10.5281/zenodo.7692014) From f268a7848460fbc9ba093ec5ceb46a56f58ea7c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Pedersen?= <andrped94@gmail.com> Date: Sat, 11 Jan 2025 12:02:21 +0100 Subject: [PATCH 4/5] Update base multitarget --- torchstain/base/normalizers/multitarget.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/torchstain/base/normalizers/multitarget.py b/torchstain/base/normalizers/multitarget.py index 779b6df..495569b 100644 --- a/torchstain/base/normalizers/multitarget.py +++ b/torchstain/base/normalizers/multitarget.py @@ -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}") From a52c85caba662a2a148b10c35640711df09ad487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Pedersen?= <andrped94@gmail.com> Date: Sat, 11 Jan 2025 12:05:35 +0100 Subject: [PATCH 5/5] Fixed usage bug of multitarget normalizer --- torchstain/base/normalizers/__init__.py | 1 + torchstain/torch/normalizers/__init__.py | 2 +- torchstain/torch/normalizers/multitarget.py | 17 +++++++++-------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/torchstain/base/normalizers/__init__.py b/torchstain/base/normalizers/__init__.py index a8f8bb7..ee38330 100644 --- a/torchstain/base/normalizers/__init__.py +++ b/torchstain/base/normalizers/__init__.py @@ -1,3 +1,4 @@ from .he_normalizer import HENormalizer from .macenko import MacenkoNormalizer +from .multitarget import MultiMacenkoNormalizer from .reinhard import ReinhardNormalizer diff --git a/torchstain/torch/normalizers/__init__.py b/torchstain/torch/normalizers/__init__.py index c7b3059..c508e04 100644 --- a/torchstain/torch/normalizers/__init__.py +++ b/torchstain/torch/normalizers/__init__.py @@ -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 diff --git a/torchstain/torch/normalizers/multitarget.py b/torchstain/torch/normalizers/multitarget.py index 88ffafc..5ed800a 100644 --- a/torchstain/torch/normalizers/multitarget.py +++ b/torchstain/torch/normalizers/multitarget.py @@ -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) @@ -59,7 +60,7 @@ 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 @@ -67,7 +68,7 @@ def fit(self, Is, Io=240, alpha=1, beta=0.15): 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 @@ -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 @@ -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: @@ -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 \ No newline at end of file + return Inorm, H, E