Blind, full-frame astrometry for fisheye and other extreme wide-field images
Peter Thejll and Chris Flynn
WIDE-FIELD SOLVER finds point-source stars directly from an image, identifies them against a catalogue, and fits the Barghini O/Z fisheye model across the whole detector. The stellar solution is seeded without an observing site, camera orientation or image timestamp.
MMT Observatory colour FITS image. The labels are generated after the astrometric fit; the display stretch does not change the native scientific samples.
- detects ordinary, undersampled, broad and saturated point sources;
- bootstraps a blind sky identification with
tetra3; - fits the Barghini fisheye camera model in angular sky coordinates;
- propagates Gaia proper motions and tests a stellar epoch;
- measures instrumental multi-channel photometry and a provisional image-derived zenith/extinction constraint;
- searches for planetary position matches in a separate, explicitly labelled stage; and
- saves an illustrated PDF report, machine-readable tables, provenance and an append-only SQLite record.
The v0.9.0 solver was run on one MMT Observatory colour FITS image dated 2026-01-15. It obtained:
| Measure | Result |
|---|---|
| Matched stars | 1,380 |
| Astrometric RMS | 0.3267 px |
| Median residual | 0.2350 px |
| 90th-percentile residual | 0.4964 px |
| Angular RMS | 2.502 arcmin |
These are fitted residuals, not an independent withheld-star accuracy test. The result is nevertheless directly comparable in detector units with the approximately 0.33-pixel whole-night result reported for the MMTO camera's operational calibration.
The tested platform is Linux. Install
uv, then run:
git clone https://github.com/hotblack43/WIDE-FIELD-SOLVER.git
cd WIDE-FIELD-SOLVER
uv sync --project v16 --frozen
./go16.sh /full/path/to/image.fitsCompressed FITS input is accepted directly:
./go16.sh /full/path/to/image.fits.bz2Canon CR2 input is decoded from the native 14-bit Bayer sensor data, not its embedded JPEG preview:
./go16.sh /full/path/to/image.cr2Shell-expanded wildcards process a batch sequentially with the same options:
./go16.sh raw_allsky_samples/mmto/mmto-skycam/*/*.fits.bz2 \
--results-dir /path/to/resultsEach image gets its own run directory. A failed image does not prevent later images from running; the final batch summary and exit status report whether any failed. An unmatched wildcard is rejected before analysis starts.
Each run gets a unique directory. The command prints the location of
analysis/report.pdf, the run log and stars.sqlite when it starts and again
when it finishes.
| If you want to… | Read… |
|---|---|
| install and solve a first image | Quick start |
| understand the fitted model | Method |
| interpret reports and output files | Results and outputs |
| see what is validated and what remains provisional | Scientific status |
| choose among preserved versions | Version guide |
| see every launcher and advanced option | Complete reference |
The documentation source is configured for MkDocs/Read the Docs. Build it locally with:
uvx --with mkdocs-material==9.7.7 mkdocs==1.6.1 build --strictThe code distinguishes a successful plate solution from an identified physical zenith or observation epoch. Instrumental count rates are not calibrated fluxes. Metadata is not allowed to seed or tune the stellar astrometric fit; when used later for planetary analysis it is labelled as metadata-conditioned. See the durable scientific goal and the detailed v0.16.0 feature inventory.
The project-authored software is copyright © 2026 Peter Thejll and Chris Flynn
and is available under the BSD 3-Clause License. You may use, modify
and redistribute it under those terms. If you use WIDE-FIELD SOLVER in
scientific work, please use the repository's CITATION.cff;
GitHub's Cite this repository menu supplies APA and BibTeX forms.
For a current repository checkout, go16.sh is the recommended launcher. The
latest packaged GitHub release remains v0.11.0; its complete go11.sh runtime
is frozen by docs/v11-runtime.json. Historical solvers remain available for
reproducible comparison and are protected by recorded manifests and regression
tests.
The BSD licence covers project-authored software, not third-party code, catalogues or images. Those materials retain their own terms. Read NOTICE.md before redistribution.
Analyses and publications using MMTO all-sky-camera data should acknowledge the MMT Observatory. We thank Tim Pickering and the Observatory for maintaining the camera and making its colour FITS archive available.
download_samples.py lists archive metadata, applies deterministic cadence and
the absolute file cap, and only then downloads selected originals. The default
cadence is ten minutes; omitting it never means “download everything”. Existing
files are checksum-verified through raw_allsky_samples/manifest.sqlite.
Preview one MMTO night without downloading:
uv run --frozen python download_samples.py \
--site mmto --night 2026-09-18 --sun-below -18 --moon-down \
--cadence 20m --max-files 20 --dry-run--night is the observing-night date: local noon on that date through local
noon the following date. --sun-below -12 retains only exposures with the
geometric centre of the Sun more than 12 degrees below the site's horizon,
excluding bright dusk and dawn twilight. Site latitude,
longitude and timezone come from sources.json; missing latitude or longitude
is an error rather than an assumed location. --moon-down additionally retains
only exposures for which the Moon's topocentric geometric centre is below the
horizon. It uses Astropy's built-in ephemeris and no atmospheric refraction.
Both filters run on listing timestamps before any raw image is downloaded.
Remove --dry-run to download.
--date and --start/--end retain their site-local civil-day meanings:
uv run --frozen python download_samples.py \
--site mmto --start 2026-09-01 --end 2026-09-03 \
--cadence 30m --max-files 20 --dry-run
uv run --frozen python download_samples.py \
--site trex --camera luck_rgb-03 --date 2026-01-15 \
--cadence 30m --max-files 15 --dry-runInspect downloaded FITS, HDF5 and camera-RAW originals without modifying them:
uv run --script inspect_allsky_samples.pyThe script isolates rawpy and h5py from every preserved solver runtime.
Inspection results go to raw_allsky_samples/inspection.json; display previews
are derived products. See the source audit for exact URLs and
scientific validation.
New downloads are grouped as
raw_allsky_samples/<source>/<camera>/<observing-night>/filename, where the
observing-night label changes at local noon rather than midnight. Existing MMTO
downloads have been migrated to this layout; historical files were preserved
even when they predate the solar-altitude filter.
The safe incremental MMTO command uses a Phoenix-local noon-to-noon observing night, rejects exposures with solar altitude greater than or equal to -12 degrees, uses a 20-minute cadence, selects the oldest cadence slot not already recorded as downloaded, and permits at most one new raw file per invocation. This lets a late-published archive file be recovered on a later cycle instead of being permanently skipped:
uv run --frozen python run_raw_allsky_cron.py --site mmto --dry-runInstalling the repository does not modify cron. On a capture host, the MMTO
acquisition command is normally scheduled at minutes 0, 20 and 40 under
/tmp/wide-field-raw-allsky-mmto.lock. Moon-down selection is available through
--moon-down, and solar-altitude selection through --sun-below, for any
registered telescope with latitude and longitude.
New files downloaded by the recurring MMTO command are atomically added to a
durable FIFO queue in raw_allsky_samples/manifest.sqlite. Existing files and
checksum-verified REUSE events are deliberately not bulk-enqueued. A separate
worker takes at most one image per invocation and calls the frozen root
go10.sh with that image only. It writes automated runs to
results/mmto-automatic/; neither go10.sh nor v10/ is modified, and the
frozen go11.sh/v11/ release is explicitly outside this queue.
The processing worker is normally scheduled every two minutes under
/tmp/wide-field-solver-mmto-processing.lock, independently of acquisition.
If one solve takes longer, later cron invocations fail to acquire that worker
lock and do nothing. New downloads continue to enter the queue, and the oldest
pending image is selected when the worker becomes free. Files, logs, run
directories, database rows and queue events are never erased by the worker.
The solver inherits the worker lock and writes its attempt log directly, so
killing the wrapper does not unlock a still-running solver or lose its output.
Inspect the live queue without changing it:
uv run --frozen python run_allsky_processing.py statusThe states are pending, running, succeeded, solver_failed,
operational_failed, and interrupted. A stale running row means the worker
was probably stopped after claiming the image; it does not block later pending
rows. Scientific solver failures and operational failures are both terminal:
there is no automatic retry and no stochastic assumption that an identical
rerun will improve the answer.
The cleanup command is an explicit later sweep. It is read-only unless
--apply is supplied, and an applying sweep must be bounded by a source,
observing night, job ID, or remote URL plus its absolute --limit:
uv run --frozen python run_allsky_processing.py cleanup \
--source mmto --night 2026-09-18 --limit 20
uv run --frozen python run_allsky_processing.py cleanup \
--source mmto --night 2026-09-18 --limit 20 --applyBefore requeueing, cleanup searches the dedicated stars.sqlite by the
verified source checksum. It records one existing matching solver receipt
instead of rerunning the image and refuses ambiguous multiple receipts.
An applying cleanup must also acquire the processing-worker lock, so it
refuses to alter a stale-looking row while a long solver process is still
active.
Cleanup may explicitly requeue overlooked downloads, stale interrupted jobs,
or selected operational failures. It never requeues solver_failed jobs.
Each attempt records the exact hashes of go10.sh and
v10/SOURCE_MANIFEST.json used for provenance; those hashes are evidence, not
input to the blind solution.
