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 __future__ import division 

2import numpy as np 

3import healpy as hp 

4import warnings 

5 

6__all__ = ['hpid2RaDec', 'raDec2Hpid', 'healbin', '_hpid2RaDec', '_raDec2Hpid', 

7 '_healbin', 'moc2array', 'hp_grow_argsort'] 

8 

9 

10def _hpid2RaDec(nside, hpids, **kwargs): 

11 """ 

12 Correct for healpy being silly and running dec from 0-180. 

13 

14 Parameters 

15 ---------- 

16 nside : int 

17 Must be a value of 2^N. 

18 hpids : np.array 

19 Array (or single value) of healpixel IDs. 

20 

21 Returns 

22 ------- 

23 raRet : float (or np.array) 

24 RA positions of the input healpixel IDs. In radians. 

25 decRet : float (or np.array) 

26 Dec positions of the input healpixel IDs. In radians. 

27 """ 

28 

29 lat, lon = hp.pix2ang(nside, hpids, **kwargs) 

30 decRet = np.pi / 2.0 - lat 

31 raRet = lon 

32 

33 return raRet, decRet 

34 

35 

36def hpid2RaDec(nside, hpids, **kwargs): 

37 """ 

38 Correct for healpy being silly and running dec from 0-180. 

39 

40 Parameters 

41 ---------- 

42 nside : int 

43 Must be a value of 2^N. 

44 hpids : np.array 

45 Array (or single value) of healpixel IDs. 

46 

47 Returns 

48 ------- 

49 raRet : float (or np.array) 

50 RA positions of the input healpixel IDs. In degrees. 

51 decRet : float (or np.array) 

52 Dec positions of the input healpixel IDs. In degrees. 

53 """ 

54 ra, dec = _hpid2RaDec(nside, hpids, **kwargs) 

55 return np.degrees(ra), np.degrees(dec) 

56 

57 

58def _raDec2Hpid(nside, ra, dec, **kwargs): 

59 """ 

60 Assign ra,dec points to the correct healpixel. 

61 

62 Parameters 

63 ---------- 

64 nside : int 

65 Must be a value of 2^N. 

66 ra : np.array 

67 RA values to assign to healpixels. Radians. 

68 dec : np.array 

69 Dec values to assign to healpixels. Radians. 

70 

71 Returns 

72 ------- 

73 hpids : np.array 

74 Healpixel IDs for the input positions. 

75 """ 

76 lat = np.pi / 2.0 - dec 

77 hpids = hp.ang2pix(nside, lat, ra, **kwargs) 

78 return hpids 

79 

80 

81def raDec2Hpid(nside, ra, dec, **kwargs): 

82 """ 

83 Assign ra,dec points to the correct healpixel. 

84 

85 Parameters 

86 ---------- 

87 nside : int 

88 Must be a value of 2^N. 

89 ra : np.array 

90 RA values to assign to healpixels. Degrees. 

91 dec : np.array 

92 Dec values to assign to healpixels. Degrees. 

93 

94 Returns 

95 ------- 

96 hpids : np.array 

97 Healpixel IDs for the input positions. 

98 """ 

99 return _raDec2Hpid(nside, np.radians(ra), np.radians(dec), **kwargs) 

100 

101 

102def _healbin(ra, dec, values, nside=128, reduceFunc=np.mean, dtype=float, fillVal=hp.UNSEEN): 

103 """ 

104 Take arrays of ra's, dec's, and value and bin into healpixels. Like numpy.hexbin but for 

105 bins on a sphere. 

106 

107 Parameters 

108 ---------- 

109 ra : np.array 

110 RA positions of the data points. Radians. 

111 dec : np.array 

112 Dec positions of the data points. Radians 

113 values : np.array 

114 The values at each ra,dec position. 

115 nside : int 

116 Healpixel nside resolution. Must be a value of 2^N. 

117 reduceFunc : function (numpy.mean) 

118 A function that will return a single value given a subset of `values`. 

119 dtype : dtype ('float') 

120 Data type of the resulting mask 

121 

122 Returns 

123 ------- 

124 mapVals : np.array 

125 A numpy array that is a valid Healpixel map. 

126 """ 

