dataflow-cv 1.6.2__tar.gz → 1.7.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.
- {dataflow_cv-1.6.2/dataflow_cv.egg-info → dataflow_cv-1.7.0}/PKG-INFO +46 -5
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/README.md +45 -4
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/__init__.py +1 -1
- dataflow_cv-1.7.0/dataflow/analyse/__init__.py +41 -0
- dataflow_cv-1.7.0/dataflow/analyse/base.py +154 -0
- dataflow_cv-1.7.0/dataflow/analyse/log_templates.py +156 -0
- dataflow_cv-1.7.0/dataflow/analyse/split.py +265 -0
- dataflow_cv-1.7.0/dataflow/analyse/stats.py +182 -0
- dataflow_cv-1.7.0/dataflow/analyse/utils.py +286 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/commands/__init__.py +2 -1
- dataflow_cv-1.7.0/dataflow/cli/commands/analyse.py +220 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/commands/convert.py +6 -6
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/commands/evaluate.py +14 -19
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/commands/utils.py +4 -3
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/commands/visualize.py +3 -3
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/main.py +2 -1
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/__init__.py +2 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/base.py +53 -1
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/coco_and_labelme.py +1 -2
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/labelme_and_yolo.py +63 -33
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/rle_converter.py +0 -1
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/utils.py +8 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/yolo_and_coco.py +1 -2
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/evaluate/base.py +88 -5
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/evaluate/log_templates.py +1 -1
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/evaluate/metrics.py +4 -2
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/evaluate/utils.py +3 -7
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/label/__init__.py +1 -1
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/label/coco_handler.py +22 -10
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/label/labelme_handler.py +2 -5
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/label/yolo_handler.py +67 -29
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/util/__init__.py +8 -1
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/visualize/__init__.py +3 -1
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/visualize/base.py +51 -19
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0/dataflow_cv.egg-info}/PKG-INFO +46 -5
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow_cv.egg-info/SOURCES.txt +7 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/pyproject.toml +1 -1
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/LICENSE +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/__init__.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/exceptions.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/log_templates.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/evaluate/__init__.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/evaluate/evaluator.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/evaluate/result.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/label/base.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/label/models.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/label/utils.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/util/logging.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/visualize/coco_visualizer.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/visualize/labelme_visualizer.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/visualize/log_templates.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/visualize/utils.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/visualize/yolo_visualizer.py +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow_cv.egg-info/dependency_links.txt +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow_cv.egg-info/entry_points.txt +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow_cv.egg-info/not-zip-safe +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow_cv.egg-info/requires.txt +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow_cv.egg-info/top_level.txt +0 -0
- {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dataflow-cv
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: A computer vision dataset processing library
|
|
5
5
|
Author: DataFlow-CV Team
|
|
6
6
|
License: MIT
|
|
@@ -39,7 +39,7 @@ Dynamic: license-file
|
|
|
39
39
|
|
|
40
40
|
# DataFlow-CV
|
|
41
41
|
|
|
42
|
-
> 🌊 **Everything your model doesn't do.**
|
|
42
|
+
> 🌊 **Everything your model doesn't do.** Analyse, convert, visualize, evaluate — a single CLI for all CV data.
|
|
43
43
|
|
|
44
44
|
<p align="center">
|
|
45
45
|
<a href="https://pypi.org/project/dataflow-cv/"><img src="https://img.shields.io/pypi/v/dataflow-cv.svg" alt="PyPI"></a>
|
|
@@ -55,10 +55,11 @@ Dynamic: license-file
|
|
|
55
55
|
<img src="https://img.shields.io/badge/COCO-.json-e74c3c?style=flat-square" alt="COCO">
|
|
56
56
|
</p>
|
|
57
57
|
|
|
58
|
-
A computer vision dataset processing library — convert, visualize, and evaluate annotations across YOLO, LabelMe, and COCO formats.
|
|
58
|
+
A computer vision dataset processing library — analyse, convert, visualize, and evaluate annotations across YOLO, LabelMe, and COCO formats.
|
|
59
59
|
|
|
60
60
|
| | | |
|
|
61
61
|
|:---|:---|:---|
|
|
62
|
+
| 🔍 **Analyse** | Dataset statistics & train/test splitting with format auto-detection | `dataflow-cv analyse stats ...` |
|
|
62
63
|
| 🔄 **Convert** | 6 directions: YOLO ↔ LabelMe ↔ COCO, plus model predictions | `dataflow-cv convert yolo2coco ...` |
|
|
63
64
|
| 🎨 **Visualize** | OpenCV rendering with color-coded classes, display & save modes | `dataflow-cv visualize yolo ...` |
|
|
64
65
|
| 📊 **Evaluate** | COCO mAP via pycocotools, single-threshold P/R/F1 per class | `dataflow-cv evaluate detection ...` |
|
|
@@ -88,6 +89,26 @@ cd DataFlow-CV && pip install .
|
|
|
88
89
|
|
|
89
90
|
All required parameters (image directories, label directories, class files, output paths) are positional arguments for better usability. Use `--help` on any subcommand for detailed usage.
|
|
90
91
|
|
|
92
|
+
#### 🔍 Dataset Analysis
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Dataset statistics (auto-detects YOLO / LabelMe / COCO)
|
|
96
|
+
dataflow-cv analyse stats yolo_labels/ --image-dir images/ --class-file classes.txt
|
|
97
|
+
dataflow-cv analyse stats labelme_json/
|
|
98
|
+
dataflow-cv analyse stats coco_annotations.json
|
|
99
|
+
|
|
100
|
+
# Train / test split
|
|
101
|
+
dataflow-cv analyse split yolo_labels/ output/ --ratio 0.8 --seed 42 --class-file classes.txt
|
|
102
|
+
dataflow-cv analyse split coco_annotations.json output/ --ratio 0.8 --seed 42
|
|
103
|
+
dataflow-cv analyse split labelme_json/ output/ --ratio 0.8
|
|
104
|
+
|
|
105
|
+
# Sort by count descending (default: class ID ascending)
|
|
106
|
+
dataflow-cv analyse stats --sort-by count --descending yolo_labels/
|
|
107
|
+
|
|
108
|
+
# Verbose logging
|
|
109
|
+
dataflow-cv analyse stats --verbose yolo_labels/ --class-file classes.txt
|
|
110
|
+
```
|
|
111
|
+
|
|
91
112
|
#### 🔄 Format Conversion
|
|
92
113
|
|
|
93
114
|
```bash
|
|
@@ -177,6 +198,9 @@ dataflow-cv evaluate segmentation --verbose anno.json pred.json
|
|
|
177
198
|
|
|
178
199
|
# Save results as JSON
|
|
179
200
|
dataflow-cv evaluate detection --output results.json anno.json pred.json
|
|
201
|
+
|
|
202
|
+
# Custom log directory
|
|
203
|
+
dataflow-cv evaluate detection --verbose --log-dir logs/eval/ anno.json pred.json
|
|
180
204
|
```
|
|
181
205
|
|
|
182
206
|
##### ③ Detection vs Segmentation
|
|
@@ -192,10 +216,26 @@ Two evaluation modes, distinguished by how overlap is measured:
|
|
|
192
216
|
|
|
193
217
|
```python
|
|
194
218
|
from dataflow.util.logging import LogConfig
|
|
219
|
+
from dataflow.analyse import StatsAnalyser, SplitAnalyser
|
|
195
220
|
from dataflow.convert import YoloAndCocoConverter
|
|
196
221
|
from dataflow.visualize import YOLOVisualizer
|
|
197
222
|
from dataflow.evaluate import DetectionEvaluator, compute_pr_f1
|
|
198
223
|
|
|
224
|
+
# ── Analyse ─────────────────────────────────────────
|
|
225
|
+
log_cfg = LogConfig(name="analyse", verbose=True)
|
|
226
|
+
|
|
227
|
+
# Dataset statistics
|
|
228
|
+
analyser = StatsAnalyser(log_config=log_cfg)
|
|
229
|
+
result = analyser.analyse("yolo_labels/", class_file="classes.txt")
|
|
230
|
+
print(f"{result.data.total_files} images, {result.data.total_annotations} objects")
|
|
231
|
+
|
|
232
|
+
# Train/test split
|
|
233
|
+
splitter = SplitAnalyser(log_config=log_cfg)
|
|
234
|
+
result = splitter.analyse(
|
|
235
|
+
"yolo_labels/", "output/", ratio=0.8, seed=42, class_file="classes.txt",
|
|
236
|
+
)
|
|
237
|
+
print(f"Train: {result.data.train_count}, Val: {result.data.val_count}")
|
|
238
|
+
|
|
199
239
|
# ── Convert ──────────────────────────────────────────
|
|
200
240
|
# YOLO labels → COCO (label mode)
|
|
201
241
|
log_cfg = LogConfig(name="convert", verbose=True)
|
|
@@ -270,7 +310,7 @@ For detailed developer guidance including advanced test commands, debugging, and
|
|
|
270
310
|
|
|
271
311
|
### 🧪 Testing
|
|
272
312
|
|
|
273
|
-
**
|
|
313
|
+
**471 tests, 77% code coverage (3957 statements).**
|
|
274
314
|
|
|
275
315
|
```bash
|
|
276
316
|
pytest # All tests
|
|
@@ -320,12 +360,13 @@ pre-commit run --all-files # Manual run against all files
|
|
|
320
360
|
```
|
|
321
361
|
dataflow/
|
|
322
362
|
├── label/ # Annotation handlers + data models
|
|
363
|
+
├── analyse/ # Dataset statistics & train/test split
|
|
323
364
|
├── convert/ # Format converters, RLE utility, log templates
|
|
324
365
|
├── visualize/ # OpenCV-based rendering, log templates
|
|
325
366
|
├── evaluate/ # pycocotools-based metrics, log templates
|
|
326
367
|
├── util/ # Unified logging (LogManager + format helpers)
|
|
327
368
|
└── cli/ # CLI entry point, commands, validation
|
|
328
|
-
tests/ # Unit & integration tests (
|
|
369
|
+
tests/ # Unit & integration tests (471 tests, conftest fixtures)
|
|
329
370
|
samples/ # Python API usage examples
|
|
330
371
|
assets/ # Test data (det/seg by format)
|
|
331
372
|
specs/ # Canonical specifications (evaluate/ + formats/ + modules/)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DataFlow-CV
|
|
2
2
|
|
|
3
|
-
> 🌊 **Everything your model doesn't do.**
|
|
3
|
+
> 🌊 **Everything your model doesn't do.** Analyse, convert, visualize, evaluate — a single CLI for all CV data.
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
6
|
<a href="https://pypi.org/project/dataflow-cv/"><img src="https://img.shields.io/pypi/v/dataflow-cv.svg" alt="PyPI"></a>
|
|
@@ -16,10 +16,11 @@
|
|
|
16
16
|
<img src="https://img.shields.io/badge/COCO-.json-e74c3c?style=flat-square" alt="COCO">
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
|
-
A computer vision dataset processing library — convert, visualize, and evaluate annotations across YOLO, LabelMe, and COCO formats.
|
|
19
|
+
A computer vision dataset processing library — analyse, convert, visualize, and evaluate annotations across YOLO, LabelMe, and COCO formats.
|
|
20
20
|
|
|
21
21
|
| | | |
|
|
22
22
|
|:---|:---|:---|
|
|
23
|
+
| 🔍 **Analyse** | Dataset statistics & train/test splitting with format auto-detection | `dataflow-cv analyse stats ...` |
|
|
23
24
|
| 🔄 **Convert** | 6 directions: YOLO ↔ LabelMe ↔ COCO, plus model predictions | `dataflow-cv convert yolo2coco ...` |
|
|
24
25
|
| 🎨 **Visualize** | OpenCV rendering with color-coded classes, display & save modes | `dataflow-cv visualize yolo ...` |
|
|
25
26
|
| 📊 **Evaluate** | COCO mAP via pycocotools, single-threshold P/R/F1 per class | `dataflow-cv evaluate detection ...` |
|
|
@@ -49,6 +50,26 @@ cd DataFlow-CV && pip install .
|
|
|
49
50
|
|
|
50
51
|
All required parameters (image directories, label directories, class files, output paths) are positional arguments for better usability. Use `--help` on any subcommand for detailed usage.
|
|
51
52
|
|
|
53
|
+
#### 🔍 Dataset Analysis
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Dataset statistics (auto-detects YOLO / LabelMe / COCO)
|
|
57
|
+
dataflow-cv analyse stats yolo_labels/ --image-dir images/ --class-file classes.txt
|
|
58
|
+
dataflow-cv analyse stats labelme_json/
|
|
59
|
+
dataflow-cv analyse stats coco_annotations.json
|
|
60
|
+
|
|
61
|
+
# Train / test split
|
|
62
|
+
dataflow-cv analyse split yolo_labels/ output/ --ratio 0.8 --seed 42 --class-file classes.txt
|
|
63
|
+
dataflow-cv analyse split coco_annotations.json output/ --ratio 0.8 --seed 42
|
|
64
|
+
dataflow-cv analyse split labelme_json/ output/ --ratio 0.8
|
|
65
|
+
|
|
66
|
+
# Sort by count descending (default: class ID ascending)
|
|
67
|
+
dataflow-cv analyse stats --sort-by count --descending yolo_labels/
|
|
68
|
+
|
|
69
|
+
# Verbose logging
|
|
70
|
+
dataflow-cv analyse stats --verbose yolo_labels/ --class-file classes.txt
|
|
71
|
+
```
|
|
72
|
+
|
|
52
73
|
#### 🔄 Format Conversion
|
|
53
74
|
|
|
54
75
|
```bash
|
|
@@ -138,6 +159,9 @@ dataflow-cv evaluate segmentation --verbose anno.json pred.json
|
|
|
138
159
|
|
|
139
160
|
# Save results as JSON
|
|
140
161
|
dataflow-cv evaluate detection --output results.json anno.json pred.json
|
|
162
|
+
|
|
163
|
+
# Custom log directory
|
|
164
|
+
dataflow-cv evaluate detection --verbose --log-dir logs/eval/ anno.json pred.json
|
|
141
165
|
```
|
|
142
166
|
|
|
143
167
|
##### ③ Detection vs Segmentation
|
|
@@ -153,10 +177,26 @@ Two evaluation modes, distinguished by how overlap is measured:
|
|
|
153
177
|
|
|
154
178
|
```python
|
|
155
179
|
from dataflow.util.logging import LogConfig
|
|
180
|
+
from dataflow.analyse import StatsAnalyser, SplitAnalyser
|
|
156
181
|
from dataflow.convert import YoloAndCocoConverter
|
|
157
182
|
from dataflow.visualize import YOLOVisualizer
|
|
158
183
|
from dataflow.evaluate import DetectionEvaluator, compute_pr_f1
|
|
159
184
|
|
|
185
|
+
# ── Analyse ─────────────────────────────────────────
|
|
186
|
+
log_cfg = LogConfig(name="analyse", verbose=True)
|
|
187
|
+
|
|
188
|
+
# Dataset statistics
|
|
189
|
+
analyser = StatsAnalyser(log_config=log_cfg)
|
|
190
|
+
result = analyser.analyse("yolo_labels/", class_file="classes.txt")
|
|
191
|
+
print(f"{result.data.total_files} images, {result.data.total_annotations} objects")
|
|
192
|
+
|
|
193
|
+
# Train/test split
|
|
194
|
+
splitter = SplitAnalyser(log_config=log_cfg)
|
|
195
|
+
result = splitter.analyse(
|
|
196
|
+
"yolo_labels/", "output/", ratio=0.8, seed=42, class_file="classes.txt",
|
|
197
|
+
)
|
|
198
|
+
print(f"Train: {result.data.train_count}, Val: {result.data.val_count}")
|
|
199
|
+
|
|
160
200
|
# ── Convert ──────────────────────────────────────────
|
|
161
201
|
# YOLO labels → COCO (label mode)
|
|
162
202
|
log_cfg = LogConfig(name="convert", verbose=True)
|
|
@@ -231,7 +271,7 @@ For detailed developer guidance including advanced test commands, debugging, and
|
|
|
231
271
|
|
|
232
272
|
### 🧪 Testing
|
|
233
273
|
|
|
234
|
-
**
|
|
274
|
+
**471 tests, 77% code coverage (3957 statements).**
|
|
235
275
|
|
|
236
276
|
```bash
|
|
237
277
|
pytest # All tests
|
|
@@ -281,12 +321,13 @@ pre-commit run --all-files # Manual run against all files
|
|
|
281
321
|
```
|
|
282
322
|
dataflow/
|
|
283
323
|
├── label/ # Annotation handlers + data models
|
|
324
|
+
├── analyse/ # Dataset statistics & train/test split
|
|
284
325
|
├── convert/ # Format converters, RLE utility, log templates
|
|
285
326
|
├── visualize/ # OpenCV-based rendering, log templates
|
|
286
327
|
├── evaluate/ # pycocotools-based metrics, log templates
|
|
287
328
|
├── util/ # Unified logging (LogManager + format helpers)
|
|
288
329
|
└── cli/ # CLI entry point, commands, validation
|
|
289
|
-
tests/ # Unit & integration tests (
|
|
330
|
+
tests/ # Unit & integration tests (471 tests, conftest fixtures)
|
|
290
331
|
samples/ # Python API usage examples
|
|
291
332
|
assets/ # Test data (det/seg by format)
|
|
292
333
|
specs/ # Canonical specifications (evaluate/ + formats/ + modules/)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Analyse module — dataset introspection and preparation.
|
|
3
|
+
|
|
4
|
+
Provides dataset statistics and train/test splitting for all supported
|
|
5
|
+
annotation formats (YOLO, LabelMe, COCO). Format is auto-detected from
|
|
6
|
+
the label path.
|
|
7
|
+
|
|
8
|
+
Key features:
|
|
9
|
+
- **Dataset statistics**: Count total files, total annotations, and
|
|
10
|
+
per-class distribution
|
|
11
|
+
- **Train/test split**: Deterministic dataset splitting with configurable
|
|
12
|
+
ratio and seed
|
|
13
|
+
|
|
14
|
+
Example::
|
|
15
|
+
|
|
16
|
+
from dataflow.analyse import StatsAnalyser
|
|
17
|
+
|
|
18
|
+
analyser = StatsAnalyser()
|
|
19
|
+
result = analyser.analyse(
|
|
20
|
+
label_path=Path("yolo_labels/"),
|
|
21
|
+
class_file=Path("classes.txt"),
|
|
22
|
+
)
|
|
23
|
+
if result.success:
|
|
24
|
+
stats = result.data
|
|
25
|
+
print(f"Total: {stats.total_annotations} annotations")
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from . import utils
|
|
29
|
+
from .base import AnalysisResult, BaseAnalyser, SplitResult, StatsResult
|
|
30
|
+
from .split import SplitAnalyser
|
|
31
|
+
from .stats import StatsAnalyser
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"BaseAnalyser",
|
|
35
|
+
"AnalysisResult",
|
|
36
|
+
"StatsResult",
|
|
37
|
+
"SplitResult",
|
|
38
|
+
"StatsAnalyser",
|
|
39
|
+
"SplitAnalyser",
|
|
40
|
+
"utils",
|
|
41
|
+
]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes and data models for the Analyse module.
|
|
3
|
+
|
|
4
|
+
Provides shared logging infrastructure (``BaseAnalyser``) and result
|
|
5
|
+
containers (``AnalysisResult``, ``StatsResult``, ``SplitResult``).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# Result data models
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class AnalysisResult:
|
|
20
|
+
"""Top-level return type shared by all analysers.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
success: Whether the analysis completed successfully.
|
|
24
|
+
data: Module-specific result data (``StatsResult`` or ``SplitResult``).
|
|
25
|
+
errors: Accumulated error messages (non-empty if ``success=False``).
|
|
26
|
+
warnings: Accumulated non-fatal warning messages.
|
|
27
|
+
log_path: Log file path (verbose mode only, ``None`` otherwise).
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
success: bool = True
|
|
31
|
+
data: Optional[Any] = None
|
|
32
|
+
errors: List[str] = field(default_factory=list)
|
|
33
|
+
warnings: List[str] = field(default_factory=list)
|
|
34
|
+
log_path: Optional[str] = None
|
|
35
|
+
|
|
36
|
+
def add_error(self, error: str) -> None:
|
|
37
|
+
"""Add an error and mark the result as failed."""
|
|
38
|
+
self.errors.append(error)
|
|
39
|
+
self.success = False
|
|
40
|
+
|
|
41
|
+
def add_warning(self, warning: str) -> None:
|
|
42
|
+
"""Add a non-fatal warning."""
|
|
43
|
+
self.warnings.append(warning)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class StatsResult:
|
|
48
|
+
"""Container for dataset statistics.
|
|
49
|
+
|
|
50
|
+
Attributes:
|
|
51
|
+
total_files: Number of label files (or images for COCO).
|
|
52
|
+
total_annotations: Total annotation objects across all images.
|
|
53
|
+
per_class: ``{class_name: count}``, ordered by class_file or
|
|
54
|
+
count descending.
|
|
55
|
+
format: Detected format (``"yolo"`` | ``"labelme"`` | ``"coco"``).
|
|
56
|
+
categories: ``{class_id: class_name}`` mapping.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
total_files: int
|
|
60
|
+
total_annotations: int
|
|
61
|
+
per_class: Dict[str, int]
|
|
62
|
+
format: str
|
|
63
|
+
categories: Dict[int, str] = field(default_factory=dict)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class SplitResult:
|
|
68
|
+
"""Container for train/test split results.
|
|
69
|
+
|
|
70
|
+
Attributes:
|
|
71
|
+
train_count: Number of images in the training set.
|
|
72
|
+
val_count: Number of images in the validation set.
|
|
73
|
+
train_dir: Path to the train output directory.
|
|
74
|
+
val_dir: Path to the validation output directory.
|
|
75
|
+
ratio: Train ratio used (e.g., 0.8).
|
|
76
|
+
seed: Random seed used for reproducibility.
|
|
77
|
+
format: Detected format (``"yolo"`` | ``"labelme"`` | ``"coco"``).
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
train_count: int
|
|
81
|
+
val_count: int
|
|
82
|
+
train_dir: Path
|
|
83
|
+
val_dir: Path
|
|
84
|
+
ratio: float
|
|
85
|
+
seed: int
|
|
86
|
+
format: str
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# Base class
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class BaseAnalyser:
|
|
95
|
+
"""Shared logging infrastructure for dataset analysers.
|
|
96
|
+
|
|
97
|
+
Concrete analysers (``StatsAnalyser``, ``SplitAnalyser``) extend this
|
|
98
|
+
class to inherit logging helpers. Analyse operations are read-only —
|
|
99
|
+
errors are logged and accumulated in the result, never raised.
|
|
100
|
+
|
|
101
|
+
Constructor follows the project-wide ``LogConfig`` → ``LogManager``
|
|
102
|
+
pattern (see ``dataflow/evaluate/base.py``).
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, log_config: Optional[Any] = None):
|
|
106
|
+
"""Initialise the analyser.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
log_config: Optional ``LogConfig`` instance. If ``None``, a
|
|
110
|
+
default ``LogConfig(name="analyse")`` is used.
|
|
111
|
+
"""
|
|
112
|
+
from ..util.logging import LogConfig, LogManager
|
|
113
|
+
|
|
114
|
+
if log_config is None:
|
|
115
|
+
log_config = LogConfig(name="analyse")
|
|
116
|
+
self._log_manager = LogManager(log_config)
|
|
117
|
+
self.logger = self._log_manager.logger
|
|
118
|
+
|
|
119
|
+
# ------------------------------------------------------------------
|
|
120
|
+
# Logging helpers
|
|
121
|
+
# ------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
def _log_info(self, message: str) -> None:
|
|
124
|
+
"""Log an informational message."""
|
|
125
|
+
self.logger.info(message)
|
|
126
|
+
|
|
127
|
+
def _log_warning(self, message: str) -> None:
|
|
128
|
+
"""Log a warning message."""
|
|
129
|
+
self.logger.warning(message)
|
|
130
|
+
|
|
131
|
+
def _log_error(self, message: str) -> None:
|
|
132
|
+
"""Log an error message.
|
|
133
|
+
|
|
134
|
+
Unlike Convert (which raises in strict mode) and Evaluate (which
|
|
135
|
+
always raises), Analyse is a read-only operation — errors are
|
|
136
|
+
accumulated in ``AnalysisResult.errors`` rather than raised.
|
|
137
|
+
"""
|
|
138
|
+
self.logger.error(message)
|
|
139
|
+
|
|
140
|
+
# ------------------------------------------------------------------
|
|
141
|
+
# Result helpers
|
|
142
|
+
# ------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def _create_result(data: Any = None) -> AnalysisResult:
|
|
146
|
+
"""Create a new ``AnalysisResult`` with default success state.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
data: Optional result data (``StatsResult`` or ``SplitResult``).
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
A fresh ``AnalysisResult`` instance.
|
|
153
|
+
"""
|
|
154
|
+
return AnalysisResult(success=True, data=data)
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Log formatting templates for the Analyse module.
|
|
3
|
+
|
|
4
|
+
Pure functions that return formatted strings — they never call the
|
|
5
|
+
logger directly. The caller decides the log level and passes the
|
|
6
|
+
result to ``self.logger.info()`` / ``.debug()`` / etc.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict
|
|
11
|
+
|
|
12
|
+
from ..util.logging import (
|
|
13
|
+
format_divider,
|
|
14
|
+
format_kv,
|
|
15
|
+
format_result_block,
|
|
16
|
+
format_section,
|
|
17
|
+
format_table,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def format_analyse_header(
|
|
22
|
+
operation: str,
|
|
23
|
+
label_path: Path,
|
|
24
|
+
format_name: str,
|
|
25
|
+
) -> str:
|
|
26
|
+
"""Header block shown at the start of an analysis operation.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
operation: Operation name, e.g. ``"Dataset Statistics"``.
|
|
30
|
+
label_path: Path to the label source.
|
|
31
|
+
format_name: Detected format, e.g. ``"yolo (auto-detected)"``.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Formatted header string.
|
|
35
|
+
"""
|
|
36
|
+
lines = [
|
|
37
|
+
format_divider("═"),
|
|
38
|
+
f"Analyse: {operation}",
|
|
39
|
+
format_kv("Source", str(label_path)),
|
|
40
|
+
format_kv("Format", format_name),
|
|
41
|
+
"",
|
|
42
|
+
]
|
|
43
|
+
return "\n".join(lines)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def format_stats_result(
|
|
47
|
+
total_files: int,
|
|
48
|
+
total_annotations: int,
|
|
49
|
+
per_class: Dict[str, int],
|
|
50
|
+
categories: Dict[int, str] = None,
|
|
51
|
+
) -> str:
|
|
52
|
+
"""Per-class statistics table with summary.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
total_files: Number of label files.
|
|
56
|
+
total_annotations: Total annotation objects.
|
|
57
|
+
per_class: ``{class_name: count}`` dict, pre-ordered.
|
|
58
|
+
categories: ``{class_id: class_name}`` dict for ID lookup.
|
|
59
|
+
When provided, an ``ID`` column is added (0-indexed).
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Formatted statistics output.
|
|
63
|
+
"""
|
|
64
|
+
lines = [
|
|
65
|
+
format_section("Summary"),
|
|
66
|
+
format_kv("Total files", str(total_files)),
|
|
67
|
+
format_kv("Total annotations", str(total_annotations)),
|
|
68
|
+
format_kv("Categories", str(len(per_class))),
|
|
69
|
+
"",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
if per_class:
|
|
73
|
+
# Build name→id reverse mapping
|
|
74
|
+
name_to_id: Dict[str, int] = {}
|
|
75
|
+
if categories:
|
|
76
|
+
for cid, cname in categories.items():
|
|
77
|
+
if cname not in name_to_id:
|
|
78
|
+
name_to_id[cname] = cid
|
|
79
|
+
|
|
80
|
+
if categories:
|
|
81
|
+
headers = ["Class", "ID", "Count"]
|
|
82
|
+
rows = [
|
|
83
|
+
[name, str(name_to_id.get(name, "")), str(count)]
|
|
84
|
+
for name, count in per_class.items()
|
|
85
|
+
]
|
|
86
|
+
rows.append(["─" * 15, "─" * 4, "─" * 7])
|
|
87
|
+
rows.append([
|
|
88
|
+
f"Total ({len(per_class)})",
|
|
89
|
+
"",
|
|
90
|
+
str(sum(per_class.values())),
|
|
91
|
+
])
|
|
92
|
+
else:
|
|
93
|
+
headers = ["Class", "Count"]
|
|
94
|
+
rows = [[name, str(count)] for name, count in per_class.items()]
|
|
95
|
+
rows.append(["─" * 15, "─" * 7])
|
|
96
|
+
rows.append([
|
|
97
|
+
f"Total ({len(per_class)})",
|
|
98
|
+
str(sum(per_class.values())),
|
|
99
|
+
])
|
|
100
|
+
lines.append(format_section("Per-Class"))
|
|
101
|
+
lines.append(format_table(headers, rows))
|
|
102
|
+
else:
|
|
103
|
+
lines.append(format_section("Per-Class"))
|
|
104
|
+
lines.append(" (no annotations)")
|
|
105
|
+
|
|
106
|
+
return "\n".join(lines)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def format_split_result(
|
|
110
|
+
train_count: int,
|
|
111
|
+
val_count: int,
|
|
112
|
+
train_dir: Path,
|
|
113
|
+
val_dir: Path,
|
|
114
|
+
ratio: float,
|
|
115
|
+
seed: int,
|
|
116
|
+
) -> str:
|
|
117
|
+
"""Split summary block.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
train_count: Number of images in training set.
|
|
121
|
+
val_count: Number of images in validation set.
|
|
122
|
+
train_dir: Output directory for training data.
|
|
123
|
+
val_dir: Output directory for validation data.
|
|
124
|
+
ratio: Train ratio used.
|
|
125
|
+
seed: Random seed used.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Formatted split summary.
|
|
129
|
+
"""
|
|
130
|
+
total = train_count + val_count
|
|
131
|
+
lines = [
|
|
132
|
+
format_kv("Ratio", str(ratio)),
|
|
133
|
+
format_kv("Seed", str(seed)),
|
|
134
|
+
"",
|
|
135
|
+
format_section("Split"),
|
|
136
|
+
format_kv("Train", f"{train_count} images → {train_dir}"),
|
|
137
|
+
format_kv("Val", f"{val_count} images → {val_dir}"),
|
|
138
|
+
format_kv("Total", str(total)),
|
|
139
|
+
]
|
|
140
|
+
return "\n".join(lines)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def format_analyse_result(
|
|
144
|
+
status: str,
|
|
145
|
+
log_path: str,
|
|
146
|
+
) -> str:
|
|
147
|
+
"""Final result block.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
status: Status string, e.g. ``"✓ Success"``.
|
|
151
|
+
log_path: Path to the log file (or empty string).
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Formatted result block.
|
|
155
|
+
"""
|
|
156
|
+
return format_result_block(status, {}, log_path or None)
|