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 builtins import zip 

2from builtins import object 

3import numpy as np 

4import ephem 

5from lsst.sims.utils import (haversine, _raDecFromAltAz, _altAzPaFromRaDec, Site, 

6 ObservationMetaData, _approx_altAz2RaDec, _approx_RaDec2AltAz) 

7import warnings 

8from lsst.sims.skybrightness.utils import wrapRA, mjd2djd 

9from .interpComponents import (ScatteredStar, Airglow, LowerAtm, UpperAtm, MergedSpec, TwilightInterp, 

10 MoonInterp, ZodiacalInterp) 

11from lsst.sims.photUtils import Sed 

12 

13 

14__all__ = ['justReturn', 'SkyModel'] 

15 

16 

17def justReturn(inval): 

18 """ 

19 Really, just return the input. 

20 

21 Parameters 

22 ---------- 

23 input : anything 

24 

25 Returns 

26 ------- 

27 input : anything 

28 Just return whatever you sent in. 

29 """ 

30 return inval 

31 

32 

33def inrange(inval, minimum=-1., maximum=1.): 

34 """ 

35 Make sure values are within min/max 

36 """ 

37 inval = np.array(inval) 

38 below = np.where(inval < minimum) 

39 inval[below] = minimum 

40 above = np.where(inval > maximum) 

41 inval[above] = maximum 

42 return inval 

43 

44 

45def calcAzRelMoon(azs, moonAz): 

46 azRelMoon = wrapRA(azs - moonAz) 

47 if isinstance(azs, np.ndarray): 

48 over = np.where(azRelMoon > np.pi) 

49 azRelMoon[over] = 2. * np.pi - azRelMoon[over] 

50 else: 

51 if azRelMoon > np.pi: 

52 azRelMoon = 2.0 * np.pi - azRelMoon 

53 return azRelMoon 

54 

55 

56class SkyModel(object): 

57 

58 def __init__(self, observatory='LSST', 

59 twilight=True, zodiacal=True, moon=True, 

60 airglow=True, lowerAtm=False, upperAtm=False, scatteredStar=False, 

61 mergedSpec=True, mags=False, preciseAltAz=False, airmass_limit=3.0): 

62 """ 

63 Instatiate the SkyModel. This loads all the required template spectra/magnitudes 

64 that will be used for interpolation. 

65 

66 Parameters 

67 ---------- 

68 Observatory : Site object 

69 object with attributes lat, lon, elev. But default loads LSST. 

70 

71 twilight : bool (True) 

72 Include twilight component (True) 

73 zodiacal : bool (True) 

74 Include zodiacal light component (True) 

75 moon : bool (True) 

76 Include scattered moonlight component (True) 

77 airglow : bool (True) 

78 Include airglow component 

79 lowerAtm : bool (False) 

80 Include lower atmosphere component. This component is part of `mergedSpec`. 

81 upperAtm : bool (False) 

82 Include upper atmosphere component. This component is part of `mergedSpec`. 

83 scatteredStar : bool (False) 

84 Include scattered starlight component. This component is part of `mergedSpec`. 

85 mergedSpec : bool (True) 

86 Compute the lowerAtm, upperAtm, and scatteredStar simultaneously since they are all 

87 functions of only airmass. 

88 mags : bool (False) 

89 By default, the sky model computes a 17,001 element spectrum. If `mags` is True, 

90 the model will return the LSST ugrizy magnitudes (in that order). 

91 preciseAltAz : bool (False) 

92 If False, use the fast alt, az to ra, dec coordinate 

93 transformations that do not take abberation, diffraction, etc 

94 into account. Results in errors up to ~1.5 degrees, 

95 but an order of magnitude faster than coordinate transforms in sims_utils. 

96 airmass_limit : float (3.0) 

97 Most of the models are only accurate to airmass 3.0. If set higher, airmass values 

98 higher than 3.0 are set to 3.0. 

99 """ 

100 

101 self.moon = moon 

102 self.lowerAtm = lowerAtm 

103 self.twilight = twilight 

104 self.zodiacal = zodiacal 

105 self.upperAtm = upperAtm 

106 self.airglow = airglow 

107 self.scatteredStar = scatteredStar 

108 self.mergedSpec = mergedSpec 

109 self.mags = mags 

110 self.preciseAltAz = preciseAltAz 

111 

112 # set this as a way to track if coords have been set 

113 self.azs = None 

114 

