mcpField
A
astropy-astronomy

astropy-astronomy

github.com/jaechang-hits数据与数据库38493 次安装1 周前更新
代码仓库

关于 astropy-astronomy

Astropy 是面向研究人员和开发者的 Python 天文学工具包,将带单位的计算、坐标与时间处理、FITS 读写、宇宙学、表格操作和 WCS 转换整合在一个包中。

主要功能

  1. 01带单位的计算与转换
  2. 02天球坐标系转换
  3. 03FITS 图像和表格读写
  4. 04精确时间及时间尺度处理
  5. 05宇宙学计算
  6. 06WCS 像素与世界坐标映射

适用场景

  1. 01在天球坐标系之间转换坐标
  2. 02读取和分析 FITS 图像
  3. 03交叉匹配天文星表
  4. 04计算宇宙学距离

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jaechang-hits/sciagent-skills --skill astropy-astronomy

安装后在 Claude Code 中输入/astropy-astronomy确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
auxiliary_modules.md
coordinates_time_cosmology.md
data_io_guide.md

详细介绍

Astropy — 天文学与天体物理学工具包

概述

Astropy 是用于天文学的核心 Python 软件包,为天文研究提供必要功能:支持单位的计算、天球坐标转换、FITS 文件 I/O、宇宙学计算、精确时间处理、表格数据操作,以及 WCS 图像坐标映射。

适用场景

  • 在不同天球坐标系(ICRS、Galactic、FK5、AltAz)之间转换
  • 处理物理量和单位(Jy→mJy、秒差距→km、光谱等效关系)
  • 读取、写入或操作 FITS 文件(图像和表格)
  • 宇宙学计算(光度距离、回望时间、共动体积)
  • 使用多种时间尺度(UTC、TAI、TT、TDB)和格式(JD、MJD、ISO)进行精确时间处理
  • 根据天球位置对天文星表进行交叉匹配
  • 在像素坐标和世界坐标之间进行 WCS 转换
  • 对于通用表格数据:改用 pandas 或 polars
  • 对于射电干涉测量:改用 CASA

前置条件

pip install astropy           # Core package
pip install astropy[all]      # With optional dependencies (regions, photutils, etc.)
pip install pytz              # For timezone conversions

快速入门

import astropy.units as u
from astropy.coordinates import SkyCoord
from astropy.time import Time
from astropy.io import fits
from astropy.table import Table
from astropy.cosmology import Planck18

# Units and quantities
distance = 100 * u.pc
print(f"{distance.to(u.km):.3e}")  # 3.086e+15 km

# Coordinates
coord = SkyCoord(ra=10.5*u.degree, dec=41.2*u.degree, frame='icrs')
print(f"Galactic: l={coord.galactic.l:.2f}, b={coord.galactic.b:.2f}")

# Cosmology
d_L = Planck18.luminosity_distance(z=1.0)
print(f"Luminosity distance at z=1: {d_L:.1f}")  # ~6780 Mpc

# Time
t = Time('2023-01-15 12:30:00')
print(f"JD: {t.jd:.6f}, MJD: {t.mjd:.6f}")

核心 API

1. 单位与物理量(astropy.units)
import astropy.units as u
import numpy as np

# Create quantities
distance = 10 * u.kpc
flux = 3.5e-15 * u.erg / u.s / u.cm**2
wavelength = 6563 * u.Angstrom

# Unit conversions
distance_ly = distance.to(u.lyr)
flux_jy = flux.to(u.Jy, equivalencies=u.spectral_density(wavelength))
print(f"Distance: {distance_ly:.2f}")

# Arithmetic with automatic unit tracking
velocity = 300 * u.km / u.s
time = 1 * u.Gyr
distance_traveled = (velocity * time).to(u.Mpc)
print(f"Distance traveled: {distance_traveled:.2f}")

# Equivalencies for domain-specific conversions
freq = wavelength.to(u.Hz, equivalencies=u.spectral())
energy = wavelength.to(u.eV, equivalencies=u.spectral())
parallax_dist = (0.1 * u.arcsec).to(u.pc, equivalencies=u.parallax())
print(f"Frequency: {freq:.3e}, Parallax distance: {parallax_dist:.1f}")
# Logarithmic units (magnitudes)
mag = -2.5 * u.mag
flux_ratio = mag.to(u.dimensionless_unscaled)

