Offline astrometric plate-solving for Go - Solve astronomical images locally without internet access using the astrometry-dockerised-solver Docker container. Complete privacy and control over your data with no dependency on external services.
- 100% Offline Operation - No internet required after initial setup
- Complete Privacy - Your images never leave your machine
- No Rate Limits - Solve unlimited images without API quotas
- Fast Local Processing - No network latency or upload time
- Simple, type-safe API for plate-solving astronomical images
- Docker-based integration (no complex installation required)
- Support for scale hints, downsampling, and RA/Dec position hints
- WCS header parsing with structured results
- Context-aware with timeout support
- Comprehensive error handling
- CLI tool for quick command-line solving
- Full test coverage with integration tests validating both Docker implementations (diarmuidk/astrometry-dockerised-solver and dm90/astrometry)
Unlike cloud-based plate-solving services, this library runs entirely on your local machine:
- Privacy First - Your astrophotography images and location data stay private
- No Internet Required - Work in remote locations, observatories, or anywhere offline
- Free & Unlimited - No API keys, rate limits, or subscription fees
- Faster Processing - No upload/download time, just local computation
- Full Control - Customize solve parameters without service restrictions
- Self-Hosted - Perfect for automated pipelines, observatories, and air-gapped systems
Note: Internet is only required once during initial setup to download the Docker image and index files. After setup, the solver runs 100% offline.
- Go 1.21+
- Docker with the
diarmuidk/astrometry-dockerised-solverimage pulled (one-time download) - Astrometry.net index files downloaded to a local directory (one-time download)
# Clone the repository (for development)
git clone https://github.com/DiarmuidKelly/astrometry-go-client.git
cd astrometry-go-client
# Download all index files (~350MB)
./scripts/download-indexes.shAstrometry.net requires index files that match your camera's field of view (FOV). Using an index that's too wide for your FOV guarantees failure - the star patterns physically won't fit in your image.
First, calculate your FOV:
FOV = 2 × arctan(sensor_width / (2 × focal_length))
FOV ≈ sensor_width / focal_length (in radians)
FOV ≈ (sensor_width / focal_length) × 57.3 (in degrees)
FOV (degrees) = (sensor_width_mm / focal_length_mm) × 57.3
Then download 2-3 indexes that bracket your FOV:
| Index File | Field Width | Size | Full Frame (36mm) | APS-C Canon (22.3mm, 1.6x) | Download |
|---|---|---|---|---|---|
| index-4119 | 0.1° - 0.2° | 144 KB | 10,000-20,000mm | 6,400-12,800mm | Download |
| index-4118 | 0.2° - 0.28° | 187 KB | 7,300-10,000mm | 4,500-6,400mm | Download |
| index-4117 | 0.28° - 0.4° | 248 KB | 5,100-7,300mm | 3,200-4,500mm | Download |
| index-4116 | 0.4° - 0.56° | 409 KB | 3,600-5,100mm | 2,300-3,200mm | Download |
| index-4115 | 0.56° - 0.8° | 740 KB | 2,500-3,600mm | 1,600-2,300mm | Download |
| index-4114 | 0.8° - 1.1° | 1.4 MB | 1,800-2,500mm | 1,200-1,600mm | Download |
| index-4113 | 1.1° - 1.6° | 2.7 MB | 1,300-1,800mm | 800-1,200mm | Download |
| index-4112 | 1.6° - 2.2° | 5.3 MB | 900-1,300mm | 600-800mm | Download |
| index-4111 | 2.2° - 3.0° | 10 MB | 700-900mm | 400-600mm | Download |
| index-4110 | 3.0° - 4.2° | 25 MB | 500-700mm | 300-400mm | Download |
| index-4109 | 4.2° - 5.6° | 50 MB | 350-500mm | 220-300mm | Download |
| index-4108 | 5.6° - 8.0° | 95 MB | 250-350mm | 160-220mm | Download |
| index-4107 | 8.0° - 11.0° | 165 MB | 180-250mm | 110-160mm | Download |
Total size of all indexes: ~350 MB
DSLR Astrophotography (APS-C sensor, 24mm wide):
mkdir -p ~/astrometry-data && cd ~/astrometry-data
# 200mm lens: ~7° FOV
wget http://data.astrometry.net/4100/index-4108.fits
wget http://data.astrometry.net/4100/index-4109.fits
# 50mm lens: ~27° FOV
wget http://data.astrometry.net/4100/index-4110.fits
wget http://data.astrometry.net/4100/index-4111.fitsTelescope (1000mm focal length, APS-C sensor):
mkdir -p ~/astrometry-data && cd ~/astrometry-data
# ~1.4° FOV
wget http://data.astrometry.net/4100/index-4113.fits
wget http://data.astrometry.net/4100/index-4114.fitsQuick Solver (50mm-300mm lenses - recommended default):
mkdir -p ~/astrometry-data && cd ~/astrometry-data
# Download 1.1° - 4.2° range (~43 MB total)
# Covers DSLR + 50-300mm focal lengths
wget http://data.astrometry.net/4100/index-4110.fits # 3.0° - 4.2° (50-70mm)
wget http://data.astrometry.net/4100/index-4111.fits # 2.2° - 3.0° (70-110mm)
wget http://data.astrometry.net/4100/index-4112.fits # 1.6° - 2.2° (110-150mm)
wget http://data.astrometry.net/4100/index-4113.fits # 1.1° - 1.6° (150-220mm)Note: The scripts/download-indexes.sh script downloads all indexes automatically.
Index files contain pre-computed star patterns (called "quads") at specific angular scales. Using the wrong index causes failure, not just slowness.
What happens with mismatched indexes:
| Scenario | Result | Why |
|---|---|---|
| Narrow image (0.5°) + Wide index (6-8°) | ❌ Guaranteed failure | Star patterns in the index span 6-8°. Your 0.5° image physically cannot contain patterns that large - they don't fit in the frame. |
| Wide image (8°) + Narrow index (0.5°) | Your wide image does contain small sub-regions with narrow patterns, but matching is unreliable, very slow, and usually fails. |
The critical rule:
- Index scale larger than your FOV → guaranteed failure (patterns don't fit in your image)
- Index scale smaller than your FOV → likely failure (unreliable matching of sub-regions)
✅ Best practice: Download 2-3 indexes that bracket your expected FOV (one above, one below). This ensures reliable, fast solving regardless of minor FOV variations.
This library uses the diarmuidk/astrometry-dockerised-solver Docker container to perform plate-solving. You have several options for running the dependency:
The library supports two Docker execution modes:
How it works: The library spawns a new Docker container for each solve operation, then removes it.
Pros:
- Simple setup - no container management required
- Clean isolation per solve
Cons:
- Slower for multiple solves (~1-2s container startup overhead per solve)
- More Docker overhead
Setup: Pull the image, then use the library - that's it!
# Pull from DockerHub (recommended)
docker pull diarmuidk/astrometry-dockerised-solver:latest
# Or from GitHub Container Registry (GHCR)
docker pull ghcr.io/diarmuidkelly/astrometry-dockerised-solver:latestClient configuration:
config := &client.ClientConfig{
IndexPath: "/path/to/astrometry-data", // Path to your index files
// DockerImage defaults to ghcr.io/diarmuidkelly/astrometry-dockerised-solver:latest
// To use dm90/astrometry instead: DockerImage: "dm90/astrometry"
}
c, err := client.NewClient(config)How it works: Uses a long-running Docker container and executes solve commands via docker exec.
Pros:
- Faster: No container startup overhead (typically 1-2s faster per solve)
- Ideal for development, testing, or batch processing
Cons:
- Requires manual container management
Setup Option A: Using Docker Compose (Recommended)
# 1. Copy and configure environment
cp .env.example .env
# Edit .env and set ASTROMETRY_INDEX_PATH to your index files directory
# 2. Start the container
docker compose up -d
# 3. Verify it's running
docker ps | grep astrometry-solverSetup Option B: Manual Docker Run
docker run -d \
--name astrometry-solver \
-v ~/astrometry-data:/usr/local/astrometry/data:ro \
-v /tmp/astrometry-shared:/shared-data \
diarmuidk/astrometry-dockerised-solver:latest \
tail -f /dev/nullAlternative: You can also use the original dm90/astrometry image if you prefer:
docker pull dm90/astrometry:latestClient configuration:
config := &client.ClientConfig{
IndexPath: "/path/to/astrometry-data",
UseDockerExec: true,
ContainerName: "astrometry-solver",
}
c, err := client.NewClient(config)| Operation | Docker Run Mode | Docker Exec Mode | Difference |
|---|---|---|---|
| First solve | ~15s | ~13s | ~2s faster |
| Subsequent solves (each) | ~15s | ~13s | ~2s faster |
| Setup overhead | None | Start container once | One-time |
Recommendation: Use docker exec mode for development/testing. Either mode works well for production depending on your orchestration setup.
For a complete REST API server with web interface, see the Astrometry API Server project. It includes:
- docker-compose orchestration for both the API server and solver
- RESTful HTTP API
- Swagger documentation
- Health monitoring
go get github.com/DiarmuidKelly/astrometry-go-clientgo install github.com/DiarmuidKelly/astrometry-go-client/cmd/astro-cli@latestOr build from source:
git clone https://github.com/DiarmuidKelly/astrometry-go-client.git
cd astrometry-go-client
make installpackage main
import (
"context"
"fmt"
"log"
"github.com/DiarmuidKelly/astrometry-go-client"
)
func main() {
// Create client
config := &client.ClientConfig{
IndexPath: "/path/to/astrometry-data",
}
c, err := client.NewClient(config)
if err != nil {
log.Fatal(err)
}
// Configure solve options
opts := client.DefaultSolveOptions()
opts.ScaleLow = 1.0 // 1 arcmin/width
opts.ScaleHigh = 3.0 // 3 arcmin/width
// Solve the image
result, err := c.Solve(context.Background(), "image.jpg", opts)
if err != nil {
log.Fatal(err)
}
if result.Solved {
fmt.Printf("RA: %.6f, Dec: %.6f\n", result.RA, result.Dec)
fmt.Printf("Pixel Scale: %.2f arcsec/pixel\n", result.PixelScale)
} else {
fmt.Println("Image could not be solved")
}
}astro-cli \
--image photo.jpg \
--index-path ~/astrometry-data \
--scale-low 1 \
--scale-high 3 \
--downsample 2Output (JSON):
{
"solved": true,
"ra": 120.123456,
"dec": 45.987654,
"pixel_scale": 1.23,
"rotation": 15.5,
"field_width": 2.5,
"field_height": 1.8,
"solve_time": 12.34
}type ClientConfig struct {
DockerImage string // Default: "ghcr.io/diarmuidkelly/astrometry-dockerised-solver:latest"
// Also compatible with: "dm90/astrometry"
IndexPath string // Required: path to index files
TempDir string // Optional: temp directory for processing
Timeout time.Duration // Default: 5 minutes
UseDockerExec bool // Use docker exec mode (default: false)
ContainerName string // Container name for docker exec mode
}type SolveOptions struct {
ScaleLow float64 // Lower bound of image scale
ScaleHigh float64 // Upper bound of image scale
ScaleUnits string // "degwidth", "arcminwidth", "arcsecperpix"
DownsampleFactor int // Reduce resolution (default: 2)
DepthLow int // Min quads to try (default: 10)
DepthHigh int // Max quads to try (default: 20)
NoPlots bool // Disable plot generation (default: true)
RA float64 // RA hint in degrees (optional)
Dec float64 // Dec hint in degrees (optional)
Radius float64 // Search radius in degrees (optional)
Verbose bool // Enable verbose output
}type Result struct {
Solved bool // Whether the image was solved
RA float64 // Right ascension (J2000, degrees)
Dec float64 // Declination (J2000, degrees)
PixelScale float64 // arcsec/pixel
Rotation float64 // Field rotation (degrees)
FieldWidth float64 // Field of view width (degrees)
FieldHeight float64 // Field of view height (degrees)
WCSHeader map[string]string // Raw WCS header fields
OutputFiles []string // Paths to generated files
SolveTime float64 // Solve duration (seconds)
}NewClient(config *ClientConfig) (*Client, error)
Creates a new astrometry client with the given configuration.
Solve(ctx context.Context, imagePath string, opts *SolveOptions) (*Result, error)
Solves a single image file and returns the plate solution.
SolveBytes(ctx context.Context, data []byte, format string, opts *SolveOptions) (*Result, error)
Solves image data from a byte slice (useful for in-memory images).
See the examples/ directory for more usage examples:
examples/basic/- Basic plate-solving exampleexamples/batch/- Batch processing multiple images (coming soon)examples/with-hints/- Using RA/Dec hints for faster solving (coming soon)
The library provides structured error types:
var (
ErrNoSolution = errors.New("no solution found")
ErrTimeout = errors.New("solve operation timed out")
ErrDockerFailed = errors.New("docker command failed")
ErrInvalidInput = errors.New("invalid input parameters")
ErrWCSParseFailed = errors.New("failed to parse WCS output")
)Example:
result, err := c.Solve(ctx, imagePath, opts)
if err != nil {
if errors.Is(err, client.ErrTimeout) {
log.Println("Solve timed out - try increasing timeout or downsample")
} else if errors.Is(err, client.ErrDockerFailed) {
log.Println("Docker error - check Docker is running")
}
return err
}
if !result.Solved {
log.Println("No solution - try adjusting scale parameters")
}- Use scale bounds: Providing
ScaleLowandScaleHighdramatically speeds up solving - Downsample: Higher downsample factors (2-4) work well for most images
- RA/Dec hints: If you know approximate coordinates, use them to reduce search space
- Index files: Download only the indexes appropriate for your field of view
make build # Build all binaries
make test # Run tests
make lint # Run linter
make clean # Clean build artifactsgo test ./...Integration tests requiring Docker can be run with:
go test -tags=integration ./...Contributions welcome! See CONTRIBUTING.md for workflow details.
Quick Start:
- Fork the repository
- Create a feature branch (
git checkout -b feat/my-feature) - Make your changes with conventional commits
- Push and create a PR with
[MAJOR],[MINOR], or[PATCH]prefix
Auto-release workflow handles versioning, changelog, and releases on PR merge.
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
- Astrometry API Server - REST API server for this library
- Astrometry Dockerised Solver - Maintained solver-only Docker image
- Changelog
- Contributing Guide
- Issues
- Astrometry.net
- dam90/astrometry - Alternative Docker image with web UI
- Astrometry.net - The plate-solving engine
- dam90/astrometry - Original containerized version with web UI
- astrometry-dockerised-solver - Maintained solver-only image