Simulation

wcc_etc.simulation.calculate_bg_normalization_magnitude(bg_surface_brightness, psf_area)[source]

Convert the Background Surface Brightness into the total magnitude given the PSF area.

The area needs to be in square arcseconds since the typical definition of Surface Brightness is in units of magnitudes per arcseconds^2.

Parameters:
  • bg_surface_brightness (float) – The background surface brightness in mag/arcsec^2.

  • psf_area (float) – The area of the PSF in arcsec^2.

Returns:

The total background magnitude.

Return type:

float

class wcc_etc.simulation.Simulation(telescope, sensor, scene=None, time=90, r_aper_mas=None, n_reads=1, meta={})[source]

Bases: _MetaHolder_

A class to manage and run image exposure simulations.

Variables:
  • telescope (Telescope) – The telescope used for the simulation.

  • sensor (Sensor) – The sensor used for the simulation.

  • scene (Scene) – The scene being observed.

  • psf_profile (dict) – Calculated PSF profile parameters.

classmethod from_config(config)[source]

Initialize a Simulation from a configuration dictionary.

Parameters:

config (dict) – Configuration dictionary containing ‘telescope’, ‘sensor’, and ‘scene’ keys.

Returns:

The initialized Simulation object.

Return type:

Simulation

classmethod from_sensor(sensor, scene=None)[source]

Naming simplification of from_sensor_and_scene().

Parameters:
Return type:

Simulation

classmethod from_sensor_and_scene(sensor, scene, gain_mode=None)[source]

Create a Simulation from a sensor specification and a scene.

Parameters:
  • sensor (str, list, tuple, or Sensor) – Sensor specification. If str, format ‘kind:band’.

  • scene (Scene) – The scene to observe.

  • gain_mode (str, optional) – Gain mode for detectors that have several (qCMOS: ‘high’ or ‘low’). Ignored when sensor is already a Sensor.

Return type:

Simulation

classmethod from_sensorfilter(sensorfilter, scene, gain_mode=None)[source]

Create a Simulation from a sensorfilter label, auto-selecting the PSF.

The label must be a key in sensor_info (e.g. ‘zwo:r’, ‘zwo:r+1’, ‘qcmos:bb’). The PSF matching the sensor’s focus_level is stored as default_psf; get_image_snr uses it when no psf= argument is given.

Parameters:
  • sensorfilter (str) – Label from sensor_info, format ‘kind:band’ (e.g. ‘zwo:r’, ‘zwo:r+1’, ‘qcmos:bb’). Only canonical labels are accepted; nicknames like ‘sony:r’ are not resolved here (use from_sensor_and_scene for those).

  • scene (Scene)

  • gain_mode (str, optional) – Gain mode for detectors that have several (qCMOS: ‘high’ or ‘low’).

Return type:

Simulation

set_scene(scene_or_config)[source]

Set the scene for the simulation.

Parameters:

scene_or_config (Scene or dict) – The Scene object or its configuration.

set_sensor(sensor_or_config)[source]

Set the sensor for the simulation.

Parameters:

sensor_or_config (Sensor or dict) – The Sensor object or its configuration.

set_telescope(telescope_or_config)[source]

Set the telescope for the simulation.

Parameters:

telescope_or_config (Telescope or dict) – The Telescope object or its configuration.

reset()[source]

Reset the simulation and its components.

update(**kwargs)[source]

Update simulation parameters.

The keyword should in principle have the following structure scene__source__mag = 21 to change the self.scene.source.mag parameter to 21.

To simplify the use, if the name has no ambiguity, you can skip the first structure elements. For instance, only scene__ has a source__mag. So source__mag=21 will automatically be associated with scene__source__mag=21. Same would work, for instance for dark_current: only detector__ has a dark_current mutable_parameter. However mag=21 is not clear enough, as scene__source__mag, scene__background__mag or scene__host__mag exist.

Parameters:

**kwargs – Parameters to update. Can use double-underscore for sub-elements.

update_telescope(**kwargs)[source]

Update telescope parameters.

Parameters:

**kwargs – Telescope parameters.

update_scene(**kwargs)[source]

Update scene parameters.

Parameters:

**kwargs – Scene parameters.

update_sensor(**kwargs)[source]

Update sensor parameters.

Parameters:

**kwargs – Sensor parameters.

