Relative Photometry
Relative photometry ensures that all images in a field are on the same flux scale, so that the same star has the same measured brightness regardless of which exposure it was taken from. This matters in two contexts:
Mosaicking: SWarp co-adds overlapping CCD images into tile mosaics. If two exposures are not on the same flux scale, the mosaic will have visible brightness steps at the seams between them.
Continuum subtraction:
CleanStarsbuilds a continuum image from broadband (r) and off-line (N708) data and subtracts it from each emission-line image. The stellar flux must be the same in both images so that stars cancel cleanly. A 5% error in the relative scale between filters leaves 5% stellar residuals in the subtracted image.
The Standard Approach: MAGZERO
The NOIRLAB community pipeline assigns a photometric zero point (MAGZERO)
to each MEF file. MefPrep uses this to rescale every CCD image:
After this step, 1 DN in every output image corresponds to a source of magnitude 28, regardless of the original exposure time, filter, or night. All images can then be directly compared or co-added.
In practice the NOIRLAB MAGZERO values are consistent across exposures
to ~10–15 mmag, which is sufficient for most science. The tools below
allow you to verify this for your specific dataset and, if necessary,
improve it.
Checking Consistency
The diagnostic workflow uses MefPhot, CalcZeroPoint, and PhotEval.
All three work on the raw MEF files — it is not necessary to re-run photometry
on the DECam_PREP/ output. The reason is that the magnitude you would
measure in a PREP image,
is identical to the corrected magnitude magc that PhotEval already
computes from the TabPhot/ catalogs.
MEF files
│
▼
MefPhot.py -cat smash (or -cat gaia for emission-line filters)
│
▼ TabPhot/*.smash.fits
│
├── CalcZeroPoint.py -filter r
│ ──► zeropoints.fits (per-file ZP and delta_zp)
│ Summary/{field}_mef.tab (ZP columns appended)
│
└── PhotEval.py -filter r [-zp_table zeropoints.fits]
──► phot_eval_r.fits (per-star scatter statistics)
Step 1: Forced photometry on MEF files
MefPhot measures stellar fluxes at catalog positions in each CCD
extension of every MEF file:
# r-band: use SMASH (DECam photometric system, deep, good star-galaxy separation)
MefPhot.py -cat smash -r 6 DECam_MEF/LMC_c42/*.fits
# Emission-line filters or fields outside LMC/SMC: use Gaia
MefPhot.py -r 6 DECam_MEF/LMC_c42/*.fits
Output tables are written to TabPhot/:
TabPhot/<root>.smash.fits— SMASH-matched photometryTabPhot/<root>.gaia.fits— Gaia-matched photometry
Each table carries phot_mag (instrumental magnitude, ZP = 28) and
MAGZERO (from the MEF header).
By default MefPhot restricts photometry to catalog stars in the range
14 < R < 22. Stars brighter than 14 are likely saturated; stars fainter
than 22 have negligible weight in the calibration fits and would only slow
processing. The limits are stored in the output header (MAGBRITE,
MAGFAINT) and can be overridden with -mag_bright and -mag_faint.
This step needs to be run only once. The same TabPhot/ files feed both
the consistency checks below and the absolute calibration tools in
Absolute Photometry.
Step 2: Per-frame zero-point summary
CalcZeroPoint reads the TabPhot/ files and computes a sigma-clipped
median zero point for each file independently of the header MAGZERO:
CalcZeroPoint.py -filter r # r-band SMASH files
CalcZeroPoint.py -filter N662 # Ha, Gaia files
CalcZeroPoint.py -filter N673 # [SII], Gaia files
Key output columns in zeropoints.fits:
Column |
Meaning |
|---|---|
|
Sigma-clipped median ZP measured from reference stars |
|
Pipeline value from the MEF header |
|
|
|
Scatter of per-star ZP estimates within the file (reflects PSF and crowding effects, not frame-to-frame calibration) |
|
Number of reference stars used after sigma-clipping |
CalcZeroPoint also updates Summary/{field}_mef.tab automatically,
adding columns ZP_{cat}_{band}, ZP_std_{cat}_{band}, and
ZP_n_{cat}_{band} (e.g. ZP_smash_r, ZP_gaia_g).
Interpreting delta_zp:
A mean offset that is the same for all files in a filter indicates a systematic difference between the reference catalog and the pipeline photometric system (a color term). This does not affect how consistently frames are scaled relative to each other.
A scatter in delta_zp across files — quantified by std(delta_zp)
— indicates that some frames have genuinely different MAGZERO values.
This is what hurts consistency and is the case where the empirical correction
below is useful.
Step 3: Multi-frame scatter check
PhotEval groups all detections of the same star across overlapping frames
and computes per-star scatter statistics:
PhotEval.py -filter r -min_n 3 -o phot_eval_r.fits
The printed summary gives an immediate answer:
--- Summary (N sources) ---
magc_std : median=0.051 (header MAGZERO correction)
mag_err : median=0.056 (photon noise per measurement)
chi2_nu_c : median=0.996 (header MAGZERO)
The key metric is chi2_nu_c: the reduced chi-squared of
phot_mag + MAGZERO − 28 relative to the photon-noise expectation.
≈ 1 — the
MAGZEROvalues are mutually consistent at the photon-noise level. No further calibration is needed.Significantly > 1 — there is excess frame-to-frame scatter beyond photon noise. The empirical ZP correction (Empirical Zero-Point Correction (optional)) may help.
The 90th-percentile chi2_nu_c characterises the tail of inconsistent
sources (variable stars, blends, or chip-edge artefacts) even when the
bulk of the data is well-behaved.
To compare the header MAGZERO directly against the empirical ZP from
CalcZeroPoint, pass the zeropoints.fits table:
PhotEval.py -filter r -zp_table zeropoints.fits -o phot_eval_r_compare.fits
The summary will show both corrections side by side:
chi2_nu_c : median=0.996 (header MAGZERO)
chi2_nu_emp : median=1.024 (empirical ZP)
If chi2_nu_emp is not lower than chi2_nu_c, the header values are
already as consistent as possible and no further action is needed.
Inter-Filter Consistency
Intra-filter consistency (checked above with PhotEval) ensures that all
r-band exposures are mutually consistent. A separate question is whether
the r-band images and the emission-line images (N662, N673) are on the same
flux scale, which is what CleanStars requires.
CleanStars performs a straight subtraction:
A star cancels only if it has the same DN in both images. After MefPrep
has placed all images on the mag-28 scale, this is equivalent to asking
whether the star has the same MAGZERO-corrected magnitude
(\(m_c = m_{\rm phot} + \mathrm{MAGZERO} - 28\))
in both filters.
Diagnostic workflow:
# Run MefPhot with Gaia for ALL filters (no -cat smash)
MefPhot.py -r 6 DECam_MEF/LMC_c42/*.fits
# Compare r vs Hα
PhotAnal.py -filter1 r -filter2 N662 TabPhot/*.gaia.fits
# Compare r vs [SII]
PhotAnal.py -filter1 r -filter2 N673 TabPhot/*.gaia.fits
# Compare off-line vs Hα (N708 subtraction path)
PhotAnal.py -filter1 N708 -filter2 N662 TabPhot/*.gaia.fits
Interpreting the output:
The printed summary reports two scatter values:
--- Inter-filter consistency: r vs N662 ---
N stars matched : 3842
Mean Δmag (r−N662) : +0.021 mag
Scatter (total) : σ = 0.048 mag → 4.5% typical stellar residual
Color term b (G−R) : +0.028 mag/mag
Scatter (after color) : σ = 0.031 mag → 2.9% typical stellar residual [floor]
Mean Δmag: a systematic offset between the two zero points. This shifts all stars by the same factor and is the dominant error when it is non-zero. If
|mean Δmag| > 0.05, the MAGZERO for one filter may need adjustment.Scatter (total): includes both the color-term spread and any calibration noise.
Scatter (after color): the irreducible floor set by stellar color diversity. Stars of different temperatures have different flux ratios between the two filters; this scatter cannot be eliminated by an overall scale adjustment. It sets the minimum stellar residual that will remain after
CleanStarssubtraction.
The color term \(b\) describes how the filter-to-filter offset depends on stellar color (Gaia G−R). A large \(b\) means that hot blue stars and cool red stars subtract differently even with the best mean scale factor.
Diagnostic plots: PhotAnal saves two panels:
Δmag vs Gaia G−R color with the best-fit color term (left)
Histogram of residuals after removing the color fit (right)
Output files:
filter_compare_<f1>_<f2>.fits— per-star table with columnsSource_name,magc_1,magc_2,delta_mag,G_R(Gaia G−R color),delta_mag_corrected(residual after color-term removal).filter_compare_<f1>_<f2>.png— diagnostic plot.
Note
PhotAnal statistics are unweighted: every star in the magnitude range
contributes equally to the scatter and color-term fit regardless of
photometric S/N. This means the reported sigma_residual is a slight
overestimate of the true calibration floor, particularly near the faint
magnitude limit. For a cleaner result restrict the Gaia files to a
brighter limit (e.g. -mag_faint 20) when running MefPhot.
Running the complete check: CheckPhot
CheckPhot.py runs the full intra-filter and inter-filter workflow in a
single command:
CheckPhot.py LMC_c42
CheckPhot.py -np 16 LMC_c42 # more parallel workers for MefPhot
CheckPhot.py -no_mefphot LMC_c42 # skip MefPhot if TabPhot/ already exists
CheckPhot.py -o mydir LMC_c42 # write all outputs to mydir/ instead
CheckPhot.py -summary CheckPhot/LMC_c42_phot_check.fits # reprint summary only
It runs MefPhot (SMASH + Gaia), CalcZeroPoint, PhotEval, and
PhotAnal in sequence. All output files are collected in a single
CheckPhot/ directory (override with -o DIR) to avoid cluttering
the working directory. All filenames include the field name so multiple
fields share the directory without collision. The final summary is a
single FITS file with two named table extensions:
CheckPhot/LMC_c42_phot_check.fits # ext INTRA + INTER
Image groups
CheckPhot reads config/DeMCELS_images.txt (or the file named by
-config) to determine which exposures belong together. Each named
Image group identifies a set of exposures that will be co-added into one
mosaic tile:
# Image FILTER EXPTIME
N662 N662 400
N662 N662 600
N662 N662 800
N662_s N662 30
N662_s N662 60
r r 30
r r 60
r_s r 5
Groups whose label ends in _s are short-exposure (shallow) groups;
all others are long-exposure. The INTRA table has one row per Image
group that has matching files in TabPhot/. The INTER table compares
every pair of groups within the same tier (long or short) that have
different filters — these are auto-generated, so no filter list needs to
be hard-coded.
Intermediate products in CheckPhot/:
{field}_zeropoints_{label}.fits— CalcZeroPoint per-file ZP table{field}_phot_eval_{label}.fits— PhotEval per-source chi² table{field}_filter_compare_{l1}_{l2}.fits— PhotAnal matched-star table
To read the summary tables in Python:
from astropy.table import Table
intra = Table.read('CheckPhot/LMC_c42_phot_check.fits', hdu='INTRA')
inter = Table.read('CheckPhot/LMC_c42_phot_check.fits', hdu='INTER')
The -summary flag re-reads an existing FITS file and reprints the
formatted comparison without rerunning any photometry:
CheckPhot.py -summary CheckPhot/LMC_c42_phot_check.fits
The same output can be generated from Python:
import CheckPhot
CheckPhot.summarize('CheckPhot/LMC_c42_phot_check.fits')
or, if you already have the tables in memory:
from astropy.table import Table
intra = Table.read('CheckPhot/LMC_c42_phot_check.fits', hdu='INTRA')
inter = Table.read('CheckPhot/LMC_c42_phot_check.fits', hdu='INTER')
CheckPhot._print_summary(intra, inter, 'LMC_c42')
Interpreting the intra-filter table (INTRA)
The INTRA extension has one row per active Image group. Zero-point
columns are named {stat}_{catalog}_{ref} where {catalog} is
smash or gaia and {ref} is the reference band (r, g).
The per-source consistency columns are at the right.
Image Filt N_src chi2_emp MagZero GaiaG SmashR best
---------- ----- -------- -------- -------- -------- -------- -------
N662 N662 525648 1.913 0.0123 0.0441 0.0131 MagZero/SmashR
N662_s N662 225356 0.813 0.0362 0.0450 0.0341 MagZero/SmashR
N673 N673 527994 1.112 0.0113 0.0161 0.0104 MagZero/SmashR
N673_s N673 270392 0.762 0.0146 0.0188 0.0129 MagZero/SmashR
N708 N708 473965 1.329 0.0106 0.0508 0.0148 MagZero
N708_s N708 280882 0.921 0.0634 0.0259 0.0146 SmashR
r r 511339 0.926 0.0104 0.0131 0.0098 MagZero/SmashR
r_s r 336795 0.853 0.0149 0.0420 0.0129 MagZero/SmashR
The three sigma_c_* columns show the per-star scatter of
\(m_c = m_{\rm phot} + \mathrm{ZP_{ref}} - 28\) across all frames
of the same Image group, for three different choices of zero-point
reference. They answer the question: if we scale all frames of this
group to ZP = 28 using reference X, how consistently does the same star
come out?
sigma_c_magzero — uses the header MAGZERO (pipeline default).
sigma_c_gaia_g — uses a ZP measured directly from Gaia G magnitudes
for each frame. For broadband (r) filters this is competitive; for
narrowband filters the broad Gaia G passband introduces a color-term
scatter that makes it worse than MAGZERO.
sigma_c_smash_r — uses a ZP measured from SMASH r magnitudes. This
is competitive with MAGZERO for most groups and wins clearly when
MAGZERO is inconsistent (e.g. N708_s, where sigma_c_magzero = 0.063
vs sigma_c_smash_r = 0.015).
std_dzp_{ref} (MAD scatter of per-file MAGZERO deviations, robust to outlier exposures):
< 0.03 mag — excellent; header
MAGZEROvalues are mutually consistent.0.03–0.06 mag — moderate; narrowband filters often fall here due to atmospheric variability in the filter bandpass.
> 0.06 mag — investigate; consider the empirical ZP correction below.
mean_dzp_{ref} (systematic offset of header MAGZERO from measured ZP):
A small mean (< 0.02 mag) with low scatter is fine; it reflects a color term between the catalog and the DECam system.
A large mean (> 0.05 mag) means the pipeline MAGZERO is systematically wrong for that group. Check whether the same offset is present in both filters used for subtraction — if the offset is equal it cancels in the inter-filter comparison.
chi2_nu_c_emp_med (frame-to-frame consistency relative to photon noise, empirical noise model):
≈ 1 — frames are mutually consistent at the photon-noise level.
1.1–1.7 — moderate excess scatter; typical for narrowband filters where atmospheric airglow or extended nebulosity affects the background.
chi2_nu_c_90 >> 1— the high-chi2 tail in N662 and N673 consists largely of genuine emission-line objects (Be stars, symbiotic stars, etc.) varying intrinsically in Hα / [SII]. This is astrophysics, not a calibration problem.
Interpreting the inter-filter table (INTER)
The INTER extension has one row per Image-group pair. Pairs are formed
automatically within each tier: all combinations of long-exposure groups
with different filters, and separately all combinations of short-exposure
groups with different filters. The columns Image1, Image2,
Filter1, Filter2 identify each pair.
Pair N_stars ---- sigma_c ---- resid_MZ resid% ---- std_dzp ----
MagZero GaiaG SmashR MagZero GaiaG SmashR
---------------------- -------- -------- -------- -------- -------- ------ -------- -------- --------
r × N662 780502 0.0724 0.0726 0.0723 0.0568 5.4% 0.0257 0.0159 0.0370
r × N673 803780 0.0617 0.0617 0.0617 0.0471 4.4% 0.0166 0.0148 0.0233
r × N708 800114 0.1126 0.1128 0.1125 0.0793 7.6% 0.0215 0.0124 0.0293
N662 × N673 780732 0.0323 0.0328 0.0322 0.0325 3.0% 0.0219 0.0130 0.0360
N662 × N708 782266 0.0726 0.0732 0.0721 0.0571 5.4% 0.0263 0.0122 0.0403
N673 × N708 800184 0.0628 0.0630 0.0627 0.0465 4.4% 0.0207 0.0138 0.0268
sigma_c_magzero / sigma_c_gaia_g / sigma_c_smash_r (scatter of \(m_{c,F1} - m_{c,F2}\) per matched star, for three ZP references):
These three columns are the inter-filter analogue of the same-named columns in the INTRA table. The meaning is: after correcting both filters to ZP = 28 using reference X, how much does the same star’s flux ratio scatter from star to star? For the long-exposure LMC_c42 pairs above, all three references give essentially identical values — the dominant scatter is stellar color diversity, not calibration noise.
sigma_residual / residual_pct (continuum subtraction floor):
The scatter that remains after fitting and subtracting the best-fit
color term (Gaia G−R). This is the irreducible floor for CleanStars
stellar subtraction. Values of 4–8% are typical for DECam broadband vs
narrowband comparisons because of the stellar color diversity in the
LMC/SMC. A 6% residual means the brightest stars leave ±6% flux rings
after continuum subtraction. This floor cannot be improved by better
photometric calibration — it is set by the physics of the stellar
population.
mean_delta_mag (systematic inter-filter offset):
|mean_delta_mag| < 0.03 mag — acceptable; a small multiplicative correction is applied implicitly by the mean subtraction in CleanStars.
|mean_delta_mag| > 0.05 mag — significant; consider adjusting MAGZERO for one of the two filters.
color_term_b (slope of Δmag vs Gaia G−R):
A positive \(b\) for r vs N662/N673 means blue stars have a smaller r−narrowband offset than red stars; hot OB stars will be over-subtracted while cool giants are under-subtracted.
The sign flips to negative for N708 vs narrowband because N708 lies red-ward of the emission lines.
std_dzp_magzero / std_dzp_gaia_g / std_dzp_smash_r (image-to-image variation in the inter-filter mean offset, for three ZP references):
Each (Image1 file, Image2 file) pair contributes one mean Δmag; std_dzp
is the MAD scatter of those per-pair means. This is the inter-filter
analogue of std_dzp in the INTRA table: it measures how consistently
any two specific images from the two groups are scaled relative to each
other.
Values ≲ 0.02 mag mean image-to-image calibration is stable.
Values > 0.05 mag suggest individual image pairs are poorly matched and the continuum subtraction will vary from one set of images to another.
Note that for the long-exposure pairs above, Gaia G gives the smallest
std_dzp (~0.012–0.016 mag vs ~0.017–0.026 for MAGZERO), though the
difference is small compared with sigma_residual.
Printed summary
At the end of every CheckPhot run (and when called with -summary),
a formatted comparison table is printed that places the three ZP references
side by side and marks which is best for each Image group. The INTRA
section shows sigma_c for each ZP reference and the empirical chi2.
The INTER section shows sigma_c and resid_MZ (the continuum
subtraction floor using MAGZERO), with pairs sorted so that broadband ×
narrowband pairs appear first, r listed first within each pair, and
short-exposure pairs grouped separately below the long-exposure pairs.
Choosing a zero-point reference: for most Image groups and filter pairs,
MAGZERO and SMASH r give essentially identical sigma_c, and
GAIA G is noticeably worse for narrowband filters. The main exception
is any Image group where the MAGZERO values are internally inconsistent
(sigma_c_magzero >> sigma_c_smash_r); in that case SMASH r is
the better choice for MefPrep rescaling. The best column in the
printed INTRA summary flags this automatically.
The continuum subtraction floor (residual_pct) is the same regardless
of which ZP reference is used, because it is set by stellar color diversity
rather than calibration accuracy.
Empirical Zero-Point Correction (optional)
If std(delta_zp) is larger than ~0.03 mag, or if chi2_nu_c is
significantly above 1, you can replace the header MAGZERO with a value
measured directly from reference stars. This requires a second pass of
MefPrep.
CalcZeroPoint writes the empirical ZPs to Summary/{field}_mef.tab
automatically (Steps 1–2 above must already have been run). Re-run
MefPrep with -zp pointing at the appropriate column:
MefPrep.py -np 8 -zp ZP_smash_r LMC_c42 # r-band, SMASH
MefPrep.py -np 8 -zp ZP_gaia_g LMC_c42 # emission-line, Gaia
MefPrep falls back to header MAGZERO for any row where the column
value is absent or outside the range 20–35, with a warning. Every output
file records the ZP actually used:
ZP_USE— zero point applied for flux scalingZP_SRC— source: the column name (e.g.ZP_smash_r) orMAGZERO
After the second pass, continue the pipeline from SetupTile as normal.
Note
Use ZP_smash_r only for r-band images of LMC/SMC fields; SMASH does
not cover other regions or emission-line filters. Use ZP_gaia_g for
N662, N673, or fields outside the Magellanic Cloud footprint. Both
columns can coexist in the same _mef.tab file.
Output Files
File |
Created by |
Contents |
|---|---|---|
|
MefPhot -cat smash |
Per-star photometry at SMASH positions; |
|
MefPhot |
Per-star photometry at Gaia positions; same structure |
|
CalcZeroPoint |
Per-file: |
|
CalcZeroPoint |
Appended columns: |
|
PhotEval |
Per-star: |
|
CheckPhot |
Two-extension FITS: |