Headphone and earbud product photos: logo and orientation QC

Make headphone or earbud lifestyle shots with Sume's image API, then catch flipped logos, wrong earcup sides and cable errors with a Pillow contact sheet.

4 min readSume
All posts

Headphones are symmetric until they are not: a left cup carries an L, a logo faces outward, and a cable leaves one side. Generated photos often flip such details. A review step is worth more than a longer prompt.

Generate with Sume's Image API, using one clean product photo as a reference, and review a grid of variants at once.

Generate several in one request

Sume's n parameter returns up to 10 images per call, with lower limits on some models. Ask for four scenes of the same headphones: desk, commute, gym, sofa. Keep the prompt strict about what must match the reference.

import os, requests

H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
resp = requests.post("https://api.sume.com/v1/images", headers=H, timeout=90, json={
    "model": "openai/gpt-image-2.5",
    "prompt": "Photograph these exact over-ear headphones worn by a person at a desk, soft daylight. Keep the earcup shape, the logo on the outside of the left cup, and the headband as in the reference.",
    "input_references": [
        {"type": "image_url", "image_url": {"url": "https://example.com/headphones.jpg"}}
    ],
    "aspect_ratio": "auto",
    "n": 4,
})
resp.raise_for_status()
if resp.status_code == 202:
    raise SystemExit("job envelope: poll data.status_url")
body = resp.json()
print([d["url"] for d in body["data"]], body.get("usage"))

A contact sheet for review

Paste the variants beside the reference so a reviewer sees all five at once and judges against the original. The script uses the data[].url values from the response.

import io, requests
from PIL import Image

urls = ["https://example.com/ref.jpg", "https://example.com/v1.png",
        "https://example.com/v2.png", "https://example.com/v3.png"]
tiles = []
for u in urls:
    r = requests.get(u, timeout=60)
    r.raise_for_status()
    tiles.append(Image.open(io.BytesIO(r.content)).convert("RGB").resize((400, 400)))
sheet = Image.new("RGB", (400 * len(tiles), 400), "white")
for i, t in enumerate(tiles):
    sheet.paste(t, (400 * i, 0))
sheet.save("contact-sheet.jpg")
print("saved", sheet.size)

Checklist

  • The logo is on the same cup, facing the same way, as on the reference.
  • L and R marks, if visible, are on the correct sides.
  • The cable leaves the same earcup and does not appear twice.
  • Earbud pairs show two matched buds, not one and a half.

Reject variants quickly and keep the cost log: usage.cost comes back with each response, so a rejected batch of four has a known price before you decide whether to ask again.

Before you run a catalog

Run one SKU end to end first. POST /v1/images returns the images directly when it finishes within 30 seconds; past that it returns a 202 envelope with status_url and result_url, which the jobs and results guide explains. Handle that branch before you loop over a catalog, and write each result's URL and usage.cost to a file keyed by SKU, so a failed run restarts where it stopped and nothing is paid for twice.

Sources

Related posts

More in Use cases

All Use cases posts

Written by Sume