Skill Tracer
Skills › Scientific Agent Skills › astropy › wcs_and_other_modules.md

wcs_and_other_modules.md

403 lines · skills/astropy/references/wcs_and_other_modules.md at 5f2bc03

Open on GitHub
1# WCS and Other Astropy Modules
2

WCS and Other Astropy Modules

Raw lines 1 to 2
3## World Coordinate System (astropy.wcs)
4
5The WCS module manages transformations between pixel coordinates in images and world coordinates.
6Examples here assume a two-dimensional celestial WCS. Cubes can return multiple
7world objects; inspect `world_axis_physical_types` and select/slice axes explicitly.
8High-level methods use zero-based `(x, y)` pixels; NumPy indexes `(row, column)`
9or `(y, x)`. FITS `CRPIX` remains one-based. Low-level `all_pix2world` and
10`all_world2pix` require explicit `origin=0` or `1`. Preserve distortion HDUs
11with `WCS(header, fobj=hdul)` where needed and check warnings and round trips.
12User-file and GUI snippets are illustrative; synthetic numerical coverage is
13listed in [review evidence](review.md).

World Coordinate System (astropy.wcs)

The WCS module manages transformations between pixel coordinates in images and world coordinates. Examples here assume a two-dimensional celestial WCS. Cubes can return multiple world objects; inspect world_axis_physical_types and select/slice axes explicitly. High-level methods use zero-based (x, y) pixels; NumPy indexes (row, column) or (y, x). FITS CRPIX remains one-based. Low-level all_pix2world and all_world2pix require explicit origin=0 or 1. Preserve distortion HDUs with WCS(header, fobj=hdul) where needed and check warnings and round trips. User-file and GUI snippets are illustrative; synthetic numerical coverage is listed in review evidence.

Raw lines 3 to 14
15### Reading WCS from FITS
17```python
18from astropy.wcs import WCS
19from astropy.io import fits
21# Read WCS from FITS header
22with fits.open('image.fits') as hdul:
23 wcs = WCS(hdul[0].header)
24```

Reading WCS from FITS

from astropy.wcs import WCS
from astropy.io import fits

# Read WCS from FITS header
with fits.open('image.fits') as hdul:
    wcs = WCS(hdul[0].header)
Raw lines 15 to 25
26### Pixel to World Transformations
28```python
29# Single pixel to world coordinates
30world = wcs.pixel_to_world(100, 200) # Returns SkyCoord
31print(f"RA: {world.ra}, Dec: {world.dec}")
33# Arrays of pixels
34import numpy as np
35x_pixels = np.array([100, 200, 300])
36y_pixels = np.array([150, 250, 350])
37world_coords = wcs.pixel_to_world(x_pixels, y_pixels)
38```

Pixel to World Transformations

# Single pixel to world coordinates
world = wcs.pixel_to_world(100, 200)  # Returns SkyCoord
print(f"RA: {world.ra}, Dec: {world.dec}")

# Arrays of pixels
import numpy as np
x_pixels = np.array([100, 200, 300])
y_pixels = np.array([150, 250, 350])
world_coords = wcs.pixel_to_world(x_pixels, y_pixels)
Raw lines 26 to 39
40### World to Pixel Transformations
42```python
43from astropy.coordinates import SkyCoord
44import astropy.units as u
46# Single coordinate
47coord = SkyCoord(ra=10.5*u.degree, dec=41.2*u.degree)
48x, y = wcs.world_to_pixel(coord)
50# Array of coordinates
51coords = SkyCoord(ra=[10, 11, 12]*u.degree, dec=[41, 42, 43]*u.degree)
52x_pixels, y_pixels = wcs.world_to_pixel(coords)
53```

World to Pixel Transformations

from astropy.coordinates import SkyCoord
import astropy.units as u

# Single coordinate
coord = SkyCoord(ra=10.5*u.degree, dec=41.2*u.degree)
x, y = wcs.world_to_pixel(coord)

