From 7d0efd73df4f5ce4064e7d10fc0e1b8dab38c9ed Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Tue, 29 Oct 2024 12:01:17 -0700 Subject: [PATCH 01/24] setuptools 69.3+ normalizes sdist name to openslide_python Current versions of setuptools normalize the filename of the source tarball and of its toplevel directory to openslide_python-* per PEP 625. This is an intentional ecosystem-wide change, so adapt to it rather than trying to undo it. Signed-off-by: Benjamin Gilbert --- .github/workflows/python.yml | 8 ++++---- CHANGELOG.md | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 307b61ab..3adaf2eb 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -283,14 +283,14 @@ jobs: run: | version=$(echo "${{ github.ref_name }}" | sed "s/^v//") # recompress tarball with xz - gunzip -k "${{ needs.pre-commit.outputs.dist-base }}/openslide-python-${version}.tar.gz" - tar xf "${{ needs.pre-commit.outputs.dist-base }}/openslide-python-${version}.tar" - xz -9 "${{ needs.pre-commit.outputs.dist-base }}/openslide-python-${version}.tar" + gunzip -k "${{ needs.pre-commit.outputs.dist-base }}/openslide_python-${version}.tar.gz" + tar xf "${{ needs.pre-commit.outputs.dist-base }}/openslide_python-${version}.tar" + xz -9 "${{ needs.pre-commit.outputs.dist-base }}/openslide_python-${version}.tar" # extract changelog awk -e '/^## / && ok {exit}' \ -e '/^## / {ok=1; next}' \ -e 'ok {print}' \ - "openslide-python-$version/CHANGELOG.md" > changes + "openslide_python-$version/CHANGELOG.md" > changes gh release create --latest --verify-tag \ --repo "${{ github.repository }}" \ --title "OpenSlide Python $version" \ diff --git a/CHANGELOG.md b/CHANGELOG.md index fcd632ac..f348fce6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Changes * Drop wheel for 32-bit Windows +* Rename source distribution to `openslide_python` per [PEP 625][] * Require `AbstractSlide` subclasses to implement all abstract methods * Provide default `AbstractSlide.set_cache()` implementation * Switch to [PEP 621][] project metadata @@ -29,6 +30,7 @@ * docs: Fix types of properties that return tuples of items [installed from PyPI]: https://pypi.org/project/openslide-bin/ +[PEP 625]: https://peps.python.org/pep-0625/ [PEP 621]: https://peps.python.org/pep-0621/ [Python filesystem encoding]: https://docs.python.org/3/glossary.html#term-filesystem-encoding-and-error-handler From d7c0f0ce64a8f568cb031608fe0e63a0ebdc31ff Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Tue, 29 Oct 2024 14:21:28 -0700 Subject: [PATCH 02/24] templates: release checklist updates The GitHub release will have multiple source tarballs. We should retile the demo site after a release because the OpenSlide Python version is listed on the site. EPEL packaging might skip some OpenSlide Python releases due to the EPEL update policy. Signed-off-by: Benjamin Gilbert --- .github/ISSUE_TEMPLATE/release.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md index 08ead1d9..c4d29a15 100644 --- a/.github/ISSUE_TEMPLATE/release.md +++ b/.github/ISSUE_TEMPLATE/release.md @@ -6,11 +6,12 @@ - [ ] Once the build finishes, approve deployment to PyPI - [ ] Download the docs artifact - [ ] Verify that the workflow created a [PyPI release](https://pypi.org/p/openslide-python) with a description, source tarball, and wheels -- [ ] Verify that the workflow created a [GitHub release](https://github.com/openslide/openslide-python/releases) with release notes, a source tarball, and wheels +- [ ] Verify that the workflow created a [GitHub release](https://github.com/openslide/openslide-python/releases) with release notes, source tarballs, and wheels - [ ] `cd` into website checkout; `rm -r api/python && unzip /path/to/downloaded/openslide-python-docs.zip && mv openslide-python-docs-* api/python` - [ ] Update website: `_data/releases.yaml`, `_includes/news.md` +- [ ] Start a [CI build](https://github.com/openslide/openslide.github.io/actions/workflows/retile.yml) of the demo site - [ ] Update Ubuntu PPA -- [ ] Update Fedora and EPEL packages +- [ ] Update Fedora and possibly EPEL packages - [ ] Check that [Copr package](https://copr.fedorainfracloud.org/coprs/g/openslide/openslide/builds/) built successfully - [ ] Send mail to -announce and -users - [ ] Post to [forum.image.sc](https://forum.image.sc/c/announcements/10) From 5994c37a87c6262f3827da92f64dbe2daddca554 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Tue, 29 Oct 2024 15:39:25 -0700 Subject: [PATCH 03/24] Fix configuration of package data When setuptools 61+ is configured via pyproject.toml, it defaults to include-package-data = true, rather than the historical default of false. We don't want wheels to include random files from the package directory, in particular _convert.c. Disable include-package-data. In addition, we were inadvertently relying on setuptools 69+ experimental functionality to include .pyi and py.typed files in the sdist and .pyi files in wheels. Explicitly configure this. Move py.typed package-data declaration from setup.py to pyproject.toml. Fixes: b893ba288c25 ("Switch to PEP 621 project metadata") Fixes: a40175e028b5 ("_convert: add type hints") Fixes: 5f49c0173cd8 ("Export type hints from openslide package") Signed-off-by: Benjamin Gilbert --- MANIFEST.in | 1 + pyproject.toml | 4 ++++ setup.py | 3 --- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 2aa1e1d6..6ac08ff1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include *.md +global-include py.typed *.pyi recursive-include doc *.py *.rst recursive-include examples *.html *.js *.png *.py recursive-include tests *.dcm *.png *.py *.svs *.tiff diff --git a/pyproject.toml b/pyproject.toml index 6bb05bb2..6394f6fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,11 +38,15 @@ Documentation = "https://openslide.org/api/python/" Repository = "https://github.com/openslide/openslide-python" [tool.setuptools] +include-package-data = false packages = ["openslide"] [tool.setuptools.dynamic] version = {attr = "openslide._version.__version__"} +[tool.setuptools.package-data] +openslide = ["py.typed", "*.pyi"] + [tool.black] skip-string-normalization = true target-version = ["py38", "py39", "py310", "py311", "py312", "py313"] diff --git a/setup.py b/setup.py index 4a5e532c..a4702e72 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,4 @@ # tag wheel for Limited API 'bdist_wheel': {'py_limited_api': 'cp311'} if _abi3 else {}, }, - package_data={ - 'openslide': ['py.typed'], - }, ) From c885d443d9d06d8f9a8e15cbc303c097f497ed22 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Tue, 29 Oct 2024 17:08:47 -0700 Subject: [PATCH 04/24] pre-commit: update versions Signed-off-by: Benjamin Gilbert --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7d43bcbd..9affb182 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade - rev: v3.18.0 + rev: v3.19.0 hooks: - id: pyupgrade name: Modernize python code @@ -35,7 +35,7 @@ repos: name: Format python code with black - repo: https://github.com/asottile/blacken-docs - rev: 1.19.0 + rev: 1.19.1 hooks: - id: blacken-docs name: Format python code in documentation @@ -54,7 +54,7 @@ repos: additional_dependencies: [flake8-bugbear, Flake8-pyproject] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.12.0 + rev: v1.13.0 hooks: - id: mypy name: Check Python types From e3d30a49b7d5595f6be9acf6f860081289d2b60c Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Wed, 30 Oct 2024 11:08:36 -0700 Subject: [PATCH 05/24] examples/deepzoom: Work around pathlib incompat in old Jinja2 FileSystemLoader in Jinja2 < 2.11.0 (including on Ubuntu 20.04) fails to wrap a single Path argument in a list because it thinks it's already a sequence. Work around this. Signed-off-by: Benjamin Gilbert --- examples/deepzoom/deepzoom_tile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/deepzoom/deepzoom_tile.py b/examples/deepzoom/deepzoom_tile.py index 6f52324d..b637095b 100755 --- a/examples/deepzoom/deepzoom_tile.py +++ b/examples/deepzoom/deepzoom_tile.py @@ -335,7 +335,7 @@ def _write_html(self) -> None: # We're not running from a module (e.g. "python deepzoom_tile.py") # so PackageLoader('__main__') doesn't work in jinja2 3.x. # Load templates directly from the filesystem. - loader = jinja2.FileSystemLoader(Path(__file__).parent / 'templates') + loader = jinja2.FileSystemLoader([Path(__file__).parent / 'templates']) env = jinja2.Environment(loader=loader, autoescape=True) template = env.get_template('slide-multipane.html') associated_urls = {n: self._url_for(n) for n in self._slide.associated_images} From 43a34553882f21cce59c46781c0e044d452b32ec Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Wed, 30 Oct 2024 10:44:46 -0700 Subject: [PATCH 06/24] Fix `setup.py install` with old setuptools We still need to support building distro packages with older setuptools that doesn't understand PEP 621. Re-add enough setup.py configuration (duplicating pyproject.toml) to make older setuptools happy. With setuptools >= 62.3.0, `setup.py install` will now warn about duplicate specification of dependencies, but the warning is harmless: SetuptoolsWarning: `install_requires` overwritten in `pyproject.toml` (dependencies) Signed-off-by: Benjamin Gilbert --- .github/workflows/python.yml | 19 +++++++++++++++++++ setup.py | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 3adaf2eb..01274db6 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -231,6 +231,25 @@ jobs: path: artifacts/whl compression-level: 0 + setuptools: + name: Setuptools install + needs: pre-commit + runs-on: ubuntu-20.04 + steps: + - name: Check out repo + uses: actions/checkout@v4 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libopenslide0 python3-pil + pip install pytest + - name: Install OpenSlide Python + run: sudo python setup.py install + - name: Run tests + run: pytest -v + - name: Tile slide + run: python examples/deepzoom/deepzoom_tile.py --viewer -o tiled tests/fixtures/small.svs + docs: name: Docs needs: pre-commit diff --git a/setup.py b/setup.py index a4702e72..68147386 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,12 @@ +from pathlib import Path import sys from setuptools import Extension, setup +# Load version string +with open(Path(__file__).parent / 'openslide/_version.py') as _fh: + exec(_fh.read()) # instantiates __version__ + # use the Limited API on Python 3.11+; build release-specific wheels on # older Python _abi3 = sys.version_info >= (3, 11) @@ -21,4 +26,18 @@ # tag wheel for Limited API 'bdist_wheel': {'py_limited_api': 'cp311'} if _abi3 else {}, }, + # + # setuptools < 61 compatibility for distro packages building from source + name='openslide-python', + version=__version__, # type: ignore[name-defined] # noqa: F821 + install_requires=[ + 'Pillow', + ], + packages=[ + 'openslide', + ], + package_data={ + 'openslide': ['py.typed', '*.pyi'], + }, + zip_safe=False, ) From 52a281f64bf7b0b1ba4b61150bb1cb294177ee60 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Wed, 30 Oct 2024 12:42:46 -0700 Subject: [PATCH 07/24] Update for release Signed-off-by: Benjamin Gilbert --- CHANGELOG.md | 8 ++++++++ openslide/_version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f348fce6..18cd5dd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Notable Changes in OpenSlide Python +## Version 1.4.1, 2024-10-30 + +### Bug fixes + +* Fix `setup.py install` with old setuptools (1.4.0 regression) +* examples: Fix `deepzoom_tile.py -r` with Jinja \< 2.11.0 (1.4.0 regression) + + ## Version 1.4.0, 2024-10-29 ### New features diff --git a/openslide/_version.py b/openslide/_version.py index ad0dab29..7e95329b 100644 --- a/openslide/_version.py +++ b/openslide/_version.py @@ -22,4 +22,4 @@ This module is an implementation detail. The package version should be obtained from openslide.__version__.""" -__version__ = '1.4.0' +__version__ = '1.4.1' From e2c870c7a98ecd4ce65d93b4e25e527f3e431a4e Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Wed, 30 Oct 2024 16:13:42 -0700 Subject: [PATCH 08/24] workflows: omit *.publish.attestation files from release artifacts gh-action-pypi-publish now writes attestation files to the artifact directory that should not be uploaded to the GitHub release. Add explicit wildcards for the file extensions we do want to upload. Signed-off-by: Benjamin Gilbert --- .github/workflows/python.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 01274db6..96ffb069 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -310,9 +310,11 @@ jobs: -e '/^## / {ok=1; next}' \ -e 'ok {print}' \ "openslide_python-$version/CHANGELOG.md" > changes + # create release; upload artifacts but not *.publish.attestation + # files created by gh-action-pypi-publish gh release create --latest --verify-tag \ --repo "${{ github.repository }}" \ --title "OpenSlide Python $version" \ --notes-file changes \ "${{ github.ref_name }}" \ - "${{ needs.pre-commit.outputs.dist-base }}/"* + "${{ needs.pre-commit.outputs.dist-base }}/"*.{tar.gz,tar.xz,whl} From dd707709d15de5aa496b197e5b96d8b4f7b72c39 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Mon, 23 Dec 2024 23:04:24 -0800 Subject: [PATCH 09/24] examples/deepzoom: update OpenSeadragon to 5.0.1 Signed-off-by: Benjamin Gilbert --- examples/deepzoom/static/openseadragon.js | 106 ++++++++++++++-------- 1 file changed, 66 insertions(+), 40 deletions(-) diff --git a/examples/deepzoom/static/openseadragon.js b/examples/deepzoom/static/openseadragon.js index dec64da0..8b887076 100644 --- a/examples/deepzoom/static/openseadragon.js +++ b/examples/deepzoom/static/openseadragon.js @@ -1,6 +1,6 @@ -//! openseadragon 5.0.0 -//! Built on 2024-08-14 -//! Git commit: v5.0.0-0-f28b7fc1 +//! openseadragon 5.0.1 +//! Built on 2024-12-09 +//! Git commit: v5.0.1-0-480de92d //! http://openseadragon.github.io //! License: http://openseadragon.github.io/license/ @@ -90,7 +90,7 @@ /** * @namespace OpenSeadragon - * @version openseadragon 5.0.0 + * @version openseadragon 5.0.1 * @classdesc The root namespace for OpenSeadragon. All utility methods * and classes are defined on or below this namespace. * @@ -220,7 +220,7 @@ * For complete list of modes, please @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation/ globalCompositeOperation} * * @property {Boolean} [imageSmoothingEnabled=true] - * Image smoothing for canvas rendering (only if the canvas drawer is used). Note: Ignored + * Image smoothing for rendering (only if the canvas or webgl drawer is used). Note: Ignored * by some (especially older) browsers which do not support this canvas property. * This property can be changed in {@link Viewer.DrawerBase.setImageSmoothingEnabled}. * @@ -856,10 +856,10 @@ function OpenSeadragon( options ){ * @since 1.0.0 */ $.version = { - versionStr: '5.0.0', + versionStr: '5.0.1', major: parseInt('5', 10), minor: parseInt('0', 10), - revision: parseInt('0', 10) + revision: parseInt('1', 10) }; @@ -10407,8 +10407,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, }, /** - * Update pixel density ratio, clears all tiles and triggers updates for - * all items if the ratio has changed. + * Update pixel density ratio and forces a resize operation. * @private */ _updatePixelDensityRatio: function() { @@ -10416,8 +10415,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, var currentPixelDensityRatio = $.getCurrentPixelDensityRatio(); if (previusPixelDensityRatio !== currentPixelDensityRatio) { $.pixelDensityRatio = currentPixelDensityRatio; - this.world.resetItems(); - this.forceRedraw(); + this.forceResize(); } }, @@ -12289,7 +12287,6 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /* }, setDisplayTransform: function(rule) { - setElementTransform(this.displayRegion, rule); setElementTransform(this.canvas, rule); setElementTransform(this.element, rule); }, @@ -19044,9 +19041,17 @@ $.Tile.prototype = { }; } + this.elementWrapper = document.createElement('div'); this.element = options.element; - this.element.innerHTML = "
" + this.element.innerHTML + "
"; - this.style = options.element.style; + this.elementWrapper.appendChild(this.element); + + if (this.element.id) { + this.elementWrapper.id = "overlay-wrapper-" + this.element.id; + } else { + this.elementWrapper.id = "overlay-wrapper"; + } + + this.style = this.elementWrapper.style; this._init(options); }; @@ -19113,7 +19118,7 @@ $.Tile.prototype = { * @function */ destroy: function() { - var element = this.element; + var element = this.elementWrapper; var style = this.style; if (element.parentNode) { @@ -19158,7 +19163,7 @@ $.Tile.prototype = { * @param {Element} container */ drawHTML: function(container, viewport) { - var element = this.element; + var element = this.elementWrapper; if (element.parentNode !== container) { //save the source parent for later if we need it element.prevElementParent = element.parentNode; @@ -19169,7 +19174,7 @@ $.Tile.prototype = { this.style.position = "absolute"; // this.size is used by overlays which don't get scaled in at // least one direction when this.checkResize is set to false. - this.size = $.getElementSize(element); + this.size = $.getElementSize(this.elementWrapper); } var positionAndSize = this._getOverlayPositionAndSize(viewport); var position = positionAndSize.position; @@ -19186,15 +19191,15 @@ $.Tile.prototype = { this.onDraw(position, size, this.element); } else { var style = this.style; - var innerElement = element.firstChild; - var innerStyle = innerElement.style; + var innerStyle = this.element.style; + innerStyle.display = "block"; style.left = position.x + "px"; style.top = position.y + "px"; if (this.width !== null) { - style.width = size.x + "px"; + innerStyle.width = size.x + "px"; } if (this.height !== null) { - style.height = size.y + "px"; + innerStyle.height = size.y + "px"; } var transformOriginProp = $.getCssPropertyWithVendorPrefix( 'transformOrigin'); @@ -19219,7 +19224,7 @@ $.Tile.prototype = { style[transformProp] = ""; } } - style.display = 'block'; + style.display = 'flex'; } }, @@ -19271,7 +19276,7 @@ $.Tile.prototype = { } if (this.checkResize && (this.width === null || this.height === null)) { - var eltSize = this.size = $.getElementSize(this.element); + var eltSize = this.size = $.getElementSize(this.elementWrapper); if (this.width === null) { width = eltSize.x; } @@ -21155,6 +21160,8 @@ function determineSubPixelRoundingRule(subPixelRoundingRules) { this._renderingCanvas = null; this._backupCanvasDrawer = null; + this._imageSmoothingEnabled = true; // will be updated by setImageSmoothingEnabled + // Add listeners for events that require modifying the scene or camera this._boundToTileReady = ev => this._tileReadyHandler(ev); this._boundToImageUnloaded = ev => this._imageUnloadedHandler(ev); @@ -21198,10 +21205,7 @@ function determineSubPixelRoundingRule(subPixelRoundingRules) { gl.bindRenderbuffer(gl.RENDERBUFFER, null); gl.bindFramebuffer(gl.FRAMEBUFFER, null); - let canvases = Array.from(this._TextureMap.keys()); - canvases.forEach(canvas => { - this._cleanupImageData(canvas); // deletes texture, removes from _TextureMap - }); + this._unloadTextures(); // Delete all our created resources gl.deleteBuffer(this._secondPass.bufferOutputPosition); @@ -21223,6 +21227,7 @@ function determineSubPixelRoundingRule(subPixelRoundingRules) { // unbind our event listeners from the viewer this.viewer.removeHandler("tile-ready", this._boundToTileReady); this.viewer.removeHandler("image-unloaded", this._boundToImageUnloaded); + this.viewer.removeHandler("resize", this._resizeHandler); // set our webgl context reference to null to enable garbage collection this._gl = null; @@ -21317,9 +21322,10 @@ function determineSubPixelRoundingRule(subPixelRoundingRules) { */ draw(tiledImages){ let gl = this._gl; + const bounds = this.viewport.getBoundsNoRotateWithMargins(true); let view = { - bounds: this.viewport.getBoundsNoRotate(true), - center: this.viewport.getCenter(true), + bounds: bounds, + center: new OpenSeadragon.Point(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2), rotation: this.viewport.getRotation(true) * Math.PI / 180 }; @@ -21511,7 +21517,7 @@ function determineSubPixelRoundingRule(subPixelRoundingRules) { gl.bindBuffer(gl.ARRAY_BUFFER, this._secondPass.bufferTexturePosition); gl.vertexAttribPointer(this._secondPass.aTexturePosition, 2, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, this._secondPass.bufferOutputPosition); - gl.vertexAttribPointer(this._firstPass.aOutputPosition, 2, gl.FLOAT, false, 0, 0); + gl.vertexAttribPointer(this._secondPass.aOutputPosition, 2, gl.FLOAT, false, 0, 0); // Draw the quad (two triangles) gl.drawArrays(gl.TRIANGLES, 0, 6); @@ -21547,11 +21553,15 @@ function determineSubPixelRoundingRule(subPixelRoundingRules) { // Public API required by all Drawer implementations /** - * Required by DrawerBase, but has no effect on WebGLDrawer. - * @param {Boolean} enabled + * Sets whether image smoothing is enabled or disabled + * @param {Boolean} enabled If true, uses gl.LINEAR as the TEXTURE_MIN_FILTER and TEXTURE_MAX_FILTER, otherwise gl.NEAREST. */ setImageSmoothingEnabled(enabled){ - // noop - this property does not impact WebGLDrawer + if( this._imageSmoothingEnabled !== enabled ){ + this._imageSmoothingEnabled = enabled; + this._unloadTextures(); + this.viewer.world.draw(); + } } /** @@ -21661,6 +21671,11 @@ function determineSubPixelRoundingRule(subPixelRoundingRules) { } + // private + _textureFilter(){ + return this._imageSmoothingEnabled ? this._gl.LINEAR : this._gl.NEAREST; + } + // private _setupRenderer(){ let gl = this._gl; @@ -21677,7 +21692,7 @@ function determineSubPixelRoundingRule(subPixelRoundingRules) { gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this._renderToTexture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this._renderingCanvas.width, this._renderingCanvas.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this._textureFilter()); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); @@ -21876,7 +21891,7 @@ function determineSubPixelRoundingRule(subPixelRoundingRules) { gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this._renderToTexture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this._textureFilter()); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); @@ -21902,8 +21917,7 @@ function determineSubPixelRoundingRule(subPixelRoundingRules) { this._gl = this._renderingCanvas.getContext('webgl'); - //make the additional canvas elements mirror size changes to the output canvas - this.viewer.addHandler("resize", function(){ + this._resizeHandler = function(){ if(_this._outputCanvas !== _this.viewer.drawer.canvas){ _this._outputCanvas.style.width = _this.viewer.drawer.canvas.clientWidth + 'px'; @@ -21924,7 +21938,10 @@ function determineSubPixelRoundingRule(subPixelRoundingRules) { // important - update the size of the rendering viewport! _this._resizeRenderer(); - }); + }; + + //make the additional canvas elements mirror size changes to the output canvas + this.viewer.addHandler("resize", this._resizeHandler); } // private @@ -22014,8 +22031,8 @@ function determineSubPixelRoundingRule(subPixelRoundingRules) { // Set the parameters so we can render any size image. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this._textureFilter()); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this._textureFilter()); // Upload the image into the texture. this._uploadImageData(tileContext); @@ -22039,6 +22056,14 @@ function determineSubPixelRoundingRule(subPixelRoundingRules) { }; } + // private + _unloadTextures(){ + let canvases = Array.from(this._TextureMap.keys()); + canvases.forEach(canvas => { + this._cleanupImageData(canvas); // deletes texture, removes from _TextureMap + }); + } + // private _uploadImageData(tileContext){ @@ -25101,6 +25126,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag this._clip = null; } + this._needsUpdate = true; this._needsDraw = true; /** * Raised when the TiledImage's clip is changed. From 05f29703eb194bf6500d8808299e74732a8f7c8d Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Wed, 15 Jan 2025 15:24:51 -0800 Subject: [PATCH 10/24] workflows: switch setuptools job to Ubuntu 20.04 container GitHub is deprecating the ubuntu-20.04 image: https://github.blog/changelog/2025-01-15-github-actions-ubuntu-20-runner-image-brownout-dates-and-other-breaking-changes/ Move repo checkout after dependency installation so it can use Git. Signed-off-by: Benjamin Gilbert --- .github/workflows/python.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 96ffb069..f0999253 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -234,21 +234,23 @@ jobs: setuptools: name: Setuptools install needs: pre-commit - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest + container: ubuntu:20.04 steps: - - name: Check out repo - uses: actions/checkout@v4 - name: Install dependencies run: | - sudo apt-get update - sudo apt-get install -y libopenslide0 python3-pil + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + git libopenslide0 python3-jinja2 python3-pil python3-pip pip install pytest + - name: Check out repo + uses: actions/checkout@v4 - name: Install OpenSlide Python - run: sudo python setup.py install + run: python3 setup.py install - name: Run tests run: pytest -v - name: Tile slide - run: python examples/deepzoom/deepzoom_tile.py --viewer -o tiled tests/fixtures/small.svs + run: python3 examples/deepzoom/deepzoom_tile.py --viewer -o tiled tests/fixtures/small.svs docs: name: Docs From 36ebf82764cb4f32dca790befe3636404f9291ec Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Thu, 16 Jan 2025 09:13:52 -0800 Subject: [PATCH 11/24] workflows: switch ARM jobs to publicly-available runners Drop the paid runners. https://github.blog/changelog/2025-01-16-linux-arm64-hosted-runners-now-available-for-free-in-public-repositories-public-preview/ Signed-off-by: Benjamin Gilbert --- .github/workflows/python.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index f0999253..b54d3560 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -42,7 +42,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, ubuntu-24.04-aarch64, macos-latest] + os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest] python-version: [3.8, 3.9, "3.10", "3.11", "3.12", "3.13"] openslide: [system, wheel] include: @@ -96,7 +96,7 @@ jobs: echo OS_ARCH_TAG=linux-x86_64 >> $GITHUB_ENV sudo apt-get install libopenslide0 ;; - ubuntu-24.04-aarch64) + ubuntu-24.04-arm) echo OS_ARCH_TAG=linux-aarch64 >> $GITHUB_ENV sudo apt-get install libopenslide0 ;; From f9af6e8fead6b14a33b47227913a943700f84963 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Mon, 14 Apr 2025 12:00:10 -0500 Subject: [PATCH 12/24] pre-commit: add codespell Signed-off-by: Benjamin Gilbert --- .pre-commit-config.yaml | 8 ++++++++ pyproject.toml | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9affb182..457d20b1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -67,6 +67,14 @@ repos: name: Validate reStructuredText syntax additional_dependencies: [sphinx, toml] + - repo: https://github.com/codespell-project/codespell + rev: v2.4.1 + hooks: + - id: codespell + name: Check spelling with codespell + additional_dependencies: + - tomli # Python < 3.11 + - repo: meta hooks: - id: check-hooks-apply diff --git a/pyproject.toml b/pyproject.toml index 6394f6fd..bb8eaea9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,12 @@ openslide = ["py.typed", "*.pyi"] skip-string-normalization = true target-version = ["py38", "py39", "py310", "py311", "py312", "py313"] +# Ref: https://github.com/codespell-project/codespell#using-a-config-file +[tool.codespell] +check-hidden = true +# ignore-regex = "" +# ignore-words-list = "" + # https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#flake8 # also ignore: # - E741 ambiguous variable name From 3e48604c2c3b0721ac29b6e6926b319dd26f28a7 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Mon, 14 Apr 2025 13:17:31 -0500 Subject: [PATCH 13/24] pre-commit: update versions _Func.__call__() correctly does not have a **kwargs argument. Signed-off-by: Benjamin Gilbert --- .pre-commit-config.yaml | 10 +++++----- openslide/deepzoom.py | 4 ++-- openslide/lowlevel.py | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 457d20b1..633bcbf7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,20 +16,20 @@ repos: - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade - rev: v3.19.0 + rev: v3.19.1 hooks: - id: pyupgrade name: Modernize python code args: ["--py38-plus"] - repo: https://github.com/PyCQA/isort - rev: 5.13.2 + rev: 6.0.1 hooks: - id: isort name: Reorder python imports with isort - repo: https://github.com/psf/black - rev: 24.10.0 + rev: 25.1.0 hooks: - id: black name: Format python code with black @@ -47,14 +47,14 @@ repos: additional_dependencies: [flake8-bugbear, Flake8-pyproject] - repo: https://github.com/PyCQA/flake8 - rev: 7.1.1 + rev: 7.2.0 hooks: - id: flake8 name: Lint python code with flake8 additional_dependencies: [flake8-bugbear, Flake8-pyproject] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.13.0 + rev: v1.15.0 hooks: - id: mypy name: Check Python types diff --git a/openslide/deepzoom.py b/openslide/deepzoom.py index 9d0b3ab8..e712b0e6 100644 --- a/openslide/deepzoom.py +++ b/openslide/deepzoom.py @@ -263,10 +263,10 @@ def _z_from_t(self, t: int) -> int: @staticmethod def _pairs_from_n_tuples( - tuples: tuple[tuple[int, ...], ...] + tuples: tuple[tuple[int, ...], ...], ) -> tuple[tuple[int, int], ...]: def all_pairs( - tuples: tuple[tuple[int, ...], ...] + tuples: tuple[tuple[int, ...], ...], ) -> TypeGuard[tuple[tuple[int, int], ...]]: return all(len(t) == 2 for t in tuples) diff --git a/openslide/lowlevel.py b/openslide/lowlevel.py index 11ccc575..9aab42b8 100644 --- a/openslide/lowlevel.py +++ b/openslide/lowlevel.py @@ -322,7 +322,7 @@ def __call__(self, *_args: Any) -> Any: class _Func(Protocol[_P, _T]): available: bool - def __call__(self, *args: _P.args) -> _T: ... + def __call__(self, *args: _P.args) -> _T: ... # type: ignore[valid-type] class _CTypesFunc(_Func[_P, _T]): restype: type | None @@ -358,7 +358,7 @@ def _func( def _wraps_funcs( - wrapped: list[_Func[..., Any]] + wrapped: list[_Func[..., Any]], ) -> Callable[[Callable[_P, _T]], _Func[_P, _T]]: def decorator(fn: Callable[_P, _T]) -> _Func[_P, _T]: if TYPE_CHECKING: From 775f52a632faaa45fafa98a7d4660874d3fef68b Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Tue, 22 Apr 2025 01:51:07 -0500 Subject: [PATCH 14/24] workflows: switch setuptools job to Ubuntu 22.04 container Ubuntu 20.04 is exiting standard security support at the end of May. Switch to 22.04, which still has an old enough setuptools. Stick with the container, rather than using GitHub's ubuntu-22.04 runner image, for better control over the included software versions. Signed-off-by: Benjamin Gilbert --- .github/workflows/python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index b54d3560..d463e80f 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -235,7 +235,7 @@ jobs: name: Setuptools install needs: pre-commit runs-on: ubuntu-latest - container: ubuntu:20.04 + container: ubuntu:22.04 steps: - name: Install dependencies run: | From e97555ba1f9993c71d1ffbfc30fd1dad4c02fa47 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Thu, 24 Apr 2025 17:08:43 -0500 Subject: [PATCH 15/24] workflows: expect macOS universal binaries from setup-python on 3.{8,9,10} It appears setup-python's Python 3.8, 3.9, and 3.10 macOS builds are now capable of producing universal binaries. Stop using the upstream Python 3.9 and 3.10 builds and remove the test exception for 3.8. The change seems to have happened before we released any macOS wheels, so this won't affect the 3.8 release artifacts. Signed-off-by: Benjamin Gilbert --- .github/workflows/python.yml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index d463e80f..3ecadf08 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -50,16 +50,6 @@ jobs: python-version: "3.13" openslide: system sdist: sdist - # Python 3.8 is too old to support universal binaries, and - # setup-python's Python 3.9 and 3.10 won't build them. Use the - # last upstream patch releases that ship with installers. - # https://github.com/actions/setup-python/issues/439#issuecomment-1247646682 - - os: macos-latest - python-version: "3.9" - upstream-python: 3.9.13 - - os: macos-latest - python-version: "3.10" - upstream-python: 3.10.11 steps: - name: Check out repo uses: actions/checkout@v4 @@ -122,7 +112,7 @@ jobs: auditwheel repair --only-plat -w dist old/*whl ;; macos-*) - if [ "${{ matrix.python-version }}" != 3.8 -a ! -e dist/*universal2* ]; then + if [ ! -e dist/*universal2* ]; then echo "Wheel is not universal:" ls dist exit 1 From a9dbe638a1cd10ff1ddd9b5e13678b74e7714767 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Thu, 24 Apr 2025 19:59:06 -0500 Subject: [PATCH 16/24] examples/deepzoom: add license files for bundled JS The licenses are included or linked at the top of the JS files, but it's better to also ship them separately. Also document these licenses in the README. Signed-off-by: Benjamin Gilbert --- MANIFEST.in | 2 +- README.md | 8 +++++- examples/deepzoom/licenses/LICENSE.jquery | 20 +++++++++++++ .../deepzoom/licenses/LICENSE.openseadragon | 28 +++++++++++++++++++ .../licenses/LICENSE.openseadragon-scalebar | 9 ++++++ 5 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 examples/deepzoom/licenses/LICENSE.jquery create mode 100644 examples/deepzoom/licenses/LICENSE.openseadragon create mode 100644 examples/deepzoom/licenses/LICENSE.openseadragon-scalebar diff --git a/MANIFEST.in b/MANIFEST.in index 6ac08ff1..ea26c3f7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include *.md -global-include py.typed *.pyi +global-include LICENSE.* py.typed *.pyi recursive-include doc *.py *.rst recursive-include examples *.html *.js *.png *.py recursive-include tests *.dcm *.png *.py *.svs *.tiff diff --git a/README.md b/README.md index b1ec2667..a675dead 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,13 @@ installing so OpenSlide Python can find OpenSlide. ## License OpenSlide Python is released under the terms of the [GNU Lesser General -Public License, version 2.1](https://openslide.org/license/). +Public License, version 2.1](https://openslide.org/license/). The Deep Zoom +example code includes JavaScript released under the +[BSD license](https://github.com/openslide/openslide-python/tree/main/examples/deepzoom/licenses/LICENSE.openseadragon), +the +[MIT license](https://github.com/openslide/openslide-python/tree/main/examples/deepzoom/licenses/LICENSE.jquery), +and released into the +[public domain](https://github.com/openslide/openslide-python/tree/main/examples/deepzoom/licenses/LICENSE.openseadragon-scalebar). OpenSlide Python is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY diff --git a/examples/deepzoom/licenses/LICENSE.jquery b/examples/deepzoom/licenses/LICENSE.jquery new file mode 100644 index 00000000..f642c3f7 --- /dev/null +++ b/examples/deepzoom/licenses/LICENSE.jquery @@ -0,0 +1,20 @@ +Copyright OpenJS Foundation and other contributors, https://openjsf.org/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/examples/deepzoom/licenses/LICENSE.openseadragon b/examples/deepzoom/licenses/LICENSE.openseadragon new file mode 100644 index 00000000..247d11af --- /dev/null +++ b/examples/deepzoom/licenses/LICENSE.openseadragon @@ -0,0 +1,28 @@ +Copyright (C) 2009 CodePlex Foundation +Copyright (C) 2010-2024 OpenSeadragon contributors + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of CodePlex Foundation nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/examples/deepzoom/licenses/LICENSE.openseadragon-scalebar b/examples/deepzoom/licenses/LICENSE.openseadragon-scalebar new file mode 100644 index 00000000..1625fc0e --- /dev/null +++ b/examples/deepzoom/licenses/LICENSE.openseadragon-scalebar @@ -0,0 +1,9 @@ +This software was developed at the National Institute of Standards and +Technology by employees of the Federal Government in the course of +their official duties. Pursuant to title 17 Section 105 of the United +States Code this software is not subject to copyright protection and is +in the public domain. This software is an experimental system. NIST assumes +no responsibility whatsoever for its use by other parties, and makes no +guarantees, expressed or implied, about its quality, reliability, or +any other characteristic. We would appreciate acknowledgement if the +software is used. From 50dd399b0248ff708e00310a5a283c1c521ce59b Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Thu, 24 Apr 2025 14:33:54 -0500 Subject: [PATCH 17/24] Drop support for Python 3.8 It's EOL. Ubuntu 20.04 is the last officially-supported distro that uses it, and Ubuntu 20.04 will exit standard security maintenance on May 31. Signed-off-by: Benjamin Gilbert --- .github/workflows/python.yml | 4 ++-- .pre-commit-config.yaml | 2 +- README.md | 2 +- examples/deepzoom/deepzoom_server.py | 4 ++-- openslide/__init__.py | 3 ++- pyproject.toml | 5 ++--- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 3ecadf08..6c60d9f7 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -43,7 +43,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest] - python-version: [3.8, 3.9, "3.10", "3.11", "3.12", "3.13"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] openslide: [system, wheel] include: - os: ubuntu-latest @@ -158,7 +158,7 @@ jobs: shell: bash strategy: matrix: - python-version: [3.8, 3.9, "3.10", "3.11", "3.12", "3.13"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] openslide: [zip, wheel] steps: - name: Check out repo diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 633bcbf7..e01c82ae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: hooks: - id: pyupgrade name: Modernize python code - args: ["--py38-plus"] + args: ["--py39-plus"] - repo: https://github.com/PyCQA/isort rev: 6.0.1 diff --git a/README.md b/README.md index a675dead..50f426d9 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ OpenSlide can read virtual slides in several formats: ## Requirements -* Python ≥ 3.8 +* Python ≥ 3.9 * OpenSlide ≥ 3.4.0 * Pillow diff --git a/examples/deepzoom/deepzoom_server.py b/examples/deepzoom/deepzoom_server.py index fb9efbf1..cc051cad 100755 --- a/examples/deepzoom/deepzoom_server.py +++ b/examples/deepzoom/deepzoom_server.py @@ -23,12 +23,12 @@ from argparse import ArgumentParser import base64 -from collections.abc import Callable +from collections.abc import Callable, Mapping from io import BytesIO import os from pathlib import Path import re -from typing import TYPE_CHECKING, Any, Literal, Mapping +from typing import TYPE_CHECKING, Any, Literal from unicodedata import normalize import zlib diff --git a/openslide/__init__.py b/openslide/__init__.py index 39b83fa9..1db83c2e 100644 --- a/openslide/__init__.py +++ b/openslide/__init__.py @@ -26,9 +26,10 @@ from __future__ import annotations from abc import ABCMeta, abstractmethod +from collections.abc import Iterator, Mapping from io import BytesIO from types import TracebackType -from typing import Iterator, Literal, Mapping, TypeVar +from typing import Literal, TypeVar from PIL import Image, ImageCms diff --git a/pyproject.toml b/pyproject.toml index bb8eaea9..4969715b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,6 @@ classifiers = [ "Operating System :: POSIX :: Linux", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -27,7 +26,7 @@ classifiers = [ "Topic :: Scientific/Engineering :: Bio-Informatics", "Typing :: Typed", ] -requires-python = ">= 3.8" +requires-python = ">= 3.9" dependencies = ["Pillow"] dynamic = ["version"] @@ -49,7 +48,7 @@ openslide = ["py.typed", "*.pyi"] [tool.black] skip-string-normalization = true -target-version = ["py38", "py39", "py310", "py311", "py312", "py313"] +target-version = ["py39", "py310", "py311", "py312", "py313"] # Ref: https://github.com/codespell-project/codespell#using-a-config-file [tool.codespell] From a107b17958c12fa77e72f74d4ac020bd5469abe3 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sat, 26 Apr 2025 06:24:28 -0500 Subject: [PATCH 18/24] Sync LGPLv2.1 text with FSF; remove FSF mailing address Signed-off-by: Benjamin Gilbert --- COPYING.LESSER | 9 ++++----- doc/jekyll_fix.py | 3 +-- examples/deepzoom/deepzoom_multiserver.py | 3 +-- examples/deepzoom/deepzoom_server.py | 3 +-- examples/deepzoom/deepzoom_tile.py | 3 +-- openslide/__init__.py | 3 +-- openslide/_convert.c | 3 +-- openslide/_convert.pyi | 3 +-- openslide/_version.py | 3 +-- openslide/deepzoom.py | 3 +-- openslide/lowlevel.py | 3 +-- tests/common.py | 3 +-- tests/test_base.py | 3 +-- tests/test_deepzoom.py | 3 +-- tests/test_imageslide.py | 3 +-- tests/test_openslide.py | 3 +-- 16 files changed, 19 insertions(+), 35 deletions(-) diff --git a/COPYING.LESSER b/COPYING.LESSER index 4362b491..f6683e74 100644 --- a/COPYING.LESSER +++ b/COPYING.LESSER @@ -2,7 +2,7 @@ Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -484,8 +484,7 @@ convey the exclusion of warranty; and each file should have at least the Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + License along with this library; if not, see . Also add information on how to contact you by electronic and paper mail. @@ -496,7 +495,7 @@ necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. - , 1 April 1990 - Ty Coon, President of Vice + , 1 April 1990 + Moe Ghoul, President of Vice That's all there is to it! diff --git a/doc/jekyll_fix.py b/doc/jekyll_fix.py index ef6555c5..d7fe8bf2 100644 --- a/doc/jekyll_fix.py +++ b/doc/jekyll_fix.py @@ -14,8 +14,7 @@ # License for more details. # # You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# along with this library. If not, see . # # Sphinx hardcodes that certain output paths have names starting with diff --git a/examples/deepzoom/deepzoom_multiserver.py b/examples/deepzoom/deepzoom_multiserver.py index 8142b6fe..0f43680e 100755 --- a/examples/deepzoom/deepzoom_multiserver.py +++ b/examples/deepzoom/deepzoom_multiserver.py @@ -15,8 +15,7 @@ # License for more details. # # You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# along with this library. If not, see . # from __future__ import annotations diff --git a/examples/deepzoom/deepzoom_server.py b/examples/deepzoom/deepzoom_server.py index fb9efbf1..9b30f207 100755 --- a/examples/deepzoom/deepzoom_server.py +++ b/examples/deepzoom/deepzoom_server.py @@ -15,8 +15,7 @@ # License for more details. # # You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# along with this library. If not, see . # from __future__ import annotations diff --git a/examples/deepzoom/deepzoom_tile.py b/examples/deepzoom/deepzoom_tile.py index b637095b..65b75b3e 100755 --- a/examples/deepzoom/deepzoom_tile.py +++ b/examples/deepzoom/deepzoom_tile.py @@ -15,8 +15,7 @@ # License for more details. # # You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# along with this library. If not, see . # """An example program to generate a Deep Zoom directory tree from a slide.""" diff --git a/openslide/__init__.py b/openslide/__init__.py index 39b83fa9..d743780b 100644 --- a/openslide/__init__.py +++ b/openslide/__init__.py @@ -14,8 +14,7 @@ # License for more details. # # You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# along with this library. If not, see . # """A library for reading whole-slide images. diff --git a/openslide/_convert.c b/openslide/_convert.c index 6b0bf838..37d6e75a 100644 --- a/openslide/_convert.c +++ b/openslide/_convert.c @@ -13,8 +13,7 @@ * License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with this library; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * along with this library. If not, see . */ #include diff --git a/openslide/_convert.pyi b/openslide/_convert.pyi index c68c9781..03bf743b 100644 --- a/openslide/_convert.pyi +++ b/openslide/_convert.pyi @@ -13,8 +13,7 @@ # License for more details. # # You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# along with this library. If not, see . # from typing import Protocol diff --git a/openslide/_version.py b/openslide/_version.py index 7e95329b..9d37969a 100644 --- a/openslide/_version.py +++ b/openslide/_version.py @@ -13,8 +13,7 @@ # License for more details. # # You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# along with this library. If not, see . # """The openslide package version. diff --git a/openslide/deepzoom.py b/openslide/deepzoom.py index e712b0e6..fe690b44 100644 --- a/openslide/deepzoom.py +++ b/openslide/deepzoom.py @@ -13,8 +13,7 @@ # License for more details. # # You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# along with this library. If not, see . # """Support for Deep Zoom images. diff --git a/openslide/lowlevel.py b/openslide/lowlevel.py index 9aab42b8..6608033d 100644 --- a/openslide/lowlevel.py +++ b/openslide/lowlevel.py @@ -14,8 +14,7 @@ # License for more details. # # You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# along with this library. If not, see . # """ diff --git a/tests/common.py b/tests/common.py index ca034195..aaa86f7f 100644 --- a/tests/common.py +++ b/tests/common.py @@ -13,8 +13,7 @@ # License for more details. # # You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# along with this library. If not, see . # from __future__ import annotations diff --git a/tests/test_base.py b/tests/test_base.py index 2ed9116e..9b3b3867 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -13,8 +13,7 @@ # License for more details. # # You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# along with this library. If not, see . # from __future__ import annotations diff --git a/tests/test_deepzoom.py b/tests/test_deepzoom.py index 1c67ab06..33b98688 100644 --- a/tests/test_deepzoom.py +++ b/tests/test_deepzoom.py @@ -13,8 +13,7 @@ # License for more details. # # You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# along with this library. If not, see . # from __future__ import annotations diff --git a/tests/test_imageslide.py b/tests/test_imageslide.py index 051263ee..dd3fe6f9 100644 --- a/tests/test_imageslide.py +++ b/tests/test_imageslide.py @@ -13,8 +13,7 @@ # License for more details. # # You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# along with this library. If not, see . # from __future__ import annotations diff --git a/tests/test_openslide.py b/tests/test_openslide.py index 2be656fd..26077efe 100644 --- a/tests/test_openslide.py +++ b/tests/test_openslide.py @@ -13,8 +13,7 @@ # License for more details. # # You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# along with this library. If not, see . # from __future__ import annotations From ad386c884d267b44785e256c181cf45dc74ac05d Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sat, 26 Apr 2025 05:39:59 -0500 Subject: [PATCH 19/24] pyproject: add dependency group for test deps Add a PEP 735 dependency group that installs a supported version of pytest. This allows downstream build systems (e.g. Fedora) to autodetect test dependencies. pip 25.1 allows installing these dependencies with `pip install --group test`. Signed-off-by: Benjamin Gilbert --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 4969715b..fe649f64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,9 @@ Documentation = "https://openslide.org/api/python/" "Release notes" = "https://github.com/openslide/openslide-python/blob/main/CHANGELOG.md" Repository = "https://github.com/openslide/openslide-python" +[dependency-groups] +test = ["pytest >= 7"] + [tool.setuptools] include-package-data = false packages = ["openslide"] From 43049b6e5dc5d567472247ff665c53936c5e331c Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Fri, 25 Apr 2025 23:44:20 -0500 Subject: [PATCH 20/24] pyproject: switch to PEP 639 license metadata Conform with PEP 639 by adding links to our license files, using a SPDX license expression to declare our licenses, and dropping the deprecated Trove license classifier. Without this change, setuptools shows a large deprecation warning saying that support for the old project.license syntax will be removed on 2026-Feb-18. However, the new syntax requires setuptools >= 77, so this change breaks compatibility with setuptools 61-76. Users installing from source will be unaffected because the build frontend automatically updates setuptools if required, but Linux distro packaging disallows such updates, so this change will break package builds for many still-supported distro releases. To build with setuptools 61 through 76, distros will need to patch out the project.license field and reduce the build-system.requires dependency. Signed-off-by: Benjamin Gilbert --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fe649f64..a76799db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,14 +5,14 @@ maintainers = [ ] description = "Python interface to OpenSlide" readme = "README.md" -license = {text = "GNU Lesser General Public License, version 2.1"} +license = "LGPL-2.1-only AND BSD-3-Clause AND MIT AND LicenseRef-Public-Domain" +license-files = ["COPYING.LESSER", "**/LICENSE.*"] keywords = ["OpenSlide", "whole-slide image", "virtual slide", "library"] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: Healthcare Industry", "Intended Audience :: Science/Research", - "License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", @@ -87,5 +87,5 @@ pythonpath = "tests" ignore_messages = "(Hyperlink target \".*\" is not referenced\\.$)" [build-system] -requires = ["setuptools >= 61.0.0"] +requires = ["setuptools >= 77.0.0"] build-backend = "setuptools.build_meta" From fe11899da72b72264ebc81707e14e0bd6ca9d89d Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Mon, 28 Apr 2025 21:57:16 -0500 Subject: [PATCH 21/24] OpenSlide Python 1.4.2 Signed-off-by: Benjamin Gilbert --- CHANGELOG.md | 14 ++++++++++++++ openslide/_version.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18cd5dd1..fae3f137 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Notable Changes in OpenSlide Python +## Version 1.4.2, 2025-04-28 + +### Changes + +* Drop support for Python 3.8 +* Switch to [PEP 639][] project license metadata +* examples: Update OpenSeadragon to 5.0.1 +* examples: Add license files for bundled JavaScript +* tests: Add [PEP 735][] dependency group for test dependencies + +[PEP 639]: https://peps.python.org/pep-0639/ +[PEP 735]: https://peps.python.org/pep-0735/ + + ## Version 1.4.1, 2024-10-30 ### Bug fixes diff --git a/openslide/_version.py b/openslide/_version.py index 9d37969a..21324249 100644 --- a/openslide/_version.py +++ b/openslide/_version.py @@ -21,4 +21,4 @@ This module is an implementation detail. The package version should be obtained from openslide.__version__.""" -__version__ = '1.4.1' +__version__ = '1.4.2' From 0501e5f16fe258851a4ac8bf42c271b5d021e634 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sun, 29 Jun 2025 13:41:12 -0700 Subject: [PATCH 22/24] convert: name structs consistently Signed-off-by: Benjamin Gilbert --- openslide/_convert.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openslide/_convert.c b/openslide/_convert.c index 37d6e75a..efec39c8 100644 --- a/openslide/_convert.c +++ b/openslide/_convert.c @@ -96,22 +96,22 @@ _convert_argb2rgba(PyObject *self, PyObject *args) return ret; } -static PyMethodDef ConvertMethods[] = { +static PyMethodDef _convert_methods[] = { {"argb2rgba", _convert_argb2rgba, METH_VARARGS, "Convert aRGB to RGBA in place."}, {NULL, NULL, 0, NULL} }; -static struct PyModuleDef convertmodule = { +static struct PyModuleDef _convert_module = { PyModuleDef_HEAD_INIT, "_convert", NULL, 0, - ConvertMethods + _convert_methods, }; PyMODINIT_FUNC PyInit__convert(void) { - return PyModule_Create2(&convertmodule, PYTHON_API_VERSION); + return PyModule_Create2(&_convert_module, PYTHON_API_VERSION); } From 442f91ba0b30a2682711be7235efe6d2f64b26d9 Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sun, 29 Jun 2025 13:47:42 -0700 Subject: [PATCH 23/24] convert: switch to multi-phase module initialization Single-phase initialization is considered legacy. Signed-off-by: Benjamin Gilbert --- openslide/_convert.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openslide/_convert.c b/openslide/_convert.c index efec39c8..b9422f07 100644 --- a/openslide/_convert.c +++ b/openslide/_convert.c @@ -102,16 +102,21 @@ static PyMethodDef _convert_methods[] = { {NULL, NULL, 0, NULL} }; +static PyModuleDef_Slot _convert_slots[] = { + {0, NULL} +}; + static struct PyModuleDef _convert_module = { PyModuleDef_HEAD_INIT, "_convert", NULL, 0, _convert_methods, + _convert_slots, }; PyMODINIT_FUNC PyInit__convert(void) { - return PyModule_Create2(&_convert_module, PYTHON_API_VERSION); + return PyModuleDef_Init(&_convert_module); } From 174814ad74762dce1790d4a700ca164b08bf0f1e Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sat, 19 Jul 2025 18:06:41 -0700 Subject: [PATCH 24/24] workflows: add tests on Python 3.14 dev release Signed-off-by: Benjamin Gilbert --- .github/workflows/python.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 6c60d9f7..64887e34 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -43,7 +43,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest] - python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13", "3.14-dev"] openslide: [system, wheel] include: - os: ubuntu-latest @@ -158,7 +158,7 @@ jobs: shell: bash strategy: matrix: - python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13", "3.14-dev"] openslide: [zip, wheel] steps: - name: Check out repo