Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thumbnails #85

Merged
merged 3 commits into from
Jan 30, 2025
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
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.15.0] - 2025-01-30

### Added

- Reading out thumbnails from supported formats.

## [0.14.0] - 2025-01-29

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

- Initial release of opentile.

[Unreleased]: https://github.com/imi-bigpicture/opentile/compare/v0.14.0..HEAD
[Unreleased]: https://github.com/imi-bigpicture/opentile/compare/v0.15.0..HEAD
[0.15.0]: https://github.com/imi-bigpicture/opentile/compare/v0.14.0..v0.15.0
[0.14.0]: https://github.com/imi-bigpicture/opentile/compare/v0.13.3..v0.14.0
[0.13.4]: https://github.com/imi-bigpicture/opentile/compare/v0.13.2..v0.13.3
[0.13.3]: https://github.com/imi-bigpicture/opentile/compare/v0.13.2..v0.13.3
Expand Down
2 changes: 1 addition & 1 deletion opentile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
from opentile.opentile import OpenTile
from opentile.metadata import Metadata

__version__ = "0.14.0"
__version__ = "0.15.0"
8 changes: 8 additions & 0 deletions opentile/formats/histech/histech_tiff_tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ def get_label(self, page: int = 0) -> TiffImage:
def get_overview(self, page: int = 0) -> TiffImage:
raise NotImplementedError()

def get_thumbnail(self, page: int = 0) -> TiffImage:
raise NotImplementedError()

@staticmethod
def _is_level_series(series: TiffPageSeries) -> bool:
return series.index == 0
Expand All @@ -86,3 +89,8 @@ def _is_overview_series(series: TiffPageSeries) -> bool:
def _is_label_series(series: TiffPageSeries) -> bool:
"""Return true if series is a label series."""
return False

@staticmethod
def _is_thumbnail_series(series: TiffPageSeries) -> bool:
"""Return true if series is a thumbnail series."""
return False
8 changes: 8 additions & 0 deletions opentile/formats/ndpi/ndpi_tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ def _is_label_series(series: TiffPageSeries) -> bool:
"""Return true if series is a label series."""
return False

@staticmethod
def _is_thumbnail_series(series: TiffPageSeries) -> bool:
"""Return true if series is a thumbnail series."""
return False