# Array of coordinates
coords = SkyCoord(ra=[10, 11, 12]*u.degree, dec=[41, 42, 43]*u.degree)
x_pixels, y_pixels = wcs.world_to_pixel(coords)
Raw lines 40 to 54
55### WCS Information
57```python
58# Print WCS details
59print(wcs)
61# Access key properties
62print(wcs.wcs.crpix) # Reference pixel
63print(wcs.wcs.crval) # Reference value (world coords)
64print(wcs.pixel_scale_matrix) # Works with either CD or PC/CDELT representation
65print(wcs.wcs.ctype) # Coordinate types
67# Pixel scale
68pixel_scale = wcs.proj_plane_pixel_scales() # List of axis Quantities at CRPIX
69# Projection-plane scales are not a global angular scale for a distorted image.
70```

WCS Information

# Print WCS details
print(wcs)

# Access key properties
print(wcs.wcs.crpix)  # Reference pixel
print(wcs.wcs.crval)  # Reference value (world coords)
print(wcs.pixel_scale_matrix)  # Works with either CD or PC/CDELT representation
print(wcs.wcs.ctype)  # Coordinate types

# Pixel scale
pixel_scale = wcs.proj_plane_pixel_scales()  # List of axis Quantities at CRPIX
# Projection-plane scales are not a global angular scale for a distorted image.
Raw lines 55 to 71
72### Creating WCS
74```python
75from astropy.wcs import WCS
77# Create new WCS
78wcs = WCS(naxis=2)
79wcs.wcs.crpix = [512.0, 512.0] # One-based; reference is pixel (511, 511) here
80wcs.wcs.crval = [10.5, 41.2] # RA, Dec at reference pixel
81wcs.wcs.ctype = ['RA---TAN', 'DEC--TAN'] # Projection type
82wcs.wcs.cdelt = [-0.0001, 0.0001] # Pixel scale (degrees/pixel)
83wcs.wcs.cunit = ['deg', 'deg']
84wcs.array_shape = (1024, 1024) # (rows, columns), needed for footprint/bounds
85```

Creating WCS

from astropy.wcs import WCS

# Create new WCS
wcs = WCS(naxis=2)
wcs.wcs.crpix = [512.0, 512.0]  # One-based; reference is pixel (511, 511) here
wcs.wcs.crval = [10.5, 41.2]     # RA, Dec at reference pixel
wcs.wcs.ctype = ['RA---TAN', 'DEC--TAN']  # Projection type
wcs.wcs.cdelt = [-0.0001, 0.0001]  # Pixel scale (degrees/pixel)
wcs.wcs.cunit = ['deg', 'deg']
wcs.array_shape = (1024, 1024)  # (rows, columns), needed for footprint/bounds
Raw lines 72 to 86
87### Footprint and Coverage
89```python
90# Calculate image footprint (corner coordinates)
91footprint = wcs.calc_footprint()
92# Returns [RA, Dec] corners for this celestial WCS; shape must be known.
94# Round trip and safe array indexing
95world = wcs.pixel_to_world(511, 511)
96x, y = wcs.world_to_pixel(world)
97assert np.allclose([x, y], [511, 511], atol=1e-7)
98row, col = wcs.world_to_array_index(world)
99assert 0 <= row < wcs.array_shape[0] and 0 <= col < wcs.array_shape[1]
100```

Footprint and Coverage

# Calculate image footprint (corner coordinates)
footprint = wcs.calc_footprint()
# Returns [RA, Dec] corners for this celestial WCS; shape must be known.

# Round trip and safe array indexing
world = wcs.pixel_to_world(511, 511)
x, y = wcs.world_to_pixel(world)
assert np.allclose([x, y], [511, 511], atol=1e-7)
row, col = wcs.world_to_array_index(world)
assert 0 <= row < wcs.array_shape[0] and 0 <= col < wcs.array_shape[1]
Raw lines 87 to 101
102## NDData (astropy.nddata)
104Container for n-dimensional datasets with metadata, uncertainty, and masking.

NDData (astropy.nddata)

Container for n-dimensional datasets with metadata, uncertainty, and masking.

Raw lines 102 to 105
106### Creating NDData
108```python
109from astropy.nddata import NDData
110import numpy as np
111import astropy.units as u
113# Basic NDData
114data = np.random.random((100, 100))
115ndd = NDData(data)
117# With units
118ndd = NDData(data, unit=u.electron/u.s)
120# With an illustrative uncertainty array; replace with a calibrated noise model.
121# sqrt(data) is not a general uncertainty model for calibrated rates.
122from astropy.nddata import StdDevUncertainty
123uncertainty = StdDevUncertainty(np.sqrt(data))
124ndd = NDData(data, uncertainty=uncertainty, unit=u.electron/u.s)
126# With mask
127mask = data < 0.1 # Mask low values
128ndd = NDData(data, mask=mask)
130# With WCS
131from astropy.wcs import WCS
132ndd = NDData(data, wcs=wcs)
133```

