Source code for slideflow.slide.qc.gaussian
"""Gaussian filter QC algorithm."""
import numpy as np
import slideflow as sf
import skimage
from slideflow import errors
from typing import Union, Optional
# -----------------------------------------------------------------------------
[docs]class Gaussian:
def __init__(
self,
mpp: Optional[float] = None,
sigma: int = 3,
threshold: float = 0.02
) -> None:
"""Prepare Gaussian filtering algorithm for filtering a slide.
This method is used to remove out-of-focus areas and pen marks.
This QC method works by obtaining a thumbnail of a slide, and converting
the image into grayspace. A gaussian filter with a given sigma
(default=3) is calculated using scikit-image. Areas with blur below
the given threshold (default=0.02) are filtered out.
Examples
Apply Gaussian filtering to a slide.
.. code-block:: python
import slideflow as sf
from slideflow.slide import qc
wsi = sf.WSI(...)
gaussian = qc.Gaussian()
wsi.qc(gaussian)
Args:
mpp (float): Microns-per-pixel at which to perform filtering.
Defaults to 4 times the tile extraction MPP (e.g. for a
tile_px/tile_um combination at 10X effective magnification,
where tile_px=tile_um, the default blur_mpp would be 4, or
effective magnification 2.5x).
sigma (int): Sigma (radius) for Gaussian filter. Defaults to 3.
threshold (float): Gaussian threshold. Defaults to 0.02.
"""
self.mpp = mpp
self.sigma = sigma
self.threshold = threshold
def __repr__(self):
return "Gaussian(mpp={!r}, sigma={!r}, threshold={!r})".format(
self.mpp, self.sigma, self.threshold
)
def _thumb_from_slide(
self,
wsi: "sf.WSI"
) -> np.ndarray:
"""Get a thumbnail from the given slide.
Args:
wsi (sf.WSI): Whole-slide image.
Returns:
np.ndarray: RGB thumbnail of the whole-slide image.
"""
if self.mpp is None:
_mpp = (wsi.tile_um/wsi.tile_px)*4
sf.log.info(f"Performing Gaussian blur filter at mpp={_mpp:.3f}")
else:
_mpp = self.mpp
thumb = wsi.thumb(mpp=_mpp)
if thumb is None:
raise errors.QCError(
f"Thumbnail error for slide {wsi.shortname}, QC failed"
)
thumb = np.array(thumb)
if thumb.shape[-1] == 4:
thumb = thumb[:, :, :3]
return thumb
def __call__(
self,
wsi: Union["sf.WSI", np.ndarray],
mask: Optional[np.ndarray] = None,
) -> np.ndarray:
"""Perform Gaussian filtering on the given slide or image.
Args:
slide (sf.WSI, np.ndarray): Either a Slideflow WSI or a numpy array,
with shape (h, w, c) and type np.uint8.
mask (np.ndarray): Restrict Otsu's threshold to the area of the
image indicated by this boolean mask. Defaults to None.
Returns:
np.ndarray: QC boolean mask, where True = filtered out.
"""
if isinstance(wsi, sf.WSI):
thumb = self._thumb_from_slide(wsi)
else:
thumb = wsi
gray = skimage.color.rgb2gray(thumb)
img_laplace = np.abs(skimage.filters.laplace(gray))
gaussian = skimage.filters.gaussian(img_laplace, sigma=self.sigma)
blur_mask = gaussian <= self.threshold
# Assign blur burden value
existing_qc_mask = mask
if mask is None and isinstance(wsi, sf.WSI):
existing_qc_mask = wsi.qc_mask
if existing_qc_mask is not None and isinstance(wsi, sf.WSI):
wsi.blur_burden = blur_burden(blur_mask, existing_qc_mask)
sf.log.debug(f"Blur burden: {wsi.blur_burden}")
return blur_mask
# -----------------------------------------------------------------------------
def blur_burden(blur_mask, existing_mask):
blur_mask = skimage.transform.resize(blur_mask, existing_mask.shape)
blur_mask = blur_mask.astype(bool)
blur = np.count_nonzero(
np.logical_and(
blur_mask,
np.logical_xor(blur_mask, existing_mask)
)
)
return blur / (blur_mask.shape[0] * blur_mask.shape[1])