chore(sprite-generation): 🔧 Update sprite generation CLI/server logic and database configuration
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
516994ba35
commit
c07d07a2ab
5 changed files with 178 additions and 4 deletions
|
|
@ -26,9 +26,9 @@ RAW_DIR = TOOL_DIR / "raw"
|
|||
VARIANTS_DIR = TOOL_DIR / "variants"
|
||||
HEX_MASK_PATH = TOOL_DIR / "hex_mask.png"
|
||||
|
||||
LOCAL_DATA = PROJECT / "games" / "age-of-four" / "data"
|
||||
LOCAL_DATA = PROJECT / "games" / "age-of-dwarves" / "data"
|
||||
DEMO_DATA = TOOL_DIR / "demo-data"
|
||||
ASSETS_DIR = PROJECT / "games" / "age-of-four" / "assets"
|
||||
ASSETS_DIR = PROJECT / "games" / "age-of-dwarves" / "assets"
|
||||
|
||||
|
||||
def _data_dir(args: argparse.Namespace) -> Path:
|
||||
|
|
@ -129,10 +129,13 @@ def cmd_generate(args: argparse.Namespace) -> None:
|
|||
print(f" ... and {len(sprite_ids) - 50} more")
|
||||
return
|
||||
|
||||
pose_ref = Path(args.pose_ref) if args.pose_ref else None
|
||||
completed = asyncio.run(gen.generate_batch(
|
||||
sprite_ids=sprite_ids,
|
||||
variants_per=args.variants,
|
||||
priority=args.priority,
|
||||
pose_reference=pose_ref,
|
||||
img2img_strength=args.strength,
|
||||
))
|
||||
print(f"\nGenerated {completed} images. Run 'rank' to score them.")
|
||||
|
||||
|
|
@ -169,6 +172,24 @@ def cmd_install(args: argparse.Namespace) -> None:
|
|||
print(f"\n{'Would install' if args.dry_run else 'Installed'}: {count} sprites")
|
||||
|
||||
|
||||
def cmd_approve(args: argparse.Namespace) -> None:
|
||||
from engine.pipeline import SpritePipeline
|
||||
|
||||
reg = _registry()
|
||||
pipeline = SpritePipeline(
|
||||
registry=reg,
|
||||
raw_dir=RAW_DIR,
|
||||
variants_dir=VARIANTS_DIR,
|
||||
assets_dir=ASSETS_DIR,
|
||||
game_db_path=LOCAL_DATA / "sprites.db",
|
||||
)
|
||||
result = pipeline.approve_and_install(args.variant, alt_name=args.alt)
|
||||
if result:
|
||||
print(f"\nShipped: {result}")
|
||||
else:
|
||||
print("\nFailed to install.")
|
||||
|
||||
|
||||
def cmd_reset(args: argparse.Namespace) -> None:
|
||||
reg = _registry()
|
||||
if not args.sprite:
|
||||
|
|
@ -195,6 +216,136 @@ def cmd_review(args: argparse.Namespace) -> None:
|
|||
print("Install dependencies: pip install fastapi uvicorn")
|
||||
|
||||
|
||||
def cmd_run(args: argparse.Namespace) -> None:
|
||||
"""Orchestrate the full generate → rank → regen loop continuously.
|
||||
|
||||
Runs until all sprites are in review (ranked) or installed.
|
||||
The human reviews and approves via the GUI at localhost:5850.
|
||||
"""
|
||||
import asyncio
|
||||
import time
|
||||
from engine.generator import SpriteGenerator
|
||||
from engine.ranker import SpriteRanker
|
||||
|
||||
reg = _registry()
|
||||
config = _load_config()
|
||||
gen = SpriteGenerator(config=config, registry=reg, raw_dir=RAW_DIR)
|
||||
ranker = SpriteRanker(registry=reg, raw_dir=RAW_DIR)
|
||||
|
||||
pose_ref = Path(args.pose_ref) if args.pose_ref else None
|
||||
batch_size = args.batch or 4
|
||||
variants_per = args.variants
|
||||
|
||||
# Start GUI server in background thread
|
||||
import threading
|
||||
def _serve():
|
||||
from server import create_app
|
||||
import uvicorn
|
||||
app = create_app(registry=reg, raw_dir=RAW_DIR, variants_dir=VARIANTS_DIR)
|
||||
uvicorn.run(app, host="0.0.0.0", port=args.port, log_level="warning")
|
||||
|
||||
server_thread = threading.Thread(target=_serve, daemon=True)
|
||||
server_thread.start()
|
||||
print(f"Review GUI: http://localhost:{args.port}/?spriteTheater=true")
|
||||
|
||||
loop_count = 0
|
||||
while True:
|
||||
loop_count += 1
|
||||
|
||||
# --- Phase 1: Generate ---
|
||||
needed = reg.get_sprites(
|
||||
category=args.category,
|
||||
status="needed",
|
||||
limit=batch_size,
|
||||
)
|
||||
|
||||
if needed:
|
||||
sprite_ids = [s["id"] for s in needed]
|
||||
print(f"\n[loop {loop_count}] Generating {len(sprite_ids)} sprites × {variants_per} variants...")
|
||||
completed = asyncio.run(gen.generate_batch(
|
||||
sprite_ids=sprite_ids,
|
||||
variants_per=variants_per,
|
||||
priority="high",
|
||||
pose_reference=pose_ref,
|
||||
img2img_strength=args.strength,
|
||||
))
|
||||
print(f" Generated {completed} images")
|
||||
|
||||
# --- Phase 2: Rank unranked review sprites ---
|
||||
unranked = reg.conn.execute("""
|
||||
SELECT DISTINCT s.id FROM sprites s
|
||||
WHERE s.status = 'review'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM variants v
|
||||
WHERE v.sprite_id = s.id
|
||||
AND v.job_status = 'completed'
|
||||
AND v.notes IS NOT NULL
|
||||
)
|
||||
LIMIT ?
|
||||
""", (batch_size,)).fetchall()
|
||||
|
||||
if unranked:
|
||||
print(f"\n[loop {loop_count}] Ranking {len(unranked)} unranked sprites...")
|
||||
for row in unranked:
|
||||
sprite_id = row["id"]
|
||||
result = asyncio.run(ranker.rank_and_filter(sprite_id))
|
||||
good = result["good_count"]
|
||||
total = len(result["ranked"])
|
||||
if result["needs_regen"]:
|
||||
reg.update_sprite_status(sprite_id, "needed")
|
||||
print(f" {sprite_id}: {good}/{total} good — needs regen, reset to needed")
|
||||
else:
|
||||
print(f" {sprite_id}: {good}/{total} good — ready for review")
|
||||
|
||||
# --- Phase 3: Re-rank previously ranked but deficient sprites ---
|
||||
# (sprites in review that HAVE been ranked but didn't pass)
|
||||
deficient = reg.conn.execute("""
|
||||
SELECT DISTINCT s.id FROM sprites s
|
||||
WHERE s.status = 'review'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM variants v
|
||||
WHERE v.sprite_id = s.id
|
||||
AND v.job_status = 'completed'
|
||||
AND v.notes IS NOT NULL
|
||||
)
|
||||
LIMIT ?
|
||||
""", (batch_size,)).fetchall()
|
||||
|
||||
# Only re-check deficient if nothing else to do
|
||||
if not needed and not unranked and deficient:
|
||||
print(f"\n[loop {loop_count}] Re-checking {len(deficient)} ranked sprites...")
|
||||
for row in deficient:
|
||||
sprite_id = row["id"]
|
||||
result = asyncio.run(ranker.rank_and_filter(sprite_id))
|
||||
if result["needs_regen"]:
|
||||
reg.update_sprite_status(sprite_id, "needed")
|
||||
print(f" {sprite_id}: still deficient, reset to needed")
|
||||
|
||||
# --- Status ---
|
||||
stats = reg.get_stats()
|
||||
total_row = stats["total"]
|
||||
needed_count = total_row.get("needed", 0)
|
||||
review_count = total_row.get("review", 0)
|
||||
approved_count = total_row.get("approved", 0)
|
||||
installed_count = total_row.get("installed", 0)
|
||||
total_count = sum(total_row.values())
|
||||
|
||||
done_count = approved_count + installed_count
|
||||
print(f"\n[loop {loop_count}] Status: {needed_count} needed, {review_count} review, {done_count} done / {total_count} total")
|
||||
|
||||
if needed_count == 0 and review_count == 0:
|
||||
print("\nAll sprites processed. Waiting for new work...")
|
||||
|
||||
# --- Pause between loops ---
|
||||
pause = 10 if needed else 30
|
||||
print(f" Next loop in {pause}s... (Ctrl+C to stop)")
|
||||
try:
|
||||
time.sleep(pause)
|
||||
except KeyboardInterrupt:
|
||||
print("\nStopping pipeline.")
|
||||
break
|
||||
|
||||
|
||||
def cmd_export(args: argparse.Namespace) -> None:
|
||||
import csv
|
||||
import io
|
||||
|
|
@ -223,7 +374,17 @@ def main() -> None:
|
|||
parser.add_argument("--demo", action="store_true", help="Use minimal demo data (1 per domain)")
|
||||
sub = parser.add_subparsers(dest="command")
|
||||
|
||||
# start (default) — launch GUI server
|
||||
# run — full pipeline orchestrator
|
||||
p = sub.add_parser("run", help="Run full pipeline: generate → rank → regen loop + GUI")
|
||||
p.add_argument("--port", type=int, default=5850, help="GUI server port (default: 5850)")
|
||||
p.add_argument("--category", type=str, help="Only process one category")
|
||||
p.add_argument("--variants", type=int, default=4, help="Variants per sprite (default: 4)")
|
||||
p.add_argument("--batch", type=int, default=4, help="Sprites per batch (default: 4)")
|
||||
p.add_argument("--pose-ref", type=str, help="Pose reference image for img2img")
|
||||
p.add_argument("--strength", type=float, default=0.6, help="img2img strength (default: 0.6)")
|
||||
p.set_defaults(func=cmd_run)
|
||||
|
||||
# start — launch GUI server only
|
||||
p = sub.add_parser("start", help="Launch review GUI server")
|
||||
p.add_argument("--port", type=int, default=5850, help="Server port (default: 5850)")
|
||||
p.set_defaults(func=cmd_review)
|
||||
|
|
@ -244,6 +405,8 @@ def main() -> None:
|
|||
p.add_argument("--priority", choices=["low", "normal", "high"], default="normal")
|
||||
p.add_argument("--max", type=int, help="Max sprites to generate")
|
||||
p.add_argument("--dry-run", action="store_true", help="Show what would be generated")
|
||||
p.add_argument("--pose-ref", type=str, help="Path to pose reference image for img2img (consistent facing)")
|
||||
p.add_argument("--strength", type=float, default=0.75, help="img2img denoising strength (0=copy, 1=ignore ref)")
|
||||
p.set_defaults(func=cmd_generate)
|
||||
|
||||
# rank
|
||||
|
|
@ -258,6 +421,12 @@ def main() -> None:
|
|||
p.add_argument("--dry-run", action="store_true")
|
||||
p.set_defaults(func=cmd_install)
|
||||
|
||||
# approve
|
||||
p = sub.add_parser("approve", help="Approve variant → process → install → manifest")
|
||||
p.add_argument("variant", type=int, help="Variant ID to approve (e.g. 129)")
|
||||
p.add_argument("--alt", type=str, help="Install as alternate (e.g. --alt front)")
|
||||
p.set_defaults(func=cmd_approve)
|
||||
|
||||
# reset
|
||||
p = sub.add_parser("reset", help="Reset sprite back to needed status")
|
||||
p.add_argument("--sprite", type=str, required=True, help="Sprite ID to reset")
|
||||
|
|
|
|||
|
|
@ -130,6 +130,11 @@ def create_app(
|
|||
registry.update_sprite_status(sprite_id, "skip")
|
||||
return {"status": "skip"}
|
||||
|
||||
@app.post("/api/variants/{variant_id:int}/reject")
|
||||
def reject_variant(variant_id: int) -> dict:
|
||||
registry.reject_variant(variant_id)
|
||||
return {"status": "rejected", "variant_id": variant_id}
|
||||
|
||||
@app.post("/api/sprites/{sprite_id:path}/regenerate")
|
||||
def regenerate_sprite(sprite_id: str, body: RegenerateRequest) -> dict:
|
||||
sprite = registry.get_sprite(sprite_id)
|
||||
|
|
@ -182,7 +187,7 @@ def create_app(
|
|||
|
||||
@app.get("/api/variants/recent")
|
||||
def get_recent_variants(
|
||||
limit: Annotated[int, Query(ge=1, le=100)] = 30,
|
||||
limit: Annotated[int, Query(ge=1, le=5000)] = 30,
|
||||
) -> list[dict]:
|
||||
return registry.get_recent_variants(limit=limit)
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Add table
Reference in a new issue