machinevision-toolbox-python 2.4.0__py3-none-any.whl → 2.4.1__py3-none-any.whl
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.dist-info → machinevision_toolbox_python-2.4.1.dist-info}/METADATA +6 -4
- {machinevision_toolbox_python-2.4.0.dist-info → machinevision_toolbox_python-2.4.1.dist-info}/RECORD +10 -8
- {machinevision_toolbox_python-2.4.0.dist-info → machinevision_toolbox_python-2.4.1.dist-info}/WHEEL +1 -1
- {machinevision_toolbox_python-2.4.0.dist-info → machinevision_toolbox_python-2.4.1.dist-info}/entry_points.txt +0 -1
- machinevisiontoolbox/BundleAdjust.py +983 -990
- machinevisiontoolbox/ImageFiducials.py +16 -8
- machinevisiontoolbox/Sources.py +14 -4
- machinevisiontoolbox/blocks/Icons/README.md +28 -0
- machinevisiontoolbox/blocks/Icons/make_icons.py +111 -0
- {machinevision_toolbox_python-2.4.0.dist-info → machinevision_toolbox_python-2.4.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -429,14 +429,22 @@ class FiducialCollection:
|
|
|
429
429
|
# -------------------------------------------------------------------
|
|
430
430
|
ids = ids.reshape(-1) if ids is not None else ids
|
|
431
431
|
|
|
432
|
-
# filter tags by ID
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
432
|
+
# filter tags by ID, keeping each marker's corners and id together in
|
|
433
|
+
# a single pass so the two lists cannot get out of step (detectMarkers
|
|
434
|
+
# returns ids=None when it finds nothing)
|
|
435
|
+
matches = (
|
|
436
|
+
[]
|
|
437
|
+
if ids is None
|
|
438
|
+
else [
|
|
439
|
+
(corners.T.squeeze(), id)
|
|
440
|
+
for corners, id in zip(cornerss, ids)
|
|
441
|
+
if id in self._ids
|
|
442
|
+
]
|
|
443
|
+
)
|
|
444
|
+
if not matches:
|
|
445
|
+
raise ValueError("no markers belonging to this board were detected")
|
|
446
|
+
cornerss = [corners for corners, _ in matches]
|
|
447
|
+
ids = [id for _, id in matches]
|
|
440
448
|
|
|
441
449
|
# match the markers to the board
|
|
442
450
|
# print(f"{len(ids)} markers found")
|
machinevisiontoolbox/Sources.py
CHANGED
|
@@ -602,7 +602,8 @@ class VideoCamera(ImageSource):
|
|
|
602
602
|
attached video camera. On a Mac running 13.0 (Ventura) or later and
|
|
603
603
|
an iPhone with iOS 16 or later, the Continuity Camera feature allows
|
|
604
604
|
the phone camera to be used as a local video camera, and it will
|
|
605
|
-
appear as a separate camera with its own ID.
|
|
605
|
+
appear as a separate camera with its own ID. Other phones, and cameras
|
|
606
|
+
on the network, can be used through :class:`WebCam`.
|
|
606
607
|
|
|
607
608
|
OpenCV does not expose a portable API for mapping integer ``id``
|
|
608
609
|
values to human-readable camera names. Use :meth:`list` to
|
|
@@ -612,7 +613,7 @@ class VideoCamera(ImageSource):
|
|
|
612
613
|
:references:
|
|
613
614
|
- |RVC3|, Section 11.1.3.
|
|
614
615
|
|
|
615
|
-
:seealso: :meth:`list` :func:`~machinevisiontoolbox.base.imageio.convert`
|
|
616
|
+
:seealso: :meth:`list` :class:`WebCam` :func:`~machinevisiontoolbox.base.imageio.convert`
|
|
616
617
|
`cv2.VideoCapture <https://docs.opencv.org/4.x/d8/dfe/classcv_1_1VideoCapture.html#a57c0e81e83e60f36c83027dc2a188e80>`_
|
|
617
618
|
"""
|
|
618
619
|
|
|
@@ -1729,15 +1730,24 @@ class WebCam(ImageSource):
|
|
|
1729
1730
|
pass
|
|
1730
1731
|
|
|
1731
1732
|
|
|
1732
|
-
.. note::
|
|
1733
|
+
.. note:: Many webcameras accept a query string in the URL to specify
|
|
1733
1734
|
image resolution, image format, codec and other parameters. There
|
|
1734
1735
|
is no common standard for this, see the manufacturer's datasheet
|
|
1735
1736
|
for details.
|
|
1736
1737
|
|
|
1738
|
+
.. note:: The ``url`` is passed to ``cv2.VideoCapture``, so it can be
|
|
1739
|
+
anything that OpenCV can open, for example a still-image URL, a
|
|
1740
|
+
live HTTP (MJPEG) stream or an RTSP stream. A phone can be used as a
|
|
1741
|
+
networked camera in this way: many IP-camera apps for iOS and Android
|
|
1742
|
+
publish a live stream at a URL. Which stream types and URL formats are
|
|
1743
|
+
supported depends on the app and on how your OpenCV was built. On a Mac
|
|
1744
|
+
with an iPhone, Continuity Camera makes the phone appear as a local
|
|
1745
|
+
camera instead, see :class:`VideoCamera`.
|
|
1746
|
+
|
|
1737
1747
|
:references:
|
|
1738
1748
|
- |RVC3|, Section 11.1.5.
|
|
1739
1749
|
|
|
1740
|
-
:seealso: :func:`~machinevisiontoolbox.base.imageio.convert`
|
|
1750
|
+
:seealso: :class:`VideoCamera` :func:`~machinevisiontoolbox.base.imageio.convert`
|
|
1741
1751
|
`cv2.VideoCapture <https://docs.opencv.org/4.x/d8/dfe/classcv_1_1VideoCapture.html#a57c0e81e83e60f36c83027dc2a188e80>`_
|
|
1742
1752
|
"""
|
|
1743
1753
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Block icons
|
|
2
|
+
|
|
3
|
+
Icons for the bdsim blocks in this toolbox. They follow the conventions of
|
|
4
|
+
[bdsim's `Icons` folder](https://github.com/petercorke/bdsim/blob/main/src/bdsim/blocks/Icons/README.md):
|
|
5
|
+
250x250 RGBA PNG, black "ink" on a transparent background, named as the
|
|
6
|
+
lower-case version of the block's class name.
|
|
7
|
+
|
|
8
|
+
| Icon | How it is made |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `visjac_p.png`, `estpose_p.png` | TeX math, rendered by `make_icons.py` (below). The files in git were made with `bdtex2icon` from bdsim, which needs LaTeX; `make_icons.py` makes the same glyphs, with slightly different size and placement. |
|
|
11
|
+
| `camera.png`, `imageplane.png` | not made by `make_icons.py` |
|
|
12
|
+
|
|
13
|
+
## Creating icons from TeX
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
python make_icons.py # all icons in the table at the top of the script
|
|
17
|
+
python make_icons.py visjac_p # one icon
|
|
18
|
+
python make_icons.py --outdir /tmp # write somewhere else, e.g. to compare
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This needs only matplotlib and Pillow, which this toolbox already depends on,
|
|
22
|
+
and no LaTeX installation. To add an icon, add a line to the `ICONS` table in
|
|
23
|
+
`make_icons.py`. The math is rendered by matplotlib's `mathtext`, which
|
|
24
|
+
implements a subset of TeX and does not know the custom macros of the RVC
|
|
25
|
+
notation file (`\mat`, `\pose`, ...), so write those expanded, for example
|
|
26
|
+
`\mathbf{J}_p` rather than `\mat{J}_p`.
|
|
27
|
+
|
|
28
|
+
Running the script overwrites the icons of the same name in this folder.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Create block icons from TeX math, using only matplotlib and Pillow.
|
|
4
|
+
|
|
5
|
+
Run it from anywhere to (re)create the icons listed in :data:`ICONS`::
|
|
6
|
+
|
|
7
|
+
$ python make_icons.py # all icons, written next to this script
|
|
8
|
+
$ python make_icons.py visjac_p # just one
|
|
9
|
+
$ python make_icons.py --outdir /tmp/icons # somewhere else
|
|
10
|
+
|
|
11
|
+
Each icon is a 250x250 RGBA PNG: black "ink" on a transparent background, the
|
|
12
|
+
size and style used by the bdsim block icons (see the README in this folder).
|
|
13
|
+
|
|
14
|
+
The math is rendered by matplotlib's ``mathtext`` engine with the Computer
|
|
15
|
+
Modern font set, so no LaTeX installation is needed. ``mathtext`` implements a
|
|
16
|
+
subset of TeX, and not the custom macros (``\\mat``, ``\\pose``, ...) from the
|
|
17
|
+
``rvc-notation`` file that ``bdtex2icon`` (part of bdsim) uses, so the entries
|
|
18
|
+
in :data:`ICONS` are written with those macros expanded.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import io
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
import matplotlib
|
|
26
|
+
|
|
27
|
+
matplotlib.use("Agg")
|
|
28
|
+
|
|
29
|
+
import numpy as np
|
|
30
|
+
from matplotlib import mathtext, rc_context
|
|
31
|
+
from PIL import Image
|
|
32
|
+
|
|
33
|
+
IMSIZE = 250
|
|
34
|
+
|
|
35
|
+
#: icon file stem -> TeX math (without the surrounding ``$``)
|
|
36
|
+
ICONS: dict[str, str] = {
|
|
37
|
+
"visjac_p": r"\mathbf{J}_p",
|
|
38
|
+
"estpose_p": r"\xi(p, P)",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def tex_icon(
|
|
43
|
+
tex: str, path: Path, size: int = IMSIZE, margin: float = 0.08, dpi: int = 600
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Render TeX math to a square, transparent, black-ink PNG icon.
|
|
46
|
+
|
|
47
|
+
:param tex: TeX math expression, without the surrounding ``$``
|
|
48
|
+
:param path: output file
|
|
49
|
+
:param size: width and height of the icon in pixels, defaults to 250
|
|
50
|
+
:param margin: blank border as a fraction of ``size``, on each side,
|
|
51
|
+
defaults to 0.08
|
|
52
|
+
:param dpi: resolution of the intermediate rendering, defaults to 600
|
|
53
|
+
|
|
54
|
+
The expression is rendered, cropped to its ink, scaled to fit inside the
|
|
55
|
+
margin, and centered on a transparent canvas.
|
|
56
|
+
"""
|
|
57
|
+
buf = io.BytesIO()
|
|
58
|
+
with rc_context({"mathtext.fontset": "cm"}): # Computer Modern, as LaTeX
|
|
59
|
+
mathtext.math_to_image(f"${tex}$", buf, dpi=dpi, format="png", color="black")
|
|
60
|
+
|
|
61
|
+
# math_to_image saves a figure, so it is black ink on an *opaque white*
|
|
62
|
+
# background. The ink is black, so how dark a pixel is gives its coverage,
|
|
63
|
+
# and that is all the icon needs: black everywhere, with this as the alpha.
|
|
64
|
+
# Working on one channel also avoids mixing colors into anti-aliased edges.
|
|
65
|
+
alpha = 255 - np.asarray(Image.open(buf).convert("L"))
|
|
66
|
+
ys, xs = np.nonzero(alpha)
|
|
67
|
+
ink = Image.fromarray(alpha[ys.min() : ys.max() + 1, xs.min() : xs.max() + 1])
|
|
68
|
+
|
|
69
|
+
scale = size * (1 - 2 * margin) / max(ink.size)
|
|
70
|
+
ink = ink.resize(
|
|
71
|
+
(max(1, round(ink.width * scale)), max(1, round(ink.height * scale))),
|
|
72
|
+
Image.LANCZOS,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
canvas = Image.new("L", (size, size), 0)
|
|
76
|
+
canvas.paste(ink, ((size - ink.width) // 2, (size - ink.height) // 2))
|
|
77
|
+
|
|
78
|
+
icon = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
|
79
|
+
icon.putalpha(canvas)
|
|
80
|
+
icon.save(path)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def main() -> None:
|
|
84
|
+
parser = argparse.ArgumentParser(description=__doc__.strip().splitlines()[0])
|
|
85
|
+
parser.add_argument(
|
|
86
|
+
"names",
|
|
87
|
+
nargs="*",
|
|
88
|
+
metavar="name",
|
|
89
|
+
help=f"icons to create, one or more of {', '.join(ICONS)}; default is all",
|
|
90
|
+
)
|
|
91
|
+
parser.add_argument(
|
|
92
|
+
"--outdir",
|
|
93
|
+
type=Path,
|
|
94
|
+
default=Path(__file__).resolve().parent,
|
|
95
|
+
help="output directory, defaults to the directory containing this script",
|
|
96
|
+
)
|
|
97
|
+
args = parser.parse_args()
|
|
98
|
+
|
|
99
|
+
unknown = [n for n in args.names if n not in ICONS]
|
|
100
|
+
if unknown:
|
|
101
|
+
parser.error(f"unknown icon(s) {unknown}, choose from {list(ICONS)}")
|
|
102
|
+
|
|
103
|
+
args.outdir.mkdir(parents=True, exist_ok=True)
|
|
104
|
+
for name in args.names or ICONS:
|
|
105
|
+
path = args.outdir / f"{name}.png"
|
|
106
|
+
tex_icon(ICONS[name], path)
|
|
107
|
+
print(f"Written: {path}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
main()
|
|
File without changes
|