Skip to content
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
23 changes: 23 additions & 0 deletions src/bffile/_biofile.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ class BioFile(Sequence[Series]):
See: https://docs.openmicroscopy.org/bio-formats/latest/formats/options.html
For example: to turn off chunkmap table reading for ND2 files, use
`options={"nativend2.chunkmap": False}`
channel_filler : bool or None, optional
Whether to wrap the reader with Bio-Formats' ChannelFiller, which
expands indexed-color data (e.g. GIF palettes) into RGB. If `True`,
always use ChannelFiller. If `False`, never use it. If `None`
(default), automatically apply it when the file contains true
indexed-color data.
"""

def __init__(
Expand All @@ -157,13 +163,15 @@ def __init__(
original_meta: bool = False,
memoize: int | bool = 0,
options: dict[str, bool] | None = None,
channel_filler: bool | None = None,
):
self._path = str(path)
self._lock = RLock()
self._meta = meta
self._original_meta = original_meta
self._memoize = memoize
self._options = options
self._channel_filler = channel_filler

# Reader and finalizer created in open()
self._java_reader: IFormatReader | None = None
Expand Down Expand Up @@ -281,6 +289,10 @@ def open(self) -> Self:
r = jimport("loci.formats.ImageReader")()
r.setFlattenedResolutions(False)

if self._channel_filler is True:
ChannelFiller = jimport("loci.formats.ChannelFiller")
r = ChannelFiller(r)

if self._memoize > 0:
Memoizer = jimport("loci.formats.Memoizer")
if BIOFORMATS_MEMO_DIR is not None:
Expand All @@ -301,6 +313,17 @@ def open(self) -> Self:

try:
r.setId(self._path)

# Auto-detect: wrap with ChannelFiller for true indexed color
if (
self._channel_filler is None
and r.isIndexed()
and not r.isFalseColor()
):
ChannelFiller = jimport("loci.formats.ChannelFiller")
r = ChannelFiller(r)
r.setId(self._path)

core_meta = self._get_core_metadata(r)
except Exception: # pragma: no cover
with suppress(Exception):
Expand Down
1 change: 1 addition & 0 deletions src/bffile/_core_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def from_java(cls, meta: loci.formats.CoreMetadata) -> Self:
t=meta.sizeT,
rgb=rgb_count,
),
rgb_count=rgb_count,
thumb_size_x=meta.thumbSizeX,
thumb_size_y=meta.thumbSizeY,
bits_per_pixel=meta.bitsPerPixel,
Expand Down
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ def any_file(request: pytest.FixtureRequest) -> Path:
return request.param


@pytest.fixture
def data_dir() -> Path:
"""Path to the test data directory."""
return TEST_DATA


@pytest.fixture
def simple_file() -> Path:
"""Small TIFF file for fast unit tests (7KB)."""
Expand Down
28 changes: 28 additions & 0 deletions tests/test_biofile.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,37 @@ def test_core_meta_returns_metadata(opened_biofile: BioFile) -> None:
assert hasattr(meta, "dtype")
assert hasattr(meta, "dimension_order")
assert len(meta.shape) == 6 # CoreMetadata always has 6 elements (TCZYX + rgb)
assert meta.rgb_count == meta.shape.rgb
assert isinstance(meta.dtype, np.dtype)


@pytest.mark.parametrize(
("channel_filler", "expect_rgb", "expect_indexed"),
[(None, 3, False), (True, 3, False), (False, 1, True)],
ids=["auto", "forced", "disabled"],
)
def test_channel_filler_gif(
data_dir: Path,
channel_filler: bool | None,
expect_rgb: int,
expect_indexed: bool,
) -> None:
with BioFile(data_dir / "example.gif", channel_filler=channel_filler) as bf:
meta = bf.core_metadata()
assert meta.shape.rgb == expect_rgb
assert meta.is_indexed is expect_indexed


def test_false_color_indexed_file_not_expanded(data_dir: Path) -> None:
with BioFile(data_dir / "ND2_dims_c2y32x32.nd2") as bf:
meta = bf.core_metadata()
assert meta.shape.c == 2
assert meta.shape.rgb == 1
assert meta.is_rgb is False
assert meta.is_indexed
assert meta.is_false_color


def test_ome_xml_property(opened_biofile: BioFile) -> None:
xml = opened_biofile.ome_xml
assert isinstance(xml, str)
Expand Down