Build a contact sheet from one image call with n variants in Python

Ask the Image API for several variants with n, then tile them into one sheet with Pillow so a reviewer can pick in one glance. Handles 200 versus 202.

4 min readSume
All posts

To review several ad images at once, send one POST /v1/images with n set to the number of variants, download the returned URLs, and paste them onto one canvas with Pillow. The script below does that in about twenty lines and saves a single JPEG you can open or send to a reviewer.

The Image API docs say n accepts 1 to 10 per call, but each model has its own ceiling, so read the n range from the catalog before you pick a number.

Why use n instead of several calls?

One request is one place to read the cost and one set of URLs to name. The response carries usage.cost, the amount billed to your wallet in USD, so a four-image sheet is one number to log. Separate calls give you separate failures to track.

What does the script look like?

It checks the status code first. A 200 is the image response; a 202 means the generation outlived the 30-second wait and returned a job envelope, which you read from the job result endpoint instead.

import io, os, requests
from PIL import Image, ImageOps

r = requests.post(
    "https://api.sume.com/v1/images",
    headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"},
    json={"model": "openai/gpt-image-2", "n": 4,
          "prompt": "Studio photo of a stainless water bottle on a desk"},
    timeout=60,
)
if r.status_code != 200:
    raise SystemExit(f"{r.status_code}: poll the job instead: {r.text[:200]}")
body = r.json()
tiles = []
for item in body["data"]:
    raw = requests.get(item["url"], timeout=60).content
    img = Image.open(io.BytesIO(raw)).convert("RGB")
    tiles.append(ImageOps.pad(img, (400, 400), color="white"))
sheet = Image.new("RGB", (400 * len(tiles), 400), "white")
for i, tile in enumerate(tiles):
    sheet.paste(tile, (i * 400, 0))
sheet.save("sheet.jpg", quality=88)
print(len(tiles), "tiles, billed", body["usage"]["cost"])

What happens on a 202?

The job id sits at data.job.id. Poll GET /v1/jobs/{id}/status, then fetch GET /v1/jobs/{id}/result for the images, as the jobs docs describe. Slow settings, such as 4K, high quality or a large n, are the ones most likely to take that path.

How do I choose the winner?

Number the tiles in the same order as data, write the pick next to the job id, and keep the sheet with the record. That way the winning URL is traceable later.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume