photerr — photometric error modeling
You're helping a user work with photerr, a library for simulating realistic photometric errors for LSST/Rubin, Euclid, and Roman surveys. The package extends the Ivezic (2019) point-source error model to low-SNR regimes and extended sources, computing errors in flux space rather than magnitude space.
Install: pip install photerr (requires Python ≥ 3.10, NumPy ≥ 1.23, pandas ≥ 1.4)
Citation: Crenshaw et al. 2024, AJ, 168, 80
Core concepts
- Input: a pandas
DataFramewith columns named for each photometric band, containing true source magnitudes. - Output: a
DataFramewith the same columns containing simulated observed magnitudes (true + noise), plus error columns. - Noise is drawn in flux space; errors are Gaussian and include a systematic floor (
sigmaSys=0.005mag by default). - Non-detections are flagged with
np.infby default (configurable).
Survey models
| Class | Survey | Default bands |
|---|---|---|
LsstErrorModel (= V2) |
Rubin/LSST | u g r i z y |
LsstErrorModelV1 |
Rubin/LSST (old) | u g r i z y |
EuclidErrorModel (= Wide) |
Euclid wide | VIS Y J H |
EuclidDeepErrorModel |
Euclid deep | VIS Y J H |
RomanErrorModel (= Medium) |
Roman medium | Y J H |
RomanWideErrorModel |
Roman wide | H |
RomanDeepErrorModel |
Roman deep | Z Y J H F K W |
RomanUltraDeepErrorModel |
Roman ultra-deep | Y J H |
from photerr import (
LsstErrorModel,
EuclidErrorModel, EuclidDeepErrorModel,
RomanErrorModel, RomanWideErrorModel, RomanDeepErrorModel,
)
Basic usage
import pandas as pd
from photerr import LsstErrorModel
# Catalog of true magnitudes
catalog = pd.DataFrame({
"u": [23.5, 24.0], "g": [23.2, 24.1],
"r": [23.0, 23.9], "i": [22.9, 23.8],
"z": [22.8, 23.7], "y": [22.7, 23.6],
})
errModel = LsstErrorModel()
obs = errModel(catalog, random_state=42)
# obs has the same band columns (observed noisy magnitudes) plus
# u_err, g_err, ... error columns; non-detections are np.inf
Key parameters
All parameters can be overridden at construction time as keyword args. Dict values are per-band; a scalar applies to all bands.
| Parameter | Default (LSST) | Meaning |
|---|---|---|
nYrObs |
10 | Years of observation |
nVisYr |
per-band | Mean visits per year |
m5 |
computed | 5σ single-visit depth (mag) |
gamma |
0.039 | Ivezic (2019) band parameter |
theta |
per-band | PSF FWHM (arcsec) |
airmass |
per-band | Effective airmass |
km |
per-band | Atmospheric extinction |
sigmaSys |
0.005 | Systematic error floor (mag) |
scale |
1.0 | Per-band error scaling factor |
sigLim |
0 | Detection threshold (σ); 0 = keep all |
ndMode |
"flag" | Non-detection handling (see below) |
ndFlag |
np.inf |
Value for non-detected sources |
extendedType |
"point" | Aperture type for galaxies |
decorrelate |
True |
Decorrelate inter-band errors |
highSNR |
False |
Use high-SNR Gaussian approx |
errLoc |
"after" | "after": append band_err cols; "alone": return only errors |
absFlux |
False |
Work in absolute flux units instead of magnitudes |
majorCol |
"major" | Catalog column name for galaxy semi-major axis (arcsec) |
minorCol |
"minor" | Catalog column name for galaxy semi-minor axis (arcsec) |
renameDict |
{} |
Rename bands, e.g. {"u": "lsst_u"} |
Common customisations
# Year-1 depth
errModel = LsstErrorModel(nYrObs=1)
# Custom limiting magnitudes
errModel = LsstErrorModel(m5={"u": 23.5, "g": 24.0})
# Double errors in u and y (sensitivity study)
errModel = LsstErrorModel(scale={"u": 2.0, "y": 2.0})
# Rename bands to match catalog column names
errModel = LsstErrorModel(renameDict={"u": "lsst_u", "g": "lsst_g"})
Limiting magnitudes
# 5σ coadded depth (default)
m5 = errModel.getLimitingMags()
# Single-visit, 1σ
m1_single = errModel.getLimitingMags(nSigma=1, coadded=False)
# Extended source with 1-arcsec aperture
m5_ext = errModel.getLimitingMags(aperture=1.0)
Non-detection handling
# Default: flag non-detections as np.inf
errModel = LsstErrorModel(sigLim=0, ndMode="flag")
# Replace non-detections with the limiting magnitude
errModel = LsstErrorModel(sigLim=1, ndMode="sigLim")
# Keep only > 5σ detections; flag the rest
errModel = LsstErrorModel(sigLim=5, ndMode="flag")
Extended sources (galaxies)
The catalog must include galaxy half-light radii columns (major, minor in arcsec by default, overridable with majorCol/minorCol).
galaxy_catalog = pd.DataFrame({
"u": [24.0], "g": [23.8], "r": [23.5],
"i": [23.3], "z": [23.1], "y": [23.0],
"major": [0.5], # half-light radius (arcsec)
"minor": [0.4],
})
# AUTO: aperture scales with galaxy size (Rubin-like)
errModel = LsstErrorModel(extendedType="auto")
# GAAP: Gaussian-aperture-and-PSF (KiDS-like)
errModel = LsstErrorModel(extendedType="gaap")
obs = errModel(galaxy_catalog)
Multi-survey comparison
from photerr import LsstErrorModel, EuclidErrorModel, RomanDeepErrorModel
models = {
"LSST": LsstErrorModel(nYrObs=10),
"Euclid": EuclidErrorModel(),
"Roman deep": RomanDeepErrorModel(),
}
for name, m in models.items():
print(name, m.getLimitingMags())
Notes
errModel(catalog)returns a newDataFrame; it does not modify the input.- Pass
random_statefor reproducibility. LsstErrorModelis an alias forLsstErrorModelV2;LsstErrorModelV1uses the older single-visit depth calculation.- The
ErrorModelandErrorParamsbase classes are available for building custom survey models.