machinevision-toolbox-python 1.0.1__py3-none-any.whl → 1.0.3__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-1.0.1.dist-info → machinevision_toolbox_python-1.0.3.dist-info}/METADATA +30 -30
- {machinevision_toolbox_python-1.0.1.dist-info → machinevision_toolbox_python-1.0.3.dist-info}/RECORD +28 -17
- {machinevision_toolbox_python-1.0.1.dist-info → machinevision_toolbox_python-1.0.3.dist-info}/WHEEL +1 -2
- {machinevision_toolbox_python-1.0.1.dist-info → machinevision_toolbox_python-1.0.3.dist-info}/entry_points.txt +1 -0
- machinevisiontoolbox/ImageBlobs.py +177 -87
- machinevisiontoolbox/ImageCore.py +181 -35
- machinevisiontoolbox/ImageFiducials.py +208 -51
- machinevisiontoolbox/ImageIO.py +3 -5
- machinevisiontoolbox/ImagePointFeatures.py +2 -2
- machinevisiontoolbox/ImageReshape.py +51 -12
- machinevisiontoolbox/PointCloud.py +2 -3
- machinevisiontoolbox/Sources.py +0 -1
- machinevisiontoolbox/aruco0.pdf +0 -0
- machinevisiontoolbox/aruco50.pdf +0 -0
- machinevisiontoolbox/base/color.py +25 -18
- machinevisiontoolbox/base/graphics.py +324 -243
- machinevisiontoolbox/base/imageio.py +99 -20
- machinevisiontoolbox/bin/ce.py +27 -0
- machinevisiontoolbox/bin/imtool.py +161 -0
- machinevisiontoolbox/bin/mvtbtool.py +192 -0
- machinevisiontoolbox/bin/tagtool.py +250 -0
- machinevisiontoolbox/blocks/Icons/camera.png +0 -0
- machinevisiontoolbox/blocks/Icons/estpose_p.png +0 -0
- machinevisiontoolbox/blocks/Icons/icons.sh +4 -0
- machinevisiontoolbox/blocks/Icons/imageplane.png +0 -0
- machinevisiontoolbox/blocks/Icons/visjac_p.png +0 -0
- machinevisiontoolbox/blocks/test_blocks.py +0 -0
- machinevisiontoolbox/machinevision-toolbox-python.code-workspace +7 -0
- machinevision_toolbox_python-1.0.1.dist-info/top_level.txt +0 -1
- {machinevision_toolbox_python-1.0.1.dist-info → machinevision_toolbox_python-1.0.3.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: machinevision-toolbox-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: Python tools for machine vision - education and research
|
|
5
|
-
Author: Dorian Tsai
|
|
6
|
-
Author-email: Peter Corke <rvc@petercorke.com>
|
|
7
5
|
Project-URL: Homepage, https://github.com/petercorke/machinevision-toolbox-python
|
|
8
6
|
Project-URL: Bug Tracker, https://github.com/pypa/sampleproject/issues
|
|
9
7
|
Project-URL: Documentation, https://petercorke.github.io/machinevision-toolbox-python
|
|
10
8
|
Project-URL: GitHub Source, https://github.com/petercorke/machinevision-toolbox-python
|
|
11
9
|
Project-URL: Changelog, https://github.com/petercorke/machinevision-toolbox-python/blob/master/CHANGELOG.md
|
|
12
|
-
|
|
10
|
+
Author: Dorian Tsai
|
|
11
|
+
Author-email: Peter Corke <rvc@petercorke.com>
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: blobs,bundle adjustment,color,computer vision,image features,image segmentation,machine vision,morphology,multiview geometry,open3d,opencv,stereo vision,visual servoing
|
|
13
14
|
Classifier: Development Status :: 5 - Production/Stable
|
|
14
15
|
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier:
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
16
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Requires-Python: >=3.7
|
|
22
|
-
Description-Content-Type: text/markdown
|
|
23
|
-
License-File: LICENSE
|
|
24
|
-
Requires-Dist: numpy<2.0.0,>=1.17.4
|
|
25
|
-
Requires-Dist: scipy
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: ansitable
|
|
26
23
|
Requires-Dist: matplotlib
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist:
|
|
24
|
+
Requires-Dist: mvtb-data
|
|
25
|
+
Requires-Dist: numpy
|
|
29
26
|
Requires-Dist: opencv-contrib-python
|
|
30
|
-
Requires-Dist:
|
|
27
|
+
Requires-Dist: opencv-python
|
|
31
28
|
Requires-Dist: pgraph-python
|
|
32
|
-
Requires-Dist:
|
|
33
|
-
Requires-Dist:
|
|
29
|
+
Requires-Dist: scipy
|
|
30
|
+
Requires-Dist: spatialmath-python
|
|
34
31
|
Provides-Extra: dev
|
|
35
|
-
Requires-Dist:
|
|
36
|
-
Requires-Dist:
|
|
37
|
-
Requires-Dist:
|
|
32
|
+
Requires-Dist: coverage; extra == 'dev'
|
|
33
|
+
Requires-Dist: flake8; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
38
35
|
Provides-Extra: docs
|
|
39
|
-
Requires-Dist:
|
|
40
|
-
Requires-Dist:
|
|
41
|
-
Requires-Dist: sphinx-
|
|
42
|
-
Requires-Dist: sphinx-
|
|
43
|
-
Requires-Dist:
|
|
44
|
-
Requires-Dist: sphinx-
|
|
45
|
-
Requires-Dist:
|
|
36
|
+
Requires-Dist: recommonmark; extra == 'docs'
|
|
37
|
+
Requires-Dist: sphinx; extra == 'docs'
|
|
38
|
+
Requires-Dist: sphinx-autorun; extra == 'docs'
|
|
39
|
+
Requires-Dist: sphinx-favicon; extra == 'docs'
|
|
40
|
+
Requires-Dist: sphinx-markdown-tables; extra == 'docs'
|
|
41
|
+
Requires-Dist: sphinx-rtd-theme; extra == 'docs'
|
|
42
|
+
Requires-Dist: sphinxcontrib-jsmath; extra == 'docs'
|
|
46
43
|
Provides-Extra: jupyter
|
|
47
|
-
Requires-Dist:
|
|
48
|
-
Requires-Dist:
|
|
44
|
+
Requires-Dist: ipympl; extra == 'jupyter'
|
|
45
|
+
Requires-Dist: ipywidgets; extra == 'jupyter'
|
|
46
|
+
Provides-Extra: open3d
|
|
47
|
+
Requires-Dist: open3d-python; extra == 'open3d'
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
49
|
|
|
50
50
|
# Machine Vision Toolbox for Python
|
|
51
51
|
|
{machinevision_toolbox_python-1.0.1.dist-info → machinevision_toolbox_python-1.0.3.dist-info}/RECORD
RENAMED
|
@@ -1,47 +1,58 @@
|
|
|
1
1
|
machinevisiontoolbox/BagOfWords.py,sha256=kHAt6P5Tz5iWpP9N-fcdL3KTwimBqxu9QQ1WRs04Lzc,19680
|
|
2
2
|
machinevisiontoolbox/BundleAdjust.py,sha256=syBFkXwJcRWpNr0k6gu-J3shzH0ozbgOgwaZp844fqk,36081
|
|
3
3
|
machinevisiontoolbox/Camera.py,sha256=Sz6MLiVpLEKX_EtBNcd3AhTjI3Qe_4E9JuB9ERgiugA,136424
|
|
4
|
-
machinevisiontoolbox/ImageBlobs.py,sha256=
|
|
4
|
+
machinevisiontoolbox/ImageBlobs.py,sha256=Fv2-R53MK3TXMSNHNbNaNfoIqgVNNnaNog6Nq5G_h5g,72749
|
|
5
5
|
machinevisiontoolbox/ImageColor.py,sha256=V4T_fs0EvFJegAQrNr-3pR7rg7Dye00g2V3JVEK4oOA,17111
|
|
6
6
|
machinevisiontoolbox/ImageConstants.py,sha256=a9eCbfUFUiQj0HsEH291MjYYmXGJjYSIl6Lg7idDppw,20629
|
|
7
|
-
machinevisiontoolbox/ImageCore.py,sha256=
|
|
8
|
-
machinevisiontoolbox/ImageFiducials.py,sha256=
|
|
9
|
-
machinevisiontoolbox/ImageIO.py,sha256=
|
|
7
|
+
machinevisiontoolbox/ImageCore.py,sha256=rTGHVYw9ayXmDKAXDaLAMuEKVk65aFQqeSUBxt_4qJ8,98404
|
|
8
|
+
machinevisiontoolbox/ImageFiducials.py,sha256=8QwHUrMclP3rLMr2swO44nORo6AJVuIZT777-1QSB44,22341
|
|
9
|
+
machinevisiontoolbox/ImageIO.py,sha256=9MOUSrM1xnkoHYk2W77G0IVPcVjX_YTCLfNg2ZbEFZ4,21935
|
|
10
10
|
machinevisiontoolbox/ImageLineFeatures.py,sha256=IlWSZOrvLnzvLx1vf4GuXb4Icpj214jz9FCDleguV9s,9104
|
|
11
11
|
machinevisiontoolbox/ImageMorph.py,sha256=1xn29MyEkrYRbw-Dg8X6qFKTf4_MDTQ5ddi7y8D-arw,23759
|
|
12
12
|
machinevisiontoolbox/ImageMultiview.py,sha256=wQWcq2Er-EzNNQ7_0mZRuID9VFh4rCjXPxZ-yMiQ-W4,14703
|
|
13
|
-
machinevisiontoolbox/ImagePointFeatures.py,sha256=
|
|
13
|
+
machinevisiontoolbox/ImagePointFeatures.py,sha256=Ran5vzBC3-_brg_X3b-qdXjpX0boEnzznhnkKD8J-wg,75269
|
|
14
14
|
machinevisiontoolbox/ImageProcessing.py,sha256=pZuaVRlpskO2L3JCDh6BOVEfpTUhqKXwPh4usfcTKBM,38123
|
|
15
15
|
machinevisiontoolbox/ImageRegionFeatures.py,sha256=MpqavHwkpevg_dD3zia0wMZMuOmatR2ndZ_TBQ_AbqE,14455
|
|
16
|
-
machinevisiontoolbox/ImageReshape.py,sha256=
|
|
16
|
+
machinevisiontoolbox/ImageReshape.py,sha256=3DAlTBpUdljzJwxyCswRoNiTbGBBP8nTWNpsHoPn03I,47470
|
|
17
17
|
machinevisiontoolbox/ImageSpatial.py,sha256=dxTiml3DLp3UXV5x4auo3IuQQJVXRKrjduOpogIeklE,71856
|
|
18
18
|
machinevisiontoolbox/ImageWholeFeatures.py,sha256=OBGZ_elQ6LlOxN1Hgr4dGf4AOUdgxfAD2qkgAaA7uuU,34874
|
|
19
|
-
machinevisiontoolbox/PointCloud.py,sha256=
|
|
20
|
-
machinevisiontoolbox/Sources.py,sha256=
|
|
19
|
+
machinevisiontoolbox/PointCloud.py,sha256=AMQSXsgVneoEtecUVsvcup009Ja13NcsMZ2kMddyN-E,23455
|
|
20
|
+
machinevisiontoolbox/Sources.py,sha256=mcBYA9ccvqXjPK9npwt9zAnuP18sw-q2zhwem4xW_to,28572
|
|
21
21
|
machinevisiontoolbox/VisualServo.py,sha256=PlHCz_jbvV1JFfDrePQQ7cP94Wr5nliulHtYEMGwJtw,59294
|
|
22
22
|
machinevisiontoolbox/__init__.py,sha256=UcoSG7Cr9Qot3mg2q748jBbo3TUB2lch4VWzOydKe8U,1363
|
|
23
|
+
machinevisiontoolbox/aruco0.pdf,sha256=a_JZT-TfVv7tQNOvlHSZPf9QCNiOScm_q30jMF8T2ME,75233
|
|
24
|
+
machinevisiontoolbox/aruco50.pdf,sha256=wUwqNaDmZ5EFNl1GNF_O3YRztWOZBhTq7DdMdMTskQw,77285
|
|
23
25
|
machinevisiontoolbox/camera_derivatives.py,sha256=uChdvsXC-25Cg7-xH7tIbxe3tcxxJGQ_nslSiCvnL88,17557
|
|
24
26
|
machinevisiontoolbox/decorators.py,sha256=hBZzLFQDhVOS3mj5Rs4GHbIIf30g_C2LO5m2xyLzH7s,2281
|
|
25
27
|
machinevisiontoolbox/fiducial.py,sha256=syC7iIz95nYBTr_njo4ZzLXOyAdAIqoAZq_wRVhyUHQ,1374
|
|
28
|
+
machinevisiontoolbox/machinevision-toolbox-python.code-workspace,sha256=pQBvdNW9qEbMp3qYKhvOgd3NYoO7vqnnbPMh4H8op_Y,44
|
|
26
29
|
machinevisiontoolbox/mvtb_types.py,sha256=66Oa8-J0UyBTtZQ0hH1B9qPqqw7i7dTCYe3CvgnGpXg,434
|
|
27
30
|
machinevisiontoolbox/newcameras.py,sha256=iQ8Fduiub4Clh1JIEVvQTXZVqXrmgzf0Y5iMPGTOW-w,1225
|
|
28
31
|
machinevisiontoolbox/base/__init__.py,sha256=nQTxEyan6bOvovQKUWjag-VZzfM6P20CwUv7sbCrJEs,1547
|
|
29
|
-
machinevisiontoolbox/base/color.py,sha256=
|
|
32
|
+
machinevisiontoolbox/base/color.py,sha256=GQSQIHUKrKWxuBJ4a6kfTy05RQKUhFKA4q4JyjIU358,55067
|
|
30
33
|
machinevisiontoolbox/base/data.py,sha256=tDwUOHzpvGnkzlUaA3p5jQr1rlv9WIr6taRBv3gnmeU,5988
|
|
31
34
|
machinevisiontoolbox/base/esttheta.py,sha256=pYFguhXwo9FoaH0sRAXxrcEkECYttDPnHWBi1YMfISY,2323
|
|
32
35
|
machinevisiontoolbox/base/findpeaks.py,sha256=e0dcG6ZqHMwpzRFQrZH9-OITzhspi6Lq1TZoS37IPQY,10377
|
|
33
|
-
machinevisiontoolbox/base/graphics.py,sha256=
|
|
34
|
-
machinevisiontoolbox/base/imageio.py,sha256=
|
|
36
|
+
machinevisiontoolbox/base/graphics.py,sha256=L0YuIi-HHOhogtmfwMSy9SD5tG0zdxWAYojbhCrRYCc,28956
|
|
37
|
+
machinevisiontoolbox/base/imageio.py,sha256=qUZL7xz-S3o9Xh8vLVQAWqsL7HQQNtSUROERWsUimSo,40393
|
|
35
38
|
machinevisiontoolbox/base/meshgrid.py,sha256=82u1ijws1zoXl5NLzTAMJjry9VEZuWqb_Akz6f1JJ8k,2937
|
|
36
39
|
machinevisiontoolbox/base/shapes.py,sha256=2FmjeuDnehNF5ZmssFDawmblDpKhuVUI3CmXCcILJxg,9926
|
|
37
40
|
machinevisiontoolbox/base/types.py,sha256=3znb81yTFb7sLrgjgt0P5q5wZa9_iRZGw_hWbINhCxc,4716
|
|
38
41
|
machinevisiontoolbox/base/version.py,sha256=Wf76g7w1Ikff-3e-hlExYVRxvV92p04K1m_PZLH9T_I,875
|
|
42
|
+
machinevisiontoolbox/bin/ce.py,sha256=012zsKMT58WZxIsLq8nzDO6b-YK04kduaTwZLSwTdrI,846
|
|
43
|
+
machinevisiontoolbox/bin/imtool.py,sha256=7nHNMuoI0-5y4EdWWPkWlwKdgKVqDDbYPw2bozICLQk,4920
|
|
44
|
+
machinevisiontoolbox/bin/mvtbtool.py,sha256=ehzLW6mwkNsQJVW599NFZ3k5W3ANm9UHBnAP_wLCnsg,5740
|
|
45
|
+
machinevisiontoolbox/bin/tagtool.py,sha256=Gfre8skzAwZystfGFv18W6rNcnIykvtBoCvik80TWzw,7224
|
|
39
46
|
machinevisiontoolbox/blocks/__init__.py,sha256=EsdfK2WxYLSo0SA5HSmlgsivLT1kDnoH9v-RTBDnWwc,142
|
|
40
47
|
machinevisiontoolbox/blocks/camera.py,sha256=qWT_LoDFcZkCAlRqQkRDzX0fr4EvkEgdtGo0QPoO_8s,9776
|
|
41
48
|
machinevisiontoolbox/blocks/test_blocks.py,sha256=ZKsDG_nG9g1t_OIFR6vX823yJojEAi5mtqj9PxKoDrg,1701
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
machinevision_toolbox_python-1.0.
|
|
49
|
+
machinevisiontoolbox/blocks/Icons/camera.png,sha256=CGcTB_llMoucN-rfnP6ZP_7AfE_92z6JnCdam-hDLgs,1642
|
|
50
|
+
machinevisiontoolbox/blocks/Icons/estpose_p.png,sha256=1XrwL4OkotGWMwKmhnar3tt8cQWUdZiO7PxXD354T-E,4320
|
|
51
|
+
machinevisiontoolbox/blocks/Icons/icons.sh,sha256=teezrmF3es8c3-MvwG1lDCt9SKWEqA0eVHOKFeBugig,115
|
|
52
|
+
machinevisiontoolbox/blocks/Icons/imageplane.png,sha256=34FYQMzkq6foxvRU8pn0KkcHxpHRvsJRJSYKfNu13Eg,3902
|
|
53
|
+
machinevisiontoolbox/blocks/Icons/visjac_p.png,sha256=8X023l-C9Y5DfAN8wCdWYjSjq6wp1vRzuZ85sUbwxv8,3819
|
|
54
|
+
machinevision_toolbox_python-1.0.3.dist-info/METADATA,sha256=Uf9vtJz_jlpAtYMG46K416pz_70ka4ywAK1ggX_B4B4,19851
|
|
55
|
+
machinevision_toolbox_python-1.0.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
56
|
+
machinevision_toolbox_python-1.0.3.dist-info/entry_points.txt,sha256=ZX7Ox5l4VcwNurmTuD85Um5NpAipiKthfVe16cccWU8,162
|
|
57
|
+
machinevision_toolbox_python-1.0.3.dist-info/licenses/LICENSE,sha256=niCzY_CzbIMKuUZlUkGimjrKp0vkLAtcwFmoSuSKrdw,1068
|
|
58
|
+
machinevision_toolbox_python-1.0.3.dist-info/RECORD,,
|
|
@@ -17,7 +17,7 @@ import matplotlib.pyplot as plt
|
|
|
17
17
|
|
|
18
18
|
import cv2 as cv
|
|
19
19
|
from ansitable import ANSITable, Column
|
|
20
|
-
from machinevisiontoolbox.base import
|
|
20
|
+
from machinevisiontoolbox.base import plot_labelbox
|
|
21
21
|
from spatialmath.base import plot_box, plot_point, isscalar
|
|
22
22
|
from spatialmath import SE2, base
|
|
23
23
|
from machinevisiontoolbox.decorators import scalar_result, array_result
|
|
@@ -36,37 +36,6 @@ Defines two key classes:
|
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
_moment_tuple = namedtuple(
|
|
40
|
-
"moments",
|
|
41
|
-
[
|
|
42
|
-
"m00",
|
|
43
|
-
"m10",
|
|
44
|
-
"m01",
|
|
45
|
-
"m20",
|
|
46
|
-
"m11",
|
|
47
|
-
"m02",
|
|
48
|
-
"m30",
|
|
49
|
-
"m21",
|
|
50
|
-
"m12",
|
|
51
|
-
"m03",
|
|
52
|
-
"mu20",
|
|
53
|
-
"mu11",
|
|
54
|
-
"mu02",
|
|
55
|
-
"mu30",
|
|
56
|
-
"mu21",
|
|
57
|
-
"mu12",
|
|
58
|
-
"mu03",
|
|
59
|
-
"nu20",
|
|
60
|
-
"nu11",
|
|
61
|
-
"nu02",
|
|
62
|
-
"nu30",
|
|
63
|
-
"nu21",
|
|
64
|
-
"nu12",
|
|
65
|
-
"nu03",
|
|
66
|
-
],
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
39
|
class Blob:
|
|
71
40
|
id = None
|
|
72
41
|
bbox = None
|
|
@@ -99,7 +68,7 @@ class Blob:
|
|
|
99
68
|
:return: compact string representation
|
|
100
69
|
:rtype: str
|
|
101
70
|
"""
|
|
102
|
-
return f"Blob[{self.id}](area={self.
|
|
71
|
+
return f"Blob[{self.id}](area={self.moments.m00:.2g}, color={self.color}, parent={self.parent.id if self.parent else None}"
|
|
103
72
|
|
|
104
73
|
def __repr__(self):
|
|
105
74
|
return str(self)
|
|
@@ -289,11 +258,6 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
289
258
|
pp = contour[0, :]
|
|
290
259
|
blob.color = image.A[pp[1], pp[0]]
|
|
291
260
|
|
|
292
|
-
## moments
|
|
293
|
-
|
|
294
|
-
# get moments as a dictionary for each contour
|
|
295
|
-
blob._moments = cv.moments(contour)
|
|
296
|
-
|
|
297
261
|
## perimeter, the contour is not closed
|
|
298
262
|
|
|
299
263
|
blob.perimeter = contour.T
|
|
@@ -301,17 +265,82 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
301
265
|
|
|
302
266
|
blob.contourpoint = blob.perimeter[:, 0]
|
|
303
267
|
|
|
304
|
-
##
|
|
305
|
-
|
|
306
|
-
|
|
268
|
+
## moments
|
|
269
|
+
|
|
270
|
+
# get moments as a dictionary for each contour
|
|
271
|
+
moments = cv.moments(contour)
|
|
272
|
+
|
|
273
|
+
## For a single set pixel OpenCV returns all moments as zero, let's fix it
|
|
274
|
+
if moments["m00"] == 0:
|
|
307
275
|
runts += 1
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
276
|
+
|
|
277
|
+
# Raw moments
|
|
278
|
+
for uv in contour:
|
|
279
|
+
for p, q in [
|
|
280
|
+
(0, 0),
|
|
281
|
+
(1, 0),
|
|
282
|
+
(0, 1),
|
|
283
|
+
(2, 0),
|
|
284
|
+
(1, 1),
|
|
285
|
+
(0, 2),
|
|
286
|
+
(3, 0),
|
|
287
|
+
(2, 1),
|
|
288
|
+
(1, 2),
|
|
289
|
+
(0, 3),
|
|
290
|
+
]:
|
|
291
|
+
u = uv[0]
|
|
292
|
+
v = uv[1]
|
|
293
|
+
moments[f"m{p}{q}"] += u**p * v**q
|
|
294
|
+
|
|
295
|
+
# Central moments
|
|
296
|
+
moments["mu10"] = moments["mu01"] = 0
|
|
297
|
+
moments["mu20"] = moments["m20"] - moments["m10"] ** 2 / moments["m00"]
|
|
298
|
+
moments["mu02"] = moments["m02"] - moments["m01"] ** 2 / moments["m00"]
|
|
299
|
+
moments["mu11"] = (
|
|
300
|
+
moments["m11"] - moments["m10"] * moments["m01"] / moments["m00"]
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Third-order central moments
|
|
304
|
+
moments["mu30"] = (
|
|
305
|
+
moments["m30"]
|
|
306
|
+
- 3 * moments["m20"] * moments["m10"] / moments["m00"]
|
|
307
|
+
+ 2 * (moments["m10"] ** 3) / (moments["m00"] ** 2)
|
|
308
|
+
)
|
|
309
|
+
moments["mu03"] = (
|
|
310
|
+
moments["m03"]
|
|
311
|
+
- 3 * moments["m02"] * moments["m01"] / moments["m00"]
|
|
312
|
+
+ 2 * (moments["m01"] ** 3) / (moments["m00"] ** 2)
|
|
313
|
+
)
|
|
314
|
+
moments["mu21"] = (
|
|
315
|
+
moments["m21"]
|
|
316
|
+
- 2 * moments["m11"] * moments["m10"] / moments["m00"]
|
|
317
|
+
- moments["m20"] * moments["m01"] / moments["m00"]
|
|
318
|
+
+ 2 * (moments["m10"] ** 2 * moments["m01"]) / (moments["m00"] ** 2)
|
|
319
|
+
)
|
|
320
|
+
moments["mu12"] = (
|
|
321
|
+
moments["m12"]
|
|
322
|
+
- 2 * moments["m11"] * moments["m01"] / moments["m00"]
|
|
323
|
+
- moments["m02"] * moments["m10"] / moments["m00"]
|
|
324
|
+
+ 2 * (moments["m01"] ** 2 * moments["m10"]) / (moments["m00"] ** 2)
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Normalised central moments
|
|
328
|
+
moments["nu20"] = moments["mu20"] / (moments["m00"] ** (1 + (2 / 2)))
|
|
329
|
+
moments["nu02"] = moments["mu02"] / (moments["m00"] ** (1 + (2 / 2)))
|
|
330
|
+
moments["nu11"] = moments["mu11"] / (moments["m00"] ** (1 + (2 / 2)))
|
|
331
|
+
|
|
332
|
+
# Third-order normalised moments
|
|
333
|
+
moments["nu30"] = moments["mu30"] / (moments["m00"] ** (1 + (3 / 2)))
|
|
334
|
+
moments["nu03"] = moments["mu03"] / (moments["m00"] ** (1 + (3 / 2)))
|
|
335
|
+
moments["nu21"] = moments["mu21"] / (moments["m00"] ** (1 + (3 / 2)))
|
|
336
|
+
moments["nu12"] = moments["mu12"] / (moments["m00"] ** (1 + (3 / 2)))
|
|
337
|
+
|
|
338
|
+
blob.moments = moments
|
|
339
|
+
allblobs.append(blob) # append to the list of all blobs
|
|
311
340
|
|
|
312
341
|
## second pass: equivalent ellipse
|
|
313
342
|
|
|
314
|
-
for blob, contour in zip(
|
|
343
|
+
for blob, contour in zip(allblobs, contours):
|
|
315
344
|
## moment hierarchy
|
|
316
345
|
|
|
317
346
|
# for moments in a hierarchy, for any pq moment of a blob ignoring its
|
|
@@ -320,25 +349,24 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
320
349
|
# use to compute area, centroid etc. for each contour
|
|
321
350
|
|
|
322
351
|
# TODO: this should recurse all the way down
|
|
323
|
-
M = blob.
|
|
352
|
+
M = blob.moments
|
|
324
353
|
for child in blob.children:
|
|
325
354
|
# subtract moments of the child
|
|
326
|
-
M = {key: M[key] - allblobs[child].
|
|
327
|
-
|
|
328
|
-
# convert dict to named tuple, easier to access using dot notation
|
|
329
|
-
M = _moment_tuple._make([M[field] for field in _moment_tuple._fields])
|
|
330
|
-
blob._moments = M
|
|
355
|
+
M = {key: M[key] - allblobs[child].moments[key] for key in M}
|
|
356
|
+
blob.moments = M
|
|
331
357
|
|
|
332
358
|
## centroid
|
|
333
|
-
|
|
334
|
-
|
|
359
|
+
if M["m00"] == 0:
|
|
360
|
+
continue
|
|
335
361
|
|
|
362
|
+
blob.uc = M["m10"] / M["m00"]
|
|
363
|
+
blob.vc = M["m01"] / M["m00"]
|
|
336
364
|
## equivalent ellipse
|
|
337
|
-
J = np.array([[M
|
|
365
|
+
J = np.array([[M["mu20"], M["mu11"]], [M["mu11"], M["mu02"]]])
|
|
338
366
|
e, X = np.linalg.eig(J)
|
|
339
367
|
|
|
340
|
-
blob.a = 2.0 * np.sqrt(e.max() / M
|
|
341
|
-
blob.b = 2.0 * np.sqrt(e.min() / M
|
|
368
|
+
blob.a = 2.0 * np.sqrt(e.max() / M["m00"])
|
|
369
|
+
blob.b = 2.0 * np.sqrt(e.min() / M["m00"])
|
|
342
370
|
|
|
343
371
|
# find eigenvector for largest eigenvalue
|
|
344
372
|
k = np.argmax(e)
|
|
@@ -361,13 +389,19 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
361
389
|
kfactor = kulpa
|
|
362
390
|
else:
|
|
363
391
|
kfactor = 1.0
|
|
364
|
-
blob.circularity = (4.0 * np.pi * M
|
|
392
|
+
blob.circularity = (4.0 * np.pi * M["m00"]) / (
|
|
365
393
|
blob.perimeter_length * kfactor
|
|
366
394
|
) ** 2
|
|
367
395
|
|
|
396
|
+
for blob in allblobs:
|
|
397
|
+
M = blob.moments
|
|
398
|
+
# convert moments dict to named tuple, easier to access using dot notation
|
|
399
|
+
blob.moments = namedtuple("moment_tuple", M.keys())(*M.values())
|
|
400
|
+
|
|
368
401
|
## third pass, region tree coloring to determine vertex depth
|
|
369
|
-
|
|
370
|
-
|
|
402
|
+
# level 0 is a parent, level > 0 is a child
|
|
403
|
+
while any([b.level is None for b in allblobs]): # while some uncolored
|
|
404
|
+
for blob in allblobs:
|
|
371
405
|
if blob.level is None:
|
|
372
406
|
if blob.parent == -1:
|
|
373
407
|
blob.level = 0 # root level
|
|
@@ -377,17 +411,18 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
377
411
|
|
|
378
412
|
self.filter(**kwargs)
|
|
379
413
|
|
|
380
|
-
for blob in
|
|
414
|
+
for blob in allblobs:
|
|
381
415
|
if blob.parent != -1:
|
|
382
416
|
blob.parent = allblobs[blob.parent]
|
|
383
417
|
else:
|
|
384
418
|
blob.parent = None
|
|
385
419
|
if len(blob.children) > 0:
|
|
386
420
|
blob.children = [allblobs[i] for i in blob.children] ##
|
|
387
|
-
|
|
388
421
|
if runts > 0:
|
|
389
422
|
print(f"blobs: found {runts} runt blob{'s' if runts > 1 else ''}")
|
|
390
423
|
|
|
424
|
+
self.data = allblobs
|
|
425
|
+
|
|
391
426
|
return
|
|
392
427
|
|
|
393
428
|
def filter(self, area=None, circularity=None, color=None, touch=None, aspect=None):
|
|
@@ -582,7 +617,7 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
582
617
|
b.id,
|
|
583
618
|
b.parent.id if b.parent else -1,
|
|
584
619
|
f"{b.uc:.1f}, {b.vc:.1f}",
|
|
585
|
-
b.
|
|
620
|
+
b.moments.m00,
|
|
586
621
|
b.touch,
|
|
587
622
|
b.perimeter_length,
|
|
588
623
|
b.circularity,
|
|
@@ -611,7 +646,7 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
611
646
|
>>> blobs[0].area
|
|
612
647
|
>>> blobs.area
|
|
613
648
|
"""
|
|
614
|
-
return [b.
|
|
649
|
+
return [b.moments.m00 for b in self.data]
|
|
615
650
|
|
|
616
651
|
@property
|
|
617
652
|
@scalar_result
|
|
@@ -890,7 +925,7 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
890
925
|
|
|
891
926
|
:seealso: :meth:`bbox`
|
|
892
927
|
"""
|
|
893
|
-
return [b.
|
|
928
|
+
return [b.moments.m00 / (b.bbox[2] * b.bbox[3]) for b in self.data]
|
|
894
929
|
|
|
895
930
|
@property
|
|
896
931
|
@scalar_result
|
|
@@ -1089,9 +1124,34 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
1089
1124
|
|
|
1090
1125
|
A parent of -1 is the image background.
|
|
1091
1126
|
|
|
1092
|
-
:seealso: :meth:`children` :meth:`level` :meth:`dotfile`
|
|
1127
|
+
:seealso: :meth:`id` :meth:`children` :meth:`level` :meth:`dotfile`
|
|
1128
|
+
"""
|
|
1129
|
+
return [b.parent.id if b.parent is not None else -1 for b in self.data]
|
|
1130
|
+
|
|
1131
|
+
@property
|
|
1132
|
+
@scalar_result
|
|
1133
|
+
def id(self):
|
|
1134
|
+
"""
|
|
1135
|
+
Blob id number
|
|
1136
|
+
|
|
1137
|
+
:return: index of this blob
|
|
1138
|
+
:rtype: int
|
|
1139
|
+
|
|
1140
|
+
Example:
|
|
1141
|
+
|
|
1142
|
+
.. runblock:: pycon
|
|
1143
|
+
|
|
1144
|
+
>>> from machinevisiontoolbox import Image
|
|
1145
|
+
>>> im = Image.Read('multiblobs.png')
|
|
1146
|
+
>>> blobs = im.blobs()
|
|
1147
|
+
>>> print(blobs)
|
|
1148
|
+
>>> blobs[5].id
|
|
1149
|
+
>>> blobs[6].id
|
|
1150
|
+
|
|
1151
|
+
|
|
1152
|
+
:seealso: :meth:`parent` :meth:`children` :meth:`level` :meth:`dotfile`
|
|
1093
1153
|
"""
|
|
1094
|
-
return [b.
|
|
1154
|
+
return [b.id for b in self.data]
|
|
1095
1155
|
|
|
1096
1156
|
@property
|
|
1097
1157
|
@array_result
|
|
@@ -1099,8 +1159,7 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
1099
1159
|
"""
|
|
1100
1160
|
Child blobs
|
|
1101
1161
|
|
|
1102
|
-
:return: list of indices of this blob's children
|
|
1103
|
-
:rtype: list of int
|
|
1162
|
+
:return: list of indices of this blob's children :rtype: list of int
|
|
1104
1163
|
|
|
1105
1164
|
Example:
|
|
1106
1165
|
|
|
@@ -1113,7 +1172,7 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
1113
1172
|
|
|
1114
1173
|
:seealso: :meth:`parent` :meth:`level` :meth:`dotfile`
|
|
1115
1174
|
"""
|
|
1116
|
-
return [b.children for b in self.data]
|
|
1175
|
+
return [[c.id for c in b.children] for b in self.data]
|
|
1117
1176
|
|
|
1118
1177
|
@property
|
|
1119
1178
|
@array_result
|
|
@@ -1147,7 +1206,7 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
1147
1206
|
|
|
1148
1207
|
:seealso: :meth:`centroid` :meth:`humoments`
|
|
1149
1208
|
"""
|
|
1150
|
-
return [b.
|
|
1209
|
+
return [b.moments for b in self.data]
|
|
1151
1210
|
|
|
1152
1211
|
@array_result
|
|
1153
1212
|
def humoments(self):
|
|
@@ -1175,7 +1234,7 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
1175
1234
|
"""
|
|
1176
1235
|
|
|
1177
1236
|
def hu(b):
|
|
1178
|
-
m = b.
|
|
1237
|
+
m = b.moments
|
|
1179
1238
|
phi = np.empty((7,))
|
|
1180
1239
|
phi[0] = m.nu20 + m.nu02
|
|
1181
1240
|
phi[1] = (m.nu20 - m.nu02) ** 2 + 4 * m.nu11**2
|
|
@@ -1607,10 +1666,12 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
1607
1666
|
:seealso: :meth:`plot_box` :meth:`plot_centroid` :meth:`plot_perimeter` :func:`~machinevisiontoolbox.base.graphics.plot_labelbox`
|
|
1608
1667
|
"""
|
|
1609
1668
|
|
|
1610
|
-
for blob in
|
|
1669
|
+
for blob in self:
|
|
1611
1670
|
if label is None:
|
|
1612
|
-
|
|
1613
|
-
|
|
1671
|
+
text = f"{blob.id}"
|
|
1672
|
+
else:
|
|
1673
|
+
text = label
|
|
1674
|
+
plot_labelbox(text=text, lrbt=blob.bbox, **kwargs)
|
|
1614
1675
|
|
|
1615
1676
|
def plot_centroid(self, label=False, **kwargs):
|
|
1616
1677
|
"""
|
|
@@ -1736,7 +1797,7 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
1736
1797
|
:seealso: :meth:`plot_axes` :meth:`plot_box` :meth:`plot_centroid` :func:`~spatialmath.base.plot_ellipse`
|
|
1737
1798
|
"""
|
|
1738
1799
|
for blob in self:
|
|
1739
|
-
m = blob.
|
|
1800
|
+
m = blob.moments
|
|
1740
1801
|
# fmt: off
|
|
1741
1802
|
J = np.array([
|
|
1742
1803
|
[m.mu20, m.mu11],
|
|
@@ -2012,7 +2073,7 @@ class Blobs(UserList): # lgtm[py/missing-equals]
|
|
|
2012
2073
|
for id, blob in enumerate(self):
|
|
2013
2074
|
print(' "{:d}"'.format(id), file=f)
|
|
2014
2075
|
print(
|
|
2015
|
-
' "{:d}" -> "{:d}"'.format(blob.parent
|
|
2076
|
+
' "{:d}" -> "{:d}"'.format(blob.parent if blob.parent else -1, id),
|
|
2016
2077
|
file=f,
|
|
2017
2078
|
)
|
|
2018
2079
|
|
|
@@ -2068,15 +2129,37 @@ if __name__ == "__main__":
|
|
|
2068
2129
|
from machinevisiontoolbox import Image
|
|
2069
2130
|
import matplotlib.pyplot as plt
|
|
2070
2131
|
|
|
2071
|
-
im = Image.
|
|
2132
|
+
im = Image.String(
|
|
2133
|
+
r"""
|
|
2134
|
+
..........
|
|
2135
|
+
..###.....
|
|
2136
|
+
..###.....
|
|
2137
|
+
..###.....
|
|
2138
|
+
..........
|
|
2139
|
+
....####..
|
|
2140
|
+
....####..
|
|
2141
|
+
....####..
|
|
2142
|
+
....####..
|
|
2143
|
+
..........
|
|
2144
|
+
""",
|
|
2145
|
+
binary=True,
|
|
2146
|
+
)
|
|
2072
2147
|
blobs = im.blobs()
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2148
|
+
print(blobs)
|
|
2149
|
+
pass
|
|
2150
|
+
|
|
2151
|
+
# im = Image.Read("sharks.png")
|
|
2152
|
+
# blobs = im.blobs()
|
|
2153
|
+
# im.disp()
|
|
2154
|
+
# blobs.plot_labelbox(color="yellow")
|
|
2155
|
+
# plt.show(block=True)
|
|
2156
|
+
# # frames = SE2.Empty()
|
|
2157
|
+
# # for blob in blobs:
|
|
2158
|
+
# # frames.append(SE2(*blob.centroid, blob.orientation))
|
|
2159
|
+
# frames = blobs.blob_frame()
|
|
2160
|
+
# print(frames)
|
|
2161
|
+
# print(blobs[1].moments.m00)
|
|
2162
|
+
# print(blobs.humoments())
|
|
2080
2163
|
|
|
2081
2164
|
# im.disp()
|
|
2082
2165
|
# blobs = im.blobs()
|
|
@@ -2097,10 +2180,10 @@ if __name__ == "__main__":
|
|
|
2097
2180
|
# fillstyle="full",
|
|
2098
2181
|
# )
|
|
2099
2182
|
|
|
2100
|
-
|
|
2183
|
+
im = Image.Read("multiblobs.png")
|
|
2101
2184
|
|
|
2102
|
-
|
|
2103
|
-
#
|
|
2185
|
+
f = im.blobs()
|
|
2186
|
+
# z = f.label_image()
|
|
2104
2187
|
|
|
2105
2188
|
# labels = f.label_image()
|
|
2106
2189
|
# labels.disp(
|
|
@@ -2109,6 +2192,13 @@ if __name__ == "__main__":
|
|
|
2109
2192
|
# colorbar=dict(shrink=0.8, aspect=20 * 0.8),
|
|
2110
2193
|
# block=True,
|
|
2111
2194
|
# )
|
|
2195
|
+
print(f)
|
|
2196
|
+
f2 = f[2]
|
|
2197
|
+
|
|
2198
|
+
print(f2.parent)
|
|
2199
|
+
f1 = f[1]
|
|
2200
|
+
print(f1.children)
|
|
2201
|
+
f.dotfile(show=True)
|
|
2112
2202
|
# pass
|
|
2113
2203
|
|
|
2114
2204
|
# im = Image.Read('sharks.png')
|