Hide keyboard shortcuts

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 

7 

8__all__ = ["FWHMeff2FWHMgeom", "FWHMgeom2FWHMeff", 

9 "calcNeff", "calcInstrNoiseSq", "calcTotalNonSourceNoiseSq", "calcSNR_sed", 

10 "calcM5", "calcSkyCountsPerPixelForM5", "calcGamma", "calcSNR_m5", 

11 "calcAstrometricError", "magErrorFromSNR", "calcMagError_m5", "calcMagError_sed"] 

12 

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). 

17 

18 @param [in] FWHMeff (the single-gaussian equivalent FWHM value, appropriate for calcNeff) in arcseconds 

19 

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 

24 

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). 

29 

30 @param [in] FWHMgeom (the geometric FWHM value, as measured from a typical PSF profile) in arcseconds 

31 

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 

36 

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 

42 

43 

44 @param [in] FWHMeff in arcseconds 

45 (the width of a single-gaussian that produces correct Neff for typical PSF profile) 

46 

47 @param [in] platescale in arcseconds per pixel 

48 

49 @param [out] the effective number of pixels contained in the PSF 

50 

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 

58 

59 

60def calcInstrNoiseSq(photParams): 

61 """ 

62 Combine all of the noise due to intrumentation into one value 

63 

64 @param [in] photParams is an instantiation of the 

65 PhotometricParameters class that carries details about the 

66 photometric response of the telescope. 

67 

68 @param [out] The noise due to all of these sources added in quadrature 

69 in ADU counts 

70 """ 

71 

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 

76 

77 # convert to ADU counts 

78 instNoiseSq = instNoiseSq/(photParams.gain*photParams.gain) 

79 

80 return instNoiseSq 

81 

82 

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) 

87 

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) 

91 

92 @param [in] hardwarebandpass -- an instantiation of the Bandpass class representing 

93 just the instrumentation throughputs 

94 

95 @param [in] photParams is an instantiation of the 

96 PhotometricParameters class that carries details about the 

97 photometric response of the telescope. 

98 

99 @param [in] FWHMeff in arcseconds 

100 

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 """ 

105 

106 # Calculate the effective number of pixels for double-Gaussian PSF 

107 neff = calcNeff(FWHMeff, photParams.platescale) 

108 

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. 

113 

114 skycounts = skySed.calcADU(hardwarebandpass, photParams=photParams) \ 

115 * photParams.platescale * photParams.platescale 

116 

117 # Calculate the square of the noise due to instrumental effects. 

118 # Include the readout noise as many times as there are exposures 

119 

120 noise_instr_sq = calcInstrNoiseSq(photParams=photParams) 

121 

122 # Calculate the square of the noise due to sky background poisson noise 

123 noise_sky_sq = skycounts/photParams.gain 

124 

125 # Discount error in sky measurement for now 

126 noise_skymeasurement_sq = 0 

127 

128 total_noise_sq = neff*(noise_sky_sq + noise_instr_sq + noise_skymeasurement_sq) 

129 

130 return total_noise_sq 

131 

132 

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) 

137 

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. 

142 

143 @param [in] the desired value of m5 

144 

145 @param [in] totalBandpass is an instantiation of the Bandpass class 

146 representing the total throughput of the telescope (instrumentation 

147 plus atmosphere) 

148 

149 @param [in] photParams is an instantiation of the 

150 PhotometricParameters class that carries details about the 

151 photometric response of the telescope. 

152 

153 @param [in] FWHMeff in arcseconds 

154 

155 @param [out] returns the expected number of sky counts per pixel 

156 """ 

157 

158 if FWHMeff is None: 

159 FWHMeff = LSSTdefaults().FWHMeff('r') 

160 

161 # instantiate a flat SED 

162 flatSed = Sed() 

163 flatSed.setFlatSED() 

164 

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) 

169 

170 # calculate the effective number of pixels for a double-Gaussian PSF 

171 neff = calcNeff(FWHMeff, photParams.platescale) 

172 

173 # calculate the square of the noise due to the instrument 

174 noise_instr_sq = calcInstrNoiseSq(photParams=photParams) 

175 

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 

180 

181 nSigmaSq = (sourceCounts*sourceCounts)/25.0 - sourceCounts/photParams.gain 

182 

183 skyNoiseTarget = nSigmaSq/neff - noise_instr_sq 

184 skyCountsTarget = skyNoiseTarget*photParams.gain 

185 

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. 

192 

193 return skyCountsTarget 

194 

195 

196def calcM5(skysed, totalBandpass, hardware, photParams, FWHMeff=None): 

197 """ 

198 Calculate the AB magnitude of a 5-sigma above sky background source. 

199 

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. 

205 

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. 

209 

210 @param [in] totalBandpass is an instantiation of the Bandpass class 

211 representing the total throughput of the telescope (instrumentation 

212 plus atmosphere) 

213 

214 @param [in] hardware is an instantiation of the Bandpass class representing 

215 the throughput due solely to instrumentation. 

216 

217 @param [in] photParams is an instantiation of the 

218 PhotometricParameters class that carries details about the 

219 photometric response of the telescope. 

220 

221 @param [in] FWHMeff in arcseconds 

222 

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 

227 

228 if FWHMeff is None: 

229 FWHMeff = LSSTdefaults().FWHMeff('r') 

230 

231 # create a flat fnu source 

232 flatsource = Sed() 

233 flatsource.setFlatSED() 

234 snr = 5.0 

235 v_n = calcTotalNonSourceNoiseSq(skysed, hardware, photParams, FWHMeff) 

236 

237 counts_5sigma = (snr**2)/2.0/photParams.gain + \ 

238 numpy.sqrt((snr**4)/4.0/photParams.gain + (snr**2)*v_n) 

239 

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) 

244 

245 # Calculate the AB magnitude of this source. 

246 mag_5sigma = flatsource.calcMag(totalBandpass) 

247 return mag_5sigma 

248 

249 

250def magErrorFromSNR(snr): 

251 """ 

