#!/usr/bin/env python3 """ Rebuild the folded CLK+ eye diagram for the v2 report. The original plot_eye() in make_flicker_report.py looks for an LP-11 → HS transition (CLK+ > 0.5 V then falling). In session 20260515_135656 the captures landed entirely in HS state (CLK+ stays in ~0.07–0.36 V), so the edge detector returned None for every segment and the plot rendered with zero overlays. This script auto-detects the common mode per segment and folds around every crossing of common mode — which is what the eye really wants. """ from __future__ import annotations from pathlib import Path import numpy as np import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt ARRIVE_PURPLE = "#5f016f" ARRIVE_PURPLE_DARK = "#3e0049" SESSION = Path("data/flicker_bursts/20260515_135656") BURST = 15 N_SEGS = 20 UI_NS = 2.315 OUT = Path("flicker_investigation_report_v2_plots/mipi_typical_eye.png") def fold_segment(t_ns: np.ndarray, v_mv: np.ndarray, ui_ns: float, ax: plt.Axes) -> int: """Overlay every common-mode crossing in this segment as a ±1 UI slice.""" cm = float(np.median(v_mv)) above = (v_mv > cm).astype(int) edges = np.where(np.diff(above) != 0)[0] n = 0 for e in edges: t_cross = t_ns[e] mask = (t_ns >= t_cross - ui_ns) & (t_ns <= t_ns[e] + ui_ns) if mask.sum() < 3: continue ax.plot(t_ns[mask] - t_cross, v_mv[mask] - cm, color=ARRIVE_PURPLE, linewidth=0.4, alpha=0.18) n += 1 return n def main() -> None: clk_files = sorted(SESSION.glob(f"burst_{BURST:04d}_*_mipi_seg*_clk.csv")) if not clk_files: raise SystemExit(f"no CLK files for burst {BURST} in {SESSION}") fig, ax = plt.subplots(figsize=(8.5, 3.0)) total_segs = 0 total_xings = 0 for f in clk_files[:N_SEGS]: arr = np.genfromtxt(f, delimiter=",") t_ns = arr[:, 0] * 1e9 v_mv = arr[:, 1] * 1000 n = fold_segment(t_ns, v_mv, UI_NS, ax) if n: total_segs += 1 total_xings += n ax.axhline(0, color="grey", linewidth=0.4, alpha=0.5) ax.axvline(-UI_NS / 2, color="grey", linestyle=":", linewidth=0.4, alpha=0.5) ax.axvline(+UI_NS / 2, color="grey", linestyle=":", linewidth=0.4, alpha=0.5) ax.set_xlabel(f"time (ns, folded on UI = {UI_NS} ns)") ax.set_ylabel("CLK+ − common-mode (mV)") ax.set_xlim(-UI_NS, UI_NS) ax.set_title( f"CLK+ folded eye ({total_segs} segments × ~{total_xings // max(total_segs,1)} " f"crossings overlaid on a 2-UI window, burst {BURST})", color=ARRIVE_PURPLE, fontsize=11) ax.grid(True, alpha=0.25) ax.text(0.01, 0.95, f"{total_segs} segments × ~{total_xings // max(total_segs,1)} cycles overlaid", transform=ax.transAxes, fontsize=9, color=ARRIVE_PURPLE_DARK, bbox=dict(facecolor="white", edgecolor="none", alpha=0.85), va="top") OUT.parent.mkdir(parents=True, exist_ok=True) fig.tight_layout() fig.savefig(OUT, dpi=140) print(f"wrote {OUT} ({total_segs} segments, {total_xings} crossings)") if __name__ == "__main__": main()