# Performance: pre-compute composite units
flux_unit = u.erg / u.s / u.cm**2 / u.Angstrom
fluxes = np.array([1e-15, 2e-15, 3e-15]) * flux_unit

# Custom units
bbl = u.def_unit('bbl', 158.987 * u.liter)
2. 坐标系(astropy.coordinates)
from astropy.coordinates import SkyCoord, EarthLocation, AltAz
from astropy.time import Time
import astropy.units as u

# Create coordinates (multiple formats)
c = SkyCoord(ra='05h23m34.5s', dec='-69d45m22s', frame='icrs')
c = SkyCoord(ra=10.5*u.degree, dec=41.2*u.degree)
c = SkyCoord(l=280*u.degree, b=-30*u.degree, frame='galactic')

# Transform between frames
c_gal = c.galactic
c_fk5 = c.fk5
print(f"Galactic: l={c_gal.l:.4f}, b={c_gal.b:.4f}")

# Observer-dependent AltAz (requires time + location)
location = EarthLocation(lat=40*u.deg, lon=-120*u.deg, height=1000*u.m)
obstime = Time('2023-06-15 23:00:00')
altaz = c.transform_to(AltAz(obstime=obstime, location=location))
print(f"Alt={altaz.alt:.2f}, Az={altaz.az:.2f}")
# Angular separation and matching
c1 = SkyCoord(ra=10*u.deg, dec=20*u.deg)
c2 = SkyCoord(ra=10.1*u.deg, dec=20.05*u.deg)
sep = c1.separation(c2)
print(f"Separation: {sep.arcsec:.2f} arcsec")

# Catalog matching
from astropy.coordinates import match_coordinates_sky
idx, sep, _ = coords1.match_to_catalog_sky(coords2)
matches = sep < 1 * u.arcsec

# Named object lookup
m31 = SkyCoord.from_name('M31')

# 3D coordinates with distance
c3d = SkyCoord(ra=10*u.deg, dec=20*u.deg, distance=50*u.kpc)
print(f"Cartesian: {c3d.cartesian}")

# Velocity information
c_vel = SkyCoord(ra=10*u.deg, dec=20*u.deg,
                 pm_ra_cosdec=5*u.mas/u.yr, pm_dec=-3*u.mas/u.yr,
                 radial_velocity=100*u.km/u.s)
3. FITS 文件处理(astropy.io.fits)
from astropy.io import fits
import numpy as np

# Read FITS file
with fits.open('observation.fits') as hdul:
    hdul.info()                    # Show HDU structure
    data = hdul[0].data            # Image data as NumPy array
    header = hdul[0].header        # Header as dict-like object

# Access header values
exptime = header['EXPTIME']
header['OBSERVER'] = 'Smith'       # Modify
header.add_history('Processed with astropy')

# Convenience functions
data = fits.getdata('image.fits')
header = fits.getheader('image.fits')
value = fits.getval('image.fits', 'EXPTIME')
# Create new FITS file
hdu_primary = fits.PrimaryHDU(data=np.zeros((100, 100)))
hdu_primary.header['OBJECT'] = 'M31'

# Multi-extension file
hdu_image = fits.ImageHDU(data=np.random.random((256, 256)), name='SCI')
hdu_table = fits.BinTableHDU.from_columns([
    fits.Column(name='ID', format='J', array=np.arange(100)),
    fits.Column(name='FLUX', format='E', array=np.random.random(100)),
    fits.Column(name='NAME', format='20A', array=['star']*100)
])
hdul = fits.HDUList([hdu_primary, hdu_image, hdu_table])
hdul.writeto('output.fits', overwrite=True)

# Large file handling with memory mapping
hdul = fits.open('huge.fits', memmap=True)
cutout = hdul[0].section[100:200, 100:200]  # Read only a slice
4. 表格操作(astropy.table)
from astropy.table import Table, QTable
import astropy.units as u
import numpy as np

# Create tables
t = Table({'ra': [10.0, 20.0, 30.0], 'dec': [41.0, 42.0, 43.0],
           'mag': [15.2, 16.1, 14.8]})

