wcc_phot: differential photometry
The wcc_phot package measures a relative light curve from a series of
wcc-sim frames. run_photometry and PhotometryResult are re-exported
at the top level; the modules below are listed in pipeline order.
Orchestration: run_photometry and PhotometryResult (wcc_phot.pipeline)
Orchestration: frames -> select -> centroid -> photometry -> light curve.
- class wcc_phot.pipeline.PhotometryResult(stars: astropy.table.table.Table, measurements: astropy.table.table.Table, lightcurve: astropy.table.table.Table, params: dict = <factory>)[source]
Bases:
object- plot(ax=None)[source]
The relative light curve (same as
plot_lightcurve()).
- plot_field(frame, ax=None, **kw)[source]
framewith the apertures drawn (wcc_phot.report.plot_field()).
- report(path='wcc_phot_report.pdf', frame=None)[source]
Write the one-page diagnostic report (
wcc_phot.report.make_report()).
- wcc_phot.pipeline.run_photometry(frames, target, method='aperture', n_ref=10, r_ap=None, r_in=None, r_out=None, centroid_box=None, fit_shape=None, ee=0.95, oversample=5, iso_dmag=1.0, iso_radius=None, times=None, output=None, on_frame=None, report=None)[source]
Aperture or PSF photometry of a target + best n_ref reference stars.
frames is a sequence of wcc-sim FITS paths, HDULists, or SimulatedFields of the same field; target a Gaia source_id or (ra, dec) in degrees. Every star is re-centroided in every frame starting from its WCS-predicted position. When r_ap is None the aperture geometry comes from the model-PSF encircled energy (ee). times (optional, len == n frames) is recorded in the tables; frame index is used otherwise. on_frame (optional callable, e.g. a wcc_phot.live.LiveViewer) is called after each frame is measured with an event dict (frame, n_frames, time, image_e, wcs, x, y, roles, geom, flux_e, flux_err_e, flags, rel_flux, rel_flux_err, n_ref) for live display or custom hooks; the dict holds that frame’s full-resolution image, so a callback that retains it (rather than consuming it in place) pins every frame in memory and defeats the streaming above. report (optional path) writes the one-page PDF + PNG diagnostic report (wcc_phot.report.make_report) with the first frame in the field panel. Returns a PhotometryResult (optionally also written to output as a STARS/PHOT/LC FITS).
Loading frames (wcc_phot.io)
Load WCC frames (wcc-sim FITS path, HDUList, or SimulatedField) for photometry.
- class wcc_phot.io.Frame(image_e, satmask, catalog, wcs, meta)[source]
Bases:
objectOne WCC exposure: delivered electron image + catalog + WCS + metadata.
image_eis the delivered product,(ADU - bias) * gain, whether the frame came from a SimulatedField’simage_aduor a file’sSCI: the ADC ceiling and the zero clip are in it on every path (issue #11).
- wcc_phot.io.load_frame(source)[source]
Build a Frame from a FITS path, an open HDUList, or a SimulatedField.
- wcc_phot.io.frame_meta(source)[source]
Read a frame’s header metadata without loading the pixel data.
Same dispatch as load_frame (Frame / SimulatedField / FITS path / HDUList) but for a path it reads only the SCI header, so a frame series can be validated up front without holding every image in memory. Returns the same meta dict load_frame builds.
Aperture and centroid geometry (wcc_phot.geometry)
Aperture/centroid geometry, with defaults derived from the wcc-sim PSF model.
- class wcc_phot.geometry.PhotGeometry(r_ap, r_in, r_out, centroid_box, fit_shape, ee_fraction=None)[source]
Bases:
objectPixel-space geometry shared by centroiding and photometry.
- Parameters:
- wcc_phot.geometry.geometry_from_r_ap(r_ap, r_in=None, r_out=None, centroid_box=None, fit_shape=None, ee_fraction=None)[source]
PhotGeometry from an aperture radius; unset pieces get scaled defaults.
- wcc_phot.geometry.render_model_psf(meta, oversample=5)[source]
Oversampled model PSF for a frame’s sensorfilter/focus/jitter.
Target and reference selection (wcc_phot.select)
Target matching and best-N reference-star selection from a frame catalog.
- wcc_phot.select.pick_target(catalog, target, tol_arcsec=2.0)[source]
Catalog row index of the target.
target is a Gaia source_id (int) or an (ra, dec) pair in degrees; coordinate targets must match a catalog entry within tol_arcsec.
- wcc_phot.select.pick_references(catalog, target_idx, shape, geom, n_ref=10, iso_dmag=1.0, iso_radius=None)[source]
Indices of the best n_ref comparison stars.
Criteria: on-image, unsaturated, annulus + centroid box fully inside the frame, and isolated (no catalog neighbor within iso_radius px brighter than G + iso_dmag — saturated/off-frame neighbors count too). Survivors are ranked by
|G - G_target|, photometrically most similar first.
Centroiding (wcc_phot.centroid)
Per-frame re-centroiding of WCS-predicted star positions.
Center-of-mass (not a Gaussian fit) because the +1/+2-wave defocus PSFs are centrally-depressed donuts; COM in a box sized to the PSF works for both.
Aperture photometry (wcc_phot.apphot)
Aperture photometry with CCD-equation errors from the frame header rates.
- wcc_phot.apphot.aperture_photometry_frame(frame, x, y, geom)[source]
Background-subtracted aperture fluxes at (x, y) on the electron image.
Returns (flux_e, flux_err_e, bkg_e_pix, flags). Background is the sigma-clipped annulus median; the variance is Poisson of the star plus the per-pixel background variance from the header rates ((sky+dark)*exptime + n_reads*read_noise^2), including the background-estimate term (area^2/n_annulus).
PSF photometry (wcc_phot.psfphot)
PSF-model photometry using the wcc-sim PSF as a photutils ImagePSF.
- wcc_phot.psfphot.build_psf_model(meta, oversample=5, psf_os=None)[source]
ImagePSF for the frame’s sensorfilter/focus/jitter.
The oversampled stamp is normalized so data.sum()/oversample^2 == 1, making the fitted flux parameter the total detector-integrated electrons.
- wcc_phot.psfphot.fit_flags(result)[source]
FLAG_FIT where photutils flagged the fit or the flux is not finite.
photutils’ own flags column (npixfit, bounds, convergence, …) is folded into one package bit so downstream code has a stable test; the photutils table itself is not retained.
- wcc_phot.psfphot.psf_photometry_frame(frame, x, y, flux0, model, geom)[source]
Fit flux + position per star; returns (flux, flux_err, x_fit, y_fit, flags).
Initial positions/fluxes come from the centroid + aperture pass; the local background is estimated in the same annulus as aperture mode. Saturated pixels are masked out of the fit. flags carries FLAG_FIT for stars whose fit photutils flagged or that came back non-finite.
The differential light curve (wcc_phot.lightcurve)
Differential light curve from the per-star, per-frame measurement table.
- wcc_phot.lightcurve.ensemble_ratio(flux_t, err_t, ref_flux, ref_err)[source]
Target / reference-ensemble flux for one frame.
Returns (rel, rel_err, flux_ens, err_ens, ok). The ensemble is the sum of the given reference fluxes. The error uses var(T/R) = var(T)/R^2 + T^2 var(R)/R^4, which never divides by the target flux, so a zero target is a valid measurement of zero rather than a ZeroDivisionError. ok is False, and rel/rel_err are NaN, when the target flux is not finite or the ensemble is not finite or <= 0.
- wcc_phot.lightcurve.usable_references(measurements)[source]
Star indices of the references with flags == 0 in every frame.
A fixed subset keeps the ensemble baseline stable: dropping a reference from some frames only would step the light curve by that star’s share of the ensemble, an artificial transit. If no reference is clean in every frame, all of them are used and a warning says so.
- wcc_phot.lightcurve.build_lightcurve(measurements, refs=None)[source]
One row per frame: target, reference-ensemble, and relative fluxes.
The ensemble is the straight sum of the usable reference fluxes (refs, default usable_references); rel_flux = flux_target / flux_ens with errors propagated (see ensemble_ratio), and rel_flux_norm is rel_flux divided by its median over the frames where it is defined. A frame whose ratio is undefined keeps its row with NaN fluxes and FLAG_NOFLUX set instead of aborting the series. flags is the target’s own flags plus FLAG_NOFLUX; reference flags are reported through the selection, not here.
Quality flags (wcc_phot.flags)
Per-measurement quality flag bits (bitwise OR-able).
Diagnostic report and plot panels (wcc_phot.report)
One-page PDF + PNG diagnostic report for a photometry run.
make_report(result, frame=…, path=…) renders the field with the apertures, the relative light curve, per-star raw fluxes, centroid drifts, annulus backgrounds, and two noise diagnostics (binned RMS vs bin size, per-star RMS vs G against the predicted errors) into a single figure, written as both PDF and PNG. run_photometry(…, report=path) and the CLI –report flag call it with the first frame of the series.
Each panel is also a public plot_*(result, ax=None) function, and a
method of the same name on PhotometryResult,
so a notebook can draw one of them on its own axes.
Rendering uses a plain (pyplot-free) matplotlib Figure, so it works headless and never opens a window.
- wcc_phot.report.compute_metrics(result)[source]
Noise/quality numbers shown in the report’s summary panel.
Returns a dict: rel-flux RMS and MAD-RMS, median predicted error and the RMS/prediction ratio, target centroid RMS, per-star measured RMS and predicted errors (target first, refs in star order), and per-bit flagged-measurement counts.
- wcc_phot.report.plot_field(result, frame, ax=None, title='apertures at median centroids')[source]
The frame around the stars, with apertures at the median centroids.
frameis anythingwcc_phot.io.load_frame()accepts. Target aperture and annulus in red, references in teal. Returns the axes.
- wcc_phot.report.plot_lightcurve(result, ax=None, **errorbar_kw)[source]
The normalised relative light curve with its errors. Returns the axes.
Extra keywords go to
ax.errorbar(color=,fmt=,label=), so two results can be overlaid on one axes.
- wcc_phot.report.plot_binned_rms(result, ax=None)[source]
RMS of bin means against bin size, with the white-noise line. Returns the axes.
- wcc_phot.report.plot_raw_fluxes(result, ax=None)[source]
Target, ensemble and individual reference fluxes over their medians. Returns the axes.
- wcc_phot.report.plot_rms_vs_mag(result, ax=None, metrics=None)[source]
Per-star measured RMS against G, with the predicted errors. Returns the axes.
- wcc_phot.report.plot_centroids(result, ax=None, coord='x')[source]
Centroid drift of every star about its median,
coord= “x” or “y”. Returns the axes.
- wcc_phot.report.plot_background(result, ax=None)[source]
Annulus background per star and frame. Returns the axes.
- wcc_phot.report.make_report(result, frame=None, path='wcc_phot_report.pdf')[source]
Write the diagnostic report figure for a PhotometryResult.
frame (optional wcc-sim FITS path, HDUList, SimulatedField, or Frame) fills the field panel; without it that panel is left empty. path may end in .pdf or .png — both siblings are written either way. Returns the list of written paths.
Live viewer (wcc_phot.live)
Live matplotlib view of the photometry as each frame is analyzed.
Pass a LiveViewer as run_photometry(…, on_frame=viewer) (or use the CLI –live flag): the left panel shows the current frame with the target (red) and reference (teal) apertures at their re-centroided positions, the right panel grows the relative light curve point by point. Any callable accepting the same event dict works as on_frame, so custom displays (GUIs, DS9, web) can hook in the same way.
ETC cross-check (wcc_phot.validation)
Monte-Carlo cross-check of the wcc-etc SNR model against wcc-sim + wcc-phot.
The three packages make one chain: wcc_etc predicts a signal-to-noise
ratio from a closed-form aperture equation, wcc_sim renders a noisy frame
from the same instrument model, and wcc_phot measures a star on that
frame. Nothing in the test suites of the three packages checks that the
prediction and the measurement agree, and they are computed by different code
with different aperture conventions.
This module closes that loop empirically. It renders one isolated star, draws
many noise realisations of it through wcc_sim’s own noise stage, measures
each realisation, and compares the mean and the scatter of the measured
flux against what wcc_etc said they would be. The scatter across
realisations is the only truth reference available for the noise model –
every other check compares one approximation against another.
Three quantities are returned per configuration:
etc_apertureThe frame measured with the aperture convention
wcc_etcitself uses (psfsim._radial_cumulative: whole pixels, selected by pixel-centre distance) with the background subtracted at its known value. This is the like-for-like test of the model, and it is the one that should pass.pipelineThe frame measured by
wcc_phot.apphot.aperture_photometry_frame– exact-area photutils weights, background estimated from a sigma-clipped annulus, and the annulus penalty in the error budget. This is what a user actually gets, and it is not expected to match the ETC: the differences are real and are what this module quantifies.pipeline_errwcc_phot’s own analytic error bar, checked against its own empirical scatter.
Notes
The scene render is deterministic, so it is done once per configuration and only the noise stage is repeated. The noise model is per-pixel independent, so the frame is cropped to a window around the star; the crop is exact, not an approximation.
The fractional uncertainty on a standard deviation from n samples is
1/sqrt(2(n-1)), reported as sd_err. Do not read a noise ratio to
better than that.
- wcc_phot.validation.crosscheck(spt='G5V', sensorfilter='zwo:r', gmag=22.0, exptime=60.0, n_reads=1, focus=0, r_aper_mas=70.0, nseed=400, seed0=0, ra=150.0, dec=2.0, npix=None, wings=False, scatter=False)[source]
Compare the wcc-etc prediction against measured wcc-sim realisations.
- Parameters:
spt (str, float) – Pickles spectral type and Gaia G magnitude of the star.
wcc_simnormalises in Gaia G, so the ETC scene is built the same way and the two share a count rate by construction.gmag (str, float) – Pickles spectral type and Gaia G magnitude of the star.
wcc_simnormalises in Gaia G, so the ETC scene is built the same way and the two share a count rate by construction.sensorfilter (str, int) – Focal-plane position (
'zwo:r','qcmos:bb', …) and focus state (0, 1 or 2 waves).focus (str, int) – Focal-plane position (
'zwo:r','qcmos:bb', …) and focus state (0, 1 or 2 waves).exptime (float, int) – Exposure time in seconds and number of coadded frames.
n_reads (float, int) – Exposure time in seconds and number of coadded frames.
r_aper_mas (float) – Requested aperture radius. The radius actually used is the one
wcc_etcsnaps to, and it is applied to every measurement here so the comparison is at a common radius.nseed (int) – Noise realisations. The uncertainty on a measured standard deviation is
1/sqrt(2(nseed-1))– 3.5% at 400, 2.2% at 1000.wings (bool) – Off by default: the ETC’s aperture model has no wing or halo term, so leaving them on would compare against something the ETC never claimed to predict. Turn them on to measure that difference.
scatter (bool) – Off by default: the ETC’s aperture model has no wing or halo term, so leaving them on would compare against something the ETC never claimed to predict. Turn them on to measure that difference.
- Returns:
predicted(the ETC),etc_aperture,pipeline,pipeline_err, andmeta.- Return type:
- wcc_phot.validation.ANNULUS_VARIANCE_PENALTY = 1.25
wcc_phot’s default annulus is r_in = 1.5 r_ap, r_out = 2.5 r_ap, so its area is 4x the aperture’s and the background-estimate term inflates the variance by 1 + 1/4. wcc_etc assumes the background is known exactly and carries no equivalent term.
Command line (wcc_phot.cli)
Command-line interface: wcc-phot frame1.fits frame2.fits … -o phot.fits