eye-cv 1.0.0__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 (94) hide show
  1. eye/__init__.py +115 -0
  2. eye/__init___supervision_original.py +120 -0
  3. eye/annotators/__init__.py +0 -0
  4. eye/annotators/base.py +22 -0
  5. eye/annotators/core.py +2699 -0
  6. eye/annotators/line.py +107 -0
  7. eye/annotators/modern.py +529 -0
  8. eye/annotators/trace.py +142 -0
  9. eye/annotators/utils.py +177 -0
  10. eye/assets/__init__.py +2 -0
  11. eye/assets/downloader.py +95 -0
  12. eye/assets/list.py +83 -0
  13. eye/classification/__init__.py +0 -0
  14. eye/classification/core.py +188 -0
  15. eye/config.py +2 -0
  16. eye/core/__init__.py +0 -0
  17. eye/core/trackers/__init__.py +1 -0
  18. eye/core/trackers/botsort_tracker.py +336 -0
  19. eye/core/trackers/bytetrack_tracker.py +284 -0
  20. eye/core/trackers/sort_tracker.py +200 -0
  21. eye/core/tracking.py +146 -0
  22. eye/dataset/__init__.py +0 -0
  23. eye/dataset/core.py +919 -0
  24. eye/dataset/formats/__init__.py +0 -0
  25. eye/dataset/formats/coco.py +258 -0
  26. eye/dataset/formats/pascal_voc.py +279 -0
  27. eye/dataset/formats/yolo.py +272 -0
  28. eye/dataset/utils.py +259 -0
  29. eye/detection/__init__.py +0 -0
  30. eye/detection/auto_convert.py +155 -0
  31. eye/detection/core.py +1529 -0
  32. eye/detection/detections_enhanced.py +392 -0
  33. eye/detection/line_zone.py +859 -0
  34. eye/detection/lmm.py +184 -0
  35. eye/detection/overlap_filter.py +270 -0
  36. eye/detection/tools/__init__.py +0 -0
  37. eye/detection/tools/csv_sink.py +181 -0
  38. eye/detection/tools/inference_slicer.py +288 -0
  39. eye/detection/tools/json_sink.py +142 -0
  40. eye/detection/tools/polygon_zone.py +202 -0
  41. eye/detection/tools/smoother.py +123 -0
  42. eye/detection/tools/smoothing.py +179 -0
  43. eye/detection/tools/smoothing_config.py +202 -0
  44. eye/detection/tools/transformers.py +247 -0
  45. eye/detection/utils.py +1175 -0
  46. eye/draw/__init__.py +0 -0
  47. eye/draw/color.py +154 -0
  48. eye/draw/utils.py +374 -0
  49. eye/filters.py +112 -0
  50. eye/geometry/__init__.py +0 -0
  51. eye/geometry/core.py +128 -0
  52. eye/geometry/utils.py +47 -0
  53. eye/keypoint/__init__.py +0 -0
  54. eye/keypoint/annotators.py +442 -0
  55. eye/keypoint/core.py +687 -0
  56. eye/keypoint/skeletons.py +2647 -0
  57. eye/metrics/__init__.py +21 -0
  58. eye/metrics/core.py +72 -0
  59. eye/metrics/detection.py +843 -0
  60. eye/metrics/f1_score.py +648 -0
  61. eye/metrics/mean_average_precision.py +628 -0
  62. eye/metrics/mean_average_recall.py +697 -0
  63. eye/metrics/precision.py +653 -0
  64. eye/metrics/recall.py +652 -0
  65. eye/metrics/utils/__init__.py +0 -0
  66. eye/metrics/utils/object_size.py +158 -0
  67. eye/metrics/utils/utils.py +9 -0
  68. eye/py.typed +0 -0
  69. eye/quick.py +104 -0
  70. eye/tracker/__init__.py +0 -0
  71. eye/tracker/byte_tracker/__init__.py +0 -0
  72. eye/tracker/byte_tracker/core.py +386 -0
  73. eye/tracker/byte_tracker/kalman_filter.py +205 -0
  74. eye/tracker/byte_tracker/matching.py +69 -0
  75. eye/tracker/byte_tracker/single_object_track.py +178 -0
  76. eye/tracker/byte_tracker/utils.py +18 -0
  77. eye/utils/__init__.py +0 -0
  78. eye/utils/conversion.py +132 -0
  79. eye/utils/file.py +159 -0
  80. eye/utils/image.py +794 -0
  81. eye/utils/internal.py +200 -0
  82. eye/utils/iterables.py +84 -0
  83. eye/utils/notebook.py +114 -0
  84. eye/utils/video.py +307 -0
  85. eye/utils_eye/__init__.py +1 -0
  86. eye/utils_eye/geometry.py +71 -0
  87. eye/utils_eye/nms.py +55 -0
  88. eye/validators/__init__.py +140 -0
  89. eye/web.py +271 -0
  90. eye_cv-1.0.0.dist-info/METADATA +319 -0
  91. eye_cv-1.0.0.dist-info/RECORD +94 -0
  92. eye_cv-1.0.0.dist-info/WHEEL +5 -0
  93. eye_cv-1.0.0.dist-info/licenses/LICENSE +21 -0
  94. eye_cv-1.0.0.dist-info/top_level.txt +1 -0
