Coder Social home page Coder Social logo

opentile's Introduction

opentile

opentile is a Python library for reading tiles from wsi tiff files. The aims of the proect are:

  • Allow compressed tiles to be losslessly read from wsi tiffs using 2D coordinates (tile position x, y).
  • Provide unified interface for relevant metadata.
  • Support all file formats supported by tifffile that has a non-overlapping tile structure.

opentile does not provide methods for reading regions from images (e.g. get_region()). See openslide-python, tiffslide, or wsidicomizer for such use.

Currently implemented file formats are listed and described under Implemented file formats.

Installing opentile

opentile is available on PyPI:

pip install opentile

Alternatively, it can be installed via conda:

conda install -c conda-forge opentile

Important note

Please note that this is an early release and the API is not frozen yet. Function names and functionality is prone to change.

Requirements

opentile requires python >=3.8 and uses numpy, Pillow, TiffFile, and PyTurboJPEG (with lib-turbojpeg >= 2.1 ), imagecodecs, defusedxml, and ome-types.

Limitations

Files with z-stacks are currently not fully supported for all formats.

Implemented file formats

The following description of the workings of the implemented file formats does not include the additional specifics for each format that is handled by tifffile. Additional formats supported by tifffile and that have non-overlapping tile layout are likely to be added in future release.

Hamamatsu Ndpi The Ndpi-format uses non-rectangular tile size typically 8 pixels high, i.e. stripes. To form tiles, first multiple stripes are concatenated to form a frame covering the tile region. Second, if the stripes are longer than the tile width, the tile is croped out of the frame. The concatenation and crop transformations are performed losslessly.

A ndpi-file can also contain non-tiled images. If these are part of a pyramidal series, opentile tiles the image.

The macro page in ndpi-files images the whole slide including label. A label and overview is created by cropping the macro image.

Philips tiff The Philips tiff-format allows tiles to be sparse, i.e. missing. For such tiles, opentile instead provides a blank (currently white) tile image using the same jpeg header as the rest of the image.

Aperio svs Some Asperio svs-files have corrupt tile data at edges of non-base pyramidal levels. This is observed as tiles with 0-byte length and tiles with incorrect pixel data. opentile detects such corruption and instead returns downscaled image data from lower levels. Associated images (label, overview) are currently not handled correctly.

3DHistech tiff Only the pyramidal levels are supported (not overviews or labels).

OME tiff Metadata parsing is not yet implemented.

Metadata

File metadata can be accessed through the metadata-property of a tiler. Depending on file format and content, the following metadata is available:

  • Magnification
  • Scanner manufacturer
  • Scanner model
  • Scanner software versions
  • Scanner serial number
  • Acquisition datetime

Basic usage

Load a Ndpi-file using tile size (1024, 1024) pixels.

from opentile import OpenTile
tile_size = (1024, 1024)
tiler = OpenTile.open(path_to_ndpi_file, tile_size)

Load a file using fsspec and with some file options.

from opentile import OpenTile
tiler = OpenTile.open("s3://bucket/key", file_options={"s3": "anon": True})

If turbo jpeg library path is not found.

from opentile import OpenTile
tile_size = (1024, 1024)
turbo_path = 'C:/libjpeg-turbo64/bin/turbojpeg.dll'
tiler = OpenTile.open(path_to_ndpi_file, tile_size, turbo_path)

Get rectangular tile at level 0 and position x=0, y=0.

level = tiler.get_evel(0)
tile = level.get_tile((0, 0))

Close the tiler object.

tiler.close()

Usage as context manager

The tiler can also be used as context manager:

from opentile import OpenTile
tile_size = (1024, 1024)
with OpenTile.open(path_to_ndpi_file, tile_size) as tiler:
    level = tiler.get_evel(0)
    tile = level.get_tile((0, 0))

Setup environment for development

Requires poetry and pytest and pytest-watch installed in the virtual environment.

git clone https://github.com/imi-bigpicture/opentile.git
poetry install

By default the tests looks for slides in 'tests/testdata'. This can be overridden by setting the OPENTILE_TESTDIR environment variable. The script 'tests/download_test_images.py' can be used to download publicly available openslide testdata into the set testdata folder:

python tests/download_test_images.py

The test data used for philips tiff is currently not publicly available as we dont have permission to share them. If you have slides in philips tiff format that can be freely shared we would be happy to use them instead.

To watch unit tests use:

