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,200 @@
1
+ """SORT tracker implementation."""
2
+
3
+ import numpy as np
4
+ from filterpy.kalman import KalmanFilter
5
+
6
+
7
+ class SORTTracker:
8
+ """Simple Online and Realtime Tracking (SORT) implementation."""
9
+
10
+ def __init__(self, max_age=30, min_hits=3, iou_threshold=0.3):
11
+ self.max_age = max_age
12
+ self.min_hits = min_hits
13
+ self.iou_threshold = iou_threshold
14
+ self.trackers = []
15
+ self.frame_count = 0
16
+
17
+ def update(self, dets):
18
+ """Update tracker with detections."""
19
+ self.frame_count += 1
20
+
21
+ # Get predicted locations from existing trackers
22
+ trks = np.zeros((len(self.trackers), 5))
23
+ to_del = []
24
+ for t, trk in enumerate(trks):
25
+ pos = self.trackers[t].predict()[0]
26
+ trk[:] = [pos[0], pos[1], pos[2], pos[3], 0]
27
+ if np.any(np.isnan(pos)):
28
+ to_del.append(t)
29
+
30
+ trks = np.ma.compress_rows(np.ma.masked_invalid(trks))
31
+ for t in reversed(to_del):
32
+ self.trackers.pop(t)
33
+
34
+ # Associate detections to trackers
35
+ matched, unmatched_dets, unmatched_trks = self._associate(dets, trks)
36
+
37
+ # Update matched trackers
38
+ for m in matched:
39
+ self.trackers[m[1]].update(dets[m[0], :])
40
+
41
+ # Create new trackers for unmatched detections
42
+ for i in unmatched_dets:
43
+ trk = KalmanBoxTracker(dets[i, :])
44
+ self.trackers.append(trk)
45
+
46
+ # Get results
47
+ ret = []
48
+ i = len(self.trackers)
49
+ for trk in reversed(self.trackers):
50
+ d = trk.get_state()[0]
51
+ if (trk.time_since_update < 1) and (trk.hit_streak >= self.min_hits or self.frame_count <= self.min_hits):
52
+ ret.append(np.concatenate((d, [trk.id + 1])).reshape(1, -1))
53
+ i -= 1
54
+ if trk.time_since_update > self.max_age:
55
+ self.trackers.pop(i)
56
+
57
+ if len(ret) > 0:
58
+ return np.concatenate(ret)
59
+ return np.empty((0, 5))
60
+
61
+ def _associate(self, detections, trackers):
62
+ """Associate detections to trackers."""
63
+ if len(trackers) == 0:
64
+ return np.empty((0, 2), dtype=int), np.arange(len(detections)), np.empty((0, 5), dtype=int)
65
+
66
+ iou_matrix = self._iou_batch(detections, trackers)
67
+
68
+ if min(iou_matrix.shape) > 0:
69
+ a = (iou_matrix > self.iou_threshold).astype(np.int32)
70
+ if a.sum(1).max() == 1 and a.sum(0).max() == 1:
71
+ matched_indices = np.stack(np.where(a), axis=1)
72
+ else:
73
+ matched_indices = self._linear_assignment(-iou_matrix)
74
+ else:
75
+ matched_indices = np.empty(shape=(0, 2))
76
+
77
+ unmatched_detections = []
78
+ for d, det in enumerate(detections):
79
+ if d not in matched_indices[:, 0]:
80
+ unmatched_detections.append(d)
81
+
82
+ unmatched_trackers = []
83
+ for t, trk in enumerate(trackers):
84
+ if t not in matched_indices[:, 1]:
85
+ unmatched_trackers.append(t)
86
+
87
+ matches = []
88
+ for m in matched_indices:
89
+ if iou_matrix[m[0], m[1]] < self.iou_threshold:
90
+ unmatched_detections.append(m[0])
91
+ unmatched_trackers.append(m[1])
92
+ else:
93
+ matches.append(m.reshape(1, 2))
94
+
95
+ if len(matches) == 0:
96
+ matches = np.empty((0, 2), dtype=int)
97
+ else:
98
+ matches = np.concatenate(matches, axis=0)
99
+
100
+ return matches, np.array(unmatched_detections), np.array(unmatched_trackers)
101
+
102
+ @staticmethod
103
+ def _iou_batch(bb_test, bb_gt):
104
+ """Compute IoU between two sets of boxes."""
105
+ bb_gt = np.expand_dims(bb_gt, 0)
106
+ bb_test = np.expand_dims(bb_test, 1)
107
+
108
+ xx1 = np.maximum(bb_test[..., 0], bb_gt[..., 0])
109
+ yy1 = np.maximum(bb_test[..., 1], bb_gt[..., 1])
110
+ xx2 = np.minimum(bb_test[..., 2], bb_gt[..., 2])
111
+ yy2 = np.minimum(bb_test[..., 3], bb_gt[..., 3])
112
+ w = np.maximum(0., xx2 - xx1)
113
+ h = np.maximum(0., yy2 - yy1)
114
+ wh = w * h
115
+ o = wh / ((bb_test[..., 2] - bb_test[..., 0]) * (bb_test[..., 3] - bb_test[..., 1])
116
+ + (bb_gt[..., 2] - bb_gt[..., 0]) * (bb_gt[..., 3] - bb_gt[..., 1]) - wh)
117
+ return o
118
+
119
+ @staticmethod
120
+ def _linear_assignment(cost_matrix):
121
+ """Linear assignment using scipy or lap."""
122
+ try:
123
+ import lap
124
+ _, x, y = lap.lapjv(cost_matrix, extend_cost=True)
125
+ return np.array([[y[i], i] for i in x if i >= 0])
126
+ except ImportError:
127
+ from scipy.optimize import linear_sum_assignment
128
+ x, y = linear_sum_assignment(cost_matrix)
129
+ return np.array(list(zip(x, y)))
130
+
131
+ def reset(self):
132
+ """Reset tracker."""
133
+ self.trackers = []
134
+ self.frame_count = 0
135
+
136
+
137
+ class KalmanBoxTracker:
138
+ """Kalman Filter tracker for bounding boxes."""
139
+
140
+ count = 0
141
+
142
+ def __init__(self, bbox):
143
+ self.kf = KalmanFilter(dim_x=7, dim_z=4)
144
+ self.kf.F = np.array([[1,0,0,0,1,0,0],[0,1,0,0,0,1,0],[0,0,1,0,0,0,1],[0,0,0,1,0,0,0],
145
+ [0,0,0,0,1,0,0],[0,0,0,0,0,1,0],[0,0,0,0,0,0,1]])
146
+ self.kf.H = np.array([[1,0,0,0,0,0,0],[0,1,0,0,0,0,0],[0,0,1,0,0,0,0],[0,0,0,1,0,0,0]])
147
+ self.kf.R[2:, 2:] *= 10.
148
+ self.kf.P[4:, 4:] *= 1000.
149
+ self.kf.P *= 10.
150
+ self.kf.Q[-1, -1] *= 0.01
151
+ self.kf.Q[4:, 4:] *= 0.01
152
+
153
+ self.kf.x[:4] = self._convert_bbox_to_z(bbox)
154
+ self.time_since_update = 0
155
+ self.id = KalmanBoxTracker.count
156
+ KalmanBoxTracker.count += 1
157
+ self.history = []
158
+ self.hits = 0
159
+ self.hit_streak = 0
160
+ self.age = 0
161
+
162
+ def update(self, bbox):
163
+ self.time_since_update = 0
164
+ self.history = []
165
+ self.hits += 1
166
+ self.hit_streak += 1
167
+ self.kf.update(self._convert_bbox_to_z(bbox))
168
+
169
+ def predict(self):
170
+ if (self.kf.x[6] + self.kf.x[2]) <= 0:
171
+ self.kf.x[6] *= 0.0
172
+ self.kf.predict()
173
+ self.age += 1
174
+ if self.time_since_update > 0:
175
+ self.hit_streak = 0
176
+ self.time_since_update += 1
177
+ self.history.append(self._convert_x_to_bbox(self.kf.x))
178
+ return self.history[-1]
179
+
180
+ def get_state(self):
181
+ return self._convert_x_to_bbox(self.kf.x)
182
+
183
+ @staticmethod
184
+ def _convert_bbox_to_z(bbox):
185
+ w = bbox[2] - bbox[0]
186
+ h = bbox[3] - bbox[1]
187
+ x = bbox[0] + w / 2.
188
+ y = bbox[1] + h / 2.
189
+ s = w * h
190
+ r = w / float(h)
191
+ return np.array([x, y, s, r]).reshape((4, 1))
192
+
193
+ @staticmethod
194
+ def _convert_x_to_bbox(x, score=None):
195
+ w = np.sqrt(x[2] * x[3])
196
+ h = x[2] / w
197
+ if score is None:
198
+ return np.array([x[0] - w / 2., x[1] - h / 2., x[0] + w / 2., x[1] + h / 2.]).reshape((1, 4))
199
+ else:
200
+ return np.array([x[0] - w / 2., x[1] - h / 2., x[0] + w / 2., x[1] + h / 2., score]).reshape((1, 5))
eye/core/tracking.py ADDED
@@ -0,0 +1,146 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+ from typing import Optional
5
+
6
+ import numpy as np
7
+
8
+ from eye.detection.core import Detections
9
+ from eye.core.trackers.sort_tracker import SORTTracker
10
+ from eye.core.trackers.bytetrack_tracker import ByteTrackTracker
11
+
12
+
13
+ class TrackerType(str, Enum):
14
+ SORT = "sort"
15
+ BYTETRACK = "bytetrack"
16
+
17
+
18
+ def _inflate_xyxy(xyxy: np.ndarray, factor: float) -> np.ndarray:
19
+ if factor is None or factor == 1.0:
20
+ return xyxy
21
+ xyxy = np.asarray(xyxy, dtype=np.float32)
22
+ w = xyxy[:, 2] - xyxy[:, 0]
23
+ h = xyxy[:, 3] - xyxy[:, 1]
24
+ cx = xyxy[:, 0] + 0.5 * w
25
+ cy = xyxy[:, 1] + 0.5 * h
26
+ w2 = 0.5 * w * factor
27
+ h2 = 0.5 * h * factor
28
+ out = np.empty_like(xyxy, dtype=np.float32)
29
+ out[:, 0] = cx - w2
30
+ out[:, 1] = cy - h2
31
+ out[:, 2] = cx + w2
32
+ out[:, 3] = cy + h2
33
+ return out
34
+
35
+
36
+ def _iou_matrix(a: np.ndarray, b: np.ndarray) -> np.ndarray:
37
+ """IoU matrix between a (N,4) and b (M,4) in xyxy."""
38
+ if a.size == 0 or b.size == 0:
39
+ return np.zeros((a.shape[0], b.shape[0]), dtype=np.float32)
40
+
41
+ a = a.astype(np.float32)
42
+ b = b.astype(np.float32)
43
+
44
+ xx1 = np.maximum(a[:, None, 0], b[None, :, 0])
45
+ yy1 = np.maximum(a[:, None, 1], b[None, :, 1])
46
+ xx2 = np.minimum(a[:, None, 2], b[None, :, 2])
47
+ yy2 = np.minimum(a[:, None, 3], b[None, :, 3])
48
+
49
+ inter_w = np.maximum(0.0, xx2 - xx1)
50
+ inter_h = np.maximum(0.0, yy2 - yy1)
51
+ inter = inter_w * inter_h
52
+
53
+ area_a = (a[:, 2] - a[:, 0]) * (a[:, 3] - a[:, 1])
54
+ area_b = (b[:, 2] - b[:, 0]) * (b[:, 3] - b[:, 1])
55
+
56
+ union = area_a[:, None] + area_b[None, :] - inter
57
+ return inter / (union + 1e-6)
58
+
59
+
60
+ def _assign_ids_by_iou(
61
+ det_xyxy: np.ndarray,
62
+ trk_xyxy: np.ndarray,
63
+ trk_ids: np.ndarray,
64
+ min_iou: float,
65
+ ) -> np.ndarray:
66
+ """Greedy IoU assignment. Returns per-detection tracker_id, -1 if unmatched."""
67
+ n = det_xyxy.shape[0]
68
+ if n == 0 or trk_xyxy.shape[0] == 0:
69
+ return np.full((n,), -1, dtype=np.int64)
70
+
71
+ ious = _iou_matrix(det_xyxy, trk_xyxy)
72
+ assigned = np.full((n,), -1, dtype=np.int64)
73
+
74
+ used_tracks: set[int] = set()
75
+ for det_i in range(n):
76
+ best_j = int(np.argmax(ious[det_i]))
77
+ best_iou = float(ious[det_i, best_j])
78
+ if best_iou < min_iou:
79
+ continue
80
+ if best_j in used_tracks:
81
+ continue
82
+ used_tracks.add(best_j)
83
+ assigned[det_i] = int(trk_ids[best_j])
84
+
85
+ return assigned
86
+
87
+
88
+ class Tracker:
89
+ """Compatibility tracker wrapper.
90
+
91
+ This exists to support example scripts that use `eye.Tracker` + `eye.TrackerType`.
92
+ For production, you can still use `eye.ByteTrack` directly.
93
+ """
94
+
95
+ def __init__(
96
+ self,
97
+ tracker_type: TrackerType = TrackerType.BYTETRACK,
98
+ inflation_factor: float = 1.0,
99
+ max_age: int = 30,
100
+ min_hits: int = 3,
101
+ iou_threshold: float = 0.3,
102
+ high_threshold: float = 0.5,
103
+ low_threshold: float = 0.1,
104
+ min_iou_assignment: float = 0.3,
105
+ ) -> None:
106
+ self.tracker_type = TrackerType(tracker_type)
107
+ self.inflation_factor = float(inflation_factor)
108
+ self.min_iou_assignment = float(min_iou_assignment)
109
+
110
+ if self.tracker_type == TrackerType.SORT:
111
+ self._tracker = SORTTracker(max_age=max_age, min_hits=min_hits, iou_threshold=iou_threshold)
112
+ else:
113
+ self._tracker = ByteTrackTracker(
114
+ max_age=max_age,
115
+ min_hits=min_hits,
116
+ iou_threshold=iou_threshold,
117
+ high_threshold=high_threshold,
118
+ low_threshold=low_threshold,
119
+ )
120
+
121
+ def update(self, detections: Detections) -> Detections:
122
+ if detections is None or len(detections) == 0:
123
+ if detections is not None:
124
+ detections.tracker_id = np.empty((0,), dtype=np.int64)
125
+ return detections
126
+
127
+ xyxy = np.asarray(detections.xyxy, dtype=np.float32)
128
+ conf = getattr(detections, "confidence", None)
129
+ if conf is None:
130
+ conf = np.ones((xyxy.shape[0],), dtype=np.float32)
131
+ conf = np.asarray(conf, dtype=np.float32).reshape(-1)
132
+
133
+ inflated = _inflate_xyxy(xyxy, self.inflation_factor)
134
+ dets_for_tracker = np.column_stack([inflated, conf])
135
+
136
+ tracked = self._tracker.update(dets_for_tracker)
137
+ if tracked is None or tracked.size == 0:
138
+ detections.tracker_id = np.full((len(detections),), -1, dtype=np.int64)
139
+ return detections[detections.tracker_id != -1]
140
+
141
+ trk_xyxy = np.asarray(tracked[:, :4], dtype=np.float32)
142
+ trk_ids = np.asarray(tracked[:, 4], dtype=np.int64)
143
+
144
+ assigned = _assign_ids_by_iou(inflated, trk_xyxy, trk_ids, min_iou=self.min_iou_assignment)
145
+ detections.tracker_id = assigned
146
+ return detections[detections.tracker_id != -1]
File without changes