Thin wrapper around ASTAP CLI for Python
Download ASTAP CLI and star database from: https://www.hnsky.org/astap.htm#astap_command_line
- Go to: https://www.hnsky.org/astap.htm
- Download the CLI for your operating system
- Save the binary in a suitable location, for example:
~/.local/astap - Add this location to PATH:
export PATH=~/.local/astap:$PATH - Rename the CLI executable to
astap
Download a star database (e.g., D50), unpack it, and store the data in the same directory as the ASTAP CLI executable.
Type astap --help to verify installation. It should display the help page.
Install ASTApy from PyPI: https://pypi.org/project/astapy/
pip install astapyNote: It is recommended to install in a virtual environment.
Basic example:
from pathlib import Path
from astapy import Astapy, Angle
# Define the FITS file path
file = Path("./data/test.fit")
# Define initial coordinates for searching (optional, defaults to ra=0, dec=0)
dec = Angle.from_deg(d=29, m=39, s=49)
ra = Angle.from_hour(h=6, m=30, s=49)
# Field of view of the image (must be close to actual FOV for effective solving)
fov = Angle.from_deg(d=0.18)
# Search radius
r = Angle.from_deg(d=10)
# Initialize Astapy
a = Astapy(file=file, dec=dec, ra=ra, fov=fov, r=r)
# Run plate solving and show log
wcs = a.run(log=True)
# Print the WCS header values
for val in wcs:
print(val)If your ASTAP executable has a different name or location, configure it at the top level:
from astapy import config
config({
'exe': 'astap_cli'
})The Angle class wraps an angle value stored internally in decimal degrees. It provides class methods to construct angles from degrees/minutes/seconds or hours/minutes/seconds.
Angle(_v: Optional[float] = 0.0)Initializes an Angle object directly from a degree value.
Do not use directly. Instead, use .from_deg() or .from_hour().
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
| _v | float |
0.0 | Internal angle value in degrees |
Example:
a = Angle(45)
print(a.to_deg()) # 45.0Creates an Angle from degrees, minutes, and seconds.
To create a negative angle, add a negative sign to d only.
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
| d | float |
0.0 | Degrees component |
| m | float |
0.0 | Arcminutes component |
| s | float |
0.0 | Arcseconds component |
Returns: An instance of Angle
Example:
a = Angle.from_deg(10, 30, 0)
print(a.to_deg()) # 10.5Creates an Angle from hours, minutes, and seconds. One hour corresponds to 15 degrees (useful for right ascension).
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
| h | float |
0.0 | Hours component |
| m | float |
0.0 | Minutes component |
| s | float |
0.0 | Seconds component |
Returns: An instance of Angle
Example:
a = Angle.from_hour(1, 0, 0)
print(a.to_deg()) # 15.0Returns the internal value in decimal degrees.
Example:
a = Angle.from_deg(12, 30, 0)
print(a.to_deg()) # 12.5from astapy import Angle
# From degrees/minutes/seconds
a1 = Angle.from_deg(23, 26, 45)
print(a1) # Angle(23.445833°)
# From hours/minutes/seconds
a2 = Angle.from_hour(2, 0, 0)
print(a2.to_deg()) # 30.0This module wraps the ASTAP command-line tool, allowing you to run it directly from Python.
A TypedDict defining configuration fields.
| Key | Type | Description |
|---|---|---|
| exe | str |
Path or name of the ASTAP executable |
Optional ASTAP command-line arguments. See: https://www.hnsky.org/astap.htm#astap_command_line
Custom exception raised when the ASTAP executable is missing.
Astapy(file: Path, r: Angle = 10°, ra: Angle = 0°, dec: Angle = 0°, fov: Angle = 1°)Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
| file | Path |
- | Path to the FITS or image file to solve |
| r | Angle |
10° | Search radius around starting coordinate |
| ra | Angle |
0° | Right ascension initial guess |
| dec | Angle |
0° | Declination initial guess |
| fov | Angle |
1° | Field of view |
Example:
from pathlib import Path
from astapy import Astapy, Angle
solver = Astapy(
Path("example.fits"),
ra=Angle.from_hour(h=5),
dec=Angle.from_deg(d=-10)
)Add additional ASTAP command-line arguments dynamically.
Parameters:
| Name | Type | Description |
|---|---|---|
| **kwargs | Any | Command-line options without the leading dash |
Supported args:
| key | type | description |
|---|---|---|
| z | int |
Down sample (binning) of the input image prior to solving. Specify "0" for auto selection.. |
| s | int |
Limits the number of star used for the solution. Typical value 500. |
| t | float |
Tolerance used to compare quads. Typical value 0.007. |
| m | float |
This setting could be used to filter out hot pixels. |
| check | bool |
Apply a check pattern filter prior to solving. Use for raw OSC images only when binning is set 1x1 |
| d | str |
Specify a path to the star database |
| D | str |
Specify a star database |
| sip | bool |
Add SIP (Simple Image Polynomial) coefficients. Only required to deactivate SIP. |
| speed | 'slow' | 'auto' |
"slow" is forcing more area overlap while searching to improve detection. |
| update | bool |
Add the solution to the input fits/tiff file header. In case the input is a jpeg, png a new fits will be created. |
| log | bool |
Write solver log to a .log text file. |
| analyse | float |
Analyse only and report HFD. Windows: errorlevel is the median HFD * 100M + number of stars used. So the HFD is trunc(errorlevel/1M)/100. For Linux and macOS the info is send to stdout only. |
| extract | float |
As analyse option but additionally export info of all detectable stars to a .csv file. The decimal separator is always a dot. |
| extract2 | float |
Solve image and export info of all detectable stars to a .csv file including α, δ of each detection. SIP polynomial will be used for high precision positions. The decimal separator is always a dot. In versions after 2024-2-1 |
| annotate | bool |
Produce a deep sky annotated jpeg file with same name as input file extended with _annotated. |
| sqm | float |
Measure the sky background value in magn/arcsec2 relative to the stars. The pedestal is the mean value of a dark. Also centalt and airmass are written to the header. |
Not supported: -o, -debug, -tofits, -focus1, -stack
Example:
solver.args(z=3, sigma=4)Run the ASTAP alignment/plate-solving process.
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
| log | bool |
False |
If True, print ASTAP output in real time |
| delete | bool |
True |
Whether to delete temporary files after solving |
Returns: WCS information as a list of Card or Comment objects
Raises:
AstapNotFoundif the ASTAP executable cannot be foundFileNotFoundErrorif the image file does not exist
Example:
wcs = solver.run(log=True)
print(wcs)from pathlib import Path
from astapy import Astapy, Angle
solver = Astapy(Path("image.fits")).args(z=2)
wcs = solver.run(log=True)Running: astap -f test.fit -r 10.0 -ra 6.5136111111111115 -spd 119.66361111111111 -fov 0.18 -o temp -z 1 -log
astap -f test.fit -r 10.0 -ra 6.5136111111111115 -spd 119.66361111111111 -fov 0.18 -o temp -z 1 -log
Using star database D50
Database limit for this FOV is 203 stars.
ASTAP solver version CLI-2025.10.11
Search radius: 10.0 degrees,
Start position: 06: 30 49.0, +29d 39 49
Image height: 0.18 degrees
Binning: 1x1
Image dimensions: 1375x1100
Quad tolerance: 0.007
Minimum star size: 1.5"
Speed: normal
116 stars, 88 quads selected in the image. 112 database stars, 85 database quads required for the 0.20d square search window. Step size 0.18d. Oversize 1.10
24 of 24 quads selected matching within 0.007 tolerance.
Solution["] x:=-0.574225*x+ -0.012891*y+ 401.578769, y:=-0.013370*x+ 0.573890*y+ -306.168946
Solution found: 06: 30 48.2 +29d 39 49
Running: astap -f test.fit -r 10.0 -ra 0.0 -spd 119.66361111111111 -fov 0.18 -o temp -z 1 -log
astap -f test.fit -r 10.0 -ra 0.0 -spd 119.66361111111111 -fov 0.18 -o temp -z 1 -log
Using star database D50
Database limit for this FOV is 203 stars.
ASTAP solver version CLI-2025.10.11
Search radius: 10.0 degrees,
Start position: 00: 00 00.0, +29d 39 49
Image height: 0.18 degrees
Binning: 1x1
Image dimensions: 1375x1100
Quad tolerance: 0.007
Minimum star size: 1.5"
Speed: normal
116 stars, 88 quads selected in the image. 112 database stars, 85 database quads required for the 0.20d square search window. Step size 0.18d. Oversize 1.10
0d,1d,1d,1d,1d,2d,2d,2d,2d,3d,3d,3d,3d,4d,4d,4d,4d,5d,5d,5d,5d,6d,6d,6d,6d,7d,7d,7d,7d,8d,8d,8d,9d,9d,9d,9d,10d,10d,10d,No solution found! :(
The result of plate solving is a WCS header in the form of a list of Card or Comment objects.
@dataclass
class Card:
key: str
value: str | float | bool
comment: str | None = NoneRepresents a single key-value pair in the WCS header.
Attributes:
| Name | Type | Description |
|---|---|---|
| key | str |
Header keyword (e.g., CTYPE1, CRVAL1) |
| value | str | float | bool |
Parsed header value, automatically converted when possible |
| comment | str | None |
Optional comment following a / separator |
@dataclass
class Comment:
value: strRepresents a comment line in the header file (lines starting with COMMENT).
Attributes:
| Name | Type | Description |
|---|---|---|
| value | str |
The comment text |
solver = Astapy(file=file)
wcs = solver.run(log=True)
for val in wcs:
print(val)Output:
Card(key='CTYPE1 ', value=" 'RA---TAN' ", comment=' first parameter RA, projection TANgential')
Card(key='CTYPE2 ', value=" 'DEC--TAN' ", comment=' second parameter DEC, projection TANgential')
Card(key='CUNIT1 ', value=" 'deg ' ", comment=' Unit of coordinates')
Card(key='CRPIX1 ', value=' 6.880000000000E+002 ', comment=' X of reference pixel')
Card(key='CRPIX2 ', value=' 5.505000000000E+002 ', comment=' Y of reference pixel')
Card(key='CRVAL1 ', value=' 9.770093978531E+001 ', comment=' RA of reference pixel (deg)')
Card(key='CRVAL2 ', value=' 2.966372471539E+001 ', comment=' DEC of reference pixel (deg)')
Card(key='CDELT1 ', value=' 1.595471921033E-004 ', comment=' X pixel size (deg)')
Card(key='CDELT2 ', value=' 1.594572697506E-004 ', comment=' Y pixel size (deg)')
Card(key='CROTA1 ', value=' -1.333831567642E+000 ', comment=' Image twist of X axis (deg)')
Card(key='CROTA2 ', value=' -1.286801526764E+000 ', comment=' Image twist of Y axis (deg)')
Card(key='CD1_1 ', value=' 1.595039610045E-004 ', comment=' CD matrix to convert (x,y) to (Ra, Dec)')
Card(key='CD1_2 ', value=' 3.713883671986E-006 ', comment=' CD matrix to convert (x,y) to (Ra, Dec)')
Card(key='CD2_1 ', value=' -3.580937559609E-006 ', comment=' CD matrix to convert (x,y) to (Ra, Dec)')
Card(key='CD2_2 ', value=' 1.594170560590E-004 ', comment=' CD matrix to convert (x,y) to (Ra, Dec)')
Card(key='PLTSOLVD', value=' T ', comment=' Astrometric solved by ASTAP_CLI v2025.10.11.')
Comment(value=' 7 Solved in 0.1 sec. Offset was 10.1".')
Comment(value=' cmdline:astap -f test.fit -r 1')
Comment(value=' 0.0 -ra 6.5136111111111115 -spd 119.66361111111111 -fov 0.18 -o')
Comment(value=' temp -z 1 -log')
See LICENSE file for details.
Contributions are welcome! Please open an issue or submit a pull request.