115 # Airmass limit. 

116 self.airmassLimit = airmass_limit 

117 

118 if self.mags: 

119 self.npix = 6 

120 else: 

121 self.npix = 11001 

122 

123 self.components = {'moon': self.moon, 'lowerAtm': self.lowerAtm, 'twilight': self.twilight, 

124 'upperAtm': self.upperAtm, 'airglow': self.airglow, 'zodiacal': self.zodiacal, 

125 'scatteredStar': self.scatteredStar, 'mergedSpec': self.mergedSpec} 

126 

127 # Check that the merged component isn't being run with other components 

128 mergedComps = [self.lowerAtm, self.upperAtm, self.scatteredStar] 

129 for comp in mergedComps: 

130 if comp & self.mergedSpec: 

131 warnings.warn("Adding component multiple times to the final output spectra.") 

132 

133 interpolators = {'scatteredStar': ScatteredStar, 'airglow': Airglow, 'lowerAtm': LowerAtm, 

134 'upperAtm': UpperAtm, 'mergedSpec': MergedSpec, 'moon': MoonInterp, 

135 'zodiacal': ZodiacalInterp, 'twilight': TwilightInterp} 

136 

137 # Load up the interpolation objects for each component 

138 self.interpObjs = {} 

139 for key in self.components: 

140 if self.components[key]: 

141 self.interpObjs[key] = interpolators[key](mags=self.mags) 

142 

143 # Set up a pyephem observatory object 

144 if hasattr(observatory, 'latitude_rad') & hasattr(observatory, 'longitude_rad') & hasattr(observatory, 'height'): 

145 self.telescope = observatory 

146 self.Observatory = ephem.Observer() 

147 self.Observatory.lat = self.telescope.latitude_rad 

148 self.Observatory.lon = self.telescope.longitude_rad 

149 self.Observatory.elevation = self.telescope.height 

150 elif observatory == 'LSST': 

151 self.telescope = Site('LSST') 

152 self.Observatory = ephem.Observer() 

153 self.Observatory.lat = self.telescope.latitude_rad 

154 self.Observatory.lon = self.telescope.longitude_rad 

155 self.Observatory.elevation = self.telescope.height 

156 else: 

157 self.Observatory = observatory 

158 

159 # Note that observing conditions have not been set 

160 self.paramsSet = False 

161 

162 def _initPoints(self): 

163 """ 

164 Set up an array for all the interpolation points 

165 """ 

166 

167 names = ['airmass', 'nightTimes', 'alt', 'az', 'azRelMoon', 'moonSunSep', 'moonAltitude', 

168 'altEclip', 'azEclipRelSun', 'sunAlt', 'azRelSun', 'solarFlux'] 

169 types = [float]*len(names) 

170 self.points = np.zeros(self.npts, list(zip(names, types))) 

171 

172 def setRaDecMjd(self, lon, lat, mjd, degrees=False, azAlt=False, solarFlux=130., 

173 filterNames=['u', 'g', 'r', 'i', 'z', 'y']): 

174 """ 

175 Set the sky parameters by computing the sky conditions on a given MJD and sky location. 

176 

177 

178 

179 lon: Longitude-like (RA or Azimuth). Can be single number, list, or numpy array 

180 lat: Latitude-like (Dec or Altitude) 

181 mjd: Modified Julian Date for the calculation. Must be single number. 

182 degrees: (False) Assumes lon and lat are radians unless degrees=True 

183 azAlt: (False) Assume lon, lat are RA, Dec unless azAlt=True 

184 solarFlux: solar flux in SFU Between 50 and 310. Default=130. 1 SFU=10^4 Jy. 

185 filterNames: list of fitlers to return magnitudes for (if initialized with mags=True). 

186 """ 

187 self.filterNames = filterNames 

188 if self.mags: 

189 self.npix = len(self.filterNames) 

190 # Wrap in array just in case single points were passed 

191 if np.size(lon) == 1: 

192 lon = np.array([lon]).ravel() 

193 lat = np.array([lat]).ravel() 

194 else: 

195 lon = np.array(lon) 

196 lat = np.array(lat) 

197 if degrees: 

198 self.ra = np.radians(lon) 

199 self.dec = np.radians(lat) 

200 else: 

201 self.ra = lon 

202 self.dec = lat 

203 if np.size(mjd) > 1: 

204 raise ValueError('mjd must be single value.') 

205 self.mjd = mjd 