# Read from file (auto-detect format)
t = Table.read('catalog.fits')
t = Table.read('data.csv', format='csv')
t = Table.read('catalog.vot', format='votable')

# Unit-aware QTable
qt = QTable({'distance': [10, 20, 30] * u.kpc,
             'flux': [1e-15, 2e-15, 3e-15] * u.erg / u.s / u.cm**2})

# Filter, sort, column operations
bright = t[t['mag'] < 15.5]
t.sort('mag')
t['abs_mag'] = t['mag'] - 5 * np.log10(100)
print(f"Rows: {len(t)}, Columns: {t.colnames}")
# Joins and grouping
from astropy.table import join, vstack, hstack

# Database-style join
merged = join(t1, t2, keys='id', join_type='inner')

# Stack tables
combined = vstack([t1, t2, t3])       # Vertical (row-append)
combined = hstack([t_coords, t_phot])  # Horizontal (column-append)

# Group and aggregate
grouped = t.group_by('field')
stats = grouped.groups.aggregate(np.mean)

# Write
t.write('output.fits', format='fits', overwrite=True)
t.write('output.ecsv', format='ascii.ecsv')  # Preserves units + metadata
5. 时间处理(astropy.time)
from astropy.time import Time, TimeDelta
import astropy.units as u
import numpy as np

# Create from various formats
t = Time('2023-01-15 12:30:45', format='iso', scale='utc')
t = Time(2460000.0, format='jd')
t = Time(59945.0, format='mjd')
t = Time(1673785845.0, format='unix')

# Convert between formats and scales
print(f"ISO: {t.iso}")
print(f"JD: {t.jd}, MJD: {t.mjd}")
print(f"TAI: {t.tai.iso}")      # UTC → TAI (includes leap seconds)
print(f"TDB: {t.tdb.iso}")      # UTC → Barycentric Dynamical Time

# Time arithmetic
dt = TimeDelta(7, format='jd')
t_future = t + dt
t_future = t + 1 * u.hour
duration = Time('2024-01-01') - Time('2023-01-01')
print(f"Duration: {duration.jd:.1f} days")

# Array of times
times = Time('2023-01-01') + np.arange(365) * u.day
# Observing features
from astropy.coordinates import SkyCoord, EarthLocation

location = EarthLocation.of_site('Keck Observatory')
t = Time('2023-06-15 23:00:00', location=location)

# Sidereal time
lst = t.sidereal_time('apparent')
print(f"LST: {lst}")

# Barycentric correction
target = SkyCoord(ra='23h23m08.55s', dec='+18d24m59.3s')
ltt = t.light_travel_time(target, kind='barycentric')
t_bary = t.tdb + ltt
print(f"Barycentric correction: {ltt.sec:.3f} seconds")
6. 宇宙学计算(astropy.cosmology)
from astropy.cosmology import Planck18, FlatLambdaCDM
import astropy.units as u
import numpy as np

# Built-in cosmologies: Planck18, Planck15, Planck13, WMAP9, WMAP7
z = 1.5

# Distance calculations
d_L = Planck18.luminosity_distance(z)
d_A = Planck18.angular_diameter_distance(z)
d_C = Planck18.comoving_distance(z)
dm = Planck18.distmod(z)  # Distance modulus
print(f"d_L={d_L:.1f}, d_A={d_A:.1f}, d_C={d_C:.1f}")

# Time calculations
age = Planck18.age(z)
lookback = Planck18.lookback_time(z)
print(f"Age at z={z}: {age.to(u.Gyr):.2f}")
print(f"Lookback time: {lookback.to(u.Gyr):.2f}")

# Scale and volume
scale = Planck18.kpc_proper_per_arcmin(z)
vol = Planck18.comoving_volume(z)
print(f"Scale: {scale:.2f}")
# Inverse calculations — find z for given property
from astropy.cosmology import z_at_value

z_10gyr = z_at_value(Planck18.lookback_time, 10 * u.Gyr)
z_1gpc = z_at_value(Planck18.comoving_distance, 1 * u.Gpc)
print(f"z at lookback 10 Gyr: {z_10gyr:.4f}")

# Custom cosmology
cosmo = FlatLambdaCDM(H0=70, Om0=0.3, Tcmb0=2.725)
d_L_custom = cosmo.luminosity_distance(z=1.0)

