Пример #1
0
def get_sky_spectrum(mag=None, mag_band='g', mag_system='AB'):
    sky_spec = spectrum.MaunakeaSkySpectrum()
    if mag is None:
        return sky_spec

    broadband = efficiency.FilterResponse(band=mag_band)
    sky_spec.rescale_magnitude(mag, band=broadband, system=mag_system)
    return sky_spec
Пример #2
0
def get_spectrum(wave, mag, emline_db=None, redshift=0.0, resolution=3500):
    """
    """
    spec = spectrum.ABReferenceSpectrum(wave, resolution=resolution, log=True)
    g = efficiency.FilterResponse()
    spec.rescale_magnitude(mag, band=g)
    if emline_db is None:
        return spec
    spec = spectrum.EmissionLineSpectrum(wave,
                                         emline_db['flux'],
                                         emline_db['restwave'],
                                         emline_db['fwhm'],
                                         units=emline_db['fwhmu'],
                                         redshift=redshift,
                                         resolution=resolution,
                                         log=True,
                                         continuum=spec.flux)
    warnings.warn(
        'Including emission lines, spectrum g-band magnitude changed '
        'from {0} to {1}.'.format(mag, spec.magnitude(band=g)))
    return spec
Пример #3
0
def get_spectrum(wave,
                 mag,
                 mag_band='g',
                 mag_system='AB',
                 spec_file=None,
                 spec_wave=None,
                 spec_wave_units=None,
                 spec_flux=None,
                 spec_flux_units=None,
                 spec_res_indx=None,
                 spec_res_value=None,
                 spec_table=None,
                 emline_db=None,
                 redshift=0.0,
                 resolution=3500):
    """
    """
    spec = spectrum.ABReferenceSpectrum(wave, resolution=resolution, log=True) \
                if spec_file is None \
                else read_spectrum(spec_file, spec_wave, spec_wave_units, spec_flux,
                                   spec_flux_units, spec_res_indx, spec_res_value, spec_table,
                                   wave, resolution)
    broadband = efficiency.FilterResponse(band=mag_band)
    spec.rescale_magnitude(mag, band=broadband, system=mag_system)
    if emline_db is None:
        return spec
    spec = spectrum.EmissionLineSpectrum(wave,
                                         emline_db['flux'],
                                         emline_db['restwave'],
                                         emline_db['fwhm'],
                                         units=emline_db['fwhmu'],
                                         redshift=redshift,
                                         resolution=resolution,
                                         log=True,
                                         continuum=spec.flux)
    warnings.warn(
        'Including emission lines, spectrum broadband magnitude changed '
        'from {0} to {1}.'.format(mag, spec.magnitude(band=broadband)))
    return spec
