Skip to content

Commit 3e51edc

Browse files
single param priority selection
1 parent 970cf58 commit 3e51edc

File tree

2 files changed

+74
-36
lines changed

2 files changed

+74
-36
lines changed

bioio/bio_image.py

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,18 @@
33

44
import logging
55
from pathlib import Path
6-
from typing import Any, Dict, List, Optional, Sequence, Tuple, Type, Union, get_args
6+
from typing import (
7+
Any,
8+
Dict,
9+
List,
10+
Optional,
11+
Sequence,
12+
Tuple,
13+
Type,
14+
Union,
15+
cast,
16+
get_args,
17+
)
718

819
import bioio_base as biob
920
import dask.array as da
@@ -40,9 +51,22 @@ class BioImage(biob.image_container.ImageContainer):
4051
----------
4152
image: biob.types.ImageLike
4253
A string, Path, fsspec supported URI, or arraylike to read.
43-
reader: Optional[Type[Reader]]
44-
The Reader class to specifically use for reading the provided image.
45-
Default: None (find matching reader)
54+
reader: Optional[Union[Type[biob.reader.Reader],
55+
Sequence[Type[biob.reader.Reader]]]]
56+
Controls how BioImage selects the underlying Reader:
57+
58+
* If a **single Reader subclass** is provided (e.g. ``reader=TiffReader``),
59+
that reader is used directly and plugin ordering is bypassed.
60+
61+
* If a **sequence of Reader subclasses** is provided
62+
(e.g. ``reader=[TiffReader, OmeTiffReader]``), the list is treated as a
63+
*priority list* when selecting among installed plugins that support the
64+
file’s extension. Readers earlier in the list are tried first.
65+
66+
* A warning is raised if the priority list does not contain a
67+
reader that can read the provided image
68+
69+
* If ``None`` (default), BioImage uses its default plugin ordering.
4670
reconstruct_mosaic: bool
4771
Boolean for setting that data for this object to the reconstructed / stitched
4872
mosaic image.
@@ -60,15 +84,6 @@ class BioImage(biob.image_container.ImageContainer):
6084
checking for installed plugins on each `BioImage` instance init.
6185
If True, will use the cache of installed plugins discovered last `BioImage`
6286
init.
63-
plugin_priority: Optional[Sequence[Type[biob.reader.Reader]]]
64-
An optional ordered list of Reader classes that defines the priority used
65-
when multiple plugins declare support for the same file type.
66-
67-
When provided, the ordering in this list overrides BioIO’s default plugin
68-
ordering for *that specific BioImage instance*. Only readers that actually
69-
exist in the installed plugin registry may be included. Any readers not
70-
present in the list will retain their original relative ordering after all
71-
explicitly-prioritized readers.
7287
fs_kwargs: Dict[str, Any]
7388
Any specific keyword arguments to pass down to the fsspec created filesystem.
7489
Default: {}
@@ -122,12 +137,10 @@ class BioImage(biob.image_container.ImageContainer):
122137
>>> from bioio_ome_tiff import Reader as OmeTiffReader
123138
>>> img = BioImage(
124139
... "multi_plugin_file.ome.tiff",
125-
... plugin_priority=[TiffReader, OmeTiffReader],
140+
... reader=[TiffReader, OmeTiffReader],
126141
... )
127142
... # TiffReader will be tried before OmeTiffReader for this instance.
128143
129-
Data for a mosaic file is returned pre-stitched (if the base reader supports it).
130-
131144
>>> img = BioImage("big_mosaic.czi")
132145
... img.dims # <Dimensions [T: 40, C: 3, Z: 1, Y: 30000, X: 45000]>
133146
@@ -320,27 +333,42 @@ def ends_with_ext(string: str) -> bool:
320333
@staticmethod
321334
def _get_reader(
322335
image: biob.types.ImageLike,
323-
reader: biob.reader.Reader,
336+
reader: Optional[
337+
Union[Type[biob.reader.Reader], Sequence[Type[biob.reader.Reader]]]
338+
],
324339
use_plugin_cache: bool,
325340
fs_kwargs: Dict[str, Any],
326-
plugin_priority: Optional[Sequence[Type[biob.reader.Reader]]] = None,
327341
**kwargs: Any,
328342
) -> Tuple[biob.reader.Reader, Optional[PluginEntry]]:
329343
"""
330-
Initializes and returns the reader (and plugin if relevant) for the provided
331-
image based on provided args and/or the available bioio supported plugins
344+
Initialize and return the underlying reader (and its PluginEntry, if any)
345+
for the provided image.
346+
347+
Behavior
348+
--------
349+
* If ``reader`` is a single Reader subclass, that reader is used directly
350+
and the plugin system is bypassed.
351+
352+
* If ``reader`` is a sequence of Reader subclasses, it is treated as a
353+
priority list passed to the plugin system. Readers earlier in the list
354+
are tried first when multiple plugins support the same extension.
355+
356+
* If ``reader`` is ``None``, the default plugin ordering is used.
332357
"""
333-
if reader is not None:
334-
# Check specific reader image types in a situation where a specified reader
335-
# only supports some of the ImageLike types.
336-
if not check_type(image, reader):
358+
359+
# Force specific reader
360+
if isinstance(reader, type):
361+
forced_reader = cast(Type[biob.reader.Reader], reader)
362+
if not check_type(image, forced_reader):
337363
raise biob.exceptions.UnsupportedFileFormatError(
338-
reader.__name__, str(type(image))
364+
forced_reader.__name__, str(type(image))
339365
)
366+
return forced_reader(image, fs_kwargs=fs_kwargs, **kwargs), None
340367