poetry run pytest-watch -- -m unittest

Other TIFF python tools

Contributing

We welcome any contributions to help improve this tool for the WSI community!

We recommend first creating an issue before creating potential contributions to check that the contribution is in line with the goals of the project. To submit your contribution, please issue a pull request on the imi-bigpicture/opentile repository with your changes for review.

Our aim is to provide constructive and positive code reviews for all submissions. The project relies on gradual typing and roughly follows PEP8. However, we are not dogmatic. Most important is that the code is easy to read and understand.

Acknowledgement

opentile: Copyright 2021-2024 Sectra AB, licensed under Apache 2.0.

This project is part of a project that has received funding from the Innovative Medicines Initiative 2 Joint Undertaking under grant agreement No 945358. This Joint Undertaking receives support from the European Union’s Horizon 2020 research and innovation programme and EFPIA. IMI website: www.imi.europa.eu

opentile's People

Contributors

ap-- avatar erikogabrielsson avatar sarthakpati avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

opentile's Issues

Error in readme

The example in the readme on how to read a tile using OpenTile is outdated.

test images

Hello everyone,

Is it possible to share the test images?
Or are they available somewhere online?

All the best,
Andreas

Corrupt tiles in svs-files

Some svs-files have corrupt tiles at the edge of the image at levels other than the base level. Reading such tiles either produces zero-lenght byte or a technically valid tile but with mixed up content. Openslide fixes this by scaling tiles at corrupt edges from the base layer. A fix to do this in opentile is to be implemented.

NotImplementedError: 7 is unsupported for ndpi on Python 3.11

Just a heads up. This error was raised during testing of tifffile on Python 3.11:

============================================================================== FAILURES ==============================================================================
______________________________________________________________________ test_dependent_opentile _______________________________________________________________________

    @pytest.mark.skipif(SKIP_PRIVATE or SKIP_LARGE or SKIP_WIN, reason=REASON)
    def test_dependent_opentile():
        """Test opentile package."""
        # https://github.com/imi-bigpicture/opentile
        try:
            from hashlib import md5
            import turbojpeg
            from opentile.geometry import Point
            from opentile.ndpi_tiler import NdpiTiler
        except ImportError:
            pytest.skip('opentile missing')

        fname = private_file('HamamatsuNDPI/CMU-1.ndpi')

        turbo_path = os.path.join(
            os.path.dirname(turbojpeg.__file__), 'turbojpeg.dll'
        )

        with NdpiTiler(
            fname,
            512,
            turbo_path=turbo_path,
        ) as tiler:
            # from example
>           tile = tiler.get_tile(0, 0, 0, (0, 0))

tests\test_tifffile.py:16096:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
X:\Python311\Lib\site-packages\opentile\common.py:521: in get_tile
    tiled_page = self.get_page(series, level, page)
X:\Python311\Lib\site-packages\opentile\ndpi_tiler.py:987: in get_page
    ndpi_page = self._create_page(series, level, page)
