Field of Stars — Liger IFS
[ ]:
from liger_iris_sim.sources import make_point_source_ifs_cube
from liger_iris_sim.expose import compute_throughput, expose_ifs
from liger_iris_sim.sky import get_maunakea_sky_background
from liger_iris_sim.utils import LIGER_PROPS, compute_filter_photon_flux, generate_wave_grid_for_filter, get_psfs
from liger_iris_drp_resources.filters import load_filters_summary
import numpy as np
import matplotlib.pyplot as plt
Instrument and exposure parameters
[ ]:
np.random.seed(1)
instrument_name = 'Liger'
instrument_mode = 'ifs'
ifs_mode = 'lenslet' # 'lenslet' or 'slicer'
filt = 'J'
size = (128, 128) # IFS spatial dimensions (y, x) in spaxels
read_noise = 9 # e- RMS
dark_current = 0.025 # e- / sec / pixel
scale = 0.014 # arcsec / spaxel (lenslet)
itime = 30 # integration time, sec
n_frames = 1 # number of coadded frames
resolution = 4000 # spectral resolution R = lambda / delta_lambda
collarea = LIGER_PROPS['collarea'] # m^2
Load filter data and compute throughput
load_filters_summary returns filter metadata including the central wavelength, zero-point flux, and background magnitude.
compute_throughput folds together telescope, AO, filter, and instrument throughput.
[ ]:
filter_info = load_filters_summary(filter_name=filt)
tput = compute_throughput(
instrument_name=instrument_name,
instrument_mode=instrument_mode,
wave=filter_info['wavecenter'],
ifs_mode=ifs_mode,
filt=filt,
)
print(f"Total throughput for {ifs_mode} mode at {filter_info['wavecenter']:.3f} µm: {tput:.3f}")
Build the wavelength grid
generate_wave_grid_for_filter returns a Nyquist-sampled wavelength array covering the filter bandpass at the requested spectral resolution.
[ ]:
wave = generate_wave_grid_for_filter(filter_info, resolution=resolution)
print(f"Wavelength grid: {wave[0]:.4f} – {wave[-1]:.4f} µm, {len(wave)} channels")
Define field of stars and spectrum
[ ]:
n_stars = 10
mag_range = (16, 17)
mag = np.random.uniform(mag_range[0], mag_range[1], size=n_stars)
xpix = np.random.uniform(5, size[1] - 5, size=n_stars)
ypix = np.random.uniform(5, size[0] - 5, size=n_stars)
photon_flux = compute_filter_photon_flux(mag, zp=filter_info['zpphot']) # photons / sec / m^2
# Flat spectrum normalised to unit integral
flat_spec = np.ones(len(wave), dtype=np.float32)
template = (wave, flat_spec / flat_spec.sum())
Load PSF
[ ]:
psfs, psf_infos, psf_indices = get_psfs(
instrument_name=instrument_name,
instrument_mode=instrument_mode,
wave=filter_info['wavecenter'],
xdet=xpix, ydet=ypix,
output_plate_scale=scale,
)
Compute sky emission and transmission
[ ]:
sky_data = get_maunakea_sky_background(
wave, resolution=resolution,
T_tel=275, T_atm=258, T_aos=243,
Em_tel=0.09, Em_atm=0.2, Em_aos=0.01,
plate_scale=scale,
)
sky_em_rate = sky_data['sky_em']
sky_trans = sky_data['sky_trans']
Create a field of stars
[ ]:
input_cube_rate = np.zeros((len(wave), *size), dtype=np.float32)
make_point_source_ifs_cube(
xdet=xpix, ydet=ypix, wave=wave,
template=template,
flux_int=photon_flux,
psf=psfs,
psf_indices=psf_indices,
cube_out=input_cube_rate,
)
Run the IFS exposure simulation
expose_ifs applies throughput, sky emission, sky transmission, Poisson noise, dark current, and read noise to produce a simulated data cube.
[ ]:
sim = expose_ifs(
input_cube_rate,
itime=itime, n_frames=n_frames, collarea=collarea,
sky_emission_rate=sky_em_rate,
sky_transmission=sky_trans,
tput=tput, read_noise=read_noise, dark_current=dark_current,
)
print(f"Output cube shape: {sim['observed_tot'].shape}")
print(f"Peak SNR (per voxel): {np.nanmax(sim['snr']):.1f}")
Visualize results
Collapse the cube along the wavelength axis to produce a white-light image, and show the peak-SNR spectrum.
[ ]:
snr_map = np.nansum(sim['snr']**2, axis=0)**0.5
im = plt.imshow(snr_map, origin='lower', cmap='viridis', vmin=0)
plt.colorbar(im, label='SNR')
plt.title('SNR integrated over bandpass')
plt.show()
[ ]:
# Extract and plot the spectrum of the brightest spaxel
peak_idx = np.unravel_index(np.nanargmax(snr_map), snr_map.shape)
ypeak, xpeak = peak_idx
fig, axes = plt.subplots(2, 1, figsize=(10, 6), sharex=True)
axes[0].plot(wave, sim['observed_rate'][:, ypeak, xpeak], lw=1, label='Observed')
axes[0].plot(wave, sim['source_rate'][:, ypeak, xpeak], lw=1, ls='--', label='Source (noiseless)')
axes[0].set_ylabel('Signal (e-/sec)')
axes[0].legend()
axes[0].set_title(f'Spectrum at brightest spaxel (x={xpeak}, y={ypeak})')
axes[1].plot(wave, sim['snr'][:, ypeak, xpeak], color='C2', lw=1)
axes[1].set_ylabel('SNR')
axes[1].set_xlabel('Wavelength (µm)')
plt.show()