PSF and image simulation (wcc_etc.psfsim)
- class wcc_etc.psfsim.DetectorPSFContext(npix, pixel_size_um, plate_scale_mas, wavelength_m, diameter_m, fnum, jitter_sigma_mas=0.0, center=None, oversample=11)[source]
Bases:
objectDetector + optics parameters a PSF source needs to render onto the grid.
- Parameters:
- wcc_etc.psfsim.center_crop_or_pad(img, npix, fill=0.0)[source]
Center-crop or zero-pad a 2D array to (npix, npix), preserving the center.
- wcc_etc.psfsim.recenter(psf, center)[source]
Sub-pixel shift a grid-centered PSF so its center lands at (cx, cy).
- class wcc_etc.psfsim.PSFSource[source]
Bases:
objectBase class: render a normalized (sum=1) PSF onto a DetectorPSFContext.
- cache_key()[source]
Hashable key identifying this PSF source’s parameters (not the rendered output). The render also depends on the DetectorPSFContext (wavelength, optics, jitter, npix, oversample), so a render cache must combine this key with the context and/or be invalidated when the simulation state changes — see Simulation._image_render_bundle.
- class wcc_etc.psfsim.AiryPSF[source]
Bases:
PSFSourceDiffraction-limited Airy PSF rendered on the detector grid (default).
- wcc_etc.psfsim.load_huygens_psf(path, encoding='utf-16')[source]
Load a Zemax Huygens PSF text file into a 2D float array of intensities.
- class wcc_etc.psfsim.DefocusPSF(path, src_um_per_pix=4.0, encoding='utf-16')[source]
Bases:
_ResampledPSFA defocused PSF loaded from a Zemax Huygens text file.
- cache_key()[source]
Hashable key identifying this PSF source’s parameters (not the rendered output). The render also depends on the DetectorPSFContext (wavelength, optics, jitter, npix, oversample), so a render cache must combine this key with the context and/or be invalidated when the simulation state changes — see Simulation._image_render_bundle.
- class wcc_etc.psfsim.CustomPSF(source, src_um_per_pix, encoding='utf-16')[source]
Bases:
_ResampledPSFA custom PSF from an ndarray or a Huygens-format text file (future hook).
- wcc_etc.psfsim.saturation_mask_from_image_e(sensor, image_e)[source]
Boolean mask of pixels at/over the ADC full scale or the full well.
- Parameters:
sensor (Sensor) – Provides gain, adc_max, and (optionally) meta[‘well_depth’].
image_e (ndarray) – Per-pixel charge in electrons (per frame for saturation tests).
- Returns:
True where (image_e / gain) >= adc_max, OR image_e >= well_depth when a well_depth is configured.
- Return type:
ndarray of bool
- class wcc_etc.psfsim.SimulatedImage(image_e, image_clean, saturation_mask, gain, bias_level, npix, pixel_scale_mas, psf)[source]
Bases:
objectResult of an ImageSimulator.simulate() call.
- Parameters:
- plot_image(backend='mpl', **kwargs)[source]
Plot this image (single panel). backend=’mpl’ or ‘bokeh’.
- class wcc_etc.psfsim.ImageSimulator(simulation, npix=300, oversample=11)[source]
Bases:
objectRender a point source onto a detector grid with noise, driven by an ETC Simulation.
- classmethod from_sensor_and_scene(sensor, scene, npix=300, oversample=11)[source]
Build an ImageSimulator from a sensor name (e.g. ‘sony:r’) and a Scene.
- classmethod from_sensorfilter(sensorfilter, scene, npix=300, oversample=11)[source]
Build an ImageSimulator from a sensorfilter label (e.g. ‘zwo:r+1’).
Sets _default_psf on the underlying Simulation (accessible via self.sim._default_psf) based on the sensor’s focus_level in sensor_info. Note: simulate() still requires an explicit psf= argument; _default_psf is used by Simulation.get_image_snr and get_image_exptime_for_snr.
- simulate(time=None, psf=None, jitter_sigma_mas=None, center=None, add_noise=True, seed=None)[source]
Simulate a detector image for the given exposure time and PSF.
- Parameters:
time (float or Quantity, optional) – Exposure time (seconds if a bare float). Defaults to the Simulation’s meta[‘time’].
psf (PSFSource, optional) – PSF model to render. Defaults to AiryPSF() (diffraction limited).
jitter_sigma_mas (float, optional) – Override the telescope jitter (mas). Defaults to the telescope value.
center (tuple, optional) – Sub-pixel (cx, cy) center for the PSF. Defaults to the grid center.
add_noise (bool, optional) – If True, apply Poisson shot + dark noise and Gaussian read noise. If False, return the noiseless electron image. Default True.
seed (int, optional) – Seed for the random generator, for reproducible noise.
- Returns:
Holds the electron image, the noiseless image, and a saturation mask (computed from the returned image_e against adc_max / well).
- Return type:
- wcc_etc.psfsim.howell_center(postage_stamp)[source]
Howell centroiding, from Howell’s Handbook of CCD astronomy
- INPUT:
postage_stamp - A 2d numpy array to do the centroiding
- OUTPUT:
x and y center of the numpy array
NOTES: Many thanks to Thomas Beatty and the MINERVAphot.py pipeline for this method see here: https://github.com/TGBeatty/MINERVAphot/blob/master/MINERVAphot.py
- wcc_etc.psfsim.psf_center(image)[source]
Measured center
(xc, yc)of a PSF image for radial reducers.Uses Howell centroiding so radii are measured from the actual light distribution rather than a fixed grid index. This matters because
render_detector_psfforces an odd grid (peak on the exact center pixel) andcenter_crop_or_padthen brings an even target grid back down, landing the centroid on an integer pixel — a full pixel away fromn//2and half a pixel from the geometric center(n-1)/2. It also tracks the centroid of asymmetric defocused (DefocusPSF) bands, which are not guaranteed symmetric about the grid center.Falls back to the geometric center
((nx-1)/2, (ny-1)/2)for an empty or degenerate image where the centroid is undefined (zero/negative total flux or a non-finite centroid, e.g. a flat field).
- wcc_etc.psfsim.apply_jitter(data, jitter_mas, pixel_scale)[source]
Apply jitter to the input data.
- INPUT:
data: 2d data jitter_mas: jitter in mas pixel_scale: mas/pixel
- wcc_etc.psfsim.tiff_to_fits(tiff_file, output_dir)[source]
Convert a multi-frame TIFF file to a series of FITS files.
- wcc_etc.psfsim.apply_nl_scaling(df_nl, data, how='makenonlinear', scale=1)[source]
Apply non-linear scaling to the input data using the provided scaling factors.
- Parameters:
df_nl (pd.DataFrame): DataFrame containing ‘mean_value’ and ‘scaling_factor’ columns. data (np.ndarray): Input data array to be scaled. how (str): ‘makenonlinear’ to apply non-linearity, ‘correctnonlinear’ to reverse it.
- RETURNS:
data_scaled (np.ndarray): Scaled data array.
EXAMPLE:
- NOTES:
only applies scaling to positive values; zero or negative values are unchanged.
Should only be applied after bias subtraction.
- class wcc_etc.psfsim.FitsImgList(data_list, center_list=None, **kwargs)[source]
Bases:
object
- class wcc_etc.psfsim.FitsImg(filename=None, data=None, header=None, center=None, dark_current_rate=0.0, exp_time=1.0, read_noise_rms=0.0, imgnumber=0)[source]
Bases:
object- get_centroid(plot_cross=False, ax=None, plot_lines=False)[source]
Find centroid using Howell centroiding.
See phothelp for the method
- get_centroid_line_cut(line='X', plot=False, ax=None)[source]
Horizontal (
line='X') or vertical (line='Y') cut through the centroid.- INPUT:
line - ‘X’ for the row through the centroid, ‘Y’ for the column plot - draw the cut on
ax(or a new axis)- OUTPUT:
cut - 1D array along the requested direction
- plot(stretch='hist', cmap='gray', origin='lower', ax=None, colorbar=False, title='', vmin=None, vmax=None, dpi=200)[source]
- get_radial_profile(rmax=None, plot=False, z=2.0, return_hwzm=False, ax=None, xcen=None, ycen=None, annulus_width=1, subtract_min=False)[source]
Plot radial profile
- aperture_photometry(r_ap=3.0, r_in=6.0, r_out=8.0, gain=1.0, center=None, bkg_sigma_clip=3.0, bkg_maxiters=5, plot=True, ax=None, verbose=True, vmin=None, vmax=None, cmap='viridis', origin='lower', stretch='hist', colorbar=True)[source]
Perform circular aperture photometry on the current self.data image using photutils.
- Parameters:
r_ap (float) – Aperture radius in pixels.
r_in (float) – Inner and outer radii for background annulus in pixels. If None or invalid, the image median is used as a background estimate.
r_out (float) – Inner and outer radii for background annulus in pixels. If None or invalid, the image median is used as a background estimate.
gain (float) – e/ADU (set to 1.0 if self.data is already in electrons).
center (None or (y,x)) – If provided, use this center; otherwise the method will try self.center, then the peak pixel, then image center.
bkg_sigma_clip (float) – Sigma for sigma-clipped background estimation in the annulus.
bkg_maxiters (int) – Max iterations for sigma clipping.
- Returns:
Dictionary containing ap_sum, bkg_mean_per_pix, bkg_std_per_pix, bkg_sum, net_flux, flux_err, snr, ap_area, ann_area, ann_pixels_used, center
- Return type:
- wcc_etc.psfsim.solve_time_for_snr(snr, A, B, C)[source]
Solve SNR = A*t / sqrt(B*t + C) for the positive root t.
A, B, C may be scalars or broadcastable arrays (A = signal rate, B = variance rate, C = constant read-noise variance). Returns t in the same shape (a float if all inputs are scalar). Entries with A <= 0 return +inf.
- wcc_etc.psfsim.aperture_snr_radial(psf_norm, plate_scale_mas, source_e_total, diffuse_per_pix, dark_per_pix, read_noise)[source]
SNR as a function of circular-aperture radius for a rendered PSF.
The PSF (sum=1) sets how much source light falls inside each radius; the per-pixel diffuse (sky+host), dark, and read-noise terms set the background noise that grows with the number of aperture pixels.
- Parameters:
psf_norm (ndarray) – Normalized (sum=1) PSF on the detector grid.
plate_scale_mas (float) – Detector plate scale, mas/pixel (to report radii in mas).
source_e_total (float) – Total source electrons (all of the PSF, before aperture clipping).
diffuse_per_pix (float) – Per-pixel sky+host and dark-current electrons.
dark_per_pix (float) – Per-pixel sky+host and dark-current electrons.
read_noise (float) – Read noise (electrons rms per pixel).
- Returns:
‘r_mas’, ‘enclosed_fraction’, ‘n_pix’, ‘signal_e’, ‘noise_e’, ‘snr’.
- Return type:
dict of ndarrays, sorted by ascending radius
- wcc_etc.psfsim.select_aperture(profile, r_aper_mas=None, ee_frac=None, optimize=False)[source]
Index into an aperture_snr_radial profile for the chosen aperture mode.
Precedence: optimize (max SNR) > explicit r_aper_mas > ee_frac. Raises ValueError if no mode is given.
- wcc_etc.psfsim.aperture_time_for_snr(psf_norm, plate_scale_mas, source_rate_total, diffuse_rate_per_pix, dark_rate_per_pix, read_noise, n_reads=1, snr=None, r_aper_mas=None, ee_frac=None, optimize=False)[source]
Exposure time (s) to reach snr for a rendered PSF, per aperture mode.
Rates are per second (the time dependence is solved for analytically). The read-noise variance is incurred n_reads times. Aperture precedence matches select_aperture: optimize (fastest radius) > r_aper_mas > ee_frac.
Returns {‘time_s’, ‘snr’, ‘r_aper_mas’, ‘enclosed_fraction’, ‘n_pix’}.