Esempio n. 1
0
    def _map_filter_to_standard(self, filtername):
        """Maps a passed <filtername> to a standard Johnson/Cousins/Bessell
        filter profile. Returns a SpectralElement, defaulting to V"""

        band_mapping = {
            'U': 'johnson_u',
            'B': 'johnson_b',
            'V': 'johnson_v',
            'R': conf.bessell_R_file,
            'Rc': 'cousins_r',
            'I': conf.bessell_I_file,
            'Ic': 'cousins_i',
            'J': 'bessel_j',
            'H': 'bessel_h',
            'K': 'bessel_k',
            'G': conf.gaiadr2_g_file
        }
        band_name = band_mapping.get(self._convert_filtername(filtername),
                                     'johnson_v')
        if band_name.startswith('http://svo'):
            band = SpectralElement.from_file(band_name)
        else:
            band = SpectralElement.from_filter(band_name)

        return band
Esempio n. 2
0
    def __init__(self):
        self.verbose = False
        self.telescope_area = 25.326 * 10000.  #JWST primary area in cm^2
        self.wave_bins = np.arange(0.5, 5, 0.1) * u.micron
        self.vega = SourceSpectrum.from_vega()
        self.g2v = STS.grid_to_spec('ck04models', 5860, 0., 4.40)
        self.kband = SpectralElement.from_filter('bessel_k')

        self.bpdir = './'
        self.bpfile = os.path.join(
            self.bpdir, 'F335M_nircam_plus_ote_throughput_moda_sorted.txt')
        self.bp = SpectralElement.from_file(self.bpfile, wave_unit=u.micron)
