Add reference-anchored concept generation
This commit is contained in:
1 parent
5b306b1c52
commit
e21ff30783
8 files changed
+305
-31
No files matched your search
+85
-5
@@ -323,30 +323,104 @@ def compose_prompt(name: str) -> str:
|
||||
return f"{prompt}. {style}."
|
||||
|
||||
|
||||
def generate_one(name: str, model: str, request_size: int) -> Path:
|
||||
REFERENCE_PROMPT_SUFFIX = (
|
||||
"Use the attached reference photograph as the fixed source of geometry and "
|
||||
"composition: keep the building's proportions, column rhythm, band heights, "
|
||||
"signage placement and camera angle exactly as photographed. Only restage "
|
||||
"lighting, weather, people and surface condition where the prompt asks for it. "
|
||||
"Do not invent extra storeys, wings or ornaments that the photo does not show."
|
||||
)
|
||||
|
||||
REFS_DIR = ROOT / "docs" / "screens" / "refs"
|
||||
|
||||
|
||||
def _reference_source(path_str: str, allow_unchecked: bool = False) -> dict:
|
||||
"""Resolve and licence-check a reference photo for anchored generation.
|
||||
|
||||
A file under `docs/screens/refs/` must already be listed in that venue's
|
||||
`SIDECAR.md` (the licence gate); its licence and Commons page are returned
|
||||
for provenance. Anything else requires the explicit `--reference-unchecked`
|
||||
override, which keeps unreviewed material out of the committed pipeline by
|
||||
default.
|
||||
"""
|
||||
path = Path(path_str).expanduser().resolve()
|
||||
if not path.is_file():
|
||||
sys.exit(f"error: reference photo not found: {path}")
|
||||
if REFS_DIR in path.parents:
|
||||
sidecar = path.parent / "SIDECAR.md"
|
||||
text = sidecar.read_text(encoding="utf-8") if sidecar.is_file() else ""
|
||||
row = next((line for line in text.splitlines()
|
||||
if f"`{path.name}`" in line), None)
|
||||
if not row:
|
||||
sys.exit(f"error: {path.name} is not listed in {sidecar}; add the "
|
||||
"sidecar row (licence gate) before anchoring generation on it")
|
||||
cells = [c.strip() for c in row.split("|")]
|
||||
licence = next((c for c in cells if c and c.lower().startswith(("cc", "public"))), "?")
|
||||
page = next((c.strip("<> ") for c in cells if c.startswith("<http")), "")
|
||||
return {"path": path, "licence": licence, "page": page, "checked": True}
|
||||
if not allow_unchecked:
|
||||
sys.exit(f"error: {path} is outside docs/screens/refs/ and not licence-checked; "
|
||||
"move it into a refs board with a SIDECAR row, or pass "
|
||||
"--reference-unchecked to accept the licence responsibility")
|
||||
return {"path": path, "licence": "unchecked", "page": "", "checked": False}
|
||||
|
||||
|
||||
def _write_reference_provenance(out_path: Path, reference: dict, name: str,
|
||||
model: str) -> Path:
|
||||
"""Record which photo anchored a generated frame (`.refgen.json` sidecar)."""
|
||||
sidecar = out_path.with_suffix(".refgen.json")
|
||||
sidecar.write_text(json.dumps({
|
||||
"concept": name,
|
||||
"generated": out_path.name,
|
||||
"model": model,
|
||||
"reference": {"file": str(reference["path"].relative_to(ROOT))
|
||||
if reference["path"].is_relative_to(ROOT) else str(reference["path"]),
|
||||
"licence": reference["licence"],
|
||||
"page": reference["page"],
|
||||
"licence_checked": reference["checked"]},
|
||||
"suffix": REFERENCE_PROMPT_SUFFIX,
|
||||
}, indent=2) + "\n", encoding="utf-8")
|
||||
return sidecar
|
||||
|
||||
|
||||
def generate_one(name: str, model: str, request_size: int,
|
||||
reference: dict | None = None) -> Path:
|
||||
group = CATALOGUE[name][0]
|
||||
prompt = compose_prompt(name)
|
||||
if reference:
|
||||
prompt = f"{prompt}\n\n{REFERENCE_PROMPT_SUFFIX}"
|
||||
if group == "scene":
|
||||
image = generate_textures.generate_image(prompt, model, request_size,
|
||||
transparent=False, aspect_ratio="3:2")
|
||||
transparent=False, aspect_ratio="3:2",
|
||||
reference_path=reference["path"]
|
||||
if reference else None)
|
||||
image = ImageOps.fit(image.convert("RGB"), (SCENE_W, SCENE_H),
|
||||
method=Image.Resampling.LANCZOS)
|
||||
elif group == "backdrop":
|
||||
image = generate_textures.generate_image(prompt, model, request_size,
|
||||
transparent=True, aspect_ratio="3:2")
|
||||
transparent=True, aspect_ratio="3:2",
|
||||
reference_path=reference["path"]
|
||||
if reference else None)
|
||||
else:
|
||||
image = generate_textures.generate_image(prompt, model, request_size,
|
||||
transparent=False, aspect_ratio="1:1")
|
||||
transparent=False, aspect_ratio="1:1",
|
||||
reference_path=reference["path"]
|
||||
if reference else None)
|
||||
image = image.convert("RGB")
|
||||
out = next_free_path(raw_path(name))
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
image.save(out)
|
||||
if reference:
|
||||
provenance = _write_reference_provenance(out, reference, name, model)
|
||||
print(f"provenance {_rel(provenance)} <- {reference['licence']}")
|
||||
print(f"wrote {_rel(out)} {image.size}")
|
||||
return out
|
||||
|
||||
|
||||
def cmd_generate(args) -> int:
|
||||
_load_env_file()
|
||||
reference = _reference_source(args.reference, args.reference_unchecked) \
|
||||
if args.reference else None
|
||||
names = _select(args.group, args.only)
|
||||
if not names:
|
||||
print("nothing selected", file=sys.stderr)
|
||||
@@ -356,7 +430,7 @@ def cmd_generate(args) -> int:
|
||||
for _ in range(max(1, args.variants)):
|
||||
print(f"generating {name} ...", flush=True)
|
||||
try:
|
||||
generate_one(name, args.model, args.request_size)
|
||||
generate_one(name, args.model, args.request_size, reference=reference)
|
||||
except SystemExit as exc: # generate_textures exits on API errors
|
||||
failed.append(name)
|
||||
print(f" FAILED {name}: {exc}", file=sys.stderr)
|
||||
@@ -1032,6 +1106,12 @@ def build_parser():
|
||||
gen.add_argument("--variants", type=int, default=1)
|
||||
gen.add_argument("--model", default=DEFAULT_MODEL)
|
||||
gen.add_argument("--request-size", type=int, default=1536)
|
||||
gen.add_argument("--reference", default="",
|
||||
help="anchor generation on a licence-cleared reference photo "
|
||||
"(must be listed in its refs-board SIDECAR.md)")
|
||||
gen.add_argument("--reference-unchecked", action="store_true",
|
||||
help="allow a reference photo from outside docs/screens/refs/ "
|
||||
"(you take responsibility for its licence)")
|
||||
gen.set_defaults(handler=cmd_generate)
|
||||
proc = sub.add_parser("process")
|
||||
proc.add_argument("--group", choices=("backdrop", "texture"))
|
||||
|
||||
Reference in new issue
Block a user