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

1"""Footprints: Some relevant LSST footprints, including utilities to build them. 

2 

3The goal here is to make it easy to build typical target maps and then their associated combined 

4survey inputs (maps in each filter, including scaling between filters; the associated cloud and 

5sky brightness maps that would have limits for WFD, etc.). 

6 

7For generic use for defining footprints from scratch, there is also a utility that simply generates 

8the healpix points across the sky, along with their corresponding RA/Dec/Galactic l,b/Ecliptic l,b values. 

9""" 

10 

11import os 

12import numpy as np 

13import healpy as hp 

14from astropy.coordinates import SkyCoord 

15from astropy import units as u 

16from .utils import set_default_nside, int_rounded 

17from lsst.sims.utils import _hpid2RaDec, _angularSeparation 

18from lsst.sims.utils import Site 

19from lsst.utils import getPackageDir 

20 

21__all__ = ['ra_dec_hp_map', 'generate_all_sky', 'get_dustmap', 

22 'WFD_healpixels', 'WFD_no_gp_healpixels', 'WFD_bigsky_healpixels', 'WFD_no_dust_healpixels', 

23 'SCP_healpixels', 'NES_healpixels', 

24 'galactic_plane_healpixels', #'low_lat_plane_healpixels', 'bulge_healpixels', 

25 'magellanic_clouds_healpixels', 

26 'generate_goal_map', 'standard_goals', 

27 'calc_norm_factor', 'filter_count_ratios'] 

28 

29 

30def ra_dec_hp_map(nside=None): 

31 """ 

32 Return all the RA,dec points for the centers of a healpix map, in radians. 

33 """ 

34 if nside is None: 

35 nside = set_default_nside() 

36 ra, dec = _hpid2RaDec(nside, np.arange(hp.nside2npix(nside))) 

37 return ra, dec 

38 

39 

40def get_dustmap(nside=None): 

41 if nside is None: 

42 nside = set_default_nside() 

43 ebvDataDir = getPackageDir('sims_maps') 

44 filename = 'DustMaps/dust_nside_%i.npz' % nside 

45 dustmap = np.load(os.path.join(ebvDataDir, filename))['ebvMap'] 

46 return dustmap 

47 

48 

49def generate_all_sky(nside=None, elevation_limit=20, mask=hp.UNSEEN): 

50 """Set up a healpix map over the entire sky. 

51 Calculate RA & Dec, Galactic l & b, Ecliptic l & b, for all healpixels. 

52 Calculate max altitude, to set to areas which LSST cannot reach (set these to hp.unseen). 

53 

54 This is intended to be a useful tool to use to set up target maps, beyond the standard maps 

55 provided in these utilities. Masking based on RA, Dec, Galactic or Ecliptic lat and lon is easier. 

56 

57 Parameters 

58 ---------- 

59 nside : int, opt 

60 Resolution for the healpix maps. 

61 Default None uses lsst.sims.featureScheduler.utils.set_default_nside to set default (often 32). 

62 elevation_limit : float, opt 

63 Elevation limit for map. 

64 Parts of the sky which do not reach this elevation limit will be set to mask. 

65 mask : float, opt 

66 Mask value for 'unreachable' parts of the sky, defined as elevation < 20. 

67 Note that the actual limits will be set elsewhere, using the observatory model. 

68 This limit is for use when understanding what the maps could look like. 

69 

70 Returns 

71 ------- 

72 dict of np.ndarray 

73 Returns map, RA/Dec, Gal l/b, Ecl l/b (each an np.ndarray IN RADIANS) in a dictionary. 

74 """ 

75 if nside is None: 

76 nside = set_default_nside() 

77 

78 # Calculate coordinates of everything. 

79 skymap = np.zeros(hp.nside2npix(nside), float) 

80 ra, dec = ra_dec_hp_map(nside=nside) 

81 coord = SkyCoord(ra=ra * u.rad, dec=dec * u.rad, frame='icrs') 

82 eclip_lat = coord.barycentrictrueecliptic.lat.deg 

83 eclip_lon = coord.barycentrictrueecliptic.lon.deg 

84 gal_lon = coord.galactic.l.deg 

85 gal_lat = coord.galactic.b.deg 

86 

87 # Calculate max altitude (when on meridian). 

88 lsst_site = Site('LSST') 