X:\Python311\Lib\site-packages\opentile\ndpi_tiler.py:1078: in _create_page
    return NdpiStripedPage(
X:\Python311\Lib\site-packages\opentile\ndpi_tiler.py:776: in __init__
    super().__init__(page, fh, base_shape, tile_size, jpeg, frame_cache)
X:\Python311\Lib\site-packages\opentile\ndpi_tiler.py:459: in __init__
    super().__init__(page, fh, jpeg)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <[AttributeError("'NdpiStripedPage' object has no attribute '_base_shape'") raised in repr()] NdpiStripedPage object at 0x20e988e0b10>
page = <tifffile.TiffPage 0 @184062235>, fh = <tifffile.FileHandle 'CMU-1.ndpi'>, jpeg = <opentile.jpeg.Jpeg object at 0x0000020E98927510>

    def __init__(
        self,
        page: TiffPage,
        fh: FileHandle,
        jpeg: Jpeg
    ):
        """Ndpi page that should not be tiled (e.g. overview or label).
        Image data is assumed to be jpeg.

        Parameters
        ----------
        page: TiffPage
            TiffPage defining the page.
        fh: FileHandle
            Filehandler to read data from.
        jpeg: Jpeg
            Jpeg instance to use.
        """
        super().__init__(page, fh)
        if self.compression != 'COMPRESSION.JPEG':
>           raise NotImplementedError(
                f'{self.compression} is unsupported for ndpi '
                '(Only jpeg is supported)'
            )
E           NotImplementedError: 7 is unsupported for ndpi (Only jpeg is supported)

X:\Python311\Lib\site-packages\opentile\ndpi_tiler.py:319: NotImplementedError

This is because of a change in the string representation of IntEnum members:

Python 3.11.0b3 (main, Jun  1 2022, 13:29:14) [MSC v.1932 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> from tifffile.tifffile import COMPRESSION
>>> str(COMPRESSION.JPEG)
'7'
Python 3.10.4 (tags/v3.10.4:9d38120, Mar 23 2022, 23:13:41) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from tifffile.tifffile import COMPRESSION
>>> str(COMPRESSION.JPEG)
'COMPRESSION.JPEG'

libjpeg-turbo 3.0.0

libjpeg-turbo 3.0.0 has been released. This version seems to be compatible with opentile, i.e. lossless cropping is still possible. However the new version removes the comment marker and payload. Thus produced tiles have different checksum compared to when using older versions, and tests based on tile checksums will fail.

Incorrect tile for one-framed ndpi pages

For ndpi pages that only contain one frame, cropping the frame with jpeg turbo also extends the frame to to the cropping region, e.g. cropping a 700x700 frame from position 512, 512 with size 512x512 gives a 512x512 frame. This however does not preserve the edges of the frame if the frame is not an even number of mcus in size or width. This result in a white border when viewing the level.

Simple solution is to use for example pillow to paste the frame into the extended frame. A (near) lossless solution is probably complicated as one needs to copy modified edge mcus into the frame.

TurboJpegTest.test_multiple_extend fails with Invalid crop request

Hello everyone,

The TurboJpegTest.test_multiple_extend currently fails on Ubuntu and Windows because the test image height is just 512 and an OSError seems to be raised when cropping ranges are out of bounds.

Is a specific version of jpeg turbo required for the tests to pass?

Cheers,
Andreas πŸ˜ƒ

________________________________________________________________________________________________ TurboJpegTest.test_crop_multiple_extend ________________________________________________________________________________________________

self = <tests.test_turbojpeg_patch.TurboJpegTest testMethod=test_crop_multiple_extend>

    def test_crop_multiple_extend(self):
        crop_parameters = [(0, 0, 1024, 1024)]
>       crop = self.jpeg.crop_multiple(self.buffer, crop_parameters)[0]

tests/test_turbojpeg_patch.py:119: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../venv/lib/python3.9/site-packages/turbojpeg.py:671: in crop_multiple
    self.__report_error(handle)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <opentile.turbojpeg_patch.TurboJPEG_patch object at 0x7f94e2ac4100>, handle = 44868608

    def __report_error(self, handle):
        """reports error while error occurred"""
        if self.__get_error_code is not None:
            # using new error handling logic if possible
            if self.__get_error_code(handle) == TJERR_WARNING:
                warnings.warn(self.__get_error_string(handle))
                return
        # fatal error occurred
>       raise IOError(self.__get_error_string(handle))
E       OSError: Invalid crop request

../venv/lib/python3.9/site-packages/turbojpeg.py:883: OSError
======================================================================================================== short test summary info ========================================================================================================
FAILED tests/test_turbojpeg_patch.py::TurboJpegTest::test_crop_multiple_extend - OSError: Invalid crop request

poetry dependency version specifications

Hi @erikogabrielsson

Great work on opentile πŸŽ‰
I wanted try it out just now and noticed you're using poetry caret version requirements to limit the major version of dependencies.
This causes issues for calendar versioned dependencies like tifffile and imagecodecs:

tifffile<2022.0.0,>=2021.6.14 
imagecodecs<2022.0.0,>=2021.8.26

Where it's basically unpredictable anyways when a breaking change is introduced and currently the newest version is blocked off. It might also block off newer versions of dependencies that undergo a major version increase without breaking the part of the API that opentile uses.

Do you think it would make sense to just limit the minimum required version and run all tests in the CI regularly to detect and deal with incompatibilities with updated dependencies on a case by case basis?

If yes, I can work on a PR.

Cheers,
Andreas πŸ˜ƒ

Loading TurboJPEG fails when no library path needed

If TurboJPEG should find the path to lib-turbojpeg library automatically, i.e. turbo_path is None, this fails as turbo_path is sent as str(turbo_path). Additionally, TurboJPEG_patch tries to use _find_turbojpeg() from TurboJPEG without extra __.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.