LabelCraft 2.0.4__tar.gz → 2.1.0__tar.gz
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.
- {labelcraft-2.0.4 → labelcraft-2.1.0}/LabelCraft.egg-info/PKG-INFO +5 -3
- {labelcraft-2.0.4 → labelcraft-2.1.0}/LabelCraft.egg-info/SOURCES.txt +1 -1
- {labelcraft-2.0.4 → labelcraft-2.1.0}/PKG-INFO +5 -3
- {labelcraft-2.0.4 → labelcraft-2.1.0}/README.md +4 -2
- {labelcraft-2.0.4 → labelcraft-2.1.0}/labelcraft_ui.py +224 -7
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/__init__.py +1 -1
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/annotation_converter.py +34 -1
- labelcraft-2.1.0/libs/annotation_importer.py +1010 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/locales/de-DE.json +41 -1
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/locales/en.json +41 -1
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/locales/fr-FR.json +41 -1
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/locales/ja-JP.json +41 -1
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/locales/zh-CN.json +42 -2
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/locales/zh-TW.json +41 -1
- labelcraft-2.0.4/libs/views/main_window_ui.py +0 -260
- {labelcraft-2.0.4 → labelcraft-2.1.0}/LabelCraft.egg-info/dependency_links.txt +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/LabelCraft.egg-info/entry_points.txt +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/LabelCraft.egg-info/not-zip-safe +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/LabelCraft.egg-info/requires.txt +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/LabelCraft.egg-info/top_level.txt +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/MANIFEST.in +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/data/coco_classes.txt +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/data/predefined_classes.txt +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/canvas.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/coco_io.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/colorDialog.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/combobox.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/constants.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/create_ml_io.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/csv_io.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/default_label_combobox.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/hashableQListWidgetItem.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/i18n_engine.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/json_io.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/labelDialog.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/labelFile.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/lightWidget.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/newProjectDialog.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/pascal_voc_io.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/project.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/resources.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/settings.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/shape.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/toolBar.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/ustr.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/utils.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/yolo_io.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/libs/zoomWidget.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/main.py +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/app.icns +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/app.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/app.svg +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/app_screen.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/cancel.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/close.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/color.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/color_line.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/copy.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/delete.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/done.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/done.svg +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/edit.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/expert1.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/expert2.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/eye.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/feBlend-icon.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/file.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/fit-width.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/fit-window.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/fit.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/format_createml.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/format_voc.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/format_yolo.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/help.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/labels.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/labels.svg +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/light_brighten.svg +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/light_darken.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/light_darken.svg +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/light_lighten.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/light_reset.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/light_reset.svg +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/new.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/next.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/objects.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/open.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/open.svg +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/prev.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/quit.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/resetall.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/save-as.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/save-as.svg +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/save.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/save.svg +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/undo-cross.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/undo.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/verify.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/zoom-in.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/zoom-out.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources/icons/zoom.png +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/resources.qrc +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/setup.cfg +0 -0
- {labelcraft-2.0.4 → labelcraft-2.1.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: LabelCraft
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: LabelCraft - A modern graphical image annotation tool based on labelImg
|
|
5
5
|
Home-page: https://github.com/syd168/LabelCraft
|
|
6
6
|
Author: LabelCraft Contributors
|
|
@@ -37,12 +37,12 @@ Dynamic: summary
|
|
|
37
37
|
|
|
38
38
|
# LabelCraft - Intelligent Image Annotation Tool
|
|
39
39
|
|
|
40
|
-
> **Version 2.
|
|
40
|
+
> **Version 2.1.0** - A modern image annotation tool with project management, developed based on [labelImg](https://github.com/tzutalin/labelImg)
|
|
41
41
|
|
|
42
42
|
[](LICENSE)
|
|
43
43
|
[](https://www.python.org/)
|
|
44
44
|
[](https://www.qt.io/)
|
|
45
|
-
[](https://github.com/syd168/LabelCraft/releases)
|
|
46
46
|
[](https://pepy.tech/project/labelcraft)
|
|
47
47
|
|
|
48
48
|
**[中文文档](README-CN.md)** | **[English](README.md)**
|
|
@@ -90,6 +90,8 @@ Dynamic language switching without restart:
|
|
|
90
90
|
- Default label for batch annotation
|
|
91
91
|
- Verification mode for quality control
|
|
92
92
|
- Copy previous frame annotations
|
|
93
|
+
- **Import external annotations** (v2.0.4+) - Import from YOLO, VOC, COCO, CreateML datasets
|
|
94
|
+
- **Smart label mapping** - Automatically maps imported labels to project definitions
|
|
93
95
|
|
|
94
96
|
### 🛠️ Developer Tools
|
|
95
97
|
- Built-in format converter (all 5 formats)
|
|
@@ -16,6 +16,7 @@ data/coco_classes.txt
|
|
|
16
16
|
data/predefined_classes.txt
|
|
17
17
|
libs/__init__.py
|
|
18
18
|
libs/annotation_converter.py
|
|
19
|
+
libs/annotation_importer.py
|
|
19
20
|
libs/canvas.py
|
|
20
21
|
libs/coco_io.py
|
|
21
22
|
libs/colorDialog.py
|
|
@@ -47,7 +48,6 @@ libs/locales/fr-FR.json
|
|
|
47
48
|
libs/locales/ja-JP.json
|
|
48
49
|
libs/locales/zh-CN.json
|
|
49
50
|
libs/locales/zh-TW.json
|
|
50
|
-
libs/views/main_window_ui.py
|
|
51
51
|
resources/icons/app.icns
|
|
52
52
|
resources/icons/app.png
|
|
53
53
|
resources/icons/app.svg
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: LabelCraft
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: LabelCraft - A modern graphical image annotation tool based on labelImg
|
|
5
5
|
Home-page: https://github.com/syd168/LabelCraft
|
|
6
6
|
Author: LabelCraft Contributors
|
|
@@ -37,12 +37,12 @@ Dynamic: summary
|
|
|
37
37
|
|
|
38
38
|
# LabelCraft - Intelligent Image Annotation Tool
|
|
39
39
|
|
|
40
|
-
> **Version 2.
|
|
40
|
+
> **Version 2.1.0** - A modern image annotation tool with project management, developed based on [labelImg](https://github.com/tzutalin/labelImg)
|
|
41
41
|
|
|
42
42
|
[](LICENSE)
|
|
43
43
|
[](https://www.python.org/)
|
|
44
44
|
[](https://www.qt.io/)
|
|
45
|
-
[](https://github.com/syd168/LabelCraft/releases)
|
|
46
46
|
[](https://pepy.tech/project/labelcraft)
|
|
47
47
|
|
|
48
48
|
**[中文文档](README-CN.md)** | **[English](README.md)**
|
|
@@ -90,6 +90,8 @@ Dynamic language switching without restart:
|
|
|
90
90
|
- Default label for batch annotation
|
|
91
91
|
- Verification mode for quality control
|
|
92
92
|
- Copy previous frame annotations
|
|
93
|
+
- **Import external annotations** (v2.0.4+) - Import from YOLO, VOC, COCO, CreateML datasets
|
|
94
|
+
- **Smart label mapping** - Automatically maps imported labels to project definitions
|
|
93
95
|
|
|
94
96
|
### 🛠️ Developer Tools
|
|
95
97
|
- Built-in format converter (all 5 formats)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# LabelCraft - Intelligent Image Annotation Tool
|
|
2
2
|
|
|
3
|
-
> **Version 2.
|
|
3
|
+
> **Version 2.1.0** - A modern image annotation tool with project management, developed based on [labelImg](https://github.com/tzutalin/labelImg)
|
|
4
4
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://www.python.org/)
|
|
7
7
|
[](https://www.qt.io/)
|
|
8
|
-
[](https://github.com/syd168/LabelCraft/releases)
|
|
9
9
|
[](https://pepy.tech/project/labelcraft)
|
|
10
10
|
|
|
11
11
|
**[中文文档](README-CN.md)** | **[English](README.md)**
|
|
@@ -53,6 +53,8 @@ Dynamic language switching without restart:
|
|
|
53
53
|
- Default label for batch annotation
|
|
54
54
|
- Verification mode for quality control
|
|
55
55
|
- Copy previous frame annotations
|
|
56
|
+
- **Import external annotations** (v2.0.4+) - Import from YOLO, VOC, COCO, CreateML datasets
|
|
57
|
+
- **Smart label mapping** - Automatically maps imported labels to project definitions
|
|
56
58
|
|
|
57
59
|
### 🛠️ Developer Tools
|
|
58
60
|
- Built-in format converter (all 5 formats)
|
|
@@ -8,6 +8,7 @@ import platform
|
|
|
8
8
|
import shutil
|
|
9
9
|
import sys
|
|
10
10
|
import webbrowser as wb
|
|
11
|
+
from datetime import datetime
|
|
11
12
|
from functools import partial
|
|
12
13
|
|
|
13
14
|
from PySide6.QtGui import *
|
|
@@ -398,7 +399,9 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
398
399
|
copy_prev_bounding = action(self.get_str('copyPrevBounding'), self.copy_previous_bounding_boxes, 'Ctrl+v', 'copy',
|
|
399
400
|
self.get_str('copyPrevBounding'))
|
|
400
401
|
|
|
401
|
-
#
|
|
402
|
+
# Data menu actions: import and export
|
|
403
|
+
import_annotations = action(self.get_str('importAnnotations'), self.import_annotations_dialog,
|
|
404
|
+
'Ctrl+I', 'open', self.get_str('importAnnotationsDetail'))
|
|
402
405
|
export_annotations = action(self.get_str('exportAnnotations'), self.export_annotations_dialog,
|
|
403
406
|
'Ctrl+E', 'save-as', self.get_str('exportAnnotationsDetail'))
|
|
404
407
|
|
|
@@ -615,7 +618,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
615
618
|
# Add missing actions for language switching
|
|
616
619
|
openDir=open_dir, changeSaveDir=change_save_dir,
|
|
617
620
|
openAnnotation=open_annotation, copyPrevBounding=copy_prev_bounding,
|
|
618
|
-
exportAnnotations=export_annotations,
|
|
621
|
+
importAnnotations=import_annotations, exportAnnotations=export_annotations,
|
|
619
622
|
nextImg=open_next_image, prevImg=open_prev_image,
|
|
620
623
|
verify=verify, verifyAll=verify_all, filterUnverified=filter_unverified, toggleLabels=toggle_labels,
|
|
621
624
|
setVerified=set_verified, setUnverified=set_unverified,
|
|
@@ -710,9 +713,9 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
710
713
|
self.menus.recentProjects, None, # Add recent projects menu
|
|
711
714
|
save, reset_all, delete_image, quit))
|
|
712
715
|
|
|
713
|
-
#
|
|
716
|
+
# Data menu: import and export annotations
|
|
714
717
|
add_actions(self.menus.output,
|
|
715
|
-
(export_annotations, None,
|
|
718
|
+
(import_annotations, export_annotations, None,
|
|
716
719
|
self.auto_saving, self.single_class_mode))
|
|
717
720
|
|
|
718
721
|
# Custom context menu for the canvas widget:
|
|
@@ -1572,7 +1575,10 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
1572
1575
|
self.single_class_mode.setText(self.get_str('singleClsMode'))
|
|
1573
1576
|
self.display_label_option.setText(self.get_str('displayLabel'))
|
|
1574
1577
|
|
|
1575
|
-
# Update export
|
|
1578
|
+
# Update import and export actions
|
|
1579
|
+
if hasattr(self.actions, 'importAnnotations'):
|
|
1580
|
+
self.actions.importAnnotations.setText(self.get_str('importAnnotations'))
|
|
1581
|
+
self.actions.importAnnotations.setToolTip(self.get_str('importAnnotationsDetail'))
|
|
1576
1582
|
if hasattr(self.actions, 'exportAnnotations'):
|
|
1577
1583
|
self.actions.exportAnnotations.setText(self.get_str('exportAnnotations'))
|
|
1578
1584
|
self.actions.exportAnnotations.setToolTip(self.get_str('exportAnnotationsDetail'))
|
|
@@ -4753,6 +4759,13 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
4753
4759
|
+ self.get_str('supportConversion')
|
|
4754
4760
|
)
|
|
4755
4761
|
info_label.setWordWrap(True)
|
|
4762
|
+
# Use palette colors for better readability in all themes
|
|
4763
|
+
info_label.setStyleSheet(
|
|
4764
|
+
'padding: 10px; '
|
|
4765
|
+
'background-color: palette(alternate-base); '
|
|
4766
|
+
'color: palette(text); '
|
|
4767
|
+
'border-radius: 4px;'
|
|
4768
|
+
)
|
|
4756
4769
|
main_layout.addWidget(info_label)
|
|
4757
4770
|
|
|
4758
4771
|
# Buttons
|
|
@@ -4803,8 +4816,13 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
4803
4816
|
|
|
4804
4817
|
# Create subdirectories for export
|
|
4805
4818
|
import shutil
|
|
4806
|
-
|
|
4807
|
-
|
|
4819
|
+
# For YOLO format, use standard directory structure: images/ and labels/
|
|
4820
|
+
if selected_format[0] == LabelFileFormat.YOLO:
|
|
4821
|
+
dest_images_dir = os.path.join(export_dir, 'images')
|
|
4822
|
+
dest_annotations_dir = os.path.join(export_dir, 'labels')
|
|
4823
|
+
else:
|
|
4824
|
+
dest_images_dir = os.path.join(export_dir, 'images')
|
|
4825
|
+
dest_annotations_dir = os.path.join(export_dir, 'annotations')
|
|
4808
4826
|
|
|
4809
4827
|
os.makedirs(dest_images_dir, exist_ok=True)
|
|
4810
4828
|
os.makedirs(dest_annotations_dir, exist_ok=True)
|
|
@@ -5030,11 +5048,210 @@ class MainWindow(QMainWindow, WindowMixin):
|
|
|
5030
5048
|
|
|
5031
5049
|
print(f"Export completed: {exported_count} files exported to {export_dir}")
|
|
5032
5050
|
|
|
5051
|
+
# Generate format-specific configuration files
|
|
5052
|
+
if selected_format[0] == LabelFileFormat.YOLO and exported_count > 0:
|
|
5053
|
+
try:
|
|
5054
|
+
self._generate_yolo_data_yaml(export_dir, classes_list)
|
|
5055
|
+
except Exception as yaml_error:
|
|
5056
|
+
print(f"Warning: Failed to generate data.yaml: {yaml_error}")
|
|
5057
|
+
elif selected_format[0] in [LabelFileFormat.COCO, LabelFileFormat.CREATE_ML] and exported_count > 0:
|
|
5058
|
+
try:
|
|
5059
|
+
self._merge_json_annotations(dest_annotations_dir, output_format_str, classes_list)
|
|
5060
|
+
except Exception as merge_error:
|
|
5061
|
+
print(f"Warning: Failed to merge JSON annotations: {merge_error}")
|
|
5062
|
+
|
|
5033
5063
|
except Exception as e:
|
|
5034
5064
|
QMessageBox.critical(self, self.get_str('errorTitle'),
|
|
5035
5065
|
f'{self.get_str("exportFailed")}\n\n{str(e)}')
|
|
5036
5066
|
import traceback
|
|
5037
5067
|
traceback.print_exc()
|
|
5068
|
+
|
|
5069
|
+
def _generate_yolo_data_yaml(self, export_dir, classes_list):
|
|
5070
|
+
"""Generate data.yaml file for YOLO format export
|
|
5071
|
+
|
|
5072
|
+
Args:
|
|
5073
|
+
export_dir: Export directory path
|
|
5074
|
+
classes_list: List of class names
|
|
5075
|
+
"""
|
|
5076
|
+
yaml_path = os.path.join(export_dir, 'data.yaml')
|
|
5077
|
+
|
|
5078
|
+
# Build YAML content manually to avoid dependency on PyYAML
|
|
5079
|
+
lines = []
|
|
5080
|
+
lines.append('# YOLO dataset configuration')
|
|
5081
|
+
lines.append(f'# Generated by LabelCraft on {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
|
|
5082
|
+
lines.append('')
|
|
5083
|
+
|
|
5084
|
+
# Paths (relative to this yaml file)
|
|
5085
|
+
lines.append('path: . # dataset root dir')
|
|
5086
|
+
lines.append('train: images # train images (relative to path)')
|
|
5087
|
+
lines.append('val: images # val images (relative to path)')
|
|
5088
|
+
lines.append('test: images # test images (optional, relative to path)')
|
|
5089
|
+
lines.append('')
|
|
5090
|
+
|
|
5091
|
+
# Classes
|
|
5092
|
+
lines.append(f'nc: {len(classes_list)} # number of classes')
|
|
5093
|
+
lines.append('names:')
|
|
5094
|
+
|
|
5095
|
+
for idx, class_name in enumerate(classes_list):
|
|
5096
|
+
# Escape quotes in class names
|
|
5097
|
+
escaped_name = class_name.replace('"', '\\"')
|
|
5098
|
+
lines.append(f' {idx}: "{escaped_name}"')
|
|
5099
|
+
|
|
5100
|
+
# Write to file
|
|
5101
|
+
with open(yaml_path, 'w', encoding='utf-8') as f:
|
|
5102
|
+
f.write('\n'.join(lines))
|
|
5103
|
+
f.write('\n')
|
|
5104
|
+
|
|
5105
|
+
print(f"Generated data.yaml at: {yaml_path}")
|
|
5106
|
+
print(f" - Number of classes: {len(classes_list)}")
|
|
5107
|
+
print(f" - Classes: {', '.join(classes_list)}")
|
|
5108
|
+
|
|
5109
|
+
def import_annotations_dialog(self, _value=False):
|
|
5110
|
+
"""Import annotations from external directory into current project.
|
|
5111
|
+
|
|
5112
|
+
Uses the AnnotationImporter module for a clean separation of concerns.
|
|
5113
|
+
"""
|
|
5114
|
+
from libs.annotation_importer import AnnotationImporter
|
|
5115
|
+
|
|
5116
|
+
importer = AnnotationImporter(
|
|
5117
|
+
parent=self,
|
|
5118
|
+
current_project=self.current_project,
|
|
5119
|
+
get_str_func=self.get_str
|
|
5120
|
+
)
|
|
5121
|
+
|
|
5122
|
+
importer.import_annotations()
|
|
5123
|
+
|
|
5124
|
+
def _merge_json_annotations(self, annotations_dir, format_str, classes_list):
|
|
5125
|
+
"""Merge individual JSON annotation files into a single file for COCO/CreateML formats
|
|
5126
|
+
|
|
5127
|
+
Args:
|
|
5128
|
+
annotations_dir: Directory containing individual JSON files
|
|
5129
|
+
format_str: Format string ('coco' or 'createml')
|
|
5130
|
+
classes_list: List of class names
|
|
5131
|
+
"""
|
|
5132
|
+
import glob
|
|
5133
|
+
|
|
5134
|
+
# Find all JSON files in the annotations directory
|
|
5135
|
+
json_files = sorted(glob.glob(os.path.join(annotations_dir, '*.json')))
|
|
5136
|
+
|
|
5137
|
+
if not json_files:
|
|
5138
|
+
print(f"No JSON files found in {annotations_dir}")
|
|
5139
|
+
return
|
|
5140
|
+
|
|
5141
|
+
print(f"Merging {len(json_files)} JSON files into single {format_str.upper()} format...")
|
|
5142
|
+
|
|
5143
|
+
if format_str == 'coco':
|
|
5144
|
+
self._merge_coco_annotations(json_files, annotations_dir, classes_list)
|
|
5145
|
+
elif format_str == 'createml':
|
|
5146
|
+
self._merge_createml_annotations(json_files, annotations_dir)
|
|
5147
|
+
|
|
5148
|
+
def _merge_coco_annotations(self, json_files, output_dir, classes_list):
|
|
5149
|
+
"""Merge multiple COCO JSON files into one
|
|
5150
|
+
|
|
5151
|
+
Standard COCO format has:
|
|
5152
|
+
- info: dataset information
|
|
5153
|
+
- licenses: license information
|
|
5154
|
+
- images: list of all images
|
|
5155
|
+
- annotations: list of all annotations
|
|
5156
|
+
- categories: list of all categories
|
|
5157
|
+
"""
|
|
5158
|
+
merged_data = {
|
|
5159
|
+
'info': {
|
|
5160
|
+
'description': 'Converted from LabelCraft',
|
|
5161
|
+
'version': '1.0',
|
|
5162
|
+
'year': datetime.now().year,
|
|
5163
|
+
'contributor': 'LabelCraft',
|
|
5164
|
+
'date_created': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
5165
|
+
},
|
|
5166
|
+
'licenses': [],
|
|
5167
|
+
'images': [],
|
|
5168
|
+
'annotations': [],
|
|
5169
|
+
'categories': [{'id': idx + 1, 'name': name} for idx, name in enumerate(classes_list)]
|
|
5170
|
+
}
|
|
5171
|
+
|
|
5172
|
+
image_id = 1
|
|
5173
|
+
ann_id = 1
|
|
5174
|
+
|
|
5175
|
+
for json_file in json_files:
|
|
5176
|
+
try:
|
|
5177
|
+
with open(json_file, 'r', encoding='utf-8') as f:
|
|
5178
|
+
data = json.load(f)
|
|
5179
|
+
|
|
5180
|
+
# Extract image info
|
|
5181
|
+
if 'images' in data and len(data['images']) > 0:
|
|
5182
|
+
img_info = data['images'][0]
|
|
5183
|
+
img_info['id'] = image_id
|
|
5184
|
+
merged_data['images'].append(img_info)
|
|
5185
|
+
|
|
5186
|
+
# Extract annotations and update IDs
|
|
5187
|
+
if 'annotations' in data:
|
|
5188
|
+
for ann in data['annotations']:
|
|
5189
|
+
ann['id'] = ann_id
|
|
5190
|
+
ann['image_id'] = image_id
|
|
5191
|
+
merged_data['annotations'].append(ann)
|
|
5192
|
+
ann_id += 1
|
|
5193
|
+
|
|
5194
|
+
image_id += 1
|
|
5195
|
+
|
|
5196
|
+
except Exception as e:
|
|
5197
|
+
print(f"Warning: Failed to process {json_file}: {e}")
|
|
5198
|
+
continue
|
|
5199
|
+
|
|
5200
|
+
# Write merged COCO file
|
|
5201
|
+
output_path = os.path.join(output_dir, 'instances_default.json')
|
|
5202
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
5203
|
+
json.dump(merged_data, f, indent=2, ensure_ascii=False)
|
|
5204
|
+
|
|
5205
|
+
# Remove individual JSON files
|
|
5206
|
+
for json_file in json_files:
|
|
5207
|
+
try:
|
|
5208
|
+
os.remove(json_file)
|
|
5209
|
+
except:
|
|
5210
|
+
pass
|
|
5211
|
+
|
|
5212
|
+
print(f"✓ Merged COCO annotations: {output_path}")
|
|
5213
|
+
print(f" - Images: {len(merged_data['images'])}")
|
|
5214
|
+
print(f" - Annotations: {len(merged_data['annotations'])}")
|
|
5215
|
+
print(f" - Categories: {len(merged_data['categories'])}")
|
|
5216
|
+
|
|
5217
|
+
def _merge_createml_annotations(self, json_files, output_dir):
|
|
5218
|
+
"""Merge multiple CreateML JSON files into one
|
|
5219
|
+
|
|
5220
|
+
Standard CreateML format is a list of objects:
|
|
5221
|
+
[
|
|
5222
|
+
{"image": "image1.jpg", "annotations": [...]},
|
|
5223
|
+
{"image": "image2.jpg", "annotations": [...]}
|
|
5224
|
+
]
|
|
5225
|
+
"""
|
|
5226
|
+
merged_data = []
|
|
5227
|
+
|
|
5228
|
+
for json_file in json_files:
|
|
5229
|
+
try:
|
|
5230
|
+
with open(json_file, 'r', encoding='utf-8') as f:
|
|
5231
|
+
data = json.load(f)
|
|
5232
|
+
|
|
5233
|
+
# CreateML format is already a list
|
|
5234
|
+
if isinstance(data, list) and len(data) > 0:
|
|
5235
|
+
merged_data.append(data[0]) # Take first (and usually only) item
|
|
5236
|
+
|
|
5237
|
+
except Exception as e:
|
|
5238
|
+
print(f"Warning: Failed to process {json_file}: {e}")
|
|
5239
|
+
continue
|
|
5240
|
+
|
|
5241
|
+
# Write merged CreateML file
|
|
5242
|
+
output_path = os.path.join(output_dir, 'annotations.json')
|
|
5243
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
5244
|
+
json.dump(merged_data, f, indent=2, ensure_ascii=False)
|
|
5245
|
+
|
|
5246
|
+
# Remove individual JSON files
|
|
5247
|
+
for json_file in json_files:
|
|
5248
|
+
try:
|
|
5249
|
+
os.remove(json_file)
|
|
5250
|
+
except:
|
|
5251
|
+
pass
|
|
5252
|
+
|
|
5253
|
+
print(f"✓ Merged CreateML annotations: {output_path}")
|
|
5254
|
+
print(f" - Total images: {len(merged_data)}")
|
|
5038
5255
|
|
|
5039
5256
|
|
|
5040
5257
|
def inverted(color):
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version_info__ = ('2', '
|
|
1
|
+
__version_info__ = ('2', '1', '0')
|
|
2
2
|
__version__ = '.'.join(__version_info__)
|
|
@@ -129,6 +129,9 @@ class AnnotationConverter:
|
|
|
129
129
|
if classes_file and os.path.exists(classes_file):
|
|
130
130
|
with open(classes_file, 'r') as f:
|
|
131
131
|
classes = [line.strip() for line in f.readlines()]
|
|
132
|
+
print(f"[read_yolo] Loaded {len(classes)} classes from {classes_file}")
|
|
133
|
+
if len(classes) > 0:
|
|
134
|
+
print(f"[read_yolo] First few classes: {classes[:5]}")
|
|
132
135
|
|
|
133
136
|
# Parse annotations
|
|
134
137
|
annotations = []
|
|
@@ -152,6 +155,10 @@ class AnnotationConverter:
|
|
|
152
155
|
# Get label name
|
|
153
156
|
label = classes[class_id] if class_id < len(classes) else str(class_id)
|
|
154
157
|
|
|
158
|
+
# Debug: log first annotation conversion
|
|
159
|
+
if len(annotations) == 0 and classes:
|
|
160
|
+
print(f"[read_yolo] Converting class_id={class_id} -> label='{label}' (classes has {len(classes)} entries)")
|
|
161
|
+
|
|
155
162
|
annotations.append({
|
|
156
163
|
'label': label,
|
|
157
164
|
'bbox': [x, y, w_abs, h_abs],
|
|
@@ -681,7 +688,33 @@ class AnnotationConverter:
|
|
|
681
688
|
if input_format not in readers:
|
|
682
689
|
raise ValueError(f"Unsupported input format: {input_format}")
|
|
683
690
|
|
|
684
|
-
|
|
691
|
+
# For YOLO input, classes_list is needed to convert class IDs to names
|
|
692
|
+
if input_format == 'yolo':
|
|
693
|
+
# Create a temporary classes file from classes_list
|
|
694
|
+
if classes_list:
|
|
695
|
+
print(f"[YOLO Conversion] Converting with {len(classes_list)} classes: {classes_list[:5]}...")
|
|
696
|
+
import tempfile
|
|
697
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
|
|
698
|
+
for cls in classes_list:
|
|
699
|
+
f.write(cls + '\n')
|
|
700
|
+
temp_classes_file = f.name
|
|
701
|
+
|
|
702
|
+
try:
|
|
703
|
+
internal_data = readers[input_format](input_path, classes_file=temp_classes_file)
|
|
704
|
+
print(f"[YOLO Conversion] Successfully read annotations from {os.path.basename(input_path)}")
|
|
705
|
+
if internal_data.get('annotations'):
|
|
706
|
+
sample_labels = [ann['label'] for ann in internal_data['annotations'][:3]]
|
|
707
|
+
print(f"[YOLO Conversion] Sample labels: {sample_labels}")
|
|
708
|
+
finally:
|
|
709
|
+
# Clean up temporary file
|
|
710
|
+
if os.path.exists(temp_classes_file):
|
|
711
|
+
os.remove(temp_classes_file)
|
|
712
|
+
else:
|
|
713
|
+
# No classes provided, will use numeric IDs as labels
|
|
714
|
+
print(f"Warning: No classes_list provided for YOLO input. Using numeric IDs as labels.")
|
|
715
|
+
internal_data = readers[input_format](input_path)
|
|
716
|
+
else:
|
|
717
|
+
internal_data = readers[input_format](input_path)
|
|
685
718
|
|
|
686
719
|
# Write internal format to output format
|
|
687
720
|
writers = {
|