machinevision-toolbox-python 2.3.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.3.0.dist-info → machinevision_toolbox_python-2.4.1.dist-info}/METADATA +8 -6
- {machinevision_toolbox_python-2.3.0.dist-info → machinevision_toolbox_python-2.4.1.dist-info}/RECORD +24 -22
- {machinevision_toolbox_python-2.3.0.dist-info → machinevision_toolbox_python-2.4.1.dist-info}/WHEEL +1 -1
- {machinevision_toolbox_python-2.3.0.dist-info → machinevision_toolbox_python-2.4.1.dist-info}/entry_points.txt +0 -1
- machinevisiontoolbox/BagOfWords.py +13 -1
- machinevisiontoolbox/BundleAdjust.py +983 -980
- machinevisiontoolbox/Camera.py +101 -18
- machinevisiontoolbox/ImageConstants.py +27 -6
- machinevisiontoolbox/ImageCore.py +69 -8
- machinevisiontoolbox/ImageFiducials.py +16 -8
- machinevisiontoolbox/ImageIO.py +8 -0
- machinevisiontoolbox/ImagePointFeatures.py +13 -2
- machinevisiontoolbox/ImageWholeFeatures.py +18 -5
- machinevisiontoolbox/Kernel.py +29 -0
- machinevisiontoolbox/PointCloud.py +17 -4
- machinevisiontoolbox/Sources.py +18 -9
- machinevisiontoolbox/base/__init__.py +1 -0
- machinevisiontoolbox/base/color.py +42 -16
- machinevisiontoolbox/base/imageio.py +541 -443
- machinevisiontoolbox/base/types.py +14 -0
- machinevisiontoolbox/bin/mvtbtool.py +2 -1
- machinevisiontoolbox/blocks/Icons/README.md +28 -0
- machinevisiontoolbox/blocks/Icons/make_icons.py +111 -0
- {machinevision_toolbox_python-2.3.0.dist-info → machinevision_toolbox_python-2.4.1.dist-info}/licenses/LICENSE +0 -0
machinevisiontoolbox/Kernel.py
CHANGED
|
@@ -115,6 +115,35 @@ class Kernel:
|
|
|
115
115
|
def shape(self) -> tuple[int, int]:
|
|
116
116
|
return self.K.shape
|
|
117
117
|
|
|
118
|
+
@property
|
|
119
|
+
def ndim(self) -> int:
|
|
120
|
+
return 2
|
|
121
|
+
|
|
122
|
+
def __array__(self, dtype: Dtype | None = None, copy: bool | None = None) -> np.ndarray:
|
|
123
|
+
"""
|
|
124
|
+
Convert to a plain NumPy array
|
|
125
|
+
|
|
126
|
+
:param dtype: dtype of the returned array, defaults to the kernel's own dtype
|
|
127
|
+
:type dtype: numpy dtype, optional
|
|
128
|
+
:param copy: force a copy of the underlying data, defaults to None
|
|
129
|
+
:type copy: bool, optional
|
|
130
|
+
:return: kernel weighting matrix
|
|
131
|
+
:rtype: ndarray(N,M)
|
|
132
|
+
|
|
133
|
+
Implements the NumPy array protocol, used by ``np.asarray(K)`` and by
|
|
134
|
+
any NumPy/SciPy/Matplotlib function that calls ``np.asarray()``
|
|
135
|
+
internally on whatever it's given (e.g. ``scipy.signal.convolve2d``,
|
|
136
|
+
``Axes3D.plot_surface``, ``np.linalg.svd``). Without this, such a call
|
|
137
|
+
would silently coerce a :class:`Kernel` into a useless 0-d object
|
|
138
|
+
array instead of its weighting matrix.
|
|
139
|
+
"""
|
|
140
|
+
arr = self.K
|
|
141
|
+
if dtype is not None:
|
|
142
|
+
arr = arr.astype(dtype)
|
|
143
|
+
if copy:
|
|
144
|
+
arr = arr.copy()
|
|
145
|
+
return arr
|
|
146
|
+
|
|
118
147
|
def print(
|
|
119
148
|
self, fmt: str | None = None, separator: str = " ", precision: int = 2
|
|
120
149
|
) -> None:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Thin wrapper around Open3D point-cloud objects with MVTB look and feel.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
from warnings import warn
|
|
6
7
|
from typing import Any, Callable
|
|
7
8
|
|
|
@@ -26,7 +27,7 @@ class PointCloud:
|
|
|
26
27
|
:type arg: :obj:`o3d.geometry.PointCloud`, :obj:`~machinevisiontoolbox.PointCloud`, ndarray(3,N)
|
|
27
28
|
:param depth_scale: depth scale factor, defaults to 1.0
|
|
28
29
|
:type depth_scale: float, optional
|
|
29
|
-
:raises RuntimeError: PointCloud class requires Open3D to be installed: pip install open3d
|
|
30
|
+
:raises RuntimeError: PointCloud class requires Open3D to be installed: pip install open3d or pip install machinevision-toolbox-python[open3d]
|
|
30
31
|
:raises ValueError: bad arguments
|
|
31
32
|
|
|
32
33
|
This object wraps an Open3D :obj:`open3d.geometry.PointCloud` object. It can be
|
|
@@ -50,7 +51,7 @@ class PointCloud:
|
|
|
50
51
|
if not _open3d:
|
|
51
52
|
raise RuntimeError(
|
|
52
53
|
"PointCloud class requires Open3D to be installed: "
|
|
53
|
-
"pip install open3d
|
|
54
|
+
"pip install open3d or "
|
|
54
55
|
"pip install machinevision-toolbox-python[open3d]\n"
|
|
55
56
|
" ** Note that Open3d typically does not run on the latest Python version(s)"
|
|
56
57
|
)
|
|
@@ -60,7 +61,7 @@ class PointCloud:
|
|
|
60
61
|
if not _open3d:
|
|
61
62
|
raise RuntimeError(
|
|
62
63
|
"PointCloud class requires Open3D to be installed: "
|
|
63
|
-
"pip install open3d
|
|
64
|
+
"pip install open3d or "
|
|
64
65
|
"pip install machinevision-toolbox-python[open3d]"
|
|
65
66
|
)
|
|
66
67
|
|
|
@@ -244,10 +245,22 @@ class PointCloud:
|
|
|
244
245
|
the ``data`` folder of the
|
|
245
246
|
`mvtb_data package <https://github.com/petercorke/machinevision-toolbox-python/tree/master/packages/mvtb-data>`_.
|
|
246
247
|
|
|
248
|
+
.. note:: ``filename`` may be given as a bare name (``'bunny.ply'``)
|
|
249
|
+
or with a redundant leading ``data/`` (``'data/bunny.ply'``, the
|
|
250
|
+
older convention some book examples still use) -- both resolve
|
|
251
|
+
to the same file.
|
|
252
|
+
|
|
247
253
|
"""
|
|
248
254
|
|
|
249
255
|
from machinevisiontoolbox import mvtb_path_to_datafile
|
|
250
256
|
|
|
257
|
+
# accept a caller-supplied redundant "data/" prefix (older
|
|
258
|
+
# convention) without double-joining it against the "data" this
|
|
259
|
+
# method already prepends
|
|
260
|
+
parts = Path(filename).parts
|
|
261
|
+
if parts and parts[0] == "data":
|
|
262
|
+
filename = str(Path(*parts[1:])) if len(parts) > 1 else ""
|
|
263
|
+
|
|
251
264
|
filename = mvtb_path_to_datafile("data", filename, string=True)
|
|
252
265
|
pcd = o3d.io.read_point_cloud(filename, *args, **kwargs)
|
|
253
266
|
return cls(pcd)
|
|
@@ -273,7 +286,7 @@ class PointCloud:
|
|
|
273
286
|
:type rgb: :class:`~machinevisiontoolbox.Image`, optional
|
|
274
287
|
:param depth_scale: depth scale factor, defaults to 1.0
|
|
275
288
|
:type depth_scale: float, optional
|
|
276
|
-
:raises RuntimeError: PointCloud class requires Open3D to be installed: pip install open3d
|
|
289
|
+
:raises RuntimeError: PointCloud class requires Open3D to be installed: pip install open3d or pip install machinevision-toolbox-python[open3d]
|
|
277
290
|
:raises ValueError: input images has wrong number of planes
|
|
278
291
|
|
|
279
292
|
The depth image is converted to a point cloud using the Open3D, and the pixel
|
machinevisiontoolbox/Sources.py
CHANGED
|
@@ -25,7 +25,6 @@ import warnings
|
|
|
25
25
|
import zipfile
|
|
26
26
|
from collections import Counter, deque
|
|
27
27
|
from collections.abc import Iterator
|
|
28
|
-
from numpy.char import array
|
|
29
28
|
from tqdm import tqdm
|
|
30
29
|
from datetime import datetime, timezone
|
|
31
30
|
from typing import TYPE_CHECKING, IO, Any, Literal, cast
|
|
@@ -603,7 +602,8 @@ class VideoCamera(ImageSource):
|
|
|
603
602
|
attached video camera. On a Mac running 13.0 (Ventura) or later and
|
|
604
603
|
an iPhone with iOS 16 or later, the Continuity Camera feature allows
|
|
605
604
|
the phone camera to be used as a local video camera, and it will
|
|
606
|
-
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`.
|
|
607
607
|
|
|
608
608
|
OpenCV does not expose a portable API for mapping integer ``id``
|
|
609
609
|
values to human-readable camera names. Use :meth:`list` to
|
|
@@ -613,7 +613,7 @@ class VideoCamera(ImageSource):
|
|
|
613
613
|
:references:
|
|
614
614
|
- |RVC3|, Section 11.1.3.
|
|
615
615
|
|
|
616
|
-
:seealso: :meth:`list` :func:`~machinevisiontoolbox.base.imageio.convert`
|
|
616
|
+
:seealso: :meth:`list` :class:`WebCam` :func:`~machinevisiontoolbox.base.imageio.convert`
|
|
617
617
|
`cv2.VideoCapture <https://docs.opencv.org/4.x/d8/dfe/classcv_1_1VideoCapture.html#a57c0e81e83e60f36c83027dc2a188e80>`_
|
|
618
618
|
"""
|
|
619
619
|
|
|
@@ -1730,15 +1730,24 @@ class WebCam(ImageSource):
|
|
|
1730
1730
|
pass
|
|
1731
1731
|
|
|
1732
1732
|
|
|
1733
|
-
.. note::
|
|
1733
|
+
.. note:: Many webcameras accept a query string in the URL to specify
|
|
1734
1734
|
image resolution, image format, codec and other parameters. There
|
|
1735
1735
|
is no common standard for this, see the manufacturer's datasheet
|
|
1736
1736
|
for details.
|
|
1737
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
|
+
|
|
1738
1747
|
:references:
|
|
1739
1748
|
- |RVC3|, Section 11.1.5.
|
|
1740
1749
|
|
|
1741
|
-
:seealso: :func:`~machinevisiontoolbox.base.imageio.convert`
|
|
1750
|
+
:seealso: :class:`VideoCamera` :func:`~machinevisiontoolbox.base.imageio.convert`
|
|
1742
1751
|
`cv2.VideoCapture <https://docs.opencv.org/4.x/d8/dfe/classcv_1_1VideoCapture.html#a57c0e81e83e60f36c83027dc2a188e80>`_
|
|
1743
1752
|
"""
|
|
1744
1753
|
|
|
@@ -2346,10 +2355,10 @@ class ROSTopic(ImageSource):
|
|
|
2346
2355
|
if not _open3d_available:
|
|
2347
2356
|
raise ImportError(
|
|
2348
2357
|
"open3d is required for PointCloud publish support. "
|
|
2349
|
-
"Install it with: pip install open3d
|
|
2358
|
+
"Install it with: pip install open3d "
|
|
2350
2359
|
"or pip install machinevision-toolbox-python[open3d]"
|
|
2351
2360
|
)
|
|
2352
|
-
|
|
2361
|
+
assert o3d is not None
|
|
2353
2362
|
|
|
2354
2363
|
points = np.asarray(pc._pcd.points)
|
|
2355
2364
|
if points.ndim != 2 or points.shape[1] != 3:
|
|
@@ -3573,7 +3582,7 @@ class ROSBag(ImageSource):
|
|
|
3573
3582
|
if not _open3d_available:
|
|
3574
3583
|
raise ImportError(
|
|
3575
3584
|
"open3d is required to read PointCloud2 messages. "
|
|
3576
|
-
"Install it with: pip install open3d
|
|
3585
|
+
"Install it with: pip install open3d "
|
|
3577
3586
|
"or pip install machinevision-toolbox-python[open3d]"
|
|
3578
3587
|
)
|
|
3579
3588
|
assert o3d is not None
|
|
@@ -3770,7 +3779,7 @@ class PointCloudSequence:
|
|
|
3770
3779
|
if not _open3d_available:
|
|
3771
3780
|
raise ImportError(
|
|
3772
3781
|
"open3d is required for point cloud display. "
|
|
3773
|
-
"Install it with: pip install open3d
|
|
3782
|
+
"Install it with: pip install open3d "
|
|
3774
3783
|
"or pip install machinevision-toolbox-python[open3d]"
|
|
3775
3784
|
)
|
|
3776
3785
|
assert o3d is not None
|
|
@@ -17,6 +17,7 @@ import matplotlib.path as mpath
|
|
|
17
17
|
import matplotlib.pyplot as plt
|
|
18
18
|
import numpy as np
|
|
19
19
|
import spatialmath.base as smb
|
|
20
|
+
from spatialmath import Polygon2
|
|
20
21
|
from scipy import interpolate
|
|
21
22
|
|
|
22
23
|
# import io as io
|
|
@@ -814,8 +815,10 @@ def name2color(
|
|
|
814
815
|
color = (color * np.iinfo(dtype).max).astype(dtype)
|
|
815
816
|
if colororder is not None:
|
|
816
817
|
# reorder the elements of the color if colororder is given
|
|
817
|
-
# colororder is a dict mapping plane name to index
|
|
818
|
-
|
|
818
|
+
# colororder is a dict mapping plane name to index -- fancy
|
|
819
|
+
# index rather than a list comprehension, to keep the
|
|
820
|
+
# ndarray return type promised by this function's contract
|
|
821
|
+
color = color[[colororder[c] for c in colorspace]]
|
|
819
822
|
return color
|
|
820
823
|
except ValueError:
|
|
821
824
|
return None
|
|
@@ -1777,8 +1780,8 @@ def shadow_invariant(
|
|
|
1777
1780
|
return gs
|
|
1778
1781
|
|
|
1779
1782
|
|
|
1780
|
-
def esttheta(im: Any, sharpen: np.ndarray | None = None) ->
|
|
1781
|
-
"""
|
|
1783
|
+
def esttheta(im: Any, sharpen: np.ndarray | None = None) -> float:
|
|
1784
|
+
r"""
|
|
1782
1785
|
Estimate theta for shadow invariance
|
|
1783
1786
|
|
|
1784
1787
|
:param im: input image
|
|
@@ -1790,24 +1793,27 @@ def esttheta(im: Any, sharpen: np.ndarray | None = None) -> None:
|
|
|
1790
1793
|
|
|
1791
1794
|
This is an interactive procedure where the image is displayed and the user
|
|
1792
1795
|
selects a region of homogeneous material (eg. grass or road) that includes
|
|
1793
|
-
areas that are directly lit by the sun and in shadow.
|
|
1796
|
+
areas that are directly lit by the sun and in shadow. The invariant angle
|
|
1797
|
+
θ is found by sweeping candidate angles and choosing the one that
|
|
1798
|
+
minimizes the variance of the resulting invariant image over the selected
|
|
1799
|
+
region -- at the true invariant angle, lit and shadowed pixels of the same
|
|
1800
|
+
material map to (nearly) the same greyscale value.
|
|
1794
1801
|
|
|
1795
1802
|
.. note:: The user selects boundary points by clicking the mouse. After the
|
|
1796
1803
|
last point, hit the Enter key and the region will be closed.
|
|
1804
|
+
|
|
1805
|
+
:seealso: :func:`shadow_invariant`
|
|
1797
1806
|
"""
|
|
1798
1807
|
|
|
1799
1808
|
def pickregion(im: Any) -> np.ndarray:
|
|
1800
|
-
im.disp()
|
|
1809
|
+
h = im.disp()
|
|
1801
1810
|
|
|
1802
|
-
clicks =
|
|
1811
|
+
clicks = h.figure.ginput(n=-1)
|
|
1803
1812
|
|
|
1804
1813
|
xy = np.array(clicks)
|
|
1805
|
-
print(xy)
|
|
1806
1814
|
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
polygon = smb.Polygon2(xy.T)
|
|
1810
|
-
polygon.plot("g")
|
|
1815
|
+
polygon = Polygon2(xy.T)
|
|
1816
|
+
polygon.plot(color="g")
|
|
1811
1817
|
|
|
1812
1818
|
X, Y = im.meshgrid()
|
|
1813
1819
|
inside = polygon.contains(np.c_[X.ravel(), Y.ravel()].T)
|
|
@@ -1816,13 +1822,33 @@ def esttheta(im: Any, sharpen: np.ndarray | None = None) -> None:
|
|
|
1816
1822
|
|
|
1817
1823
|
k_region = pickregion(im)
|
|
1818
1824
|
|
|
1819
|
-
imcol = im.
|
|
1820
|
-
|
|
1825
|
+
imcol = im.view1d()
|
|
1821
1826
|
z = imcol[k_region, :]
|
|
1822
|
-
print(z.shape)
|
|
1823
|
-
|
|
1827
|
+
print(f"{z.shape[0]} pixels selected in the region")
|
|
1828
|
+
|
|
1829
|
+
theta_v = np.arange(0, np.pi, 0.02)
|
|
1830
|
+
sim = np.empty_like(theta_v)
|
|
1831
|
+
for i, θ in enumerate(theta_v):
|
|
1832
|
+
gs = shadow_invariant(z.reshape(-1, 1, 3), θ, sharpen=sharpen).ravel()
|
|
1833
|
+
bad = np.isinf(gs) | np.isnan(gs)
|
|
1834
|
+
sim[i] = np.std(gs[~bad])
|
|
1835
|
+
|
|
1836
|
+
k = np.argmin(sim)
|
|
1837
|
+
theta = theta_v[k]
|
|
1838
|
+
|
|
1839
|
+
plt.figure()
|
|
1840
|
+
plt.plot(theta_v, sim)
|
|
1841
|
+
plt.plot(theta, sim[k], "o")
|
|
1842
|
+
plt.xlabel("invariant line angle (rad)")
|
|
1843
|
+
plt.ylabel("invariance image variance")
|
|
1844
|
+
plt.grid(True)
|
|
1845
|
+
|
|
1846
|
+
print(f"best angle = {theta:.4f} rad")
|
|
1847
|
+
|
|
1824
1848
|
_safe_show(block=True)
|
|
1825
1849
|
|
|
1850
|
+
return theta
|
|
1851
|
+
|
|
1826
1852
|
|
|
1827
1853
|
if __name__ == "__main__": # pragma: no cover
|
|
1828
1854
|
import os.path
|