get_parameter(name, as_dict=False)[source]

Get simulation or sub-element parameters.

Parameters:
  • name (str or list of str) – Parameter name(s).

  • as_dict (bool, optional) – Whether to return as a dictionary. Default is False.

Returns:

The requested parameters.

Return type:

list or dict

get_countrates(scene=None, band=None, units='adu/s', as_dict=True)[source]

DEPRECATED (Airy, in-aperture). Use _count_rate_components for the 2D path or get_image_snr. Retained for the analytic Airy methods.

get_signal_and_variance(time=None, units='e-', n_reads=None)[source]

Get the signal and total variance for a given exposure time.

Parameters:
  • time (float or Quantity, optional) – Exposure time. Defaults to self.meta[‘time’].

  • units (str, optional) – Units of the signal (‘e-’, ‘adu’). Default is “e-“.

  • n_reads (int, optional) – Number of coadded frames; the read-noise variance is incurred n_reads times. Defaults to self.meta[‘n_reads’] (or 1).

Returns:

(source_signal, total_variance)

Return type:

tuple

get_peak_pixel(time=None, units='adu', n_reads=None, *, psf=None, jitter_sigma_mas=None, npix=128, oversample=11)[source]

Brightest-pixel value for the actual (possibly defocused) PSF.

Peak = max over the per-frame clean image (_per_frame_clean_image_e): (source + unresolved contaminants) through the rendered PSF, plus extended profiles, sky, diffuse and dark, so defocused configs and an offset host are handled correctly. psf defaults to default_psf (diffraction-limited unless the simulation was built by from_sensorfilter). Saturation is per-frame (per-frame time = time / n_reads). Linear in time, so scalar or array time both work.

is_saturated(time=None, n_reads=None, *, psf=None, jitter_sigma_mas=None, npix=128, oversample=11)[source]

