feat: Add native YOLO dataset import and in-place editing#239
Open
LeonKraim wants to merge 6 commits into
Open
feat: Add native YOLO dataset import and in-place editing#239LeonKraim wants to merge 6 commits into
LeonKraim wants to merge 6 commits into
Conversation
Allows opening a YOLO-format dataset directory via Open Dir (Ctrl+U) and editing annotations in-place without creating AnyLabeling .json files. - New anylabeling/views/labeling/yolo_io.py with utilities: - find_dataset_yaml() walks up 5 levels looking for data.yaml - read_dataset_yaml() parses nc + names into id-to-label mapping - read_yolo_label() parses .txt files into AnyLabeling shapes - write_yolo_label() writes shapes back to YOLO .txt format - resolve_yolo_label_path() finds sibling labels/ directory - Modified label_widget.py: - import_image_folder() detects data.yaml, populates class map - load_file() falls back to .txt when no .json exists - save_labels() writes .txt when source was YOLO format - reset_state() clears YOLO path - get_label_file() returns .txt path in YOLO mode - import_dropped_image_files() checks for .txt checkmarks Priority: .json > .txt (same dir) > .txt (sibling labels/) No regression on existing JSON-only workflows.
…sses.txt Fixes a bug where _yolo_label_path was set once and never reset, causing YOLO labels from the first image to persist when navigating to other images, and saving to overwrite the wrong .txt file. Now resolves the path per-image in load_file(). Adds data.yaml / classes.txt sync: - find_dataset_config() discovers both data.yaml and classes.txt - update_dataset_config() keeps config in sync when new classes are added in the UI (targeted regex for YAML, plain rewrite for txt) - Preserves all other YAML content (comments, train/val, roboflow) Adds error handling with try/except + logging on all YOLO I/O paths to prevent crashes from corrupt label files.
When save_labels() writes data.yaml with a new class via update_dataset_config(), the in-memory _yolo_id_to_label was never updated. On the next image switch + return, read_yolo_label() would look up the new class_id in the stale _yolo_id_to_label, find None, and fall back to class_N.
During YOLO dataset import, class names were only added to the label dialog's autocomplete history (add_label_history), but not to the unique_label_list sidebar. This made the label popup always start blank on the first shape, and forced the user to type or scroll the dropdown every time — unlike the non-YOLO workflow where clicking a label in the sidebar pre-fills it for the next shape.
There was a problem hiding this comment.
Pull request overview
This PR adds first-class support for opening YOLO-format datasets via “Open Dir” and editing YOLO annotations in-place, avoiding AnyLabeling JSON sidecar creation when the source dataset is YOLO.
Changes:
- Introduces
yolo_io.pyutilities for reading/writing YOLO detection and segmentation labels, plus dataset config discovery/update helpers. - Extends
LabelingWidgetto detect YOLO datasets (data.yaml/classes.txt), load.txtlabels when.jsonis absent, and save back to YOLO format. - Adds unit tests covering YOLO label read/write and round-trip behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| tests/test_yolo_io.py | Adds unit tests for YOLO label parsing/writing (detection + polygon segmentation). |
| anylabeling/views/labeling/yolo_io.py | New YOLO dataset and label I/O utilities, including config updating logic. |
| anylabeling/views/labeling/label_widget.py | Integrates YOLO dataset detection, .txt loading fallback, and .txt saving path into the labeling UI flow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
1412
to
1420
| def reset_state(self): | ||
| self.label_list.clear() | ||
| self.filename = None | ||
| self.image_path = None | ||
| self.image_data = None | ||
| self.label_file = None | ||
| self.other_data = {} | ||
| self._yolo_label_path = None | ||
| self.canvas.reset_state() |
Comment on lines
+34
to
+39
| with open(yaml_path, "r", encoding="utf-8") as f: | ||
| data = yaml.safe_load(f) | ||
|
|
||
| nc = data.get("nc", 0) | ||
| raw_names = data.get("names", []) | ||
|
|
Comment on lines
+275
to
+287
| content = re.sub( | ||
| r"^nc:\s*\d+", | ||
| f"nc: {len(ordered_names)}", | ||
| content, | ||
| flags=re.MULTILINE, | ||
| ) | ||
| names_str = repr(ordered_names) | ||
| content = re.sub( | ||
| r"^names:\s*\[.*?\]|^names:\s*\n(\s+- .*\n?)*", | ||
| f"names: {names_str}", | ||
| content, | ||
| flags=re.MULTILINE | re.DOTALL, | ||
| ) |
Comment on lines
+1862
to
+1886
| write_yolo_label( | ||
| self._yolo_label_path, | ||
| shapes, | ||
| self.image.width(), | ||
| self.image.height(), | ||
| self._yolo_label_to_id, | ||
| ) | ||
| self.label_file = None | ||
| if ( | ||
| self._yolo_config_path | ||
| and self._yolo_label_to_id.keys() | ||
| != self._yolo_original_label_to_id.keys() | ||
| ): | ||
| update_dataset_config( | ||
| self._yolo_config_path, | ||
| self._yolo_config_type, | ||
| self._yolo_label_to_id, | ||
| ) | ||
| self._yolo_original_label_to_id = dict( | ||
| self._yolo_label_to_id | ||
| ) | ||
| self._yolo_id_to_label = { | ||
| v: k for k, v in self._yolo_label_to_id.items() | ||
| } | ||
| except Exception: |
Comment on lines
+79
to
+84
| parts = line.split() | ||
| if len(parts) < 5: | ||
| continue | ||
|
|
||
| class_id = int(parts[0]) | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Allows opening a YOLO-format dataset directory via \Open Dir\ (Ctrl+U) and editing annotations in-place without creating AnyLabeling .json files.
Changes
New file: \�nylabeling/views/labeling/yolo_io.py\ with utilities:
ead_dataset_yaml()\ — parses
c\ +
ames\ into id-to-label mapping
ead_yolo_label()\ — parses YOLO .txt into AnyLabeling shape dicts
esolve_yolo_label_path()\ — finds sibling \labels/\ directory
Modified: \�nylabeling/views/labeling/label_widget.py\
eset_state()\ clears YOLO path
Priority
.json\ > .txt\ (same dir) > .txt\ (sibling \labels/)
Testing
All 92 existing unit tests pass. No regression on JSON-only workflows.