cv4 0.0.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.
cv4/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ """cv4 is a Python library for computer vision.""" # TODO: Update description
2
+
3
+ __version__ = "0.0.1"
4
+ try:
5
+ import cv2 # pyright: ignore[reportUnusedImport] # noqa: F401
6
+ except ImportError as e:
7
+ raise ImportError(
8
+ "cv4 requires OpenCV. Install it with `pip install cv4[opencv]`, or provide your own cv2 build."
9
+ ) from e
10
+
11
+ from .main import * # noqa: F401,F403
12
+
13
+ def hello() -> str:
14
+ return "Hello from cv4!"
cv4/main.py ADDED
@@ -0,0 +1,242 @@
1
+ from collections.abc import Callable
2
+ from dataclasses import dataclass
3
+ from typing import Final
4
+
5
+ import cv2
6
+ import numpy as np
7
+
8
+
9
+ @dataclass
10
+ class HSV:
11
+ hue: float
12
+ saturation: float
13
+ value: float
14
+
15
+ def as_tuple(self) -> tuple[float, float, float]:
16
+ return (self.hue, self.saturation, self.value)
17
+
18
+ @property
19
+ def np_array(self) -> np.ndarray[np.float64, np.dtype[np.float64]]:
20
+ return np.array(self.as_tuple())
21
+
22
+
23
+ @dataclass
24
+ class RGB:
25
+ red: float
26
+ green: float
27
+ blue: float
28
+
29
+ def as_tuple(self) -> tuple[float, float, float]:
30
+ return (self.red, self.green, self.blue)
31
+
32
+ def as_bgr(self) -> "BGR":
33
+ return BGR(self.blue, self.green, self.red)
34
+
35
+
36
+ @dataclass
37
+ class Point:
38
+ x: float
39
+ y: float
40
+
41
+ def as_tuple(self) -> tuple[float, float]:
42
+ return (self.x, self.y)
43
+
44
+ def as_cv2_point(self) -> "cv2.typing.Point":
45
+ return (int(self.x), int(self.y))
46
+
47
+
48
+ @dataclass
49
+ class BGR:
50
+ blue: float
51
+ green: float
52
+ red: float
53
+
54
+ def as_tuple(self) -> tuple[float, float, float]:
55
+ return (self.blue, self.green, self.red)
56
+
57
+ def as_rgb(self) -> RGB:
58
+ return RGB(self.red, self.green, self.blue)
59
+
60
+
61
+ @dataclass
62
+ class HSVRange:
63
+ lower: HSV
64
+ upper: HSV
65
+
66
+
67
+ class Image:
68
+ def __init__(self, image: "cv2.typing.MatLike"):
69
+ self.cv2_image: cv2.typing.MatLike = image
70
+
71
+ def copy(self) -> "Image":
72
+ return self.__class__(self.cv2_image.copy())
73
+
74
+
75
+ class DrawableImage(Image):
76
+ def draw_contours(self, contours: list["Contour"], color: RGB, thickness: int = 2):
77
+ _ = cv2.drawContours(
78
+ self.cv2_image,
79
+ [contour.cv2_image for contour in contours],
80
+ -1,
81
+ color.as_bgr().as_tuple(),
82
+ thickness,
83
+ )
84
+ return self
85
+
86
+ def draw_circle(self, center: Point, radius: int, color: RGB, thickness: int = -1):
87
+ _ = cv2.circle(
88
+ self.cv2_image,
89
+ center.as_cv2_point(),
90
+ radius,
91
+ color.as_bgr().as_tuple(),
92
+ thickness,
93
+ )
94
+ return self
95
+
96
+ def draw_marker( # noqa: PLR0913, PLR0917 # just replicating cv2 API
97
+ self,
98
+ position: Point,
99
+ color: RGB,
100
+ markerType: int = cv2.MARKER_CROSS,
101
+ markerSize: int = 10,
102
+ thickness: int = 2,
103
+ lineType: int = cv2.LINE_AA,
104
+ ):
105
+ _ = cv2.drawMarker(
106
+ self.cv2_image,
107
+ position.as_cv2_point(),
108
+ color.as_bgr().as_tuple(),
109
+ markerType,
110
+ markerSize,
111
+ thickness,
112
+ lineType,
113
+ )
114
+ return self
115
+
116
+
117
+ class BGRImage(DrawableImage):
118
+ def __init__(self, image: "cv2.typing.MatLike"):
119
+ super().__init__(image)
120
+
121
+ @property
122
+ def height(self) -> int:
123
+ return self.cv2_image.shape[0]
124
+
125
+ @property
126
+ def width(self) -> int:
127
+ return self.cv2_image.shape[1]
128
+
129
+ def as_grayscale(self) -> "GrayscaleImage":
130
+ return GrayscaleImage(cv2.cvtColor(self.cv2_image, cv2.COLOR_BGR2GRAY))
131
+
132
+ def as_hsv(self) -> "HSVImage":
133
+ return HSVImage(cv2.cvtColor(self.cv2_image, cv2.COLOR_BGR2HSV))
134
+
135
+ def as_rgb(self) -> "RGBImage":
136
+ return RGBImage(cv2.cvtColor(self.cv2_image, cv2.COLOR_BGR2RGB))
137
+
138
+
139
+ class HSVImage(Image):
140
+ def __init__(self, image: "cv2.typing.MatLike"):
141
+ super().__init__(image)
142
+
143
+ def as_bgr(self) -> "BGRImage":
144
+ return BGRImage(cv2.cvtColor(self.cv2_image, cv2.COLOR_HSV2BGR))
145
+
146
+ def as_rgb(self) -> "RGBImage":
147
+ return RGBImage(cv2.cvtColor(self.cv2_image, cv2.COLOR_HSV2RGB))
148
+
149
+ def mask(self, range: HSVRange) -> "HSVMask":
150
+ return HSVMask(
151
+ cv2.inRange(self.cv2_image, range.lower.np_array, range.upper.np_array),
152
+ range,
153
+ )
154
+
155
+
156
+ class HSVMask(Image):
157
+ def __init__(self, image: "cv2.typing.MatLike", range: HSVRange | None = None):
158
+ super().__init__(image)
159
+
160
+ def bitwise_or(self, other: "HSVMask") -> "HSVMask":
161
+ self.cv2_image = cv2.bitwise_or(self.cv2_image, other.cv2_image)
162
+ return self
163
+
164
+ def clean(self, kernel_size: int) -> "HSVMask":
165
+ kernel = np.ones((kernel_size, kernel_size), np.uint8)
166
+ self.cv2_image = cv2.morphologyEx(self.cv2_image, cv2.MORPH_OPEN, kernel)
167
+ self.cv2_image = cv2.morphologyEx(self.cv2_image, cv2.MORPH_CLOSE, kernel)
168
+ return self
169
+
170
+ def find_contours(self) -> "Contours":
171
+ contours, _ = cv2.findContours(
172
+ self.cv2_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
173
+ )
174
+ return Contours([Contour(contour) for contour in contours])
175
+
176
+
177
+ class Contours(list["Contour"]):
178
+ def __init__(self, contours: list["Contour"]):
179
+ super().__init__(contours)
180
+
181
+ def filter(self, function: Callable[["Contour"], bool]) -> "Contours":
182
+ return Contours([contour for contour in self if function(contour)])
183
+
184
+
185
+ class Contour(Image):
186
+ def __init__(self, image: "cv2.typing.MatLike"):
187
+ super().__init__(image)
188
+ self._moments: Final = cv2.moments(self.cv2_image)
189
+
190
+ @property
191
+ def area(self) -> float:
192
+ return self._moments["m00"]
193
+
194
+ @property
195
+ def moments(self) -> "cv2.typing.Moments":
196
+ return self._moments
197
+
198
+ @property
199
+ def cx(self) -> float:
200
+ return self.centroid.x
201
+
202
+ @property
203
+ def cy(self) -> float:
204
+ return self.centroid.y
205
+
206
+ @property
207
+ def centroid(self) -> Point:
208
+ m00 = self._moments["m00"]
209
+ if abs(m00) > 1e-9: # noqa: PLR2004
210
+ return Point(self._moments["m10"] / m00, self._moments["m01"] / m00)
211
+ # degenerate contour (point / line) the mean of its vertices should be close enough.
212
+ x, y = self.cv2_image.reshape(-1, 2).mean(axis=0)
213
+ return Point(float(x), float(y))
214
+
215
+ @property
216
+ def base_y(self) -> float:
217
+ return float(self.cv2_image[:, :, 1].max()) # pyright: ignore[reportAny]
218
+
219
+
220
+ class RGBImage(DrawableImage):
221
+ def __init__(self, image: "cv2.typing.MatLike"):
222
+ super().__init__(image)
223
+
224
+ def as_bgr(self) -> "BGRImage":
225
+ return BGRImage(cv2.cvtColor(self.cv2_image, cv2.COLOR_RGB2BGR))
226
+
227
+ def as_hsv(self) -> "HSVImage":
228
+ return HSVImage(cv2.cvtColor(self.cv2_image, cv2.COLOR_RGB2HSV))
229
+
230
+ def as_grayscale(self) -> "GrayscaleImage":
231
+ return GrayscaleImage(cv2.cvtColor(self.cv2_image, cv2.COLOR_RGB2GRAY))
232
+
233
+
234
+ class GrayscaleImage(Image):
235
+ def __init__(self, image: "cv2.typing.MatLike"):
236
+ super().__init__(image)
237
+
238
+ def as_bgr(self) -> "BGRImage":
239
+ return BGRImage(cv2.cvtColor(self.cv2_image, cv2.COLOR_GRAY2BGR))
240
+
241
+ def as_rgb(self) -> "RGBImage":
242
+ return RGBImage(cv2.cvtColor(self.cv2_image, cv2.COLOR_GRAY2RGB))
cv4/py.typed ADDED
File without changes
@@ -0,0 +1,90 @@
1
+ Metadata-Version: 2.4
2
+ Name: cv4
3
+ Version: 0.0.1
4
+ Summary: A type-safe, builder-pattern, pythonic wrapper for OpenCV
5
+ Author: kytpbs
6
+ Author-email: kytpbs <email@example.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Dist: numpy>=2.0.0
10
+ Requires-Dist: opencv-contrib-python>=5.0.0.93 ; extra == 'contrib'
11
+ Requires-Dist: opencv-contrib-python-headless>=5.0.0.93 ; extra == 'contrib-headless'
12
+ Requires-Dist: opencv-python>=5.0.0.93 ; extra == 'opencv'
13
+ Requires-Dist: opencv-python-headless>=5.0.0.93 ; extra == 'opencv-headless'
14
+ Requires-Python: >=3.11
15
+ Project-URL: Homepage, https://github.com/kytpbs/cv4
16
+ Provides-Extra: contrib
17
+ Provides-Extra: contrib-headless
18
+ Provides-Extra: opencv
19
+ Provides-Extra: opencv-headless
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Type Safe OpenCV
23
+
24
+ A simple wrapper around opencv to make it type-safe and also follow pythonic syntax
25
+ using builder pattern and similar systems.
26
+
27
+ ## Example
28
+
29
+ <table>
30
+ <tr>
31
+ <th>The old way: raw <code>cv2</code></th>
32
+ <th>The builder pattern: <code>cv4</code></th>
33
+ </tr>
34
+ <tr>
35
+ <td>
36
+
37
+ ```python
38
+ hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
39
+ mask = cv2.inRange(hsv, lower, upper)
40
+
41
+ kernel = np.ones((5, 5), np.uint8)
42
+ mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
43
+ mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
44
+
45
+ # nothing stops you passing the HSV image here
46
+ # by mistake it runs, but the result is very wrong
47
+ contours, _ = cv2.findContours(
48
+ hsv, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
49
+
50
+ for c in contours:
51
+ if cv2.contourArea(c) < MIN_AREA:
52
+ continue
53
+ m = cv2.moments(c)
54
+ cx = m["m10"] / m["m00"] # can divide by zero
55
+ ```
56
+
57
+ </td>
58
+ <td>
59
+
60
+ ```python
61
+ contours = (
62
+ hsv.mask(BLUE)
63
+ .clean(5)
64
+ .find_contours()
65
+ .filter(lambda c: c.area > MIN_AREA)
66
+ .filter(lambda c: c.base_y >= MIN_Y)
67
+ )
68
+
69
+ # find_contours() lives ONLY on HSVMask.
70
+ # hsv.find_contours() does not exist: the
71
+ # editor won't autocomplete it, and the type
72
+ # checker rejects it before you ever run.
73
+
74
+ for c in contours:
75
+ cx = c.cx # centroid, typed
76
+ base = c.base_y # ground contact
77
+ ```
78
+
79
+ </td>
80
+ </tr>
81
+ </table>
82
+
83
+ Where `hsv` and `BLUE` come from:
84
+
85
+ ```python
86
+ from cv4 import BGRImage, HSV, HSVRange
87
+
88
+ BLUE = HSVRange(lower=HSV(100, 150, 50), upper=HSV(130, 255, 255))
89
+ hsv = BGRImage(frame).as_hsv() # frame: a BGR image from cv2
90
+ ```
@@ -0,0 +1,7 @@
1
+ cv4/__init__.py,sha256=vyvmbNJ-6Zbxn5LXH3B1ymObsLkanrC5Z6xNYMzGATY,430
2
+ cv4/main.py,sha256=u2Vx_G5i0ehiV6bjaiQnWd8MxVuV6j5U8Xe3ppJX3x4,6682
3
+ cv4/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ cv4-0.0.1.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086
5
+ cv4-0.0.1.dist-info/WHEEL,sha256=1dD_GoEqEIxQ7VnrUExgdj_eExY4b4UgfR0iBlGKVac,80
6
+ cv4-0.0.1.dist-info/METADATA,sha256=Nwm8RjyMIeZKi56HG4hybvQKwb0JdaGaHAYCLcLcyXc,2319
7
+ cv4-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.13.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Sebastián Ramírez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.