SDK-Streamlit / scripts /run_batch_meal_macro_demo.py
Gilgarmesh's picture
Upload 22 files
e0e2b27 verified
Raw
History Blame Contribute Delete
3.87 kB
"""Run the end-to-end macro demo for a small batch of meal images.
Example:
python scripts/run_batch_meal_macro_demo.py --image-glob "data/processed/foodseg103_target_yolo/images/val/*.jpg" --limit 5
"""
from __future__ import annotations
import argparse
import csv
import glob
import json
import os
import subprocess
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[1]
DEFAULT_OUTPUT_DIR = PROJECT_ROOT / "output" / "demo"
DEFAULT_SUMMARY_CSV = PROJECT_ROOT / "output" / "batch_summary.csv"
def flatten_summary(summary_path: Path) -> dict[str, object]:
"""Flatten one demo summary.json into a single CSV-friendly row."""
summary = json.loads(Path(summary_path).read_text(encoding="utf-8"))
row: dict[str, object] = {
"image": summary.get("image"),
"summary_path": str(summary_path),
}
segments = (summary.get("segments") or {}).get("segments") or []
for segment in segments:
name = segment.get("class_name")
if name:
row[f"{name}_area_fraction"] = segment.get("area_fraction")
gemini = summary.get("gemini_analysis") or {}
if gemini.get("meal_summary") is not None:
row["meal_summary"] = gemini.get("meal_summary")
for component in gemini.get("components") or []:
name = component.get("class_name")
if name:
row[f"{name}_likely_food"] = component.get("likely_food")
row[f"{name}_fdc_query"] = component.get("fdc_query")
totals = (summary.get("macro_estimate") or {}).get("totals") or {}
for key in ("grams", "kcal", "protein", "fat", "carbs"):
if key in totals:
row[key] = totals[key]
return row
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--image-glob", required=True)
parser.add_argument("--limit", type=int, default=5)
parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR)
parser.add_argument("--summary-csv", type=Path, default=DEFAULT_SUMMARY_CSV)
parser.add_argument("--use-gemini", action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--use-usda", action=argparse.BooleanOptionalAction, default=True)
return parser.parse_args()
def run_one(image_path: Path, args: argparse.Namespace, env: dict[str, str]) -> Path:
command = [
sys.executable,
"scripts/run_meal_macro_demo.py",
"--image",
str(image_path),
"--output-dir",
str(args.output_dir),
]
if not args.use_gemini:
command.append("--no-use-gemini")
if not args.use_usda:
command.append("--no-use-usda")
print(f"+ {' '.join(command)}", flush=True)
subprocess.run(command, cwd=PROJECT_ROOT, env=env, check=True)
return args.output_dir / image_path.stem / "summary.json"
def main() -> None:
args = parse_args()
image_paths = [Path(path) for path in sorted(glob.glob(args.image_glob))]
if args.limit is not None:
image_paths = image_paths[: args.limit]
if not image_paths:
raise SystemExit(f"No images matched: {args.image_glob}")
env = os.environ.copy()
rows = []
for index, image_path in enumerate(image_paths, start=1):
print(f"\n[{index}/{len(image_paths)}] {image_path}", flush=True)
summary_path = run_one(image_path, args, env)
rows.append(flatten_summary(summary_path))
fieldnames = sorted({key for row in rows for key in row})
args.summary_csv.parent.mkdir(parents=True, exist_ok=True)
with args.summary_csv.open("w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)
print(f"Wrote batch summary: {args.summary_csv}")
if __name__ == "__main__":
main()