Coverage for python/lsst/sims/catUtils/dust/EBV.py : 17%

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 os
4import numpy
5from astropy.io import fits
7from lsst.sims.utils.CodeUtilities import sims_clean_up
8from lsst.sims.utils import _galacticFromEquatorial
9from functools import reduce
11__all__ = ["EBVmap", "EBVbase"]
14def interp1D(z1, z2, offset):
15 """ 1D interpolation on a grid"""
17 zPrime = (z2-z1)*offset + z1
19 return zPrime
22class EBVmap(object):
23 '''Class for describing a map of EBV
25 Images are read in from a fits file and assume a ZEA projection
26 '''
28 def __del__(self):
29 self.hdulist.close()
31 def readMapFits(self, fileName):
32 """ read a fits file containing the ebv data"""
34 self._file_name = fileName
36 self.hdulist = fits.open(fileName)
37 self.header = self.hdulist[0].header
38 self.data = self.hdulist[0].data
39 self.nr = self.data.shape[0]
40 self.nc = self.data.shape[1]
42 # read WCS information
43 self.cd11 = self.header['CD1_1']
44 self.cd22 = self.header['CD2_2']
45 self.cd12 = 0.
46 self.cd21 = 0.
48 self.crpix1 = self.header['CRPIX1']
49 self.crval1 = self.header['CRVAL1']
51 self.crpix2 = self.header['CRPIX2']
52 self.crval2 = self.header['CRVAL2']
54 # read projection information
55 self.nsgp = self.header['LAM_NSGP']
56 self.scale = self.header['LAM_SCAL']
57 self.lonpole = self.header['LONPOLE']
59 def xyFromSky(self, gLon, gLat):
60 """ convert long, lat angles to pixel x y
62 input angles are in radians but the conversion assumes radians
64 @param [in] gLon galactic longitude in radians
66 @param [in] gLat galactic latitude in radians
68 @param [out] x is the x pixel coordinate
70 @param [out] y is the y pixel coordinate
72 """
74 rad2deg = 180./numpy.pi
76 # use the SFD approach to define xy pixel positions
77 # ROTATION - Equn (4) - degenerate case
78 if (self.crval2 > 89.9999):
79 theta = gLat*rad2deg
80 phi = gLon*rad2deg + 180.0 + self.lonpole - self.crval1
81 elif (self.crval2 < -89.9999):
82 theta = -gLat*rad2deg
83 phi = self.lonpole + self.crval1 - gLon*rad2deg
84 else:
85 # Assume it's an NGP projection ...
86 theta = gLat*rad2deg
87 phi = gLon*rad2deg + 180.0 + self.lonpole - self.crval1
89 # Put phi in the range [0,360) degrees
90 phi = phi - 360.0 * numpy.floor(phi/360.0)
92 # FORWARD MAP PROJECTION - Equn (26)
93 Rtheta = 2.0 * rad2deg * numpy.sin((0.5 / rad2deg) * (90.0 - theta))
95 # Equns (10), (11)
96 xr = Rtheta * numpy.sin(phi / rad2deg)
97 yr = - Rtheta * numpy.cos(phi / rad2deg)
99 # SCALE FROM PHYSICAL UNITS - Equn (3) after inverting the matrix
100 denom = self.cd11 * self.cd22 - self.cd12 * self.cd21
101 x = (self.cd22 * xr - self.cd12 * yr) / denom + (self.crpix1 - 1.0)
102 y = (self.cd11 * yr - self.cd21 * xr) / denom + (self.crpix2 - 1.0)
104 return x, y
106 def generateEbv(self, glon, glat, interpolate = False):
107 """
108 Calculate EBV with option for interpolation
110 @param [in] glon galactic longitude in radians
112 @param [in] galactic latitude in radians
114 @param [out] ebvVal the scalar value of EBV extinction
116 """
118 # calculate pixel values
119 x, y = self.xyFromSky(glon, glat)
121 ix = (x + 0.5).astype(int)
122 iy = (y + 0.5).astype(int)
124 unity = numpy.ones(len(ix), dtype=int)
126 if (interpolate):
128 # find the indices of the pixels bounding the point of interest
129 ixLow = numpy.minimum(ix, (self.nc - 2)*unity)
130 ixHigh = ixLow + 1
131 dx = x - ixLow
133 iyLow = numpy.minimum(iy, (self.nr - 2)*unity)
134 iyHigh = iyLow + 1
135 dy = y - iyLow
137 # interpolate the EBV value at the point of interest by interpolating
138 # first in x and then in y
139 x1 = numpy.array([self.data[ii][jj] for (ii, jj) in zip(iyLow, ixLow)])
140 x2 = numpy.array([self.data[ii][jj] for (ii, jj) in zip(iyLow, ixHigh)])
141 xLow = interp1D(x1, x2, dx)
143 x1 = numpy.array([self.data[ii][jj] for (ii, jj) in zip(iyHigh, ixLow)])
144 x2 = numpy.array([self.data[ii][jj] for (ii, jj) in zip(iyHigh, ixHigh)])
145 xHigh = interp1D(x1, x2, dx)
147 ebvVal = interp1D(xLow, xHigh, dy)
149 else:
150 ebvVal = numpy.array([self.data[ii][jj] for (ii, jj) in zip(iy, ix)])
152 return ebvVal
154 def xyIntFromSky(self, gLong, gLat):
155 x, y = self.xyFromSky(gLong, gLat)
156 ix = int(x + 0.5)
157 iy = int(y + 0.5)
159 return ix, iy
162class EBVbase(object):
163 """
164 This class will give users access to calculateEbv oustide of the framework of a catalog.
166 To find the value of EBV at a point on the sky, create an instance of this object, and
167 then call calculateEbv passing the coordinates of interest as kwargs
169 e.g.
171 ebvObject = EBVbase()
172 ebvValue = ebvObject.calculateEbv(galacticCoordinates = myGalacticCoordinates)
174 or
176 ebvValue = ebvObject.calculateEbv(equatorialCoordinates = myEquatorialCoordinates)
178 where myGalacticCoordinates is a 2-d numpy array where the first row is galactic longitude
179 and the second row is galactic latitude.
181 myEquatorialCoordinates is a 2-d numpy array where the first row is RA and the second row
182 is dec
184 All coordinates are in radians
186 You can also specify dust maps in the northern and southern galactic hemispheres, but
187 there are default values that the code will automatically load (see the class variables
188 below).
190 The information regarding where the dust maps are located is stored in
191 member variables ebvDataDir, ebvMapNorthName, ebvMapSouthName
193 The actual dust maps (when loaded) are stored in ebvMapNorth and ebvMapSouth
194 """
196 # these variables will tell the mixin where to get the dust maps
197 ebvDataDir = os.environ.get("SIMS_MAPS_DIR")
198 ebvMapNorthName = "DustMaps/SFD_dust_4096_ngp.fits"
199 ebvMapSouthName = "DustMaps/SFD_dust_4096_sgp.fits"
200 ebvMapNorth = None
201 ebvMapSouth = None
203 # A dict to hold every open instance of an EBVmap.
204 # Since this is being declared outside of the constructor,
205 # it will be a class member, which means that, every time
206 # an EBVmap is added to the cache, all EBVBase instances will
207 # know about it.
208 _ebv_map_cache = {}
210 # the set_xxxx routines below will allow the user to point elsewhere for the dust maps
211 def set_ebvMapNorth(self, word):
212 """
213 This allows the user to pick a new northern SFD map file
214 """
215 self.ebvMapNorthName = word
217 def set_ebvMapSouth(self, word):
218 """
219 This allows the user to pick a new southern SFD map file
220 """
221 self.ebvMapSouthName = word
223 # these routines will load the dust maps for the galactic north and south hemispheres
224 def _load_ebv_map(self, file_name):
225 """
226 Load the EBV map specified by file_name. If that map has already been loaded,
227 just return the map stored in self._ebv_map_cache. If it must be loaded, store
228 it in the cache.
229 """
230 if file_name in self._ebv_map_cache:
231 return self._ebv_map_cache[file_name]
233 ebv_map = EBVmap()
234 ebv_map.readMapFits(file_name)
235 self._ebv_map_cache[file_name] = ebv_map
236 return ebv_map
238 def load_ebvMapNorth(self):
239 """
240 This will load the northern SFD map
241 """
242 file_name = os.path.join(self.ebvDataDir, self.ebvMapNorthName)
243 self.ebvMapNorth = self._load_ebv_map(file_name)
244 return None
246 def load_ebvMapSouth(self):
247 """
248 This will load the southern SFD map
249 """
250 file_name = os.path.join(self.ebvDataDir, self.ebvMapSouthName)
251 self.ebvMapSouth = self._load_ebv_map(file_name)
252 return None
254 def calculateEbv(self, galacticCoordinates=None, equatorialCoordinates=None, northMap=None, southMap=None,
255 interp=False):
256 """
257 For an array of Gal long, lat calculate E(B-V)
260 @param [in] galacticCoordinates is a numpy.array; the first row is galactic longitude,
261 the second row is galactic latitude in radians
263 @param [in] equatorialCoordinates is a numpy.array; the first row is RA, the second row is Dec in
264 radians
266 @param [in] northMap the northern dust map
268 @param [in] southMap the southern dust map
270 @param [in] interp is a boolean determining whether or not to interpolate the EBV value
272 @param [out] ebv is a list of EBV values for all of the gLon, gLat pairs
274 """
276 # raise an error if the coordinates are specified in both systems
277 if galacticCoordinates is not None:
278 if equatorialCoordinates is not None:
279 raise RuntimeError("Specified both galacticCoordinates and "
280 "equatorialCoordinates in calculateEbv")
282 # convert (ra,dec) into gLon, gLat
283 if galacticCoordinates is None:
285 # raise an error if you did not specify ra or dec
286 if equatorialCoordinates is None:
287 raise RuntimeError("Must specify coordinates in calculateEbv")
289 galacticCoordinates = numpy.array(_galacticFromEquatorial(equatorialCoordinates[0, :],
290 equatorialCoordinates[1, :]))
292 if northMap is None:
293 if self.ebvMapNorth is None:
294 self.load_ebvMapNorth()
296 northMap = self.ebvMapNorth
298 if southMap is None:
299 if self.ebvMapSouth is None:
300 self.load_ebvMapSouth()
302 southMap = self.ebvMapSouth
304 ebv = None
306 if galacticCoordinates.shape[1] > 0:
308 ebv = numpy.zeros(len(galacticCoordinates[0, :]))
310 # identify (by index) which points are in the galactic northern hemisphere
311 # and which points are in the galactic southern hemisphere
312 # taken from
313 # http://stackoverflow.com/questions/4578590/python-equivalent-of-filter-getting-two-output-lists-i-e-partition-of-a-list
314 inorth, isouth = reduce(lambda x, y: x[not y[1] > 0.0].append(y[0]) or x,
315 enumerate(galacticCoordinates[1, :]), ([], []))
317 nSet = galacticCoordinates[:, inorth]
318 sSet = galacticCoordinates[:, isouth]
320 ebvNorth = northMap.generateEbv(nSet[0, :], nSet[1, :], interpolate=interp)
321 ebvSouth = southMap.generateEbv(sSet[0, :], sSet[1, :], interpolate=interp)
323 for (i, ee) in zip(inorth, ebvNorth):
324 ebv[i] = ee
326 for (i, ee) in zip(isouth, ebvSouth):
327 ebv[i] = ee
329 return ebv
332sims_clean_up.targets.append(EBVbase._ebv_map_cache)