From 53a32a26e1019cd2758d9dbf72dbf453b392984b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20M=C3=A1rquez=20Neila?= Date: Mon, 25 Nov 2024 11:17:15 +0100 Subject: [PATCH 1/4] Add support for NumPy 2.0 --- .github/workflows/maxflow-ci.yml | 81 ++++-------------------- .github/workflows/maxflow-deployment.yml | 61 ++++++++++++++++++ maxflow/__init__.py | 2 +- maxflow/src/grid.h | 13 ++-- maxflow/version.py | 3 +- pyproject.toml | 2 +- requirements.txt | 1 - setup.py | 6 +- 8 files changed, 88 insertions(+), 81 deletions(-) create mode 100644 .github/workflows/maxflow-deployment.yml delete mode 100644 requirements.txt diff --git a/.github/workflows/maxflow-ci.yml b/.github/workflows/maxflow-ci.yml index a5b7fde..1cabcfc 100644 --- a/.github/workflows/maxflow-ci.yml +++ b/.github/workflows/maxflow-ci.yml @@ -1,6 +1,9 @@ -name: PyMaxflow CI +name: PyMaxflow tests -on: [push] +on: + push: + branches: [master] + pull_request: jobs: test: @@ -8,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v3 @@ -18,76 +21,20 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - python -m pip install --upgrade pip setuptools cython oldest-supported-numpy - - name: Build the package + python -m pip install --upgrade pip + - name: Install the package run: | - python setup.py build_ext --inplace - - name: Test and coverage + python -m pip install -e . + - name: Install dependencies for test run: | - python -m pip install imageio networkx python -m pip install pytest>=7.2.0 pytest-cov>=4.0 codecov + python -m pip install imageio networkx + - name: Test and coverage + run: | + mkdir output python -m pytest --cov=maxflow --cov-report=xml codecov - name: Flake8 run: | python -m pip install flake8 flake8 . - - deploy-sdist: - name: Deploy source distribution - runs-on: ubuntu-latest - needs: test - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI }} - if: github.ref == 'refs/heads/master' - - steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - name: Install cibuildwheel - run: | - python -m pip install --upgrade pip - python -m pip install build - - name: Build sdist - run: python -m build --sdist - - name: Deploy sdist - run: | - python3 -m pip install twine - python3 -m twine upload --skip-existing dist/* - - deploy-wheels: - name: Deploy wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - needs: test - env: - CIBW_ARCHS: "auto64" - CIBW_BUILD: "cp37-* cp38-* cp39-* cp310-* cp311-* pp*" - CIBW_SKIP: "*musllinux* pp*-win* pp*-macosx*" - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI }} - if: github.ref == 'refs/heads/master' - - strategy: - matrix: - os: [ubuntu-20.04, windows-2019, macOS-11] - - steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - name: Install cibuildwheel - run: | - python -m pip install --upgrade pip - python -m pip install cibuildwheel==2.11.4 - - name: Build wheels - run: python3 -m cibuildwheel --output-dir wheelhouse - - name: Deploy - run: | - python3 -m pip install twine - python3 -m twine upload --skip-existing wheelhouse/*.whl diff --git a/.github/workflows/maxflow-deployment.yml b/.github/workflows/maxflow-deployment.yml new file mode 100644 index 0000000..a96dc38 --- /dev/null +++ b/.github/workflows/maxflow-deployment.yml @@ -0,0 +1,61 @@ +name: PyMaxflow deployment + +on: + push: + tags: 'v[0-9]+*' + +jobs: + deploy-sdist: + name: Deploy source distribution + runs-on: ubuntu-latest + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI }} + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Install cibuildwheel + run: | + python -m pip install --upgrade pip + python -m pip install build + - name: Build sdist + run: python -m build --sdist + - name: Deploy sdist + run: | + python3 -m pip install twine + python3 -m twine upload --skip-existing dist/* + + deploy-wheels: + name: Deploy wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + env: + CIBW_ARCHS: "auto64" + CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-*" + CIBW_SKIP: "*musllinux* pp*-win* pp*-macosx* pp*" + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI }} + + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest, macos-13] + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + - name: Install cibuildwheel + run: | + python -m pip install --upgrade pip + python -m pip install cibuildwheel + - name: Build wheels + run: python3 -m cibuildwheel --output-dir wheelhouse + - name: Deploy + run: | + python3 -m pip install twine + python3 -m twine upload --skip-existing wheelhouse/*.whl diff --git a/maxflow/__init__.py b/maxflow/__init__.py index e4d9674..3bdd104 100644 --- a/maxflow/__init__.py +++ b/maxflow/__init__.py @@ -35,7 +35,7 @@ import numpy as np from . import _maxflow from ._maxflow import GraphInt, GraphFloat, moore_structure, vonNeumann_structure -from .version import __version__, __version_str__, __version_core__ +from .version import __version__, __version_core__ from .fastmin import aexpansion_grid, abswap_grid Graph = {int: GraphInt, float: GraphFloat} diff --git a/maxflow/src/grid.h b/maxflow/src/grid.h index b14645a..b9713d2 100644 --- a/maxflow/src/grid.h +++ b/maxflow/src/grid.h @@ -88,7 +88,7 @@ std::vector getVector(PyArrayObject* arr, int length) return std::vector(length, value); } - // add_ndim == 1 + // arr_ndim == 1 if(arr_shape[0] < length) throw std::runtime_error("the length of `periodic` must be equal to the number of dimensions of `nodeids`"); @@ -149,11 +149,11 @@ void Graph::add_grid_edges(PyArrayObject* _nodeids, } catch(std::exception& e) { - Py_DECREF(periodicArr); - Py_DECREF(structureArr); - Py_DECREF(weights); - Py_DECREF(nodeids); - throw e; + Py_DECREF(periodicArr); + Py_DECREF(structureArr); + Py_DECREF(weights); + Py_DECREF(nodeids); + throw e; } // Create the edges @@ -168,6 +168,7 @@ void Graph::add_grid_edges(PyArrayObject* _nodeids, if(iter == NULL) { + Py_DECREF(periodicArr); Py_DECREF(structureArr); Py_DECREF(weights); Py_DECREF(nodeids); diff --git a/maxflow/version.py b/maxflow/version.py index 63d9a5d..b759c6a 100644 --- a/maxflow/version.py +++ b/maxflow/version.py @@ -1,5 +1,4 @@ # -*- encoding:utf-8 -*- -__version__ = (1, 3, 0) -__version_str__ = ".".join(map(str, __version__)) +__version__ = "1.3.2" __version_core__ = (3, 0, 4) diff --git a/pyproject.toml b/pyproject.toml index 9420f4d..9b5919a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [build-system] -requires = ["setuptools", "wheel", "Cython", "oldest-supported-numpy"] +requires = ["setuptools", "wheel", "Cython", "numpy~=2.0"] build-backend = "setuptools.build_meta" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 24ce15a..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -numpy diff --git a/setup.py b/setup.py index f73d487..c0bfd3e 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from Cython.Build import cythonize # Get the version number. -__version_str__ = runpy.run_path("maxflow/version.py")["__version_str__"] +__version__ = runpy.run_path("maxflow/version.py")["__version__"] def extensions(): @@ -44,7 +44,7 @@ def extensions(): setup( name="PyMaxflow", - version=__version_str__, + version=__version__, description="A mincut/maxflow package for Python", author="Pablo Márquez Neila", author_email="pablo.marquez@unibe.ch", @@ -82,5 +82,5 @@ def extensions(): ], packages=["maxflow"], ext_modules=extensions(), - install_requires=['numpy'] + install_requires=['numpy>=1.26'] ) From 81e46658187d3a6e17e0161c63d727c2a08f3b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20M=C3=A1rquez=20Neila?= Date: Mon, 25 Nov 2024 11:21:11 +0100 Subject: [PATCH 2/4] Change float_ to float64 --- test_maxflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_maxflow.py b/test_maxflow.py index 92ea253..89074be 100644 --- a/test_maxflow.py +++ b/test_maxflow.py @@ -172,7 +172,7 @@ def test_fastmin_edge_cases(): # Array with 0 spatial dimensions unary = np.zeros((0, 0, 3)) - binary = np.ones((3, 3), dtype=np.float_) - np.eye(3, dtype=np.float_) + binary = np.ones((3, 3), dtype=np.float64) - np.eye(3, dtype=np.float64) labels = maxflow.aexpansion_grid(unary, binary) assert labels.shape == (0, 0) From 26b864fa38362b71ca849cbe519b5f992c3c24d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20M=C3=A1rquez=20Neila?= Date: Mon, 25 Nov 2024 11:51:30 +0100 Subject: [PATCH 3/4] Change int_ to int64 --- doc/source/tutorial.rst | 2 +- examples/binary_restoration.py | 2 +- examples/layout_examples.py | 2 +- maxflow/fastmin.py | 4 ++-- maxflow/src/_maxflow.pyx | 4 ++-- test_maxflow.py | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst index 266b4c0..ee1ff24 100644 --- a/doc/source/tutorial.rst +++ b/doc/source/tutorial.rst @@ -194,7 +194,7 @@ belongs to the sink segment (i.e., the corresponding pixel has the label 0). We now get the labels for each pixel:: # The labels should be 1 where sgm is False and 0 otherwise. - img2 = np.int_(np.logical_not(sgm)) + img2 = np.int64(np.logical_not(sgm)) # Show the result. from matplotlib import pyplot as ppl ppl.imshow(img2) diff --git a/examples/binary_restoration.py b/examples/binary_restoration.py index 8dd0e35..84a2b48 100644 --- a/examples/binary_restoration.py +++ b/examples/binary_restoration.py @@ -22,7 +22,7 @@ sgm = g.get_grid_segments(nodeids) # The labels should be 1 where sgm is False and 0 otherwise. -img2 = np.int_(np.logical_not(sgm)) +img2 = np.int64(np.logical_not(sgm)) # Show the result. ppl.imshow(img2, cmap=ppl.cm.gray, interpolation='nearest') ppl.show() diff --git a/examples/layout_examples.py b/examples/layout_examples.py index 6136ffe..4fec8f5 100644 --- a/examples/layout_examples.py +++ b/examples/layout_examples.py @@ -60,7 +60,7 @@ central_node = nodeids[12] rest_of_nodes = np.hstack([nodeids[:12], nodeids[13:]]) -nodeids = np.empty((2, 24), dtype=np.int_) +nodeids = np.empty((2, 24), dtype=np.int64) nodeids[0] = central_node nodeids[1] = rest_of_nodes diff --git a/maxflow/fastmin.py b/maxflow/fastmin.py index fcef3e0..e92f70b 100644 --- a/maxflow/fastmin.py +++ b/maxflow/fastmin.py @@ -95,7 +95,7 @@ def abswap_grid(unary, binary, max_cycles=None, labels=None): if num_labels <= 127: labels = np.int8(unary.argmin(axis=-1)) else: - labels = np.int_(unary.argmin(axis=-1)) + labels = np.int64(unary.argmin(axis=-1)) else: if labels.min() < 0: raise ValueError("Values of labels must be non-negative") @@ -182,7 +182,7 @@ def aexpansion_grid(unary, binary, max_cycles=None, labels=None): if num_labels <= 127: labels = np.int8(unary.argmin(axis=-1)) else: - labels = np.int_(unary.argmin(axis=-1)) + labels = np.int64(unary.argmin(axis=-1)) else: if labels.min() < 0: raise ValueError("Values of labels must be non-negative") diff --git a/maxflow/src/_maxflow.pyx b/maxflow/src/_maxflow.pyx index 9527998..ccebd0e 100644 --- a/maxflow/src/_maxflow.pyx +++ b/maxflow/src/_maxflow.pyx @@ -161,7 +161,7 @@ cdef public class GraphInt [object PyObject_GraphInt, type GraphInt]: """ num_nodes = np.prod(shape) first = self.thisptr.add_node(int(num_nodes)) - nodes = np.arange(first, first+num_nodes, dtype=np.int_) + nodes = np.arange(first, first+num_nodes, dtype=np.int64) return np.reshape(nodes, shape) def add_edge(self, int i, int j, long capacity, long rcapacity): """ @@ -639,7 +639,7 @@ cdef public class GraphFloat [object PyObject_GraphFloat, type GraphFloat]: """ num_nodes = np.prod(shape) first = self.thisptr.add_node(int(num_nodes)) - nodes = np.arange(first, first+num_nodes, dtype=np.int_) + nodes = np.arange(first, first+num_nodes, dtype=np.int64) return np.reshape(nodes, shape) def add_edge(self, int i, int j, double capacity, double rcapacity): """ diff --git a/test_maxflow.py b/test_maxflow.py index 89074be..a851479 100644 --- a/test_maxflow.py +++ b/test_maxflow.py @@ -203,7 +203,7 @@ def test_fastmin_edge_cases(): # Shape of initial labels do not match the shape of the unary array unary = np.zeros((3, 3, 3)) binary = np.zeros((3, 3)) - labels = np.ones((4, 4), dtype=np.int_) + labels = np.ones((4, 4), dtype=np.int64) with pytest.raises(Exception): maxflow.aexpansion_grid(unary, binary, labels=labels) with pytest.raises(Exception): @@ -212,7 +212,7 @@ def test_fastmin_edge_cases(): # Initial labels contain values larger than num_labels unary = np.zeros((3, 3, 3)) binary = np.zeros((3, 3)) - labels = np.full((3, 3), 5, dtype=np.int_) + labels = np.full((3, 3), 5, dtype=np.int64) with pytest.raises(ValueError): maxflow.aexpansion_grid(unary, binary, labels=labels) with pytest.raises(ValueError): @@ -221,7 +221,7 @@ def test_fastmin_edge_cases(): # Initial labels contain negative values unary = np.zeros((3, 3, 3)) binary = np.zeros((3, 3)) - labels = np.full((3, 3), -1, dtype=np.int_) + labels = np.full((3, 3), -1, dtype=np.int64) with pytest.raises(ValueError): maxflow.aexpansion_grid(unary, binary, labels=labels) with pytest.raises(ValueError): From 2f75a794795c8deb901df797b4bd3ed78bed63b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20M=C3=A1rquez=20Neila?= Date: Mon, 25 Nov 2024 11:55:47 +0100 Subject: [PATCH 4/4] Add Python 3.13 for deployment --- .github/workflows/maxflow-deployment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maxflow-deployment.yml b/.github/workflows/maxflow-deployment.yml index a96dc38..b7b4036 100644 --- a/.github/workflows/maxflow-deployment.yml +++ b/.github/workflows/maxflow-deployment.yml @@ -34,7 +34,7 @@ jobs: runs-on: ${{ matrix.os }} env: CIBW_ARCHS: "auto64" - CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-*" + CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-* cp313-*" CIBW_SKIP: "*musllinux* pp*-win* pp*-macosx* pp*" TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI }}