Esempio n. 1
0
 def collapse(self, waveset):
     throughput = self.radiometry_table.throughput(waveset)
     self._throughput = SpectralElement(Empirical1D, points=waveset,
                                        lookup_table=throughput)
     emission = self.radiometry_table.emission(waveset)
     self._emission = SourceSpectrum(Empirical1D, points=waveset,
                                     lookup_table=emission)
Esempio n. 2
0
    def rebin_spectra(self, new_waves):
        """
        Rebin a synphot spectra to a new wavelength grid conserving flux.
        Grid does not need to be linear and can be at higher or lower resolution

        Parameters
        ----------
        new_waves: an array of the output wavelenghts in Angstroms but other units can be
            specified

        Returns
        -------
        a new Spextrum instance

        """
        if isinstance(new_waves, u.Quantity):
            new_waves = new_waves.to(u.AA).value

        waves = self.waveset.value  # else assumed to be angstroms
        f = np.ones(len(waves))
        filt = SpectralElement(Empirical1D, points=waves, lookup_table=f)
        obs = Observation(self, filt, binset=new_waves, force='taper')
        newflux = obs.binflux
        rebin_spec = Empirical1D(points=new_waves,
                                 lookup_table=newflux,
                                 meta=self.meta)
        sp = Spextrum(modelclass=rebin_spec)
        sp = self._restore_attr(sp)
        sp.meta.update({"rebinned_spectra": True})

        return sp
Esempio n. 3
0
    def photons_in_range(self,
                         wmin=None,
                         wmax=None,
                         area=1 * u.cm**2,
                         filter_curve=None):
        """
        Return the number of photons between wave_min and wave_max or within
        a bandpass (filter)

        Parameters
        ----------
        wmin :
            [Angstrom]
        wmax :
            [Angstrom]
        area : u.Quantity
            [cm2]
        filter_curve : str
                Name of a filter from
                - a generic filter name (see ``DEFAULT_FILTERS``)
                - a spanish-vo filter service reference (e.g. ``"Paranal/HAWKI.Ks"``)
                - a filter in the spextra database
                - the path to the file containing the filter (see ``Passband``)
                - a ``Passband`` or ``synphot.SpectralElement`` object

        Returns
        -------
        counts : u.Quantity array

        """
        if isinstance(area, u.Quantity):
            area = area.to(u.cm**2).value  #
        if isinstance(wmin, u.Quantity):
            wmin = wmin.to(u.Angstrom).value
        if isinstance(wmax, u.Quantity):
            wmin = wmax.to(u.Angstrom).value

        if filter_curve is None:
            # this makes a bandpass out of wmin and wmax
            try:
                mid_point = 0.5 * (wmin + wmax)
                width = abs(wmax - wmin)
                filter_curve = SpectralElement(Box1D,
                                               amplitude=1,
                                               x_0=mid_point,
                                               width=width)
            except ValueError("Please specify wmin/wmax or a filter"):
                raise

        elif os.path.exists(filter_curve):
            filter_curve = Passband.from_file(filename=filter_curve)
        elif isinstance(filter_curve, (Passband, SpectralElement)):
            filter_curve = filter_curve
        else:
            filter_curve = Passband(filter_curve)

        obs = Observation(self, filter_curve)
        counts = obs.countrate(area=area * u.cm**2)

        return counts
Esempio n. 4
0
    def _get_ter_property(self, ter_property):
        """
        Looks for arrays for transmission, emissivity, or reflection

        Parameters
        ----------
        ter_property : str
            ``transmission``, ``emissivity``, ``reflection``

        Returns
        -------
            response_curve : ``synphot.SpectralElement``

        """

        compliment_names = ["transmission", "emissivity", "reflection"]
        ii = np.where([ter_property == name
                       for name in compliment_names])[0][0]
        compliment_names.pop(ii)

        wave = self._get_array("wavelength")
        value_arr = self._get_array(ter_property)
        if value_arr is None:
            value_arr = self._compliment_array(*compliment_names)

        if value_arr is not None and wave is not None:
            response_curve = SpectralElement(Empirical1D,
                                             points=wave,
                                             lookup_table=value_arr)
        else:
            response_curve = None
            warnings.warn("Both wavelength and {} must be set"
                          "".format(ter_property))

        return response_curve
 def test_blackbody_maximum_agrees_with_wien(self, temp):
     '''Check the maximum of emission against Wien's law for photon rate'''
     emissivity = SpectralElement(Empirical1D, points=[1, 20],
                                  lookup_table=[1., 1.])
     flux = surf_utils.make_emission_from_emissivity(temp, emissivity)
     dlam = 0.1
     wave = np.arange(3, 20, dlam) * u.um
     wavemax = wave[np.argmax(flux(wave))]
     if isinstance(temp, u.Quantity):
         temp = temp.to(u.Kelvin, equivalencies=u.temperature()).value
     wienmax = 3669.7 * u.um / temp
     assert np.abs(wavemax - wienmax.to(u.um)) < dlam * u.um
Esempio n. 6
0
 def test_returns_correct_half_flux_with_bandpass(self):
     flux = np.ones(11) * u.Unit("ph s-1 m-2 um-1")
     wave = np.linspace(0.5, 2.5, 11) * u.um
     spec = SourceSpectrum(Empirical1D, points=wave, lookup_table=flux)
     bandpass = SpectralElement(Empirical1D,
                                points=np.linspace(1, 2, 13) * u.um,
                                lookup_table=0.5 * np.ones(13))
     counts = source_utils.photons_in_range([spec],
                                            1 * u.um,
                                            2 * u.um,
                                            bandpass=bandpass)
     assert counts.value == approx(0.5)