@staticmethod
def _adjust_tile_size(
requested_tile_width: int, smallest_stripe_width: Optional[int] = None
Expand Down Expand Up @@ -224,3 +229,6 @@ def get_overview(self, page: int = 0) -> TiffImage:
return NdpiCroppedImage(
tiff_page, self._file, self._jpeg, (self._label_crop_position, 1.0)
)

def get_thumbnail(self, page: int = 0) -> TiffImage:
raise NotImplementedError()
15 changes: 13 additions & 2 deletions opentile/formats/ome/ome_tiff_tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,10 @@ def supported(cls, tiff_file: TiffFile) -> bool:
return tiff_file.is_ome

def _is_level_series(self, series: TiffPageSeries) -> bool:
return not self._is_label_series(series) and not self._is_overview_series(
series
return (
not self._is_label_series(series)
and not self._is_overview_series(series)
and not self._is_thumbnail_series(series)
)

def _is_label_series(self, series: TiffPageSeries) -> bool:
Expand All @@ -83,6 +85,9 @@ def _is_label_series(self, series: TiffPageSeries) -> bool:
def _is_overview_series(self, series: TiffPageSeries) -> bool:
return series.name.strip() == "macro"

def _is_thumbnail_series(self, series: TiffPageSeries) -> bool:
return series.name.strip() == "thumbnail"

def _get_mpp(self, series_index: int) -> SizeMm:
mpp = self._get_optional_mpp(series_index)
if mpp is None:
Expand Down Expand Up @@ -134,6 +139,12 @@ def get_overview(self, page: int = 0) -> TiffImage:
raise ValueError("No overview detected in file")
return self._get_associated_image(self._overview_series_index, page)

@lru_cache(None)
def get_thumbnail(self, page: int = 0) -> TiffImage:
if self._thumbnail_series_index is None:
raise ValueError("No thumbnail detected in file")
return self._get_associated_image(self._thumbnail_series_index, page)

def _get_associated_image(self, series: int, page: int = 0) -> TiffImage:
tiff_page = self._get_tiff_page(series, 0, page)
return OmeTiffImage(
Expand Down
10 changes: 10 additions & 0 deletions opentile/formats/philips/philips_tiff_tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ def get_label(self, page: int = 0) -> TiffImage:
def get_overview(self, page: int = 0) -> TiffImage:
return self._get_image(self._overview_series_index, 0, page)

def get_thumbnail(self, page: int = 0) -> TiffImage:
return self._get_image(self._thumbnail_series_index, 0, page)

@lru_cache(None)
def _get_image(self, series: int, level: int, page: int = 0) -> TiffImage:
"""Return PhilipsTiffTiledPage for series, level, page."""
Expand Down Expand Up @@ -101,6 +104,13 @@ def _is_label_series(series: TiffPageSeries) -> bool:
assert isinstance(page, TiffPage)
return page.description.find("Label") > -1

@staticmethod
def _is_thumbnail_series(series: TiffPageSeries) -> bool:
"""Return true if series is a thumbnail series."""
page = series.pages[0]
assert isinstance(page, TiffPage)
return page.description.find("Thumbnail") > -1

@staticmethod
def _get_associated_mpp_from_page(page: TiffPage):
"""Return mpp (um/pixel) for associated image (label or
Expand Down
14 changes: 14 additions & 0 deletions opentile/formats/svs/svs_tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ def _is_label_series(series: TiffPageSeries) -> bool:
def _is_overview_series(series: TiffPageSeries) -> bool:
return series.name == "Macro"

@staticmethod
def _is_thumbnail_series(series: TiffPageSeries) -> bool:
return series.name == "Thumbnail"

@lru_cache(None)
def get_level(self, level: int, page: int = 0) -> TiffImage:
series = self._level_series_index
Expand Down Expand Up @@ -117,3 +121,13 @@ def get_overview(self, page: int = 0) -> TiffImage:
self._file,
self._jpeg,
)

@lru_cache(None)
def get_thumbnail(self, page: int = 0) -> TiffImage:
if self._thumbnail_series_index is None:
raise ValueError("No overview detected in file")
return SvsStripedImage(
self._get_tiff_page(self._thumbnail_series_index, 0, page),
self._file,
self._jpeg,
)
37 changes: 37 additions & 0 deletions opentile/tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,16 @@ def __init__(
self._level_series_index = 0
self._overview_series_index: Optional[int] = None
self._label_series_index: Optional[int] = None
self._thumbnail_series_index: Optional[int] = None
for series_index, series in enumerate(self.series):
if self._is_level_series(series):
self._level_series_index = series_index
if self._is_label_series(series):
self._label_series_index = series_index
elif self._is_overview_series(series):
self._overview_series_index = series_index
elif self._is_thumbnail_series(series):
self._thumbnail_series_index = series_index
self._icc_profile: Optional[bytes] = None
base_page = self.series[self._level_series_index].pages[0]
assert isinstance(base_page, TiffPage)
Expand Down Expand Up @@ -124,6 +127,18 @@ def overviews(self) -> List[TiffImage]:
)
]

@property
def thumbnails(self) -> List[TiffImage]:
"""Return list of thumbnail TiffImage."""
if self._thumbnail_series_index is None:
return []
return [
self.get_thumbnail(page_index)
for page_index, page in enumerate(
self.series[self._thumbnail_series_index].pages
)
]

@property
def icc_profile(self) -> Optional[bytes]:
"""Return icc profile if found in file."""
Expand Down Expand Up @@ -191,6 +206,22 @@ def get_overview(self, page: int = 0) -> TiffImage:
"""
raise NotImplementedError()

@abstractmethod
def get_thumbnail(self, page: int = 0) -> TiffImage:
"""Return thumbnail TiffImage.

Parameters
----------
page: int = 0
Index of page to get.

Returns
----------
TiffImage
Thumbnail TiffImage.
"""
raise NotImplementedError()

@staticmethod
@abstractmethod
def _is_level_series(series: TiffPageSeries) -> bool:
Expand All @@ -209,6 +240,12 @@ def _is_label_series(series: TiffPageSeries) -> bool:
"""Return true if series is a label series."""
raise NotImplementedError()

@staticmethod
@abstractmethod
def _is_thumbnail_series(series: TiffPageSeries) -> bool:
"""Return true if series is a thumbnail series."""
raise NotImplementedError()

def close(self) -> None:
"""Close tiff file."""
self._file.close()
Expand Down
Loading