machinevision-toolbox-python 2.2.0__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- machinevision_toolbox_python-2.4.0/.githooks/pre-commit +83 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/.gitignore +4 -0
- machinevision_toolbox_python-2.4.0/.release-please-manifest.json +3 -0
- machinevision_toolbox_python-2.4.0/AGENTS.md +30 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/CHANGELOG.md +64 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/PKG-INFO +18 -14
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/README.md +12 -8
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/pyproject.toml +13 -5
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/BagOfWords.py +13 -1
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/BundleAdjust.py +11 -1
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/Camera.py +101 -18
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageConstants.py +27 -6
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageCore.py +69 -8
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageIO.py +8 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImagePointFeatures.py +13 -2
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageWholeFeatures.py +18 -5
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/Kernel.py +29 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/PointCloud.py +17 -4
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/Sources.py +4 -5
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/base/__init__.py +1 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/base/color.py +42 -16
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/base/imageio.py +541 -443
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/base/types.py +14 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/bin/mvtbtool.py +2 -1
- machinevision_toolbox_python-2.2.0/.release-please-manifest.json +0 -3
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/LICENSE +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/Makefile +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/README.md +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/blobs1.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/blobs2.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/bunny.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/color.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/corners.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/matching.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/monalisa.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/morphdemo.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/randomdots.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/ransac_line.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/ros_stream.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/showzip.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/torch-yolo.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/examples/torch_seg.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/release-please-config.json +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageBlobs.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageColor.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageFiducials.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageLineFeatures.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageMorph.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageMultiview.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageProcessing.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageRegionFeatures.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageReshape.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageSpatial.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/ImageTensor.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/VisualServo.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/__init__.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/_image_typing.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/base/data.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/base/findpeaks.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/base/graphics.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/base/meshgrid.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/base/shapes.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/base/version.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/bin/_bintools.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/bin/_iminteractive.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/bin/bagtool.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/bin/imtool.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/bin/ocrtool.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/bin/tagtool.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/blocks/Icons/camera.png +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/blocks/Icons/estpose_p.png +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/blocks/Icons/imageplane.png +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/blocks/Icons/visjac_p.png +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/blocks/__init__.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/blocks/camera.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/blocks/test_blocks.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/camera_derivatives.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/decorators.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/fiducial.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/src/machinevisiontoolbox/mvtb_types.py +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/symbolic/README.md +0 -0
- {machinevision_toolbox_python-2.2.0 → machinevision_toolbox_python-2.4.0}/symbolic/bundle-adjust.py +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Pre-commit hook: keep notebook outputs clear and bootstrap cells in sync.
|
|
3
|
+
|
|
4
|
+
Activated once per machine via `git config core.hooksPath .githooks`
|
|
5
|
+
(see docs/notebooks/README.md for the full explanation). Fixes any affected
|
|
6
|
+
notebook in place and re-stages it, so the commit proceeds with the fix already
|
|
7
|
+
applied -- no interruption, no separate re-commit step.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
15
|
+
NOTEBOOKS_DIR = REPO_ROOT / "docs" / "notebooks"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def staged_files(pattern: str) -> list[str]:
|
|
19
|
+
result = subprocess.run(
|
|
20
|
+
["git", "diff", "--cached", "--name-only", "--diff-filter=ACMR", "--", pattern],
|
|
21
|
+
cwd=REPO_ROOT,
|
|
22
|
+
capture_output=True,
|
|
23
|
+
text=True,
|
|
24
|
+
check=True,
|
|
25
|
+
)
|
|
26
|
+
return [line for line in result.stdout.splitlines() if line]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def main() -> int:
|
|
30
|
+
if not NOTEBOOKS_DIR.exists():
|
|
31
|
+
return 0
|
|
32
|
+
|
|
33
|
+
staged_notebooks = staged_files("docs/notebooks/*.ipynb")
|
|
34
|
+
template_staged = bool(staged_files("docs/notebooks/_mvtb_nb_bootstrap.py"))
|
|
35
|
+
|
|
36
|
+
# Output-clearing only ever touches notebooks actually staged for this commit.
|
|
37
|
+
output_targets = staged_notebooks
|
|
38
|
+
|
|
39
|
+
# Bootstrap-cell regeneration also has to cover every notebook when the
|
|
40
|
+
# template itself changed -- otherwise the other notebooks go stale and CI
|
|
41
|
+
# fails on files this commit never touched.
|
|
42
|
+
if template_staged:
|
|
43
|
+
bootstrap_targets = sorted(
|
|
44
|
+
str(p.relative_to(REPO_ROOT)) for p in NOTEBOOKS_DIR.glob("*.ipynb")
|
|
45
|
+
)
|
|
46
|
+
else:
|
|
47
|
+
bootstrap_targets = staged_notebooks
|
|
48
|
+
|
|
49
|
+
if not output_targets and not bootstrap_targets:
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
changed_paths: set[str] = set()
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
if output_targets:
|
|
56
|
+
paths = [str(REPO_ROOT / t) for t in output_targets]
|
|
57
|
+
subprocess.run(
|
|
58
|
+
[sys.executable, str(NOTEBOOKS_DIR / "clear_outputs.py"), *paths],
|
|
59
|
+
cwd=REPO_ROOT,
|
|
60
|
+
check=True,
|
|
61
|
+
)
|
|
62
|
+
changed_paths.update(paths)
|
|
63
|
+
|
|
64
|
+
if bootstrap_targets:
|
|
65
|
+
paths = [str(REPO_ROOT / t) for t in bootstrap_targets]
|
|
66
|
+
subprocess.run(
|
|
67
|
+
[sys.executable, str(NOTEBOOKS_DIR / "sync_bootstrap.py"), *paths],
|
|
68
|
+
cwd=REPO_ROOT,
|
|
69
|
+
check=True,
|
|
70
|
+
)
|
|
71
|
+
changed_paths.update(paths)
|
|
72
|
+
except subprocess.CalledProcessError:
|
|
73
|
+
print("pre-commit hook: notebook cleanup failed unexpectedly", file=sys.stderr)
|
|
74
|
+
return 1
|
|
75
|
+
|
|
76
|
+
if changed_paths:
|
|
77
|
+
subprocess.run(["git", "add", "--", *sorted(changed_paths)], cwd=REPO_ROOT, check=True)
|
|
78
|
+
|
|
79
|
+
return 0
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
sys.exit(main())
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# machinevision-toolbox-python — Agent Instructions
|
|
2
|
+
|
|
3
|
+
Part of the RVC ecosystem. **Read [rvc-ecosystem/AGENTS.md](https://github.com/petercorke/rvc-ecosystem/blob/main/AGENTS.md) first** — it defines shared conventions: repo ownership, math invariants, dependency boundaries, git/PR workflow, code standards, tech-debt tracking. This file only adds what's specific to this repo.
|
|
4
|
+
|
|
5
|
+
| | |
|
|
6
|
+
|---|---|
|
|
7
|
+
| PyPI package | `machinevision-toolbox-python` |
|
|
8
|
+
| Nickname | MVTB |
|
|
9
|
+
| Owner | Peter Corke (`petercorke`) |
|
|
10
|
+
| Default branch | `main` |
|
|
11
|
+
| Contribution model | Branch → PR; direct push to `main` at Peter's discretion |
|
|
12
|
+
|
|
13
|
+
## Notes specific to this repo
|
|
14
|
+
|
|
15
|
+
- Pure Python — no compiled extensions, builds with Hatch (`hatchling`) directly.
|
|
16
|
+
- Depends on `spatialmath`, `pgraph-python`, `ansitable`, plus the third-party `opencv-python`/
|
|
17
|
+
`opencv-contrib-python` and `open3d`.
|
|
18
|
+
- `open3d` is consistently a Python version or two behind — never assume it supports the
|
|
19
|
+
latest Python release; check compatibility before bumping this repo's Python floor/ceiling.
|
|
20
|
+
- OpenCV 5.x support was just added alongside the existing 4.x support (a major version jump,
|
|
21
|
+
not a minor bump). This is fresh work — aim to keep both working, but treat 4.x/5.x
|
|
22
|
+
compatibility friction as expected teething problems to fix, not a regression.
|
|
23
|
+
- Tech-debt tracked as GitHub Issues labelled `tech-debt` (migrated 2026-08-02) — this repo
|
|
24
|
+
was first to migrate off `tech-debt.md`.
|
|
25
|
+
- This repo's `.github/pull_request_template.md` and `.github/ISSUE_TEMPLATE/*.yml` are the
|
|
26
|
+
canonical source other repos' templates were copied from — check here first if updating
|
|
27
|
+
template content ecosystem-wide.
|
|
28
|
+
- Codacy is wired in (badge + automated PR comments); grade A is the aspiration. Real backlog
|
|
29
|
+
exists, tracked via `tech-debt`-labelled issues (including a "mypy not in CI" entry) — see
|
|
30
|
+
ecosystem `AGENTS.md` §5 for how that target is framed.
|
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.0](https://github.com/petercorke/machinevision-toolbox-python/compare/v2.3.0...v2.4.0) (2026-08-25)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add __array__ protocol to Image and Kernel, ndim to Kernel ([e762f57](https://github.com/petercorke/machinevision-toolbox-python/commit/e762f578faa269655cbd06b02d48c4c30d48c516))
|
|
9
|
+
* relax opencv pin to support both 4.x and 5.x, add CI matrix axis ([2ec69fe](https://github.com/petercorke/machinevision-toolbox-python/commit/2ec69fe4d116bb51f717e0a4a3c8baed95a7101f)), closes [#44](https://github.com/petercorke/machinevision-toolbox-python/issues/44)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* assert hygiene -- unreachable assert, two caller-facing checks ([7afe33e](https://github.com/petercorke/machinevision-toolbox-python/commit/7afe33eb02af65d4e64d52481f668a610f63b807))
|
|
15
|
+
* CameraBase.plot() unbound call (self=None) crashed instead of working ([e2d4600](https://github.com/petercorke/machinevision-toolbox-python/commit/e2d460029d6ef1d5bb807168f37b37b3eb10ff1c))
|
|
16
|
+
* catch cv2.error in points2F(), not just F/mask=None ([c4197cb](https://github.com/petercorke/machinevision-toolbox-python/commit/c4197cbd22784b8c002df8a02bb95764dc589b50))
|
|
17
|
+
* correct stale open3d-python PyPI name, add py3.13 exclusion marker ([87c1e8f](https://github.com/petercorke/machinevision-toolbox-python/commit/87c1e8f5a2eb3a2b17f2a1f1c182b22fbb025483))
|
|
18
|
+
* esttheta() -- broken pickregion() helper and missing theta-sweep body ([50b00cc](https://github.com/petercorke/machinevision-toolbox-python/commit/50b00cc95c15b56b3598505f4cd532a0612f6ff9))
|
|
19
|
+
* exclude tests/ from Bandit's assert_used (B101) check ([e62b116](https://github.com/petercorke/machinevision-toolbox-python/commit/e62b116e0b57aef73882ee4689153eda326c399f))
|
|
20
|
+
* Histogram.plot(type='ncdf') never actually worked, docstring lied ([6d4152d](https://github.com/petercorke/machinevision-toolbox-python/commit/6d4152d58bb63970863ff0ca49593c21ec815b5b))
|
|
21
|
+
* idisp() crashes when reuse=True with no title given ([a05b13b](https://github.com/petercorke/machinevision-toolbox-python/commit/a05b13b93ab4ed03397548348e8de48230b284f3))
|
|
22
|
+
* Image.metadata() missing camera-specific Exif sub-IFD tags ([d554ef2](https://github.com/petercorke/machinevision-toolbox-python/commit/d554ef257d2d494c66daaf928114c8097fb8fb91))
|
|
23
|
+
* Image() drops maxintval when dtype= is also given, silently truncating ([15f1d32](https://github.com/petercorke/machinevision-toolbox-python/commit/15f1d32576183c862592dca009828ea7e2765d79))
|
|
24
|
+
* Image(dtype='float') resolved to float64, not float32 ([18c5d85](https://github.com/petercorke/machinevision-toolbox-python/commit/18c5d85d82ebd2c09a43adb278e4435658f0c2b7))
|
|
25
|
+
* ImageConstantsMixin factories silently downcast explicit dtype requests ([977dbf6](https://github.com/petercorke/machinevision-toolbox-python/commit/977dbf6402307f38f695c697d68009ddd914d237))
|
|
26
|
+
* mvtbtool's %precision line spuriously echoes with --showassign ([ed43ca6](https://github.com/petercorke/machinevision-toolbox-python/commit/ed43ca64652a7d64a4536ae5665e672ffcc7db4a))
|
|
27
|
+
* name2color() colororder reordering returns list, not ndarray ([c90e6fb](https://github.com/petercorke/machinevision-toolbox-python/commit/c90e6fb4db7814707fd1548a7d87b1057b665499))
|
|
28
|
+
* PointCloud.Read() double-joins a caller-supplied "data/" prefix ([fff671c](https://github.com/petercorke/machinevision-toolbox-python/commit/fff671cdd28c85a555ee9fc0b7dd6f4bad4eccc9))
|
|
29
|
+
* printstats() crashes if .stats hasn't been accessed yet ([3484224](https://github.com/petercorke/machinevision-toolbox-python/commit/34842247c529159364591528ec109deac182899f))
|
|
30
|
+
* suppress divide-by-zero RuntimeWarning in BagOfWords idf computation ([05c85e0](https://github.com/petercorke/machinevision-toolbox-python/commit/05c85e0189061019dc55888b33ddb30cc3e9d9ae))
|
|
31
|
+
* visual-odometry robustness -- match() metric, points2F(), BundleAdjust ([8691847](https://github.com/petercorke/machinevision-toolbox-python/commit/8691847df58cc3cf003838436f7d94c59d5ae609))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Documentation
|
|
35
|
+
|
|
36
|
+
* add AGENTS.md stub linking to rvc-ecosystem conventions ([0827291](https://github.com/petercorke/machinevision-toolbox-python/commit/082729184414b821a9e610f8a409f5cbeabc308c))
|
|
37
|
+
* points2F() kwargs -- correct which params apply to which method ([6b10d64](https://github.com/petercorke/machinevision-toolbox-python/commit/6b10d6401925564163568a09e1e504c99eec99fa))
|
|
38
|
+
|
|
39
|
+
## [2.3.0](https://github.com/petercorke/machinevision-toolbox-python/compare/v2.2.0...v2.3.0) (2026-08-13)
|
|
40
|
+
|
|
41
|
+
**Highlight: Notebooks working in all environments** Fixed a number of problems with getting notebooks to execute in desktop Jupyter, Colab, and
|
|
42
|
+
JupyterLite in browser. Created nice framework to keep the environment-specific import logic in each notebook consistent at commit time. Added
|
|
43
|
+
CI tests and also CI check that notebooks runs in the pyodide environment. Currently pinning to pyodide 0.6.1 to sidestep the lack of JSPI support
|
|
44
|
+
in Safari and Firefox required by later pyodide kernels.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Features
|
|
48
|
+
|
|
49
|
+
* **ci:** add a real-Pyodide dependency-completeness check for JupyterLite ([9072da6](https://github.com/petercorke/machinevision-toolbox-python/commit/9072da6e945b45bf2f04a5994f9dbc9c6d99622d))
|
|
50
|
+
* **notebooks:** add shared environment-bootstrap module and drift-check tooling ([44242e8](https://github.com/petercorke/machinevision-toolbox-python/commit/44242e81420e05d2d5656c81c53a33f1071ddac7))
|
|
51
|
+
* **notebooks:** migrate all notebooks to the generated bootstrap cell ([75f230c](https://github.com/petercorke/machinevision-toolbox-python/commit/75f230ce7d789e25edcd06694e886e0f4db8a83d))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
### Bug Fixes
|
|
55
|
+
|
|
56
|
+
* **ci:** guard release-please's PyPI-publish trigger against the truthy-string bug ([#74](https://github.com/petercorke/machinevision-toolbox-python/issues/74)) ([0d74276](https://github.com/petercorke/machinevision-toolbox-python/commit/0d742769d2553125a5b2a08a4a8a16f1e9b9f69c))
|
|
57
|
+
* **ci:** pass --repo to gh calls in release-please's PyPI-publish trigger ([#73](https://github.com/petercorke/machinevision-toolbox-python/issues/73)) ([6ff01a3](https://github.com/petercorke/machinevision-toolbox-python/commit/6ff01a395402d6363ade8b4f4e07952237089666))
|
|
58
|
+
* **docs:** pin jupyterlite-pyodide-kernel==0.6.1, matching RTB and bdsim ([76bd7e1](https://github.com/petercorke/machinevision-toolbox-python/commit/76bd7e130dab5562bb947be9a6b6c1ef3a5e2140))
|
|
59
|
+
* **notebooks:** fix JupyterLite bugs found via real-browser testing ([3979404](https://github.com/petercorke/machinevision-toolbox-python/commit/3979404b317a79f2769007a02ff7a24091e1e4b7))
|
|
60
|
+
* release-please never triggered the actual PyPI publish ([18cc08b](https://github.com/petercorke/machinevision-toolbox-python/commit/18cc08bc21e38bef2f5ed56e31daee4c9404732a))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
### Documentation
|
|
64
|
+
|
|
65
|
+
* **readme:** move to standard badge layout ([0722f9d](https://github.com/petercorke/machinevision-toolbox-python/commit/0722f9da96c97e2d49e791fd68237063df8d38cf))
|
|
66
|
+
|
|
3
67
|
## [2.2.0](https://github.com/petercorke/machinevision-toolbox-python/compare/v2.1.0...v2.2.0) (2026-08-10)
|
|
4
68
|
|
|
5
69
|
**Highlight: OpenCV 5 compatibility.** MVTB now works correctly under both OpenCV 4 and the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: machinevision-toolbox-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Object-oriented machine vision for Python.
|
|
5
5
|
Project-URL: Homepage, https://github.com/petercorke/machinevision-toolbox-python
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/pypa/sampleproject/issues
|
|
@@ -23,8 +23,8 @@ Requires-Dist: ansitable
|
|
|
23
23
|
Requires-Dist: matplotlib
|
|
24
24
|
Requires-Dist: mvtb-data
|
|
25
25
|
Requires-Dist: numpy
|
|
26
|
-
Requires-Dist: opencv-contrib-python<
|
|
27
|
-
Requires-Dist: opencv-python<
|
|
26
|
+
Requires-Dist: opencv-contrib-python<6.0.0,>=4.5
|
|
27
|
+
Requires-Dist: opencv-python<6.0.0,>=4.5
|
|
28
28
|
Requires-Dist: pgraph-python
|
|
29
29
|
Requires-Dist: pillow
|
|
30
30
|
Requires-Dist: requests
|
|
@@ -35,7 +35,7 @@ Provides-Extra: all
|
|
|
35
35
|
Requires-Dist: ipympl; extra == 'all'
|
|
36
36
|
Requires-Dist: ipython; extra == 'all'
|
|
37
37
|
Requires-Dist: ipywidgets; extra == 'all'
|
|
38
|
-
Requires-Dist: open3d
|
|
38
|
+
Requires-Dist: open3d; (python_version < '3.13') and extra == 'all'
|
|
39
39
|
Requires-Dist: pygments; extra == 'all'
|
|
40
40
|
Requires-Dist: pytesseract; extra == 'all'
|
|
41
41
|
Requires-Dist: rosbags; extra == 'all'
|
|
@@ -64,7 +64,7 @@ Requires-Dist: ipywidgets; extra == 'jupyter'
|
|
|
64
64
|
Provides-Extra: ocr
|
|
65
65
|
Requires-Dist: pytesseract; extra == 'ocr'
|
|
66
66
|
Provides-Extra: open3d
|
|
67
|
-
Requires-Dist: open3d
|
|
67
|
+
Requires-Dist: open3d; (python_version < '3.13') and extra == 'open3d'
|
|
68
68
|
Provides-Extra: ros
|
|
69
69
|
Requires-Dist: rosbags; extra == 'ros'
|
|
70
70
|
Requires-Dist: roslibpy; extra == 'ros'
|
|
@@ -99,20 +99,24 @@ Description-Content-Type: text/markdown
|
|
|
99
99
|
|
|
100
100
|
---
|
|
101
101
|
|
|
102
|
-
### Status &
|
|
103
|
-
|
|
104
|
-
[](https://qcr.github.io)
|
|
102
|
+
### Status & Project Health
|
|
103
|
+
|
|
105
104
|
[](https://github.com/petercorke/machinevision-toolbox-python/actions/workflows/ci.yml)
|
|
106
|
-
[](https://pepy.tech/projects/machinevision-toolbox-python)
|
|
107
106
|

|
|
107
|
+
[](https://codecov.io/gh/petercorke/machinevision-toolbox-python)
|
|
108
108
|
[](https://opensource.org/licenses/MIT)
|
|
109
|
-
[](https://pypistats.org/packages/machinevision-toolbox-python)
|
|
110
109
|
|
|
111
|
-
###
|
|
112
|
-
[](https://github.com/petercorke/robotics-toolbox-python)
|
|
112
|
+
[](https://qcr.github.io)
|
|
113
|
+
|
|
114
|
+
[](https://numpy.org)
|
|
115
|
+
[](https://scipy.org)
|
|
116
|
+
[](https://matplotlib.org)
|
|
114
117
|
[](https://opencv.org)
|
|
115
118
|
[](https://open3d.org)
|
|
119
|
+
[](https://github.com/petercorke/spatialmath-python)
|
|
116
120
|
|
|
117
121
|
## Synopsis
|
|
118
122
|
|
|
@@ -21,20 +21,24 @@
|
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
-
### Status &
|
|
25
|
-
|
|
26
|
-
[](https://qcr.github.io)
|
|
24
|
+
### Status & Project Health
|
|
25
|
+
|
|
27
26
|
[](https://github.com/petercorke/machinevision-toolbox-python/actions/workflows/ci.yml)
|
|
28
|
-
[](https://pepy.tech/projects/machinevision-toolbox-python)
|
|
29
28
|

|
|
29
|
+
[](https://codecov.io/gh/petercorke/machinevision-toolbox-python)
|
|
30
30
|
[](https://opensource.org/licenses/MIT)
|
|
31
|
-
[](https://pypistats.org/packages/machinevision-toolbox-python)
|
|
32
31
|
|
|
33
|
-
###
|
|
34
|
-
[](https://github.com/petercorke/robotics-toolbox-python)
|
|
34
|
+
[](https://qcr.github.io)
|
|
35
|
+
|
|
36
|
+
[](https://numpy.org)
|
|
37
|
+
[](https://scipy.org)
|
|
38
|
+
[](https://matplotlib.org)
|
|
36
39
|
[](https://opencv.org)
|
|
37
40
|
[](https://open3d.org)
|
|
41
|
+
[](https://github.com/petercorke/spatialmath-python)
|
|
38
42
|
|
|
39
43
|
## Synopsis
|
|
40
44
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "machinevision-toolbox-python"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.4.0"
|
|
4
4
|
authors = [{ name = "Peter Corke", email = "rvc@petercorke.com" }]
|
|
5
5
|
description = "Object-oriented machine vision for Python."
|
|
6
6
|
readme = "README.md"
|
|
@@ -39,8 +39,8 @@ dependencies = [
|
|
|
39
39
|
"scipy",
|
|
40
40
|
"matplotlib",
|
|
41
41
|
"pillow",
|
|
42
|
-
"opencv-python
|
|
43
|
-
"opencv-contrib-python
|
|
42
|
+
"opencv-python>=4.5,<6.0.0",
|
|
43
|
+
"opencv-contrib-python>=4.5,<6.0.0",
|
|
44
44
|
"spatialmath-python",
|
|
45
45
|
"pgraph-python",
|
|
46
46
|
"ansitable",
|
|
@@ -86,7 +86,7 @@ docs = [
|
|
|
86
86
|
|
|
87
87
|
jupyter = ["ipywidgets", "ipympl"]
|
|
88
88
|
|
|
89
|
-
open3d = ["open3d
|
|
89
|
+
open3d = ["open3d; python_version < '3.13'"]
|
|
90
90
|
|
|
91
91
|
torch = ["torch"]
|
|
92
92
|
|
|
@@ -95,7 +95,7 @@ ocr = ["pytesseract"]
|
|
|
95
95
|
all = [
|
|
96
96
|
"ipywidgets",
|
|
97
97
|
"ipympl",
|
|
98
|
-
"open3d
|
|
98
|
+
"open3d; python_version < '3.13'",
|
|
99
99
|
"torch",
|
|
100
100
|
"pytesseract",
|
|
101
101
|
"rosbags",
|
|
@@ -119,6 +119,14 @@ select = ["E", "F", "I"]
|
|
|
119
119
|
[tool.ruff.lint.isort]
|
|
120
120
|
known-first-party = ["machinevisiontoolbox"]
|
|
121
121
|
|
|
122
|
+
[tool.bandit]
|
|
123
|
+
# bare `assert` is idiomatic pytest style, not a security concern -- Bandit's
|
|
124
|
+
# B101 ("assert used") is meant for src/, where assert can silently vanish
|
|
125
|
+
# under `python -O`. Excluding tests/ keeps that check meaningful without
|
|
126
|
+
# flagging every test assertion (Codacy runs Bandit via Prospector and
|
|
127
|
+
# honours this file).
|
|
128
|
+
exclude_dirs = ["tests"]
|
|
129
|
+
|
|
122
130
|
[tool.pyright]
|
|
123
131
|
include = ["src"]
|
|
124
132
|
ignore = ["docs", "tests", "examples"]
|
|
@@ -58,6 +58,13 @@ class BagOfWords:
|
|
|
58
58
|
of image features is assigned labels from the ``.centroids`` any with a
|
|
59
59
|
label greater that ``.nstopwords`` is a stop word and can be discarded.
|
|
60
60
|
|
|
61
|
+
.. note:: Removing stop words can leave a remaining word that never
|
|
62
|
+
occurs in any image, giving it an inverse document frequency (idf)
|
|
63
|
+
of ``inf`` (``log(N / 0)``). This is harmless -- every place idf is
|
|
64
|
+
used to compute a word-frequency vector, the corresponding word
|
|
65
|
+
count is also always zero there, so the ``0 * inf`` result is
|
|
66
|
+
``nan`` and gets replaced with 0 before the vector is returned.
|
|
67
|
+
|
|
61
68
|
:reference:
|
|
62
69
|
- Video Google: a text retrieval approach to object matching in videos
|
|
63
70
|
J.Sivic and A.Zisserman,
|
|
@@ -139,7 +146,12 @@ class BagOfWords:
|
|
|
139
146
|
# total number of occurrences of word i
|
|
140
147
|
# multiple occurrences in the one image count only as one
|
|
141
148
|
ni = (W > 0).sum(axis=1)
|
|
142
|
-
|
|
149
|
+
# a word that never occurs in any image (ni=0, possible after
|
|
150
|
+
# stopword removal) gives idf=inf here; harmless since nid is
|
|
151
|
+
# always 0 at that same position below, so 0*inf->nan, already
|
|
152
|
+
# suppressed by the invalid="ignore" there.
|
|
153
|
+
with np.errstate(divide="ignore"):
|
|
154
|
+
idf = np.log(N / ni)
|
|
143
155
|
|
|
144
156
|
M = []
|
|
145
157
|
|
|
@@ -405,7 +405,8 @@ if pgraph_installed:
|
|
|
405
405
|
|
|
406
406
|
:seealso: :meth:`add_view` :meth:`add_landmark`
|
|
407
407
|
"""
|
|
408
|
-
|
|
408
|
+
if len(uv) != 2:
|
|
409
|
+
raise ValueError("uv must be a 2-vector")
|
|
409
410
|
|
|
410
411
|
edge = Observation(viewpoint, landmark, uv.flatten()) # create edge object
|
|
411
412
|
e = viewpoint.connect(landmark, edge=edge) # connect nodes with it
|
|
@@ -562,6 +563,8 @@ if pgraph_installed:
|
|
|
562
563
|
:type iterations: int, optional
|
|
563
564
|
:param verbose: show Levenberg-Marquadt status, defaults to False
|
|
564
565
|
:type verbose: bool, optional
|
|
566
|
+
:raises ValueError: no landmark projections have been added to
|
|
567
|
+
the graph
|
|
565
568
|
:return: optimized state vector
|
|
566
569
|
:rtype: ndarray(N)
|
|
567
570
|
|
|
@@ -578,6 +581,13 @@ if pgraph_installed:
|
|
|
578
581
|
|
|
579
582
|
self.update_index()
|
|
580
583
|
|
|
584
|
+
if self.g.ne == 0:
|
|
585
|
+
raise ValueError(
|
|
586
|
+
"no landmark projections in the graph -- nothing to "
|
|
587
|
+
"optimize (add_projection() was never called, or every "
|
|
588
|
+
"candidate landmark was discarded)"
|
|
589
|
+
)
|
|
590
|
+
|
|
581
591
|
if x is None:
|
|
582
592
|
x = self.getstate()
|
|
583
593
|
x0 = x
|
|
@@ -350,7 +350,8 @@ class CameraBase(ABC):
|
|
|
350
350
|
|
|
351
351
|
:seealso: :meth:`nv` :meth:`width` :meth:`imagesize`
|
|
352
352
|
"""
|
|
353
|
-
|
|
353
|
+
if self._imagesize is None:
|
|
354
|
+
raise ValueError("imagesize not set")
|
|
354
355
|
return self._imagesize[0]
|
|
355
356
|
|
|
356
357
|
@property
|
|
@@ -370,7 +371,8 @@ class CameraBase(ABC):
|
|
|
370
371
|
|
|
371
372
|
:seealso: :meth:`nu` :meth:`height` :meth:`imagesize`
|
|
372
373
|
"""
|
|
373
|
-
|
|
374
|
+
if self._imagesize is None:
|
|
375
|
+
raise ValueError("imagesize not set")
|
|
374
376
|
return self._imagesize[1]
|
|
375
377
|
|
|
376
378
|
@property
|
|
@@ -390,7 +392,8 @@ class CameraBase(ABC):
|
|
|
390
392
|
|
|
391
393
|
:seealso: :meth:`nu` :meth:`height`
|
|
392
394
|
"""
|
|
393
|
-
|
|
395
|
+
if self._imagesize is None:
|
|
396
|
+
raise ValueError("imagesize not set")
|
|
394
397
|
return self._imagesize[0]
|
|
395
398
|
|
|
396
399
|
@property
|
|
@@ -410,7 +413,8 @@ class CameraBase(ABC):
|
|
|
410
413
|
|
|
411
414
|
:seealso: :meth:`nv` :meth:`width`
|
|
412
415
|
"""
|
|
413
|
-
|
|
416
|
+
if self._imagesize is None:
|
|
417
|
+
raise ValueError("imagesize not set")
|
|
414
418
|
return self._imagesize[1]
|
|
415
419
|
|
|
416
420
|
@property
|
|
@@ -1152,7 +1156,7 @@ class CameraBase(ABC):
|
|
|
1152
1156
|
"""
|
|
1153
1157
|
Plot 3D camera icon in world view (base method)
|
|
1154
1158
|
|
|
1155
|
-
:param pose: camera pose
|
|
1159
|
+
:param pose: camera pose, required when called unbound (see below)
|
|
1156
1160
|
:type pose: :class:`~spatialmath.pose3d.SE3`
|
|
1157
1161
|
:param scale: scale factor, defaults to 1
|
|
1158
1162
|
:type scale: float
|
|
@@ -1170,6 +1174,8 @@ class CameraBase(ABC):
|
|
|
1170
1174
|
:type projection: str, optional
|
|
1171
1175
|
:param ax: axes to draw in, defaults to current 3D axes
|
|
1172
1176
|
:type ax: :class:`~matplotlib.Axes3D`, optional
|
|
1177
|
+
:raises ValueError: called unbound (eg. ``CentralCamera.plot(...)``)
|
|
1178
|
+
without a ``pose``
|
|
1173
1179
|
:return: axes drawn into
|
|
1174
1180
|
:rtype: :class:`~matplotlib.Axes3D`
|
|
1175
1181
|
|
|
@@ -1177,8 +1183,19 @@ class CameraBase(ABC):
|
|
|
1177
1183
|
plot. Two icons are supported: the traditional frustum, and a
|
|
1178
1184
|
simplistic camera comprising a box and cylinder.
|
|
1179
1185
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1186
|
+
This method can be called two ways:
|
|
1187
|
+
|
|
1188
|
+
- **Bound**, ``camera.plot(...)`` -- draws an icon for ``camera``.
|
|
1189
|
+
``pose`` defaults to the instance's own pose if not given.
|
|
1190
|
+
- **Unbound**, ``CentralCamera.plot(pose=..., ...)`` -- called on
|
|
1191
|
+
the class itself, with no camera instance involved (``self`` is
|
|
1192
|
+
``None``). Useful for drawing icons at a set of poses that don't
|
|
1193
|
+
correspond to any single camera instance -- e.g. plotting the
|
|
1194
|
+
calibration frames returned by :meth:`images2C`. ``pose`` is
|
|
1195
|
+
required in this form.
|
|
1196
|
+
|
|
1197
|
+
.. note:: If ``pose`` is not given, it defaults to the pose of
|
|
1198
|
+
``self`` -- this only applies to the bound form.
|
|
1182
1199
|
"""
|
|
1183
1200
|
|
|
1184
1201
|
# if (fig is None) and (ax is None):
|
|
@@ -1195,6 +1212,11 @@ class CameraBase(ABC):
|
|
|
1195
1212
|
ax = smb.axes_logic(ax, 3, projection=projection)
|
|
1196
1213
|
|
|
1197
1214
|
if pose is None:
|
|
1215
|
+
if self is None:
|
|
1216
|
+
raise ValueError(
|
|
1217
|
+
"pose is required when plot() is called unbound, eg. "
|
|
1218
|
+
"CentralCamera.plot(pose=...)"
|
|
1219
|
+
)
|
|
1198
1220
|
pose = self.pose
|
|
1199
1221
|
|
|
1200
1222
|
# Matplotlib >=3.10 can raise while autoscaling ragged 3D line
|
|
@@ -1216,26 +1238,27 @@ class CameraBase(ABC):
|
|
|
1216
1238
|
try:
|
|
1217
1239
|
# draw camera-like object:
|
|
1218
1240
|
if shape == "frustum":
|
|
1219
|
-
|
|
1241
|
+
CameraBase._plot_frustum(ax, pose, scale, alpha)
|
|
1220
1242
|
|
|
1221
1243
|
elif shape == "camera":
|
|
1222
|
-
|
|
1244
|
+
CameraBase._plot_camera_icon(ax, pose, scale, solid, color, alpha, label)
|
|
1223
1245
|
|
|
1224
1246
|
if frame is True:
|
|
1225
|
-
|
|
1247
|
+
pose.plot(
|
|
1226
1248
|
length=scale * 1.5,
|
|
1227
1249
|
style="line",
|
|
1228
1250
|
color=color,
|
|
1229
1251
|
flo=(0.07, 0, -0.01),
|
|
1230
1252
|
)
|
|
1231
1253
|
elif frame is not False:
|
|
1232
|
-
|
|
1254
|
+
pose.plot(**frame)
|
|
1233
1255
|
finally:
|
|
1234
1256
|
ax.add_collection3d = _orig_add_collection3d
|
|
1235
1257
|
|
|
1236
1258
|
return ax
|
|
1237
1259
|
|
|
1238
|
-
|
|
1260
|
+
@staticmethod
|
|
1261
|
+
def _plot_frustum(ax: Axes, pose: SE3, scale: float, alpha: float) -> None:
|
|
1239
1262
|
"""Draw the ``shape="frustum"`` camera icon into ``ax``."""
|
|
1240
1263
|
# TODO make this kwargs or optional inputs
|
|
1241
1264
|
# side colors:
|
|
@@ -1279,8 +1302,8 @@ class CameraBase(ABC):
|
|
|
1279
1302
|
poly = Poly3DCollection(points, facecolors=["r", "g", "r", "y"], alpha=alpha)
|
|
1280
1303
|
ax.add_collection3d(poly)
|
|
1281
1304
|
|
|
1305
|
+
@staticmethod
|
|
1282
1306
|
def _plot_camera_icon(
|
|
1283
|
-
self,
|
|
1284
1307
|
ax: Axes,
|
|
1285
1308
|
pose: SE3,
|
|
1286
1309
|
scale: float,
|
|
@@ -2684,8 +2707,41 @@ class CentralCamera(CameraBase):
|
|
|
2684
2707
|
:type p2: ndarray(2,N)
|
|
2685
2708
|
:param method: algorithm '7p', '8p' [default], 'ransac', 'lmeds'
|
|
2686
2709
|
:type method: str, optional
|
|
2687
|
-
:param
|
|
2688
|
-
|
|
2710
|
+
:param residual: also return the reprojection residual, defaults to True
|
|
2711
|
+
:type residual: bool, optional
|
|
2712
|
+
:param seed: seed the RNG used by 'ransac'/'lmeds' for repeatable
|
|
2713
|
+
results, defaults to None (not seeded)
|
|
2714
|
+
:type seed: int, optional
|
|
2715
|
+
:param kwargs: passed through to ``cv2.findFundamentalMat``.
|
|
2716
|
+
``ransacReprojThreshold`` (float, defaults to 3.0) applies only
|
|
2717
|
+
when ``method='ransac'`` -- max distance in pixels from a point
|
|
2718
|
+
to its epipolar line for the point to still count as an inlier;
|
|
2719
|
+
``method='lmeds'`` ignores it entirely (verified empirically --
|
|
2720
|
+
identical result regardless of value). ``confidence`` (float in
|
|
2721
|
+
(0,1), defaults to 0.99) and ``maxIters`` (int, defaults to
|
|
2722
|
+
1000) apply to either ``method='ransac'`` or ``method='lmeds'``
|
|
2723
|
+
-- ``method`` only ever selects one algorithm at a time (a
|
|
2724
|
+
combined "ransac+lmeds" method isn't a real OpenCV feature;
|
|
2725
|
+
verified empirically that OR-ing the two method flags together
|
|
2726
|
+
doesn't error, but silently reduces to plain LMedS -- an
|
|
2727
|
+
implementation accident, not a documented hybrid mode).
|
|
2728
|
+
Defaults verified empirically 2026-08-16 (identical inlier
|
|
2729
|
+
masks with/without them explicit, same RNG seed) -- not
|
|
2730
|
+
otherwise documented by the ``cv2`` Python bindings. Same
|
|
2731
|
+
parameter set and defaults on OpenCV 4.10 and 5.0.
|
|
2732
|
+
|
|
2733
|
+
.. warning:: ``maxIters`` only works when ``ransacReprojThreshold``
|
|
2734
|
+
and ``confidence`` are *also* given explicitly -- passing
|
|
2735
|
+
only ``maxIters=`` hits a ``cv2.findFundamentalMat``
|
|
2736
|
+
overload-resolution failure (a confusing low-level OpenCV
|
|
2737
|
+
error, not a clear Python one), since the 3-argument
|
|
2738
|
+
overload this method otherwise uses has no ``maxIters``
|
|
2739
|
+
parameter at all.
|
|
2740
|
+
:type kwargs: dict, optional
|
|
2741
|
+
:raises ValueError: fewer than the minimum number of point
|
|
2742
|
+
correspondences for ``method`` (7 for '7p', 8 otherwise), or
|
|
2743
|
+
the points are too degenerate (eg. coplanar, collinear) for
|
|
2744
|
+
``cv2.findFundamentalMat`` to estimate F from
|
|
2689
2745
|
:return: fundamental matrix and residual
|
|
2690
2746
|
:rtype: ndarray(3,3), float
|
|
2691
2747
|
|
|
@@ -2719,13 +2775,40 @@ class CentralCamera(CameraBase):
|
|
|
2719
2775
|
"ransac": cv2.FM_RANSAC,
|
|
2720
2776
|
"lmeds": cv2.FM_LMEDS,
|
|
2721
2777
|
}
|
|
2778
|
+
min_points = 7 if method == "7p" else 8
|
|
2779
|
+
if p1.shape[1] < min_points:
|
|
2780
|
+
raise ValueError(
|
|
2781
|
+
f"points2F() needs at least {min_points} point correspondences "
|
|
2782
|
+
f"for method={method!r}, got {p1.shape[1]}"
|
|
2783
|
+
)
|
|
2722
2784
|
|
|
2723
2785
|
if seed is not None:
|
|
2724
2786
|
cv2.setRNGSeed(seed)
|
|
2725
2787
|
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2788
|
+
try:
|
|
2789
|
+
F, mask = cv2.findFundamentalMat(
|
|
2790
|
+
points1=p1.T, points2=p2.T, method=points2F_dict[method], **kwargs
|
|
2791
|
+
)
|
|
2792
|
+
except cv2.error as e:
|
|
2793
|
+
# on some degenerate point configurations cv2.findFundamentalMat
|
|
2794
|
+
# raises a raw C++ assertion (eg. a Mat rowRange error) instead
|
|
2795
|
+
# of returning None -- same underlying cause as the F is None
|
|
2796
|
+
# case below, just surfaced differently by OpenCV
|
|
2797
|
+
raise ValueError(
|
|
2798
|
+
f"cv2.findFundamentalMat could not estimate F from "
|
|
2799
|
+
f"{p1.shape[1]} point correspondences using method={method!r} "
|
|
2800
|
+
f"-- points are likely too degenerate (eg. coplanar, collinear): {e}"
|
|
2801
|
+
) from e
|
|
2802
|
+
|
|
2803
|
+
if F is None or mask is None:
|
|
2804
|
+
# already know p1.shape[1] >= min_points (checked above), so a
|
|
2805
|
+
# failure here means the points themselves are degenerate (eg.
|
|
2806
|
+
# coplanar, collinear), not a raw-count problem
|
|
2807
|
+
raise ValueError(
|
|
2808
|
+
f"cv2.findFundamentalMat could not estimate F from "
|
|
2809
|
+
f"{p1.shape[1]} point correspondences using method={method!r} "
|
|
2810
|
+
"-- points are likely too degenerate (eg. coplanar, collinear)"
|
|
2811
|
+
)
|
|
2729
2812
|
|
|
2730
2813
|
mask = mask.ravel().astype(bool)
|
|
2731
2814
|
|