127 

128 hpids = _raDec2Hpid(nside, ra, dec) 

129 

130 order = np.argsort(hpids) 

131 hpids = hpids[order] 

132 values = values[order] 

133 pixids = np.unique(hpids) 

134 

135 left = np.searchsorted(hpids, pixids) 

136 right = np.searchsorted(hpids, pixids, side='right') 

137 

138 mapVals = np.zeros(hp.nside2npix(nside), dtype=dtype) + fillVal 

139 

140 # Wow, I thought histogram would be faster than the loop, but this has been faster! 

141 for i, idx in enumerate(pixids): 

142 mapVals[idx] = reduceFunc(values[left[i]:right[i]]) 

143 

144 # Change any NaNs to fill value 

145 mapVals[np.isnan(mapVals)] = fillVal 

146 

147 return mapVals 

148 

149 

150def healbin(ra, dec, values, nside=128, reduceFunc=np.mean, dtype=float): 

151 """ 

152 Take arrays of ra's, dec's, and value and bin into healpixels. Like numpy.hexbin but for 

153 bins on a sphere. 

154 

155 Parameters 

156 ---------- 

157 ra : np.array 

158 RA positions of the data points. Degrees. 

159 dec : np.array 

160 Dec positions of the data points. Degrees. 

161 values : np.array 

162 The values at each ra,dec position. 

163 nside : int 

164 Healpixel nside resolution. Must be a value of 2^N. 

165 reduceFunc : function (numpy.mean) 

166 A function that will return a single value given a subset of `values`. 

167 dtype : dtype ('float') 

168 Data type of the resulting mask 

169 

170 Returns 

171 ------- 

172 mapVals : np.array 

173 A numpy array that is a valid Healpixel map. 

174 """ 

175 return _healbin(np.radians(ra), np.radians(dec), values, nside=nside, 

176 reduceFunc=reduceFunc, dtype=dtype) 

177 

178 

179def moc2array(data, uniq, nside=128, reduceFunc=np.sum, density=True, fillVal=0.): 

180 """Convert a Multi-Order Coverage Map to a single nside HEALPix array. Useful 

181 for converting maps output by LIGO alerts. Expect that future versions of 

182 healpy or astropy will be able to replace this functionality. Note that this is 

183 a convienence function that will probably degrade portions of the MOC that are 

184 sampled at high resolution. 

185 

186 Details of HEALPix Mulit-Order Coverage map: http://ivoa.net/documents/MOC/20190404/PR-MOC-1.1-20190404.pdf 

187 

188 Parameters 

189 ---------- 

190 data : np.array 

191 Data values for the MOC map 

192 uniq : np.array 

193 The UNIQ values for the MOC map 

194 nside : int (128) 

195 The output map nside 

196 reduceFunc : function (np.sum) 

197 The function to use to combine data into single healpixels. 

198 density : bool (True) 

199 If True, multiplies data values by pixel area before applying reduceFunc, and divides 

200 the final array by the output pixel area. Should be True if working on a probability density MOC. 

201 fillVal : float (0.) 

202 Value to fill empty HEALPixels with. Good choices include 0 (default), hp.UNSEEN, and np.nan. 

203 

204 Returns 

205 ------- 

206 np.array : HEALpy array of nside. Units should be the same as the input map as processed by reduceFunc. 

207 """ 

208 

209 # NUNIQ packing, from page 12 of http://ivoa.net/documents/MOC/20190404/PR-MOC-1.1-20190404.pdf 

210 orders = np.floor(np.log2(uniq / 4) / 2).astype(int) 

211 npixs = (uniq - 4 * 4**orders).astype(int) 

212 

213 nsides = 2**orders 

214 names = ['ra', 'dec', 'area'] 

215 types = [float]*len(names) 

216 data_points = np.zeros(data.size, dtype=list(zip(names, types))) 

217 for order in np.unique(orders): 

218 good = np.where(orders == order) 

219 ra, dec = _hpid2RaDec(nsides[good][0], npixs[good], nest=True) 

