Skip to content

Commit 6d3b4ec

Browse files
committed
Working tests, pre-commit and pyright
1 parent 52cacf3 commit 6d3b4ec

16 files changed

+661
-197
lines changed

luxonis_ml/data/datasets/luxonis_dataset.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@
2828
SegmentationMaskDirectoryExporter,
2929
TensorflowCSVExporter,
3030
VOCExporter,
31-
YoloExporter,
32-
YOLOFormat,
31+
YoloV4Exporter,
32+
YoloV6Exporter,
33+
YoloV8Exporter,
3334
)
3435
from luxonis_ml.data.exporters.exporter_utils import (
3536
ExporterSpec,
@@ -1528,14 +1529,13 @@ def export(
15281529
},
15291530
),
15301531
DatasetType.YOLOV8: ExporterSpec(
1531-
YoloExporter, {"version": YOLOFormat.V8}
1532-
),
1533-
DatasetType.YOLOV6: ExporterSpec(
1534-
YoloExporter, {"version": YOLOFormat.V6}
1535-
),
1536-
DatasetType.YOLOV4: ExporterSpec(
1537-
YoloExporter, {"version": YOLOFormat.V4}
1532+
YoloV8Exporter,
1533+
{
1534+
"skeletons": getattr(self.metadata, "skeletons", None),
1535+
},
15381536
),
1537+
DatasetType.YOLOV6: ExporterSpec(YoloV6Exporter, {}),
1538+
DatasetType.YOLOV4: ExporterSpec(YoloV4Exporter, {}),
15391539
DatasetType.DARKNET: ExporterSpec(DarknetExporter, {}),
15401540
DatasetType.CLSDIR: ExporterSpec(
15411541
ClassificationDirectoryExporter, {}

luxonis_ml/data/exporters/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
)
1111
from .tensorflow_csv_exporter import TensorflowCSVExporter
1212
from .voc_exporter import VOCExporter
13-
from .yolo_exporter import YoloExporter, YOLOFormat
13+
from .yolov4_exporter import YoloV4Exporter
14+
from .yolov6_exporter import YoloV6Exporter
15+
from .yolov8_exporter import YoloV8Exporter
1416

1517
__all__ = [
1618
"BaseExporter",
@@ -23,6 +25,7 @@
2325
"SegmentationMaskDirectoryExporter",
2426
"TensorflowCSVExporter",
2527
"VOCExporter",
26-
"YOLOFormat",
27-
"YoloExporter",
28+
"YoloV4Exporter",
29+
"YoloV6Exporter",
30+
"YoloV8Exporter",
2831
]

luxonis_ml/data/exporters/base_exporter.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,7 @@ def _dump_annotations(
4444
self, annotations: dict, output_path: Path, part: int | None = None
4545
) -> None:
4646
raise NotImplementedError
47+
48+
@abstractmethod
49+
def supported_ann_types(self) -> list[str]:
50+
raise NotImplementedError

luxonis_ml/data/exporters/classification_directory_exporter.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@ class ClassificationDirectoryExporter(BaseExporter):
1111
def get_split_names(self) -> dict[str, str]:
1212
return {"train": "train", "val": "valid", "test": "test"}
1313

14+
def supported_ann_types(self) -> list[str]:
15+
return ["classification"]
16+
1417
def transform(self, prepared_ldf: PreparedLDF) -> None:
1518
ExporterUtils.check_group_file_correspondence(prepared_ldf)
19+
ExporterUtils.exporter_specific_annotation_warning(
20+
prepared_ldf, self.supported_ann_types()
21+
)
1622

