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=2.5): 

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 (2.5) 

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

98 higher than 2.5 are set to 2.5. 

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 = 17001 

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 setComponents(self, twilight=True, zodiacal=True, moon=True, 

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

164 mergedSpec=True): 

165 """ 

166 Convience function for turning on/off different sky components. 

167 """ 

168 self.moon = moon 

169 self.lowerAtm = lowerAtm 

170 self.twilight = twilight 

171 self.zodiacal = zodiacal 

172 self.upperAtm = upperAtm 

173 self.airglow = airglow 

174 self.scatteredStar = scatteredStar 

175 self.mergedSpec = mergedSpec 

176 

177 def _initPoints(self): 

178 """ 

179 Set up an array for all the interpolation points 

180 """ 

181 

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

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

184 types = [float]*len(names) 

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

186 

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

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

189 """ 

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

191 

192 

193 

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

195 lat: Latitude-like (Dec or Altitude) 

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

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

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

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

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

201 """ 

202 self.filterNames = filterNames 

203 if self.mags: 

204 self.npix = len(self.filterNames) 

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

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

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

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

209 else: 

210 lon = np.array(lon) 

211 lat = np.array(lat) 

212 if degrees: 

213 self.ra = np.radians(lon) 

214 self.dec = np.radians(lat) 

215 else: 

216 self.ra = lon 

217 self.dec = lat 

218 if np.size(mjd) > 1: 

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

220 self.mjd = mjd 

221 if azAlt: 

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

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

224 if self.preciseAltAz: 

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

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

227 else: 

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

229 self.telescope.latitude_rad, 

230 self.telescope.longitude_rad, mjd) 

231 else: 

232 if self.preciseAltAz: 

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

234 ObservationMetaData(mjd=self.mjd, 

235 site=self.telescope)) 

236 else: 

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

238 self.telescope.latitude_rad, 

239 self.telescope.longitude_rad, mjd) 

240 

241 self.npts = self.ra.size 

242 self._initPoints() 

243 

244 self.solarFlux = solarFlux 

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

246 

247 self._setupPointGrid() 

248 

249 self.paramsSet = True 

250 

251 # Assume large airmasses are the same as airmass=2.5 

252 to_fudge = np.where((self.points['airmass'] > 2.5) & (self.points['airmass'] < self.airmassLimit)) 

253 self.points['airmass'][to_fudge] = 2.499 

254 self.points['alt'][to_fudge] = np.pi/2-np.arccos(1./self.airmassLimit) 

255 

256 # Interpolate the templates to the set paramters 

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

258 if self.goodPix.size > 0: 

259 self._interpSky() 

260 else: 

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

262 

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

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

265 """ 

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

267 

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

269 """ 

270 self.filterNames = filterNames 

271 if self.mags: 

272 self.npix = len(self.filterNames) 

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

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

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

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

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

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

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

280 else: 

281 ra = np.array(ra) 

282 dec = np.array(dec) 

283 alt = np.array(alt) 

284 az = np.array(az) 

285 if degrees: 

286 self.ra = np.radians(ra) 

287 self.dec = np.radians(dec) 

288 self.alts = np.radians(alt) 

289 self.azs = np.radians(az) 

290 else: 

291 self.ra = ra 

292 self.dec = dec 

293 self.azs = az 

294 self.alts = alt 

295 if np.size(mjd) > 1: 

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

297 self.mjd = mjd 

298 

299 self.npts = self.ra.size 

300 self._initPoints() 

301 

302 self.solarFlux = solarFlux 

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

304 

305 self._setupPointGrid() 

306 

307 self.paramsSet = True 

308 

309 # Assume large airmasses are the same as airmass=2.5 

310 to_fudge = np.where((self.points['airmass'] > 2.5) & (self.points['airmass'] < self.airmassLimit)) 

311 self.points['airmass'][to_fudge] = 2.5 

312 self.points['alt'][to_fudge] = np.pi/2-np.arccos(1./self.airmassLimit) 

313 

314 # Interpolate the templates to the set paramters 

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

316 if self.goodPix.size > 0: 

317 self._interpSky() 

318 else: 

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

320 

321 def getComputedVals(self): 

322 """ 

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

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

325 all at once and document the formats. 

326 

327 Returns 

328 ------- 

329 out : dict 

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

331 (the key:values in the output dict) 

332 ra : numpy.array 

333 RA of the interpolation points (radians) 

334 dec : np.array 

335 Dec of the interpolation points (radians) 

336 alts : np.array 

337 Altitude (radians) 

338 azs : np.array 

339 Azimuth of interpolation points (radians) 

340 airmass : np.array 

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

342 solarFlux : float 

343 The solar flux used (SFU). 

344 sunAz : float 

345 Azimuth of the sun (radians) 

346 sunAlt : float 

347 Altitude of the sun (radians) 

348 sunRA : float 

349 RA of the sun (radians) 

350 sunDec : float 

351 Dec of the sun (radians) 

352 azRelSun : np.array 

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

354 moonAz : float 

355 Azimuth of the moon (radians) 

356 moonAlt : float 

357 Altitude of the moon (radians) 

358 moonRA : float 

359 RA of the moon (radians) 

360 moonDec : float 

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

362 moonPhase : float 

363 Phase of the moon (0-100) 

364 moonSunSep : float 

365 Seperation of moon and sun (degrees) 

366 azRelMoon : np.array 

367 Azimuth of each point relative to teh moon 

368 eclipLon : np.array 

369 Ecliptic longitude (radians) of each point 

370 eclipLat : np.array 

371 Ecliptic latitude (radians) of each point 

372 sunEclipLon: np.array 

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

374 

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

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

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

378 """ 

