The short version. I love pointing a telescope at the sky. I do not love the paperwork that comes after: thousands of files across many nights, calibration bookkeeping, deciding which frames were sharp enough to keep, and backups. So I built software that does the paperwork. The point of this post is not the software. It is that a single person with a hobby could build and run a genuinely distributed system, with a build partner that never got tired of the boring parts, and end up with more nights actually looking up.

Here is the part of astrophotography nobody puts on a poster. You spend a clear, cold night capturing a nebula that is a thousand light-years away. It is genuinely magical. Then the sun comes up, and you are left with a memory card full of files named things like Light_NGC2024_300.0s_Bin1_NIGHT3_225118_0009.fit, a few hundred of them, and that is just one target on one night. Multiply by every clear night in a season and the hobby quietly turns into a second job in file management: copying data off the camera before you reuse the card, matching each night's light frames to the right calibration frames, throwing out the shots the wind or a passing cloud ruined, keeping track of how much total exposure each target has, and backing all of it up somewhere safe.

None of that is the reason anyone gets into this. But all of it has to happen, correctly, or you lose data you cannot re-shoot without waiting for the same object to come back around the sky next year. For a long time I did it by hand. This is the story of the thing I built instead, and the honest version of what AI had to do with it.

A team of one

I am not a software company. I am one person who wanted his telescope's paperwork to disappear. What I ended up building, over a few hundred commits, is not a script. It is a real distributed system: a React web app I open in a browser, a Python API server that acts as the hub, a separate agent program running on a Windows PC next to the telescope gear, and a PostgreSQL database holding the whole catalog. It runs on an Intel NUC in a closet and it is in use every clear night.

The AstroTrak dashboard: a Mosaic Projects panel showing per-target frame-goal progress bars, and a Tonight's Best Targets panel listing scored objects with small altitude curves and a horizon filter.
The dashboard. Up top, mosaic projects track how close each target is to its frame goal. Below, "Tonight's Best Targets" ranks what is actually worth pointing at from my backyard right now.

I want to be precise about what AI actually did here, because the honest answer is more interesting than the marketing one. It did not hand me a finished product. It was a build partner: it knew the parts of this I did not (how to structure a WebSocket relay, the astronomy math for whether an object clears my rooftop at 10 p.m., the incantations to drive an image-processing app headlessly), and it never got bored translating "I want the card to only erase after the data is definitely safe" into code that does exactly that. What changed was not that it wrote fast. It was that it shortened the distance between I wish this existed and I use this every night from "learn three new stacks over a year" to "describe it carefully and review what comes back."

The rest of this post is the tour: the boring parts it made disappear, and the two places where it helped most with the astronomy itself.

Automation: the boring parts, gone

