Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions openslide/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from __future__ import annotations

from abc import ABCMeta, abstractmethod
from io import BytesIO
from types import TracebackType
from typing import Iterator, Literal, Mapping, TypeVar
Expand Down Expand Up @@ -62,7 +63,7 @@
_T = TypeVar('_T')


class AbstractSlide:
class AbstractSlide(metaclass=ABCMeta):
"""The base class of a slide object."""

def __init__(self) -> None:
Expand All @@ -81,22 +82,26 @@ def __exit__(
return False

@classmethod
@abstractmethod
def detect_format(cls, filename: lowlevel.Filename) -> str | None:
"""Return a string describing the format of the specified file.

If the file format is not recognized, return None."""
raise NotImplementedError

@abstractmethod
def close(self) -> None:
"""Close the slide."""
raise NotImplementedError

@property
@abstractmethod
def level_count(self) -> int:
"""The number of levels in the image."""
raise NotImplementedError

@property
@abstractmethod
def level_dimensions(self) -> tuple[tuple[int, int], ...]:
"""A tuple of (width, height) tuples, one for each level of the image.

Expand All @@ -109,20 +114,23 @@ def dimensions(self) -> tuple[int, int]:
return self.level_dimensions[0]

@property
@abstractmethod
def level_downsamples(self) -> tuple[float, ...]:
"""A tuple of downsampling factors for each level of the image.

level_downsample[n] contains the downsample factor of level n."""
raise NotImplementedError

@property
@abstractmethod
def properties(self) -> Mapping[str, str]:
"""Metadata about the image.

This is a map: property name -> property value."""
raise NotImplementedError

@property
@abstractmethod
def associated_images(self) -> Mapping[str, Image.Image]:
"""Images associated with this whole-slide image.

Expand All @@ -136,10 +144,12 @@ def color_profile(self) -> ImageCms.ImageCmsProfile | None:
return None
return ImageCms.getOpenProfile(BytesIO(self._profile))

@abstractmethod
def get_best_level_for_downsample(self, downsample: float) -> int:
"""Return the best level for displaying the given downsample."""
raise NotImplementedError

@abstractmethod
def read_region(
self, location: tuple[int, int], level: int, size: tuple[int, int]
) -> Image.Image:
Expand All @@ -151,11 +161,13 @@ def read_region(
size: (width, height) tuple giving the region size."""
raise NotImplementedError

def set_cache(self, cache: OpenSlideCache) -> None:
def set_cache(self, cache: OpenSlideCache) -> None: # noqa: B027
"""Use the specified cache to store recently decoded slide tiles.

This class does not support caching, so this method does nothing.

cache: an OpenSlideCache object."""
raise NotImplementedError
pass

def get_thumbnail(self, size: tuple[int, int]) -> Image.Image:
"""Return a PIL.Image containing an RGB thumbnail of the image.
Expand Down Expand Up @@ -299,6 +311,7 @@ def __len__(self) -> int:
def __iter__(self) -> Iterator[str]:
return iter(self._keys())

@abstractmethod
def _keys(self) -> list[str]:
# Private method; always returns list.
raise NotImplementedError()
Expand Down Expand Up @@ -406,7 +419,7 @@ def level_dimensions(self) -> tuple[tuple[int, int]]:

level_dimensions[n] contains the dimensions of level n."""
if self._image is None:
raise ValueError('Passing closed slide object')
raise ValueError('Cannot read from a closed slide')
return (self._image.size,)

@property
Expand Down Expand Up @@ -444,7 +457,7 @@ def read_region(
level: the level number.
size: (width, height) tuple giving the region size."""
if self._image is None:
raise ValueError('Passing closed slide object')
raise ValueError('Cannot read from a closed slide')
if level != 0:
raise OpenSlideError("Invalid level")
if ['fail' for s in size if s < 0]:
Expand Down Expand Up @@ -474,14 +487,6 @@ def read_region(
tile.info['icc_profile'] = self._profile
return tile

def set_cache(self, cache: OpenSlideCache) -> None:
"""Use the specified cache to store recently decoded slide tiles.

ImageSlide does not support caching, so this method does nothing.

cache: an OpenSlideCache object."""
pass


def open_slide(filename: lowlevel.Filename) -> OpenSlide | ImageSlide:
"""Open a whole-slide or regular image.
Expand Down
1 change: 0 additions & 1 deletion openslide/lowlevel.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,6 @@ def read_associated_image_icc_profile(
'openslide_set_cache',
None,
[_OpenSlide, _OpenSlideCache],
None,
minimum_version='4.0.0',
)

Expand Down
Loading