Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,6 @@ work/

# Ignore version file
*_version.py

# LGPL-licensed test fixtures (downloaded at test time)
tests/data/*.czi
1 change: 1 addition & 0 deletions docs/source/api/readers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ To retrieve a reader class
OpenSlideReader
TiffSlideReader
BioFormatsReader
PylibCZIReader
SpatialDataImage2DReader
20 changes: 20 additions & 0 deletions docs/source/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,23 @@ Installation for slide readers

pip install pyisyntax

.. tab-item:: pylibCZIrw

`pylibCZIrw <https://github.com/ZEISS/pylibczirw>`_ is the official
Python binding to `libCZI <https://github.com/ZEISS/libczi>`_,
maintained by Zeiss, for reading and writing Zeiss CZI whole-slide
images. Unlike BioFormats, it decodes JPEG-XR natively on every
platform it ships wheels for - including ``arm64`` macOS, where
BioFormats cannot load the ``ome:jxrlib`` native library. It is
therefore the recommended backend for ``.czi`` files on Apple
Silicon.

.. code-block:: bash

pip install pylibCZIrw

Wheels are published for Linux (``x86_64`` and ``aarch64``),
macOS (``arm64``), and Windows (``x86_64``) on CPython 3.9-3.13.
There is no macOS ``x86_64`` wheel, so Intel Macs will need to
build from source or use BioFormats.

3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ stain = ["torchstain"]
bioformats = ["scyjava"]
cucim = ["cucim"]
tiffslide = ["tiffslide"]
fastslide = ["fastslide"]
isyntax = ["pyisyntax"]
pylibczi = ["pylibCZIrw"]
plot = ["matplotlib", "legendkit"]

[tool.hatch.version]
Expand Down Expand Up @@ -108,6 +110,7 @@ dev = [
"tiffslide>=2.4.0",
"scyjava>=1.10.1",
"pyisyntax>=0.1.5",
"pylibCZIrw>=6.0.0",
"huggingface-hub>=1.7.2",
]
docs = [
Expand Down
16 changes: 16 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

REPO_ID = "RendeiroLab/LazySlide-data"

# Zeiss CZI test file (LGPL-licensed, not redistributable via our HF repo)
CZI_URL = "https://github.com/ZEISS/pylibczirw/raw/main/test_data/c1_bgr24.czi"


@pytest.fixture(scope="session")
def test_slide():
Expand All @@ -16,6 +19,19 @@ def test_isyntax():
return hf_hub_download(REPO_ID, "testslide.isyntax", repo_type="dataset")


@pytest.fixture(scope="session")
def test_czi():
# Zeiss c1_bgr24.czi from pylibczirw repo (LGPL license, fetched directly)
from urllib.request import urlretrieve

data_dir = Path(__file__).parent / "data"
data_dir.mkdir(parents=True, exist_ok=True)
dest = data_dir / "c1_bgr24.czi"
if not dest.exists():
urlretrieve(CZI_URL, dest)
return str(dest)


@pytest.fixture(scope="session")
def test_store():
slide_zarr_zip = hf_hub_download(REPO_ID, "sample.zarr.zip", repo_type="dataset")
Expand Down
7 changes: 7 additions & 0 deletions tests/test_readers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ def try_import(mod):
def skip_reader(reader):
if reader == "bioformats":
return not try_import("scyjava")
elif reader == "pylibczi":
return not try_import("pylibCZIrw")
else:
return not try_import(reader)

Expand Down Expand Up @@ -58,6 +60,11 @@ def test_isyntax(test_isyntax):
run_reader_test("isyntax", test_isyntax)


@pytest.mark.skipif(skip_reader("pylibczi"), reason="pylibCZIrw not installed")
def test_pylibczi(test_czi):
run_reader_test("pylibczi", test_czi)


def test_spatialdata(test_slide):
import numpy as np
from spatialdata import SpatialData
Expand Down
5,902 changes: 3,008 additions & 2,894 deletions uv.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions wsidata/io/_wsi.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
def open_wsi(
wsi: str | Path | SpatialData,
store: str = "auto",
reader: Literal["openslide", "tiffslide", "bioformats", "isyntax"] = None,
reader: str = None,
attach_images: bool = False,
image_key: str = None,
save_images: bool = True,
Expand Down Expand Up @@ -48,7 +48,7 @@ def open_wsi(
If a directory is supplied, the zarr file will be created in that directory.
This is useful when you want to store all zarr files in a specific location.
reader : str, optional
Reader to use, by default "auto", choosing available reader, first openslide, then tiffslide, then bioformats.
Reader to use, by default "auto", to check avaiable readers: `print(wsidata.READERS)`
attach_images : bool, optional, default: False
Whether to attach whole slide image to image slot in the spatial data object.
image_key : str, optional
Expand Down
2 changes: 2 additions & 0 deletions wsidata/reader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .fastslide import FastSlideReader
from .isyntax import ISyntaxReader
from .openslide import OpenSlideReader
from .pylibczi import PylibCZIReader
from .spatialdata_image2d import SpatialDataImage2DReader
from .tiffslide import TiffSlideReader

Expand All @@ -30,6 +31,7 @@
"CuCIMReader",
"FastSlideReader",
"ISyntaxReader",
"PylibCZIReader",
"SpatialDataImage2DReader",
"TiffSlideReader",
"to_datatree",
Expand Down
17 changes: 16 additions & 1 deletion wsidata/reader/_reader_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,22 @@


class ReaderRegistry(MutableMapping):
priority = ["openslide", "tiffslide", "fastslide", "bioformats", "cucim", "isyntax"]
# ``pylibczi`` is deliberately placed ahead of ``bioformats`` because
# it is CZI-only: pyczi.open_czi raises on any non-CZI input, so
# ``try_open`` falls straight through to BioFormats for other formats.
# Placing it after BioFormats would mean a ``.czi`` file auto-detects
# to BioFormats on arm64 macOS, where the required JPEG-XR native lib
# is unavailable, and the user would only see the failure at first
# read.
priority = [
"openslide",
"tiffslide",
"fastslide",
"pylibczi",
"bioformats",
"cucim",
"isyntax",
]

def __init__(self):
self._readers: Dict[str, type[ReaderBase]] = {}
Expand Down
6 changes: 4 additions & 2 deletions wsidata/reader/fastslide.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from pathlib import Path
from typing import Union

from ._reader_registry import register
from .base import AssociatedImages, ReaderBase, convert_image


@register(name="fastslide")
class FastSlideReader(ReaderBase):
"""
Use FastSlide to interface with image files.
Expand Down Expand Up @@ -59,8 +61,8 @@ def get_thumbnail(self, size, **kwargs):
return convert_image(img)

def detach_reader(self):
if self.reader is not None:
self.reader.close()
if self._reader is not None:
self._reader.close()
self.set_reader(None)

def create_reader(self):
Expand Down
4 changes: 2 additions & 2 deletions wsidata/reader/openslide.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ def get_thumbnail(self, size, **kwargs):
return convert_image(img)

def detach_reader(self):
if self.reader is not None:
if self._reader is not None:
try:
self.reader.close()
self._reader.close()
self.set_reader(None)
# There is a chance that the pointer
# to C-library is already collected
Expand Down
Loading
Loading