def duet_fluence_to_abmag(fluence, duet_no, duet=None, bandpass=None): """ Convert AB magnitude for a source into the number of source counts. Parameters ---------- fluence: float fluence in the bandpass that you're using in units (ph / cm2 / sec) duet_no: int, 1 or 2 DUET channel Other parameters ---------------- duet : `astroduet.config.Telescope` object if None, allocate a new Telescope object bandpass: array DUET bandpass you're using Returns ------- AB magnitude in the band (ABmag) Example ------- >>> funit = u.ph / u.cm**2/u.s >>> abmag = duet_fluence_to_abmag(0.01*funit, 1) >>> np.isclose(abmag.value, 18.54584301) True """ from astroduet.config import Telescope if duet is None: duet = Telescope() band = getattr(duet, f'band{duet_no}') spec = [1] * u.ph / (u.s * u.cm**2 * u.AA) wave = [band['eff_wave'].to(u.AA).value] * u.AA if bandpass is None: bandpass = band['eff_width'].to(u.AA) scale = (duet.apply_filters(wave, spec, duet_no)).value[0] fluence_corr = fluence / scale ABmag = (fluence_corr / bandpass).to(u.ABmag, equivalencies=u.spectral_density( band['eff_wave'].to(u.AA))) return ABmag
def duet_abmag_to_fluence(ABmag, duet_no, duet=None, bandpass=None): """ Convert AB magnitude for a source into the number of source counts. Parameters ---------- ABmag: float AB magnitude in the bandpass that you're using duet_no: int, 1 or 2 DUET channel Other parameters ---------------- duet : `astroduet.config.Telescope` object if None, allocate a new Telescope object bandpass: array DUET bandpass you're using Returns ------- Fluence in the band (ph / cm2 / sec) Example ------- >>> fluence = duet_abmag_to_fluence(20*u.ABmag, 1) >>> np.isclose(fluence.value, 0.00262022) True """ import numpy as np from astroduet.config import Telescope if duet is None: duet = Telescope() band = getattr(duet, f'band{duet_no}') spec = [1] * u.ph / (u.s * u.cm**2 * u.AA) wave = [band['eff_wave'].to(u.AA).value] * u.AA if bandpass is None: bandpass = band['eff_width'].to(u.AA) scale = (duet.apply_filters(wave, spec, duet_no)).value[0] funit = u.ph / u.cm**2 / u.s / u.AA # Spectral radiances per Hz or per angstrom fluence = bandpass * ABmag.to( funit, equivalencies=u.spectral_density(band['eff_wave'].to(u.AA))) return fluence * scale
def panstarrs_to_duet(panmags, duet=None): """ Converts GALEX FUV and NUV ABmags into DUET 1 and DUET 2 ABmags, assuming flat Fnu Parameters ---------- panmags: array PanSTARRS AB magnitudes and errors, input as [[g, g_err, r, r_err, i, i_err, z, z_err, y, y_err],[]] without units duet: Telescope instance Returns ------- duetfluences: Array with same shape as galmags, with panstarrs g, panstarrs r, DUET 1 and DUET 2 fluences. badstars: number of stars that were not fitted because they had only two or less good magnitudes badfits: number of stars where the fit failed due to a runtime error or value error after trying all initial guesses Example ------- >>> from astroduet.config import Telescope >>> duet = Telescope() >>> star = np.array([[11.,0.1,10.3,0.1,10,0.1,10,0.1,10,0.1]]) >>> duetfluences, badstars, badfits = panstarrs_to_duet(star,duet=duet) >>> np.isclose(duetfluences['d1_fluence'][0],0.041547) True """ from astropy.modeling.blackbody import FLAM from scipy.optimize import curve_fit from astroduet.bbmag import bbfunc, bb_abmag_fluence from astropy.table import Table if duet is None: from astroduet.config import Telescope duet = Telescope() fluxunit = u.erg / u.s / u.cm**2 # Central wavelengths of PanSTARRS bands: pswav = np.array([486.6, 621.5, 754.5, 867.9, 963.3]) * u.nm duetfluences = Table(np.zeros(4), names=('ps_g', 'ps_r', 'd1_fluence', 'd2_fluence')) badstars = 0 badfits = 0 # Loop over stars: for i, star in enumerate(panmags): # Replace bad values with nan: star[star == -999.] = np.nan # Order and convert to magnitudes mags = star[::2] * u.ABmag magerrs = star[1::2] # Find valid values: valid = ~(np.isnan(mags)) # Convert to flux densities fden = mags[valid].to(FLAM, equivalencies=u.spectral_density(pswav[valid])) snrs = 1. / (10.**(magerrs[valid] / 2.5) - 1.) # Set snr to 10 for nan errors: snrs[np.isnan(snrs)] = 10 fden_err = fden / snrs # Filter for stars with only one good data point: if len(fden) > 2: # Fit blackbody: try: # Starting value for blackbody fit: p0 = [5000, 1.E-8] coeff, var_matrix = curve_fit(bbfunc, pswav[valid].value, fden.value, p0=p0, sigma=fden_err.value, absolute_sigma=True) except RuntimeError: badfits += 1 continue except ValueError: try: p0 = [10000, 1.E-8] coeff, var_matrix = curve_fit(bbfunc, pswav[valid].value, fden.value, p0=p0, sigma=fden_err.value, absolute_sigma=True) except RuntimeError: badfits += 1 continue except ValueError: try: p0 = [5000, 1.E-9] coeff, var_matrix = curve_fit(bbfunc, pswav[valid].value, fden.value, p0=p0, sigma=fden_err.value, absolute_sigma=True) except RuntimeError: badfits += 1 continue except ValueError: try: p0 = [10000, 1.E-9] coeff, var_matrix = curve_fit(bbfunc, pswav[valid].value, fden.value, p0=p0, sigma=fden_err.value, absolute_sigma=True) except RuntimeError: badfits += 1 continue except ValueError: try: p0 = [5000, 1.E-10] coeff, var_matrix = curve_fit( bbfunc, pswav[valid].value, fden.value, p0=p0, sigma=fden_err.value, absolute_sigma=True) except RuntimeError: badfits += 1 continue except ValueError: try: p0 = [10000, 1.E-10] coeff, var_matrix = curve_fit( bbfunc, pswav[valid].value, fden.value, p0=p0, sigma=fden_err.value, absolute_sigma=True) except RuntimeError: badfits += 1 continue except ValueError: badfits += 1 # Get DUET fluences: duetfluence = bb_abmag_fluence(duet=duet, bbtemp=coeff[0] * u.K, bolflux=coeff[1] * fluxunit) duetfluences.add_row( [mags[0], mags[1], duetfluence[0], duetfluence[1]]) else: badstars += 1 duetfluences.remove_row(0) duetfluences['d1_fluence'].unit = u.ph / u.s / u.cm**2 duetfluences['d2_fluence'].unit = u.ph / u.s / u.cm**2 return duetfluences, badstars, badfits
def test_run_through_Telescope_methods(): duet = Telescope() duet_offax = Telescope(config='reduced_baseline') duet.info() duet.update_bandpass() duet.update_psf() # duet.update_psf_vals() duet.calc_radial_profile() duet.calc_psf_fwhm() duet.update_effarea() duet.fluence_to_rate(1 * u.ph / u.s / u.cm**2) duet.psf_model() duet.compute_psf_norms()
def galex_to_duet(galmags, duet=None): """ Converts GALEX FUV and NUV ABmags into DUET 1 and DUET 2 ABmags, assuming flat Fnu Parameters ---------- galmags: array GALEX AB magnitudes, either as [[FUV1, ..., FUVN],[NUV1, ..., NUVN]] or as [[FUV1, NUV1],...,[FUVN, NUVN]] Code assumes the first format if len(galmags) = 2 duet: Telescope instance Returns ------- duetmags: Array with same shape as galmags, with DUET 1 and DUET 2 ABmags. Example ------- >>> from astroduet.config import Telescope >>> duet = Telescope() >>> galmags = [20,20] >>> duetmags = galex_to_duet(galmags, duet) >>> np.allclose(duetmags, [20,20]) True """ from astropy.modeling.blackbody import FNU if duet is None: from astroduet.config import Telescope duet = Telescope() galex_fuv_lef = 151.6 * u.nm galex_nuv_lef = 226.7 * u.nm duet_1_lef = duet.band1['eff_wave'] duet_2_lef = duet.band2['eff_wave'] galex_fuv_nef = galex_fuv_lef.to(u.Hz, u.spectral()) galex_nuv_nef = galex_nuv_lef.to(u.Hz, u.spectral()) duet_1_nef = duet_1_lef.to(u.Hz, u.spectral()) duet_2_nef = duet_2_lef.to(u.Hz, u.spectral()) # Sort input array into FUV and NUV magnitudes if len(galmags) == 2: fuv_mag = galmags[0] * u.ABmag nuv_mag = galmags[1] * u.ABmag else: fuv_mag = galmags[:, 0] * u.ABmag nuv_mag = galmags[:, 1] * u.ABmag # Convert GALEX magnitudes to flux densities fuv_fnu = fuv_mag.to(FNU, u.spectral_density(galex_fuv_nef)) nuv_fnu = nuv_mag.to(FNU, u.spectral_density(galex_nuv_nef)) # Extrapolate to DUET bands assuming linear Fnu/nu delta_fnu = (nuv_fnu - fuv_fnu) / (galex_nuv_nef - galex_fuv_nef) d1_fnu = fuv_fnu + delta_fnu * (duet_1_nef - galex_fuv_nef) d2_fnu = fuv_fnu + delta_fnu * (duet_2_nef - galex_fuv_nef) # Convert back to magnitudes d1_mag = d1_fnu.to(u.ABmag, u.spectral_density(duet_1_nef)) d2_mag = d2_fnu.to(u.ABmag, u.spectral_density(duet_2_nef)) # Construct output array if len(galmags) == 2: duetmags = np.array([d1_mag.value, d2_mag.value]) else: duetmags = np.array([d1_mag.value, d2_mag.value]).transpose() return duetmags
def convert_model(filename, name='NoName', duet=None): ''' Reads in the EMGW shock breakout models, converts them to DUET fluences, and writes out the resulting models to FITS files. Parameters ---------- filename : string Path to GRB shock file. Other parameters ---------------- name : string name to use for the model. Default is 'NoName' ''' if duet is None: duet = Telescope() bandone = duet.bandpass1 bandtwo = duet.bandpass2 dist0 = 10*u.pc shock_data = np.loadtxt(filename) time = (shock_data[:,0]*u.d).to(u.s) temps = shock_data[:,2] bolflux = 10**shock_data[:,1] # Set up outputs shock_lc = Table([time, np.zeros(len(time))*u.ABmag, np.zeros(len(time))*u.ABmag, np.zeros(len(time))*u.ph/(u.s*u.cm**2), np.zeros(len(time))*u.ph/(u.s*u.cm**2)], names=('time', 'mag_D1', 'mag_D2', 'fluence_D1', 'fluence_D2'), meta={'name': name + ' at 10 pc', 'dist0_pc' : '{}'.format(dist0.to(u.pc).value)}) N = len(temps) for k, t, bf in tqdm(list(zip(np.arange(N), temps, bolflux))): t *= u.K bf *= (u.erg/u.s) /(4 * np.pi * dist0**2) band1_mag, band2_mag = bb_abmag(bbtemp=t, bolflux = bf, bandone=bandone, bandtwo=bandtwo, val=True) band1_fluence, band2_fluence = bb_abmag_fluence(bbtemp=t, bolflux=bf) shock_lc[k]['mag_D1'] = band1_mag shock_lc[k]['mag_D2'] = band2_mag shock_lc[k]['fluence_D1'] = band1_fluence.value shock_lc[k]['fluence_D2'] = band2_fluence.value shock_lc['mag_D1'].unit = None shock_lc['mag_D2'].unit = None return shock_lc
def bb_abmag_fluence(val=False, duet=None, **kwargs): """ Take a blackbody with a certain temperature and convert to photon rate in a band. Other Parameters ---------------- val : boolean Retrurns AB mags without units (False, default) or with Astropy units duet: ``astroduet.conifg.Telescope() object`` If you've already instanced a duet telecope object, feed it in here. Currently allows the use of the default bandpasses. umag : float Must have astropy AB units Apparent U-band AB mag. Only used if other values not provided? siwftmag : float Must have astropy AB units. Apparent Swift magnitude (default is 22*u.ABmag) ref : string Band to use for reference magnitude; options are 'u', 'swift' ('swift') bbtemp : float Blackbody temperature to use (20000*ur.K) dist : float Distance of the source. swiftmags are assumed to be given at a reference distance of 10 pc (I think?) bolflux : float Bolometric flux; if not 1, refmag and distance are ignored. Should have units like (1*ur.erg/ur.cm**2/ur.s) diag : boolean SHow diagnostic inforamtion Returns ------- ABmag1, ABmag2 """ import astropy.units as ur import astropy.constants as cr from astropy.modeling import models from astropy.modeling.blackbody import FLAM import numpy as np from astroduet.config import Telescope if duet is None: duet = Telescope() bbtemp = kwargs.pop('bbtemp', 20000. * ur.K) umag = kwargs.pop('umag', 22 * ur.ABmag) swiftmag = kwargs.pop('swiftmag', 22 * ur.ABmag) dist = kwargs.pop('dist', 10 * ur.pc) ref = kwargs.pop('ref', 'swift') diag = kwargs.pop('diag', False) dist0 = 10 * ur.pc bolflux = kwargs.pop('bolflux', 1. * ur.erg / (ur.cm**2 * ur.s)) bandu = [340, 380] * ur.nm # For comparison purposes bandsw = [ 172.53, 233.57 ] * ur.nm # Swift UVW2 effective band (lambda_eff +/- 0.5 width_eff) wav = np.arange(1000, 9000) * ur.AA # Wavelength scale in 1 Angstrom steps bb = models.BlackBody1D( temperature=bbtemp, bolometric_flux=bolflux) # Load the blackbody model flux = bb(wav).to(FLAM, ur.spectral_density(wav)) # Get Swift reference AB mag fluxden_sw = np.mean(flux[(wav >= bandsw[0].to(ur.AA)) & (wav <= bandsw[1].to(ur.AA))]) magsw = fluxden_sw.to(ur.ABmag, equivalencies=ur.spectral_density(np.mean(bandsw))) # Conver to flux AB mags across the band. flux_ab = flux.to(ur.ABmag, equivalencies=ur.spectral_density(wav)) # Distance modulus distmod = (5 * np.log10(dist / dist0)).value * ur.mag # Set up input: magoff = swiftmag - magsw # Apply the distance modulus and the Swift reference offset if (bolflux == 1. * ur.erg / (ur.cm**2 * ur.s)): flux_mag = flux_ab + magoff + distmod else: flux_mag = flux_ab # Convert back to flux flux_conv = flux_mag.to(FLAM, equivalencies=ur.spectral_density(wav)) dw = 1 * ur.AA ph_energy = (cr.h.cgs * cr.c.cgs / wav.cgs) / ur.ph # Convert to photon flux. ph_flux = flux_conv * dw / ph_energy # Apply filters, QE, etc. band1_fluence = duet.apply_filters(wav, ph_flux, diag=diag, **kwargs).sum() band2_fluence = duet.apply_filters(wav, ph_flux, band=2, diag=diag, **kwargs).sum() if diag: print() print('Compute ABmags in TD bands for blackbody') print('Blackbody temperature: {}'.format(bbtemp)) print('Reference UVW2-band magnitude: {}'.format(swiftmag)) print('Distance: {} (Reference distance is 10 pc)'.format(dist)) print() print('Flux density Swift: {}'.format(fluxden_sw)) print('Distance modulus: {}'.format(distmod)) print('Raw ABmag Swift: {}'.format(magsw)) print('Offset from Swift band: {}'.format(magoff)) print('Fluence band one: {}'.format(band1_fluence)) print('Fluence band two: {}'.format(band2_fluence)) print('') if val: return band1_fluence.value, band2_fluence.value else: return band1_fluence, band2_fluence
def sim_galaxy(patch_size, pixel_size, gal_type=None, gal_params=None, duet=None, band=None, duet_no=None): ''' Return 2D array of a Sersic profile to simulate a galaxy Required inputs: patch_size = Axis sizes of returned galaxy patch in pixels (15,15) pixel_size = Angular size of pixel (6 * ur.arcsec) Optional inputs: gal_type = String that loads a pre-built 'average' galaxy or allows custom definition gal_params = Dictionary of parameters for Sersic model: ... duet = Telescope instance band = duet.bandpass (defaults to DUET1; deprecated) duet_no = integer (1 or 2) for DUET bandpass ''' from astropy.modeling.models import Sersic2D from astroduet.utils import duet_abmag_to_fluence, duet_no_from_band if duet is None: duet = Telescope() if band is None: band = duet.bandpass1 if duet_no is None: duet_no = duet_no_from_band(band) x = np.linspace(-(patch_size[0] // 2), patch_size[0] // 2, patch_size[0]) y = np.linspace(-(patch_size[1] // 2), patch_size[1] // 2, patch_size[1]) x, y = np.meshgrid(x, y) # Takes either a keyword or Sersic profile parameters # Typical galaxy parameters based on Bai et al. (2013) # Hardcoded for now, to-do: take distance as an input if gal_type == 'spiral': # A typical spiral galaxy at 100 Mpc surface_mag = 26.2 * u.ABmag # surface brightness (per arcsec**2) surface_rate = duet.fluence_to_rate( duet_abmag_to_fluence(surface_mag, duet_no, duet=duet)) # surface count rate at r_eff amplitude = surface_rate * pixel_size.value**2 # surface brightness (per pixel) r_eff = 16.5 / pixel_size.value n = 1 theta = 0 ellip = 0.5 x_0, y_0 = r_eff, 0 elif gal_type == 'elliptical': # A typical elliptical galaxy at 100 Mpc surface_mag = 25.0 * u.ABmag surface_rate = duet.fluence_to_rate( duet_abmag_to_fluence(surface_mag, duet_no, duet=duet)) # surface count rate at r_eff amplitude = surface_rate * pixel_size.value**2 # surface brightness (per pixel) r_eff = 12.5 / pixel_size.value n = 4 theta = 0 ellip = 0.5 x_0, y_0 = r_eff, 0 elif gal_type == 'dwarf': # A typical dwarf galaxy at 10 Mpc surface_mag = 25.8 * u.ABmag surface_rate = duet.fluence_to_rate( duet_abmag_to_fluence(surface_mag, duet_no, duet=duet)) # surface count rate at r_eff amplitude = surface_rate * pixel_size.value**2 # surface brightness (per pixel) r_eff = 70 / pixel_size r_eff = r_eff.value n = 4 theta = 0 ellip = 0.5 x_0, y_0 = r_eff, 0 elif (gal_type == 'custom') | (gal_type == None): # Get args from gal_params, default to spiral values surface_mag = gal_params.get('magnitude', 26) * u.ABmag surface_rate = duet.fluence_to_rate( duet_abmag_to_fluence(surface_mag, duet_no, duet=duet)) # surface count rate at r_eff amplitude = surface_rate * pixel_size.value**2 # surface brightness (per pixel) r_eff = gal_params.get('r_eff', 16.5 / pixel_size.value) n = gal_params.get('n', 1) theta = gal_params.get('theta', 0) ellip = gal_params.get('ellip', 0.5) x_0 = gal_params.get('x_0', 16.5 / pixel_size.value) y_0 = gal_params.get('y_0', 0) mod = Sersic2D(amplitude=amplitude, r_eff=r_eff, n=n, x_0=x_0, y_0=y_0, ellip=ellip, theta=theta) gal = mod(x, y) return gal
def convert_sn_model(label, name='NoName', duet=None): ''' Reads in the SNe models, converts them to DUET fluences, and writes out the resulting models to FITS files. Parameters ---------- filename : string Path to SN shock file. Other parameters ---------------- name : string name to use for the model. Default is 'NoName' ''' if duet is None: duet = Telescope() bandone = duet.bandpass1 bandtwo = duet.bandpass2 dist0 = 10*u.pc temptable = \ Table.read(f'{label}_teff.txt', format='ascii', names=['time', 'T']) radiustable = \ Table.read(f'{label}_radius.txt', format='ascii', names=['time', 'R']) table = join(temptable, radiustable) N = len(table['time']) time = table['time'] * u.s shock_lc = Table([time, np.zeros(len(time))*u.ABmag, np.zeros(len(time))*u.ABmag, np.zeros(len(time))*u.ph/(u.s*u.cm**2), np.zeros(len(time))*u.ph/(u.s*u.cm**2)], names=('time', 'mag_D1', 'mag_D2', 'fluence_D1', 'fluence_D2'), meta={'name': name + ' at 10 pc', 'dist0_pc' : '{}'.format(dist0.to(u.pc).value)}) bolflux = (table['T'] * u.K) ** 4 * const.sigma_sb.cgs * ( (table['R'] * u.cm) / dist0.to(u.cm)) ** 2 temps = table['T'] * u.K for k, t, bf in tqdm(list(zip(np.arange(N), temps, bolflux))): band1_mag, band2_mag = bb_abmag(bbtemp=t, bolflux = bf, bandone=bandone, bandtwo=bandtwo, val=True) band1_fluence, band2_fluence = bb_abmag_fluence(bbtemp=t, bolflux=bf) shock_lc[k]['mag_D1'] = band1_mag shock_lc[k]['mag_D2'] = band2_mag shock_lc[k]['fluence_D1'] = band1_fluence.value shock_lc[k]['fluence_D2'] = band2_fluence.value shock_lc['mag_D1'].unit = None shock_lc['mag_D2'].unit = None return shock_lc
def run_daophot(image, threshold, star_tbl, niters=1, snr_lim=5, duet=None, diag=False): ''' Given an image and a PSF, go run DAOPhot PSF-fitting algorithm ''' from photutils.psf import DAOPhotPSFPhotometry, IntegratedGaussianPRF if duet is None: duet = Telescope() fwhm = (duet.psf_fwhm / duet.pixel).to('').value # Fix star table columns star_tbl['x_0'] = star_tbl['x'] star_tbl['y_0'] = star_tbl['y'] # Define a fittable PSF model sigma = fwhm / (2. * np.sqrt(2 * np.log(2))) # Simple Gaussian model to fit #psf_model = IntegratedGaussianPRF(sigma=sigma) #flux_norm = 1 # Use DUET-like PSF #oversample = 2 # Needs to be oversampled but only minimally #duet_psf_os = duet.psf_model(pixel_size=duet.pixel/oversample, x_size=12, y_size=12) # Even numbers work better #psf_model = EPSFModel(duet_psf_os.array,oversampling=oversample) #flux_norm = 1/oversample**2 # A quirk of constructing an oversampled ePSF using photutils psf_model = duet.epsf_model # Temporarily turn off Astropy warnings import warnings from astropy.utils.exceptions import AstropyWarning warnings.simplefilter('ignore', category=AstropyWarning) ## FROM HERE ON NO UNITS ########### # Initialise a Photometry object # This object loops find, fit and subtract threshold = threshold.to(image.unit) photometry = DAOPhotPSFPhotometry(fwhm, threshold.value, fwhm, psf_model, (5, 5), niters=niters, sigma_radius=5, aperture_radius=fwhm) # Problem with _recursive_lookup while fitting (needs latest version of astropy fix to modeling/utils.py) result = photometry(image=image.value, init_guesses=star_tbl) residual_image = photometry.get_residual_image() # Filter results to only keep those with S/N greater than snr_lim (default is 5) result_sig = result[np.abs(result['flux_fit'] / result['flux_unc']) >= snr_lim] if diag: print("PSF-fitting complete") # Turn warnings back on again warnings.simplefilter('default') ## FROM HERE ON YES UNITS ########### result_sig['flux_fit'] = result_sig['flux_fit'] * image.unit result_sig['flux_unc'] = result_sig['flux_unc'] * image.unit return result_sig, residual_image * image.unit
def construct_image(frame, exposure, duet=None, band=None, gal_type=None, gal_params=None, source=None, source_loc=None, sky_rate=None, n_exp=1, duet_no=None): """Construct a simualted image with an optional background galaxy and source. 1. Generate the empty image 2. Add galaxy (see sim_galaxy) 3. Add source (Poisson draw based on source*expossure) 4. Convolve with PSF 5. Rebin to the DUET pixel size. 6. Add in expected background rates per pixel and dark current. 7. Draw Poisson values and add read noise. Parameters ---------- frame : ``numpy.array`` Number of pixel along x and y axis. i.e., frame = np.array([30, 30]) exposure : ``astropy.units.Quantity`` Exposure time used for the light curve Other parameters ---------------- duet : ``astroduet.config.Telescope`` If None, a default one is created band : DUET bandpass (deprecated in favor of duet_no; defaults to DUET1) gal_type : string Default galaxy string ("spiral"/"elliptical") or "custom" w/ Sersic parameters in gal_params gal_params : dict Dictionary of parameters for Sersic model (see sim_galaxy) source : ``astropy.units.Quantity`` Source photon rate in ph / s; can be array for multiple sources source_loc : ``numpy.array`` Coordinates of source(s) relative to frame (values between 0 and 1). If source is an array, source_loc must be the same length. format: np.array([[X1,X2,X3,...,Xn],[Y1,Y2,Y3,...,Yn]]) sky_rate : ``astropy.units.Quantity`` Background photon rate in ph / s / pixel n_exp : int Number of simualted frames to co-add. NB: I don't like this here! duet_no : int (1 or 2) DUET band number (defaults to DUET1) Returns ------- image : array with astropy.units NxM image array with integer number of counts observed per pixel. """ from astroduet.utils import duet_no_from_band assert type( frame ) is np.ndarray, 'construct_image: Please enter frame as a numpy array' # Load telescope parameters: if duet is None: duet = Telescope() if band is None: band = duet.bandpass1 if duet_no is None: duet_no = duet_no_from_band(band) read_noise = duet.read_noise oversample = 6 pixel_size_init = duet.pixel / oversample # Load the PSF kernel. Note that this does NOT HAVE POINTING JITTER! psf_kernel = duet.psf_model(pixel_size=pixel_size_init) # 1. Generate the empty image # Initialise an image, oversampled by the oversample parameter to begin with im_array = np.zeros(frame * oversample) * u.ph / u.s # 2. Add a galaxy? if gal_type is not None: # Get a patch with a simulated galaxy on it gal = sim_galaxy(frame * oversample, pixel_size_init, gal_type=gal_type, gal_params=gal_params, duet=duet, duet_no=duet_no) im_array += gal # 3. Add a source? if source is not None: # Place source as a delta function at the center of the frame if source_loc is None: im_array[im_array.shape[0] // 2 + 1, im_array.shape[1] // 2 + 1] += source # Otherwise place sources at given source locations in frame else: source_inv = np.array([ source_loc[1], source_loc[0] ]) # Invert axes because python is weird that way source_pix = (source_inv.transpose() * np.array(im_array.shape)).transpose().astype(int) im_array[tuple(source_pix)] += source # Result should now be (floats) expected number of photons per pixel per second # in the oversampled imae # 4. Convolve with the PSF im_psf = convolve_fft(im_array.value, psf_kernel) * im_array.unit # Convolve again, now with the pointing jitter (need to re-apply units here as it's lost in convolution) #im_psf = convolve(im_psf_temp, Gaussian2DKernel((duet.jitter_rms/pixel_size_init).value)) * im_array.unit # 5. Bin up the image by oversample parameter to the correct pixel size shape = (frame[0], oversample, frame[1], oversample) im_binned = im_psf.reshape(shape).sum(-1).sum(1) # 6. Add sky background (these are both given in ph / pix / s) if sky_rate is not None: # Add sky rate per pixel across the whole image im_binned += sky_rate # 6b: Add dark current: im_binned += duet.dark_current # Convert to expected counts -- TEMPORARY: .value transform photons in a number im_counts = (im_binned * exposure) # Co-add a number of separate exposures im_final = np.zeros(frame) for i in range(n_exp): # Apply Poisson noise and instrument read noise. Note that read noise here # is im_noise = np.random.poisson(im_counts.value) + \ np.random.normal(loc=0, scale=read_noise,size=im_counts.shape) im_noise = np.floor(im_noise) im_noise[im_noise < 0] = 0 # Add to the co-add im_final += im_noise # Return image return im_final * im_counts.unit
def filter_parameters(duet=None, *args, **kwargs): """ Construct the effective central wavelength and the effective bandpass for the filters. Parameters ---------- Other parameters ---------------- vega : conditional, default False Use the Vega spetrum (9.8e3 K blackbody) to compute values. Otherwise computed "flat" values if quoting AB mags. diag : conditional, default False Show the diagnostic info on the parameters. Examples -------- >>> band1, band2 = filter_parameters() >>> allclose(band1['eff_wave'].value, 198.63858525) True """ from astroduet.config import Telescope if duet is None: duet = Telescope() from astropy.modeling import models from astropy.modeling.blackbody import FNU, FLAM from astropy import units as u import numpy as np vega = kwargs.pop('vega', False) diag = kwargs.pop('diag', False) wave = np.arange(1000, 10000) * u.AA if vega: temp = 9.8e3 * u.K bb = models.BlackBody1D(temperature=temp) flux = bb(wave).to(FLAM, u.spectral_density(wave)) else: flat_model = np.zeros_like(wave.value) flat_model += 1 flat_model *= FNU flux = flat_model.to(FLAM, u.spectral_density(wave)) band1 = duet.apply_filters(wave, flux, band=1, **kwargs) band2 = duet.apply_filters(wave, flux, band=2, **kwargs) λ_eff1 = ((band1 * wave).sum() / (band1.sum())).to(u.nm) λ_eff2 = ((band2 * wave).sum() / (band2.sum())).to(u.nm) dλ = wave[1] - wave[0] t1 = band1 / flux t2 = band2 / flux w1 = (dλ * t1.sum() / t1.max()).to(u.nm) w2 = (dλ * t2.sum() / t2.max()).to(u.nm) band1 = {'eff_wave': λ_eff1, 'eff_width': w1} band2 = {'eff_wave': λ_eff2, 'eff_width': w2} if diag: print('Band1: {0:0.2f} λ_eff, {1:0.2f} W_eff'.format(λ_eff1, w1)) print('Band2: {0:0.2f} λ_eff, {1:0.2f} W_eff'.format(λ_eff2, w2)) return band1, band2
def imsim_srcdetect_combined(run='050719', gal='spiral', zodi='low', nmags=71, sfb=[20, 30], stack=1): """ Run background estimation, image differencing and source detection on stacked simulated images Assumes that the script is run in the directory that contains the run_... directory tree Parameters ---------- run: string (date, as in '050719') To track runs zodi: 'low', 'med' or 'high', default is low gal: 'spiral', 'elliptical', 'dwarf', or 'none' sfb: [sfb_low, sfb_high], default is [20,30] List of lowest and highest surface brightness that have been simulated nmags: float, default is 71 Number of source magnitudes used in image simulations stack: int, default is 1 Number of stacked exposures Returns ------- run_gal_zodi_band.fits: fits table with source detection results """ # Get telescope configuration from teldef file: with open('run_' + run + '/teldef') as origin: for line in origin: if 'DUET Telescope State' in line: tel = line.split(':')[1].strip('\n').strip() # Initialize parameters duet = Telescope(config=tel) # Set up path path1 = 'run_' + run + '/gal_' + gal + '/zodi_' + zodi + '/duet1/' path2 = 'run_' + run + '/gal_' + gal + '/zodi_' + zodi + '/duet2/' # Make galaxy surface brightness array if gal != 'none': sfb_arr = np.arange(sfb[0], sfb[1] + 1.).astype(str) # Make source magnitude array src_arr = np.linspace(20.5 - 0.5 * (nmags - 1) * 0.1, 20.5 + 0.5 * (nmags - 1) * 0.1, num=nmags, endpoint=True) # Currently in steps of 0.1 mag # Set up results table # columns: galaxy mag, source input mag, source input count rate, distance from galaxy center, reference depth, source detected True/False, # if True: retrieved count rate, count rate error; number of false positives tab = Table(np.zeros(9), names=('galmag', 'srcmag', 'src-ctrate', 'dist', 'ref_depth', 'detected', 'ctrate', 'ctrate_err', 'false-pos'), dtype=('f8', 'f8', 'f8', 'f8', 'i8', 'b', 'f8', 'f8', 'i8'), meta={'name': gal + ' - ' + zodi + 'zodi - combined'}) print('Finding sources...') if gal == 'none': reffile1 = run + '_duet1_zodi-' + zodi + '_reference.fits' hdu_ref1 = fits.open(path1 + reffile1) reffile2 = run + '_duet2_zodi-' + zodi + '_reference.fits' hdu_ref2 = fits.open(path2 + reffile2) for srcmag in src_arr: imfile1 = run + '_duet1_zodi-' + zodi + '_stack-' + str( stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits' hdu_im1 = fits.open(path1 + imfile1) imfile2 = run + '_duet2_zodi-' + zodi + '_stack-' + str( stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits' hdu_im2 = fits.open(path2 + imfile2) # Get input countrate src_ctrate1 = duet.fluence_to_rate( duet_abmag_to_fluence(srcmag * u.ABmag, 1, duet=duet)) src_ctrate2 = duet.fluence_to_rate( duet_abmag_to_fluence(srcmag * u.ABmag, 2, duet=duet)) src_ctrate_comb = src_ctrate1 + src_ctrate2 # Run source detection for this set of HDUs: tab = run_srcdetect_combined(hdu_ref1=hdu_ref1, hdu_ref2=hdu_ref2, hdu_im1=hdu_im1, hdu_im2=hdu_im2, tab=tab, duet=duet, sfb=np.nan, srcmag=srcmag, src_ctrate=src_ctrate_comb) hdu_im1.close() hdu_im2.close() hdu_ref1.close() hdu_ref2.close() else: for sfb in sfb_arr: print('SFB: ' + sfb) reffile1 = run + '_duet1_' + gal + '_' + sfb + '_zodi-' + zodi + '_reference.fits' hdu_ref1 = fits.open(path1 + reffile1) reffile2 = run + '_duet2_' + gal + '_' + sfb + '_zodi-' + zodi + '_reference.fits' hdu_ref2 = fits.open(path2 + reffile2) for srcmag in src_arr: imfile1 = run + '_duet1_' + gal + '_' + sfb + '_zodi-' + zodi + '_stack-' + str( stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits' hdu_im1 = fits.open(path1 + imfile1) imfile2 = run + '_duet2_' + gal + '_' + sfb + '_zodi-' + zodi + '_stack-' + str( stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits' hdu_im2 = fits.open(path2 + imfile2) # Get input countrate src_ctrate1 = duet.fluence_to_rate( duet_abmag_to_fluence(srcmag * u.ABmag, 1, duet=duet)) src_ctrate2 = duet.fluence_to_rate( duet_abmag_to_fluence(srcmag * u.ABmag, 2, duet=duet)) src_ctrate_comb = src_ctrate1 + src_ctrate2 # Run source detection for this set of HDUs: tab = run_srcdetect_combined(hdu_ref1=hdu_ref1, hdu_ref2=hdu_ref2, hdu_im1=hdu_im1, hdu_im2=hdu_im2, tab=tab, duet=duet, sfb=float(sfb), srcmag=srcmag, src_ctrate=src_ctrate) hdu_im1.close() hdu_im2.close() hdu_ref1.close() hdu_ref2.close() # Save output table print('Writing file') tab.remove_row(0) tab.write('run' + run + '_gal-' + gal + '_zodi-' + zodi + '_stack-' + str(stack) + '-combined.fits', format='fits', overwrite=True) print('Done')
def imsim(**kwargs): """ Simulate images of sources in galaxies with a range of surface brightnesses Warning! Takes several hours to run (~6 hours for 10 surface brightness levels, nmag=71 and nsrc=100) Will make a directory tree run/galaxy/zodi-level/duet[1,2] in directory where it's run. Parameters ---------- tel: 'config' default is 'baseline' Sets telescope parameters run: string (date, as in '050719', or other identifying string) To track runs gal: 'spiral', 'elliptical', 'dwarf' or 'none', default is spiral Sets Sersic index and size zodi: 'low', 'med' or 'high', default is low Use the medium zodiacal background rate. Overrides low_zodi. sfb: [sfb_low, sfb_high], default is [20,30] List of lowest and highest surface brightness to simulate nmags: int, default is 71 Number of source magnitudes to simulate, in 0.1 mag steps around 20.5 nsrc: int, default is 100 Number of sources to simulate at each source mag stack: int, default is 1 Depth of science image (number of stacked 300s exposures) nref: list, default is [1,3,7,11] List of reference image depths Returns ------- telescope_band_gal_sfb_zodi_src-mag.fits: fits file with simulated images. """ # Deal with kwargs: tel = kwargs.pop('tel', 'baseline') gal = kwargs.pop('gal', 'spiral') zodi = kwargs.pop('zodi', 'low') sfb_lim = kwargs.pop('sfb', [20, 30]) nmags = kwargs.pop('nmags', 71) nsrc = kwargs.pop('nsrc', 100) run = kwargs.pop('run') stack = kwargs.pop('stack', 1) ref_arr = kwargs.pop('nref', [1, 3, 7, 11]) # set some telescope, instrument parameters duet = Telescope(config=tel) # Set/make directories: path1 = sim_path(run=run, gal=gal, zodi=zodi, band='duet1') path2 = sim_path(run=run, gal=gal, zodi=zodi, band='duet2') # Write telescope definition file if it doesn't exist yet for this run: if not os.path.exists('run_' + run + '/teldef'): teldef = duet.info() teldef_file = open('run_' + run + '/teldef', 'w+') teldef_file.write(teldef) teldef_file.close() # Define image simulation parameters exposure = 300 * u.s frame = np.array( [30, 30] ) # Dimensions of the image I'm simulating in DUET pixels (30x30 ~ 3x3 arcmin) oversample = 6 # Hardcoded in construct_image # Get backgrounds if zodi == 'low': [bgd_band1, bgd_band2] = background_pixel_rate(duet, low_zodi=True, diag=False) elif zodi == 'med': [bgd_band1, bgd_band2] = background_pixel_rate(duet, med_zodi=True, diag=False) elif zodi == 'high': [bgd_band1, bgd_band2] = background_pixel_rate(duet, high_zodi=True, diag=False) # Define galaxy: magnitude is placeholder. Sizes are typical at 100 Mpc if gal == 'spiral': reff = 16.5 * u.arcsec gal_params = { 'magnitude': 1, 'r_eff': reff / (duet.pixel / oversample), 'n': 1, 'x_0': 0, 'y_0': 0 } elif gal == 'elliptical': reff = 12.5 * u.arcsec gal_params = { 'magnitude': 1, 'r_eff': reff / (duet.pixel / oversample), 'n': 4, 'x_0': 0, 'y_0': 0 } elif gal == 'dwarf': reff = 7 * u.arcsec gal_params = { 'magnitude': 1, 'r_eff': reff / (duet.pixel / oversample), 'n': 1, 'x_0': 0, 'y_0': 0 } elif gal == 'none': gal_params = None # Make galaxy surface brightness array (if necessary) if gal != 'none': sfb_arr = np.arange(sfb_lim[0], sfb_lim[1] + 1.) # Now in steps of 1 mag # Make srcmag array: srcmag_arr = np.linspace(20.5 - 0.5 * (nmags - 1) * 0.1, 20.5 + 0.5 * (nmags - 1) * 0.1, num=nmags, endpoint=True) # Currently in steps of 0.1 mag # No background galaxy: if gal == 'none': # Make reference images: ref_hdu_1, ref_hdu_2 = run_sim_ref(duet=duet, bkg1=bgd_band1, bkg2=bgd_band2, ref_arr=ref_arr, gal=False, exposure=exposure, frame=frame) # Update headers: ref_hdu_1 = update_header(ref_hdu_1, im_type='reference', zodi=zodi, gal=gal, band='DUET1', nframes=len(ref_arr), exptime=exposure.value) ref_hdu_2 = update_header(ref_hdu_2, im_type='reference', zodi=zodi, gal=gal, band='DUET2', nframes=len(ref_arr), exptime=exposure.value) # Write files ref_filename1 = run + '_duet1_zodi-' + zodi + '_reference.fits' ref_hdu_1.writeto(path1 + '/' + ref_filename1, overwrite=True) ref_filename2 = run + '_duet2_zodi-' + zodi + '_reference.fits' ref_hdu_2.writeto(path2 + '/' + ref_filename2, overwrite=True) # Make source images: for srcmag in srcmag_arr: src_hdu_1, src_hdu_2 = run_sim(duet=duet, bkg1=bgd_band1, bkg2=bgd_band2, stack=stack, srcmag=srcmag, nsrc=nsrc, gal=False, exposure=exposure, frame=frame) # Update headers src_hdu_1 = update_header(src_hdu_1, im_type='source', zodi=zodi, gal=gal, band='DUET1', srcmag=srcmag, nframes=nsrc, exptime=exposure.value) src_hdu_2 = update_header(src_hdu_2, im_type='source', zodi=zodi, gal=gal, band='DUET2', srcmag=srcmag, nframes=nsrc, exptime=exposure.value) # Write file filename1 = run + '_duet1_zodi-' + zodi + '_stack-' + str( stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits' src_hdu_1.writeto(path1 + '/' + filename1, overwrite=True) filename2 = run + '_duet2_zodi-' + zodi + '_stack-' + str( stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits' src_hdu_2.writeto(path2 + '/' + filename2, overwrite=True) # Yes background galaxy: else: for i, sfb in enumerate(sfb_arr): print('Surface brightness level ' + str(i + 1) + ' of ' + str(len(sfb_arr)) + '...') # Calculate count rate: gal_params['magnitude'] = sfb # Make reference images: ref_hdu_1, ref_hdu_2 = run_sim_ref(duet=duet, bkg1=bgd_band1, bkg2=bgd_band2, ref_arr=ref_arr, gal=True, gal_params=gal_params, exposure=exposure, frame=frame) # Update headers: ref_hdu_1 = update_header(ref_hdu_1, im_type='reference', zodi=zodi, gal=gal, band='DUET1', nframes=len(ref_arr), exptime=exposure.value) ref_hdu_2 = update_header(ref_hdu_2, im_type='reference', zodi=zodi, gal=gal, band='DUET2', nframes=len(ref_arr), exptime=exposure.value) # Write files: ref_filename1 = run + '_duet1_' + gal + '_' + str( sfb) + '_zodi-' + zodi + '_reference.fits' ref_hdu_1.writeto(path1 + '/' + ref_filename1, overwrite=True) ref_filename2 = run + '_duet2_' + gal + '_' + str( sfb) + '_zodi-' + zodi + '_reference.fits' ref_hdu_2.writeto(path2 + '/' + ref_filename2, overwrite=True) # Make source images: for srcmag in srcmag_arr: src_hdu_1, src_hdu_2 = run_sim(duet=duet, bkg1=bgd_band1, bkg2=bgd_band2, stack=stack, srcmag=srcmag, nsrc=nsrc, gal=True, gal_params=gal_params, exposure=exposure, frame=frame) # Update headers: src_hdu_1 = update_header(src_hdu_1, im_type='source', zodi=zodi, gal=gal, band='DUET1', srcmag=srcmag, nframes=nsrc, exptime=exposure.value) src_hdu_2 = update_header(src_hdu_2, im_type='source', zodi=zodi, gal=gal, band='DUET2', srcmag=srcmag, nframes=nsrc, exptime=exposure.value) # Write files: filename1 = run + '_duet1_' + gal + '_' + str( sfb) + '_zodi-' + zodi + '_stack-' + str( stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits' src_hdu_1.writeto(path1 + '/' + filename1, overwrite=True) filename2 = run + '_duet2_' + gal + '_' + str( sfb) + '_zodi-' + zodi + '_stack-' + str( stack) + '_src-' + "{:5.2f}".format(srcmag) + '.fits' src_hdu_2.writeto(path2 + '/' + filename2, overwrite=True)