89 elev_max = np.pi / 2. - np.abs(dec - lsst_site.latitude_rad) 

90 skymap = np.where(int_rounded(elev_max) >= int_rounded(np.radians(elevation_limit), skymap, mask)) 

91 

92 return {'map': skymap, 'ra': np.degrees(ra), 'dec': np.degrees(dec), 

93 'eclip_lat': eclip_lat, 'eclip_lon': eclip_lon, 

94 'gal_lat': gal_lat, 'gal_lon': gal_lon} 

95 

96 

97def WFD_healpixels(nside=None, dec_min=-62.5, dec_max=3.6): 

98 """ 

99 Define a region based on declination limits only. 

100 

101 Parameters 

102 ---------- 

103 nside : int, opt 

104 Resolution for the healpix maps. 

105 Default None uses lsst.sims.featureScheduler.utils.set_default_nside to set default (often 32). 

106 dec_min : float, opt 

107 Minimum declination of the region (deg). Default -62.5. 

108 dec_max : float, opt 

109 Maximum declination of the region (deg). Default 3.6. 

110 

111 Returns 

112 ------- 

113 np.ndarray 

114 Healpix map with regions in declination-limited 'wfd' region as 1. 

115 """ 

116 if nside is None: 

117 nside = set_default_nside() 

118 

119 ra, dec = ra_dec_hp_map(nside=nside) 

120 result = np.zeros(ra.size, float) 

121 dec = int_rounded(dec) 

122 good = np.where((dec >= int_rounded(np.radians(dec_min))) & 

123 (dec <= int_rounded(np.radians(dec_max)))) 

124 result[good] = 1 

125 return result 

126 

127 

128def WFD_no_gp_healpixels(nside, dec_min=-62.5, dec_max=3.6, 

129 center_width=10., end_width=4., gal_long1=290., gal_long2=70.): 

130 """ 

131 Define a wide fast deep region with a galactic plane limit. 

132 

133 Parameters 

134 ---------- 

135 nside : int, opt 

136 Resolution for the healpix maps. 

137 Default None uses lsst.sims.featureScheduler.utils.set_default_nside to set default (often 32). 

138 dec_min : float, opt 

139 Minimum declination of the region (deg). 

140 dec_max : float, opt 

141 Maximum declination of the region (deg). 

142 center_width : float, opt 

143 Width across the central part of the galactic plane region. 

144 end_width : float, opt 

145 Width across the remainder of the galactic plane region. 

146 gal_long1 : float, opt 

147 Longitude at which to start tapering from center_width to end_width. 

148 gal_long2 : float, opt 

149 Longitude at which to stop tapering from center_width to end_width. 

150 

151 Returns 

152 ------- 

153 np.ndarray 

154 Healpix map with regions in declination-limited 'wfd' region as 1. 

155 """ 

156 wfd_dec = WFD_healpixels(nside, dec_min=dec_min, dec_max=dec_max) 

157 gp = galactic_plane_healpixels(nside=nside, center_width=center_width, end_width=end_width, 

158 gal_long1=gal_long1, gal_long2=gal_long2) 

159 sky = np.where(wfd_dec - gp > 0, wfd_dec - gp, 0) 

160 return sky 

161 

162 

163def WFD_bigsky_healpixels(nside): 

164 sky = WFD_no_gp_healpixels(nside, dec_min=-72.25, dec_max=12.4, center_width=14.9, 

165 gal_long1=0, gal_long2=360) 

166 return sky 

167 

168 

169def WFD_no_dust_healpixels(nside, dec_min=-72.25, dec_max=12.4, dust_limit=0.19): 

170 """Define a WFD region with a dust extinction limit. 

171 

172 Parameters 

173 ---------- 

174 nside : int, opt 

175 Resolution for the healpix maps. 

176 Default None uses lsst.sims.featureScheduler.utils.set_default_nside to set default (often 32). 

177 dec_min : float, opt 

178 Minimum dec of the region (deg). Default -72.5 deg. 

179 dec_max : float, opt. 

180 Maximum dec of the region (deg). Default 12.5 deg. 

181 1.75 is the FOV radius in deg. 

182 dust_limit : float, None 

183 Remove pixels with E(B-V) values greater than dust_limit from the footprint. 

184 

185 Returns 

186 ------- 

187 result : numpy array 

188 """ 

