[med-svn] [Git][med-team/openslide-python][upstream] New upstream version 1.4.6

Étienne Mollier (@emollier) gitlab at salsa.debian.org
Sat Jun 27 12:35:55 BST 2026



Étienne Mollier pushed to branch upstream at Debian Med / openslide-python


Commits:
bd248eda by Étienne Mollier at 2026-06-27T13:01:03+02:00
New upstream version 1.4.6
- - - - -


25 changed files:

- CHANGELOG.md
- PKG-INFO
- README.md
- doc/index.rst
- doc/jekyll_fix.py
- examples/deepzoom/deepzoom_multiserver.py
- examples/deepzoom/deepzoom_server.py
- examples/deepzoom/deepzoom_tile.py
- examples/deepzoom/static/jquery.js
- examples/deepzoom/static/openseadragon.js
- examples/deepzoom/templates/files.html
- examples/deepzoom/templates/slide-fullpage.html
- examples/deepzoom/templates/slide-multipane.html
- openslide/__init__.py
- openslide/_convert.c
- openslide/_version.py
- openslide/deepzoom.py
- openslide/lowlevel.py
- openslide_python.egg-info/PKG-INFO
- pyproject.toml
- tests/common.py
- tests/test_base.py
- tests/test_deepzoom.py
- tests/test_imageslide.py
- tests/test_openslide.py


Changes:

=====================================
CHANGELOG.md
=====================================
@@ -1,5 +1,13 @@
 # Notable Changes in OpenSlide Python
 
+## Version 1.4.6, 2026-06-07
+
+### Changes
+
+* Add attribute for `openslide.barcode` property
+* examples: Update OpenSeadragon to 6.0.2
+
+
 ## Version 1.4.3, 2025-12-03
 
 ### New features


=====================================
PKG-INFO
=====================================
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: openslide-python
-Version: 1.4.3
+Version: 1.4.6
 Summary: Python interface to OpenSlide
 Maintainer-email: OpenSlide project <openslide-users at lists.andrew.cmu.edu>
 License-Expression: LGPL-2.1-only AND BSD-3-Clause AND MIT AND LicenseRef-Public-Domain
@@ -51,8 +51,10 @@ closest to a desired zoom level.
 OpenSlide can read virtual slides in several formats:
 
 * [Aperio][] (`.svs`, `.tif`)
+* [ARGOS][] (`.avs`)
 * [DICOM][] (`.dcm`)
 * [Hamamatsu][] (`.ndpi`, `.vms`, `.vmu`)
+* [Huron][] (`.tif`)
 * [Leica][] (`.scn`)
 * [MIRAX][] (`.mrxs`)
 * [Philips][] (`.tiff`)
@@ -64,8 +66,10 @@ OpenSlide can read virtual slides in several formats:
 
 [OpenSlide]: https://openslide.org/
 [Aperio]: https://openslide.org/formats/aperio/
+[ARGOS]: https://openslide.org/formats/argos/
 [DICOM]: https://openslide.org/formats/dicom/
 [Hamamatsu]: https://openslide.org/formats/hamamatsu/
+[Huron]: https://openslide.org/formats/huron/
 [Leica]: https://openslide.org/formats/leica/
 [MIRAX]: https://openslide.org/formats/mirax/
 [Philips]: https://openslide.org/formats/philips/


=====================================
README.md
=====================================
@@ -14,8 +14,10 @@ closest to a desired zoom level.
 OpenSlide can read virtual slides in several formats:
 
 * [Aperio][] (`.svs`, `.tif`)
+* [ARGOS][] (`.avs`)
 * [DICOM][] (`.dcm`)
 * [Hamamatsu][] (`.ndpi`, `.vms`, `.vmu`)
+* [Huron][] (`.tif`)
 * [Leica][] (`.scn`)
 * [MIRAX][] (`.mrxs`)
 * [Philips][] (`.tiff`)
@@ -27,8 +29,10 @@ OpenSlide can read virtual slides in several formats:
 
 [OpenSlide]: https://openslide.org/
 [Aperio]: https://openslide.org/formats/aperio/
+[ARGOS]: https://openslide.org/formats/argos/
 [DICOM]: https://openslide.org/formats/dicom/
 [Hamamatsu]: https://openslide.org/formats/hamamatsu/
+[Huron]: https://openslide.org/formats/huron/
 [Leica]: https://openslide.org/formats/leica/
 [MIRAX]: https://openslide.org/formats/mirax/
 [Philips]: https://openslide.org/formats/philips/


=====================================
doc/index.rst
=====================================
@@ -16,8 +16,10 @@ closest to a desired zoom level.
 OpenSlide can read virtual slides in several formats:
 
 * Aperio_ (``.svs``, ``.tif``)
+* ARGOS_ (``.avs``)
 * DICOM_ (``.dcm``)
 * Hamamatsu_ (``.ndpi``, ``.vms``, ``.vmu``)
+* Huron_ (``.tif``)
 * Leica_ (``.scn``)
 * MIRAX_ (``.mrxs``)
 * Philips_ (``.tiff``)
