From b3a6603d465f75d7b4f41663d2580f10f52b4d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Pedersen?= Date: Sat, 28 Jan 2023 13:13:46 +0100 Subject: [PATCH 1/3] moved implementations table to top of README --- README.md | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 74af142..f7483e6 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,11 @@ GPU-accelerated stain normalization tools for histopathological images. Compatible with PyTorch, TensorFlow, and Numpy. Normalization algorithms currently implemented: -- Macenko [\[1\]](#reference) (ported from [numpy implementation](https://github.com/schaugf/HEnorm_python)) -- Reinhard [\[2\]](#reference) -- Modified Reinhard [\[3\]](#reference) +| Algorithm | numpy | torch | tensorflow | +|-|-|-|-| +| Macenko [\[1\]](#reference) | ✓ | ✓ | ✓ | +| Reinhard [\[2\]](#reference) | ✓ | ✓ | ✓ | +| Modified Reinhard [\[3\]](#reference) | ✓ | ✓ | ✓ | ## Installation @@ -45,14 +47,6 @@ norm, H, E = normalizer.normalize(I=t_to_transform, stains=True) ![alt text](data/result.png) -## Implemented algorithms - -| Algorithm | numpy | torch | tensorflow | -|-|-|-|-| -| Macenko | ✓ | ✓ | ✓ | -| Reinhard | ✓ | ✓ | ✓ | -| Modified Reinhard | ✓ | ✓ | ✓ | - ## Backend comparison Results with 10 runs per size on a Intel(R) Core(TM) i5-8365U CPU @ 1.60GHz From 1076efe53c7edef85285d56a2537dfa8b5c7dc5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Pedersen?= Date: Sat, 28 Jan 2023 13:16:57 +0100 Subject: [PATCH 2/3] refactor: adjusted minor inconsistencies in README + specified macenko runtimes in backend comparison [no ci] --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f7483e6..f7b8a60 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ norm, H, E = normalizer.normalize(I=t_to_transform, stains=True) ## Backend comparison -Results with 10 runs per size on a Intel(R) Core(TM) i5-8365U CPU @ 1.60GHz +Macenko runtime results using different backends with 10 runs per image size on a Intel(R) Core(TM) i5-8365U CPU @ 1.60GHz. | size | numpy avg. time | torch avg. time | tf avg. time | |--------|-------------------|-------------------|------------------| @@ -66,7 +66,7 @@ Results with 10 runs per size on a Intel(R) Core(TM) i5-8365U CPU @ 1.60GHz - [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. +- [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. ## Citing @@ -74,8 +74,7 @@ If you find this software useful for your research, please cite it as: ```bibtex @software{barbano2022torchstain, - author = {Carlo Alberto Barbano and - AndrĂ© Pedersen}, + author = {Carlo Alberto Barbano and AndrĂ© Pedersen}, title = {EIDOSLAB/torchstain: v1.2.0-stable}, month = aug, year = 2022, From aaedd68cdbfd80a1d44f27e327dabc1a9c74aba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Pedersen?= Date: Sat, 28 Jan 2023 14:04:16 +0100 Subject: [PATCH 3/3] refactor: corrected for inconsistencies in code --- compare.py | 1 - example.py | 1 - torchstain/numpy/normalizers/macenko.py | 32 ++++++++++++------------- torchstain/tf/normalizers/macenko.py | 7 +++--- torchstain/torch/normalizers/macenko.py | 4 ++-- torchstain/torch/utils/rgb2lab.py | 4 ++-- 6 files changed, 23 insertions(+), 26 deletions(-) diff --git a/compare.py b/compare.py index cacab8c..8c9e527 100644 --- a/compare.py +++ b/compare.py @@ -1,7 +1,6 @@ import torch from torchvision import transforms import torchstain - import cv2 import matplotlib.pyplot as plt import time diff --git a/example.py b/example.py index 98ba5b9..0da14cb 100644 --- a/example.py +++ b/example.py @@ -5,7 +5,6 @@ from torchvision import transforms import time - size = 1024 target = cv2.resize(cv2.cvtColor(cv2.imread("./data/target.png"), cv2.COLOR_BGR2RGB), (size, size)) to_transform = cv2.resize(cv2.cvtColor(cv2.imread("./data/source.png"), cv2.COLOR_BGR2RGB), (size, size)) diff --git a/torchstain/numpy/normalizers/macenko.py b/torchstain/numpy/normalizers/macenko.py index faada97..0ede5eb 100644 --- a/torchstain/numpy/normalizers/macenko.py +++ b/torchstain/numpy/normalizers/macenko.py @@ -10,13 +10,13 @@ def __init__(self): super().__init__() self.HERef = np.array([[0.5626, 0.2159], - [0.7201, 0.8012], - [0.4062, 0.5581]]) + [0.7201, 0.8012], + [0.4062, 0.5581]]) self.maxCRef = np.array([1.9705, 1.0308]) def __convert_rgb2od(self, I, Io=240, beta=0.15): # calculate optical density - OD = -np.log((I.astype(float)+1)/Io) + OD = -np.log((I.astype(float) + 1) / Io) # remove transparent pixels ODhat = OD[~np.any(OD < beta, axis=1)] @@ -26,22 +26,22 @@ def __convert_rgb2od(self, I, Io=240, beta=0.15): def __find_HE(self, ODhat, eigvecs, alpha): #project on the plane spanned by the eigenvectors corresponding to the two # largest eigenvalues - That = ODhat.dot(eigvecs[:,1:3]) + That = ODhat.dot(eigvecs[:, 1:3]) - phi = np.arctan2(That[:,1],That[:,0]) + phi = np.arctan2(That[:, 1], That[:, 0]) minPhi = np.percentile(phi, alpha) - maxPhi = np.percentile(phi, 100-alpha) + maxPhi = np.percentile(phi, 100 - alpha) - vMin = eigvecs[:,1:3].dot(np.array([(np.cos(minPhi), np.sin(minPhi))]).T) - vMax = eigvecs[:,1:3].dot(np.array([(np.cos(maxPhi), np.sin(maxPhi))]).T) + vMin = eigvecs[:, 1:3].dot(np.array([(np.cos(minPhi), np.sin(minPhi))]).T) + vMax = eigvecs[:, 1:3].dot(np.array([(np.cos(maxPhi), np.sin(maxPhi))]).T) # a heuristic to make the vector corresponding to hematoxylin first and the # one corresponding to eosin second if vMin[0] > vMax[0]: - HE = np.array((vMin[:,0], vMax[:,0])).T + HE = np.array((vMin[:, 0], vMax[:, 0])).T else: - HE = np.array((vMax[:,0], vMin[:,0])).T + HE = np.array((vMax[:, 0], vMin[:, 0])).T return HE @@ -55,7 +55,7 @@ def __find_concentration(self, OD, HE): return C def __compute_matrices(self, I, Io, alpha, beta): - I = I.reshape((-1,3)) + I = I.reshape((-1, 3)) OD, ODhat = self.__convert_rgb2od(I, Io=Io, beta=beta) @@ -67,7 +67,7 @@ def __compute_matrices(self, I, Io, alpha, beta): C = self.__find_concentration(OD, HE) # normalize stain concentrations - maxC = np.array([np.percentile(C[0,:], 99), np.percentile(C[1,:],99)]) + maxC = np.array([np.percentile(C[0, :], 99), np.percentile(C[1, :],99)]) return HE, C, maxC @@ -81,7 +81,7 @@ def normalize(self, I, Io=240, alpha=1, beta=0.15, stains=True): ''' Normalize staining appearence of H&E stained images Example use: - see test.py + see example.py Input: I: RGB input image @@ -97,7 +97,7 @@ def normalize(self, I, Io=240, alpha=1, beta=0.15, stains=True): Macenko et al., ISBI 2009 ''' h, w, c = I.shape - I = I.reshape((-1,3)) + I = I.reshape((-1, 3)) HE, C, maxC = self.__compute_matrices(I, Io, alpha, beta) @@ -114,11 +114,11 @@ def normalize(self, I, Io=240, alpha=1, beta=0.15, stains=True): if stains: # unmix hematoxylin and eosin - H = np.multiply(Io, np.exp(np.expand_dims(-self.HERef[:,0], axis=1).dot(np.expand_dims(C2[0,:], axis=0)))) + H = np.multiply(Io, np.exp(np.expand_dims(-self.HERef[:, 0], axis=1).dot(np.expand_dims(C2[0, :], axis=0)))) H[H > 255] = 255 H = np.reshape(H.T, (h, w, c)).astype(np.uint8) - E = np.multiply(Io, np.exp(np.expand_dims(-self.HERef[:,1], axis=1).dot(np.expand_dims(C2[1,:], axis=0)))) + E = np.multiply(Io, np.exp(np.expand_dims(-self.HERef[:, 1], axis=1).dot(np.expand_dims(C2[1, :], axis=0)))) E[E > 255] = 255 E = np.reshape(E.T, (h, w, c)).astype(np.uint8) diff --git a/torchstain/tf/normalizers/macenko.py b/torchstain/tf/normalizers/macenko.py index bf21666..e44101b 100644 --- a/torchstain/tf/normalizers/macenko.py +++ b/torchstain/tf/normalizers/macenko.py @@ -4,7 +4,6 @@ import numpy as np import tensorflow.keras.backend as K - """ Source code ported from: https://github.com/schaugf/HEnorm_python Original implementation: https://github.com/mitkovetta/staining-normalization @@ -14,8 +13,8 @@ def __init__(self): super().__init__() self.HERef = tf.constant([[0.5626, 0.2159], - [0.7201, 0.8012], - [0.4062, 0.5581]]) + [0.7201, 0.8012], + [0.4062, 0.5581]]) self.maxCRef = tf.constant([1.9705, 1.0308]) def __convert_rgb2od(self, I, Io, beta): @@ -78,7 +77,7 @@ def normalize(self, I, Io=240, alpha=1, beta=0.15, stains=True): ''' Normalize staining appearence of H&E stained images Example use: - see test.py + see example.py Input: I: RGB input image: tensor of shape [C, H, W] and type uint8 diff --git a/torchstain/torch/normalizers/macenko.py b/torchstain/torch/normalizers/macenko.py index b04cd28..e8b2059 100644 --- a/torchstain/torch/normalizers/macenko.py +++ b/torchstain/torch/normalizers/macenko.py @@ -20,7 +20,7 @@ def __convert_rgb2od(self, I, Io, beta): I = I.permute(1, 2, 0) # calculate optical density - OD = -torch.log((I.reshape((-1, I.shape[-1])).float() + 1)/Io) + OD = -torch.log((I.reshape((-1, I.shape[-1])).float() + 1) / Io) # remove transparent pixels ODhat = OD[~torch.any(OD < beta, dim=1)] @@ -79,7 +79,7 @@ def normalize(self, I, Io=240, alpha=1, beta=0.15, stains=True): ''' Normalize staining appearence of H&E stained images Example use: - see test.py + see example.py Input: I: RGB input image: tensor of shape [C, H, W] and type uint8 diff --git a/torchstain/torch/utils/rgb2lab.py b/torchstain/torch/utils/rgb2lab.py index 3b1aa50..1650b33 100644 --- a/torchstain/torch/utils/rgb2lab.py +++ b/torchstain/torch/utils/rgb2lab.py @@ -2,8 +2,8 @@ # constant conversion matrices between color spaces: https://gist.github.com/bikz05/6fd21c812ef6ebac66e1 _rgb2xyz = torch.tensor([[0.412453, 0.357580, 0.180423], - [0.212671, 0.715160, 0.072169], - [0.019334, 0.119193, 0.950227]]) + [0.212671, 0.715160, 0.072169], + [0.019334, 0.119193, 0.950227]]) _white = torch.tensor([0.95047, 1., 1.08883])