Metadata-Version: 2.5
Name: arcsecond-service-platesolver-astrometry
Version: 1.4.0
Summary: Arcsecond plate solver based on astrometry.net indexes
Project-URL: Homepage, https://github.com/arcsecond-io/arcsecond-service-platesolver-astrometry
Project-URL: Issues, https://github.com/arcsecond-io/arcsecond-service-platesolver-astrometry/issues
Author: Arcsecond
License: MIT
Requires-Python: >=3.12
Requires-Dist: arcsecond-astrometry>=4.3.0; platform_system == 'Windows'
Requires-Dist: astrometry>=4.3.0; platform_system != 'Windows'
Requires-Dist: astropy>=7.2
Requires-Dist: fastapi>=0.110
Requires-Dist: numpy>=2.2
Requires-Dist: pydantic>=2.6
Requires-Dist: uvicorn[standard]>=0.27
Description-Content-Type: text/markdown

# Arcsecond Service: Platesolver (astrometry)

This repository provides a FastAPI plate-solving service for Arcsecond, based
on [astrometry.net](https://astrometry.net) indexes and Neuromorphics
Systems' [astrometry](https://github.com/neuromorphicsystems/astrometry) Python package.

Astrometry index files (~10 GB) are baked into the Docker image at build time
under `/opt/astrometry`. No downloads occur at container startup.

## Run with Docker

```bash
docker run --rm \
  -p 127.0.0.1:8900:8900 \
  arcsecond-service-platesolver-astrometry:latest
```

## Run Natively (Linux/macOS)

```bash
python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install .
python -m arcsecond_service_platesolver.main
```

## Run Natively (Windows)

1. Install Python 3.12+.
2. Install Microsoft C++ Build Tools (Desktop development with C++).
3. Install Rust (`rustup`), because `astrometry` may need a local native build when no matching wheel is available.
4. This project pins Windows installs to a Windows-compatible astrometry fork commit from `arcsecond-io/astrometry`.

```powershell
py -3.12 -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install .
python -m arcsecond_service_platesolver.main
```

## Tests

```
uv sync --group dev
uv run pytest
```

The suite stubs the solver out entirely, so it needs no index files and runs in ~10s. It covers
the deadline machinery (breach, worker kill, respawn, repeated breaches, serialisation) and the
`/platesolve` response contract.

## Configuration

- `HOST`: bind host (default `0.0.0.0`).
- `PORT`: bind port (default `8900`).
- `LOG_LEVEL`: Uvicorn log level (default `info`).
- `ARCSECOND_PLATESOLVER_SCALES_5200`: comma-separated scale numbers for the 5200 series (default `2,3,4,5,6`).
- `ARCSECOND_PLATESOLVER_SCALES_4100`: comma-separated scale numbers for the 4100 series (default `7,8,9,10,11`).
- `ARCSECOND_PLATESOLVER_SCALES_4200`: comma-separated scale numbers for the 4200 series (default `6,7,8`). Set to empty string to disable a series entirely.
- `ARCSECOND_PLATESOLVER_SOLVE_DEADLINE_SECONDS`: wall-clock ceiling on a single solve (default `50`). Set to `0` or an empty string to disable. Keep it below the calling client's HTTP timeout (the Arcsecond backend uses 60s) so a hopeless field returns a clean `no_match` instead of timing out the connection.

### Why solves are fast

The solver stops at the first accepted match. By default `astrometry` keeps combing the search
cone after it already has a solution and SIP-fits every further match it accepts, which makes a
rich, well-exposed frame *slower* than a poor one — measured on a clean 50-star field, 169
matches in 40.0s versus 0.23s to stop at the first, for the same centre and scale to five
decimals. That, not pointing error, is what made real solves take 37-47s; a 2 deg pointing
offset only moved a 39.9s solve to 39.1s. Stopping early is safe because a match is only
accepted above odds of 1e9 to 1, and it is what astrometry.net's own solve-field does.

### Solve deadline

A blind or badly-hinted solve can run indefinitely: `astrometry` has no timeout parameter, and
its only early-exit hook (`logodds_callback`) fires when a match is *found* — a hopeless 50-star
field was measured running 70s while calling it zero times. The service therefore runs solves in
a dedicated worker process and kills it when the deadline passes, which is the only mechanism
that actually bounds the C solver. The request then returns `{"status": "no_match"}` and logs
`reason=deadline`. The worker is respawned on the next request (~0.7s cold start against the
full index set, since indexes are mmapped rather than read).

One consequence: solves are serialised. A solve is CPU-bound, and single-flight keeps the kill
unambiguous — it can never destroy a concurrent request's in-flight solve.
