import json import os import sys import tempfile import pytest from PIL import Image sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from data_utils.privileged_schema import ( heuristic_bbox_from_visual_fact, normalize_evidence_bbox, parse_visual_fact, resolve_crop_bbox, ) from opsd_utils import debug_log as opsd_debug from opsd_utils.privileged import build_privileged_context, maybe_save_privileged_images from opsd_utils.privileged.image_utils import crop_image, load_rgb, resolve_teacher_images from opsd_utils.privileged.profiles import effective_profile def _make_image(path: str, size=(100, 100), color=(255, 0, 0)): img = Image.new("RGB", size, color) img.save(path) return path def test_text_provider(): sample = {"hint": "Rep=67", "answer": "Answer: 131"} suffix, images = build_privileged_context(sample, ["text"], privileged_profile="text") assert "Rep=67" in suffix assert "131" in suffix assert images == [] def test_hybrid_provider_suffix(): img = Image.new("RGB", (32, 32)) sample = {"hint": "step", "visual_fact": "bar=3", "answer": "Answer: 3", "image": img} suffix, images = build_privileged_context( sample, privileged_profile="hybrid", opsd_config={"privileged_image": {"mode": "dual"}}, ) assert "Visual Facts" in suffix assert "Reference" in suffix assert len(images) == 2 def test_hybrid_default_single_image_for_chartqa(): img = Image.new("RGB", (32, 32)) sample = {"hint": "step", "visual_fact": "bar=3", "answer": "Answer: 3", "image": img} suffix, images = build_privileged_context(sample, privileged_profile="hybrid") assert "Visual Facts" in suffix assert "Reference" in suffix assert len(images) == 1 def test_visual_profile_excludes_answer(): img = Image.new("RGB", (32, 32)) sample = {"hint": "secret", "visual_fact": '{"objects":[]}', "answer": "Answer: 3", "image": img} suffix, _ = build_privileged_context(sample, privileged_profile="visual") assert "Visual Facts" in suffix assert "Reference Answer" not in suffix def test_math_lm_downgrade(): sample = {"hint": "step", "answer": "Answer: 1"} profile = effective_profile(sample, "hybrid") assert profile == "text" def test_normalize_evidence_bbox_c2(): assert normalize_evidence_bbox([0.1, 0.2, 0.8, 0.9]) == [0.1, 0.2, 0.8, 0.9] assert normalize_evidence_bbox([0.1, 0.2, 1.5, 0.9]) is None def test_heuristic_bbox_d2(): vf = json.dumps({"objects": [{"name": "cat", "position": "center"}]}) bbox = heuristic_bbox_from_visual_fact(vf) assert bbox == [0.25, 0.25, 0.75, 0.75] def test_crop_image_normalized_bbox(): img = Image.new("RGB", (100, 100), (0, 255, 0)) crop, strategy = crop_image(img, bbox_norm=[0.2, 0.2, 0.8, 0.8], strategy="bbox") assert strategy == "bbox" assert crop.size[0] > 0 def test_resolve_teacher_images_dual(): img = Image.new("RGB", (80, 80), (0, 0, 255)) sample = { "image": img, "visual_fact": json.dumps({"objects": [{"position": "top"}]}), } images, meta = resolve_teacher_images(sample, "hybrid", crop_cfg={"mode": "dual"}) assert len(images) == 2 assert meta["num_teacher_images"] == 2 assert meta["crop_strategy"] in ("heuristic", "center", "center_fallback", "bbox") def test_chartqa_enriched_visual_fact_hint(): """Enriched ChartQA records (F1+F2) should activate VisualFactsProvider.""" from data_utils.chart.deplot_pipeline import build_deplot_visual_fact sample = { "hint": "Goal: Find the lowest value.\nObservation: values are 70, 72, 77.", "answer": "Answer: 70", "visual_fact_hint": "Goal: Find the lowest value.\nObservation: values are 70, 72, 77.", "visual_fact": "Goal: Find the lowest value.\nObservation: values are 70, 72, 77.", "visual_fact_deplot": build_deplot_visual_fact( {"question": "q"}, "Year | Value\n2019 | 70\n2020 | 72" ), "image": Image.new("RGB", (64, 64)), } suffix, images = build_privileged_context( sample, ["text", "visual_facts"], privileged_profile="hybrid", ) assert "Visual Facts - Hint" in suffix assert "Visual Facts - DePlot" in suffix assert "2019 | 70" in suffix assert "Reference Reasoning" in suffix assert len(images) == 1 vf_raw = sample.get("visual_fact") or sample.get("visual_facts") assert vf_raw and len(vf_raw.strip()) > 0 def test_visual_facts_f1_f2_merge(): from data_utils.chart.deplot_pipeline import build_deplot_visual_fact sample = { "visual_fact_hint": "hint table", "visual_fact_deplot": build_deplot_visual_fact( {"question": "q"}, "Col | Val\nA | 1" ), "image": Image.new("RGB", (32, 32)), } suffix, _ = build_privileged_context(sample, privileged_profile="hybrid") assert "Visual Facts - Hint" in suffix assert "Visual Facts - DePlot" in suffix assert "Col | Val" in suffix def test_parse_visual_fact_b1(): raw = {"objects": [{"name": "a"}]} text = parse_visual_fact(raw) assert "objects" in text def test_debug_artifacts_respect_detail_every(): opsd_debug.configure(enabled=True, detail_every=10, rank=0, world_size=1) with tempfile.TemporaryDirectory() as tmp: img = Image.new("RGB", (32, 32)) path = maybe_save_privileged_images(5, 0, img, img, meta={"crop_strategy": "center"}, output_dir=tmp) assert path is None assert not os.path.exists(os.path.join(tmp, "logs", "images")) path = maybe_save_privileged_images(10, 0, img, img, meta={"crop_strategy": "center"}, output_dir=tmp) assert path is not None assert os.path.exists(f"{path}_full.png") assert os.path.exists(f"{path}_meta.json") if __name__ == "__main__": test_text_provider() test_hybrid_provider_suffix() test_math_lm_downgrade() test_debug_artifacts_respect_detail_every() print("Privileged provider tests passed.")