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# (c) Stefan Countryman 2019 

2 

3""" 

4Utility functions used across healpix_skymap classes. 

5""" 

6 

7from math import pi 

8import logging 

9import gzip 

10from tempfile import NamedTemporaryFile 

11import binascii 

12 

13LOGGER = logging.getLogger(__name__) 

14GZIP_BUFFSIZE = 10**5 

15 

16 

17def sky_area(): 

18 """Get the area of the entire sky as an ``Astropy.units.Quantity``.""" 

19 from astropy.units import Quantity 

20 return Quantity(4*pi, "rad2") 

21 

22 

23def sky_area_deg(): 

24 """Get the area of the entire sky in square degrees.""" 

25 return sky_area().to("deg**2").value 

26 

27 

28def dangle_rad(ra, dec, mapra, mapdec): # pylint: disable=invalid-name 

29 """Get an array of angular distances in radians between the point specified 

30 in ``ra``, ``dec`` and the points specified in ``mapra``, ``mapdec``. All 

31 angles, including the return value, are specified in radians. 

32 

33 sin(DEC1)*sin(DEC2) + cos(DEC1)*cos(DEC2)*cos(RA2-RA1) 

34 

35 So we get the angle by taking the arccos of this function. 

36 

37 Parameters 

38 ---------- 

39 ra : float 

40 Right ascension of the single point [radians] 

41 dec : float 

42 Declination of the single point [radians] 

43 mapra : array 

44 Right ascensions whose angular distances to the single point will be 

45 calculated; must be same length as ``mapdec``. [radians] 

46 mapdec : array 

47 Declinations whose angular distances to the single point will be 

48 calculated; must be same length as ``mapra``. [radians] 

49 

50 Returns 

51 ------- 

52 dangles : array 

53 Angular distances between the single point provided and the arrays of 

54 points [radians]. 

55 """ 

56 import numpy as np 

57 delta_ra = mapra - ra 

58 dot_prod = (np.sin(mapdec)*np.sin(dec) + 

59 np.cos(mapdec)*np.cos(dec)*np.cos(delta_ra)) 

60 return np.arccos(dot_prod) 

61 

62 

63def nest2uniq(indices, nside): 

64 """Return the NUNIQ pixel indices for nested ``indices`` with 

65 NSIDE=``nside``. 

66 

67 Parameters 

68 ---------- 

69 indices : array 

70 Indices of HEALPix pixels in NEST ordering. 

71 nside : int 

72 A valid NSIDE value not greater than any of the NSIDE values of the 

73 ``indices``. 

74 

75 Returns 

76 ------- 

77 uniq : array 

78 An array of HEALPix pixels in NEST ordering corresponding to the input 

79 ``indices`` and ``nside``. 

80 

81 Raises 

82 ------ 

83 ValueError 

84 If ``nside`` is an invalid NSIDE value, i.e. not a power of 2. 

85 

86 Examples 

87 -------- 

88 >>> import numpy as np 

89 >>> nest2uniq(np.array([284, 286, 287, 10, 8, 2, 279, 278, 285]), 8) 

90 array([540, 542, 543, 266, 264, 258, 535, 534, 541]) 

91 """ 

92 check_valid_nside(nside) 

93 return indices + 4*nside**2 

94 

95 

96def check_valid_nside(nside): 

97 """Checks whether ``nside`` is a valid HEALPix NSIDE value. Raises a 

98 ValueError if it is not. 

99 

100 Parameters 

101 ---------- 

102 nside : int or array 

103 An integer or array of integers representing HEALPix NSIDE values. 

104 

105 Raises 

106 ------ 

107 ValueError 

108 If any of the values are not valid HEALPix NSIDE values. 

109 """ 

110 import numpy as np 

111 if np.any(2**np.floor(np.log2(nside)) != nside): 

112 raise ValueError("Not a valid NSIDE value: {}".format(nside)) 

113 

114 

115def check_valid_nuniq(indices): 

116 """Checks that ``indices`` are valid NUNIQ indices. 

117 

118 Raises 

119 ------ 

120 ValueError 

121 If ``indices`` are not valid NUNIQ indices, i.e. they are not integers 

122 greater than 3. 

123 """ 

124 import numpy as np 

125 if not np.all(np.mod(indices, 1) == 0): 

126 raise ValueError("NUNIQ indices must be integers.") 

127 if not indices.min() > 3: 

