diff --git a/tests/integration/converters/test_ome_tiff.py b/tests/integration/converters/test_ome_tiff.py index 96ddaf2a..6ddd3595 100644 --- a/tests/integration/converters/test_ome_tiff.py +++ b/tests/integration/converters/test_ome_tiff.py @@ -9,25 +9,32 @@ from tiledb.bioimg.openslide import TileDBOpenSlide -def test_ome_tiff_converter(tmp_path): - OMETiffConverter.to_tiledb(get_path("CMU-1-Small-Region.ome.tiff"), str(tmp_path)) +@pytest.mark.parametrize("open_fileobj", [False, True]) +def test_ome_tiff_converter(tmp_path, open_fileobj): + input_path = str(get_path("CMU-1-Small-Region.ome.tiff")) + output_path = str(tmp_path) + if open_fileobj: + with open(input_path, "rb") as f: + OMETiffConverter.to_tiledb(f, output_path) + else: + OMETiffConverter.to_tiledb(input_path, output_path) - t = TileDBOpenSlide.from_group_uri(str(tmp_path)) - assert len(tiledb.Group(str(tmp_path))) == t.level_count == 2 + with TileDBOpenSlide.from_group_uri(output_path) as t: + assert len(tiledb.Group(output_path)) == t.level_count == 2 - schemas = (get_schema(2220, 2967), get_schema(574, 768)) - assert t.dimensions == schemas[0].shape[:-3:-1] - for i in range(t.level_count): - assert t.level_dimensions[i] == schemas[i].shape[:-3:-1] - with tiledb.open(str(tmp_path / f"l_{i}.tdb")) as A: - assert A.schema == schemas[i] + schemas = (get_schema(2220, 2967), get_schema(574, 768)) + assert t.dimensions == schemas[0].shape[:-3:-1] + for i in range(t.level_count): + assert t.level_dimensions[i] == schemas[i].shape[:-3:-1] + with tiledb.open(str(tmp_path / f"l_{i}.tdb")) as A: + assert A.schema == schemas[i] - region = t.read_region(level=0, location=(100, 100), size=(300, 400)) - assert isinstance(region, np.ndarray) - assert region.ndim == 3 - assert region.dtype == np.uint8 - img = PIL.Image.fromarray(region) - assert img.size == (300, 400) + region = t.read_region(level=0, location=(100, 100), size=(300, 400)) + assert isinstance(region, np.ndarray) + assert region.ndim == 3 + assert region.dtype == np.uint8 + img = PIL.Image.fromarray(region) + assert img.size == (300, 400) def test_ome_tiff_converter_different_dtypes(tmp_path): @@ -85,8 +92,8 @@ def test_ome_tiff_converter_artificial_rountrip(tmp_path, filename, dims, tiles) OMETiffConverter.to_tiledb(input_path, str(tiledb_path), tiles=tiles) - t = TileDBOpenSlide.from_group_uri(str(tiledb_path)) - assert len(tiledb.Group(str(tiledb_path))) == t.level_count == 1 + with TileDBOpenSlide.from_group_uri(str(tiledb_path)) as t: + assert len(tiledb.Group(str(tiledb_path))) == t.level_count == 1 with tiledb.open(str(tiledb_path / "l_0.tdb")) as A: assert "".join(dim.name for dim in A.domain) == dims diff --git a/tests/integration/converters/test_ome_zarr.py b/tests/integration/converters/test_ome_zarr.py index d5651c76..f97ba165 100644 --- a/tests/integration/converters/test_ome_zarr.py +++ b/tests/integration/converters/test_ome_zarr.py @@ -23,20 +23,20 @@ def test_ome_zarr_converter(tmp_path, series_idx): with tiledb.open(str(tmp_path / "l_0.tdb")) as A: assert A.schema == schema - t = TileDBOpenSlide.from_group_uri(str(tmp_path)) - assert t.dimensions == t.level_dimensions[0] == schema.shape[:-3:-1] - - region_location = (100, 100) - region_size = (300, 400) - region = t.read_region(level=0, location=region_location, size=region_size) - assert isinstance(region, np.ndarray) - assert region.ndim == 3 - assert region.dtype == np.uint8 - img = PIL.Image.fromarray(region) - assert img.size == ( - min(t.dimensions[0] - region_location[0], region_size[0]), - min(t.dimensions[1] - region_location[1], region_size[1]), - ) + with TileDBOpenSlide.from_group_uri(str(tmp_path)) as t: + assert t.dimensions == t.level_dimensions[0] == schema.shape[:-3:-1] + + region_location = (100, 100) + region_size = (300, 400) + region = t.read_region(level=0, location=region_location, size=region_size) + assert isinstance(region, np.ndarray) + assert region.ndim == 3 + assert region.dtype == np.uint8 + img = PIL.Image.fromarray(region) + assert img.size == ( + min(t.dimensions[0] - region_location[0], region_size[0]), + min(t.dimensions[1] - region_location[1], region_size[1]), + ) @pytest.mark.parametrize("series_idx", [0, 1, 2]) @@ -87,13 +87,13 @@ def test_ome_zarr_converter_incremental(tmp_path): input_path = get_path("CMU-1-Small-Region.ome.zarr/0") OMEZarrConverter.to_tiledb(input_path, str(tmp_path), level_min=1) - t = TileDBOpenSlide.from_group_uri(str(tmp_path)) - assert len(tiledb.Group(str(tmp_path))) == t.level_count == 1 + with TileDBOpenSlide.from_group_uri(str(tmp_path)) as t: + assert len(tiledb.Group(str(tmp_path))) == t.level_count == 1 OMEZarrConverter.to_tiledb(input_path, str(tmp_path), level_min=0) - t = TileDBOpenSlide.from_group_uri(str(tmp_path)) - assert len(tiledb.Group(str(tmp_path))) == t.level_count == 2 + with TileDBOpenSlide.from_group_uri(str(tmp_path)) as t: + assert len(tiledb.Group(str(tmp_path))) == t.level_count == 2 OMEZarrConverter.to_tiledb(input_path, str(tmp_path), level_min=0) - t = TileDBOpenSlide.from_group_uri(str(tmp_path)) - assert len(tiledb.Group(str(tmp_path))) == t.level_count == 2 + with TileDBOpenSlide.from_group_uri(str(tmp_path)) as t: + assert len(tiledb.Group(str(tmp_path))) == t.level_count == 2 diff --git a/tests/integration/converters/test_openslide.py b/tests/integration/converters/test_openslide.py index 944c1f9a..40c05e0d 100644 --- a/tests/integration/converters/test_openslide.py +++ b/tests/integration/converters/test_openslide.py @@ -17,16 +17,16 @@ def test_openslide_converter(tmp_path): assert A.schema == get_schema(2220, 2967) o = openslide.open_slide(svs_path) - t = TileDBOpenSlide.from_group_uri(str(tmp_path)) + with TileDBOpenSlide.from_group_uri(str(tmp_path)) as t: - assert t.level_count == o.level_count - assert t.dimensions == o.dimensions - assert t.level_dimensions == o.level_dimensions - assert t.level_downsamples == o.level_downsamples + assert t.level_count == o.level_count + assert t.dimensions == o.dimensions + assert t.level_dimensions == o.level_dimensions + assert t.level_downsamples == o.level_downsamples - region = t.read_region(level=0, location=(100, 100), size=(300, 400)) - assert isinstance(region, np.ndarray) - assert region.ndim == 3 - assert region.dtype == np.uint8 - img = PIL.Image.fromarray(region) - assert img.size == (300, 400) + region = t.read_region(level=0, location=(100, 100), size=(300, 400)) + assert isinstance(region, np.ndarray) + assert region.ndim == 3 + assert region.dtype == np.uint8 + img = PIL.Image.fromarray(region) + assert img.size == (300, 400) diff --git a/tests/unit/test_openslide.py b/tests/unit/test_openslide.py index a2ea9105..b497862b 100644 --- a/tests/unit/test_openslide.py +++ b/tests/unit/test_openslide.py @@ -22,6 +22,6 @@ def r(): A.meta["level"] = level G.add(level_path) - tdb_os = TileDBOpenSlide.from_group_uri(group_path) - assert tdb_os.level_count == len(level_dimensions) - assert tdb_os.level_dimensions == tuple(level_dimensions) + with TileDBOpenSlide.from_group_uri(group_path) as t: + assert t.level_count == len(level_dimensions) + assert t.level_dimensions == tuple(level_dimensions) diff --git a/tiledb/bioimg/openslide.py b/tiledb/bioimg/openslide.py index ffed37a4..c4e5fc86 100644 --- a/tiledb/bioimg/openslide.py +++ b/tiledb/bioimg/openslide.py @@ -1,7 +1,7 @@ from __future__ import annotations from operator import itemgetter -from typing import Sequence, Tuple +from typing import Any, Sequence, Tuple import numpy as np @@ -11,10 +11,6 @@ class TileDBOpenSlide: - def __init__(self, level_info: Sequence[Tuple[str, int, int]]): - self._level_uris = tuple(map(itemgetter(0), level_info)) - self._level_dims = tuple(map(itemgetter(1, 2), level_info)) # (width, height) - @classmethod def from_group_uri(cls, uri: str) -> TileDBOpenSlide: """ @@ -24,15 +20,22 @@ def from_group_uri(cls, uri: str) -> TileDBOpenSlide: with tiledb.Group(uri) as G: level_info = [] for o in G: - uri = o.uri - with tiledb.open(uri) as a: - level = a.meta.get("level", 0) - width = a.shape[-1] - height = a.shape[-2] - level_info.append((level, uri, width, height)) + array = tiledb.open(o.uri) + level = array.meta.get("level", 0) + level_info.append((level, array)) # sort by level level_info.sort(key=itemgetter(0)) - return cls(tuple(map(itemgetter(1, 2, 3), level_info))) + return cls(tuple(map(itemgetter(1), level_info))) + + def __init__(self, level_arrays: Sequence[tiledb.Array]): + self._level_arrays = level_arrays + + def __enter__(self) -> TileDBOpenSlide: + return self + + def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: + for array in self._level_arrays: + array.close() @property def level_count(self) -> int: @@ -42,7 +45,7 @@ def level_count(self) -> int: :return: The number of levels in the slide """ - return len(self._level_dims) + return len(self._level_arrays) @property def dimensions(self) -> Tuple[int, int]: @@ -57,7 +60,7 @@ def level_dimensions(self) -> Sequence[Tuple[int, int]]: :return: A sequence of dimensions for each level """ - return self._level_dims + return tuple((array.shape[-1], array.shape[-2]) for array in self._level_arrays) @property def level_downsamples(self) -> Sequence[float]: @@ -84,9 +87,9 @@ def read_region( x, y = location w, h = size dim_to_slice = {"X": slice(x, x + w), "Y": slice(y, y + h)} - with tiledb.open(self._level_uris[level]) as a: - dims = [dim.name for dim in a.domain] - image = a[tuple(dim_to_slice.get(dim, slice(None)) for dim in dims)] + array = self._level_arrays[level] + dims = [dim.name for dim in array.domain] + image = array[tuple(dim_to_slice.get(dim, slice(None)) for dim in dims)] # transpose image to YXC return transpose_array(image, dims, "YXC")