Creating NDData

from astropy.nddata import NDData
import numpy as np
import astropy.units as u

# Basic NDData
data = np.random.random((100, 100))
ndd = NDData(data)

# With units
ndd = NDData(data, unit=u.electron/u.s)

# With an illustrative uncertainty array; replace with a calibrated noise model.
# sqrt(data) is not a general uncertainty model for calibrated rates.
from astropy.nddata import StdDevUncertainty
uncertainty = StdDevUncertainty(np.sqrt(data))
ndd = NDData(data, uncertainty=uncertainty, unit=u.electron/u.s)

# With mask
mask = data < 0.1  # Mask low values
ndd = NDData(data, mask=mask)

# With WCS
from astropy.wcs import WCS
ndd = NDData(data, wcs=wcs)
Raw lines 106 to 134
135### CCDData for CCD Images
137```python
138from astropy.nddata import CCDData
140# Create CCDData
141ccd = CCDData(data, unit=u.adu, meta={'object': 'M31'})
143# Read from FITS
144ccd = CCDData.read('image.fits', unit=u.adu)
146# Write to FITS
147ccd.write('output.fits', overwrite=True)
148```

CCDData for CCD Images

from astropy.nddata import CCDData

# Create CCDData
ccd = CCDData(data, unit=u.adu, meta={'object': 'M31'})

# Read from FITS
ccd = CCDData.read('image.fits', unit=u.adu)

# Write to FITS
ccd.write('output.fits', overwrite=True)
Raw lines 135 to 149
150## Modeling (astropy.modeling)
152Framework for creating and fitting models to data.

Modeling (astropy.modeling)

Framework for creating and fitting models to data.

Raw lines 150 to 153
154### Common Models
156```python
157from astropy.modeling import models, fitting
158import numpy as np
160# 1D Gaussian
161gauss = models.Gaussian1D(amplitude=10, mean=5, stddev=1)
162x = np.linspace(0, 10, 100)
163y = gauss(x)
165# 2D Gaussian
166gauss_2d = models.Gaussian2D(amplitude=10, x_mean=50, y_mean=50,
167 x_stddev=5, y_stddev=3)
169# Polynomial
170poly = models.Polynomial1D(degree=3)
172# Power law
173power_law = models.PowerLaw1D(amplitude=10, x_0=1, alpha=2)
174```

Common Models

from astropy.modeling import models, fitting
import numpy as np

# 1D Gaussian
gauss = models.Gaussian1D(amplitude=10, mean=5, stddev=1)
x = np.linspace(0, 10, 100)
y = gauss(x)

# 2D Gaussian
gauss_2d = models.Gaussian2D(amplitude=10, x_mean=50, y_mean=50,
                              x_stddev=5, y_stddev=3)

# Polynomial
poly = models.Polynomial1D(degree=3)

# Power law
power_law = models.PowerLaw1D(amplitude=10, x_0=1, alpha=2)
Raw lines 154 to 175
176### Fitting Models to Data
178```python
179# Generate noisy data
180true_model = models.Gaussian1D(amplitude=10, mean=5, stddev=1)
181x = np.linspace(0, 10, 100)
182y_true = true_model(x)
183rng = np.random.default_rng(42)
184y_noisy = y_true + rng.normal(0, 0.5, x.shape)
186# Fit model
187fitter = fitting.TRFLSQFitter() # Current recommended bounded nonlinear fitter
188initial_model = models.Gaussian1D(amplitude=8, mean=4, stddev=1.5)
189fitted_model = fitter(initial_model, x, y_noisy)
191print(f"Fitted amplitude: {fitted_model.amplitude.value}")
192print(f"Fitted mean: {fitted_model.mean.value}")
193print(f"Fitted stddev: {fitted_model.stddev.value}")
194```

Fitting Models to Data

