PSFCraft — Introduction¶
What you will learn:
- What PSFCraft is and what problem it solves
- The core object model (telescope → PSF)
- How to compute and visualise a PSF in under 20 lines
What is PSFCraft?¶
PSFCraft is a Python library for simulating the Point Spread Function (PSF) of optical telescopes.
A PSF describes how a point source of light (e.g. a distant star) is spread across detector pixels by an optical system. Accurate PSF modelling is essential for:
- Astronomical image analysis (source detection, photometry, shape measurement)
- Optical system design and performance evaluation
- Training machine-learning models for wavefront sensing
PSFCraft builds on top of POPPY, a Fourier-optics propagation library, and adds:
- Ready-to-use telescope models (Newtonian, Euclid-like, …)
- Zernike-polynomial wavefront error (WFE) injection
- Polychromatic source support (black-body stellar spectra)
- Batch PSF dataset generation for ML pipelines
Minimal Example¶
The snippet below is everything you need to simulate your first PSF.
In [1]:
Copied!
%matplotlib inline
import psfcraft
import matplotlib.pyplot as plt
# --- 1. Create a Newtonian telescope (default: 0.5 m primary, 0.1 m secondary) ---
tel = psfcraft.NewtonianTelescope()
# --- 2. Compute a monochromatic PSF at 1 µm ---
psf = tel.calc_psf(monochromatic=1e-6, fov_pixels=64)
# --- 3. Display the result ---
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
psfcraft.display_psf(psf, ax=axes[0], title="Oversampled PSF (ext 0)", ext=0)
psfcraft.display_psf(psf, ax=axes[1], title="Detector-sampled PSF (ext 1)", ext=1)
plt.suptitle("First PSF with PSFCraft", fontsize=14)
plt.tight_layout()
plt.show()
%matplotlib inline
import psfcraft
import matplotlib.pyplot as plt
# --- 1. Create a Newtonian telescope (default: 0.5 m primary, 0.1 m secondary) ---
tel = psfcraft.NewtonianTelescope()
# --- 2. Compute a monochromatic PSF at 1 µm ---
psf = tel.calc_psf(monochromatic=1e-6, fov_pixels=64)
# --- 3. Display the result ---
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
psfcraft.display_psf(psf, ax=axes[0], title="Oversampled PSF (ext 0)", ext=0)
psfcraft.display_psf(psf, ax=axes[1], title="Detector-sampled PSF (ext 1)", ext=1)
plt.suptitle("First PSF with PSFCraft", fontsize=14)
plt.tight_layout()
plt.show()
pysynphot is not installed. Please install it to use PSF generation with polychromatic sources.
Understanding the output¶
calc_psf() returns a FITS HDUList with two extensions:
| Extension | Content | Sampling |
|---|---|---|
psf[0] |
Oversampled PSF (Fourier-optics resolution) | OVERSAMP × pixelscale |
psf[1] |
Detector-binned PSF | pixelscale (arcsec/pixel) |
You can inspect the metadata directly from the FITS header:
In [2]:
Copied!
psf.info()
print()
for ext in [0, 1]:
print(
f"Extension {ext}: "
f"oversample={psf[ext].header['OVERSAMP']}, "
f"pixelscale={psf[ext].header['PIXELSCL']:.4f} arcsec/pix, "
f"shape={psf[ext].data.shape}"
)
psf.info()
print()
for ext in [0, 1]:
print(
f"Extension {ext}: "
f"oversample={psf[ext].header['OVERSAMP']}, "
f"pixelscale={psf[ext].header['PIXELSCL']:.4f} arcsec/pix, "
f"shape={psf[ext].data.shape}"
)
Filename: (No file associated with this HDUList) No. Name Ver Type Cards Dimensions Format 0 OVERSAMP 1 PrimaryHDU 37 (256, 256) float64 1 DET_SAMP 1 ImageHDU 39 (64, 64) float64 Extension 0: oversample=4, pixelscale=0.0745 arcsec/pix, shape=(256, 256) Extension 1: oversample=1, pixelscale=0.2980 arcsec/pix, shape=(64, 64) Extension 0: oversample=4, pixelscale=0.0745 arcsec/pix, shape=(256, 256) Extension 1: oversample=1, pixelscale=0.2980 arcsec/pix, shape=(64, 64)
Key Takeaways¶
psfcraft.NewtonianTelescope()creates a ready-to-use reflector telescope model.tel.calc_psf(monochromatic=λ)runs a Fourier-optics propagation and returns a FITS HDUList.- The result always contains two images: oversampled (ideal) and detector-binned (realistic).
psfcraft.display_psf()provides a single-call convenience wrapper for visualisation.
Next steps:
→ 02_getting_started.ipynb — explore telescope parameters, pixel scale, and FOV.