189 if nside is None: 

190 nside = set_default_nside() 

191 

192 ra, dec = ra_dec_hp_map(nside=nside) 

193 dustmap = get_dustmap(nside) 

194 

195 result = np.zeros(ra.size, float) 

196 # First set based on dec range. 

197 dec = int_rounded(dec) 

198 good = np.where((dec >= int_rounded(np.radians(dec_min))) & 

199 (dec <= int_rounded(np.radians(dec_max)))) 

200 result[good] = 1 

201 # Now remove areas with dust extinction beyond the limit. 

202 result = np.where(dustmap >= dust_limit, 0, result) 

203 return result 

204 

205 

206def SCP_healpixels(nside=None, dec_max=-60.): 

207 """ 

208 Define the South Celestial Pole region. Return a healpix map with SCP pixels as 1. 

209 """ 

210 if nside is None: 

211 nside = set_default_nside() 

212 

213 ra, dec = ra_dec_hp_map(nside=nside) 

214 result = np.zeros(ra.size, float) 

215 good = np.where(int_rounded(dec) < int_rounded(np.radians(dec_max))) 

216 result[good] = 1 

217 return result 

218 

219 

220def NES_healpixels(nside=None, min_EB=-30.0, max_EB = 10.0, dec_min=2.8): 

221 """ 

222 Define the North Ecliptic Spur region. Return a healpix map with NES pixels as 1. 

223 

224 Parameters 

225 ---------- 

226 nside : int 

227 A valid healpix nside 

228 min_EB : float (-30.) 

229 Minimum barycentric true ecliptic latitude (deg) 

230 max_EB : float (10.) 

231 Maximum barycentric true ecliptic latitude (deg) 

232 dec_min : float (2.8) 

233 Minimum dec in degrees 

234 

235 Returns 

236 ------- 

237 result : numpy array 

238 """ 

239 if nside is None: 

240 nside = set_default_nside() 

241 

242 ra, dec = ra_dec_hp_map(nside=nside) 

243 result = np.zeros(ra.size, float) 

244 coord = SkyCoord(ra=ra*u.rad, dec=dec*u.rad) 

245 eclip_lat = coord.barycentrictrueecliptic.lat.radian 

246 eclip_lat = int_rounded(eclip_lat) 

247 dec = int_rounded(dec) 

248 good = np.where((eclip_lat > int_rounded(np.radians(min_EB))) & 

249 (eclip_lat < int_rounded(np.radians(max_EB))) & 

250 (dec > int_rounded(np.radians(dec_min)))) 

251 result[good] = 1 

252 

253 return result 

254 

255 

256def galactic_plane_healpixels(nside=None, center_width=10., end_width=4., 

257 gal_long1=290., gal_long2=70.): 

258 """ 

259 Define a Galactic Plane region. 

260 

261 Parameters 

262 ---------- 

263 nside : int, opt 

264 Resolution for the healpix maps. 

265 Default None uses lsst.sims.featureScheduler.utils.set_default_nside to set default (often 32). 

266 center_width : float, opt 

267 Width at the center of the galactic plane region. 

268 end_width : float, opt 

269 Width at the remainder of the galactic plane region. 

270 gal_long1 : float, opt 

271 Longitude at which to start the GP region. 

272 gal_long2 : float, opt 

273 Longitude at which to stop the GP region. 

274 Order matters for gal_long1 / gal_long2! 

275 

276 Returns 

277 ------- 

278 np.ndarray 

279 Healpix map with galactic plane regions set to 1. 

280 """ 

281 if nside is None: 

282 nside = set_default_nside() 

283 ra, dec = ra_dec_hp_map(nside=nside) 

284 

285 coord = SkyCoord(ra=ra*u.rad, dec=dec*u.rad) 

286 gal_lon, gal_lat = coord.galactic.l.deg, coord.galactic.b.deg 

287 # Reject anything beyond the central width. 

288 sky = np.where(np.abs(gal_lat) < center_width, 1, 0) 

289 # Apply the galactic longitude cuts, so that plane goes between gal_long1 to gal_long2. 

290 # This is NOT the shortest distance between the angles. 

291 gp_length = (gal_long2 - gal_long1) % 360 

292 # If the length is greater than 0 then we can add additional cuts. 

