Beispiel #1
0
    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)
Beispiel #2
0
    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)
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
 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()
Beispiel #6
0
 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()
Beispiel #7
0
    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)
Beispiel #8
0
    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()
Beispiel #9
0
 def theta_range(self, theta_range):
     self._theta_range = ensure_unit(theta_range, u.radian)
     self._update_properties()