dataflow-cv 1.2.0__tar.gz → 1.3.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.2.0 → dataflow_cv-1.3.0}/PKG-INFO +33 -20
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/README.md +32 -19
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/__init__.py +1 -1
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/cli/commands/convert.py +3 -2
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/cli/commands/evaluate.py +24 -23
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/convert/base.py +104 -69
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/convert/coco_and_labelme.py +16 -94
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/convert/labelme_and_yolo.py +13 -56
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/convert/utils.py +140 -5
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/convert/yolo_and_coco.py +29 -147
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/evaluate/base.py +7 -3
- dataflow_cv-1.3.0/dataflow/evaluate/metrics.py +456 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/evaluate/result.py +5 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/evaluate/utils.py +105 -5
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/label/base.py +29 -3
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/label/coco_handler.py +32 -5
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/util/logging_util.py +67 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/visualize/base.py +16 -21
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow_cv.egg-info/PKG-INFO +33 -20
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/pyproject.toml +1 -1
- dataflow_cv-1.2.0/dataflow/evaluate/metrics.py +0 -276
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/LICENSE +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/cli/__init__.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/cli/commands/__init__.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/cli/commands/utils.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/cli/commands/visualize.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/cli/exceptions.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/cli/main.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/convert/__init__.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/convert/rle_converter.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/evaluate/__init__.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/evaluate/evaluator.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/label/__init__.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/label/labelme_handler.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/label/models.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/label/utils.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/label/yolo_handler.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/util/__init__.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/util/file_util.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/visualize/__init__.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/visualize/coco_visualizer.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/visualize/labelme_visualizer.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/visualize/utils.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow/visualize/yolo_visualizer.py +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow_cv.egg-info/SOURCES.txt +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow_cv.egg-info/dependency_links.txt +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow_cv.egg-info/entry_points.txt +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow_cv.egg-info/not-zip-safe +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow_cv.egg-info/requires.txt +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.0}/dataflow_cv.egg-info/top_level.txt +0 -0
- {dataflow_cv-1.2.0 → dataflow_cv-1.3.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.3.0
|
|
4
4
|
Summary: A computer vision dataset processing library
|
|
5
5
|
Author-email: DataFlow-CV Team <example@example.com>
|
|
6
6
|
License: MIT
|
|
@@ -73,10 +73,10 @@ graph LR
|
|
|
73
73
|
|
|
74
74
|
| | | |
|
|
75
75
|
|:---|:---|:---|
|
|
76
|
-
| 🔄 **Format Conversion** | Convert between YOLO, LabelMe, and COCO in any direction — 6 conversion paths, plus prediction file support
|
|
76
|
+
| 🔄 **Format Conversion** | Convert between YOLO, LabelMe, and COCO in any direction — 6 conversion paths, plus prediction file support (outputs standard list-format COCO predictions) |
|
|
77
77
|
| 🎯 **Detection & Segmentation** | Handle both object detection (bbox) and instance segmentation (polygon/RLE) annotations |
|
|
78
78
|
| 🎨 **Visualization** | Render annotations with OpenCV — color-coded classes, semi-transparent masks, display & save modes |
|
|
79
|
-
| 📊 **Evaluation** | COCO-standard 12-metric output (mAP, AP50, AP75, AR) via pycocotools, with per-class breakdowns |
|
|
79
|
+
| 📊 **Evaluation** | COCO-standard 12-metric output (mAP, AP50, AP75, AR) via pycocotools, with per-class breakdowns and macro/micro P/R/F1 |
|
|
80
80
|
| 💻 **Command-line Interface** | Intuitive CLI with `convert`, `visualize`, and `evaluate` subcommands — positional args, rich `--help` |
|
|
81
81
|
| 🐍 **Python API** | Programmatic access for integration into larger ML pipelines |
|
|
82
82
|
| 📝 **Verbose Logging** | File-based debug logging with timestamps — toggle with `--verbose` |
|
|
@@ -146,7 +146,7 @@ dataflow-cv convert coco2yolo input.json yolo_labels/
|
|
|
146
146
|
# COCO → LabelMe
|
|
147
147
|
dataflow-cv convert coco2labelme input.json labelme_json/
|
|
148
148
|
|
|
149
|
-
# YOLO predictions → COCO (
|
|
149
|
+
# YOLO predictions → COCO (output: plain JSON list — prediction format)
|
|
150
150
|
dataflow-cv convert yolo2coco --prediction images/ yolo_preds/ classes.txt pred.json
|
|
151
151
|
|
|
152
152
|
# Options
|
|
@@ -174,10 +174,10 @@ dataflow-cv visualize yolo --verbose --no-display images/ yolo_labels/ classes.t
|
|
|
174
174
|
|
|
175
175
|
Evaluate object detection and instance segmentation model outputs using COCO-standard metrics. Two COCO-format JSON files are required:
|
|
176
176
|
|
|
177
|
-
| File | Role | Source |
|
|
178
|
-
|
|
179
|
-
| **`anno.json`** | Ground Truth (GT) — reference annotations | `yolo2coco` (label mode) |
|
|
180
|
-
| **`pred.json`** | Detection (DT) — model predictions with `score` | `yolo2coco --prediction
|
|
177
|
+
| File | Role | Format | Source |
|
|
178
|
+
|------|------|--------|--------|
|
|
179
|
+
| **`anno.json`** | Ground Truth (GT) — reference annotations | Full COCO dict (`images`, `annotations`, `categories`) | `yolo2coco` (label mode) |
|
|
180
|
+
| **`pred.json`** | Detection (DT) — model predictions | Plain JSON list of annotation dicts (with `score`) | `yolo2coco --prediction`, Detectron2, MMDetection |
|
|
181
181
|
|
|
182
182
|
##### ① Preparing Evaluation Data
|
|
183
183
|
|
|
@@ -195,7 +195,9 @@ dataflow-cv convert yolo2coco images/ yolo_labels/ classes.txt anno.json
|
|
|
195
195
|
dataflow-cv convert yolo2coco --prediction images/ yolo_preds/ classes.txt pred.json
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
-
> ⚠️ **Important**: YOLO label files (GT) use **odd** token counts, while prediction files (DT) use **even** token counts with a trailing `confidence`. The `--prediction` flag is required for DT — it
|
|
198
|
+
> ⚠️ **Important**: YOLO label files (GT) use **odd** token counts, while prediction files (DT) use **even** token counts with a trailing `confidence`. The `--prediction` flag is required for DT — it outputs a **plain JSON list** of annotation dicts (not a full COCO dict with `images`/`categories`). Mixed label/prediction files in the same directory are not supported.
|
|
199
|
+
>
|
|
200
|
+
> ℹ️ **Note**: The `--prediction` flag is **only available for `yolo2coco`**. `labelme2coco` does not support prediction conversion — LabelMe files (.json) have no label vs prediction format distinction, so there is no equivalent prediction source format to convert from.
|
|
199
201
|
|
|
200
202
|
##### ② Detection vs Segmentation — Format Requirements
|
|
201
203
|
|
|
@@ -219,9 +221,12 @@ dataflow-cv evaluate detection anno.json pred.json
|
|
|
219
221
|
# Verbose per-class breakdown
|
|
220
222
|
dataflow-cv evaluate detection --verbose anno.json pred.json
|
|
221
223
|
|
|
222
|
-
# With P/R/F1 at IoU=0.5
|
|
224
|
+
# With P/R/F1 at IoU=0.5 (default: macro averaging)
|
|
223
225
|
dataflow-cv evaluate detection --prf1 --prf1-iou 0.5 anno.json pred.json
|
|
224
226
|
|
|
227
|
+
# With P/R/F1 using micro averaging
|
|
228
|
+
dataflow-cv evaluate detection --prf1 --prf1-method micro anno.json pred.json
|
|
229
|
+
|
|
225
230
|
# Instance segmentation evaluation (mask IoU)
|
|
226
231
|
dataflow-cv evaluate segmentation anno.json pred.json
|
|
227
232
|
|
|
@@ -273,9 +278,17 @@ evaluator = DetectionEvaluator(verbose=True)
|
|
|
273
278
|
result = evaluator.evaluate("anno.json", "pred.json")
|
|
274
279
|
print(f"AP: {result.metrics.ap:.3f}, AP50: {result.metrics.ap50:.3f}")
|
|
275
280
|
|
|
276
|
-
# Quick P/R/F1 at IoU=0.5
|
|
281
|
+
# Quick P/R/F1 at IoU=0.5 (default: macro averaging, bbox IoU)
|
|
277
282
|
prf1 = compute_pr_f1("anno.json", "pred.json", iou_threshold=0.5)
|
|
278
|
-
print(f"F1: {prf1.overall.f1_score:.3f}")
|
|
283
|
+
print(f"Macro F1: {prf1.overall.f1_score:.3f}")
|
|
284
|
+
|
|
285
|
+
# Micro averaging P/R/F1 (samples weighted equally)
|
|
286
|
+
prf1 = compute_pr_f1("anno.json", "pred.json", method="micro")
|
|
287
|
+
print(f"Micro F1: {prf1.overall.f1_score:.3f}")
|
|
288
|
+
|
|
289
|
+
# Segmentation P/R/F1 (mask IoU)
|
|
290
|
+
prf1 = compute_pr_f1("anno_segm.json", "pred_segm.json", iou_type="segm")
|
|
291
|
+
print(f"Segm F1: {prf1.overall.f1_score:.3f}")
|
|
279
292
|
```
|
|
280
293
|
|
|
281
294
|
> 📂 See the `samples/` directory for complete examples: `samples/visualize/` (YOLO, LabelMe, COCO demos), `samples/convert/` (conversion examples).
|
|
@@ -301,8 +314,8 @@ print(f"F1: {prf1.overall.f1_score:.3f}")
|
|
|
301
314
|
- **Headless Support**: Use `--no-display` for servers/Docker; pair with `--save` to output visualization images without a window.
|
|
302
315
|
- **Keyboard Shortcuts**: During visualization — `q`/`ESC` to exit, `Enter`/`Space` to advance, any other key to continue.
|
|
303
316
|
- **Color Management**: Each class ID gets a unique color from an HSV-based palette (up to 1000 classes) for consistent visualization.
|
|
304
|
-
- **Evaluation Metrics**: COCO-standard 12-metric output with optional per-class breakdown
|
|
305
|
-
- **Prediction Files**: YOLO prediction files
|
|
317
|
+
- **Evaluation Metrics**: COCO-standard 12-metric output with optional per-class breakdown. P/R/F1 supports both macro (default) and micro averaging, and both bbox and mask IoU.
|
|
318
|
+
- **Prediction Files**: YOLO prediction files use 6 tokens (detection) or even tokens (segmentation) vs 5/odd for labels. `--prediction` outputs a plain JSON list of annotation dicts — the standard prediction exchange format compatible with pycocotools `loadRes()`.
|
|
306
319
|
|
|
307
320
|
---
|
|
308
321
|
|
|
@@ -312,7 +325,7 @@ For detailed developer guidance including advanced test commands, debugging, and
|
|
|
312
325
|
|
|
313
326
|
### 🧪 Testing
|
|
314
327
|
|
|
315
|
-
**
|
|
328
|
+
**405 tests, 76% code coverage (3986 statements).**
|
|
316
329
|
|
|
317
330
|
```bash
|
|
318
331
|
pytest # All tests
|
|
@@ -326,12 +339,12 @@ pytest tests/evaluate/test_evaluator.py # Single module
|
|
|
326
339
|
|
|
327
340
|
| Module | Coverage | Highlights |
|
|
328
341
|
|--------|:--------:|------------|
|
|
329
|
-
| `dataflow/label/` | 68% | models (87%), coco_handler (
|
|
330
|
-
| `dataflow/convert/` |
|
|
331
|
-
| `dataflow/visualize/` | 81% | yolo_vis (
|
|
332
|
-
| `dataflow/evaluate/` |
|
|
342
|
+
| `dataflow/label/` | 68% | models (87%), coco_handler (75%), labelme_handler (70%), yolo_handler (58%) |
|
|
343
|
+
| `dataflow/convert/` | 87% | yolo_and_coco (90%), labelme_and_yolo (86%), coco_and_labelme (87%), rle (80%), base (83%), utils (92%) |
|
|
344
|
+
| `dataflow/visualize/` | 81% | yolo_vis (100%), labelme_vis (100%), coco_vis (97%), base (74%) |
|
|
345
|
+
| `dataflow/evaluate/` | 87% | evaluator (100%), metrics (93%), result (99%), base (91%), utils (68%) |
|
|
333
346
|
| `dataflow/cli/` | 59% | main (96%), convert cmd (48%), evaluate cmd (24%), visualize cmd (84%), utils (86%) |
|
|
334
|
-
| `dataflow/util/` | 93% | logging (
|
|
347
|
+
| `dataflow/util/` | 93% | logging (98%), file_util (84%) |
|
|
335
348
|
|
|
336
349
|
</details>
|
|
337
350
|
|
|
@@ -30,10 +30,10 @@ graph LR
|
|
|
30
30
|
|
|
31
31
|
| | | |
|
|
32
32
|
|:---|:---|:---|
|
|
33
|
-
| 🔄 **Format Conversion** | Convert between YOLO, LabelMe, and COCO in any direction — 6 conversion paths, plus prediction file support
|
|
33
|
+
| 🔄 **Format Conversion** | Convert between YOLO, LabelMe, and COCO in any direction — 6 conversion paths, plus prediction file support (outputs standard list-format COCO predictions) |
|
|
34
34
|
| 🎯 **Detection & Segmentation** | Handle both object detection (bbox) and instance segmentation (polygon/RLE) annotations |
|
|
35
35
|
| 🎨 **Visualization** | Render annotations with OpenCV — color-coded classes, semi-transparent masks, display & save modes |
|
|
36
|
-
| 📊 **Evaluation** | COCO-standard 12-metric output (mAP, AP50, AP75, AR) via pycocotools, with per-class breakdowns |
|
|
36
|
+
| 📊 **Evaluation** | COCO-standard 12-metric output (mAP, AP50, AP75, AR) via pycocotools, with per-class breakdowns and macro/micro P/R/F1 |
|
|
37
37
|
| 💻 **Command-line Interface** | Intuitive CLI with `convert`, `visualize`, and `evaluate` subcommands — positional args, rich `--help` |
|
|
38
38
|
| 🐍 **Python API** | Programmatic access for integration into larger ML pipelines |
|
|
39
39
|
| 📝 **Verbose Logging** | File-based debug logging with timestamps — toggle with `--verbose` |
|
|
@@ -103,7 +103,7 @@ dataflow-cv convert coco2yolo input.json yolo_labels/
|
|
|
103
103
|
# COCO → LabelMe
|
|
104
104
|
dataflow-cv convert coco2labelme input.json labelme_json/
|
|
105
105
|
|
|
106
|
-
# YOLO predictions → COCO (
|
|
106
|
+
# YOLO predictions → COCO (output: plain JSON list — prediction format)
|
|
107
107
|
dataflow-cv convert yolo2coco --prediction images/ yolo_preds/ classes.txt pred.json
|
|
108
108
|
|
|
109
109
|
# Options
|
|
@@ -131,10 +131,10 @@ dataflow-cv visualize yolo --verbose --no-display images/ yolo_labels/ classes.t
|
|
|
131
131
|
|
|
132
132
|
Evaluate object detection and instance segmentation model outputs using COCO-standard metrics. Two COCO-format JSON files are required:
|
|
133
133
|
|
|
134
|
-
| File | Role | Source |
|
|
135
|
-
|
|
136
|
-
| **`anno.json`** | Ground Truth (GT) — reference annotations | `yolo2coco` (label mode) |
|
|
137
|
-
| **`pred.json`** | Detection (DT) — model predictions with `score` | `yolo2coco --prediction
|
|
134
|
+
| File | Role | Format | Source |
|
|
135
|
+
|------|------|--------|--------|
|
|
136
|
+
| **`anno.json`** | Ground Truth (GT) — reference annotations | Full COCO dict (`images`, `annotations`, `categories`) | `yolo2coco` (label mode) |
|
|
137
|
+
| **`pred.json`** | Detection (DT) — model predictions | Plain JSON list of annotation dicts (with `score`) | `yolo2coco --prediction`, Detectron2, MMDetection |
|
|
138
138
|
|
|
139
139
|
##### ① Preparing Evaluation Data
|
|
140
140
|
|
|
@@ -152,7 +152,9 @@ dataflow-cv convert yolo2coco images/ yolo_labels/ classes.txt anno.json
|
|
|
152
152
|
dataflow-cv convert yolo2coco --prediction images/ yolo_preds/ classes.txt pred.json
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
> ⚠️ **Important**: YOLO label files (GT) use **odd** token counts, while prediction files (DT) use **even** token counts with a trailing `confidence`. The `--prediction` flag is required for DT — it
|
|
155
|
+
> ⚠️ **Important**: YOLO label files (GT) use **odd** token counts, while prediction files (DT) use **even** token counts with a trailing `confidence`. The `--prediction` flag is required for DT — it outputs a **plain JSON list** of annotation dicts (not a full COCO dict with `images`/`categories`). Mixed label/prediction files in the same directory are not supported.
|
|
156
|
+
>
|
|
157
|
+
> ℹ️ **Note**: The `--prediction` flag is **only available for `yolo2coco`**. `labelme2coco` does not support prediction conversion — LabelMe files (.json) have no label vs prediction format distinction, so there is no equivalent prediction source format to convert from.
|
|
156
158
|
|
|
157
159
|
##### ② Detection vs Segmentation — Format Requirements
|
|
158
160
|
|
|
@@ -176,9 +178,12 @@ dataflow-cv evaluate detection anno.json pred.json
|
|
|
176
178
|
# Verbose per-class breakdown
|
|
177
179
|
dataflow-cv evaluate detection --verbose anno.json pred.json
|
|
178
180
|
|
|
179
|
-
# With P/R/F1 at IoU=0.5
|
|
181
|
+
# With P/R/F1 at IoU=0.5 (default: macro averaging)
|
|
180
182
|
dataflow-cv evaluate detection --prf1 --prf1-iou 0.5 anno.json pred.json
|
|
181
183
|
|
|
184
|
+
# With P/R/F1 using micro averaging
|
|
185
|
+
dataflow-cv evaluate detection --prf1 --prf1-method micro anno.json pred.json
|
|
186
|
+
|
|
182
187
|
# Instance segmentation evaluation (mask IoU)
|
|
183
188
|
dataflow-cv evaluate segmentation anno.json pred.json
|
|
184
189
|
|
|
@@ -230,9 +235,17 @@ evaluator = DetectionEvaluator(verbose=True)
|
|
|
230
235
|
result = evaluator.evaluate("anno.json", "pred.json")
|
|
231
236
|
print(f"AP: {result.metrics.ap:.3f}, AP50: {result.metrics.ap50:.3f}")
|
|
232
237
|
|
|
233
|
-
# Quick P/R/F1 at IoU=0.5
|
|
238
|
+
# Quick P/R/F1 at IoU=0.5 (default: macro averaging, bbox IoU)
|
|
234
239
|
prf1 = compute_pr_f1("anno.json", "pred.json", iou_threshold=0.5)
|
|
235
|
-
print(f"F1: {prf1.overall.f1_score:.3f}")
|
|
240
|
+
print(f"Macro F1: {prf1.overall.f1_score:.3f}")
|
|
241
|
+
|
|
242
|
+
# Micro averaging P/R/F1 (samples weighted equally)
|
|
243
|
+
prf1 = compute_pr_f1("anno.json", "pred.json", method="micro")
|
|
244
|
+
print(f"Micro F1: {prf1.overall.f1_score:.3f}")
|
|
245
|
+
|
|
246
|
+
# Segmentation P/R/F1 (mask IoU)
|
|
247
|
+
prf1 = compute_pr_f1("anno_segm.json", "pred_segm.json", iou_type="segm")
|
|
248
|
+
print(f"Segm F1: {prf1.overall.f1_score:.3f}")
|
|
236
249
|
```
|
|
237
250
|
|
|
238
251
|
> 📂 See the `samples/` directory for complete examples: `samples/visualize/` (YOLO, LabelMe, COCO demos), `samples/convert/` (conversion examples).
|
|
@@ -258,8 +271,8 @@ print(f"F1: {prf1.overall.f1_score:.3f}")
|
|
|
258
271
|
- **Headless Support**: Use `--no-display` for servers/Docker; pair with `--save` to output visualization images without a window.
|
|
259
272
|
- **Keyboard Shortcuts**: During visualization — `q`/`ESC` to exit, `Enter`/`Space` to advance, any other key to continue.
|
|
260
273
|
- **Color Management**: Each class ID gets a unique color from an HSV-based palette (up to 1000 classes) for consistent visualization.
|
|
261
|
-
- **Evaluation Metrics**: COCO-standard 12-metric output with optional per-class breakdown
|
|
262
|
-
- **Prediction Files**: YOLO prediction files
|
|
274
|
+
- **Evaluation Metrics**: COCO-standard 12-metric output with optional per-class breakdown. P/R/F1 supports both macro (default) and micro averaging, and both bbox and mask IoU.
|
|
275
|
+
- **Prediction Files**: YOLO prediction files use 6 tokens (detection) or even tokens (segmentation) vs 5/odd for labels. `--prediction` outputs a plain JSON list of annotation dicts — the standard prediction exchange format compatible with pycocotools `loadRes()`.
|
|
263
276
|
|
|
264
277
|
---
|
|
265
278
|
|
|
@@ -269,7 +282,7 @@ For detailed developer guidance including advanced test commands, debugging, and
|
|
|
269
282
|
|
|
270
283
|
### 🧪 Testing
|
|
271
284
|
|
|
272
|
-
**
|
|
285
|
+
**405 tests, 76% code coverage (3986 statements).**
|
|
273
286
|
|
|
274
287
|
```bash
|
|
275
288
|
pytest # All tests
|
|
@@ -283,12 +296,12 @@ pytest tests/evaluate/test_evaluator.py # Single module
|
|
|
283
296
|
|
|
284
297
|
| Module | Coverage | Highlights |
|
|
285
298
|
|--------|:--------:|------------|
|
|
286
|
-
| `dataflow/label/` | 68% | models (87%), coco_handler (
|
|
287
|
-
| `dataflow/convert/` |
|
|
288
|
-
| `dataflow/visualize/` | 81% | yolo_vis (
|
|
289
|
-
| `dataflow/evaluate/` |
|
|
299
|
+
| `dataflow/label/` | 68% | models (87%), coco_handler (75%), labelme_handler (70%), yolo_handler (58%) |
|
|
300
|
+
| `dataflow/convert/` | 87% | yolo_and_coco (90%), labelme_and_yolo (86%), coco_and_labelme (87%), rle (80%), base (83%), utils (92%) |
|
|
301
|
+
| `dataflow/visualize/` | 81% | yolo_vis (100%), labelme_vis (100%), coco_vis (97%), base (74%) |
|
|
302
|
+
| `dataflow/evaluate/` | 87% | evaluator (100%), metrics (93%), result (99%), base (91%), utils (68%) |
|
|
290
303
|
| `dataflow/cli/` | 59% | main (96%), convert cmd (48%), evaluate cmd (24%), visualize cmd (84%), utils (86%) |
|
|
291
|
-
| `dataflow/util/` | 93% | logging (
|
|
304
|
+
| `dataflow/util/` | 93% | logging (98%), file_util (84%) |
|
|
292
305
|
|
|
293
306
|
</details>
|
|
294
307
|
|
|
@@ -28,8 +28,9 @@ def convert_group():
|
|
|
28
28
|
@click.option(
|
|
29
29
|
"--prediction",
|
|
30
30
|
is_flag=True,
|
|
31
|
-
help="Treat input as prediction format (
|
|
32
|
-
"Output COCO JSON
|
|
31
|
+
help="Treat input as prediction format (YOLO: 6 tokens for detection, "
|
|
32
|
+
"even tokens for segmentation). Output COCO as plain JSON list of "
|
|
33
|
+
"annotation dicts (prediction format, Variant B).",
|
|
33
34
|
)
|
|
34
35
|
def yolo2coco(
|
|
35
36
|
ctx,
|
|
@@ -48,6 +48,13 @@ def add_evaluate_options(func):
|
|
|
48
48
|
show_default=True,
|
|
49
49
|
help="Confidence threshold for P/R/F1 calculation",
|
|
50
50
|
)
|
|
51
|
+
@click.option(
|
|
52
|
+
"--prf1-method",
|
|
53
|
+
type=click.Choice(["macro", "micro"], case_sensitive=False),
|
|
54
|
+
default="macro",
|
|
55
|
+
show_default=True,
|
|
56
|
+
help="Aggregation method for overall P/R/F1",
|
|
57
|
+
)
|
|
51
58
|
@click.option(
|
|
52
59
|
"--output",
|
|
53
60
|
"-o",
|
|
@@ -98,7 +105,7 @@ def evaluate_group():
|
|
|
98
105
|
"dt_json",
|
|
99
106
|
type=click.Path(exists=True, path_type=Path),
|
|
100
107
|
)
|
|
101
|
-
def detection(ctx, gt_json, dt_json, verbose, prf1, prf1_iou, prf1_conf, output):
|
|
108
|
+
def detection(ctx, gt_json, dt_json, verbose, prf1, prf1_iou, prf1_conf, prf1_method, output):
|
|
102
109
|
"""Evaluate object detection results (bbox IoU).
|
|
103
110
|
|
|
104
111
|
GT_JSON: COCO format Ground Truth JSON file.
|
|
@@ -140,7 +147,7 @@ def detection(ctx, gt_json, dt_json, verbose, prf1, prf1_iou, prf1_conf, output)
|
|
|
140
147
|
|
|
141
148
|
# Print results
|
|
142
149
|
if result.success and result.metrics is not None:
|
|
143
|
-
_print_detection_result(result, verbose, prf1, prf1_iou, prf1_conf, gt_json, dt_json, logger)
|
|
150
|
+
_print_detection_result(result, verbose, prf1, prf1_iou, prf1_conf, prf1_method, gt_json, dt_json, logger)
|
|
144
151
|
|
|
145
152
|
if output:
|
|
146
153
|
_save_result_json(result, output)
|
|
@@ -171,7 +178,7 @@ def detection(ctx, gt_json, dt_json, verbose, prf1, prf1_iou, prf1_conf, output)
|
|
|
171
178
|
"dt_json",
|
|
172
179
|
type=click.Path(exists=True, path_type=Path),
|
|
173
180
|
)
|
|
174
|
-
def segmentation(ctx, gt_json, dt_json, verbose, prf1, prf1_iou, prf1_conf, output):
|
|
181
|
+
def segmentation(ctx, gt_json, dt_json, verbose, prf1, prf1_iou, prf1_conf, prf1_method, output):
|
|
175
182
|
"""Evaluate instance segmentation results (mask IoU).
|
|
176
183
|
|
|
177
184
|
GT_JSON: COCO format Ground Truth JSON file (annotations must include 'segmentation').
|
|
@@ -213,7 +220,7 @@ def segmentation(ctx, gt_json, dt_json, verbose, prf1, prf1_iou, prf1_conf, outp
|
|
|
213
220
|
|
|
214
221
|
# Print results
|
|
215
222
|
if result.success and result.metrics is not None:
|
|
216
|
-
_print_segmentation_result(result, verbose, prf1, prf1_iou, prf1_conf, gt_json, dt_json, logger)
|
|
223
|
+
_print_segmentation_result(result, verbose, prf1, prf1_iou, prf1_conf, prf1_method, gt_json, dt_json, logger)
|
|
217
224
|
|
|
218
225
|
if output:
|
|
219
226
|
_save_result_json(result, output)
|
|
@@ -234,7 +241,7 @@ def segmentation(ctx, gt_json, dt_json, verbose, prf1, prf1_iou, prf1_conf, outp
|
|
|
234
241
|
# Output helpers
|
|
235
242
|
# ---------------------------------------------------------------------------
|
|
236
243
|
|
|
237
|
-
def _print_detection_result(result, verbose, prf1, prf1_iou, prf1_conf, gt_json, dt_json, logger):
|
|
244
|
+
def _print_detection_result(result, verbose, prf1, prf1_iou, prf1_conf, prf1_method, gt_json, dt_json, logger):
|
|
238
245
|
"""Print detection evaluation results."""
|
|
239
246
|
from dataflow.evaluate.utils import format_metric_table, format_per_class_table, format_prf1_output
|
|
240
247
|
|
|
@@ -262,12 +269,14 @@ def _print_detection_result(result, verbose, prf1, prf1_iou, prf1_conf, gt_json,
|
|
|
262
269
|
str(gt_json), str(dt_json),
|
|
263
270
|
iou_threshold=prf1_iou,
|
|
264
271
|
confidence_threshold=prf1_conf,
|
|
265
|
-
iou_type="bbox",
|
|
272
|
+
iou_type="bbox",
|
|
273
|
+
method=prf1_method,
|
|
274
|
+
verbose=verbose, logger=logger,
|
|
266
275
|
)
|
|
267
276
|
click.echo(format_prf1_output(prf1_result))
|
|
268
277
|
|
|
269
278
|
|
|
270
|
-
def _print_segmentation_result(result, verbose, prf1, prf1_iou, prf1_conf, gt_json, dt_json, logger):
|
|
279
|
+
def _print_segmentation_result(result, verbose, prf1, prf1_iou, prf1_conf, prf1_method, gt_json, dt_json, logger):
|
|
271
280
|
"""Print segmentation evaluation results."""
|
|
272
281
|
from dataflow.evaluate.utils import format_metric_table, format_per_class_table, format_prf1_output
|
|
273
282
|
|
|
@@ -291,22 +300,14 @@ def _print_segmentation_result(result, verbose, prf1, prf1_iou, prf1_conf, gt_js
|
|
|
291
300
|
if prf1:
|
|
292
301
|
from dataflow.evaluate import compute_pr_f1
|
|
293
302
|
click.echo()
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
except NotImplementedError:
|
|
303
|
-
logger.warning("Mask IoU P/R/F1 not yet supported in manual matching. Falling back to bbox IoU.")
|
|
304
|
-
prf1_result = compute_pr_f1(
|
|
305
|
-
str(gt_json), str(dt_json),
|
|
306
|
-
iou_threshold=prf1_iou,
|
|
307
|
-
confidence_threshold=prf1_conf,
|
|
308
|
-
iou_type="bbox", verbose=verbose, logger=logger,
|
|
309
|
-
)
|
|
303
|
+
prf1_result = compute_pr_f1(
|
|
304
|
+
str(gt_json), str(dt_json),
|
|
305
|
+
iou_threshold=prf1_iou,
|
|
306
|
+
confidence_threshold=prf1_conf,
|
|
307
|
+
iou_type="segm",
|
|
308
|
+
method=prf1_method,
|
|
309
|
+
verbose=verbose, logger=logger,
|
|
310
|
+
)
|
|
310
311
|
click.echo(format_prf1_output(prf1_result))
|
|
311
312
|
|
|
312
313
|
|
|
@@ -200,6 +200,10 @@ class BaseConverter(ABC):
|
|
|
200
200
|
target_path=target_path,
|
|
201
201
|
)
|
|
202
202
|
|
|
203
|
+
# Store source_path for subclasses that need it in
|
|
204
|
+
# create_target_handler() (e.g., image copying in LabelMe→YOLO)
|
|
205
|
+
self._source_path = source_path
|
|
206
|
+
|
|
203
207
|
num_images = 0
|
|
204
208
|
num_objects = 0
|
|
205
209
|
|
|
@@ -267,48 +271,56 @@ class BaseConverter(ABC):
|
|
|
267
271
|
return result
|
|
268
272
|
|
|
269
273
|
def convert(self, source_path: str, target_path: str, **kwargs) -> ConversionResult:
|
|
270
|
-
"""
|
|
271
|
-
|
|
274
|
+
"""Convert annotations from source format to target format.
|
|
275
|
+
|
|
276
|
+
Auto-dispatches to the correct pipeline based on target format:
|
|
277
|
+
- COCO target (single JSON) → batch pipeline (``_batch_convert()``)
|
|
278
|
+
- YOLO / LabelMe target (per-file output) → streaming pipeline
|
|
279
|
+
(``stream_convert()``)
|
|
280
|
+
|
|
281
|
+
Subclasses should NOT override this method. Instead, override the
|
|
282
|
+
abstract hooks: ``create_source_handler()``, ``create_target_handler()``,
|
|
283
|
+
``_convert_single_image()``, ``convert_annotations()``, and optionally
|
|
284
|
+
``_post_batch_convert()``.
|
|
272
285
|
|
|
273
286
|
Args:
|
|
274
|
-
source_path: Path to source annotations
|
|
275
|
-
target_path: Path for target annotations
|
|
276
|
-
**kwargs: Additional conversion parameters
|
|
287
|
+
source_path: Path to source annotations.
|
|
288
|
+
target_path: Path for target annotations.
|
|
289
|
+
**kwargs: Additional conversion parameters.
|
|
277
290
|
|
|
278
291
|
Returns:
|
|
279
|
-
ConversionResult containing conversion status and details
|
|
292
|
+
ConversionResult containing conversion status and details.
|
|
280
293
|
"""
|
|
281
|
-
|
|
282
|
-
|
|
294
|
+
if self.target_format == "coco":
|
|
295
|
+
return self._batch_convert(source_path, target_path, **kwargs)
|
|
296
|
+
else:
|
|
297
|
+
return self.stream_convert(source_path, target_path, **kwargs)
|
|
283
298
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
299
|
+
def _batch_convert(
|
|
300
|
+
self, source_path: str, target_path: str, **kwargs
|
|
301
|
+
) -> ConversionResult:
|
|
302
|
+
"""Batch pipeline: read ALL → convert ALL → write ALL.
|
|
288
303
|
|
|
289
|
-
|
|
304
|
+
Used when the target format is a single file (COCO JSON).
|
|
305
|
+
Subclasses should NOT override this — override the abstract
|
|
306
|
+
hooks and the optional ``_post_batch_convert()`` hook instead.
|
|
307
|
+
"""
|
|
308
|
+
self._source_annotations_for_target = None
|
|
309
|
+
|
|
310
|
+
# 1. Validate inputs
|
|
290
311
|
if not self.validate_inputs(source_path, target_path, kwargs):
|
|
291
|
-
if self.verbose:
|
|
292
|
-
self.logger.error("Input parameter validation failed")
|
|
293
312
|
return self._create_conversion_result(
|
|
294
313
|
success=False,
|
|
295
314
|
source_path=source_path,
|
|
296
315
|
target_path=target_path,
|
|
297
|
-
errors=["Input
|
|
316
|
+
errors=["Input validation failed"],
|
|
298
317
|
log_file_path=self.log_file_path,
|
|
299
318
|
)
|
|
300
319
|
|
|
301
|
-
# 2.
|
|
320
|
+
# 2. Read data using source handler
|
|
302
321
|
source_handler = self.create_source_handler(source_path, kwargs)
|
|
303
|
-
if self.verbose:
|
|
304
|
-
self.logger.debug(
|
|
305
|
-
f"Created source handler: {source_handler.__class__.__name__}"
|
|
306
|
-
)
|
|
307
|
-
|
|
308
322
|
read_result = source_handler.read()
|
|
309
323
|
if not read_result.success:
|
|
310
|
-
if self.verbose:
|
|
311
|
-
self.logger.error(f"Failed to read source data: {read_result.errors}")
|
|
312
324
|
return self._create_conversion_result(
|
|
313
325
|
success=False,
|
|
314
326
|
source_path=source_path,
|
|
@@ -317,34 +329,33 @@ class BaseConverter(ABC):
|
|
|
317
329
|
log_file_path=self.log_file_path,
|
|
318
330
|
)
|
|
319
331
|
|
|
320
|
-
#
|
|
332
|
+
# 3. Convert data
|
|
321
333
|
annotations = read_result.data
|
|
322
334
|
if self.verbose:
|
|
323
|
-
self.logger.info(
|
|
335
|
+
self.logger.info(
|
|
336
|
+
f"Read annotations for {annotations.num_images} images"
|
|
337
|
+
)
|
|
324
338
|
self.logger.debug(f"Category count: {len(annotations.categories)}")
|
|
325
339
|
|
|
326
|
-
# 3. Convert data (format-specific conversion)
|
|
327
|
-
if self.verbose:
|
|
328
|
-
self.logger.debug("Starting format-specific conversion")
|
|
329
|
-
|
|
330
340
|
converted_annotations = self.convert_annotations(annotations, kwargs)
|
|
331
|
-
self.conversion_stats["objects_converted"] = converted_annotations.num_objects
|
|
332
341
|
|
|
333
342
|
if self.verbose:
|
|
334
343
|
self.logger.debug(
|
|
335
|
-
f"Conversion completed, object count:
|
|
344
|
+
f"Conversion completed, object count: "
|
|
345
|
+
f"{converted_annotations.num_objects}"
|
|
336
346
|
)
|
|
337
347
|
|
|
338
|
-
# 4.
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
self.
|
|
342
|
-
|
|
348
|
+
# 4. Write data (with state cleanup guarantee)
|
|
349
|
+
self._source_annotations_for_target = converted_annotations
|
|
350
|
+
try:
|
|
351
|
+
target_handler = self.create_target_handler(target_path, kwargs)
|
|
352
|
+
write_result = target_handler.write(
|
|
353
|
+
converted_annotations, target_path
|
|
343
354
|
)
|
|
355
|
+
finally:
|
|
356
|
+
self._source_annotations_for_target = None
|
|
344
357
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
# 5. Create result
|
|
358
|
+
# 5. Build result
|
|
348
359
|
result = self._create_conversion_result(
|
|
349
360
|
success=write_result.success,
|
|
350
361
|
source_path=source_path,
|
|
@@ -354,32 +365,37 @@ class BaseConverter(ABC):
|
|
|
354
365
|
log_file_path=self.log_file_path,
|
|
355
366
|
)
|
|
356
367
|
|
|
357
|
-
# Add verbose log entries
|
|
358
368
|
if self.verbose:
|
|
359
|
-
result.add_verbose_log(f"Source format: {self.source_format}")
|
|
360
|
-
result.add_verbose_log(f"Target format: {self.target_format}")
|
|
361
|
-
result.add_verbose_log(f"Source path: {source_path}")
|
|
362
|
-
result.add_verbose_log(f"Target path: {target_path}")
|
|
363
|
-
result.add_verbose_log(f"Images processed: {annotations.num_images}")
|
|
364
369
|
result.add_verbose_log(
|
|
365
|
-
f"
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
if write_result.warnings:
|
|
369
|
-
for warning in write_result.warnings:
|
|
370
|
-
result.add_verbose_log(f"Warning: {warning}")
|
|
371
|
-
|
|
372
|
-
# Record conversion statistics
|
|
373
|
-
self.conversion_stats["end_time"] = datetime.datetime.now()
|
|
374
|
-
duration = (
|
|
375
|
-
self.conversion_stats["end_time"] - self.conversion_stats["start_time"]
|
|
370
|
+
f"Images processed: {annotations.num_images}"
|
|
376
371
|
)
|
|
377
372
|
result.add_verbose_log(
|
|
378
|
-
f"
|
|
373
|
+
f"Objects converted: {converted_annotations.num_objects}"
|
|
379
374
|
)
|
|
375
|
+
if write_result.errors:
|
|
376
|
+
for error in write_result.errors:
|
|
377
|
+
result.add_verbose_log(f"Error: {error}")
|
|
378
|
+
|
|
379
|
+
# 6. Post-processing hook (e.g., RLE warnings)
|
|
380
|
+
self._post_batch_convert(result, source_handler, kwargs)
|
|
380
381
|
|
|
381
382
|
return result
|
|
382
383
|
|
|
384
|
+
def _post_batch_convert(
|
|
385
|
+
self,
|
|
386
|
+
result: ConversionResult,
|
|
387
|
+
source_handler: BaseAnnotationHandler,
|
|
388
|
+
kwargs: Dict,
|
|
389
|
+
) -> None:
|
|
390
|
+
"""Optional post-processing hook for batch conversions.
|
|
391
|
+
|
|
392
|
+
Called after a successful batch write. Subclasses override this
|
|
393
|
+
to add format-specific warnings or metadata (e.g., RLE accuracy
|
|
394
|
+
warnings).
|
|
395
|
+
|
|
396
|
+
Default: no-op.
|
|
397
|
+
"""
|
|
398
|
+
|
|
383
399
|
def validate_inputs(self, source_path: str, target_path: str, kwargs: Dict) -> bool:
|
|
384
400
|
"""
|
|
385
401
|
Validate conversion input parameters.
|
|
@@ -442,19 +458,38 @@ class BaseConverter(ABC):
|
|
|
442
458
|
def convert_annotations(
|
|
443
459
|
self, source_annotations: DatasetAnnotations, kwargs: Dict
|
|
444
460
|
) -> DatasetAnnotations:
|
|
445
|
-
"""
|
|
446
|
-
|
|
461
|
+
"""Convert annotation data (format-specific transformation).
|
|
462
|
+
|
|
463
|
+
Subclasses MUST override this to implement the coordinate
|
|
464
|
+
transformation for their conversion direction. There is no
|
|
465
|
+
safe pass-through default because coordinate semantics differ
|
|
466
|
+
between formats.
|
|
467
|
+
|
|
468
|
+
The canonical implementation delegates to
|
|
469
|
+
``_convert_single_image()`` per image:
|
|
470
|
+
|
|
471
|
+
.. code-block:: python
|
|
472
|
+
|
|
473
|
+
target = DatasetAnnotations(format=target_format)
|
|
474
|
+
target.categories = source_annotations.categories.copy()
|
|
475
|
+
for img in source_annotations.images:
|
|
476
|
+
target.add_image(self._convert_single_image(img, **kwargs))
|
|
477
|
+
return target
|
|
447
478
|
|
|
448
479
|
Args:
|
|
449
|
-
source_annotations: Annotations
|
|
450
|
-
kwargs: Additional conversion parameters
|
|
480
|
+
source_annotations: Annotations in source-native coordinates.
|
|
481
|
+
kwargs: Additional conversion parameters.
|
|
451
482
|
|
|
452
483
|
Returns:
|
|
453
|
-
Converted DatasetAnnotations
|
|
484
|
+
Converted DatasetAnnotations in target-native coordinates.
|
|
485
|
+
|
|
486
|
+
Raises:
|
|
487
|
+
NotImplementedError: Always — subclasses must implement.
|
|
454
488
|
"""
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
489
|
+
raise NotImplementedError(
|
|
490
|
+
"Subclass must implement convert_annotations() "
|
|
491
|
+
"for this conversion direction"
|
|
492
|
+
)
|
|
458
493
|
|
|
459
494
|
def _create_conversion_result(
|
|
460
495
|
self,
|
|
@@ -514,6 +549,6 @@ class BaseConverter(ABC):
|
|
|
514
549
|
|
|
515
550
|
def _log_error(self, message: str):
|
|
516
551
|
"""Log error message, raise exception in strict mode."""
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
552
|
+
from dataflow.util.logging_util import logging_error_or_raise
|
|
553
|
+
|
|
554
|
+
logging_error_or_raise(message, self.logger, self.strict_mode)
|