206 if azAlt: 

207 self.azs = self.ra.copy() 

208 self.alts = self.dec.copy() 

209 if self.preciseAltAz: 

210 self.ra, self.dec = _raDecFromAltAz(self.alts, self.azs, 

211 ObservationMetaData(mjd=self.mjd, site=self.telescope)) 

212 else: 

213 self.ra, self.dec = _approx_altAz2RaDec(self.alts, self.azs, 

214 self.telescope.latitude_rad, 

215 self.telescope.longitude_rad, mjd) 

216 else: 

217 if self.preciseAltAz: 

218 self.alts, self.azs, pa = _altAzPaFromRaDec(self.ra, self.dec, 

219 ObservationMetaData(mjd=self.mjd, 

220 site=self.telescope)) 

221 else: 

222 self.alts, self.azs = _approx_RaDec2AltAz(self.ra, self.dec, 

223 self.telescope.latitude_rad, 

224 self.telescope.longitude_rad, mjd) 

225 

226 self.npts = self.ra.size 

227 self._initPoints() 

228 

229 self.solarFlux = solarFlux 

230 self.points['solarFlux'] = self.solarFlux 

231 

232 self._setupPointGrid() 

233 

234 self.paramsSet = True 

235 

236 # Interpolate the templates to the set paramters 

237 self.goodPix = np.where((self.airmass <= self.airmassLimit) & (self.airmass >= 1.))[0] 

238 if self.goodPix.size > 0: 

239 self._interpSky() 

240 else: 

241 warnings.warn('No valid points to interpolate') 

242 

243 def setRaDecAltAzMjd(self, ra, dec, alt, az, mjd, degrees=False, solarFlux=130., 

244 filterNames=['u', 'g', 'r', 'i', 'z', 'y']): 

245 """ 

246 Set the sky parameters by computing the sky conditions on a given MJD and sky location. 

247 

248 Use if you already have alt az coordinates so you can skip the coordinate conversion. 

249 """ 

250 self.filterNames = filterNames 

251 if self.mags: 

252 self.npix = len(self.filterNames) 

253 # Wrap in array just in case single points were passed 

254 if not type(ra).__module__ == np.__name__: 

255 if np.size(ra) == 1: 

256 ra = np.array([ra]).ravel() 

257 dec = np.array([dec]).ravel() 

258 alt = np.array(alt).ravel() 

259 az = np.array(az).ravel() 

260 else: 

261 ra = np.array(ra) 

262 dec = np.array(dec) 

263 alt = np.array(alt) 

264 az = np.array(az) 

265 if degrees: 

266 self.ra = np.radians(ra) 

267 self.dec = np.radians(dec) 

268 self.alts = np.radians(alt) 

269 self.azs = np.radians(az) 

270 else: 

271 self.ra = ra 

272 self.dec = dec 

273 self.azs = az 

274 self.alts = alt 

275 if np.size(mjd) > 1: 

276 raise ValueError('mjd must be single value.') 

277 self.mjd = mjd 

278 

279 self.npts = self.ra.size 

280 self._initPoints() 

281 

282 self.solarFlux = solarFlux 

283 self.points['solarFlux'] = self.solarFlux 

284 

285 self._setupPointGrid() 

286 

287 self.paramsSet = True 

288 

289 # Interpolate the templates to the set paramters 

290 self.goodPix = np.where((self.airmass <= self.airmassLimit) & (self.airmass >= 1.))[0] 

291 if self.goodPix.size > 0: 

292 self._interpSky() 

293 else: 

294 warnings.warn('No valid points to interpolate') 

295 

296 def getComputedVals(self): 

