def transmission(self, waves, theta_range=None): """ Return filter transmission at the given wavelength(s). For filter bandpasses defined by data tables this will interpolate/extrapolate as required while for filter bandpasses defined by analytic expressions it will be calculated directly. Parameters ---------- waves : astropy.units.Quantity Wavelength(s) for which the filter transmission is required theta_range : astropy.units.Quantity, optional 2 element quantity specifying the range of angles of incidence (min, max). If specified this will be used to model the effect of a converging beam on the filter bandpass. If not specified the default values set when creating the Filter instance will be used. Returns ------- waves : astropy.units.Quantity Filter transmission at the given wavelength(s) """ waves = ensure_unit(waves, u.nm) if self.apply_aoi and (theta_range or self.theta_range): if theta_range: theta_range = ensure_unit(theta_range, u.radian) else: theta_range = self.theta_range # Thetas spanning range with equal spacing in theta^2 (approximate weighting by solid angle) thetas = np.linspace(theta_range.min()**2, theta_range.max()**2, num=10)**0.5 thetas = thetas.reshape((1, len(thetas))) # For each input wavelength create shifted effective wavelengths for each angle of incidence. len_waves = 1 if waves.isscalar else len(waves) # len() fails on scalar Quantities shifted_waves = waves.reshape((len_waves, 1)) / (1 - (np.sin(thetas / self.n_eff))**2)**0.5 # Use interpolator to look up transmission for every shifted wavelength trans = self._interpolator(shifted_waves) # Finally take mean over thetas (effectively an approximate integral over solid angle) trans = trans.mean(axis=1) else: trans = self._interpolator(waves) # Make sure interpolation/extrapolation doesn't take transmission unphysically below zero or above 1 trans = np.where(trans > 0, trans, 0) trans = np.where(trans <= 1, trans, 1) # Put units back (interpolator doesn't work with Quantity, nor does np.where) return ensure_unit(trans, u.dimensionless_unscaled)
def __init__(self, FWHM, pixel_scale=None, **kwargs): self._FWHM = ensure_unit(FWHM, u.arcsecond) if pixel_scale: self.pixel_scale = pixel_scale super().__init__(**kwargs)
def cheby_band(w, w1, w2, N, ripple=1, scale=0.95): """ Simple Chebyshev Type I bandpass filter function in wavelength space. Parameters ---------- w : astropy.units.Quantity Wavelength w1 : astropy.units.Quantity Wavelength of short wavelength edge of bandpass w2 : astropy.units.Quantity Wavelength of long wavelength edge of bandpass N : int Order of the Chebyshev function ripple : float, optional Scaling to apply to the ripple of the Chebyshev function, default 1.0 scale : float, optional Scaling to apply to the transmission of the Chebyshev function, default 0.95 Returns ------- transmission : astropy.units.Quantity Filter transmission at wavelength `w`. """ # Bandpass implemented as low pass and high pass in series w = ensure_unit(w, u.nm) g1 = 1 / np.sqrt(1 + ripple**2 * eval_chebyt(N, (w1/w).to(u.dimensionless_unscaled).value)**2) g2 = 1 / np.sqrt(1 + ripple**2 * eval_chebyt(N, (w/w2).to(u.dimensionless_unscaled).value)**2) return scale * g1 * g2
def butter_band(w, w1, w2, N, scale=0.95): """ Simple Butterworth bandpass filter function in wavelength space. Parameters ---------- w : astropy.units.Quantity Wavelength w1 : astropy.units.Quantity Wavelength of short wavelength edge of bandpass w2 : astropy.units.Quantity Wavelength of long wavelength edge of bandpass N : int Order of the Butterworth function scale : float, optional Scaling to apply to the transmission of the Butterworth function, default 0.95 Returns ------- transmission : astropy.units.Quantity Filter transmission at wavelength `w`. """ # Bandpass implemented as low pass and high pass in series w = ensure_unit(w, u.nm) g1 = np.sqrt(1 / (1 + (w1/w).to(u.dimensionless_unscaled)**(2*N))) g2 = np.sqrt(1 / (1 + (w/w2).to(u.dimensionless_unscaled)**(2*N))) return scale * g1 * g2
def pixel_scale(self, pixel_scale): pixel_scale = ensure_unit(pixel_scale, (u.arcsecond / u.pixel)) if pixel_scale <= 0 * u.arcsecond / u.pixel: raise ValueError( "Pixel scale should be > 0, got {}!".format(pixel_scale)) else: self._pixel_scale = pixel_scale # When pixel scale is set/changed need to update model parameters: self._update_model()
def FWHM(self, FWHM): FWHM = ensure_unit(FWHM, u.arcsecond) if FWHM <= 0 * u.arcsecond: raise ValueError("FWHM should be > 0, got {}!".format(FWHM)) else: self._FWHM = FWHM # If a pixel scale has already been set should update model parameters when FWHM changes. if self.pixel_scale: self._update_model()
def __init__(self, aperture, focal_length, throughput_filename, central_obstruction=0 * u.mm): self.aperture = ensure_unit(aperture, u.mm) self.central_obstruction = ensure_unit(central_obstruction, u.mm) self.aperture_area = np.pi * ( self.aperture**2 - self.central_obstruction**2).to(u.m**2) / 4 self.focal_length = ensure_unit(focal_length, u.mm) self.focal_ratio = (self.focal_length / self.aperture).to( u.dimensionless_unscaled) # Calculate beam half-cones angles at the focal plane if central_obstruction == 0 * u.mm: theta_min = 0 * u.radian else: theta_min = np.arctan( (self.central_obstruction / 2) / self.focal_length) theta_max = np.arctan((self.aperture / 2) / self.focal_length) self.theta_range = u.Quantity((theta_min, theta_max)).to(u.degree) tau_data = Table.read( get_pkg_data_filename(os.path.join(data_dir, throughput_filename))) if not tau_data['Wavelength'].unit: tau_data['Wavelength'].unit = u.nm self.wavelengths = tau_data['Wavelength'].quantity.to(u.nm) if not tau_data['Throughput'].unit: tau_data['Throughput'].unit = u.dimensionless_unscaled self.throughput = tau_data['Throughput'].quantity.to( u.dimensionless_unscaled)
def __init__(self, transmission_filename=None, chebyshev_params=None, butterworth_params=None, apply_aoi=False, n_eff=1.75, theta_range=None, *kwargs): n_args = np.count_nonzero((transmission_filename, chebyshev_params, butterworth_params)) if n_args != 1: raise ValueError("One and only one of `tranmission_filename`, `chebyshev_params` & `butterworth_params`" + "must be specified, got {}!".format(n_args)) self.apply_aoi = apply_aoi self.n_eff = n_eff if theta_range: self._theta_range = ensure_unit(theta_range, u.radian) else: self._theta_range = None if transmission_filename: transmission_data = Table.read(get_pkg_data_filename(os.path.join(data_dir, transmission_filename))) if not transmission_data['Wavelength'].unit: transmission_data['Wavelength'].unit = u.nm self.wavelengths = transmission_data['Wavelength'].quantity.to(u.nm) if not transmission_data['Transmission'].unit: transmission_data['Transmission'].unit = u.dimensionless_unscaled self._transmission = transmission_data['Transmission'].quantity.to(u.dimensionless_unscaled) # Create linear interpolator for calculating transmission at arbitrary wavelength self._interpolator = interp1d(self.wavelengths, self._transmission, kind='linear', fill_value='extrapolate') elif chebyshev_params: # Ensure all parameters are present (filling in defaults where necessary) and are the correct types/units wave1 = ensure_unit(chebyshev_params['wave1'], u.nm) wave2 = ensure_unit(chebyshev_params['wave2'], u.nm) order = int(chebyshev_params['order']) ripple = chebyshev_params.get('ripple', 1) peak = ensure_unit(chebyshev_params.get('peak', 0.95), u.dimensionless_unscaled) # Create a lambda function to calculate transmission at arbitrary wavelength. scale = peak / cheby_band(w=(wave1 + wave2)/2, w1=wave1, w2=wave2, N=order, ripple=ripple, scale=1) self._interpolator = lambda x: cheby_band(x, w1=wave1, w2=wave2, N=order, ripple=ripple, scale=scale) # Store parameters for later reference self._params = {'wave1': wave1, 'wave2': wave2, 'order': order, 'ripple': ripple, 'scale': scale, 'peak': peak} elif butterworth_params: # Ensure all parameters are present (filling in defaults where necessary) and are the correct types/units wave1 = ensure_unit(butterworth_params['wave1'], u.nm) wave2 = ensure_unit(butterworth_params['wave2'], u.nm) order = int(butterworth_params['order']) peak = ensure_unit(butterworth_params.get('peak', 0.95), u.dimensionless_unscaled) # Create a lambda function to calculate transmission at arbitrary wavelength. scale = peak / butter_band(w=(wave1 + wave2) / 2, w1=wave1, w2=wave2, N=order, scale=1) self._interpolator = lambda x: butter_band(x, w1=wave1, w2=wave2, N=order, scale=scale) # Store parameters for later reference self._params = {'wave1': wave1, 'wave2': wave2, 'order': order, 'scale': scale, 'peak': peak} self._update_properties()
def theta_range(self, theta_range): self._theta_range = ensure_unit(theta_range, u.radian) self._update_properties()