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.
Files changed (30) hide show
  1. {machinevision_toolbox_python-1.0.1.dist-info → machinevision_toolbox_python-1.0.3.dist-info}/METADATA +30 -30
  2. {machinevision_toolbox_python-1.0.1.dist-info → machinevision_toolbox_python-1.0.3.dist-info}/RECORD +28 -17
  3. {machinevision_toolbox_python-1.0.1.dist-info → machinevision_toolbox_python-1.0.3.dist-info}/WHEEL +1 -2
  4. {machinevision_toolbox_python-1.0.1.dist-info → machinevision_toolbox_python-1.0.3.dist-info}/entry_points.txt +1 -0
  5. machinevisiontoolbox/ImageBlobs.py +177 -87
  6. machinevisiontoolbox/ImageCore.py +181 -35
  7. machinevisiontoolbox/ImageFiducials.py +208 -51
  8. machinevisiontoolbox/ImageIO.py +3 -5
  9. machinevisiontoolbox/ImagePointFeatures.py +2 -2
  10. machinevisiontoolbox/ImageReshape.py +51 -12
  11. machinevisiontoolbox/PointCloud.py +2 -3
  12. machinevisiontoolbox/Sources.py +0 -1
  13. machinevisiontoolbox/aruco0.pdf +0 -0
  14. machinevisiontoolbox/aruco50.pdf +0 -0
  15. machinevisiontoolbox/base/color.py +25 -18
  16. machinevisiontoolbox/base/graphics.py +324 -243
  17. machinevisiontoolbox/base/imageio.py +99 -20
  18. machinevisiontoolbox/bin/ce.py +27 -0
  19. machinevisiontoolbox/bin/imtool.py +161 -0
  20. machinevisiontoolbox/bin/mvtbtool.py +192 -0
  21. machinevisiontoolbox/bin/tagtool.py +250 -0
  22. machinevisiontoolbox/blocks/Icons/camera.png +0 -0
  23. machinevisiontoolbox/blocks/Icons/estpose_p.png +0 -0
  24. machinevisiontoolbox/blocks/Icons/icons.sh +4 -0
  25. machinevisiontoolbox/blocks/Icons/imageplane.png +0 -0
  26. machinevisiontoolbox/blocks/Icons/visjac_p.png +0 -0
  27. machinevisiontoolbox/blocks/test_blocks.py +0 -0
  28. machinevisiontoolbox/machinevision-toolbox-python.code-workspace +7 -0
  29. machinevision_toolbox_python-1.0.1.dist-info/top_level.txt +0 -1
  30. {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.2
1
+ Metadata-Version: 2.4
2
2
  Name: machinevision-toolbox-python
3
- Version: 1.0.1
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
- Keywords: machine vision,computer vision,multiview geometry,stereo vision,bundle adjustment,visual servoing,image features,color,blobs,morphology,image segmentation,opencv,open3d
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: Programming Language :: Python :: 3.9
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
- Classifier: License :: OSI Approved :: MIT License
20
- Classifier: Operating System :: OS Independent
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: opencv-python
28
- Requires-Dist: open3d
24
+ Requires-Dist: mvtb-data
25
+ Requires-Dist: numpy
29
26
  Requires-Dist: opencv-contrib-python
30
- Requires-Dist: spatialmath-python
27
+ Requires-Dist: opencv-python
31
28
  Requires-Dist: pgraph-python
32
- Requires-Dist: ansitable
33
- Requires-Dist: mvtb-data
29
+ Requires-Dist: scipy
30
+ Requires-Dist: spatialmath-python
34
31
  Provides-Extra: dev
35
- Requires-Dist: pytest; extra == "dev"
36
- Requires-Dist: coverage; extra == "dev"
37
- Requires-Dist: flake8; extra == "dev"
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: sphinx; extra == "docs"
40
- Requires-Dist: recommonmark; extra == "docs"
41
- Requires-Dist: sphinx-rtd-theme; extra == "docs"
42
- Requires-Dist: sphinx-autorun; extra == "docs"
43
- Requires-Dist: sphinxcontrib-jsmath; extra == "docs"
44
- Requires-Dist: sphinx-markdown-tables; extra == "docs"
45
- Requires-Dist: sphinx-favicon; extra == "docs"
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: ipywidgets; extra == "jupyter"
48
- Requires-Dist: ipympl; extra == "jupyter"
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
 
@@ -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=O6ASg_gdiJ7f3HtogzsfDplCNSBTRyaB-8Xat_8Gc0Y,68961
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=WHbd9L36bQFifzn89-IvDv3RH8i2TClK_KCYRgVyZlQ,92320
8
- machinevisiontoolbox/ImageFiducials.py,sha256=h0t5NJYr9M_wLRHifZeU2EStb3QllE7BsmRUXCq6vSk,16207
9
- machinevisiontoolbox/ImageIO.py,sha256=UbTzycJOE2Gf9pkya57-5Kzg3aEvNU4G0M_xFoApYo0,21843
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=mqG3ddncU_s4b_GiPh2EFgS-glBGa--PLjVivtCTyfk,75219
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=mYxqVLMmDUMYsUSf70tZqpEXoUA_65HFcovv2Esg-Bk,45905
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=jTiqcOUe8MlpENHZJs_y9CurK3bwyVv8VIUP6BQ2ybk,23451
20
- machinevisiontoolbox/Sources.py,sha256=Z-cwsn8fuKzwVAPTx8h6Hr4axBhXi5i7NtcrlJKVWHI,28624
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=0O_yyfOg2KV6Xk4gks728bc737rcRH_037MFSQUfYEQ,54329
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=WkqJPLKzZvSIJE5tFQ07Fa5dK8gBmJxGEvCwmxkVtrU,24663
34
- machinevisiontoolbox/base/imageio.py,sha256=mWYqjTEC1sGLR0HL1fwWw0J6EzdeaWRvmnhY2gTAdXM,37418
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
- machinevision_toolbox_python-1.0.1.dist-info/LICENSE,sha256=niCzY_CzbIMKuUZlUkGimjrKp0vkLAtcwFmoSuSKrdw,1068
43
- machinevision_toolbox_python-1.0.1.dist-info/METADATA,sha256=ZNSpL78lcKjOUrPmTmKiy6el3djAvjMZj1dHhtt3rrk,19866
44
- machinevision_toolbox_python-1.0.1.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
45
- machinevision_toolbox_python-1.0.1.dist-info/entry_points.txt,sha256=WkGwGZcQb334xVPQLt1cktOcWL3wXljzp1LkMO1PS30,114
46
- machinevision_toolbox_python-1.0.1.dist-info/top_level.txt,sha256=HC0K-k55L-H_l8QzNMmP1_vLgBkdTrsM0Kj_rjRrulo,21
47
- machinevision_toolbox_python-1.0.1.dist-info/RECORD,,
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,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.1.0)
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-
@@ -1,3 +1,4 @@
1
1
  [console_scripts]
