def optimize_filter(low_wave, high_wave, **kwargs): """ Optimizes out-of-band filters based on the input bandpass --- Inputs: low_wave = Lower side of bandpass (units consistent with length) high_wave = High side of the bandpass Option inputs: qe_band = Which QE file to use (Default is "1") target_ratio = Out-of-band to in-band counts (0.5) blue_filter = If there's an asymmetric filter, apply this to everything blue-ward of the low_wave """ from tdsat_telescope import load_qe, load_reflectivity from apply_transmission import apply_trans from zodi import load_zodi import astropy.units as ur # Check if the inputs make sense assert low_wave.unit.is_equivalent( ur.m), "Low-side wavelength does not have unit of length" assert high_wave.unit.is_equivalent( ur.m), "High-side wavelength does not have unit of length" qe_band = kwargs.pop('qe_band', 1) target_ratio = kwargs.pop('target_ratio', 0.5) blue_filter = kwargs.pop('blue_filter', False) diag = kwargs.pop('diag', False) # Load zodiacal background. Note that the out-of-band Zodi dominates over the # atmospheric lines (which are present here). Using the lowest Zodi background # represents the "worst case". zodi = load_zodi() # Load reflectivity and QE curves: ref_wave, reflectivity = load_reflectivity() qe_wave, qe = load_qe(band=qe_band) # Apply these to the Zodi spectrum: ref_flux = apply_trans(zodi['wavelength'], zodi['flux'], ref_wave, reflectivity / 100.) qe_flux = apply_trans(zodi['wavelength'], ref_flux, qe_wave, qe) # Make a "standard" red filter: rejection = 1.0 red_filter = make_red_filter(zodi['wavelength'], low_wave=low_wave, high_wave=high_wave, rejection=rejection, blue_filter=blue_filter, diag=diag) band_flux = apply_trans(zodi['wavelength'], qe_flux, zodi['wavelength'], red_filter) # Get the in-band, out-of-band ratio: in_band = band_flux[(zodi['wavelength'] > low_wave) & (zodi['wavelength'] < high_wave)].sum() out_of_band = band_flux[( (zodi['wavelength'] < low_wave) | (zodi['wavelength'] > high_wave)) & (zodi['wavelength'] < 1 * ur.micron)].sum() # Comput ratio: ratio = out_of_band / in_band target_rejection = (rejection * (target_ratio / ratio)).value if diag: print() print('Optimize filter diagnostics:') print('Low wave:{}'.format(low_wave)) print('High wave:{}'.format(high_wave)) print('Blue filter? {}'.format(blue_filter)) print('Target ratio: {}'.format(target_ratio)) print('Target rejection: {}'.format(target_rejection)) print() return target_rejection
def outofband_bgd_sky_rate(**kwargs): """ Loads the zodiacal background and normalizes it at 500 nm to a particular flux level (low_zodi = 77, med_zodi = 300, high_zodi = 6000), which are taken from a paper (to be dropped here later). Calculates out-of-band contribution to the background (up to a maximum wavelength, default 900 nm). Out-of-band rejection efficiency is folded in later, in the snr calculation. Optional Inputs (defaults): band = Bandpass (180-220)*ur.nm diameter=Telescope Diameter (21*ur.cm) pixel_size=Angular size of the pixel (6*ur.arcsec) max_wav = cutoff wavelength of detector (900*ur.nm) diag = Diagnostics toggle (False) low_zodi = (True) medium_zodi = (False) high_zodi = (False) Returns NumPh, NumElectrons, each of which are ph / cm2 / pixel and e- / cm2 / pixel """ import astropy.units as ur import astropy.constants as cr import numpy as np from zodi import load_zodi # Set up units here for flux conversion below fλ_unit = ur.erg / ur.cm**2 / ur.s / ur.Angstrom # Spectral radiances per Hz or per angstrom fλ_density_unit = fλ_unit / (ur.arcsec * ur.arcsec) diag = kwargs.pop('diag', False) pixel_size = kwargs.pop('pixel_size', 6 * ur.arcsec) pixel_area = pixel_size**2 diameter = kwargs.pop('diameter', 21. * ur.cm) Area_Tel = np.pi * (diameter.to(ur.cm) * 0.5)**2 max_wav = kwargs.pop('max_wav', 900. * ur.nm) low_zodi = kwargs.pop('low_zodi', True) med_zodi = kwargs.pop('med_zodi', False) high_zodi = kwargs.pop('high_zodi', False) band = kwargs.pop('band', [180, 220] * ur.nm) bandpass = np.abs(band[1] - band[0]) effective_wavelength = (np.mean(band)).to(ur.AA) ph_energy = (cr.h.cgs * cr.c.cgs / effective_wavelength.cgs).to(ur.eV) elec_per_eV = 1 / (3.6 * ur.eV) # per electron for Si # ABmag = 20*ur.ABmag # Just a place holder here # F_λ = ABmag.to(fλ_unit, equivalencies=ur.spectral_density(λ_mid)) # Already converts to flux at this midpoint if low_zodi: zodi_level = 77 if med_zodi: zodi_level = 300 if high_zodi: zodi_level = 6000 zodi = load_zodi(scale=zodi_level) ctr = 0 flux_density = 0 for ind, wv in enumerate(zodi['wavelength']): if ((wv >= band[0].to(ur.AA).value) & (wv <= band[1].to(ur.AA).value) | (wv >= max_wav.to(ur.AA).value)): continue ctr += 1 flux_density += zodi['flux'][ind] # Effective flux density in the band, per arcsecond: flux_density /= float(ctr) fden = flux_density.to(fλ_density_unit) outofbandpass = np.abs(max_wav - zodi['wavelength'][0] * ur.AA) - bandpass ReceivedPower = (outofbandpass.to(ur.AA) * fden * Area_Tel * pixel_area).to(ur.eV / ur.s) NumPhotons = ReceivedPower / ph_energy # Number of photons NumPhotons = NumPhotons ElectronsPerPhoton = (ph_energy.to(ur.eV)) * elec_per_eV NumElectrons = ReceivedPower * elec_per_eV if diag: print('') print('Out of band Background Computation Integrating over Pixel Area') print('Telescope diameter: {}'.format(diameter)) print('Telescope aperture: {}'.format(Area_Tel)) print('Fλ total per arcsec2 {}'.format(fden)) print('Fλ ABmag per pixel {}'.format( (fden * pixel_area).to( ur.ABmag, equivalencies=ur.spectral_density(effective_wavelength)))) print('Bandpass: {}'.format(bandpass)) print('Detector cutoff wavelength: {}'.format(max_wav)) print('Collecting Area: {}'.format(Area_Tel)) print('Pixel Area: {}'.format(pixel_area)) print('Photons {}'.format(NumPhotons)) return NumPhotons, NumElectrons
def bgd_sky_qe_rate(**kwargs): """ Loads the zodiacal background and normalizes it at 500 nm to a particular flux level (low_zodi = 77, med_zodi = 300, high_zodi = 6000). See the docstring for load_zodi in zodi.py Optional Inputs (defaults): band = Bandpass (180-220)*ur.nm diameter=Telescope Diameter (21*u.cm) pixel_size=Angular size of the pixel (6*ur.arcsec) rejection = Out of band rejection (1e-3) diag = Diagnostics toggle (False) low_zodi = (True) medium_zodi = (False) high_zodi = (False) qe_band = Whivch QE curve to us (1 --> 180-220 nm, 2-->260-300 nm) blue_filter = Apply blue_side filter (False) Returns bgd_rate which is ph / s / pixel """ import astropy.units as ur import astropy.constants as cr import numpy as np from zodi import load_zodi, wavelength_to_energy from apply_transmission import apply_trans from tdsat_telescope import load_qe, load_reflectivity, load_redfilter, apply_filters from duet_filters import make_red_filter, optimize_filter # Set up units here for flux conversion below # fλ_unit = ur.erg/ur.cm**2/ur.s / ur.Angstrom # Spectral radiances per Hz or per angstrom # fλ_density_unit = fλ_unit / (ur.arcsec *ur.arcsec) diag = kwargs.pop('diag', False) pixel_size = kwargs.pop('pixel_size', 6 * ur.arcsec) pixel_area = pixel_size**2 diameter = kwargs.pop('diameter', 21. * ur.cm) Area_Tel = np.pi * (diameter.to(ur.cm) * 0.5)**2 low_zodi = kwargs.pop('low_zodi', True) med_zodi = kwargs.pop('med_zodi', False) high_zodi = kwargs.pop('high_zodi', False) band = kwargs.pop('band', [180, 220] * ur.nm) bandpass = np.abs(band[1] - band[0]) qe_band = kwargs.pop('qe_band', 1) blue_filter = kwargs.pop('blue_filter', False) filter_target = kwargs.pop('filter_target', 0.5) real_red = kwargs.pop('real_red', False) # effective_wavelength = (np.mean(band)).to(ur.AA) # ph_energy = (cr.h.cgs * cr.c.cgs / effective_wavelength.cgs).to(ur.eV) # Specified from Kristin. The highest Zodi that we hit is actually only 900 # (down from 6000 in previous iterations) sine any closer to the Sun we violate # our Sun-angle constraints. if low_zodi: zodi_level = 77 if med_zodi: zodi_level = 165 if high_zodi: zodi_level = 900 zodi = load_zodi(scale=zodi_level) wave = zodi['wavelength'] flux = zodi['flux'] if real_red: band_flux = apply_filters(zodi['wavelength'], zodi['flux'], band=qe_band, diag=diag, **kwargs) else: # Make the red filter low_wave = band[0] high_wave = band[1] rejection = optimize_filter(low_wave, high_wave, target_ratio=filter_target, blue_filter=blue_filter) red_trans = make_red_filter(wave, rejection=rejection, high_wave=high_wave, low_wave=low_wave, blue_filter=blue_filter) red_wave = wave # Load reflectivity and QE curves: ref_wave, reflectivity = load_reflectivity() qe_wave, qe = load_qe(band=qe_band) # Apply reflectivity and QE to the Zodi spectrum: ref_flux = apply_trans(zodi['wavelength'], zodi['flux'], ref_wave, reflectivity / 100.) qe_flux = apply_trans(zodi['wavelength'], ref_flux, qe_wave, qe) # Apply red filter band_flux = apply_trans(wave, qe_flux, red_wave, red_trans) # Assume bins are the same size: de = wave[1] - wave[0] # Convert to more convenient units: ph_flux = ((de * band_flux).cgs).to(1 / ((ur.cm**2 * ur.arcsec**2 * ur.s))) fluence = ph_flux.sum() BgdRatePerPix = pixel_area * fluence * Area_Tel if diag: print('Background Computation Integrating over Pixel Area') print('Telescope diameter: {}'.format(diameter)) print('Collecting Area: {}'.format(Area_Tel)) print('Band: {}'.format(band)) print('Bandpass: {}'.format(bandpass)) print() # print('Out-of-band rejection: {}'.format(rejection)) # print('Apply blue filter? {}'.format(blue_filter)) print() print('Pixel Area: {}'.format(pixel_area)) print() print('Background fluence per arcsec2 {}'.format(fluence)) print('Rate {}'.format(BgdRatePerPix)) return BgdRatePerPix