Jupyter contact sheet: one prompt across four Sume image models
A notebook cell that sends one prompt to four Sume image models, tiles the results into a labeled contact sheet with Pillow, and prints each billed cost.

To compare image models fairly, send the same prompt, the same aspect_ratio and n: 1 to each model on Sume's POST /v1/images, download each result, paste them into one labeled Pillow sheet, and print each usage.cost beside it. One notebook cell does it, and the sheet is what you show a teammate when the question is which model to pin.
Keep the comparison honest by changing nothing but model. Different models accept different parameters, so the cell sends only model, prompt, n and aspect_ratio, and a model that rejects one of them returns a 400 you can see in the output.
Which four models make a useful first sheet?
Ids are the public org/slug ids from the Sume image page; list prices are the catalog list rate before Sume's 1.25 multiplier.
| Model id | Catalog list price per image | Why include it |
|---|---|---|
google/nano-banana-2 | $0.08 at the 1K default | Fast general model |
bytedance-seed/seedream-5-lite | $0.035 | Low-cost Seedream |
ideogram/ideogram-v4.5 | $0.06 at the default medium quality | Text-heavy layouts |
openai/gpt-image-2.5 | Token-based; high at 1024 square is the listed rate | Instruction following and edits |
What is the notebook cell?
Run it once; it prints the status of any model that fails so one error does not blank the whole sheet.
import io, os, requests
from PIL import Image, ImageDraw
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
MODELS = ["google/nano-banana-2", "bytedance-seed/seedream-5-lite",
"ideogram/ideogram-v4.5", "openai/gpt-image-2.5"]
PROMPT = "a ceramic teapot on a wooden table, soft window light, product photo"
tiles = []
for m in MODELS:
r = requests.post("https://api.sume.com/v1/images", headers=H, timeout=90,
json={"model": m, "prompt": PROMPT, "n": 1,
"aspect_ratio": "1:1"})
if r.status_code != 200:
print(m, r.status_code, r.text[:120]); continue
b = r.json()
im = Image.open(io.BytesIO(requests.get(b["data"][0]["url"]).content))
im = im.convert("RGB").resize((384, 384))
ImageDraw.Draw(im).text((8, 8), f"{m} ${b['usage']['cost']}", fill="white")
tiles.append(im)
sheet = Image.new("RGB", (384 * len(tiles), 384))
for i, t in enumerate(tiles):
sheet.paste(t, (i * 384, 0))
sheetHow do I read the sheet?
Judge on the property you care about and write it down before you look: text spelled correctly, product shape kept, lighting plausible. Run the same cell three times with different prompts, because a single sample says little. Cost is on the tile, so a cheaper model that passes your check wins automatically.
Ratios matter. 1:1 is accepted widely, but the catalog is per-model, and a ratio a model does not list returns 400. Read each model's aspect_ratio descriptor from GET /v1/images/models before adding a ratio such as 4:5.
What does this cost to run?
At the list prices above times 1.25, one pass of the four models is a few tens of cents, with the ChatGPT Image 2.5 tile depending on quality and size. Failed generations are not billed, and a completed one is billed in full, so re-running a cell re-bills every tile that succeeds.
Sources
Related posts
More in Developers
- Keep your Sora-style create_video() call: map it onto Sume
Sora's seconds, size and input_reference become duration, resolution plus aspect_ratio, and a first frame. Here is that map as a Python wrapper over Sume.
- Kestra webhook trigger has no HMAC: verify Sume deliveries first
Kestra's Webhook trigger is protected by its key alone. Verify Sume's sume-v1 signature in a thin relay, then start the flow, and submit with a stable key.
- Kling 3 on Sume rejects reference_*_urls: which model takes references
The kling-3 row supports text-to-video and start/end frames only. Send reference_image_urls and you get a 400; here is where references do work.
- Koa receiver for Sume webhooks: verify with koa-bodyparser rawBody
koa-bodyparser keeps the raw string on ctx.request.rawBody. Check the sume-v1 HMAC against it, not against a re-stringified ctx.request.body, then answer 204.
Written by Sume