from pathlib import Path
from typing import List
from PIL.Image import Image
from openslide import open_slide
from wsipipe.load.slides.slide import SlideBase
from wsipipe.load.slides.region import Region
from wsipipe.utils import Size, Point
[docs]class OSSlide(SlideBase):
"""
Read slides to generic format using the openslide package.
For example, to open OMETiff WSIs.
"""
def __init__(self, path: Path) -> None:
self._path = path
self._osr = None
[docs] def open(self) -> None:
self._osr = open_slide(str(self._path))
[docs] def close(self) -> None:
self._osr.close()
@property
def path(self) -> Path:
return self._path
@property
def dimensions(self) -> List[Size]:
""" Gets slide dimensions in pixels for all levels in pyramid
If fewer than 10 levels exist in the pyramid it calculates the
extra sizes and adds them to the list
Returns:
sizelist (List[Size]): A list of sizes
"""
# TODO: review limits - make sure to docuement
sizelist = [Size(*dim) for dim in self._osr.level_dimensions]
size_smallest_level = sizelist[-1]
size_smallest_level = min(size_smallest_level.width, size_smallest_level.height)
nlevels = len(sizelist)
while nlevels < 10:
max_level_dim = sizelist[-1]
next_level_size = Size(int(max_level_dim.width // 2), int(max_level_dim.height // 2))
sizelist.append(next_level_size)
size_smallest_level = sizelist[-1]
size_smallest_level = min(size_smallest_level.width, size_smallest_level.height)
nlevels = len(sizelist)
return sizelist
[docs] def check_level(self, region: Region) -> bool:
""" Checks if level specified in region exists in pyramid
Args:
region (Region): A Region to check
Returns:
(bool): True if level in region exists in pyramid
"""
level_count = self._osr.level_count
return region.level < level_count
[docs] def convert_region(self, region: Region) -> Image:
""" Creates a PIL image of a region by downsampling from lower level
Args:
region (Region): A Region to create
Returns:
image (Image): A downsampled PIL Image
"""
max_level = self._osr.level_count - 1
level_diff = region.level - max_level
size_at_max_lev = Size(region.size.width * (2 ** level_diff), region.size.height * (2 ** level_diff))
x_at_max_lev = region.location.x * (2 ** level_diff)
y_at_max_lev = region.location.y * (2 ** level_diff)
new_region = Region(location=Point(x_at_max_lev, y_at_max_lev), size=size_at_max_lev, level=max_level)
image = self._osr.read_region(new_region.location, new_region.level, new_region.size)
image = image.resize((region.size))
return image
[docs] def read_region(self, region: Region) -> Image:
"""Read a region from a WSI
Checks if the specified level for the region exists in the pyramid.
If not reads the region from the highest level that exists and downscales it
Args:
region (Region): A region of the image
Returns:
image (Image): A PIL Image of the specified region
"""
if self.check_level(region):
region_out = self._osr.read_region(region.location, region.level, region.size)
else:
region_out = self.convert_region(region)
return region_out
[docs] def read_regions(self, regions: List[Region]) -> List[Image]:
# TODO: this call could be parallelised
# though pytorch loaders will do this for us
regions = [self.read_region(region) for region in regions]
return regions