25
25
26
26
from __future__ import annotations
27
27
28
+ from abc import ABCMeta , abstractmethod
28
29
from io import BytesIO
29
30
from types import TracebackType
30
31
from typing import Iterator , Literal , Mapping , TypeVar
62
63
_T = TypeVar ('_T' )
63
64
64
65
65
- class AbstractSlide :
66
+ class AbstractSlide ( metaclass = ABCMeta ) :
66
67
"""The base class of a slide object."""
67
68
68
69
def __init__ (self ) -> None :
@@ -81,22 +82,26 @@ def __exit__(
81
82
return False
82
83
83
84
@classmethod
85
+ @abstractmethod
84
86
def detect_format (cls , filename : lowlevel .Filename ) -> str | None :
85
87
"""Return a string describing the format of the specified file.
86
88
87
89
If the file format is not recognized, return None."""
88
90
raise NotImplementedError
89
91
92
+ @abstractmethod
90
93
def close (self ) -> None :
91
94
"""Close the slide."""
92
95
raise NotImplementedError
93
96
94
97
@property
98
+ @abstractmethod
95
99
def level_count (self ) -> int :
96
100
"""The number of levels in the image."""
97
101
raise NotImplementedError
98
102
99
103
@property
104
+ @abstractmethod
100
105
def level_dimensions (self ) -> tuple [tuple [int , int ], ...]:
101
106
"""A tuple of (width, height) tuples, one for each level of the image.
102
107
@@ -109,20 +114,23 @@ def dimensions(self) -> tuple[int, int]:
109
114
return self .level_dimensions [0 ]
110
115
111
116
@property
117
+ @abstractmethod
112
118
def level_downsamples (self ) -> tuple [float , ...]:
113
119
"""A tuple of downsampling factors for each level of the image.
114
120
115
121
level_downsample[n] contains the downsample factor of level n."""
116
122
raise NotImplementedError
117
123
118
124
@property
125
+ @abstractmethod
119
126
def properties (self ) -> Mapping [str , str ]:
120
127
"""Metadata about the image.
121
128
122
129
This is a map: property name -> property value."""
123
130
raise NotImplementedError
124
131
125
132
@property
133
+ @abstractmethod
126
134
def associated_images (self ) -> Mapping [str , Image .Image ]:
127
135
"""Images associated with this whole-slide image.
128
136
@@ -136,10 +144,12 @@ def color_profile(self) -> ImageCms.ImageCmsProfile | None:
136
144
return None
137
145
return ImageCms .getOpenProfile (BytesIO (self ._profile ))
138
146
147
+ @abstractmethod
139
148
def get_best_level_for_downsample (self , downsample : float ) -> int :
140
149
"""Return the best level for displaying the given downsample."""
141
150
raise NotImplementedError
142
151
152
+ @abstractmethod
143
153
def read_region (
144
154
self , location : tuple [int , int ], level : int , size : tuple [int , int ]
145
155
) -> Image .Image :
@@ -151,11 +161,13 @@ def read_region(
151
161
size: (width, height) tuple giving the region size."""
152
162
raise NotImplementedError
153
163
154
- def set_cache (self , cache : OpenSlideCache ) -> None :
164
+ def set_cache (self , cache : OpenSlideCache ) -> None : # noqa: B027
155
165
"""Use the specified cache to store recently decoded slide tiles.
156
166
167
+ This class does not support caching, so this method does nothing.
168
+
157
169
cache: an OpenSlideCache object."""
158
- raise NotImplementedError
170
+ pass
159
171
160
172
def get_thumbnail (self , size : tuple [int , int ]) -> Image .Image :
161
173
"""Return a PIL.Image containing an RGB thumbnail of the image.
@@ -299,6 +311,7 @@ def __len__(self) -> int:
299
311
def __iter__ (self ) -> Iterator [str ]:
300
312
return iter (self ._keys ())
301
313
314
+ @abstractmethod
302
315
def _keys (self ) -> list [str ]:
303
316
# Private method; always returns list.
304
317
raise NotImplementedError ()
@@ -406,7 +419,7 @@ def level_dimensions(self) -> tuple[tuple[int, int]]:
406
419
407
420
level_dimensions[n] contains the dimensions of level n."""
408
421
if self ._image is None :
409
- raise ValueError ('Passing closed slide object ' )
422
+ raise ValueError ('Cannot read from a closed slide' )
410
423
return (self ._image .size ,)
411
424
412
425
@property
@@ -444,7 +457,7 @@ def read_region(
444
457
level: the level number.
445
458
size: (width, height) tuple giving the region size."""
446
459
if self ._image is None :
447
- raise ValueError ('Passing closed slide object ' )
460
+ raise ValueError ('Cannot read from a closed slide' )
448
461
if level != 0 :
449
462
raise OpenSlideError ("Invalid level" )
450
463
if ['fail' for s in size if s < 0 ]:
@@ -474,14 +487,6 @@ def read_region(
474
487
tile .info ['icc_profile' ] = self ._profile
475
488
return tile
476
489
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
-
485
490
486
491
def open_slide (filename : lowlevel .Filename ) -> OpenSlide | ImageSlide :
487
492
"""Open a whole-slide or regular image.
0 commit comments