Esempio n. 7
0
def filter_mag(filterfile, filterzero, syn_spec, syn_err=None, wrange=None):
    '''
    ######################################################
    # Input                                              #
    # -------------------------------------------------- #
    # filterfile: file containing filter function        #
    # filterzero: flux [Jy] corresponding to mag=0       #
    #   syn_spec: synphot spectrum object of source      #
    #     wrange: (start,end) observed wavelength range  #
    # -------------------------------------------------- #
    # Output                                             #
    # -------------------------------------------------- #
    # mag: magnitude of source in filter system.         #
    ######################################################
    '''
    from synphot import SpectralElement, Observation, Empirical1D, SourceSpectrum
    import astropy.units as u
    
    #Load ascii filter function
    if filterfile.split('/')[-1][:6] == 'Bessel':
        filt = SpectralElement.from_file(filterfile, wave_unit='nm')
    else:
        filt = SpectralElement.from_file(filterfile, wave_unit='AA')
    if wrange is None:
        fwave = filt.waveset
        swave = syn_spec.waveset
        wrange = (max(fwave[0],swave[0]),min(fwave[-1],swave[-1]))
    waves = np.linspace(wrange[0], wrange[-1], 10000)
    #Synthetic observation
    obs = Observation(syn_spec, filt, force='extrap')
    flux = obs.effstim(flux_unit='jy', waverange=wrange).value
    #flux = obs.effstim(flux_unit='jy', wavelengths=waves).value
    #Calibrate magnitude with zero point
    mag = -2.512*np.log10(flux/filterzero)
    #Synthetic observation of error spectrum
    if syn_err is not None:
        #square filter and error spectrum
        filt2 = SpectralElement(Empirical1D, points=fwave,
                                lookup_table=np.square(filt(fwave)))
        pseudo_flux = np.square(syn_err(swave,flux_unit='jy')).value
        syn_err2 = SourceSpectrum(Empirical1D, points=swave,
                                  lookup_table=pseudo_flux)
        #sum errors in quadrature
        obs = Observation(syn_err2, filt2, force='extrap')
        #flux_err = np.sqrt(obs.effstim(waverange=wrange).value)
        flux_err = np.sqrt(obs.effstim(wavelengths=waves).value)
        #convert to magnitude error
        mag_err = (2.5/np.log(10))*(flux_err/flux)
        return mag, mag_err
    else:
        return mag
Esempio n. 8
0
    def rebin_transmission(self, wavenew):
        """Rebins a potentially higher resolution atmospheric transmission
        spectrum to a new wavelength grid <wavenew>.
        Returns a new BaseUnitlessSpectrum constructed from the new passed
        wavelength grid and the resampled transmission"""

        wave = self.transmission.waveset
        specin = self.transmission(wave)
        # Have to pretend transmission is in a flux unit to make a SourceSpectrum
        # so drop here and put back at the end
        orig_unit = specin.unit
        spec = SourceSpectrum(Empirical1D,
                              points=wave,
                              lookup_table=specin.value)
        f = np.ones(len(wave))
        filt = SpectralElement(Empirical1D, points=wave, lookup_table=f)
        obs = Observation(spec, filt, binset=wavenew, force='taper')

        new_trans = SpectralElement(Empirical1D,
                                    points=wavenew,
                                    lookup_table=obs.binflux.value * orig_unit)

        return new_trans
Esempio n. 9
0
    def photons_from_source(self,
                            mag,
                            mag_filter,
                            filtername,
                            source_spec=None,
                            force_overlap='taper',
                            normalize=True):
        """Computes the photons coming from the source given by [source_spec] (Vega
        is assumed if not given) of the given <mag> in <mag_filter> (e.g. for "V=20",
        mag=20, mag_filter='V') when observed through the instrument's filter
        given by <filtername>
        """
        print("photons_from_source:", filtername)
        standard_filter = self._convert_filtername(filtername)
        band = self._map_filter_to_standard(mag_filter)
        print(band.meta.get('expr', 'Unknown'), type(source_spec))
        source_spec = source_spec or self._vega
        if type(source_spec) != SourceSpectrum:
            raise ETCError('Invalid sourcespec; must be a SourceSpectrum')

        # XXX Todo: allow support for AB mags here
        # test line below for comparison with SIGNAL. Difference in mag when
        # using proper normalization is ~mag-0.0155 (for B=22.7)
#        source_spec_norm = source_spec*10**(-0.4*mag)
        if normalize is True:
            source_spec_norm = source_spec.normalize(mag * units.VEGAMAG,
                                                     band,
                                                     vegaspec=self._vega)
        else:
            source_spec_norm = source_spec

        self._create_combined()
        filter_waves, filter_trans = self.instrument.filterset[
            filtername]._get_arrays(None)
        throughput = self._throughput_for_filter(filtername)
        waves, thru = throughput._get_arrays(filter_waves)
        spec_elements = SpectralElement(Empirical1D,
                                        points=filter_waves,
                                        lookup_table=filter_trans * thru)

        # get the synphot observation object
        synphot_obs = Observation(source_spec_norm,
                                  spec_elements,
                                  force=force_overlap)
        if self.obs is None:
            self.obs = synphot_obs
        countrate = synphot_obs.countrate(area=self.telescope.area)

        return countrate
