|
@staticmethod |
|
def filter_coordinates( |
|
mask_reader: VirtualWSIReader, |
|
bounds: np.ndarray, |
|
resolution: Resolution | None = None, |
|
units: Units | None = None, |
|
) -> np.ndarray: |
|
"""Indicates which coordinate is valid basing on the mask. |
|
|
|
To use your own approaches, either subclass to overwrite or |
|
directly assign your own function to this name. In either cases, |
|
the function must obey the API defined here. |
|
|
|
Args: |
|
mask_reader (:class:`.VirtualReader`): |
|
A virtual pyramidal reader of the mask related to the |
|
WSI from which we want to extract the patches. |
|
bounds (ndarray and np.int32): |
|
Coordinates to be checked via the `func`. They must be |
|
in the same resolution as requested `resolution` and |
|
`units`. The shape of `coordinates` is (N, K) where N is |
|
the number of coordinate sets and K is either 2 for |
|
centroids or 4 for bounding boxes. When using the |
|
default `func=None`, K should be 4, as we expect the |
|
`coordinates` to be bounding boxes in `[start_x, |
|
start_y, end_x, end_y]` format. |
|
resolution (Resolution): |
|
Resolution of the requested patch. |
|
units (Units): |
|
Units of the requested patch. |
|
|
|
Returns: |
|
:class:`numpy.ndarray`: |
|
List of flags to indicate which coordinate is valid. |
|
|
|
Examples: |
|
>>> # API of function expected to overwrite `filter_coordinates` |
|
>>> def func(reader, bounds, resolution, units): |
|
... # as example, only select first bound |
|
... return np.array([1, 0]) |
|
>>> coords = [[0, 0, 256, 256], [128, 128, 384, 384]] |
|
>>> segmentor = SemanticSegmentor(model='unet') |
|
>>> segmentor.filter_coordinates = func |
|
|
|
""" |
|
if not isinstance(mask_reader, VirtualWSIReader): |
|
msg = "`mask_reader` should be VirtualWSIReader." |
|
raise TypeError(msg) |
|
|
|
if not isinstance(bounds, np.ndarray) or not np.issubdtype( |
|
bounds.dtype, |
|
np.integer, |
|
): |
|
msg = "`coordinates` should be ndarray of integer type." |
|
raise ValueError(msg) |
|
|
|
mask_real_shape = mask_reader.img.shape[:2] |
|
mask_resolution_shape = mask_reader.slide_dimensions( |
|
resolution=resolution, |
|
units=units, |
|
)[::-1] |
|
mask_real_shape = np.array(mask_real_shape) |
|
mask_resolution_shape = np.array(mask_resolution_shape) |
|
scale_factor = mask_real_shape / mask_resolution_shape |
|
scale_factor = scale_factor[0] # what if ratio x != y |
|
|
|
def sel_func(coord: np.ndarray) -> bool: |
|
"""Accept coord as long as its box contains part of mask.""" |
|
coord_in_real_mask = np.ceil(scale_factor * coord).astype(np.int32) |
|
start_x, start_y, end_x, end_y = coord_in_real_mask |
|
roi = mask_reader.img[start_y:end_y, start_x:end_x] |
|
return np.sum(roi > 0) > 0 |
|
|
|
flags = [sel_func(bound) for bound in bounds] |
|
return np.array(flags) |
Description
Existing functionality: When using
SlidingWindowPatchExtractor, we can specifymin_mask_ratioto filter out patches with little tissue.Proposed feature: When computing features with
DeepFeatureExtractor, we rely on theSemanticSegmentor.filter_coordinates()method, which, as far as I understand based on the definition ofsel_func(), needs just one pixel from the patch to be within the mask for the patch to be selected.tiatoolbox/tiatoolbox/models/engine/semantic_segmentor.py
Lines 634 to 708 in 0ddd2b1
Benefit: Adding
min_mask_ratiocan significantly speed up the feature computation by filtering out patches that mostly contain background pixels in advance.