Skip to content

Commit

Permalink
Merge pull request #87 from bayer-science-for-a-better-life/fix-m1-jv…
Browse files Browse the repository at this point in the history
…m-issue

Fix m1 jvm issue
  • Loading branch information
ap-- committed Nov 18, 2022
2 parents f5cd079 + 03a0b54 commit de5f916
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 10 deletions.
2 changes: 2 additions & 0 deletions paquo/.paquo.defaults.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ java_opts = [
# paquo options
safe_truncate = true

# allows to use a different jvm_path for running qupath
jvm_path_override = ""

# internal settings
# -----------------
Expand Down
2 changes: 1 addition & 1 deletion paquo/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def uri(self, image_id: SimpleFileImageId) -> Optional['URIString']:
"""accepts a path and returns a URIString"""
if not isinstance(image_id, (Path, str, ImageProvider.FilenamePathId)):
raise TypeError("image_id not of correct format") # pragma: no cover
if isinstance(image_id, str) and "://" in image_id:
if isinstance(image_id, str) and image_id.startswith("file:/"):
# image_id is uri
image_id = _normalize_pathlib_uris(image_id)
return ImageProvider.URIString(image_id)
Expand Down
66 changes: 57 additions & 9 deletions paquo/jpype_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import re
import shlex
import sys
import textwrap
from contextlib import contextmanager
from contextlib import nullcontext
from itertools import chain
Expand All @@ -14,8 +15,8 @@
from typing import Dict
from typing import Iterable
from typing import List
from typing import NamedTuple
from typing import Optional
from typing import Tuple
from typing import Union
from warnings import warn

Expand All @@ -25,20 +26,27 @@

# types
PathOrStr = Union[Path, str]
QuPathJVMInfo = Tuple[Path, Path, Path, List[str]]

__all__ = ["JClass", "start_jvm", "find_qupath"]

JClass = jpype.JClass


class QuPathJVMInfo(NamedTuple):
app_dir: Path
runtime_dir: Path
jvm_path: Path
jvm_options: List[str]


def find_qupath(*,
qupath_dir: Optional[PathOrStr] = None,
qupath_search_dirs: Optional[Union[PathOrStr, List[PathOrStr]]] = None,
qupath_search_dir_regex: Optional[str] = None,
qupath_search_conda: Optional[bool] = None,
qupath_prefer_conda: Optional[bool] = None,
java_opts: Optional[Union[List[str], str]] = None,
jvm_path_override: Optional[PathOrStr] = None,
**_kwargs) -> QuPathJVMInfo:
"""find current qupath installation and jvm paths/options
Expand All @@ -55,12 +63,24 @@ def find_qupath(*,
elif isinstance(java_opts, str):
java_opts = shlex.split(java_opts)

if jvm_path_override:
jvm_path_override = Path(jvm_path_override)
if not jvm_path_override.exists():
raise FileNotFoundError(jvm_path_override)
if jvm_path_override.is_dir():
raise IsADirectoryError(jvm_path_override)
else:
jvm_path_override = None

if qupath_dir:
# short circuit in case we provide a qupath_dir
# --> when qupath_dir is provided, search is disabled
if isinstance(qupath_dir, str):
qupath_dir = Path(qupath_dir)
return qupath_jvm_info_from_qupath_dir(qupath_dir, java_opts)
info = qupath_jvm_info_from_qupath_dir(qupath_dir, java_opts)
if jvm_path_override:
return info._replace(jvm_path=jvm_path_override)
return info

if qupath_search_dirs is None:
qupath_search_dirs = []
Expand All @@ -80,9 +100,13 @@ def find_qupath(*,

for qupath_dir in search_dirs:
try:
return qupath_jvm_info_from_qupath_dir(qupath_dir, java_opts)
info = qupath_jvm_info_from_qupath_dir(qupath_dir, java_opts)
except FileNotFoundError:
continue
else:
if jvm_path_override:
return info._replace(jvm_path=jvm_path_override)
return info
else:
raise ValueError("no valid qupath installation found")

Expand Down Expand Up @@ -121,30 +145,30 @@ def qupath_jvm_info_from_qupath_dir(qupath_dir: Path, jvm_options: List[str]) ->
if system == "Linux":
app_dir = qupath_dir / "lib" / "app"
runtime_dir = qupath_dir / "lib" / "runtime"
jvm_dir = runtime_dir / "lib" / "server" / "libjvm.so"
jvm_path = runtime_dir / "lib" / "server" / "libjvm.so"

elif system == "Darwin":
app_dir = qupath_dir / "Contents" / "app"
runtime_dir = qupath_dir / "Contents" / "runtime" / "Contents" / "Home"
jvm_dir = runtime_dir / "lib" / "libjli.dylib" # not server/libjvm.dylib
jvm_path = runtime_dir / "lib" / "libjli.dylib" # not server/libjvm.dylib

elif system == "Windows":
app_dir = qupath_dir / "app"
runtime_dir = qupath_dir / "runtime"
jvm_dir = runtime_dir / "bin" / "server" / "jvm.dll"
jvm_path = runtime_dir / "bin" / "server" / "jvm.dll"

else: # pragma: no cover
raise ValueError(f'Unknown platform {system}')

# verify that paths are sane
if not (app_dir.is_dir() and runtime_dir.is_dir() and jvm_dir.is_file()):
if not (app_dir.is_dir() and runtime_dir.is_dir() and jvm_path.is_file()):
raise FileNotFoundError('qupath installation is incompatible')

# Add java.library.path so that the qupath provided openslide works
jvm_options.append(f"-Djava.library.path={app_dir}")
jvm_options = list(dict.fromkeys(jvm_options)) # keep options unique and in order

return app_dir, runtime_dir, jvm_dir, jvm_options
return QuPathJVMInfo(app_dir, runtime_dir, jvm_path, jvm_options)


# stores qupath version to handle consecutive calls to start_jvm
Expand Down Expand Up @@ -226,6 +250,30 @@ def is_windows_store_python() -> bool:
ignoreUnrecognized=False,
convertStrings=False
)
except OSError as err:
if (
err.errno == 0
and "JVM DLL not found:" in str(err)
and jvm_path.is_file()
and platform.uname().machine == "arm64"
):
msg = textwrap.dedent("""\
Probably a JVM and Python architecture issue on M1:
You can fix this by running a JVM with the same architecture as your
Python interpreter. Usually paquo uses the JVM that ships with QuPath,
but you can override this by setting 'jvm_path_override' in `.paquo.toml`
In case you are running an arm64 Python interpreter on your mac try
installing an arm jvm from
https://www.azul.com/downloads/?version=java-17-lts&os=macos&architecture=arm-64-bit&package=jdk
```
$ cat .paquo.toml
# use the path to libjli.dylib valid on your machine:
jvm_path_override = "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libjli.dylib"
```
If this doesn't work, please open an issue on github.
""")
raise RuntimeError(msg) from err
except RuntimeError as jvm_error: # pragma: no cover
# there's a chance that this RuntimeError occurred because a user provided
# jvm_option is incorrect. let's try if that is the case and crash with a
Expand Down
4 changes: 4 additions & 0 deletions paquo/tests/test_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ def test_image_properties_from_image_server(image_entry):
assert image_entry.num_z_slices == 1


@pytest.mark.xfail(
platform.uname().machine == "arm64",
reason="QuPath-vendored openslide not working on arm64"
)
def test_image_downsample_levels(image_entry):
levels = [
{'downsample': 1.0,
Expand Down

0 comments on commit de5f916

Please sign in to comment.