2525
2626from __future__ import annotations
2727
28+ from abc import ABCMeta , abstractmethod
2829from io import BytesIO
2930from types import TracebackType
3031from typing import Iterator , Literal , Mapping , TypeVar
6263_T = TypeVar ('_T' )
6364
6465
65- class AbstractSlide :
66+ class AbstractSlide ( metaclass = ABCMeta ) :
6667 """The base class of a slide object."""
6768
6869 def __init__ (self ) -> None :
@@ -81,22 +82,26 @@ def __exit__(
8182 return False
8283
8384 @classmethod
85+ @abstractmethod
8486 def detect_format (cls , filename : lowlevel .Filename ) -> str | None :
8587 """Return a string describing the format of the specified file.
8688
8789 If the file format is not recognized, return None."""
8890 raise NotImplementedError
8991
92+ @abstractmethod
9093 def close (self ) -> None :
9194 """Close the slide."""
9295 raise NotImplementedError
9396
9497 @property
98+ @abstractmethod
9599 def level_count (self ) -> int :
96100 """The number of levels in the image."""
97101 raise NotImplementedError
98102
99103 @property
104+ @abstractmethod
100105 def level_dimensions (self ) -> tuple [tuple [int , int ], ...]:
101106 """A tuple of (width, height) tuples, one for each level of the image.
102107
@@ -109,20 +114,23 @@ def dimensions(self) -> tuple[int, int]:
109114 return self .level_dimensions [0 ]
110115
111116 @property
117+ @abstractmethod
112118 def level_downsamples (self ) -> tuple [float , ...]:
113119 """A tuple of downsampling factors for each level of the image.
114120
115121 level_downsample[n] contains the downsample factor of level n."""
116122 raise NotImplementedError
117123
118124 @property
125+ @abstractmethod
119126 def properties (self ) -> Mapping [str , str ]:
120127 """Metadata about the image.
121128
122129 This is a map: property name -> property value."""
123130 raise NotImplementedError
124131
125132 @property
133+ @abstractmethod
126134 def associated_images (self ) -> Mapping [str , Image .Image ]:
127135 """Images associated with this whole-slide image.
128136
@@ -136,10 +144,12 @@ def color_profile(self) -> ImageCms.ImageCmsProfile | None:
136144 return None
137145 return ImageCms .getOpenProfile (BytesIO (self ._profile ))
138146
147+ @abstractmethod
139148 def get_best_level_for_downsample (self , downsample : float ) -> int :
140149 """Return the best level for displaying the given downsample."""
141150 raise NotImplementedError
142151
152+ @abstractmethod
143153 def read_region (
144154 self , location : tuple [int , int ], level : int , size : tuple [int , int ]
145155 ) -> Image .Image :
@@ -151,11 +161,13 @@ def read_region(
151161 size: (width, height) tuple giving the region size."""
152162 raise NotImplementedError
153163
154- def set_cache (self , cache : OpenSlideCache ) -> None :
164+ def set_cache (self , cache : OpenSlideCache ) -> None : # noqa: B027
155165 """Use the specified cache to store recently decoded slide tiles.
156166
167+ This class does not support caching, so this method does nothing.
168+
157169 cache: an OpenSlideCache object."""
158- raise NotImplementedError
170+ pass
159171
160172 def get_thumbnail (self , size : tuple [int , int ]) -> Image .Image :
161173 """Return a PIL.Image containing an RGB thumbnail of the image.
@@ -299,6 +311,7 @@ def __len__(self) -> int:
299311 def __iter__ (self ) -> Iterator [str ]:
300312 return iter (self ._keys ())
301313
314+ @abstractmethod
302315 def _keys (self ) -> list [str ]:
303316 # Private method; always returns list.
304317 raise NotImplementedError ()
@@ -406,7 +419,7 @@ def level_dimensions(self) -> tuple[tuple[int, int]]:
406419
407420 level_dimensions[n] contains the dimensions of level n."""
408421 if self ._image is None :
409- raise ValueError ('Passing closed slide object ' )
422+ raise ValueError ('Cannot read from a closed slide' )
410423 return (self ._image .size ,)
411424
412425 @property
@@ -444,7 +457,7 @@ def read_region(
444457 level: the level number.
445458 size: (width, height) tuple giving the region size."""
446459 if self ._image is None :
447- raise ValueError ('Passing closed slide object ' )
460+ raise ValueError ('Cannot read from a closed slide' )
448461 if level != 0 :
449462 raise OpenSlideError ("Invalid level" )
450463 if ['fail' for s in size if s < 0 ]:
@@ -474,14 +487,6 @@ def read_region(
474487 tile .info ['icc_profile' ] = self ._profile
475488 return tile
476489
477- def set_cache (self , cache : OpenSlideCache ) -> None :
478- """Use the specified cache to store recently decoded slide tiles.
479-
480- ImageSlide does not support caching, so this method does nothing.
481-
482- cache: an OpenSlideCache object."""
483- pass
484-
485490
486491def open_slide (filename : lowlevel .Filename ) -> OpenSlide | ImageSlide :
487492 """Open a whole-slide or regular image.
0 commit comments