|
class WholeSlideImagePatches(torch.utils.data.Dataset): |
|
"""Dataset of one whole slide image. |
|
|
|
This object retrieves patches from a whole slide image on the fly. |
|
|
|
Parameters |
|
---------- |
|
wsi_path : str, Path |
|
Path to whole slide image file. |
|
patch_path : str, Path |
|
Path to npy file with coordinates of input image. |
|
um_px : float |
|
Scale of the resulting patches. For example, 0.5 for ~20x magnification. |
|
patch_size : int |
|
The size of patches in pixels. |
|
transform : callable, optional |
|
A callable to modify a retrieved patch. The callable must accept a |
|
PIL.Image.Image instance and return a torch.Tensor. |
|
roi_path : str, Path, optional |
|
Path to GeoJSON file that outlines the region of interest (ROI). Only patches |
|
within the ROI(s) will be used. |
|
""" |
|
|
|
def __init__( |
|
self, |
|
wsi_path: str | Path, |
|
patch_path: str | Path, |
|
um_px: float, |
|
patch_size: int, |
|
transform: Optional[Callable[[Image.Image], torch.Tensor]] = None, |
|
roi_path: Optional[str | Path] = None, |
|
): |
|
self.wsi_path = wsi_path |
|
self.patch_path = patch_path |
|
self.um_px = float(um_px) |
|
self.patch_size = int(patch_size) |
|
self.transform = transform |
|
self.roi_path = roi_path |
|
|
|
assert Path(wsi_path).exists(), "wsi path not found" |
|
assert Path(patch_path).exists(), "patch path not found" |
|
if roi_path is not None: |
|
assert Path(roi_path).exists(), "roi path not found" |
|
|
|
self.patches = _read_patch_coords(self.patch_path) |
|
|
|
# If an ROI is given, keep patches that intersect it. |
|
if self.roi_path is not None: |
|
self.patches = _filter_patches_in_rois( |
|
geojson_path=self.roi_path, coords=self.patches |
|
) |
|
if self.patches.shape[0] == 0: |
|
raise ValueError("No patches left after taking intersection with ROI") |
|
|
|
assert self.patches.ndim == 2, "expected 2D array of patch coordinates" |
|
# x, y, width, height |
|
assert self.patches.shape[1] == 4, "expected second dimension to have len 4" |
|
|
|
def worker_init(self, *_): |
|
self.slide = WSI(self.wsi_path) |
|
|
|
def __len__(self): |
|
return self.patches.shape[0] |
|
|
|
def __getitem__( |
|
self, idx: int |
|
) -> Tuple[Union[Image.Image, torch.Tensor], torch.Tensor]: |
|
coords: Sequence[int] = self.patches[idx] |
|
assert len(coords) == 4, "expected 4 coords (minx, miny, width, height)" |
|
minx, miny, width, height = coords |
|
|
|
patch_im = self.slide.read_region( |
|
location=(minx, miny), level=0, size=(width, height) |
|
) |
|
patch_im = patch_im.convert("RGB") |
|
|
|
if self.transform is not None: |
|
patch_im = self.transform(patch_im) |
|
|
|
return patch_im, torch.as_tensor([minx, miny, width, height]) |
add tests for the torch dataset child class
WholeSlideImagePatches.wsinfer/wsinfer/modellib/data.py
Lines 93 to 172 in 8629418
you may want to create your own reference tiff image, as in
wsinfer/tests/test_all.py
Lines 27 to 43 in 8629418