def photons_in_range(self, wmin=None, wmax=None, area=1 * u.cm**2, filter_curve=None): """ Return the number of photons between wave_min and wave_max or within a bandpass (filter) Parameters ---------- wmin : [Angstrom] wmax : [Angstrom] area : u.Quantity [cm2] filter_curve : str Name of a filter from - a generic filter name (see ``DEFAULT_FILTERS``) - a spanish-vo filter service reference (e.g. ``"Paranal/HAWKI.Ks"``) - a filter in the spextra database - the path to the file containing the filter (see ``Passband``) - a ``Passband`` or ``synphot.SpectralElement`` object Returns ------- counts : u.Quantity array """ if isinstance(area, u.Quantity): area = area.to(u.cm**2).value # if isinstance(wmin, u.Quantity): wmin = wmin.to(u.Angstrom).value if isinstance(wmax, u.Quantity): wmin = wmax.to(u.Angstrom).value if filter_curve is None: # this makes a bandpass out of wmin and wmax try: mid_point = 0.5 * (wmin + wmax) width = abs(wmax - wmin) filter_curve = SpectralElement(Box1D, amplitude=1, x_0=mid_point, width=width) except ValueError("Please specify wmin/wmax or a filter"): raise elif os.path.exists(filter_curve): filter_curve = Passband.from_file(filename=filter_curve) elif isinstance(filter_curve, (Passband, SpectralElement)): filter_curve = filter_curve else: filter_curve = Passband(filter_curve) obs = Observation(self, filter_curve) counts = obs.countrate(area=area * u.cm**2) return counts
def calculate_values(detector, filt, mjd, aper): # parameters can be removed from obsmode as needed obsmode = 'wfc3,{},{},mjd#{},aper#{}'.format(detector, filt, mjd, aper) bp = stsyn.band(obsmode) # STMag photflam = bp.unit_response(stsyn.conf.area) # inverse sensitivity in flam stmag = -21.1 - 2.5 * log10(photflam.value) # Pivot Wavelength and bandwidth photplam = bp.pivot() # pivot wavelength in angstroms bandwidth = bp.photbw() # bandwidth in angstroms # ABMag abmag = stmag - 5 * log10(photplam.value) + 18.6921 # Vegamag #for some reason stsyn.Vega doesn't load so we redefine it stsyn.Vega = SourceSpectrum.from_vega() obs = Observation( stsyn.Vega, bp, binset=bp.binset ) # synthetic observation of vega in bandpass using vega spectrum vegamag = -obs.effstim(flux_unit='obmag', area=stsyn.conf.area) return obsmode, photplam.value, bandwidth.value, photflam.value, stmag, abmag, vegamag.value
def get_phot(spec, bands): """ Compute the fluxes in the requested bands. Parameters ---------- spec : SpecData object the spectrum bands: list of strings bands requested Outputs ------- band fluxes : numpy array calculated band fluxes """ # create a SourceSpectrum object from the spectrum, excluding the bad regions spectrum = SourceSpectrum( Empirical1D, points=spec.waves.to(u.Angstrom)[spec.npts != 0], lookup_table=spec.fluxes[spec.npts != 0], ) # path for band response curves band_path = pkg_resources.resource_filename("measure_extinction", "data/Band_RespCurves/") # dictionary linking the bands to their response curves bandnames = { "J": "2MASSJ", "H": "2MASSH", "K": "2MASSKs", "IRAC1": "IRAC1", "IRAC2": "IRAC2", "WISE1": "WISE1", "WISE2": "WISE2", "L": "AAOL", "M": "AAOM", } # define the units of the output fluxes funit = u.erg / (u.s * u.cm * u.cm * u.Angstrom) # compute the flux in each band fluxes = np.zeros(len(bands)) for k, band in enumerate(bands): # create the bandpass (as a SpectralElement object) bp = SpectralElement.from_file( "%s%s.dat" % (band_path, bandnames[band])) # assumes wavelengths are in Angstrom!! # integrate the spectrum over the bandpass, only if the bandpass fully overlaps with the spectrum (this actually excludes WISE2) if bp.check_overlap(spectrum) == "full": obs = Observation(spectrum, bp) fluxes[k] = obs.effstim(funit).value else: fluxes[k] = np.nan return fluxes
def _observe_through_filter(bp, B, unit): if not synphot: raise AstropyWarning('synphot is required for bandpass filtering.') obs = Observation(B, bp) wave = obs.effective_wavelength() fluxd = obs.effstim(unit) return wave, fluxd
def filter_vega_zp(filterfile, filterzero): ''' ###################################################### # Input # # -------------------------------------------------- # # filterfile: file containing filter function # # filterzero: flux corresponding to mag=0 # # -------------------------------------------------- # # Output # # -------------------------------------------------- # # mag_0: magnitude of Vega in filter system. # ###################################################### ''' from synphot import SourceSpectrum, SpectralElement, Observation #load Vega spectrum spec = SourceSpectrum.from_vega() #Load ascii filter function if filterfile.split('/')[-1][:6] == 'Bessel': filt = SpectralElement.from_file(filterfile, wave_unit='nm') else: filt = SpectralElement.from_file(filterfile, wave_unit='AA') wave = filt.waveset #Synthetic observation obs = Observation(spec, filt) flux = obs.effstim(flux_unit='jy', waverange=(wave[0],wave[-1])) #Calibrate to zero point (zp) mag_0 = -2.512*np.log10(flux/filterzero) return mag_0
def calcMagFromSpec(bp, specPath, redden=None): sp = SourceSpectrum.from_file(specPath) if redden: sp = sp * redden obs = Observation(sp, bp) counts = obs.countrate(1.0) return -2.5 * np.log10(counts.value)
def get_phot(waves, exts, bands): """ Compute the extinction in the requested bands Parameters ---------- waves : numpy.ndarray The wavelengths exts : numpy.ndarray The extinction values at wavelengths "waves" bands: list of strings Bands requested Outputs ------- band extinctions : numpy array Calculated band extinctions """ # create a SourceSpectrum object from the extinction curve spectrum = SourceSpectrum( Empirical1D, points=waves * 1e4, lookup_table=exts, ) # path for band response curves band_path = ( "/Users/mdecleir/measure_extinction/measure_extinction/data/Band_RespCurves/" ) # dictionary linking the bands to their response curves bandnames = { "J": "2MASSJ", "H": "2MASSH", "K": "2MASSKs", "IRAC1": "IRAC1", "IRAC2": "IRAC2", "WISE1": "WISE1", "WISE2": "WISE2", "L": "AAOL", "M": "AAOM", } # compute the extinction value in each band band_ext = np.zeros(len(bands)) for k, band in enumerate(bands): # create the bandpass (as a SpectralElement object) bp = SpectralElement.from_file( "%s%s.dat" % (band_path, bandnames[band])) # assumes wavelengths are in Angstrom!! # integrate the extinction curve over the bandpass, only if the bandpass fully overlaps with the extinction curve (this actually excludes WISE2) if bp.check_overlap(spectrum) == "full": obs = Observation(spectrum, bp) band_ext[k] = obs.effstim().value else: band_ext[k] = np.nan return band_ext
def kvega_to_f335abmag(self, kmag): '''Convert K band vegamag to F335M ABmag''' kmag = kmag * units.VEGAMAG new_g2v = self.g2v.normalize(kmag, band=self.kband, area=self.telescope_area, vegaspec=self.vega) obs = Observation(new_g2v, self.bp, binset=self.wave_bins) abmag = obs.effstim('ABmag', area=self.telescope_area) return abmag
def setup_class(self): """Subclass needs to define ``obsmode`` and ``spectrum`` class variables for this to work. """ if not HAS_PYSYNPHOT: raise ImportError( 'ASTROLIB PYSYNPHOT must be installed to run these tests') # Make sure both software use the same graph and component tables. conf.graphtable = self.tables['graphtable'] conf.comptable = self.tables['comptable'] conf.thermtable = self.tables['thermtable'] S.setref(graphtable=self.tables['graphtable'], comptable=self.tables['comptable'], thermtable=self.tables['thermtable']) # Construct spectra for both software. self.sp = parse_spec(self.spectrum) self.bp = band(self.obsmode) # Astropy version has no prior knowledge of instrument-specific # binset, so it has to be set explicitly. if hasattr(self.bp, 'binset'): self.obs = Observation(self.sp, self.bp, force=self.force, binset=self.bp.binset) else: self.obs = Observation(self.sp, self.bp, force=self.force) # Astropy version does not assume a default waveset # (you either have it or you don't). If there is no # waveset, no point comparing obs waveset against ASTROLIB. if self.sp.waveset is None or self.bp.waveset is None: self._has_obswave = False else: self._has_obswave = True self.spref = old_parse_spec(self.spectrum) self.bpref = S.ObsBandpass(self.obsmode) self.obsref = S.Observation(self.spref, self.bpref, force=self.force) # Ensure we are comparing in the same units self.bpref.convert(self.bp._internal_wave_unit.name) self.spref.convert(self.sp._internal_wave_unit.name) self.spref.convert(self.sp._internal_flux_unit.name) self.obsref.convert(self.obs._internal_wave_unit.name) self.obsref.convert(self.obs._internal_flux_unit.name)
def f335abmag_to_kvega(self, abmag): '''Convert F335M ABmag to K band vegamag''' abmag = abmag * u.ABmag new_g2v = self.g2v.normalize(abmag, band=self.bp, area=self.telescope_area, vegaspec=self.vega) obs = Observation(new_g2v, self.kband, binset=self.wave_bins) kmag = obs.effstim('Vegamag', area=self.telescope_area, vegaspec=self.vega) return kmag
def setup_class(self): """Subclass needs to define ``obsmode`` and ``spectrum`` class variables for this to work. """ if not HAS_PYSYNPHOT: raise ImportError( 'ASTROLIB PYSYNPHOT must be installed to run these tests') # Make sure both software use the same graph and component tables. conf.graphtable = self.tables['graphtable'] conf.comptable = self.tables['comptable'] conf.thermtable = self.tables['thermtable'] S.setref(graphtable=self.tables['graphtable'], comptable=self.tables['comptable'], thermtable=self.tables['thermtable']) # Construct spectra for both software. self.sp = parse_spec(self.spectrum) self.bp = band(self.obsmode) # Astropy version has no prior knowledge of instrument-specific # binset, so it has to be set explicitly. if hasattr(self.bp, 'binset'): self.obs = Observation(self.sp, self.bp, force=self.force, binset=self.bp.binset) else: self.obs = Observation(self.sp, self.bp, force=self.force) # Astropy version does not assume a default waveset # (you either have it or you don't). If there is no # waveset, no point comparing obs waveset against ASTROLIB. if self.sp.waveset is None or self.bp.waveset is None: self._has_obswave = False else: self._has_obswave = True self.spref = old_parse_spec(self.spectrum) self.bpref = S.ObsBandpass(self.obsmode) self.obsref = S.Observation(self.spref, self.bpref, force=self.force) # Ensure we are comparing in the same units self.bpref.convert(self.bp._internal_wave_unit.name) self.spref.convert(self.sp._internal_wave_unit.name) self.spref.convert(self.sp._internal_flux_unit.name) self.obsref.convert(self.obs._internal_wave_unit.name) self.obsref.convert(self.obs._internal_flux_unit.name)
def rebin_spectra(self, new_waves): """ Rebin a synphot spectra to a new wavelength grid conserving flux. Grid does not need to be linear and can be at higher or lower resolution Parameters ---------- new_waves: an array of the output wavelenghts in Angstroms but other units can be specified Returns ------- a new Spextrum instance """ if isinstance(new_waves, u.Quantity): new_waves = new_waves.to(u.AA).value waves = self.waveset.value # else assumed to be angstroms f = np.ones(len(waves)) filt = SpectralElement(Empirical1D, points=waves, lookup_table=f) obs = Observation(self, filt, binset=new_waves, force='taper') newflux = obs.binflux rebin_spec = Empirical1D(points=new_waves, lookup_table=newflux, meta=self.meta) sp = Spextrum(modelclass=rebin_spec) sp = self._restore_attr(sp) sp.meta.update({"rebinned_spectra": True}) return sp
def get_magnitude(self, filter_curve=None, system_name="AB"): """ Obtain the magnitude in filter for a user specified photometric system Parameters ---------- filter_curve : str Name of a filter from - a generic filter name (see ``DEFAULT_FILTERS``) - a spanish-vo filter service reference (e.g. ``"Paranal/HAWKI.Ks"``) - a filter in the spextra database - the path to the file containing the filter (see ``Passband``) - a ``Passband`` or ``synphot.SpectralElement`` object system_name: str The photometric system Vega, AB or ST Returns ------- spectrum : a Spextrum Input spectrum scaled to the given amplitude in the given filter """ if os.path.exists(filter_curve): filter_curve = Passband.from_file(filename=filter_curve) elif isinstance(filter_curve, (Passband, SpectralElement)): filter_curve = filter_curve else: filter_curve = Passband(filter_curve) if system_name.lower() in ["vega"]: unit = u.mag elif system_name.lower() in ["st", "hst"]: unit = u.STmag else: unit = u.ABmag ref_spec = self.flat_spectrum(amplitude=0 * unit) ref_flux = Observation(ref_spec, filter_curve).effstim(flux_unit=units.PHOTLAM) real_flux = Observation(self, filter_curve).effstim(flux_unit=units.PHOTLAM) mag = -2.5 * np.log10(real_flux.value / ref_flux.value) return mag * unit
def zero_mag_flux(filter_name, photometric_system, return_filter=False): """ Returns the zero magnitude photon flux for a filter Acceptable filter names are those given in ``scopesim.effects.ter_curves_utils.FILTER_DEFAULTS`` or a string with an appropriate name for a filter in the Spanish-VO filter-service. Such strings must use the naming convention: observatory/instrument.filter. E.g: ``paranal/HAWKI.Ks``, or ``Gemini/GMOS-N.CaT``. Parameters ---------- filter_name : str Name of the filter - see above photometric_system : str ["vega", "AB", "ST"] Name of the photometric system return_filter : bool, optional If True, also returns the filter curve object Returns ------- flux : float [PHOTLAM] filt : ``synphot.SpectralElement`` If ``return_filter`` is True """ filt = get_filter(filter_name) spec = get_zero_mag_spectrum(photometric_system) obs = Observation(spec, filt) flux = obs.effstim(flux_unit=PHOTLAM) if return_filter: return flux, filt else: return flux
def synphot_calcs(self, bpfile): """ Calculate zeropoint for a given bandpass in several photometric systems Arguments: ---------- bpfile -- Text file containing the throuput table for a single bandpass Returns: -------- Bandpass zeropoint value in Vega mags, AB mags, ST mags, photflam, photfnu, and megajansky. Also returns pivot wavelength """ # Define wavelength list to use #wave_bins = np.arange(0.5, 5, 0.1) * u.micron # Use refactored synphot to calculate zeropoints orig_bp = SpectralElement.from_file(bpfile) # Now reduce the PCE curve by a factor equal to # the gain, in order to get the output to translate # from ADU/sec rather than e/sec bp = orig_bp / self.gain #bp.model.lookup_table = bp.model.lookup_table / self.gain photflam = bp.unit_response(self.telescope_area) photplam = bp.pivot() st_zpt = -2.5 * np.log10(photflam.value) - 21.1 ab_zpt = (-2.5 * np.log10(photflam.value) - 21.1 - 5 * np.log10(photplam.value) + 18.6921) #mjy_zpt = units.convert_flux(photplam,photflam, 'MJy') mjy_zpt = photflam.to(u.MJy, u.spectral_density(photplam)) obs = Observation(self.vega, bp, binset=wave_bins) vega_zpt = -obs.effstim(flux_unit='obmag', area=self.telescope_area) photfnu = units.convert_flux(photplam, photflam, units.FNU) return (vega_zpt.value, ab_zpt, st_zpt, photflam.value, photfnu.value, mjy_zpt.value, photplam.value)
def get_flux(self, wmin=None, wmax=None, filter_curve=None, flux_unit=units.FLAM): """ Return the flux within a passband Parameters ---------- wmin : float, u.Quantity minimum wavelength wmax : float, u.Quantity maximum wavelength filter_curve : str Name of a filter from - a generic filter name (see ``DEFAULT_FILTERS``) - a spanish-vo filter service reference (e.g. ``"Paranal/HAWKI.Ks"``) - a filter in the spextra database - the path to the file containing the filter (see ``Passband``) - a ``Passband`` or ``synphot.SpectralElement`` object flux_unit: synphot.units, u.Quantity Returns ------- """ if isinstance(wmin, u.Quantity): wmin = wmin.to(u.Angstrom).value if isinstance(wmax, u.Quantity): wmin = wmax.to(u.Angstrom).value if filter_curve is None: # this makes a bandpass out of wmin and wmax try: filter_curve = Passband.square(wmin=wmin, wmax=wmax) except ValueError("Please specify wmin/wmax or a filter"): raise elif os.path.exists(filter_curve): filter_curve = Passband.from_file(filename=filter_curve) elif isinstance(filter_curve, (Passband, SpectralElement)): filter_curve = filter_curve else: filter_curve = Passband(filter_curve) flux = Observation(self, filter_curve).effstim(flux_unit=flux_unit) return flux
def getcountsflux(self, bandpass): """ Calculate the total flux through a bandpass Arguments: ---------- bandpass -- synphot SpectralElement object that contains the bandpass to use Returns: A synphot Observation object that combines the bandpass with a Vega spectrum """ obs = Observation(self.vega, bandpass) return obs
def get_limmag(source_spectrum, *, snr, exptime, coord, time, night, bandpass=None): """Get the limiting magnitude for a given SNR. Parameters ---------- source_spectrum : synphot.SourceSpectrum The spectrum of the source. snr : float The desired SNR. exptime : astropy.units.Quantity The exposure time coord : astropy.coordinates.SkyCoord The coordinates of the source, for calculating zodiacal light time : astropy.time.Time The time of the observation, for calculating zodiacal light night : bool Whether the observation occurs on the day or night side of the Earth, for estimating airglow bandpass : synphot.SpecralElement Bandpass. Default: Dorado current baseline estimate. Returns ------- astropy.units.Quantity The AB magnitude of the source """ if bandpass is None: bandpass = bandpasses.NUV_D mag0 = Observation(source_spectrum, bandpass).effstim( u.ABmag, area=constants.AREA) result = _amp_for_signal_to_noise_oir_ccd( snr, exptime, constants.APERTURE_CORRECTION * _get_count_rate( source_spectrum, bandpass), _get_background_count_rate(bandpass, coord, time, night), constants.DARK_NOISE, constants.READ_NOISE, constants.NPIX ).to_value(u.dimensionless_unscaled) return -2.5 * np.log10(result) * u.mag + mag0
def filter_mag(filterfile, filterzero, syn_spec, syn_err=None, wrange=None): ''' ###################################################### # Input # # -------------------------------------------------- # # filterfile: file containing filter function # # filterzero: flux [Jy] corresponding to mag=0 # # syn_spec: synphot spectrum object of source # # wrange: (start,end) observed wavelength range # # -------------------------------------------------- # # Output # # -------------------------------------------------- # # mag: magnitude of source in filter system. # ###################################################### ''' from synphot import SpectralElement, Observation, Empirical1D, SourceSpectrum import astropy.units as u #Load ascii filter function if filterfile.split('/')[-1][:6] == 'Bessel': filt = SpectralElement.from_file(filterfile, wave_unit='nm') else: filt = SpectralElement.from_file(filterfile, wave_unit='AA') if wrange is None: fwave = filt.waveset swave = syn_spec.waveset wrange = (max(fwave[0],swave[0]),min(fwave[-1],swave[-1])) waves = np.linspace(wrange[0], wrange[-1], 10000) #Synthetic observation obs = Observation(syn_spec, filt, force='extrap') flux = obs.effstim(flux_unit='jy', waverange=wrange).value #flux = obs.effstim(flux_unit='jy', wavelengths=waves).value #Calibrate magnitude with zero point mag = -2.512*np.log10(flux/filterzero) #Synthetic observation of error spectrum if syn_err is not None: #square filter and error spectrum filt2 = SpectralElement(Empirical1D, points=fwave, lookup_table=np.square(filt(fwave))) pseudo_flux = np.square(syn_err(swave,flux_unit='jy')).value syn_err2 = SourceSpectrum(Empirical1D, points=swave, lookup_table=pseudo_flux) #sum errors in quadrature obs = Observation(syn_err2, filt2, force='extrap') #flux_err = np.sqrt(obs.effstim(waverange=wrange).value) flux_err = np.sqrt(obs.effstim(wavelengths=waves).value) #convert to magnitude error mag_err = (2.5/np.log(10))*(flux_err/flux) return mag, mag_err else: return mag
def filter_flux(filterfile, syn_spec, syn_err=None, wrange=None): ''' ###################################################### # Input # # -------------------------------------------------- # # filterfile: file containing filter function # # syn_spec: synphot spectrum object of source # # wrange: (start,end) observed wavelength range # # -------------------------------------------------- # # Output # # -------------------------------------------------- # # flux: flux of source in filter system. # ###################################################### ''' from synphot import SpectralElement, Observation, Empirical1D, SourceSpectrum import astropy.units as u #Load ascii filter function if filterfile.split('/')[-1][:6] == 'Bessel': filt = SpectralElement.from_file(filterfile, wave_unit='nm') else: filt = SpectralElement.from_file(filterfile, wave_unit='AA') if wrange is None: fwave = filt.waveset swave = syn_spec.waveset wrange = (max(fwave[0],swave[0]),min(fwave[-1],swave[-1])) wlengths = fwave[np.logical_and(fwave>wrange[0], fwave<wrange[1])] else: fwave = filt.waveset wlengths = fwave[np.logical_and(fwave>wrange[0]*u.AA, fwave<wrange[1]*u.AA)] #Synthetic observation obs = Observation(syn_spec, filt, force='extrap') flux = obs.effstim(flux_unit='jy', waverange=wrange).value #flux = obs.effstim(flux_unit='jy', wavelengths=wlengths).value #Synthetic observation of error spectrum if syn_err is not None: #square filter and error spectrum filt2 = SpectralElement(Empirical1D, points=fwave, lookup_table=np.square(filt(fwave))) pseudo_flux = np.square(syn_err(swave,flux_unit='jy')).value syn_err2 = SourceSpectrum(Empirical1D, points=swave, lookup_table=pseudo_flux) #sum errors in quadrature obs = Observation(syn_err2, filt2, force='extrap') flux_err = np.sqrt(obs.effstim(waverange=wrange).value) return flux, flux_err else: return flux
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])
class CommCase(object): """Base class for commissioning tests.""" obsmode = None # Observation mode string spectrum = None # SYNPHOT-like string to construct spectrum force = None # Default tables are the latest available as of 2016-07-25. tables = { 'graphtable': os.path.join('mtab$OLD_FILES', '07r1502mm_tmg.fits'), 'comptable': os.path.join('mtab$OLD_FILES', '07r1502nm_tmc.fits'), 'thermtable': 'mtab$tae17277m_tmt.fits'} def setup_class(self): """Subclass needs to define ``obsmode`` and ``spectrum`` class variables for this to work. """ if not HAS_PYSYNPHOT: raise ImportError( 'ASTROLIB PYSYNPHOT must be installed to run these tests') # Make sure both software use the same graph and component tables. conf.graphtable = self.tables['graphtable'] conf.comptable = self.tables['comptable'] conf.thermtable = self.tables['thermtable'] S.setref(graphtable=self.tables['graphtable'], comptable=self.tables['comptable'], thermtable=self.tables['thermtable']) # Construct spectra for both software. self.sp = parse_spec(self.spectrum) self.bp = band(self.obsmode) # Astropy version has no prior knowledge of instrument-specific # binset, so it has to be set explicitly. if hasattr(self.bp, 'binset'): self.obs = Observation(self.sp, self.bp, force=self.force, binset=self.bp.binset) else: self.obs = Observation(self.sp, self.bp, force=self.force) # Astropy version does not assume a default waveset # (you either have it or you don't). If there is no # waveset, no point comparing obs waveset against ASTROLIB. if self.sp.waveset is None or self.bp.waveset is None: self._has_obswave = False else: self._has_obswave = True self.spref = old_parse_spec(self.spectrum) self.bpref = S.ObsBandpass(self.obsmode) self.obsref = S.Observation(self.spref, self.bpref, force=self.force) # Ensure we are comparing in the same units self.bpref.convert(self.bp._internal_wave_unit.name) self.spref.convert(self.sp._internal_wave_unit.name) self.spref.convert(self.sp._internal_flux_unit.name) self.obsref.convert(self.obs._internal_wave_unit.name) self.obsref.convert(self.obs._internal_flux_unit.name) @staticmethod def _get_new_wave(sp): """Astropy version does not assume a default waveset (you either have it or you don't). This is a convenience method to duck-type ASTROLIB waveset behavior. """ wave = sp.waveset if wave is None: wave = conf.waveset_array else: wave = wave.value return wave def _assert_allclose(self, actual, desired, rtol=1e-07, atol=sys.float_info.min): """``assert_allclose`` only report percentage but we also want to know some extra info conveniently.""" if isinstance(actual, Iterable): ntot = len(actual) else: ntot = 1 n = np.count_nonzero( abs(actual - desired) > atol + rtol * abs(desired)) msg = 'obsmode: {0}\nspectrum: {1}\n(mismatch {2}/{3})'.format( self.obsmode, self.spectrum, n, ntot) assert_allclose(actual, desired, rtol=rtol, atol=atol, err_msg=msg) # TODO: Confirm whether non-default atol is acceptable. # Have to use this value to avoid AssertionError for very # small non-zero flux values like 1.8e-26 to 2e-311. def _compare_nonzero(self, new, old, thresh=0.01, atol=1e-29): """Compare normally when results from both are non-zero.""" i = (new != 0) & (old != 0) # Make sure non-zero atol is not too high, otherwise just let it fail. if atol > (thresh * min(new.max(), old.max())): atol = sys.float_info.min self._assert_allclose(new[i], old[i], rtol=thresh, atol=atol) def _compare_zero(self, new, old, thresh=0.01): """Special handling for comparison when one of the results is zero. This is because ``rtol`` will not work.""" i = ((new == 0) | (old == 0)) & (new != old) try: self._assert_allclose(new[i], old[i], rtol=thresh) except AssertionError as e: pytest.xfail(str(e)) # TODO: Will revisit later def test_band_wave(self, thresh=0.01): """Test bandpass waveset.""" wave = self._get_new_wave(self.bp) self._assert_allclose(wave, self.bpref.wave, rtol=thresh) def test_spec_wave(self, thresh=0.01): """Test source spectrum waveset.""" wave = self._get_new_wave(self.sp) # TODO: Failure due to different wavesets for blackbody; Ignore? try: self._assert_allclose(wave, self.spref.wave, rtol=thresh) except (AssertionError, ValueError): self._has_obswave = False # Skip obs waveset tests if 'bb(' in self.spectrum: pytest.xfail('Blackbody waveset implementations are different') elif 'unit(' in self.spectrum: pytest.xfail('Flat does not use default waveset anymore') else: raise def test_obs_wave(self, thresh=0.01): """Test observation waveset.""" if not self._has_obswave: # Nothing to test return # Native wave = self.obs.waveset.value # TODO: Failure due to different wavesets for blackbody; Ignore? try: self._assert_allclose(wave, self.obsref.wave, rtol=thresh) except (AssertionError, ValueError): if 'bb(' in self.spectrum: pytest.xfail('Blackbody waveset implementations are different') elif 'unit(' in self.spectrum: self._has_obswave = False # Skip binned flux test pytest.xfail('Flat does not use default waveset anymore') else: raise # Binned binset = self.obs.binset.value self._assert_allclose(binset, self.obsref.binwave, rtol=thresh) @pytest.mark.parametrize('thrutype', ['zero', 'nonzero']) def test_band_thru(self, thrutype, thresh=0.01): """Test bandpass throughput, which is always between 0 and 1.""" wave = self.bpref.wave thru = self.bp(wave).value if thrutype == 'zero': self._compare_zero(thru, self.bpref.throughput, thresh=thresh) else: # nonzero self._compare_nonzero(thru, self.bpref.throughput, thresh=thresh) @pytest.mark.parametrize('fluxtype', ['zero', 'nonzero']) def test_spec_flux(self, fluxtype, thresh=0.01): """Test flux for source spectrum in PHOTLAM.""" wave = self.spref.wave flux = self.sp(wave).value if fluxtype == 'zero': self._compare_zero(flux, self.spref.flux, thresh=thresh) else: # nonzero self._compare_nonzero(flux, self.spref.flux, thresh=thresh) @pytest.mark.parametrize('fluxtype', ['zero', 'nonzero']) def test_obs_flux(self, fluxtype, thresh=0.01): """Test flux for observation in PHOTLAM.""" wave = self.obsref.wave flux = self.obs(wave).value # Native if fluxtype == 'zero': self._compare_zero(flux, self.obsref.flux, thresh=thresh) else: # nonzero self._compare_nonzero(flux, self.obsref.flux, thresh=thresh) if not self._has_obswave: # Do not compare binned flux return # Binned (cannot be resampled) binflux = self.obs.binflux.value if fluxtype == 'zero': self._compare_zero(binflux, self.obsref.binflux, thresh=thresh) else: # nonzero try: self._compare_nonzero(binflux, self.obsref.binflux, thresh=thresh) except AssertionError as e: if 'unit(' in self.spectrum: pytest.xfail('Flat does not use default waveset anymore:\n' '{0}'.format(str(e))) else: raise def test_countrate(self, thresh=0.01): """Test observation countrate calculations.""" ans = self.obsref.countrate() # Astropy version does not assume a default area. val = self.obs.countrate(conf.area).value self._assert_allclose(val, ans, rtol=thresh) def test_efflam(self, thresh=0.01): """Test observation effective wavelength.""" ans = self.obsref.efflam() val = self.obs.effective_wavelength().value self._assert_allclose(val, ans, rtol=thresh) def teardown_class(self): """Reset config for both software.""" for cfgname in self.tables: conf.reset(cfgname) S.setref()
class CommCase(object): """Base class for commissioning tests.""" obsmode = None # Observation mode string spectrum = None # SYNPHOT-like string to construct spectrum force = None # Default tables are the latest available as of 2016-07-25. tables = { 'graphtable': os.path.join('mtab$OLD_FILES', '07r1502mm_tmg.fits'), 'comptable': os.path.join('mtab$OLD_FILES', '07r1502nm_tmc.fits'), 'thermtable': 'mtab$tae17277m_tmt.fits' } def setup_class(self): """Subclass needs to define ``obsmode`` and ``spectrum`` class variables for this to work. """ if not HAS_PYSYNPHOT: raise ImportError( 'ASTROLIB PYSYNPHOT must be installed to run these tests') # Make sure both software use the same graph and component tables. conf.graphtable = self.tables['graphtable'] conf.comptable = self.tables['comptable'] conf.thermtable = self.tables['thermtable'] S.setref(graphtable=self.tables['graphtable'], comptable=self.tables['comptable'], thermtable=self.tables['thermtable']) # Construct spectra for both software. self.sp = parse_spec(self.spectrum) self.bp = band(self.obsmode) # Astropy version has no prior knowledge of instrument-specific # binset, so it has to be set explicitly. if hasattr(self.bp, 'binset'): self.obs = Observation(self.sp, self.bp, force=self.force, binset=self.bp.binset) else: self.obs = Observation(self.sp, self.bp, force=self.force) # Astropy version does not assume a default waveset # (you either have it or you don't). If there is no # waveset, no point comparing obs waveset against ASTROLIB. if self.sp.waveset is None or self.bp.waveset is None: self._has_obswave = False else: self._has_obswave = True self.spref = old_parse_spec(self.spectrum) self.bpref = S.ObsBandpass(self.obsmode) self.obsref = S.Observation(self.spref, self.bpref, force=self.force) # Ensure we are comparing in the same units self.bpref.convert(self.bp._internal_wave_unit.name) self.spref.convert(self.sp._internal_wave_unit.name) self.spref.convert(self.sp._internal_flux_unit.name) self.obsref.convert(self.obs._internal_wave_unit.name) self.obsref.convert(self.obs._internal_flux_unit.name) @staticmethod def _get_new_wave(sp): """Astropy version does not assume a default waveset (you either have it or you don't). This is a convenience method to duck-type ASTROLIB waveset behavior. """ wave = sp.waveset if wave is None: wave = conf.waveset_array else: wave = wave.value return wave def _assert_allclose(self, actual, desired, rtol=1e-07, atol=sys.float_info.min): """``assert_allclose`` only report percentage but we also want to know some extra info conveniently.""" if isinstance(actual, Iterable): ntot = len(actual) else: ntot = 1 n = np.count_nonzero( abs(actual - desired) > atol + rtol * abs(desired)) msg = 'obsmode: {0}\nspectrum: {1}\n(mismatch {2}/{3})'.format( self.obsmode, self.spectrum, n, ntot) assert_allclose(actual, desired, rtol=rtol, atol=atol, err_msg=msg) # TODO: Confirm whether non-default atol is acceptable. # Have to use this value to avoid AssertionError for very # small non-zero flux values like 1.8e-26 to 2e-311. def _compare_nonzero(self, new, old, thresh=0.01, atol=1e-29): """Compare normally when results from both are non-zero.""" i = (new != 0) & (old != 0) # Make sure non-zero atol is not too high, otherwise just let it fail. if atol > (thresh * min(new.max(), old.max())): atol = sys.float_info.min self._assert_allclose(new[i], old[i], rtol=thresh, atol=atol) def _compare_zero(self, new, old, thresh=0.01): """Special handling for comparison when one of the results is zero. This is because ``rtol`` will not work.""" i = ((new == 0) | (old == 0)) & (new != old) try: self._assert_allclose(new[i], old[i], rtol=thresh) except AssertionError as e: pytest.xfail(str(e)) # TODO: Will revisit later def test_band_wave(self, thresh=0.01): """Test bandpass waveset.""" wave = self._get_new_wave(self.bp) self._assert_allclose(wave, self.bpref.wave, rtol=thresh) def test_spec_wave(self, thresh=0.01): """Test source spectrum waveset.""" wave = self._get_new_wave(self.sp) # TODO: Failure due to different wavesets for blackbody; Ignore? try: self._assert_allclose(wave, self.spref.wave, rtol=thresh) except (AssertionError, ValueError) as e: self._has_obswave = False # Skip obs waveset tests if 'bb(' in self.spectrum: pytest.xfail('Blackbody waveset implementations are different') elif 'unit(' in self.spectrum: pytest.xfail('Flat does not use default waveset anymore') else: raise def test_obs_wave(self, thresh=0.01): """Test observation waveset.""" if not self._has_obswave: # Nothing to test return # Native wave = self.obs.waveset.value # TODO: Failure due to different wavesets for blackbody; Ignore? try: self._assert_allclose(wave, self.obsref.wave, rtol=thresh) except (AssertionError, ValueError) as e: if 'bb(' in self.spectrum: pytest.xfail('Blackbody waveset implementations are different') elif 'unit(' in self.spectrum: self._has_obswave = False # Skip binned flux test pytest.xfail('Flat does not use default waveset anymore') else: raise # Binned binset = self.obs.binset.value self._assert_allclose(binset, self.obsref.binwave, rtol=thresh) @pytest.mark.parametrize('thrutype', ['zero', 'nonzero']) def test_band_thru(self, thrutype, thresh=0.01): """Test bandpass throughput, which is always between 0 and 1.""" wave = self.bpref.wave thru = self.bp(wave).value if thrutype == 'zero': self._compare_zero(thru, self.bpref.throughput, thresh=thresh) else: # nonzero self._compare_nonzero(thru, self.bpref.throughput, thresh=thresh) @pytest.mark.parametrize('fluxtype', ['zero', 'nonzero']) def test_spec_flux(self, fluxtype, thresh=0.01): """Test flux for source spectrum in PHOTLAM.""" wave = self.spref.wave flux = self.sp(wave).value if fluxtype == 'zero': self._compare_zero(flux, self.spref.flux, thresh=thresh) else: # nonzero self._compare_nonzero(flux, self.spref.flux, thresh=thresh) @pytest.mark.parametrize('fluxtype', ['zero', 'nonzero']) def test_obs_flux(self, fluxtype, thresh=0.01): """Test flux for observation in PHOTLAM.""" wave = self.obsref.wave flux = self.obs(wave).value # Native if fluxtype == 'zero': self._compare_zero(flux, self.obsref.flux, thresh=thresh) else: # nonzero self._compare_nonzero(flux, self.obsref.flux, thresh=thresh) if not self._has_obswave: # Do not compare binned flux return # Binned (cannot be resampled) binflux = self.obs.binflux.value if fluxtype == 'zero': self._compare_zero(binflux, self.obsref.binflux, thresh=thresh) else: # nonzero try: self._compare_nonzero(binflux, self.obsref.binflux, thresh=thresh) except AssertionError as e: if 'unit(' in self.spectrum: pytest.xfail('Flat does not use default waveset anymore:\n' '{0}'.format(str(e))) else: raise def test_countrate(self, thresh=0.01): """Test observation countrate calculations.""" ans = self.obsref.countrate() # Astropy version does not assume a default area. val = self.obs.countrate(conf.area).value self._assert_allclose(val, ans, rtol=thresh) def test_efflam(self, thresh=0.01): """Test observation effective wavelength.""" ans = self.obsref.efflam() val = self.obs.effective_wavelength().value self._assert_allclose(val, ans, rtol=thresh) def teardown_class(self): """Reset config for both software.""" for cfgname in self.tables: conf.reset(cfgname) S.setref()
def calcMag(bp, temp): bb = SourceSpectrum(BlackBodyNorm1D, temperature=temp) obs = Observation(bb, bp) counts = obs.countrate(1.0) return -2.5 * np.log10(counts.value)
def test_spectra_rescaling(): """Test the functionality for rescaling input spectra to a given magnitude in a given filter """ # JWST primary mirror area in cm^2. Needed for countrate check # at the end primary_area = 25.326 * (u.m * u.m) # Create spectrum: one source to be normalized # and the other should not be waves = np.arange(0.4, 5.6, 0.01) flux = np.repeat(1e-16, len(waves)) flux2 = np.repeat(4.24242424242e-18, len(waves)) # arbitrary value spectra = { 1: { "wavelengths": waves * u.micron, "fluxes": flux * u.pct }, 2: { "wavelengths": waves * u.micron, "fluxes": flux2 * units.FLAM } } # Create source catalog containing scaling info catalog = Table() catalog['index'] = [1, 2] catalog['nircam_f322w2_magnitude'] = [18.] * 2 catalog['niriss_f090w_magnitude'] = [18.] * 2 #catalog['fgs_magnitude'] = [18.] * 2 # Instrument info instrument = ['nircam', 'niriss'] # , 'fgs'] filter_name = ['F322W2', 'F090W'] # , 'N/A'] module = ['B', 'N'] # , 'F'] detector = ['NRCA1', 'NIS'] # , 'GUIDER1'] # Magnitude systems of renormalization magnitudes mag_sys = ['vegamag', 'abmag', 'stmag'] # Loop over options and test each for inst, filt, mod, det in zip(instrument, filter_name, module, detector): # Extract the appropriate column from the catalog information magcol = [col for col in catalog.colnames if inst in col] sub_catalog = catalog['index', magcol[0]] # Filter throughput files filter_thru_file = get_filter_throughput_file(instrument=inst, filter_name=filt, nircam_module=mod, fgs_detector=det) # Retrieve the correct gain value that goes with the fluxcal info if inst == 'nircam': gain = MEAN_GAIN_VALUES['nircam']['lwb'] elif inst == 'niriss': gain = MEAN_GAIN_VALUES['niriss'] elif inst == 'fgs': gain = MEAN_GAIN_VALUES['fgs'][det.lower()] # Create filter bandpass object, to be used in the final # comparison filter_tp = ascii.read(filter_thru_file) bp_waves = filter_tp['Wavelength_microns'].data * u.micron thru = filter_tp['Throughput'].data bandpass = SpectralElement( Empirical1D, points=bp_waves, lookup_table=thru) / gain # Check the renormalization in all photometric systems for magsys in mag_sys: rescaled_spectra = spec.rescale_normalized_spectra( spectra, sub_catalog, magsys, filter_thru_file, gain) # Calculate the countrate associated with the renormalized # spectrum through the requested filter for dataset in rescaled_spectra: if dataset == 1: # This block is for the spectra that are rescaled rescaled_spectrum = SourceSpectrum( Empirical1D, points=rescaled_spectra[dataset]['wavelengths'], lookup_table=rescaled_spectra[dataset]['fluxes']) obs = Observation(rescaled_spectrum, bandpass, binset=bandpass.waveset) renorm_counts = obs.countrate(area=primary_area) # Calculate the countrate associated with an object of # matching magnitude if inst != 'fgs': mag_col = '{}_{}_magnitude'.format( inst.lower(), filt.lower()) else: mag_col = 'fgs_magnitude' filt_info = spec.get_filter_info([mag_col], magsys) magnitude = catalog[mag_col][dataset - 1] photflam, photfnu, zeropoint, pivot = filt_info[mag_col] check_counts = magnitude_to_countrate( 'imaging', magsys, magnitude, photfnu=photfnu.value, photflam=photflam.value, vegamag_zeropoint=zeropoint) if magsys != 'vegamag': # As long as the correct gain is used, AB mag and ST mag # count rates agree very well tolerance = 0.0005 else: # Vegamag count rates for NIRISS have a sligtly larger # disagreement. Zeropoints were derived assuming Vega = 0.02 # magnitudes. This offset has been added to the rescaling # function, but may not be exact. tolerance = 0.0015 # This dataset has been rescaled, so check that the # countrate from the rescaled spectrum matches that from # the magnitude it was rescaled to if isinstance(check_counts, u.quantity.Quantity): check_counts = check_counts.value if isinstance(renorm_counts, u.quantity.Quantity): renorm_counts = renorm_counts.value print(inst, filt, magsys, renorm_counts, check_counts, renorm_counts / check_counts) assert np.isclose(renorm_counts, check_counts, atol=0, rtol=tolerance), \ print('Failed assertion: ', inst, filt, magsys, renorm_counts, check_counts, renorm_counts / check_counts) elif dataset == 2: # Not rescaled. In this case Mirage ignores the magnitude # value in the catalog, so we can't check against check_counts. # Just make sure that the rescaling function did not # change the spectrum at all assert np.all(spectra[dataset]['fluxes'] == rescaled_spectra[dataset]['fluxes'])
def scale_spectrum(spectrum, filter_name, amplitude): """ Scales a SourceSpectrum to a value in a filter Parameters ---------- spectrum : synphot.SourceSpectrum filter_name : str Name of a filter from - a local instrument package (available in ``rc.__search_path__``) - a generic filter name (see ``ter_curves_utils.FILTER_DEFAULTS``) - a spanish-vo filter service reference (e.g. ``"Paranal/HAWKI.Ks"``) amplitude : ``astropy.Quantity``, float The value that the spectrum should have in the given filter. Acceptable astropy quantities are: - u.mag : Vega magnitudes - u.ABmag : AB magnitudes - u.STmag : HST magnitudes - u.Jy : Jansky per filter bandpass Additionally the ``FLAM`` and ``FNU`` units from ``synphot.units`` can be used when passing the quantity for ``amplitude``: Returns ------- spectrum : synphot.SourceSpectrum Input spectrum scaled to the given amplitude in the given filter Examples -------- :: >>> from scopesim.source.source_templates import vega_spectrum >>> from scopesim.effects.ter_curves_utils as ter_utils >>> >>> spec = vega_spectrum() >>> vega_185 = ter_utils.scale_spectrum(spec, "Ks", -1.85 * u.mag) >>> ab_0 = ter_utils.scale_spectrum(spec, "Ks", 0 * u.ABmag) >>> jy_3630 = ter_utils.scale_spectrum(spec, "Ks", 3630 * u.Jy) """ if isinstance(amplitude, u.Quantity): if amplitude.unit.physical_type == "spectral flux density": if amplitude.unit != u.ABmag: amplitude = amplitude.to(u.ABmag) ref_spec = ab_spectrum(amplitude.value) elif amplitude.unit.physical_type == "spectral flux density wav": if amplitude.unit != u.STmag: amplitude = amplitude.to(u.STmag) ref_spec = st_spectrum(amplitude.value) elif amplitude.unit == u.mag: ref_spec = vega_spectrum(amplitude.value) else: raise ValueError(f"Units of amplitude must be one of " f"[u.mag, u.ABmag, u.STmag, u.Jy]: {amplitude}") else: ref_spec = vega_spectrum(amplitude) filt = get_filter(filter_name) ref_flux = Observation(ref_spec, filt).effstim(flux_unit=PHOTLAM) real_flux = Observation(spectrum, filt).effstim(flux_unit=PHOTLAM) scale_factor = ref_flux / real_flux spectrum *= scale_factor.value return spectrum
def scale_to_magnitude(self, amplitude, filter_curve=None): """ Scales a Spectrum to a value in a filter copied from scopesim.effects.ter_curves.scale_spectrum with slight modifications Parameters ---------- amplitude : ``astropy.Quantity``, float The value that the spectrum should have in the given filter. Acceptable astropy quantities are: - u.mag : Vega magnitudes - u.ABmag : AB magnitudes - u.STmag : HST magnitudes - u.Jy : Jansky per filter bandpass Additionally the ``FLAM`` and ``FNU`` units from ``synphot.units`` can be used when passing the quantity for ``amplitude``: filter_name : str Name of a filter from - a generic filter name (see ``DEFAULT_FILTERS``) - a spanish-vo filter service reference (e.g. ``"Paranal/HAWKI.Ks"``) - a filter in the spextra database - the path to a filename containing the curve (see ``Passband``) - a ``Passband`` or ``synphot.SpectralElement`` instance filter_file: str A file with a transmission curve Returns ------- spectrum : a Spectrum Input spectrum scaled to the given amplitude in the given filter """ if os.path.exists(filter_curve): filter_curve = Passband.from_file(filename=filter_curve) elif isinstance(filter_curve, (Passband, SpectralElement)): filter_curve = filter_curve else: filter_curve = Passband(filter_curve) if isinstance(amplitude, u.Quantity) is False: amplitude = amplitude * u.ABmag # if amplitude.unit.physical_type == "spectral flux density": # if amplitude.unit != u.ABmag: # amplitude = amplitude.to(u.ABmag) # ref_spec = self.flat_spectrum(amplitude=amplitude.value, system_name="AB") # elif amplitude.unit.physical_type == "spectral flux density wav": # if amplitude.unit != u.STmag: # amplitude = amplitude.to(u.STmag) # ref_spec = self.flat_spectrum(amplitude=amplitude.value, system_name="ST") # elif amplitude.unit == u.mag: # ref_spec = self.flat_spectrum(amplitude=amplitude.value, system_name="Vega") # else: # raise ValueError("Units of amplitude must be one of " # "[u.mag, u.ABmag, u.STmag]: {}".format(amplitude)) # else: ref_spec = self.flat_spectrum(amplitude=amplitude) ref_flux = Observation(ref_spec, filter_curve).effstim(flux_unit=units.PHOTLAM) real_flux = Observation(self, filter_curve).effstim(flux_unit=units.PHOTLAM) scale_factor = ref_flux / real_flux sp = self * scale_factor sp = self._restore_attr(sp) sp.meta.update({"magnitude": amplitude, "filter_curve": filter_curve}) return sp