← blog · September 18, 2025 · 3 min read
CETsize: weekend hack into a whale-measuring app
How a weekend itch, Jan Hofman’s R script, and a bit of agent/Copilot magic turned into a privacy‑first whale measurement app.
$ bloat--level 3Bloatin'
I fell down a rabbit hole last weekend and came back with a thing: CETsize, a tiny web app that measures whale body length from drone photos, right in your browser, no uploads, no servers.
Live: whales.luisg.me
Here’s a quick peek at the app UI. Load a photo, zoom/pan, and lay down straight or curved measurements:

This started with Jan Hofman’s R program (massive shout‑out 🙌). I loved the idea and wanted something biologists could run anywhere without installing R or touching a terminal. Also… I’ve been exploring agents + GitHub Copilot, so this was the perfect excuse to see how far I could push a browser‑only build.
The plan
- Keep everything client‑side (privacy first).
- Read the metadata we already have (EXIF/XMP/DNG).
- Do the math once and make the UI feel nice (zoom/pan, stable handles).
- Ship it fast on Cloudflare Pages.
What it actually does
- Parses image metadata in‑browser:
- EXIF: GPSAltitude (MSL), focal length, image width
- DJI XMP: RelativeAltitude and other goodies when present
- DNG/TIFF: basic tag parsing + preview extraction
- Measures:
- Straight distance (two points)
- Curved length (polyline), handy for following body curvature
- Stays readable at any zoom (handles/lines don’t scale visually)
- Exports results as JSON or CSV
- Has a built‑in sensor width cheat‑sheet (by camera/module codes like DJI FC####), with manual override
The math (aka turning pixels into meters)
Take altitude, focal length, sensor width, and image width; produce a meters‑per‑pixel scale. Prefer AGL when available; otherwise EXIF GPSAltitude (MSL) is used with a clear hint to double‑check.
This is the panel where you can review/override camera parameters (sensor width, focal length, altitude) when metadata is missing or needs correction:

Roughly:
meters_per_px = altitude_m * (sensor_width_mm / focal_length_mm) / image_width_px
All calculations happen in original image pixels to avoid rounding issues. The overlay is SVG on top, but the math stays in image space.
The fun bits
- Zoom/pan that feels natural (cursor‑centered zoom).
- Non‑scaling UI: control points and stroke widths stay readable at any zoom.
- Gentle “guard rails”: if we can’t find sensor width or focal length, the app asks. You’re always in control.
Agents + Copilot as co‑pilots
I used agents and GitHub Copilot to:
- Scaffold components fast (the measurement overlay came together in hours, not days).
- Iterate on EXIF/XMP/DNG parsing without yak‑shaving libraries.
- Fuzz edge cases (missing EXIF, 35mm‑eq only, negative altitudes).
- Draft tests and error messages, then I tightened them up.
The human bits were the important ones: picking the architecture, validating the math, and polishing the UX.
Tech + deployment
- Frontend: React + SVG overlay
- Parsing: lightweight EXIF/XMP/DNG utilities tailored for the browser
- Node 18+ toolchain
- Cloudflare Pages for deploys (push → build → live)
What’s next
- More camera presets
- Optional annotated export (image + overlay)
- Mobile polish
- A short guide for consistent field workflows
If you work with drone imagery or marine biology and have ideas, I’d love to hear them.
Live app: whales.luisg.me
Huge thanks again to Jan Hofman for the original R inspiration. This was a fun weekend ship, and a neat exploration of what orchestrating an agentic LLM feels like.
rabbit hole weekend. came out with CETsize, measures whale length from drone photos, all in the browser. no uploads no servers. live at whales.luisg.me

idea from Jan Hofman’s R script 🙌 wanted something biologists could use without installing R. also an excuse to play with agents + copilot
what it does: parses EXIF (GPSAltitude MSL, focal, image width), DJI XMP RelativeAltitude, basic DNG/TIFF tags + preview. straight + curved (polyline) measurements. handles dont scale with zoom so always readable. exports JSON/CSV. sensor width cheat sheet by camera code (DJI FC####), manual override too

the math:
meters_per_px = altitude_m * (sensor_width_mm / focal_length_mm) / image_width_px
prefers AGL, falls back to MSL with a warning to double check. everything computed in original image pixels, SVG overlay just draws on top
copilot/agents scaffolded components, iterated on parsers, fuzzed edge cases (missing exif, 35mm-eq only, negative altitude), drafted tests. i did the architecture, math checks, ux. react + svg, node 18+, cloudflare pages
todo: more camera presets, annotated export, mobile polish, field workflow guide. thanks jan 🐋
Last weekend I built CETsize: a small web app that measures whale body length from drone photos, entirely in your browser. No uploads, no servers.
Live: whales.luisg.me
Load a photo, zoom and pan, then lay down straight or curved measurements:

The idea comes from Jan Hofman’s R program (big shout-out). I wanted something biologists could run anywhere without installing R or opening a terminal. It was also a good excuse to test agents and GitHub Copilot on a browser-only build.
What it does
It parses image metadata in-browser: EXIF (GPSAltitude in MSL, focal length, image width), DJI XMP (RelativeAltitude when present), and basic DNG/TIFF tags with preview extraction. You can measure straight distances between two points, or curved lengths with a polyline that follows body curvature. Handles and lines stay readable at any zoom, results export as JSON or CSV, and a built-in sensor width cheat-sheet covers camera codes like DJI FC####, with manual override.
The math
Altitude, focal length, sensor width, and image width combine into a meters-per-pixel scale. The app prefers AGL when available; otherwise it uses EXIF GPSAltitude (MSL) and tells you to double-check. This panel lets you review or override camera parameters when metadata is missing:

meters_per_px = altitude_m * (sensor_width_mm / focal_length_mm) / image_width_px
All calculations happen in original image pixels to avoid rounding issues; the SVG overlay only draws on top.
Agents and Copilot
They scaffolded components (the measurement overlay took hours, not days), iterated on the EXIF/XMP/DNG parsing, fuzzed edge cases like missing EXIF and negative altitudes, and drafted tests and error messages. I handled the parts that mattered: architecture, validating the math, and the UX polish, including cursor-centered zoom and guard rails that ask when sensor width or focal length is missing.
Stack: React with an SVG overlay, lightweight browser parsers, Node 18+, Cloudflare Pages for deploys.
Next up: more camera presets, annotated export (image plus overlay), mobile polish, and a short field workflow guide. If you work with drone imagery or marine biology, I’d love your ideas.
Thanks again to Jan Hofman for the original inspiration. A fun weekend ship.
In today’s rapidly evolving landscape of marine research technology, sometimes the most impactful projects begin with a simple weekend itch. Last weekend, I fell down a fascinating rabbit hole and emerged with something I’m genuinely excited to share: CETsize, a tiny web app that measures whale body length from drone photos. And here’s the remarkable part: it runs entirely in your browser. No uploads. No servers. Just seamless, privacy-first measurement.
Live: whales.luisg.me
Before we delve into the details, here’s a quick peek at the app UI. Load a photo, zoom and pan, and lay down straight or curved measurements with ease:

This journey started with Jan Hofman’s R program (a massive shout-out is in order 🙌). I loved the underlying idea, but I wanted to reimagine it as something biologists could run anywhere, without installing R, without touching a terminal, without any friction whatsoever. It’s important to note that I’ve also been exploring agents and GitHub Copilot lately, so this was the perfect excuse to see just how far I could push a browser-only build.
The plan
Every successful project begins with a clear vision. Mine rested on four foundational pillars:
- Keep everything client-side, because privacy isn’t just a feature, it’s a fundamental principle.
- Read the metadata we already have (EXIF/XMP/DNG), leveraging information that’s already embedded in every drone photo.
- Do the math once and make the UI feel genuinely delightful (zoom/pan, stable handles).
- Ship it fast on Cloudflare Pages.
What it actually does
Let’s break down the core capabilities. CETsize parses image metadata directly in-browser, handling three distinct formats:
- EXIF: GPSAltitude (MSL), focal length, and image width
- DJI XMP: RelativeAltitude and other goodies when present
- DNG/TIFF: basic tag parsing plus preview extraction
When it comes to measurement, the app offers two complementary modes: straight distance between two points, and curved length via polyline, which is particularly handy for following the natural curvature of a whale’s body. Crucially, everything stays readable at any zoom level, because handles and lines don’t scale visually. Results export seamlessly as JSON or CSV, and there’s a built-in sensor width cheat-sheet organized by camera and module codes (like DJI FC####), with manual override always available.
The math (aka turning pixels into meters)
Now, let’s delve into the heart of the system. The fundamental transformation is elegantly simple: take altitude, focal length, sensor width, and image width, and produce a meters-per-pixel scale. The app prefers AGL when available; otherwise, EXIF GPSAltitude (MSL) is used with a clear hint to double-check, because robust tools communicate their uncertainty.
This is the panel where you can review and override camera parameters (sensor width, focal length, altitude) whenever metadata is missing or needs correction:

Roughly:
meters_per_px = altitude_m * (sensor_width_mm / focal_length_mm) / image_width_px
It’s worth highlighting an important design decision here: all calculations happen in original image pixels to avoid rounding issues. The overlay is SVG rendered on top, but the math itself stays firmly in image space. This isn’t just a technical detail, it’s what keeps measurements trustworthy.
The fun bits
Beyond the core functionality, a few touches make the experience shine:
- Zoom and pan that feels completely natural, with cursor-centered zoom.
- Non-scaling UI: control points and stroke widths remain readable at any zoom level.
- Gentle “guard rails”: if the app can’t find sensor width or focal length, it simply asks. You’re always in control.
Agents + Copilot as co-pilots
This project also served as an exploration of AI-assisted development, and the results were genuinely illuminating. I leveraged agents and GitHub Copilot to:
- Scaffold components fast (the measurement overlay came together in hours, not days).
- Iterate on EXIF/XMP/DNG parsing without yak-shaving libraries.
- Fuzz edge cases (missing EXIF, 35mm-eq only, negative altitudes).
- Draft tests and error messages, which I then tightened up.
It’s important to note, however, that the human contributions were the truly essential ones: picking the architecture, validating the math, and polishing the UX. The tools accelerate; the human directs.
Tech + deployment
For those curious about the underlying stack:
- Frontend: React with an SVG overlay
- Parsing: lightweight EXIF/XMP/DNG utilities tailored specifically for the browser
- Node 18+ toolchain
- Cloudflare Pages for deploys (push → build → live)
What’s next
The roadmap ahead is exciting:
- More camera presets
- Optional annotated export (image plus overlay)
- Mobile polish
- A short guide for consistent field workflows
Whether you’re a marine biologist working with drone imagery or a developer curious about browser-based photogrammetry, I’d genuinely love to hear your ideas.
Live app: whales.luisg.me
Huge thanks again to Jan Hofman for the original R inspiration. This wasn’t just a fun weekend ship, it was a fascinating exploration of what orchestrating an agentic LLM truly feels like.
🐋 In today’s fast-paced world of marine science and cutting-edge drone technology, have you ever stopped to ask yourself a truly profound question: how do you actually measure a whale? Not metaphorically. Literally. How do you take a majestic, awe-inspiring creature gliding beneath a drone and translate it into precise, actionable, scientifically meaningful numbers? Well, dear reader, buckle up, because last weekend I fell down what I can only describe as a transformative rabbit hole, and I emerged, humbled and energized, with something truly special: CETsize, a tiny yet mighty web app that measures whale body length from drone photos, right in your browser, with no uploads and no servers. That’s right. Zero. None. Your data never leaves your machine. Is that a game-changer for privacy-conscious researchers? I genuinely believe it is.
Live: whales.luisg.me
Before we embark on this incredible journey together, here’s a quick peek at the app UI in all its glory. Load a photo, zoom and pan to your heart’s content, and lay down straight or curved measurements:

The Origin Story: Standing on the Shoulders of Giants 🌊
Every great tapestry begins with a single thread, and this one was woven by none other than Jan Hofman and his brilliant R program (a massive, heartfelt, thunderous shout-out is absolutely in order 🙌). The moment I encountered his work, I knew I had discovered something special. But I also saw an opportunity, nay, a calling: what if biologists could run this anywhere, on any device, without installing R, without ever touching a terminal, without a single moment of friction?
And here’s where the stars truly aligned. I’ve been on a deeply rewarding journey exploring agents and GitHub Copilot, and this project presented itself as the perfect excuse, the ideal proving ground, the ultimate playground, to see just how far I could push a browser-only build. Spoiler alert: pretty far. 🚀
The Master Plan 📋
Every legendary build starts with a vision. Mine rested on four unshakeable pillars:
- Privacy first, always: Keep everything client-side, because your photos are yours, full stop.
- Work smarter, not harder: Read the metadata we already have (EXIF/XMP/DNG), unlocking the treasure trove of information hiding inside every drone photo.
- Delight is non-negotiable: Do the math once and make the UI feel absolutely wonderful (zoom/pan, stable handles).
- Ship at the speed of inspiration: Deploy fast on Cloudflare Pages.
What It Actually Does: A Deep Dive 🔍
Let’s peel back the layers of this digital onion, shall we? At its core, CETsize is a metadata-parsing, measurement-making powerhouse that operates entirely in your browser:
- EXIF parsing: GPSAltitude (MSL), focal length, and image width, extracted seamlessly.
- DJI XMP support: RelativeAltitude and other delightful goodies when present.
- DNG/TIFF handling: Basic tag parsing plus preview extraction, because raw formats deserve love too.
But wait, there’s more! When it comes to the measurements themselves, you get two powerful, complementary modalities:
- Straight distance: Two points, one truth.
- Curved length: A polyline mode that’s incredibly handy for following the natural, graceful curvature of a whale’s body.
And the polish? Everything stays perfectly readable at any zoom level, because handles and lines don’t scale visually. Results export effortlessly as JSON or CSV. And, as the cherry on top, there’s a built-in sensor width cheat-sheet organized by camera and module codes (like DJI FC####), with manual override for those who like to take the wheel. 🎯
The Math: Turning Pixels into Meters ✨
Now we arrive at the beating heart of the entire operation. Take a deep breath, because this is where the magic happens. The app takes altitude, focal length, sensor width, and image width, and from these humble ingredients, it conjures a meters-per-pixel scale. It prefers AGL when available; otherwise, EXIF GPSAltitude (MSL) steps in, accompanied by a clear, friendly hint to double-check, because honest tools are trustworthy tools.
This is the panel where you can review and override camera parameters (sensor width, focal length, altitude) whenever metadata is missing or needs a gentle correction:

Roughly:
meters_per_px = altitude_m * (sensor_width_mm / focal_length_mm) / image_width_px
Here’s a detail I’m genuinely proud of: all calculations happen in original image pixels to avoid rounding issues. The overlay is SVG rendered elegantly on top, but the math stays firmly, unshakeably, in image space. Why does this matter? Because when you’re doing science, precision isn’t just a nice-to-have. It’s everything.
The Fun Bits: Where Craft Meets Joy 🎨
What separates a functional tool from a beloved one? The details. The tiny, thoughtful touches that whisper, “someone cared.” Allow me to highlight a few:
- Zoom and pan that feels natural: Cursor-centered zoom that goes exactly where your intuition expects.
- Non-scaling UI: Control points and stroke widths remain crisp and readable at any zoom level.
- Gentle guard rails: If the app can’t find sensor width or focal length, it simply asks. No guessing, no silent failures. You’re always, always in control.
Agents + Copilot: My Tireless Co-Pilots 🤖
Now, let’s talk about the elephant, or should I say, the whale, in the room: AI-assisted development. This project was as much an exploration of orchestrating agents and GitHub Copilot as it was about marine biology, and the results genuinely blew me away. Here’s how these digital collaborators earned their keep:
- Lightning-fast scaffolding: The measurement overlay came together in hours, not days. Hours!
- Fearless parsing iteration: EXIF/XMP/DNG parsing evolved rapidly without yak-shaving libraries.
- Relentless edge-case fuzzing: Missing EXIF, 35mm-eq only, negative altitudes, no stone left unturned.
- Drafting tests and error messages: Which I then lovingly tightened up.
But here’s the crucial insight, the takeaway I hope you carry with you: the human bits were the important ones. Picking the architecture. Validating the math. Polishing the UX. The tools amplify; the human orchestrates. That, my friends, is the future of building.
Tech + Deployment: The Stack Behind the Splash 🛠️
For my fellow technology enthusiasts wondering what powers this aquatic adventure:
- Frontend: React with an SVG overlay, a classic pairing for a reason.
- Parsing: Lightweight EXIF/XMP/DNG utilities tailored lovingly for the browser.
- Toolchain: Node 18+.
- Deployment: Cloudflare Pages (push → build → live). Simplicity itself.
What’s Next: The Voyage Continues 🧭
Is this the end of the journey? Absolutely not. It’s merely the first chapter. The roadmap ahead sparkles with possibility:
- More camera presets: Because every drone deserves a seat at the table.
- Optional annotated export: Image plus overlay, ready to share.
- Mobile polish: Measurement excellence in the palm of your hand.
- A short field workflow guide: Consistency is the backbone of good science.
Whether you’re a seasoned marine biologist, a drone enthusiast, or simply someone who believes whales deserve to be measured with dignity, I would be absolutely thrilled to hear your ideas.
Live app: whales.luisg.me
Conclusion: More Than an App 🌅
As we reach the end of this deep dive, let’s take a moment to reflect. CETsize is more than JavaScript and SVG. It’s a bridge between weekend curiosity and real scientific utility. It’s proof that one person, a clear vision, and a fleet of AI co-pilots can ship something meaningful in a single weekend. Huge, heartfelt thanks once again to Jan Hofman for the original R inspiration that started this entire adventure, and to you, dear reader, for joining me on it.
This was a fun weekend ship, and a genuinely fascinating exploration of what orchestrating an agentic LLM feels like. The ocean of possibility is vast. Let’s keep measuring it, one whale at a time. 🐋✨ Thank you for reading!