Esempio n. 3
0
class ETC(object):

    _internal_wave_unit = u.nm
    _sun_raw = SourceSpectrum.from_file(os.path.expandvars(conf.sun_file))
    _vega = SourceSpectrum.from_file(os.path.expandvars(conf.vega_file))
    _V_band = SpectralElement.from_filter('johnson_v')

    def __init__(self, config_file=None, components=None):
        PRESET_MODELS = toml.loads(
            pkg_resources.read_text(data, "FTN_FLOYDS.toml"))
        self.components = components if components is not None else []
        if config_file is None and len(self.components) == 0:
            component_config = PRESET_MODELS
        elif config_file is not None:
            if type(config_file) == dict:
                component_config = config_file
            else:
                component_config = toml.load(config_file)
        else:
            component_config = {}
        if len(component_config) > 0:
            self._create_components_from_config(component_config)

        self.combined = None
        self.combined_noatmos = None
        self.obs = None

    def _create_components_from_config(self, config):
        class_mapping = {
            'site': Site,
            'telescope': Telescope,
            'instrument': Instrument
        }
        for component, component_config in config.items():
            class_name = class_mapping.get(component.lower(), None)
            if class_name:
                #                print(class_name, component_config)
                output_component = class_name(**component_config)
                self.components.append(output_component)

    @property
    def site(self):
        sites = [x for x in self.components if isinstance(x, Site)]
        return sites[0] if len(sites) == 1 else None

    @property
    def telescope(self):
        tels = [x for x in self.components if isinstance(x, Telescope)]
        return tels[0] if len(tels) == 1 else None

    @property
    def instrument(self):
        insts = [x for x in self.components if isinstance(x, Instrument)]
        return insts[0] if len(insts) == 1 else None

    def _convert_filtername(self, filtername):
        """Converts system+filter name (e.g. 'WHT::B' to a bare filter name
        which is returned"""

        new_filtername = filtername
        if '::' in filtername:
            new_filtername = filtername.split('::')[-1]
        return new_filtername

    def _map_filter_to_standard(self, filtername):
        """Maps a passed <filtername> to a standard Johnson/Cousins/Bessell
        filter profile. Returns a SpectralElement, defaulting to V"""

        band_mapping = {
            'U': 'johnson_u',
            'B': 'johnson_b',
            'V': 'johnson_v',
            'R': conf.bessell_R_file,
            'Rc': 'cousins_r',
            'I': conf.bessell_I_file,
            'Ic': 'cousins_i',
            'J': 'bessel_j',
            'H': 'bessel_h',
            'K': 'bessel_k',
            'G': conf.gaiadr2_g_file
        }
        band_name = band_mapping.get(self._convert_filtername(filtername),
                                     'johnson_v')
        if band_name.startswith('http://svo'):
            band = SpectralElement.from_file(band_name)
        else:
            band = SpectralElement.from_filter(band_name)

        return band

    def pickles_to_source_spec(self, sp_type):
        """Returns a SourceSpectrum from the passed <sp_type> if it is found
        in the Pickles atlas, otherwise None is returned.
        The path to the library (which can be remote e.g.
        https://ssb.stsci.edu/trds/grid/pickles/dat_uvi) is given in
        `config.Conf.pickles_library_path"""

        source_spec = None
        filename = sptype_to_pickles_standard(sp_type)
        if filename:
            filepath = os.path.expandvars(
                os.path.join(conf.pickles_library_path, filename))
            source_spec = SourceSpectrum.from_file(filepath)
        return source_spec

    def sso_to_source_spec(self, tax_type):
        """Returns a SourceSpectrum from the passed <tax_type> if it is found
        in the Bus-DeMeo taxonomy, otherwise None is returned.
        The spectrum is produced by multiplying the reflectance spectra by
        a Kurucz model for the Sun so will need to be normalized"""

        source_spec = None

        if tax_type.lower().startswith('sso::') is False:
            tax_type = 'sso::' + tax_type.strip()
        config_item = conf.source_mapping.get(tax_type, None)
        if config_item is not None:
            filename = config_item()
            file_path = os.path.expandvars(filename)
            if not os.path.exists(file_path):
                file_path = str(
                    pkg_resources.files('etc.data').joinpath(filename))
            # Flux unit for LSST throughputs (*almost* FLAM but nm not Angstroms)
            lsst_funit = u.erg / u.cm**2 / u.s / u.nm
            source_spec = SourceSpectrum.from_file(file_path,
                                                   wave_unit=u.nm,
                                                   flux_unit=lsst_funit,
                                                   header_start=1)
            source_spec.meta['header']['source'] = config_item.description
            source_spec.meta['header']['filename'] = filename

        return source_spec

    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

    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

    def _compute_snr(self, sobj2, ssky2, npix, dark_count, readnoise_sq):
        """Compute the signal-to-noise via the CCD equation
        Ref: Howell (2006), page 76
        """
        snr = sobj2 / np.sqrt(sobj2 + npix *
                              (ssky2 + dark_count + readnoise_sq))

        return snr

    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

    def _get_shotnoise(self, detector_property):
        """
        Returns the shot noise (i.e. non-Poissonion noise) in the correct
        units. ``detector_property`` must be a Quantity.
        """
        # Ensure detector_property is in the correct units:
        if detector_property.unit in (u.electron / u.pix, ):
            detector_property = (detector_property.value /
                                 self.instrument.ccd_gain.value) * (u.ct /
                                                                    u.pix)
        detector_property = detector_property.to(u.ct / u.pixel)
        return detector_property.value * np.sqrt(1 * (u.ct / u.pixel))

    def _t_with_small_errs(self, t, background_rate, darkcurrent_rate,
                           gain_err, readnoise, countrate, npix, n_background):
        """
        Returns the full expression for the exposure time including the
        contribution to the noise from the background and the gain.
        """
        if not hasattr(t, 'unit'):
            t = t * u.s

        detector_noise = (background_rate * t + darkcurrent_rate * t +
                          gain_err**2 + readnoise**2)
        radicand = countrate * t + (npix *
                                    (1 + npix / n_background) * detector_noise)

        return countrate * t / np.sqrt(radicand)

    def _do_plot(self,
                 waves,
                 thru,
                 filterlist=[],
                 filterset=None,
                 title='',
                 plot_label='',
                 left=300 * u.nm,
                 right=1200 * u.nm,
                 bottom=0.0,
                 top=None):
        """Plot worker.

        Parameters
        ----------
        waves, thru : `~astropy.units.quantity.Quantity`
            Wavelength and throughput to plot.

        filterset : list
            List of filter bandpasses to plot

        kwargs :

        title : str
            Plot title.

        plot_label : str
            Line plot label for legend

        left, right : `None`, number or `Quantity`
            Minimum and maximum wavelengths to plot.
            If `None`, uses the whole range. If a number is given,
            must be in Angstrom. If an `~astropy.units.quantity.Quantity`
            is given, it will be converted tp the internal wave units.

        bottom, top : `None` or number
            Minimum and maximum flux/throughput to plot.
            If `None`, uses the whole range. If a number is given,
            it assumed to be in throughput (0..1).
        """
        try:
            import matplotlib.pyplot as plt
        except ImportError:
            print(
                'No matplotlib installation found; plotting disabled as a result.'
            )
            return

        fig, ax = plt.subplots()
        ax.plot(waves, thru, label=plot_label)

        # Plot filters
        for filtername in filterlist:
            filter_wave, filter_trans = filterset[filtername]._get_arrays(
                waves)
            filt_plot = ax.plot(filter_wave.to(self._internal_wave_unit),
                                filter_trans * thru,
                                linestyle='--',
                                linewidth=1,
                                label=filtername)

        # Custom wavelength limits
        if left is not None:
            if isinstance(left, u.Quantity):
                left_val = left.to(self._internal_wave_unit).value
            else:
                left_val = left
            ax.set_xlim(left=left_val)
        if right is not None:
            if isinstance(right, u.Quantity):
                right_val = right.to(self._internal_wave_unit).value
            else:
                right_val = right
            ax.set_xlim(right=right_val)

        # Custom flux/throughput limit
        if bottom is not None:
            ax.set_ylim(bottom=bottom)
        if top is not None:
            ax.set_ylim(top=top)

        wave_unit = waves.unit
        ax.set_xlabel('Wavelength ({0})'.format(wave_unit))

        yu = thru.unit
        if yu is u.dimensionless_unscaled:
            ax.set_ylabel('Unitless')
        elif yu is u.percent:
            ax.set_ylabel('Efficiency ({0})'.format(yu))
        else:
            ax.set_ylabel('Flux ({0})'.format(yu))

        if title:
            ax.set_title(title)

        # Turn on minorticks on both axes
        ax.minorticks_on()
        if len(filterlist) > 0 or plot_label != '':
            ax.legend()

        plt.draw()

    def plot(self, filterlist=[], **kwargs):
        """Plot combined system throughput and filters. By default (if
        `filterlist=[]`) then all instrument filters are plotted. To plot
        specific filters, set `filterlist` to a string of the filtername or
        a list of filternames. To disable filter plotting completely, set
        `filterlist=None`

        Parameters
        ----------
        waves, thru : `~astropy.units.quantity.Quantity`
            Wavelength and throughput to plot.

        filterlist : list, str or None
            Filter name(s) to plot

        kwargs :
            atmos : bool
                Whether to include the atmosphere or not
            Also see :func:`do_plot`.

        """
        self._create_combined()
        if kwargs.get('atmos', True) is True:
            waves, trans = self.combined[0]._get_arrays(None)
        else:
            waves, trans = self.combined_noatmos[0]._get_arrays(None)
        for channel_num in range(1, self.instrument.num_channels):
            if kwargs.get('atmos', True) is True:
                chan_trans = self.combined[channel_num](waves)
            else:
                chan_trans = self.combined_noatmos[channel_num](waves)
            trans *= chan_trans

        if 'atmos' in kwargs:
            del (kwargs['atmos'])
        filterset = None
        # Handle single filter string case
        if isinstance(filterlist, str):
            filterlist = [
                filterlist,
            ]
        elif filterlist == []:
            filterlist = self.instrument.filterlist
        elif filterlist is None:
            filterlist = []
        if len(filterlist) > 0 and set(filterlist).issubset(
                set(self.instrument.filterlist)):
            filterset = self.instrument.filterset
        self._do_plot(waves.to(self._internal_wave_unit), trans, filterlist,
                      filterset, **kwargs)

    def plot_efficiency(self, filtername, **kwargs):
        """Plot combined system throughput for filtername.

        Parameters
        ----------
        filtername : str
            Filter name to plot

        kwargs :
            atmos : bool
                Whether to include the atmosphere or not (default is True)
            Also see :func:`do_plot` for additional options.

        """
        # Handle single filter string case
        if filtername in self.instrument.filterlist:
            throughput = self._throughput_for_filter(filtername,
                                                     atmos=kwargs.get(
                                                         'atmos', True))

            if kwargs.get('atmos', True) is True:
                atmos_presence = 'incl.'
            else:
                atmos_presence = 'excl.'
            if 'atmos' in kwargs:
                del (kwargs['atmos'])
            if 'title' not in kwargs:
                kwargs['title'] = 'System Efficiency {} Atmosphere'.format(
                    atmos_presence)

            efficiency = throughput * self.instrument.filterset[filtername]
            # Add slit/fiber vignetting (if applicable)
            vignetting = self.instrument.slit_vignette()
            #            print("Vignetting", vignetting, (1.0-vignetting)*100., self.instrument.fiber_diameter, self.instrument.fwhm)
            efficiency = efficiency * vignetting
            self.efficiency = efficiency
            waves = efficiency.waveset
            thru = efficiency(waves) * 100.0 * u.percent
            self._do_plot(waves.to(self._internal_wave_unit),
                          thru,
                          filterlist=[],
                          filterset=[],
                          **kwargs)
        else:
            raise ETCError('Filter name {0} is invalid.'.format(filtername))

    def _channel_for_filter(self, filtername):
        """Maps the passed <filtername> to Instrument channel number which is
        returned"""

        channel_name = self.instrument.filter2channel_map.get(
            filtername, 'default')
        try:
            index = list(self.instrument.channelset).index(channel_name)
        except ValueError:
            print("Filter {} not in instrument channel map".format(filtername))
        return index

    def _throughput_for_filter(self, filtername, atmos=True):
        self._create_combined()
        if atmos is True:
            throughput = self.combined
        else:
            throughput = self.combined_noatmos
        # Map filtername to instrument channel and correct throughput
        index = self._channel_for_filter(filtername)
        throughput = throughput[index]

        return throughput

    def _create_combined(self):
        if self.combined_noatmos is None:
            self.combined_noatmos = []
            for channel in self.instrument.channelset.values():
                channel_combined = self.telescope.reflectivity * self.instrument.transmission * channel.transmission * channel.ccd_qe
                self.combined_noatmos.append(channel_combined)
        if self.combined is None:
            self.combined = []
            for channel in self.combined_noatmos:
                channel_combined = channel * self.site.transmission
                self.combined.append(channel_combined)
        # Collapse lists back down to single instance if only one channel
        # if self.instrument.num_channels == 1:
        # self.combined_noatmos = self.combined_noatmos[0]
        # self.combined = self.combined[0]

    def __repr__(self):
        prefixstr = '<' + self.__class__.__name__ + ' '
        compstr = "->".join([repr(x) for x in self.components])
        return f'{prefixstr}{compstr}>'