Esempio n. 10
0
 def test_with_bandpass_and_area_returns_correct_value(
         self, flux, area, expected):
     flux = flux * u.Unit("ph s-1 m-2 um-1")
     spec = SourceSpectrum(Empirical1D,
                           points=np.linspace(0.5, 2.5, 11) * u.um,
                           lookup_table=flux)
     bandpass = SpectralElement(Empirical1D,
                                points=np.linspace(1, 2, 13) * u.um,
                                lookup_table=0.5 * np.ones(13))
     counts = source_utils.photons_in_range([spec],
                                            1 * u.um,
                                            2 * u.um,
                                            bandpass=bandpass,
                                            area=area)
     assert counts.value == approx(expected)
Esempio n. 11
0
def filter_flux(filterfile, syn_spec, syn_err=None, wrange=None):
    '''
    ######################################################
    # Input                                              #
    # -------------------------------------------------- #
    # filterfile: file containing filter function        #
    #   syn_spec: synphot spectrum object of source      #
    #     wrange: (start,end) observed wavelength range  #
    # -------------------------------------------------- #
    # Output                                             #
    # -------------------------------------------------- #
    # flux: flux of source in filter system.             #
    ######################################################
    '''
    from synphot import SpectralElement, Observation, Empirical1D, SourceSpectrum
    import astropy.units as u
    
    #Load ascii filter function
    if filterfile.split('/')[-1][:6] == 'Bessel':
        filt = SpectralElement.from_file(filterfile, wave_unit='nm')
    else:
        filt = SpectralElement.from_file(filterfile, wave_unit='AA')
    if wrange is None:
        fwave = filt.waveset
        swave = syn_spec.waveset
        wrange = (max(fwave[0],swave[0]),min(fwave[-1],swave[-1]))
        wlengths = fwave[np.logical_and(fwave>wrange[0], fwave<wrange[1])]
    else:
        fwave = filt.waveset
        wlengths = fwave[np.logical_and(fwave>wrange[0]*u.AA, fwave<wrange[1]*u.AA)]
    #Synthetic observation
    obs = Observation(syn_spec, filt, force='extrap')
    flux = obs.effstim(flux_unit='jy', waverange=wrange).value
    #flux = obs.effstim(flux_unit='jy', wavelengths=wlengths).value
    #Synthetic observation of error spectrum
    if syn_err is not None:
        #square filter and error spectrum
        filt2 = SpectralElement(Empirical1D, points=fwave,
                          lookup_table=np.square(filt(fwave)))
        pseudo_flux = np.square(syn_err(swave,flux_unit='jy')).value
        syn_err2 = SourceSpectrum(Empirical1D, points=swave,
                                   lookup_table=pseudo_flux)
        #sum errors in quadrature
        obs = Observation(syn_err2, filt2, force='extrap')
        flux_err = np.sqrt(obs.effstim(waverange=wrange).value)
        return flux, flux_err
    else:
        return flux
Esempio n. 12
0
    def system_transmission(self):

        wave_unit = u.Unit(rc.__currsys__["!SIM.spectral.wave_unit"])
        dwave = rc.__currsys__["!SIM.spectral.spectral_bin_width"]
        wave_min = rc.__currsys__["!SIM.spectral.wave_min"]
        wave_max = rc.__currsys__["!SIM.spectral.wave_max"]
        wave = np.arange(wave_min, wave_max, dwave)
        trans = np.ones_like(wave)
        sys_trans = SpectralElement(Empirical1D,
                                    points=wave * u.Unit(wave_unit),
                                    lookup_table=trans)

        for effect in self.get_z_order_effects(100):
            sys_trans *= effect.throughput

        return sys_trans
Esempio n. 13
0
    def square(cls, wmin, wmax, transmission):
        """
        Creates a filter with a rectangular shape
        Returns
        -------

        """
        if isinstance(wmin, u.Quantity) is False:
            wmin = wmin*u.AA
        if isinstance(wmax, u.Quantity) is False:
            wmax = wmax*u.AA

        center = (wmax + wmin) * 0.5
        width = wmax - wmin
        modelclass = SpectralElement(Box1D, amplitude=transmission, x_0=center, width=width)

        return cls(modelclass=modelclass)
Esempio n. 14
0
    def gaussian(cls, center, fwhm, peak, **kwargs):
        """
        Creates a filter with a gaussian shape with given user parameters
        Returns
        -------

        """
        if isinstance(center, u.Quantity) is False:
            center = center*u.AA
        if isinstance(fwhm, u.Quantity) is False:
            fwhm = fwhm*u.AA

        sigma = fwhm / (2.0 * np.sqrt(2.0 * np.log(2.0)))

        modelclass = SpectralElement(Gaussian1D, amplitude=peak, mean=center, stddev=sigma, **kwargs)

        return cls(modelclass=modelclass)
Esempio n. 15
0
def download_svo_filter(filter_name, return_style="synphot"):
    """
    Query the SVO service for the true transmittance for a given filter

    Copied 1 to 1 from tynt by Brett Morris

    Parameters
    ----------
    filt : str
        Name of the filter as available on the spanish VO filter service
        e.g: ``Paranal/HAWKI.Ks``

    return_style : str, optional
        Defines the format the data is returned
        - synphot: synphot.SpectralElement
        - table: astropy.table.Table
        - quantity: astropy.unit.Quantity [wave, trans]
        - array: np.ndarray [wave, trans], where wave is in Angstrom
        - vo_table : astropy.table.Table - original output from SVO service

    Returns
    -------
    filt_curve : See return_style
        Astronomical filter object.

    """
    path = download_file('http://svo2.cab.inta-csic.es/'
                         'theory/fps3/fps.php?ID={}'.format(filter_name),
                         cache=True)

    tbl = Table.read(path, format='votable')
    wave = u.Quantity(tbl['Wavelength'].data.data, u.Angstrom, copy=False)
    trans = tbl['Transmission'].data.data
    if return_style == "synphot":
        filt = SpectralElement(Empirical1D, points=wave, lookup_table=trans)
    elif return_style == "table":
        filt = Table(data=[wave, trans], names=["wavelength", "transmission"])
        filt.meta["wavelength_unit"] = "Angstrom"
    elif return_style == "quantity":
        filt = [wave, trans]
    elif return_style == "array":
        filt = [wave.value, trans]
    elif return_style == "vo_table":
        filt = tbl

    return filt
