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.
Files changed (59) hide show
  1. {dataflow_cv-1.6.2/dataflow_cv.egg-info → dataflow_cv-1.7.0}/PKG-INFO +46 -5
  2. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/README.md +45 -4
  3. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/__init__.py +1 -1
  4. dataflow_cv-1.7.0/dataflow/analyse/__init__.py +41 -0
  5. dataflow_cv-1.7.0/dataflow/analyse/base.py +154 -0
  6. dataflow_cv-1.7.0/dataflow/analyse/log_templates.py +156 -0
  7. dataflow_cv-1.7.0/dataflow/analyse/split.py +265 -0
  8. dataflow_cv-1.7.0/dataflow/analyse/stats.py +182 -0
  9. dataflow_cv-1.7.0/dataflow/analyse/utils.py +286 -0
  10. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/commands/__init__.py +2 -1
  11. dataflow_cv-1.7.0/dataflow/cli/commands/analyse.py +220 -0
  12. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/commands/convert.py +6 -6
  13. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/commands/evaluate.py +14 -19
  14. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/commands/utils.py +4 -3
  15. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/commands/visualize.py +3 -3
  16. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/main.py +2 -1
  17. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/__init__.py +2 -0
  18. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/base.py +53 -1
  19. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/coco_and_labelme.py +1 -2
  20. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/labelme_and_yolo.py +63 -33
  21. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/rle_converter.py +0 -1
  22. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/utils.py +8 -0
  23. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/yolo_and_coco.py +1 -2
  24. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/evaluate/base.py +88 -5
  25. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/evaluate/log_templates.py +1 -1
  26. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/evaluate/metrics.py +4 -2
  27. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/evaluate/utils.py +3 -7
  28. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/label/__init__.py +1 -1
  29. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/label/coco_handler.py +22 -10
  30. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/label/labelme_handler.py +2 -5
  31. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/label/yolo_handler.py +67 -29
  32. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/util/__init__.py +8 -1
  33. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/visualize/__init__.py +3 -1
  34. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/visualize/base.py +51 -19
  35. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0/dataflow_cv.egg-info}/PKG-INFO +46 -5
  36. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow_cv.egg-info/SOURCES.txt +7 -0
  37. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/pyproject.toml +1 -1
  38. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/LICENSE +0 -0
  39. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/__init__.py +0 -0
  40. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/cli/exceptions.py +0 -0
  41. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/convert/log_templates.py +0 -0
  42. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/evaluate/__init__.py +0 -0
  43. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/evaluate/evaluator.py +0 -0
  44. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/evaluate/result.py +0 -0
  45. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/label/base.py +0 -0
  46. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/label/models.py +0 -0
  47. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/label/utils.py +0 -0
  48. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/util/logging.py +0 -0
  49. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/visualize/coco_visualizer.py +0 -0
  50. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/visualize/labelme_visualizer.py +0 -0
  51. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/visualize/log_templates.py +0 -0
  52. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/visualize/utils.py +0 -0
  53. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow/visualize/yolo_visualizer.py +0 -0
  54. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow_cv.egg-info/dependency_links.txt +0 -0
  55. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow_cv.egg-info/entry_points.txt +0 -0
  56. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow_cv.egg-info/not-zip-safe +0 -0
  57. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow_cv.egg-info/requires.txt +0 -0
  58. {dataflow_cv-1.6.2 → dataflow_cv-1.7.0}/dataflow_cv.egg-info/top_level.txt +0 -0
  59. {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.6.2
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.** Convert, visualize, evaluate, and more — a single CLI for all CV data.
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
- **440 tests, 77% code coverage (3957 statements).**
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 (440 tests, conftest fixtures)
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.** Convert, visualize, evaluate, and more — a single CLI for all CV data.
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
- **440 tests, 77% code coverage (3957 statements).**
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 (440 tests, conftest fixtures)
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/)
@@ -1,4 +1,4 @@
1
1
  """DataFlow-CV: A computer vision dataset processing library."""
2
2
 
3
- __version__ = "1.6.2"
3
+ __version__ = "1.7.0"
4
4
  __author__ = "DataFlow-CV Team"
@@ -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)