# Generate noisy data
true_model = models.Gaussian1D(amplitude=10, mean=5, stddev=1)
x = np.linspace(0, 10, 100)
y_true = true_model(x)
rng = np.random.default_rng(42)
y_noisy = y_true + rng.normal(0, 0.5, x.shape)

# Fit model
fitter = fitting.TRFLSQFitter()  # Current recommended bounded nonlinear fitter
initial_model = models.Gaussian1D(amplitude=8, mean=4, stddev=1.5)
fitted_model = fitter(initial_model, x, y_noisy)

print(f"Fitted amplitude: {fitted_model.amplitude.value}")
print(f"Fitted mean: {fitted_model.mean.value}")
print(f"Fitted stddev: {fitted_model.stddev.value}")
Raw lines 176 to 195
196### Compound Models
198```python
199# Add models
200double_gauss = models.Gaussian1D(amplitude=5, mean=3, stddev=1) + \
201 models.Gaussian1D(amplitude=8, mean=7, stddev=1.5)
203# Compose models
204composite = models.Gaussian1D(amplitude=10, mean=5, stddev=1) | \
205 models.Scale(factor=2) # Scale output
206```

Compound Models

# Add models
double_gauss = models.Gaussian1D(amplitude=5, mean=3, stddev=1) + \
               models.Gaussian1D(amplitude=8, mean=7, stddev=1.5)

# Compose models
composite = models.Gaussian1D(amplitude=10, mean=5, stddev=1) | \
            models.Scale(factor=2)  # Scale output
Raw lines 196 to 207
208## Visualization (astropy.visualization)
210Tools for visualizing astronomical images and data.

Visualization (astropy.visualization)

Tools for visualizing astronomical images and data.

Raw lines 208 to 211
212### Image Normalization
214```python
215from astropy.visualization import simple_norm
216import matplotlib.pyplot as plt
218# Load image
219from astropy.io import fits
220data = fits.getdata('image.fits')
222# Normalize for display
223norm = simple_norm(data, 'sqrt', percent=99)
225# Display
226plt.imshow(data, norm=norm, cmap='gray', origin='lower')
227plt.colorbar()
228plt.show()
229```
231Check finite pixels and `vmin <= vmax` before plotting. Nearly constant images
232can produce degenerate ZScale limits; use an explicit scientifically appropriate
233display range in that case. Display normalization never calibrates the data.

Image Normalization

from astropy.visualization import simple_norm
import matplotlib.pyplot as plt

# Load image
from astropy.io import fits
data = fits.getdata('image.fits')

# Normalize for display
norm = simple_norm(data, 'sqrt', percent=99)

# Display
plt.imshow(data, norm=norm, cmap='gray', origin='lower')
plt.colorbar()
plt.show()

Check finite pixels and vmin <= vmax before plotting. Nearly constant images can produce degenerate ZScale limits; use an explicit scientifically appropriate display range in that case. Display normalization never calibrates the data.

Raw lines 212 to 234
235### Stretching and Intervals
237```python
238from astropy.visualization import (MinMaxInterval, AsinhStretch,
239 ImageNormalize, ZScaleInterval)
241# Z-scale interval
242interval = ZScaleInterval()
243vmin, vmax = interval.get_limits(data)
245# Asinh stretch
246stretch = AsinhStretch()
247norm = ImageNormalize(data, interval=interval, stretch=stretch)
249plt.imshow(data, norm=norm, cmap='gray', origin='lower')
250```

Stretching and Intervals

from astropy.visualization import (MinMaxInterval, AsinhStretch,
                                    ImageNormalize, ZScaleInterval)

# Z-scale interval
interval = ZScaleInterval()
vmin, vmax = interval.get_limits(data)

# Asinh stretch
stretch = AsinhStretch()
norm = ImageNormalize(data, interval=interval, stretch=stretch)

plt.imshow(data, norm=norm, cmap='gray', origin='lower')
Raw lines 235 to 251
252### PercentileInterval
254```python
255from astropy.visualization import PercentileInterval
257# Show data between 5th and 95th percentiles
258interval = PercentileInterval(90) # 90% of data
259vmin, vmax = interval.get_limits(data)
261plt.imshow(data, vmin=vmin, vmax=vmax, cmap='gray', origin='lower')
262```

PercentileInterval

from astropy.visualization import PercentileInterval