128 raise ValueError("NUNIQ indices start at 4; found smaller values.") 

129 

130 

131def uniq2nside(indices): 

132 """Get the NSIDE value of the given NUNIQ-ordered indices. 

133 

134 Raises 

135 ------ 

136 ValueError 

137 If ``indices`` are not valid NUNIQ indices, i.e. they are not integers 

138 greater than 3. 

139 """ 

140 import numpy as np 

141 check_valid_nuniq(indices) 

142 return 2**np.array(np.floor(np.log2(indices/4.)/2.), dtype=int) 

143 

144 

145def uniq2nest_and_nside(indices): 

146 """Parameters 

147 ---------- 

148 indices : array 

149 A scalar or numpy array of NUNIQ indices 

150 

151 Returns 

152 ------- 

153 nest_indices : array 

154 The indices in nest ordering at their respective resolutions 

155 nside : array 

156 The resolution expressed as NSIDE of the indices given in 

157 ``nest_indices`` 

158 

159 Examples 

160 -------- 

161 >>> import numpy as np 

162 >>> nuniq = np.array([540, 542, 543, 266, 264, 258, 535, 534, 541]) 

163 >>> uniq2nest_and_nside(nuniq) 

164 (array([284, 286, 287, 10, 8, 2, 279, 278, 285]), array([8, 8, 8, 8, 8, 8, 8, 8, 8])) 

165 

166 Confirm that this is the inverse of nest2uniq: 

167 >>> all(nest2uniq(*uniq2nest_and_nside(nuniq)) == nuniq) 

168 True 

169 """ 

170 # uniq2nside implicitly checks whether the indices are valid NUNIQ indices 

171 nside = uniq2nside(indices) 

172 return indices - 4*nside**2, nside 

173 

174 

175def uniq2uniq_lowres(indices, nside): 

176 """Find the indices at resolutions ``nside`` (each of which MUST be lower 

177 resolution than that of the corresponding pixel in ``indices``) which 

178 contain the higher resolution pixels described by the corresponding 

179 ``indices``. 

180 

181 Parameters 

182 ---------- 

183 indices : array 

184 Indices of HEALPix pixels in NUNIQ ordering. 

185 nside : array 

186 A valid NSIDE value not greater than any of the NSIDE values of the 

187 ``indices``. Each of these corresponds to an index in ``indices``. 

188 

189 Returns 

190 ------- 

191 lowres_indices : array 

192 Indices of HEALPix pixels in NUNIQ ordering with resolution ``nside`` 

193 which contain the corresponding pixels in ``indices``. 

194 

195 Raises 

196 ------ 

197 ValueError 

198 If the NSIDE resolution of any index in ``indices`` is smaller than 

199 ``nside``, or if ``nside`` is an invalid NSIDE value, i.e. not a power 

200 of 2. 

201 

202 Examples 

203 -------- 

204 Take a few NUNIQ pixels at NSIDE=16 and an NUNIQ pixel at NSIDE=1024 and 

205 find which NUNIQ pixels they correspond to at NSIDE=8. The first four 

206 should all correspond to the same pixel at NSIDE=8. 

207 

208 >>> import numpy as np 

209 >>> indices = np.array([1024, 1025, 1026, 11295603]) 

210 >>> uniq2uniq_lowres(indices, 8) 

211 array([256, 256, 256, 689]) 

212 """ 

213 import numpy as np 

214 check_valid_nside(nside) 

215 # we will multiply by the following factor and take the floor to find the 

216 # corresponding lowres pixels. the values of nside_factor must be strictly 

217 # less-than or equal to 1. uniq2nside implicitly checks whether the indices 

218 # are valid NUNIQ indices. 

219 nside_factor = (uniq2nside(indices) / nside)**-2 

220 if np.max(nside_factor) > 1.0: 

221 raise ValueError(("nside must be lower than the NSIDE resolution of " 

222 "corresponding input indices.")) 

223 # use the fact that, at NSIDE=N, pixel p corresponds to NSIDE=2N pixels 

224 # 4p, 4p+1, 4p+2, 4p+3 

225 return np.array(np.floor(indices*nside_factor), dtype=int) 

226 

227 

228def uniq_intersection(indices1, indices2): 