297 """ 

298 Return the intermediate values that are caluculated by setRaDecMjd and used for interpolation. 

299 All of these values are also accesible as class atributes, this is a convience method to grab them 

300 all at once and document the formats. 

301 

302 Returns 

303 ------- 

304 out : dict 

305 Dictionary of all the intermediate calculated values that may be of use outside 

306 (the key:values in the output dict) 

307 ra : numpy.array 

308 RA of the interpolation points (radians) 

309 dec : np.array 

310 Dec of the interpolation points (radians) 

311 alts : np.array 

312 Altitude (radians) 

313 azs : np.array 

314 Azimuth of interpolation points (radians) 

315 airmass : np.array 

316 Airmass values for each point, computed via 1./np.cos(np.pi/2.-self.alts). 

317 solarFlux : float 

318 The solar flux used (SFU). 

319 sunAz : float 

320 Azimuth of the sun (radians) 

321 sunAlt : float 

322 Altitude of the sun (radians) 

323 sunRA : float 

324 RA of the sun (radians) 

325 sunDec : float 

326 Dec of the sun (radians) 

327 azRelSun : np.array 

328 Azimuth of each point relative to the sun (0=same direction as sun) (radians) 

329 moonAz : float 

330 Azimuth of the moon (radians) 

331 moonAlt : float 

332 Altitude of the moon (radians) 

333 moonRA : float 

334 RA of the moon (radians) 

335 moonDec : float 

336 Dec of the moon (radians). Note, if you want distances 

337 moonPhase : float 

338 Phase of the moon (0-100) 

339 moonSunSep : float 

340 Seperation of moon and sun (degrees) 

341 azRelMoon : np.array 

342 Azimuth of each point relative to teh moon 

343 eclipLon : np.array 

344 Ecliptic longitude (radians) of each point 

345 eclipLat : np.array 

346 Ecliptic latitude (radians) of each point 

347 sunEclipLon: np.array 

348 Ecliptic longitude (radians) of each point with the sun at longitude zero 

349 

350 Note that since the alt and az can be calculated using the fast approximation, if one wants 

351 to compute the distance between the the points and the sun or moon, it is probably better to 

352 use the ra,dec positions rather than the alt,az positions. 

353 """ 

354 

355 result = {} 

356 attributes = ['ra', 'dec', 'alts', 'azs', 'airmass', 'solarFlux', 'moonPhase', 

357 'moonAz', 'moonAlt', 'sunAlt', 'sunAz', 'azRelSun', 'moonSunSep', 

358 'azRelMoon', 'eclipLon', 'eclipLat', 'moonRA', 'moonDec', 'sunRA', 

359 'sunDec', 'sunEclipLon'] 

360 

361 for attribute in attributes: 

362 if hasattr(self, attribute): 

363 result[attribute] = getattr(self, attribute) 

364 else: 

365 result[attribute] = None 

366 

367 return result 

368 

369 def _setupPointGrid(self): 

370 """ 

371 Setup the points for the interpolation functions. 

372 """ 

373 # Switch to Dublin Julian Date for pyephem 

374 self.Observatory.date = mjd2djd(self.mjd) 

375 

376 sun = ephem.Sun() 

377 sun.compute(self.Observatory) 

378 self.sunAlt = sun.alt 

379 self.sunAz = sun.az 

380 self.sunRA = sun.ra 

381 self.sunDec = sun.dec 

382 

383 # Compute airmass the same way as ESO model 

384 self.airmass = 1./np.cos(np.pi/2.-self.alts) 

385 

386 self.points['airmass'] = self.airmass 

387 self.points['nightTimes'] = 0 

388 self.points['alt'] = self.alts 

389 self.points['az'] = self.azs 

390 

391 if self.twilight: 

392 self.points['sunAlt'] = self.sunAlt 

393 self.azRelSun = wrapRA(self.azs - self.sunAz) 

394 self.points['azRelSun'] = self.azRelSun 

395 

396 if self.moon: 

397 moon = ephem.Moon() 

398 moon.compute(self.Observatory) 

399 self.moonPhase = moon.phase 

400 self.moonAlt = moon.alt 

401 self.moonAz = moon.az 

402 self.moonRA = moon.ra 

403 self.moonDec = moon.dec 

404 # Calc azimuth relative to moon 

405 self.azRelMoon = calcAzRelMoon(self.azs, self.moonAz) 

406 self.moonTargSep = haversine(self.azs, self.alts, self.moonAz, self.moonAlt) 

407 self.points['moonAltitude'] += np.degrees(self.moonAlt) 

408 self.points['azRelMoon'] += self.azRelMoon 

409 self.moonSunSep = self.moonPhase/100.*180. 

410 self.points['moonSunSep'] += self.moonSunSep 

411 

412 if self.zodiacal: 

413 self.eclipLon = np.zeros(self.npts) 

414 self.eclipLat = np.zeros(self.npts) 

415 

416 for i, temp in enumerate(self.ra): 

417 eclip = ephem.Ecliptic(ephem.Equatorial(self.ra[i], self.dec[i], epoch='2000')) 

418 self.eclipLon[i] += eclip.lon 

419 self.eclipLat[i] += eclip.lat 

