Skip to content
This repository was archived by the owner on Dec 1, 2025. It is now read-only.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ optionally installed using `[...]` syntax.
- For multiple additional supported formats: `pip install aicsimageio[base-imageio,nd2]`
- For all additional supported (and openly licensed) formats: `pip install aicsimageio[all]`
- Due to the GPL license, LIF support is not included with the `[all]` extra, and must be installed manually with `pip install aicsimageio readlif>=0.6.4`
- Due to the GPL license, Bio-Formats support is not included with the `[all]` extra, and must be installed manually with `pip install aicsimageio bioformats_jar`
- Due to the GPL license, CZI support is not included with the `[all]` extra, and must be installed manually with `pip install aicsimageio aicspylibczi>=3.0.5`
- Due to the GPL license, Bio-Formats support is not included with the `[all]` extra, and must be installed manually with `pip install aicsimageio bioformats_jar`. **Important!!** Bio-Formats support also requires a `java` executable in the environment. The simplest method is to install `bioformats_jar` from conda: `conda install -c conda-forge bioformats_jar` (which will additionally bring `openjdk`).

## Documentation

Expand Down
85 changes: 53 additions & 32 deletions aicsimageio/readers/bioformats_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from .reader import Reader

if TYPE_CHECKING:
from bioformats_jar import _loci
from fsspec.spec import AbstractFileSystem

from .. import types
Expand All @@ -32,21 +33,27 @@
try:
import jpype
from bioformats_jar import get_loci
except ImportError:
except ImportError as e:
raise ImportError(
"bioformats_jar is required for this reader. "
"Install with `pip install bioformats_jar`"
)
"Install with `pip install bioformats_jar` or `conda install bioformats_jar`"
) from e

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 love this!

try:
from jgo.jgo import ExecutableNotFound
except ImportError:

class ExecutableNotFound(Exception): # type: ignore
...


class BioformatsReader(Reader):
"""Read files using bioformats.

This reader requires `bioformats_jar` to be installed in the environment, and
requires the java executable to be available on the path, or via the JAVA_HOME
environment variable.
requires the java executable to be available on the path (or via the JAVA_HOME
environment variable), along with the `mvn` executable.

To install java with conda, run `conda install -c conda-forge openjdk`.
To install java and maven with conda, run `conda install -c conda-forge scyjava`.
You may need to deactivate/reactivate your environment after installing. If you
are *still* getting a `JVMNotFoundException`, try setting JAVA_HOME as follows:

Expand Down Expand Up @@ -142,12 +149,12 @@ def __init__(
self._scenes: Tuple[str, ...] = tuple(
str(md.getImageName(i)) for i in range(md.getImageCount())
)
except jpype.JVMNotFoundException:
except RuntimeError:
raise
except Exception:
except Exception as e:
raise exceptions.UnsupportedFileFormatError(
self.__class__.__name__, self._path
)
) from e

@property
def scenes(self) -> Tuple[str, ...]:
Expand Down Expand Up @@ -211,9 +218,7 @@ def _to_xarray(self, delayed: bool = True) -> xr.DataArray:
@staticmethod
def bioformats_version() -> str:
"""The version of the bioformats_package.jar being used."""
from bioformats_jar import get_loci

return get_loci().__version__
return _try_get_loci().__version__


class CoreMeta(NamedTuple):
Expand Down Expand Up @@ -293,20 +298,7 @@ def __init__(
dask_tiles: bool = False,
tile_size: Optional[Tuple[int, int]] = None,
):
try:
loci = get_loci()
except jpype.JVMNotFoundException as e:
raise type(e)(
str(e) + "\n\nBioformatsReader requires a java executable to be "
"available in your environment. If you are using conda, you can "
"install with `conda install -c conda-forge openjdk`.\n\n"
"Note: you may need to reactivate your conda environment after "
"installing opendjk. If you still have this error, try:\n\n"
"# mac and linux:\n"
"export JAVA_HOME=$CONDA_PREFIX\n\n"
"# windows:\n"
"set JAVA_HOME=%CONDA_PREFIX%\\Library"
)
loci = _try_get_loci() # may raise RuntimeError

self._path = str(path)
self._r = loci.formats.ImageReader()
Expand Down Expand Up @@ -549,9 +541,7 @@ def _dask_chunk(self, block_id: Tuple[int, ...]) -> np.ndarray:
@classmethod
def _create_ome_meta(cls) -> Any:
"""create an OMEXMLMetadata object to populate"""
from bioformats_jar import get_loci

loci = get_loci()
loci = _try_get_loci()
if not cls._service:
factory = loci.common.services.ServiceFactory()
cls._service = factory.getInstance(loci.formats.services.OMEXMLService)
Expand All @@ -560,9 +550,7 @@ def _create_ome_meta(cls) -> Any:

def _pixtype2dtype(pixeltype: int, little_endian: bool) -> np.dtype:
"""Convert a loci.formats PixelType integer into a numpy dtype."""
from bioformats_jar import get_loci

FT = get_loci().formats.FormatTools
FT = _try_get_loci().formats.FormatTools
fmt2type: Dict[int, str] = {
FT.INT8: "i1",
FT.UINT8: "u1",
Expand Down Expand Up @@ -633,3 +621,36 @@ def _hide_memoization_warning() -> None:

System = jpype.JPackage("java").lang.System
System.err.close()


MAVEN_ERROR_MSG = """
BioformatsReader requires the maven ('mvn') executable to be
available in your environment. If you are using conda, you can
install with `conda install -c conda-forge scyjava`.

Alternatively, install from https://maven.apache.org/download.cgi
"""

JAVA_ERROR_MSG = """
BioformatsReader requires a java executable to be available
in your environment. If you are using conda, you can install
with `conda install -c conda-forge scyjava`.

