def input_skymap(order1, d_order, fraction): """Construct a test multi-resolution sky map, with values that are proportional to the NESTED pixel index. To make the test more interesting by mixing together multiple resolutions, part of the sky map is refined to a higher order. Parameters ---------- order1 : int The HEALPix resolution order. d_order : int The increase in orer for part of the sky map. fraction : float The fraction of the original pixels to refine. """ order2 = order1 + d_order npix1 = ah.nside_to_npix(ah.level_to_nside(order1)) npix2 = ah.nside_to_npix(ah.level_to_nside(order2)) ipix1 = np.arange(npix1) ipix2 = np.arange(npix2) # Create a random sky map. area = ah.nside_to_pixel_area(ah.level_to_nside(order1)).to_value(u.sr) probdensity = np.random.uniform(0, 1, npix1) prob = probdensity * area normalization = prob.sum() prob /= normalization probdensity /= normalization distmean = np.random.uniform(100, 110, npix1) diststd = np.random.uniform(0, 1 / np.sqrt(3) - 0.1, npix1) * distmean distmu, distsigma, distnorm = moments_to_parameters(distmean, diststd) assert np.all(np.isfinite(distmu)) data1 = table.Table({ 'UNIQ': moc.nest2uniq(order1, ipix1), 'PROBDENSITY': probdensity, 'DISTMU': distmu, 'DISTSIGMA': distsigma, 'DISTNORM': distnorm }) # Add some upsampled pixels. data2 = table.Table(np.repeat(data1, npix2 // npix1)) data2['UNIQ'] = moc.nest2uniq(order2, ipix2) n = int(npix1 * (1 - fraction)) result = table.vstack((data1[:n], data2[n * npix2 // npix1:])) # Add marginal distance mean and standard deviation. rbar = (prob * distmean).sum() r2bar = (prob * (np.square(diststd) + np.square(distmean))).sum() result.meta['distmean'] = rbar result.meta['diststd'] = np.sqrt(r2bar - np.square(rbar)) return result
def test_rasterize_downsample(order_in, d_order_in, fraction_in, order_out): npix_in = ah.nside_to_npix(ah.level_to_nside(order_in)) npix_out = ah.nside_to_npix(ah.level_to_nside(order_out)) skymap_in = input_skymap(order_in, d_order_in, fraction_in) skymap_out = moc.rasterize(skymap_in, order_out) assert len(skymap_out) == npix_out reps = npix_in // npix_out expected = np.mean(np.arange(npix_in).reshape(-1, reps), axis=1) np.testing.assert_array_equal(skymap_out['VALUE'], expected)
def test_rasterize_upsample(order_in, d_order_in, fraction_in, order_out): npix_in = ah.nside_to_npix(ah.level_to_nside(order_in)) npix_out = ah.nside_to_npix(ah.level_to_nside(order_out)) skymap_in = input_skymap(order_in, d_order_in, fraction_in) skymap_out = moc.rasterize(skymap_in, order_out) assert len(skymap_out) == npix_out ipix = np.arange(npix_in) reps = npix_out // npix_in for i in range(reps): np.testing.assert_array_equal(skymap_out['VALUE'][i::reps], ipix)
def test_from_valued_healpix_cells_bayestar(): from astropy.io import fits fits_image_filename = './resources/bayestar.multiorder.fits' with fits.open(fits_image_filename) as hdul: hdul.info() hdul[1].columns data = hdul[1].data uniq = data['UNIQ'] probdensity = data['PROBDENSITY'] import astropy_healpix as ah import astropy.units as u level, ipix = ah.uniq_to_level_ipix(uniq) area = ah.nside_to_pixel_area(ah.level_to_nside(level)).to_value( u.steradian) prob = probdensity * area cumul_to = np.linspace(0.01, 2.0, num=10) for b in cumul_to: MOC.from_valued_healpix_cells(uniq, prob, 12, cumul_from=0.0, cumul_to=b)
def main(args=None): p = parser() opts = parser().parse_args(args) import astropy_healpix as ah import astropy.units as u try: from mocpy import MOC except ImportError: p.error('This command-line tool requires mocpy >= 0.8.2. ' 'Please install it by running "pip install mocpy".') from ..io import read_sky_map # Read multi-order sky map skymap = read_sky_map(opts.input.name, moc=True) uniq = skymap['UNIQ'] probdensity = skymap['PROBDENSITY'] level, ipix = ah.uniq_to_level_ipix(uniq) area = ah.nside_to_pixel_area( ah.level_to_nside(level)).to_value(u.steradian) prob = probdensity * area # Create MOC contour_decimal = opts.contour / 100 moc = MOC.from_valued_healpix_cells( uniq, prob, cumul_from=0.0, cumul_to=contour_decimal) # Write MOC moc.write(opts.output, format='fits', overwrite=True)
def _reconstruct_nested_breadthfirst(m, extra): m = np.asarray(m) max_npix = len(m) max_nside = ah.npix_to_nside(max_npix) max_order = ah.nside_to_level(max_nside) seen = np.zeros(max_npix, dtype=bool) for order in range(max_order + 1): nside = ah.level_to_nside(order) npix = ah.nside_to_npix(nside) skip = max_npix // npix if skip > 1: b = m.reshape(-1, skip) a = b[:, 0].reshape(-1, 1) b = b[:, 1:] aseen = seen.reshape(-1, skip) eq = ((a == b) | ((a != a) & (b != b))).all(1) & (~aseen).all(1) else: eq = ~seen for ipix in np.flatnonzero(eq): ipix0 = ipix * skip ipix1 = (ipix + 1) * skip seen[ipix0:ipix1] = True if extra: yield _HEALPixTreeVisitExtra(nside, max_nside, ipix, ipix0, ipix1, m[ipix0]) else: yield _HEALPixTreeVisit(nside, ipix)
def flat_bitmap(self): """Return flattened HEALPix representation.""" m = np.empty(ah.nside_to_npix(ah.level_to_nside(self.order))) for nside, full_nside, ipix, ipix0, ipix1, samples in self.visit(): pixarea = ah.nside_to_pixel_area(nside).to_value(u.sr) m[ipix0:ipix1] = len(samples) / pixarea return m
def input_skymap(order1, d_order, fraction): """Construct a test multi-resolution sky map, with values that are proportional to the NESTED pixel index. To make the test more interesting by mixing together multiple resolutions, part of the sky map is refined to a higher order. Parameters ---------- order1 : int The HEALPix resolution order. d_order : int The increase in orer for part of the sky map. fraction : float The fraction of the original pixels to refine. """ order2 = order1 + d_order npix1 = ah.nside_to_npix(ah.level_to_nside(order1)) npix2 = ah.nside_to_npix(ah.level_to_nside(order2)) ipix1 = np.arange(npix1) ipix2 = np.arange(npix2) data1 = table.Table({ 'UNIQ': moc.nest2uniq(order1, ipix1), 'VALUE': ipix1.astype(float), 'VALUE2': np.pi * ipix1.astype(float) }) data2 = table.Table({ 'UNIQ': moc.nest2uniq(order2, ipix2), 'VALUE': np.repeat(ipix1, npix2 // npix1).astype(float), 'VALUE2': np.pi * np.repeat(ipix1, npix2 // npix1).astype(float) }) n = int(npix1 * (1 - fraction)) return table.vstack((data1[:n], data2[n * npix2 // npix1:]))
def _build_moc_map(self): pts = np.column_stack((self._grb_phi, self._grb_theta)) self._kde_map = Clustered2DSkyKDE(pts, jobs=12) data = self._kde_map.as_healpix(top_nside=self._npix) self._uniq = data["UNIQ"] self._prob_density = data["PROBDENSITY"] level, ipix = ah.uniq_to_level_ipix(self._uniq) area = ah.nside_to_pixel_area(ah.level_to_nside(level)).to_value( u.steradian) self._prob = self._prob_density * area
def test_rasterize_default(order): npix = ah.nside_to_npix(ah.level_to_nside(order)) skymap_in = input_skymap(order, 0, 0) skymap_out = moc.rasterize(skymap_in) assert len(skymap_out) == npix
from astropy import units as u from astropy_healpix import (level_to_nside, nside_to_pixel_area, uniq_to_level_ipix, HEALPix) from mocpy import MOC from sqlalchemy import BigInteger, Column, Index from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import relationship from sqlalchemy.orm.mapper import Mapper from sqlalchemy.sql.expression import func LEVEL = MOC.HPY_MAX_NORDER """Base HEALPix resolution. This is the maximum HEALPix level that can be stored in a signed 8-byte integer data type.""" HPX = HEALPix(nside=level_to_nside(LEVEL), order='nested', frame=ICRS()) """HEALPix projection object.""" PIXEL_AREA = HPX.pixel_area.to_value(u.sr) """Native pixel area in steradians.""" class Point: """Mixin class for a table that stores a HEALPix multiresolution point.""" nested = Column(BigInteger, index=True, nullable=False, comment=f'HEALPix nested index at nside=2**{LEVEL}')
fits_image_filename = './../../resources/bayestar.multiorder.fits' with fits.open(fits_image_filename) as hdul: hdul.info() hdul[1].columns data = hdul[1].data uniq=data['UNIQ'] probdensity=data['PROBDENSITY'] import astropy_healpix as ah import astropy.units as u level, ipix = ah.uniq_to_level_ipix(uniq) area = ah.nside_to_pixel_area(ah.level_to_nside(level)).to_value(u.steradian) prob = probdensity * area from mocpy import MOC import numpy as np cumul_to = np.linspace(0.5, 0.9, 5)[::-1] colors = ['blue', 'green', 'yellow', 'orange', 'red'] mocs = [MOC.from_valued_healpix_cells(uniq, prob, cumul_to=c) for c in cumul_to] from mocpy import World2ScreenMPL from astropy.coordinates import Angle, SkyCoord import astropy.units as u # Plot the MOC using matplotlib
def crossmatch(sky_map, coordinates=None, contours=(), areas=(), modes=False, cosmology=False): """Cross match a sky map with a catalog of points. Given a sky map and the true right ascension and declination (in radians), find the smallest area in deg^2 that would have to be searched to find the source, the smallest posterior mass, and the angular offset in degrees from the true location to the maximum (mode) of the posterior. Optionally, also compute the areas of and numbers of modes within the smallest contours containing a given total probability. Parameters ---------- sky_map : :class:`astropy.table.Table` A multiresolution sky map, as returned by :func:`ligo.skymap.io.fits.read_sky_map` called with the keyword argument ``moc=True``. coordinates : :class:`astropy.coordinates.SkyCoord`, optional The catalog of target positions to match against. contours : :class:`tuple`, optional Credible levels between 0 and 1. If this argument is present, then calculate the areas and volumes of the 2D and 3D credible regions that contain these probabilities. For example, for ``contours=(0.5, 0.9)``, then areas and volumes of the 50% and 90% credible regions. areas : :class:`tuple`, optional Credible areas in square degrees. If this argument is present, then calculate the probability contained in the 2D credible levels that have these areas. For example, for ``areas=(20, 100)``, then compute the probability within the smallest credible levels of 20 degĀ² and 100 degĀ², respectively. modes : :class:`bool`, optional If True, then enable calculation of the number of distinct modes or islands of probability. Note that this option may be computationally expensive. cosmology : :class:`bool`, optional If True, then search space by descending probability density per unit comoving volume. If False, then search space by descending probability per luminosity distance cubed. Returns ------- result : :class:`~ligo.skymap.postprocess.crossmatch.CrossmatchResult` Notes ----- This function is also be used for injection finding; see :doc:`/tool/ligo_skymap_stats`. Examples -------- First, some imports: >>> from astroquery.vizier import VizierClass >>> from astropy.coordinates import SkyCoord >>> from ligo.skymap.io import read_sky_map >>> from ligo.skymap.postprocess import crossmatch Next, retrieve the GLADE catalog using Astroquery and get the coordinates of all its entries: >>> vizier = VizierClass( ... row_limit=-1, columns=['GWGC', '_RAJ2000', '_DEJ2000', 'Dist']) >>> cat, = vizier.get_catalogs('VII/281/glade2') >>> coordinates = SkyCoord(cat['_RAJ2000'], cat['_DEJ2000'], cat['Dist']) Load the multiresolution sky map for S190814bv: >>> url = 'https://gracedb.ligo.org/api/superevents/S190814bv/files/bayestar.multiorder.fits' >>> skymap = read_sky_map(url, moc=True) Perform the cross match: >>> result = crossmatch(skymap, coordinates) Using the cross match results, we can list the galaxies within the 90% credible volume: >>> print(cat[result.searched_prob_vol < 0.9]) GWGC _RAJ2000 _DEJ2000 Dist deg deg Mpc ---------- -------------------- -------------------- -------------------- NGC0171 9.3396699999999999 -19.9342460000000017 57.56212553960000 --- 20.2009090000000064 -31.1146050000000010 137.16022925600001 ESO540-003 8.9144679999999994 -20.1252980000000008 49.07809291930000 --- 10.6762720000000009 -21.7740819999999999 276.46938505499998 --- 13.5855169999999994 -23.5523850000000010 138.44550704800000 --- 20.6362969999999990 -29.9825149999999958 160.23313164900000 --- 13.1923879999999993 -22.9750179999999986 236.96795954500001 --- 11.7813630000000007 -24.3706470000000017 244.25031189699999 --- 19.1711120000000008 -31.4339490000000019 152.13614001400001 --- 13.6367060000000002 -23.4948789999999974 141.25162979500001 ... ... ... ... --- 11.3517000000000010 -25.8596999999999966 335.73800000000000 --- 11.2073999999999998 -25.7149000000000001 309.02999999999997 --- 11.1875000000000000 -25.7503999999999991 295.12099999999998 --- 10.8608999999999991 -25.6904000000000003 291.07200000000000 --- 10.6938999999999975 -25.6778300000000002 323.59399999999999 --- 15.4935000000000009 -26.0304999999999964 304.78899999999999 --- 15.2794000000000008 -27.0410999999999966 320.62700000000001 --- 14.8323999999999980 -27.0459999999999994 320.62700000000001 --- 14.5341000000000005 -26.0949000000000026 307.61000000000001 --- 23.1280999999999963 -31.1109199999999966 320.62700000000001 Length = 1479 rows """ # noqa: E501 # Astropy coordinates that are constructed without distance have # a distance field that is unity (dimensionless). if coordinates is None: true_ra = true_dec = true_dist = None else: # Ensure that coordinates are in proper frame and representation coordinates = SkyCoord(coordinates, representation_type=SphericalRepresentation, frame=ICRS) true_ra = coordinates.ra.rad true_dec = coordinates.dec.rad if np.any(coordinates.distance != 1): true_dist = coordinates.distance.to_value(u.Mpc) else: true_dist = None contours = np.asarray(contours) # Sort the pixels by descending posterior probability. sky_map = np.flipud(np.sort(sky_map, order='PROBDENSITY')) # Find the pixel that contains the injection. order, ipix = moc.uniq2nest(sky_map['UNIQ']) max_order = np.max(order) max_nside = ah.level_to_nside(max_order) max_ipix = ipix << np.int64(2 * (max_order - order)) if true_ra is not None: true_theta = 0.5 * np.pi - true_dec true_phi = true_ra true_pix = hp.ang2pix(max_nside, true_theta, true_phi, nest=True) i = np.argsort(max_ipix) true_idx = i[np.digitize(true_pix, max_ipix[i]) - 1] # Find the angular offset between the mode and true locations. mode_theta, mode_phi = hp.pix2ang(ah.level_to_nside(order[0]), ipix[0], nest=True) if true_ra is None: offset = np.nan else: offset = np.rad2deg( angle_distance(true_theta, true_phi, mode_theta, mode_phi)) # Calculate the cumulative area in deg2 and the cumulative probability. dA = moc.uniq2pixarea(sky_map['UNIQ']) dP = sky_map['PROBDENSITY'] * dA prob = np.cumsum(dP) area = np.cumsum(dA) * np.square(180 / np.pi) if true_ra is None: searched_area = searched_prob = probdensity = np.nan else: # Find the smallest area that would have to be searched to find # the true location. searched_area = area[true_idx] # Find the smallest posterior mass that would have to be searched to # find the true location. searched_prob = prob[true_idx] # Find the probability density. probdensity = sky_map['PROBDENSITY'][true_idx] # Find the contours of the given credible levels. contour_idxs = np.digitize(contours, prob) - 1 # For each of the given confidence levels, compute the area of the # smallest region containing that probability. contour_areas = np.interp(contours, prob, area, left=0, right=4 * 180**2 / np.pi).tolist() # For each listed area, find the probability contained within the # smallest credible region of that area. area_probs = np.interp(areas, area, prob, left=0, right=1).tolist() if modes: if true_ra is None: searched_modes = np.nan else: # Count up the number of modes in each of the given contours. searched_modes = count_modes_moc(sky_map['UNIQ'], true_idx) contour_modes = [ count_modes_moc(sky_map['UNIQ'], i) for i in contour_idxs ] else: searched_modes = np.nan contour_modes = np.nan # Distance stats now... if 'DISTMU' in sky_map.dtype.names: dP_dA = sky_map['PROBDENSITY'] mu = sky_map['DISTMU'] sigma = sky_map['DISTSIGMA'] norm = sky_map['DISTNORM'] # Set up distance grid. n_r = 1000 distmean, _ = distance.parameters_to_marginal_moments(dP, mu, sigma) max_r = 6 * distmean if true_dist is not None and np.size(true_dist) != 0 \ and np.max(true_dist) > max_r: max_r = np.max(true_dist) d_r = max_r / n_r # Calculate searched_prob_dist and contour_dists. r = d_r * np.arange(1, n_r) P_r = distance.marginal_cdf(r, dP, mu, sigma, norm) if true_dist is None: searched_prob_dist = np.nan else: searched_prob_dist = np.interp(true_dist, r, P_r, left=0, right=1) if len(contours) == 0: contour_dists = [] else: lo, hi = np.interp(np.row_stack( (0.5 * (1 - contours), 0.5 * (1 + contours))), P_r, r, left=0, right=np.inf) contour_dists = (hi - lo).tolist() # Calculate volume of each voxel, defined as the region within the # HEALPix pixel and contained within the two centric spherical shells # with radii (r - d_r / 2) and (r + d_r / 2). dV = (np.square(r) + np.square(d_r) / 12) * d_r * dA.reshape(-1, 1) # Calculate probability within each voxel. dP = np.exp(-0.5 * np.square( (r.reshape(1, -1) - mu.reshape(-1, 1)) / sigma.reshape(-1, 1))) * ( dP_dA * norm / (sigma * np.sqrt(2 * np.pi))).reshape(-1, 1) * dV dP[np.isnan(dP)] = 0 # Suppress invalid values # Calculate probability density per unit volume. if cosmology: dV *= dVC_dVL_for_DL(r) dP_dV = dP / dV i = np.flipud(np.argsort(dP_dV.ravel())) P_flat = np.cumsum(dP.ravel()[i]) V_flat = np.cumsum(dV.ravel()[i]) contour_vols = np.interp(contours, P_flat, V_flat, left=0, right=np.inf).tolist() P = np.empty_like(P_flat) V = np.empty_like(V_flat) P[i] = P_flat V[i] = V_flat P = P.reshape(dP.shape) V = V.reshape(dV.shape) if true_dist is None: searched_vol = searched_prob_vol = probdensity_vol = np.nan else: i_radec = true_idx i_dist = np.digitize(true_dist, r) - 1 probdensity_vol = dP_dV[i_radec, i_dist] searched_prob_vol = P[i_radec, i_dist] searched_vol = V[i_radec, i_dist] else: searched_vol = searched_prob_vol = searched_prob_dist \ = probdensity_vol = np.nan contour_dists = [np.nan] * len(contours) contour_vols = [np.nan] * len(contours) # Done. return CrossmatchResult(searched_area, searched_prob, offset, searched_modes, contour_areas, area_probs, contour_modes, searched_prob_dist, contour_dists, searched_vol, searched_prob_vol, contour_vols, probdensity, probdensity_vol)
Multiresolution HEALPix trees """ import astropy_healpix as ah from astropy import units as u import numpy as np import healpy as hp import collections import itertools __all__ = ('HEALPIX_MACHINE_ORDER', 'HEALPIX_MACHINE_NSIDE', 'HEALPixTree', 'adaptive_healpix_histogram', 'interpolate_nested', 'reconstruct_nested') # Maximum 64-bit HEALPix resolution. HEALPIX_MACHINE_ORDER = 29 HEALPIX_MACHINE_NSIDE = ah.level_to_nside(HEALPIX_MACHINE_ORDER) _HEALPixTreeVisitExtra = collections.namedtuple( 'HEALPixTreeVisit', 'nside full_nside ipix ipix0 ipix1 value') _HEALPixTreeVisit = collections.namedtuple('HEALPixTreeVisit', 'nside ipix') class HEALPixTree: """Data structure used internally by the function adaptive_healpix_histogram().""" def __init__(self, samples, max_samples_per_pixel, max_order, order=0,