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.
@@ -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
- cornerss = [
434
- corners.T.squeeze()
435
- for corners, id in zip(cornerss, ids)
436
- if id in self._ids
437
- ]
438
- ids = [id for corners, id in zip(cornerss, ids) if id in self._ids]
439
- # ids = np.reshape(ids, (-1, 1))
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")
@@ -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:: Manu webcameras accept a query string in the URL to specify
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()