Пример #4
0
def main(args):

    if args.sky_err < 0 or args.sky_err > 1:
        raise ValueError(
            '--sky_err option must provide a value between 0 and 1.')

    t = time.perf_counter()

    # Constants:
    resolution = 3500.  # lambda/dlambda
    fiber_diameter = 0.8  # Arcsec
    rn = 2.  # Detector readnoise (e-)
    dark = 0.0  # Detector dark-current (e-/s)

    # Temporary numbers that assume a given spectrograph PSF and LSF.
    # Assume 3 pixels per spectral and spatial FWHM.
    spatial_fwhm = args.spot_fwhm
    spectral_fwhm = args.spot_fwhm

    # Get source spectrum in 1e-17 erg/s/cm^2/angstrom. Currently, the
    # source spectrum is assumed to be
    #   - normalized by the total integral of the source flux
    #   - independent of position within the source
    dw = 1 / spectral_fwhm / resolution / numpy.log(10)
    wavelengths = [3100, 10000, dw]
    wave = get_wavelength_vector(wavelengths[0], wavelengths[1],
                                 wavelengths[2])
    emline_db = None if args.emline is None else read_emission_line_database(
        args.emline)
    spec = get_spectrum(wave,
                        args.mag,
                        mag_band=args.mag_band,
                        mag_system=args.mag_system,
                        spec_file=args.spec_file,
                        spec_wave=args.spec_wave,
                        spec_wave_units=args.spec_wave_units,
                        spec_flux=args.spec_flux,
                        spec_flux_units=args.spec_flux_units,
                        spec_res_indx=args.spec_res_indx,
                        spec_res_value=args.spec_res_value,
                        spec_table=args.spec_table,
                        emline_db=emline_db,
                        redshift=args.redshift,
                        resolution=resolution)

    # Get the source distribution.  If the source is uniform, onsky is None.
    onsky = get_source_distribution(args.fwhm, args.uniform, args.sersic)

    # Show the rendered source
    #    if onsky is not None:
    #        pyplot.imshow(onsky.data, origin='lower', interpolation='nearest')
    #        pyplot.show()

    # Get the sky spectrum
    sky_spectrum = get_sky_spectrum(args.sky_mag,
                                    mag_band=args.sky_mag_band,
                                    mag_system=args.sky_mag_system)

    # Overplot the source and sky spectrum
    #    ax = spec.plot()
    #    ax = sky_spectrum.plot(ax=ax, show=True)

    # Get the atmospheric throughput
    atmospheric_throughput = efficiency.AtmosphericThroughput(
        airmass=args.airmass)

    # Set the telescope. Defines the aperture area and throughput
    # (nominally 3 aluminum reflections for Keck)
    telescope = telescopes.KeckTelescope()

    # Define the observing aperture; fiber diameter is in arcseconds,
    # center is 0,0 to put the fiber on the target center. "resolution"
    # sets the resolution of the fiber rendering; it has nothing to do
    # with spatial or spectral resolution of the instrument
    fiber = aperture.FiberAperture(0, 0, fiber_diameter, resolution=100)

    # Get the spectrograph throughput (circa June 2018; TODO: needs to
    # be updated). Includes fibers + foreoptics + FRD + spectrograph +
    # detector QE (not sure about ADC). Because this is the total
    # throughput, define a generic efficiency object.
    thru_db = numpy.genfromtxt(
        os.path.join(os.environ['SYNOSPEC_DIR'], 'data/efficiency',
                     'fobos_throughput.db'))
    spectrograph_throughput = efficiency.Efficiency(thru_db[:, 1],
                                                    wave=thru_db[:, 0])

    # System efficiency combines the spectrograph and the telescope
    system_throughput = efficiency.SystemThroughput(
        wave=spec.wave,
        spectrograph=spectrograph_throughput,
        telescope=telescope.throughput)

    # Instantiate the detector; really just a container for the rn and
    # dark current for now. QE is included in fobos_throughput.db file,
    # so I set it to 1 here.
    det = detector.Detector(rn=rn, dark=dark, qe=1.0)

    # Extraction: makes simple assumptions about the detector PSF for
    # each fiber spectrum and mimics a "perfect" extraction, including
    # an assumption of no cross-talk between fibers. Ignore the
    # "spectral extraction".
    extraction = extract.Extraction(det,
                                    spatial_fwhm=spatial_fwhm,
                                    spatial_width=1.5 * spatial_fwhm,
                                    spectral_fwhm=spectral_fwhm,
                                    spectral_width=spectral_fwhm)

    # Perform the observation
    obs = Observation(telescope,
                      sky_spectrum,
                      fiber,
                      args.time,
                      det,
                      system_throughput=system_throughput,
                      atmospheric_throughput=atmospheric_throughput,
                      airmass=args.airmass,
                      onsky_source_distribution=onsky,
                      source_spectrum=spec,
                      extraction=extraction,
                      snr_units=args.snr_units)

    # Construct the S/N spectrum
    snr = obs.snr(sky_sub=True, sky_err=args.sky_err)
    if args.ipython:
        embed()

    snr_label = 'S/N per {0}'.format('R element' if args.snr_units ==
                                     'resolution' else args.snr_units)

    if args.plot:
        w, h = pyplot.figaspect(1)
        fig = pyplot.figure(figsize=(1.5 * w, 1.5 * h))

        ax = fig.add_axes([0.1, 0.5, 0.8, 0.4])
        ax.set_xlim([wave[0], wave[-1]])
        ax.minorticks_on()
        ax.tick_params(which='major',
                       length=8,
                       direction='in',
                       top=True,
                       right=True)
        ax.tick_params(which='minor',
                       length=4,
                       direction='in',
                       top=True,
                       right=True)
        ax.grid(True, which='major', color='0.8', zorder=0, linestyle='-')
        ax.xaxis.set_major_formatter(ticker.NullFormatter())
        ax.set_yscale('log')

        ax = spec.plot(ax=ax, label='Object')
        ax = sky_spectrum.plot(ax=ax, label='Sky')
        ax.legend()
        ax.text(-0.1,
                0.5,
                r'Flux [10$^{-17}$ erg/s/cm$^2$/${\rm \AA}$]',
                ha='center',
                va='center',
                transform=ax.transAxes,
                rotation='vertical')

        ax = fig.add_axes([0.1, 0.1, 0.8, 0.4])
        ax.set_xlim([wave[0], wave[-1]])
        ax.minorticks_on()
        ax.tick_params(which='major',
                       length=8,
                       direction='in',
                       top=True,
                       right=True)
        ax.tick_params(which='minor',
                       length=4,
                       direction='in',
                       top=True,
                       right=True)
        ax.grid(True, which='major', color='0.8', zorder=0, linestyle='-')

        ax = snr.plot(ax=ax)

        ax.text(0.5,
                -0.1,
                r'Wavelength [${\rm \AA}$]',
                ha='center',
                va='center',
                transform=ax.transAxes)
        ax.text(-0.1,
                0.5,
                snr_label,
                ha='center',
                va='center',
                transform=ax.transAxes,
                rotation='vertical')

        pyplot.show()

    # Report
    g = efficiency.FilterResponse(band='g')
    r = efficiency.FilterResponse(band='r')
    iband = efficiency.FilterResponse(band='i')
    print('-' * 70)
    print('{0:^70}'.format('FOBOS S/N Calculation (v0.2)'))
    print('-' * 70)
    print('Compute time: {0} seconds'.format(time.perf_counter() - t))
    print('Object g- and r-band AB magnitude: {0:.1f} {1:.1f}'.format(
        spec.magnitude(band=g), spec.magnitude(band=r)))
    print('Sky g- and r-band AB surface brightness: {0:.1f} {1:.1f}'.format(
        sky_spectrum.magnitude(band=g), sky_spectrum.magnitude(band=r)))
    print('Exposure time: {0:.1f} (s)'.format(args.time))
    if not args.uniform:
        print('Aperture Loss: {0:.1f}%'.format(
            (1 - obs.aperture_factor) * 100))
    print('Extraction Loss: {0:.1f}%'.format(
        (1 - obs.extraction.spatial_efficiency) * 100))
    print('Median {0}: {1:.1f}'.format(snr_label, numpy.median(snr.flux)))
    print('g-band weighted mean {0} {1:.1f}'.format(
        snr_label,
        numpy.sum(g(snr.wave) * snr.flux) / numpy.sum(g(snr.wave))))
    print('r-band weighted mean {0} {1:.1f}'.format(
        snr_label,
        numpy.sum(r(snr.wave) * snr.flux) / numpy.sum(r(snr.wave))))
    print('i-band weighted mean {0} {1:.1f}'.format(
        snr_label,
        numpy.sum(iband(snr.wave) * snr.flux) / numpy.sum(iband(snr.wave))))
