One command in, a rotoscoped video out. No styling flags.
The pipeline demuxes the source with ffmpeg, runs salient-foreground segmentation (rembg, isnet-general-use) with temporal hysteresis, quantises the subject to 1-bit line art on a roughly 160px-tall grid using per-frame Otsu thresholds, positions face-landmark strokes from YuNet and a mediapipe mesh, then reconstructs colour by looking up a measured phosphor transfer function keyed on signed distance to the ink boundary. Colour is never chosen; it is read off a lookup table built from measurement.
The CLI surface is one console entry point (pixelize) and one GUI entry point (pixelize-gui), each taking two positional arguments (input, output) and two optional flags (--limit, --work-dir). input accepts a local file path or a YouTube URL resolved through yt-dlp. There is no third path: no preset library, no colour picker, no style parameter exposed to the end user.
Every figure below traces to a command or a file path.
Compiled from a single squashed commit plus the uncommitted work sitting on top of it. Where two numbers exist for the same claim (the README's pre-fix RMSE versus the current one), both are shown.
| Metric | Value |
|---|---|
| Commits | 1 |
| Commit date / hash | 2026-08-01 17:20:35+02:00, dd6f4c8 |
| Uncommitted changes on top of HEAD | 9 modified files, 10 untracked paths |
| Tracked files | 70 |
Production Python, pixelize/ package | 1,785 lines across 12 files |
Production Python, scripts/ tooling | 1,125 lines across 9 files |
| Total current-path Python LOC | 2,910 |
| Legacy Python (V17-era, retained as history) | 122 lines, 2 files |
| Legacy shell scripts (V16/V17 line) | 21 files, 2,597 lines |
| Tracked Markdown docs | 18 files, 3,448 lines |
| Package identity | pixelize 0.1.0, editable-install only, no PyPI publish, no git tags |
| CLI surface | 1 console entry point (pixelize), 1 GUI entry point (pixelize-gui); 2 positional args plus --limit, --work-dir |
| Utility scripts | 9: bench_matte.py, render_fixture.py, validate_crt.py, make_contact_sheet.py, build_phosphor_lut.py, build_fixture_gt.py, validate_matte.py, build_app.py, validate_style.py |
| Traditional unit tests | 0 (no test_*.py files) |
| Quality-gate fixtures | 5 clips (chair, choir, closeup, duo, guitar), each with a manifest; 2 of 5 (chair, guitar) carry hand-verified ground truth |
| Declared runtime dependencies | 4: numpy, scipy, pillow, rembg[cpu] |
| Imported but undeclared dependencies | opencv-contrib-python, mediapipe, yt-dlp, onnxruntime |
| CRT-fidelity validation, current | RMSE 4.32 against a 6.0 threshold |
| CRT-fidelity validation, README pre-fix | RMSE 4.43 against a 6.0 threshold, 3 held-out frames |
| Matte thin-structure recall improvement | 0.335 → 0.997 (chair-leg / guitar-neck fixtures) |
| Face feature-drawing rate improvement | 0.5–0.92 → 1.0 across all 5 fixtures |
| Render cost | about 1.1s per art frame; about 1 minute per 10s of 1080p footage |
Twelve modules, one frame loop, zero colour decisions.
cli.py and gui.py are thin front ends; both call into pipeline.py, which owns the frame loop and orchestrates everything else in sequence.
nb_frames, which was measured wrong by 27 frames on the reference clip.Persistence is applied per written output frame, not per art frame, so trails fade under a held frame instead of reading as a crisp double image. That fix, and the solid-fill separation floor below, both shipped 2026-08-01.
Eight calls, each with a measurement behind it.
1-bit-then-CRT, not direct colour rendering
Reference hue tracks stroke thickness (R−B runs from −22 at 1px to +29 at 6px), which is the signature of a monochrome CRT's phosphor response, not an artistic colour choice. Every earlier version, V1 through V17E, tried to assign hair and skin colour directly and failed to generalise.
A measured LUT replaced a two-colour lerp
The original design assumed bloom could be a lerp between a lit and unlit colour. Reference boundary pixels are dark but strongly blue, which no two-colour interpolation reproduces, so colour is looked up by signed distance instead.
Automatic mid-tone dithering, dropped
Applying MacPaint-style dither to the mid-luminance cluster buried any backlit subject's torso in pattern. Removed in favour of a clean body, with shading left as an unsolved, unattempted accent.
Solid-fill gated on measured class separation
A luminance-crushed close-up, 90% of the subject interior below 0.125 luminance, let the two-stage Otsu split "find" a darkest class that was shadowed skin, not hair. MIN_FILL_SEPARATION = 0.10 sits in the gap between that failure (0.047 separation) and every legitimate fill (0.145 to 0.366). Added 2026-08-01.
Persistence moved from per-art-frame to per-written-frame
Blending the previous frame once per art frame held a ghost unchanged across both frames of a 2x hold, reading as a crisp double image instead of a fade. Confirmed by rendering the same timestamp with and without the blend. Added 2026-08-01.
BiRefNet abandoned for matte segmentation
A killed CoreML compile of the swin-based model wedges the Apple Neural Engine service machine-wide until reboot; CPU-only fallback measured 10.5s per frame, 513 minutes for a full render. isnet-general-use plus temporal hysteresis was used instead.
Face landmarks are structural only
face.py was added despite an earlier blanket "no face parsing" rule, on the argument that landmark positions, not colour assignment, are needed to keep skin paper-white and place deliberate feature strokes. The rule against colour assignment still stands.
Dependency declaration is incomplete
pyproject.toml declares only numpy, scipy, pillow, and rembg[cpu], but the code imports, and the local virtualenv has installed, opencv-contrib-python, mediapipe, and yt-dlp. None of those three are transitive dependencies of the declared four. A fresh pip install -e . per the README's own instructions would not install what face.py and fetch.py require.
One squashed commit; the real history lives in the changelog.
Git carries a single commit (dd6f4c8, 2026-08-01), so phase history here comes from CHANGELOG.md and on-disk file dates, not commit history.
The abandoned V1 to V17E line
Geometric zone heuristics and semantic hair/beard segmentation, tracked across five separate status and bugfix documents plus 21 shell scripts. A 2025-11-17 cleanup pass removed 2.4GB of superseded frame dumps.
V16/V17 retired; pixelize built from scratch
"The semantic hair-fill problem they solved does not exist in the target aesthetic." Demux, matte, 1-bit line art, and measured CRT phosphor simulation built the same day; the tkinter GUI shipped alongside it.
Packaged as an app; URL input added
GUI button rendering fixed (tk.Button to ttk.Button); packaged as Pixelize.app, thinned to arm64 and ad-hoc signed. YouTube URL input via yt-dlp added to both CLI and GUI.
Real progress counters; the git repository begins here
Progress reporting switched to real ffmpeg and yt-dlp counters; nb_frames dropped as the frame-count source. The initial commit was made. A full-video QA pass against 60 reference frames found and fixed three defects, closed out the same day.
Fidelity upgrade, not yet committed
Matte temporal hysteresis, motion-gated carry-forward, face.py's structural landmarks and skin guard, style_params.py split out, fixture ground truth added, and a QA skill introduced. QA run 1 converged in one iteration across all 5 fixtures with zero parameter changes needed. This work sits on disk, uncommitted.
What this is not.
Honesty over polish. The gaps below are as real as the numbers above.
face.py is structural only
This is not a face-recognition, identity, or skin-tone classification tool. face.py performs landmark detection only and is explicitly forbidden from making colour decisions.
Matte starvation on underexposed footage
A severely underexposed, crushed-dark shot fails the QA rubric by eye. This failure mode is known and currently unresolved.
Pixelize.app is checkout-specific
The packaged app is not relocatable and points at this specific checkout's virtualenv. The package has never been published to PyPI, and no git tag or release exists.
Correctness is argued, not covered
There are zero traditional unit tests. Correctness rests entirely on three numeric validator scripts run against five hand-picked fixture clips, which is targeted evidence, not general-purpose test coverage.
Evidence: compiled from the pixel-yt repository on 2026-08-03. Every number on this page traces to a file path or command output in the source tree. Last updated: 2026-08-03.