420 # Subtract off the sun ecliptic longitude 

421 sunEclip = ephem.Ecliptic(sun) 

422 self.sunEclipLon = sunEclip.lon 

423 self.points['altEclip'] += self.eclipLat 

424 self.points['azEclipRelSun'] += wrapRA(self.eclipLon - self.sunEclipLon) 

425 

426 self.mask = np.where((self.airmass > self.airmassLimit) | (self.airmass < 1.))[0] 

427 self.goodPix = np.where((self.airmass <= self.airmassLimit) & (self.airmass >= 1.))[0] 

428 

429 def setParams(self, airmass=1., azs=90., alts=None, moonPhase=31.67, moonAlt=45., 

430 moonAz=0., sunAlt=-12., sunAz=0., sunEclipLon=0., 

431 eclipLon=135., eclipLat=90., degrees=True, solarFlux=130., 

432 filterNames=['u', 'g', 'r', 'i', 'z', 'y']): 

433 """ 

434 Set parameters manually. 

435 Note, you can put in unphysical combinations of paramters if you want to 

436 (e.g., put a full moon at zenith at sunset). 

437 if the alts kwarg is set it will override the airmass kwarg. 

438 MoonPhase is percent of moon illuminated (0-100) 

439 """ 

440 

441 # Convert all values to radians for internal use. 

442 self.filterNames = filterNames 

443 if self.mags: 

444 self.npix = len(self.filterNames) 

445 if degrees: 

446 convertFunc = np.radians 

447 else: 

448 convertFunc = justReturn 

449 

450 self.solarFlux = solarFlux 

451 self.sunAlt = convertFunc(sunAlt) 

452 self.moonPhase = moonPhase 

453 self.moonAlt = convertFunc(moonAlt) 

454 self.moonAz = convertFunc(moonAz) 

455 self.eclipLon = convertFunc(eclipLon) 

456 self.eclipLat = convertFunc(eclipLat) 

457 self.sunEclipLon = convertFunc(sunEclipLon) 

458 self.azs = convertFunc(azs) 

459 if alts is not None: 

460 self.airmass = 1./np.cos(np.pi/2.-convertFunc(alts)) 

461 self.alts = convertFunc(alts) 

462 else: 

463 self.airmass = airmass 

464 self.alts = np.pi/2.-np.arccos(1./airmass) 

465 self.moonTargSep = haversine(self.azs, self.alts, moonAz, self.moonAlt) 

466 self.npts = np.size(self.airmass) 

467 self._initPoints() 

468 

469 self.points['airmass'] = self.airmass 

470 self.points['nightTimes'] = 0 

471 self.points['alt'] = self.alts 

472 self.points['az'] = self.azs 

473 self.azRelMoon = calcAzRelMoon(self.azs, self.moonAz) 

474 self.points['moonAltitude'] += np.degrees(self.moonAlt) 

475 self.points['azRelMoon'] = self.azRelMoon 

476 self.points['moonSunSep'] += self.moonPhase/100.*180. 

477 

478 self.eclipLon = convertFunc(eclipLon) 

479 self.eclipLat = convertFunc(eclipLat) 

480 

481 self.sunEclipLon = convertFunc(sunEclipLon) 

482 self.points['altEclip'] += self.eclipLat 

483 self.points['azEclipRelSun'] += wrapRA(self.eclipLon - self.sunEclipLon) 

484 

485 self.sunAz = convertFunc(sunAz) 

486 self.points['sunAlt'] = self.sunAlt 

487 self.points['azRelSun'] = wrapRA(self.azs - self.sunAz) 

488 self.points['solarFlux'] = solarFlux 

489 

490 self.paramsSet = True 

491 

492 self.mask = np.where((self.airmass > self.airmassLimit) | (self.airmass < 1.))[0] 

493 self.goodPix = np.where((self.airmass <= self.airmassLimit) & (self.airmass >= 1.))[0] 

494 # Interpolate the templates to the set paramters 

495 if self.goodPix.size > 0: 

496 self._interpSky() 

497 else: 

498 warnings.warn('No points in interpolation range') 

499 

500 def _interpSky(self): 

501 """ 

502 Interpolate the template spectra to the set RA, Dec and MJD. 

503 

504 the results are stored as attributes of the class: 

505 .wave = the wavelength in nm 

506 .spec = array of spectra with units of ergs/s/cm^2/nm 

507 """ 

508 

