#!/usr/bin/env python3
"""Measure the Chicago skyline in the time-lapse the book links (Joshua Nowicki,
"Time-lapse: Looking toward Chicago from Michigan 2", YouTube FTFEu-Tod7s,
Grand Mere State Park, 30 April 2015).

For each sampled frame: the row of the water horizon, and how many pixels above
it the four tall towers' features stand. Widths are counted per row against the
sky beside each tower, so the silhouette's steps read as features:

  Willis Tower   tier   the 40-px-wide block (the 5- and 7-tube tiers) ends at floor 90
                 roof   the two-tube top tier (about 20 px wide) ends at the roof
                 tip    the masts
  Hancock, Aon, Trump   roof  (344-357 m; Trump's spire and Hancock's masts are too
                        faint against the dusk sky to be measured reliably)

The video is not kept in this repository. Point --src at the downloaded file; the
numbers this produced are in docs/long-path-cases/data/nowicki-2015-04-30-frames.json.

    python3 measure_chicago_frames.py --src /path/to/video.mkv
"""
import argparse
import json
import os
import subprocess
import tempfile

import numpy as np
from PIL import Image

HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, "..", "docs", "long-path-cases", "data", "nowicki-2015-04-30-frames.json")

# tower columns in the 1920x1080 frame, and the width (px) at which each feature begins
COLS = {"willis": (455, 515), "hancock": (1355, 1425), "trump": (880, 960), "aon": (690, 760)}
FEATURES = {"willis": {"tip": 2, "roof": 8, "tier": 30}, "hancock": {"roof": 14}, "trump": {"roof": 20}, "aon": {"roof": 20}}
FRAMES = list(range(8, 31, 2))       # seconds into the clip; the caption is gone by 8 s, the sky dark after 30


def horizon(im):
    band = im[700:950, 1500:1800].mean(1)
    return 700 + int(np.argmin(np.diff(band))) + 1


def widths(im, hz, x0, x1, pad=(60, 20)):
    reg = im[hz - 260:hz, x0:x1]
    beside = np.concatenate([im[hz - 260:hz, x0 - pad[0]:x0 - pad[1]], im[hz - 260:hz, x1 + pad[1]:x1 + pad[0]]], 1)
    sky = np.median(beside, axis=1)
    return ((sky[:, None] - reg) > 7).sum(1)


def first_row_at_least(w, t):
    """px above the horizon of the first row (from the top) where the width is >= t for three rows running"""
    ok = np.convolve((w >= t).astype(int), np.ones(3, int), "valid") >= 3
    return int(260 - np.argmax(ok)) if ok.any() else None


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--src", required=True)
    ap.add_argument("--out", default=OUT)
    a = ap.parse_args()
    rows = []
    with tempfile.TemporaryDirectory() as td:
        subprocess.run(["ffmpeg", "-loglevel", "error", "-i", a.src, "-vf", "fps=1", os.path.join(td, "f%03d.png")], check=True)
        for n in FRAMES:
            im = np.asarray(Image.open(os.path.join(td, f"f{n:03d}.png")).convert("L"), dtype=float)
            hz = horizon(im)
            r = {"second": n, "horizon_row": hz}
            for tower, (x0, x1) in COLS.items():
                w = widths(im, hz, x0, x1)
                for feat, t in FEATURES[tower].items():
                    r[f"{tower}_{feat}"] = first_row_at_least(w, t)
            rows.append(r)
    os.makedirs(os.path.dirname(a.out), exist_ok=True)
    json.dump({"source": "YouTube FTFEu-Tod7s, Joshua Nowicki, 30 April 2015, 1920x1080 as uploaded",
               "units": "pixels above the water horizon", "frames": rows}, open(a.out, "w"), indent=1)
    for r in rows:
        print(r)
    print("wrote", os.path.normpath(a.out))


if __name__ == "__main__":
    main()
