Scattered-light halo (wcc_sim.scatter)
Scattered-light halo as an azimuthally averaged radial profile.
The Lazuli stray-light budget comes from a FRED ray trace of the telescope
at 450 nm, which wcc_etc ships and combines with a diffraction core in
wcc_etc.scatter_psf.make_total_psf:
total = (1 - f_scat) * core + f_scat * scatter / P_full
f_scat is the instrument-wide scattered fraction – a property of the
optics, not of any stamp. FRED quotes it two ways:
P_FULL = 5.542e-3 per watt launched at the aperture, and F_FOCAL =
7.13e-3 per watt reaching the image plane; they differ by the mirror
throughput (0.777). A unit-sum PSF describes detected photons, so wcc-sim
uses F_FOCAL by default and records the choice as SCATNORM in the
FITS header. wcc_etc.scatter_psf.make_total_psf still uses P_FULL,
which understates the halo by 22%; pass frac_total=table.meta["P_FULL"]
to reproduce it.
Why a 1D profile and not a 2D kernel
The FRED grid has 0.4 mm cells, which at the WCC’s 3.76 um pixels is 106
detector pixels per cell, and the map is azimuthally symmetric to 0.3-2%
rms at every radius from 1 mm to 130 mm. So the halo carries no azimuthal
information worth keeping and is intrinsically smooth on a ~100 px scale:
a radial profile reproduces it to within its own symmetry scatter, in ~10 kB
instead of the ~10^9 pixels a full-plane 2D kernel would need. Convolving
with such a kernel would also cost more than the rest of the simulation put
together, and would forfeit the sub-pixel star placement that
wcc_sim.render.add_star() gets from the oversampled core stamp.
The halo is therefore added as a second, additive term rather than spliced onto the core: no join radius, no crossfade, and no amplitude step to reconcile. It reaches to 138 mm = 36,800 detector pixels, well beyond the 11,500 px chip diagonal, so a star in one corner of the array still deposits a correct halo in the opposite corner.
The profile keeps FRED’s absolute surface brightness and is truncated at that reach rather than renormalized to unit energy. Renormalizing would inflate the halo by ~1/0.875, corrupting the far-field brightness that is the whole reason for using the map; and extrapolating a power law past the edge invents 41% of a unit of flux the map does not contain. The 12.5% of the scattered power that lies outside the modelled disc sits in the FRED map’s long-axis corners, 37,000+ px off-axis – off any detector, so it is not deposited and not counted.
The FRED run is monochromatic at 450 nm and the halo is treated as
achromatic here. Real surface-scatter BRDF falls roughly as lambda^-2, so
the halo is over-predicted in redder bands; WAVELEN is recorded in the
profile metadata so that can be revisited.
- wcc_sim.scatter.PROFILE_PATH = '/home/docs/checkouts/readthedocs.org/user_builds/schmidt-observatories-wcc-sim/checkouts/latest/src/wcc_sim/data/psfs/lazuli_scatter_450nm_profile.ecsv'
Packaged azimuthal profile, built from the FRED map by
scripts/build_scatter_profile.py. The 24 MB.fgdand the 75 MB combined-PSF FITS are deliberately not in the repo: runtime needs neither.
- class wcc_sim.scatter.ScatterHalo(r_px, frac_per_px, frac_total, meta=<factory>)[source]
Bases:
objectAzimuthally averaged scattered-light halo on detector pixels.
- Parameters:
r_px (ndarray) – Ascending radii [detector px] at which the profile is tabulated.
frac_per_px (ndarray) – PSF fraction per detector pixel at
r_px– already carrying the scattered fraction, so it is directly comparable withwcc_sim.wings.WingModel.profile().frac_total (float) – The instrument-wide scattered fraction this profile represents. Stored as metadata;
energy_beyond()integrates the table itself rather than trusting it.flat (Inside r_px[0] the profile is held)
across (detector pixels)
halo (divergence. It does not matter -- the diffraction core outruns the)
core (near the)
a (and extrapolating a power law inward would invent)
halo
there. (by ~10^9)
meta (dict)
- wcc_sim.scatter.build_scatter_table(fgd_path=None, n_bins=300)[source]
Azimuthally average a FRED
.fgdstray-light map into a radial table.Reads the map through wcc_etc’s own
read_fgd/fgd_gridso the two packages cannot drift apart on the grid convention (FRED’s axis limits are cell edges, so samples sit at cell centers).Only annuli that lie entirely inside the map are used. The map is rectangular (+/-335 x +/-140 mm), so averaging over partial annuli beyond the short half-axis and then multiplying by a full 2*pi*r biases the integral high – by 13% when measured against FRED’s exact 2D integral.
- Returns:
Columns
r_mmandfrac_per_mm2(halo fraction per mm^2, normalized so the profile integrates to 1 over the plane), with the provenance and the pre-normalization diagnostics in.meta.- Return type:
- wcc_sim.scatter.scatter_halo(mm_per_pixel, frac_total=None, table=None)[source]
A
ScatterHaloon the pixel grid of a detector.- Parameters:
mm_per_pixel (float) – Detector pixel pitch at the focal plane [mm]; sets the conversion from the profile’s physical units to fraction per detector pixel.
frac_total (float, optional) – Instrument-wide scattered fraction. Defaults to
F_FOCALfrom the profile metadata (7.13e-3 for the packaged profile): scattered power as a fraction of the power reaching the image plane, which is what a unit-sum PSF of detected photons needs.table.meta["P_FULL"](5.542e-3, per watt launched at the aperture) is the wcc_etc convention.meta["SCATNORM"]records which one was used (F_FOCAL,P_FULLorUSER).table (astropy.table.Table, optional) – A profile table; defaults to the packaged one.
- wcc_sim.scatter.halo_for_sensorfilter(sensorfilter, frac_total=None, table=None, sim=None)[source]
A
ScatterHaloon the pixel grid of a wcc_etc sensorfilter.