509 if not self.paramsSet: 

510 raise ValueError( 

511 'No parameters have been set. Must run setRaDecMjd or setParams before running interpSky.') 

512 

513 # set up array to hold the resulting spectra for each ra, dec point. 

514 self.spec = np.zeros((self.npts, self.npix), dtype=float) 

515 

516 # Rebuild the components dict so things can be turned on/off 

517 self.components = {'moon': self.moon, 'lowerAtm': self.lowerAtm, 'twilight': self.twilight, 

518 'upperAtm': self.upperAtm, 'airglow': self.airglow, 'zodiacal': self.zodiacal, 

519 'scatteredStar': self.scatteredStar, 'mergedSpec': self.mergedSpec} 

520 

521 # Loop over each component and add it to the result. 

522 mask = np.ones(self.npts) 

523 for key in self.components: 

524 if self.components[key]: 

525 result = self.interpObjs[key](self.points[self.goodPix], filterNames=self.filterNames) 

526 # Make sure the component has something 

527 if np.size(result['spec']) == 0: 

528 self.spec[self.mask, :] = np.nan 

529 return 

530 if np.max(result['spec']) > 0: 

531 mask[np.where(np.sum(result['spec'], axis=1) == 0)] = 0 

532 self.spec[self.goodPix] += result['spec'] 

533 if not hasattr(self, 'wave'): 

534 self.wave = result['wave'] 

535 else: 

536 if not np.allclose(result['wave'], self.wave, rtol=1e-5, atol=1e-5): 

537 warnings.warn('Wavelength arrays of components do not match.') 

538 if self.airmassLimit <= 2.5: 

539 self.spec[np.where(mask == 0), :] = 0 

540 self.spec[self.mask, :] = np.nan 

541 

542 def returnWaveSpec(self): 

543 """ 

544 Return the wavelength and spectra. 

545 Wavelenth in nm 

546 spectra is flambda in ergs/cm^2/s/nm 

547 """ 

548 if self.azs is None: 

549 raise ValueError('No coordinates set. Use setRaDecMjd, setRaDecAltAzMjd, or setParams methods before calling returnWaveSpec.') 

550 if self.mags: 

551 raise ValueError('SkyModel set to interpolate magnitudes. Initialize object with mags=False') 

552 # Mask out high airmass points 

553 # self.spec[self.mask] *= 0 

554 return self.wave, self.spec 

555 

556 def returnMags(self, bandpasses=None): 

557 """ 

558 Convert the computed spectra to a magnitude using the supplied bandpass, 

559 or, if self.mags=True, return the mags in the LSST filters 

560 

561 If mags=True when initialized, return mags returns an structured array with 

562 dtype names u,g,r,i,z,y. 

563 

564 bandpasses: optional dictionary with bandpass name keys and bandpass object values. 

565 

566 """ 

567 if self.azs is None: 

568 raise ValueError('No coordinates set. Use setRaDecMjd, setRaDecAltAzMjd, or setParams methods before calling returnMags.') 

569 

570 if self.mags: 

571 if bandpasses: 

572 warnings.warn('Ignoring set bandpasses and returning LSST ugrizy.') 

573 mags = -2.5*np.log10(self.spec)+np.log10(3631.) 

574 # Mask out high airmass 

575 mags[self.mask] *= np.nan 

576 mags = mags.swapaxes(0, 1) 

577 magsBack = {} 

578 for i, f in enumerate(self.filterNames): 

579 magsBack[f] = mags[i] 

580 else: 

581 magsBack = {} 

582 for key in bandpasses: 

583 mags = np.zeros(self.npts, dtype=float)-666 

584 tempSed = Sed() 

585 isThrough = np.where(bandpasses[key].sb > 0) 

586 minWave = bandpasses[key].wavelen[isThrough].min() 

587 maxWave = bandpasses[key].wavelen[isThrough].max() 

588 inBand = np.where((self.wave >= minWave) & (self.wave <= maxWave)) 

589 for i, ra in enumerate(self.ra): 

590 # Check that there is flux in the band, otherwise calcMag fails 

591 if np.max(self.spec[i, inBand]) > 0: 

592 tempSed.setSED(self.wave, flambda=self.spec[i, :]) 

593 mags[i] = tempSed.calcMag(bandpasses[key]) 

594 # Mask out high airmass 

595 mags[self.mask] *= np.nan 

596 magsBack[key] = mags 

597 return magsBack