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/keypoint/core.py ADDED
@@ -0,0 +1,687 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import suppress
4
+ from dataclasses import dataclass, field
5
+ from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union
6
+
7
+ import numpy as np
8
+ import numpy.typing as npt
9
+
10
+ from eye.config import CLASS_NAME_DATA_FIELD
11
+ from eye.detection.core import Detections
12
+ from eye.detection.utils import get_data_item, is_data_equal
13
+ from eye.validators import validate_keypoints_fields
14
+
15
+
16
+ @dataclass
17
+ class KeyPoints:
18
+ """
19
+ The `sv.KeyPoints` class in the eye library standardizes results from
20
+ various keypoint detection and pose estimation models into a consistent format. This
21
+ class simplifies data manipulation and filtering, providing a uniform API for
22
+ integration with eye [keypoints annotators](/latest/keypoint/annotators).
23
+
24
+ === "Ultralytics"
25
+
26
+ Use [`sv.KeyPoints.from_ultralytics`](/latest/keypoint/core/#eye.keypoint.core.KeyPoints.from_ultralytics)
27
+ method, which accepts [YOLOv8](https://github.com/ultralytics/ultralytics)
28
+ pose result.
29
+
30
+ ```python
31
+ import cv2
32
+ import eye as sv
33
+ from ultralytics import YOLO
34
+
35
+ image = cv2.imread(<SOURCE_IMAGE_PATH>)
36
+ model = YOLO('yolov8s-pose.pt')
37
+
38
+ result = model(image)[0]
39
+ key_points = sv.KeyPoints.from_ultralytics(result)
40
+ ```
41
+
42
+ === "Inference"
43
+
44
+ Use [`sv.KeyPoints.from_inference`](/latest/keypoint/core/#eye.keypoint.core.KeyPoints.from_inference)
45
+ method, which accepts [Inference](https://inference.roboflow.com/) pose result.
46
+
47
+ ```python
48
+ import cv2
49
+ import eye as sv
50
+ from inference import get_model
51
+
52
+ image = cv2.imread(<SOURCE_IMAGE_PATH>)
53
+ model = get_model(model_id=<POSE_MODEL_ID>, api_key=<ROBOFLOW_API_KEY>)
54
+
55
+ result = model.infer(image)[0]
56
+ key_points = sv.KeyPoints.from_inference(result)
57
+ ```
58
+
59
+ === "MediaPipe"
60
+
61
+ Use [`sv.KeyPoints.from_mediapipe`](/latest/keypoint/core/#eye.keypoint.core.KeyPoints.from_mediapipe)
62
+ method, which accepts [MediaPipe](https://github.com/google-ai-edge/mediapipe)
63
+ pose result.
64
+
65
+ ```python
66
+ import cv2
67
+ import mediapipe as mp
68
+ import eye as sv
69
+
70
+ image = cv2.imread(<SOURCE_IMAGE_PATH>)
71
+ image_height, image_width, _ = image.shape
72
+ mediapipe_image = mp.Image(
73
+ image_format=mp.ImageFormat.SRGB,
74
+ data=cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
75
+
76
+ options = mp.tasks.vision.PoseLandmarkerOptions(
77
+ base_options=mp.tasks.BaseOptions(
78
+ model_asset_path="pose_landmarker_heavy.task"
79
+ ),
80
+ running_mode=mp.tasks.vision.RunningMode.IMAGE,
81
+ num_poses=2)
82
+
83
+ PoseLandmarker = mp.tasks.vision.PoseLandmarker
84
+ with PoseLandmarker.create_from_options(options) as landmarker:
85
+ pose_landmarker_result = landmarker.detect(mediapipe_image)
86
+
87
+ key_points = sv.KeyPoints.from_mediapipe(
88
+ pose_landmarker_result, (image_width, image_height))
89
+ ```
90
+
91
+ Attributes:
92
+ xy (np.ndarray): An array of shape `(n, m, 2)` containing
93
+ `n` detected objects, each composed of `m` equally-sized
94
+ sets of keypoints, where each point is `[x, y]`.
95
+ confidence (Optional[np.ndarray]): An array of shape
96
+ `(n, m)` containing the confidence scores of each keypoint.
97
+ class_id (Optional[np.ndarray]): An array of shape
98
+ `(n,)` containing the class ids of the detected objects.
99
+ data (Dict[str, Union[np.ndarray, List]]): A dictionary containing additional
100
+ data where each key is a string representing the data type, and the value
101
+ is either a NumPy array or a list of corresponding data of length `n`
102
+ (one entry per detected object).
103
+ """ # noqa: E501 // docs
104
+
105
+ xy: npt.NDArray[np.float32]
106
+ class_id: Optional[npt.NDArray[np.int_]] = None
107
+ confidence: Optional[npt.NDArray[np.float32]] = None
108
+ data: Dict[str, Union[npt.NDArray[Any], List]] = field(default_factory=dict)
109
+
110
+ def __post_init__(self):
111
+ validate_keypoints_fields(
112
+ xy=self.xy,
113
+ confidence=self.confidence,
114
+ class_id=self.class_id,
115
+ data=self.data,
116
+ )
117
+
118
+ def __len__(self) -> int:
119
+ """
120
+ Returns the number of keypoints in the `sv.KeyPoints` object.
121
+ """
122
+ return len(self.xy)
123
+
124
+ def __iter__(
125
+ self,
126
+ ) -> Iterator[
127
+ Tuple[
128
+ np.ndarray,
129
+ Optional[np.ndarray],
130
+ Optional[float],
131
+ Optional[int],
132
+ Optional[int],
133
+ Dict[str, Union[np.ndarray, List]],
134
+ ]
135
+ ]:
136
+ """
137
+ Iterates over the Keypoint object and yield a tuple of
138
+ `(xy, confidence, class_id, data)` for each object detection.
139
+ """
140
+ for i in range(len(self.xy)):
141
+ yield (
142
+ self.xy[i],
143
+ self.confidence[i] if self.confidence is not None else None,
144
+ self.class_id[i] if self.class_id is not None else None,
145
+ get_data_item(self.data, i),
146
+ )
147
+
148
+ def __eq__(self, other: KeyPoints) -> bool:
149
+ return all(
150
+ [
151
+ np.array_equal(self.xy, other.xy),
152
+ np.array_equal(self.class_id, other.class_id),
153
+ np.array_equal(self.confidence, other.confidence),
154
+ is_data_equal(self.data, other.data),
155
+ ]
156
+ )
157
+
158
+ @classmethod
159
+ def from_inference(cls, inference_result: Union[dict, Any]) -> KeyPoints:
160
+ """
161
+ Create a `sv.KeyPoints` object from the [Roboflow](https://roboflow.com/)
162
+ API inference result or the [Inference](https://inference.roboflow.com/)
163
+ package results.
164
+
165
+ Args:
166
+ inference_result (dict, any): The result from the
167
+ Roboflow API or Inference package containing predictions with keypoints.
168
+
169
+ Returns:
170
+ A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
171
+ and class names, and confidences of each keypoint.
172
+
173
+ Examples:
174
+ ```python
175
+ import cv2
176
+ import eye as sv
177
+ from inference import get_model
178
+
179
+ image = cv2.imread(<SOURCE_IMAGE_PATH>)
180
+ model = get_model(model_id=<POSE_MODEL_ID>, api_key=<ROBOFLOW_API_KEY>)
181
+
182
+ result = model.infer(image)[0]
183
+ key_points = sv.KeyPoints.from_inference(result)
184
+ ```
185
+
186
+ ```python
187
+ import cv2
188
+ import eye as sv
189
+ from inference_sdk import InferenceHTTPClient
190
+
191
+ image = cv2.imread(<SOURCE_IMAGE_PATH>)
192
+ client = InferenceHTTPClient(
193
+ api_url="https://detect.roboflow.com",
194
+ api_key=<ROBOFLOW_API_KEY>
195
+ )
196
+
197
+ result = client.infer(image, model_id=<POSE_MODEL_ID>)
198
+ key_points = sv.KeyPoints.from_inference(result)
199
+ ```
200
+ """
201
+ if isinstance(inference_result, list):
202
+ raise ValueError(
203
+ "from_inference() operates on a single result at a time."
204
+ "You can retrieve it like so: inference_result = model.infer(image)[0]"
205
+ )
206
+
207
+ with suppress(AttributeError):
208
+ inference_result = inference_result.dict(exclude_none=True, by_alias=True)
209
+
210
+ if not inference_result.get("predictions"):
211
+ return cls.empty()
212
+
213
+ xy = []
214
+ confidence = []
215
+ class_id = []
216
+ class_names = []
217
+
218
+ for prediction in inference_result["predictions"]:
219
+ prediction_xy = []
220
+ prediction_confidence = []
221
+ for keypoint in prediction["keypoints"]:
222
+ prediction_xy.append([keypoint["x"], keypoint["y"]])
223
+ prediction_confidence.append(keypoint["confidence"])
224
+ xy.append(prediction_xy)
225
+ confidence.append(prediction_confidence)
226
+
227
+ class_id.append(prediction["class_id"])
228
+ class_names.append(prediction["class"])
229
+
230
+ data = {CLASS_NAME_DATA_FIELD: np.array(class_names)}
231
+
232
+ return cls(
233
+ xy=np.array(xy, dtype=np.float32),
234
+ confidence=np.array(confidence, dtype=np.float32),
235
+ class_id=np.array(class_id, dtype=int),
236
+ data=data,
237
+ )
238
+
239
+ @classmethod
240
+ def from_mediapipe(
241
+ cls, mediapipe_results, resolution_wh: Tuple[int, int]
242
+ ) -> KeyPoints:
243
+ """
244
+ Creates a `sv.KeyPoints` instance from a
245
+ [MediaPipe](https://github.com/google-ai-edge/mediapipe)
246
+ pose landmark detection inference result.
247
+
248
+ Args:
249
+ mediapipe_results (Union[PoseLandmarkerResult, FaceLandmarkerResult, SolutionOutputs]):
250
+ The output results from Mediapipe. It support pose and face landmarks
251
+ from `PoseLandmaker`, `FaceLandmarker` and the legacy ones
252
+ from `Pose` and `FaceMesh`.
253
+ resolution_wh (Tuple[int, int]): A tuple of the form `(width, height)`
254
+ representing the resolution of the frame.
255
+
256
+ Returns:
257
+ A `sv.KeyPoints` object containing the keypoint coordinates and
258
+ confidences of each keypoint.
259
+
260
+ !!! tip
261
+ Before you start, download model bundles from the
262
+ [MediaPipe website](https://ai.google.dev/edge/mediapipe/solutions/vision/pose_landmarker/index#models).
263
+
264
+ Examples:
265
+ ```python
266
+ import cv2
267
+ import mediapipe as mp
268
+ import eye as sv
269
+
270
+ image = cv2.imread(<SOURCE_IMAGE_PATH>)
271
+ image_height, image_width, _ = image.shape
272
+ mediapipe_image = mp.Image(
273
+ image_format=mp.ImageFormat.SRGB,
274
+ data=cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
275
+
276
+ options = mp.tasks.vision.PoseLandmarkerOptions(
277
+ base_options=mp.tasks.BaseOptions(
278
+ model_asset_path="pose_landmarker_heavy.task"
279
+ ),
280
+ running_mode=mp.tasks.vision.RunningMode.IMAGE,
281
+ num_poses=2)
282
+
283
+ PoseLandmarker = mp.tasks.vision.PoseLandmarker
284
+ with PoseLandmarker.create_from_options(options) as landmarker:
285
+ pose_landmarker_result = landmarker.detect(mediapipe_image)
286
+
287
+ key_points = sv.KeyPoints.from_mediapipe(
288
+ pose_landmarker_result, (image_width, image_height))
289
+ ```
290
+
291
+ ```python
292
+ import cv2
293
+ import mediapipe as mp
294
+ import eye as sv
295
+
296
+ image = cv2.imread(<SOURCE_IMAGE_PATH>)
297
+ image_height, image_width, _ = image.shape
298
+ mediapipe_image = mp.Image(
299
+ image_format=mp.ImageFormat.SRGB,
300
+ data=cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
301
+
302
+ options = mp.tasks.vision.FaceLandmarkerOptions(
303
+ base_options=mp.tasks.BaseOptions(
304
+ model_asset_path="face_landmarker.task"
305
+ ),
306
+ output_face_blendshapes=True,
307
+ output_facial_transformation_matrixes=True,
308
+ num_faces=2)
309
+
310
+ FaceLandmarker = mp.tasks.vision.FaceLandmarker
311
+ with FaceLandmarker.create_from_options(options) as landmarker:
312
+ face_landmarker_result = landmarker.detect(mediapipe_image)
313
+
314
+ key_points = sv.KeyPoints.from_mediapipe(
315
+ face_landmarker_result, (image_width, image_height))
316
+ ```
317
+ """ # noqa: E501 // docs
318
+ if hasattr(mediapipe_results, "pose_landmarks"):
319
+ results = mediapipe_results.pose_landmarks
320
+ if not isinstance(mediapipe_results.pose_landmarks, list):
321
+ if mediapipe_results.pose_landmarks is None:
322
+ results = []
323
+ else:
324
+ results = [
325
+ [
326
+ landmark
327
+ for landmark in mediapipe_results.pose_landmarks.landmark
328
+ ]
329
+ ]
330
+ elif hasattr(mediapipe_results, "face_landmarks"):
331
+ results = mediapipe_results.face_landmarks
332
+ elif hasattr(mediapipe_results, "multi_face_landmarks"):
333
+ if mediapipe_results.multi_face_landmarks is None:
334
+ results = []
335
+ else:
336
+ results = [
337
+ face_landmark.landmark
338
+ for face_landmark in mediapipe_results.multi_face_landmarks
339
+ ]
340
+
341
+ if len(results) == 0:
342
+ return cls.empty()
343
+
344
+ xy = []
345
+ confidence = []
346
+ for pose in results:
347
+ prediction_xy = []
348
+ prediction_confidence = []
349
+ for landmark in pose:
350
+ keypoint_xy = [
351
+ landmark.x * resolution_wh[0],
352
+ landmark.y * resolution_wh[1],
353
+ ]
354
+ prediction_xy.append(keypoint_xy)
355
+ prediction_confidence.append(landmark.visibility)
356
+
357
+ xy.append(prediction_xy)
358
+ confidence.append(prediction_confidence)
359
+
360
+ return cls(
361
+ xy=np.array(xy, dtype=np.float32),
362
+ confidence=np.array(confidence, dtype=np.float32),
363
+ )
364
+
365
+ @classmethod
366
+ def from_ultralytics(cls, ultralytics_results) -> KeyPoints:
367
+ """
368
+ Creates a `sv.KeyPoints` instance from a
369
+ [YOLOv8](https://github.com/ultralytics/ultralytics) pose inference result.
370
+
371
+ Args:
372
+ ultralytics_results (ultralytics.engine.results.Keypoints):
373
+ The output Results instance from YOLOv8
374
+
375
+ Returns:
376
+ A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
377
+ and class names, and confidences of each keypoint.
378
+
379
+ Examples:
380
+ ```python
381
+ import cv2
382
+ import eye as sv
383
+ from ultralytics import YOLO
384
+
385
+ image = cv2.imread(<SOURCE_IMAGE_PATH>)
386
+ model = YOLO('yolov8s-pose.pt')
387
+
388
+ result = model(image)[0]
389
+ key_points = sv.KeyPoints.from_ultralytics(result)
390
+ ```
391
+ """
392
+ if ultralytics_results.keypoints.xy.numel() == 0:
393
+ return cls.empty()
394
+
395
+ xy = ultralytics_results.keypoints.xy.cpu().numpy()
396
+ class_id = ultralytics_results.boxes.cls.cpu().numpy().astype(int)
397
+ class_names = np.array([ultralytics_results.names[i] for i in class_id])
398
+
399
+ confidence = ultralytics_results.keypoints.conf.cpu().numpy()
400
+ data = {CLASS_NAME_DATA_FIELD: class_names}
401
+ return cls(xy, class_id, confidence, data)
402
+
403
+ @classmethod
404
+ def from_yolo_nas(cls, yolo_nas_results) -> KeyPoints:
405
+ """
406
+ Create a `sv.KeyPoints` instance from a [YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS-POSE.md)
407
+ pose inference results.
408
+
409
+ Args:
410
+ yolo_nas_results (ImagePoseEstimationPrediction): The output object from
411
+ YOLO NAS.
412
+
413
+ Returns:
414
+ A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
415
+ and class names, and confidences of each keypoint.
416
+
417
+ Examples:
418
+ ```python
419
+ import cv2
420
+ import torch
421
+ import eye as sv
422
+ import super_gradients
423
+
424
+ image = cv2.imread(<SOURCE_IMAGE_PATH>)
425
+
426
+ device = "cuda" if torch.cuda.is_available() else "cpu"
427
+ model = super_gradients.training.models.get(
428
+ "yolo_nas_pose_s", pretrained_weights="coco_pose").to(device)
429
+
430
+ results = model.predict(image, conf=0.1)
431
+ key_points = sv.KeyPoints.from_yolo_nas(results)
432
+ ```
433
+ """
434
+ if len(yolo_nas_results.prediction.poses) == 0:
435
+ return cls.empty()
436
+
437
+ xy = yolo_nas_results.prediction.poses[:, :, :2]
438
+ confidence = yolo_nas_results.prediction.poses[:, :, 2]
439
+
440
+ # yolo_nas_results treats params differently.
441
+ # prediction.labels may not exist, whereas class_names might be None
442
+ if hasattr(yolo_nas_results.prediction, "labels"):
443
+ class_id = yolo_nas_results.prediction.labels # np.array[int]
444
+ else:
445
+ class_id = None
446
+
447
+ data = {}
448
+ if class_id is not None and yolo_nas_results.class_names is not None:
449
+ class_names = []
450
+ for c_id in class_id:
451
+ name = yolo_nas_results.class_names[c_id] # tuple[str]
452
+ class_names.append(name)
453
+ data[CLASS_NAME_DATA_FIELD] = class_names
454
+
455
+ return cls(
456
+ xy=xy,
457
+ confidence=confidence,
458
+ class_id=class_id,
459
+ data=data,
460
+ )
461
+
462
+ @classmethod
463
+ def from_detectron2(cls, detectron2_results: Any) -> KeyPoints:
464
+ """
465
+ Create a `sv.KeyPoints` object from the
466
+ [Detectron2](https://github.com/facebookresearch/detectron2) inference result.
467
+
468
+ Args:
469
+ detectron2_results (Any): The output of a
470
+ Detectron2 model containing instances with prediction data.
471
+
472
+ Returns:
473
+ A `sv.KeyPoints` object containing the keypoint coordinates, class IDs,
474
+ and class names, and confidences of each keypoint.
475
+
476
+ Example:
477
+ ```python
478
+ import cv2
479
+ import eye as sv
480
+ from detectron2.engine import DefaultPredictor
481
+ from detectron2.config import get_cfg
482
+
483
+
484
+ image = cv2.imread(<SOURCE_IMAGE_PATH>)
485
+ cfg = get_cfg()
486
+ cfg.merge_from_file(<CONFIG_PATH>)
487
+ cfg.MODEL.WEIGHTS = <WEIGHTS_PATH>
488
+ predictor = DefaultPredictor(cfg)
489
+
490
+ result = predictor(image)
491
+ keypoints = sv.KeyPoints.from_detectron2(result)
492
+ ```
493
+ """
494
+
495
+ if hasattr(detectron2_results["instances"], "pred_keypoints"):
496
+ if detectron2_results["instances"].pred_keypoints.cpu().numpy().size == 0:
497
+ return cls.empty()
498
+ return cls(
499
+ xy=detectron2_results["instances"]
500
+ .pred_keypoints.cpu()
501
+ .numpy()[:, :, :2],
502
+ confidence=detectron2_results["instances"]
503
+ .pred_keypoints.cpu()
504
+ .numpy()[:, :, 2],
505
+ class_id=detectron2_results["instances"]
506
+ .pred_classes.cpu()
507
+ .numpy()
508
+ .astype(int),
509
+ )
510
+ else:
511
+ return cls.empty()
512
+
513
+ def __getitem__(
514
+ self, index: Union[int, slice, List[int], np.ndarray, str]
515
+ ) -> Union[KeyPoints, List, np.ndarray, None]:
516
+ """
517
+ Get a subset of the `sv.KeyPoints` object or access an item from its data field.
518
+
519
+ When provided with an integer, slice, list of integers, or a numpy array, this
520
+ method returns a new `sv.KeyPoints` object that represents a subset of the
521
+ original `sv.KeyPoints`. When provided with a string, it accesses the
522
+ corresponding item in the data dictionary.
523
+
524
+ Args:
525
+ index (Union[int, slice, List[int], np.ndarray, str]): The index, indices,
526
+ or key to access a subset of the `sv.KeyPoints` or an item from the
527
+ data.
528
+
529
+ Returns:
530
+ A subset of the `sv.KeyPoints` object or an item from the data field.
531
+
532
+ Examples:
533
+ ```python
534
+ import eye as sv
535
+
536
+ key_points = sv.KeyPoints()
537
+
538
+ # access the first keypoint using an integer index
539
+ key_points[0]
540
+
541
+ # access the first 10 keypoints using index slice
542
+ key_points[0:10]
543
+
544
+ # access selected keypoints using a list of indices
545
+ key_points[[0, 2, 4]]
546
+
547
+ # access keypoints with selected class_id
548
+ key_points[key_points.class_id == 0]
549
+
550
+ # access keypoints with confidence greater than 0.5
551
+ key_points[key_points.confidence > 0.5]
552
+ ```
553
+ """
554
+ if isinstance(index, str):
555
+ return self.data.get(index)
556
+ if isinstance(index, int):
557
+ index = [index]
558
+ return KeyPoints(
559
+ xy=self.xy[index],
560
+ confidence=self.confidence[index] if self.confidence is not None else None,
561
+ class_id=self.class_id[index] if self.class_id is not None else None,
562
+ data=get_data_item(self.data, index),
563
+ )
564
+
565
+ def __setitem__(self, key: str, value: Union[np.ndarray, List]):
566
+ """
567
+ Set a value in the data dictionary of the `sv.KeyPoints` object.
568
+
569
+ Args:
570
+ key (str): The key in the data dictionary to set.
571
+ value (Union[np.ndarray, List]): The value to set for the key.
572
+
573
+ Examples:
574
+ ```python
575
+ import cv2
576
+ import eye as sv
577
+ from ultralytics import YOLO
578
+
579
+ image = cv2.imread(<SOURCE_IMAGE_PATH>)
580
+ model = YOLO('yolov8s.pt')
581
+
582
+ result = model(image)[0]
583
+ keypoints = sv.KeyPoints.from_ultralytics(result)
584
+
585
+ keypoints['class_name'] = [
586
+ model.model.names[class_id]
587
+ for class_id
588
+ in keypoints.class_id
589
+ ]
590
+ ```
591
+ """
592
+ if not isinstance(value, (np.ndarray, list)):
593
+ raise TypeError("Value must be a np.ndarray or a list")
594
+
595
+ if isinstance(value, list):
596
+ value = np.array(value)
597
+
598
+ self.data[key] = value
599
+
600
+ @classmethod
601
+ def empty(cls) -> KeyPoints:
602
+ """
603
+ Create an empty Keypoints object with no keypoints.
604
+
605
+ Returns:
606
+ An empty `sv.KeyPoints` object.
607
+
608
+ Examples:
609
+ ```python
610
+ import eye as sv
611
+
612
+ key_points = sv.KeyPoints.empty()
613
+ ```
614
+ """
615
+ return cls(xy=np.empty((0, 0, 2), dtype=np.float32))
616
+
617
+ def is_empty(self) -> bool:
618
+ """
619
+ Returns `True` if the `KeyPoints` object is considered empty.
620
+ """
621
+ empty_keypoints = KeyPoints.empty()
622
+ empty_keypoints.data = self.data
623
+ return self == empty_keypoints
624
+
625
+ def as_detections(
626
+ self, selected_keypoint_indices: Optional[Iterable[int]] = None
627
+ ) -> Detections:
628
+ """
629
+ Convert a KeyPoints object to a Detections object. This
630
+ approximates the bounding box of the detected object by
631
+ taking the bounding box that fits all keypoints.
632
+
633
+ Arguments:
634
+ selected_keypoint_indices (Optional[Iterable[int]]): The
635
+ indices of the keypoints to include in the bounding box
636
+ calculation. This helps focus on a subset of keypoints,
637
+ e.g. when some are occluded. Captures all keypoints by default.
638
+
639
+ Returns:
640
+ detections (Detections): The converted detections object.
641
+
642
+ Example:
643
+ ```python
644
+ keypoints = sv.KeyPoints.from_inference(...)
645
+ detections = keypoints.as_detections()
646
+ ```
647
+ """
648
+ if self.is_empty():
649
+ return Detections.empty()
650
+
651
+ detections_list = []
652
+ for i, xy in enumerate(self.xy):
653
+ if selected_keypoint_indices:
654
+ xy = xy[selected_keypoint_indices]
655
+
656
+ # [0, 0] used by some frameworks to indicate missing keypoints
657
+ xy = xy[~np.all(xy == 0, axis=1)]
658
+ if len(xy) == 0:
659
+ xyxy = np.array([[0, 0, 0, 0]], dtype=np.float32)
660
+ else:
661
+ x_min = xy[:, 0].min()
662
+ x_max = xy[:, 0].max()
663
+ y_min = xy[:, 1].min()
664
+ y_max = xy[:, 1].max()
665
+ xyxy = np.array([[x_min, y_min, x_max, y_max]], dtype=np.float32)
666
+
667
+ if self.confidence is None:
668
+ confidence = None
669
+ else:
670
+ confidence = self.confidence[i]
671
+ if selected_keypoint_indices:
672
+ confidence = confidence[selected_keypoint_indices]
673
+ confidence = np.array([confidence.mean()], dtype=np.float32)
674
+
675
+ detections_list.append(
676
+ Detections(
677
+ xyxy=xyxy,
678
+ confidence=confidence,
679
+ )
680
+ )
681
+
682
+ detections = Detections.merge(detections_list)
683
+ detections.class_id = self.class_id
684
+ detections.data = self.data
685
+ detections = detections[detections.area > 0]
686
+
687
+ return detections