@@ -32,8 +34,10 @@ Public License, version 2.1`_.
 
 .. _OpenSlide: https://openslide.org/
 .. _Aperio: https://openslide.org/formats/aperio/
+.. _ARGOS: https://openslide.org/formats/argos/
 .. _DICOM: https://openslide.org/formats/dicom/
 .. _Hamamatsu: https://openslide.org/formats/hamamatsu/
+.. _Huron: https://openslide.org/formats/huron/
 .. _Leica: https://openslide.org/formats/leica/
 .. _MIRAX: https://openslide.org/formats/mirax/
 .. _Philips: https://openslide.org/formats/philips/
@@ -286,26 +290,38 @@ Standard properties
 The :mod:`openslide` module provides attributes containing the names of
 some commonly-used OpenSlide properties.
 
-.. data:: PROPERTY_NAME_COMMENT
+.. data:: PROPERTY_NAME_BACKGROUND_COLOR
 
-   The name of the property containing a slide's comment, if any.
+   The name of the property containing a slide's background color, if any.
+   It is represented as an RGB hex triplet.
 
-.. data:: PROPERTY_NAME_VENDOR
+.. data:: PROPERTY_NAME_BARCODE
 
-   The name of the property containing an identification of the vendor.
+   The name of the property containing a slide's barcode, if any.
 
-.. data:: PROPERTY_NAME_QUICKHASH1
+.. data:: PROPERTY_NAME_BOUNDS_HEIGHT
 
-   The name of the property containing the "quickhash-1" sum.
+   The name of the property containing the height of the rectangle bounding
+   the non-empty region of the slide, if available.
 
-.. data:: PROPERTY_NAME_BACKGROUND_COLOR
+.. data:: PROPERTY_NAME_BOUNDS_WIDTH
 
-   The name of the property containing a slide's background color, if any.
-   It is represented as an RGB hex triplet.
+   The name of the property containing the width of the rectangle bounding
+   the non-empty region of the slide, if available.
 
-.. data:: PROPERTY_NAME_OBJECTIVE_POWER
+.. data:: PROPERTY_NAME_BOUNDS_X
 
-   The name of the property containing a slide's objective power, if known.
+   The name of the property containing the X coordinate of the rectangle
+   bounding the non-empty region of the slide, if available.
+
+.. data:: PROPERTY_NAME_BOUNDS_Y
+
+   The name of the property containing the Y coordinate of the rectangle
+   bounding the non-empty region of the slide, if available.
+
+.. data:: PROPERTY_NAME_COMMENT
+
+   The name of the property containing a slide's comment, if any.
 
 .. data:: PROPERTY_NAME_MPP_X
 
@@ -317,25 +333,17 @@ some commonly-used OpenSlide properties.
    The name of the property containing the number of microns per pixel in
    the Y dimension of level 0, if known.
 
-.. data:: PROPERTY_NAME_BOUNDS_X
-
-   The name of the property containing the X coordinate of the rectangle
-   bounding the non-empty region of the slide, if available.
-
-.. data:: PROPERTY_NAME_BOUNDS_Y
+.. data:: PROPERTY_NAME_OBJECTIVE_POWER
 
-   The name of the property containing the Y coordinate of the rectangle
-   bounding the non-empty region of the slide, if available.
+   The name of the property containing a slide's objective power, if known.
 
-.. data:: PROPERTY_NAME_BOUNDS_WIDTH
+.. data:: PROPERTY_NAME_QUICKHASH1
 
-   The name of the property containing the width of the rectangle bounding
-   the non-empty region of the slide, if available.
+   The name of the property containing the "quickhash-1" sum.
 
-.. data:: PROPERTY_NAME_BOUNDS_HEIGHT
+.. data:: PROPERTY_NAME_VENDOR
 
-   The name of the property containing the height of the rectangle bounding
-   the non-empty region of the slide, if available.
+   The name of the property containing an identification of the vendor.
 
 
 Exceptions


=====================================
doc/jekyll_fix.py
=====================================
@@ -38,7 +38,7 @@ DIRS = {
 }
 FILES = {
     # Added in Sphinx 5.0.0, scheduled to be removed in Sphinx 6
-    'static/_sphinx_javascript_frameworks_compat.js': 'static/sphinx_javascript_frameworks_compat.js',  # noqa: E501
+    'static/_sphinx_javascript_frameworks_compat.js': 'static/sphinx_javascript_frameworks_compat.js',
 }
 REWRITE_EXTENSIONS = {'.html', '.js'}
 


=====================================
examples/deepzoom/deepzoom_multiserver.py
=====================================
@@ -28,20 +28,16 @@ from io import BytesIO
 import os
 from pathlib import Path, PurePath
 from threading import Lock
-from typing import TYPE_CHECKING, Any, Literal
+from typing import Any, Literal, TypeAlias
 import zlib
 
-from PIL import Image, ImageCms
 from flask import Flask, Response, abort, make_response, render_template, url_for
-
-if TYPE_CHECKING:
-    # Python 3.10+
-    from typing import TypeAlias
+from PIL import Image, ImageCms
 
 if os.name == 'nt':
     _dll_path = os.getenv('OPENSLIDE_PATH')
     if _dll_path is not None:
-        with os.add_dll_directory(_dll_path):  # type: ignore[attr-defined,unused-ignore]  # noqa: E501
+        with os.add_dll_directory(_dll_path):  # type: ignore[attr-defined,unused-ignore]
             import openslide
     else:
         import openslide
@@ -68,17 +64,16 @@ SRGB_PROFILE_BYTES = zlib.decompress(
 )
 SRGB_PROFILE = ImageCms.getOpenProfile(BytesIO(SRGB_PROFILE_BYTES))
 
-if TYPE_CHECKING:
-    ColorMode: TypeAlias = Literal[
-        'default',
-        'absolute-colorimetric',
-        'perceptual',
-        'relative-colorimetric',
-        'saturation',
-        'embed',
-        'ignore',
-    ]
-    Transform: TypeAlias = Callable[[Image.Image], None]
+ColorMode: TypeAlias = Literal[
+    'default',
+    'absolute-colorimetric',
+    'perceptual',
+    'relative-colorimetric',
+    'saturation',
+    'embed',
+    'ignore',
+]
+Transform: TypeAlias = Callable[[Image.Image], None]
 
 
 class DeepZoomMultiServer(Flask):
@@ -192,7 +187,7 @@ def create_app(
             icc_profile=tile.info.get('icc_profile'),
         )
         resp = make_response(buf.getvalue())
-        resp.mimetype = 'image/%s' % format
+        resp.mimetype = f'image/{format}'
         return resp
 
     return app


=====================================
examples/deepzoom/deepzoom_server.py
=====================================
@@ -27,21 +27,17 @@ from io import BytesIO
 import os
 from pathlib import Path
 import re
-from typing import TYPE_CHECKING, Any, Literal
+from typing import Any, Literal, TypeAlias
 from unicodedata import normalize
 import zlib
 
-from PIL import Image, ImageCms
 from flask import Flask, Response, abort, make_response, render_template, url_for
-
-if TYPE_CHECKING:
-    # Python 3.10+
-    from typing import TypeAlias
+from PIL import Image, ImageCms
 
 if os.name == 'nt':
     _dll_path = os.getenv('OPENSLIDE_PATH')
     if _dll_path is not None:
-        with os.add_dll_directory(_dll_path):  # type: ignore[attr-defined,unused-ignore]  # noqa: E501
+        with os.add_dll_directory(_dll_path):  # type: ignore[attr-defined,unused-ignore]
             import openslide
     else:
         import openslide
@@ -70,17 +66,16 @@ SRGB_PROFILE_BYTES = zlib.decompress(
 )
 SRGB_PROFILE = ImageCms.getOpenProfile(BytesIO(SRGB_PROFILE_BYTES))
 
-if TYPE_CHECKING:
-    ColorMode: TypeAlias = Literal[
-        'default',
-        'absolute-colorimetric',
-        'perceptual',
-        'relative-colorimetric',
-        'saturation',
-        'embed',
-        'ignore',
-    ]
-    Transform: TypeAlias = Callable[[Image.Image], None]
+ColorMode: TypeAlias = Literal[
+    'default',
+    'absolute-colorimetric',
+    'perceptual',
+    'relative-colorimetric',
+    'saturation',
+    'embed',
+    'ignore',
+]
+Transform: TypeAlias = Callable[[Image.Image], None]
 
 
 class DeepZoomServer(Flask):
@@ -193,7 +188,7 @@ def create_app(
             icc_profile=tile.info.get('icc_profile'),
         )
         resp = make_response(buf.getvalue())
-        resp.mimetype = 'image/%s' % format
+        resp.mimetype = f'image/{format}'
         return resp
 
     return app


=====================================
examples/deepzoom/deepzoom_tile.py
=====================================
@@ -34,20 +34,16 @@ from pathlib import Path
 import re
 import shutil
 import sys
-from typing import TYPE_CHECKING, Literal
+from typing import TYPE_CHECKING, Literal, TypeAlias
 from unicodedata import normalize
 import zlib
 
 from PIL import Image, ImageCms
 
-if TYPE_CHECKING:
-    # Python 3.10+
-    from typing import TypeAlias
-
 if os.name == 'nt':
     _dll_path = os.getenv('OPENSLIDE_PATH')
     if _dll_path is not None:
-        with os.add_dll_directory(_dll_path):  # type: ignore[attr-defined,unused-ignore]  # noqa: E501
+        with os.add_dll_directory(_dll_path):  # type: ignore[attr-defined,unused-ignore]
             import openslide
     else:
         import openslide
@@ -76,20 +72,21 @@ SRGB_PROFILE_BYTES = zlib.decompress(
 )
 SRGB_PROFILE = ImageCms.getOpenProfile(BytesIO(SRGB_PROFILE_BYTES))
 
+ColorMode: TypeAlias = Literal[
+    'default',
+    'absolute-colorimetric',
+    'perceptual',
+    'relative-colorimetric',
+    'saturation',
+    'embed',
+    'ignore',
+]
+Transform: TypeAlias = Callable[[Image.Image], None]
 if TYPE_CHECKING:
-    ColorMode: TypeAlias = Literal[
-        'default',
-        'absolute-colorimetric',
-        'perceptual',
-        'relative-colorimetric',
-        'saturation',
-        'embed',
-        'ignore',
-    ]
+    # Python 3.12+
     TileQueue: TypeAlias = multiprocessing.queues.JoinableQueue[
         tuple[str | None, int, tuple[int, int], Path] | None
     ]
-    Transform: TypeAlias = Callable[[Image.Image], None]
 
 
 class TileWorker(Process):
@@ -231,8 +228,7 @@ class DeepZoomImageTiler:
         count, total = self._processed, self._dz.tile_count
         if count % 100 == 0 or count == total:
             print(
-                "Tiling %s: wrote %d/%d tiles"
-                % (self._associated or 'slide', count, total),
+                f'Tiling {self._associated or "slide"}: wrote {count}/{total} tiles',
                 end='\r',
                 file=sys.stderr,
             )
@@ -320,7 +316,7 @@ class DeepZoomStaticTiler:
             base = VIEWER_SLIDE_NAME
         else:
             base = self._slugify(associated)
-        return '%s.dzi' % base
+        return f'{base}.dzi'
 
     def _write_html(self) -> None:
         import jinja2


=====================================
examples/deepzoom/static/jquery.js
=====================================
The diff for this file was not included because it is too large.

=====================================
examples/deepzoom/static/openseadragon.js
=====================================
The diff for this file was not included because it is too large.

=====================================
examples/deepzoom/templates/files.html
=====================================
@@ -1,7 +1,7 @@
 <!doctype html>
 <title>Available Slides</title>
 
-<style type="text/css">
+<style>
 li {
     list-style-type: none;
     margin: 0.4em 0;


=====================================
examples/deepzoom/templates/slide-fullpage.html
=====================================
@@ -2,7 +2,7 @@
 <meta charset="utf-8">
 <title>{{ slide_filename }}</title>
 
-<style type="text/css">
+<style>
 html {
     overflow: hidden;
 }
@@ -22,10 +22,10 @@ div#view {
 
 <div id="view"></div>
 
-<script type="text/javascript" src="{{ url_for('static', filename='jquery.js') }}"></script>
-<script type="text/javascript" src="{{ url_for('static', filename='openseadragon.js') }}"></script>
-<script type="text/javascript" src="{{ url_for('static', filename='openseadragon-scalebar.js') }}"></script>
-<script type="text/javascript">
+<script src="{{ url_for('static', filename='jquery.js') }}"></script>
+<script src="{{ url_for('static', filename='openseadragon.js') }}"></script>
+<script src="{{ url_for('static', filename='openseadragon-scalebar.js') }}"></script>
+<script>
 $(function() {
     var viewer = new OpenSeadragon({
         id: "view",


=====================================
examples/deepzoom/templates/slide-multipane.html
=====================================
@@ -3,7 +3,7 @@
 <title>Slide Viewer</title>
 <meta name="viewport" content="user-scalable=no">
 
-<style type="text/css">
+<style>
 html {
     overflow: hidden;
 }
@@ -110,10 +110,10 @@ div#properties dd {
     {% endif %}
 </div>
 
-<script type="text/javascript" src="static/jquery.js"></script>
-<script type="text/javascript" src="static/openseadragon.js"></script>
-<script type="text/javascript" src="static/openseadragon-scalebar.js"></script>
-<script type="text/javascript">
+<script src="static/jquery.js"></script>
+<script src="static/openseadragon.js"></script>
+<script src="static/openseadragon-scalebar.js"></script>
+<script>
 $(function() {
     var dzi_data = {{ dzi_data|default('{}')|safe }};
     var viewer = new OpenSeadragon({


=====================================
openslide/__init__.py
=====================================
@@ -35,30 +35,27 @@ from PIL import Image, ImageCms
 from openslide import lowlevel
 
 # Re-exports for the benefit of library users
-from openslide._version import (  # noqa: F401  module-imported-but-unused
-    __version__ as __version__,
-)
+from openslide._version import __version__ as __version__
+from openslide.lowlevel import OpenSlideError as OpenSlideError
 from openslide.lowlevel import (
     OpenSlideUnsupportedFormatError as OpenSlideUnsupportedFormatError,
 )
-from openslide.lowlevel import (  # noqa: F401  module-imported-but-unused
-    OpenSlideVersionError as OpenSlideVersionError,
-)
-from openslide.lowlevel import OpenSlideError as OpenSlideError
+from openslide.lowlevel import OpenSlideVersionError as OpenSlideVersionError
 
 __library_version__ = lowlevel.get_version()
 
-PROPERTY_NAME_COMMENT = 'openslide.comment'
-PROPERTY_NAME_VENDOR = 'openslide.vendor'
-PROPERTY_NAME_QUICKHASH1 = 'openslide.quickhash-1'
 PROPERTY_NAME_BACKGROUND_COLOR = 'openslide.background-color'
-PROPERTY_NAME_OBJECTIVE_POWER = 'openslide.objective-power'
-PROPERTY_NAME_MPP_X = 'openslide.mpp-x'
-PROPERTY_NAME_MPP_Y = 'openslide.mpp-y'
+PROPERTY_NAME_BARCODE = 'openslide.barcode'
+PROPERTY_NAME_BOUNDS_HEIGHT = 'openslide.bounds-height'
+PROPERTY_NAME_BOUNDS_WIDTH = 'openslide.bounds-width'
 PROPERTY_NAME_BOUNDS_X = 'openslide.bounds-x'
 PROPERTY_NAME_BOUNDS_Y = 'openslide.bounds-y'
-PROPERTY_NAME_BOUNDS_WIDTH = 'openslide.bounds-width'
-PROPERTY_NAME_BOUNDS_HEIGHT = 'openslide.bounds-height'
+PROPERTY_NAME_COMMENT = 'openslide.comment'
+PROPERTY_NAME_MPP_X = 'openslide.mpp-x'
+PROPERTY_NAME_MPP_Y = 'openslide.mpp-y'
+PROPERTY_NAME_OBJECTIVE_POWER = 'openslide.objective-power'
+PROPERTY_NAME_QUICKHASH1 = 'openslide.quickhash-1'
+PROPERTY_NAME_VENDOR = 'openslide.vendor'
 
 _T = TypeVar('_T')
 
@@ -173,7 +170,9 @@ class AbstractSlide(metaclass=ABCMeta):
         """Return a PIL.Image containing an RGB thumbnail of the image.
 
         size:     the maximum size of the thumbnail."""
-        downsample = max(dim / thumb for dim, thumb in zip(self.dimensions, size))
+        downsample = max(
+            dim / thumb for dim, thumb in zip(self.dimensions, size, strict=True)
+        )
         level = self.get_best_level_for_downsample(downsample)
         tile = self.read_region((0, 0), level, self.level_dimensions[level])
         # Apply on solid background
@@ -394,9 +393,7 @@ class ImageSlide(AbstractSlide):
         If the file format is not recognized, return None."""
         try:
             with Image.open(filename) as img:
-                # img currently resolves as Any
-                # https://github.com/python-pillow/Pillow/pull/8362
-                return img.format  # type: ignore[no-any-return]
+                return img.format
         except OSError:
             return None
 
@@ -459,27 +456,32 @@ class ImageSlide(AbstractSlide):
         if self._image is None:
             raise ValueError('Cannot read from a closed slide')
         if level != 0:
-            raise OpenSlideError("Invalid level")
+            raise OpenSlideError('Invalid level')
         if ['fail' for s in size if s < 0]:
-            raise OpenSlideError(f"Size {size} must be non-negative")
+            raise OpenSlideError(f'Size {size} must be non-negative')
         # Any corner of the requested region may be outside the bounds of
         # the image.  Create a transparent tile of the correct size and
         # paste the valid part of the region into the correct location.
         image_topleft = [
-            max(0, min(l, limit - 1)) for l, limit in zip(location, self._image.size)
+            max(0, min(l, limit - 1))
+            for l, limit in zip(location, self._image.size, strict=True)
         ]
         image_bottomright = [
             max(0, min(l + s - 1, limit - 1))
-            for l, s, limit in zip(location, size, self._image.size)
+            for l, s, limit in zip(location, size, self._image.size, strict=True)
         ]
-        tile = Image.new("RGBA", size, (0,) * 4)
+        tile = Image.new('RGBA', size, (0,) * 4)
         if not [
-            'fail' for tl, br in zip(image_topleft, image_bottomright) if br - tl < 0
+            'fail'
+            for tl, br in zip(image_topleft, image_bottomright, strict=True)
+            if br - tl < 0
         ]:  # "< 0" not a typo
             # Crop size is greater than zero in both dimensions.
             # PIL thinks the bottom right is the first *excluded* pixel
             crop_box = tuple(image_topleft + [d + 1 for d in image_bottomright])
-            tile_offset = tuple(il - l for il, l in zip(image_topleft, location))
+            tile_offset = tuple(
+                il - l for il, l in zip(image_topleft, location, strict=True)
+            )
             assert len(crop_box) == 4 and len(tile_offset) == 2
             crop = self._image.crop(crop_box)
             tile.paste(crop, tile_offset)
@@ -502,12 +504,12 @@ def open_slide(filename: lowlevel.Filename) -> OpenSlide | ImageSlide:
 if __name__ == '__main__':
     import sys
 
-    print("OpenSlide vendor:", OpenSlide.detect_format(sys.argv[1]))
-    print("PIL format:", ImageSlide.detect_format(sys.argv[1]))
+    print('OpenSlide vendor:', OpenSlide.detect_format(sys.argv[1]))
+    print('PIL format:', ImageSlide.detect_format(sys.argv[1]))
     with open_slide(sys.argv[1]) as _slide:
-        print("Dimensions:", _slide.dimensions)
-        print("Levels:", _slide.level_count)
-        print("Level dimensions:", _slide.level_dimensions)
-        print("Level downsamples:", _slide.level_downsamples)
-        print("Properties:", _slide.properties)
-        print("Associated images:", _slide.associated_images)
+        print('Dimensions:', _slide.dimensions)
+        print('Levels:', _slide.level_count)
+        print('Level dimensions:', _slide.level_dimensions)
+        print('Level downsamples:', _slide.level_downsamples)
+        print('Properties:', _slide.properties)
+        print('Associated images:', _slide.associated_images)


=====================================
openslide/_convert.c
=====================================
@@ -48,9 +48,9 @@ argb2rgba(PY_UINT32_T *buf, Py_ssize_t len)
             u8 g = 255 * ((val >>  8) & 0xff) / a;
             u8 b = 255 * ((val >>  0) & 0xff) / a;
 #ifdef WORDS_BIGENDIAN
-            val = r << 24 | g << 16 | b << 8 | a;
+            val = (PY_UINT32_T) r << 24 | g << 16 | b << 8 | a;
 #else
-            val = a << 24 | b << 16 | g << 8 | r;
+            val = (PY_UINT32_T) a << 24 | b << 16 | g << 8 | r;
 #endif
             buf[cur] = val;
             break;


=====================================
openslide/_version.py
=====================================
@@ -21,4 +21,4 @@
 This module is an implementation detail.  The package version should be
 obtained from openslide.__version__."""
 
-__version__ = '1.4.3'
+__version__ = '1.4.6'


=====================================
openslide/deepzoom.py
=====================================
@@ -26,17 +26,13 @@ from __future__ import annotations
 
 from io import BytesIO
 import math
-from typing import TYPE_CHECKING
+from typing import TypeGuard
 from xml.etree.ElementTree import Element, ElementTree, SubElement
 
 from PIL import Image
 
 import openslide
 
-if TYPE_CHECKING:
-    # Python 3.10+
-    from typing import TypeGuard
-
 
 class DeepZoomGenerator:
     """Generates Deep Zoom tiles and metadata."""
@@ -88,13 +84,15 @@ class DeepZoomGenerator:
             # Slide level dimensions scale factor in each axis
             size_scale = tuple(
                 int(osr.properties.get(prop, l0_lim)) / l0_lim
-                for prop, l0_lim in zip(self.BOUNDS_SIZE_PROPS, osr.dimensions)
+                for prop, l0_lim in zip(
+                    self.BOUNDS_SIZE_PROPS, osr.dimensions, strict=True
+                )
             )
             # Dimensions of active area
             self._l_dimensions = tuple(
                 tuple(
-                    int(math.ceil(l_lim * scale))
-                    for l_lim, scale in zip(l_size, size_scale)
+                    math.ceil(l_lim * scale)
+                    for l_lim, scale in zip(l_size, size_scale, strict=True)
                 )
                 for l_size in osr.level_dimensions
             )
@@ -106,14 +104,14 @@ class DeepZoomGenerator:
         z_size = self._l0_dimensions
         z_dimensions = [z_size]
         while z_size[0] > 1 or z_size[1] > 1:
-            z_size = tuple(max(1, int(math.ceil(z / 2))) for z in z_size)
+            z_size = tuple(max(1, math.ceil(z / 2)) for z in z_size)
             z_dimensions.append(z_size)
         # Narrow the type, for self.level_dimensions
         self._z_dimensions = self._pairs_from_n_tuples(tuple(reversed(z_dimensions)))
 
         # Tile
         def tiles(z_lim: int) -> int:
-            return int(math.ceil(z_lim / self._z_t_downsample))
+            return math.ceil(z_lim / self._z_t_downsample)
 
         self._t_dimensions = tuple(
             (tiles(z_w), tiles(z_h)) for z_w, z_h in self._z_dimensions
@@ -147,13 +145,7 @@ class DeepZoomGenerator:
         )
 
     def __repr__(self) -> str:
-        return '{}({!r}, tile_size={!r}, overlap={!r}, limit_bounds={!r})'.format(
-            self.__class__.__name__,
-            self._osr,
-            self._z_t_downsample,
-            self._z_overlap,
-            self._limit_bounds,
-        )
+        return f'{self.__class__.__name__}({self._osr!r}, tile_size={self._z_t_downsample!r}, overlap={self._z_overlap!r}, limit_bounds={self._limit_bounds!r})'
 
     @property
     def level_count(self) -> int:
@@ -208,10 +200,10 @@ class DeepZoomGenerator:
     ) -> tuple[tuple[tuple[int, int], int, tuple[int, int]], tuple[int, int]]:
         # Check parameters
         if dz_level < 0 or dz_level >= self._dz_levels:
-            raise ValueError("Invalid level")
-        for t, t_lim in zip(t_location, self._t_dimensions[dz_level]):
+            raise ValueError('Invalid level')
+        for t, t_lim in zip(t_location, self._t_dimensions[dz_level], strict=True):
             if t < 0 or t >= t_lim:
-                raise ValueError("Invalid address")
+                raise ValueError('Invalid address')
 
         # Get preferred slide level
         slide_level = self._slide_from_dz_level[dz_level]
@@ -220,14 +212,18 @@ class DeepZoomGenerator:
         z_overlap_tl = tuple(self._z_overlap * int(t != 0) for t in t_location)
         z_overlap_br = tuple(
             self._z_overlap * int(t != t_lim - 1)
-            for t, t_lim in zip(t_location, self.level_tiles[dz_level])
+            for t, t_lim in zip(t_location, self.level_tiles[dz_level], strict=True)
         )
 
         # Get final size of the tile
         z_size = tuple(
             min(self._z_t_downsample, z_lim - self._z_t_downsample * t) + z_tl + z_br
             for t, z_lim, z_tl, z_br in zip(
-                t_location, self._z_dimensions[dz_level], z_overlap_tl, z_overlap_br
+                t_location,
+                self._z_dimensions[dz_level],
+                z_overlap_tl,
+                z_overlap_br,
+                strict=True,
             )
         )
 
@@ -235,16 +231,18 @@ class DeepZoomGenerator:
         z_location = [self._z_from_t(t) for t in t_location]
         l_location = [
             self._l_from_z(dz_level, z - z_tl)
-            for z, z_tl in zip(z_location, z_overlap_tl)
+            for z, z_tl in zip(z_location, z_overlap_tl, strict=True)
         ]
         # Round location down and size up, and add offset of active area
         l0_location = tuple(
             int(self._l0_from_l(slide_level, l) + l0_off)
-            for l, l0_off in zip(l_location, self._l0_offset)
+            for l, l0_off in zip(l_location, self._l0_offset, strict=True)
         )
         l_size = tuple(
             int(min(math.ceil(self._l_from_z(dz_level, dz)), l_lim - math.ceil(l)))
-            for l, dz, l_lim in zip(l_location, z_size, self._l_dimensions[slide_level])
+            for l, dz, l_lim in zip(
+                l_location, z_size, self._l_dimensions[slide_level], strict=True
+            )
         )
 
         # Return read_region() parameters plus tile size for final scaling


=====================================
openslide/lowlevel.py
=====================================
@@ -50,16 +50,13 @@ from ctypes import (
 from itertools import count
 import os
 import platform
-from typing import TYPE_CHECKING, Any, Protocol, TypeVar, cast
+from typing import TYPE_CHECKING, Any, ParamSpec, Protocol, TypeAlias, TypeVar, cast
 
 from PIL import Image
 
 from . import _convert
 
 if TYPE_CHECKING:
-    # Python 3.10+
-    from typing import ParamSpec, TypeAlias
-
     from _convert import _Buffer
 
 
@@ -89,10 +86,10 @@ def _load_library() -> CDLL:
         except FileNotFoundError as exc:
             raise ModuleNotFoundError(
                 "Couldn't locate OpenSlide DLL. "
-                "Try `pip install openslide-bin`, "
+                'Try `pip install openslide-bin`, '
                 "or if you're using an OpenSlide binary package, "
                 "ensure you've called os.add_dll_directory(). "
-                "https://openslide.org/api/python/#installing"
+                'https://openslide.org/api/python/#installing'
             ) from exc
     elif platform.system() == 'Darwin':
         try:
@@ -107,8 +104,8 @@ def _load_library() -> CDLL:
             if lib is None:
                 raise ModuleNotFoundError(
                     "Couldn't locate OpenSlide dylib. "
-                    "Try `pip install openslide-bin`. "
-                    "https://openslide.org/api/python/#installing"
+                    'Try `pip install openslide-bin`. '
+                    'https://openslide.org/api/python/#installing'
                 ) from exc
             return cdll.LoadLibrary(lib)
     else:
@@ -117,8 +114,8 @@ def _load_library() -> CDLL:
         except OSError as exc:
             raise ModuleNotFoundError(
                 "Couldn't locate OpenSlide shared library. "
-                "Try `pip install openslide-bin`. "
-                "https://openslide.org/api/python/#installing"
+                'Try `pip install openslide-bin`. '
+                'https://openslide.org/api/python/#installing'
             ) from exc
 
 
@@ -170,11 +167,11 @@ class _OpenSlide:
     @classmethod
     def from_param(cls, obj: _OpenSlide) -> _OpenSlide:
         if obj.__class__ != cls:
-            raise ValueError("Not an OpenSlide reference")
+            raise ValueError('Not an OpenSlide reference')
         if not obj._as_parameter_:
-            raise ValueError("Passing undefined slide object")
+            raise ValueError('Passing undefined slide object')
         if not obj._valid:
-            raise ValueError("Passing closed slide object")
+            raise ValueError('Passing closed slide object')
         return obj
 
 
@@ -193,15 +190,13 @@ class _OpenSlideCache:
     @classmethod
     def from_param(cls, obj: _OpenSlideCache) -> _OpenSlideCache:
         if obj.__class__ != cls:
-            raise ValueError("Not an OpenSlide cache reference")
+            raise ValueError('Not an OpenSlide cache reference')
         if not obj._as_parameter_:
-            raise ValueError("Passing undefined cache object")
+            raise ValueError('Passing undefined cache object')
         return obj
 
 
-if TYPE_CHECKING:
-    # Python 3.10+
-    Filename: TypeAlias = str | bytes | os.PathLike[Any]
+Filename: TypeAlias = str | bytes | os.PathLike[Any]
 
 
 class _filename_p:
@@ -247,7 +242,7 @@ class _size_t:
 
 
 def _load_image(buf: _Buffer, size: tuple[int, int]) -> Image.Image:
-    '''buf must be a mutable buffer.'''
+    """buf must be a mutable buffer."""
     _convert.argb2rgba(buf)
     return Image.frombuffer('RGBA', size, buf, 'raw', 'RGBA', 0, 1)
 
@@ -255,7 +250,7 @@ def _load_image(buf: _Buffer, size: tuple[int, int]) -> Image.Image:
 # check for errors opening an image file and wrap the resulting handle
 def _check_open(result: int | None, _func: Any, _args: Any) -> _OpenSlide:
     if result is None:
-        raise OpenSlideUnsupportedFormatError("Unsupported or missing image file")
+        raise OpenSlideUnsupportedFormatError('Unsupported or missing image file')
     slide = _OpenSlide(c_void_p(result))
     err = get_error(slide)
     if err is not None:
@@ -303,7 +298,7 @@ def _check_name_list(result: _Pointer[c_char_p], func: Any, args: Any) -> list[s
 
 
 class _FunctionUnavailable:
-    '''Standin for a missing optional function.  Fails when called.'''
+    """Standin for a missing optional function.  Fails when called."""
 
     def __init__(self, minimum_version: str):
         self._minimum_version = minimum_version
@@ -314,24 +309,25 @@ class _FunctionUnavailable:
         raise OpenSlideVersionError(self._minimum_version)
 
 
-# gate runtime code that requires ParamSpec, Python 3.10+
-if TYPE_CHECKING:
-    _P = ParamSpec('_P')
-    _T = TypeVar('_T', covariant=True)
+_P = ParamSpec('_P')
+_T = TypeVar('_T', covariant=True)
 
-    class _Func(Protocol[_P, _T]):
-        available: bool
 
-        def __call__(self, *args: _P.args) -> _T: ...  # type: ignore[valid-type]
+class _Func(Protocol[_P, _T]):
+    available: bool
 
-    class _CTypesFunc(_Func[_P, _T]):
-        restype: type | None
-        argtypes: list[type]
-        errcheck: _ErrCheck
+    def __call__(self, *args: _P.args) -> _T: ...  # type: ignore[valid-type]
 
-    _ErrCheck: TypeAlias = (
-        Callable[[Any, _CTypesFunc[..., Any], tuple[Any, ...]], Any] | None
-    )
+
+class _CTypesFunc(_Func[_P, _T]):
+    restype: type | None
+    argtypes: list[type]
+    errcheck: _ErrCheck
+
+
+_ErrCheck: TypeAlias = (
+    Callable[[Any, _CTypesFunc[..., Any], tuple[Any, ...]], Any] | None
+)
 
 
 # resolve and return an OpenSlide function with the specified properties
@@ -361,11 +357,7 @@ def _wraps_funcs(
     wrapped: list[_Func[..., Any]],
 ) -> Callable[[Callable[_P, _T]], _Func[_P, _T]]:
     def decorator(fn: Callable[_P, _T]) -> _Func[_P, _T]:
-        if TYPE_CHECKING:
-            # requires ParamSpec, Python 3.10+
-            f = cast(_Func[_P, _T], fn)
-        else:
-            f = fn
+        f = cast('_Func[_P, _T]', fn)
         f.available = True
         for w in wrapped:
             f.available = f.available and w.available
@@ -379,7 +371,7 @@ try:
         'openslide_detect_vendor', c_char_p, [_filename_p], _check_string
     )
 except AttributeError:
-    raise OpenSlideVersionError('3.4.0')
+    raise OpenSlideVersionError('3.4.0') from None
 
 open: _Func[[Filename], _OpenSlide] = _func(
     'openslide_open', c_void_p, [_filename_p], _check_open
@@ -434,7 +426,7 @@ def read_region(
         # OpenSlide would catch this, but not before we tried to allocate
         # a negative-size buffer
         raise OpenSlideError(
-            "negative width (%d) or negative height (%d) not allowed" % (w, h)
+            f'negative width ({w}) or negative height ({h}) not allowed'
         )
     if w == 0 or h == 0:
         # Image.frombuffer() would raise an exception


=====================================
openslide_python.egg-info/PKG-INFO
=====================================
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: openslide-python
-Version: 1.4.3
+Version: 1.4.6
 Summary: Python interface to OpenSlide
 Maintainer-email: OpenSlide project <openslide-users at lists.andrew.cmu.edu>
 License-Expression: LGPL-2.1-only AND BSD-3-Clause AND MIT AND LicenseRef-Public-Domain
@@ -51,8 +51,10 @@ closest to a desired zoom level.
 OpenSlide can read virtual slides in several formats:
 
 * [Aperio][] (`.svs`, `.tif`)
+* [ARGOS][] (`.avs`)
 * [DICOM][] (`.dcm`)
 * [Hamamatsu][] (`.ndpi`, `.vms`, `.vmu`)
+* [Huron][] (`.tif`)
 * [Leica][] (`.scn`)
 * [MIRAX][] (`.mrxs`)
 * [Philips][] (`.tiff`)
@@ -64,8 +66,10 @@ OpenSlide can read virtual slides in several formats:
 
 [OpenSlide]: https://openslide.org/
 [Aperio]: https://openslide.org/formats/aperio/
+[ARGOS]: https://openslide.org/formats/argos/
 [DICOM]: https://openslide.org/formats/dicom/
 [Hamamatsu]: https://openslide.org/formats/hamamatsu/
+[Huron]: https://openslide.org/formats/huron/
 [Leica]: https://openslide.org/formats/leica/
 [MIRAX]: https://openslide.org/formats/mirax/
 [Philips]: https://openslide.org/formats/philips/


=====================================
pyproject.toml
=====================================
@@ -50,28 +50,12 @@ version = {attr = "openslide._version.__version__"}
 [tool.setuptools.package-data]
 openslide = ["py.typed", "*.pyi"]
 
-[tool.black]
-skip-string-normalization = true
-target-version = ["py310", "py311", "py312", "py313", "py314"]
-
 # Ref: https://github.com/codespell-project/codespell#using-a-config-file
 [tool.codespell]
 check-hidden = true
 # ignore-regex = ""
 # ignore-words-list = ""
 
-# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#flake8
-# also ignore:
-# - E741 ambiguous variable name
-# requires Flake8-pyproject
-[tool.flake8]
-max-line-length = 88
-extend-ignore = ["E203", "E741"]
-
-[tool.isort]
-profile = "black"
-force_sort_within_sections = true
-
 [tool.mypy]
 python_version = "3.10"
 strict = true
@@ -87,6 +71,17 @@ pythonpath = "tests"
 [tool.rstcheck]
 ignore_messages = "(Hyperlink target \".*\" is not referenced\\.$)"
 
+[tool.ruff.format]
+quote-style = "single"
+
+[tool.ruff.lint]
+extend-select = ["B", "C4", "FLY", "I", "RUF", "UP"]
+# ambiguous-variable-name
+ignore = ["E741"]
+
+[tool.ruff.lint.isort]
+force-sort-within-sections = true
+
 [build-system]
 requires = ["setuptools >= 77.0.0"]
 build-backend = "setuptools.build_meta"


=====================================
tests/common.py
=====================================
@@ -29,7 +29,7 @@ if os.name == 'nt':
     # environment.
     _dll_path = os.getenv('OPENSLIDE_PATH')
     if _dll_path is not None:
-        with os.add_dll_directory(_dll_path):  # type: ignore[attr-defined,unused-ignore]  # noqa: E501
+        with os.add_dll_directory(_dll_path):  # type: ignore[attr-defined,unused-ignore]
             import openslide  # noqa: F401  module-imported-but-unused
 
 


=====================================
tests/test_base.py
=====================================
@@ -34,7 +34,7 @@ class TestLibrary(unittest.TestCase):
             self.assertTrue(isinstance(osr, ImageSlide))
 
     def test_lowlevel_available(self) -> None:
-        '''Ensure all exported functions have an 'available' attribute.'''
+        """Ensure all exported functions have an 'available' attribute."""
         for name in dir(lowlevel):
             attr = getattr(lowlevel, name)
             # ignore classes and unexported functions


=====================================
tests/test_deepzoom.py
=====================================
@@ -44,8 +44,7 @@ class _Abstract:
         def test_repr(self) -> None:
             self.assertEqual(
                 repr(self.dz),
-                'DeepZoomGenerator(%r, tile_size=254, overlap=1, limit_bounds=False)'
-                % self.osr,
+                f'DeepZoomGenerator({self.osr!r}, tile_size=254, overlap=1, limit_bounds=False)',
             )
 
         def test_metadata(self) -> None:
@@ -87,7 +86,7 @@ class _Abstract:
 
         def test_tile_color_profile(self) -> None:
             if self.CLASS is OpenSlide and not lowlevel.read_icc_profile.available:
-                self.skipTest("requires OpenSlide 4.0.0")
+                self.skipTest('requires OpenSlide 4.0.0')
             self.assertEqual(len(self.dz.get_tile(9, (1, 0)).info['icc_profile']), 588)
 
         def test_get_tile_bad_level(self) -> None:


=====================================
tests/test_imageslide.py
=====================================
@@ -21,8 +21,8 @@ from __future__ import annotations
 import sys
 import unittest
 
-from PIL import Image
 from common import file_path
+from PIL import Image
 
 from openslide import ImageSlide, OpenSlideCache, OpenSlideError, lowlevel
 
@@ -42,7 +42,7 @@ class TestImageWithoutOpening(unittest.TestCase):
         with Image.open(file_path('boxes.png')) as img:
             with ImageSlide(img) as osr:
                 self.assertEqual(osr.dimensions, (300, 250))
-                self.assertEqual(repr(osr), 'ImageSlide(%r)' % img)
+                self.assertEqual(repr(osr), f'ImageSlide({img!r})')
 
     @unittest.skipUnless(
         sys.getfilesystemencoding() == 'utf-8',
@@ -96,7 +96,7 @@ class TestImage(_Abstract.SlideTest):
     FILENAME = 'boxes.png'
 
     def test_repr(self) -> None:
-        self.assertEqual(repr(self.osr), 'ImageSlide(%r)' % file_path('boxes.png'))
+        self.assertEqual(repr(self.osr), f'ImageSlide({file_path("boxes.png")!r})')
 
     def test_metadata(self) -> None:
         self.assertEqual(self.osr.level_count, 1)
@@ -141,7 +141,7 @@ class TestImage(_Abstract.SlideTest):
     def test_thumbnail(self) -> None:
         self.assertEqual(self.osr.get_thumbnail((100, 100)).size, (100, 83))
 
-    @unittest.skipUnless(lowlevel.cache_create.available, "requires OpenSlide 4.0.0")
+    @unittest.skipUnless(lowlevel.cache_create.available, 'requires OpenSlide 4.0.0')
     def test_set_cache(self) -> None:
         self.osr.set_cache(OpenSlideCache(64 << 10))
         self.assertEqual(self.osr.read_region((0, 0), 0, (400, 400)).size, (400, 400))


=====================================
tests/test_openslide.py
=====================================
@@ -36,14 +36,15 @@ from openslide import (
 
 
 class TestCache(unittest.TestCase):
-    @unittest.skipUnless(lowlevel.cache_create.available, "requires OpenSlide 4.0.0")
+    @unittest.skipUnless(lowlevel.cache_create.available, 'requires OpenSlide 4.0.0')
     def test_create_cache(self) -> None:
         OpenSlideCache(0)
         OpenSlideCache(1)
         OpenSlideCache(4 << 20)
         self.assertRaises(ArgumentError, lambda: OpenSlideCache(-1))
         self.assertRaises(
-            ArgumentError, lambda: OpenSlideCache(1.3)  # type: ignore[arg-type]
+            ArgumentError,
+            lambda: OpenSlideCache(1.3),  # type: ignore[arg-type]
         )
 
 
@@ -123,7 +124,7 @@ class TestSlide(_Abstract.SlideTest):
     FILENAME = 'boxes.tiff'
 
     def test_repr(self) -> None:
-        self.assertEqual(repr(self.osr), 'OpenSlide(%r)' % file_path('boxes.tiff'))
+        self.assertEqual(repr(self.osr), f'OpenSlide({file_path("boxes.tiff")!r})')
 
     def test_basic_metadata(self) -> None:
         self.assertEqual(self.osr.level_count, 4)
@@ -145,15 +146,13 @@ class TestSlide(_Abstract.SlideTest):
         self.assertEqual(self.osr.properties['openslide.vendor'], 'generic-tiff')
         self.assertRaises(KeyError, lambda: self.osr.properties['__does_not_exist'])
         # test __len__ and __iter__
+        self.assertEqual(len(list(self.osr.properties)), len(self.osr.properties))
         self.assertEqual(
-            len([v for v in self.osr.properties]), len(self.osr.properties)
-        )
-        self.assertEqual(
-            repr(self.osr.properties), '<_PropertyMap %r>' % dict(self.osr.properties)
+            repr(self.osr.properties), f'<_PropertyMap {dict(self.osr.properties)!r}>'
         )
 
     @unittest.skipUnless(
-        lowlevel.read_icc_profile.available, "requires OpenSlide 4.0.0"
+        lowlevel.read_icc_profile.available, 'requires OpenSlide 4.0.0'
     )
     def test_color_profile(self) -> None:
         assert self.osr.color_profile is not None  # for type inference
@@ -192,15 +191,17 @@ class TestSlide(_Abstract.SlideTest):
     def test_thumbnail(self) -> None:
         self.assertEqual(self.osr.get_thumbnail((100, 100)).size, (100, 83))
 
-    @unittest.skipUnless(lowlevel.cache_create.available, "requires OpenSlide 4.0.0")
+    @unittest.skipUnless(lowlevel.cache_create.available, 'requires OpenSlide 4.0.0')
     def test_set_cache(self) -> None:
         self.osr.set_cache(OpenSlideCache(64 << 10))
         self.assertEqual(self.osr.read_region((0, 0), 0, (400, 400)).size, (400, 400))
         self.assertRaises(
-            TypeError, lambda: self.osr.set_cache(None)  # type: ignore[arg-type]
+            TypeError,
+            lambda: self.osr.set_cache(None),  # type: ignore[arg-type]
         )
         self.assertRaises(
-            TypeError, lambda: self.osr.set_cache(3)  # type: ignore[arg-type]
+            TypeError,
+            lambda: self.osr.set_cache(3),  # type: ignore[arg-type]
         )
 
 
@@ -212,7 +213,7 @@ class TestAperioSlide(_Abstract.SlideTest):
         self.assertRaises(KeyError, lambda: self.osr.associated_images['__missing'])
         # test __len__ and __iter__
         self.assertEqual(
-            len([v for v in self.osr.associated_images]),
+            len(list(self.osr.associated_images)),
             len(self.osr.associated_images),
         )
 
@@ -221,7 +222,7 @@ class TestAperioSlide(_Abstract.SlideTest):
 
         self.assertEqual(
             mangle_repr(self.osr.associated_images),
-            '<_AssociatedImageMap %s>' % mangle_repr(dict(self.osr.associated_images)),
+            f'<_AssociatedImageMap {mangle_repr(dict(self.osr.associated_images))}>',
         )
 
     def test_color_profile(self) -> None:
@@ -236,7 +237,7 @@ class TestAperioSlide(_Abstract.SlideTest):
 # Requires DICOM support in OpenSlide.  Use associated image ICC support as
 # a proxy.
 @unittest.skipUnless(
-    lowlevel.read_associated_image_icc_profile.available, "requires OpenSlide 4.0.0"
+    lowlevel.read_associated_image_icc_profile.available, 'requires OpenSlide 4.0.0'
 )
 class TestDicomSlide(_Abstract.SlideTest):
     FILENAME = 'boxes_0.dcm'



View it on GitLab: https://salsa.debian.org/med-team/openslide-python/-/commit/bd248edaceedaba4f505393c6408622a3bbaf229

-- 
View it on GitLab: https://salsa.debian.org/med-team/openslide-python/-/commit/bd248edaceedaba4f505393c6408622a3bbaf229
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20260627/b5106fff/attachment-0001.htm>


More information about the debian-med-commit mailing list