Esempio n. 16
0
    def set_bandpass_from_filter(self, filtername):
        """Loads the specified <filtername> from the transmission profile file
        which is mapped via the etc.config.Conf() items.
        Returns a SpectralElement instance for the filter profile
        """

        if len(filtername) == 2 and filtername[1] == 'p':
            filtername = filtername[0]

        filename = conf.mapping.get(filtername, None)
        if filename is None:
            raise ETCError('Filter name {0} is invalid.'.format(filtername))
        if 'LCO_' in filename().upper() and '.csv' in filename().lower():
            file_path = pkg_resources.files('etc.data').joinpath(
                os.path.expandvars(filename()))
            source = "LCO iLab format"
            header, wavelengths, throughput = self._read_lco_filter_csv(
                file_path)
        elif 'http://svo' in filename().lower():
            source = "SVO filter service"
            header, wavelengths, throughput = specio.read_remote_spec(
                filename(), wave_unit=u.AA, flux_unit=units.THROUGHPUT)
        else:
            source = "local file"
            file_path = pkg_resources.files('etc.data').joinpath(
                os.path.expandvars(filename()))
            warnings.simplefilter('ignore', category=AstropyUserWarning)
            header, wavelengths, throughput = specio.read_ascii_spec(
                file_path, wave_unit=u.nm, flux_unit=units.THROUGHPUT)
            if throughput.mean() > 1.0:
                throughput /= 100.0
                header['notes'] = 'Divided by 100.0 to convert from percentage'
        print("Reading from {} for {}".format(source, filtername))

        header['filename'] = filename
        header['descrip'] = filename.description
        meta = {'header': header, 'expr': filtername}

        return SpectralElement(Empirical1D,
                               points=wavelengths,
                               lookup_table=throughput,
                               meta=meta)
Esempio n. 17
0
    def from_vectors(cls, waves, trans, meta=None, wave_unit=u.AA):
        """
        Create a ``Passband`` directly from from vectos (lists, numpy.arrays, etc)
        Parameters
        ----------
        waves: list-like
        trans: list-like
        meta: dictionary containing the metadata
        wave_unit: u.Quantity, defaulted to angstroms

        Returns
        -------
        Passband
        """
        if isinstance(waves, u.Quantity) is False:
            waves = waves*wave_unit

        modelclass = SpectralElement(Empirical1D, points=waves, lookup_table=trans, meta=meta)

        return cls(modelclass=modelclass)
Esempio n. 18
0
def get_filter(filter_name):
    # first check locally
    # check generics
    # check spanish_vo
    path = find_file(filter_name, silent=True)

    if path is not None:
        tbl = ioascii.read(path)
        wave = quantity_from_table("wavelength", tbl, u.um).to(u.um)
        filt = SpectralElement(Empirical1D, points=wave,
                               lookup_table=tbl["transmission"])
    elif filter_name in FILTER_DEFAULTS:
        filt = download_svo_filter(FILTER_DEFAULTS[filter_name])
    else:
        try:
            filt = download_svo_filter(filter_name)
        except:
            filt = None

    return filt
Esempio n. 19
0
    def __init__(self, name=None, camera_type="CCD", **kwargs):
        _cam_types = [
            "CCD",
        ]
        self.camera_type = camera_type.upper() if camera_type.upper(
        ) in _cam_types else "CCD"
        self.name = name if name is not None else self.camera_type + ' camera'

        # Defaults assume no elements per channel on the assumption that it's
        # in the common optics (populated in Instrument())
        self.num_ar_coatings = kwargs.get('num_chan_ar_coatings', 0)
        self.num_lenses = kwargs.get('num_chan_lenses', 0)
        self.num_mirrors = kwargs.get('num_chan_mirrors', 0)

        # Fused silica (common lens material) and fused quartz (common for CCD windows)
        # turn out to have the same transmission...
        self.lens_trans = kwargs.get('chan_lens_trans', 0.93)

        self.mirror_refl = kwargs.get('chan_mirror_refl', 0.9925)
        # Transmission/Reflection values of optical elements coating
        self.ar_coating = kwargs.get('chan_ar_coating_refl', 0.99)

        # Read common components first
        trans_components = kwargs.get('trans_components', None)
        wavelengths = np.arange(300, 1501, 1) * u.nm
        if trans_components:
            print("Computing channel transmission from components")
            trans = len(wavelengths) * [
                1.0,
            ]
            for comp_name in trans_components.split(","):
                print(comp_name)
                element = read_element(comp_name)
                trans *= element(wavelengths)
        else:
            print("Computing channel transmission from elements")
            transmission = self._compute_transmission()
            trans = len(wavelengths) * [
                transmission,
            ]
        header = {}
        self.transmission = SpectralElement(Empirical1D,
                                            points=wavelengths,
                                            lookup_table=trans,
                                            keep_neg=True,
                                            meta={'header': header})

        ccd_qe = kwargs.get('ccd_qe', 0.9)
        if not isinstance(ccd_qe, (u.Quantity, numbers.Number)):
            file_path = os.path.expandvars(ccd_qe)
            if not os.path.exists(file_path):
                file_path = str(
                    pkg_resources.files('etc.data').joinpath(ccd_qe))
            header, wavelengths, throughput = specio.read_ascii_spec(
                file_path, wave_unit=u.nm, flux_unit=units.THROUGHPUT)
            if throughput.mean() > 1.0:
                throughput /= 100.0
                header['notes'] = 'Divided by 100.0 to convert from percentage'
            header['filename'] = ccd_qe
            self.ccd_qe = BaseUnitlessSpectrum(Empirical1D,
                                               points=wavelengths,
                                               lookup_table=throughput,
                                               keep_neg=False,
                                               meta={'header': header})
        else:
            self.ccd_qe = ccd_qe
        self.ccd_gain = kwargs.get('ccd_gain', 1) * (u.electron / u.adu)
        self.ccd_readnoise = kwargs.get('ccd_readnoise',
                                        0) * (u.electron / u.pix)
        ccd_pixsize = kwargs.get('ccd_pixsize', 0)
        try:
            ccd_pixsize_units = u.Unit(
                kwargs.get('ccd_pixsize_units', 'micron'))
        except ValueError:
            ccd_pixsize_units = u.micron
        self.ccd_pixsize = (ccd_pixsize * ccd_pixsize_units).to(u.micron)
        self.ccd_xpixels = kwargs.get('ccd_xpixels', 0)
        self.ccd_ypixels = kwargs.get('ccd_ypixels', 0)
        self.ccd_xbinning = kwargs.get('ccd_xbinning', 1)
        self.ccd_ybinning = kwargs.get('ccd_ybinning', 1)
