Source code for glasscut.tile

from pathlib import Path
from typing import Any, cast
import os

import numpy as np
from PIL import Image

from glasscut.utils import lazyproperty
from glasscut.tissue_detectors import TissueDetector, OtsuTissueDetector


[docs] class Tile: """Provide Tile object representing a tile generated from a Slide object. Arguments --------- image : Image.Image Image describing the tile coords : tuple[int, int] Coordinates (x, y) of the tile at level 0 (upper-left corner) magnification : int | float Magnification at which the tile was extracted tissue_detector : TissueDetector | None, optional Strategy for tissue detection. Defaults to OtsuTissueDetector. """
[docs] def __init__( self, image: Image.Image, coords: tuple[int, int] | None, magnification: int | float | None, tissue_detector: TissueDetector | None = None, **kwargs: Any, ) -> None: """Initialize a Tile. Parameters ---------- image : Image.Image The tile image in RGB format coords : tuple[int, int] | None Coordinates (x, y) of the tile at level 0. Can be None for utility tiles. magnification : int | float | None Magnification at which the tile was extracted. Can be None for utility tiles. tissue_detector : TissueDetector | None, optional Strategy for detecting tissue. If None, uses OtsuTissueDetector. **kwargs : Any Optional keyword-only extensions. Supported key: ``precomputed_tissue_ratio`` (float), used to skip recomputing tissue ratio. """ precomputed_tissue_ratio = kwargs.pop("precomputed_tissue_ratio", None) if kwargs: unknown = ", ".join(sorted(kwargs)) raise TypeError(f"Unexpected keyword arguments for Tile: {unknown}") self.image = image self.coords = coords self.magnification = magnification self.tissue_detector = tissue_detector or OtsuTissueDetector() self._precomputed_tissue_ratio = ( float(precomputed_tissue_ratio) if precomputed_tissue_ratio is not None else None )
[docs] def set_precomputed_tissue_ratio(self, tissue_ratio: float) -> None: """Set a precomputed tissue ratio to avoid re-running detection.""" self._precomputed_tissue_ratio = tissue_ratio
[docs] def has_enough_tissue( self, tissue_threshold: float = 0.2, ) -> bool: """Check if the tile has enough tissue. This method checks if the proportion of the detected tissue over the total area of the tile is above a specified threshold (by default 20%). Parameters ---------- tissue_threshold : float, optional Number between 0.0 and 1.0 representing the minimum required percentage of tissue over the total area of the image, default is 0.2 Returns ------- enough_tissue : bool Whether the image has enough tissue, i.e. if the proportion of tissue over the total area of the image is more than ``tissue_threshold``. """ tissue_ratio = cast(float, np.mean(self.tissue_mask)) return tissue_ratio > tissue_threshold
[docs] def save(self, path: str | Path) -> None: """Save tile at given path. The format to use is determined from the filename extension (to be compatible to PIL.Image formats). If no extension is provided, the image will be saved in png format. Parameters --------- path: str or pathlib.Path Path to which the tile is saved. """ ext = os.path.splitext(path)[1] if not ext: path = f"{path}.png" Path(path).parent.mkdir(parents=True, exist_ok=True) self.image.save(path)
@lazyproperty def tissue_mask(self) -> np.ndarray: """Binary mask representing the tissue in the tile. The mask is computed using the configured tissue detector strategy. Returns ------- np.ndarray Binary mask representing the tissue in the tile (dtype: uint8, values 0 or 1) """ return self.tissue_detector.detect(self.image) @lazyproperty def tissue_ratio(self) -> float: """Ratio of the tissue area over the total area of the tile. Returns ------- float Ratio of the tissue area over the total area of the tile """ if self._precomputed_tissue_ratio is not None: return float(self._precomputed_tissue_ratio) tissue_ratio = np.count_nonzero(self.tissue_mask) / self.tissue_mask.size return tissue_ratio