341-
return reader(image, fs_kwargs=fs_kwargs, **kwargs), None
342-
343-
# Determine reader class based on available plugins
368+
# Determine plugin with priority
369+
plugin_priority: Optional[Sequence[Type[biob.reader.Reader]]] = None
370+
if isinstance(reader, Sequence):
371+
plugin_priority = reader
344372
plugin = BioImage.determine_plugin(
345373
image,
346374
fs_kwargs=fs_kwargs,
@@ -349,16 +377,28 @@ def _get_reader(
349377
**kwargs,
350378
)
351379
ReaderClass = plugin.metadata.get_reader()
380+
381+
# Warn if user prioritized specific readers but none were usable
382+
if plugin_priority and ReaderClass not in plugin_priority:
383+
log.warning(
384+
"A reader priority list was provided (%s), but none of those "
385+
"readers could open '%s'. Falling back to '%s'.",
386+
", ".join(cls.__name__ for cls in plugin_priority),
387+
image,
388+
ReaderClass.__name__,
389+
)
390+
352391
return ReaderClass(image, fs_kwargs=fs_kwargs, **kwargs), plugin
353392

354393
def __init__(
355394
self,
356395
image: biob.types.ImageLike,
357-
reader: Optional[Type[biob.reader.Reader]] = None,
396+
reader: Optional[
397+
Union[Type[biob.reader.Reader], Sequence[Type[biob.reader.Reader]]]
398+
] = None,
358399
reconstruct_mosaic: bool = True,
359400
use_plugin_cache: bool = False,
360401
fs_kwargs: Dict[str, Any] = {},
361-
plugin_priority: Optional[Sequence[Type[biob.reader.Reader]]] = None,
362402
**kwargs: Any,
363403
):
364404
try:
@@ -367,7 +407,6 @@ def __init__(
367407
reader,
368408
use_plugin_cache,
369409
fs_kwargs,
370-
plugin_priority=plugin_priority,
371410
**kwargs,
372411
)
373412
except biob.exceptions.UnsupportedFileFormatError:
@@ -382,7 +421,6 @@ def __init__(
382421
reader,
383422
use_plugin_cache,
384423
fs_kwargs | {"anon": True},
385-
plugin_priority=plugin_priority,
386424
**kwargs,
387425
)
388426

bioio/tests/test_plugins.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def test_bioimage_plugin_priority_modulates_reader(
8080

8181
# Build a controlled plugin mapping where both readers claim the same extension
8282
# and where the base order is [Dummy, Accepting]. This lets us see exactly how
83-
# plugin_priority changes which reader BioImage selects.
83+
# the reader list changes which reader BioImage selects.
8484
test_ext = ".czi"
8585
fake_plugins_by_ext = {test_ext: [dummy_entry, accepting_entry]}
8686

@@ -132,12 +132,12 @@ def dummy_init(
132132
"accepting": AcceptingReader,
133133
}
134134

135-
plugin_priority = [name_to_cls[name] for name in priority_order]
135+
reader_priority = [name_to_cls[name] for name in priority_order]
136136
expected_cls = name_to_cls[expected_first]
137137
test_path = f"test{test_ext}"
138138

139-
# Act
140-
img = BioImage(test_path, plugin_priority=plugin_priority)
139+
# Act: pass the priority list via `reader=` (new API)
140+
img = BioImage(test_path, reader=reader_priority)
141141

142142
# Assert
143143
assert isinstance(img.reader, expected_cls)

0 commit comments

Comments
 (0)