Astrometry Go Client

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.
Features
- 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)
Why Offline?
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
Prerequisites
Note: Internet is only required once during initial setup to download the Docker image and index files. After setup, the solver runs 100% offline.
Required
- Go 1.21+
- Docker with the
diarmuidk/astrometry-dockerised-solver image pulled (one-time download)
- Astrometry.net index files downloaded to a local directory (one-time download)
Quick Setup
# 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.sh
Index Files Guide
Astrometry.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.
Choosing the Right Index Files
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
Example Setups
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.fits
Telescope (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.fits
Quick 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.
Why Index Matching Matters
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°) |
⚠️ Likely failure |
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.
Docker Setup
This library uses the diarmuidk/astrometry-dockerised-solver Docker container to perform plate-solving. You have several options for running the dependency:
Docker Execution Modes
The library supports two Docker execution modes:
1. Docker Run Mode (Default)
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:latest
Client 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)
2. Docker Exec Mode (Recommended for Development)
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-solver
Setup 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/null
Alternative: You can also use the original dm90/astrometry image if you prefer:
docker pull dm90/astrometry:latest
Client 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.
Full Stack 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
Installation
As a Library
go get github.com/DiarmuidKelly/astrometry-go-client
go install github.com/DiarmuidKelly/astrometry-go-client/cmd/astro-cli@latest
Or build from source:
git clone https://github.com/DiarmuidKelly/astrometry-go-client.git
cd astrometry-go-client
make install
Quick Start
Library Usage
package 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")
}
}
CLI Usage
astro-cli \
--image photo.jpg \
--index-path ~/astrometry-data \
--scale-low 1 \
--scale-high 3 \
--downsample 2
Output (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
}
API Reference
Client Configuration
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
}
Solve Options
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
}
Result Structure
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)
}
Methods
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).
Examples
See the examples/ directory for more usage examples:
examples/basic/ - Basic plate-solving example
examples/batch/ - Batch processing multiple images (coming soon)
examples/with-hints/ - Using RA/Dec hints for faster solving (coming soon)
Error Handling
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
ScaleLow and ScaleHigh dramatically 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
Development
Building
make build # Build all binaries
make test # Run tests
make lint # Run linter
make clean # Clean build artifacts
Running Tests
go test ./...
Integration tests requiring Docker can be run with:
go test -tags=integration ./...
Contributing
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.
License
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
Links
Acknowledgments