220 data_points['ra'][good] = ra 

221 data_points['dec'][good] = dec 

222 data_points['area'][good] = hp.nside2pixarea(nsides[good][0]) 

223 

224 if density: 

225 tobin_data = data*data_points['area'] 

226 else: 

227 tobin_data = data 

228 

229 result = _healbin(data_points['ra'], data_points['dec'], tobin_data, nside=nside, 

230 reduceFunc=reduceFunc, fillVal=fillVal) 

231 

232 if density: 

233 good = np.where(result != fillVal) 

234 result[good] = result[good] / hp.nside2pixarea(nside) 

235 

236 return result 

237 

238 

239def hp_grow_argsort(in_map, ignore_nan=True): 

240 """Find the maximum of a healpix map, then orders healpixels by selecting the maximum bordering the selected area. 

241 

242 Parameters 

243 ---------- 

244 in_map : np.array 

245 A valid HEALpix array 

246 ignore_nan : bool (True) 

247 If true, ignores values that are NaN 

248 

249 Returns 

250 ------- 

251 ordered_hp : int array 

252 The indices that put in_map in the correct order 

253 """ 

254 nside = hp.npix2nside(np.size(in_map)) 

255 npix = np.size(in_map) 

256 pix_indx = np.arange(npix) 

257 

258 if ignore_nan: 

259 not_nan_pix = ~np.isnan(in_map) 

260 npix = np.size(in_map[not_nan_pix]) 

261 

262 # Check if we have already run with this nside 

263 if hasattr(hp_grow_argsort, 'nside'): 

264 nside_match = nside == hp_grow_argsort.nside 

265 else: 

266 nside_match = False 

267 

268 # If we already have neighbors chached, just use it 

269 if nside_match: 

270 neighbors = hp_grow_argsort.neighbors_cache 

271 else: 

272 # Running a new nside, or for the first time, compute neighbors and set attributes 

273 # Make a boolean area to keep track of which pixels still need to be sorted 

274 neighbors = hp.get_all_neighbours(nside, pix_indx).T 

275 hp_grow_argsort.neighbors_cache = neighbors 

276 hp_grow_argsort.nside = nside 

277 

278 valid_neighbors_mask = np.ones(neighbors.shape, dtype=bool) 

279 

280 # Sometimes there can be no neighbors in some directions 

281 valid_neighbors_mask[np.where(neighbors == -1)] = False 

282 

283 ordered_hp = np.zeros(npix, dtype=int) 

284 current_max = np.where(in_map == np.nanmax(in_map))[0].min() 

285 

286 ordered_hp[0] = current_max 

287 

288 # Remove max from valid_neighbors. Can be clever with indexing 

289 # so we don't have to do a brute force search of the entire 

290 # neghbors array to mask it. 

291 # valid_neighbors_mask[np.where(neighbors == current_max)] = False 

292 current_neighbors = neighbors[current_max][valid_neighbors_mask[current_max]] 

293 sub_indx = np.where(neighbors[current_neighbors] == current_max) 

294 valid_neighbors_mask[(current_neighbors[sub_indx[0]], sub_indx[1])] = False 

295 

296 for i in np.arange(1, npix): 

297 current_neighbors = neighbors[ordered_hp[0:i]][valid_neighbors_mask[ordered_hp[0:i]]] 

298 indx = np.where(in_map[current_neighbors] == np.nanmax(in_map[current_neighbors]))[0] 

299 if np.size(indx) == 0: 

300 # We can't connect to any more pixels 

301 warnings.warn('Can not connect to any more pixels.') 

302 return ordered_hp[0:i] 

303 else: 

304 indx = np.min(indx) 

305 current_max = current_neighbors[indx] 

306 ordered_hp[i] = current_max 

307 # current_max is no longer a valid neighbor to consider 

308 neighbors_of_current = neighbors[current_max] 

309 sub_indx = np.where(neighbors[neighbors_of_current] == current_max) 

310 valid_neighbors_mask[(neighbors_of_current[sub_indx[0]], sub_indx[1])] = False 

311 

312 return ordered_hp