Skill Tracer
Skills › Scientific Agent Skills › histolab › tissue_masks.md

tissue_masks.md

142 lines · skills/histolab/references/tissue_masks.md at 5f2bc03

Open on GitHub
1# Tissue masks (Histolab 0.7.0)
2
3Source: [0.7.0 masks](https://github.com/histolab/histolab/blob/v0.7.0/histolab/masks.py)
4and [default compositions](https://github.com/histolab/histolab/blob/v0.7.0/histolab/filters/compositions.py).
5

Tissue masks (Histolab 0.7.0)

Source: 0.7.0 masks and default compositions.

Raw lines 1 to 5
6## Choose a mask
7
8- `TissueMask()` keeps the detected tissue across all sections.
9- `BiggestTissueBoxMask()` selects the bounding box of the largest connected
10 region. It is the tiler default, and the rectangle can include background.
11- `BinaryMask` is the abstract base for custom masks. Implement `_mask(slide)`
12 and return a two-dimensional Boolean array, not a grayscale image.
14The slide default pipeline is grayscale, Otsu (darker pixels), dilation,
15small-hole filling and small-object removal. Tile defaults differ: smaller
16dilation and hole filling. Neither pipeline guarantees removal of folds,
17bubbles, pen ink or all non-tissue artifacts. Review masks on varied stains.

Choose a mask

  • TissueMask() keeps the detected tissue across all sections.
  • BiggestTissueBoxMask() selects the bounding box of the largest connected region. It is the tiler default, and the rectangle can include background.
  • BinaryMask is the abstract base for custom masks. Implement _mask(slide) and return a two-dimensional Boolean array, not a grayscale image.

The slide default pipeline is grayscale, Otsu (darker pixels), dilation, small-hole filling and small-object removal. Tile defaults differ: smaller dilation and hole filling. Neither pipeline guarantees removal of folds, bubbles, pen ink or all non-tissue artifacts. Review masks on varied stains.

Raw lines 6 to 18
19## Custom filters are positional
21<!-- recipe: custom-mask -->
22```python
23from histolab.masks import TissueMask
24from histolab.filters.image_filters import RgbToGrayscale, OtsuThreshold
25from histolab.filters.morphological_filters import (
26 BinaryDilation, RemoveSmallHoles, RemoveSmallObjects,
29mask_filters = [
30 RgbToGrayscale(), OtsuThreshold(), BinaryDilation(disk_size=2),
31 RemoveSmallHoles(area_threshold=100),
32 RemoveSmallObjects(min_size=64, avoid_overmask=False),
34custom_mask = TissueMask(*mask_filters)
35```
37`TissueMask(filters=...)` is invalid. A `Compose` object can also be supplied as
38one positional callable. Area thresholds are **mask pixels**, not level-0 pixels
39or square micrometers. `RemoveSmallObjects` normally retries with smaller
40thresholds when the mask removes too much (`avoid_overmask=True`); disable that
41behavior when a fixed threshold is scientifically required.

Custom filters are positional

<!-- recipe: custom-mask -->

from histolab.masks import TissueMask
from histolab.filters.image_filters import RgbToGrayscale, OtsuThreshold
from histolab.filters.morphological_filters import (
    BinaryDilation, RemoveSmallHoles, RemoveSmallObjects,
)

mask_filters = [
    RgbToGrayscale(), OtsuThreshold(), BinaryDilation(disk_size=2),
    RemoveSmallHoles(area_threshold=100),
    RemoveSmallObjects(min_size=64, avoid_overmask=False),
]
custom_mask = TissueMask(*mask_filters)

TissueMask(filters=...) is invalid. A Compose object can also be supplied as one positional callable. Area thresholds are mask pixels, not level-0 pixels or square micrometers. RemoveSmallObjects normally retries with smaller thresholds when the mask removes too much (avoid_overmask=True); disable that behavior when a fixed threshold is scientifically required.

Raw lines 19 to 42
43## Mask coordinate frame and display
45In 0.7.0, slide masks use whichever has more pixels: `slide.thumbnail` or
46`slide.scaled_image(scale_factor=32)`. Do not assume that a mask matches the
47thumbnail size. This function reproduces the release's choice for custom masks:
49<!-- recipe: mask-image -->
50```python
51def mask_image(slide):
52 thumbnail = slide.thumbnail
53 scaled = slide.scaled_image(scale_factor=32)
54 return thumbnail if thumbnail.width * thumbnail.height > scaled.width * scaled.height else scaled
55```
57`slide.locate_mask(mask)` returns a Pillow preview; it does not open a window.
58Save the returned image, or use `plt.imshow(...)` or notebook `display(...)`.
59For exact categorical overlays, resize the Boolean mask with nearest-neighbor
60interpolation; see the visualization reference.

Mask coordinate frame and display

In 0.7.0, slide masks use whichever has more pixels: slide.thumbnail or slide.scaled_image(scale_factor=32). Do not assume that a mask matches the thumbnail size. This function reproduces the release's choice for custom masks:

<!-- recipe: mask-image -->

def mask_image(slide):
    thumbnail = slide.thumbnail
    scaled = slide.scaled_image(scale_factor=32)
    return thumbnail if thumbnail.width * thumbnail.height > scaled.width * scaled.height else scaled

slide.locate_mask(mask) returns a Pillow preview; it does not open a window. Save the returned image, or use plt.imshow(...) or notebook display(...). For exact categorical overlays, resize the Boolean mask with nearest-neighbor interpolation; see the visualization reference.

Raw lines 43 to 61
62## Level-0 rectangular ROI
64This reusable class converts a level-0 box to the actual mask image grid.
65Intersect it with a tissue mask when the rectangle alone includes background.
66ROI examples must use the same slide orientation as the underlying pixels.
68<!-- recipe: rectangular-mask -->
69```python
70import math
71import numpy as np
72from histolab.masks import BinaryMask, TissueMask
73from histolab.types import CoordinatePair
75class RectangularMask(BinaryMask):
76 def __init__(self, bounds):
77 self.bounds = bounds
79 def _mask(self, slide):
80 image = mask_image(slide)
81 width, height = slide.dimensions
82 x0, y0, x1, y1 = self.bounds
83 if not (0 <= x0 < x1 <= width and 0 <= y0 < y1 <= height):
84 raise ValueError("ROI must be a nonempty box within level-0 slide bounds")
85 left = math.floor(x0 * image.width / width)
86 top = math.floor(y0 * image.height / height)
87 right = math.ceil(x1 * image.width / width)
88 bottom = math.ceil(y1 * image.height / height)
89 roi = np.zeros((image.height, image.width), dtype=bool)
90 roi[top:bottom, left:right] = True
91 return roi
93roi_mask = RectangularMask(CoordinatePair(100, 200, 1000, 1200))
94```
96Rasterization can expand the ROI by a mask pixel at its boundary; retain original
97level-0 bounds and validate saved tile coordinates if strict containment is
98required. Histolab's grid mask test and tissue fraction test are separate gates.

Level-0 rectangular ROI

This reusable class converts a level-0 box to the actual mask image grid. Intersect it with a tissue mask when the rectangle alone includes background. ROI examples must use the same slide orientation as the underlying pixels.

<!-- recipe: rectangular-mask -->

import math
import numpy as np
from histolab.masks import BinaryMask, TissueMask
from histolab.types import CoordinatePair

class RectangularMask(BinaryMask):
    def __init__(self, bounds):
        self.bounds = bounds

    def _mask(self, slide):
        image = mask_image(slide)
        width, height = slide.dimensions
        x0, y0, x1, y1 = self.bounds
        if not (0 <= x0 < x1 <= width and 0 <= y0 < y1 <= height):
            raise ValueError("ROI must be a nonempty box within level-0 slide bounds")
        left = math.floor(x0 * image.width / width)
        top = math.floor(y0 * image.height / height)
        right = math.ceil(x1 * image.width / width)
        bottom = math.ceil(y1 * image.height / height)
        roi = np.zeros((image.height, image.width), dtype=bool)
        roi[top:bottom, left:right] = True
        return roi

roi_mask = RectangularMask(CoordinatePair(100, 200, 1000, 1200))

Rasterization can expand the ROI by a mask pixel at its boundary; retain original level-0 bounds and validate saved tile coordinates if strict containment is required. Histolab's grid mask test and tissue fraction test are separate gates.

Raw lines 62 to 99
100## Exclude pen-colored pixels
102Histolab's native pen filters return RGB images with detected ink pixels set to
103black. Compare those images with the original to obtain an exclusion mask in
104the same image frame:
106<!-- recipe: pen-mask -->
107```python
108from histolab.masks import BinaryMask, TissueMask
109from histolab.filters.image_filters import Compose, BluePenFilter, GreenPenFilter, RedPenFilter
110import numpy as np
112class AnnotationExclusionMask(BinaryMask):
113 def _mask(self, slide):
114 image = mask_image(slide)
115 tissue = TissueMask()(slide)
116 cleaned = Compose([BluePenFilter(), GreenPenFilter(), RedPenFilter()])(image)
117 changed = np.any(np.asarray(cleaned) != np.asarray(image), axis=2)
118 return tissue & ~changed
119```
121These color heuristics can reject valid blue/purple H&E tissue. They are an
122illustrative starting point, not a validated annotation detector. A separately
123supplied annotation polygon must be registered to level-0 coordinates and
124rasterized to the same mask grid before Boolean combination. Excluding a few
125mask pixels does not ensure that a saved tile has zero ink; inspect extracted
126tiles or enforce a tile-level exclusion criterion as well.

Exclude pen-colored pixels

Histolab's native pen filters return RGB images with detected ink pixels set to black. Compare those images with the original to obtain an exclusion mask in the same image frame:

<!-- recipe: pen-mask -->

from histolab.masks import BinaryMask, TissueMask
from histolab.filters.image_filters import Compose, BluePenFilter, GreenPenFilter, RedPenFilter
import numpy as np

class AnnotationExclusionMask(BinaryMask):
    def _mask(self, slide):
        image = mask_image(slide)
        tissue = TissueMask()(slide)
        cleaned = Compose([BluePenFilter(), GreenPenFilter(), RedPenFilter()])(image)
        changed = np.any(np.asarray(cleaned) != np.asarray(image), axis=2)
        return tissue & ~changed

These color heuristics can reject valid blue/purple H&E tissue. They are an illustrative starting point, not a validated annotation detector. A separately supplied annotation polygon must be registered to level-0 coordinates and rasterized to the same mask grid before Boolean combination. Excluding a few mask pixels does not ensure that a saved tile has zero ink; inspect extracted tiles or enforce a tile-level exclusion criterion as well.

Raw lines 100 to 127
128## Use the same mask for preview and extraction
130```python
131from histolab.tiler import RandomTiler
132from histolab.masks import TissueMask
134mask = TissueMask()
135tiler = RandomTiler(tile_size=(512, 512), n_tiles=100, level=0, seed=42)
136preview = tiler.locate_tiles(slide, extraction_mask=mask)
137preview.save("locations.png")
138tiler.extract(slide, extraction_mask=mask)
139```
141Do not place `extraction_mask` in the tiler constructor. If extraction fails,
142check for an empty mask and inspect the actual slide image before lowering QC.

Use the same mask for preview and extraction

from histolab.tiler import RandomTiler
from histolab.masks import TissueMask

mask = TissueMask()
tiler = RandomTiler(tile_size=(512, 512), n_tiles=100, level=0, seed=42)
preview = tiler.locate_tiles(slide, extraction_mask=mask)
preview.save("locations.png")
tiler.extract(slide, extraction_mask=mask)

Do not place extraction_mask in the tiler constructor. If extraction fails, check for an empty mask and inspect the actual slide image before lowering QC.

Raw lines 128 to 142