import tempfile import unittest from pathlib import Path from PIL import Image import scene_concepts as sc class CatalogueTests(unittest.TestCase): def test_groups_and_prompts(self): groups = {g for g, _ in sc.CATALOGUE.values()} self.assertEqual(groups, {"scene", "backdrop", "texture"}) for name in sc.CATALOGUE: prompt = sc.compose_prompt(name) self.assertGreater(len(prompt), 80, name) for day in ("skyline_day", "skyline_dusk", "skyline_night"): self.assertEqual(sc.CATALOGUE[day][0], "backdrop") def test_shots_point_at_scene_concepts(self): for shot, concept in sc.SHOTS.items(): self.assertEqual(sc.CATALOGUE[concept][0], "scene", shot) def test_paths_stay_in_their_folders(self): self.assertTrue(sc.raw_path("boulevard_skyline_day").is_relative_to(sc.CONCEPT_DIR)) self.assertTrue(sc.raw_path("hollywood_boulevard_entrance_day").is_relative_to( sc.CONCEPT_DIR / "hollywood_boulevard")) self.assertTrue(sc.raw_path("joke_shop_entrance_day").is_relative_to( sc.CONCEPT_DIR / "joke_shop")) self.assertTrue(sc.raw_path("skyline_day").is_relative_to(sc.RAW_DIR)) self.assertTrue(sc.raw_path("asphalt").is_relative_to(sc.RAW_DIR)) class NoOverwriteTests(unittest.TestCase): def test_next_free_path_never_returns_an_existing_file(self): with tempfile.TemporaryDirectory() as tmp: base = Path(tmp) / "a.png" self.assertEqual(sc.next_free_path(base), base) base.write_bytes(b"x") second = sc.next_free_path(base) self.assertEqual(second.name, "a-v2.png") second.write_bytes(b"x") self.assertEqual(sc.next_free_path(base).name, "a-v3.png") class ProcessingTests(unittest.TestCase): def _skyline(self, opaque=False): image = Image.new("RGBA", (300, 200), (120, 170, 230, 255 if opaque else 0)) for x in range(300): top = 80 + (x * 7) % 50 for y in range(top, 200): image.putpixel((x, y), (40 + x % 30, 40, 50, 255)) return image def test_key_sky_makes_an_opaque_sky_transparent(self): keyed = sc.key_sky(self._skyline(opaque=True)) self.assertEqual(keyed.getpixel((5, 5))[3], 0) self.assertEqual(keyed.getpixel((5, 199))[3], 255) def test_band_keeps_tower_tops(self): band = sc._band(self._skyline()) self.assertAlmostEqual(band.size[0] / band.size[1], 2.0, delta=0.6) alpha = band.getchannel("A") self.assertEqual(alpha.getpixel((0, band.size[1] - 1)), 255) def test_processed_backdrop_audits_clean(self): with tempfile.TemporaryDirectory() as tmp: src = Path(tmp) / "raw.png" self._skyline().save(src) out = sc.process_backdrop(src, Path(tmp) / "skyline.png") result = sc.audit_backdrop(out) self.assertEqual(result["errors"], []) def test_texture_processing_writes_tile_and_normal(self): with tempfile.TemporaryDirectory() as tmp: src = Path(tmp) / "raw.png" Image.effect_noise((128, 128), 40).convert("RGB").save(src) out = sc.process_texture(src, Path(tmp) / "tile.png", size=64) self.assertEqual(Image.open(out).size, (64, 64)) normal = Image.open(out.with_name("tile_normal.png")) self.assertEqual(normal.size, (64, 64)) r, g, b = normal.getpixel((10, 10)) self.assertGreater(b, 128) # normals point out of the surface def _striped(period=64, size=256): image = Image.new("RGB", (size, size), (200, 190, 170)) for x in range(size): if (x // (period // 2)) % 2 == 0: for y in range(size): image.putpixel((x, y), (150, 40, 45)) return image class StripeTests(unittest.TestCase): def test_period_found_and_tile_wraps(self): tile = _striped() self.assertEqual(sc.stripe_period(tile), 64) aligned = sc.align_stripes(tile) self.assertEqual(aligned.size, tile.size) # The wrap step along x matches a normal neighbouring-column step. profile = sc._column_profile(aligned) wrap = sc._step(profile[-1], profile[0]) hardest = max(sc._step(profile[x], profile[x + 1]) for x in range(aligned.size[0] - 1)) self.assertLessEqual(wrap, hardest * 1.25 + 0.5) def test_flat_texture_has_no_period(self): self.assertEqual(sc.stripe_period(Image.new("RGB", (128, 128), (90, 90, 90))), 0) flat = Image.new("RGB", (64, 64), (128, 128, 128)) self.assertEqual(sc.align_stripes(flat).size, flat.size) def test_striped_audit_accepts_a_boundary_on_the_edge(self): with tempfile.TemporaryDirectory() as tmp: path = Path(tmp) / "awning_canvas.png" image = _striped(period=64, size=sc.TILE) image.save(path) result = sc.audit_striped_tile(path) self.assertEqual(result["errors"], []) class PromoteTests(unittest.TestCase): def test_promote_copies_and_never_overwrites(self): with tempfile.TemporaryDirectory() as tmp: picks = Path(tmp) / "picks" global_dir = sc.PICKS_DIR sc.PICKS_DIR = picks try: sc.READY_DIR = Path(tmp) Image.new("RGB", (16, 16), (10, 20, 30)).save(Path(tmp) / "src.png") Image.new("RGB", (16, 16), (128, 128, 255)).save(Path(tmp) / "src_normal.png") target = sc.promote("pick", "src") self.assertTrue(target.is_file()) self.assertTrue((picks / "pick_normal.png").is_file()) Image.new("RGB", (16, 16), (0, 0, 0)).save(Path(tmp) / "src2.png") again = sc.promote("pick", "src2") self.assertEqual(again, target) self.assertNotEqual(Image.open(target).getpixel((0, 0)), (0, 0, 0)) finally: sc.PICKS_DIR = global_dir sc.READY_DIR = Path(tmp).parent # restored by the loader below def test_picks_exist_in_catalogue(self): for name, source in sc.PICKS.items(): self.assertTrue(source.startswith(name), f"{name} <- {source}") class RealismTests(unittest.TestCase): def test_metrics_and_gap(self): with tempfile.TemporaryDirectory() as tmp: flat = Path(tmp) / "flat.png" busy = Path(tmp) / "busy.png" Image.new("RGB", (320, 180), (128, 128, 128)).save(flat) Image.effect_noise((320, 180), 60).convert("RGB").save(busy) m_flat, m_busy = sc.image_metrics(flat), sc.image_metrics(busy) self.assertLess(m_flat["edge_density"], m_busy["edge_density"]) self.assertEqual(sc.metric_gap(m_busy, m_busy), 0.0) def test_parse_realism_response_clamps_and_limits(self): payload = {"choices": [{"message": {"content": '{"scores": {"overall": 14, "lighting": "3"}, "fixes": ["a","b","c","d","e","f"]}'}}]} verdict = sc.parse_realism_response(payload) self.assertEqual(verdict["scores"], {"overall": 10.0, "lighting": 3.0}) self.assertEqual(len(verdict["fixes"]), 5) self.assertEqual(sc.parse_realism_response({})["scores"], {}) def test_summary_averages_per_set(self): report = {"sets": {"a": { "x": {"metric_gap": 0.2, "judge": {"scores": {"overall": 2.0}}}, "y": {"metric_gap": 0.4, "judge": {"scores": {"overall": 4.0}}}}}} summary = sc.summarize(report) self.assertEqual(summary["a"]["overall"], 3.0) self.assertAlmostEqual(summary["a"]["metric_gap"], 0.3) self.assertIn("Mean scores", sc.render_markdown( {"run": "r", "judge_model": None, "sets": report["sets"] | {}} | { "sets": {"a": {k: dict(v, target="t.png", metrics={ "edge_density": 1, "fine_detail": 1, "contrast": 1}) for k, v in report["sets"]["a"].items()}}})) def test_request_carries_both_images(self): with tempfile.TemporaryDirectory() as tmp: a = Path(tmp) / "a.png" Image.new("RGB", (8, 8)).save(a) body = sc.build_realism_request("m", a, a) parts = body["messages"][0]["content"] self.assertEqual([p["type"] for p in parts], ["text", "image_url", "image_url"]) if __name__ == "__main__": unittest.main()