highlight audited YOLO label relationships
This commit is contained in:
+13
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"ledger_id": "reviewedexp6-ai-assisted-review-r2-full-corpus",
|
"ledger_id": "reviewedexp6-ai-assisted-review-r2-full-corpus",
|
||||||
"reviewed_at": "2026-08-09T15:00:00+02:00",
|
"reviewed_at": "2026-08-09T19:10:57+02:00",
|
||||||
"reviewer": {
|
"reviewer": {
|
||||||
"id": "openai-codex",
|
"id": "openai-codex",
|
||||||
"type": "ai_assistant",
|
"type": "ai_assistant",
|
||||||
@@ -78,12 +78,23 @@
|
|||||||
"audit_sha256": "2a219f650376e77a927c1ca37f801ac9fe8c2b8406cab6cd149be07e0557af73",
|
"audit_sha256": "2a219f650376e77a927c1ca37f801ac9fe8c2b8406cab6cd149be07e0557af73",
|
||||||
"focused_contact_sheet_path": "/app/storage/operator-data/model-review/reviewedexp6-corpus/nested-pairs-review-r1/contact_sheet_001.png",
|
"focused_contact_sheet_path": "/app/storage/operator-data/model-review/reviewedexp6-corpus/nested-pairs-review-r1/contact_sheet_001.png",
|
||||||
"focused_contact_sheet_sha256": "7c27fc66bb1978296efc53d3e9f78ae6aa27816d5893c77f44d8803e57a40ee5",
|
"focused_contact_sheet_sha256": "7c27fc66bb1978296efc53d3e9f78ae6aa27816d5893c77f44d8803e57a40ee5",
|
||||||
|
"relationship_highlight_summary_path": "/app/storage/operator-data/model-review/reviewedexp6-corpus/nested-pairs-review-r2/operator_yolo_label_qa_summary.json",
|
||||||
|
"relationship_highlight_summary_sha256": "e4e6edc5c7223334c4feca9e242ac6d3c345b633a476aa9356b44133dcffe619",
|
||||||
|
"relationship_highlight_contact_sheet_path": "/app/storage/operator-data/model-review/reviewedexp6-corpus/nested-pairs-review-r2/contact_sheet_001.png",
|
||||||
|
"relationship_highlight_contact_sheet_sha256": "2a6bdd542b8ac49bcd5ac4d9c84999982461463ddd3c2fe0dc7332ceeb7a5e60",
|
||||||
"exact_duplicate_pairs": 0,
|
"exact_duplicate_pairs": 0,
|
||||||
"near_duplicate_pairs_iou_at_least_0_90": 0,
|
"near_duplicate_pairs_iou_at_least_0_90": 0,
|
||||||
"possible_nested_pairs_containment_at_least_0_98": 43,
|
"possible_nested_pairs_containment_at_least_0_98": 43,
|
||||||
"flagged_tile_count": 36,
|
"flagged_tile_count": 36,
|
||||||
|
"highlighted_unique_label_rows": 86,
|
||||||
|
"highlight_legend": {
|
||||||
|
"exact_duplicate": "cyan",
|
||||||
|
"near_duplicate": "red",
|
||||||
|
"possible_nested": "magenta",
|
||||||
|
"unflagged": "yellow"
|
||||||
|
},
|
||||||
"share_of_rendered_labels": 0.000543,
|
"share_of_rendered_labels": 0.000543,
|
||||||
"ai_visual_followup": "no systematic duplicate-label pattern observed; relationships mostly correspond to adjacent or complex building components",
|
"ai_visual_followup": "all 86 implicated label rows were index-highlighted; no cyan exact duplicates or red near duplicates exist, while the 43 magenta nesting pairs are dispersed across dense reference AOIs and predominantly show larger GRB envelopes around smaller building components rather than one systematic duplicate-label pattern",
|
||||||
"action": "retain for experimental analysis, require human adjudication before release, and perform no automatic rewrite or exclusion"
|
"action": "retain for experimental analysis, require human adjudication before release, and perform no automatic rewrite or exclusion"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12565,3 +12565,33 @@ Open:
|
|||||||
on a dedicated 384 px contact sheet. Visual follow-up found complex/adjacent
|
on a dedicated 384 px contact sheet. Visual follow-up found complex/adjacent
|
||||||
building components rather than a systematic duplicate-label pattern.
|
building components rather than a systematic duplicate-label pattern.
|
||||||
- Targeted auditor and renderer tests passed: 9 tests.
|
- Targeted auditor and renderer tests passed: 9 tests.
|
||||||
|
|
||||||
|
## 2026-08-09 - Index-bound relationship highlighting
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- Connected the relationship-audit manifest to the visual label renderer so
|
||||||
|
exact duplicates, near duplicates and possible nesting receive distinct
|
||||||
|
colors and every implicated YOLO row is labelled with its source index.
|
||||||
|
- Generated a new immutable 36-tile focused sheet on Tower. It renders 17,253
|
||||||
|
valid labels, highlights all 86 rows participating in the 43 nesting pairs
|
||||||
|
and contains zero invalid label rows.
|
||||||
|
- AI-assisted inspection confirmed that the focused corpus contains only
|
||||||
|
magenta nesting candidates; no exact or high-IoU duplicate category is
|
||||||
|
present. The cases remain dispersed dense-GRB review candidates rather than
|
||||||
|
a safe deterministic rewrite set.
|
||||||
|
- Retained every source file and label unchanged. Excluding entire flagged
|
||||||
|
tiles would remove 17,167 unflagged labels and is therefore not justified.
|
||||||
|
|
||||||
|
### Verified
|
||||||
|
|
||||||
|
- Targeted relationship-auditor, renderer and Sprint 167 regression suite:
|
||||||
|
10 tests passed.
|
||||||
|
- Scoped Ruff check, Ruff format check, Python compilation and Git whitespace
|
||||||
|
check passed.
|
||||||
|
|
||||||
|
### Remaining limitation
|
||||||
|
|
||||||
|
- This is explicit AI-assisted triage, not human adjudication. It improves the
|
||||||
|
evidence and review precision but does not unlock governed production
|
||||||
|
training or checkpoint promotion.
|
||||||
|
|||||||
@@ -1157,6 +1157,8 @@ This file now starts with the current implementation status. Older preparation/b
|
|||||||
legitimately.
|
legitimately.
|
||||||
- [x] Persist a deterministic label-relationship audit and a focused 36-tile
|
- [x] Persist a deterministic label-relationship audit and a focused 36-tile
|
||||||
contact sheet; AI follow-up found no systematic duplicate-label pattern.
|
contact sheet; AI follow-up found no systematic duplicate-label pattern.
|
||||||
|
- [x] Bind the relationship audit back to exact label-row indices and render
|
||||||
|
all 86 implicated rows with type-specific highlighting for focused review.
|
||||||
- [ ] Convert the AI-assisted ledger into no stronger claim than experimental
|
- [ ] Convert the AI-assisted ledger into no stronger claim than experimental
|
||||||
triage; a real human must independently review and sign the frozen artifacts
|
triage; a real human must independently review and sign the frozen artifacts
|
||||||
before the governed training wrapper may unlock.
|
before the governed training wrapper may unlock.
|
||||||
|
|||||||
@@ -117,3 +117,15 @@ AI-assisted inspection found no systematic duplicate-label pattern: the
|
|||||||
relationships predominantly represent adjacent or complex building components
|
relationships predominantly represent adjacent or complex building components
|
||||||
in dense GRB contexts. All tiles remain available for experimental analysis,
|
in dense GRB contexts. All tiles remain available for experimental analysis,
|
||||||
while the exact 43 pairs stay visible for human release adjudication.
|
while the exact 43 pairs stay visible for human release adjudication.
|
||||||
|
|
||||||
|
A second focused render now binds the relationship manifest back to the exact
|
||||||
|
YOLO label-row indices. It highlights all 86 implicated rows: cyan for exact
|
||||||
|
duplicates, red for near duplicates and magenta for possible nesting. The sheet
|
||||||
|
contains only magenta relationship marks, confirming visually and
|
||||||
|
machine-readably that there are no exact or high-IoU duplicates in this set.
|
||||||
|
The nesting cases are geographically dispersed and commonly combine a larger
|
||||||
|
GRB object envelope with a smaller component. They are not deterministic
|
||||||
|
rewrite candidates. Excluding the 36 complete tiles would also discard 17,167
|
||||||
|
unflagged labels, so no automatic tile exclusion or label mutation was made.
|
||||||
|
The checksum-bound highlighted sheet and summary are recorded in the AI review
|
||||||
|
ledger.
|
||||||
|
|||||||
+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
|
`render_operator_yolo_label_qa_contact_sheets.py` paginates complete visual
|
||||||
reviews with `--tiles-per-sheet` (default `64`). This keeps large corpora
|
reviews with `--tiles-per-sheet` (default `64`). This keeps large corpora
|
||||||
inspectable while preserving deterministic tile selection, ordering, label
|
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
|
`audit_yolo_label_relationships.py` performs a read-only, same-class audit of
|
||||||
exact duplicate, high-IoU and possible-containment pairs inside YOLO label
|
exact duplicate, high-IoU and possible-containment pairs inside YOLO label
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ def draw_tile_card(
|
|||||||
invalid_label_count: int,
|
invalid_label_count: int,
|
||||||
missing_label_file: bool,
|
missing_label_file: bool,
|
||||||
low_visual_variance: bool,
|
low_visual_variance: bool,
|
||||||
|
relationship_highlights: dict[int, str],
|
||||||
) -> Image.Image:
|
) -> Image.Image:
|
||||||
header_height = 44
|
header_height = 44
|
||||||
card = Image.new(
|
card = Image.new(
|
||||||
@@ -277,10 +278,17 @@ def draw_tile_card(
|
|||||||
subtitle_parts.append(f"invalid:{invalid_label_count}")
|
subtitle_parts.append(f"invalid:{invalid_label_count}")
|
||||||
if low_visual_variance:
|
if low_visual_variance:
|
||||||
subtitle_parts.append("low-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, 6), title[:44], fill=(255, 255, 255), font=font)
|
||||||
draw.text((6, 24), " | ".join(subtitle_parts)[:52], fill=(191, 219, 254), 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
|
x_center = box["center_x"] * thumb_size
|
||||||
y_center = box["center_y"] * thumb_size + header_height
|
y_center = box["center_y"] * thumb_size + header_height
|
||||||
width = box["width"] * thumb_size
|
width = box["width"] * thumb_size
|
||||||
@@ -291,11 +299,65 @@ def draw_tile_card(
|
|||||||
bottom = min(thumb_size + header_height - 1, y_center + height / 2)
|
bottom = min(thumb_size + header_height - 1, y_center + height / 2)
|
||||||
if right < left or bottom < top:
|
if right < left or bottom < top:
|
||||||
continue
|
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
|
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:
|
def image_has_low_visual_variance(image_path: Path, blank_range_threshold: int) -> bool:
|
||||||
image = Image.open(image_path).convert("L")
|
image = Image.open(image_path).convert("L")
|
||||||
min_value, max_value = image.getextrema()
|
min_value, max_value = image.getextrema()
|
||||||
@@ -344,6 +406,8 @@ def build_report(
|
|||||||
invalid_label_count = 0
|
invalid_label_count = 0
|
||||||
valid_label_count = 0
|
valid_label_count = 0
|
||||||
low_visual_variance_tile_count = 0
|
low_visual_variance_tile_count = 0
|
||||||
|
relationship_highlight_count = 0
|
||||||
|
relationship_highlights = build_relationship_highlights(summary)
|
||||||
|
|
||||||
for tile in selected_tiles:
|
for tile in selected_tiles:
|
||||||
image_path = resolve_path(tile.get("image_path"), summary_path)
|
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(
|
boxes, tile_invalid_count, missing_label_file = parse_yolo_label_file(
|
||||||
label_path
|
label_path
|
||||||
)
|
)
|
||||||
|
tile_relationship_highlights = relationship_highlights.get(str(label_path), {})
|
||||||
|
relationship_highlight_count += len(tile_relationship_highlights)
|
||||||
invalid_label_count += tile_invalid_count
|
invalid_label_count += tile_invalid_count
|
||||||
valid_label_count += len(boxes)
|
valid_label_count += len(boxes)
|
||||||
if missing_label_file:
|
if missing_label_file:
|
||||||
@@ -375,6 +441,7 @@ def build_report(
|
|||||||
invalid_label_count=tile_invalid_count,
|
invalid_label_count=tile_invalid_count,
|
||||||
missing_label_file=missing_label_file,
|
missing_label_file=missing_label_file,
|
||||||
low_visual_variance=low_visual_variance,
|
low_visual_variance=low_visual_variance,
|
||||||
|
relationship_highlights=tile_relationship_highlights,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rendered = True
|
rendered = True
|
||||||
@@ -393,6 +460,7 @@ def build_report(
|
|||||||
"invalid_label_count": tile_invalid_count,
|
"invalid_label_count": tile_invalid_count,
|
||||||
"missing_label_file": missing_label_file,
|
"missing_label_file": missing_label_file,
|
||||||
"low_visual_variance": low_visual_variance,
|
"low_visual_variance": low_visual_variance,
|
||||||
|
"relationship_highlight_count": len(tile_relationship_highlights),
|
||||||
"rendered": rendered,
|
"rendered": rendered,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -435,6 +503,7 @@ def build_report(
|
|||||||
"invalid_label_count": invalid_label_count,
|
"invalid_label_count": invalid_label_count,
|
||||||
"valid_label_count": valid_label_count,
|
"valid_label_count": valid_label_count,
|
||||||
"low_visual_variance_tile_count": low_visual_variance_tile_count,
|
"low_visual_variance_tile_count": low_visual_variance_tile_count,
|
||||||
|
"relationship_highlight_count": relationship_highlight_count,
|
||||||
"blank_range_threshold": args.blank_range_threshold,
|
"blank_range_threshold": args.blank_range_threshold,
|
||||||
"contact_sheets": contact_sheets,
|
"contact_sheets": contact_sheets,
|
||||||
"selected_tiles": selected_report_tiles,
|
"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"- invalid label rows: {report['invalid_label_count']}",
|
||||||
f"- low-variance rendered tiles: {report['low_visual_variance_tile_count']}",
|
f"- low-variance rendered tiles: {report['low_visual_variance_tile_count']}",
|
||||||
f"- valid labels rendered: {report['valid_label_count']}",
|
f"- valid labels rendered: {report['valid_label_count']}",
|
||||||
|
f"- relationship-highlighted labels: {report['relationship_highlight_count']}",
|
||||||
"",
|
"",
|
||||||
"## Contact Sheets",
|
"## Contact Sheets",
|
||||||
"",
|
"",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import pytest
|
|||||||
|
|
||||||
from scripts.render_operator_yolo_label_qa_contact_sheets import (
|
from scripts.render_operator_yolo_label_qa_contact_sheets import (
|
||||||
CONTACT_SHEET_NAME_TEMPLATE,
|
CONTACT_SHEET_NAME_TEMPLATE,
|
||||||
|
build_relationship_highlights,
|
||||||
filter_tiles_by_samples,
|
filter_tiles_by_samples,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,3 +35,40 @@ def test_filter_tiles_by_samples_without_filter_preserves_tiles() -> None:
|
|||||||
def test_contact_sheet_name_template_is_stable_and_one_indexed() -> None:
|
def test_contact_sheet_name_template_is_stable_and_one_indexed() -> None:
|
||||||
assert CONTACT_SHEET_NAME_TEMPLATE.format(index=1) == "contact_sheet_001.png"
|
assert CONTACT_SHEET_NAME_TEMPLATE.format(index=1) == "contact_sheet_001.png"
|
||||||
assert CONTACT_SHEET_NAME_TEMPLATE.format(index=12) == "contact_sheet_012.png"
|
assert CONTACT_SHEET_NAME_TEMPLATE.format(index=12) == "contact_sheet_012.png"
|
||||||
|
|
||||||
|
|
||||||
|
def test_relationship_highlights_bind_indices_and_preserve_highest_priority() -> None:
|
||||||
|
summary = {
|
||||||
|
"flagged_tiles": [
|
||||||
|
{
|
||||||
|
"label_path": "/data/tile.txt",
|
||||||
|
"relationships": [
|
||||||
|
{
|
||||||
|
"relationship": "possible_nested",
|
||||||
|
"first_index": 2,
|
||||||
|
"second_index": 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relationship": "exact_duplicate",
|
||||||
|
"first_index": 2,
|
||||||
|
"second_index": 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relationship": "near_duplicate",
|
||||||
|
"first_index": 6,
|
||||||
|
"second_index": 7,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert build_relationship_highlights(summary) == {
|
||||||
|
"/data/tile.txt": {
|
||||||
|
2: "exact_duplicate",
|
||||||
|
4: "possible_nested",
|
||||||
|
5: "exact_duplicate",
|
||||||
|
6: "near_duplicate",
|
||||||
|
7: "near_duplicate",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user