diff --git a/README.md b/README.md index 32813365b..cf4e230d6 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/aicsimageio/readers/bioformats_reader.py b/aicsimageio/readers/bioformats_reader.py index 97099185e..fc681506f 100644 --- a/aicsimageio/readers/bioformats_reader.py +++ b/aicsimageio/readers/bioformats_reader.py @@ -24,6 +24,7 @@ from .reader import Reader if TYPE_CHECKING: + from bioformats_jar import _loci from fsspec.spec import AbstractFileSystem from .. import types @@ -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 +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: @@ -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, ...]: @@ -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): @@ -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() @@ -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) @@ -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", @@ -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 diff --git a/aicsimageio/tests/readers/extra_readers/test_bioformats_reader.py b/aicsimageio/tests/readers/extra_readers/test_bioformats_reader.py index 306d74b98..88edb49c1 100644 --- a/aicsimageio/tests/readers/extra_readers/test_bioformats_reader.py +++ b/aicsimageio/tests/readers/extra_readers/test_bioformats_reader.py @@ -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( @@ -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, @@ -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, diff --git a/setup.cfg b/setup.cfg index ec37d4980..072516356 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,6 +29,7 @@ xfail_strict = true filterwarnings = ignore::UserWarning ignore::FutureWarning + ignore:distutils Version classes are deprecated: addopts = -p no:faulthandler [flake8] diff --git a/tox.ini b/tox.ini index b2458a9ef..4e4435d52 100644 --- a/tox.ini +++ b/tox.ini @@ -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}