# Array operations (all methods accept arrays)
z_array = np.linspace(0.1, 3.0, 100)
distances = Planck18.luminosity_distance(z_array)
print(f"Distance array shape: {distances.shape}")  # (100,)
7. WCS 与图像处理
from astropy.wcs import WCS
from astropy.io import fits
import astropy.units as u

# Read WCS from FITS
with fits.open('image.fits') as hdul:
    wcs = WCS(hdul[0].header)

# Pixel ↔ world transformations
world = wcs.pixel_to_world(100, 200)  # Returns SkyCoord
print(f"RA: {world.ra:.6f}, Dec: {world.dec:.6f}")

from astropy.coordinates import SkyCoord
coord = SkyCoord(ra=10.5*u.degree, dec=41.2*u.degree)
x, y = wcs.world_to_pixel(coord)

# WCS properties
print(f"Ref pixel: {wcs.wcs.crpix}")
print(f"Ref value: {wcs.wcs.crval}")
print(f"Pixel scale: {wcs.proj_plane_pixel_scales()}")
footprint = wcs.calc_footprint()  # Corner coordinates
# Image visualization
from astropy.visualization import simple_norm, ZScaleInterval, AsinhStretch, ImageNormalize
import matplotlib.pyplot as plt

data = fits.getdata('image.fits')
norm = simple_norm(data, 'sqrt', percent=99)
plt.imshow(data, norm=norm, cmap='gray', origin='lower')
plt.colorbar()

# Advanced normalization
interval = ZScaleInterval()
stretch = AsinhStretch()
norm = ImageNormalize(data, interval=interval, stretch=stretch)

# Sigma clipping for robust statistics
from astropy.stats import sigma_clipped_stats
mean, median, std = sigma_clipped_stats(data, sigma=3.0)
print(f"Background: {median:.2f} ± {std:.2f}")

核心概念

单位等效关系系统

Astropy 的 equivalencies 参数支持不满足量纲等价关系的特定领域转换:

等效关系转换内容示例
u.spectral()波长 ↔ 频率 ↔ 能量(500*u.nm).to(u.THz, u.spectral())
u.spectral_density(wav)流量密度(Fλ ↔ Fν ↔ Jy)flux.to(u.Jy, u.spectral_density(wav))
u.parallax()视差角 ↔ 距离(10*u.mas).to(u.pc, u.parallax())
u.doppler_optical(rest)速度 ↔ 波长(光学)vel.to(u.Angstrom, u.doppler_optical(rest))
u.brightness_temperature(freq)流量 ↔ 温度用于射电天文学
时间尺度
尺度说明适用场景
UTC协调世界时(含闰秒)默认;民用时间
TAI国际原子时(UTC + 闰秒)连续计时
TT地球时(TAI + 32.184s)地心计算
TDB太阳系质心力学时太阳系动力学、星历
UT1地球自转角恒星时、AltAz 转换

通过以下属性访问:t.utc、t.tai、t.tt、t.tdb、t.ut1

坐标系层级
  • ICRS — 国际天球参考系(默认,约为 J2000)
  • FK5 / FK4 — 历史赤道坐标系(FK4 需要春分点)
  • Galactic — 银河坐标系(l、b)
  • AltAz — 取决于观测者(需要 obstime + location)
  • Ecliptic — 太阳系平面
  • Galactocentric — 以星系为中心的笛卡尔坐标系

常见工作流

工作流 1:坐标转换流程
from astropy.coordinates import SkyCoord, EarthLocation, AltAz
from astropy.time import Time
import astropy.units as u

# Load catalog of sources
from astropy.table import Table
cat = Table.read('sources.fits')
coords = SkyCoord(ra=cat['RA']*u.degree, dec=cat['DEC']*u.degree)

# Transform to galactic
gal = coords.galactic
print(f"Galactic l range: {gal.l.min():.1f} to {gal.l.max():.1f}")

