Add operator YOLO label QA contact sheets
This commit is contained in:
@@ -61,6 +61,10 @@ def test_all_in_one_dockerfile_copies_operator_scripts_for_runtime_use() -> None
|
||||
assert "COPY scripts/prepare_operator_real_data_samples.py /app/scripts/prepare_operator_real_data_samples.py" in dockerfile
|
||||
assert "COPY scripts/export_operator_yolo_tile_dataset.py /app/scripts/export_operator_yolo_tile_dataset.py" in dockerfile
|
||||
assert "COPY scripts/audit_operator_yolo_dataset_quality.py /app/scripts/audit_operator_yolo_dataset_quality.py" in dockerfile
|
||||
assert (
|
||||
"COPY scripts/render_operator_yolo_label_qa_contact_sheets.py "
|
||||
"/app/scripts/render_operator_yolo_label_qa_contact_sheets.py"
|
||||
) in dockerfile
|
||||
assert "COPY scripts/train_operator_yolo_detector.sh /app/scripts/train_operator_yolo_detector.sh" in dockerfile
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def test_operator_yolo_label_qa_contact_sheets_render_visual_artifacts(tmp_path: Path) -> None:
|
||||
script_path = ROOT / "scripts" / "render_operator_yolo_label_qa_contact_sheets.py"
|
||||
assert script_path.exists()
|
||||
|
||||
dataset_dir = tmp_path / "yolo-dataset"
|
||||
image_train = dataset_dir / "images" / "train"
|
||||
image_val = dataset_dir / "images" / "val"
|
||||
labels_train = dataset_dir / "labels" / "train"
|
||||
labels_val = dataset_dir / "labels" / "val"
|
||||
image_train.mkdir(parents=True)
|
||||
image_val.mkdir(parents=True)
|
||||
labels_train.mkdir(parents=True)
|
||||
labels_val.mkdir(parents=True)
|
||||
|
||||
for path, color in (
|
||||
(image_train / "dense_000.png", (120, 130, 140)),
|
||||
(image_train / "invalid_000.png", (80, 100, 120)),
|
||||
(image_val / "missing_000.png", (90, 120, 90)),
|
||||
(image_val / "negative_000.png", (50, 50, 55)),
|
||||
):
|
||||
Image.new("RGB", (64, 64), color=color).save(path)
|
||||
|
||||
(labels_train / "dense_000.txt").write_text(
|
||||
"0 0.500000 0.500000 0.500000 0.500000\n"
|
||||
"0 0.250000 0.250000 0.250000 0.250000\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(labels_train / "invalid_000.txt").write_text(
|
||||
"0 0.500000 0.500000 0.300000 0.300000\n"
|
||||
"not-a-valid-yolo-row\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(labels_val / "negative_000.txt").write_text("", encoding="utf-8")
|
||||
|
||||
missing_label_path = labels_val / "missing_000.txt"
|
||||
summary_path = dataset_dir / "yolo_tile_dataset_summary.json"
|
||||
summary_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"status": "ok",
|
||||
"dataset_yaml": str(dataset_dir / "dataset.yaml"),
|
||||
"output_dir": str(dataset_dir),
|
||||
"class_names": ["building"],
|
||||
"tile_size": 64,
|
||||
"stride": 64,
|
||||
"tiles": [
|
||||
{
|
||||
"sample_slug": "dense",
|
||||
"sample_role": "reference",
|
||||
"background_category": "reference_aoi",
|
||||
"split": "train",
|
||||
"tile_index": 0,
|
||||
"kept": True,
|
||||
"image_path": str(image_train / "dense_000.png"),
|
||||
"label_path": str(labels_train / "dense_000.txt"),
|
||||
"label_count": 2,
|
||||
"is_negative": False,
|
||||
},
|
||||
{
|
||||
"sample_slug": "invalid",
|
||||
"sample_role": "reference",
|
||||
"background_category": "reference_aoi",
|
||||
"split": "train",
|
||||
"tile_index": 1,
|
||||
"kept": True,
|
||||
"image_path": str(image_train / "invalid_000.png"),
|
||||
"label_path": str(labels_train / "invalid_000.txt"),
|
||||
"label_count": 1,
|
||||
"is_negative": False,
|
||||
},
|
||||
{
|
||||
"sample_slug": "missing",
|
||||
"sample_role": "background_candidate",
|
||||
"background_category": "sparse_building_context",
|
||||
"split": "val",
|
||||
"tile_index": 2,
|
||||
"kept": True,
|
||||
"image_path": str(image_val / "missing_000.png"),
|
||||
"label_path": str(missing_label_path),
|
||||
"label_count": 1,
|
||||
"is_negative": False,
|
||||
},
|
||||
{
|
||||
"sample_slug": "negative",
|
||||
"sample_role": "background_candidate",
|
||||
"background_category": "pure_empty_negative",
|
||||
"split": "val",
|
||||
"tile_index": 3,
|
||||
"kept": True,
|
||||
"image_path": str(image_val / "negative_000.png"),
|
||||
"label_path": str(labels_val / "negative_000.txt"),
|
||||
"label_count": 0,
|
||||
"is_negative": True,
|
||||
},
|
||||
],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
output_dir = tmp_path / "label-qa"
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(script_path),
|
||||
"--summary-path",
|
||||
str(summary_path),
|
||||
"--output-dir",
|
||||
str(output_dir),
|
||||
"--max-tiles",
|
||||
"4",
|
||||
"--columns",
|
||||
"2",
|
||||
"--thumb-size",
|
||||
"128",
|
||||
],
|
||||
cwd=ROOT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
assert "Operator YOLO label QA contact sheets rendered" in result.stdout
|
||||
|
||||
report = json.loads((output_dir / "operator_yolo_label_qa_summary.json").read_text(encoding="utf-8"))
|
||||
assert report["status"] == "ok"
|
||||
assert report["selected_tile_count"] == 4
|
||||
assert report["rendered_tile_count"] == 4
|
||||
assert report["missing_label_file_count"] == 1
|
||||
assert report["invalid_label_count"] == 1
|
||||
assert [tile["sample_slug"] for tile in report["selected_tiles"]] == [
|
||||
"dense",
|
||||
"invalid",
|
||||
"missing",
|
||||
"negative",
|
||||
]
|
||||
|
||||
sheet_path = output_dir / report["contact_sheets"][0]["path"]
|
||||
assert sheet_path.exists()
|
||||
sheet = Image.open(sheet_path).convert("RGB")
|
||||
assert sheet.size[0] >= 256
|
||||
assert sheet.size[1] >= 256
|
||||
assert len(sheet.getcolors(maxcolors=1000000) or []) > 4
|
||||
|
||||
markdown = (output_dir / "operator_yolo_label_qa_contact_sheet.md").read_text(encoding="utf-8")
|
||||
assert "Operator YOLO Label QA Contact Sheets" in markdown
|
||||
assert "missing label files: 1" in markdown
|
||||
assert "invalid label rows: 1" in markdown
|
||||
assert "contact_sheet_001.png" in markdown
|
||||
Reference in New Issue
Block a user