2
2
  imtool = machinevisiontoolbox.bin.imtool:main
3
3
  mvtbtool = machinevisiontoolbox.bin.mvtbtool:main
4
+ tagtool = machinevisiontoolbox.bin.tagtool:main
@@ -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 color_bgr, plot_labelbox
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._moments.m00:.2g}, color={self.color}, parent={self.parent.id if self.parent else None}"
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
- ## For a single set pixel OpenCV returns all moments as zero, skip such blobs
305
- ## TODO handle this situation by setting m00=1, m10=x, m01=y etc.
306
- if blob._moments["m00"] == 0:
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
- else:
309
- self.data.append(blob)
310
- allblobs.append(blob)
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(self.data, contours):
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._moments
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]._moments[key] for key in M}
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
- blob.uc = M.m10 / M.m00
334
- blob.vc = M.m01 / M.m00
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.mu20, M.mu11], [M.mu11, M.mu02]])
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.m00)
341
- blob.b = 2.0 * np.sqrt(e.min() / M.m00)
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.m00) / (
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
- while any([b.level is None for b in self.data]): # while some uncolored
370
- for blob in self.data:
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 self.data:
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._moments.m00,
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._moments.m00 for b in self.data]
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._moments.m00 / (b.bbox[2] * b.bbox[3]) for b in self.data]
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.parent for b in self.data]
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._moments for b in self.data]
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._moments
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 enumerate(self):
1669
+ for blob in self:
1611
1670
  if label is None:
1612
- label = f"{blob.id}"
1613
- plot_labelbox(text=label, lrbt=blob.bbox, **kwargs)
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._moments
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.id if blob.parent else -1, id),
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.Read("sharks.png")
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
- # frames = SE2.Empty()
2074
- # for blob in blobs:
2075
- # frames.append(SE2(*blob.centroid, blob.orientation))
2076
- frames = blobs.blob_frame()
2077
- print(frames)
2078
- print(blobs[1].moments.m00)
2079
- print(blobs.humoments())
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
- # im = Image.Read("multiblobs.png")
2183
+ im = Image.Read("multiblobs.png")
2101
2184
 
2102
- # f = im.blobs()
2103
- # # z = f.label_image()
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')