229 """Downselect the pixel indices given in ``indices1`` to the set that 

230 overlaps with pixels in ``indices2`` and return pairs of indices into 

231 both of the input index lists showing which pixel in ``indices2`` each 

232 downselected pixel from ``indices1`` overlaps with. Use this to get rid of 

233 pixels outside of a given region, or alternatively use it as part of a 

234 calculation involving two multi-resolution skymaps whose pixel sizes are 

235 non-identical. 

236 Should have stable O(len(indices1)*len(indices2)) performance, so make 

237 sure that your index lists are not both huge. 

238 

239 Parameters 

240 ---------- 

241 indices1 : array 

242 Indices of HEALPix pixels in NUNIQ ordering. 

243 indices2 : array 

244 Indices of HEALPix pixels in NUNIQ ordering. 

245 

246 Returns 

247 ------- 

248 indices_into_indices1 : array 

249 Indices *into* ``indices1`` that overlap with ``indices2``. 

250 indices_into_indices2 : array 

251 Corresponding indices *into* ``indices2`` that overlap with 

252 ``indices1``. 

253 

254 Examples 

255 -------- 

256 Some pixels with NSIDE of 16, 32, and 64, respectively: 

257 >>> import numpy as np 

258 >>> indices1 = np.array([1024, 4100, 44096]) 

259 

260 Pixels at NSIDE = 32 that overlap with only the first and last pixels of 

261 ``indices1``: 

262 >>> indices2 = np.array([4096, 4097, 11024]) 

263 

264 We should see correspondence between index 0 of ``indices1`` and indices 0, 

265 1 of ``indices1``; and correspondence between index 2 of ``indices1`` and 

266 index 2 of ``indices2``: 

267 >>> uniq_intersection(indices1, indices2) 

268 (array([0, 0, 2]), array([0, 1, 2])) 

269 """ 

270 # create a grid of NSIDE differences where the rows correspond to 

271 # ``indices1`` and columns correspond to ``indices2``. ``nside_diff[i][j]`` 

272 # compares the ith index in ``indices1`` to the jth index in ``indices2`` 

273 # and calculates the factor that the higher-resolution pixel will need to 

274 # be multiplied by to make the pixel comparison. It is less than 1 if the 

275 # ith index from ``indices1`` has higher NSIDE (resolution) than the jth 

276 # from ``indices2``; 1 if they are the same; and greater than 1 if that 

277 # index from ``indices2`` has higher NSIDE. the case of equal resolution 

278 # is symmetric, so don't sweat it. Here we are using the fact that pixels 

279 # 4p, 4p+1, 4p+2, and 4p+3 in a double resolution NUNIQ skymap correspond 

280 # to pixel p to find overlap. uniq2nside implicitly checks whether the 

281 # indices are valid NUNIQ indices. 

282 import numpy as np 

283 nside_factor = (uniq2nside(indices1).reshape((-1, 1)) / 

284 uniq2nside(indices2))**-2 

285 ind1_highres = np.nonzero(nside_factor < 1) 

286 ind2_highres = np.nonzero(nside_factor >= 1) 

287 # reuse nside_factor as a temp array for the differences in pixels, which 

288 # will be in [0, 1) if the pixels overlap 

289 nside_factor[ind1_highres] = ( 

290 indices1[ind1_highres[0]] * nside_factor[ind1_highres] - 

291 indices2[ind1_highres[1]] 

292 ) 

293 nside_factor[ind2_highres] = ( 

294 indices2[ind2_highres[1]] / nside_factor[ind2_highres] - 

295 indices1[ind2_highres[0]] 

296 ) 

297 return np.nonzero(np.floor(nside_factor) == 0) 

298 

299 

300def uniq_rasterize(indices, values, density=True, nside=None): 

