← back to Corkwallcovering

scripts/crop_logo_header.py

78 lines

#!/usr/bin/env python3
"""
Detect & crop the PHILLIPE ROMANO logo-header banner off the TOP of cork product
images (corkwallcovering.com).

The banner is a solid BLACK horizontal band flush to the top of the image,
carrying the white/gold wordmark "PHILLIPE ROMANO / WALLPAPER - FABRICS -
LOS ANGELES". It is visually unmistakable: the very top rows are ~100% black
pixels (text sits inside the black field), and the band ends with a SHARP
transition to bright cork texture.

Signal (per-row, top region only):
  dark_frac[r] = fraction of pixels in row r that are near-black (gray < DARK_T).
  - A banner requires the very top to be a near-solid black band
    (mean dark_frac over the first few rows >= TOP_REQ).
  - The band bottom = first row, after the band, where dark_frac collapses to
    ~0 (cork texture) and STAYS low for a sustained run.
Safety:
  - Band must be within [MIN_FRAC, MAX_FRAC] of height, else NO crop.
  - If the top is not a solid black band, NO crop (image left untouched).
  - Crop-only, never upscale. Add a tiny SAFETY_PAD downward so we never clip
    into the product (errs toward leaving a sliver of band rather than product).
"""
import sys, numpy as np
from PIL import Image

DARK_T     = 60     # pixel is "dark" if gray < this
TOP_REQ    = 0.85   # first TOP_ROWS must average >= this dark_frac to be a banner
TOP_ROWS_F = 0.015  # fraction of height used as the "very top" probe (min 3 rows)
LOW_T      = 0.15   # dark_frac considered "product texture" below this
RUN        = 10     # sustained low rows required to confirm band end
MIN_FRAC   = 0.04
MAX_FRAC   = 0.30
SAFETY_PAD = 0      # extra rows kept below boundary (transition row is product)

def detect_band(path):
    im = Image.open(path).convert('RGB')
    a = np.asarray(im).astype(float)
    h, w, _ = a.shape
    gray = a.mean(axis=2)
    df = (gray < DARK_T).mean(axis=1)

    top_rows = max(3, int(h * TOP_ROWS_F))
    if df[:top_rows].mean() < TOP_REQ:
        return None, dict(reason='top is not a solid black banner',
                          top_darkfrac=round(float(df[:top_rows].mean()), 3))

    # Walk down to find sustained product texture (band end).
    boundary = None
    limit = int(h * MAX_FRAC) + 1
    for r in range(top_rows, limit):
        if df[r] < LOW_T and (df[r:r+RUN] < LOW_T).all():
            boundary = r
            break
    if boundary is None:
        return None, dict(reason='no sharp band->texture transition within max_frac',
                          top_darkfrac=round(float(df[:top_rows].mean()), 3))

    boundary = min(boundary + SAFETY_PAD, int(h * MAX_FRAC))
    frac = boundary / h
    if not (MIN_FRAC <= frac <= MAX_FRAC):
        return None, dict(reason=f'band {frac:.2%} out of range', boundary=boundary)
    return boundary, dict(frac=round(frac, 4),
                          top_darkfrac=round(float(df[:top_rows].mean()), 3))

def crop(path, out, boundary):
    im = Image.open(path)
    w, h = im.size
    im.crop((0, boundary, w, h)).save(out, quality=92)

if __name__ == '__main__':
    p = sys.argv[1]
    b, info = detect_band(p)
    print(p, '-> BAND row', b, info)
    if b and len(sys.argv) > 2:
        crop(p, sys.argv[2], b)
        print('cropped ->', sys.argv[2])