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
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
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
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))))
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()
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)
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()