Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IO-1405][external] YoloV8 Segementation dataset support #643

Merged
merged 5 commits into from
Aug 14, 2023
Merged

Conversation

owencjones
Copy link
Contributor

Problem

We don't support YoloV8 Segmentation

Solution

Have implemented YoloV8 Segmentation datasets.

Changelog

Added new Yolo segementation exporter
Added Yolo shared helpers
Added social test for the exporter

@owencjones owencjones changed the title Io 1405 [IO-1405][external] YoloV8 Segementation dataset support Aug 4, 2023
@linear
Copy link

linear bot commented Aug 4, 2023

IO-1405 REQUEST: support Segmentation in the YOLO exports

Product Request from: Nathan Ascott
Summary (describe an issue):
Can we start to support Segmentation in the YOLO exports, as YOLO V8 supports segmentation from V8 onwards (Jan 2023). Customers looking to train YOLO models have requested this compatibility.
Darwin affected version
Both
Acceptance criteria
Being able to export polygons in YOLO format too or convert to them from JSON in the CLI.
Product area
IO
Impact
🟠 High - not vital, but adds significant financial or customer value
Deadline

Additional information

@@ -0,0 +1,46 @@
from pathlib import Path
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted some normal YOLO functions out for common usage by YOLO and YOLO segementation

_save_class_index(class_index, output_dir)


def _export_file(annotation_file: dt.AnnotationFile, class_index: ClassIndex, output_dir: Path) -> None:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions now in the shared helper file



def _build_txt(annotation_file: dt.AnnotationFile, class_index: ClassIndex) -> str:
yolo_lines = []
for annotation in annotation_file.annotations:
annotation_type = annotation.annotation_class.annotation_type

if isinstance(annotation, dt.VideoAnnotation):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixes type error - possibly YOLO can support Video, but our usage of it in the exporter doesn't and never has.


logger = getLogger(__name__)

CLOSE_VERTICES: bool = False # Set true if polygons need to be closed
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are relatively confident that we don't need to close vertices on a polygon, but switching this constant to True will make the exporter close vertices by adding the first coordinate at the end.

POLYGON = auto()


def _build_text(annotation_file: AnnotationFile, class_index: ClassIndex) -> str:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extensive logging which largely only shows at DEBUG level, so won't usually show to users, but they could opt to see. Allows for good diagnostics if this turns out not to work as intended.


try:
# Create 8 coordinates for the x,y pairs of the 4 corners
x1, y1, x2, y2, x3, y3, x4, y4, x5, y5 = (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bounding box is just the original coordinate, followed by 3 offset by width, then height, then -width.

Comment on lines 113 to 122
if "x" in annotation.data and "y" in annotation.data and "w" in annotation.data and "h" in annotation.data:
annotation_type = YoloSegmentedAnnotationType.BOUNDING_BOX
elif "points" in annotation.data:
if isinstance(annotation.data["points"][0], list):
logger.warn(f"Skipped annotation at index {annotation_index} because it's a complex polygon'")
continue

annotation_type = YoloSegmentedAnnotationType.POLYGON
else:
annotation_type = YoloSegmentedAnnotationType.UNKNOWN

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be useful to extract to a helper func, improves the readability then we can do something like:

annotation_type = get_annotation_type(annotation.data)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, agreed. In fact I only didn't because of the time frame. The original plan had a helper func for this, and separate predicate functions for handling bounding boxes and polygons

Comment on lines 134 to 191
logger.debug(f"Exporting bounding box at index {annotation_index}.")

try:
# Create 8 coordinates for the x,y pairs of the 4 corners
x1, y1, x2, y2, x3, y3, x4, y4, x5, y5 = (
data["x"],
data["y"],
(data["x"] + data["w"]),
(data["y"] + data["h"]),
(data["x"] + data["w"]),
data["y"],
data["x"],
(data["y"] + data["h"]),
data["x"],
data["y"],
)

logger.debug(
"Coordinates for bounding box: "
f"({x1}, {y1}), ({x2}, {y2}), "
f"({x3}, {y3}), ({x4}, {y4}), "
f"({x5}, {y5})" # Unsure if we have to close this.
)

# Normalize the coordinates to a proportion of the image size
n_x1 = normalise(x1, im_w)
n_y1 = normalise(y1, im_h)
n_x2 = normalise(x2, im_w)
n_y2 = normalise(y2, im_h)
n_x3 = normalise(x3, im_w)
n_y3 = normalise(y3, im_h)
n_x4 = normalise(x4, im_w)
n_y4 = normalise(y4, im_h)
n_x5 = normalise(x5, im_w)
n_y5 = normalise(y5, im_w)

logger.debug(
"Normalized coordinates for bounding box: "
f"({n_x1}, {n_y1}), ({n_x2}, {n_y2}), "
f"({n_x3}, {n_y3}), ({n_x4}, {n_y4}), "
f"({n_x5}, {n_y5})"
)

# Add the coordinates to the points list
points.append(Point(x=n_x1, y=n_y1))
points.append(Point(x=n_x2, y=n_y2))
points.append(Point(x=n_x3, y=n_y3))
points.append(Point(x=n_x4, y=n_y4))

if CLOSE_VERTICES:
points.append(Point(x=n_x5, y=n_y5))

except KeyError as exc:
logger.warn(
f"Skipped annotation at index {annotation_index} because an"
"expected key was not found in the data.",
exc_info=exc,
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also would extract to helper to improve readability

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, originally I did, but time was against me on this one

@rslota rslota merged commit 5f4e07d into master Aug 14, 2023
9 checks passed
@Nathanjp91 Nathanjp91 deleted the io_1405 branch November 8, 2023 10:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants