Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 72 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
# - smoke-wheels: install the freshly built wheel on the real target and run
# tools/smoke_test_python.py (import + geometry op) for every Python.
# - publish-pypi / publish-testpypi: upload via Trusted Publishing (OIDC).
#
# Like fimage and unlike fastslide, NO artifacts are attached to a GitHub
# Release: PyPI is the sole distribution channel for dlup.
# - github-release: needs every build/smoke/publish job, so a release is only
# cut when all selected platforms succeed. Attaches the wheels + sdist (with
# SLSA build-provenance attestation), mirroring fastslide. PyPI remains the
# primary install channel; the GitHub Release is a secondary, signed mirror.
#
# NOTE: dlup depends on fastslide and fimage-python at runtime, so a full PyPI
# release (and its smoke tests) requires both of those to already be published
Expand Down Expand Up @@ -158,7 +159,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v5
- name: Set up uv
uses: astral-sh/setup-uv@v6
uses: astral-sh/setup-uv@v7
- name: Build sdist
shell: bash
run: uv build --sdist --out-dir artifacts/sdist
Expand Down Expand Up @@ -218,7 +219,7 @@ jobs:
run: softwareupdate --install-rosetta --agree-to-license
- name: Set up uv
if: steps.gate.outputs.run == 'true'
uses: astral-sh/setup-uv@v6
uses: astral-sh/setup-uv@v7
- name: Download wheels
if: steps.gate.outputs.run == 'true'
uses: actions/download-artifact@v6
Expand Down Expand Up @@ -303,3 +304,69 @@ jobs:
packages-dir: dist
repository-url: https://test.pypi.org/legacy/
skip-existing: true
github-release:
name: Publish GitHub Release
needs: [smoke-wheels, build-sdist, publish-pypi, publish-testpypi]
# publish-pypi / publish-testpypi are mutually exclusive, so one is always
# skipped. A skipped need is not success(), which would skip this job too;
# gate on "nothing failed/cancelled" instead so the skipped sibling is OK.
if: ${{ !cancelled() && !failure() }}
runs-on: ubuntu-24.04
permissions:
contents: write # create the release
id-token: write # SLSA provenance signing
attestations: write # record the attestation
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Download wheels
uses: actions/download-artifact@v6
with:
path: artifacts/wheels
pattern: "wheels-*"
merge-multiple: true
- name: Download sdist
uses: actions/download-artifact@v6
with:
name: sdist
path: artifacts/sdist
- name: List collected artifacts
shell: bash
run: |
echo "Wheels:"; ls -l artifacts/wheels
echo "Sdist:"; ls -l artifacts/sdist
- name: Generate build provenance attestation
uses: actions/attest-build-provenance@v3
with:
subject-path: |
artifacts/wheels/*.whl
artifacts/sdist/*.tar.gz
# Full release -> tag == version, marked latest. Snapshot -> a prerelease
# tagged <version>-dev.<shortsha>. --clobber lets a re-run replace the
# assets on an existing tag (mirrors the publish steps' skip-existing).
- name: Publish
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
version="$(sed -n 's/^version *= *"\(.*\)"/\1/p' pyproject.toml | head -n1)"
if [ -z "$version" ]; then
echo "Could not read version from pyproject.toml" >&2
exit 1
fi
notes="dlup artifacts for \`${version}\`. Python wheels published to PyPI (also attached here), with the sdist as the source fallback."
if [[ "${{ inputs.release }}" == "true" ]]; then
tag="$version"
flags=(--latest --title "dlup $tag")
else
tag="${version}-dev.${GITHUB_SHA::7}"
flags=(--prerelease --title "dlup $tag")
fi
assets=(artifacts/wheels/*.whl artifacts/sdist/*.tar.gz)
if gh release view "$tag" >/dev/null 2>&1; then
gh release upload "$tag" "${assets[@]}" --clobber
else
gh release create "$tag" "${assets[@]}" \
"${flags[@]}" --notes "$notes" --generate-notes
fi
22 changes: 21 additions & 1 deletion BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,16 @@ DLUP_VERSION = "0.9.2"
# We intentionally avoid depending on Bazel `requirement(...)` targets here so
# the wheel build can work across multiple Python versions without requiring a
# per-Python locked wheel set in the repo.
#
# Keep this in sync with pyproject.toml: openslide is an OPTIONAL backend, so
# openslide-python is NOT a hard dependency. It lives in the `openslide` extra
# below alongside the prebuilt `openslide-bin` (which ships libopenslide). The
# wheel built here (Linux/macOS, via Bazel) must expose the same `openslide`
# extra as the meson-python wheel (Windows), so `pip install dlup[openslide]`
# behaves identically on every platform.
DLUP_WHEEL_REQUIRES = [
"fastslide",
"fimage-python",
"openslide-python",
"numpy",
"packaging",
"pillow",
Expand All @@ -55,6 +61,19 @@ DLUP_WHEEL_REQUIRES = [
"xsdata",
]

# Optional dependency groups (PEP 508 extras), mirroring
# pyproject.toml [project.optional-dependencies].
DLUP_WHEEL_EXTRA_REQUIRES = {
"openslide": [
"openslide-python>=1.4",
"openslide-bin",
],
"dev": [
"pytest>=7.0",
"pytest-mock",
],
}

# Platform-specific compatibility - supports Linux, macOS, and Windows.
COMPATIBLE_TARGETS = select({
"@platforms//os:linux": [],
Expand Down Expand Up @@ -261,6 +280,7 @@ multi_version_py_wheel(
platform = WHEEL_PLATFORM,
python_requires = ">=3.10",
python_versions = PYTHON_VERSIONS,
extra_requires = DLUP_WHEEL_EXTRA_REQUIRES,
requires = DLUP_WHEEL_REQUIRES,
strip_path_prefixes = [],
summary = "Deep Learning Utilities for Pathology",
Expand Down
2 changes: 1 addition & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ git_override(
bazel_dep(name = "fimage", version = "0.1.2")
git_override(
module_name = "fimage",
commit = "136bfbe5ca6ce8cdd2557a5b2b068bc71a5bd866",
commit = "327dead92b02ed6292131d915aa0a0f439dcdd56",
remote = "https://github.com/NKI-AI/fimage.git",
)

Expand Down
5 changes: 4 additions & 1 deletion dlup/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@

if not OPENSLIDE_AVAILABLE:
warnings.warn(
"Openslide is not available. OpenSlide backend will not be available. To install it, run `pip install openslide-python`."
"OpenSlide is not available. The OpenSlide backend will be disabled. This means either the "
"`openslide-python` wrapper is not installed or the native `libopenslide` library could not be "
"loaded. To enable it, install both with `pip install dlup[openslide]` (which pulls in the "
"prebuilt `openslide-bin`)."
)
else:
from .openslide_backend import OpenSlideSlide as OpenSlideSlide # noqa: F401
Expand Down
25 changes: 24 additions & 1 deletion dlup/utils/imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,33 @@ def _module_available(module_path: str) -> bool:
return False


def _openslide_available() -> bool:
r"""Check whether the OpenSlide backend can actually be used.

Unlike a plain ``find_spec`` probe, this performs a real import: the
``openslide-python`` wrapper ``dlopen``s the native ``libopenslide`` at
import time, so the wrapper package being present does not guarantee a
working backend. When the native library is missing the wrapper raises
``OSError``/``ModuleNotFoundError`` on import, in which case the backend is
treated as unavailable rather than crashing every ``import dlup``.

Returns:
``True`` only if ``import openslide`` succeeds (wrapper *and* native
library present), ``False`` otherwise.
"""
if not _module_available("openslide"):
return False
try:
import openslide # noqa: F401 # pylint: disable=import-outside-toplevel,unused-import
except (ImportError, OSError):
return False
return True


PYTORCH_AVAILABLE = _module_available("pytorch")
PYHALOXML_AVAILABLE = _module_available("pyhaloxml")
DARWIN_SDK_AVAILABLE = _module_available("darwin")
SHAPELY_AVAILABLE = _module_available("shapely")
OPENSLIDE_AVAILABLE = _module_available("openslide")
OPENSLIDE_AVAILABLE = _openslide_available()
TIFFFILE_AVAILABLE = _module_available("tifffile")
AIOHTTP_AVAILABLE = _module_available("aiohttp")
2 changes: 2 additions & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pillow
shapely
tifffile
xsdata
fastslide==0.7.4
fimage-python==0.1.2

# OpenSlide backend. openslide-bin ships the prebuilt OpenSlide C library as a
# wheel (Linux/macOS/Windows), so `import openslide` works in Bazel's sandbox
Expand Down
67 changes: 67 additions & 0 deletions requirements_darwin.txt
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,72 @@ cycler==0.12.1 \
--hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 \
--hash=sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c
# via matplotlib
fastslide==0.7.4 \
--hash=sha256:07496c5a0ca90c76bd538621d9146d68ee55c5c7b4e1fb52b26ea8f708be42f5 \
--hash=sha256:0d97e601d32cc5608d997e67aef4ea2f39e91c58cae5362e64c71b5a640de189 \
--hash=sha256:178f14ad6e7ee638e730a5b0b942f3a74076fd0ea98ce48e4c0748ff3ef6bbee \
--hash=sha256:180d2ea5b9ff449ff2a9437d7a707374a3b211116e8552b6c57fe46ac69e117f \
--hash=sha256:20e434d846353e3a1fd78aeecfab9339fd93256a10661436a7e5144d4fc34154 \
--hash=sha256:23148ceb5b6889cb38dfc38ef7c9fbe1a8b20386bd08dfdfa40af5c5717cab89 \
--hash=sha256:2598c08c0b02158569cbfed9c7c33c38079813a3da65551ca2b1f8bfccb1c8f6 \
--hash=sha256:2dc48dcf1f05470ab3af9e4e17fa0223e7505a16486dbc28845e0f55a8311641 \
--hash=sha256:386df9ecba74cafe137338ff9f2e647198f2af625700c3b555603f23cf3603e1 \
--hash=sha256:3b67a4c1fed9e5b37e25ebd2fbae0e8acb2d18de99fa33c8e36cd3bba5037c8d \
--hash=sha256:53bd2af03e72fad7b4eeb3e740d4cd1bcfab8444c69075d8b3e1fded61a4d3b2 \
--hash=sha256:5b9b596c1852d7d9b110696b2caa45e4ee7889a0664222e64b79f99510d33b71 \
--hash=sha256:5d930bb7ea1a06040c1770d0281768523ac9a2d80c0aa287093a41f0b6a1ddf4 \
--hash=sha256:5e98e97baf88c1fba1756f21c6b20bc67a3c464aea48759b75c15d59eb32f059 \
--hash=sha256:61d52a01d0f09ca6646dfa6d48a4052a85c00d467cb4eaa44cd9303a27f31eb1 \
--hash=sha256:6834834a76a9dd8685a419f1b6388c4278b5f223a75b29a7873bcb825c3a7193 \
--hash=sha256:8a63882576783674258e2dd87c5cb3e299a8eff22f06f02ce8c7422903a7186e \
--hash=sha256:8cb3f3deadc9ad012bd38e2015400ed29ae57b52cd5fe8ca09876edafe25ffbc \
--hash=sha256:94103b460e29fe3053bf4ee4d821a6687f0d9f1e3e599bb561ea384a6a9df449 \
--hash=sha256:997a12cbd564dcbe45e36eadb507baa8798fe8e729e80386d470b7882c324cd0 \
--hash=sha256:9f2a24596452bfe7d203567abb6c27c58900e5269fa9dc10b8e62523710e24a2 \
--hash=sha256:9fd14e296dca3e36c4665b4b603d5d374a6e6f15cdecce526bf7d6f892ee4577 \
--hash=sha256:a183bd87d30f1dbca1cdb5fb6a13499d3891073249bf578eac5006134dbf0b19 \
--hash=sha256:b3c69eb8c61052d1ec40d6dc19d7bfac0fdb984b4a13a58a268e0a492b978e4a \
--hash=sha256:bb27b763919a7c9ef0052a75e9ba37dffc4a2b9916e6c67fabace2372fbe5d95 \
--hash=sha256:bc57e028816cad6d173271a11acdb4ab610082bf5c028531e261386df5ac06ae \
--hash=sha256:cc7364855446152df4e10c82e08d16ab66df5074bfdf8ab658689b1ebe6a77e4 \
--hash=sha256:e28c709ddaae5128e0229aa63c013ac1c4942e78f377d26f2d2652dd26d7d1a4 \
--hash=sha256:e8372e2c2f6c590e530a6889e39509658b63d20c7486a28031bbac9359929177 \
--hash=sha256:f04e71fcde493182ae7ca5089ef1cec6c6459aba573a14a120dc80dcd8b3473a \
--hash=sha256:f6ae451ca65f404e9e0caa5908b6cc1cc5692e8d532d179b79994b57fcf48d9f
# via -r requirements.in
fimage-python==0.1.2 \
--hash=sha256:0ec259ccf20a16fd5d92b9ff3688a0684886bbecb56c98c526e2135bd7144147 \
--hash=sha256:1380ca673563db8657993a31f4b31ff87bad51ead80a1d55cfe8c666ccf1aa04 \
--hash=sha256:141aeb3854a6a9490ba9e1cf8bee27347ce8537d2a3f6fc2939ebb1d481a719c \
--hash=sha256:1fe3ee4bf0181b961b0a7fbd388d5ab7ba88d936fc58232dca3ac422b872dfb9 \
--hash=sha256:281fd45114e0ebad8dd9f9bf9707a758ca5425e0109ad7d393e3ffad6ba0c61f \
--hash=sha256:326cdf24b0e06603ed22c8c1ca5d662c93c070ba39b771bcc61a3cc22d987d22 \
--hash=sha256:3432f95c152825bf2acbefa43332613a584e9ca9e65e77e106b2fd1739bf7976 \
--hash=sha256:3c6be2ddfa652303b511185951f3f149158681daadf5215d716ef1a7ef832079 \
--hash=sha256:4d900d1d54bddf908f9cf6693e220ceb1bfe2ec99204591c88a566b8da526f02 \
--hash=sha256:4dba1f0c6605855c2e10c7ead3b86cc7d01761c0dcdc2bd5ad2387abb00a2722 \
--hash=sha256:5a529e58838cbfa8aaed5f8ebf554b476ab6a8667036e11e0cc534d983e3ffdf \
--hash=sha256:62f3c1afaf639c60a42034afaa9acd922895b1796d86d48cd8be67557966de39 \
--hash=sha256:77cebd6077cb8fc63af238f3415e5e70b03ea0b0ae333d7c6271b039bf667a62 \
--hash=sha256:7e9f334f6d18b1befaa6f8f4b9d6f1998ee31b0e27609de83c4e2d0155dd5f46 \
--hash=sha256:84ef119b9e5b1d8196e4fac641610fec5684c4c29326349f54bd6b0f823f4042 \
--hash=sha256:865dff155225067a12b0efe137fddc6256346499f4902b449318df8b10ac1f46 \
--hash=sha256:9230779d64350859400cd19846f3627c8b373b76e9d1a05e70a8d9a1c24e5211 \
--hash=sha256:939c69efbe0d3403370a089deccfff84ca5124487fced265f9cd8c6e001e487d \
--hash=sha256:a34c2bc3e26ad7172976f66c71a43516fd80b5d2dfb4530597c4e55e59c7a3bc \
--hash=sha256:a893ebe5729ded01c625f83da5ca04f254d86fdf98caaaf090cc11d1a197a740 \
--hash=sha256:a9697c7e72f72157b668ea9f7d7ed2fcacadc8fa5de13055ae067c3f96352b40 \
--hash=sha256:c5d0d19ae774ab50106b6ce4afceb6695a8711825f387bb6807daa76bd1ce6e8 \
--hash=sha256:cc23c8324a3322158ee73bad2afbd12b953208a5d654218149491559fd433435 \
--hash=sha256:cf7649b74a157b3bf83fdd91fc54c688da4f1a8642c7b5270f2dcc9fc715fdd1 \
--hash=sha256:d26d916cccb1e06b81e8ee84a002534d6643263622377116b69c6e3ca75445b4 \
--hash=sha256:d3c6975db0bdc9c51df032d6fd8251576145c22235b8a873b1eb4b4c10c84b5e \
--hash=sha256:d86c2c9c3d9f6f7def874623f3bfce3288bf6a8d43ddb86abcf6607b9018251c \
--hash=sha256:da75688feac06214a3e2735ef2edfaf20abb9dccadf65ca3d68a4902ebf36cf2 \
--hash=sha256:e988944a10820c70d6e30a6efe298adc974f471dca101539c66f80d50310a7a8 \
--hash=sha256:f017a4c89ca4a8c7ff912970113c3a0ec63eb49aa04c4a8b90ffaf12556917c6 \
--hash=sha256:f90726086d7c53299d749907be9fc4c3d1c37200d7fd185de6d218d6d939bf5d
# via -r requirements.in
fonttools==4.63.0 \
--hash=sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69 \
--hash=sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c \
Expand Down Expand Up @@ -953,6 +1019,7 @@ numpy==2.4.6 \
# via
# -r requirements.in
# contourpy
# fimage-python
# matplotlib
# shapely
# tifffile
Expand Down
Loading
Loading