Skip to content

Commit

Permalink
Czi fixes (#105)
Browse files Browse the repository at this point in the history
* Fix missing tiles in  block directory

* Fix lock re-entry when block data is not cached
  • Loading branch information
erikogabrielsson committed May 7, 2024
1 parent 295c330 commit 561cbdc
Show file tree
Hide file tree
Showing 7 changed files with 470 additions and 370 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ jobs:
run: sudo apt-get install -y libturbojpeg libopenslide0
- name: Set up OSX
if: matrix.os == 'macos-latest'
run: brew install jpeg-turbo openslide
run: |
echo OS_TAG=macos >> $GITHUB_ENV
echo DYLD_LIBRARY_PATH=/opt/homebrew/lib >> $GITHUB_ENV
brew install jpeg-turbo openslide
- name: Set up Windows
shell: pwsh
if: matrix.os == 'windows-latest'
Expand Down
22 changes: 15 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.14.0] - 2023-04-15
## [0.14.1] - 2024-05-07

### Fixed

- Fix stripes with missing image data for czi source.
- Fix possible cache deadlock for czi source.

## [0.14.0] - 2024-04-15

### Added

Expand All @@ -18,33 +25,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Missing ´label` parameter in bioformats cli.
- Change to empty `WsiDicomizerMetadata` for bioformat source.

## [0.13.2] - 2023-03-20
## [0.13.2] - 2024-03-20

### Fixed

- Missing handling of pyramid index when creating WsiInstance using create_instance().

## [0.13.1] - 2023-02-22
## [0.13.1] - 2024-02-22

### Fixed

- Updated version of `WsiDicom` to 0.20.1 to fix missing to close file handle.

## [0.13.0] - 2023-02-15
## [0.13.0] - 2024-02-15

### Added

- Support for incorporating ICC profiles from source image if available. This is currently limited to images read by the `OpenTile` and `OpenSlide` source, and only for supported formats (primarily `Aperio svs`).

- If no ICC profile is present in the source file or the provided metadata, an empty profile will be used if the photometric interpretation requires it for DICOM compatibility. This behavior can be disabled with `settings.insert_icc_profile_if_missing = False`

## [0.12.1] - 2023-01-12
## [0.12.1] - 2024-01-12

### Fixed

- Fixed missing support for Python 3.12.

## [0.12.0] - 2023-01-12
## [0.12.0] - 2024-01-12

### Changed

Expand Down Expand Up @@ -241,7 +248,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.14.0..HEAD
[Unreleased]: https://github.com/imi-bigpicture/wsidicomizer/compare/0.14.1..HEAD
[0.14.1]: https://github.com/imi-bigpicture/wsidicomizer/compare/0.14.0..0.14.1
[0.14.0]: https://github.com/imi-bigpicture/wsidicomizer/compare/0.13.2..0.14.0
[0.13.2]: https://github.com/imi-bigpicture/wsidicomizer/compare/0.13.1..0.13.2
[0.13.1]: https://github.com/imi-bigpicture/wsidicomizer/compare/0.13.0..0.13.1
Expand Down
768 changes: 424 additions & 344 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wsidicomizer"
version = "0.14.0"
version = "0.14.1"
description = "Tool for reading WSI files from proprietary formats and optionally convert them to to DICOM"
authors = ["Erik O Gabrielsson <erik.o.gabrielsson@sectra.com>"]
license = "Apache-2.0"
Expand Down
8 changes: 7 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,13 @@
"level": 0,
"size": {"width": 200, "height": 200},
"md5": "aa9e76930398facc8c7910e053a7f418",
}
},
{
"location": {"x": 30720, "y": 25600},
"level": 0,
"size": {"width": 512, "height": 512},
"md5": "ac145933f80f64abac8d69eeb2ea537b",
},
],
"read_region_openslide": [],
"read_thumbnail": [],
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.14.0"
__version__ = "0.14.1"

__all__ = ["WsiDicomizer"]
33 changes: 18 additions & 15 deletions wsidicomizer/sources/czi/czi_image_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from dataclasses import dataclass
from functools import cached_property, lru_cache
from pathlib import Path
from threading import Lock
from threading import RLock
from typing import Dict, List, Optional, Tuple

import numpy as np
Expand Down Expand Up @@ -78,7 +78,7 @@ def __init__(
self._tile_size = Size(tile_size, tile_size)
assert isinstance(self._czi.filtered_subblock_directory, list)
self._block_directory = self._czi.filtered_subblock_directory
self._block_locks: Dict[int, Lock] = defaultdict(Lock)
self._block_locks: Dict[int, RLock] = defaultdict(RLock)

if self._merged_metadata.pixel_spacing is None:
raise ValueError("Could not determine pixel spacing for czi image.")
Expand Down Expand Up @@ -157,7 +157,7 @@ def tile_directory(self) -> Dict[Tuple[Point, float, str], List[CziBlock]]:
block_start, block_size, z, c = self._get_block_dimensions(block)
tile_region = Region.from_points(
block_start // self.tile_size,
(block_start + block_size) // self.tile_size,
(block_start + block_size).ceil_div(self.tile_size),
)
for tile in tile_region.iterate_all():
tile_directory[tile, z, c].append(
Expand Down Expand Up @@ -202,9 +202,8 @@ def _get_tile(self, tile_point: Point, z: float, path: str) -> np.ndarray:
# should already have checked).
return image_data

# For each block covering the tile
for block in self.tile_directory[tile_point, z, path]:
# For each block covering the tile

# Start and end coordinates for block and tile
block_end = block.start + block.size
tile_start = tile_point * self.tile_size
Expand Down Expand Up @@ -318,17 +317,21 @@ def _get_tile_data(self, block_index: int) -> np.ndarray:
prevent multiple threads proceseing the same tile, use a lock for
each block."""
block_lock = self._block_locks[block_index]
if block_lock.locked():
# Another thread is already reading the block. Wait for lock and
# read from cache.
with block_lock:
try:
# Try to lock block.
aquired = block_lock.acquire(blocking=False)
if not aquired:
# Another thread is already reading the block.
# Wait for lock and hopefully read from cache.
aquired = block_lock.acquire(blocking=True)
return self._get_tile_data(block_index)

with block_lock:
# Lock block and read from block.
block = self.block_directory[block_index]
data = block.data_segment().data()
return data
else:
# Read the block data.
block = self.block_directory[block_index]
return block.data_segment().data()
finally:
if aquired:
block_lock.release()

def _get_block_dimensions(
self, block: DirectoryEntryDV
Expand Down

0 comments on commit 561cbdc

Please sign in to comment.