Esempio n. 20
0
    def __init__(self, name=None, inst_type="IMAGER", **kwargs):

        _ins_types = ["IMAGER", "SPECTROGRAPH"]
        self.name = name if name is not None else "Undefined"
        self.inst_type = inst_type.upper() if inst_type.upper(
        ) in _ins_types else "IMAGER"
        self.fiber_diameter = kwargs.get('fiber_diameter', None)
        if self.fiber_diameter is not None:
            self.fiber_diameter = self.fiber_diameter * u.arcsec

        self.grating_linespermm = kwargs.get('grating_linespermm', None)
        if self.grating_linespermm:
            self.grating_spacing = 1.0 / (self.grating_linespermm * (1 / u.mm))
        self.grating_blaze = kwargs.get('grating_blaze', 0.0)
        self.grating_gamma = kwargs.get('grating_gamma', 0.0)
        self.cam_focallength = kwargs.get('cam_focallength', None)
        if self.cam_focallength:
            self.cam_focallength = self.cam_focallength * u.mm
        self.dispersion_along_x = kwargs.get('dispersion_along_x', True)
        self._echelle_constant = None

        # Defaults assume a "standard" imager with a single lens, 2 AR coatings
        # on the front and back surfaces and no mirrors
        self.num_ar_coatings = kwargs.get('num_ar_coatings', 2)
        self.num_lenses = kwargs.get('num_inst_lenses', 1)
        self.num_mirrors = kwargs.get('num_inst_mirrors', 0)

        # Fused silica (common lens material) and fused quartz (common for CCD windows)
        # turn out to have the same transmission...
        self.lens_trans = kwargs.get('inst_lens_trans', 0.93)

        self.mirror_refl = kwargs.get('inst_mirror_refl', 0.9925)
        # Transmission/Reflection values of optical elements coating
        self.ar_coating = kwargs.get('inst_ar_coating_refl', 0.99)

        # Read common components first
        trans_components = kwargs.get('trans_components', None)
        wavelengths = np.arange(300, 1501, 1) * u.nm
        if trans_components:
            trans = len(wavelengths) * [
                1.0,
            ]
            for comp_name in trans_components.split(","):
                print(comp_name)
                element = read_element(comp_name.strip())
                trans *= element(wavelengths)
        else:
            print("Computing transmission from elements")
            transmission = self._compute_transmission()
            trans = len(wavelengths) * [
                transmission,
            ]
        header = {}
        self.transmission = SpectralElement(Empirical1D, points=wavelengths, lookup_table=trans,\
            keep_neg=True, meta={'header': header})

        fwhm = kwargs.get('fwhm', 1)
        try:
            fwhm_units = u.Unit(kwargs.get('fwhm_units', 'arcsec'))
        except ValueError:
            fwhm_units = u.arcsec
        try:
            # Already a Quantity
            self.fwhm = fwhm.to(u.arcsec)
        except AttributeError:
            self.fwhm = fwhm * fwhm_units

        focal_scale = kwargs.get('focal_scale', 0)
        try:
            focal_scale_units = u.Unit(
                kwargs.get('focal_scale_units', 'arcsec/mm'))
        except ValueError:
            focal_scale_units = u.arcsec / u.mm
        self.focal_scale = focal_scale * focal_scale_units

        channels = kwargs.get('channels', {'default': {}})
        self._num_channels = max(len(channels), 1)
        self.channelset = OrderedDict()
        self.filter2channel_map = OrderedDict()

        self.filterlist = kwargs.get('filterlist', [])
        self.filterset = OrderedDict()
        for filtername in self.filterlist:
            if filtername not in self.filterset:
                self.filterset[filtername] = self.set_bandpass_from_filter(
                    filtername)
                self.filter2channel_map[filtername] = 'default'

        for channel in channels:
            # Make copy of common params defined at the Instrument level and then
            # overwrite with channel/camera-specific versions
            camera_kwargs = kwargs.copy()
            # Delete any trans_components
            if 'trans_components' in camera_kwargs:
                del (camera_kwargs['trans_components'])
            for k, v in channels[channel].items():
                camera_kwargs[k] = v
            self.channelset[channel] = Camera(**camera_kwargs)
            if self.channelset[
                    channel].ccd_pixsize != 0 and self.focal_scale != 0:
                self.channelset[channel].ccd_pixscale = self.focal_scale.to(
                    u.arcsec / u.mm) * self.channelset[channel].ccd_pixsize.to(
                        u.mm)
            cam_filterlist = camera_kwargs.get('filterlist', [])
            for filtername in cam_filterlist:
                if filtername not in self.filterset:
                    self.filterlist.append(filtername)
                    self.filterset[filtername] = self.set_bandpass_from_filter(
                        filtername)
                self.filter2channel_map[filtername] = channel
