Add operator YOLO dataset quality audit
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def test_operator_yolo_dataset_quality_audit_reports_dataset_risks(tmp_path: Path) -> None:
|
||||
script_path = ROOT / "scripts" / "audit_operator_yolo_dataset_quality.py"
|
||||
assert script_path.exists()
|
||||
|
||||
dataset_dir = tmp_path / "yolo-dataset"
|
||||
labels_train = dataset_dir / "labels" / "train"
|
||||
labels_val = dataset_dir / "labels" / "val"
|
||||
labels_train.mkdir(parents=True)
|
||||
labels_val.mkdir(parents=True)
|
||||
|
||||
(labels_train / "geel_000.txt").write_text(
|
||||
"0 0.500000 0.500000 0.010000 0.010000\n"
|
||||
"0 0.250000 0.250000 0.100000 0.100000\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(labels_train / "postel_bos_000.txt").write_text("", encoding="utf-8")
|
||||
(labels_train / "postel_bos_000_hn01.txt").write_text("", encoding="utf-8")
|
||||
(labels_val / "turnhout_000.txt").write_text(
|
||||
"0 0.600000 0.600000 0.080000 0.080000\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
summary_path = dataset_dir / "yolo_tile_dataset_summary.json"
|
||||
summary_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"status": "ready",
|
||||
"dataset_yaml": str(dataset_dir / "dataset.yaml"),
|
||||
"output_dir": str(dataset_dir),
|
||||
"class_names": ["building"],
|
||||
"tile_size": 160,
|
||||
"stride": 80,
|
||||
"negative_keep_ratio": 1.0,
|
||||
"background_negative_repeat": 2,
|
||||
"min_label_px": 2,
|
||||
"source_sample_count": 3,
|
||||
"tile_count": 4,
|
||||
"positive_tile_count": 2,
|
||||
"negative_tile_count": 2,
|
||||
"skipped_negative_tile_count": 0,
|
||||
"label_count": 3,
|
||||
"train_tile_count": 3,
|
||||
"val_tile_count": 1,
|
||||
"tiles": [
|
||||
{
|
||||
"sample_slug": "geel",
|
||||
"sample_role": "reference",
|
||||
"split": "train",
|
||||
"tile_index": 0,
|
||||
"repeat_index": 0,
|
||||
"kept": True,
|
||||
"label_path": str(labels_train / "geel_000.txt"),
|
||||
"label_count": 2,
|
||||
"is_negative": False,
|
||||
"is_repeated_background_negative": False,
|
||||
},
|
||||
{
|
||||
"sample_slug": "postel_bos",
|
||||
"sample_role": "background_candidate",
|
||||
"split": "train",
|
||||
"tile_index": 1,
|
||||
"repeat_index": 0,
|
||||
"kept": True,
|
||||
"label_path": str(labels_train / "postel_bos_000.txt"),
|
||||
"label_count": 0,
|
||||
"is_negative": True,
|
||||
"is_repeated_background_negative": False,
|
||||
},
|
||||
{
|
||||
"sample_slug": "postel_bos",
|
||||
"sample_role": "background_candidate",
|
||||
"split": "train",
|
||||
"tile_index": 1,
|
||||
"repeat_index": 1,
|
||||
"kept": True,
|
||||
"label_path": str(labels_train / "postel_bos_000_hn01.txt"),
|
||||
"label_count": 0,
|
||||
"is_negative": True,
|
||||
"is_repeated_background_negative": True,
|
||||
},
|
||||
{
|
||||
"sample_slug": "turnhout",
|
||||
"sample_role": "reference",
|
||||
"split": "val",
|
||||
"tile_index": 2,
|
||||
"repeat_index": 0,
|
||||
"kept": True,
|
||||
"label_path": str(labels_val / "turnhout_000.txt"),
|
||||
"label_count": 1,
|
||||
"is_negative": False,
|
||||
"is_repeated_background_negative": False,
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
output_dir = tmp_path / "audit"
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(script_path),
|
||||
"--summary-path",
|
||||
str(summary_path),
|
||||
"--output-dir",
|
||||
str(output_dir),
|
||||
"--min-positive-samples",
|
||||
"3",
|
||||
"--min-val-positive-samples",
|
||||
"2",
|
||||
"--max-repeated-negative-share",
|
||||
"0.25",
|
||||
"--min-median-box-area",
|
||||
"0.02",
|
||||
"--max-small-box-share",
|
||||
"0.25",
|
||||
],
|
||||
cwd=ROOT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
assert "Operator YOLO dataset quality audit passed" in result.stdout
|
||||
|
||||
report = json.loads(
|
||||
(output_dir / "operator_yolo_dataset_quality_audit.json").read_text(encoding="utf-8")
|
||||
)
|
||||
assert report["status"] == "needs_attention"
|
||||
assert report["sample_count"] == 3
|
||||
assert report["positive_sample_count"] == 2
|
||||
assert report["background_sample_count"] == 1
|
||||
assert report["train_negative_tile_count"] == 2
|
||||
assert report["repeated_background_negative_tile_count"] == 1
|
||||
assert report["label_stats"]["parsed_label_count"] == 3
|
||||
assert report["label_stats"]["invalid_label_count"] == 0
|
||||
|
||||
warning_codes = {warning["code"] for warning in report["warnings"]}
|
||||
assert "positive_sample_count_below_gate" in warning_codes
|
||||
assert "val_positive_sample_count_below_gate" in warning_codes
|
||||
assert "repeated_background_negative_share_above_gate" in warning_codes
|
||||
assert "median_box_area_below_gate" in warning_codes
|
||||
assert "small_box_share_above_gate" in warning_codes
|
||||
|
||||
markdown = (output_dir / "operator_yolo_dataset_quality_audit.md").read_text(encoding="utf-8")
|
||||
assert "Operator YOLO Dataset Quality Audit" in markdown
|
||||
assert "Label Quality" in markdown
|
||||
assert "positive_sample_count_below_gate" in markdown
|
||||
Reference in New Issue
Block a user