301 """Increase resolution of an NUNIQ HEALPix skymap and return NUNIQ indices 

302 and values at the same higher resolution (same NSIDE). Doesn't do any 

303 interpolation; just used to make calculations easier. 

304 

305 Parameters 

306 ---------- 

307 indices : array 

308 Non-overlapping HEALPix NUNIQ indices into the skymap. 

309 values : array 

310 Pixel values corresponding to the ``indices``. 

311 density : bool, optional 

312 Whether the pixel values are some sort of density. If True (the 

313 default), the pixel values will not be modified (since they don't 

314 depend on pixel area. Otherwise, the pixel value will be split evenly 

315 between sub-pixels. 

316 nside : int, optional 

317 The NSIDE value to convert the skymap to. If not provided, the skymap 

318 will be converted to the largest NSIDE of the provided pixels. 

319 

320 Returns 

321 ------- 

322 raster_indices : array 

323 A unique flat array of HEALPix NUNIQ indices into the rasterized 

324 skymap, sorted in ascending order. 

325 raster_values : array 

326 A flat array of pixel values of the rasterized skymap corresponding to 

327 ``raster_indices``. 

328 

329 Raises 

330 ------ 

331 ValueError 

332 If the given ``nside`` value is either an invalid NSIDE value or if it 

333 is lower than the highest NSIDE value calculated from ``indices``, or 

334 if the given ``indices`` are overlapping. 

335 

336 Examples 

337 -------- 

338 Some example inputs with NSIDE of 16, 32, and 64, respectively: 

339 >>> import numpy as np 

340 >>> indices = np.array([1024, 4100, 44096]) 

341 >>> values = np.ones_like(indices) 

342 

343 Rasterize to the max resolution of NSIDE=64, assuming values represent a 

344 density: 

345 >>> uniq_rasterize(indices, values) 

346 (array([16384, 16385, 16386, 16387, 16388, 16389, 16390, 16391, 16392, 

347 16393, 16394, 16395, 16396, 16397, 16398, 16399, 16400, 16401, 

348 16402, 16403, 44096]), array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])) 

349 

350 Rasterize just the first two indices, this time explicitly to NSIDE=64 (vs. 

351 the default max NSIDE, 32 in this case), explicitly treating the values NOT 

352 as a density (and therefore splitting them amongst subpixels): 

353 >>> uniq_rasterize(indices[0:2], values[0:2], density=False, nside=64) 

354 (array([16384, 16385, 16386, 16387, 16388, 16389, 16390, 16391, 16392, 

355 16393, 16394, 16395, 16396, 16397, 16398, 16399, 16400, 16401, 

356 16402, 16403]), array([0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 

357 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 

358 0.25 , 0.25 , 0.25 , 0.25 ])) 

359 """ 

360 import numpy as np 

361 indices = indices.flatten() 

362 values = values.flatten() 

363 nside_in = uniq2nside(indices) 

364 if nside is None: 

365 nside = np.max(nside_in) 

366 else: 

367 check_valid_nside(nside) 

368 if np.max(nside_in) > nside: 

369 raise ValueError("Invalid nside; must be greater than or equal to" 

370 " max nside of input skymap.") 

371 nside_factor = np.array((nside / nside_in)**2, dtype=int) 

372 scaled_values = values if density else values/nside_factor 

373 raster_values = np.repeat(scaled_values, nside_factor) 

374 raster_indices = np.repeat(nside_factor*indices, nside_factor) 

375 # This section is O(L) where L is the max number of LIGO MOC pixels 

376 # overlapping with the search area, bounded by ~20k pixels in current LIGO 

377 # MOC skymaps (L will be the number of for loops). In practice, it will 

378 # perform best when subdividing large pixels, which corresponds with the 

379 # typical non-detection case (which will make this method not only faster 

380 # asymptotically, but also much faster for the typical background search). 

381 # pre-allocate incrementor 

382 inc = np.arange(np.max(nside_factor)) 

383 first_indices = np.roll(nside_factor.cumsum(), shift=1) 

384 first_indices[0] = 0 

385 for i, ind1 in enumerate(first_indices): 

386 raster_indices[ind1:ind1+nside_factor[i]] += inc[:nside_factor[i]] 

387 sort = raster_indices.argsort() 

388 raster_indices = raster_indices[sort] 

389 if not np.all(np.unique(raster_indices) == raster_indices): 

390 raise ValueError("Must provide non-overlapping pixels in ``indices``.") 

391 return raster_indices, raster_values[sort] 

392 

393 

394def upres_nest(mask_nested, mask_nside, nside, mask_sorted=False): 