Пример #5
0
def main(args):

    if args.sky_err < 0 or args.sky_err > 1:
        raise ValueError(
            '--sky_err option must provide a value between 0 and 1.')

    t = time.perf_counter()

    # Extract the slit properties for clarity
    slit_x, slit_y, slit_width, slit_length, slit_rotation = args.slit
    effective_slit_width = slit_width / numpy.cos(numpy.radians(slit_rotation))
    _extract_length = args.fwhm if args.extract is None else args.extract

    # TODO: The slit length is currently not used. Instead, the length
    # of the slit is set to the extraction length. This is mostly just
    # because of the current way the Observation class works.

    # Slit aperture. This representation of the slit is *always*
    # centered at (0,0). Set the aperture based on the extraction
    # length for now.
    slit = aperture.SlitAperture(0.,
                                 0.,
                                 slit_width,
                                 _extract_length,
                                 rotation=slit_rotation)

    # Get the source distribution.  If the source is uniform, onsky is None.
    onsky = get_source_distribution(args.fwhm, args.uniform, args.sersic)

    # Sky spectrum and atmospheric throughput
    sky_spectrum = spectrum.MaunakeaSkySpectrum()
    atmospheric_throughput = efficiency.AtmosphericThroughput(
        airmass=args.airmass)

    # Emission lines to add
    emline_db = None if args.emline is None else read_emission_line_database(
        args.emline)

    # Setup the raw object spectrum
    if args.spec_file is None:
        wavelengths = [3100, 10000, 1e-5]
        wave = get_wavelength_vector(*wavelengths)
        obj_spectrum = spectrum.ABReferenceSpectrum(wave, log=True)
    else:
        obj_spectrum = read_spectrum(args.spec_file, args.spec_wave,
                                     args.spec_wave_units, args.spec_flux,
                                     args.spec_flux_units, args.spec_res_indx,
                                     args.spec_res_value, args.spec_table)

    #-------------------------------------------------------------------
    #-------------------------------------------------------------------
    # Setup the instrument arms

    #-------------------------------------------------------------------
    # Blue Arm
    blue_arm = TMTWFOSBlue(reflectivity=args.refl,
                           grating=args.blue_grat,
                           cen_wave=args.blue_wave,
                           grating_angle=args.blue_angle)

    # Pixels per resolution element
    blue_res_pix = blue_arm.resolution_element(slit_width=effective_slit_width, units='pixels') \
                        / args.blue_binning[0]

    # Get the wavelength range for each arm
    blue_wave_lim = blue_arm.wavelength_limits(slit_x,
                                               slit_y,
                                               add_grating_limits=True)

    # Setup dummy wavelength vectors to get something appropriate for sampling
    max_resolution = blue_arm.resolution(blue_wave_lim[1],
                                         x=slit_x,
                                         slit_width=effective_slit_width)

    # Set the wavelength vector to allow for a regular, logarithmic binning
    dw = 1 / blue_res_pix / max_resolution / numpy.log(10)
    blue_wave = get_wavelength_vector(blue_wave_lim[0], blue_wave_lim[1], dw)
    resolution = blue_arm.resolution(blue_wave,
                                     x=slit_x,
                                     slit_width=effective_slit_width)
    blue_spec = observed_spectrum(obj_spectrum,
                                  blue_wave,
                                  resolution,
                                  mag=args.mag,
                                  mag_band=args.mag_band,
                                  mag_system=args.mag_system,
                                  redshift=args.redshift,
                                  emline_db=emline_db)

    # Resample to linear to better match what's expected for the detector
    blue_ang_per_pix = blue_arm.resolution_element(
        wave=blue_wave_lim, slit_width=effective_slit_width,
        units='angstrom') / blue_res_pix
    blue_wave = get_wavelength_vector(blue_wave_lim[0],
                                      blue_wave_lim[1],
                                      numpy.mean(blue_ang_per_pix),
                                      linear=True)
    blue_spec = blue_spec.resample(wave=blue_wave, log=False)

    # Spectrograph arm efficiency (this doesn't include the telescope)
    blue_arm_eff = blue_arm.efficiency(blue_spec.wave,
                                       x=slit_x,
                                       y=slit_y,
                                       same_type=False)

    # System efficiency combines the spectrograph and the telescope
    blue_thru = efficiency.SystemThroughput(
        wave=blue_spec.wave,
        spectrograph=blue_arm_eff,
        telescope=blue_arm.telescope.throughput)

    # Extraction: makes simple assumptions about the monochromatic
    # image and extracts the flux within the aperture, assuming the
    # flux from both the object and sky is uniformly distributed across
    # all detector pixels (incorrect!).

    # Extraction width in pixels
    spatial_width = slit.length * numpy.cos(numpy.radians(slit.rotation)) / blue_arm.pixelscale \
                        / args.blue_binning[1]
    blue_ext = extract.Extraction(blue_arm.det,
                                  spatial_width=spatial_width,
                                  profile='uniform')

    # Perform the observation
    blue_obs = Observation(blue_arm.telescope,
                           sky_spectrum,
                           slit,
                           args.time,
                           blue_arm.det,
                           system_throughput=blue_thru,
                           atmospheric_throughput=atmospheric_throughput,
                           airmass=args.airmass,
                           onsky_source_distribution=onsky,
                           source_spectrum=blue_spec,
                           extraction=blue_ext,
                           snr_units=args.snr_units)

    # Construct the S/N spectrum
    blue_snr = blue_obs.snr(sky_sub=True, sky_err=args.sky_err)

    #-------------------------------------------------------------------
    # Red Arm
    red_arm = TMTWFOSRed(reflectivity=args.refl,
                         grating=args.red_grat,
                         cen_wave=args.red_wave,
                         grating_angle=args.red_angle)

    # Pixels per resolution element
    red_res_pix = red_arm.resolution_element(slit_width=effective_slit_width, units='pixels') \
                    / args.red_binning[0]

    # Get the wavelength range for each arm
    red_wave_lim = red_arm.wavelength_limits(slit_x,
                                             slit_y,
                                             add_grating_limits=True)

    # Setup dummy wavelength vectors to get something appropriate for sampling
    max_resolution = red_arm.resolution(red_wave_lim[1],
                                        x=slit_x,
                                        slit_width=effective_slit_width)

    # Set the wavelength vector to allow for a regular, logarithmic binning
    dw = 1 / red_res_pix / max_resolution / numpy.log(10)
    red_wave = get_wavelength_vector(red_wave_lim[0], red_wave_lim[1], dw)
    resolution = red_arm.resolution(red_wave,
                                    x=slit_x,
                                    slit_width=effective_slit_width)
    red_spec = observed_spectrum(obj_spectrum,
                                 red_wave,
                                 resolution,
                                 mag=args.mag,
                                 mag_band=args.mag_band,
                                 mag_system=args.mag_system,
                                 redshift=args.redshift,
                                 emline_db=emline_db)

    # Resample to linear to better match what's expected for the detector
    red_ang_per_pix = red_arm.resolution_element(
        wave=red_wave_lim, slit_width=effective_slit_width,
        units='angstrom') / red_res_pix
    red_wave = get_wavelength_vector(red_wave_lim[0],
                                     red_wave_lim[1],
                                     numpy.mean(red_ang_per_pix),
                                     linear=True)
    ree_spec = red_spec.resample(wave=red_wave, log=False)

    # Spectrograph arm efficiency (this doesn't include the telescope)
    red_arm_eff = red_arm.efficiency(red_spec.wave,
                                     x=slit_x,
                                     y=slit_y,
                                     same_type=False)

    # System efficiency combines the spectrograph and the telescope
    red_thru = efficiency.SystemThroughput(
        wave=red_spec.wave,
        spectrograph=red_arm_eff,
        telescope=red_arm.telescope.throughput)

    # Extraction: makes simple assumptions about the monochromatic
    # image and extracts the flux within the aperture, assuming the
    # flux from both the object and sky is uniformly distributed across
    # all detector pixels (incorrect!).

    # Extraction width in pixels
    spatial_width = slit.length * numpy.cos(numpy.radians(slit.rotation)) / red_arm.pixelscale \
                        / args.red_binning[1]
    red_ext = extract.Extraction(red_arm.det,
                                 spatial_width=spatial_width,
                                 profile='uniform')

    # Perform the observation
    red_obs = Observation(red_arm.telescope,
                          sky_spectrum,
                          slit,
                          args.time,
                          red_arm.det,
                          system_throughput=red_thru,
                          atmospheric_throughput=atmospheric_throughput,
                          airmass=args.airmass,
                          onsky_source_distribution=onsky,
                          source_spectrum=red_spec,
                          extraction=red_ext,
                          snr_units=args.snr_units)

    # Construct the S/N spectrum
    red_snr = red_obs.snr(sky_sub=True, sky_err=args.sky_err)

    # Set the wavelength vector
    dw = 1 / (5 if red_res_pix > 5 else
              red_res_pix) / max_resolution / numpy.log(10)
    red_wave = get_wavelength_vector(red_wave_lim[0], red_wave_lim[1], dw)
    resolution = red_arm.resolution(red_wave,
                                    x=slit_x,
                                    slit_width=effective_slit_width)
    red_spec = observed_spectrum(obj_spectrum,
                                 red_wave,
                                 resolution,
                                 mag=args.mag,
                                 mag_band=args.mag_band,
                                 mag_system=args.mag_system,
                                 redshift=args.redshift,
                                 emline_db=emline_db)
    #-------------------------------------------------------------------
    #-------------------------------------------------------------------

    snr_label = 'S/N per {0}'.format('R element' if args.snr_units ==
                                     'resolution' else args.snr_units)

    # Report
    g = efficiency.FilterResponse(band='g')
    r = efficiency.FilterResponse(band='r')
    #    iband = efficiency.FilterResponse(band='i')
    print('-' * 70)
    print('{0:^70}'.format('WFOS S/N Calculation (v0.1)'))
    print('-' * 70)
    print('Compute time: {0} seconds'.format(time.perf_counter() - t))
    print('Object g- and r-band AB magnitude: {0:.1f} {1:.1f}'.format(
        obj_spectrum.magnitude(band=g), obj_spectrum.magnitude(band=r)))
    print('Sky g- and r-band AB surface brightness: {0:.1f} {1:.1f}'.format(
        sky_spectrum.magnitude(band=g), sky_spectrum.magnitude(band=r)))
    print('Exposure time: {0:.1f} (s)'.format(args.time))
    if not args.uniform:
        print('Aperture Loss: {0:.1f}%'.format(
            (1 - red_obs.aperture_factor) * 100))
