Fix a washed-out AI image with ImageOps.autocontrast before you re-run

A flat, low-contrast result from a Sume image model needs no second paid call. Measure the brightness range, apply autocontrast with a cutoff, and compare.

5 min readSume
All posts

Check the image's brightness range with ImageStat. If it spans far less than 0 to 255, run ImageOps.autocontrast(img, cutoff=1) before paying for another generation. It remaps the darkest 1 percent to black and the lightest 1 percent to white and stretches the rest, which fixes most washed-out results for free.

How to spot a flat image

Look at the extremes, not the average. ImageStat.Stat(gray).extrema returns the minimum and maximum brightness. A healthy photo reaches near 0 and near 255. A foggy result might run from 60 to 210.

Foggy results show up with prompts that ask for soft, hazy, pastel scenes, with some models more than others. A repeat call can return the same haze, so it is worth trying the free fix first. A second call costs the full image price again, for example $0.05 billed on Seedream 4.5.

Measure, fix, compare

The script prints the range before and after, and saves both so you can look at them. Apply the fix per channel (the default for RGB) for a stronger effect, or on a luminance-only copy to avoid color shifts via preserve_tone=True.

import os, io, requests
from PIL import Image
H = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"]}
def gen(**body):
    r = requests.post("https://api.sume.com/v1/images", json=body, headers=H, timeout=60)
    r.raise_for_status()
    if r.status_code == 202:
        raise SystemExit("queued, read /v1/jobs/{id}/result: " + r.text)
    return r.json()
def fetch(u):
    return Image.open(io.BytesIO(requests.get(u, timeout=60).content))
from PIL import ImageOps, ImageStat
out = gen(model="bytedance-seed/seedream-4.5", aspect_ratio="4:3",
          prompt="Misty pine forest at dawn, soft pastel haze, gentle light")
img = fetch(out["data"][0]["url"]).convert("RGB")
lo, hi = ImageStat.Stat(img.convert("L")).extrema[0]
print("range before", lo, hi, "cost", out["usage"]["cost"])
fixed = ImageOps.autocontrast(img, cutoff=1, preserve_tone=True)
lo2, hi2 = ImageStat.Stat(fixed.convert("L")).extrema[0]
print("range after", lo2, hi2)
img.save("before.png")
fixed.save("after.png")

Which setting to pick

Cutoff sets how aggressively extremes are clipped. Preserving tone avoids hue shifts but gives a gentler change.

autocontrast settings, arguments read 2026-10-05
SettingEffectUse for
cutoff=0Uses the true min and maxClean images with one hot pixel risk
cutoff=1Ignores 1 percent each endDefault for most results
cutoff=3Strong stretchVery hazy scenes
preserve_tone=TrueStretches luminance onlyWhen hue must not shift

Limits

  • It cannot add detail the model did not draw. A hazy image will look crisper, not sharper.
  • Strong stretching can band smooth skies. Add light noise afterward if that shows.
  • Brand colors may drift. Run your color check after the fix.
  • If the whole series is flat, change the prompt or the model instead of fixing each file.

Make it routine

Put the extrema check in your pipeline and fix only images below a threshold such as a range under 200. Then confirm the brand colors with the delta E check, keep the fix inside your regression test so changes are visible, and keep lossless files between steps as in PNG between passes.

Sources

Related posts

More in Media tools

All Media tools posts

Written by Sume