Skip to content

Commit 296e4bb

Browse files
committed
Make point_map.fits plotting more friendly.
1 parent 56321ef commit 296e4bb

File tree

5 files changed

+106
-16
lines changed

5 files changed

+106
-16
lines changed

src/hats/inspection/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from .visualize_catalog import plot_pixel_list, plot_pixels, plot_points
1+
from .visualize_catalog import plot_density, plot_pixel_list, plot_pixels

src/hats/inspection/visualize_catalog.py

+29-5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from mocpy.moc.plot.culling_backfacing_cells import backface_culling
2727
from mocpy.moc.plot.utils import _set_wcs
2828

29+
import hats.pixel_math.healpix_shim as hp
2930
from hats.io import file_io, paths
3031
from hats.pixel_math import HealpixPixel
3132
from hats.pixel_tree.moc_filter import perform_filter_by_moc
@@ -49,19 +50,42 @@ def _read_point_map(catalog_base_dir):
4950
return file_io.read_fits_image(map_file_pointer)
5051

5152

52-
def plot_points(catalog: Catalog, plot_title: str | None = None, **kwargs):
53-
"""Create a visual map of the input points of an in-memory catalog.
54-
53+
def plot_density(catalog: Catalog, *, plot_title: str | None = None, order=None, unit=None, **kwargs):
54+
"""Create a visual map of the density of input points of a catalog on-disk.
5555
Args:
5656
catalog (`hats.catalog.Catalog`) Catalog to display
5757
plot_title (str): Optional title for the plot
58+
order (int): Optionally reduce the display healpix order, and aggregate smaller tiles.
5859
kwargs: Additional args to pass to `plot_healpix_map`
5960
"""
6061
if not catalog.on_disk:
6162
raise ValueError("on disk catalog required for point-wise visualization")
6263
point_map = _read_point_map(catalog.catalog_base_dir)
63-
default_title = f"Catalog point density map - {catalog.catalog_name}"
64-
return plot_healpix_map(point_map, title=default_title if plot_title is None else plot_title, **kwargs)
64+
map_order = hp.npix2order(len(point_map))
65+
66+
if order is not None:
67+
if order > map_order:
68+
raise ValueError(f"plotting order should be less than stored density map order ({map_order})")
69+
## Create larger pixel sums from the constituent pixels.
70+
point_map = point_map.reshape((hp.order2npix(order), 2 ** (2 * (map_order - order)))).sum(axis=1)
71+
else:
72+
order = map_order
73+
if unit is None:
74+
unit = u.deg
75+
76+
pix_area = hp.order2pixarea(order, unit=unit)
77+
78+
point_map = point_map / pix_area
79+
default_title = f"Angular density of catalog {catalog.catalog_name}"
80+
fig, ax = plot_healpix_map(
81+
point_map, title=default_title if plot_title is None else plot_title, cbar=False, **kwargs
82+
)
83+
col = ax.collections[0]
84+
plt.colorbar(
85+
col,
86+
label=f"count / {unit} sq",
87+
)
88+
return fig, ax
6589

6690

6791
def plot_pixels(catalog: HealpixDataset, plot_title: str | None = None, **kwargs):

src/hats/pixel_math/healpix_shim.py

+32-8
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,45 @@ def order2npix(order: int) -> int:
4040
return 12 * (1 << (2 * order))
4141

4242

43-
def order2resol(order: int, arcmin: bool = False) -> float:
44-
resol_rad = np.sqrt(order2pixarea(order))
45-
43+
def order2resol(order: int, *, arcmin: bool = False, unit=None) -> float:
4644
if arcmin:
47-
return np.rad2deg(resol_rad) * 60
45+
unit = u.arcmin
46+
else:
47+
unit = _convert_unit(unit, default=u.rad)
48+
49+
return np.sqrt(order2pixarea(order, unit=unit))
4850

49-
return resol_rad
5051

52+
def order2pixarea(order: int, *, degrees: bool = False, unit=None) -> float:
53+
if degrees:
54+
unit = u.deg
55+
else:
56+
unit = _convert_unit(unit, default=u.rad)
5157

52-
def order2pixarea(order: int, degrees: bool = False) -> float:
5358
npix = order2npix(order)
5459
pix_area_rad = 4 * np.pi / npix
55-
if degrees:
60+
61+
# Yes, we're using astropy units, but those are not for SOLID angles.
62+
if unit == u.rad:
63+
return pix_area_rad
64+
if unit == u.deg:
5665
return pix_area_rad * (180 / np.pi) * (180 / np.pi)
57-
return pix_area_rad
66+
if unit == u.arcmin:
67+
return pix_area_rad * (180 / np.pi) * (180 / np.pi) * 3600
68+
if unit == u.arcsec:
69+
return pix_area_rad * (180 / np.pi) * (180 / np.pi) * 12960000
70+
raise ValueError("Unit must be angular measurement.")
71+
72+
73+
def _convert_unit(unit, default=u.rad):
74+
if unit is not None:
75+
unit = u.Unit(unit)
76+
else:
77+
unit = default
78+
79+
if unit not in (u.arcmin, u.arcsec, u.deg, u.rad):
80+
raise ValueError("Unit must be angular measurement.")
81+
return unit
5882

5983

6084
def radec2pix(order: int, ra: float, dec: float) -> np.ndarray[np.int64]:

tests/hats/inspection/test_visualize_catalog.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
from mocpy.moc.plot.culling_backfacing_cells import from_moc
1616
from mocpy.moc.plot.utils import build_plotting_moc
1717

18-
from hats.inspection import plot_pixels
18+
from hats import read_hats
19+
from hats.inspection import plot_density, plot_pixels
1920
from hats.inspection.visualize_catalog import (
2021
compute_healpix_vertices,
2122
cull_from_pixel_map,
@@ -831,6 +832,22 @@ def test_plot_existing_wrong_axes():
831832
np.testing.assert_array_equal(col.get_array(), pix_map)
832833

833834

835+
def test_catalog_plot_density(small_sky_source_dir):
836+
"""Test plotting pixel-density for on-disk catalog.
837+
Confirm plotting at lower order doesn't have a warning, and creates fewer plot paths."""
838+
small_sky_source_catalog = read_hats(small_sky_source_dir)
839+
with pytest.warns(match="smaller"):
840+
_, ax = plot_density(small_sky_source_catalog)
841+
order10_paths = ax.collections[0].get_paths()
842+
assert "Angular density of catalog small_sky_source" == ax.get_title()
843+
844+
_, ax = plot_density(small_sky_source_catalog, order=3)
845+
order3_paths = ax.collections[-1].get_paths()
846+
assert "Angular density of catalog small_sky_source" == ax.get_title()
847+
848+
assert len(order3_paths) < len(order10_paths)
849+
850+
834851
def test_catalog_plot(small_sky_order1_catalog):
835852
fig, ax = plot_pixels(small_sky_order1_catalog)
836853
pixels = sorted(small_sky_order1_catalog.get_healpix_pixels())