#    print('Extraction Loss: {0:.1f}%'.format((1-obs.extraction.spatial_efficiency)*100))
#    print('Median {0}: {1:.1f}'.format(snr_label, numpy.median(snr.flux)))
#    print('g-band weighted mean {0} {1:.1f}'.format(snr_label,
#                numpy.sum(g(snr.wave)*snr.flux)/numpy.sum(g(snr.wave))))
#    print('r-band weighted mean {0} {1:.1f}'.format(snr_label,
#                numpy.sum(r(snr.wave)*snr.flux)/numpy.sum(r(snr.wave))))
#    print('i-band weighted mean {0} {1:.1f}'.format(snr_label,
#                numpy.sum(iband(snr.wave)*snr.flux)/numpy.sum(iband(snr.wave))))

    if args.plot:
        w, h = pyplot.figaspect(1)
        fig = pyplot.figure(figsize=(1.5 * w, 1.5 * h))

        ax = fig.add_axes([0.1, 0.5, 0.8, 0.4])
        ax.set_xlim([obj_spectrum.wave[0], obj_spectrum.wave[-1]])
        ax.minorticks_on()
        ax.tick_params(which='major',
                       length=8,
                       direction='in',
                       top=True,
                       right=True)
        ax.tick_params(which='minor',
                       length=4,
                       direction='in',
                       top=True,
                       right=True)
        ax.grid(True, which='major', color='0.8', zorder=0, linestyle='-')
        ax.xaxis.set_major_formatter(ticker.NullFormatter())
        ax.set_yscale('log')

        ax = obj_spectrum.plot(ax=ax, label='Object', color='k', lw=1)
        ax = sky_spectrum.plot(ax=ax, label='Sky', color='0.5', lw=0.5)
        ax.legend()
        ax.text(-0.1,
                0.5,
                r'Flux [10$^{-17}$ erg/s/cm$^2$/${\rm \AA}$]',
                ha='center',
                va='center',
                transform=ax.transAxes,
                rotation='vertical')

        ax = fig.add_axes([0.1, 0.1, 0.8, 0.4])
        ax.set_xlim([obj_spectrum.wave[0], obj_spectrum.wave[-1]])
        ax.minorticks_on()
        ax.tick_params(which='major',
                       length=8,
                       direction='in',
                       top=True,
                       right=True)
        ax.tick_params(which='minor',
                       length=4,
                       direction='in',
                       top=True,
                       right=True)
        ax.grid(True, which='major', color='0.8', zorder=0, linestyle='-')

        ax = blue_snr.plot(ax=ax, color='C0', label='Blue Arm')
        ax = red_snr.plot(ax=ax, color='C3', label='Red Arm')

        ax.text(0.5,
                -0.1,
                r'Wavelength [${\rm \AA}$]',
                ha='center',
                va='center',
                transform=ax.transAxes)
        ax.text(-0.1,
                0.5,
                snr_label,
                ha='center',
                va='center',
                transform=ax.transAxes,
                rotation='vertical')

        pyplot.show()

    if args.ipython:
        embed()
