def test_combine(tmpdir): """Test ligo-skymap-combine.""" fn1 = str(tmpdir / 'skymap1.fits.gz') fn2 = str(tmpdir / 'skymap2.fits.gz') fn3 = str(tmpdir / 'joint_skymap.fits.gz') # generate a hemisphere of constant probability nside1 = 32 npix1 = ah.nside_to_npix(nside1) m1 = np.zeros(npix1) disc_idx = hp.query_disc(nside1, (1, 0, 0), np.pi / 2) m1[disc_idx] = 1 m1 /= m1.sum() hp.write_map(fn1, m1, column_names=['PROBABILITY'], extra_header=[('INSTRUME', 'X1')]) # generate another hemisphere of constant probability # but with higher resolution and rotated 90 degrees nside2 = 64 npix2 = ah.nside_to_npix(nside2) m2 = np.zeros(npix2) disc_idx = hp.query_disc(nside2, (0, 1, 0), np.pi / 2) m2[disc_idx] = 1 m2 /= m2.sum() hp.write_map(fn2, m2, column_names=['PROBABILITY'], extra_header=[('INSTRUME', 'Y1')]) run_entry_point('ligo-skymap-combine', fn1, fn2, fn3) m3 = hp.read_map(fn3, nest=True) npix3 = len(m3) nside3 = ah.npix_to_nside(npix3) pix_area3 = ah.nside_to_pixel_area(nside3).to_value(u.sr) # resolution must match the highest original resolution assert npix3 == npix2 # probability must be normalized to 1 assert m3.sum() == pytest.approx(1) # support must be ¼ of the sphere tolerance = 10 * ah.nside_to_pixel_area(nside1).to_value(u.sr) assert sum(m3 > 0) * pix_area3 == pytest.approx(np.pi, abs=tolerance) # generate a BAYESTAR-like map with mock distance information d_mu = np.zeros_like(m1) d_sigma = np.ones_like(m1) d_norm = np.ones_like(m1) io.write_sky_map(fn1, [m1, d_mu, d_sigma, d_norm]) run_entry_point('ligo-skymap-combine', fn1, fn2, fn3) m3, meta3 = io.read_sky_map(fn3, nest=True, distances=True) # check that marginal distance moments match what was simulated mean, std, _ = distance.parameters_to_moments(d_mu[0], d_sigma[0]) assert meta3['distmean'] == pytest.approx(mean) assert meta3['diststd'] == pytest.approx(std)
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_reproject_healpix_to_image_round_trip(wcsapi, nside, nested, healpix_system, image_system, dtype): """Test round-trip HEALPix->WCS->HEALPix conversion for a random map, with a WCS projection large enough to store each HEALPix pixel""" npix = nside_to_npix(nside) healpix_data = np.random.uniform(size=npix).astype(dtype) reference_header = get_reference_header(oversample=2, nside=nside) wcs_out = WCS(reference_header) shape_out = reference_header['NAXIS2'], reference_header['NAXIS1'] if wcsapi: wcs_out = as_high_level_wcs(wcs_out) image_data, footprint = reproject_from_healpix( (healpix_data, healpix_system), wcs_out, shape_out=shape_out, order='nearest-neighbor', nested=nested) healpix_data_2, footprint = reproject_to_healpix((image_data, wcs_out), healpix_system, nside=nside, order='nearest-neighbor', nested=nested) np.testing.assert_array_equal(healpix_data, healpix_data_2)
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 cartesian_gaussian_to_skymap(level, mean, cov): """Convert a 3D Cartesian Gaussian to a 3D sky map.""" # Set up HEALPix grid. nside = 2**level npix = ah.nside_to_npix(nside) ipix = np.arange(npix) coords = np.column_stack(hp.pix2vec(nside, ipix, nest=True)) # Create sky map using same method as KDE to convert from Cartesian # to spherical representation. This is just a special case where there # is only a single cluster and a single KDE sample. means = mean[np.newaxis, ..., np.newaxis] inv_covs = np.linalg.inv(cov)[np.newaxis, ...] weights = np.ones(1) probdensity, distmean, diststd = np.transpose([ cartesian_kde_to_moments(n, means, inv_covs, weights) for n in coords ]) # Create 3D, multi-order sky map. uniq = nest2uniq(level, ipix) distmu, distsigma, distnorm = moments_to_parameters(distmean, diststd) return Table({ 'UNIQ': uniq, 'PROBDENSITY': probdensity, 'DISTMU': distmu, 'DISTSIGMA': distsigma, 'DISTNORM': distnorm })
def _bayestar_adaptive_grid(self, top_nside=16, rounds=8): """Implement of the BAYESTAR adaptive mesh refinement scheme as described in Section VI of Singer & Price 2016, PRD, 93, 024013 (http://dx.doi.org/10.1103/PhysRevD.93.024013). FIXME: Consider refactoring BAYESTAR itself to perform the adaptation step in Python. """ top_npix = ah.nside_to_npix(top_nside) nrefine = top_npix // 4 cells = zip([0] * nrefine, [top_nside // 2] * nrefine, range(nrefine)) for iround in range(rounds - 1): print('adaptive refinement round {} of {} ...'.format( iround + 1, rounds - 1)) cells = sorted(cells, key=lambda p_n_i: p_n_i[0] / p_n_i[1]**2) new_nside, new_ipix = np.transpose([ (nside * 2, ipix * 4 + i) for _, nside, ipix in cells[-nrefine:] for i in range(4) ]) theta, phi = hp.pix2ang(new_nside, new_ipix, nest=True) ra = phi dec = 0.5 * np.pi - theta p = self(np.column_stack((ra, dec))) cells[-nrefine:] = zip(p, new_nside, new_ipix) return cells
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 make_earth_vis_grids(nside=32, n_reps=1): from acis_taco import calc_earth_vis, RAD_EARTH, acis_taco hp = astropy_healpix.HEALPix(nside=nside, order='nested') npix = astropy_healpix.nside_to_npix(nside) print(f'npix={npix}') lons, lats = hp.healpix_to_lonlat(np.arange(npix)) time0 = time.time() # Allow randomization between altitudes for i_rep in range(n_reps): vis_arrays = [] # Randomize the ray-trace points for each rep acis_taco._RANDOM_SALT = None acis_taco.SPHERE_XYZ = acis_taco.random_hemisphere(acis_taco.N_SPHERE) acis_taco.SPHERE_X = acis_taco.SPHERE_XYZ[:, 0].copy() for i_alt, alt in enumerate(alts): srs = SphericalRepresentation(lon=lons, lat=lats, distance=alt + RAD_EARTH) xyzs = srs.to_cartesian() peb_xs = xyzs.x.to_value() peb_ys = xyzs.y.to_value() peb_zs = xyzs.z.to_value() vis = [] for peb_x, peb_y, peb_z in zip(peb_xs, peb_ys, peb_zs): _, illums, _ = calc_earth_vis( p_earth_body=[peb_x, peb_y, peb_z]) vis.append(np.sum(illums)) vis = np.array(vis) vis_arrays.append(vis) vis_grid = np.vstack(vis_arrays) if i_alt % 10 == 0: print( f'alt={alt / 1000:.2f} km at dt={time.time() - time0:.1f}') ii = 1 while True: filename = Path(f'earth_vis_grid_nside{nside}_rep{ii}.npy') if filename.exists(): ii += 1 continue else: print(f'Saving {filename}') np.save(filename, vis_grid) break return vis_grid
def make_earth_vis_grid_fits(nside=32): """Collect the reps and average and write to FITS""" from acis_taco import RAD_EARTH ii = 1 vis_list = [] while True: filename = Path(f'earth_vis_grid_nside{nside}_rep{ii}.npy') if filename.exists(): # print(f'Reading {filename}') vis = np.load(filename) vis_list.append(vis) ii += 1 else: break npix = astropy_healpix.nside_to_npix(nside) visl = np.array(vis_list) print(visl.shape) vis = visl.mean(axis=0) # Some sanity checking assert vis.shape == (100, npix) assert np.min(vis) == 0.0 assert np.max(vis) < 3.0 scale = 2**16 / 3.0 visi = np.round(vis * scale) assert np.max(visi) < 2**16 # Convert to 16 bits. Also transpose so that interpolation along # distance is contiguous in memory. # BTW see: https://github.com/astropy/astropy/issues/8726 for the # origin of the .copy() visi2 = visi.astype(np.uint16).transpose().copy() hdu = fits.PrimaryHDU(visi2) hdu.header['nside'] = nside hdu.header['alt_min'] = alt_min.to_value(u.m) hdu.header['alt_max'] = alt_max.to_value(u.m) hdu.header['n_alt'] = n_alt hdu.header['scale'] = scale hdu.header['earthrad'] = RAD_EARTH filename = Path(f'earth_vis_grid_nside{nside}.fits.gz') print(f'Writing {filename}') hdul = fits.HDUList([hdu]) hdul.writeto(filename, overwrite=True)
def test_allsky_axes(rcparams, coordsys, units, proj): """Test projection of a HEALPix image onto allsky axes, either in celestial or earth-fixed coordinates. """ # Set up axes. (The obstime has an effect only for geographic axes.) fig = plt.figure(figsize=(6, 4)) ax = fig.add_subplot(111, projection=coordsys + ' ' + units + ' ' + proj, obstime='2017-08-17T12:41:04.444458') # Build a low-resolution example HEALPix sky map: # the value is equal to the right ascension. nside = 8 npix = ah.nside_to_npix(nside) ra, dec = hp.pix2ang(nside, np.arange(npix), lonlat=True) img = np.sin(np.deg2rad(ra)) # Plot, show grid, and return figure. ax.imshow_hpx((img, 'ICRS')) ax.grid() return fig
def test_flatten(tmpdir, order_in, d_order_in, fraction_in, nside_out): """Test ligo-skymap-flatten.""" input_filename = str(tmpdir / 'bayestar.fits') output_filename = str(tmpdir / 'bayestar.fits.gz') skymap = input_skymap(order_in, d_order_in, fraction_in) write_sky_map(input_filename, skymap, moc=True) expected_distmean = skymap.meta['distmean'] expected_diststd = skymap.meta['diststd'] args = ['ligo-skymap-flatten', input_filename, output_filename] if nside_out is not None: args.extend(['--nside', str(nside_out)]) run_entry_point(*args) (prob, distmu, distsigma, distnorm), _ = read_sky_map(output_filename, distances=True) distmean, diststd = parameters_to_marginal_moments(prob, distmu, distsigma) if nside_out is not None: assert len(prob) == ah.nside_to_npix(nside_out) assert prob.sum() == pytest.approx(1) assert distmean == pytest.approx(expected_distmean) assert diststd == pytest.approx(expected_diststd) # Now try removing the distance information. skymap_2d = skymap['UNIQ', 'PROBDENSITY'] del skymap_2d.meta['distmean'] del skymap_2d.meta['diststd'] write_sky_map(input_filename, skymap_2d, moc=True, ovewrite=True) args = ['ligo-skymap-flatten', input_filename, output_filename] if nside_out is not None: args.extend(['--nside', str(nside_out)]) run_entry_point(*args) prob_2d, _ = read_sky_map(output_filename) assert np.all(prob == prob_2d)
def test_reproject_healpix_to_image_round_trip( nside, nested, healpix_system, image_system, dtype): """Test round-trip HEALPix->WCS->HEALPix conversion for a random map, with a WCS projection large enough to store each HEALPix pixel""" npix = nside_to_npix(nside) healpix_data = np.random.uniform(size=npix).astype(dtype) reference_header = get_reference_header(oversample=2, nside=nside) wcs_out = WCS(reference_header) shape_out = reference_header['NAXIS2'], reference_header['NAXIS1'] image_data, footprint = reproject_from_healpix( (healpix_data, healpix_system), wcs_out, shape_out=shape_out, order='nearest-neighbor', nested=nested) healpix_data_2, footprint = reproject_to_healpix( (image_data, wcs_out), healpix_system, nside=nside, order='nearest-neighbor', nested=nested) np.testing.assert_array_equal(healpix_data, healpix_data_2)
def smooth_ud_grade(m, nside, nest=False): """Resample a sky map to a new resolution using bilinear interpolation. Parameters ---------- m : np.ndarray The input HEALPix array. nest : bool, default=False Indicates whether the input sky map is in nested rather than ring-indexed HEALPix coordinates (default: ring). Returns ------- new_m : np.ndarray The resampled HEALPix array. The sum of `m` is approximately preserved. """ npix = ah.nside_to_npix(nside) theta, phi = hp.pix2ang(nside, np.arange(npix), nest=nest) new_m = hp.get_interp_val(m, theta, phi, nest=nest) return new_m * len(m) / len(new_m)
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
m = m[hp.ring2nest(nside, np.arange(npix))] elif not m.meta['nest'] and nest: m = m[hp.nest2ring(nside, np.arange(npix))] if moc: return m elif distances: return tuple(np.asarray(m[name]) for name in DEFAULT_NESTED_NAMES), m.meta else: return np.asarray(m[DEFAULT_NESTED_NAMES[0]]), m.meta if __name__ == '__main__': import os nside = 128 npix = ah.nside_to_npix(nside) prob = np.random.random(npix) prob /= sum(prob) write_sky_map('test.fits.gz', prob, objid='FOOBAR 12345', gps_time=1049492268.25, creator=os.path.basename(__file__), url='http://www.youtube.com/watch?v=0ccKPSVQcFk', origin='LIGO Scientific Collaboration', runtime=21.5) print(read_sky_map('test.fits.gz'))
def gsm_sky_model(freqs, resolution="hi", nside=None): """ Return a pyradiosky SkyModel object populated with a Global Sky Model datacube in healpix format. Parameters ---------- freqs : array_like Frequency array, in Hz. resolution : str, optional Whether to use the high or low resolution pygdsm maps. Options are 'hi' or 'low'. nside : int, optional Healpix nside to up- or down-sample the GSM sky model to. Default: `None` (use the default from `pygdsm`, which is 1024). Returns ------- sky_model : pyradiosky.SkyModel SkyModel object. """ import pygdsm # Initialise GSM object gsm = pygdsm.GlobalSkyModel2016(data_unit="TRJ", resolution=resolution, freq_unit="Hz") # Construct GSM datacube hpmap = gsm.generate(freqs=freqs) # FIXME: nside=1024, ring ordering, galactic coords hpmap_units = "K" # Set nside or resample nside_gsm = int(astropy_healpix.npix_to_nside(hpmap.shape[-1])) if nside is None: # Use default nside from pygdsm map nside = nside_gsm else: # Transform to a user-selected nside hpmap_new = np.zeros((hpmap.shape[0], astropy_healpix.nside_to_npix(nside)), dtype=hpmap.dtype) for i in range(hpmap.shape[0]): hpmap_new[i,:] = hp.ud_grade(hpmap[i,:], nside_out=nside, order_in="RING", order_out="RING") hpmap = hpmap_new # Get datacube properties npix = astropy_healpix.nside_to_npix(nside) indices = np.arange(npix) history = "pygdsm.GlobalSkyModel2016, data_unit=TRJ, resolution=low, freq_unit=MHz" freq = units.Quantity(freqs, "hertz") # hmap is in K stokes = units.Quantity(np.zeros((4, len(freq), len(indices))), hpmap_units) stokes[0] = hpmap * units.Unit(hpmap_units) # Construct pyradiosky SkyModel sky_model = pyradiosky.SkyModel( nside=nside, hpx_inds=indices, stokes=stokes, spectral_type="full", freq_array=freq, history=history, frame="galactic", hpx_order="ring" ) sky_model.healpix_interp_transform(frame='icrs', full_sky=True, inplace=True) # do coord transform assert sky_model.component_type == "healpix" return sky_model