Skip to content

Commit

Permalink
Fixes (#119)
Browse files Browse the repository at this point in the history
* Add thread_safe property

* Replace lossy_compressed with lossy_compression property

* Pin zarr to <3.0

* Test openslide cross platform

* Update changelog

* Update version and packages

* Fix openslide checksums

* Skip openslide test on mac on mirax
  • Loading branch information
erikogabrielsson authored Jan 29, 2025
1 parent 74da149 commit ba46340
Show file tree
Hide file tree
Showing 12 changed files with 83 additions and 42 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.16.0] - 2025-01-29

### Added

- Support for reading isyntax files using pyisyntax.
Expand All @@ -15,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Reading correct image size in pyramid series in Bioformats reader.
- Skip non-dyadic pyramid series in Bioformats reader.
- Missing values in `LossyImageCompressionRatio` and `LossyImageCompressionMethod` when converting without re-encoding.
- Pin zarr to <3.0 to fix import exception.

## [0.15.1] - 2025-01-07

Expand Down Expand Up @@ -285,7 +289,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Initial release of wsidicomizer

[Unreleased]: https://github.com/imi-bigpicture/wsidicomizer/compare/0.15.1..HEAD
[Unreleased]: https://github.com/imi-bigpicture/wsidicomizer/compare/0.16.0..HEAD
[0.16.0]: https://github.com/imi-bigpicture/wsidicomizer/compare/0.16.0..0.16.0
[0.15.1]: https://github.com/imi-bigpicture/wsidicomizer/compare/0.15.0..0.15.1
[0.15.0]: https://github.com/imi-bigpicture/wsidicomizer/compare/0.14.2..0.15.0
[0.14.2]: https://github.com/imi-bigpicture/wsidicomizer/compare/0.14.1..0.14.2
Expand Down
14 changes: 7 additions & 7 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wsidicomizer"
version = "0.15.1"
version = "0.16.0"
description = "Tool for reading WSI files from proprietary formats and optionally convert them to to DICOM"
authors = ["Erik O Gabrielsson <[email protected]>"]
license = "Apache-2.0"
Expand All @@ -18,8 +18,8 @@ classifiers = [

[tool.poetry.dependencies]
python = "^3.10"
wsidicom = "^0.22.0"
opentile = "^0.13.1"
wsidicom = "^0.23.0"
opentile = "^0.14.0"
numpy = ">=1.22.0"
pydicom = ">=3.0.0"
czifile = "^2019.7.2"
Expand All @@ -29,6 +29,7 @@ scyjava = { version = "^1.8.1", optional = true }
ome-types = {version = "^0.5.0", optional = true }
pyisyntax = {version = "^0.1.2", optional = true }
imagecodecs = { version = "^2024.12.30", optional = true }
zarr = ">=2.11.0, <3.0"

[tool.poetry.extras]
openslide = ["openslide-python"]
Expand Down
38 changes: 13 additions & 25 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,31 +157,18 @@
"image_coordinate_system": {"x": 2.3061675, "y": 20.79015},
"icc_profile": False,
"read_region": [
# OpenSlide produces different results across platforms
# {
# "location": {
# "x": 50,
# "y": 100
# },
# "level": 6,
# "size": {
# "width": 500,
# "height": 500
# },
# "md5": "fe29e76f5904d65253d8eb742b244789"
# },
# {
# "location": {
# "x": 400,
# "y": 500
# },
# "level": 4,
# "size": {
# "width": 500,
# "height": 500
# },
# "md5": "4f4c904ed9257e385fc8f0818337d9e7"
# }
{
"location": {"x": 50, "y": 100},
"level": 6,
"size": {"width": 500, "height": 500},
"md5": "b9052fc1ebbe99582906c575511fe944",
},
{
"location": {"x": 400, "y": 500},
"level": 4,
"size": {"width": 500, "height": 500},
"md5": "a11881460a4fae78ddfef1100339cac0",
},
],
"read_region_openslide": [
{
Expand All @@ -196,6 +183,7 @@
},
],
"read_thumbnail": [],
"skip_hash_test_platforms": ["Darwin"],
}
},
"ndpi": {
Expand Down
2 changes: 1 addition & 1 deletion wsidicomizer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@

from wsidicomizer.wsidicomizer import WsiDicomizer

__version__ = "0.15.1"
__version__ = "0.16.0"

__all__ = ["WsiDicomizer"]
4 changes: 4 additions & 0 deletions wsidicomizer/extras/bioformats/bioformats_image_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ def photometric_interpretation(self) -> str:
data."""
return self.encoder.photometric_interpretation

@property
def thread_safe(self) -> bool:
return True

def _get_tile(
self, tile_point: Point, z: float, path: str
) -> ContextManager[np.ndarray]:
Expand Down
8 changes: 8 additions & 0 deletions wsidicomizer/extras/isyntax/isyntax_image_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ def blank_color(self) -> Union[int, Tuple[int, int, int]]:
def image_coordinate_system(self) -> ImageCoordinateSystem:
return ImageCoordinateSystem(PointMm(0, 0), 0)

@property
def thread_safe(selt) -> bool:
return False

def stitch_tiles(self, region: Region, path: str, z: float, threads: int) -> Image:
"""Overrides ImageData stitch_tiles() to read reagion directly from
ISyntax object.
Expand Down Expand Up @@ -302,6 +306,10 @@ def pyramid_index(self) -> int:
def image_coordinate_system(self) -> ImageCoordinateSystem:
return ImageCoordinateSystem(PointMm(0, 0), 0)

@property
def thread_safe(self) -> bool:
return True

def _get_encoded_tile(self, tile: Point, z: float, path: str) -> bytes:
if z not in self.focal_planes or path not in self.optical_paths:
raise WsiDicomNotFoundError(
Expand Down
4 changes: 4 additions & 0 deletions wsidicomizer/extras/openslide/openslide_image_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ def optical_paths(self) -> List[str]:
def blank_color(self) -> Union[int, Tuple[int, int, int]]:
return self._blank_color

@property
def thread_safe(self) -> bool:
return True

def _get_blank_color(
self, photometric_interpretation: str
) -> Union[int, Tuple[int, int, int]]:
Expand Down
10 changes: 7 additions & 3 deletions wsidicomizer/image_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
files."""

from abc import ABCMeta, abstractmethod
from typing import List, Optional, Tuple

import numpy as np
from PIL import Image as Pillow
from PIL.Image import Image
from wsidicom import ImageData
from wsidicom.codec import LossyCompressionIsoStandard
from wsidicom.geometry import PointMm, Size
from wsidicom.metadata import ImageCoordinateSystem

Expand Down Expand Up @@ -49,9 +51,11 @@ def bits(self) -> int:
return self.encoder.bits

@property
def lossy_compressed(self) -> bool:
# TODO: This should be set from encoder and base file.
return True
def lossy_compression(
self,
) -> Optional[List[Tuple[LossyCompressionIsoStandard, float]]]:
"""Return None as image compression is for most format not known."""
return None

def _encode(self, image_data: np.ndarray) -> bytes:
"""Return image data encoded in jpeg using set quality and subsample
Expand Down
4 changes: 4 additions & 0 deletions wsidicomizer/sources/czi/czi_image_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ def photometric_interpretation(self) -> str:
def pixel_spacing(self) -> SizeMm:
return self._pixel_spacing

@property
def thread_safe(self) -> bool:
return True

@cached_property
def image_size(self) -> Size:
"""The pixel size of the image."""
Expand Down
23 changes: 21 additions & 2 deletions wsidicomizer/sources/opentile/opentile_image_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@

"""Image data for opentile compatible file."""

from typing import Iterable, Iterator, List, Optional
from typing import Iterable, Iterator, List, Optional, Tuple

from opentile.tiff_image import TiffImage
from PIL import Image as Pillow
from PIL.Image import Image
from pydicom.uid import JPEG2000, UID, JPEG2000Lossless, JPEGBaseline8Bit
from tifffile import COMPRESSION, PHOTOMETRIC
from wsidicom.codec import Encoder
from wsidicom.codec import Encoder, LossyCompressionIsoStandard
from wsidicom.geometry import Point, Size, SizeMm
from wsidicom.metadata import Image as ImageMetadata
from wsidicom.metadata import ImageCoordinateSystem
Expand Down Expand Up @@ -138,6 +138,25 @@ def photometric_interpretation(self) -> str:
def samples_per_pixel(self) -> int:
return self._tiff_image.samples_per_pixel

@property
def thread_safe(self) -> bool:
return True

@property
def lossy_compression(
self,
) -> Optional[List[Tuple[LossyCompressionIsoStandard, float]]]:
"""Return lossy compression method and compression ratio if lossy compressed."""
iso = LossyCompressionIsoStandard.transfer_syntax_to_iso(self.transfer_syntax)
if iso is None:
return None
uncompressed_size = (
self.image_size.area * self.samples_per_pixel * self.bits // 8
)
compressed_size = self._tiff_image.compressed_size
compression_ratio = round(compressed_size / uncompressed_size, 2)
return [(iso, compression_ratio)]

def _get_encoded_tile(self, tile: Point, z: float, path: str) -> bytes:
"""Return image bytes for tile. Returns transcoded tile if
non-supported encoding.
Expand Down
4 changes: 4 additions & 0 deletions wsidicomizer/sources/tiffslide/tiffslide_image_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ def optical_paths(self) -> List[str]:
def blank_color(self) -> Union[int, Tuple[int, int, int]]:
return self._blank_color

@property
def thread_safe(self) -> bool:
return True

def _get_blank_color(
self, photometric_interpretation: str
) -> Union[int, Tuple[int, int, int]]:
Expand Down

0 comments on commit ba46340

Please sign in to comment.