Repository: remydubois/lsnms (language: python)

Issue:
chore: update upper limits of python and numpy
Looking to make code more flexible to updates in python and numpy to continue to use in the future. 

Passed all pytests. 


Discussion / hints:
Hi !

Thanks for your PR. I am a bit surprised CI did not run 🤔 . I would rather try to run CI and make sure install completes even on 3.11. I am not sure my test matrix goes up to 3.11, [cf the matrix](https://github.com/remydubois/lsnms/blob/3148c508a6c71a128af12dbe3c7ffae9b454b66d/.github/workflows/python-package.yml#L19).

Will do asap
Thanks Remy! Let me know if there is anything that we can do to help you with that! We would love to have this in and it will help us continue to use your library!
________________________________
From: Rémy Dubois ***@***.***>
Sent: Friday, December 15, 2023 11:30 AM
To: remydubois/lsnms ***@***.***>
Cc: Chott, Taylor - US ***@***.***>; Author ***@***.***>
Subject: Re: [remydubois/lsnms] chore: update upper limits of python and numpy (PR #24)


EXTERNAL EMAIL - This email originated from outside of CACI. Do not click any links or attachments unless you recognize and trust the sender.





Hi !

Thanks for your PR. I am a bit surprised CI did not run 🤔 . I would rather try to run CI and make sure install completes even on 3.11. I am not sure my test matrix goes up to 3.11, cf the matrix<https://github.com/remydubois/lsnms/blob/3148c508a6c71a128af12dbe3c7ffae9b454b66d/.github/workflows/python-package.yml#L19>.

Will do asap

—
Reply to this email directly, view it on GitHub<https://github.com/remydubois/lsnms/pull/24#issuecomment-1858319249>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AKGVYINRA73ZVI56GYWRJTTYJSJMLAVCNFSM6AAAAABAVHBRDGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQNJYGMYTSMRUHE>.
You are receiving this because you authored the thread.Message ID: ***@***.***>

________________________________

This electronic message contains information from CACI International Inc or subsidiary companies, which may be company sensitive, proprietary, privileged or otherwise protected from disclosure. The information is intended to be used solely by the recipient(s) named above. If you are not an intended recipient, be aware that any review, disclosure, copying, distribution or use of this transmission or its contents is prohibited. If you have received this transmission in error, please notify the sender immediately.

Hi, not much, thanks. 

I took the occasions to solve old PRs I have had hanging around which should introduce some more speedup. I will integrate your version change in this new PR, and close this one once it's done !

Fix patch:
diff --git a/.github/workflows/post-merge.yml b/.github/workflows/post-merge.yml
new file mode 100644
index 0000000..7e0ec70
--- /dev/null
+++ b/.github/workflows/post-merge.yml
@@ -0,0 +1,47 @@
+# This workflow will install run speed benchmarks at each merged PR on main
+# and track benchmarks history.
+
+name: Post-merge
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
+    types: [closed]
+
+jobs:
+  run-and-push-benchmarks:
+    if: github.event.pull_request.merged == true
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - name: Set up Python 3.11
+        uses: actions/setup-python@v3
+        with:
+          python-version: 3.11
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade poetry
+          python -m poetry install --with dev
+      - name: Run benchmarks
+        run: python -m poetry run pytest --benchmark-warmup="on" --benchmark-enable --benchmark-autosave
+      - name: Log benchmark
+        run: poetry run pytest-benchmark compare --group-by="func" > benchmarks-history.txt
+      - name: Checkout new branch
+        run: |
+          git config --global user.name "Continuous Integration"
+          git config --global user.email "username@users.noreply.github.com"
+          git checkout -b CI/benchmarks
+      - name: Add benchmark and history files
+        run: git add .
+      - name: Commit benchmark files
+        run: git commit -m "(AUTO-GENERATED) Added benchmark files"
+      - name: Push changes
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: git push --set-upstream origin CI/benchmarks
+      - name: Create PR from new branch
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: gh pr create -B main --assignee remydubois -t "(CI) Benchmarks update"
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 9b8e72b..0565e6a 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -11,13 +11,11 @@ on:
 
 jobs:
   build:
-
     runs-on: ubuntu-latest
     strategy:
       fail-fast: false
       matrix:
-        python-version: ["3.8", "3.9", "3.10"]
-
+        python-version: ["3.8", "3.9", "3.10", "3.11"]
     steps:
     - uses: actions/checkout@v3
     - name: Set up Python ${{ matrix.python-version }}
@@ -37,4 +35,4 @@ jobs:
         flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
     - name: Test with pytest
       run: |
-        python -m poetry run pytest
+        python -m poetry run pytest --benchmark-disable
diff --git a/poetry.lock b/poetry.lock
index c0261ea..7865b67 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -687,6 +687,48 @@ files = [
 dev = ["pre-commit", "tox"]
 testing = ["pytest", "pytest-benchmark"]
 
+[[package]]
+name = "py-cpuinfo"
+version = "9.0.0"
+description = "Get CPU info with pure Python"
+optional = false
+python-versions = "*"
+files = [
+    {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"},
+    {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"},
+]
+
+[[package]]
+name = "pygal"
+version = "3.0.4"
+description = "A Python svg graph plotting library"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "pygal-3.0.4-py2.py3-none-any.whl", hash = "sha256:e931caf08b4be0e6ec119a4c0e20dbed2d77829c641b7dea0ed21fe6ec81f2ea"},
+    {file = "pygal-3.0.4.tar.gz", hash = "sha256:6c5da33f1041e8b30cbc980f8a34910d9edc584b833240298f6a25df65425289"},
+]
+
+[package.dependencies]
+importlib-metadata = "*"
+
+[package.extras]
+docs = ["pygal-sphinx-directives", "sphinx", "sphinx-rtd-theme"]
+lxml = ["lxml"]
+png = ["cairosvg"]
+test = ["cairosvg", "coveralls", "flake8", "flask", "lxml", "pygal-maps-ch", "pygal-maps-fr", "pygal-maps-world", "pyquery", "pytest", "pytest-cov", "pytest-isort", "pytest-runner"]
+
+[[package]]
+name = "pygaljs"
+version = "1.0.2"
+description = "Python package providing assets from https://github.com/Kozea/pygal.js"
+optional = false
+python-versions = "*"
+files = [
+    {file = "pygaljs-1.0.2-py2.py3-none-any.whl", hash = "sha256:d75e18cb21cc2cda40c45c3ee690771e5e3d4652bf57206f20137cf475c0dbe8"},
+    {file = "pygaljs-1.0.2.tar.gz", hash = "sha256:0b71ee32495dcba5fbb4a0476ddbba07658ad65f5675e4ad409baf154dec5111"},
+]
+
 [[package]]
 name = "pytest"
 version = "7.4.0"
@@ -709,6 +751,28 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
 [package.extras]
 testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
 
+[[package]]
+name = "pytest-benchmark"
+version = "4.0.0"
+description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "pytest-benchmark-4.0.0.tar.gz", hash = "sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1"},
+    {file = "pytest_benchmark-4.0.0-py3-none-any.whl", hash = "sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6"},
+]
+
+[package.dependencies]
+py-cpuinfo = "*"
+pygal = {version = "*", optional = true, markers = "extra == \"histogram\""}
+pygaljs = {version = "*", optional = true, markers = "extra == \"histogram\""}
+pytest = ">=3.8"
+
+[package.extras]
+aspect = ["aspectlib"]
+elasticsearch = ["elasticsearch"]
+histogram = ["pygal", "pygaljs"]
+
 [[package]]
 name = "requests"
 version = "2.31.0"
@@ -960,5 +1024,5 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
 
 [metadata]
 lock-version = "2.0"
-python-versions = ">=3.8,<3.11"
-content-hash = "732457f769d5db701f9fa73c50e84df5d2378d9ea5e1988eee79a6ad8348b5f4"
+python-versions = ">=3.8,<3.12"
+content-hash = "4b1071fcb5a9ed53e0df699e77ad716818e1282ee45ea00555b18145da008191"
diff --git a/pyproject.toml b/pyproject.toml
index 98c06a1..645c7cb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,7 +10,7 @@ keywords = ["NMS", "Non Maximum Suppression", "Histology image", "Satellite imag
 classifiers=["Programming Language :: Python :: 3", "Topic :: Scientific/Engineering"]
 
 [tool.poetry.dependencies]
-python = ">=3.8,<3.11"
+python = ">=3.8,<3.12"
 numpy = ">=1.24"
 numba = "^0.57.1"
 
@@ -19,6 +19,7 @@ pytest = "^7.4.0"
 pillow = "^10.0.0"
 torch = "2.0.0"
 torchvision = "0.15.1"
+pytest-benchmark = {extras = ["histogram"], version = "^4.0.0"}
 
 [tool.black]
 line-length = 100


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 5c34c0a..a4ee0c7 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -4,11 +4,12 @@
 
 @pytest.fixture
 def instances():
-    topleft = np.random.uniform(0.0, high=1_000, size=(10_000, 2))
-    wh = np.random.uniform(15, 45, size=topleft.shape)
+    rng = np.random.RandomState(seed=0)
+    topleft = rng.uniform(0.0, high=1_000, size=(10_000, 2))
+    wh = rng.uniform(15, 45, size=topleft.shape)
 
     boxes = np.concatenate([topleft, topleft + wh], axis=1)
-    scores = np.random.uniform(0.01, 1.0, size=len(topleft))
+    scores = rng.uniform(0.01, 1.0, size=len(topleft))
 
     return boxes, scores
 
diff --git a/tests/test_nms.py b/tests/test_nms.py
index cbf60e7..a4fd5d0 100644
--- a/tests/test_nms.py
+++ b/tests/test_nms.py
@@ -7,7 +7,7 @@
 from lsnms.nms import naive_nms
 
 
-def test_rtree_nms(instances, score_threshold):
+def test_rtree_nms(instances, score_threshold, benchmark):
 
     boxes, scores = instances
 
@@ -22,7 +22,7 @@ def test_rtree_nms(instances, score_threshold):
     k1 = np.argwhere(scores_mask)[_k1, 0]
 
     # Compare sparse NMS
-    k2 = nms(boxes, scores, 0.5, score_threshold)
+    k2 = benchmark(nms, boxes, scores, 0.5, score_threshold)
 
     assert np.allclose(k1, k2)
 
@@ -35,7 +35,7 @@ def test_empty_nms(instances):
     assert keep.size == 0
 
 
-def test_rtree_multiclass_nms(instances, score_threshold):
+def test_rtree_multiclass_nms(instances, score_threshold, benchmark):
 
     boxes, scores = instances
     class_ids = np.random.randint(0, 50, size=len(boxes))
@@ -50,10 +50,10 @@ def test_rtree_multiclass_nms(instances, score_threshold):
     _k1 = box_ops.batched_nms(
         torch.tensor(torch_boxes), torch.tensor(torch_scores), torch.tensor(torch_class_ids), 0.5
     ).numpy()
-    k1 = np.argwhere(score_mask)[_k1, 0] 
+    k1 = np.argwhere(score_mask)[_k1, 0]
 
     # Compare sparse NMS
-    k2 = nms(boxes, scores, 0.5, score_threshold, class_ids=class_ids)
+    k2 = benchmark(nms, boxes, scores, 0.5, score_threshold, class_ids=class_ids)
 
     assert np.allclose(k1, k2)
 
diff --git a/tests/test_rtree.py b/tests/test_rtree.py
index 8f42dd6..c0e456d 100644
--- a/tests/test_rtree.py
+++ b/tests/test_rtree.py
@@ -17,11 +17,9 @@ def intersection(boxA, boxB):
     return dx * dy
 
 
-def test_intersect_tree():
+def test_intersect_tree(instances, benchmark):
 
-    topleft = np.random.uniform(0.0, high=1_000, size=(10_000, 2))
-    wh = np.random.uniform(15, 45, size=topleft.shape)
-    boxes = np.concatenate([topleft, topleft + wh], axis=1)
+    boxes, _ = instances
 
     tree = RTree(boxes, 16)
 
@@ -37,6 +35,8 @@ def test_intersect_tree():
         np.testing.assert_allclose(in_inter, intersections)
         np.testing.assert_array_less(out_inter, min_area)
 
+    benchmark(tree.intersect, queries, 0)
+
 
 def test_build_odd_tree(instances):
     boxes, _ = instances
@@ -44,6 +44,11 @@ def test_build_odd_tree(instances):
         _ = RTree(boxes, leaf_size=0)
 
 
+def test_build_tree(instances, benchmark):
+    boxes, _ = instances
+    _ = benchmark(RTree, boxes, leaf_size=1)
+
+
 def test_build_rnode_default_args(instances):
     boxes, _ = instances