Esempio n. 21
0
def read_element(filtername_or_filename,
                 element_type='element',
                 wave_units=u.nm,
                 flux_units=units.THROUGHPUT):
    """Generic reader for optical elements, filters and others.
    The passed <filtername_or_filename> is first looked up in the `Conf` mapping
    dictionary for a match; if there is no match, it is assumed to be a filename
    or location specifier. These can be of 3 types:
    1. LCO Imaging Lab scanned optical element CSV files (contain 'LCO_' and '.csv' in the filename)
    2. SVO filter service references (contain 'http://svo')
    3. Local files (either ASCII or FITS)

    Checking on the read wavelengths is performed for local files:
    * if the first wavelength value is <100nm and the user didn't override the
    units through [wave_units], the wavelengths are assumed to be in, and are
    converted to, microns.
    * if the first wavelength value is >3000nm and the user didn't override the
    units through [wave_units], the wavelengths are assumed to be in, and are
    converted to, angstroms.
    """

    element_type = element_type.lower()
    filename = conf.mapping.get(filtername_or_filename, None)
    if filename is None:
        filename = filtername_or_filename
    else:
        filename = filename()
        element_type = 'spectral_element'
    if 'LCO_' in filename.upper() and '.csv' in filename.lower():
        file_path = pkg_resources.files('etc.data').joinpath(
            os.path.expandvars(filename))
        source = "LCO iLab format"
        header, wavelengths, throughput = read_lco_filter_csv(file_path)
    elif 'http://svo' in filename.lower():
        source = "SVO filter service"
        header, wavelengths, throughput = specio.read_remote_spec(
            filename, wave_unit=u.AA, flux_unit=units.THROUGHPUT)
    else:
        source = "local file"
        file_path = os.path.expandvars(filename)
        if not os.path.exists(file_path):
            file_path = str(pkg_resources.files('etc.data').joinpath(filename))
        warnings.simplefilter('ignore', category=AstropyUserWarning)
        if filename.lower().endswith('fits') or filename.lower().endswith(
                'fit'):
            try:
                header, wavelengths, throughput = specio.read_spec(
                    file_path,
                    wave_col='lam',
                    flux_col='trans',
                    wave_unit=u.nm,
                    flux_unit=u.dimensionless_unscaled)
            except KeyError:
                # ESO-SM01 format; different column name for transmission and micron vs nm
                header, wavelengths, throughput = specio.read_spec(
                    file_path,
                    wave_col='lam',
                    flux_col='flux',
                    wave_unit=u.micron,
                    flux_unit=u.dimensionless_unscaled)
        else:
            header, wavelengths, throughput = specio.read_ascii_spec(
                file_path, wave_unit=wave_units, flux_unit=flux_units)
        if wavelengths[0].value < 100.0 and wave_units == u.nm:
            # Small values seen, Convert to microns
            wavelengths = wavelengths.value * u.micron
        elif wavelengths[0].value > 3000.0 and wave_units == u.nm:
            # Large values seen, Convert to angstroms
            wavelengths = wavelengths.value * u.AA
        if element_type != 'spectrum' and throughput.mean() > 1.5:
            # Test for mean throughput is above 1 to catch case where a throughput
            # fudge may be in the range ~1 to a few e.g. ESO Omegacam optics fudge
            # which goes to 3.2 and averages out to ~1.4
            throughput /= 100.0
            header['notes'] = 'Divided by 100.0 to convert from percentage'
    header['source'] = source
    header['filename'] = filename
    if element_type == 'spectrum':
        # SourceSpectrum can't use the default units.THROUGHPUT so we need to
        # change to an assumed units.PHOTLAM (u.photon / (u.cm**2 * u.s * u.AA))
        # if nothing was passed by the user
        if flux_units == units.THROUGHPUT:
            throughput = throughput.value * units.PHOTLAM
        element = SourceSpectrum(Empirical1D,
                                 points=wavelengths,
                                 lookup_table=throughput,
                                 keep_neg=False,
                                 meta={'header': header})
    elif element_type == 'spectral_element':
        element = SpectralElement(Empirical1D,
                                  points=wavelengths,
                                  lookup_table=throughput,
                                  keep_neg=False,
                                  meta={'header': header})
    else:
        element = BaseUnitlessSpectrum(Empirical1D,
                                       points=wavelengths,
                                       lookup_table=throughput,
                                       keep_neg=False,
                                       meta={'header': header})

    return element