Esempio n. 4
0
    '/home/anadkarni/NIRCam_AZ_TSO_Data_Challenge/GJ436_2102/GJ436_sim_data/')
ensure_dir_exists(output_data_dir)
tsdir = '/home/anadkarni/NIRCam_AZ_TSO_Data_Challenge/GJ436_LC_TS_Params/'

# ----------  Prepare Inputs  ----------

xml_file = os.path.join(input_data_path, 'GJ436.xml')
pointing_file = xml_file.replace('.xml', '.pointing')

# ----------  Stellar Spectrum  ----------

t_eff = 3500  # surface temperature
metallicity = 0.02  # Fe/H
log_g = 5.0  # surface gravity = 182 m/s^2
sp = stsyn.grid_to_spec('ck04models', t_eff, metallicity, log_g)
bp = SpectralElement.from_filter('johnson_k')
vega = SourceSpectrum.from_vega()
sp_norm = sp.normalize(6.073 * units.VEGAMAG, bp, vegaspec=vega)
wavelengths = sp_norm.waveset.to(u.micron)
fluxes = sp_norm(wavelengths, flux_unit='flam')
wavelength_units = 'microns'
flux_units = 'flam'
sed_file = os.path.join(output_dir, 'GJ436_stellar_spectrum.hdf5')
fluxes = [fluxes]
wavelengths = [wavelengths]
with h5py.File(sed_file, "w") as file_obj:
    for i in range(len(fluxes)):
        dset = file_obj.create_dataset(
            str(i + TSO_GRISM_INDEX),
            data=[wavelengths[i].value, fluxes[i].value],
            dtype='f',
Esempio n. 5
0
    def set_band(self, band):
        '''You can use this line to set the band of the bolometric correction
        dynamically for a hardcoded number of bands.
        '''
        if band == 'Ks':
            self.B = SpectralElement.from_filter('bessel_k')
        if band == 'J':
            self.B = SpectralElement.from_filter('bessel_j')
        if band == 'H':
            self.B = SpectralElement.from_filter('bessel_h')

        if band == 'B':
            self.B = SpectralElement.from_filter('johnson_b')
        if band == 'V':
            self.B = SpectralElement.from_filter('johnson_v')
        if band == 'R':
            self.B = SpectralElement.from_filter('cousins_r')
        if band == 'I':
            self.B = SpectralElement.from_filter('cousins_i')

        if band == 'Gaia':
            df = np.genfromtxt(datadir + '/GaiaDR2_RevisedPassbands.dat')
            df = pd.DataFrame(
                df, columns=['wl', 'G', 'Gerr', 'BP', 'BPerr', 'RP', 'RPerr'])
            df[df == 99.99] = 0.
            self.B = interpolate.interp1d(df.wl * 1e1,
                                          df.G,
                                          fill_value='extrapolate',
                                          bounds_error=False)
        if band == 'Gaia_B':
            df = np.genfromtxt(datadir + '/GaiaDR2_RevisedPassbands.dat')
            df = pd.DataFrame(
                df, columns=['wl', 'G', 'Gerr', 'BP', 'BPerr', 'RP', 'RPerr'])
            df[df == 99.99] = 0.
            self.B = interpolate.interp1d(df.wl * 10.,
                                          df.BP,
                                          fill_value='extrapolate',
                                          bounds_error=False)
        if band == 'Gaia_R':
            df = np.genfromtxt(datadir + '/GaiaDR2_RevisedPassbands.dat')
            df = pd.DataFrame(
                df, columns=['wl', 'G', 'Gerr', 'BP', 'BPerr', 'RP', 'RPerr'])
            df[df == 99.99] = 0.
            self.B = interpolate.interp1d(df.wl * 10.,
                                          df.RP,
                                          fill_value='extrapolate',
                                          bounds_error=False)

        if band == 'Kepler':
            df = np.genfromtxt(datadir + '/kepler_response_hires1.txt').T
            self.B = interpolate.interp1d(df[0] * 1e1,
                                          df[1],
                                          fill_value='extrapolate',
                                          bounds_error=False)

        if band == 'W1':
            df = np.genfromtxt(datadir + '/RSR-W1.txt').T
            self.B = interpolate.interp1d(df[0] * 1e5,
                                          df[1],
                                          fill_value='extrapolate',
                                          bounds_error=False)
        if band == 'W2':
            df = np.genfromtxt(datadir + '/RSR-W2.txt').T
            self.B = interpolate.interp1d(df[0] * 1e5,
                                          df[1],
                                          fill_value='extrapolate',
                                          bounds_error=False)
        if band == 'W3':
            df = np.genfromtxt(datadir + '/RSR-W3.txt').T
            self.B = interpolate.interp1d(df[0] * 1e5,
                                          df[1],
                                          fill_value='extrapolate',
                                          bounds_error=False)
        if band == 'W4':
            df = np.genfromtxt(datadir + '/RSR-W4.txt').T
            self.B = interpolate.interp1d(df[0] * 1e5,
                                          df[1],
                                          fill_value='extrapolate',
                                          bounds_error=False)

        self.band = band
Esempio n. 6
0
def flux2ABmag(wave, flux, band, mag, plot=False):

    # "band" should be either "sdss_g" or "V" for the moment

    # The input "wave" should be in nm

    # Define bandpass:
    if band == "sdss_g":
        bp = SpectralElement.from_file('../utility/photometry/g.dat')
        magvega = -0.08
    elif band == "V":
        bp = SpectralElement.from_filter('johnson_v')
        magvega = 0.03

    # SDSS g-band magnitude of Vega
    #gmagvega=-0.08

    # Read the spectrum of Vega
    sp_vega = SourceSpectrum.from_vega()
    wv_vega = sp_vega.waveset
    fl_vega = sp_vega(wv_vega, flux_unit=units.FLAM)

    ## Convolve with the bandpass
    obs_vega = Observation(sp_vega, bp)

    ## Integrated flux
    fluxtot_vega = obs_vega.integrate()

    # Read the synthetic spectrum
    sp = SourceSpectrum(Empirical1D, points = wave * 10., \
                        lookup_table=flux*units.FLAM)
    wv = sp.waveset
    fl = sp(wv, flux_unit=units.FLAM)

    ## Convolve with the bandpass
    obs = Observation(sp, bp, force='extrap')

    ## Integrated g-band flux
    fluxtot = obs.integrate()

    # Scaling factor to make the flux compatible with the desired magnitude
    dm = mag - magvega
    const = fluxtot_vega * 10**(-0.4 * dm) / fluxtot

    # Scale the original flux by const
    fl_scale = const * fl

    # Convert to ABmag
    fl_scale_mag = units.convert_flux(wv, fl_scale, out_flux_unit='abmag')

    sp_scaled_mag = SourceSpectrum(Empirical1D,
                                   points=wv,
                                   lookup_table=fl_scale_mag)

    # Plot
    if plot == True:
        fig, ax = plt.subplots(2, 1, sharex=True)
        ax[0].plot(wave * 10., flux, linestyle="-", marker="")
        ax[1].plot(wv[1:-1],
                   sp_scaled_mag(wv, flux_unit=u.ABmag)[1:-1],
                   linestyle="-",
                   marker="")
        ax[1].set_xlabel("Angstrom")
        ax[0].set_ylabel("$F_{\lambda}$")
        ax[1].set_ylabel("ABmag")
        ax[1].set_ylim(mag + 3., mag - 2.0)
        #plt.show()

    return (wv[1:-1] / 10., sp_scaled_mag(wv, flux_unit=u.ABmag)[1:-1])