293 if gp_length > 0: 

294 # First, remove anything outside the gal_long1/gal_long2 region. 

295 sky = np.where(int_rounded((gal_lon - gal_long1) % 360) < int_rounded(gp_length), sky, 0) 

296 # Add the tapers. 

297 # These slope from the center (gp_center @ center_width) 

298 # to the edge (gp_center + gp_length/2 @ end_width). 

299 half_width = gp_length / 2. 

300 slope = (center_width - end_width) / half_width 

301 gp_center = (gal_long1 + half_width) % 360 

302 gp_dist = gal_lon - gp_center 

303 gp_dist = np.abs(np.where(int_rounded(gp_dist) > int_rounded(180), (180 - gp_dist) % 180, gp_dist)) 

304 lat_limit = np.abs(center_width - slope * gp_dist) 

305 sky = np.where(int_rounded(np.abs(gal_lat)) < int_rounded(lat_limit), sky, 0) 

306 return sky 

307 

308 

309def magellanic_clouds_healpixels(nside=None, lmc_radius=10, smc_radius=5): 

310 """ 

311 Define the Galactic Plane region. Return a healpix map with GP pixels as 1. 

312 """ 

313 if nside is None: 

314 nside = set_default_nside() 

315 ra, dec = ra_dec_hp_map(nside=nside) 

316 result = np.zeros(hp.nside2npix(nside)) 

317 

318 lmc_ra = np.radians(80.893860) 

319 lmc_dec = np.radians(-69.756126) 

320 lmc_radius = np.radians(lmc_radius) 

321 

322 smc_ra = np.radians(13.186588) 

323 smc_dec = np.radians(-72.828599) 

324 smc_radius = np.radians(smc_radius) 

325 

326 dist_to_lmc = _angularSeparation(lmc_ra, lmc_dec, ra, dec) 

327 lmc_pix = np.where(int_rounded(dist_to_lmc) < int_rounded(lmc_radius)) 

328 result[lmc_pix] = 1 

329 

330 dist_to_smc = _angularSeparation(smc_ra, smc_dec, ra, dec) 

331 smc_pix = np.where(int_rounded(dist_to_smc) < int_rounded(smc_radius)) 

332 result[smc_pix] = 1 

333 return result 

334 

335 

336def generate_goal_map(nside=None, NES_fraction = .3, WFD_fraction = 1., 

337 SCP_fraction=0.4, GP_fraction = 0.2, 

338 NES_min_EB = -30., NES_max_EB = 10, NES_dec_min = 3.6, 

339 SCP_dec_max=-62.5, gp_center_width=10., 

340 gp_end_width=4., gp_long1=290., gp_long2=70., 

341 wfd_dec_min=-62.5, wfd_dec_max=3.6, 

342 generate_id_map=False): 

343 """ 

344 Handy function that will put together a target map in the proper order. 

345 """ 

346 if nside is None: 

347 nside = set_default_nside() 

348 

349 # Note, some regions overlap, thus order regions are added is important. 

350 result = np.zeros(hp.nside2npix(nside), dtype=float) 

351 id_map = np.zeros(hp.nside2npix(nside), dtype=int) 

352 pid = 1 

353 prop_name_dict = dict() 

354 

355 if NES_fraction > 0.: 

356 nes = NES_healpixels(nside=nside, min_EB = NES_min_EB, max_EB = NES_max_EB, 

357 dec_min=NES_dec_min) 

358 result[np.where(nes != 0)] = 0 

359 result += NES_fraction*nes 

360 id_map[np.where(nes != 0)] = 1 

361 pid += 1 

362 prop_name_dict[1] = 'NorthEclipticSpur' 

363 

364 if WFD_fraction > 0.: 

365 wfd = WFD_healpixels(nside=nside, dec_min=wfd_dec_min, dec_max=wfd_dec_max) 

366 result[np.where(wfd != 0)] = 0 

367 result += WFD_fraction*wfd 

368 id_map[np.where(wfd != 0)] = 3 

369 pid += 1 

370 prop_name_dict[3] = 'WideFastDeep' 

371 

372 if SCP_fraction > 0.: 

373 scp = SCP_healpixels(nside=nside, dec_max=SCP_dec_max) 

374 result[np.where(scp != 0)] = 0 

375 result += SCP_fraction*scp 