Whether the brightest pixel of the clean per-frame image reaches sensor.saturation_e (min of full well and ADC clip after bias), for the actual (possibly defocused) PSF. See get_peak_pixel and Sensor.saturation_limit for which limit binds. Same threshold as psfsim.saturation_mask_from_image_e and ImageSimulator.simulate (#64).

peak_pixel_fraction(psf=None, jitter_sigma_mas=None, npix=128, oversample=11)[source]

Fraction of total source flux in the brightest detector pixel for the actual (possibly defocused) PSF.

PSF-aware replacement for the retired psf_profile[‘peak_pixel_fraction’] (which was in-focus-Airy only). psf defaults to default_psf.

extended_rate_image(psf_norm, ctx, comps=None)[source]

PSF-convolved e-/s/pix image of the profiled (extended) elements.

Each profile is rendered at the integer centre of a grid padded by npix (rounded to even) and convolved with psf_norm in mode="same", whose kernel origin is that same integer centre (psfsim.grid_center). The host therefore lands wherever the PSF put the source – ctx.center or the PSF class’s natural centre, odd or even grid – and dx/dy are offsets from the source. The padding lets light from just outside the detector grid convolve inward – no edge loss. Zeros when the scene has no extended element.

get_image_snr(time=None, mags=None, psf=None, r_aper_mas=None, ee_frac=None, optimize=False, jitter_sigma_mas=None, n_reads=None, npix=128, oversample=11, warn=True)[source]

PSF-aware aperture signal-to-noise ratio.

Unlike get_snr_airy (the analytic Airy-disk approximation), this renders the given PSF (default AiryPSF) on the detector grid and computes the SNR for a circular aperture. get_snr now delegates to this method; the in-focus default-aperture case reproduces get_snr_airy to within ~1%. Aperture precedence: optimize > r_aper_mas > ee_frac; if none is given, default_aperture decides (70 mas for the Airy PSF, optimized otherwise).

Parameters:
  • time (float, array_like, or Quantity, optional) – Exposure time(s) in seconds (bare floats are interpreted as seconds). Defaults to meta[‘time’]. Must be finite and >= 0; zero time gives snr = 0. A scalar yields a dict of Python float/int; an array yields a dict of equal-length ndarrays.

  • mags (float, array_like, or None, optional) – Source magnitude(s) to evaluate at, sweeping the source brightness relative to its set magnitude (host/background/dark/read held fixed). None (default) leaves the source unchanged. A scalar yields a dict of floats; a 1-D array yields a dict of arrays over magnitude. time and mags may not both be arrays. Requires a scene with a source.

  • psf (PSFSource, optional) – PSF model. If None, uses default_psf (the focus-level PSF when built by from_sensorfilter, otherwise a diffraction-limited AiryPSF).

  • r_aper_mas (float, optional) – Fixed aperture radius (mas).

  • ee_frac (float, optional) – Aperture enclosing this fraction of the PSF.

  • optimize (bool, optional) – If True, use the radius that maximizes SNR.

  • jitter_sigma_mas (float, optional) – Override the telescope jitter (mas).

  • npix (int, optional) – Render grid size and oversampling. npix must be large enough to contain the PSF; the default (128) contains the bundled defocus PSFs for the current sensors, but very small pixels or stronger defocus may need a larger value.

  • oversample (int, optional) – Render grid size and oversampling. npix must be large enough to contain the PSF; the default (128) contains the bundled defocus PSFs for the current sensors, but very small pixels or stronger defocus may need a larger value.

  • warn (bool, optional) – If True (default), emit a warning when any pixel in the rendered per-frame image saturates. The ‘n_saturated’/’saturated’ values are returned regardless of this flag.

Returns:

‘snr’, ‘signal_e’, ‘noise_e’, ‘enclosed_fraction’, ‘r_aper_mas’, ‘n_pix’, plus saturation info ‘n_saturated’ (count of pixels in the rendered per-frame image at or above full well / ADC clip) and ‘saturated’ (bool). Values are Python float/int/bool for scalar time and mags, or ndarrays (n_pix/n_saturated as int, saturated as bool) when time or mags is an array.

Return type:

dict

get_image_exptime_for_snr(snr, psf=None, r_aper_mas=None, ee_frac=None, optimize=False, jitter_sigma_mas=None, n_reads=None, npix=128, oversample=11, warn=True)[source]

Exposure time (s) to reach a target SNR on the PSF-aware path.

Inverse of get_image_snr. Aperture precedence: optimize > r_aper_mas > ee_frac; if none is given default_aperture decides. Note optimize here picks the radius that reaches the target SNR fastest (minimum time), the inverse of get_image_snr’s max-SNR optimize. Returns a dict {‘time_s’, ‘snr’, ‘r_aper_mas’, ‘enclosed_fraction’, ‘n_pix’, ‘n_saturated’, ‘saturated’}.

Parameters:
  • psf (PSFSource, optional) – PSF model. If None, uses default_psf (the focus-level PSF when built by from_sensorfilter, otherwise a diffraction-limited AiryPSF).

  • warn (bool, optional) – If True (default), warn when the rendered image saturates at the solved time; ‘n_saturated’/’saturated’ are returned regardless.

get_snr(time=None, psf=None, r_aper_mas=None, ee_frac=None, optimize=False, jitter_sigma_mas=None, n_reads=None, npix=128, oversample=11, warn=True)[source]

Signal-to-noise ratio via the 2D image simulation (PSF-aware default).

Delegates to get_image_snr; see it for parameter details and the returned dict. time may be a scalar or an array (returns a dict of arrays). For the legacy analytic Airy approximation use get_snr_airy (deprecated).

Returns:

Same as get_image_snr: ‘snr’, ‘signal_e’, ‘noise_e’, ‘enclosed_fraction’, ‘r_aper_mas’, ‘n_pix’, ‘n_saturated’, ‘saturated’ (scalar values for scalar time, ndarrays for array time). Pass warn=False to silence the saturation warning.

Return type:

dict

plot_snr(time, **kwargs)[source]

SNR vs exposure time; see plotting.plot_snr.

plot(time, **kwargs)

SNR vs exposure time; see plotting.plot_snr.

plot_snr_vs_mag(mags, **kwargs)[source]

SNR vs source magnitude; see plotting.plot_snr_vs_mag.

plot_sweep(key, values, **kwargs)[source]

SNR vs any update() key; see plotting.plot_sweep.

plot_peak_pixel(time, **kwargs)[source]

Peak pixel vs time with the saturation ceiling; see plotting.plot_peak_pixel.

plot_exptime_for_snr(snr, **kwargs)[source]

Exposure time vs target SNR; see plotting.plot_exptime_for_snr.

plot_snr_vs_aperture(radii, **kwargs)[source]

SNR vs aperture radius; see plotting.plot_snr_vs_aperture.

get_snr_airy(time=None, n_reads=None, warn=True)[source]

DEPRECATED analytic Airy-disk SNR approximation.

Use get_snr, which computes the SNR via the 2D image simulation.

Parameters:
  • time (float or Quantity, optional) – Exposure time.

  • n_reads (int, optional) – Number of reads. Defaults to meta[‘n_reads’] if set, else 1.

  • warn (bool, optional) – If True (default), warn when the detector saturates at this time.

Returns:

The SNR.

Return type:

Quantity

get_exptime_for_snr(snr, n_reads=None, warn=True)[source]

Exposure time (seconds, Quantity) to reach a target SNR on the analytic (Airy) path. Inverse of get_snr_airy. Returns inf*u.s if the source rate is 0.

Parameters:
  • snr (float) – Target SNR.

  • n_reads (int, optional) – Number of reads. Defaults to meta[‘n_reads’] if set, else 1.

  • warn (bool, optional) – If True (default), warn when the detector saturates at the solved exposure time.

compute_psf_profile()[source]

Compute the PSF profile and associated metrics.

DEPRECATED: use psf_profile (the cached property) or the PSF-aware 2D path (get_image_snr / get_image_exptime_for_snr / get_peak_pixel).

Returns:

Dictionary containing ‘wavelength’, ‘r_psf_mas’, ‘psf1d’, ‘ee’, ‘ee_at_aper’, ‘num_psf_pixels’, and ‘psf_area’.

Return type:

dict

has_element(which)[source]

Check if the simulation has a specific component.

Parameters:

which (str) – Component name (‘telescope’, ‘sensor’, ‘scene’).

Return type:

bool

property scene

The Scene object.

property telescope

The Telescope object.

property sensor

The Sensor object.

property meta

Combined metadata from simulation and components.

default_aperture(psf)[source]

Aperture mode used when a call gives no r_aper_mas/ee_frac/optimize.

Returns (r_aper_mas, optimize): the Simulation’s own r_aper_mas if one was set; else 70 mas for an AiryPSF; else optimize=True, since the fixed Airy-core aperture holds only a few percent of a defocused PSF (#95).

property default_psf

The PSF this simulation uses when no psf= argument is given.

from_sensorfilter sets this from the filter’s focus level (an AiryPSF for 0wave, a DefocusPSF for 1wave / 2wave); every other construction path leaves it diffraction limited.

property mutable_parameters

List of all mutable parameters (including sub-elements).

property effective_wavelength

Photon-weighted effective wavelength of the bandpass times the source SED.

This is the wavelength the PSF is rendered at (see wcc_etc.psfsim.AiryPSF). Unlike sensor.wavelength – the pivot wavelength, a property of the throughput curve alone – it moves with the colour of the source, which matters because the diffraction scale is linear in wavelength. Through the WCC broad band the pivot is 582 nm for every source, while the photons actually delivered average 541 nm for an O5V and 716 nm for an M5V; rendering the M5V PSF at the pivot overpredicts its central-pixel fraction by ~26% and so predicts premature saturation for red sources.

Falls back to sensor.wavelength when the simulation carries no source spectrum. Cached, and invalidated by update / set_scene / set_sensor / set_telescope / reset like the render caches.

Returns:

The effective wavelength, in nm.

Return type:

Quantity

polychromatic_psf(n_sub=7, base=None)[source]

A photon-weighted polychromatic PSF for this simulation’s band and source.

The monochromatic default (rendered at effective_wavelength) already removes the colour bias in the PSF width; this goes further and coadds n_sub renders across the band, so the wings and the diffraction ring structure are broadened the way a real broadband PSF is.

Parameters:
  • n_sub (int, optional) – Number of equal-photon-weight sub-bands. Default 7. Cost is linear in this; 5-9 is plenty for a broad optical band.

  • base (PSFSource, optional) – PSF rendered at each sub-band wavelength. Defaults to the simulation’s own default PSF (AiryPSF unless from_sensorfilter selected a defocused one).

Returns:

Pass it as psf= to get_image_snr / get_peak_pixel / peak_pixel_fraction / ImageSimulator.simulate.

Return type:

PolychromaticPSF

Examples

>>> sim.get_peak_pixel(10, psf=sim.polychromatic_psf())
property psf_profile

The calculated PSF profile.