Faint LightFast plate-solving/astrometry.
It implements the same nova.astrometry.net HTTP API, so you can use it as a drop-in replacement.
Solves 10-100× faster using the same index files.
Warning: This project is still in beta. Currently solving 99.2% of the images tested.
Full benchmark, method and the other charts:
https://nightsky.observer/faint-light
FAINT_LIGHT_CACHE_GB) that prefetches the indexes relevant to your rig.http://localhost:7222/ in a browser to plate solve from there.
openapi.yml - the complete API contractYou can download it pre-compiled here.
If you're on Windows and not sure of what you're doing, you probably want to download it here.
The headless version includes just the Faint Light API Server. The desktop GUI version includes both the Faint Light API Server and a simple Windows-like desktop GUI. The desktop GUI was designed for Windows users that want to avoid Docker and CLIs. It's just for setting the server on/off, and basic configuration. Plate-solving and other features are done via the API or the web UI, which both versions have.
flowchart LR
subgraph Core["Faint Light API Server (Headless)"]
direction TB
Nova["nova.astrometry.net API"]
API["Faint Light API"]
Web["Web UI"]
end
subgraph Desktop["Desktop GUI Version"]
GUI["Manages the headless version"]
end
GUI --> Core
Put astrometry.net index files in ./indexes or set FAINT_LIGHT_INDEX_DIR (see scripts/download_indexes.sh for downloading it).
docker compose up -d --build
FAINT_LIGHT_INDEX_DIR=./indexes cargo run --release -p fl-server
Test a submission end to end:
./scripts/test_submission.sh path/to/image.jpg localhost:7222
Just configure the index directory to point to the astrometry.net index files path on your computer, make sure the index cache in GB is set to a comfortable amount and start the server. For more information about this GUI, check its documentation.
In NINA -> Options -> Plate Solving -> Astrometry.net, set the API URL to:
http://localhost:7222/nova (note the /nova)
And you can use any API key (it's ignored).
That's it.
With the server running, open http://localhost:7222/. Drop in a FITS, JPEG, PNG or TIFF and press Solve.
| Variable | Default | Meaning |
|---|---|---|
FAINT_LIGHT_BIND | 0.0.0.0 | Interface to listen on. 127.0.0.1 keeps the server to this machine |
FAINT_LIGHT_PORT | 7222 | HTTP port |
FAINT_LIGHT_INDEX_DIR | /index, else ./indexes | Directory of index-*.fits files |
FAINT_LIGHT_CACHE_GB | 2.0 | RAM budget for prefetching/pinning index data |
FAINT_LIGHT_SCALE_LOW / FAINT_LIGHT_SCALE_HIGH | unset | Your rig's pixel scale range (arcsec/px). Optional, but makes the first solve after a restart as fast as a warm one |
FAINT_LIGHT_SOLVE_TIMEOUT | 300 | Per-job solve timeout (seconds) |
FAINT_LIGHT_FAKE | unset | 1 = return canned solutions (API testing without indexes) |
RUST_LOG | info | Log filter |
The complete contract, every endpoint, parameter and response, is specified in openapi.yml.
| Path | What |
|---|---|
/api/v1/... | The Faint Light API (Plate-solving and custom features) |
/nova/... | The nova.astrometry.net contract, for NINA and other clients (Plate-solving only) |
/ | The web UI |
Served under /nova, both with and without trailing slashes. Any or no API key works, authentication is bypassed.
Not implemented: url_upload, annotated preview images, SIP distortion polynomials (NINA consumes none of these).
POST /api/v1/solve solves an image synchronously, no polling.
curl -X POST http://localhost:7222/api/v1/solve -F file=@image.jpg
{
"status": "success",
"job": 2,
"solved_in_ms": 324,
"image": {
"width": 1024,
"height": 1024
},
"calibration": {
"ra": 83.8182,
"dec": -5.3882,
"pixscale": 14.0687,
"...": "..."
},
"wcs": {
"crval": [
83.8182,
-5.3882
],
"crpix": [
512.5,
512.5
],
"cd": [
[
-0.00391,
0
],
[
0,
-0.00391
]
]
},
"match": {
"logodds": 193.2,
"nmatch": 41,
"index_id": 4111
}
}
Three flags decide what else it produces:
| Field | Effect |
|---|---|
fits=1 | Adds fits_url: the image as FITS carrying the solution. A FITS upload comes back as it arrived, same pixels, same cards, with only its WCS keywords replaced. Anything else becomes a 32-bit float image HDU |
skyview=1 | Adds a skyview object with the field's alt/az and chart_url. Needs timestamp, latitude and longitude |
preview=1 | Adds preview_url: the upload as an auto-stretched greyscale PNG, at most 2048 px on its longest edge, plus a preview object with its size and reduction factor. What the web UI shows |
Optional solve hints: scale_low/scale_high (arcsec/px), center_ra/center_dec/radius (degrees), downsample, parity.
Both outputs are fetched from the URLs the response reports:
curl -X POST http://localhost:7222/api/v1/solve \
-F file=@image.jpg -F fits=1 -F skyview=1 \
-F timestamp=2026-07-13T02:10:00Z -F latitude=40.7128 -F longitude=-74.0060
curl -O http://localhost:7222/api/v1/jobs/2/fits
curl -O http://localhost:7222/api/v1/jobs/2/skyview.svg
curl -O http://localhost:7222/api/v1/jobs/2/preview.png # with -F preview=1
The chart below, for this frame of Messier 22:
A 3.13° × 2.05° frame of Messier 22, placed in the sky over the observer's site at the time of exposure.
Constellation figures are derived from d3-celestial (BSD-3-Clause).
# Unit tests
cargo test
# + golden tests vs real index files
FAINT_LIGHT_TEST_INDEX_DIR=~/indexes cargo test --release
./scripts/bench_vs_astrometry.sh localhost:7222/nova reference-host:8000 image.jpg
# The optional GUI
cargo run --release -p fl-server --features gui --bin faint-light-gui
cargo test -p fl-server --features gui
# The debugging commands
cargo build --release -p fl-server --features tools
./target/release/faint-light-tools solve --index-dir ./indexes image.jpg
./target/release/faint-light-tools extract image.jpg
./target/release/faint-light-tools index-dump ./indexes/index-4110.fits
A plain build produces one executable, faint-light. The GUI and the debugging commands are separate --features so they stay out of the way.
The released version is the single line in VERSION, see Releases for how it is used and how to cut one.
Layout:
fl-fits - minimal FITS reader and writer (headers, HDU offsets, image HDUs, WCS injection)fl-index - astrometry.net index files: mmap, libkd kd-tree traversal, quadsfl-extract - image decoding + simplexy-style star extractionfl-solve - geometric-hash quad matching, TAN WCS fitting (Procrustes), log-odds verification, warm-start enginefl-sky - alt/az sky charts: embedded constellation figures, sidereal time / horizontal coordinates, SVG renderingfl-server - axum HTTP server (v1 and nova routers over a shared solve worker and job store, and the web UI from crates/fl-server/web/, embedded at build time); behind --features gui the FLTK front end, behind --features tools the debugging commandsApache License 2.0. See LICENSE.
6 commits
Rust
58.9%
Python
23.3%
JavaScript
5.5%
CSS
3.1%
Shell
3.0%
PowerShell
2.7%
HTML
2.3%
Faint LightFast plate-solving/astrometry.
It implements the same nova.astrometry.net HTTP API, so you can use it as a drop-in replacement.
Solves 10-100× faster using the same index files.
Warning: This project is still in beta. Currently solving 99.2% of the images tested.
Full benchmark, method and the other charts:
https://nightsky.observer/faint-light
FAINT_LIGHT_CACHE_GB) that prefetches the indexes relevant to your rig.http://localhost:7222/ in a browser to plate solve from there.
openapi.yml - the complete API contractYou can download it pre-compiled here.
If you're on Windows and not sure of what you're doing, you probably want to download it here.
The headless version includes just the Faint Light API Server. The desktop GUI version includes both the Faint Light API Server and a simple Windows-like desktop GUI. The desktop GUI was designed for Windows users that want to avoid Docker and CLIs. It's just for setting the server on/off, and basic configuration. Plate-solving and other features are done via the API or the web UI, which both versions have.
flowchart LR
subgraph Core["Faint Light API Server (Headless)"]
direction TB
Nova["nova.astrometry.net API"]
API["Faint Light API"]
Web["Web UI"]
end
subgraph Desktop["Desktop GUI Version"]
GUI["Manages the headless version"]
end
GUI --> Core
Put astrometry.net index files in ./indexes or set FAINT_LIGHT_INDEX_DIR (see scripts/download_indexes.sh for downloading it).
docker compose up -d --build
FAINT_LIGHT_INDEX_DIR=./indexes cargo run --release -p fl-server
Test a submission end to end:
./scripts/test_submission.sh path/to/image.jpg localhost:7222
Just configure the index directory to point to the astrometry.net index files path on your computer, make sure the index cache in GB is set to a comfortable amount and start the server. For more information about this GUI, check its documentation.
In NINA -> Options -> Plate Solving -> Astrometry.net, set the API URL to:
http://localhost:7222/nova (note the /nova)
And you can use any API key (it's ignored).
That's it.
With the server running, open http://localhost:7222/. Drop in a FITS, JPEG, PNG or TIFF and press Solve.
| Variable | Default | Meaning |
|---|---|---|
FAINT_LIGHT_BIND | 0.0.0.0 | Interface to listen on. 127.0.0.1 keeps the server to this machine |
FAINT_LIGHT_PORT | 7222 | HTTP port |
FAINT_LIGHT_INDEX_DIR | /index, else ./indexes | Directory of index-*.fits files |
FAINT_LIGHT_CACHE_GB | 2.0 | RAM budget for prefetching/pinning index data |
FAINT_LIGHT_SCALE_LOW / FAINT_LIGHT_SCALE_HIGH | unset | Your rig's pixel scale range (arcsec/px). Optional, but makes the first solve after a restart as fast as a warm one |
FAINT_LIGHT_SOLVE_TIMEOUT | 300 | Per-job solve timeout (seconds) |
FAINT_LIGHT_FAKE | unset | 1 = return canned solutions (API testing without indexes) |
RUST_LOG | info | Log filter |
The complete contract, every endpoint, parameter and response, is specified in openapi.yml.
| Path | What |
|---|---|
/api/v1/... | The Faint Light API (Plate-solving and custom features) |
/nova/... | The nova.astrometry.net contract, for NINA and other clients (Plate-solving only) |
/ | The web UI |
Served under /nova, both with and without trailing slashes. Any or no API key works, authentication is bypassed.
Not implemented: url_upload, annotated preview images, SIP distortion polynomials (NINA consumes none of these).
POST /api/v1/solve solves an image synchronously, no polling.
curl -X POST http://localhost:7222/api/v1/solve -F file=@image.jpg
{
"status": "success",
"job": 2,
"solved_in_ms": 324,
"image": {
"width": 1024,
"height": 1024
},
"calibration": {
"ra": 83.8182,
"dec": -5.3882,
"pixscale": 14.0687,
"...": "..."
},
"wcs": {
"crval": [
83.8182,
-5.3882
],
"crpix": [
512.5,
512.5
],
"cd": [
[
-0.00391,
0
],
[
0,
-0.00391
]
]
},
"match": {
"logodds": 193.2,
"nmatch": 41,
"index_id": 4111
}
}
Three flags decide what else it produces:
| Field | Effect |
|---|---|
fits=1 | Adds fits_url: the image as FITS carrying the solution. A FITS upload comes back as it arrived, same pixels, same cards, with only its WCS keywords replaced. Anything else becomes a 32-bit float image HDU |
skyview=1 | Adds a skyview object with the field's alt/az and chart_url. Needs timestamp, latitude and longitude |
preview=1 | Adds preview_url: the upload as an auto-stretched greyscale PNG, at most 2048 px on its longest edge, plus a preview object with its size and reduction factor. What the web UI shows |
Optional solve hints: scale_low/scale_high (arcsec/px), center_ra/center_dec/radius (degrees), downsample, parity.
Both outputs are fetched from the URLs the response reports:
curl -X POST http://localhost:7222/api/v1/solve \
-F file=@image.jpg -F fits=1 -F skyview=1 \
-F timestamp=2026-07-13T02:10:00Z -F latitude=40.7128 -F longitude=-74.0060
curl -O http://localhost:7222/api/v1/jobs/2/fits
curl -O http://localhost:7222/api/v1/jobs/2/skyview.svg
curl -O http://localhost:7222/api/v1/jobs/2/preview.png # with -F preview=1
The chart below, for this frame of Messier 22:
A 3.13° × 2.05° frame of Messier 22, placed in the sky over the observer's site at the time of exposure.
Constellation figures are derived from d3-celestial (BSD-3-Clause).
# Unit tests
cargo test
# + golden tests vs real index files
FAINT_LIGHT_TEST_INDEX_DIR=~/indexes cargo test --release
./scripts/bench_vs_astrometry.sh localhost:7222/nova reference-host:8000 image.jpg
# The optional GUI
cargo run --release -p fl-server --features gui --bin faint-light-gui
cargo test -p fl-server --features gui
# The debugging commands
cargo build --release -p fl-server --features tools
./target/release/faint-light-tools solve --index-dir ./indexes image.jpg
./target/release/faint-light-tools extract image.jpg
./target/release/faint-light-tools index-dump ./indexes/index-4110.fits
A plain build produces one executable, faint-light. The GUI and the debugging commands are separate --features so they stay out of the way.
The released version is the single line in VERSION, see Releases for how it is used and how to cut one.
Layout:
fl-fits - minimal FITS reader and writer (headers, HDU offsets, image HDUs, WCS injection)fl-index - astrometry.net index files: mmap, libkd kd-tree traversal, quadsfl-extract - image decoding + simplexy-style star extractionfl-solve - geometric-hash quad matching, TAN WCS fitting (Procrustes), log-odds verification, warm-start enginefl-sky - alt/az sky charts: embedded constellation figures, sidereal time / horizontal coordinates, SVG renderingfl-server - axum HTTP server (v1 and nova routers over a shared solve worker and job store, and the web UI from crates/fl-server/web/, embedded at build time); behind --features gui the FLTK front end, behind --features tools the debugging commandsApache License 2.0. See LICENSE.
6 commits
Rust
58.9%
Python
23.3%
JavaScript
5.5%
CSS
3.1%
Shell
3.0%
PowerShell
2.7%
HTML
2.3%