allsky_meteordetect¶
A temporal meteor detection module for Allsky.
Unlike single-frame streak detectors, this module tells meteors apart from aircraft and satellites — the thing single-frame detectors fundamentally cannot do.
Why another meteor module?¶
The detector, historically, bundled with Allsky — and the one in indi-allsky — look for straight lines in a single image (Canny → Hough). That approach cannot distinguish a meteor from an aircraft, a satellite, a cloud edge or a power line, because in one frame they all look like a bright streak. The result is either constant false alarms or a threshold so high that real meteors are missed.
The physical thing that makes a meteor a meteor is transience: it is present in one frame. A satellite or aircraft moves across several consecutive frames. This module uses that.
How it works¶
frame N-1, frame N ─► difference ─► threshold ─► soft mask
└► connected components + PCA ─► streaks (length + elongation)
│
▼ classify against neighbouring frames
streak continues a PROGRESSING track → satellite / aircraft (rejected)
streak repeats at the SAME location → disappearance of a meteor (de-duped)
isolated, transient streak → meteor candidate → confirmed next frame → saved
Key points:
- Frame differencing removes stars and static clouds, so — unlike single-frame detection — the sky background does not generate lines.
- PCA streaks (connected components + principal-axis length/elongation) are robust against gaps and reject blobby cloud brightening.
- Deferred confirmation: a candidate is held for one frame and only saved if the next frame shows no progressing continuation — so a satellite is rejected even on its first appearance (a live detector has no future frames).
- Soft, feathered mask edge (a trick borrowed from indi-allsky) so the mask boundary itself is never detected as a streak.
- Cloud and twilight gates skip frames that are too bright or changing too much.
Requirements¶
- Allsky 2025 or later, installed with the Module Package Manager. (The module also runs on Allsky 2024, copied in by hand — see the module's own repository.)
- Python packages already present in the Allsky virtualenv:
opencv-python,numpy; the calibration tool also usesscipy.
Installation¶
1. Install it from the WebUI's Module Package Manager (Meteor Detection (temporal)).
It installs the module into ~/allsky/config/myFiles/modules/ and its bundled files —
allsky_fisheye.py, stars.json and the tools below — into
~/allsky/config/myFiles/modules/moduledata/data/allsky_meteordetect/.
The tools run with Allsky's Python; the examples below use
PY=~/allsky/venv/bin/python3
T=~/allsky/config/myFiles/modules/moduledata/data/allsky_meteordetect/tools
2. Add it to the night flow. Open the Module Manager in the WebUI and switch to the
night flow — the module declares "events": ["night"], so it deliberately does not
appear under day. “Meteor Detection (temporal)” is then in the list of available modules.
Allsky's own single-frame module is called plain “Meteor Detection” — that is not this
one. Running both is possible but redundant; if you do, keep this one directly after
Load Image so the other's debug annotations never end up in the saved meteor images.
3. Put it directly after Load Image. The module saves the frame as it stands at its position in the flow, so anything running before it — the overlay, for instance — ends up in the saved meteor images.
Building a detection mask¶
Trees, buildings and the lens vignette should be excluded, or wind-blown leaves
produce endless false positives. tools/build_mask.py builds the mask
automatically from your own daytime images:
$PY $T/build_mask.py \
--images ~/allsky/images \
--nights 20260703 20260704 20260705 \
--out meteor_mask.png \
--preview preview.jpg
Copy meteor_mask.png into ~/allsky/config/overlay/images/ and select it as the
module's Detection Mask.
You can also build a mask manually using the built in mask editor
Auto created masks cannot be edited
Unlke masks created in the mask editor auto generated masks cannot be edited
A building as bright as the sky is found by its outline. Averaged over many
frames the sky is smooth, while buildings, trees and poles keep sharp edges, so the
tool fills the sky outward from the image centre without crossing such an edge.
Whatever it can't reach, that rises from the edge of the image and has some area to
it, is an obstruction, however bright. Overlay text isn't: it is only thin strokes.
--no-edges turns this off. For anything it still misses, paint it black in the
mask, for example with the Mask Editor in Allsky's Overlay Editor (Expert mode),
or in any image editor.
Method. Obstructions are persistently dark silhouettes. For every pixel the tool measures, across many daytime frames, how often it is markedly darker than the sky (referenced to the bright image centre). Sky is rarely dark, trees almost always are — a far more robust separator than brightness or texture, both of which fail because tree interiors are smooth and averaging washes out their texture.
Configuration¶
| Setting | Default | Meaning |
|---|---|---|
| Detection Mask | meteor_mask.png |
White = sky to analyse, black = ignore |
| Min Streak Length | 50 px |
Minimum streak length — the main lever against short star artifacts |
| Difference Threshold | 22 |
Brightness increase over previous frame to count as “new” |
| Min Elongation | 5.0 |
Length/width ratio (rejects round star blobs and clouds); real meteors here measured ≥7 |
| Max Streak Area | 6000 px |
Larger regions = cloud brightening |
| Cloud Skip | 2.0 % |
Skip frame if more than this share of sky changed |
| Mask Edge Feather | 35 px |
Soft mask fade so the edge is not detected |
| Reject Dashed Trails | on | Reject a long streak broken into many bright/dark segments — a tumbling satellite or strobing aircraft |
| Dash Segments | 10 |
Segment count that marks a streak as dashed (real meteor ≤5, a dashed satellite scored 19) |
| Dash Min Length | 120 px |
Only test streaks at least this long for a dashed pattern; short meteors are exempt |
| Reject Fragmented Trails (arm) | off | Arm the fragmented-trail veto. Off = shadow mode: the collinear-fragment metric is measured and logged (frag_n/frag_ext, frag-shadow) but nothing is vetoed. Before arming, check what your real meteors score — see below |
| Fragment Segments | 5 |
Collinear diff fragments beyond a streak's ends that mark it as the head of a fragmented dashed trail. Real meteors are not always 0: here they reached 4, satellite trails 3–11 — see below |
| Fragment Min Length | 120 px |
Only test streaks at least this long for a collinear fragmented tail |
| Reject Edge Glow (arm) | off | Reject a long, fat streak with both ends on the mask border — horizon or lens-rim glow leaking through the feathered edge. Off = shadow mode: logged as edge-shadow, and every saved meteor records edge_d. See below |
| Edge Margin | 50 px |
Both ends closer than this to the mask border count as "on the border" |
| Edge Glow Max Elongation | 10 |
Only fatter streaks can be edge glow (glow here 5–8, real streaks at the border over 10) |
| Edge Glow Min Length | 80 px |
Only longer streaks; spares a short meteor vanishing behind a tree at the border |
| Reject Satellites/Aircraft | on | Discard progressing tracks |
| Scintillation Guard | on | On very clear nights, if a frame has more than Scintillation Max streaks keep only a clearly dominant one |
| Scintillation Max | 8 |
Streak count that marks a scintillation-dominated frame |
| Reject Recurring Positions | on | Reject a spot that keeps firing across frames (scintillation/bloom/trailed star/reflection); a real meteor appears once |
| Recurrence Frames | 3 |
Earlier frames at the same spot (~55 px, ~25 min) needed to call it recurring — keep ≥3 |
| Reject Star-Trail Orientation | on | Reject a streak parallel to the local diurnal star-trail direction (needs the fisheye calibration); fireballs >130 px exempt |
| Star-Trail Tolerance | 12° |
How close to the trail direction counts as a trailed star |
| Reject Bright-Star Scintillation | on | Reject a short streak sitting on a catalogue bright star — a star twinkling brighter between frames makes a compact diff blob at its position that mimics a meteor. Needs the fisheye calibration + stars.json; fireballs >130 px exempt |
| Star-Match Radius | 16 px |
How close a streak's centre must be to a projected catalogue star to count as that star. Size it to the calibration RMS (~4–6 px) plus a few px of blob offset |
| Star Magnitude Limit | 5.0 |
Only stars brighter than this are used; fainter stars rarely brighten enough to trigger, and including them risks vetoing a real meteor |
| Glare Spike Filter | on | Reject a streak that points straight away from a bright, saturated light (the Moon, a street light): its lens and dome spikes turn as it moves and show up in the frame difference |
| Glare Reach | 5 radii |
How far from the light, in radii of its saturated disc, a streak can be a spike |
| Glare Angle Tolerance | 6° |
How close to the direction away from the light a streak must point |
| Traffic Filter (arm) | off | Reject a streak that lies along the track a known satellite or aircraft took during that exposure. Needs the Sky Traffic module in the same flow. Off = shadow mode: logged as traffic-shadow with the name, and the saved meteor records traffic. See below |
| Great-Circle Filter (arm) | off | Reject a streak that lies on one great circle with a streak in the image before or after, however far apart: a satellite or aircraft that crossed the highest part of its path between two exposures. Needs the fisheye calibration. Off = shadow mode: logged as circle-shadow, and the saved meteor records circle. See below |
| Upload to Remote Website | on | Upload each hit via Allsky's upload.sh |
| Save Rejected-Candidate Crops | on | Save a labelling crop of every rejected streak (into vetoed/) as the negative examples for a future classifier |
| Browse in the Allsky WebUI | on | Also file each meteor under images/<day>/meteors/ so the WebUI's Meteors page can browse it day by day — see Output |
| Save Marked Copy | on | Extra copy with brackets around the streak, plus its thumbnail (the WebUI's Use Marked Meteors option needs both) |
Clear nights are the hard case. Star scintillation and slight frame shake make bright stars flicker into short streaks that share a meteor's appear-then-disappear signature. The defenses are, in order of impact: geometry (a real meteor is long and thin — raise Min Streak Length / Min Elongation if a clear night still produces false positives), the scintillation guard, and same-location confirmation. There is no single perfect filter; tune the geometry to your sky. With a fisheye calibration, the bright-star scintillation veto adds a targeted defense: it rejects a short blob that sits exactly on a catalogue bright star, which is what a twinkling star produces — geometry alone can't tell that blob from a faint short meteor, but its position on a known star can.
Fisheye calibration & geometric radiant matching (optional)¶
With a calibrated fisheye projection the module can attribute each meteor to the shower whose radiant its streak actually points back to — real geometry, not just "which showers are active tonight".
tools/calibrate_fisheye.py fits the camera model (optical centre, radial
distortion, rotation, handedness) from clear night frames. It identifies the stars
itself; give it two frames a few hours apart:
$PY $T/calibrate_fisheye.py image-A.jpg image-B.jpg --out ~/allsky/config/myFiles/modules/calibration.json --preview check.jpg
If it can't find a clear match (clouds, a short exposure, or two solutions that fit about equally well) it says so, writes nothing, and lists the bright stars that were up. Then identify two bright stars in the first frame and give their pixel positions:
$PY $T/calibrate_fisheye.py image-A.jpg --list-stars # which bright stars were up, and where
$PY $T/calibrate_fisheye.py image-A.jpg image-B.jpg --star vega 2828 1213 --star altair 2527 1976 \
--out ~/allsky/config/myFiles/modules/calibration.json --preview check.jpg
Read the two stars' pixel positions in any image viewer that shows the cursor position,
on the full-size frame. Both tools only read Allsky's files; calibrate_fisheye.py
writes nothing but --out and --preview, and align_overlay.py changes nothing
without --apply. align_overlay.py uses an installed calibration or the one given
with --calibration, and refuses a calibration made at another site.
Two stars fix centre, scale and rotation. To find them without help the tool does what astrometry does: every pair of bright points in the frame against every pair of bright catalogue stars gives a guess, scored by how many other bright stars then land on a point. It refines the best guesses and accepts the winner only if it fits at least 12 stars to better than 0.6° and no different solution comes within 70 % of its star count. It tries the frames in turn and starts from the first clear winner.
From there the tool computes where every bright catalogue star (Vmag ≤ 3) stood at each
frame's time and place, looks for it in a window around that prediction, and fits the
lens by least squares — shrinking the window from 100 to 20 px as the model improves. A
star is used only when the brightest point in its window clearly outshines everything
else there, so it cannot be confused with a neighbour. Clicks 10 px off, or any of three
different star pairs, all converge to the same solution. --seed calibration.json
starts from an earlier calibration instead.
On the author's camera, 21 night frames from 8 nights and one day frame: 10 night frames were identified automatically, some with the full Moon up or thin cirrus. From each single frame the lens came out within 0.3° of the calibration below over most of the sky. The other 11, overcast or only 2 s exposures, and the day frame were refused. None was identified wrongly. Run automatically on the two frames below, the tool gives the same calibration to 0.5 px.
Here, from two frames four hours apart: 77 bright stars, 0.28° RMS (5 px) from the
zenith down to 15° altitude. Check --preview: every green circle should sit on a star.
Earlier calibrations were wrong away from the zenith. Until v0.5.8 the fit matched stars to their nearest detection in a deep catalogue. In a dense Milky Way field a wrong model still finds a neighbour within a few pixels for almost every star, so it reported 0.15° over 317 stars while placing stars at 30° altitude 470 px off. If you made a
calibration.jsonwith an earlier version, make it again.
allsky_fisheye.py then provides pixel_to_altaz / altaz_to_pixel and
match_radiant: a meteor travels along a great circle whose backward extension
passes through its radiant, so the module tests which active shower's radiant lies
on that circle (and above the horizon).
The same projection also drives the bright-star scintillation veto: a bundled
Hipparcos subset (stars.json, Vmag < 6) is projected to pixels for each frame's
time, and a short candidate that lands within Star-Match Radius of a catalogue
star brighter than Star Magnitude Limit is rejected as a twinkling star rather
than a meteor. stars.json is bundled; without a calibration the veto is silently
skipped. Accuracy matters: size the radius to
your calibration RMS — regenerate calibration.json if bright stars drift off their
catalogue positions, or the veto will either miss scintillation or clip real meteors.
Put calibration.json beside the module, in ~/allsky/config/myFiles/modules/ —
not in the data folder, which the package manager replaces on every update. Without it,
radiant matching is silently skipped. The calibration is per-camera — make it for
your own camera with calibrate_fisheye.py; the package does not ship one.
Output¶
Each hit is written to two places, because the website and the WebUI want different layouts.
Website folder (meteors/, the source for the remote upload and the per-night
charts):
meteors-<timestamp>.jpg, named like the image it is in (image-<timestamp>.jpg, the start of the exposure), plus a thumbnail inmeteors/thumbnails/— picked up automatically by Allsky's meteor gallery page. The gallery image keeps the meteor's true colours, untouched.marked/meteors-<timestamp>-marked.jpgand its thumbnail inmarked/thumbnails/, when Save Marked Copy is on. They are in a subfolder so the Website's Meteors page, which lists every image in the folder, doesn't show each meteor twice. Marked copies saved by earlier versions next to the gallery images can be deleted:rm ~/allsky/html/allsky/meteors/*-marked.jpg ~/allsky/html/allsky/meteors/thumbnails/*-marked.jpg(the WebUI's copies underimages/<day>/are separate and stay).meteors.json— a rolling log of{time, file, length, angle, elong, peak, frag_n, frag_ext, showers, radiant}for later statistics (showers= active by date,radiant= geometric attribution if calibrated,frag_n= collinear-fragment shadow metric).- Optional remote-website upload of each hit.
Allsky WebUI folder (under images/<day>/, when Browse in the Allsky WebUI
is on) — the layout the WebUI's Meteors page reads:
images/<day>/meteors/meteors-<timestamp>.jpg
images/<day>/meteors/meteors-<timestamp>-marked.jpg (Save Marked Copy)
images/<day>/meteors/meteors-<timestamp>.json
images/<day>/meteorsthumbnails/meteors-<timestamp>.jpg
images/<day>/meteorsthumbnails/meteors-<timestamp>-marked.jpg
meteors-<timestamp>.json— a sidecar holding only that image's streaks (same fields as the rolling log). The WebUI reads one file per image rather than scanning a rolling log, so the sidecar exists alongside it, not instead of it.- Thumbnails sit in the sibling
meteorsthumbnails/, not in athumbnails/subfolder ofmeteors/— that is where the WebUI's Meteors page reads them. The website folder above keeps its ownmeteors/thumbnails/, which its gallery page expects.
The day folder follows Allsky's own convention: it is the night's evening date
(DATE_NAME, 12-hour offset), while the file name carries the real timestamp. A
meteor at 03:15 on the 19th therefore lands in images/20260918/ as
meteors-20260919031514.jpg — exactly like Allsky's own image-*.jpg files.
Variables¶
Each frame publishes its result as Allsky variables — in the WebUI's variable list, usable in overlays, and passed on by modules such as MQTT. The first four use the same names as Allsky's built-in meteor module, so an overlay or a Home Assistant feed built on those keeps working when you switch to this module:
| Variable | Meaning |
|---|---|
AS_METEORCOUNT |
meteors confirmed on this frame |
AS_METEORIMAGE |
file name of the meteor image saved on this frame, empty if none |
AS_METEORIMAGEPATH |
full path of that image under images/<day>/meteors/ |
AS_METEORIMAGEURL |
WebUI URL of its thumbnail in meteorsthumbnails/ |
AS_METEORMOVING |
streaks rejected as satellites/aircraft on this frame |
AS_METEORVETOED |
streaks rejected by the other filters on this frame |
One difference to the built-in: its image values point at the frame it analysed. Here a meteor is only confirmed one frame later, so they point at the saved meteor image instead. With Browse in the Allsky WebUI off, the path points into the website folder and the URL stays empty.
Run only one of the two meteor modules: both publish AS_METEORCOUNT, and whichever
runs last wins.
Why true colour matters¶
Meteor colour encodes composition — green from magnesium/oxygen, yellow/orange from sodium/iron, blue-white for fast trails. The gallery image is therefore never painted over; the optional marked copy draws brackets around the streak, never on it.
Arming the fragmented-trail veto¶
The veto ships in shadow mode: it measures frag_n on every detection and writes it to
meteors.json, but rejects nothing. That record is what tells you whether arming it is
safe. On this camera, two months of saved detections (107) gave:
| frag_n | detections | inspected by eye |
|---|---|---|
| 0–2 | 96 | — |
| 3 | 7 | 4 satellite trails or artefacts, 1 unclear, 2 real meteors |
| 4 | 2 | 1 dawn contrail, 1 real meteor |
| 6, 11 | 2 | both satellite trails |
So at the original threshold of 3 it would have removed seven false detections and three
real meteors; the scores simply overlap. At 5 it removes the two clearest satellite
trails and no meteor, which is why frag_min now defaults to 5. Check your own record
before arming — this lists every long detection with its score:
python3 -c 'import json, os; [print(e["time"], "frag_n=%s" % e["frag_n"], "len=%d" % e["length"]) for e in json.load(open(os.path.expanduser("~/allsky/html/allsky/meteors/meteors.json"))) if e.get("frag_n", 0) >= 3 and e["length"] >= 120]'
and tools/replay_night.py <night> --set frag_filter=true shows what it would have
rejected on a saved night.
Arming the edge-glow veto¶
About a quarter of the detections saved here touched the border of the detection mask. Looking at them, two kinds were mixed together:
- Real streaks — satellites and meteors — cross the border: one end sits at it, the other points into the sky, and the trail visibly carries on behind the mask. They are thin: elongation 10 to 47.
- Edge glow — the brightening rim of the fisheye, horizon glow, a lit tree edge — lies along the border as a diffuse band. Both ends sit on the border, and it is fat: elongation 5 to 8.
The veto rejects the second kind: at least 80 px long, elongation below 10, and both ends within 50 px of the border. Over two months of detections here that matched 9, all inspected and all edge glow, and not one real streak. The result did not change between 50 and 60 px, or between 70 and 80 px minimum length. The length limit is there for one case in particular: a short, bright streak that disappears behind a tree right at the border, which may well be a meteor and is left alone.
It ships in shadow mode, like the fragmented-trail veto. Every saved meteor records
edge_d, the distance of its farther end from the border, so you can see what it would
catch on your sky first — or replay a night with --set edge_filter=true.
Arming the great-circle filter¶
The moving-track filter connects a streak with one in the next or previous image only if the two are at most 400 px apart. A satellite or aircraft that passes high overhead can cross the highest part of its path in the pause between two exposures. It then leaves two streaks far apart, in different parts of the sky, and each looks like a meteor. A user's camera saved one this way: 26° in the north-west in one image, 24° in the east in the next, 66° apart.
Seen from the camera, anything that moves in a straight line through space moves along a great circle. With a fisheye calibration the filter fits one great circle through both streaks together, through their ends and centres. It rejects the candidate when every point lies within 1.2° of that circle and the other streak lies further along it, at most 120° away. Both streaks must be at least 6° long. Two short streaks almost always fit some common circle: without that limit, 4 of 10 real meteors on one night matched a short streak in the next image.
Replayed over 7 nights on the author's camera (14 to 27 September), the filter matched 8 of the 26 streaks the module would have saved as meteors. All 8 were long, evenly bright satellite trails, including two pairs where the same object is in two images in a row. None of the other 18 was touched. On the user's two streaks it matches; with the second streak shifted sideways by 100 px (about 3°) it no longer does.
Short pieces. A camera that takes a 10 s exposure about once a minute sees an
aircraft as short pieces of 3–5°, far apart. Those are too short for the circle alone.
They still count when the gap between the two pieces matches the object's speed: the
faster piece's length over its exposure, times the time between the two exposures,
must give 0.5 to 2 times the gap. (The faster piece gives the speed, because a piece
that fades or leaves the sky covers only part of its exposure.) The filter works out
which image each piece is in from the two differences, and it takes the exposure
start and length from Allsky (AS_TIMESTAMP, AS_EXPOSURE_US).
Tested on a user's camera (6–15 s exposures every 66 s, frames before and after each saved meteor), it linked a short "meteor" to the bright dashed aircraft trail in the image before it (gap 0.9 times the expected distance), and a later piece of another aircraft. The two real meteors in those sequences were not linked; one sat on a common circle with an unrelated piece, but its gap was 3.5 times too large. On the author's 7 nights it marked no additional streak and no real meteor.
It ships in shadow mode like the others: check the circle entries of your saved
meteors before you arm it.
Arming the traffic filter¶
The moving-track filter only catches a satellite or aircraft that leaves a streak in two frames in a row. A short glint of a satellite, a trail that ends in the Earth's shadow, or an aircraft whose lights don't blink appears in one frame only, like a meteor.
The Sky Traffic module knows where every catalogued satellite, and with an ADS-B
source every aircraft, was during each exposure. Put it in the same night flow. It
writes the tracks of the last images to allsky_skytraffic_tracks.json in Allsky's tmp
folder, and this filter checks every candidate against the tracks of the image it
appeared in. A candidate is on a track when its centre lies within Sky Traffic's
Match tolerance (0.6°) of the part the object covered while sunlit, it runs along
it within 8°, and it is no longer than that part. Without Sky Traffic, or without a
lens calibration, nothing happens.
Tested on the author's camera, 25 meteors saved from 15 to 27 September and 216 rejected streaks:
- One "meteor", 61 px long at 05:37 on 27 September, lies 4.7 px from the track of the weather satellite FENGYUN 3D and runs along it. The satellite's track in that exposure was 257 px long, so the streak was a short glint.
- To see how often a streak lies on a track by chance, the same check ran with the times shifted by ±5 to ±30 minutes (9 shifts). About 400 satellite tracks cross the image in each exposure, and 3 of the 225 shifted meteor checks matched, each 1–9 px from a Starlink or Qianfan track. One of them was a 214 px streak on a 150 px track, which the length rule rejects; that leaves 2 of 225, about 1 in 110 meteors.
- A replay of that morning with Sky Traffic running rejects the glint as
satellite,FENGYUN 3D, and in shadow mode saves it with"traffic": "FENGYUN 3D".
So on this sky about 1 in 110 real meteors would also be rejected. That is why it ships
in shadow mode: look at the traffic entries of your saved meteors, and at the crops in
vetoed/, before you arm it.
Noisy cameras and curved streaks¶
Two fixes came from a user's RPi HQ camera in Australia (30 s at gain 16, a long fireball near the edge of a 1.56 mm lens):
- Sensor noise. About 5.7 % of single pixels changed by more than the Difference Threshold between any two frames. The cloud gate took that for cloud and skipped every frame, and the noise merged with the fireball into a shapeless blob. When a 3×3 median removes most of what is over the threshold (single pixels do, clouds, streaks and twinkling stars don't), the frame is treated as noisy and analysed after the median: 5.7 % became 0.8 %, and the 563 px fireball was found with no noise streaks around it. Other frames are analysed as before, so a 1 px meteor on a normal camera isn't thinned out.
- Curved streaks. The dash count sampled along the straight line between a streak's ends. The fisheye bent the fireball up to 12 px away from that line in the middle, the dip looked like a gap, and noise at its edges made 18 "dashes". The streak's bend is now fitted from its pixels and followed.
Replaying 7 of the author's nights gives exactly the same meteors and rejections as before. On the user's sequences, the fireball and a meteor from a second camera are saved; an aircraft, insects and moonlit cloud are not.
Testing without waiting for a clear night¶
Meteors are rare and clear nights rarer, so "is it working, are my settings right" can
otherwise take weeks to answer. tools/replay_night.py runs the detector over a night
Allsky has already saved and reports what it would have confirmed and rejected, and why:
$PY $T/replay_night.py 20260917 # a night folder under images/
$PY $T/replay_night.py 20260917 --compare # vs. what was saved live that night
$PY $T/replay_night.py 20260917 --set min_length=60 # try a setting without changing it
It tests the installed module with the settings of your night flow — the same code and the same fisheye calibration that run live — and only the frames Allsky would have treated as night. Everything happens in a sandbox folder it prints at the start: no detector state, no settings, no website and no overlay variables are touched, and nothing is uploaded. The marked copies it saves show exactly what was detected.
How close it gets. On 2026-09-17 here, the replay found 4 of the 6 meteors the module saved live that night and reported 3 more, which on inspection were two satellite trails and a cloud wisp. So it is somewhat more eager than a live night, for two reasons:
- It replays the images Allsky saved, which have the overlay on them and have been JPEG-compressed once more. Live, the module sees the frame before the overlay module runs (with this module right after Load Image).
- The detector's memory of recurring hot spots starts empty, as on a new install.
That makes it a good tool for comparing settings on the same night, and for checking that detection works at all — not an exact re-run of a live night.
Use --compare to see which meteors were found live, in the replay, or both. Live stamps
record when a frame was processed, replay stamps when it was captured, so the tool
pairs a live stamp with the latest replayed meteor up to one maximum exposure plus a
margin before it.
Aligning the Website's constellation overlay¶
Allsky's documentation calls aligning the constellation overlay "a trial-and-error effort
that takes time": guess the overlay's size, nudge its offsets, rotate it, reload, repeat.
With a fisheye calibration (see above) there is nothing left to guess.
tools/align_overlay.py computes all five settings — and the projection that fits your
lens best — from the plate solve:
$PY $T/align_overlay.py # print the settings, change nothing
$PY $T/align_overlay.py --preview image-XXXX.jpg # mark stars vs. overlay on a night frame
$PY $T/align_overlay.py --apply # write them into the local Website
$PY $T/align_overlay.py --config ~/allsky/config/remote_configuration.json # remote Website
How it works. The overlay is drawn by virtualsky, whose zenith-centred projections
are polar (equidistant), fisheye (equisolid — Allsky's default) and ortho. The
calibration's centre and rotation carry over exactly. Its radial law generally matches
none of the three, so the tool fits the radius over the sky your camera actually sees,
tries all three projections, and reports the error that remains.
How good it gets. Here the lens turns out to be close to equisolid, so Allsky's
default fisheye projection fits best: 0.30° RMS over the visible sky, under 0.7°
over 95 % of it, 1.3° at worst near the edge (polar 1.2°, ortho 2.5°). So for this
lens virtualsky needs no new projection, only the right size, offsets and rotation.
Verified in a browser, against real stars. The real virtualsky.js, run in headless
Chromium with these settings, puts the bright stars of a frame from 2026-09-17 0.46°
RMS from where they actually are in the image (measured star centres, not the model).
One limit: virtualsky always draws east on the left. A calibration with east on the
right (flip: +1) cannot be matched by any setting; the tool says so instead of
writing something wrong.
Credits & inspiration¶
- Allsky by Thomas Jacquin and the Allsky team.
- indi-allsky by Aaron Morris — the feathered-mask trick and the SQM/chart approach on the roadmap are inspired by it.
- Built for astronomy.garden.
License¶
MIT — see the module's repository.