class m5calculations(): def __init__(self, default_y='y4'): """Instantiate the object.""" self.default_y = default_y return def setup_Throughputs(self, rootDir=None, verbose=True): """Read bandpasses and dark sky sed. Call this method first before doing anything else. """ self.filterlist = ('u', 'g', 'r', 'i', 'z', 'y') if rootDir == None: rootDir = os.getenv('LSST_THROUGHPUTS_DEFAULT') if rootDir == None: raise Exception('Either provide "rootDir" or set $LSST_THROUGHPUTS_DEFAULT') # Read in the total transmission curves. self.lsst = {} hardwarecomponentlist = ['detector.dat','lens1.dat', 'lens2.dat', 'lens3.dat', 'm1.dat', 'm2.dat', 'm3.dat'] commoncomponentlist = hardwarecomponentlist + ['atmos_10.dat',] for f in self.filterlist: componentlist = commoncomponentlist + ['filter_' +f +'.dat',] self.lsst[f] = Bandpass() self.lsst[f].readThroughputList(componentlist, rootDir=rootDir, verbose=verbose) # Read in the hardware-only transmission curves (no atmosphere). self.hardware = {} for f in self.filterlist: componentlist = hardwarecomponentlist + ['filter_' +f +'.dat',] self.hardware[f] = Bandpass() self.hardware[f].readThroughputList(componentlist, rootDir=rootDir, verbose=verbose) # Read in the dark sky SED. darkskyfile = 'darksky.dat' self.darksky = Sed() self.darksky.readSED_flambda(os.path.join(rootDir, darkskyfile)) # Set up a flat SED. self.flatsed = Sed() self.flatsed.setFlatSED() return def setup_values(self, expTime=15.0, nexp=2., instrument_noise=None, instrument_noise_visit=None): """Set appropriate default values. Allows user to change expTime, nexp, or instrument_noise. Call this method second - and potentially many times. The exposure time is used to calculate dark current noise, m5 and Cm values, as well as ZP_t/ZP_h. These should be separated out when I have more time, to allow variable expTime in calcM5 below. """ # exposure time (seconds) self.expTime = expTime # number of exposures per visit (for calculating total m5 per visit rather than per exposure) self.nexp = nexp # Gain, electrons/adu. self.gain = 2.3 if instrument_noise != None: self.instrument_noise = instrument_noise self.othernoise = 0.0 self.rdnoise = self.instrument_noise elif instrument_noise_visit != None: self.instrument_noise = instrument_noise_visit / numpy.sqrt(self.nexp) self.othernoise = 0.0 self.rdnoise = self.instrument_noise else: # othernoise == camera electronics noise (electrons/exposure/pixel) self.othernoise = 3.9 # actual readnoise straight off the camera (electrons/exposure/pixel) self.rdnoise = 5.9 # total instrumental noise (camera readnoise + other). electrons/exposure/pixel. # add 0.5 * gain to (allow for potential undersampling of readnoise. with gain*0.5 term) #self.instrument_noise = numpy.sqrt(self.othernoise**2 + self.rdnoise**2 + (self.gain*0.5)**2) self.instrument_noise = numpy.sqrt(self.othernoise**2 + self.rdnoise**2) # Dark current, electrons/pix/second. self.dark_current = 0.2 # Plate scale, arcseconds per pixel. self.platescale = 0.2 # Telescope primary mirror diameter, effective collecting area (cm^2). self.effarea = numpy.pi*(6.5*100/2.0)**2 # Set default y band (for inputs where only 'y' is specified). self.default_y = 'y4' # Calculate Tb / Sb (integral of transmission/wavelength) as in LSE-40 (Table 7), to calculate kAtm. Tb = {} Sb = {} self.kAtm = {} stepsize = self.lsst[self.filterlist[0]].wavelen[1] - self.lsst[self.filterlist[0]].wavelen[0] for f in self.filterlist: self.kAtm[f] = [0.0, 0.0] Tb[f] = (self.lsst[f].sb / self.lsst[f].wavelen)*stepsize Sb[f] = (self.hardware[f].sb / self.hardware[f].wavelen)*stepsize if f.startswith('y'): condition = (self.lsst[f].wavelen >= 975) | (self.lsst[f].wavelen <= 800) self.kAtm[f][0] = -2.5*numpy.log10(Tb[f][condition].sum() / Sb[f][condition].sum()) condition = (self.lsst[f].wavelen > 800) & (self.lsst[f].wavelen < 975) self.kAtm[f][1] = -2.5*numpy.log10(Tb[f][condition].sum() / Sb[f][condition].sum()) else: self.kAtm[f][0] = -2.5*numpy.log10(Tb[f].sum() / Sb[f].sum()) # Calculate telescope zeropoints. self.zpT = {} self.zpH = {} for f in self.filterlist: self.zpT[f] = self.lsst[f].calcZP_t(expTime=self.nexp*self.expTime, effarea=self.effarea, gain=self.gain) self.zpH[f] = self.hardware[f].calcZP_t(expTime=self.nexp*self.expTime, effarea=self.effarea, gain=self.gain) return def print_values(self): """Print a report of the values used in the maglimit calculations.""" # Calculate some additional stuff which can be printed out. self.C_m = {} self.darkskymags = {} self.m5 = {} for f in self.filterlist: self.darkskymags[f] = self.darksky.calcMag(self.hardware[f]) self.m5[f] = self.lsst[f].calcM5(self.darksky, self.hardware[f], expTime=self.expTime, nexp=self.nexp, readnoise=self.rdnoise, othernoise=self.othernoise, darkcurrent=self.dark_current, gain=self.gain, effarea=self.effarea, seeing = 0.7, platescale = self.platescale) self.C_m[f] = self.m5[f] - 0.50*(self.darkskymags[f] - 21.0) # Start printing stuff to screen. print 'Exposure time ', self.expTime print 'Number of exposures per visit ', self.nexp print 'Gain ', self.gain print 'Camera readnoise ', self.rdnoise, ' and other readnoise ', self.othernoise print 'Instrumental noise per exposure ', self.instrument_noise print 'Dark current per second per pixel ', self.dark_current print 'Dark current per exposure ', self.dark_current*self.expTime print 'Total camera noise per visit (e/pix/visit) ', \ numpy.sqrt(self.nexp*self.expTime*self.dark_current + self.nexp*(self.instrument_noise)**2) print 'Platescale ', self.platescale print 'Telescope effective area ', self.effarea print 'Scaling relation C_m and kAtm values: ' for f in self.filterlist: print '\t \tin filter ', f, ' C_m=', self.C_m[f], ' kAtm= ', self.kAtm[f] print 'C_m=', self.C_m print 'kAtm=', self.kAtm print 'Telescope and Hardware zeropoints (%f sec visit):' %(self.expTime*self.nexp) for f in self.filterlist: print '\t\tin filter ', f, ' zpT = ', self.zpT[f], ' zpH = ', self.zpH[f] print 'Dark sky m5 limits :' for f in self.filterlist: print '\t\tin filter ', f, ' m5 = ', self.m5[f] print 'Dark sky noise (seeing=0.7 arcsec, @ zenith) & camera noise (both in electrons | ADU):' for f in self.filterlist: skynoise = numpy.sqrt(self.darksky.calcADU(self.hardware[f], expTime=self.nexp*self.expTime, gain=self.gain, effarea=self.effarea) \ * self.platescale**2 * self.gain) # electrons instnoise = numpy.sqrt(self.nexp *(self.dark_current*self.expTime + self.instrument_noise**2)) #electrons print '\t\tin filter ', f, ' skynoise = ', skynoise, '|', skynoise/self.gain, ' instnoise = ', instnoise, '|', instnoise/self.gain return def check_filter(self, filter): """Check filter array for consistency with internal set. Basically this means replace 'y' with the default y band choice. """ # Might have to generate a new filter array, if the length of the previous values was too short to hold default_y. newfilter = numpy.empty(len(filter), dtype=('str', len(self.default_y))) condition = (filter == 'y') newfilter[condition] = self.default_y condition = (filter != 'y') newfilter[condition] = filter[condition] filter = newfilter filters_used = numpy.unique(filter) for f in filters_used: if f not in self.filterlist: print 'Having a problem with %s, which has length %d (spaces?)' %(f, len(f)) indices = numpy.where(filter==f) print 'This pops up at observation(s) ', indices raise Exception('I do not recognize filter %s' %(f)) return filter def calc_maglimit(self, seeing, skybrightness, filter, airmass, snr=5.0): """Calculate limiting magnitude at snr, for nexp/expTime/gain/instNoise/zeropoint values of class, under conditions of seeing (arcseconds) and skybrightness (mag/arcsecond^2). Returns mag limit. """ # neff = 'effective' pixel area for a point source. (see LSE-40 - SNR doc - eqn 31). neff = 2.436 * (seeing/self.platescale)**2 # Calculate sky counts (counts/pixel/exp) in this bandpass from the skybrightness (mag/arcsecond^2). filters_used = numpy.unique(filter) skycounts = numpy.zeros(len(skybrightness), float) for f in filters_used: condition = (filter == f) # Convert skycounts from mags/''sq to counts/''sq for visit. skycounts[condition] = (10.**(-0.4*(skybrightness[condition] - self.zpH[f]))) # Convert to skycounts per pixel. skycounts = skycounts * self.platescale**2 #(mag/pixel) # Calculate the sky noise (squared) in ADU. skynoise_sq = skycounts / self.gain # Calculate noise (squared) from the instrument, converting result to ADU. instnoise_sq = self.nexp*(self.dark_current*self.expTime + self.instrument_noise**2) / (self.gain**2.0) # see equation 42 from SNR doc noise_sq = (skycounts / self.gain + instnoise_sq) * neff # Translate this to the required counts for a source using equations 45/46 from SNR doc. # Counts are in ADU using this formula. counts = (snr**2.0)/self.gain/2.0 + numpy.sqrt((snr**4.0)/(self.gain)**2/4.0 + (snr**2.0)*noise_sq) # And translate to counts, at this airmass in this filter. # Note we did not have to correct for extinction for the skycounts, because the sky background # is already extinction-corrected (which is part of the reason we must use hardware ZP only). mags = numpy.zeros(len(counts), 'float') for f in filters_used: condition = (filter == f) # Convert to magnitudes mags[condition] = -2.5*numpy.log10(counts[condition]) + self.zpT[f] # Correct for atmospheric extinction (note there is already a factor of X=1 in the zeropoint). mags[condition] = (mags[condition] - self.kAtm[f][0]*(airmass[condition]-1) - self.kAtm[f][1]*numpy.sqrt(airmass[condition]-1)) return mags
def ImportSED(filename,d_lambda): sed = Sed() sed.readSED_flambda(filename) sed.synchronizeSED(wavelen_step = d_lambda) return(sed)
class m5calculations(): def __init__(self, default_y='y4'): """Instantiate the object.""" self.default_y = default_y return def setup_Throughputs(self, rootDir=None, verbose=True): """Read bandpasses and dark sky sed. Call this method first before doing anything else. """ self.filterlist = ('u', 'g', 'r', 'i', 'z', 'y') if rootDir == None: rootDir = os.getenv('LSST_THROUGHPUTS_DEFAULT') if rootDir == None: raise Exception( 'Either provide "rootDir" or set $LSST_THROUGHPUTS_DEFAULT') # Read in the total transmission curves. self.lsst = {} hardwarecomponentlist = [ 'detector.dat', 'lens1.dat', 'lens2.dat', 'lens3.dat', 'm1.dat', 'm2.dat', 'm3.dat' ] commoncomponentlist = hardwarecomponentlist + [ 'atmos_10.dat', ] for f in self.filterlist: componentlist = commoncomponentlist + [ 'filter_' + f + '.dat', ] self.lsst[f] = Bandpass() self.lsst[f].readThroughputList(componentlist, rootDir=rootDir, verbose=verbose) # Read in the hardware-only transmission curves (no atmosphere). self.hardware = {} for f in self.filterlist: componentlist = hardwarecomponentlist + [ 'filter_' + f + '.dat', ] self.hardware[f] = Bandpass() self.hardware[f].readThroughputList(componentlist, rootDir=rootDir, verbose=verbose) # Read in the dark sky SED. darkskyfile = 'darksky.dat' self.darksky = Sed() self.darksky.readSED_flambda(os.path.join(rootDir, darkskyfile)) # Set up a flat SED. self.flatsed = Sed() self.flatsed.setFlatSED() return def setup_values(self, expTime=15.0, nexp=2., instrument_noise=None, instrument_noise_visit=None): """Set appropriate default values. Allows user to change expTime, nexp, or instrument_noise. Call this method second - and potentially many times. The exposure time is used to calculate dark current noise, m5 and Cm values, as well as ZP_t/ZP_h. These should be separated out when I have more time, to allow variable expTime in calcM5 below. """ # exposure time (seconds) self.expTime = expTime # number of exposures per visit (for calculating total m5 per visit rather than per exposure) self.nexp = nexp # Gain, electrons/adu. self.gain = 2.3 if instrument_noise != None: self.instrument_noise = instrument_noise self.othernoise = 0.0 self.rdnoise = self.instrument_noise elif instrument_noise_visit != None: self.instrument_noise = instrument_noise_visit / numpy.sqrt( self.nexp) self.othernoise = 0.0 self.rdnoise = self.instrument_noise else: # othernoise == camera electronics noise (electrons/exposure/pixel) self.othernoise = 3.9 # actual readnoise straight off the camera (electrons/exposure/pixel) self.rdnoise = 5.9 # total instrumental noise (camera readnoise + other). electrons/exposure/pixel. # add 0.5 * gain to (allow for potential undersampling of readnoise. with gain*0.5 term) #self.instrument_noise = numpy.sqrt(self.othernoise**2 + self.rdnoise**2 + (self.gain*0.5)**2) self.instrument_noise = numpy.sqrt(self.othernoise**2 + self.rdnoise**2) # Dark current, electrons/pix/second. self.dark_current = 0.2 # Plate scale, arcseconds per pixel. self.platescale = 0.2 # Telescope primary mirror diameter, effective collecting area (cm^2). self.effarea = numpy.pi * (6.5 * 100 / 2.0)**2 # Set default y band (for inputs where only 'y' is specified). self.default_y = 'y4' # Calculate Tb / Sb (integral of transmission/wavelength) as in LSE-40 (Table 7), to calculate kAtm. Tb = {} Sb = {} self.kAtm = {} stepsize = self.lsst[self.filterlist[0]].wavelen[1] - self.lsst[ self.filterlist[0]].wavelen[0] for f in self.filterlist: self.kAtm[f] = [0.0, 0.0] Tb[f] = (self.lsst[f].sb / self.lsst[f].wavelen) * stepsize Sb[f] = (self.hardware[f].sb / self.hardware[f].wavelen) * stepsize if f.startswith('y'): condition = (self.lsst[f].wavelen >= 975) | (self.lsst[f].wavelen <= 800) self.kAtm[f][0] = -2.5 * numpy.log10( Tb[f][condition].sum() / Sb[f][condition].sum()) condition = (self.lsst[f].wavelen > 800) & (self.lsst[f].wavelen < 975) self.kAtm[f][1] = -2.5 * numpy.log10( Tb[f][condition].sum() / Sb[f][condition].sum()) else: self.kAtm[f][0] = -2.5 * numpy.log10(Tb[f].sum() / Sb[f].sum()) # Calculate telescope zeropoints. self.zpT = {} self.zpH = {} for f in self.filterlist: self.zpT[f] = self.lsst[f].calcZP_t(expTime=self.nexp * self.expTime, effarea=self.effarea, gain=self.gain) self.zpH[f] = self.hardware[f].calcZP_t(expTime=self.nexp * self.expTime, effarea=self.effarea, gain=self.gain) return def print_values(self): """Print a report of the values used in the maglimit calculations.""" # Calculate some additional stuff which can be printed out. self.C_m = {} self.darkskymags = {} self.m5 = {} for f in self.filterlist: self.darkskymags[f] = self.darksky.calcMag(self.hardware[f]) self.m5[f] = self.lsst[f].calcM5(self.darksky, self.hardware[f], expTime=self.expTime, nexp=self.nexp, readnoise=self.rdnoise, othernoise=self.othernoise, darkcurrent=self.dark_current, gain=self.gain, effarea=self.effarea, seeing=0.7, platescale=self.platescale) self.C_m[f] = self.m5[f] - 0.50 * (self.darkskymags[f] - 21.0) # Start printing stuff to screen. print 'Exposure time ', self.expTime print 'Number of exposures per visit ', self.nexp print 'Gain ', self.gain print 'Camera readnoise ', self.rdnoise, ' and other readnoise ', self.othernoise print 'Instrumental noise per exposure ', self.instrument_noise print 'Dark current per second per pixel ', self.dark_current print 'Dark current per exposure ', self.dark_current * self.expTime print 'Total camera noise per visit (e/pix/visit) ', \ numpy.sqrt(self.nexp*self.expTime*self.dark_current + self.nexp*(self.instrument_noise)**2) print 'Platescale ', self.platescale print 'Telescope effective area ', self.effarea print 'Scaling relation C_m and kAtm values: ' for f in self.filterlist: print '\t \tin filter ', f, ' C_m=', self.C_m[ f], ' kAtm= ', self.kAtm[f] print 'C_m=', self.C_m print 'kAtm=', self.kAtm print 'Telescope and Hardware zeropoints (%f sec visit):' % ( self.expTime * self.nexp) for f in self.filterlist: print '\t\tin filter ', f, ' zpT = ', self.zpT[ f], ' zpH = ', self.zpH[f] print 'Dark sky m5 limits :' for f in self.filterlist: print '\t\tin filter ', f, ' m5 = ', self.m5[f] print 'Dark sky noise (seeing=0.7 arcsec, @ zenith) & camera noise (both in electrons | ADU):' for f in self.filterlist: skynoise = numpy.sqrt(self.darksky.calcADU(self.hardware[f], expTime=self.nexp*self.expTime, gain=self.gain, effarea=self.effarea) \ * self.platescale**2 * self.gain) # electrons instnoise = numpy.sqrt(self.nexp * (self.dark_current * self.expTime + self.instrument_noise**2)) #electrons print '\t\tin filter ', f, ' skynoise = ', skynoise, '|', skynoise / self.gain, ' instnoise = ', instnoise, '|', instnoise / self.gain return def check_filter(self, filter): """Check filter array for consistency with internal set. Basically this means replace 'y' with the default y band choice. """ # Might have to generate a new filter array, if the length of the previous values was too short to hold default_y. newfilter = numpy.empty(len(filter), dtype=('str', len(self.default_y))) condition = (filter == 'y') newfilter[condition] = self.default_y condition = (filter != 'y') newfilter[condition] = filter[condition] filter = newfilter filters_used = numpy.unique(filter) for f in filters_used: if f not in self.filterlist: print 'Having a problem with %s, which has length %d (spaces?)' % ( f, len(f)) indices = numpy.where(filter == f) print 'This pops up at observation(s) ', indices raise Exception('I do not recognize filter %s' % (f)) return filter def calc_maglimit(self, seeing, skybrightness, filter, airmass, snr=5.0): """Calculate limiting magnitude at snr, for nexp/expTime/gain/instNoise/zeropoint values of class, under conditions of seeing (arcseconds) and skybrightness (mag/arcsecond^2). Returns mag limit. """ # neff = 'effective' pixel area for a point source. (see LSE-40 - SNR doc - eqn 31). neff = 2.436 * (seeing / self.platescale)**2 # Calculate sky counts (counts/pixel/exp) in this bandpass from the skybrightness (mag/arcsecond^2). filters_used = numpy.unique(filter) skycounts = numpy.zeros(len(skybrightness), float) for f in filters_used: condition = (filter == f) # Convert skycounts from mags/''sq to counts/''sq for visit. skycounts[condition] = (10.**( -0.4 * (skybrightness[condition] - self.zpH[f]))) # Convert to skycounts per pixel. skycounts = skycounts * self.platescale**2 #(mag/pixel) # Calculate the sky noise (squared) in ADU. skynoise_sq = skycounts / self.gain # Calculate noise (squared) from the instrument, converting result to ADU. instnoise_sq = self.nexp * (self.dark_current * self.expTime + self.instrument_noise**2) / (self.gain** 2.0) # see equation 42 from SNR doc noise_sq = (skycounts / self.gain + instnoise_sq) * neff # Translate this to the required counts for a source using equations 45/46 from SNR doc. # Counts are in ADU using this formula. counts = (snr**2.0) / self.gain / 2.0 + numpy.sqrt( (snr**4.0) / (self.gain)**2 / 4.0 + (snr**2.0) * noise_sq) # And translate to counts, at this airmass in this filter. # Note we did not have to correct for extinction for the skycounts, because the sky background # is already extinction-corrected (which is part of the reason we must use hardware ZP only). mags = numpy.zeros(len(counts), 'float') for f in filters_used: condition = (filter == f) # Convert to magnitudes mags[condition] = -2.5 * numpy.log10( counts[condition]) + self.zpT[f] # Correct for atmospheric extinction (note there is already a factor of X=1 in the zeropoint). mags[condition] = ( mags[condition] - self.kAtm[f][0] * (airmass[condition] - 1) - self.kAtm[f][1] * numpy.sqrt(airmass[condition] - 1)) return mags
def ImportSED(filename, d_lambda): sed = Sed() sed.readSED_flambda(filename) sed.synchronizeSED(wavelen_step=d_lambda) return (sed)
Sb[f] = (hardware[f].sb / hardware[f].wavelen).sum() * hardware[f].wavelen_step Tb[f] = (total[f].sb / total[f].wavelen).sum() * total[f].wavelen_step writestring1 = 'Sb: ' writestring2 = 'Tb: ' for f in filterlist: writestring1 += '%s %.3f ' %(f, Sb[f]) writestring2 += '%s %.3f ' %(f, Tb[f]) print writestring1 print writestring2 # Set up to calculate m5. # Read in the dark sky SED darksky = Sed() darksky.readSED_flambda(os.path.join(throughputsDir, 'darksky.dat')) # Set up a range of exposure times (per exposure, not per visit) exptimes = numpy.arange(0.1, 100., 5.) # Set a range of readnoise values and zero out other noise contributions (I think this is what you want, to isolate readnoise completely) # (plus the camera team includes 'othernoise' into their 'instrumental noise' value .. this is another potential source of confusion when talking # to them about this .. usually when they say 'readnoise' they actually mean the full instrumental noise, but do not consider dark current) othernoise = 0 darkcurrent = 0 readnoises = [10., 13.] # noise per VISIT (so set nexp=1 and then exptime = visit time) nexp = 1 # Calculate m5 values for each of these exposure times. for exptime in exptimes: for readnoise in readnoises: writestring = 'Exptime %.3f Nexp %d Instnoise %.1f --M5' %(exptime, nexp, readnoise)