376 id_map[np.where(scp != 0)] = 2 

377 pid += 1 

378 prop_name_dict[2] = 'SouthCelestialPole' 

379 

380 if GP_fraction > 0.: 

381 gp = galactic_plane_healpixels(nside=nside, center_width=gp_center_width, 

382 end_width=gp_end_width, gal_long1=gp_long1, 

383 gal_long2=gp_long2) 

384 result[np.where(gp != 0)] = 0 

385 result += GP_fraction*gp 

386 id_map[np.where(gp != 0)] = 4 

387 pid += 1 

388 prop_name_dict[4] = 'GalacticPlane' 

389 

390 if generate_id_map: 

391 return result, id_map, prop_name_dict 

392 else: 

393 return result 

394 

395 

396def standard_goals(nside=None): 

397 """ 

398 A quick function to generate the "standard" goal maps. This is the traditional WFD/mini survey footprint. 

399 """ 

400 if nside is None: 

401 nside = set_default_nside() 

402 

403 result = {} 

404 result['u'] = generate_goal_map(nside=nside, NES_fraction=0., 

405 WFD_fraction=0.31, SCP_fraction=0.15, 

406 GP_fraction=0.15, 

407 wfd_dec_min=-62.5, wfd_dec_max=3.6) 

408 result['g'] = generate_goal_map(nside=nside, NES_fraction=0.2, 

409 WFD_fraction=0.44, SCP_fraction=0.15, 

410 GP_fraction=0.15, 

411 wfd_dec_min=-62.5, wfd_dec_max=3.6) 

412 result['r'] = generate_goal_map(nside=nside, NES_fraction=0.46, 

413 WFD_fraction=1.0, SCP_fraction=0.15, 

414 GP_fraction=0.15, 

415 wfd_dec_min=-62.5, wfd_dec_max=3.6) 

416 result['i'] = generate_goal_map(nside=nside, NES_fraction=0.46, 

417 WFD_fraction=1.0, SCP_fraction=0.15, 

418 GP_fraction=0.15, 

419 wfd_dec_min=-62.5, wfd_dec_max=3.6) 

420 result['z'] = generate_goal_map(nside=nside, NES_fraction=0.4, 

421 WFD_fraction=0.9, SCP_fraction=0.15, 

422 GP_fraction=0.15, 

423 wfd_dec_min=-62.5, wfd_dec_max=3.6) 

424 result['y'] = generate_goal_map(nside=nside, NES_fraction=0., 

425 WFD_fraction=0.9, SCP_fraction=0.15, 

426 GP_fraction=0.15, 

427 wfd_dec_min=-62.5, wfd_dec_max=3.6) 

428 return result 

429 

430 

431def calc_norm_factor(goal_dict, radius=1.75): 

432 """Calculate how to normalize a Target_map_basis_function. 

433 This is basically: 

434 the area of the fov / area of a healpixel / 

435 the sum of all of the weighted-healpix values in the footprint. 

436 

437 Parameters 

438 ----------- 

439 goal_dict : dict of healpy maps 

440 The target goal map(s) being used 

441 radius : float (1.75) 

442 Radius of the FoV (degrees) 

443 

444 Returns 

445 ------- 

446 Value to use as Target_map_basis_function norm_factor kwarg 

447 """ 

448 all_maps_sum = 0 

449 for key in goal_dict: 

450 good = np.where(goal_dict[key] > 0) 

451 all_maps_sum += goal_dict[key][good].sum() 

452 nside = hp.npix2nside(goal_dict[key].size) 

453 hp_area = hp.nside2pixarea(nside, degrees=True) 

454 norm_val = radius**2*np.pi/hp_area/all_maps_sum 

455 return norm_val 

456 

457 

458def filter_count_ratios(target_maps): 

459 """Given the goal maps, compute the ratio of observations we want in each filter. 

460 This is basically: 

461 per filter, sum the number of pixels in each map and return this per filter value, normalized 

462 so that the total sum across all filters is 1. 

463 """ 

464 results = {} 

465 all_norm = 0. 

466 for key in target_maps: 

467 good = target_maps[key] > 0 

468 results[key] = np.sum(target_maps[key][good]) 

469 all_norm += results[key] 

470 for key in results: 

471 results[key] /= all_norm 

472 return results