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
@@ -0,0 +1,123 @@
1
+ import warnings
2
+ from collections import defaultdict, deque
3
+ from copy import deepcopy
4
+ from typing import Optional
5
+
6
+ import numpy as np
7
+
8
+ from eye.detection.core import Detections
9
+ from eye.utils.internal import EyeWarnings
10
+
11
+
12
+ class DetectionsSmoother:
13
+ """
14
+ A utility class for smoothing detections over multiple frames in video tracking.
15
+ It maintains a history of detections for each track and provides smoothed
16
+ predictions based on these histories.
17
+
18
+ <video controls>
19
+ <source
20
+ src="https://media.roboflow.com/eye-detection-smoothing.mp4"
21
+ type="video/mp4">
22
+ </video>
23
+
24
+ !!! warning
25
+
26
+ - `DetectionsSmoother` requires the `tracker_id` for each detection. Refer to
27
+ Eye Trackers for information on integrating tracking into your inference pipeline.
28
+ - This class is not compatible with segmentation models.
29
+
30
+ Example:
31
+ ```python
32
+ import eye as sv
33
+
34
+ from ultralytics import YOLO
35
+
36
+ video_info = sv.VideoInfo.from_video_path(video_path=<SOURCE_FILE_PATH>)
37
+ frame_generator = sv.get_video_frames_generator(source_path=<SOURCE_FILE_PATH>)
38
+
39
+ model = YOLO(<MODEL_PATH>)
40
+ tracker = sv.ByteTrack(frame_rate=video_info.fps)
41
+ smoother = sv.DetectionsSmoother()
42
+
43
+ box_annotator = sv.BoxAnnotator()
44
+
45
+ with sv.VideoSink(<TARGET_FILE_PATH>, video_info=video_info) as sink:
46
+ for frame in frame_generator:
47
+ result = model(frame)[0]
48
+ detections = sv.Detections.from_ultralytics(result)
49
+ detections = tracker.update_with_detections(detections)
50
+ detections = smoother.update_with_detections(detections)
51
+
52
+ annotated_frame = box_annotator.annotate(frame.copy(), detections)
53
+ sink.write_frame(annotated_frame)
54
+ ```
55
+ """
56
+
57
+ def __init__(self, length: int = 5) -> None:
58
+ """
59
+ Args:
60
+ length (int): The maximum number of frames to consider for smoothing
61
+ detections. Defaults to 5.
62
+ """
63
+ self.tracks = defaultdict(lambda: deque(maxlen=length))
64
+
65
+ def update_with_detections(self, detections: Detections) -> Detections:
66
+ """
67
+ Updates the smoother with a new set of detections from a frame.
68
+
69
+ Args:
70
+ detections (Detections): The detections to add to the smoother.
71
+ """
72
+
73
+ if detections.tracker_id is None:
74
+ warnings.warn(
75
+ "Smoothing skipped. DetectionsSmoother requires tracker_id. Refer to "
76
+ "Eye trackers are required for smoothing. "
77
+ "Ensure tracker_id is available.",
78
+ category=EyeWarnings,
79
+ )
80
+ return detections
81
+
82
+ for detection_idx in range(len(detections)):
83
+ tracker_id = detections.tracker_id[detection_idx]
84
+
85
+ self.tracks[tracker_id].append(detections[detection_idx])
86
+
87
+ for track_id in self.tracks.keys():
88
+ if track_id not in detections.tracker_id:
89
+ self.tracks[track_id].append(None)
90
+
91
+ for track_id in list(self.tracks.keys()):
92
+ if all([d is None for d in self.tracks[track_id]]):
93
+ del self.tracks[track_id]
94
+
95
+ return self.get_smoothed_detections()
96
+
97
+ def get_track(self, track_id: int) -> Optional[Detections]:
98
+ track = self.tracks.get(track_id, None)
99
+ if track is None:
100
+ return None
101
+
102
+ track = [d for d in track if d is not None]
103
+ if len(track) == 0:
104
+ return None
105
+
106
+ ret = deepcopy(track[0])
107
+ ret.xyxy = np.mean([d.xyxy for d in track], axis=0)
108
+ ret.confidence = np.mean([d.confidence for d in track], axis=0)
109
+
110
+ return ret
111
+
112
+ def get_smoothed_detections(self) -> Detections:
113
+ tracked_detections = []
114
+ for track_id in self.tracks:
115
+ track = self.get_track(track_id)
116
+ if track is not None:
117
+ tracked_detections.append(track)
118
+
119
+ detections = Detections.merge(tracked_detections)
120
+ if len(detections) == 0:
121
+ detections.tracker_id = np.array([], dtype=int)
122
+
123
+ return detections
@@ -0,0 +1,179 @@
1
+ """Advanced jitter reduction and smoothing for tracking."""
2
+
3
+ import numpy as np
4
+ from filterpy.kalman import KalmanFilter
5
+ from typing import Dict, Optional
6
+
7
+
8
+ class KalmanSmoothing:
9
+ """Kalman filter for smooth tracking (jitter reduction).
10
+
11
+ Use for: Smooth camera movement, reduce box jitter with predictive modeling.
12
+ """
13
+
14
+ def __init__(
15
+ self,
16
+ process_noise: float = 0.01,
17
+ measurement_noise: float = 0.1
18
+ ):
19
+ """
20
+ Args:
21
+ process_noise: How much the object can move between frames (lower = smoother)
22
+ measurement_noise: Trust in measurements (lower = trust more)
23
+ """
24
+ self.process_noise = process_noise
25
+ self.measurement_noise = measurement_noise
26
+ self.filters: Dict[int, KalmanFilter] = {}
27
+
28
+ def update(self, tracker_id: int, box: np.ndarray) -> np.ndarray:
29
+ """Smooth box using Kalman filter.
30
+
31
+ Args:
32
+ tracker_id: Object ID
33
+ box: [x1, y1, x2, y2]
34
+
35
+ Returns:
36
+ Smoothed box
37
+ """
38
+ if tracker_id not in self.filters:
39
+ self._init_filter(tracker_id, box)
40
+
41
+ kf = self.filters[tracker_id]
42
+
43
+ # Measurement: [x_center, y_center, width, height]
44
+ cx = (box[0] + box[2]) / 2
45
+ cy = (box[1] + box[3]) / 2
46
+ w = box[2] - box[0]
47
+ h = box[3] - box[1]
48
+ measurement = np.array([cx, cy, w, h])
49
+
50
+ # Predict and update
51
+ kf.predict()
52
+ kf.update(measurement)
53
+
54
+ # Get smoothed state
55
+ state = kf.x.flatten()
56
+ smoothed_cx, smoothed_cy, smoothed_w, smoothed_h = state[:4]
57
+
58
+ # Convert back to xyxy
59
+ return np.array([
60
+ smoothed_cx - smoothed_w / 2,
61
+ smoothed_cy - smoothed_h / 2,
62
+ smoothed_cx + smoothed_w / 2,
63
+ smoothed_cy + smoothed_h / 2
64
+ ])
65
+
66
+ def _init_filter(self, tracker_id: int, box: np.ndarray):
67
+ """Initialize Kalman filter for new track."""
68
+ kf = KalmanFilter(dim_x=8, dim_z=4)
69
+
70
+ # State: [cx, cy, w, h, vcx, vcy, vw, vh]
71
+ cx = (box[0] + box[2]) / 2
72
+ cy = (box[1] + box[3]) / 2
73
+ w = box[2] - box[0]
74
+ h = box[3] - box[1]
75
+ kf.x = np.array([cx, cy, w, h, 0, 0, 0, 0]).reshape(8, 1)
76
+
77
+ # State transition matrix
78
+ kf.F = np.eye(8)
79
+ kf.F[:4, 4:] = np.eye(4)
80
+
81
+ # Measurement matrix
82
+ kf.H = np.zeros((4, 8))
83
+ kf.H[:4, :4] = np.eye(4)
84
+
85
+ # Covariance matrices
86
+ kf.P *= 10
87
+ kf.R *= self.measurement_noise
88
+ kf.Q[4:, 4:] *= self.process_noise
89
+
90
+ self.filters[tracker_id] = kf
91
+
92
+ def reset(self, tracker_id: int = None):
93
+ """Reset filter(s)."""
94
+ if tracker_id is None:
95
+ self.filters.clear()
96
+ elif tracker_id in self.filters:
97
+ del self.filters[tracker_id]
98
+
99
+
100
+ class ExponentialSmoothing:
101
+ """Exponential moving average for simple smoothing.
102
+
103
+ Use for: Quick smoothing, less computational overhead than Kalman.
104
+ """
105
+
106
+ def __init__(self, alpha: float = 0.3):
107
+ """
108
+ Args:
109
+ alpha: Smoothing factor (0-1)
110
+ Lower = smoother but slower response
111
+ Higher = faster response but less smooth
112
+ """
113
+ self.alpha = alpha
114
+ self.previous: Dict[int, np.ndarray] = {}
115
+
116
+ def update(self, tracker_id: int, box: np.ndarray) -> np.ndarray:
117
+ """Smooth box using exponential moving average.
118
+
119
+ Args:
120
+ tracker_id: Object ID
121
+ box: [x1, y1, x2, y2]
122
+
123
+ Returns:
124
+ Smoothed box
125
+ """
126
+ if tracker_id not in self.previous:
127
+ self.previous[tracker_id] = box
128
+ return box
129
+
130
+ # EMA: smoothed = alpha * new + (1 - alpha) * previous
131
+ smoothed = self.alpha * box + (1 - self.alpha) * self.previous[tracker_id]
132
+ self.previous[tracker_id] = smoothed
133
+
134
+ return smoothed
135
+
136
+ def reset(self, tracker_id: int = None):
137
+ """Reset smoothing history."""
138
+ if tracker_id is None:
139
+ self.previous.clear()
140
+ elif tracker_id in self.previous:
141
+ del self.previous[tracker_id]
142
+
143
+
144
+ def smooth_detections(
145
+ detections,
146
+ smoother,
147
+ inplace: bool = False
148
+ ):
149
+ """Apply smoothing to all detections.
150
+
151
+ Args:
152
+ detections: Detections object
153
+ smoother: KalmanSmoothing or ExponentialSmoothing instance
154
+ inplace: Modify in place (faster) or return new
155
+
156
+ Returns:
157
+ Smoothed detections
158
+ """
159
+ if detections.tracker_id is None or len(detections) == 0:
160
+ return detections
161
+
162
+ smoothed_boxes = np.empty_like(detections.xyxy)
163
+
164
+ for i in range(len(detections)):
165
+ tid = detections.tracker_id[i]
166
+ smoothed_boxes[i] = smoother.update(tid, detections.xyxy[i])
167
+
168
+ if inplace:
169
+ object.__setattr__(detections, 'xyxy', smoothed_boxes)
170
+ return detections
171
+ else:
172
+ from eye.detection.core import Detections
173
+ return Detections(
174
+ xyxy=smoothed_boxes,
175
+ confidence=detections.confidence,
176
+ class_id=detections.class_id,
177
+ tracker_id=detections.tracker_id,
178
+ data=detections.data
179
+ )
@@ -0,0 +1,202 @@
1
+ """Easy smoothing configuration with presets and recommendations."""
2
+
3
+ from typing import Optional, Dict, Any
4
+ from enum import Enum
5
+
6
+
7
+ class SmoothingPreset(Enum):
8
+ """Predefined smoothing presets for common scenarios."""
9
+ NONE = "none"
10
+ LIGHT = "light"
11
+ MEDIUM = "medium"
12
+ HEAVY = "heavy"
13
+ ULTRA = "ultra"
14
+
15
+
16
+ class SmoothingConfig:
17
+ """Easy-to-configure smoothing settings.
18
+
19
+ Just pick a preset or customize!
20
+
21
+ Example:
22
+ ```python
23
+ import eye as eye
24
+
25
+ # Use preset
26
+ config = eye.SmoothingConfig(eye.SmoothingPreset.HEAVY)
27
+
28
+ # Get recommendation
29
+ config = eye.SmoothingConfig.get_recommendation("highway")
30
+
31
+ # Custom alpha
32
+ config = eye.SmoothingConfig(eye.SmoothingPreset.MEDIUM, custom_alpha=0.25)
33
+
34
+ # Use with ExponentialSmoothing
35
+ smoother = eye.ExponentialSmoothing(alpha=config.alpha)
36
+ ```
37
+ """
38
+
39
+ PRESETS = {
40
+ SmoothingPreset.NONE: {
41
+ 'enabled': False,
42
+ 'alpha': 1.0,
43
+ 'description': 'No smoothing (raw detections)'
44
+ },
45
+ SmoothingPreset.LIGHT: {
46
+ 'enabled': True,
47
+ 'alpha': 0.5,
48
+ 'description': 'Light smoothing (fast response, minimal jitter reduction)'
49
+ },
50
+ SmoothingPreset.MEDIUM: {
51
+ 'enabled': True,
52
+ 'alpha': 0.3,
53
+ 'description': 'Medium smoothing (balanced - RECOMMENDED)'
54
+ },
55
+ SmoothingPreset.HEAVY: {
56
+ 'enabled': True,
57
+ 'alpha': 0.15,
58
+ 'description': 'Heavy smoothing (slow response, max jitter reduction)'
59
+ },
60
+ SmoothingPreset.ULTRA: {
61
+ 'enabled': True,
62
+ 'alpha': 0.05,
63
+ 'description': 'Ultra smooth (very slow response, cinematic look)'
64
+ }
65
+ }
66
+
67
+ def __init__(
68
+ self,
69
+ preset: SmoothingPreset = SmoothingPreset.MEDIUM,
70
+ custom_alpha: Optional[float] = None
71
+ ):
72
+ """
73
+ Args:
74
+ preset: Preset smoothing level
75
+ custom_alpha: Override alpha (0-1, lower = smoother)
76
+ """
77
+ self.preset = preset
78
+ self.config = self.PRESETS[preset].copy()
79
+
80
+ if custom_alpha is not None:
81
+ self.config['alpha'] = custom_alpha
82
+ self.config['enabled'] = True
83
+
84
+ @property
85
+ def enabled(self) -> bool:
86
+ """Is smoothing enabled?"""
87
+ return self.config['enabled']
88
+
89
+ @property
90
+ def alpha(self) -> float:
91
+ """Smoothing alpha value."""
92
+ return self.config['alpha']
93
+
94
+ @property
95
+ def description(self) -> str:
96
+ """Description of current settings."""
97
+ return self.config['description']
98
+
99
+ @classmethod
100
+ def get_recommendation(cls, use_case: str) -> 'SmoothingConfig':
101
+ """Get recommended smoothing for use case.
102
+
103
+ Args:
104
+ use_case: Scenario name (highway, city, sports, surveillance, etc.)
105
+
106
+ Returns:
107
+ SmoothingConfig with recommended preset
108
+
109
+ Example:
110
+ >>> # For Dakar racing (fast vehicles)
111
+ >>> config = SmoothingConfig.get_recommendation("highway")
112
+ >>> smoother = eye.ExponentialSmoothing(alpha=config.alpha)
113
+ """
114
+ recommendations = {
115
+ # Traffic scenarios
116
+ 'highway': SmoothingPreset.LIGHT, # Fast vehicles (Dakar!)
117
+ 'racing': SmoothingPreset.LIGHT, # Racing vehicles
118
+ 'dakar': SmoothingPreset.LIGHT, # Dakar racing
119
+ 'city': SmoothingPreset.MEDIUM, # Mixed speeds
120
+ 'parking': SmoothingPreset.HEAVY, # Slow, stable
121
+
122
+ # People scenarios
123
+ 'pedestrians': SmoothingPreset.MEDIUM, # Walking
124
+ 'crowd': SmoothingPreset.HEAVY, # Dense, slow
125
+ 'running': SmoothingPreset.LIGHT, # Fast motion
126
+
127
+ # Sports
128
+ 'sports': SmoothingPreset.LIGHT, # Fast, dynamic
129
+ 'soccer': SmoothingPreset.LIGHT,
130
+ 'basketball': SmoothingPreset.LIGHT,
131
+
132
+ # Surveillance
133
+ 'surveillance': SmoothingPreset.HEAVY, # Stable, professional
134
+ 'security': SmoothingPreset.HEAVY,
135
+
136
+ # Drones/aerial
137
+ 'drone': SmoothingPreset.ULTRA, # Camera shake
138
+ 'aerial': SmoothingPreset.ULTRA,
139
+
140
+ # Warehouse/retail
141
+ 'warehouse': SmoothingPreset.MEDIUM,
142
+ 'retail': SmoothingPreset.MEDIUM,
143
+
144
+ # Default
145
+ 'default': SmoothingPreset.MEDIUM
146
+ }
147
+
148
+ preset = recommendations.get(use_case.lower(), SmoothingPreset.MEDIUM)
149
+ return cls(preset)
150
+
151
+ def __repr__(self) -> str:
152
+ return f"SmoothingConfig(preset={self.preset.value}, alpha={self.alpha}, enabled={self.enabled})"
153
+
154
+
155
+ def get_smoothing_guide() -> Dict[str, Any]:
156
+ """Get complete smoothing configuration guide.
157
+
158
+ Returns:
159
+ Dictionary with presets, parameters, and tips
160
+
161
+ Example:
162
+ >>> guide = eye.get_smoothing_guide()
163
+ >>> print(guide['tips'])
164
+ """
165
+ return {
166
+ 'presets': {
167
+ preset.value: {
168
+ 'alpha': config['alpha'],
169
+ 'description': config['description'],
170
+ 'use_for': _get_use_cases_for_preset(preset)
171
+ }
172
+ for preset, config in SmoothingConfig.PRESETS.items()
173
+ },
174
+ 'parameters': {
175
+ 'alpha': {
176
+ 'range': '0.0 - 1.0',
177
+ 'low': 'Very smooth, slow response (0.05-0.15)',
178
+ 'medium': 'Balanced (0.2-0.4)',
179
+ 'high': 'Fast response, less smooth (0.5-0.8)'
180
+ }
181
+ },
182
+ 'tips': [
183
+ 'Start with MEDIUM preset (alpha=0.3)',
184
+ 'Lower alpha for more smoothing but slower tracking',
185
+ 'Higher alpha for faster response but more jitter',
186
+ 'Use ULTRA for drone/handheld footage',
187
+ 'Use LIGHT for fast-moving objects (Dakar, sports)',
188
+ 'Use HEAVY for surveillance/security cameras'
189
+ ]
190
+ }
191
+
192
+
193
+ def _get_use_cases_for_preset(preset: SmoothingPreset) -> list:
194
+ """Get use cases for a preset."""
195
+ mapping = {
196
+ SmoothingPreset.NONE: ['Debugging', 'Raw data analysis'],
197
+ SmoothingPreset.LIGHT: ['Fast vehicles', 'Sports', 'Running', 'Highway', 'Dakar racing'],
198
+ SmoothingPreset.MEDIUM: ['City traffic', 'Pedestrians', 'Warehouse', 'General use'],
199
+ SmoothingPreset.HEAVY: ['Surveillance', 'Parking lots', 'Crowds', 'Security'],
200
+ SmoothingPreset.ULTRA: ['Drones', 'Handheld cameras', 'Cinematic footage']
201
+ }
202
+ return mapping.get(preset, [])