Esempio n. 22
0
    def exptime_from_ccd_snr(self,
                             snr,
                             V_mag,
                             filtername,
                             npix=1 * u.pixel,
                             n_background=np.inf * u.pixel,
                             background_rate=0 * (u.ct / u.pixel / u.s),
                             sky_mag=None,
                             darkcurrent_rate=0 * (u.ct / u.pixel / u.s)):

        countrate = self.photons_from_source(V_mag, filtername)

        # make sure the gain is in the correct units
        gain = self.instrument.ccd_gain
        if gain.unit in (u.electron / u.adu, u.photon / u.adu):
            gain = gain.value * (u.ct / u.adu)
        elif gain.unit != u.ct / u.adu:
            raise u.UnitsError('gain must have units of (either '
                               'astropy.units.ct, astropy.units.electron, or '
                               'astropy.units.photon) / astropy.units.adu')

        # get the countrate from the synphot observation object
        countrate = countrate / gain
        print(countrate)
        # define counts to be in ADU, which are not technically convertible in
        # astropy.units:
        countrate = countrate.value * (u.ct / u.s)

        # necessary for units to work in countrate calculation:
        if not hasattr(snr, 'unit'):
            snr = snr * np.sqrt(1 * u.ct)
        readnoise = self._get_shotnoise(self.instrument.ccd_readnoise)
        gain_err = self._get_shotnoise(self.instrument.ccd_gain *
                                       self.instrument.adc_error)
        print(readnoise, gain_err)
        if sky_mag is not None:
            # Compute countrate from given sky magnitude.
            # XXX need to refactor photons_from_source() to do this also
            sky_filtername = filtername
            if '::' in filtername:
                sky_filtername = filtername.split('::')[-1]
            sky = self.site.sky_spectrum(sky_filtername)
            print("  Original sky=", sky(sky.avgwave()))
            sky2 = sky.normalize(sky_mag * units.VEGAMAG,
                                 self._V_band,
                                 vegaspec=self._vega)
            print("Normalized sky=", sky2(sky2.avgwave()),
                  sky(sky.avgwave()) * 10**(-sky_mag / 2.5))
            self._create_combined()
            waves, thru = self.combined._get_arrays(None)
            obs_filter = self.instrument.filterset[filtername]
            filter_waves, filter_trans = obs_filter._get_arrays(waves)
            signal_combined = self.combined / self.instrument.ccd_qe
            signal_waves, signal_thru = signal_combined._get_arrays(None)
            print('thruput (Atm*tel*instr)=', signal_thru.max())
            print('thruput (Atm*tel*instr*CCD)=', thru.max(),
                  self.telescope.area.to(units.AREA) * thru.max())
            spec_elements = SpectralElement(Empirical1D,
                                            points=filter_waves,
                                            lookup_table=thru * filter_trans)

            # get the synphot observation object
            with warnings.simplefilter('ignore', category=AstropyUserWarning):
                sky_obs = Observation(sky2, spec_elements)

            sky_countrate = sky_obs.countrate(area=self.telescope.area)
            # Actually a count rate per arcsec^2 as the original magnitude is
            # also per sq. arcsec
            sky_countrate /= u.arcsec**2
            print("Sky (photons/AA/arcsec^2)=", sky_countrate,
                  sky_countrate / (1.0 * 860) * u.AA)
            sky_per_pixel = sky_countrate * self.instrument.ccd_pixscale * self.instrument.ccd_pixscale
            sky_per_pixel /= u.pixel
            print("Sky (photons/pixel)=", sky_per_pixel)
            background_rate = sky_per_pixel

        npix = np.pi * (self.instrument.fwhm / self.instrument.ccd_pixscale)**2
        npix *= u.pixel
        print("npix=", npix)

        # solve t with the quadratic equation (pg. 77 of Howell 2006)
        A = countrate**2
        B = (-1) * snr**2 * (countrate + npix *
                             (background_rate + darkcurrent_rate))
        C = (-1) * snr**2 * npix * readnoise**2
        t = (-B + np.sqrt(B**2 - 4 * A * C)) / (2 * A)

        if gain_err.value > 1 or np.isfinite(n_background.value):
            from scipy.optimize import fsolve
            # solve t numerically
            t = fsolve(_t_with_small_errs,
                       t,
                       args=(background_rate, darkcurrent_rate, gain_err,
                             readnoise, countrate, npix, n_background))
            t = float(t) * u.s

        return t