252 convert flux signal to noise ratio to an error in magnitude 

253 

254 @param [in] snr is the signal to noise ratio in flux 

255 

256 @param [out] the resulting error in magnitude 

257 """ 

258 

259 #see www.ucolick.org/~bolte/AY257/s_n.pdf section 3.1 

260 return 2.5*numpy.log10(1.0+1.0/snr) 

261 

262 

263def calcGamma(bandpass, m5, photParams): 

264 

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) 

269 

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 

273 

274 @param [in] m5 is the magnitude at which a 5-sigma detection occurs 

275 in this Bandpass 

276 

277 @param [in] photParams is an instantiation of the 

278 PhotometricParameters class that carries details about the 

279 photometric response of the telescope. 

280 

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) 

286 

287 # instantiate a flat SED 

288 flatSed = Sed() 

289 flatSed.setFlatSED() 

290 

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) 

295 

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 

316 

317 gamma = 0.04 - 1.0/(counts*photParams.gain) 

318 

319 return gamma 

320 

321 

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 

325 

326 @param [in] magnitude of the sources whose signal to noise you are calculating 

327 (can be a numpy array) 

328 

329 @param [in] bandpass (an instantiation of the class Bandpass) in which the magnitude 

330 was calculated 

331 

332 @param [in] m5 is the 5-sigma limiting magnitude for the bandpass 

333 

334 @param [in] photParams is an instantiation of the 

335 PhotometricParameters class that carries details about the 

336 photometric response of the telescope. 

337 

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. 

340 

341 @param [out] snr is the signal to noise ratio corresponding to 

342 the input magnitude. 

343 

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). 

346 

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 """ 

350 

351 if gamma is None: 

352 gamma = calcGamma(bandpass, m5, photParams=photParams) 

353 

354 dummySed = Sed() 

355 m5Flux = dummySed.fluxFromMag(m5) 

356 sourceFlux = dummySed.fluxFromMag(magnitude) 

357 

358 fluxRatio = m5Flux/sourceFlux 

359 

360 noise = numpy.sqrt((0.04-gamma)*fluxRatio+gamma*fluxRatio*fluxRatio) 

361 

362 return 1.0/noise, gamma 

363 

364 

365def calcMagError_m5(magnitude, bandpass, m5, photParams, gamma=None): 

366 """ 

367 Calculate magnitude error using the model from equation (5) of arXiv:0805.2366 

368 

369 @param [in] magnitude of the source whose error you want 

370 to calculate (can be a numpy array) 

371 

372 @param [in] bandpass (an instantiation of the Bandpass class) in question 

373 

374 @param [in] m5 is the 5-sigma limiting magnitude in that bandpass 

375 

376 @param [in] photParams is an instantiation of the 

377 PhotometricParameters class that carries details about the 

378 photometric response of the telescope. 

379 

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. 

382 

383 @param [out] the error associated with the magnitude 

384 

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). 

387 

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 """ 

391 

392 snr, gamma = calcSNR_m5(magnitude, bandpass, m5, photParams, gamma=gamma) 

393 

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 

398 

399 

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. 

404 

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. 

408 

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 

411 

412 @param [in] totalbandpass is an instantiation of the Bandpass class 

413 representing the total throughput (system + atmosphere) 

414 

415 @param [in] skysed is an instantiation of the Sed class representing 

416 the sky emission per square arcsecond. 

417 

418 @param [in] hardwarebandpass is an instantiation of the Bandpass class 

419 representing just the throughput of the system hardware. 

420 

421 @param [in] photParams is an instantiation of the 

422 PhotometricParameters class that carries details about the 

423 photometric response of the telescope. 

424 

425 @param [in] FWHMeff in arcseconds 

426 

427 @param [in] verbose is a boolean 

428 

429 @param [out] signal to noise ratio 

430 """ 

431 

432 # Calculate the counts from the source. 

433 sourcecounts = sourceSed.calcADU(totalbandpass, photParams=photParams) 

434 

435 # Calculate the (square of the) noise due to signal poisson noise. 

436 noise_source_sq = sourcecounts/photParams.gain 

437 

438 non_source_noise_sq = calcTotalNonSourceNoiseSq(skysed, hardwarebandpass, photParams, FWHMeff) 

439 

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) 

449 

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 

459 

460 

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. 

465 

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. 

469 

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 

472 

473 @param [in] totalbandpass is an instantiation of the Bandpass class 

474 representing the total throughput (system + atmosphere) 

475 

476 @param [in] skysed is an instantiation of the Sed class representing 

477 the sky emission per square arcsecond. 

478 

479 @param [in] hardwarebandpass is an instantiation of the Bandpass class 

480 representing just the throughput of the system hardware. 

481 

482 @param [in] photParams is an instantiation of the 

483 PhotometricParameters class that carries details about the 

484 photometric response of the telescope. 

485 

486 @param [in] FWHMeff in arcseconds 

487 

488 @param [in] verbose is a boolean 

489 

490 @param [out] magnitude error 

491 """ 

492 

493 snr = calcSNR_sed(sourceSed, totalbandpass, skysed, hardwarebandpass, 

494 photParams, FWHMeff, verbose=verbose) 

495 

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) 

500 

501 

502def calcAstrometricError(mag, m5, nvisit=1): 

503 """ 

504 Calculate the astrometric error, for object catalog purposes. 

505 

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