diff --git a/openslide/__init__.py b/openslide/__init__.py index 8994bcdf..60a32b41 100644 --- a/openslide/__init__.py +++ b/openslide/__init__.py @@ -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 @@ -62,7 +63,7 @@ _T = TypeVar('_T') -class AbstractSlide: +class AbstractSlide(metaclass=ABCMeta): """The base class of a slide object.""" def __init__(self) -> None: @@ -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. @@ -109,6 +114,7 @@ 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. @@ -116,6 +122,7 @@ def level_downsamples(self) -> tuple[float, ...]: raise NotImplementedError @property + @abstractmethod def properties(self) -> Mapping[str, str]: """Metadata about the image. @@ -123,6 +130,7 @@ def properties(self) -> Mapping[str, str]: raise NotImplementedError @property + @abstractmethod def associated_images(self) -> Mapping[str, Image.Image]: """Images associated with this whole-slide image. @@ -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: @@ -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. @@ -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() @@ -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 @@ -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]: @@ -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. diff --git a/openslide/lowlevel.py b/openslide/lowlevel.py index 48963057..c24ca7c9 100644 --- a/openslide/lowlevel.py +++ b/openslide/lowlevel.py @@ -568,7 +568,6 @@ def read_associated_image_icc_profile( 'openslide_set_cache', None, [_OpenSlide, _OpenSlideCache], - None, minimum_version='4.0.0', )