Пример #6
0
def observed_spectrum(spec,
                      wave,
                      resolution,
                      mag=None,
                      mag_band='g',
                      mag_system='AB',
                      redshift=None,
                      emline_db=None):
    """
    Convert input spectrum to expected (noise-free) observation.

    Operations in order:
        - Redshifts spectrum, if redshift provided
        - Rescales magnitude, if mag provided
        - Matches the spectral resolution, if the input spectrum as a defined resolution vector
        - Matches the sampling to the input wavelength vector
        - Add emission lines, if provided
    """
    if redshift is not None:
        spec.redshift(redshift)
    if mag is not None:
        broadband = efficiency.FilterResponse(band=mag_band)
        # Check that the magnitude can be calculated
        indx = (broadband.wave < spec.wave[0]) & (broadband.eta > 0)
        if numpy.any(indx):
            raise ValueError(
                'Input spectrum (after applying any redshift) does not fully '
                'overlap selected broad-band filter.  Use a different band or '
                'different spectrum to rescale to the given magnitude.')
        spec.rescale_magnitude(mag, band=broadband, system=mag_system)

    _resolution = set_resolution(wave, resolution)

    # Force the spectrum to be regularly sampled on a logarithmic grid
    if not spec.regular:
        print('Imposing uniform logarithmic sampling')
        # TODO: I'm not sure that this has to be logarithmic.
        spec = spec.resample(log=True)

    # If the resolution is available match it to the resolution
    # expected for the instrument
    if spec.sres is not None:
        new_sres = interpolate.interp1d(wave,
                                        _resolution,
                                        fill_value=(_resolution[0],
                                                    _resolution[-1]),
                                        bounds_error=False)(spec.wave)
        indx = spec.sres < new_sres
        # TODO: set some tolerance (i.e., some fraction of the spectral range)
        if numpy.any(indx):
            warnings.warn(
                'Spectral resolution of input spectrum is lower than what will be '
                'provided by the instrument over {0:.1f}% of the spectral range.'
                .format(numpy.sum(indx) / indx.size))
        print(
            'Convolving to delivered spectral resolution (as best as possible).'
        )
        spec = spec.match_resolution(_resolution, wave=wave)
    else:
        spec.sres = interpolate.interp1d(wave,
                                         _resolution,
                                         fill_value=(_resolution[0],
                                                     _resolution[-1]),
                                         bounds_error=False)(spec.wave)

    # Down-sample to the provided wavelength vector
    print('Resampling to the provided wavelength sampling.')
    spec = spec.resample(wave=wave, log=True)

    if emline_db is None:
        return spec

    warnings.warn(
        'Adding emission lines will change the magnitude of the object.')
    return spectrum.EmissionLineSpectrum(spec.wave,
                                         emline_db['flux'],
                                         emline_db['restwave'],
                                         emline_db['fwhm'],
                                         units=emline_db['fwhmu'],
                                         redshift=redshift,
                                         resolution=spec.sres,
                                         log=True,
                                         continuum=spec.flux)
