dataset-toolkit 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

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.
@@ -15,9 +15,9 @@ Dataset Toolkit - 计算机视觉数据集处理工具包
15
15
  >>> export_to_coco(dataset, "output.json")
16
16
  """
17
17
 
18
- __version__ = "0.1.0"
19
- __author__ = "Your Name"
20
- __email__ = "your.email@example.com"
18
+ __version__ = "0.1.2"
19
+ __author__ = "wenxiang.han"
20
+ __email__ = "wenxiang.han@anker-in.com"
21
21
 
22
22
  # 导入核心类和函数,提供简洁的顶层API
23
23
  from dataset_toolkit.models import (
@@ -27,7 +27,8 @@ from dataset_toolkit.models import (
27
27
  )
28
28
 
29
29
  from dataset_toolkit.loaders.local_loader import (
30
- load_yolo_from_local
30
+ load_yolo_from_local,
31
+ load_csv_result_from_local
31
32
  )
32
33
 
33
34
  from dataset_toolkit.processors.merger import (
@@ -42,6 +43,11 @@ from dataset_toolkit.exporters.txt_exporter import (
42
43
  export_to_txt
43
44
  )
44
45
 
46
+ from dataset_toolkit.exporters.yolo_exporter import (
47
+ export_to_yolo_format,
48
+ export_to_yolo_and_txt
49
+ )
50
+
45
51
  from dataset_toolkit.utils.coords import (
46
52
  yolo_to_absolute_bbox
47
53
  )
@@ -62,6 +68,7 @@ __all__ = [
62
68
 
63
69
  # 加载器
64
70
  "load_yolo_from_local",
71
+ "load_csv_result_from_local",
65
72
 
66
73
  # 处理器
67
74
  "merge_datasets",
@@ -69,6 +76,8 @@ __all__ = [
69
76
  # 导出器
70
77
  "export_to_coco",
71
78
  "export_to_txt",
79
+ "export_to_yolo_format",
80
+ "export_to_yolo_and_txt",
72
81
 
73
82
  # 工具函数
74
83
  "yolo_to_absolute_bbox",
@@ -0,0 +1,155 @@
1
+ # dataset_toolkit/exporters/yolo_exporter.py
2
+ """
3
+ 导出为 YOLO 格式(完整的 images/ + labels/ 目录结构)
4
+ """
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+
10
+ def export_to_yolo_format(
11
+ dataset,
12
+ output_dir: str,
13
+ use_symlinks: bool = True,
14
+ overwrite: bool = False
15
+ ):
16
+ """
17
+ 导出数据集为完整的 YOLO 格式目录结构
18
+
19
+ 参数:
20
+ dataset: Dataset 对象
21
+ output_dir: 输出目录路径
22
+ use_symlinks: 是否使用软链接(True)或复制文件(False)
23
+ overwrite: 是否覆盖已存在的文件
24
+
25
+ 输出结构:
26
+ output_dir/
27
+ ├── images/
28
+ │ ├── img1.jpg
29
+ │ └── img2.jpg
30
+ └── labels/
31
+ ├── img1.txt
32
+ └── img2.txt
33
+ """
34
+ output_path = Path(output_dir)
35
+ images_dir = output_path / 'images'
36
+ labels_dir = output_path / 'labels'
37
+
38
+ # 创建目录
39
+ images_dir.mkdir(parents=True, exist_ok=True)
40
+ labels_dir.mkdir(parents=True, exist_ok=True)
41
+
42
+ print(f"导出 YOLO 格式到: {output_path}")
43
+ print(f" 使用软链接: {use_symlinks}")
44
+
45
+ success_count = 0
46
+ error_count = 0
47
+
48
+ for img in dataset.images:
49
+ try:
50
+ # 获取图片文件名(不含扩展名)
51
+ img_path = Path(img.path)
52
+ img_name = img_path.name
53
+ stem = img_path.stem
54
+
55
+ # 1. 处理图片(软链接或复制)
56
+ target_img_path = images_dir / img_name
57
+
58
+ if target_img_path.exists() and not overwrite:
59
+ # 文件已存在,跳过
60
+ pass
61
+ else:
62
+ if use_symlinks:
63
+ # 使用软链接
64
+ if target_img_path.exists():
65
+ target_img_path.unlink()
66
+ target_img_path.symlink_to(img_path.resolve())
67
+ else:
68
+ # 复制文件
69
+ import shutil
70
+ shutil.copy2(img_path, target_img_path)
71
+
72
+ # 2. 生成标注文件
73
+ label_path = labels_dir / f"{stem}.txt"
74
+
75
+ with open(label_path, 'w') as f:
76
+ for ann in img.annotations:
77
+ # 内部格式: [x_min, y_min, width, height] (绝对像素值)
78
+ # YOLO 格式: class_id x_center y_center width height (归一化)
79
+
80
+ x_min, y_min, width, height = ann.bbox
81
+
82
+ # 转换为 YOLO 归一化格式
83
+ x_center = (x_min + width / 2) / img.width
84
+ y_center = (y_min + height / 2) / img.height
85
+ norm_width = width / img.width
86
+ norm_height = height / img.height
87
+
88
+ # 写入:class_id x_center y_center width height
89
+ f.write(f"{ann.category_id} {x_center:.6f} {y_center:.6f} {norm_width:.6f} {norm_height:.6f}\n")
90
+
91
+ success_count += 1
92
+
93
+ except Exception as e:
94
+ print(f"警告: 处理图片失败 {img.path}: {e}")
95
+ error_count += 1
96
+ continue
97
+
98
+ print(f"✓ 导出完成:")
99
+ print(f" 成功: {success_count} 张图片")
100
+ if error_count > 0:
101
+ print(f" 失败: {error_count} 张图片")
102
+ print(f" 图片目录: {images_dir}")
103
+ print(f" 标注目录: {labels_dir}")
104
+
105
+ return output_path
106
+
107
+
108
+ def export_to_yolo_and_txt(
109
+ dataset,
110
+ yolo_dir: str,
111
+ txt_file: str,
112
+ use_symlinks: bool = True,
113
+ use_relative_paths: bool = False
114
+ ):
115
+ """
116
+ 导出为 YOLO 格式并生成对应的 txt 列表文件
117
+
118
+ 参数:
119
+ dataset: Dataset 对象
120
+ yolo_dir: YOLO 格式输出目录
121
+ txt_file: txt 列表文件路径
122
+ use_symlinks: 是否使用软链接
123
+ use_relative_paths: txt 中是否使用相对路径
124
+
125
+ 返回:
126
+ yolo_dir_path: YOLO 目录路径
127
+ """
128
+ # 1. 导出为 YOLO 格式
129
+ yolo_path = export_to_yolo_format(dataset, yolo_dir, use_symlinks=use_symlinks)
130
+
131
+ # 2. 生成 txt 列表文件(指向 YOLO 目录中的 images/)
132
+ images_dir = yolo_path / 'images'
133
+ txt_path = Path(txt_file)
134
+ txt_path.parent.mkdir(parents=True, exist_ok=True)
135
+
136
+ print(f"\n生成 txt 列表: {txt_file}")
137
+
138
+ with open(txt_file, 'w') as f:
139
+ for img in dataset.images:
140
+ img_name = Path(img.path).name
141
+ # 指向 YOLO 目录中的图片(可能是软链接)
142
+ img_in_yolo = images_dir / img_name
143
+
144
+ if use_relative_paths:
145
+ # 相对于 txt 文件的路径
146
+ rel_path = os.path.relpath(img_in_yolo, txt_path.parent)
147
+ f.write(f"{rel_path}\n")
148
+ else:
149
+ # 绝对路径(指向 YOLO images 目录,不要 resolve,保持 YOLO 结构)
150
+ f.write(f"{str(img_in_yolo.absolute())}\n")
151
+
152
+ print(f"✓ txt 列表已生成: {len(dataset.images)} 行")
153
+
154
+ return yolo_path
155
+
@@ -2,6 +2,8 @@
2
2
  from pathlib import Path
3
3
  from typing import Dict
4
4
  from PIL import Image
5
+ import csv
6
+ import json
5
7
 
6
8
  # 从我们自己的包中导入模块
7
9
  from dataset_toolkit.models import Dataset, ImageAnnotation, Annotation
@@ -61,4 +63,127 @@ def load_yolo_from_local(dataset_path: str, categories: Dict[int, str]) -> Datas
61
63
  dataset.images.append(image_annotation)
62
64
 
63
65
  print(f"加载完成. 共找到 {len(dataset.images)} 张图片.")
66
+ return dataset
67
+
68
+
69
+ def load_csv_result_from_local(dataset_path: str, categories: Dict[int, str] = None) -> Dataset:
70
+ """
71
+ 从本地文件系统加载包含 result.csv 的数据集。
72
+
73
+ 数据集结构:
74
+ - 根目录下包含 jpg 图片文件
75
+ - result.csv 文件,格式为:file_id,result_json
76
+ - result_json 是 JSON 数组,包含检测结果
77
+ 格式: [{"box": [x1, y1, x2, y2], "conf": 0.xx, "class_id": 0, "class_name": "parcel"}]
78
+
79
+ 参数:
80
+ dataset_path: 数据集根目录路径
81
+ categories: 类别映射字典 {class_id: class_name},如果为 None 则从数据自动提取
82
+ """
83
+ root_path = Path(dataset_path)
84
+ csv_path = root_path / 'result.csv'
85
+
86
+ if not csv_path.exists():
87
+ raise FileNotFoundError(f"result.csv 文件不存在: {csv_path}")
88
+
89
+ # 如果没有提供 categories,则使用空字典,稍后从数据中提取
90
+ if categories is None:
91
+ categories = {}
92
+
93
+ dataset = Dataset(name=root_path.name, categories=categories)
94
+ supported_extensions = ['.jpg', '.jpeg', '.png']
95
+
96
+ print(f"开始加载数据集: {root_path.name}...")
97
+
98
+ # 读取 CSV 文件,建立 file_id 到 result_json 的映射
99
+ # 手动解析以处理 JSON 中的逗号
100
+ results_dict = {}
101
+ with open(csv_path, 'r', encoding='utf-8') as f:
102
+ # 跳过表头
103
+ header = f.readline().strip()
104
+ if header != 'file_id,result_json':
105
+ print(f"警告: CSV 表头格式不匹配,期望 'file_id,result_json',实际为 '{header}'")
106
+
107
+ # 逐行解析
108
+ for line_num, line in enumerate(f, start=2):
109
+ line = line.strip()
110
+ if not line:
111
+ continue
112
+
113
+ # 查找第一个逗号作为分隔符,之后的所有内容都是 result_json
114
+ comma_idx = line.find(',')
115
+ if comma_idx == -1:
116
+ print(f"警告: 第 {line_num} 行格式错误,已跳过: {line}")
117
+ continue
118
+
119
+ file_id = line[:comma_idx].strip()
120
+ result_json = line[comma_idx + 1:].strip()
121
+ results_dict[file_id] = result_json
122
+
123
+ print(f"从 result.csv 读取了 {len(results_dict)} 条标注记录.")
124
+
125
+ # 遍历根目录下的所有图片文件
126
+ image_count = 0
127
+ for image_path in root_path.iterdir():
128
+ if not image_path.is_file() or image_path.suffix.lower() not in supported_extensions:
129
+ continue
130
+
131
+ try:
132
+ with Image.open(image_path) as img:
133
+ img_width, img_height = img.size
134
+ except IOError:
135
+ print(f"警告: 无法打开图片,已跳过: {image_path}")
136
+ continue
137
+
138
+ image_annotation = ImageAnnotation(
139
+ image_id=image_path.name,
140
+ path=str(image_path.resolve()),
141
+ width=img_width,
142
+ height=img_height
143
+ )
144
+
145
+ # 查找对应的标注结果(文件名不含后缀)
146
+ file_id = image_path.stem
147
+ if file_id in results_dict:
148
+ result_json = results_dict[file_id]
149
+
150
+ # 解析 JSON 格式的检测结果
151
+ try:
152
+ detections = json.loads(result_json)
153
+
154
+ # 遍历每个检测框
155
+ for det in detections:
156
+ box = det.get('box', [])
157
+ conf = det.get('conf', 1.0)
158
+ class_id = det.get('class_id', 0)
159
+ class_name = det.get('class_name', 'unknown')
160
+
161
+ # 如果 categories 中没有这个类别,自动添加
162
+ if class_id not in dataset.categories:
163
+ dataset.categories[class_id] = class_name
164
+
165
+ # box 格式为 [x1, y1, x2, y2],需要转换为 [x_min, y_min, width, height]
166
+ if len(box) == 4:
167
+ x1, y1, x2, y2 = box
168
+ x_min = x1
169
+ y_min = y1
170
+ width = x2 - x1
171
+ height = y2 - y1
172
+
173
+ annotation = Annotation(
174
+ category_id=class_id,
175
+ bbox=[x_min, y_min, width, height]
176
+ )
177
+ image_annotation.annotations.append(annotation)
178
+ else:
179
+ print(f"警告: 无效的边界框格式,已跳过: {file_id} -> {box}")
180
+
181
+ except json.JSONDecodeError as e:
182
+ print(f"警告: 无法解析 JSON,已跳过: {file_id} -> {e}")
183
+
184
+ dataset.images.append(image_annotation)
185
+ image_count += 1
186
+
187
+ print(f"加载完成. 共找到 {image_count} 张图片, {len(dataset.categories)} 个类别.")
188
+ print(f"类别映射: {dataset.categories}")
64
189
  return dataset
dataset_toolkit/models.py CHANGED
@@ -8,6 +8,7 @@ class Annotation:
8
8
  category_id: int
9
9
  # 存储格式为 [x_min, y_min, width, height],单位是绝对像素值
10
10
  bbox: List[float]
11
+ confidence: float = 1.0 # 检测置信度,默认为 1.0
11
12
 
12
13
  @dataclass
13
14
  class ImageAnnotation:
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataset-toolkit
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: 一个用于加载、处理和导出计算机视觉数据集的工具包
5
5
  Home-page: https://github.com/yourusername/dataset-toolkit
6
- Author: Your Name
7
- Author-email: Your Name <your.email@example.com>
6
+ Author: wenxiang.han
7
+ Author-email: "wenxiang.han" <wenxiang.han@anker-in.com>
8
8
  License: MIT
9
9
  Project-URL: Homepage, https://github.com/yourusername/dataset-toolkit
10
10
  Project-URL: Documentation, https://dataset-toolkit.readthedocs.io
@@ -1,17 +1,18 @@
1
- dataset_toolkit/__init__.py,sha256=yMm1ajpItXWlKdiqEmY3kRXDI9F0Voreg4hEH0xxM1s,1604
2
- dataset_toolkit/models.py,sha256=uVtTbVYdHMECPL_waDhEebLvL_VwqSEm9XFC5QYIB10,767
1
+ dataset_toolkit/__init__.py,sha256=3KUOm1r2ldAGf6V0zPbx69QaoJvC9fsuasAvstcN4Vs,1846
2
+ dataset_toolkit/models.py,sha256=9HD2lAOPuEytFb1qRejODLJAD-uKHc8Ya1n9nbGhRpg,830
3
3
  dataset_toolkit/pipeline.py,sha256=iBJD7SemEVFTwzHxRQrjpUIQQcVdPSZnD4sB_y56Md0,5697
4
4
  dataset_toolkit/exporters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  dataset_toolkit/exporters/coco_exporter.py,sha256=l5sfj7rOcvcMC0-4LNOEJ4PeklGQORDflU_um5GGnxA,2120
6
6
  dataset_toolkit/exporters/txt_exporter.py,sha256=9nTWs6M89MdKJhlODtmfzeZqWkliXac9NMWPgVUrE7c,1246
7
+ dataset_toolkit/exporters/yolo_exporter.py,sha256=g0jaY6cg7I4ZfxXkE_vmK87OS1-DP6dXEjsP2iVP9D4,5144
7
8
  dataset_toolkit/loaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- dataset_toolkit/loaders/local_loader.py,sha256=Wy_hXY2B-SDxAmJGBYQpqBUe3cjz-k_McYhYf7cLgCk,2501
9
+ dataset_toolkit/loaders/local_loader.py,sha256=SCOYG5pursEIL_m3QYGcm-2skXoapiOA4yhqqa2wrDM,7468
9
10
  dataset_toolkit/processors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
11
  dataset_toolkit/processors/merger.py,sha256=h8qQNgSmkPrhoQ3QiWEyIl11CmmjT5K1-8TzNb7_jbk,2834
11
12
  dataset_toolkit/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
13
  dataset_toolkit/utils/coords.py,sha256=GtTQz2gFyFQfXhKfecI8tzqWFjraJY6Xo85-kRXYAYc,614
13
- dataset_toolkit-0.1.0.dist-info/licenses/LICENSE,sha256=8_up1FX6vk2DRcusQEZ4pWJGkgkjvEkD14xB1hdLe3c,1067
14
- dataset_toolkit-0.1.0.dist-info/METADATA,sha256=yjqPr_Wjioiw5v7AOkqduI5B_Y6oyBbKrTpJGIKVIWw,7225
15
- dataset_toolkit-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
- dataset_toolkit-0.1.0.dist-info/top_level.txt,sha256=B4D5vMLjUNJBZDdL7Utc0FYIfYoWbzyIGBMVYaeMd3U,16
17
- dataset_toolkit-0.1.0.dist-info/RECORD,,
14
+ dataset_toolkit-0.1.2.dist-info/licenses/LICENSE,sha256=8_up1FX6vk2DRcusQEZ4pWJGkgkjvEkD14xB1hdLe3c,1067
15
+ dataset_toolkit-0.1.2.dist-info/METADATA,sha256=qH0LX-6NPdZEd0htGIxuIMHueDmHxRsZmWbrGnbpEtA,7236
16
+ dataset_toolkit-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ dataset_toolkit-0.1.2.dist-info/top_level.txt,sha256=B4D5vMLjUNJBZDdL7Utc0FYIfYoWbzyIGBMVYaeMd3U,16
18
+ dataset_toolkit-0.1.2.dist-info/RECORD,,