Coverage for python/lsst/sims/photUtils/SignalToNoise.py : 18%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import print_function
2from __future__ import absolute_import
3import numpy
4from .Sed import Sed
5from .Bandpass import Bandpass
6from . import LSSTdefaults
8__all__ = ["FWHMeff2FWHMgeom", "FWHMgeom2FWHMeff",
9 "calcNeff", "calcInstrNoiseSq", "calcTotalNonSourceNoiseSq", "calcSNR_sed",
10 "calcM5", "calcSkyCountsPerPixelForM5", "calcGamma", "calcSNR_m5",
11 "calcAstrometricError", "magErrorFromSNR", "calcMagError_m5", "calcMagError_sed"]
13def FWHMeff2FWHMgeom(FWHMeff):
14 """
15 Convert FWHMeff to FWHMgeom.
16 This conversion was calculated by Bo Xin and Zeljko Ivezic (and will be in an update on the LSE-40 and overview papers).
18 @param [in] FWHMeff (the single-gaussian equivalent FWHM value, appropriate for calcNeff) in arcseconds
20 @param [out] FWHMgeom (the geometric FWHM value, as measured from a typical PSF profile) in arcseconds
21 """
22 FWHMgeom = 0.822*FWHMeff + 0.052
23 return FWHMgeom
25def FWHMgeom2FWHMeff(FWHMgeom):
26 """
27 Convert FWHMgeom to FWHMeff.
28 This conversion was calculated by Bo Xin and Zeljko Ivezic (and will be in an update on the LSE-40 and overview papers).
30 @param [in] FWHMgeom (the geometric FWHM value, as measured from a typical PSF profile) in arcseconds
32 @param [out] FWHMeff (the single-gaussian equivalent FWHM value, appropriate for calcNeff) in arcseconds
33 """
34 FWHMeff = (FWHMgeom - 0.052)/0.822
35 return FWHMeff
37def calcNeff(FWHMeff, platescale):
38 """
39 Calculate the effective number of pixels in a single gaussian PSF.
40 This equation comes from LSE-40, equation 27.
41 https://docushare.lsstcorp.org/docushare/dsweb/ImageStoreViewer/LSE-40
44 @param [in] FWHMeff in arcseconds
45 (the width of a single-gaussian that produces correct Neff for typical PSF profile)
47 @param [in] platescale in arcseconds per pixel
49 @param [out] the effective number of pixels contained in the PSF
51 The FWHMeff is a way to represent the equivalent seeing value, if the
52 atmosphere could be simply represented as a single gaussian (instead of a more
53 complicated von Karman profile for the atmosphere, convolved properly with the
54 telescope hardware additional blurring of 0.4").
55 A translation from the geometric FWHM to the FWHMeff is provided in FWHMgeom2FWHMeff.
56 """
57 return 2.266*(FWHMeff/platescale)**2
60def calcInstrNoiseSq(photParams):
61 """
62 Combine all of the noise due to intrumentation into one value
64 @param [in] photParams is an instantiation of the
65 PhotometricParameters class that carries details about the
66 photometric response of the telescope.
68 @param [out] The noise due to all of these sources added in quadrature
69 in ADU counts
70 """
72 # instrumental squared noise in electrons
73 instNoiseSq = photParams.nexp*photParams.readnoise**2 + \
74 photParams.darkcurrent*photParams.exptime*photParams.nexp + \
75 photParams.nexp*photParams.othernoise**2
77 # convert to ADU counts
78 instNoiseSq = instNoiseSq/(photParams.gain*photParams.gain)
80 return instNoiseSq
83def calcTotalNonSourceNoiseSq(skySed, hardwarebandpass, photParams, FWHMeff):
84 """
85 Calculate the noise due to things that are not the source being observed
86 (i.e. intrumentation and sky background)
88 @param [in] skySed -- an instantiation of the Sed class representing the sky
89 (normalized so that skySed.calcMag() gives the sky brightness in magnitudes
90 per square arcsecond)
92 @param [in] hardwarebandpass -- an instantiation of the Bandpass class representing
93 just the instrumentation throughputs
95 @param [in] photParams is an instantiation of the
96 PhotometricParameters class that carries details about the
97 photometric response of the telescope.
99 @param [in] FWHMeff in arcseconds
101 @param [out] total non-source noise squared (in ADU counts)
102 (this is simga^2_tot * neff in equation 41 of the SNR document
103 https://docushare.lsstcorp.org/docushare/dsweb/ImageStoreViewer/LSE-40 )
104 """
106 # Calculate the effective number of pixels for double-Gaussian PSF
107 neff = calcNeff(FWHMeff, photParams.platescale)
109 # Calculate the counts from the sky.
110 # We multiply by two factors of the platescale because we expect the
111 # skySed to be normalized such that calcADU gives counts per
112 # square arc second, and we need to convert to counts per pixel.
114 skycounts = skySed.calcADU(hardwarebandpass, photParams=photParams) \
115 * photParams.platescale * photParams.platescale
117 # Calculate the square of the noise due to instrumental effects.
118 # Include the readout noise as many times as there are exposures
120 noise_instr_sq = calcInstrNoiseSq(photParams=photParams)
122 # Calculate the square of the noise due to sky background poisson noise
123 noise_sky_sq = skycounts/photParams.gain
125 # Discount error in sky measurement for now
126 noise_skymeasurement_sq = 0
128 total_noise_sq = neff*(noise_sky_sq + noise_instr_sq + noise_skymeasurement_sq)
130 return total_noise_sq
133def calcSkyCountsPerPixelForM5(m5target, totalBandpass, photParams, FWHMeff=None):
134 """
135 Calculate the number of sky counts per pixel expected for a given
136 value of the 5-sigma limiting magnitude (m5)
138 The 5-sigma limiting magnitude (m5) for an observation is
139 determined by a combination of the telescope and camera parameters
140 (such as diameter of the mirrors and the readnoise) together with the
141 sky background.
143 @param [in] the desired value of m5
145 @param [in] totalBandpass is an instantiation of the Bandpass class
146 representing the total throughput of the telescope (instrumentation
147 plus atmosphere)
149 @param [in] photParams is an instantiation of the
150 PhotometricParameters class that carries details about the
151 photometric response of the telescope.
153 @param [in] FWHMeff in arcseconds
155 @param [out] returns the expected number of sky counts per pixel
156 """
158 if FWHMeff is None:
159 FWHMeff = LSSTdefaults().FWHMeff('r')
161 # instantiate a flat SED
162 flatSed = Sed()
163 flatSed.setFlatSED()
165 # normalize the SED so that it has a magnitude equal to the desired m5
166 fNorm = flatSed.calcFluxNorm(m5target, totalBandpass)
167 flatSed.multiplyFluxNorm(fNorm)
168 sourceCounts = flatSed.calcADU(totalBandpass, photParams=photParams)
170 # calculate the effective number of pixels for a double-Gaussian PSF
171 neff = calcNeff(FWHMeff, photParams.platescale)
173 # calculate the square of the noise due to the instrument
174 noise_instr_sq = calcInstrNoiseSq(photParams=photParams)
176 # now solve equation 41 of the SNR document for the neff * sigma_total^2 term
177 # given snr=5 and counts as calculated above
178 # SNR document can be found at
179 # https://docushare.lsstcorp.org/docushare/dsweb/ImageStoreViewer/LSE-40
181 nSigmaSq = (sourceCounts*sourceCounts)/25.0 - sourceCounts/photParams.gain
183 skyNoiseTarget = nSigmaSq/neff - noise_instr_sq
184 skyCountsTarget = skyNoiseTarget*photParams.gain
186 # TODO:
187 # This method should throw an error if skyCountsTarget is negative
188 # unfortunately, that currently happens for default values of
189 # m5 as taken from arXiv:0805.2366, table 2. Adding the error
190 # should probably wait for a later issue in which we hash out what
191 # the units are for all of the parameters stored in PhotometricDefaults.
193 return skyCountsTarget
196def calcM5(skysed, totalBandpass, hardware, photParams, FWHMeff=None):
197 """
198 Calculate the AB magnitude of a 5-sigma above sky background source.
200 The 5-sigma limiting magnitude (m5) for an observation is determined by
201 a combination of the telescope and camera parameters (such as diameter
202 of the mirrors and the readnoise) together with the sky background. This
203 method (calcM5) calculates the expected m5 value for an observation given
204 a sky background Sed and hardware parameters.
206 @param [in] skysed is an instantiation of the Sed class representing
207 sky emission, normalized so that skysed.calcMag gives the sky brightness
208 in magnitudes per square arcsecond.
210 @param [in] totalBandpass is an instantiation of the Bandpass class
211 representing the total throughput of the telescope (instrumentation
212 plus atmosphere)
214 @param [in] hardware is an instantiation of the Bandpass class representing
215 the throughput due solely to instrumentation.
217 @param [in] photParams is an instantiation of the
218 PhotometricParameters class that carries details about the
219 photometric response of the telescope.
221 @param [in] FWHMeff in arcseconds
223 @param [out] returns the value of m5 for the given bandpass and sky SED
224 """
225 # This comes from equation 45 of the SNR document (v1.2, May 2010)
226 # https://docushare.lsstcorp.org/docushare/dsweb/ImageStoreViewer/LSE-40
228 if FWHMeff is None:
229 FWHMeff = LSSTdefaults().FWHMeff('r')
231 # create a flat fnu source
232 flatsource = Sed()
233 flatsource.setFlatSED()
234 snr = 5.0
235 v_n = calcTotalNonSourceNoiseSq(skysed, hardware, photParams, FWHMeff)
237 counts_5sigma = (snr**2)/2.0/photParams.gain + \
238 numpy.sqrt((snr**4)/4.0/photParams.gain + (snr**2)*v_n)
240 # renormalize flatsource so that it has the required counts to be a 5-sigma detection
241 # given the specified background
242 counts_flat = flatsource.calcADU(totalBandpass, photParams=photParams)
243 flatsource.multiplyFluxNorm(counts_5sigma/counts_flat)
245 # Calculate the AB magnitude of this source.
246 mag_5sigma = flatsource.calcMag(totalBandpass)
247 return mag_5sigma
250def magErrorFromSNR(snr):
251 """
252 convert flux signal to noise ratio to an error in magnitude
254 @param [in] snr is the signal to noise ratio in flux
256 @param [out] the resulting error in magnitude
257 """
259 #see www.ucolick.org/~bolte/AY257/s_n.pdf section 3.1
260 return 2.5*numpy.log10(1.0+1.0/snr)
263def calcGamma(bandpass, m5, photParams):
265 """
266 Calculate the gamma parameter used for determining photometric
267 signal to noise in equation 5 of the LSST overview paper
268 (arXiv:0805.2366)
270 @param [in] bandpass is an instantiation of the Bandpass class
271 representing the bandpass for which you desire to calculate the
272 gamma parameter
274 @param [in] m5 is the magnitude at which a 5-sigma detection occurs
275 in this Bandpass
277 @param [in] photParams is an instantiation of the
278 PhotometricParameters class that carries details about the
279 photometric response of the telescope.
281 @param [out] gamma
282 """
283 # This is based on the LSST SNR document (v1.2, May 2010)
284 # https://docushare.lsstcorp.org/docushare/dsweb/ImageStoreViewer/LSE-40
285 # as well as equations 4-6 of the overview paper (arXiv:0805.2366)
287 # instantiate a flat SED
288 flatSed = Sed()
289 flatSed.setFlatSED()
291 # normalize the SED so that it has a magnitude equal to the desired m5
292 fNorm = flatSed.calcFluxNorm(m5, bandpass)
293 flatSed.multiplyFluxNorm(fNorm)
294 counts = flatSed.calcADU(bandpass, photParams=photParams)
296 # The expression for gamma below comes from:
297 #
298 # 1) Take the approximation N^2 = N0^2 + alpha S from footnote 88 in the overview paper
299 # where N is the noise in flux of a source, N0 is the noise in flux due to sky brightness
300 # and instrumentation, S is the number of counts registered from the source and alpha
301 # is some constant
302 #
303 # 2) Divide by S^2 and demand that N/S = 0.2 for a source detected at m5. Solve
304 # the resulting equation for alpha in terms of N0 and S5 (the number of counts from
305 # a source at m5)
306 #
307 # 3) Substitute this expression for alpha back into the equation for (N/S)^2
308 # for a general source. Re-factor the equation so that it looks like equation
309 # 5 of the overview paper (note that x = S5/S). This should give you gamma = (N0/S5)^2
310 #
311 # 4) Solve equation 41 of the SNR document for the neff * sigma_total^2 term
312 # given snr=5 and counts as calculated above. Note that neff * sigma_total^2
313 # is N0^2 in the equation above
314 #
315 # This should give you
317 gamma = 0.04 - 1.0/(counts*photParams.gain)
319 return gamma
322def calcSNR_m5(magnitude, bandpass, m5, photParams, gamma=None):
323 """
324 Calculate signal to noise in flux using the model from equation (5) of arXiv:0805.2366
326 @param [in] magnitude of the sources whose signal to noise you are calculating
327 (can be a numpy array)
329 @param [in] bandpass (an instantiation of the class Bandpass) in which the magnitude
330 was calculated
332 @param [in] m5 is the 5-sigma limiting magnitude for the bandpass
334 @param [in] photParams is an instantiation of the
335 PhotometricParameters class that carries details about the
336 photometric response of the telescope.
338 @param [in] gamma (optional) is the gamma parameter from equation(5) of
339 arXiv:0805.2366. If not provided, this method will calculate it.
341 @param [out] snr is the signal to noise ratio corresponding to
342 the input magnitude.
344 @param [out] gamma is the calculated gamma parameter for the
345 bandpass used here (in case the user wants to call this method again).
347 Note: You can also pass in a numpy array of magnitudes calculated
348 in the same bandpass with the same m5 and get a numpy array of SNR out.
349 """
351 if gamma is None:
352 gamma = calcGamma(bandpass, m5, photParams=photParams)
354 dummySed = Sed()
355 m5Flux = dummySed.fluxFromMag(m5)
356 sourceFlux = dummySed.fluxFromMag(magnitude)
358 fluxRatio = m5Flux/sourceFlux
360 noise = numpy.sqrt((0.04-gamma)*fluxRatio+gamma*fluxRatio*fluxRatio)
362 return 1.0/noise, gamma
365def calcMagError_m5(magnitude, bandpass, m5, photParams, gamma=None):
366 """
367 Calculate magnitude error using the model from equation (5) of arXiv:0805.2366
369 @param [in] magnitude of the source whose error you want
370 to calculate (can be a numpy array)
372 @param [in] bandpass (an instantiation of the Bandpass class) in question
374 @param [in] m5 is the 5-sigma limiting magnitude in that bandpass
376 @param [in] photParams is an instantiation of the
377 PhotometricParameters class that carries details about the
378 photometric response of the telescope.
380 @param [in] gamma (optional) is the gamma parameter from equation(5) of
381 arXiv:0805.2366. If not provided, this method will calculate it.
383 @param [out] the error associated with the magnitude
385 @param [out] gamma is the calculated gamma parameter for the
386 bandpass used here (in case the user wants to call this method again).
388 Note: you can also pass in a numpy of array of magnitudes calculated in
389 the same Bandpass with the same m5 and get a numpy array of errors out.
390 """
392 snr, gamma = calcSNR_m5(magnitude, bandpass, m5, photParams, gamma=gamma)
394 if photParams.sigmaSys is not None:
395 return numpy.sqrt(numpy.power(magErrorFromSNR(snr),2) + numpy.power(photParams.sigmaSys,2)), gamma
396 else:
397 return magErrorFromSNR(snr), gamma
400def calcSNR_sed(sourceSed, totalbandpass, skysed, hardwarebandpass,
401 photParams, FWHMeff, verbose=False):
402 """
403 Calculate the signal to noise ratio for a source, given the bandpass(es) and sky SED.
405 For a given source, sky sed, total bandpass and hardware bandpass, as well as
406 FWHMeff / exptime, calculates the SNR with optimal PSF extraction
407 assuming a double-gaussian PSF.
409 @param [in] sourceSed is an instantiation of the Sed class containing the SED of
410 the object whose signal to noise ratio is being calculated
412 @param [in] totalbandpass is an instantiation of the Bandpass class
413 representing the total throughput (system + atmosphere)
415 @param [in] skysed is an instantiation of the Sed class representing
416 the sky emission per square arcsecond.
418 @param [in] hardwarebandpass is an instantiation of the Bandpass class
419 representing just the throughput of the system hardware.
421 @param [in] photParams is an instantiation of the
422 PhotometricParameters class that carries details about the
423 photometric response of the telescope.
425 @param [in] FWHMeff in arcseconds
427 @param [in] verbose is a boolean
429 @param [out] signal to noise ratio
430 """
432 # Calculate the counts from the source.
433 sourcecounts = sourceSed.calcADU(totalbandpass, photParams=photParams)
435 # Calculate the (square of the) noise due to signal poisson noise.
436 noise_source_sq = sourcecounts/photParams.gain
438 non_source_noise_sq = calcTotalNonSourceNoiseSq(skysed, hardwarebandpass, photParams, FWHMeff)
440 # Calculate total noise
441 noise = numpy.sqrt(noise_source_sq + non_source_noise_sq)
442 # Calculate the signal to noise ratio.
443 snr = sourcecounts / noise
444 if verbose:
445 skycounts = skysed.calcADU(hardwarebandpass, photParams) * (photParams.platescale**2)
446 noise_sky_sq = skycounts/photParams.gain
447 neff = calcNeff(FWHMeff, photParams.platescale)
448 noise_instr_sq = calcInstrNoiseSq(photParams)
450 print("For Nexp %.1f of time %.1f: " % (photParams.nexp, photParams.exptime))
451 print("Counts from source: %.2f Counts from sky: %.2f" %(sourcecounts, skycounts))
452 print("FWHMeff: %.2f('') Neff pixels: %.3f(pix)" %(FWHMeff, neff))
453 print("Noise from sky: %.2f Noise from instrument: %.2f" \
454 %(numpy.sqrt(noise_sky_sq), numpy.sqrt(noise_instr_sq)))
455 print("Noise from source: %.2f" %(numpy.sqrt(noise_source_sq)))
456 print(" Total Signal: %.2f Total Noise: %.2f SNR: %.2f" %(sourcecounts, noise, snr))
457 # Return the signal to noise value.
458 return snr
461def calcMagError_sed(sourceSed, totalbandpass, skysed, hardwarebandpass,
462 photParams, FWHMeff, verbose=False):
463 """
464 Calculate the magnitudeError for a source, given the bandpass(es) and sky SED.
466 For a given source, sky sed, total bandpass and hardware bandpass, as well as
467 FWHMeff / exptime, calculates the SNR with optimal PSF extraction
468 assuming a double-gaussian PSF.
470 @param [in] sourceSed is an instantiation of the Sed class containing the SED of
471 the object whose signal to noise ratio is being calculated
473 @param [in] totalbandpass is an instantiation of the Bandpass class
474 representing the total throughput (system + atmosphere)
476 @param [in] skysed is an instantiation of the Sed class representing
477 the sky emission per square arcsecond.
479 @param [in] hardwarebandpass is an instantiation of the Bandpass class
480 representing just the throughput of the system hardware.
482 @param [in] photParams is an instantiation of the
483 PhotometricParameters class that carries details about the
484 photometric response of the telescope.
486 @param [in] FWHMeff in arcseconds
488 @param [in] verbose is a boolean
490 @param [out] magnitude error
491 """
493 snr = calcSNR_sed(sourceSed, totalbandpass, skysed, hardwarebandpass,
494 photParams, FWHMeff, verbose=verbose)
496 if photParams.sigmaSys is not None:
497 return numpy.sqrt(numpy.power(magErrorFromSNR(snr),2) + numpy.power(photParams.sigmaSys,2))
498 else:
499 return magErrorFromSNR(snr)
502def calcAstrometricError(mag, m5, nvisit=1):
503 """
504 Calculate the astrometric error, for object catalog purposes.
506 Returns astrometric error for a given SNR, in mas.
507 """
508 # The astrometric error can be applied to parallax or proper motion (for nvisit>1).
509 # If applying to proper motion, should also divide by the # of years of the survey.
510 # This is also referenced in the astroph/0805.2366 paper.
511 # D. Monet suggests sqrt(Nvisit/2) for first 3 years, sqrt(N) for longer, in reduction of error
512 # because of the astrometric measurement method, the systematic and random error are both reduced.
513 # Zeljko says 'be conservative', so removing this reduction for now.
514 rgamma = 0.039
515 xval = numpy.power(10, 0.4*(mag-m5))
516 # The average FWHMeff is 0.7" (or 700 mas).
517 error_rand = 700.0 * numpy.sqrt((0.04-rgamma)*xval + rgamma*xval*xval)
518 error_rand = error_rand / numpy.sqrt(nvisit)
519 # The systematic error floor in astrometry:
520 error_sys = 10.0
521 # These next few lines are the code removed due to Zeljko's 'be conservative' requirement.
522 #if (nvisit<30):
523 # error_sys = error_sys/numpy.sqrt(nvisit/2.0)
524 #if (nvisit>30):
525 # error_sys = error_sys/numpy.sqrt(nvisit)
526 astrom_error = numpy.sqrt(error_sys * error_sys + error_rand*error_rand)
527 return astrom_error