tests/hats/pixel_math/test_healpix_shim.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import astropy.units as u
12
import cdshealpix
23
import numpy as np
34
import pytest
@@ -112,6 +113,17 @@ def test_order2pixarea_degrees():
112113
assert pix_area_test == pix_area_expected
113114

114115

116+
def test_order2pixarea_arcmin():
117+
orders = [20, 29]
118+
npix = [12 * (4**order) for order in orders]
119+
pix_area_expected = [np.rad2deg(np.rad2deg(4 * np.pi / x)) * 3600 for x in npix]
120+
pix_area_test = [hps.order2pixarea(order, unit=u.arcmin) for order in orders]
121+
assert_allclose(pix_area_test, pix_area_expected)
122+
123+
pix_area_test = [hps.order2pixarea(order, unit=u.arcminute) for order in orders]
124+
assert_allclose(pix_area_test, pix_area_expected)
125+
126+
115127
def test_order2resol():
116128
orders = [0, 1, 5, 10, 20, 29]
117129
resol_expected = [np.sqrt(hps.order2pixarea(order)) for order in orders]
@@ -123,7 +135,20 @@ def test_order2resol_arcmin():
123135
orders = [0, 1, 5, 10, 20, 29]
124136
resol_expected = [np.rad2deg(np.sqrt(hps.order2pixarea(order))) * 60 for order in orders]
125137
resol_test = [hps.order2resol(order, arcmin=True) for order in orders]
126-
assert resol_test == resol_expected
138+
assert_allclose(resol_test, resol_expected)
139+
140+
141+
def test_order2resol_degree():
142+
orders = [0, 1, 5, 10, 20, 29]
143+
resol_expected = [np.rad2deg(np.sqrt(hps.order2pixarea(order))) for order in orders]
144+
resol_test = [hps.order2resol(order, unit=u.deg) for order in orders]
145+
assert_allclose(resol_test, resol_expected)
146+
147+
resol_test = [hps.order2resol(order, unit=u.degree) for order in orders]
148+
assert_allclose(resol_test, resol_expected)
149+
150+
resol_test = [hps.order2resol(order, unit="deg") for order in orders]
151+
assert_allclose(resol_test, resol_expected)
127152

128153

129154
def test_radec2pix_lonlat():

0 commit comments

Comments
 (0)