395 """ 

396 Get ``nested`` indices for a skymap mask at a higher resolution. Output 

397 will be sorted. 

398 

399 Parameters 

400 ---------- 

401 mask_nested : array-like 

402 The set of ``nested`` HEALPix pixels that you'd like to have at higher 

403 resolution. 

404 mask_nside : int 

405 The HEALPix ``nside`` parameter of this mask. Must be a power of 2. 

406 nside : int 

407 The ``nside`` value you would like for your output. Must be a power of 

408 2. 

409 mask_sorted : bool, optional 

410 Whether the input ``mask_nested`` is already sorted. If it is sorted 

411 and is large, specifying this will skip a sort step, potentially saving 

412 time. 

413 

414 Returns 

415 ------- 

416 highres_mask_nested : np.array 

417 The same sky region specified in ``mask_nested`` at ``mask_nside`` but 

418 at the resolution specified in ``nside``. This will be a factor of 

419 ``(nside/mask_nside)**2`` larger. Will be a new array independent of 

420 inputs, so feel free to mutate it. 

421 

422 Raises 

423 ------ 

424 ValueError 

425 if ``mask_nside`` is larger than ``nside`` 

426 """ 

427 import numpy as np 

428 check_valid_nside(mask_nside) 

429 check_valid_nside(nside) 

430 if not mask_sorted: 

431 mask = np.sort(mask_nested) 

432 else: 

433 mask = np.array(mask_nested, copy=True) 

434 if mask_nside == nside: 

435 return mask 

436 elif mask_nside > nside: 

437 raise ValueError(f"mask_nside ({mask_nside}) must be smaller than " 

438 f"nside ({nside})") 

439 factor = nside//mask_nside 

440 factor *= factor 

441 assert isinstance(factor, int) 

442 return (np.array([mask*factor]).T + 

443 np.array([np.arange(factor)])).flatten() 

444 

445 

446def downres_mask_nest(mask_nested, mask_nside, nside, mask_sorted=False): 

447 """ 

448 Get a lower-resolution set of pixel indices that fully cover the region 

449 specified in ``mask_nested``. The returned indices correspond to larger 

450 pixels which might contain part of the sky that are not included in the 

451 original mask (e.g. if the larger pixel only contains one of the original 

452 pixels in ``mask_nested``). 

453 

454 Parameters 

455 ---------- 

456 mask_nested : array-like 

457 The set of ``nested`` HEALPix pixels that you'd like to have at lower 

458 resolution. 

459 mask_nside : int 

460 The HEALPix ``nside`` parameter of this mask. Must be a power of 2. 

461 nside : int 

462 The ``nside`` value you would like for your output. Must be a power of 

463 2. 

464 mask_sorted : bool, optional 

465 Whether the input ``mask_nested`` is already sorted. If it is sorted 

466 and is large, specifying this will skip a sort step, potentially saving 

467 time. 

468 

469 Returns 

470 ------- 

471 lowres_mask_nested : np.array 

472 The smallest sky region that can be specified at the resolution given 

473 by ``nside`` that contains the sky region specified in ``mask_nested`` 

474 at ``mask_nside``. This will be roughly a factor of 

475 ``(nside/mask_nside)**2`` smaller, but do **not** count on this ratio, 

476 since the pixels in the returned mask might not have all of their 

477 corresponding subpixels in the original ``mask_nested``; in 

478 pathological cases, the returned array can be up to the same size (if 

479 every pixel in ``mask_nested`` only appears in a single larger pixel in 

480 at resolution ``nside``). Will be a new array independent of inputs, so 

481 feel free to mutate it. 

482 

483 Raises 

484 ------ 

485 ValueError 

486 if ``mask_nside`` is smaller than ``nside`` 

487 """ 

488 import numpy as np 

489 check_valid_nside(mask_nside) 

490 check_valid_nside(nside) 

491 if not mask_sorted: 

492 mask = np.sort(mask_nested) 

493 else: 

494 mask = np.array(mask_nested, copy=True) 

495 if mask_nside == nside: 

496 return mask 

497 elif mask_nside < nside: 

498 raise ValueError(f"mask_nside ({mask_nside}) must be larger than " 

499 f"nside ({nside})") 

500 factor = mask_nside//nside 

501 factor *= factor 

502 assert isinstance(factor, int) 