eye/draw/__init__.py ADDED
File without changes
eye/draw/color.py ADDED
@@ -0,0 +1,154 @@
1
+ """Simple color management - Easy to use."""
2
+
3
+ import numpy as np
4
+ from dataclasses import dataclass
5
+ from typing import Tuple, List
6
+
7
+ @dataclass
8
+ class Color:
9
+ """RGB color."""
10
+ r: int
11
+ g: int
12
+ b: int
13
+
14
+ def as_bgr(self) -> Tuple[int, int, int]:
15
+ """Convert to BGR for OpenCV."""
16
+ return (self.b, self.g, self.r)
17
+
18
+ def as_rgb(self) -> Tuple[int, int, int]:
19
+ """Get as RGB tuple."""
20
+ return (self.r, self.g, self.b)
21
+
22
+ @classmethod
23
+ def from_hex(cls, hex_color: str) -> 'Color':
24
+ """Create from hex string (#RRGGBB)."""
25
+ hex_color = hex_color.lstrip('#')
26
+ return cls(
27
+ int(hex_color[0:2], 16),
28
+ int(hex_color[2:4], 16),
29
+ int(hex_color[4:6], 16)
30
+ )
31
+
32
+ # Predefined colors for supervision compatibility
33
+ Color.ROBOFLOW = Color(255, 107, 0)
34
+ Color.WHITE = Color(255, 255, 255)
35
+ Color.BLACK = Color(0, 0, 0)
36
+ Color.RED = Color(255, 0, 0)
37
+ Color.GREEN = Color(0, 255, 0)
38
+ Color.BLUE = Color(0, 0, 255)
39
+ Color.YELLOW = Color(255, 255, 0)
40
+
41
+
42
+ class Palette:
43
+ """Palette of colors."""
44
+
45
+ def __init__(self, colors: List[Color]):
46
+ self.colors = colors
47
+
48
+ def __len__(self) -> int:
49
+ return len(self.colors)
50
+
51
+ def __getitem__(self, index: int) -> Color:
52
+ """Get color by index with wrapping."""
53
+ return self.colors[index % len(self.colors)]
54
+
55
+ def by_id(self, obj_id: int) -> Color:
56
+ """Get color by object/class ID."""
57
+ return self[obj_id]
58
+
59
+
60
+ # Alias for backward compatibility
61
+ ColorPalette = Palette
62
+
63
+
64
+ class PredefinedPalettes:
65
+ """Predefined color palettes."""
66
+
67
+ @staticmethod
68
+ def bright() -> Palette:
69
+ """Bright, vibrant colors."""
70
+ return Palette([
71
+ Color(255, 0, 0), # Red
72
+ Color(0, 255, 0), # Green
73
+ Color(0, 0, 255), # Blue
74
+ Color(255, 255, 0), # Yellow
75
+ Color(255, 0, 255), # Magenta
76
+ Color(0, 255, 255), # Cyan
77
+ Color(255, 128, 0), # Orange
78
+ Color(128, 0, 255), # Purple
79
+ Color(255, 192, 203), # Pink
80
+ Color(0, 255, 128), # Spring Green
81
+ ])
82
+
83
+ @staticmethod
84
+ def pastel() -> Palette:
85
+ """Soft pastel colors."""
86
+ return Palette([
87
+ Color(255, 179, 186), # Pastel Red
88
+ Color(186, 255, 201), # Pastel Green
89
+ Color(186, 225, 255), # Pastel Blue
90
+ Color(255, 255, 186), # Pastel Yellow
91
+ Color(255, 223, 186), # Pastel Orange
92
+ Color(226, 186, 255), # Pastel Purple
93
+ ])
94
+
95
+ @staticmethod
96
+ def traffic() -> Palette:
97
+ """Traffic/vehicle themed."""
98
+ return Palette([
99
+ Color(220, 20, 60), # Crimson (cars)
100
+ Color(70, 130, 180), # Steel Blue (trucks)
101
+ Color(255, 215, 0), # Gold (buses)
102
+ Color(50, 205, 50), # Lime Green (motorcycles)
103
+ Color(255, 140, 0), # Dark Orange (vans)
104
+ ])
105
+
106
+ @staticmethod
107
+ def monochrome(base: Color, steps: int = 10) -> 'Palette':
108
+ """Monochrome gradient from black to base color."""
109
+ colors = []
110
+ for i in range(steps):
111
+ factor = i / (steps - 1)
112
+ colors.append(Color(
113
+ int(base.r * factor),
114
+ int(base.g * factor),
115
+ int(base.b * factor)
116
+ ))
117
+
118
+ return Palette(colors)
119
+
120
+
121
+ # Palette alias for convenience
122
+ ColorPalette = Palette
123
+
124
+ # Add DEFAULT palette
125
+ Palette.DEFAULT = PredefinedPalettes.bright()
126
+
127
+ # Legacy color palette for backward compatibility
128
+ LEGACY_COLOR_PALETTE = Palette.DEFAULT
129
+
130
+
131
+ # Predefined eye colors
132
+ Color.EYE_ORANGE = Color(255, 107, 0)
133
+ Color.WHITE = Color(255, 255, 255)
134
+ Color.BLACK = Color(0, 0, 0)
135
+ Color.RED = Color(255, 0, 0)
136
+ Color.GREEN = Color(0, 255, 0)
137
+ Color.BLUE = Color(0, 0, 255)
138
+ Color.YELLOW = Color(255, 255, 0)
139
+
140
+
141
+ # Quick color access - Return Color objects
142
+ class Colors:
143
+ """Easy color access with Color objects."""
144
+ RED = Color(255, 0, 0)
145
+ GREEN = Color(0, 255, 0)
146
+ BLUE = Color(0, 0, 255)
147
+ YELLOW = Color(255, 255, 0)
148
+ CYAN = Color(0, 255, 255)
149
+ MAGENTA = Color(255, 0, 255)
150
+ WHITE = Color(255, 255, 255)
151
+ EYE_ORANGE = Color(255, 107, 0)
152
+ BLACK = Color(0, 0, 0)
153
+ ORANGE = Color(255, 165, 0)
154
+ PURPLE = Color(128, 0, 128)
eye/draw/utils.py ADDED
@@ -0,0 +1,374 @@
1
+ import os
2
+ from typing import Optional, Tuple, Union
3
+
4
+ import cv2
5
+ import numpy as np
6
+
7
+ from eye.draw.color import Color
8
+ from eye.geometry.core import Point, Rect
9
+
10
+
11
+ def draw_line(
12
+ scene: np.ndarray,
13
+ start: Point,
14
+ end: Point,
15
+ color: Color = Color.EYE_ORANGE,
16
+ thickness: int = 2,
17
+ ) -> np.ndarray:
18
+ """
19
+ Draws a line on a given scene.
20
+
21
+ Parameters:
22
+ scene (np.ndarray): The scene on which the line will be drawn
23
+ start (Point): The starting point of the line
24
+ end (Point): The end point of the line
25
+ color (Color): The color of the line, defaults to Color.EYE_ORANGE
26
+ thickness (int): The thickness of the line
27
+
28
+ Returns:
29
+ np.ndarray: The scene with the line drawn on it
30
+ """
31
+ cv2.line(
32
+ scene,
33
+ start.as_xy_int_tuple(),
34
+ end.as_xy_int_tuple(),
35
+ color.as_bgr(),
36
+ thickness=thickness,
37
+ )
38
+ return scene
39
+
40
+
41
+ def draw_rectangle(
42
+ scene: np.ndarray, rect: Rect, color: Color = Color.EYE_ORANGE, thickness: int = 2
43
+ ) -> np.ndarray:
44
+ """
45
+ Draws a rectangle on an image.
46
+
47
+ Parameters:
48
+ scene (np.ndarray): The scene on which the rectangle will be drawn
49
+ rect (Rect): The rectangle to be drawn
50
+ color (Color): The color of the rectangle
51
+ thickness (int): The thickness of the rectangle border
52
+
53
+ Returns:
54
+ np.ndarray: The scene with the rectangle drawn on it
55
+ """
56
+ cv2.rectangle(
57
+ scene,
58
+ rect.top_left.as_xy_int_tuple(),
59
+ rect.bottom_right.as_xy_int_tuple(),
60
+ color.as_bgr(),
61
+ thickness=thickness,
62
+ )
63
+ return scene
64
+
65
+
66
+ def draw_filled_rectangle(
67
+ scene: np.ndarray, rect: Rect, color: Color = Color.EYE_ORANGE, opacity: float = 1
68
+ ) -> np.ndarray:
69
+ """
70
+ Draws a filled rectangle on an image.
71
+
72
+ Parameters:
73
+ scene (np.ndarray): The scene on which the rectangle will be drawn
74
+ rect (Rect): The rectangle to be drawn
75
+ color (Color): The color of the rectangle
76
+ opacity (float): The opacity of rectangle when drawn on the scene.
77
+
78
+ Returns:
79
+ np.ndarray: The scene with the rectangle drawn on it
80
+ """
81
+ if opacity == 1:
82
+ cv2.rectangle(
83
+ scene,
84
+ rect.top_left.as_xy_int_tuple(),
85
+ rect.bottom_right.as_xy_int_tuple(),
86
+ color.as_bgr(),
87
+ -1,
88
+ )
89
+ else:
90
+ scene_with_annotations = scene.copy()
91
+ cv2.rectangle(
92
+ scene_with_annotations,
93
+ rect.top_left.as_xy_int_tuple(),
94
+ rect.bottom_right.as_xy_int_tuple(),
95
+ color.as_bgr(),
96
+ -1,
97
+ )
98
+ cv2.addWeighted(
99
+ scene_with_annotations, opacity, scene, 1 - opacity, gamma=0, dst=scene
100
+ )
101
+
102
+ return scene
103
+
104
+
105
+ def draw_rounded_rectangle(
106
+ scene: np.ndarray,
107
+ rect: Rect,
108
+ color: Color,
109
+ border_radius: int,
110
+ ) -> np.ndarray:
111
+ """
112
+ Draws a rounded rectangle on an image.
113
+
114
+ Parameters:
115
+ scene (np.ndarray): The image on which the rounded rectangle will be drawn.
116
+ rect (Rect): The rectangle to be drawn.
117
+ color (Color): The color of the rounded rectangle.
118
+ border_radius (int): The radius of the corner rounding.
119
+
120
+ Returns:
121
+ np.ndarray: The image with the rounded rectangle drawn on it.
122
+ """
123
+ x1, y1, x2, y2 = rect.as_xyxy_int_tuple()
124
+ width, height = x2 - x1, y2 - y1
125
+ border_radius = min(border_radius, min(width, height) // 2)
126
+
127
+ rectangle_coordinates = [
128
+ ((x1 + border_radius, y1), (x2 - border_radius, y2)),
129
+ ((x1, y1 + border_radius), (x2, y2 - border_radius)),
130
+ ]
131
+ circle_centers = [
132
+ (x1 + border_radius, y1 + border_radius),
133
+ (x2 - border_radius, y1 + border_radius),
134
+ (x1 + border_radius, y2 - border_radius),
135
+ (x2 - border_radius, y2 - border_radius),
136
+ ]
137
+
138
+ for coordinates in rectangle_coordinates:
139
+ cv2.rectangle(
140
+ img=scene,
141
+ pt1=coordinates[0],
142
+ pt2=coordinates[1],
143
+ color=color.as_bgr(),
144
+ thickness=-1,
145
+ )
146
+ for center in circle_centers:
147
+ cv2.circle(
148
+ img=scene,
149
+ center=center,
150
+ radius=border_radius,
151
+ color=color.as_bgr(),
152
+ thickness=-1,
153
+ )
154
+ return scene
155
+
156
+
157
+ def draw_polygon(
158
+ scene: np.ndarray,
159
+ polygon: np.ndarray,
160
+ color: Color = Color.EYE_ORANGE,
161
+ thickness: int = 2,
162
+ ) -> np.ndarray:
163
+ """Draw a polygon on a scene.
164
+
165
+ Parameters:
166
+ scene (np.ndarray): The scene to draw the polygon on.
167
+ polygon (np.ndarray): The polygon to be drawn, given as a list of vertices.
168
+ color (Color): The color of the polygon. Defaults to Color.EYE_ORANGE.
169
+ thickness (int): The thickness of the polygon lines, by default 2.
170
+
171
+ Returns:
172
+ np.ndarray: The scene with the polygon drawn on it.
173
+ """
174
+ cv2.polylines(
175
+ scene, [polygon], isClosed=True, color=color.as_bgr(), thickness=thickness
176
+ )
177
+ return scene
178
+
179
+
180
+ def draw_filled_polygon(
181
+ scene: np.ndarray,
182
+ polygon: np.ndarray,
183
+ color: Color = Color.EYE_ORANGE,
184
+ opacity: float = 1,
185
+ ) -> np.ndarray:
186
+ """Draw a filled polygon on a scene.
187
+
188
+ Parameters:
189
+ scene (np.ndarray): The scene to draw the polygon on.
190
+ polygon (np.ndarray): The polygon to be drawn, given as a list of vertices.
191
+ color (Color): The color of the polygon. Defaults to Color.EYE_ORANGE.
192
+ opacity (float): The opacity of polygon when drawn on the scene.
193
+
194
+ Returns:
195
+ np.ndarray: The scene with the polygon drawn on it.
196
+ """
197
+ if opacity == 1:
198
+ cv2.fillPoly(scene, [polygon], color=color.as_bgr())
199
+ else:
200
+ scene_with_annotations = scene.copy()
201
+ cv2.fillPoly(scene_with_annotations, [polygon], color=color.as_bgr())
202
+ cv2.addWeighted(
203
+ scene_with_annotations, opacity, scene, 1 - opacity, gamma=0, dst=scene
204
+ )
205
+
206
+ return scene
207
+
208
+
209
+ def draw_text(
210
+ scene: np.ndarray,
211
+ text: str,
212
+ text_anchor: Point,
213
+ text_color: Color = Color.BLACK,
214
+ text_scale: float = 0.5,
215
+ text_thickness: int = 1,
216
+ text_padding: int = 10,
217
+ text_font: int = cv2.FONT_HERSHEY_SIMPLEX,
218
+ background_color: Optional[Color] = None,
219
+ ) -> np.ndarray:
220
+ """
221
+ Draw text with background on a scene.
222
+
223
+ Parameters:
224
+ scene (np.ndarray): A 2-dimensional numpy ndarray representing an image or scene
225
+ text (str): The text to be drawn.
226
+ text_anchor (Point): The anchor point for the text, represented as a
227
+ Point object with x and y attributes.
228
+ text_color (Color): The color of the text. Defaults to black.
229
+ text_scale (float): The scale of the text. Defaults to 0.5.
230
+ text_thickness (int): The thickness of the text. Defaults to 1.
231
+ text_padding (int): The amount of padding to add around the text
232
+ when drawing a rectangle in the background. Defaults to 10.
233
+ text_font (int): The font to use for the text.
234
+ Defaults to cv2.FONT_HERSHEY_SIMPLEX.
235
+ background_color (Optional[Color]): The color of the background rectangle,
236
+ if one is to be drawn. Defaults to None.
237
+
238
+ Returns:
239
+ np.ndarray: The input scene with the text drawn on it.
240
+
241
+ Examples:
242
+ ```python
243
+ import numpy as np
244
+
245
+ scene = np.zeros((100, 100, 3), dtype=np.uint8)
246
+ text_anchor = Point(x=50, y=50)
247
+ scene = draw_text(scene=scene, text="Hello, world!",text_anchor=text_anchor)
248
+ ```
249
+ """
250
+ text_width, text_height = cv2.getTextSize(
251
+ text=text,
252
+ fontFace=text_font,
253
+ fontScale=text_scale,
254
+ thickness=text_thickness,
255
+ )[0]
256
+
257
+ text_anchor_x, text_anchor_y = text_anchor.as_xy_int_tuple()
258
+
259
+ text_rect = Rect(
260
+ x=text_anchor_x - text_width // 2,
261
+ y=text_anchor_y - text_height // 2,
262
+ width=text_width,
263
+ height=text_height,
264
+ ).pad(text_padding)
265
+
266
+ if background_color is not None:
267
+ scene = draw_filled_rectangle(
268
+ scene=scene, rect=text_rect, color=background_color
269
+ )
270
+
271
+ cv2.putText(
272
+ img=scene,
273
+ text=text,
274
+ org=(text_anchor_x - text_width // 2, text_anchor_y + text_height // 2),
275
+ fontFace=text_font,
276
+ fontScale=text_scale,
277
+ color=text_color.as_bgr(),
278
+ thickness=text_thickness,
279
+ lineType=cv2.LINE_AA,
280
+ )
281
+ return scene
282
+
283
+
284
+ def draw_image(
285
+ scene: np.ndarray, image: Union[str, np.ndarray], opacity: float, rect: Rect
286
+ ) -> np.ndarray:
287
+ """
288
+ Draws an image onto a given scene with specified opacity and dimensions.
289
+
290
+ Args:
291
+ scene (np.ndarray): Background image where the new image will be drawn.
292
+ image (Union[str, np.ndarray]): Image to draw.
293
+ opacity (float): Opacity of the image to be drawn.
294
+ rect (Rect): Rectangle specifying where to draw the image.
295
+
296
+ Returns:
297
+ np.ndarray: The updated scene.
298
+
299
+ Raises:
300
+ FileNotFoundError: If the image path does not exist.
301
+ ValueError: For invalid opacity or rectangle dimensions.
302
+ """
303
+
304
+ # Validate and load image
305
+ if isinstance(image, str):
306
+ if not os.path.exists(image):
307
+ raise FileNotFoundError(f"Image path ('{image}') does not exist.")
308
+ image = cv2.imread(image, cv2.IMREAD_UNCHANGED)
309
+
310
+ # Validate opacity
311
+ if not 0.0 <= opacity <= 1.0:
312
+ raise ValueError("Opacity must be between 0.0 and 1.0.")
313
+
314
+ # Validate rectangle dimensions
315
+ if (
316
+ rect.x < 0
317
+ or rect.y < 0
318
+ or rect.x + rect.width > scene.shape[1]
319
+ or rect.y + rect.height > scene.shape[0]
320
+ ):
321
+ raise ValueError("Invalid rectangle dimensions.")
322
+
323
+ # Resize and isolate alpha channel
324
+ image = cv2.resize(image, (rect.width, rect.height))
325
+ alpha_channel = (
326
+ image[:, :, 3]
327
+ if image.shape[2] == 4
328
+ else np.ones((rect.height, rect.width), dtype=image.dtype) * 255
329
+ )
330
+ alpha_scaled = cv2.convertScaleAbs(alpha_channel * opacity)
331
+
332
+ # Perform blending
333
+ scene_roi = scene[rect.y : rect.y + rect.height, rect.x : rect.x + rect.width]
334
+ alpha_float = alpha_scaled.astype(np.float32) / 255.0
335
+ blended_roi = cv2.convertScaleAbs(
336
+ (1 - alpha_float[..., np.newaxis]) * scene_roi
337
+ + alpha_float[..., np.newaxis] * image[:, :, :3]
338
+ )
339
+
340
+ # Update the scene
341
+ scene[rect.y : rect.y + rect.height, rect.x : rect.x + rect.width] = blended_roi
342
+
343
+ return scene
344
+
345
+
346
+ def calculate_optimal_text_scale(resolution_wh: Tuple[int, int]) -> float:
347
+ """
348
+ Calculate font scale based on the resolution of an image.
349
+
350
+ Parameters:
351
+ resolution_wh (Tuple[int, int]): A tuple representing the width and height
352
+ of the image.
353
+
354
+ Returns:
355
+ float: The calculated font scale factor.
356
+ """
357
+ return min(resolution_wh) * 1e-3
358
+
359
+
360
+ def calculate_optimal_line_thickness(resolution_wh: Tuple[int, int]) -> int:
361
+ """
362
+ Calculate line thickness based on the resolution of an image.
363
+
364
+ Parameters:
365
+ resolution_wh (Tuple[int, int]): A tuple representing the width and height
366
+ of the image.
367
+
368
+ Returns:
369
+ int: The calculated line thickness in pixels.
370
+ """
371
+ if min(resolution_wh) < 1080:
372
+ return 2
373
+ return 4
374
+
eye/filters.py ADDED
@@ -0,0 +1,112 @@
1
+ """Detection filtering pipeline."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ import numpy as np
5
+ from typing import List
6
+ from .core.detections import Detections
7
+
8
+
9
+ class Filter(ABC):
10
+ """Base filter class."""
11
+
12
+ @abstractmethod
13
+ def __call__(self, detections: Detections) -> Detections:
14
+ """Apply filter to detections."""
15
+ pass
16
+
17
+
18
+ class ConfidenceFilter(Filter):
19
+ """Filter by confidence threshold."""
20
+
21
+ def __init__(self, min_confidence: float = 0.5):
22
+ self.min_confidence = min_confidence
23
+
24
+ def __call__(self, detections: Detections) -> Detections:
25
+ if detections.confidence is None or len(detections) == 0:
26
+ return detections
27
+ mask = detections.confidence >= self.min_confidence
28
+ return detections.filter(mask)
29
+
30
+
31
+ class AreaFilter(Filter):
32
+ """Filter by bounding box area."""
33
+
34
+ def __init__(self, min_area: float = 100, max_area: float = float('inf')):
35
+ self.min_area = min_area
36
+ self.max_area = max_area
37
+
38
+ def __call__(self, detections: Detections) -> Detections:
39
+ if len(detections) == 0:
40
+ return detections
41
+ areas = detections.area
42
+ mask = (areas >= self.min_area) & (areas <= self.max_area)
43
+ return detections.filter(mask)
44
+
45
+
46
+ class AspectRatioFilter(Filter):
47
+ """Filter by aspect ratio."""
48
+
49
+ def __init__(self, min_ratio: float = 0.1, max_ratio: float = 10.0):
50
+ self.min_ratio = min_ratio
51
+ self.max_ratio = max_ratio
52
+
53
+ def __call__(self, detections: Detections) -> Detections:
54
+ if len(detections) == 0:
55
+ return detections
56
+ ratios = detections.aspect_ratio
57
+ mask = (ratios >= self.min_ratio) & (ratios <= self.max_ratio)
58
+ return detections.filter(mask)
59
+
60
+
61
+ class ClassFilter(Filter):
62
+ """Filter by class IDs."""
63
+
64
+ def __init__(self, allowed_classes: List[int]):
65
+ self.allowed_classes = np.array(allowed_classes)
66
+
67
+ def __call__(self, detections: Detections) -> Detections:
68
+ if detections.class_id is None or len(detections) == 0:
69
+ return detections
70
+ mask = np.isin(detections.class_id, self.allowed_classes)
71
+ return detections.filter(mask)
72
+
73
+
74
+ class FilterPipeline:
75
+ """Chain multiple filters together.
76
+
77
+ Innovation: Composable filtering with statistics tracking.
78
+ """
79
+
80
+ def __init__(self, filters: List[Filter]):
81
+ self.filters = filters
82
+ self.stats = {
83
+ 'total_processed': 0,
84
+ 'total_filtered': 0,
85
+ 'filter_counts': [0] * len(filters)
86
+ }
87
+
88
+ def __call__(self, detections: Detections) -> Detections:
89
+ """Apply all filters in sequence."""
90
+ self.stats['total_processed'] += len(detections)
91
+ initial_count = len(detections)
92
+
93
+ for i, filter_obj in enumerate(self.filters):
94
+ before = len(detections)
95
+ detections = filter_obj(detections)
96
+ after = len(detections)
97
+ self.stats['filter_counts'][i] += (before - after)
98
+
99
+ self.stats['total_filtered'] += (initial_count - len(detections))
100
+ return detections
101
+
102
+ def get_stats(self) -> dict:
103
+ """Get filtering statistics."""
104
+ return self.stats.copy()
105
+
106
+ def reset_stats(self):
107
+ """Reset statistics."""
108
+ self.stats = {
109
+ 'total_processed': 0,
110
+ 'total_filtered': 0,
111
+ 'filter_counts': [0] * len(self.filters)
112
+ }
File without changes