Coverage for python/lsst/sims/featureScheduler/utils/footprints.py : 11%

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.
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.).
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"""
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
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']
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
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
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).
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.
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.
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()
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
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))
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}
97def WFD_healpixels(nside=None, dec_min=-62.5, dec_max=3.6):
98 """
99 Define a region based on declination limits only.
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.
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()
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
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.
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.
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
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
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.
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.
185 Returns
186 -------
187 result : numpy array
188 """
189 if nside is None:
190 nside = set_default_nside()
192 ra, dec = ra_dec_hp_map(nside=nside)
193 dustmap = get_dustmap(nside)
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
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()
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
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.
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
235 Returns
236 -------
237 result : numpy array
238 """
239 if nside is None:
240 nside = set_default_nside()
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
253 return result
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.
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!
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)
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
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))
318 lmc_ra = np.radians(80.893860)
319 lmc_dec = np.radians(-69.756126)
320 lmc_radius = np.radians(lmc_radius)
322 smc_ra = np.radians(13.186588)
323 smc_dec = np.radians(-72.828599)
324 smc_radius = np.radians(smc_radius)
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
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
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()
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()
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'
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'
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'
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'
390 if generate_id_map:
391 return result, id_map, prop_name_dict
392 else:
393 return result
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()
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
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.
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)
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
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