Skip to content

ratiopath.augmentations.StainAugmentor

Bases: ImageOnlyTransform

Applies stain augmentation to histopathological images.

Reference

Tellez, D., Balkenhol, M., Karssemeijer, N., Litjens, G., van der Laak, J., & Ciompi, F. (2018, March). H&E stain augmentation improves generalization of convolutional networks for histopathological mitosis detection. In Medical Imaging 2018: Digital Pathology (Vol. 10581, pp. 264-270). SPIE. https://geertlitjens.nl/publication/tell-18-a/tell-18-a.pdf

Source code in ratiopath/augmentations/stain_augmentor.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
class StainAugmentor(ImageOnlyTransform):
    """Applies stain augmentation to histopathological images.

    Reference:
        Tellez, D., Balkenhol, M., Karssemeijer, N., Litjens, G.,
        van der Laak, J., & Ciompi, F. (2018, March). H&E stain augmentation improves
        generalization of convolutional networks for histopathological mitosis detection.
        In Medical Imaging 2018: Digital Pathology (Vol. 10581, pp. 264-270). SPIE.
        https://geertlitjens.nl/publication/tell-18-a/tell-18-a.pdf
    """

    def __init__(
        self,
        conv_matrix: Callable[[np.ndarray], np.ndarray] | np.ndarray,
        alpha: float = 0.02,
        beta: float = 0.02,
        **kwargs: Any,
    ) -> None:
        """Initializes StainAugmentor.

        Args:
            conv_matrix (Callable[[np.ndarray], np.ndarray] | np.ndarray): Stain matrix for stain separation.
                Can be a fixed matrix or a callable that returns a matrix from an image.
            alpha (float): Multiplicative factor range for stain augmentation.
            beta (float): Additive factor range for stain augmentation.
            **kwargs (Any): Keyword arguments for ImageOnlyTransform.
        """
        super().__init__(**kwargs)
        self.conv_matrix = conv_matrix
        self.alpha = alpha
        self.beta = beta

        if isinstance(self.conv_matrix, np.ndarray):
            self.inv_conv_matrix = np.linalg.inv(self.conv_matrix)

    def apply(
        self,
        img: np.ndarray,
        conv_matrix: np.ndarray,
        inv_conv_matrix: np.ndarray,
        alphas: list[float],
        betas: list[float],
        **params: dict[str, Any],
    ) -> np.ndarray:
        stains = separate_stains(img, inv_conv_matrix)

        for i in range(stains.shape[-1]):
            stains[..., i] *= alphas[i]
            stains[..., i] += betas[i]

        return np.astype(combine_stains(stains, conv_matrix) * 255, np.uint8)

    def get_params_dependent_on_data(
        self, params: dict[str, Any], data: dict[str, Any]
    ) -> dict[str, Any]:
        conv_matrix = (
            self.conv_matrix(data["image"])
            if callable(self.conv_matrix)
            else self.conv_matrix
        )
        inv_conv_matrix = (
            self.inv_conv_matrix
            if hasattr(self, "inv_conv_matrix")
            else np.linalg.inv(conv_matrix)
        )

        return {
            "conv_matrix": conv_matrix,
            "inv_conv_matrix": inv_conv_matrix,
            "alphas": [
                self.py_random.uniform(1 - self.alpha, 1 + self.alpha)
                for _ in range(conv_matrix.shape[-1])
            ],
            "betas": [
                self.py_random.uniform(-self.beta, self.beta)
                for _ in range(conv_matrix.shape[-1])
            ],
        }

alpha = alpha instance-attribute

beta = beta instance-attribute

conv_matrix = conv_matrix instance-attribute

inv_conv_matrix = np.linalg.inv(self.conv_matrix) instance-attribute

__init__(conv_matrix, alpha=0.02, beta=0.02, **kwargs)

Initializes StainAugmentor.

Parameters:

Name Type Description Default
conv_matrix Callable[[ndarray], ndarray] | ndarray

Stain matrix for stain separation. Can be a fixed matrix or a callable that returns a matrix from an image.

required
alpha float

Multiplicative factor range for stain augmentation.

0.02
beta float

Additive factor range for stain augmentation.

0.02
**kwargs Any

Keyword arguments for ImageOnlyTransform.

{}
Source code in ratiopath/augmentations/stain_augmentor.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def __init__(
    self,
    conv_matrix: Callable[[np.ndarray], np.ndarray] | np.ndarray,
    alpha: float = 0.02,
    beta: float = 0.02,
    **kwargs: Any,
) -> None:
    """Initializes StainAugmentor.

    Args:
        conv_matrix (Callable[[np.ndarray], np.ndarray] | np.ndarray): Stain matrix for stain separation.
            Can be a fixed matrix or a callable that returns a matrix from an image.
        alpha (float): Multiplicative factor range for stain augmentation.
        beta (float): Additive factor range for stain augmentation.
        **kwargs (Any): Keyword arguments for ImageOnlyTransform.
    """
    super().__init__(**kwargs)
    self.conv_matrix = conv_matrix
    self.alpha = alpha
    self.beta = beta

    if isinstance(self.conv_matrix, np.ndarray):
        self.inv_conv_matrix = np.linalg.inv(self.conv_matrix)

apply(img, conv_matrix, inv_conv_matrix, alphas, betas, **params)

Source code in ratiopath/augmentations/stain_augmentor.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def apply(
    self,
    img: np.ndarray,
    conv_matrix: np.ndarray,
    inv_conv_matrix: np.ndarray,
    alphas: list[float],
    betas: list[float],
    **params: dict[str, Any],
) -> np.ndarray:
    stains = separate_stains(img, inv_conv_matrix)

    for i in range(stains.shape[-1]):
        stains[..., i] *= alphas[i]
        stains[..., i] += betas[i]

    return np.astype(combine_stains(stains, conv_matrix) * 255, np.uint8)

get_params_dependent_on_data(params, data)

Source code in ratiopath/augmentations/stain_augmentor.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def get_params_dependent_on_data(
    self, params: dict[str, Any], data: dict[str, Any]
) -> dict[str, Any]:
    conv_matrix = (
        self.conv_matrix(data["image"])
        if callable(self.conv_matrix)
        else self.conv_matrix
    )
    inv_conv_matrix = (
        self.inv_conv_matrix
        if hasattr(self, "inv_conv_matrix")
        else np.linalg.inv(conv_matrix)
    )

    return {
        "conv_matrix": conv_matrix,
        "inv_conv_matrix": inv_conv_matrix,
        "alphas": [
            self.py_random.uniform(1 - self.alpha, 1 + self.alpha)
            for _ in range(conv_matrix.shape[-1])
        ],
        "betas": [
            self.py_random.uniform(-self.beta, self.beta)
            for _ in range(conv_matrix.shape[-1])
        ],
    }