Add the photo-anchored variant pass with night and overcast facade frames
Full build / Import + headless tests (push) Successful in 40s
Full build / Full desktop build + optional release (push) Skipped

This commit is contained in:
zwitschi committed 2026-11-03 15:04:20 +01:00
1 parent 6e04c59589
commit ad91e71eeb
9 files changed
+183 -2

No files matched your search

+74
View File
@@ -31,6 +31,7 @@ import argparse
import json
import math
import os
import re
import sys
from pathlib import Path
@@ -1088,6 +1089,62 @@ def render_markdown(report: dict) -> str:
# ---------------------------------------------------------------------------
# Treatment clauses for the photo-anchored variant pass: one framing, several
# restaged conditions, so a concept board stays comparable with its photo board.
TREATMENTS = {
"day": "clean midday daylight, soft haze fading the far street to beige",
"night": "night scene lit by practical lights only (marquee, signage, street "
"lamps), dark sky, wet pavement reflections",
"weathered": "overcast flat light, weathered and grimy facade surfaces, faded "
"signage, scattered litter near the kerb",
}
def _anchor_treatment_prompt(context: str, treatment: str) -> str:
if treatment not in TREATMENTS:
sys.exit(f"error: unknown treatment '{treatment}'; "
f"choose from {sorted(TREATMENTS)}")
return f"{context}\n\n{REFERENCE_PROMPT_SUFFIX}\n\nRestage the scene: {TREATMENTS[treatment]}"
def cmd_anchor(args) -> int:
"""Generate day/night/weathered variants of one reference photo's framing."""
_load_env_file()
reference = _reference_source(args.reference, args.reference_unchecked)
treatments = [t.strip() for t in args.treatments.split(",") if t.strip()]
unknown = [t for t in treatments if t not in TREATMENTS]
if unknown:
sys.exit(f"error: unknown treatment(s) {unknown}; "
f"choose from {sorted(TREATMENTS)}")
out_dir = ROOT / args.out_dir
out_dir.mkdir(parents=True, exist_ok=True)
stem = args.name or re.sub(r"-[0-9]{4}-[0-9]{2}-[0-9]{2}$", "",
reference["path"].stem)
stem = re.sub(r"[^a-z0-9_]+", "_", stem.lower()).strip("_")
failed = []
for treatment in treatments:
prompt = _anchor_treatment_prompt(args.context, treatment)
print(f"anchoring {stem}_{treatment} ...", flush=True)
try:
image = generate_textures.generate_image(
prompt, args.model, args.request_size,
reference_path=reference["path"])
image = ImageOps.fit(image.convert("RGB"), (SCENE_W, SCENE_H),
method=Image.Resampling.LANCZOS)
except SystemExit as exc: # generate_textures exits on API errors
failed.append(treatment)
print(f" FAILED {treatment}: {exc}", file=sys.stderr)
continue
out = next_free_path(out_dir / f"{stem}_{treatment}.png")
image.save(out)
_write_reference_provenance(out, reference, f"{stem}_{treatment}", args.model)
print(f"wrote {_rel(out)} {image.size}")
if failed:
print("failed: " + ", ".join(failed), file=sys.stderr)
return 1
return 0
def cmd_list(_args) -> int:
for name, (group, _) in CATALOGUE.items():
have = len(raw_variants(name))
@@ -1123,6 +1180,23 @@ def build_parser():
prom.add_argument("--pick", default="", help="name=candidate overrides")
prom.set_defaults(handler=cmd_promote)
sub.add_parser("audit").set_defaults(handler=cmd_audit)
anchor = sub.add_parser("anchor",
help="generate day/night/weathered variants of one "
"reference photo's framing")
anchor.add_argument("--reference", required=True,
help="licence-cleared photo (listed in its refs SIDECAR.md)")
anchor.add_argument("--context", required=True,
help="what the photo shows and how to restage it")
anchor.add_argument("--treatments", default="day,night,weathered",
help="comma list from: " + ", ".join(sorted(TREATMENTS)))
anchor.add_argument("--out-dir", required=True,
help="concepts subfolder for the frames, relative to the repo root")
anchor.add_argument("--name", default="",
help="output stem (default: derived from the photo filename)")
anchor.add_argument("--model", default=DEFAULT_MODEL)
anchor.add_argument("--request-size", type=int, default=1536)
anchor.add_argument("--reference-unchecked", action="store_true")
anchor.set_defaults(handler=cmd_anchor)
real = sub.add_parser("realism")
real.add_argument("--run", required=True)
real.add_argument("--judge", action="store_true")
+57
View File
@@ -1,4 +1,5 @@
import json
import re
import tempfile
import unittest
from unittest import mock
@@ -245,3 +246,59 @@ class RealismTests(unittest.TestCase):
if __name__ == "__main__":
unittest.main()
class AnchorTests(unittest.TestCase):
ROOT = Path(sc.__file__).resolve().parent.parent
def test_treatment_prompt_carries_context_suffix_and_clause(self):
prompt = sc._anchor_treatment_prompt("A 1920s masonic facade.", "night")
self.assertTrue(prompt.startswith("A 1920s masonic facade."))
self.assertIn("keep the building", prompt.lower())
self.assertIn("Restage the scene:", prompt)
self.assertIn("practical lights", prompt)
def test_unknown_treatment_names_are_rejected(self):
with self.assertRaises(SystemExit):
sc._anchor_treatment_prompt("c", "underwater")
def test_name_derivation_strips_the_capture_date(self):
reference = {"path": self.ROOT / "x" / "some-facade-2009-10-27",
"licence": "CC0", "page": "", "checked": True}
# exercised through cmd_anchor's stem logic via a tiny local copy
import re as _re
stem = _re.sub(r"-[0-9]{4}-[0-9]{2}-[0-9]{2}$", "",
"el-capitan-facade-day-wikimedia-2009-10-27")
self.assertEqual(stem, "el-capitan-facade-day-wikimedia")
def test_cmd_anchor_generates_each_treatment_with_provenance(self):
reference = {"path": self.ROOT / "docs/screens/refs/hollywood/"
"el-capitan-facade-day-wikimedia-2009-10-27.jpg",
"licence": "CC BY 2.0", "page": "https://commons/x",
"checked": True}
class FakeImage:
def convert(self, mode):
return Image.new("RGB", (1600, 900))
with tempfile.TemporaryDirectory() as tmp:
out_dir = Path(tmp) / "frames"
with mock.patch.object(sc, "_reference_source", return_value=reference), \
mock.patch.object(sc.generate_textures, "generate_image",
return_value=FakeImage()) as mocked, \
mock.patch.object(sc, "next_free_path",
side_effect=lambda p: p), \
mock.patch.object(sc, "_rel", side_effect=str):
args = mock.Mock(reference="r", reference_unchecked=False,
context="A facade.", treatments="day,night",
out_dir=str(out_dir), model="m",
request_size=1536)
args.name = "" # `name` is reserved on Mock; set it explicitly
self.assertEqual(sc.cmd_anchor(args), 0)
stems = [c.args[0] for c in mocked.call_args_list]
self.assertEqual(len(stems), 2)
for treatment, call in zip(("day", "night"), mocked.call_args_list):
self.assertIn("Restage the scene:", call.args[0])
self.assertEqual(call.kwargs["reference_path"],
reference["path"])
self.assertTrue((out_dir /
f"el_capitan_facade_day_wikimedia_{treatment}.png")
.exists())