Replace the generic boulevard facade with a stylized 6840 Hollywood Blvd block: six Ionic columns, eight stone steps, Masonic frieze/medallions, event banners, side doors, El Capitan Theatre and retail neighbours, Ovation Hollywood arcade, Walk of Fame and four-lane street markings. Add the Day and Night Hollywood venue-menu rows. Night uses a phase-time offset so a full-length shift stays in LATE while the clock starts at zero; late routine traffic keeps the street populated and the offset round-trips through checkpoint restore. Retune local-light falloff to linear attenuation, add a cool north sky fill, and use the smooth limestone facade texture. Refresh the staged gallery and map references; add geometry-budget, menu, night-offset and late-traffic tests.
188 lines
8.2 KiB
Python
188 lines
8.2 KiB
Python
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("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()
|