Note: you may need to reactivate your conda environment after
installing opendjk. If you still have this error, try:

# mac and linux:
export JAVA_HOME=$CONDA_PREFIX

# windows:
set JAVA_HOME=%CONDA_PREFIX%\\Library
"""


def _try_get_loci() -> _loci.__module_protocol__:
try:
return get_loci()
except ExecutableNotFound as e:
raise RuntimeError(MAVEN_ERROR_MSG) from e
except jpype.JVMNotFoundException as e:
raise RuntimeError(JAVA_ERROR_MSG) from e
Comment on lines +654 to +656

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These verbose error messages are fantastic!

17 changes: 13 additions & 4 deletions aicsimageio/tests/readers/extra_readers/test_bioformats_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@

from ...conftest import LOCAL, get_resource_full_path, host

try:
bfj = pytest.importorskip("bioformats_jar")
bf_version = tuple(int(x) for x in str(bfj.__version__).split(".")[:2])
except Exception:
bf_version = (6, 7)

# bioformats changed their DICOM scene labeling scheme at some point
SERIES_0 = "PRIMARY" if bf_version > (6, 7) else "Series 0"


@host
@pytest.mark.parametrize(
Expand Down Expand Up @@ -209,8 +218,8 @@
),
(
"DICOM_samples_MR-MONO2-8-16x-heart.dcm",
"Series 0",
("Series 0",),
SERIES_0,
(SERIES_0,),
(1, 1, 16, 256, 256),
np.uint8,
dimensions.DEFAULT_DIMENSION_ORDER,
Expand Down Expand Up @@ -505,8 +514,8 @@ def test_bioformats_dask_tiling_read(filename: str) -> None:
),
(
"DICOM_samples_MR-MONO2-8-16x-heart.dcm",
"Series 0",
("Series 0",),
SERIES_0,
(SERIES_0,),
(1, 1, 16, 256, 256),
np.uint8,
dimensions.DEFAULT_DIMENSION_ORDER,
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ xfail_strict = true
filterwarnings =
ignore::UserWarning
ignore::FutureWarning
ignore:distutils Version classes are deprecated:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What code is bringing about this warning?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both xarray and setuptools:

Details
================================================================= warnings summary =================================================================
../../../../miniconda3/envs/aics/lib/python3.9/site-packages/xarray/core/pycompat.py:22
aicsimageio/tests/readers/extra_readers/test_bioformats_reader.py::test_bioformats_reader[DICOM_samples_MR-MONO2-8-16x-heart.dcm-PRIMARY-expected_scenes16-expected_shape16-uint8-TCZYX-expected_channel_names16-expected_physical_pixel_sizes16-LOCAL]
aicsimageio/tests/readers/extra_readers/test_bioformats_reader.py::test_bioformats_reader[DICOM_samples_MR-MONO2-8-16x-heart.dcm-PRIMARY-expected_scenes16-expected_shape16-uint8-TCZYX-expected_channel_names16-expected_physical_pixel_sizes16-LOCAL]
aicsimageio/tests/readers/extra_readers/test_bioformats_reader.py::test_bioformats_reader[DICOM_samples_MR-MONO2-8-16x-heart.dcm-PRIMARY-expected_scenes16-expected_shape16-uint8-TCZYX-expected_channel_names16-expected_physical_pixel_sizes16-LOCAL]
  /Users/talley/miniconda3/envs/aics/lib/python3.9/site-packages/xarray/core/pycompat.py:22: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    duck_array_version = LooseVersion(duck_array_module.__version__)

../../../../miniconda3/envs/aics/lib/python3.9/site-packages/xarray/core/pycompat.py:37
../../../../miniconda3/envs/aics/lib/python3.9/site-packages/xarray/core/pycompat.py:37
  /Users/talley/miniconda3/envs/aics/lib/python3.9/site-packages/xarray/core/pycompat.py:37: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    duck_array_version = LooseVersion("0.0.0")

../../../../miniconda3/envs/aics/lib/python3.9/site-packages/setuptools/_distutils/version.py:351
../../../../miniconda3/envs/aics/lib/python3.9/site-packages/setuptools/_distutils/version.py:351
../../../../miniconda3/envs/aics/lib/python3.9/site-packages/setuptools/_distutils/version.py:351
../../../../miniconda3/envs/aics/lib/python3.9/site-packages/setuptools/_distutils/version.py:351
  /Users/talley/miniconda3/envs/aics/lib/python3.9/site-packages/setuptools/_distutils/version.py:351: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    other = LooseVersion(other)

../../../../miniconda3/envs/aics/lib/python3.9/site-packages/xarray/core/npcompat.py:82
  /Users/talley/miniconda3/envs/aics/lib/python3.9/site-packages/xarray/core/npcompat.py:82: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    if LooseVersion(np.__version__) >= "1.20.0":

../../../../miniconda3/envs/aics/lib/python3.9/site-packages/xarray/core/pdcompat.py:45
  /Users/talley/miniconda3/envs/aics/lib/python3.9/site-packages/xarray/core/pdcompat.py:45: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead.
    if LooseVersion(pd.__version__) < "0.25.0":

-- Docs: https://docs.pytest.org/en/stable/warnings.html

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh gotcha. I've been getting a lot of those LooseVersion warnings on other projects. Good to know I can borrow this ignoration 😁

addopts = -p no:faulthandler

[flake8]
Expand Down
3 changes: 2 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ setenv =
PYTHONPATH = {toxinidir}
extras =
test
deps = bioformats_jar
deps =
bioformats_jar
commands =
pytest --basetemp={envtmpdir} --cov-report xml --cov-report html --cov=aicsimageio aicsimageio/tests/readers/extra_readers/test_bioformats_reader.py {posargs}

Expand Down