Esempio n. 23
0
    def ccd_snr(self,
                exp_time,
                V_mag,
                filtername,
                source_spec=None,
                npix=1 * u.pixel,
                n_background=np.inf * u.pixel,
                background_rate=0 * (u.ph / u.pixel / u.s),
                sky_mag=None,
                darkcurrent_rate=0 * (u.ph / u.pixel / u.s),
                normalize=True):
        """Calculate the SNR in a given exposure time <exp_time> for a given <V_mag>
        """

        if filtername not in self.instrument.filterlist:
            raise ETCError('Filter name {0} is invalid.'.format(filtername))

        try:
            exp_time = exp_time.to(u.s)
        except AttributeError:
            exp_time = exp_time * u.s

        # Get countrate per second for this magnitude in the filter
        # Check first if we weren't given a rate directly
        try:
            countrate = V_mag.to(u.photon / u.s)
        except (AttributeError, u.UnitConversionError):
            countrate = self.photons_from_source(V_mag,
                                                 'V',
                                                 filtername,
                                                 source_spec=source_spec,
                                                 normalize=normalize)

        # define countrate to be in photons/sec, which can be treated as the
        # same as (photo)electrons for CCDs but are not technically convertible in
        # astropy.units:
        countrate = countrate.value * (u.photon / u.s)

        print("Counts/s=", countrate)

        readnoise = self.instrument.ccd_readnoise.value * (u.photon / u.pixel)
        readnoise_sq = readnoise.value**2 * (u.photon / u.pixel)
        gain_err = self._get_shotnoise(self.instrument.ccd_gain *
                                       self.instrument.adc_error)
        print("Readnoise=", readnoise, readnoise_sq, gain_err)

        fwhm = self.instrument.fwhm
        pixel_scale = self.instrument.ccd_pixscale
        pixel_area = pixel_scale * pixel_scale

        obs_filter = self.instrument.filterset[filtername]

        if background_rate > 0:
            print("Specific rate given")
            sky_countrate = background_rate / pixel_area / obs_filter.equivwidth(
            )
        else:
            # Obtain sky value based on flux at mag=0 for given filter
            sky_filtername = self._convert_filtername(filtername)
            sky = self.site.sky_spectrum(sky_filtername)
            print("  Original sky for =", sky(sky.avgwave()),
                  sky.meta.get('header', {}).get('filename', ''))
            prefix = "Using"
            sky2 = None
            if sky_mag is None:
                # Try and obtain sky magnitude from Site. This should probably
                # move into sky_spectrum()
                sky_mag = self.site.sky_mags.get(sky_filtername, None)
                prefix = "Determined new"
                if sky_mag is None:
                    if self.site.radiance is None:
                        raise ETCError(
                            'Could not determine a valid sky magnitude for filter: {0}'
                            .format(sky_filtername))
                    else:
                        print(
                            "Attempting synthesized sky magnitude from radiance spectrum"
                        )
                        normalizing_sky_mag = self.site.sky_mags.get('V', None)
                        V_band = self._map_filter_to_standard('V')
                        print(
                            "Normalizing sky spectrum to {} in V band".format(
                                normalizing_sky_mag))
                        radiance_norm = sky.normalize(normalizing_sky_mag *
                                                      units.VEGAMAG,
                                                      V_band,
                                                      vegaspec=self._vega)
                        obs_rad_norm = Observation(radiance_norm,
                                                   obs_filter,
                                                   force='taper')
                        sky_mag = obs_rad_norm.effstim(units.VEGAMAG,
                                                       vegaspec=self._vega)
                        sky_mag = sky_mag.value
                        sky2 = radiance_norm

            print("{} sky magnitude of {:.2f} for {} band".format(
                prefix, sky_mag, sky_filtername))
            if sky2 is None:
                # Compute countrate from given sky magnitude.
                # XXX need to refactor photons_from_source() to do this also
                sky_filter = self._map_filter_to_standard(sky_filtername)
                print("sky_filter", sky_filter.meta.get('expr', 'Unknown'),
                      type(sky))

                sky2 = sky.normalize(sky_mag * units.VEGAMAG,
                                     sky_filter,
                                     vegaspec=self._vega)
            print("Normalized sky=", sky2(sky2.avgwave()),
                  sky2(sky2.avgwave()) * 10**(-sky_mag / 2.5))
            self._create_combined()
            throughput = self._throughput_for_filter(filtername, atmos=False)
            waves, thru = throughput._get_arrays(None)
            filter_waves, filter_trans = obs_filter._get_arrays(waves)
            print('thruput=', thru.max(),
                  self.telescope.area.to(units.AREA) * thru.max())
            spec_elements = SpectralElement(Empirical1D,
                                            points=filter_waves,
                                            lookup_table=thru * filter_trans)

            # get the synphot observation object
            warnings.simplefilter('ignore', category=AstropyUserWarning)
            sky_obs = Observation(sky2, spec_elements, force='taper')

            sky_countrate = sky_obs.countrate(area=self.telescope.area)
            # Actually a count rate per arcsec^2 as the original magnitude is
            # also per sq. arcsec. Also set to photons/s to match signal from object
            sky_countrate = sky_countrate.value * (u.photon / u.s /
                                                   u.arcsec**2)
            print("Sky (photons/AA/arcsec^2)=", sky_countrate,
                  sky_countrate / obs_filter.equivwidth())
            sky_per_pixel = sky_countrate * pixel_area
            sky_per_pixel /= u.pixel
            print("Sky (photons/pixel)=", sky_per_pixel)
            background_rate = sky_per_pixel

        # For spectrographs, calculate fraction of light passing through slit
        vign = self.instrument.slit_vignette()

        if npix == 1 * u.pixel:
            # Calculate new value based on area of FWHM and pixelscale
            # IRAF `ccdtime` uses this definition. Pass in a suitable
            # value for npix into this method to emulate this.
            #        npix = 1.4*(fwhm / pixel_scale)**2
            npix = np.pi * (fwhm / pixel_scale)**2
            print("npix (fractional pixels)=", npix)
            npix = int(round(npix.value)) * u.pixel

        print("npix (whole pixels)=", npix)
        sobj2 = countrate * exp_time
        sky_countrate = sky_countrate * exp_time / obs_filter.equivwidth()
        dark_count = darkcurrent_rate.to(u.photon / u.pix / u.s) * exp_time
        print("Noise(Star)=", np.sqrt(sobj2))
        print("Noise(Dark)=", np.sqrt(npix * dark_count), dark_count)
        print("Noise( CCD)=", np.sqrt(npix * readnoise_sq))
        noise_ccd = np.sqrt(npix * (dark_count + readnoise_sq))
        ssky2 = background_rate * exp_time
        peak = sobj2 / 1.14 / fwhm / fwhm * pixel_area


        print('Detected photons            from object =', sobj2, \
             ' (max',peak,' /pixel)')
        print('Detected photons/A/arcsec^2 from sky    =', sky_countrate)
        print('Detected photons/pixel      from sky    =', ssky2)
        snr = self._compute_snr(sobj2, ssky2, npix, dark_count, readnoise_sq)
        print('Signal-to-noise                         =', snr.value)
        return snr.value
Esempio n. 24
0
def _get_bandpass(name):
    with resources.path(data, f'{name}_effective_area.ecsv') as p:
        t = QTable.read(p)
    return SpectralElement(Empirical1D,
                           points=t['wavelength'],
                           lookup_table=t['effective_area'] / constants.AREA)