LabelCraft 2.0.4__tar.gz → 2.1.1__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.
Files changed (103) hide show
  1. {labelcraft-2.0.4 → labelcraft-2.1.1}/LabelCraft.egg-info/PKG-INFO +5 -3
  2. {labelcraft-2.0.4 → labelcraft-2.1.1}/LabelCraft.egg-info/SOURCES.txt +1 -1
  3. {labelcraft-2.0.4 → labelcraft-2.1.1}/PKG-INFO +5 -3
  4. {labelcraft-2.0.4 → labelcraft-2.1.1}/README.md +4 -2
  5. {labelcraft-2.0.4 → labelcraft-2.1.1}/labelcraft_ui.py +234 -8
  6. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/__init__.py +1 -1
  7. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/annotation_converter.py +34 -1
  8. labelcraft-2.1.1/libs/annotation_importer.py +1010 -0
  9. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/locales/de-DE.json +41 -1
  10. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/locales/en.json +41 -1
  11. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/locales/fr-FR.json +41 -1
  12. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/locales/ja-JP.json +41 -1
  13. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/locales/zh-CN.json +42 -2
  14. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/locales/zh-TW.json +41 -1
  15. labelcraft-2.0.4/libs/views/main_window_ui.py +0 -260
  16. {labelcraft-2.0.4 → labelcraft-2.1.1}/LabelCraft.egg-info/dependency_links.txt +0 -0
  17. {labelcraft-2.0.4 → labelcraft-2.1.1}/LabelCraft.egg-info/entry_points.txt +0 -0
  18. {labelcraft-2.0.4 → labelcraft-2.1.1}/LabelCraft.egg-info/not-zip-safe +0 -0
  19. {labelcraft-2.0.4 → labelcraft-2.1.1}/LabelCraft.egg-info/requires.txt +0 -0
  20. {labelcraft-2.0.4 → labelcraft-2.1.1}/LabelCraft.egg-info/top_level.txt +0 -0
  21. {labelcraft-2.0.4 → labelcraft-2.1.1}/MANIFEST.in +0 -0
  22. {labelcraft-2.0.4 → labelcraft-2.1.1}/data/coco_classes.txt +0 -0
  23. {labelcraft-2.0.4 → labelcraft-2.1.1}/data/predefined_classes.txt +0 -0
  24. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/canvas.py +0 -0
  25. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/coco_io.py +0 -0
  26. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/colorDialog.py +0 -0
  27. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/combobox.py +0 -0
  28. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/constants.py +0 -0
  29. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/create_ml_io.py +0 -0
  30. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/csv_io.py +0 -0
  31. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/default_label_combobox.py +0 -0
  32. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/hashableQListWidgetItem.py +0 -0
  33. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/i18n_engine.py +0 -0
  34. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/json_io.py +0 -0
  35. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/labelDialog.py +0 -0
  36. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/labelFile.py +0 -0
  37. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/lightWidget.py +0 -0
  38. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/newProjectDialog.py +0 -0
  39. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/pascal_voc_io.py +0 -0
  40. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/project.py +0 -0
  41. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/resources.py +0 -0
  42. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/settings.py +0 -0
  43. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/shape.py +0 -0
  44. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/toolBar.py +0 -0
  45. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/ustr.py +0 -0
  46. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/utils.py +0 -0
  47. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/yolo_io.py +0 -0
  48. {labelcraft-2.0.4 → labelcraft-2.1.1}/libs/zoomWidget.py +0 -0
  49. {labelcraft-2.0.4 → labelcraft-2.1.1}/main.py +0 -0
  50. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/app.icns +0 -0
  51. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/app.png +0 -0
  52. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/app.svg +0 -0
  53. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/app_screen.png +0 -0
  54. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/cancel.png +0 -0
  55. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/close.png +0 -0
  56. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/color.png +0 -0
  57. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/color_line.png +0 -0
  58. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/copy.png +0 -0
  59. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/delete.png +0 -0
  60. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/done.png +0 -0
  61. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/done.svg +0 -0
  62. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/edit.png +0 -0
  63. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/expert1.png +0 -0
  64. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/expert2.png +0 -0
  65. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/eye.png +0 -0
  66. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/feBlend-icon.png +0 -0
  67. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/file.png +0 -0
  68. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/fit-width.png +0 -0
  69. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/fit-window.png +0 -0
  70. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/fit.png +0 -0
  71. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/format_createml.png +0 -0
  72. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/format_voc.png +0 -0
  73. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/format_yolo.png +0 -0
  74. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/help.png +0 -0
  75. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/labels.png +0 -0
  76. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/labels.svg +0 -0
  77. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/light_brighten.svg +0 -0
  78. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/light_darken.png +0 -0
  79. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/light_darken.svg +0 -0
  80. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/light_lighten.png +0 -0
  81. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/light_reset.png +0 -0
  82. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/light_reset.svg +0 -0
  83. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/new.png +0 -0
  84. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/next.png +0 -0
  85. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/objects.png +0 -0
  86. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/open.png +0 -0
  87. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/open.svg +0 -0
  88. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/prev.png +0 -0
  89. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/quit.png +0 -0
  90. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/resetall.png +0 -0
  91. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/save-as.png +0 -0
  92. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/save-as.svg +0 -0
  93. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/save.png +0 -0
  94. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/save.svg +0 -0
  95. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/undo-cross.png +0 -0
  96. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/undo.png +0 -0
  97. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/verify.png +0 -0
  98. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/zoom-in.png +0 -0
  99. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/zoom-out.png +0 -0
  100. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources/icons/zoom.png +0 -0
  101. {labelcraft-2.0.4 → labelcraft-2.1.1}/resources.qrc +0 -0
  102. {labelcraft-2.0.4 → labelcraft-2.1.1}/setup.cfg +0 -0
  103. {labelcraft-2.0.4 → labelcraft-2.1.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: LabelCraft
3
- Version: 2.0.4
3
+ Version: 2.1.1
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.0.0** - A modern image annotation tool with project management, developed based on [labelImg](https://github.com/tzutalin/labelImg)
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](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
43
43
  [![Python Version](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/)
44
44
  [![PySide6](https://img.shields.io/badge/PySide6-6.5+-green.svg)](https://www.qt.io/)
45
- [![Version](https://img.shields.io/badge/version-2.0.0-orange.svg)](https://github.com/syd168/LabelCraft/releases)
45
+ [![Version](https://img.shields.io/badge/version-2.1.0-orange.svg)](https://github.com/syd168/LabelCraft/releases)
46
46
  [![Downloads](https://pepy.tech/badge/labelcraft)](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.4
3
+ Version: 2.1.1
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.0.0** - A modern image annotation tool with project management, developed based on [labelImg](https://github.com/tzutalin/labelImg)
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](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
43
43
  [![Python Version](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/)
44
44
  [![PySide6](https://img.shields.io/badge/PySide6-6.5+-green.svg)](https://www.qt.io/)
45
- [![Version](https://img.shields.io/badge/version-2.0.0-orange.svg)](https://github.com/syd168/LabelCraft/releases)
45
+ [![Version](https://img.shields.io/badge/version-2.1.0-orange.svg)](https://github.com/syd168/LabelCraft/releases)
46
46
  [![Downloads](https://pepy.tech/badge/labelcraft)](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.0.0** - A modern image annotation tool with project management, developed based on [labelImg](https://github.com/tzutalin/labelImg)
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](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
6
  [![Python Version](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/)
7
7
  [![PySide6](https://img.shields.io/badge/PySide6-6.5+-green.svg)](https://www.qt.io/)
8
- [![Version](https://img.shields.io/badge/version-2.0.0-orange.svg)](https://github.com/syd168/LabelCraft/releases)
8
+ [![Version](https://img.shields.io/badge/version-2.1.0-orange.svg)](https://github.com/syd168/LabelCraft/releases)
9
9
  [![Downloads](https://pepy.tech/badge/labelcraft)](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
- # Export annotations action
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
- # Output menu: export and save dir related functions
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 action
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
- dest_images_dir = os.path.join(export_dir, 'images')
4807
- dest_annotations_dir = os.path.join(export_dir, 'annotations')
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)
@@ -4869,7 +4887,16 @@ class MainWindow(QMainWindow, WindowMixin):
4869
4887
  output_format_str = format_map.get(selected_format[0], 'voc')
4870
4888
 
4871
4889
  # Get classes list for YOLO and COCO formats
4872
- classes_list = self.label_hist if self.label_hist else []
4890
+ # Priority: project labels > label_hist > empty list
4891
+ if self.current_project and self.current_project.labels:
4892
+ classes_list = self.current_project.labels
4893
+ print(f"Using {len(classes_list)} classes from project: {classes_list[:5]}...")
4894
+ elif self.label_hist:
4895
+ classes_list = self.label_hist
4896
+ print(f"Using {len(classes_list)} classes from label history")
4897
+ else:
4898
+ classes_list = []
4899
+ print("Warning: No classes list available. YOLO/COCO export may fail.")
4873
4900
 
4874
4901
  try:
4875
4902
  # Iterate through all annotation files
@@ -5030,11 +5057,210 @@ class MainWindow(QMainWindow, WindowMixin):
5030
5057
 
5031
5058
  print(f"Export completed: {exported_count} files exported to {export_dir}")
5032
5059
 
5060
+ # Generate format-specific configuration files
5061
+ if selected_format[0] == LabelFileFormat.YOLO and exported_count > 0:
5062
+ try:
5063
+ self._generate_yolo_data_yaml(export_dir, classes_list)
5064
+ except Exception as yaml_error:
5065
+ print(f"Warning: Failed to generate data.yaml: {yaml_error}")
5066
+ elif selected_format[0] in [LabelFileFormat.COCO, LabelFileFormat.CREATE_ML] and exported_count > 0:
5067
+ try:
5068
+ self._merge_json_annotations(dest_annotations_dir, output_format_str, classes_list)
5069
+ except Exception as merge_error:
5070
+ print(f"Warning: Failed to merge JSON annotations: {merge_error}")
5071
+
5033
5072
  except Exception as e:
5034
5073
  QMessageBox.critical(self, self.get_str('errorTitle'),
5035
5074
  f'{self.get_str("exportFailed")}\n\n{str(e)}')
5036
5075
  import traceback
5037
5076
  traceback.print_exc()
5077
+
5078
+ def _generate_yolo_data_yaml(self, export_dir, classes_list):
5079
+ """Generate data.yaml file for YOLO format export
5080
+
5081
+ Args:
5082
+ export_dir: Export directory path
5083
+ classes_list: List of class names
5084
+ """
5085
+ yaml_path = os.path.join(export_dir, 'data.yaml')
5086
+
5087
+ # Build YAML content manually to avoid dependency on PyYAML
5088
+ lines = []
5089
+ lines.append('# YOLO dataset configuration')
5090
+ lines.append(f'# Generated by LabelCraft on {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
5091
+ lines.append('')
5092
+
5093
+ # Paths (relative to this yaml file)
5094
+ lines.append('path: . # dataset root dir')
5095
+ lines.append('train: images # train images (relative to path)')
5096
+ lines.append('val: images # val images (relative to path)')
5097
+ lines.append('test: images # test images (optional, relative to path)')
5098
+ lines.append('')
5099
+
5100
+ # Classes
5101
+ lines.append(f'nc: {len(classes_list)} # number of classes')
5102
+ lines.append('names:')
5103
+
5104
+ for idx, class_name in enumerate(classes_list):
5105
+ # Escape quotes in class names
5106
+ escaped_name = class_name.replace('"', '\\"')
5107
+ lines.append(f' {idx}: "{escaped_name}"')
5108
+
5109
+ # Write to file
5110
+ with open(yaml_path, 'w', encoding='utf-8') as f:
5111
+ f.write('\n'.join(lines))
5112
+ f.write('\n')
5113
+
5114
+ print(f"Generated data.yaml at: {yaml_path}")
5115
+ print(f" - Number of classes: {len(classes_list)}")
5116
+ print(f" - Classes: {', '.join(classes_list)}")
5117
+
5118
+ def import_annotations_dialog(self, _value=False):
5119
+ """Import annotations from external directory into current project.
5120
+
5121
+ Uses the AnnotationImporter module for a clean separation of concerns.
5122
+ """
5123
+ from libs.annotation_importer import AnnotationImporter
5124
+
5125
+ importer = AnnotationImporter(
5126
+ parent=self,
5127
+ current_project=self.current_project,
5128
+ get_str_func=self.get_str
5129
+ )
5130
+
5131
+ importer.import_annotations()
5132
+
5133
+ def _merge_json_annotations(self, annotations_dir, format_str, classes_list):
5134
+ """Merge individual JSON annotation files into a single file for COCO/CreateML formats
5135
+
5136
+ Args:
5137
+ annotations_dir: Directory containing individual JSON files
5138
+ format_str: Format string ('coco' or 'createml')
5139
+ classes_list: List of class names
5140
+ """
5141
+ import glob
5142
+
5143
+ # Find all JSON files in the annotations directory
5144
+ json_files = sorted(glob.glob(os.path.join(annotations_dir, '*.json')))
5145
+
5146
+ if not json_files:
5147
+ print(f"No JSON files found in {annotations_dir}")
5148
+ return
5149
+
5150
+ print(f"Merging {len(json_files)} JSON files into single {format_str.upper()} format...")
5151
+
5152
+ if format_str == 'coco':
5153
+ self._merge_coco_annotations(json_files, annotations_dir, classes_list)
5154
+ elif format_str == 'createml':
5155
+ self._merge_createml_annotations(json_files, annotations_dir)
5156
+
5157
+ def _merge_coco_annotations(self, json_files, output_dir, classes_list):
5158
+ """Merge multiple COCO JSON files into one
5159
+
5160
+ Standard COCO format has:
5161
+ - info: dataset information
5162
+ - licenses: license information
5163
+ - images: list of all images
5164
+ - annotations: list of all annotations
5165
+ - categories: list of all categories
5166
+ """
5167
+ merged_data = {
5168
+ 'info': {
5169
+ 'description': 'Converted from LabelCraft',
5170
+ 'version': '1.0',
5171
+ 'year': datetime.now().year,
5172
+ 'contributor': 'LabelCraft',
5173
+ 'date_created': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
5174
+ },
5175
+ 'licenses': [],
5176
+ 'images': [],
5177
+ 'annotations': [],
5178
+ 'categories': [{'id': idx + 1, 'name': name} for idx, name in enumerate(classes_list)]
5179
+ }
5180
+
5181
+ image_id = 1
5182
+ ann_id = 1
5183
+
5184
+ for json_file in json_files:
5185
+ try:
5186
+ with open(json_file, 'r', encoding='utf-8') as f:
5187
+ data = json.load(f)
5188
+
5189
+ # Extract image info
5190
+ if 'images' in data and len(data['images']) > 0:
5191
+ img_info = data['images'][0]
5192
+ img_info['id'] = image_id
5193
+ merged_data['images'].append(img_info)
5194
+
5195
+ # Extract annotations and update IDs
5196
+ if 'annotations' in data:
5197
+ for ann in data['annotations']:
5198
+ ann['id'] = ann_id
5199
+ ann['image_id'] = image_id
5200
+ merged_data['annotations'].append(ann)
5201
+ ann_id += 1
5202
+
5203
+ image_id += 1
5204
+
5205
+ except Exception as e:
5206
+ print(f"Warning: Failed to process {json_file}: {e}")
5207
+ continue
5208
+
5209
+ # Write merged COCO file
5210
+ output_path = os.path.join(output_dir, 'instances_default.json')
5211
+ with open(output_path, 'w', encoding='utf-8') as f:
5212
+ json.dump(merged_data, f, indent=2, ensure_ascii=False)
5213
+
5214
+ # Remove individual JSON files
5215
+ for json_file in json_files:
5216
+ try:
5217
+ os.remove(json_file)
5218
+ except:
5219
+ pass
5220
+
5221
+ print(f"✓ Merged COCO annotations: {output_path}")
5222
+ print(f" - Images: {len(merged_data['images'])}")
5223
+ print(f" - Annotations: {len(merged_data['annotations'])}")
5224
+ print(f" - Categories: {len(merged_data['categories'])}")
5225
+
5226
+ def _merge_createml_annotations(self, json_files, output_dir):
5227
+ """Merge multiple CreateML JSON files into one
5228
+
5229
+ Standard CreateML format is a list of objects:
5230
+ [
5231
+ {"image": "image1.jpg", "annotations": [...]},
5232
+ {"image": "image2.jpg", "annotations": [...]}
5233
+ ]
5234
+ """
5235
+ merged_data = []
5236
+
5237
+ for json_file in json_files:
5238
+ try:
5239
+ with open(json_file, 'r', encoding='utf-8') as f:
5240
+ data = json.load(f)
5241
+
5242
+ # CreateML format is already a list
5243
+ if isinstance(data, list) and len(data) > 0:
5244
+ merged_data.append(data[0]) # Take first (and usually only) item
5245
+
5246
+ except Exception as e:
5247
+ print(f"Warning: Failed to process {json_file}: {e}")
5248
+ continue
5249
+
5250
+ # Write merged CreateML file
5251
+ output_path = os.path.join(output_dir, 'annotations.json')
5252
+ with open(output_path, 'w', encoding='utf-8') as f:
5253
+ json.dump(merged_data, f, indent=2, ensure_ascii=False)
5254
+
5255
+ # Remove individual JSON files
5256
+ for json_file in json_files:
5257
+ try:
5258
+ os.remove(json_file)
5259
+ except:
5260
+ pass
5261
+
5262
+ print(f"✓ Merged CreateML annotations: {output_path}")
5263
+ print(f" - Total images: {len(merged_data)}")
5038
5264
 
5039
5265
 
5040
5266
  def inverted(color):
@@ -1,2 +1,2 @@
1
- __version_info__ = ('2', '0', '4')
1
+ __version_info__ = ('2', '1', '1')
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
- internal_data = readers[input_format](input_path)
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 = {