Skip to content

Commit

Permalink
Merge pull request #111 from ap--/fix-qet_qupath-0.5.0
Browse files Browse the repository at this point in the history
Adjust for QuPath 0.5.0
  • Loading branch information
ap-- authored Feb 22, 2024
2 parents 944ae6d + 48d1aca commit ce13dce
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 62 deletions.
14 changes: 8 additions & 6 deletions .github/workflows/run_pytests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,29 @@ on:
pull_request: {}

env:
QUPATH_VERSION: 0.4.3
QUPATH_VERSION: 0.5.0

jobs:
# RUN PYTEST ON PAQUO SOURCE
tests:
name: pytest ${{ matrix.os }}::py${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
strategy:
max-parallel: 7
max-parallel: 8
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.11"]
python-version: ["3.12"]
include:
# we'll test the python support on ubuntu
- os: ubuntu-latest
python-version: '3.10'
python-version: '3.11'
- os: ubuntu-latest
python-version: 3.9
python-version: '3.12'
- os: ubuntu-latest
python-version: 3.8
python-version: '3.9'
- os: ubuntu-latest
python-version: '3.8'
steps:
- uses: actions/checkout@v3
with:
Expand Down
14 changes: 7 additions & 7 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-added-large-files
- repo: https://github.com/asottile/pyupgrade
rev: v3.9.0
rev: v3.15.1
hooks:
- id: pyupgrade
args: [--py38-plus]
- repo: https://github.com/pycqa/isort
rev: 5.12.0
rev: 5.13.2
hooks:
- id: isort
exclude: ^examples|^extras|^docs|tests.*|setup.py
Expand All @@ -21,21 +21,21 @@ repos:
# - id: black
# language_version: python3
- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v1.4.1'
rev: 'v1.8.0'
hooks:
- id: mypy
exclude: ^examples|^extras|^docs|tests.*
additional_dependencies: [packaging, ome-types]
- repo: https://github.com/PyCQA/flake8
rev: '6.0.0'
rev: '7.0.0'
hooks:
- id: flake8
additional_dependencies:
- flake8-typing-imports==1.12.0
- flake8-typing-imports==1.15.0
language_version: python3
exclude: "^(build|docs|examples|extras|setup.py)|tests[/]"
- repo: https://github.com/PyCQA/bandit
rev: '1.7.5'
rev: '1.7.7'
hooks:
- id: bandit
args: ["-lll", "--ini", ".bandit"]
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ unintuitive, slow or if its documentation is confusing, it's a bug in
tracker!

Development
[happens on github](https://github.com/bayer-science-for-a-better-life/paquo)
[happens on GitHub](https://github.com/bayer-science-for-a-better-life/paquo)
:octocat:

## Documentation
Expand Down Expand Up @@ -54,18 +54,18 @@ paquo to use that version. Currently, paquo supports every version of QuPath fro
`0.2.0` to the most recent. _(We even support older `0.2.0-mX` versions but no guarantees)._

```shell
> paquo get_qupath --install-path "/some/path/on/your/machine" 0.4.3
# downloading: https://github.com/qupath/qupath/releases/download/v0.4.3/QuPath-0.4.3-Linux.tar.xz
> paquo get_qupath --install-path "/some/path/on/your/machine" 0.5.0
# downloading: https://github.com/qupath/qupath/releases/download/v0.5.0/QuPath-0.4.3-Linux.tar.xz
# progress ................... OK
# extracting: [...]/QuPath-0.4.3-Linux.tar.xz
# available at: /some/path/on/your/machine/QuPath-0.4.3
# extracting: [...]/QuPath-0.5.0-Linux.tar.xz
# available at: /some/path/on/your/machine/QuPath-0.5.0
#
# use via environment variable:
# $ export PAQUO_QUPATH_DIR=/some/path/on/your/machine/QuPath-0.4.3
# $ export PAQUO_QUPATH_DIR=/some/path/on/your/machine/QuPath-0.5.0
#
# use via .paquo.toml config file:
# qupath_dir="/some/path/on/your/machine/QuPath-0.4.3"
/some/path/on/your/machine/QuPath-0.4.3
# qupath_dir="/some/path/on/your/machine/QuPath-0.5.0"
/some/path/on/your/machine/QuPath-0.5.0
```


Expand All @@ -88,9 +88,9 @@ so go ahead and hack.
- When contributing code, please try to use Pull Requests.
- tests go hand in hand with modules on ```tests``` packages at the same level. We use ```pytest```.

You can setup your IDE to help you adhering to these guidelines.
You can set up your IDE to help you to adhere to these guidelines.
<br>
_([Santi](https://github.com/sdvillal) is happy to help you setting up pycharm in 5 minutes)_
_([Santi](https://github.com/sdvillal) is happy to help you to set up pycharm in 5 minutes)_


## Acknowledgements
Expand Down
26 changes: 16 additions & 10 deletions paquo/__main__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import argparse
import functools
import os
Expand All @@ -8,6 +10,7 @@
from itertools import repeat
from logging.config import dictConfig
from pathlib import Path
from typing import Callable

from paquo._cli import DirectoryType
from paquo._cli import argument
Expand Down Expand Up @@ -270,14 +273,14 @@ def quickview(args, subparser):

f_annotations = None
if args.annotations:
def f_annotations(name):
def f_annotations(name): # noqa
if name != image.name:
return []
return list(args.annotations)

f_annotations_cmd = None
if args.annotations_cmd:
def f_annotations_cmd(name):
def f_annotations_cmd(name): # noqa
import shlex
_cmd = shlex.split(f"{args.annotations_cmd} {name}")
print("annotations", _cmd)
Expand All @@ -286,7 +289,7 @@ def f_annotations_cmd(name):

cmd = None
if f_annotations_cmd or f_annotations:
def cmd(name):
def cmd(name): # noqa
names = []
if f_annotations:
names.extend(f_annotations(name))
Expand Down Expand Up @@ -332,13 +335,16 @@ def _download_cb(it, name):
finally:
print("")

file = download_qupath(
args.version,
path=args.download_path,
callback=_download_cb,
system=system,
ssl_verify=not args.no_ssl_verify
)
try:
file = download_qupath(
args.version,
path=args.download_path,
callback=_download_cb,
system=system,
ssl_verify=not args.no_ssl_verify
)
except Exception:
raise SystemExit(1)
print("# extracting:", file)
app = extract_qupath(file, args.install_path, system=system)
print("# available at:", app)
Expand Down
25 changes: 20 additions & 5 deletions paquo/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,23 @@ def download_qupath(
else:
raise ValueError(f"unsupported platform.system() == {system!r}")

if "rc" not in version:
if not version.startswith("v"):
version = f"v{version}"

if Version(version) > Version("0.4.4"):
if system == "Darwin":
if platform.machine() == "arm64":
_sys = "Mac-arm64"
else:
_sys = "Mac-x64"
name = f"QuPath-{version}-{_sys}"
else:
name = f"QuPath-{version}"
if "rc" not in version:
name = f"QuPath-{version[1:]}-{_sys}"
else:
name = f"QuPath-{version[1:]}"

url = f"https://github.com/qupath/qupath/releases/download/v{version}/{name}.{ext}"
url = f"https://github.com/qupath/qupath/releases/download/{version}/{name}.{ext}"

chunk_size = 10 * 1024 * 1024

Expand All @@ -153,6 +164,7 @@ def download_qupath(
for chunk in callback(iter(lambda: f.read(chunk_size), b""), name=url):
tmp.write(chunk)
except Exception:
print("# error requesting:", url, file=sys.stderr)
try:
os.unlink(out_fn)
except OSError:
Expand All @@ -168,7 +180,7 @@ def extract_qupath(file, destination, system=None):

# normalize QuPath App dirname
m = re.match(
r"QuPath-(?P<version>[0-9]+[.][0-9]+[.][0-9]+(-rc[0-9]+|-m[0-9]+)?)",
r"QuPath-v?(?P<version>[0-9]+[.][0-9]+[.][0-9]+(-rc[0-9]+|-m[0-9]+)?)",
fn,
)

Expand Down Expand Up @@ -236,9 +248,12 @@ def extract_qupath(file, destination, system=None):
pth = os.path.join(tmp_dir, name)
if name.startswith("QuPath") and os.path.isdir(pth):
break
if name.startswith("QuPath") and name.endswith(".exe") and os.path.isfile(pth):
pth = tmp_dir
break
else:
raise RuntimeError("no qupath extracted?")
shutil.move(os.path.join(tmp_dir, name), qp_dst)
shutil.move(pth, qp_dst)
return qp_dst

else:
Expand Down
3 changes: 2 additions & 1 deletion paquo/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,8 @@ def downsample_levels(self) -> List[Dict[str, float]]:
The available downsample levels can differ dependent
on which image backend is used by QuPath
"""
md = self._image_server.getMetadata()
with redirect(stdout=True, stderr=True):
md = self._image_server.getMetadata()
levels = []
for level in range(int(md.nLevels())):
resolution_level = md.getLevel(level)
Expand Down
7 changes: 6 additions & 1 deletion paquo/java.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,12 @@ def supports_newer_addobject_and_pathclass(self) -> bool:

PathAnnotationObject = JClass("qupath.lib.objects.PathAnnotationObject")
PathClass = JClass('qupath.lib.objects.classes.PathClass')
PathClassFactory = JClass('qupath.lib.objects.classes.PathClassFactory')

if not compatibility.supports_newer_addobject_and_pathclass():
PathClassFactory = JClass('qupath.lib.objects.classes.PathClassFactory')
else:
PathClassFactory = None

PathDetectionObject = JClass("qupath.lib.objects.PathDetectionObject")
PathIO = JClass("qupath.lib.io.PathIO")
PathObjectHierarchy = JClass('qupath.lib.objects.hierarchy.PathObjectHierarchy')
Expand Down
56 changes: 37 additions & 19 deletions paquo/tests/test_images.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import platform
import shutil
import sys
import tempfile
from contextlib import nullcontext
from operator import itemgetter
Expand All @@ -23,7 +24,11 @@ def image_entry(svs_small):

@pytest.fixture(scope='function')
def removable_svs_small(svs_small):
with tempfile.TemporaryDirectory(prefix='paquo-') as tmpdir:
kw = {} if sys.version_info < (3, 10) else {"ignore_cleanup_errors": True}
with tempfile.TemporaryDirectory(
prefix='paquo-',
**kw
) as tmpdir:
new_path = Path(tmpdir) / svs_small.name
shutil.copy(svs_small, new_path)
yield new_path
Expand All @@ -32,21 +37,35 @@ def removable_svs_small(svs_small):
@pytest.fixture(scope='function')
def project_with_removed_image(removable_svs_small):
with tempfile.TemporaryDirectory(prefix='paquo-') as tmpdir:
qp = QuPathProject(tmpdir, mode='x')
_ = qp.add_image(removable_svs_small, image_type=QuPathImageType.BRIGHTFIELD_H_E)
qp.save()
removable_svs_small.unlink()
yield qp.path
with QuPathProject(tmpdir, mode='x') as qp:
_ = qp.add_image(removable_svs_small, image_type=QuPathImageType.BRIGHTFIELD_H_E)
qp.save()
path = qp.path
try:
removable_svs_small.unlink()
except PermissionError:
if platform.system() == "Windows":
pytest.xfail("Windows QuPath==0.5.0 permission issue")
else:
raise
yield path


@pytest.fixture(scope='function')
def project_with_removed_image_without_image_data(removable_svs_small):
with tempfile.TemporaryDirectory(prefix='paquo-') as tmpdir:
qp = QuPathProject(tmpdir, mode='x')
_ = qp.add_image(removable_svs_small)
qp.save()
removable_svs_small.unlink()
yield qp.path
with QuPathProject(tmpdir, mode='x') as qp:
_ = qp.add_image(removable_svs_small)
qp.save()
path = qp.path
try:
removable_svs_small.unlink()
except PermissionError:
if platform.system() == "Windows":
pytest.xfail("Windows QuPath==0.5.0 permission issue")
else:
raise
yield path


def test_image_entry_return_hierarchy(image_entry):
Expand Down Expand Up @@ -74,22 +93,21 @@ 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,
'height': 2967,
'width': 2220},
# todo: when openslide can be used by qupath, this downsample level
# in the test image disappears. investigate...
# {'downsample': 3.865438534407666,
# 'height': 768,
# 'width': 574},
{'downsample': 3.865438534407666,
'height': 768,
'width': 574},
]
assert image_entry.downsample_levels == levels
assert (
image_entry.downsample_levels == levels
or image_entry.downsample_levels == levels[:1]
)


def test_metadata_interface(image_entry):
Expand Down
Loading

0 comments on commit ce13dce

Please sign in to comment.