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
File without changes
@@ -0,0 +1,258 @@
1
+ import os
2
+ from datetime import datetime
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING, Dict, List, Tuple
5
+
6
+ import numpy as np
7
+ import numpy.typing as npt
8
+
9
+ from eye.dataset.utils import (
10
+ approximate_mask_with_polygons,
11
+ map_detections_class_id,
12
+ mask_to_rle,
13
+ rle_to_mask,
14
+ )
15
+ from eye.detection.core import Detections
16
+ from eye.detection.utils import (
17
+ contains_holes,
18
+ contains_multiple_segments,
19
+ polygon_to_mask,
20
+ )
21
+ from eye.utils.file import read_json_file, save_json_file
22
+
23
+ if TYPE_CHECKING:
24
+ from eye.dataset.core import DetectionDataset
25
+
26
+
27
+ def coco_categories_to_classes(coco_categories: List[dict]) -> List[str]:
28
+ return [
29
+ category["name"]
30
+ for category in sorted(coco_categories, key=lambda category: category["id"])
31
+ ]
32
+
33
+
34
+ def build_coco_class_index_mapping(
35
+ coco_categories: List[dict], target_classes: List[str]
36
+ ) -> Dict[int, int]:
37
+ source_class_to_index = {
38
+ category["name"]: category["id"] for category in coco_categories
39
+ }
40
+ return {
41
+ source_class_to_index[target_class_name]: target_class_index
42
+ for target_class_index, target_class_name in enumerate(target_classes)
43
+ }
44
+
45
+
46
+ def classes_to_coco_categories(classes: List[str]) -> List[dict]:
47
+ return [
48
+ {
49
+ "id": class_id,
50
+ "name": class_name,
51
+ "supercategory": "common-objects",
52
+ }
53
+ for class_id, class_name in enumerate(classes)
54
+ ]
55
+
56
+
57
+ def group_coco_annotations_by_image_id(
58
+ coco_annotations: List[dict],
59
+ ) -> Dict[int, List[dict]]:
60
+ annotations = {}
61
+ for annotation in coco_annotations:
62
+ image_id = annotation["image_id"]
63
+ if image_id not in annotations:
64
+ annotations[image_id] = []
65
+ annotations[image_id].append(annotation)
66
+ return annotations
67
+
68
+
69
+ def coco_annotations_to_masks(
70
+ image_annotations: List[dict], resolution_wh: Tuple[int, int]
71
+ ) -> npt.NDArray[np.bool_]:
72
+ return np.array(
73
+ [
74
+ rle_to_mask(
75
+ rle=np.array(image_annotation["segmentation"]["counts"]),
76
+ resolution_wh=resolution_wh,
77
+ )
78
+ if image_annotation["iscrowd"]
79
+ else polygon_to_mask(
80
+ polygon=np.reshape(
81
+ np.asarray(image_annotation["segmentation"], dtype=np.int32),
82
+ (-1, 2),
83
+ ),
84
+ resolution_wh=resolution_wh,
85
+ )
86
+ for image_annotation in image_annotations
87
+ ],
88
+ dtype=bool,
89
+ )
90
+
91
+
92
+ def coco_annotations_to_detections(
93
+ image_annotations: List[dict], resolution_wh: Tuple[int, int], with_masks: bool
94
+ ) -> Detections:
95
+ if not image_annotations:
96
+ return Detections.empty()
97
+
98
+ class_ids = [
99
+ image_annotation["category_id"] for image_annotation in image_annotations
100
+ ]
101
+ xyxy = [image_annotation["bbox"] for image_annotation in image_annotations]
102
+ xyxy = np.asarray(xyxy)
103
+ xyxy[:, 2:4] += xyxy[:, 0:2]
104
+
105
+ if with_masks:
106
+ mask = coco_annotations_to_masks(
107
+ image_annotations=image_annotations, resolution_wh=resolution_wh
108
+ )
109
+ return Detections(
110
+ class_id=np.asarray(class_ids, dtype=int), xyxy=xyxy, mask=mask
111
+ )
112
+
113
+ return Detections(xyxy=xyxy, class_id=np.asarray(class_ids, dtype=int))
114
+
115
+
116
+ def detections_to_coco_annotations(
117
+ detections: Detections,
118
+ image_id: int,
119
+ annotation_id: int,
120
+ min_image_area_percentage: float = 0.0,
121
+ max_image_area_percentage: float = 1.0,
122
+ approximation_percentage: float = 0.75,
123
+ ) -> Tuple[List[Dict], int]:
124
+ coco_annotations = []
125
+ for xyxy, mask, _, class_id, _, _ in detections:
126
+ box_width, box_height = xyxy[2] - xyxy[0], xyxy[3] - xyxy[1]
127
+ segmentation = []
128
+ iscrowd = 0
129
+ if mask is not None:
130
+ iscrowd = contains_holes(mask=mask) or contains_multiple_segments(mask=mask)
131
+
132
+ if iscrowd:
133
+ segmentation = {
134
+ "counts": mask_to_rle(mask=mask),
135
+ "size": list(mask.shape[:2]),
136
+ }
137
+ else:
138
+ segmentation = [
139
+ list(
140
+ approximate_mask_with_polygons(
141
+ mask=mask,
142
+ min_image_area_percentage=min_image_area_percentage,
143
+ max_image_area_percentage=max_image_area_percentage,
144
+ approximation_percentage=approximation_percentage,
145
+ )[0].flatten()
146
+ )
147
+ ]
148
+ coco_annotation = {
149
+ "id": annotation_id,
150
+ "image_id": image_id,
151
+ "category_id": int(class_id),
152
+ "bbox": [xyxy[0], xyxy[1], box_width, box_height],
153
+ "area": box_width * box_height,
154
+ "segmentation": segmentation,
155
+ "iscrowd": iscrowd,
156
+ }
157
+ coco_annotations.append(coco_annotation)
158
+ annotation_id += 1
159
+ return coco_annotations, annotation_id
160
+
161
+
162
+ def load_coco_annotations(
163
+ images_directory_path: str,
164
+ annotations_path: str,
165
+ force_masks: bool = False,
166
+ ) -> Tuple[List[str], List[str], Dict[str, Detections]]:
167
+ coco_data = read_json_file(file_path=annotations_path)
168
+ classes = coco_categories_to_classes(coco_categories=coco_data["categories"])
169
+ class_index_mapping = build_coco_class_index_mapping(
170
+ coco_categories=coco_data["categories"], target_classes=classes
171
+ )
172
+ coco_images = coco_data["images"]
173
+ coco_annotations_groups = group_coco_annotations_by_image_id(
174
+ coco_annotations=coco_data["annotations"]
175
+ )
176
+
177
+ images = []
178
+ annotations = {}
179
+
180
+ for coco_image in coco_images:
181
+ image_name, image_width, image_height = (
182
+ coco_image["file_name"],
183
+ coco_image["width"],
184
+ coco_image["height"],
185
+ )
186
+ image_annotations = coco_annotations_groups.get(coco_image["id"], [])
187
+ image_path = os.path.join(images_directory_path, image_name)
188
+
189
+ annotation = coco_annotations_to_detections(
190
+ image_annotations=image_annotations,
191
+ resolution_wh=(image_width, image_height),
192
+ with_masks=force_masks,
193
+ )
194
+ annotation = map_detections_class_id(
195
+ source_to_target_mapping=class_index_mapping,
196
+ detections=annotation,
197
+ )
198
+
199
+ images.append(image_path)
200
+ annotations[image_path] = annotation
201
+
202
+ return classes, images, annotations
203
+
204
+
205
+ def save_coco_annotations(
206
+ dataset: "DetectionDataset",
207
+ annotation_path: str,
208
+ min_image_area_percentage: float = 0.0,
209
+ max_image_area_percentage: float = 1.0,
210
+ approximation_percentage: float = 0.75,
211
+ ) -> None:
212
+ Path(annotation_path).parent.mkdir(parents=True, exist_ok=True)
213
+ licenses = [
214
+ {
215
+ "id": 1,
216
+ "url": "https://creativecommons.org/licenses/by/4.0/",
217
+ "name": "CC BY 4.0",
218
+ }
219
+ ]
220
+
221
+ coco_annotations = []
222
+ coco_images = []
223
+ coco_categories = classes_to_coco_categories(classes=dataset.classes)
224
+
225
+ image_id, annotation_id = 1, 1
226
+ for image_path, image, annotation in dataset:
227
+ image_height, image_width, _ = image.shape
228
+ image_name = f"{Path(image_path).stem}{Path(image_path).suffix}"
229
+ coco_image = {
230
+ "id": image_id,
231
+ "license": 1,
232
+ "file_name": image_name,
233
+ "height": image_height,
234
+ "width": image_width,
235
+ "date_captured": datetime.now().strftime("%m/%d/%Y,%H:%M:%S"),
236
+ }
237
+
238
+ coco_images.append(coco_image)
239
+ coco_annotation, annotation_id = detections_to_coco_annotations(
240
+ detections=annotation,
241
+ image_id=image_id,
242
+ annotation_id=annotation_id,
243
+ min_image_area_percentage=min_image_area_percentage,
244
+ max_image_area_percentage=max_image_area_percentage,
245
+ approximation_percentage=approximation_percentage,
246
+ )
247
+
248
+ coco_annotations.extend(coco_annotation)
249
+ image_id += 1
250
+
251
+ annotation_dict = {
252
+ "info": {},
253
+ "licenses": licenses,
254
+ "categories": coco_categories,
255
+ "images": coco_images,
256
+ "annotations": coco_annotations,
257
+ }
258
+ save_json_file(annotation_dict, file_path=annotation_path)
@@ -0,0 +1,279 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Dict, List, Optional, Tuple
4
+ from xml.etree.ElementTree import Element, SubElement
5
+
6
+ import cv2
7
+ import numpy as np
8
+ from defusedxml.ElementTree import parse, tostring
9
+ from defusedxml.minidom import parseString
10
+
11
+ from eye.dataset.utils import approximate_mask_with_polygons
12
+ from eye.detection.core import Detections
13
+ from eye.detection.utils import polygon_to_mask, polygon_to_xyxy
14
+ from eye.utils.file import list_files_with_extensions
15
+
16
+
17
+ def object_to_pascal_voc(
18
+ xyxy: np.ndarray, name: str, polygon: Optional[np.ndarray] = None
19
+ ) -> Element:
20
+ root = Element("object")
21
+
22
+ object_name = SubElement(root, "name")
23
+ object_name.text = name
24
+
25
+ # https://github.com/roboflow/eye/issues/144
26
+ xyxy += 1
27
+
28
+ bndbox = SubElement(root, "bndbox")
29
+ xmin = SubElement(bndbox, "xmin")
30
+ xmin.text = str(int(xyxy[0]))
31
+ ymin = SubElement(bndbox, "ymin")
32
+ ymin.text = str(int(xyxy[1]))
33
+ xmax = SubElement(bndbox, "xmax")
34
+ xmax.text = str(int(xyxy[2]))
35
+ ymax = SubElement(bndbox, "ymax")
36
+ ymax.text = str(int(xyxy[3]))
37
+
38
+ if polygon is not None:
39
+ # https://github.com/roboflow/eye/issues/144
40
+ polygon += 1
41
+ object_polygon = SubElement(root, "polygon")
42
+ for index, point in enumerate(polygon, start=1):
43
+ x_coordinate, y_coordinate = point
44
+ x = SubElement(object_polygon, f"x{index}")
45
+ x.text = str(x_coordinate)
46
+ y = SubElement(object_polygon, f"y{index}")
47
+ y.text = str(y_coordinate)
48
+
49
+ return root
50
+
51
+
52
+ def detections_to_pascal_voc(
53
+ detections: Detections,
54
+ classes: List[str],
55
+ filename: str,
56
+ image_shape: Tuple[int, int, int],
57
+ min_image_area_percentage: float = 0.0,
58
+ max_image_area_percentage: float = 1.0,
59
+ approximation_percentage: float = 0.75,
60
+ ) -> str:
61
+ """
62
+ Converts Detections object to Pascal VOC XML format.
63
+
64
+ Args:
65
+ detections (Detections): A Detections object containing bounding boxes,
66
+ class ids, and other relevant information.
67
+ classes (List[str]): A list of class names corresponding to the
68
+ class ids in the Detections object.
69
+ filename (str): The name of the image file associated with the detections.
70
+ image_shape (Tuple[int, int, int]): The shape of the image
71
+ file associated with the detections.
72
+ min_image_area_percentage (float): Minimum detection area
73
+ relative to area of image associated with it.
74
+ max_image_area_percentage (float): Maximum detection area
75
+ relative to area of image associated with it.
76
+ approximation_percentage (float): The percentage of
77
+ polygon points to be removed from the input polygon, in the range [0, 1).
78
+ Returns:
79
+ str: An XML string in Pascal VOC format representing the detections.
80
+ """
81
+ height, width, depth = image_shape
82
+
83
+ # Create root element
84
+ annotation = Element("annotation")
85
+
86
+ # Add folder element
87
+ folder = SubElement(annotation, "folder")
88
+ folder.text = "VOC"
89
+
90
+ # Add filename element
91
+ file_name = SubElement(annotation, "filename")
92
+ file_name.text = filename
93
+
94
+ # Add source element
95
+ source = SubElement(annotation, "source")
96
+ database = SubElement(source, "database")
97
+ database.text = "roboflow.ai"
98
+
99
+ # Add size element
100
+ size = SubElement(annotation, "size")
101
+ w = SubElement(size, "width")
102
+ w.text = str(width)
103
+ h = SubElement(size, "height")
104
+ h.text = str(height)
105
+ d = SubElement(size, "depth")
106
+ d.text = str(depth)
107
+
108
+ # Add segmented element
109
+ segmented = SubElement(annotation, "segmented")
110
+ segmented.text = "0"
111
+
112
+ # Add object elements
113
+ for xyxy, mask, _, class_id, _, _ in detections:
114
+ name = classes[class_id]
115
+ if mask is not None:
116
+ polygons = approximate_mask_with_polygons(
117
+ mask=mask,
118
+ min_image_area_percentage=min_image_area_percentage,
119
+ max_image_area_percentage=max_image_area_percentage,
120
+ approximation_percentage=approximation_percentage,
121
+ )
122
+ for polygon in polygons:
123
+ xyxy = polygon_to_xyxy(polygon=polygon)
124
+ next_object = object_to_pascal_voc(
125
+ xyxy=xyxy, name=name, polygon=polygon
126
+ )
127
+ annotation.append(next_object)
128
+ else:
129
+ next_object = object_to_pascal_voc(xyxy=xyxy, name=name)
130
+ annotation.append(next_object)
131
+
132
+ # Generate XML string
133
+ xml_string = parseString(tostring(annotation)).toprettyxml(indent=" ")
134
+ return xml_string
135
+
136
+
137
+ def load_pascal_voc_annotations(
138
+ images_directory_path: str,
139
+ annotations_directory_path: str,
140
+ force_masks: bool = False,
141
+ ) -> Tuple[List[str], List[str], Dict[str, Detections]]:
142
+ """
143
+ Loads PASCAL VOC XML annotations and returns the image name,
144
+ a Detections instance, and a list of class names.
145
+
146
+ Args:
147
+ images_directory_path (str): The path to the directory containing the images.
148
+ annotations_directory_path (str): The path to the directory containing the
149
+ PASCAL VOC annotation files.
150
+ force_masks (bool): If True, forces masks to be loaded for all
151
+ annotations, regardless of whether they are present.
152
+
153
+ Returns:
154
+ Tuple[List[str], List[str], Dict[str, Detections]]: A tuple with a list
155
+ of class names, a list of paths to images, and a dictionary with image
156
+ paths as keys and corresponding Detections instances as values.
157
+ """
158
+
159
+ image_paths = [
160
+ str(path)
161
+ for path in list_files_with_extensions(
162
+ directory=images_directory_path, extensions=["jpg", "jpeg", "png"]
163
+ )
164
+ ]
165
+
166
+ classes: List[str] = []
167
+ annotations = {}
168
+
169
+ for image_path in image_paths:
170
+ image_stem = Path(image_path).stem
171
+ annotation_path = os.path.join(annotations_directory_path, f"{image_stem}.xml")
172
+ if not os.path.exists(annotation_path):
173
+ annotations[image_path] = Detections.empty()
174
+ continue
175
+
176
+ tree = parse(annotation_path)
177
+ root = tree.getroot()
178
+
179
+ image = cv2.imread(image_path)
180
+ resolution_wh = (image.shape[1], image.shape[0])
181
+ annotation, classes = detections_from_xml_obj(
182
+ root, classes, resolution_wh, force_masks
183
+ )
184
+ annotations[image_path] = annotation
185
+
186
+ return classes, image_paths, annotations
187
+
188
+
189
+ def detections_from_xml_obj(
190
+ root: Element, classes: List[str], resolution_wh, force_masks: bool = False
191
+ ) -> Tuple[Detections, List[str]]:
192
+ """
193
+ Converts an XML object in Pascal VOC format to a Detections object.
194
+ Expected XML format:
195
+ <annotation>
196
+ ...
197
+ <object>
198
+ <name>dog</name>
199
+ <bndbox>
200
+ <xmin>48</xmin>
201
+ <ymin>240</ymin>
202
+ <xmax>195</xmax>
203
+ <ymax>371</ymax>
204
+ </bndbox>
205
+ <polygon>
206
+ <x1>48</x1>
207
+ <y1>240</y1>
208
+ <x2>195</x2>
209
+ <y2>240</y2>
210
+ <x3>195</x3>
211
+ <y3>371</y3>
212
+ <x4>48</x4>
213
+ <y4>371</y4>
214
+ </polygon>
215
+ </object>
216
+ </annotation>
217
+
218
+ Returns:
219
+ Tuple[Detections, List[str]]: A tuple containing a Detections object and an
220
+ updated list of class names, extended with the class names
221
+ from the XML object.
222
+ """
223
+ xyxy = []
224
+ class_names = []
225
+ masks = []
226
+ with_masks = False
227
+ extended_classes = classes[:]
228
+ for obj in root.findall("object"):
229
+ class_name = obj.find("name").text
230
+ class_names.append(class_name)
231
+
232
+ bbox = obj.find("bndbox")
233
+ x1 = int(bbox.find("xmin").text)
234
+ y1 = int(bbox.find("ymin").text)
235
+ x2 = int(bbox.find("xmax").text)
236
+ y2 = int(bbox.find("ymax").text)
237
+
238
+ xyxy.append([x1, y1, x2, y2])
239
+
240
+ with_masks = obj.find("polygon") is not None
241
+ with_masks = force_masks if force_masks else with_masks
242
+
243
+ for polygon in obj.findall("polygon"):
244
+ polygon = parse_polygon_points(polygon)
245
+ # https://github.com/roboflow/eye/issues/144
246
+ polygon -= 1
247
+
248
+ mask_from_polygon = polygon_to_mask(
249
+ polygon=polygon,
250
+ resolution_wh=resolution_wh,
251
+ )
252
+ masks.append(mask_from_polygon)
253
+
254
+ xyxy = np.array(xyxy) if len(xyxy) > 0 else np.empty((0, 4))
255
+
256
+ # https://github.com/roboflow/eye/issues/144
257
+ xyxy -= 1
258
+
259
+ for k in set(class_names):
260
+ if k not in extended_classes:
261
+ extended_classes.append(k)
262
+ class_id = np.array(
263
+ [extended_classes.index(class_name) for class_name in class_names]
264
+ )
265
+
266
+ annotation = Detections(
267
+ xyxy=xyxy.astype(np.float32),
268
+ mask=np.array(masks).astype(bool) if with_masks else None,
269
+ class_id=class_id,
270
+ )
271
+
272
+ return annotation, extended_classes
273
+
274
+
275
+ def parse_polygon_points(polygon: Element) -> np.ndarray:
276
+ coordinates = [int(coord.text) for coord in polygon.findall(".//*")]
277
+ return np.array(
278
+ [(coordinates[i], coordinates[i + 1]) for i in range(0, len(coordinates), 2)]
279
+ )