The first job was the file shuffle. The imaging controller writes everything to an SD card. After a session, those files need to land on the PC, sorted by target and by night, with a consistent naming scheme, and matched up with the darks and flats (calibration frames that let you subtract out the camera's own noise and the dust on the optics). AstroTrak does this on a button press: it scans the card, works out which files are new, figures out which observation night each belongs to, numbers them in order, copies them into place, and links the right calibration frames to each target.

It also tracks intent. I can set a frame goal for a target, say 200 good frames of the Horsehead, and the dashboard shows me how far along I am and notices automatically when a night's imaging pushed me closer. That sounds small. In practice it is the difference between "I think I have enough of that one" and knowing.

The piece I was most careful about is deletion. Freeing up the SD card is the whole reason you copy data off it, but deleting the only copy of an irreplaceable night is the one mistake you cannot undo. So that path is deliberately paranoid: the system verifies that every file made it across, checking counts and sizes, and only then offers to erase the card. It will show me exactly what it is about to delete before it touches anything. This was the one place I told my build partner to be conservative and slow, not clever.

The one rule I never let automation get casual about. Everything else in this system optimizes for less work. The delete-from-card path optimizes for never losing data, even at the cost of extra steps. Automation is a wonderful servant right up until it deletes the only copy of something. Knowing which parts of your own system deserve that suspicion is most of the craft.

Night planning: pointing the telescope smarter

The second job is deciding what to shoot in the first place. A clear night is a scarce resource, and it is easy to waste one on a target that never actually rises high enough above the trees, or one the moon washes out. This is where the software had to learn some real astronomy, and where AI did more than turn my intentions into syntax.

AstroTrak carries a local catalog of deep-sky objects and, for any given night and my location, works out which ones are actually worth pointing at. That means real calculations: where each object sits in the sky hour by hour, how high it climbs, when it crosses the meridian (its highest, sharpest point), what the moon is doing. It even knows my horizon, not a flat idealized one, because I traced the actual treeline and rooflines around my yard, so it will not suggest an object that technically rises but spends the whole night behind my neighbor's chimney. It pulls the weather forecast so I know whether it is even worth uncovering the gear. And it draws all of this on an interactive sky map.

A night-planning view: altitude-versus-time curves for two targets plus the Moon and the local horizon, with night-start and night-end markers, a 30-degree minimum-altitude line, and an imaging schedule assigning each target a time window.
A night, planned. Each colored curve is a target's altitude through the night; the dotted line is the Moon, the shaded band is below my usable altitude. The schedule underneath slots each target into the window where it rides highest.

The astronomy here is not hard because the math is exotic. It is hard because it is fiddly and unforgiving: coordinate systems, time zones, the difference between when the sky is dark and when your specific target is usable. Getting it right by hand is a research project. Getting it right with a build partner that already knows the standard libraries for this was a few focused evenings instead.

A sky-survey framing preview: a real deep-sky survey image of the Veil Nebula region with a 2 by 2 mosaic grid of camera field-of-view rectangles overlaid, plus camera and telescope selectors.
Framing a shot before the gear is even out. This lays my exact camera-and-telescope field of view over a real sky survey, so I can plan a mosaic and know it will actually fit the target. (Sky view via Aladin Lite.)

Multi-device coordination: four boxes acting like one

Here is the part that surprised me most, because it is the part that looks the most like "real" infrastructure. This is not one program on one computer. It is four separate machines that have to cooperate, and the reason is a hard physical constraint: the web app runs in a closet, but the telescope's files live on a drive attached to a PC in a different room, on a different network, that the closet server cannot touch directly.

Imaging controller SD card PC agent Windows, next to the gear Backend hub API + database NUC in a closet Browser me, planning share ws http The backend never touches the telescope's files directly. It asks the agent, and the agent does every file operation on its behalf.
Four machines, one system. The agent exists because the server in the closet physically cannot reach the drive by the telescope, so every copy, verify, and delete is a message the backend sends and the agent carries out.

The solution is an agent: a small program that runs on the PC by the telescope and does file work on the backend's behalf. The backend and the agent hold a live connection open between them, and the backend sends commands down it, "discover what is on the card," "copy these files," "verify this target," "erase these," while the agent reports progress back in real time so the web app can show a progress bar. The same live-connection trick is what lets the browser show sync progress as it happens instead of making me refresh.

I did not set out to build a distributed system. I set out to solve "the files are over there and the app is over here." But that is the shape of the problem: boxes that cannot reach each other directly, cooperating through messages. Having a build partner that had seen this pattern a thousand times meant I got the good version of it, with reconnect logic and graceful handling of the telescope being powered off, instead of the fragile version I would have hacked together alone.

Data analysis: which photons were worth keeping

Once the files are safely home, the next question is which of them are any good. Not every frame is a keeper. A gust of wind, a satellite streak, the target sinking too low, a focus that drifted, all of it produces frames that will hurt the final image if you stack them in. The traditional fix is to squint at a few hundred frames one at a time. It is exactly the kind of tedious, judgment-heavy work that is easy to do badly at 2 a.m.

So AstroTrak grades them. It drives a real astronomical image-analysis tool to measure each frame on the things that actually matter: how tight the stars are (a blur measurement called FWHM), how round they are (elongated stars mean the mount was tracking poorly), an estimate of the signal-to-noise ratio, how many stars it could even detect, and how high the target was in the sky at the time. Then a scoring engine turns those numbers into a verdict.

The interesting part is that the grading is not one threshold. It is layered, the same shape as a good code review:

LayerWhat it does
Weighted scoreCombines the metrics into one 0-100 number, weighted toward signal-to-noise and star sharpness, since those matter most to the final image.
Hard gatesAbsolute deal-breakers. Stars too elongated, or the target too low in the sky, and the frame is rejected no matter how good the rest looks.
Session-relative outlier checkCompares each frame to the rest of that night's frames and flags the ones that fall well outside the pack, catching the "everything was fine until a cloud rolled through" frames.

Every frame comes out labeled keep, review, or reject, with the reason. I can override any of it, but mostly I do not have to. If that layered idea, a weighted score plus hard limits plus an outlier check against the local batch, sounds familiar from the last post on this blog, that is not an accident. Deciding which telescope frames to keep and deciding whether a smaller language model is good enough turn out to be the same problem: you need an instrument that fails loudly, not a gut feeling.

A frame-grading view: a summary of 50 keep, 3 review, and 11 reject out of 64 frames, and a Quality vs Altitude scatter plot with each frame colored green, yellow, or red against a dashed altitude-warning threshold.
Grading, made visible. Every dot is one frame, scored for quality and plotted against how high the target was in the sky. Frames shot low, left of the "altitude warn" line, score worst and mostly get rejected; the keepers cluster high and sharp on the right.

It also taught me the hobby

Here is the part I did not expect. I knew the recipe already: shoot darks and flats, keep the good subs, stack the rest. What I did not always have, and what AI turned out to be unreasonably good at handing me, was the why one level down, at the depth where it actually changes what you do. A few of the things I understand properly now:

Why total integration behaves the way it does, and why "usable" is the honest number. Photon arrival is Poisson, so the noise in a single sub scales as the square root of the signal. Stack N frames and the real signal adds linearly while the random noise adds in quadrature, so signal-to-noise improves as the square root of N: you shoot four times as long to get twice as clean. That is why 50 good subs genuinely beat 64 with cloud in them, and why AstroTrak reports usable integration rather than how long the shutter was open. The same statistics settle the endless "how long should each sub be" argument, which turns out to have almost nothing to do with the target. You want each exposure long enough that shot noise from the sky background comfortably swamps the camera's read noise (roughly a factor of three over read noise leaves total noise only about five percent above the sky-noise floor). Past that, longer subs buy almost nothing and cost you real data, because now a satellite or a gust ruins a bigger unit of time. The right sub length falls out of your sensor's read noise and your sky brightness, not out of a forum post.

What the sharpness metrics are actually measuring. The grader reports FWHM in arcseconds, not pixels, and the conversion is what makes frames comparable across different nights and gear: image scale is 206.265 times the pixel size in microns, divided by the focal length in millimeters. Get that scale wrong relative to the seeing and you are either undersampling, throwing away resolution the atmosphere was briefly handing you, or oversampling, smearing the same photons across more pixels for no gain; the sweet spot is two or three pixels across a star's FWHM. Eccentricity, the other headline number, measures how elongated the stars are, the square root of one minus the axis ratio squared, and it is the signature of tracking error, tilt, or flexure rather than seeing. Once that clicked, the altitude gate stopped looking arbitrary: a target low in the sky is imaged through a longer, more turbulent column of air, with airmass and extinction climbing steeply toward the horizon, which is exactly the regime where FWHM balloons and the blue end fades first (Rayleigh scattering scales as wavelength to the fourth power). The gate is not a preference. It is physics.

Why rejection works, and why the grader uses a stubborn kind of statistic. When you integrate a stack you do not take a plain average, you sigma-clip: at each pixel you discard the values sitting too many deviations from the local median. That alone kills the transient stuff, satellite trails, aircraft, and cosmic-ray hits, because each strikes a given pixel in only one frame. The subtler win comes from dithering, nudging the mount a few pixels between subs, so that anything fixed to the sensor (a hot pixel, a warm column, the faint walking-noise pattern) falls on different sky each frame and finally reads as an outlier the clip can reject too. The frame grader borrows the same instinct one level up: its session-relative check flags entire frames using the median absolute deviation rather than the standard deviation, precisely because the standard deviation is inflated by the very outliers you are trying to catch, while the MAD shrugs them off. I had been sigma-clipping for years by ticking a checkbox. Understanding why it needs dithering is what made me trust it.

Why my backyard images are not just gray smudges. The nebulae in this post are narrowband, and the physics there is genuinely elegant. Excited hydrogen, oxygen, and sulphur emit at sharp, specific lines (hydrogen-alpha at 656 nanometers, oxygen-III at 501, sulphur-II at 672), while light pollution is a broad continuum smeared across the whole spectrum. A three-nanometer filter passes most of the emission line and almost none of the continuum, so it does the one thing no amount of processing can: it removes the skyglow optically, before the photons ever reach the sensor. Mapping those three isolated lines onto red, green, and blue is what lets a suburban backyard produce the gold-and-teal, Hubble-palette frames you have been scrolling past.

All of it lands back on the thing this blog is actually about. The method here never changes: measure, do not speculate. But you cannot measure well what you do not understand, and every one of AstroTrak's thresholds is now a number I can derive instead of copy. The altitude gate is airmass. The signal-to-noise weighting is Poisson statistics. The outlier rejection is a robust estimator picked for a specific reason. AI helped me build the instruments, and, less obviously and far more valuably, it helped me understand the physics well enough to trust what they tell me. That is the quieter version of AI-assisted work I keep coming back to, here and in the day job: not the model doing the thinking for me, but the model closing the gaps in my own understanding fast enough that I can do the thinking better.

The galleries an LLM writes

Most of what AstroTrak does is unglamorous automation. This part is the one indulgence, and the place where that same explaining gets wired straight into the app. When a target is finished and processed into a final image, it lands in a gallery. And a vision-capable language model looks at the actual image and writes it up: what the object is, roughly how far away, how big it appears, what you are actually looking at.

The AstroTrak gallery: a grid of finished astrophotography images, each with a favorite heart, beneath a navigation bar with Dashboard, Explore, Plan, Lists, Equipment, Sky Map, Sync, Gallery, and Settings.
The gallery. Finished targets land here as a grid. The nav bar across the top is the rest of the app, from planning to sync to equipment.
What the camera gives me
target_10 · Light_NGC2024_300.0s_Bin1_NIGHT3_225118_0009.fit · (× 214 more)
What the gallery shows

"The Horsehead Nebula (Barnard 33), a dark column of cold dust silhouetted against the red glow of hydrogen in the emission nebula IC 434, in the constellation Orion, roughly 1,375 light-years away. Below and left, the Flame Nebula (NGC 2024) burns gold around a young star cluster hidden behind its own dust lanes. A classic winter target, and a demanding one: the dust is faint and the bright star Alnitak sits just out of frame, ready to flare across the optics."

I wired up three different providers for this, so I could compare how each one describes the same image, and there is one design choice I am quietly proud of. The model is told to identify the object from the image itself and not simply trust the name I filed it under. If I fat-fingered a target name, or framed something unexpected, I want the description to catch it, not to confidently repeat my mistake back to me. It is a small thing to ask for, but it is the difference between a caption that describes reality and one that describes my filing system.

A gallery detail view of NGC 7000, the North America Nebula, showing its coordinates and a multi-paragraph AI-generated description covering the emission nebula, its distance, the hydrogen-alpha emission, and the dark dust lanes.
The real thing, running on my North America Nebula. Coordinates up top, and underneath, the writeup a vision model produced from the image itself.

I will be honest about one thing: the models can get a little breathless. This one opened with "this stunning astrophotography image captures NGC 7000," which is exactly the sort of phrase I spend my own edits deleting. I keep the descriptions anyway, because the astronomy underneath them is accurate and genuinely useful, and because a caption that is a touch purple is still better than a filename.

What AI actually changed

I want to close honestly, because it is easy to overstate what AI actually did here.

AI did not write flawless code. This project has scars. There is a file-naming convention so specific that getting it slightly wrong caused the same bug more than once, and it now has a warning in capital letters at the top of my project notes. There was a memorable night when a sync silently froze partway through with no error at all, and finding out why meant reading logs at midnight. Building with an AI partner does not remove the debugging; it moves it. You spend less time typing and more time reviewing, deciding, and being the adult in the room about the parts that matter.

But here is what genuinely changed. A few years ago, "a distributed system that coordinates my telescope, grades my images, and captions my galleries" would have been out of reach for a solo hobbyist, or a multi-year project that ate the hobby it was supposed to serve. Instead it is a thing that exists, that I use, that I understand well enough to fix. The advantage was not that AI is a genius. It is that it is a tireless, knowledgeable partner for exactly the parts I found tedious or unfamiliar, which freed me up for the parts I am actually here for.

The real dividend
Every hour the software saves me sorting files, verifying copies, and squinting at frames is an hour I get back for the actual hobby. The best outcome was not the code. It was more clear nights spent looking up, and fewer mornings spent in a file browser.

The telescope still does the beautiful part. It always did. I just do a lot less paperwork now.