Пример #7
0
def main(args):

    t = time.perf_counter()

    # Constants:
    resolution = 3500.  # lambda/dlambda
    fiber_diameter = 0.8  # Arcsec
    rn = 2.  # Detector readnoise (e-)
    dark = 0.0  # Detector dark-current (e-/s)

    # Temporary numbers that assume a given spectrograph PSF and LSF.
    # Assume 3 pixels per spectral and spatial FWHM.
    spatial_fwhm = 3.0
    spectral_fwhm = 3.0

    mags = numpy.arange(args.mag[0], args.mag[1] + args.mag[2], args.mag[2])
    times = numpy.arange(args.time[0], args.time[1] + args.time[2],
                         args.time[2])

    # Get source spectrum in 1e-17 erg/s/cm^2/angstrom. Currently, the
    # source spectrum is assumed to be
    #   - normalized by the total integral of the source flux
    #   - independent of position within the source
    wave = get_wavelength_vector(args.wavelengths[0], args.wavelengths[1],
                                 args.wavelengths[2])
    spec = get_spectrum(wave, mags[0], resolution=resolution)

    # Get the source distribution.  If the source is uniform, onsky is None.
    onsky = None

    # Get the sky spectrum
    sky_spectrum = spectrum.MaunakeaSkySpectrum()

    # Overplot the source and sky spectrum
    #    ax = spec.plot()
    #    ax = sky_spectrum.plot(ax=ax, show=True)

    # Get the atmospheric throughput
    atmospheric_throughput = efficiency.AtmosphericThroughput(
        airmass=args.airmass)

    # Set the telescope. Defines the aperture area and throughput
    # (nominally 3 aluminum reflections for Keck)
    telescope = telescopes.KeckTelescope()

    # Define the observing aperture; fiber diameter is in arcseconds,
    # center is 0,0 to put the fiber on the target center. "resolution"
    # sets the resolution of the fiber rendering; it has nothing to do
    # with spatial or spectral resolution of the instrument
    fiber = aperture.FiberAperture(0, 0, fiber_diameter, resolution=100)

    # Get the spectrograph throughput (circa June 2018; TODO: needs to
    # be updated). Includes fibers + foreoptics + FRD + spectrograph +
    # detector QE (not sure about ADC). Because this is the total
    # throughput, define a generic efficiency object.
    thru_db = numpy.genfromtxt(
        os.path.join(os.environ['SYNOSPEC_DIR'], 'data/efficiency',
                     'fobos_throughput.db'))
    spectrograph_throughput = efficiency.Efficiency(thru_db[:, 1],
                                                    wave=thru_db[:, 0])

    # System efficiency combines the spectrograph and the telescope
    system_throughput = efficiency.SystemThroughput(
        wave=spec.wave,
        spectrograph=spectrograph_throughput,
        telescope=telescope.throughput)

    # Instantiate the detector; really just a container for the rn and
    # dark current for now. QE is included in fobos_throughput.db file,
    # so I set it to 1 here.
    det = detector.Detector(rn=rn, dark=dark, qe=1.0)

    # Extraction: makes simple assumptions about the detector PSF for
    # each fiber spectrum and mimics a "perfect" extraction, including
    # an assumption of no cross-talk between fibers. Ignore the
    # "spectral extraction".
    extraction = extract.Extraction(det,
                                    spatial_fwhm=spatial_fwhm,
                                    spatial_width=1.5 * spatial_fwhm,
                                    spectral_fwhm=spectral_fwhm,
                                    spectral_width=spectral_fwhm)

    snr_label = 'S/N per {0}'.format('R element' if args.snr_units ==
                                     'resolution' else args.snr_units)

    # SNR
    g = efficiency.FilterResponse()
    r = efficiency.FilterResponse(band='r')
    snr_g = numpy.empty((mags.size, times.size), dtype=float)
    snr_r = numpy.empty((mags.size, times.size), dtype=float)
    for i in range(mags.size):
        spec.rescale_magnitude(mags[i], band=g)
        for j in range(times.size):
            print('{0}/{1} ; {2}/{3}'.format(i + 1, mags.size, j + 1,
                                             times.size),
                  end='\r')
            # Perform the observation
            obs = Observation(telescope,
                              sky_spectrum,
                              fiber,
                              times[j],
                              det,
                              system_throughput=system_throughput,
                              atmospheric_throughput=atmospheric_throughput,
                              airmass=args.airmass,
                              onsky_source_distribution=onsky,
                              source_spectrum=spec,
                              extraction=extraction,
                              snr_units=args.snr_units)

            # Construct the S/N spectrum
            snr_spec = obs.snr(sky_sub=True)
            snr_g[i,
                  j] = numpy.sum(g(snr_spec.wave) * snr_spec.flux) / numpy.sum(
                      g(snr_spec.wave))
            snr_r[i,
                  j] = numpy.sum(r(snr_spec.wave) * snr_spec.flux) / numpy.sum(
                      r(snr_spec.wave))
    print('{0}/{1} ; {2}/{3}'.format(i + 1, mags.size, j + 1, times.size))

    extent = [
        args.time[0] - args.time[2] / 2, args.time[1] + args.time[2] / 2,
        args.mag[0] - args.mag[2] / 2, args.mag[1] + args.mag[2] / 2
    ]

    w, h = pyplot.figaspect(1)
    fig = pyplot.figure(figsize=(1.5 * w, 1.5 * h))
    ax = fig.add_axes([0.15, 0.2, 0.7, 0.7])
    img = ax.imshow(snr_g,
                    origin='lower',
                    interpolation='nearest',
                    extent=extent,
                    aspect='auto',
                    norm=colors.LogNorm(vmin=snr_g.min(), vmax=snr_g.max()))
    cax = fig.add_axes([0.86, 0.2, 0.02, 0.7])
    pyplot.colorbar(img, cax=cax)
    cax.text(4,
             0.5,
             snr_label,
             ha='center',
             va='center',
             transform=cax.transAxes,
             rotation='vertical')
    ax.text(0.5,
            -0.08,
            'Exposure Time [s]',
            ha='center',
            va='center',
            transform=ax.transAxes)
    ax.text(-0.12,
            0.5,
            r'Surface Brightness [AB mag/arcsec$^2$]',
            ha='center',
            va='center',
            transform=ax.transAxes,
            rotation='vertical')
    ax.text(0.5,
            1.03,
            r'$g$-band S/N',
            ha='center',
            va='center',
            transform=ax.transAxes,
            fontsize=12)
    pyplot.show()