def _map_filter_to_standard(self, filtername): """Maps a passed <filtername> to a standard Johnson/Cousins/Bessell filter profile. Returns a SpectralElement, defaulting to V""" band_mapping = { 'U': 'johnson_u', 'B': 'johnson_b', 'V': 'johnson_v', 'R': conf.bessell_R_file, 'Rc': 'cousins_r', 'I': conf.bessell_I_file, 'Ic': 'cousins_i', 'J': 'bessel_j', 'H': 'bessel_h', 'K': 'bessel_k', 'G': conf.gaiadr2_g_file } band_name = band_mapping.get(self._convert_filtername(filtername), 'johnson_v') if band_name.startswith('http://svo'): band = SpectralElement.from_file(band_name) else: band = SpectralElement.from_filter(band_name) return band
def 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 __init__(self): self.verbose = False self.telescope_area = 25.326 * 10000. #JWST primary area in cm^2 self.wave_bins = np.arange(0.5, 5, 0.1) * u.micron self.vega = SourceSpectrum.from_vega() self.g2v = STS.grid_to_spec('ck04models', 5860, 0., 4.40) self.kband = SpectralElement.from_filter('bessel_k') self.bpdir = './' self.bpfile = os.path.join( self.bpdir, 'F335M_nircam_plus_ote_throughput_moda_sorted.txt') self.bp = SpectralElement.from_file(self.bpfile, wave_unit=u.micron)
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 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 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 collapse(self, waveset): throughput = self.radiometry_table.throughput(waveset) self._throughput = SpectralElement(Empirical1D, points=waveset, lookup_table=throughput) emission = self.radiometry_table.emission(waveset) self._emission = SourceSpectrum(Empirical1D, points=waveset, lookup_table=emission)
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 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 _get_ter_property(self, ter_property): """ Looks for arrays for transmission, emissivity, or reflection Parameters ---------- ter_property : str ``transmission``, ``emissivity``, ``reflection`` Returns ------- response_curve : ``synphot.SpectralElement`` """ compliment_names = ["transmission", "emissivity", "reflection"] ii = np.where([ter_property == name for name in compliment_names])[0][0] compliment_names.pop(ii) wave = self._get_array("wavelength") value_arr = self._get_array(ter_property) if value_arr is None: value_arr = self._compliment_array(*compliment_names) if value_arr is not None and wave is not None: response_curve = SpectralElement(Empirical1D, points=wave, lookup_table=value_arr) else: response_curve = None warnings.warn("Both wavelength and {} must be set" "".format(ter_property)) return response_curve
def get_phot(mwave, mflux, band_names, band_resp_filenames): """ Compute the magnitudes in the requested bands. Parameters ---------- mwave : vector wavelengths of model flux mflux : vector model fluxes band_names: list of strings names of bands requested band_resp_filename : list of strings filenames of band response functions for the requested bands Outputs ------- bandinfo : BandData object """ # get a BandData object bdata = BandData("BAND") # path for non-HST band response curves data_path = pkg_resources.resource_filename("measure_extinction", "data/Band_RespCurves/") # compute the fluxes in each band for k, cband in enumerate(band_names): if "HST" in cband: bp_info = cband.split("_") bp = STS.band("%s,%s,%s" % (bp_info[1], bp_info[2], bp_info[3])) ncband = "%s_%s" % (bp_info[1], bp_info[3]) else: bp = SpectralElement.from_file("%s%s" % (data_path, band_resp_filenames[k])) ncband = cband # check if the wavelength units are in microns instead of Angstroms # may not work if max(bp.waveset) < 500 * u.Angstrom: print("filter wavelengths not in angstroms") exit() # a.waveset *= 1e4 iresp = bp(mwave) bflux = np.sum(iresp * mflux) / np.sum(iresp) bflux_unc = 0.0 bdata.band_fluxes[ncband] = (bflux, bflux_unc) # calculate the band magnitudes from the fluxes bdata.get_band_mags_from_fluxes() # get the band fluxes from the magnitudes # partially redundant, but populates variables useful later bdata.get_band_fluxes() return bdata
def download_svo_filter(filter_name, return_style="synphot"): """ Query the SVO service for the true transmittance for a given filter Copied 1 to 1 from tynt by Brett Morris Parameters ---------- filt : str Name of the filter as available on the spanish VO filter service e.g: ``Paranal/HAWKI.Ks`` return_style : str, optional Defines the format the data is returned - synphot: synphot.SpectralElement - table: astropy.table.Table - quantity: astropy.unit.Quantity [wave, trans] - array: np.ndarray [wave, trans], where wave is in Angstrom - vo_table : astropy.table.Table - original output from SVO service Returns ------- filt_curve : See return_style Astronomical filter object. """ path = download_file('http://svo2.cab.inta-csic.es/' 'theory/fps3/fps.php?ID={}'.format(filter_name), cache=True) tbl = Table.read(path, format='votable') wave = u.Quantity(tbl['Wavelength'].data.data, u.Angstrom, copy=False) trans = tbl['Transmission'].data.data if return_style == "synphot": filt = SpectralElement(Empirical1D, points=wave, lookup_table=trans) elif return_style == "table": filt = Table(data=[wave, trans], names=["wavelength", "transmission"]) filt.meta["wavelength_unit"] = "Angstrom" elif return_style == "quantity": filt = [wave, trans] elif return_style == "array": filt = [wave.value, trans] elif return_style == "vo_table": filt = tbl return filt
def test_blackbody_maximum_agrees_with_wien(self, temp): '''Check the maximum of emission against Wien's law for photon rate''' emissivity = SpectralElement(Empirical1D, points=[1, 20], lookup_table=[1., 1.]) flux = surf_utils.make_emission_from_emissivity(temp, emissivity) dlam = 0.1 wave = np.arange(3, 20, dlam) * u.um wavemax = wave[np.argmax(flux(wave))] if isinstance(temp, u.Quantity): temp = temp.to(u.Kelvin, equivalencies=u.temperature()).value wienmax = 3669.7 * u.um / temp assert np.abs(wavemax - wienmax.to(u.um)) < dlam * u.um
def test_returns_correct_half_flux_with_bandpass(self): flux = np.ones(11) * u.Unit("ph s-1 m-2 um-1") wave = np.linspace(0.5, 2.5, 11) * u.um spec = SourceSpectrum(Empirical1D, points=wave, lookup_table=flux) bandpass = SpectralElement(Empirical1D, points=np.linspace(1, 2, 13) * u.um, lookup_table=0.5 * np.ones(13)) counts = source_utils.photons_in_range([spec], 1 * u.um, 2 * u.um, bandpass=bandpass) assert counts.value == approx(0.5)
def __init__(self, filter_name=None, modelclass=None, **kwargs): if filter_name is not None: if filter_name in DEFAULT_FILTERS: filter_name = DEFAULT_FILTERS[filter_name] try: FilterContainer.__init__(self, filter_name) meta, wave, trans = self._loader() SpectralElement.__init__(self, Empirical1D, points=wave, lookup_table=trans, meta=meta) except ValueError as e1: try: # try to download it from SVO meta, wave, trans = self._from_svo(filter_name) SpectralElement.__init__(self, Empirical1D, points=wave, lookup_table=trans, meta=meta) except ValueError as e2: print("filter %s doesn't exist" % filter_name) elif modelclass is not None: SpectralElement.__init__(self, modelclass, **kwargs) else: raise ValueError("please define a filter")
def rebin_transmission(self, wavenew): """Rebins a potentially higher resolution atmospheric transmission spectrum to a new wavelength grid <wavenew>. Returns a new BaseUnitlessSpectrum constructed from the new passed wavelength grid and the resampled transmission""" wave = self.transmission.waveset specin = self.transmission(wave) # Have to pretend transmission is in a flux unit to make a SourceSpectrum # so drop here and put back at the end orig_unit = specin.unit spec = SourceSpectrum(Empirical1D, points=wave, lookup_table=specin.value) f = np.ones(len(wave)) filt = SpectralElement(Empirical1D, points=wave, lookup_table=f) obs = Observation(spec, filt, binset=wavenew, force='taper') new_trans = SpectralElement(Empirical1D, points=wavenew, lookup_table=obs.binflux.value * orig_unit) return new_trans
def photons_from_source(self, mag, mag_filter, filtername, source_spec=None, force_overlap='taper', normalize=True): """Computes the photons coming from the source given by [source_spec] (Vega is assumed if not given) of the given <mag> in <mag_filter> (e.g. for "V=20", mag=20, mag_filter='V') when observed through the instrument's filter given by <filtername> """ print("photons_from_source:", filtername) standard_filter = self._convert_filtername(filtername) band = self._map_filter_to_standard(mag_filter) print(band.meta.get('expr', 'Unknown'), type(source_spec)) source_spec = source_spec or self._vega if type(source_spec) != SourceSpectrum: raise ETCError('Invalid sourcespec; must be a SourceSpectrum') # XXX Todo: allow support for AB mags here # test line below for comparison with SIGNAL. Difference in mag when # using proper normalization is ~mag-0.0155 (for B=22.7) # source_spec_norm = source_spec*10**(-0.4*mag) if normalize is True: source_spec_norm = source_spec.normalize(mag * units.VEGAMAG, band, vegaspec=self._vega) else: source_spec_norm = source_spec self._create_combined() filter_waves, filter_trans = self.instrument.filterset[ filtername]._get_arrays(None) throughput = self._throughput_for_filter(filtername) waves, thru = throughput._get_arrays(filter_waves) spec_elements = SpectralElement(Empirical1D, points=filter_waves, lookup_table=filter_trans * thru) # get the synphot observation object synphot_obs = Observation(source_spec_norm, spec_elements, force=force_overlap) if self.obs is None: self.obs = synphot_obs countrate = synphot_obs.countrate(area=self.telescope.area) return countrate
def test_with_bandpass_and_area_returns_correct_value( self, flux, area, expected): flux = flux * u.Unit("ph s-1 m-2 um-1") spec = SourceSpectrum(Empirical1D, points=np.linspace(0.5, 2.5, 11) * u.um, lookup_table=flux) bandpass = SpectralElement(Empirical1D, points=np.linspace(1, 2, 13) * u.um, lookup_table=0.5 * np.ones(13)) counts = source_utils.photons_in_range([spec], 1 * u.um, 2 * u.um, bandpass=bandpass, area=area) assert counts.value == approx(expected)
def system_transmission(self): wave_unit = u.Unit(rc.__currsys__["!SIM.spectral.wave_unit"]) dwave = rc.__currsys__["!SIM.spectral.spectral_bin_width"] wave_min = rc.__currsys__["!SIM.spectral.wave_min"] wave_max = rc.__currsys__["!SIM.spectral.wave_max"] wave = np.arange(wave_min, wave_max, dwave) trans = np.ones_like(wave) sys_trans = SpectralElement(Empirical1D, points=wave * u.Unit(wave_unit), lookup_table=trans) for effect in self.get_z_order_effects(100): sys_trans *= effect.throughput return sys_trans
def gaussian(cls, center, fwhm, peak, **kwargs): """ Creates a filter with a gaussian shape with given user parameters Returns ------- """ if isinstance(center, u.Quantity) is False: center = center*u.AA if isinstance(fwhm, u.Quantity) is False: fwhm = fwhm*u.AA sigma = fwhm / (2.0 * np.sqrt(2.0 * np.log(2.0))) modelclass = SpectralElement(Gaussian1D, amplitude=peak, mean=center, stddev=sigma, **kwargs) return cls(modelclass=modelclass)
def square(cls, wmin, wmax, transmission): """ Creates a filter with a rectangular shape Returns ------- """ if isinstance(wmin, u.Quantity) is False: wmin = wmin*u.AA if isinstance(wmax, u.Quantity) is False: wmax = wmax*u.AA center = (wmax + wmin) * 0.5 width = wmax - wmin modelclass = SpectralElement(Box1D, amplitude=transmission, x_0=center, width=width) return cls(modelclass=modelclass)
def plot_bandpasses(bands): # create the figure plt.figure(figsize=(15, 5)) # plot all response curves for band in bands: bp = SpectralElement.from_file("%s%s.dat" % (band_path, band)) if "MIPS" in band: wavelengths = bp.waveset * 10**4 else: wavelengths = bp.waveset plt.plot(wavelengths, bp(bp.waveset), label=band) plt.xlabel(r"$\lambda$ [$\AA$]", size=15) plt.ylabel("transmission", size=15) plt.legend(ncol=2, loc=1) plt.show()
def set_bandpass_from_filter(self, filtername): """Loads the specified <filtername> from the transmission profile file which is mapped via the etc.config.Conf() items. Returns a SpectralElement instance for the filter profile """ if len(filtername) == 2 and filtername[1] == 'p': filtername = filtername[0] filename = conf.mapping.get(filtername, None) if filename is None: raise ETCError('Filter name {0} is invalid.'.format(filtername)) if 'LCO_' in filename().upper() and '.csv' in filename().lower(): file_path = pkg_resources.files('etc.data').joinpath( os.path.expandvars(filename())) source = "LCO iLab format" header, wavelengths, throughput = self._read_lco_filter_csv( file_path) elif 'http://svo' in filename().lower(): source = "SVO filter service" header, wavelengths, throughput = specio.read_remote_spec( filename(), wave_unit=u.AA, flux_unit=units.THROUGHPUT) else: source = "local file" file_path = pkg_resources.files('etc.data').joinpath( os.path.expandvars(filename())) warnings.simplefilter('ignore', category=AstropyUserWarning) header, wavelengths, throughput = specio.read_ascii_spec( file_path, wave_unit=u.nm, flux_unit=units.THROUGHPUT) if throughput.mean() > 1.0: throughput /= 100.0 header['notes'] = 'Divided by 100.0 to convert from percentage' print("Reading from {} for {}".format(source, filtername)) header['filename'] = filename header['descrip'] = filename.description meta = {'header': header, 'expr': filtername} return SpectralElement(Empirical1D, points=wavelengths, lookup_table=throughput, meta=meta)
def from_vectors(cls, waves, trans, meta=None, wave_unit=u.AA): """ Create a ``Passband`` directly from from vectos (lists, numpy.arrays, etc) Parameters ---------- waves: list-like trans: list-like meta: dictionary containing the metadata wave_unit: u.Quantity, defaulted to angstroms Returns ------- Passband """ if isinstance(waves, u.Quantity) is False: waves = waves*wave_unit modelclass = SpectralElement(Empirical1D, points=waves, lookup_table=trans, meta=meta) return cls(modelclass=modelclass)
def get_filter(filter_name): # first check locally # check generics # check spanish_vo path = find_file(filter_name, silent=True) if path is not None: tbl = ioascii.read(path) wave = quantity_from_table("wavelength", tbl, u.um).to(u.um) filt = SpectralElement(Empirical1D, points=wave, lookup_table=tbl["transmission"]) elif filter_name in FILTER_DEFAULTS: filt = download_svo_filter(FILTER_DEFAULTS[filter_name]) else: try: filt = download_svo_filter(filter_name) except: filt = None return filt
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 __init__(self, name=None, camera_type="CCD", **kwargs): _cam_types = [ "CCD", ] self.camera_type = camera_type.upper() if camera_type.upper( ) in _cam_types else "CCD" self.name = name if name is not None else self.camera_type + ' camera' # Defaults assume no elements per channel on the assumption that it's # in the common optics (populated in Instrument()) self.num_ar_coatings = kwargs.get('num_chan_ar_coatings', 0) self.num_lenses = kwargs.get('num_chan_lenses', 0) self.num_mirrors = kwargs.get('num_chan_mirrors', 0) # Fused silica (common lens material) and fused quartz (common for CCD windows) # turn out to have the same transmission... self.lens_trans = kwargs.get('chan_lens_trans', 0.93) self.mirror_refl = kwargs.get('chan_mirror_refl', 0.9925) # Transmission/Reflection values of optical elements coating self.ar_coating = kwargs.get('chan_ar_coating_refl', 0.99) # Read common components first trans_components = kwargs.get('trans_components', None) wavelengths = np.arange(300, 1501, 1) * u.nm if trans_components: print("Computing channel transmission from components") trans = len(wavelengths) * [ 1.0, ] for comp_name in trans_components.split(","): print(comp_name) element = read_element(comp_name) trans *= element(wavelengths) else: print("Computing channel transmission from elements") transmission = self._compute_transmission() trans = len(wavelengths) * [ transmission, ] header = {} self.transmission = SpectralElement(Empirical1D, points=wavelengths, lookup_table=trans, keep_neg=True, meta={'header': header}) ccd_qe = kwargs.get('ccd_qe', 0.9) if not isinstance(ccd_qe, (u.Quantity, numbers.Number)): file_path = os.path.expandvars(ccd_qe) if not os.path.exists(file_path): file_path = str( pkg_resources.files('etc.data').joinpath(ccd_qe)) header, wavelengths, throughput = specio.read_ascii_spec( file_path, wave_unit=u.nm, flux_unit=units.THROUGHPUT) if throughput.mean() > 1.0: throughput /= 100.0 header['notes'] = 'Divided by 100.0 to convert from percentage' header['filename'] = ccd_qe self.ccd_qe = BaseUnitlessSpectrum(Empirical1D, points=wavelengths, lookup_table=throughput, keep_neg=False, meta={'header': header}) else: self.ccd_qe = ccd_qe self.ccd_gain = kwargs.get('ccd_gain', 1) * (u.electron / u.adu) self.ccd_readnoise = kwargs.get('ccd_readnoise', 0) * (u.electron / u.pix) ccd_pixsize = kwargs.get('ccd_pixsize', 0) try: ccd_pixsize_units = u.Unit( kwargs.get('ccd_pixsize_units', 'micron')) except ValueError: ccd_pixsize_units = u.micron self.ccd_pixsize = (ccd_pixsize * ccd_pixsize_units).to(u.micron) self.ccd_xpixels = kwargs.get('ccd_xpixels', 0) self.ccd_ypixels = kwargs.get('ccd_ypixels', 0) self.ccd_xbinning = kwargs.get('ccd_xbinning', 1) self.ccd_ybinning = kwargs.get('ccd_ybinning', 1)
def __init__(self, name=None, inst_type="IMAGER", **kwargs): _ins_types = ["IMAGER", "SPECTROGRAPH"] self.name = name if name is not None else "Undefined" self.inst_type = inst_type.upper() if inst_type.upper( ) in _ins_types else "IMAGER" self.fiber_diameter = kwargs.get('fiber_diameter', None) if self.fiber_diameter is not None: self.fiber_diameter = self.fiber_diameter * u.arcsec self.grating_linespermm = kwargs.get('grating_linespermm', None) if self.grating_linespermm: self.grating_spacing = 1.0 / (self.grating_linespermm * (1 / u.mm)) self.grating_blaze = kwargs.get('grating_blaze', 0.0) self.grating_gamma = kwargs.get('grating_gamma', 0.0) self.cam_focallength = kwargs.get('cam_focallength', None) if self.cam_focallength: self.cam_focallength = self.cam_focallength * u.mm self.dispersion_along_x = kwargs.get('dispersion_along_x', True) self._echelle_constant = None # Defaults assume a "standard" imager with a single lens, 2 AR coatings # on the front and back surfaces and no mirrors self.num_ar_coatings = kwargs.get('num_ar_coatings', 2) self.num_lenses = kwargs.get('num_inst_lenses', 1) self.num_mirrors = kwargs.get('num_inst_mirrors', 0) # Fused silica (common lens material) and fused quartz (common for CCD windows) # turn out to have the same transmission... self.lens_trans = kwargs.get('inst_lens_trans', 0.93) self.mirror_refl = kwargs.get('inst_mirror_refl', 0.9925) # Transmission/Reflection values of optical elements coating self.ar_coating = kwargs.get('inst_ar_coating_refl', 0.99) # Read common components first trans_components = kwargs.get('trans_components', None) wavelengths = np.arange(300, 1501, 1) * u.nm if trans_components: trans = len(wavelengths) * [ 1.0, ] for comp_name in trans_components.split(","): print(comp_name) element = read_element(comp_name.strip()) trans *= element(wavelengths) else: print("Computing transmission from elements") transmission = self._compute_transmission() trans = len(wavelengths) * [ transmission, ] header = {} self.transmission = SpectralElement(Empirical1D, points=wavelengths, lookup_table=trans,\ keep_neg=True, meta={'header': header}) fwhm = kwargs.get('fwhm', 1) try: fwhm_units = u.Unit(kwargs.get('fwhm_units', 'arcsec')) except ValueError: fwhm_units = u.arcsec try: # Already a Quantity self.fwhm = fwhm.to(u.arcsec) except AttributeError: self.fwhm = fwhm * fwhm_units focal_scale = kwargs.get('focal_scale', 0) try: focal_scale_units = u.Unit( kwargs.get('focal_scale_units', 'arcsec/mm')) except ValueError: focal_scale_units = u.arcsec / u.mm self.focal_scale = focal_scale * focal_scale_units channels = kwargs.get('channels', {'default': {}}) self._num_channels = max(len(channels), 1) self.channelset = OrderedDict() self.filter2channel_map = OrderedDict() self.filterlist = kwargs.get('filterlist', []) self.filterset = OrderedDict() for filtername in self.filterlist: if filtername not in self.filterset: self.filterset[filtername] = self.set_bandpass_from_filter( filtername) self.filter2channel_map[filtername] = 'default' for channel in channels: # Make copy of common params defined at the Instrument level and then # overwrite with channel/camera-specific versions camera_kwargs = kwargs.copy() # Delete any trans_components if 'trans_components' in camera_kwargs: del (camera_kwargs['trans_components']) for k, v in channels[channel].items(): camera_kwargs[k] = v self.channelset[channel] = Camera(**camera_kwargs) if self.channelset[ channel].ccd_pixsize != 0 and self.focal_scale != 0: self.channelset[channel].ccd_pixscale = self.focal_scale.to( u.arcsec / u.mm) * self.channelset[channel].ccd_pixsize.to( u.mm) cam_filterlist = camera_kwargs.get('filterlist', []) for filtername in cam_filterlist: if filtername not in self.filterset: self.filterlist.append(filtername) self.filterset[filtername] = self.set_bandpass_from_filter( filtername) self.filter2channel_map[filtername] = channel
def grism_cal(self, throughput_files): """ Calculate flux cal outputs for grism mode. Input files should contain the total system throughput for the grism plus crossing filter. The input file list should contain throughputs for all orders of just a single grism+crossing filter. Arguments: ---------- throughput_files -- list of text files containing filter throughputs Returns: -------- Astropy table containing flux calibration information for grism mode """ print('need to update this for synphot') allrows = [] filters = np.array([]) pupils = np.array([]) orders = np.array([]) fluxes = np.array([]) uncs = np.array([]) nelems = np.array([]) waves = np.array([]) resps = np.array([]) #waves = np.expand_dims(waves,axis=0) #resps = np.expand_dims(resps,axis=0) for file in throughput_files: # Read in necessary file info with fits.open(file) as h: cname = h[0].header['COMPNAME'] junk, filter, mod = cname.split('_') mod = mod[-1].upper() pupil = 'GRISMR' print('Eventually need to be smarter about pupil value!!!!!') print("opening {}".format(file)) bp = SpectralElement.from_file(file) # 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 from e/sec bp.model.lookup_table = bp.model.lookup_table / self.gain # Pivot wavelength in microns pwave = bp.pivot() #pwave = pwave.to(u.micron) # Need to find the order here, so that the proper # dispersion value is used. ord = file.find('order') order = file[ord + 5] dispersion = self.disp[order] #find pivot? mean? center? effective? wavelength #denom = self.h * self.c / eff_lambda #countratedensity = self.telescope_area * tp['Throughput'] * vegaflux / denom #countratedensityflux,countratedensitywave,pwave = self.getcountsflux(bp) #totalrate = self.integrate(countratedensity) #Is the variable below used at all? #grism_total_rate += totalrate obs = self.getcountsflux(bp) # From observation, get the flux in counts countratedensityflux = obs(obs.binset, flux_unit='count', area=self.telescope_area) print('countratedensityflux', countratedensityflux) # Multiply by dispersion countratedensityflux *= dispersion # Countrate density at the pivot wavelength print('pivot', pwave.value) print('countratedensityflux*dispersion', countratedensityflux) print('obs.binset', obs.binset) #cnorm = np.interp(pwave.value,obs.binset,countratedensityflux) cnorm = obs(pwave, flux_unit='count', area=self.telescope_area) * dispersion print('cnorm', cnorm) # Vega flux value at pivot wavelength, convert to Jansky vega_pivot = self.vega(pwave) j0 = units.convert_flux(pwave, vega_pivot, 'MJy') # Now we need to divide by the area of a pixel in # sterradians, so we can eventually get MJy/str per ADU/sec j0 /= self.pixel_area_sr #vega_pivotorig = np.interp(pwave.value,self.vega.waveset,self.vega(self.vega.waveset)) #print("units of vega flux are: {}".format(self.vega(self.vega.waveset).unit)) #j0 = self.toJansky(vega_pivot.value,pwave.value) / 1.e6 # Ratio of Vega flux to the countrate density at pivot wavelength ratio = j0 / cnorm print('') print('PHOTMJSR', ratio) print( 'NIRISS values are 0.01 to 2.0. I would expect ours to be similar!' ) print('') # Define a set of wavelengths to evaluate relative fluxcal goodpts = bp(bp.waveset) > 0.0001 minwave = np.min(bp.waveset[goodpts]) maxwave = np.max(bp.waveset[goodpts]) w = minwave.value allwaves = np.array([w]) while (w < maxwave.value): delt = w / (np.absolute(np.int(order)) * self.resolving_power) allwaves = np.append(allwaves, w + delt) w += delt # Calculate Vega flux at each wavelength nelem = len(allwaves) allfluxes = self.vega(allwaves) alljansky = units.convert_flux(allwaves, allfluxes, 'MJy') allcounts = np.interp(allwaves, obs.binset, countratedensityflux) #allfluxes = np.interp(allwaves,self.vega.waveset,self.vega(self.vega.waveset)) #alljansky = self.toJansky(allfluxes,allwaves) / 1.e6 # Normalize the Vega counts at all wavelengths by the value at the pivot # wavelength allratio = alljansky.value / allcounts / ratio / self.pixel_area_sr #print(np.min(allwaves),np.max(allwaves),allwaves[0],allwaves[-1]) #f,a = plt.subplots() #a.plot(allwaves,allratio,color='red') #a.set_xlabel('Wavelength ({})'.format(bp.waveset.unit)) #a.set_ylabel('Normalized Ratio (MJy/str per count/sec)') #a.set_title("{}, Order {}".format(filter.upper(),order)) #f.savefig(os.path.split(file)[-1]+'_allratios.pdf') #f,a = plt.subplots() #a.plot(allwaves,alljansky,color='red') #a.set_xlabel('Wavelength ({})'.format(bp.waveset.unit)) #a.set_ylabel('Vega Flux (MJy)') #a.set_title("{}, Order {}".format(filter.upper(),order)) #f.savefig(os.path.split(file)[-1]+'_alljansky.pdf') #f,a = plt.subplots() #a.plot(allwaves,allcounts,color='red') #a.set_xlabel('Wavelength ({})'.format(bp.waveset.unit)) #a.set_ylabel('Counts (count/sec)') #a.set_title("{}, Order {}".format(filter.upper(),order)) #f.savefig(os.path.split(file)[-1]+'_allcounts.pdf') if np.min(allcounts) < 0: print('WARNING: counts<0 for {},{}'.format( filter.upper(), order)) stop if '444' in filter: print("") print('444!!!!!!') print(allratio.value) print(len(allratio.value)) print(type(allratio.value)) #bad = allratio.value > 1e5 bad = np.where(allratio.value > 1e5) print(len(bad[0])) print(type(bad[0])) print(alljansky.value[bad[0][0:10]]) print(allcounts[bad[0][0:10]]) print(ratio) print(self.str_per_detector) print(allratio.value[bad[0][0:10]]) print(allwaves) stop # Pad allwaves and allratio to be the length needed by the # photom reference file. Also convert wavelengths to microns. allwaves = np.pad(allwaves / 1.e4, (0, self.maxlen - nelem), 'constant') allratio = np.pad(allratio, (0, self.maxlen - nelem), 'constant') print(allwaves[100:105]) print(allcounts[100:105]) print(alljansky[100:105]) print(alljansky[100:105] / allcounts[100:105]) print(ratio) print(allratio[100:105]) print("******************") print("******************") print("******************") print("need real conversion factor and uncertainty!!!") conversion_factor = 1000. uncertainty = ratio * .1 #row = [filter,pupil,np.int(order),ratio*conversion_factor,uncertainty,nelem,allwaves,allratio] # Populate lists that will be used to create the final table filters = np.append(filters, filter) pupils = np.append(pupils, pupil) orders = np.append(orders, np.int(order)) fluxes = np.append(fluxes, ratio * conversion_factor) uncs = np.append(uncs, uncertainty) nelems = np.append(nelems, nelem) print(allwaves.shape) if len(waves) == 0: waves = allwaves waves = np.expand_dims(waves, axis=0) resps = allratio resps = np.expand_dims(resps, axis=0) else: waves = np.append(waves, np.expand_dims(allwaves, axis=0), axis=0) resps = np.append(resps, np.expand_dims(allratio, axis=0), axis=0) print('waves.shape', waves.shape) print('resps.shape', resps.shape) print(filters) print(pupils) print(orders) print(fluxes) print(uncs) print(nelems) print(waves[0, 40:45]) print(resps[0, 40:45]) # Zip all data together alldata = np.array(zip(np.array(filters), np.array(pupils), np.array(fluxes), np.array(uncs), np.array(orders), np.array(nelems), np.array(waves), np.array(resps)), dtype=self.model.phot_table.dtype) return alldata