# Show data between 5th and 95th percentiles
interval = PercentileInterval(90)  # 90% of data
vmin, vmax = interval.get_limits(data)

plt.imshow(data, vmin=vmin, vmax=vmax, cmap='gray', origin='lower')
Raw lines 252 to 263
264## Constants (astropy.constants)
266Physical and astronomical constants with units.
268Note: through Astropy 7.x the default constants are CODATA 2018 + IAU 2015; Astropy 8.0 switches the default to CODATA 2022. For reproducible results across versions, select an explicit constants version via the science state (e.g., `astropy.physical_constants.set('codata2018')` before other astropy imports).
270```python
271from astropy import constants as const
273# Speed of light
274c = const.c
275print(f"c = {c}")
276print(f"c in km/s = {c.to(u.km/u.s)}")
278# Gravitational constant
279G = const.G
281# Astronomical constants
282M_sun = const.M_sun # Solar mass
283R_sun = const.R_sun # Solar radius
284L_sun = const.L_sun # Solar luminosity
285au = const.au # Astronomical unit
286pc = const.pc # Parsec
288# Fundamental constants
289h = const.h # Planck constant
290hbar = const.hbar # Reduced Planck constant
291k_B = const.k_B # Boltzmann constant
292m_e = const.m_e # Electron mass
293m_p = const.m_p # Proton mass
294e = const.e # Elementary charge
295N_A = const.N_A # Avogadro constant
296```

Constants (astropy.constants)

Physical and astronomical constants with units.

Note: through Astropy 7.x the default constants are CODATA 2018 + IAU 2015; Astropy 8.0 switches the default to CODATA 2022. For reproducible results across versions, select an explicit constants version via the science state (e.g., astropy.physical_constants.set('codata2018') before other astropy imports).

from astropy import constants as const

# Speed of light
c = const.c
print(f"c = {c}")
print(f"c in km/s = {c.to(u.km/u.s)}")

# Gravitational constant
G = const.G

# Astronomical constants
M_sun = const.M_sun     # Solar mass
R_sun = const.R_sun     # Solar radius
L_sun = const.L_sun     # Solar luminosity
au = const.au           # Astronomical unit
pc = const.pc           # Parsec

# Fundamental constants
h = const.h             # Planck constant
hbar = const.hbar       # Reduced Planck constant
k_B = const.k_B         # Boltzmann constant
m_e = const.m_e         # Electron mass
m_p = const.m_p         # Proton mass
e = const.e             # Elementary charge
N_A = const.N_A         # Avogadro constant
Raw lines 264 to 297
298### Using Constants in Calculations
300```python
301# Calculate Schwarzschild radius
302M = 10 * const.M_sun
303r_s = 2 * const.G * M / const.c**2
304print(f"Schwarzschild radius: {r_s.to(u.km)}")
306# Calculate escape velocity
307M = const.M_earth
308R = const.R_earth
309v_esc = np.sqrt(2 * const.G * M / R)
310print(f"Earth escape velocity: {v_esc.to(u.km/u.s)}")
311```

Using Constants in Calculations

# Calculate Schwarzschild radius
M = 10 * const.M_sun
r_s = 2 * const.G * M / const.c**2
print(f"Schwarzschild radius: {r_s.to(u.km)}")

# Calculate escape velocity
M = const.M_earth
R = const.R_earth
v_esc = np.sqrt(2 * const.G * M / R)
print(f"Earth escape velocity: {v_esc.to(u.km/u.s)}")
Raw lines 298 to 312
313## Convolution (astropy.convolution)
315Convolution kernels for image processing.
317```python
318from astropy.convolution import Gaussian2DKernel, convolve
320# Create Gaussian kernel
321kernel = Gaussian2DKernel(x_stddev=2)
323# Convolve image
324smoothed_image = convolve(data, kernel)
326# Handle NaNs
327from astropy.convolution import convolve_fft
328smoothed = convolve_fft(data, kernel, nan_treatment='interpolate')
329```

Convolution (astropy.convolution)

Convolution kernels for image processing.

from astropy.convolution import Gaussian2DKernel, convolve

# Create Gaussian kernel
kernel = Gaussian2DKernel(x_stddev=2)

# Convolve image
smoothed_image = convolve(data, kernel)