379 

380 result = {} 

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

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

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

384 'sunDec', 'sunEclipLon'] 

385 

386 for attribute in attributes: 

387 if hasattr(self, attribute): 

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

389 else: 

390 result[attribute] = None 

391 

392 return result 

393 

394 def _setupPointGrid(self): 

395 """ 

396 Setup the points for the interpolation functions. 

397 """ 

398 # Switch to Dublin Julian Date for pyephem 

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

400 

401 sun = ephem.Sun() 

402 sun.compute(self.Observatory) 

403 self.sunAlt = sun.alt 

404 self.sunAz = sun.az 

405 self.sunRA = sun.ra 

406 self.sunDec = sun.dec 

407 

408 # Compute airmass the same way as ESO model 

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

410 

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

412 self.points['nightTimes'] = 2 

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

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

415 

416 if self.twilight: 

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

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

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

420 

421 if self.moon: 

422 moon = ephem.Moon() 

423 moon.compute(self.Observatory) 

424 self.moonPhase = moon.phase 

425 self.moonAlt = moon.alt 

426 self.moonAz = moon.az 

427 self.moonRA = moon.ra 

428 self.moonDec = moon.dec 

429 # Calc azimuth relative to moon 

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

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

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

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

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

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

436 

437 if self.zodiacal: 

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

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

440 

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

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

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

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

445 # Subtract off the sun ecliptic longitude 

446 sunEclip = ephem.Ecliptic(sun) 

447 self.sunEclipLon = sunEclip.lon 

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

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

450 

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

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

453 

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

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

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

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

458 """ 

459 Set parameters manually. 

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

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

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

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

464 """ 

465 

466 # Convert all values to radians for internal use. 

467 self.filterNames = filterNames 

468 if self.mags: 

469 self.npix = len(self.filterNames) 

470 if degrees: 

471 convertFunc = np.radians 

472 else: 

473 convertFunc = justReturn 

474 

475 self.solarFlux = solarFlux 

476 self.sunAlt = convertFunc(sunAlt) 

477 self.moonPhase = moonPhase 

478 self.moonAlt = convertFunc(moonAlt) 

479 self.moonAz = convertFunc(moonAz) 

480 self.eclipLon = convertFunc(eclipLon) 

481 self.eclipLat = convertFunc(eclipLat) 

482 self.sunEclipLon = convertFunc(sunEclipLon) 

483 self.azs = convertFunc(azs) 

484 if alts is not None: 

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

486 self.alts = convertFunc(alts) 

487 else: 

488 self.airmass = airmass 

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

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

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

492 self._initPoints() 

493 

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

495 self.points['nightTimes'] = 2 

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

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

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

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

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

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

502 

503 self.eclipLon = convertFunc(eclipLon) 

504 self.eclipLat = convertFunc(eclipLat) 

505 

506 self.sunEclipLon = convertFunc(sunEclipLon) 

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

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

509 

510 self.sunAz = convertFunc(sunAz) 

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

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

513 self.points['solarFlux'] = solarFlux 

514 

515 self.paramsSet = True 

516 

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

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

519 # Interpolate the templates to the set paramters 

520 if self.goodPix.size > 0: 

521 self._interpSky() 

522 else: 

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

524 

525 def _interpSky(self): 

526 """ 

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

528 

529 the results are stored as attributes of the class: 

530 .wave = the wavelength in nm 

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

532 """ 

533 

534 if not self.paramsSet: 

535 raise ValueError( 

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

537 

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

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

540 

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

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

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

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

545 

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

547 mask = np.ones(self.npts) 

548 for key in self.components: 

549 if self.components[key]: 

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

551 # Make sure the component has something 

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

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

554 return 

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

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

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

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

559 self.wave = result['wave'] 

560 else: 

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

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

563 if self.airmassLimit <= 2.5: 

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

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

566 

567 def returnWaveSpec(self): 

568 """ 

569 Return the wavelength and spectra. 

570 Wavelenth in nm 

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

572 """ 

573 if self.azs is None: 

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

575 if self.mags: 

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

577 # Mask out high airmass points 

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

579 return self.wave, self.spec 

580 

581 def returnMags(self, bandpasses=None): 

582 """ 

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

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

585 

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

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

588 

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

590 

591 """ 

592 if self.azs is None: 

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

594 

595 if self.mags: 

596 if bandpasses: 

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

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

599 # Mask out high airmass 

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

601 mags = mags.swapaxes(0, 1) 

602 magsBack = {} 

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

604 magsBack[f] = mags[i] 

605 else: 

606 magsBack = {} 

607 for key in bandpasses: 

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

609 tempSed = Sed() 

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

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

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

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

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

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

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

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

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

619 # Mask out high airmass 

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

621 magsBack[key] = mags 

622 return magsBack