astro-resolver
Astrophotography toolkit for FITS workflows and imaging pipelines. Two independent packages, one library:
| Package | Purpose |
|---|---|
astro_resolver |
Turns messy OBJECT headers into clean folder names |
astro_quality |
Analyses FITS subframes for stacking quality |
Installation
pip install astro-resolver
# With star detection / FWHM support (recommended):
pip install "astro-resolver[quality]"
astro_resolver — Object name resolution
Resolves FITS OBJECT header values like "M 81", "sh2-101" or
"barnard 33" into filesystem-safe folder names such as
"M81_BodesGalaxy" or "B33_HorseheadNebula".
Features
- Local-first — SIMBAD only as a last resort
- Solar System, Caldwell, custom aliases, OpenNGC (~14k objects), the complete Sharpless catalogue and selected Barnard objects bundled offline
- Stellar catalog IDs (HD, HIP, SAO, TYC, …) skip the SIMBAD name round-trip and go straight to coordinate lookup
- Coordinate fallback via SIMBAD when name lookup fails
- Output is always filesystem-safe (no spaces, special chars)
Resolution priority
- Solar System (local)
- Caldwell (local)
- Custom aliases (local)
- OpenNGC offline (~14k NGC/IC objects)
- Sharpless catalog (Sh 2-xxx HII regions)
- Barnard catalog (dark nebulae)
- Sharpless by coordinates (local)
- SIMBAD by name (online)
- SIMBAD by coordinates (online)
- Sanitized header string as fallback
Usage
from astro_resolver import resolve_object_name
resolve_object_name("M 81") # → "M81_BodesGalaxy"
resolve_object_name("sh2-101") # → "Sh2-101_TulipNebula"
resolve_object_name("barnard 33") # → "B33_HorseheadNebula"
# Stellar catalog ID with coordinates → SIMBAD coord lookup
resolve_object_name("HD 1", ra="00 05 09", dec="+67 50 24")
The resolver accepts an optional log callable so it can be plugged into
Siril scripts (log=siril.log) or any other host environment.
The bundled Sharpless data contains all 313 H II regions from VizieR catalogue
VII/20, based on Sharpless (1959), ApJS 4, 257 (1959ApJS....4..257S). Its
coordinates retain the original B1900 frame. Common names are a separately
curated convenience layer and are not fields from the historical catalogue.
from astro_resolver import sharpless_lookup
entry = sharpless_lookup("Sh 2-101")
entry.catalog_id # → "Sh2-101"
entry.diameter_arcmin # → 20
entry.ra_b1900 # original catalogue coordinate
astro_quality — Subframe quality analysis
Analyses FITS light frames for stacking quality. Supports any camera
(DSLR, ZWO ASI, QHY, DWARF, …) — Bayer RAW and mono alike.
Calibration frames (Flats, Darks, Bias) are detected and skipped
automatically via filename patterns and the IMAGETYP FITS header.
Metrics per subframe
| Metric | Description | Direction |
|---|---|---|
background_noise |
Sky background σ (ADU, sigma-clipped) | lower = better |
snr_proxy |
Signal/noise ratio proxy | higher = better |
gradient |
Spatial background variation (light pollution gradient) | lower = better |
saturation_pct |
Fraction of saturated pixels (%) | lower = better |
star_count |
Number of detected stars (transparency proxy) | higher = better |
fwhm_median |
Median star width in luminance pixels (focus/seeing) | lower = better |
eccentricity |
Median star roundness 0=round 1=line (tracking quality) | lower = better |
Star count, FWHM and eccentricity require photutils (pip install "astrolib[quality]").
Scoring
Each subframe receives a relative quality score 0–100 within its session. Scores are relative: a score of 90 in a poor night may be worse in absolute terms than 70 in an excellent night. Use raw metric values for cross-session comparison.
| Grade | Score | Meaning |
|---|---|---|
| A | 85–100 | Excellent |
| B | 70–84 | Good |
| C | 50–69 | Average |
| D | 30–49 | Poor |
| F | 0–29 | Reject |
Usage
Analyse a single frame:
from pathlib import Path
from astro_quality import analyze_fits, score_sub, grade, compute_session_stats
result = analyze_fits(Path("IC443_0042.fits"))
if not result["error"]:
m = result["metrics"]
print(f"Noise: {m['background_noise']:.1f} ADU")
print(f"SNR: {m['snr_proxy']:.1f}")
print(f"Stars: {m['star_count']}") # -1 if photutils not installed
Score a session (relative quality):
from astro_quality import analyze_fits, compute_session_stats, score_sub, grade
results = [analyze_fits(f) for f in session_fits_files]
stats = compute_session_stats(results)
for r in results:
s = score_sub(r["metrics"], stats)
print(f"{grade(s)} ({s:.1f}) {r['filename']}")
Scan an archive and build a quality report:
from pathlib import Path
from astro_quality import build_report_data
from astro_quality.archive import iter_sessions, iter_fits
from astro_quality.cache import load_cache, save_cache
ARCHIVE = Path("z:/AstroArchiv/Objects")
CACHE = Path("quality_cache.json")
cache = load_cache(CACHE)
for obj, sess, sess_path in iter_sessions(ARCHIVE):
for f in iter_fits(sess_path): # skips Flats/Darks automatically
if str(f) not in cache:
result = analyze_fits(f)
result["object"] = obj
result["session"] = sess
cache[str(f)] = result
save_cache(cache, CACHE)
report = build_report_data(cache, obj_filter="IC443")
for sess, data in report["IC443_GemA"].items():
print(f"{sess}: {data['score_mean']:.1f} ({data['grade']}) "
f"noise={data['stats']['noise_med']:.1f}")
Calibration frame detection:
from astro_quality.archive import is_calib_frame
from pathlib import Path
is_calib_frame(Path("Flat_30s_ISO800_0001.fit")) # True
is_calib_frame(Path("Light_M51_120s_0001.fit")) # False
Archive layout expected by iter_sessions / iter_fits
<root>/
├── IC443_GemA/
│ ├── 2026-01-19_DWARF_3/
│ │ ├── IC 443_60s60_..._0001.fits ← light frames
│ │ └── Flat_200ms_..._0001.fits ← skipped automatically
│ └── 2026-04-01_DWARF_3/
│ └── ...
└── M51_WhirlpoolGalaxy/
├── 2026-04-25_ZWO_ASI585MC_Pro/
│ └── Light_M51_120s_..._0001.fit
└── presets/ ← skipped (non-session dir)
Non-session subdirectories (presets, flats, calibration, masters, …)
are skipped automatically.
Data sources
License
MIT
Release files for astro-resolver 0.5.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| astro_resolver-0.5.1.tar.gz | 920.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| astro_resolver-0.5.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.9 MB
Release files / astro_resolver-0.5.1.tar.gz
| Download URL | astro_resolver-0.5.1.tar.gz |
|---|---|
| Size | 920.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b3e548efeaba3ac72290c57a71d253d22afa35a88705806825dc77749898bcd1
|
|
BLAKE2b-256 checksum How to use checksums |
e5c362e7ba016f11ac5b69eade9ceb21771ff594b25cee95e63577714d92fcf0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / astro_resolver-0.5.1-py3-none-any.whl
| Download URL | astro_resolver-0.5.1-py3-none-any.whl |
|---|---|
| Size | 931.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
18089561855074beb4ee4f41f164a034f1e50b29cd98c73a2d559a08ec3831fa
|
|
BLAKE2b-256 checksum How to use checksums |
490cdac3fe6efebe61ca99205e83fc189c79cb58de2cd710e9939edd66d4815d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|