503 return np.unique(mask // factor) 

504 

505 

506def read_partial_skymap(infile, mask_nested, mask_nside, 

507 mask_sorted=False, memmap=True): 

508 """ 

509 Read in pixels from a FITS skymap (or a gzip-compressed FITS skymap) that 

510 lie in a specific sky region. Attempts to minimize memory usage by 

511 memory-mapping pixels in the input file and only loading those specified in 

512 ``mask_nested``. 

513 

514 Parameters 

515 ---------- 

516 infile : str 

517 A FITS HEALPix skymap (optionally compressed; see note under 

518 ``memmap``) 

519 mask_nested : array-like 

520 HEALPix pixel indices in ``nested`` ordering specifying the region 

521 of the skymap that should be loaded 

522 mask_nside : int 

523 The resolution of the pixel mask 

524 mask_nested : bool, optional 

525 Whether ``mask_nested`` is already sorted. You can specify 

526 ``mask_sorted=True``, potentially saving some sorting time 

527 memmap : bool, optional 

528 If ``True``, use memory mapping when reading in files. This can VASTLY 

529 reduce memory required for high-resolution skymaps. If ``infile`` is 

530 gzip-compressed and ``memmap`` is ``True``, then ``infile`` will be 

531 decompressed to a temporary file and data will be read from it 

532 (necessary to constrain memory usage); for high-resolution skymaps, 

533 this can require the availability of several gigabytes of tempfile 

534 storage. 

535 

536 Returns 

537 ------- 

538 partial_skymap : astropy.table.Table 

539 A partial skymap table in ``nested`` ordering. Has two columns: 

540 ``INDICES`` and ``PROB``. If the resolution of the original 

541 HEALPix skymap file is lower than that of the mask, then any pixels 

542 overlapping with those in ``mask_nested`` will be used; this might 

543 result in a larger portion of the skymap being used than that 

544 specified in ``mask_nested``. The resolution of this skymap will be 

545 the resolution of the smallest pixel loaded from the input file (in 

546 the case of ``ring`` or ``nested`` ordering, this is just the 

547 resolution of the input skymap). 

548 """ 

549 from astropy.table import Table 

550 import numpy as np 

551 import healpy as hp 

552 # from https://stackoverflow.com/a/47080739/3601493 

553 with open(infile, 'rb') as test_f: 

554 magic_number = binascii.hexlify(test_f.read(2)) 

555 if memmap and magic_number == b'1f8b': 

556 try: 

557 with NamedTemporaryFile('wb', suffix='.fits') as tmp: 

558 LOGGER.debug(f"zipped skymap {infile} detected, attempting to " 

559 f"decompress to tempfile {tmp}") 

560 with gzip.open("lvc_skymap.fits.gz", "rb") as infile: 

561 buf = infile.read(GZIP_BUFFSIZE) 

562 while buf: 

563 tmp.write(buf) 

564 buf = infile.read(GZIP_BUFFSIZE) 

565 return read_partial_skymap(tmp.name, mask_nested, mask_nside, 

566 mask_sorted=mask_sorted, 

567 memmap=memmap) 

568 except OSError as err: 

569 if 'gzip' in str(err): 

570 LOGGER.error("OSError while attempting to decompress, giving " 

571 "up and letting astropy try to read it. error: " + 

572 str(err)) 

573 check_valid_nside(mask_nside) 

574 tab = Table.read(infile, memmap=memmap) 

575 if tab.meta['ORDERING'] == 'NUNIQ': 

576 #TODO: fill out 

577 raise NotImplementedError() 

578 nside = tab.meta['NSIDE'] 

579 if nside >= mask_nside: 

580 mask = upres_nest(mask_nested, mask_nside, nside, 

581 mask_sorted=mask_sorted) 

582 else: 

583 mask = downres_mask_nest(mask_nested, mask_nside, nside, 

584 mask_sorted=mask_sorted) 

585 if tab.meta['ORDERING'] == 'NESTED': 

586 prob = tab['PROB'][mask].copy() 

587 return Table( 

588 np.array([mask, prob]).transpose(), 

589 names=['INDICES', 'PROB'], 

590 meta=tab.meta 

591 ) 

592 if tab.meta['ORDERING'] == 'RING': 

593 # we'll use the PROB column as a temporary holder for the RING 

594 # pixel indices while we load them in order to preserve sort orders 

595 results = Table( 

596 np.array([ 

597 mask, 

598 hp.nest2ring(nside, mask), 

599 ]).transpose(), 

600 names=['INDICES', 'RING'], 

601 meta=tab.meta, 

602 ) 

603 results.sort(keys='RING') 

604 prob = tab['PROB'][results['RING']].copy() 

605 results['RING'] = prob 

606 # change the name to PROB now that we've replaced the contents 

607 results['RING'].name = 'PROB' 

608 results.sort(keys='INDICES') 

609 return results 

610 raise ValueError(f"Unexpected ORDERING in {infile}: " 

611 f"{tab.meta['ORDERING']}")