Skip to content

Commit f40148b

Browse files
committed
Add AbstractSlide.color_profile property
For any given slide, every image returned by read_region() will have the same color profile. It's important that we attach it to every region to document the correct color space, but applications that want to do color conversion may find it more efficient to build a transform once, up front. Add a property to help them do this. Signed-off-by: Benjamin Gilbert <[email protected]>
1 parent 2b8fdd1 commit f40148b

File tree

4 files changed

+22
-1
lines changed

4 files changed

+22
-1
lines changed

doc/index.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ OpenSlide objects
148148

149149
Unlike in the C interface, these images are not premultiplied.
150150

151+
.. attribute:: color_profile
152+
153+
The embedded :ref:`color profile <color-management>` for this slide,
154+
as a Pillow :class:`~PIL.ImageCms.ImageCmsProfile`, or :obj:`None` if
155+
not available.
156+
151157
.. method:: read_region(location, level, size)
152158

153159
Return an RGBA :class:`Image <PIL.Image.Image>` containing the
@@ -188,6 +194,8 @@ OpenSlide objects
188194
Close the OpenSlide object.
189195

190196

197+
.. _color-management:
198+
191199
Color management
192200
----------------
193201

openslide/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@
2424
"""
2525

2626
from collections.abc import Mapping
27+
from io import BytesIO
2728

28-
from PIL import Image
29+
from PIL import Image, ImageCms
2930

3031
from openslide import lowlevel
3132

@@ -114,6 +115,13 @@ def associated_images(self):
114115
This is a map: image name -> PIL.Image."""
115116
raise NotImplementedError
116117

118+
@property
119+
def color_profile(self):
120+
"""Color profile for the whole-slide image, or None if unavailable."""
121+
if self._profile is None:
122+
return None
123+
return ImageCms.getOpenProfile(BytesIO(self._profile))
124+
117125
def get_best_level_for_downsample(self, downsample):
118126
"""Return the best level for displaying the given downsample."""
119127
raise NotImplementedError

tests/test_imageslide.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def test_metadata(self):
8989
self.assertEqual(self.osr.associated_images, {})
9090

9191
def test_color_profile(self):
92+
self.assertEqual(self.osr.color_profile.profile.device_class, 'mntr')
9293
self.assertEqual(
9394
len(self.osr.read_region((0, 0), 0, (100, 100)).info['icc_profile']), 588
9495
)
@@ -127,6 +128,7 @@ class TestNoIccImage(_SlideTest, unittest.TestCase):
127128
FILENAME = 'boxes-no-icc.png'
128129

129130
def test_color_profile(self):
131+
self.assertIsNone(self.osr.color_profile)
130132
self.assertNotIn(
131133
'icc_profile', self.osr.read_region((0, 0), 0, (100, 100)).info
132134
)

tests/test_openslide.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ def test_properties(self):
127127
lowlevel.read_icc_profile.available, "requires OpenSlide 4.0.0"
128128
)
129129
def test_color_profile(self):
130+
self.assertEqual(self.osr.color_profile.profile.device_class, 'mntr')
130131
self.assertEqual(
131132
len(self.osr.read_region((0, 0), 0, (100, 100)).info['icc_profile']), 588
132133
)
@@ -196,6 +197,7 @@ def mangle_repr(o):
196197
)
197198

198199
def test_color_profile(self):
200+
self.assertIsNone(self.osr.color_profile)
199201
self.assertNotIn(
200202
'icc_profile', self.osr.read_region((0, 0), 0, (100, 100)).info
201203
)
@@ -212,6 +214,7 @@ class TestDicomSlide(_SlideTest, unittest.TestCase):
212214
FILENAME = 'boxes_0.dcm'
213215

214216
def test_color_profile(self):
217+
self.assertEqual(self.osr.color_profile.profile.device_class, 'mntr')
215218
main_profile = self.osr.read_region((0, 0), 0, (100, 100)).info['icc_profile']
216219
associated_profile = self.osr.associated_images['thumbnail'].info['icc_profile']
217220
self.assertEqual(len(main_profile), 456)

0 commit comments

Comments
 (0)