# Handle NaNs
from astropy.convolution import convolve_fft
smoothed = convolve_fft(data, kernel, nan_treatment='interpolate')
Raw lines 313 to 330
331## Stats (astropy.stats)
333Statistical functions for astronomical data.
335```python
336from astropy.stats import sigma_clip, sigma_clipped_stats
338# Sigma clipping
339clipped_data = sigma_clip(data, sigma=3, maxiters=5)
341# Get statistics with sigma clipping
342mean, median, std = sigma_clipped_stats(data, sigma=3.0)
344# Robust statistics
345from astropy.stats import mad_std, biweight_location, biweight_scale
346robust_std = mad_std(data)
347robust_mean = biweight_location(data)
348robust_scale = biweight_scale(data)
349```

Stats (astropy.stats)

Statistical functions for astronomical data.

from astropy.stats import sigma_clip, sigma_clipped_stats

# Sigma clipping
clipped_data = sigma_clip(data, sigma=3, maxiters=5)

# Get statistics with sigma clipping
mean, median, std = sigma_clipped_stats(data, sigma=3.0)

# Robust statistics
from astropy.stats import mad_std, biweight_location, biweight_scale
robust_std = mad_std(data)
robust_mean = biweight_location(data)
robust_scale = biweight_scale(data)
Raw lines 331 to 350
353### Data Downloads
355`download_file()` fetches a remote URL; persistent caching is opt-in (`cache=True`). Treat the URL as data you are disclosing to the remote host; do not pass confidential signed URLs or internal file locations unless the workflow explicitly permits it. Use `cache=False` for one-off downloads that should not be retained in Astropy's cache.
357```python
358from astropy.utils.data import download_file
360# Download file (caches locally)
361url = 'https://example.com/data.fits'
362local_file = download_file(url, cache=True)
363```

Data Downloads

download_file() fetches a remote URL; persistent caching is opt-in (cache=True). Treat the URL as data you are disclosing to the remote host; do not pass confidential signed URLs or internal file locations unless the workflow explicitly permits it. Use cache=False for one-off downloads that should not be retained in Astropy's cache.

from astropy.utils.data import download_file

# Download file (caches locally)
url = 'https://example.com/data.fits'
local_file = download_file(url, cache=True)
Raw lines 353 to 364
365### Progress Bars
367```python
368from astropy.utils.console import ProgressBar
370with ProgressBar(len(data_list)) as bar:
371 for item in data_list:
372 # Process item
373 bar.update()
374```

Progress Bars

from astropy.utils.console import ProgressBar

with ProgressBar(len(data_list)) as bar:
    for item in data_list:
        # Process item
        bar.update()
Raw lines 365 to 375
376## SAMP (Simple Application Messaging Protocol)
378Astropy 8 deprecates `astropy.samp`; SAMP moved to `pyvo.samp` in PyVO 1.9.
379The following is an illustrative, unexecuted local-app integration requiring
380PyVO and a running hub. It sends the table URI to every connected SAMP client;
381use it only for an explicitly requested share and keep proprietary data local.
383```python
384from pyvo.samp import SAMPIntegratedClient
386# Connect to SAMP hub
387client = SAMPIntegratedClient()
388client.connect()
390# Broadcast table to other applications
391message = {
392 'samp.mtype': 'table.load.votable',
393 'samp.params': {
394 'url': 'file:///path/to/table.xml',
395 'table-id': 'my_table',
396 'name': 'My Catalog'
397 }
399client.notify_all(message)
401# Disconnect
402client.disconnect()
403```

SAMP (Simple Application Messaging Protocol)

Astropy 8 deprecates astropy.samp; SAMP moved to pyvo.samp in PyVO 1.9. The following is an illustrative, unexecuted local-app integration requiring PyVO and a running hub. It sends the table URI to every connected SAMP client; use it only for an explicitly requested share and keep proprietary data local.

from pyvo.samp import SAMPIntegratedClient

# Connect to SAMP hub
client = SAMPIntegratedClient()
client.connect()

# Broadcast table to other applications
message = {
    'samp.mtype': 'table.load.votable',
    'samp.params': {
        'url': 'file:///path/to/table.xml',
        'table-id': 'my_table',
        'name': 'My Catalog'
    }
}
client.notify_all(message)

# Disconnect
client.disconnect()
Raw lines 376 to 403