{"$schema":"https://ejfox.com/schema/page-twin@1.json","kind":"project","url":"https://ejfox.com/projects/motorcycle-viz","json_url":"https://ejfox.com/projects/motorcycle-viz.json","generator":"ejfox.com/json-twin@1","data":{"cacheVersion":"2026-05-13-gear-cards","html":"<p class=\"\">I started filming my rides in case something went wrong. A helmet cam is insurance — if an accident or something crazy happened, at least there'd be a tape. But over a few years the reason quietly changed on me. I'd flip it on for the beautiful stuff instead: a good stretch of road, the light, whatever cool thing was happening. One time I caught a train and a helicopter lining up over the Hudson River exactly as I rode past.</p>\n<p class=\"\">By the end of those first few years I had gigabytes and gigabytes of it, and I got curious what all of it looked like at once — what years of riding might add up to. So I ran the whole corpus through a battery of meta-visualization techniques, pulling images out of the footage that no single frame holds.</p>\n<figure role=\"figure\" aria-label=\"Scrolling the helmet-cam corpus explorer — 294 rides, the frame contact sheet, and GPS route maps\" class=\"\"><video class=\"img-full my-8 rounded-sm\" autoplay loop muted playsinline preload=\"metadata\" poster=\"https://res.cloudinary.com/ejf/video/upload/so_0,f_jpg,q_auto,w_1280/projects/motorcycle-viz/corpus-scroll.jpg\" src=\"https://res.cloudinary.com/ejf/video/upload/q_auto/projects/motorcycle-viz/corpus-scroll.mp4\" aria-label=\"Scrolling the helmet-cam corpus explorer — 294 rides, the frame contact sheet, and GPS route maps\"></video><figcaption class=\"\">Scrolling the helmet-cam corpus explorer — 294 rides, the frame contact sheet, and GPS route maps</figcaption></figure>\n<figure role=\"figure\" aria-label=\"Every frame from 3.5 years of riding — 294 clips, 78 rides — averaged into a single image\" class=\"\">\n<img src=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/salavon-overall.png\" alt=\"Every frame from 3.5 years of riding — 294 clips, 78 rides — averaged into a single image\" loading=\"lazy\" decoding=\"async\" srcset=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_640/projects/motorcycle-viz/salavon-overall.png 640w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_960/projects/motorcycle-viz/salavon-overall.png 960w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/salavon-overall.png 1280w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1920/projects/motorcycle-viz/salavon-overall.png 1920w\" sizes=\"(max-width: 430px) 92vw, (max-width: 768px) 85vw, 900px\" title=\"Every frame from 3.5 years of riding — 294 clips, 78 rides — averaged into a single image\" width=\"640\" height=\"360\" style=\"--splay-rot:-2.13deg;\" data-dimensions=\"640×360\" class=\"img-full my-8 rounded-sm img-splay aspect-video w-full mx-auto py-4\" crossorigin=\"anonymous\">\n<figcaption class=\"\">Every frame from 3.5 years of riding — 294 clips, 78 rides — averaged into a single image</figcaption>\n</figure>\n<p class=\"\"><strong class=\"\">Frame averaging</strong> (after Jason Salavon) stacks thousands of frames into one — the constants of riding (road below, sky above, the fairing) resolve into a soft ghost while everything transient blurs away. Run it per year and you can watch the ghost shift as the routes and seasons change:</p>\n<figure role=\"figure\" aria-label=\"The average of every 2024 frame\" class=\"\">\n<img src=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/salavon-2024.png\" alt=\"The average of every 2024 frame\" loading=\"lazy\" decoding=\"async\" srcset=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_640/projects/motorcycle-viz/salavon-2024.png 640w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_960/projects/motorcycle-viz/salavon-2024.png 960w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/salavon-2024.png 1280w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1920/projects/motorcycle-viz/salavon-2024.png 1920w\" sizes=\"(max-width: 430px) 92vw, (max-width: 768px) 85vw, 900px\" title=\"The average of every 2024 frame\" width=\"640\" height=\"360\" style=\"--splay-rot:-2.37deg;\" data-dimensions=\"640×360\" class=\"img-full my-8 rounded-sm img-splay aspect-video w-full mx-auto py-4\" crossorigin=\"anonymous\">\n<figcaption class=\"\">The average of every 2024 frame</figcaption>\n</figure>\n<p class=\"\"><strong class=\"\">Slit-scan</strong> takes a single column from every frame and lays them side by side, turning a whole ride into a striated band of color and light — both horizontally and vertically.</p>\n<figure role=\"figure\" aria-label=\"Slit-scan: one column per frame, a whole ride compressed into a band\" class=\"\">\n<img src=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/slit-scan.png\" alt=\"Slit-scan: one column per frame, a whole ride compressed into a band\" loading=\"lazy\" decoding=\"async\" srcset=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_640/projects/motorcycle-viz/slit-scan.png 640w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_960/projects/motorcycle-viz/slit-scan.png 960w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/slit-scan.png 1280w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1920/projects/motorcycle-viz/slit-scan.png 1920w\" sizes=\"(max-width: 430px) 92vw, (max-width: 768px) 85vw, 900px\" title=\"Slit-scan: one column per frame, a whole ride compressed into a band\" width=\"2387\" height=\"360\" style=\"--splay-rot:1.88deg;\" data-dimensions=\"2387×360\" class=\"img-full my-8 rounded-sm img-splay w-full mx-auto py-4\" crossorigin=\"anonymous\">\n<figcaption class=\"\">Slit-scan: one column per frame, a whole ride compressed into a band</figcaption>\n</figure>\n<figure role=\"figure\" aria-label=\"Vertical slit-scan variant\" class=\"\">\n<img src=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/slit-scan-vertical.png\" alt=\"Vertical slit-scan variant\" loading=\"lazy\" decoding=\"async\" srcset=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_640/projects/motorcycle-viz/slit-scan-vertical.png 640w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_960/projects/motorcycle-viz/slit-scan-vertical.png 960w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/slit-scan-vertical.png 1280w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1920/projects/motorcycle-viz/slit-scan-vertical.png 1920w\" sizes=\"(max-width: 430px) 92vw, (max-width: 768px) 85vw, 900px\" title=\"Vertical slit-scan variant\" width=\"640\" height=\"1085\" style=\"--splay-rot:-0.6deg;\" data-dimensions=\"640×1085\" class=\"img-full my-8 rounded-sm img-splay w-full mx-auto py-4\" crossorigin=\"anonymous\">\n<figcaption class=\"\">Vertical slit-scan variant</figcaption>\n</figure>\n<figure role=\"figure\" aria-label=\"Dusk gradient — the color of the light, averaged across the golden-hour rides\" class=\"\">\n<img src=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/dusk-gradient.png\" alt=\"Dusk gradient — the color of the light, averaged across the golden-hour rides\" loading=\"lazy\" decoding=\"async\" srcset=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_640/projects/motorcycle-viz/dusk-gradient.png 640w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_960/projects/motorcycle-viz/dusk-gradient.png 960w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/dusk-gradient.png 1280w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1920/projects/motorcycle-viz/dusk-gradient.png 1920w\" sizes=\"(max-width: 430px) 92vw, (max-width: 768px) 85vw, 900px\" title=\"Dusk gradient — the color of the light, averaged across the golden-hour rides\" width=\"2400\" height=\"804\" style=\"--splay-rot:1.76deg;\" data-dimensions=\"2400×804\" class=\"img-full my-8 rounded-sm img-splay w-full mx-auto py-4\" crossorigin=\"anonymous\">\n<figcaption class=\"\">Dusk gradient — the color of the light, averaged across the golden-hour rides</figcaption>\n</figure>\n<p class=\"\"><strong class=\"\">Cinema redux</strong> shrinks every frame to a tile and tiles them into a single mosaic — the entire footage archive as one dense image, a barcode of where I've been.</p>\n<figure role=\"figure\" aria-label=\"Cinema redux — every frame as a tile, the whole archive in one mosaic\" class=\"\">\n<img src=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/cinema-redux.png\" alt=\"Cinema redux — every frame as a tile, the whole archive in one mosaic\" loading=\"lazy\" decoding=\"async\" srcset=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_640/projects/motorcycle-viz/cinema-redux.png 640w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_960/projects/motorcycle-viz/cinema-redux.png 960w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/cinema-redux.png 1280w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1920/projects/motorcycle-viz/cinema-redux.png 1920w\" sizes=\"(max-width: 430px) 92vw, (max-width: 768px) 85vw, 900px\" title=\"Cinema redux — every frame as a tile, the whole archive in one mosaic\" width=\"3000\" height=\"2510\" style=\"--splay-rot:0.6deg;\" data-dimensions=\"3000×2510\" class=\"img-full my-8 rounded-sm img-splay w-full mx-auto py-4\" crossorigin=\"anonymous\">\n<figcaption class=\"\">Cinema redux — every frame as a tile, the whole archive in one mosaic</figcaption>\n</figure>\n<figure role=\"figure\" aria-label=\"Cinema redux, frame variant — a denser grid\" class=\"\">\n<img src=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/cinema-redux-frames.png\" alt=\"Cinema redux, frame variant — a denser grid\" loading=\"lazy\" decoding=\"async\" srcset=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_640/projects/motorcycle-viz/cinema-redux-frames.png 640w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_960/projects/motorcycle-viz/cinema-redux-frames.png 960w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/cinema-redux-frames.png 1280w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1920/projects/motorcycle-viz/cinema-redux-frames.png 1920w\" sizes=\"(max-width: 430px) 92vw, (max-width: 768px) 85vw, 900px\" title=\"Cinema redux, frame variant — a denser grid\" width=\"4000\" height=\"3434\" style=\"--splay-rot:-2.69deg;\" data-dimensions=\"4000×3434\" class=\"img-full my-8 rounded-sm img-splay w-full mx-auto py-4\" crossorigin=\"anonymous\">\n<figcaption class=\"\">Cinema redux, frame variant — a denser grid</figcaption>\n</figure>\n<figure role=\"figure\" aria-label=\"A contact-grid of frames pulled from the rides\" class=\"\">\n<img src=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/frame-grid.png\" alt=\"A contact-grid of frames pulled from the rides\" loading=\"lazy\" decoding=\"async\" srcset=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_640/projects/motorcycle-viz/frame-grid.png 640w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_960/projects/motorcycle-viz/frame-grid.png 960w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/frame-grid.png 1280w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1920/projects/motorcycle-viz/frame-grid.png 1920w\" sizes=\"(max-width: 430px) 92vw, (max-width: 768px) 85vw, 900px\" title=\"A contact-grid of frames pulled from the rides\" width=\"4506\" height=\"3870\" style=\"--splay-rot:-0.73deg;\" data-dimensions=\"4506×3870\" class=\"img-full my-8 rounded-sm img-splay w-full mx-auto py-4\" crossorigin=\"anonymous\">\n<figcaption class=\"\">A contact-grid of frames pulled from the rides</figcaption>\n</figure>\n<figure role=\"figure\" aria-label=\"Dusk anthology — a tall contact sheet of golden-hour frames\" class=\"\">\n<img src=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/dusk-contact.png\" alt=\"Dusk anthology — a tall contact sheet of golden-hour frames\" loading=\"lazy\" decoding=\"async\" srcset=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_640/projects/motorcycle-viz/dusk-contact.png 640w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_960/projects/motorcycle-viz/dusk-contact.png 960w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/motorcycle-viz/dusk-contact.png 1280w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1920/projects/motorcycle-viz/dusk-contact.png 1920w\" sizes=\"(max-width: 430px) 92vw, (max-width: 768px) 85vw, 900px\" title=\"Dusk anthology — a tall contact sheet of golden-hour frames\" width=\"1940\" height=\"5684\" style=\"--splay-rot:1.7deg;\" data-dimensions=\"1940×5684\" class=\"img-full my-8 rounded-sm img-splay w-full mx-auto py-4\" crossorigin=\"anonymous\">\n<figcaption class=\"\">Dusk anthology — a tall contact sheet of golden-hour frames</figcaption>\n</figure>\n<p class=\"\">So what did it all add up to? Honestly, not much you could point at. The specific moments I'd gone looking for — the train and the helicopter, the good light — washed out in the averaging. What was left was just the general shape and colors of the Hudson Valley and its roads. Unremarkable, but with a kind of haunting beauty to it. A consistency.</p>\n<p class=\"\">The real blessing turned out to be in what <em class=\"\">isn't</em> there. Across all of it — every gig of footage, every year — no accident, nothing terrible ever happened. The insurance I started rolling never had to pay out. That's the thing the average quietly confirms.</p>\n<h2 class=\"\" id=\"mapping-the-rides\">Mapping the rides</h2>\n<figure role=\"figure\" aria-label=\"A 56-mile Hudson Valley loop mapped from a Garmin file\" class=\"\">\n<img src=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/moto-gpx/map.png\" alt=\"A 56-mile Hudson Valley loop mapped from a Garmin file\" loading=\"lazy\" decoding=\"async\" srcset=\"https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_640/projects/moto-gpx/map.png 640w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_960/projects/moto-gpx/map.png 960w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1280/projects/moto-gpx/map.png 1280w, https://res.cloudinary.com/ejf/image/upload/c_scale,f_auto,q_auto:good,w_1920/projects/moto-gpx/map.png 1920w\" sizes=\"(max-width: 430px) 92vw, (max-width: 768px) 85vw, 900px\" title=\"A 56-mile Hudson Valley loop mapped from a Garmin file\" width=\"1180\" height=\"1380\" style=\"--splay-rot:2.48deg;\" data-dimensions=\"1180×1380\" class=\"img-full my-8 rounded-sm img-splay w-full mx-auto py-4\" crossorigin=\"anonymous\">\n<figcaption class=\"\">A 56-mile Hudson Valley loop mapped from a Garmin file</figcaption>\n</figure>\n<p class=\"\"><a href=\"https://ejfox.github.io/moto-gpx/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"external-link group inline-flex items-center text-blue-600 dark:text-blue-400\" data-preview-url=\"https://ejfox.github.io/moto-gpx/\">Moto GPX<svg class=\"inline-block w-4 h-4 ml-1 opacity-50 dark:opacity-75 group-hover:opacity-100 transition-opacity align-text-bottom\" xmlns=\"http://www.w3.org/2000/svg\" width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M14.435 12.973c.269 0 .492.133.686.396c.192.265.294.588.294.975c0 .385-.102.711-.294.973c-.193.265-.417.396-.686.396c-.278 0-.522-.131-.715-.396c-.192-.262-.294-.588-.294-.973c0-.387.102-.71.294-.975c.192-.264.436-.396.715-.396m3.44-3.559c.746.811 1.125 1.795 1.125 2.953q0 1.122-.259 2.023q-.263.896-.654 1.459a4.3 4.3 0 0 1-.974.989a5 5 0 0 1-1.065.623a5.5 5.5 0 0 1-1.111.306a9 9 0 0 1-.943.123l-.685.014l-.547.015a18 18 0 0 1-1.524 0l-.547-.015l-.685-.014a9 9 0 0 1-.943-.123a5.3 5.3 0 0 1-1.111-.306a5 5 0 0 1-1.064-.623a4.3 4.3 0 0 1-.975-.989q-.392-.563-.654-1.459q-.26-.9-.259-2.023c0-1.158.379-2.143 1.125-2.953q-.123-.062-.008-1.217a7 7 0 0 1 .495-2.132c.934.099 2.09.629 3.471 1.581c.466-.119 1.101-.183 1.917-.183q1.278.002 1.918.184q.943-.638 1.805-1.034c.584-.261 1.005-.416 1.269-.457l.396-.09q.404.974.496 2.132q.114 1.154-.009 1.216m-5.845 7.82c1.688 0 2.954-.202 3.821-.607c.855-.404 1.292-1.238 1.292-2.496q0-1.096-.822-1.828a1.85 1.85 0 0 0-.989-.486q-.563-.091-1.72 0q-1.155.091-1.582.09c-.385 0-.8-.018-1.319-.059q-.78-.06-1.223-.078a3.7 3.7 0 0 0-.958.108a1.9 1.9 0 0 0-.853.425c-.521.469-.79 1.077-.79 1.828q0 1.888 1.28 2.496c.85.405 2.113.607 3.802.607zm-2.434-4.261c.268 0 .492.133.685.396c.192.265.294.588.294.975c0 .385-.102.711-.294.973c-.192.265-.417.396-.685.396c-.279 0-.522-.131-.716-.396c-.192-.262-.294-.588-.294-.973c0-.387.102-.71.294-.975c.193-.264.436-.396.716-.396\" class=\"\"></path></svg></a> dumps a folder of GPX tracks into map-ready GeoJSON split by day, hour, or stage, and can merge photos and videos in via exiftool. It also generates elevation and speed profiles, with the day's peak and top-speed moment marked.</p>","title":"Motorcycle Meta-Visualizations","metadata":{"title":"Motorcycle Meta-Visualizations","date":"2026-05-01T04:00:00.000Z","featured":true,"category":"Art","url":"https://github.com/ejfox/motorcycle-viz","tech":["FFmpeg","Python","Generative Art","Data Visualization"],"state":"doing","ai-involvement":"ai-assisted","tags":["motorcycle","dataviz","generative","art","video"],"words":525,"images":11,"imageDetails":{"total":11,"cloudinary":11,"withDimensions":0},"links":1,"codeBlocks":0,"headers":{"h2":1},"toc":[{"text":"Mapping the rides","slug":"mapping-the-rides","level":"h2","children":[]}],"type":"post"}},"_links":{"self":"https://ejfox.com/projects/motorcycle-viz.json","html":"https://ejfox.com/projects/motorcycle-viz","index":"/projects.json"}}