1723
grouped = prepared_ldf.processed_df.group_by(
1824
["file", "group_id"], maintain_order=True

luxonis_ml/data/exporters/coco_exporter.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def __init__(
2424
max_partition_size_gb: float | None,
2525
format: COCOFormat = COCOFormat.ROBOFLOW,
2626
*,
27-
skeletons: dict[str, Any] | list[dict[str, Any]] | None = None,
27+
skeletons: dict[str, Any] | None = None,
2828
):
2929
super().__init__(
3030
dataset_identifier, output_path, max_partition_size_gb
@@ -56,14 +56,21 @@ def get_split_names(self) -> dict[str, str]:
5656
return {"train": "train", "val": "valid", "test": "test"}
5757
return {"train": "train", "val": "validation", "test": "test"}
5858

59+
def supported_ann_types(self) -> list[str]:
60+
return ["boundingbox", "instance_segmentation", "keypoints"]
61+
5962
def transform(self, prepared_ldf: PreparedLDF) -> None:
63+
ExporterUtils.check_group_file_correspondence(prepared_ldf)
64+
ExporterUtils.exporter_specific_annotation_warning(
65+
prepared_ldf, self.supported_ann_types()
66+
)
67+
6068
splits = self.get_split_names()
6169
annotation_splits: dict[str, dict[str, Any]] = {
6270
s: {"images": [], "categories": [], "annotations": []}
6371
for s in splits
6472
}
6573
ann_id_counter: dict[str, int] = {s: 1 for s in splits}
66-
ExporterUtils.check_group_file_correspondence(prepared_ldf)
6774

6875
grouped = prepared_ldf.processed_df.group_by(
6976
["file", "instance_id", "group_id"], maintain_order=True
@@ -262,8 +269,8 @@ def _fill_instance_segmentation(
262269
"size": [H, W],
263270
"counts": counts.encode("utf-8"),
264271
}
265-
area = float(maskUtils.area(rle_runtime))
266-
bbox = maskUtils.toBbox(rle_runtime).tolist() # [x,y,w,h]
272+
area = float(maskUtils.area(rle_runtime)) # type: ignore[arg-type]
273+
bbox = maskUtils.toBbox(rle_runtime).tolist() # type: ignore[arg-type]
267274
ann["area"] = area
268275
if not ann.get("bbox"):
269276
ann["bbox"] = bbox

luxonis_ml/data/exporters/createml_exporter.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,14 @@ def __init__(
2525
def get_split_names() -> dict[str, str]:
2626
return {"train": "train", "val": "valid", "test": "test"}
2727

28+
def supported_ann_types(self) -> list[str]:
29+
return ["boundingbox"]
30+
2831
def transform(self, prepared_ldf: PreparedLDF) -> None:
2932
ExporterUtils.check_group_file_correspondence(prepared_ldf)
33+
ExporterUtils.exporter_specific_annotation_warning(
34+
prepared_ldf, self.supported_ann_types()
35+
)
3036

3137
anns_by_split: dict[str, dict[str, list[dict[str, Any]]]] = {
3238
v: {} for v in self.get_split_names().values()

luxonis_ml/data/exporters/darknet_exporter.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,14 @@ def __init__(
2525
def get_split_names() -> dict[str, str]:
2626
return {"train": "train", "val": "valid", "test": "test"}
2727

28+
def supported_ann_types(self) -> list[str]:
29+
return ["boundingbox"]
30+
2831
def transform(self, prepared_ldf: PreparedLDF) -> None:
2932
ExporterUtils.check_group_file_correspondence(prepared_ldf)
33+
ExporterUtils.exporter_specific_annotation_warning(
34+
prepared_ldf, self.supported_ann_types()
35+
)
3036

3137
labels_by_split: dict[str, dict[str, list[str]]] = {
3238
v: {} for v in self.get_split_names().values()

luxonis_ml/data/exporters/exporter_utils.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from typing import TYPE_CHECKING, Any
55

66
import cv2
7+
import numpy as np
78
import polars as pl
9+
from loguru import logger
810
from pycocotools import mask
911

1012
if TYPE_CHECKING:
@@ -99,6 +101,29 @@ def check_group_file_correspondence(prepared_ldf: PreparedLDF) -> None:
99101
f"To export multiple files (e.g. RGB, depth) per instance, export to Native format"
100102
)
101103

104+
@staticmethod
105+
def exporter_specific_annotation_warning(
106+
prepared_ldf: PreparedLDF, supported_ann_types: list[str]
107+
) -> None:
108+
df = prepared_ldf.processed_df
109+
110+
present_task_types = (
111+
df.select(pl.col("task_type").drop_nans().drop_nulls().unique())
112+
.to_series()
113+
.to_list()
114+
)
115+
116+
unsupported = [
117+
name
118+
for name in present_task_types
119+
if name not in supported_ann_types
120+
]
121+
122+
for name in unsupported:
123+
logger.warning(
124+
f"Found unsupported annotation type '{name}'; skipping this annotation type."
125+
)
126+
102127
@staticmethod
103128
def split_of_group(prepared_ldf: PreparedLDF, group_id: Any) -> str:
104129
split = next(
@@ -137,7 +162,7 @@ def create_zip_output(
137162

138163
@staticmethod
139164
def get_single_skeleton(
140-
allow_keypoints: bool, skeletons: dict
165+
allow_keypoints: bool, skeletons: dict[str, Any] | None = None
141166
) -> tuple[list[str], list[list[int]]]:
142167
"""Returns (labels, skeleton_edges_1_based) for the single
143168
skeleton.
@@ -178,6 +203,45 @@ def rle_to_yolo_polygon(rle: str, height: int, width: int) -> list:
178203

179204
return polygons
180205

206+
@staticmethod
207+
def _bbox_from_poly(
208+
coords: list[float],
209+
) -> tuple[float, float, float, float]:
210+
xs = coords[0::2]
211+
ys = coords[1::2]
212+
x_min, x_max = min(xs), max(xs)
213+
y_min, y_max = min(ys), max(ys)
214+
return x_min, y_min, (x_max - x_min), (y_max - y_min)
215+
216+
@staticmethod
217+
def _iou_xywh(
218+
a: tuple[float, float, float, float],
219+
b: tuple[float, float, float, float],
220+
) -> float:
221+
ax, ay, aw, ah = a
222+
bx, by, bw, bh = b
223+
ax2, ay2 = ax + aw, ay + ah
224+
bx2, by2 = bx + bw, by + bh
225+
inter_w = max(0.0, min(ax2, bx2) - max(ax, bx))
226+
inter_h = max(0.0, min(ay2, by2) - max(ay, by))
227+
inter = inter_w * inter_h
228+
if inter <= 0.0:
229+
return 0.0
230+
union = aw * ah + bw * bh - inter
231+
return inter / union if union > 0.0 else 0.0
232+
233+
@staticmethod
234+
def decode_rle_with_pycoco(ann: dict[str, Any]) -> np.ndarray:
235+
h = int(ann["height"])
236+
w = int(ann["width"])
237+
counts = ann["counts"]
238+
239+
# pycocotools expects an RLE object with 'size' and 'counts'
240+
rle = {"size": [h, w], "counts": counts.encode("utf-8")}
241+
242+
m = mask.decode(rle) # type: ignore[arg-type]
243+
return np.array(m, dtype=np.uint8, order="C")
244+
181245
def _normalize(
182246
self, xs: list[float], ys: list[float], w: float, h: float
183247
) -> list[float]:

luxonis_ml/data/exporters/native_exporter.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,19 @@ class NativeExporter(BaseExporter):
1919
def get_split_names() -> dict[str, str]:
2020
return {"train": "train", "val": "val", "test": "test"}
2121

22-
SUPPORTED_ANN_TYPES: set[str] = {
23-
"boundingbox",
24-
"segmentation",
25-
"keypoints",
26-
"instance_segmentation",
27-
}
22+
def supported_ann_types(self) -> list[str]:
23+
return [
24+
"boundingbox",
25+
"segmentation",
26+
"keypoints",
27+
"instance_segmentation",
28+
]
2829

2930
def transform(self, prepared_ldf: PreparedLDF) -> None:
31+
ExporterUtils.exporter_specific_annotation_warning(
32+
prepared_ldf, self.supported_ann_types()
33+
)
34+
3035
annotation_splits: dict[str, list[dict[str, Any]]] = {
3136
k: [] for k in self.get_split_names()
3237
}
@@ -125,7 +130,7 @@ def _process_row(
125130
"instance_id": instance_id,
126131
"class": class_name,
127132
}
128-
if task_type in self.SUPPORTED_ANN_TYPES:
133+
if task_type in self.supported_ann_types():
129134
ann[task_type] = data
130135
elif task_type.startswith("metadata/"):
131136
ann["metadata"] = {task_type[9:]: data}

0 commit comments

Comments
 (0)