highlight audited YOLO label relationships
This commit is contained in:
+4
-1
@@ -9,7 +9,10 @@ the script neither reads protected test data nor promotes a model.
|
||||
`render_operator_yolo_label_qa_contact_sheets.py` paginates complete visual
|
||||
reviews with `--tiles-per-sheet` (default `64`). This keeps large corpora
|
||||
inspectable while preserving deterministic tile selection, ordering, label
|
||||
accounting and stable `contact_sheet_001.png` naming for the first page.
|
||||
accounting and stable `contact_sheet_001.png` naming for the first page. When
|
||||
the input is a relationship-audit manifest, exact duplicates are highlighted
|
||||
cyan, near duplicates red and possible nesting magenta; every highlighted box
|
||||
also shows its zero-based label-row index. Unflagged labels remain yellow.
|
||||
|
||||
`audit_yolo_label_relationships.py` performs a read-only, same-class audit of
|
||||
exact duplicate, high-IoU and possible-containment pairs inside YOLO label
|
||||
|
||||
@@ -250,6 +250,7 @@ def draw_tile_card(
|
||||
invalid_label_count: int,
|
||||
missing_label_file: bool,
|
||||
low_visual_variance: bool,
|
||||
relationship_highlights: dict[int, str],
|
||||
) -> Image.Image:
|
||||
header_height = 44
|
||||
card = Image.new(
|
||||
@@ -277,10 +278,17 @@ def draw_tile_card(
|
||||
subtitle_parts.append(f"invalid:{invalid_label_count}")
|
||||
if low_visual_variance:
|
||||
subtitle_parts.append("low-variance")
|
||||
if relationship_highlights:
|
||||
subtitle_parts.append(f"highlighted:{len(relationship_highlights)}")
|
||||
draw.text((6, 6), title[:44], fill=(255, 255, 255), font=font)
|
||||
draw.text((6, 24), " | ".join(subtitle_parts)[:52], fill=(191, 219, 254), font=font)
|
||||
|
||||
for box in boxes:
|
||||
highlight_styles = {
|
||||
"exact_duplicate": ((0, 220, 255), 5),
|
||||
"near_duplicate": ((255, 55, 55), 5),
|
||||
"possible_nested": ((255, 0, 220), 5),
|
||||
}
|
||||
for box_index, box in enumerate(boxes):
|
||||
x_center = box["center_x"] * thumb_size
|
||||
y_center = box["center_y"] * thumb_size + header_height
|
||||
width = box["width"] * thumb_size
|
||||
@@ -291,11 +299,65 @@ def draw_tile_card(
|
||||
bottom = min(thumb_size + header_height - 1, y_center + height / 2)
|
||||
if right < left or bottom < top:
|
||||
continue
|
||||
draw.rectangle((left, top, right, bottom), outline=(255, 214, 10), width=3)
|
||||
relationship = relationship_highlights.get(box_index)
|
||||
color, stroke_width = highlight_styles.get(relationship, ((255, 214, 10), 3))
|
||||
draw.rectangle((left, top, right, bottom), outline=color, width=stroke_width)
|
||||
if relationship:
|
||||
draw.rectangle(
|
||||
(left, top, min(right, left + 30), min(bottom, top + 12)),
|
||||
fill=(20, 31, 44),
|
||||
)
|
||||
draw.text(
|
||||
(left + 2, top + 1),
|
||||
f"#{box_index}",
|
||||
fill=color,
|
||||
font=font,
|
||||
)
|
||||
|
||||
return card
|
||||
|
||||
|
||||
def build_relationship_highlights(
|
||||
summary: dict[str, Any],
|
||||
) -> dict[str, dict[int, str]]:
|
||||
"""Map audited label rows to their highest-priority visual warning."""
|
||||
priorities = {
|
||||
"possible_nested": 1,
|
||||
"near_duplicate": 2,
|
||||
"exact_duplicate": 3,
|
||||
}
|
||||
highlights: dict[str, dict[int, str]] = {}
|
||||
flagged_tiles = summary.get("flagged_tiles") or []
|
||||
if not isinstance(flagged_tiles, list):
|
||||
return highlights
|
||||
|
||||
for tile in flagged_tiles:
|
||||
if not isinstance(tile, dict) or not tile.get("label_path"):
|
||||
continue
|
||||
tile_highlights = highlights.setdefault(str(tile["label_path"]), {})
|
||||
relationships = tile.get("relationships") or []
|
||||
if not isinstance(relationships, list):
|
||||
continue
|
||||
for relationship in relationships:
|
||||
if not isinstance(relationship, dict):
|
||||
continue
|
||||
relationship_type = str(relationship.get("relationship") or "")
|
||||
if relationship_type not in priorities:
|
||||
continue
|
||||
for field in ("first_index", "second_index"):
|
||||
index = relationship.get(field)
|
||||
if not isinstance(index, int) or index < 0:
|
||||
continue
|
||||
existing = tile_highlights.get(index)
|
||||
if (
|
||||
existing is None
|
||||
or priorities[relationship_type] > priorities[existing]
|
||||
):
|
||||
tile_highlights[index] = relationship_type
|
||||
|
||||
return highlights
|
||||
|
||||
|
||||
def image_has_low_visual_variance(image_path: Path, blank_range_threshold: int) -> bool:
|
||||
image = Image.open(image_path).convert("L")
|
||||
min_value, max_value = image.getextrema()
|
||||
@@ -344,6 +406,8 @@ def build_report(
|
||||
invalid_label_count = 0
|
||||
valid_label_count = 0
|
||||
low_visual_variance_tile_count = 0
|
||||
relationship_highlight_count = 0
|
||||
relationship_highlights = build_relationship_highlights(summary)
|
||||
|
||||
for tile in selected_tiles:
|
||||
image_path = resolve_path(tile.get("image_path"), summary_path)
|
||||
@@ -351,6 +415,8 @@ def build_report(
|
||||
boxes, tile_invalid_count, missing_label_file = parse_yolo_label_file(
|
||||
label_path
|
||||
)
|
||||
tile_relationship_highlights = relationship_highlights.get(str(label_path), {})
|
||||
relationship_highlight_count += len(tile_relationship_highlights)
|
||||
invalid_label_count += tile_invalid_count
|
||||
valid_label_count += len(boxes)
|
||||
if missing_label_file:
|
||||
@@ -375,6 +441,7 @@ def build_report(
|
||||
invalid_label_count=tile_invalid_count,
|
||||
missing_label_file=missing_label_file,
|
||||
low_visual_variance=low_visual_variance,
|
||||
relationship_highlights=tile_relationship_highlights,
|
||||
)
|
||||
)
|
||||
rendered = True
|
||||
@@ -393,6 +460,7 @@ def build_report(
|
||||
"invalid_label_count": tile_invalid_count,
|
||||
"missing_label_file": missing_label_file,
|
||||
"low_visual_variance": low_visual_variance,
|
||||
"relationship_highlight_count": len(tile_relationship_highlights),
|
||||
"rendered": rendered,
|
||||
}
|
||||
)
|
||||
@@ -435,6 +503,7 @@ def build_report(
|
||||
"invalid_label_count": invalid_label_count,
|
||||
"valid_label_count": valid_label_count,
|
||||
"low_visual_variance_tile_count": low_visual_variance_tile_count,
|
||||
"relationship_highlight_count": relationship_highlight_count,
|
||||
"blank_range_threshold": args.blank_range_threshold,
|
||||
"contact_sheets": contact_sheets,
|
||||
"selected_tiles": selected_report_tiles,
|
||||
@@ -456,6 +525,7 @@ def write_markdown(report: dict[str, Any], output_dir: Path) -> None:
|
||||
f"- invalid label rows: {report['invalid_label_count']}",
|
||||
f"- low-variance rendered tiles: {report['low_visual_variance_tile_count']}",
|
||||
f"- valid labels rendered: {report['valid_label_count']}",
|
||||
f"- relationship-highlighted labels: {report['relationship_highlight_count']}",
|
||||
"",
|
||||
"## Contact Sheets",
|
||||
"",
|
||||
|
||||
Reference in New Issue
Block a user