# Check observability (AltAz)
location = EarthLocation.of_site('Paranal Observatory')
obstime = Time('2023-06-15 23:00:00')
altaz = coords.transform_to(AltAz(obstime=obstime, location=location))
observable = altaz.alt > 30 * u.deg
print(f"Observable (alt>30°): {observable.sum()} of {len(coords)}")
工作流 2:FITS 图像分析
from astropy.io import fits
from astropy.wcs import WCS
from astropy.stats import sigma_clipped_stats
from astropy.visualization import simple_norm
import numpy as np

# Load image and WCS
with fits.open('science_image.fits') as hdul:
    data = hdul[0].data.astype(float)
    wcs = WCS(hdul[0].header)

# Background statistics
mean, median, std = sigma_clipped_stats(data, sigma=3.0)
print(f"Background: {median:.2f} ± {std:.2f}")

# Find bright pixels (simple threshold detection)
threshold = median + 5 * std
sources = np.where(data > threshold)
print(f"Pixels above 5σ: {len(sources[0])}")

# Convert pixel positions to sky coordinates
sky_coords = wcs.pixel_to_world(sources[1], sources[0])
print(f"RA range: {sky_coords.ra.min():.4f} to {sky_coords.ra.max():.4f}")
工作流 3:星表交叉匹配
from astropy.table import Table
from astropy.coordinates import SkyCoord
import astropy.units as u

# Read two catalogs
cat1 = Table.read('catalog1.fits')
cat2 = Table.read('catalog2.fits')

coords1 = SkyCoord(ra=cat1['RA']*u.degree, dec=cat1['DEC']*u.degree)
coords2 = SkyCoord(ra=cat2['RA']*u.degree, dec=cat2['DEC']*u.degree)

# Match
idx, sep, _ = coords1.match_to_catalog_sky(coords2)
max_sep = 1 * u.arcsec
matches = sep < max_sep

cat1_matched = cat1[matches]
cat2_matched = cat2[idx[matches]]
print(f"Matched: {matches.sum()} of {len(cat1)} (within {max_sep})")

关键参数

参数模块默认值说明
frameSkyCoord'icrs'坐标参考系
scaleTime'utc'时间尺度(utc、tai、tt、tdb、ut1)
formatTimeauto时间格式(iso、jd、mjd、unix 等)
equivalencies.to()None特定领域的单位转换规则
memmapfits.openTrue对大型文件使用内存映射
join_typejoin()'inner'联接类型(inner、outer、left、right)
sigmasigma_clip3.0以标准差表示的裁剪阈值
stretchsimple_norm'linear'图像拉伸方式(linear、sqrt、log、asinh)
percentsimple_norm100归一化范围的百分位数

最佳实践

  1. 始终附加单位 — 使用 Quantity 对象(例如 10 * u.kpc),以避免量纲错误。裸数值会悄无声息地产生错误结果。

  2. 对 FITS 使用上下文管理器 — with fits.open(...) as hdul: 可确保正确关闭文件并清理内存映射。

  3. 处理数组,而不是循环 — 所有 astropy 操作都接受数组。一次处理 10,000 个坐标,而不是逐个循环处理。

  4. 明确指定时间尺度 — Time('2023-01-15', scale='utc') 可避免歧义。对高精度计时而言,UTC↔TDB 的差异很重要。

  5. 使用 QTable 保存带单位的列 — QTable 会在输入/输出过程中保留单位;普通 Table 仅将单位作为元数据存储。

  6. 使用 ECSV 确保往返保真度 — t.write('file.ecsv') 会保留单位、数据类型和元数据。CSV/FITS 会丢失部分元数据。

  7. 反模式 — 使用错误的宇宙学模型:务必指定所用的宇宙学模型。不同模型在相同红移下会给出不同距离。Planck18 是当前标准。

  8. 反模式 — 忽略 WCS 原点约定:astropy 使用从 0 开始的像素坐标。FITS 标准使用从 1 开始的坐标。请使用 wcs.pixel_to_world()(会自动处理此问题),而不要手动计算。

常见示例

示例:包含中微子的自定义宇宙学模型
from astropy.cosmology import FlatLambdaCDM
import astropy.units as u

cosmo = FlatLambdaCDM(
    H0=67.66, Om0=0.3111, Tcmb0=2.7255,
    Neff=3.046, m_nu=[0, 0, 0.06] * u.eV
)
print(f"Age of universe: {cosmo.age(0).to(u.Gyr):.3f}")
示例:模型拟合
from astropy.modeling import models, fitting
import numpy as np

# Generate noisy Gaussian data
x = np.linspace(0, 10, 100)
y = 10 * np.exp(-0.5 * ((x - 5) / 1.0)**2) + np.random.normal(0, 0.5, 100)

# Fit
fitter = fitting.LevMarLSQFitter()
model = models.Gaussian1D(amplitude=8, mean=4, stddev=1.5)
fitted = fitter(model, x, y)
print(f"Amplitude: {fitted.amplitude.value:.2f}")
print(f"Mean: {fitted.mean.value:.2f}")
print(f"Stddev: {fitted.stddev.value:.2f}")
示例:NDData 和 CCDData
from astropy.nddata import CCDData, StdDevUncertainty
import astropy.units as u
import numpy as np

# Create CCDData with uncertainty
data = np.random.random((256, 256))
uncertainty = StdDevUncertainty(np.sqrt(np.abs(data)))
ccd = CCDData(data, unit=u.adu, uncertainty=uncertainty,
              meta={'OBJECT': 'M31', 'EXPTIME': 300.0})

# Read/write
ccd.write('processed.fits', overwrite=True)
ccd2 = CCDData.read('processed.fits', unit=u.adu)

故障排除

问题原因解决方案
UnitConversionError单位不兼容且未指定等价关系添加 equivalencies=u.spectral() 或适当的等价关系
转换后坐标参考系错误AltAz 缺少 obstime/location同时提供二者:AltAz(obstime=t, location=loc)
ErfaWarning: dubious yearUT1 时间超出 1960–2040 年范围对极端日期使用 scale='tt' 或 scale='tdb'
IERS 数据出现 FileNotFoundError尚未下载闰秒表运行 from astropy.utils.iers import IERS_Auto; IERS_Auto.open()
FITS 头出现 VerifyError使用了非标准 FITS 关键字使用 fits.open(f, ignore_missing_end=True) 或 hdul.verify('fix')
坐标转换缓慢对单个坐标进行循环处理使用数组形式的 SkyCoord:SkyCoord(ra=ra_array, dec=dec_array)
QTable 写入时丢失单位使用了 CSV 格式使用 ECSV 格式:qt.write('file.ecsv')
访问 FITS 扩展时出现 KeyError扩展索引错误使用 hdul.info() 查看结构;按名称访问:hdul['SCI']
重心修正不准确时间尺度错误或缺少位置使用 scale='utc',并在 Time 对象上设置 location
ModelFit 未收敛初始参数不佳提供更接近的初始猜测值;尝试 SimplexLSQFitter

捆绑资源

  • references/data_io_guide.md — 详细介绍 FITS 操作(标头、多扩展、二进制表、列格式代码、内存映射、远程访问)、表操作(创建、I/O 格式、联接、分组、索引、QTable、掩码数据、显示、性能)以及集合转换模式。由原始 fits.md 和 tables.md 合并而成。
  • references/coordinates_time_cosmology.md — 完整介绍坐标系参考(所有坐标系、3D 坐标、自行、表示形式、目录匹配)、时间处理(所有格式、所有时间刻度、TimeDelta、恒星时、光行时、重心修正、精度)和宇宙学模型(所有内置模型、自定义模型、距离、体积、逆向计算、中微子效应)。由原始 coordinates.md、time.md 和 cosmology.md 合并而成。
  • references/auxiliary_modules.md — WCS 详细操作、NDData/CCDData、建模框架(1D/2D 模型、拟合、复合模型)、图像可视化(拉伸、区间、归一化)、常数目录、卷积、稳健统计、SAMP 互操作性、数据下载工具。由原始 wcs_and_other_modules.md 和 units.md(等价关系详情)合并而成。

未迁移为单独文件:原先有 7 个参考文件。现已合并为 3 个主题参考文件,涵盖所有功能。units.md 中的等价关系内容分为 SKILL.md Key Concepts(摘要表)和 auxiliary_modules.md(详细代码)。

相关技能

  • matplotlib-scientific-plotting — 用于 astropy 图像显示和绘图的可视化库
  • zarr-python — 分块数组存储;与 astropy 配合用于大型天文数据集

参考资料

评论

支持 Markdown