healpix_mollview — HEALPix Mollweide Visualisation#
A
healpy-free replacement forhealpy.mollviewandhealpy.gnomview, built on top of healpix-geo for all coordinate-system geometry.
Table of Contents#
-
4.1
mollview4.2
mollgnomview
1. Overview#
mollview.py provides two visualisation functions for HEALPix sky/sphere maps:
Function |
Projection |
healpy equivalent |
|---|---|---|
|
Mollweide (equal-area, full sky) |
|
|
Gnomonic (tangent-plane, local zoom) |
|
Key differences from healpy:
No healpy dependency. All HEALPix geometry is handled by
healpix-geo.Depth is inferred automatically from the map size — you never pass it explicitly.
RING order by default, like healpy. Pass
nest=Truefor NESTED maps.Non-spherical ellipsoids (e.g. WGS84) are supported via healpix-geo.
No return value — the function draws into the current matplotlib state, exactly like
healpy.mollview.holdandsubparameters control where the plot appears, mirroring healpy’s interface.Two rendering backends, selected automatically:
Fast path (default): pure matplotlib, no cartopy required, ~70 ms.
Cartopy path: activated by
coastlines=True, ~500 ms.
2. Installation & Dependencies#
Always required:
pip install healpix-geo numpy matplotlib
Optional — only needed when coastlines=True:
pip install cartopy
cartopy is imported lazily: it is never loaded unless you explicitly pass
coastlines=True. If cartopy is not installed and coastlines=False (the
default), the module works normally. If cartopy is absent and coastlines=True,
a clear ImportError is raised with installation instructions.
Python >= 3.10 is required (uses X | Y union type hints).
3. Quick Start#
import numpy as np
import matplotlib.pyplot as plt
from mollview import mollview, mollgnomview
# --- Synthetic RING map (depth 5, nside 32) ---
depth = 5
npix = 12 * 4**depth # 12 288 pixels
m = np.random.default_rng(0).standard_normal(npix)
# Fast full-sky view (pure matplotlib, no cartopy)
mollview(m, title="My map", cmap="RdBu_r", unit="K")
plt.show()
# With coastlines (requires cartopy)
mollview(
m,
title="With coastlines",
coastlines=True,
coastline_kwargs={"linewidth": 0.8, "edgecolor": "cyan"},
)
plt.show()
# Local zoom centred on (lon=45deg, lat=30deg)
mollgnomview(
m, lon_center=45.0, lat_center=30.0, fov_deg=20.0, title="Zoom 20deg", cmap="plasma"
)
plt.show()
4. Public API#
4.1 mollview#
mollview(
hpx_map,
nest=False,
title="",
cmap="viridis",
vmin=None,
vmax=None,
rot=0.0,
ellipsoid="sphere",
graticule=True,
graticule_step=30.0,
unit="",
bgcolor="black",
n_lon=1800,
n_lat=900,
norm=None,
bad_color="gray",
flip="geo",
figsize=(14, 7),
colorbar=True,
hold=False,
sub=None,
coastlines=False,
coastline_kwargs=None,
)
Renders a HEALPix map in the Mollweide equal-area projection (full sky).
The rendering backend is chosen automatically:
|
Backend |
cartopy required |
Typical time |
|---|---|---|---|
|
Pure matplotlib |
No |
~70 ms |
|
Cartopy |
Yes |
~500 ms |
Parameters#
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
|
— |
Input HEALPix map. RING order by default; use |
|
|
|
Pixel ordering. |
|
|
|
Title displayed above the map. |
|
|
|
Matplotlib colormap. |
|
|
|
Lower bound of the colour scale. Defaults to the 2nd percentile of finite values. |
|
|
|
Upper bound of the colour scale. Defaults to the 98th percentile of finite values. |
|
|
|
Central longitude of the map in degrees. |
|
|
|
Reference ellipsoid for healpix-geo. |
|
|
|
Draw meridians and parallels. |
|
|
|
Spacing of graticule lines in degrees. |
|
|
|
Unit string shown below the colorbar. |
|
|
|
Background colour outside the Mollweide ellipse. |
|
|
|
Number of sample columns in the internal raster grid. Increase for high-depth maps (depth >= 8). |
|
|
|
Number of sample rows in the internal raster grid. |
|
|
|
Custom matplotlib normalisation (e.g. |
|
|
|
Colour for |
|
|
|
East/west convention. |
|
|
|
Figure size in inches. Only used when a new figure is created. |
|
|
|
Show a horizontal colorbar below the map. |
|
|
|
If |
|
|
|
|
|
|
|
Overlay Natural Earth coastlines. Activates the cartopy backend — cartopy must be installed. |
|
|
|
Extra kwargs forwarded to |
Returns#
None. Access the current figure with plt.gcf().
Raises#
Exception |
Condition |
|---|---|
|
|
|
|
|
|
4.2 mollgnomview#
mollgnomview(
hpx_map,
lon_center,
lat_center,
nest=False,
fov_deg=10.0,
title="",
cmap="viridis",
vmin=None,
vmax=None,
ellipsoid="sphere",
unit="",
n_lon=800,
n_lat=800,
figsize=(7, 7),
colorbar=True,
hold=False,
sub=None,
coastlines=False,
coastline_kwargs=None,
)
Renders a local zoom in the gnomonic (tangent-plane) projection. The depth is inferred automatically from the map size.
The same backend selection logic as mollview applies: coastlines=False
uses the fast matplotlib path; coastlines=True activates cartopy.
Parameters specific to mollgnomview#
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
|
— |
Longitude of the view centre in degrees. |
|
|
— |
Latitude of the view centre in degrees. |
|
|
|
Total field of view (square side) in degrees. |
All other parameters (nest, cmap, vmin, vmax, ellipsoid, unit,
n_lon, n_lat, figsize, colorbar, hold, sub, coastlines,
coastline_kwargs) behave identically to those of mollview.
5. Figure Layout — hold and sub#
The three modes below are mutually exclusive, with priority order:
sub > hold=True > hold=False.
New figure (default)#
mollview(m, title="My map")
plt.show()
A fresh plt.figure() is created on each call. Same behaviour as
healpy.mollview.
hold=True — reuse the current axes#
plt.figure(figsize=(14, 7))
mollview(m, hold=True, title="Overlay")
plt.show()
The map is drawn into whichever axes is currently active.
sub=(nrows, ncols, idx) — subplot grid#
Place multiple maps in a single figure using standard matplotlib subplot indexing (1-based).
fig = plt.figure(figsize=(18, 5), facecolor="black")
mollview(m1, sub=(1, 2, 1), title="Map A", cmap="plasma")
mollview(m2, sub=(1, 2, 2), title="Map B", cmap="RdBu_r")
plt.tight_layout()
plt.savefig("comparison.png", dpi=150, bbox_inches="tight", facecolor="black")
plt.close()
More complex layouts:
plt.figure(figsize=(18, 10), facecolor="black")
mollview(m1, sub=(2, 3, 1), title="(1,1)")
mollview(m2, sub=(2, 3, 2), title="(1,2)")
mollview(m3, sub=(2, 3, 3), title="(1,3)")
mollview(m4, sub=(2, 3, 4), title="(2,1)")
mollview(m5, sub=(2, 3, 5), title="(2,2)")
mollview(m6, sub=(2, 3, 6), title="(2,3)")
plt.tight_layout()
plt.show()
6. RING vs NESTED pixel ordering#
HEALPix maps can be stored in two pixel orderings:
Ordering |
Description |
Default in |
|---|---|---|
RING |
Pixels ordered in iso-latitude rings, west to east |
|
NESTED |
Pixels ordered along a space-filling (Z-order) curve |
|
This module always uses RING order by default (nest=False), matching
healpy.mollview.
# RING map (healpy default)
mollview(m_ring)
# NESTED map
mollview(m_nested, nest=True)
7. Ellipsoid support#
The ellipsoid parameter is forwarded directly to
healpix_geo.nested.lonlat_to_healpix. On a sphere (ellipsoid="sphere",
the default), results are numerically identical to healpy. On a non-spherical
ellipsoid, the authalic latitude is used for the lon/lat → cell-ID conversion.
# Geographic data referenced to WGS84
mollview(m_ring, ellipsoid="WGS84", title="WGS84 Mollweide")
Note: changing the ellipsoid only affects which HEALPix cell each image pixel maps to. The visual shape of the Mollweide projection is always the same mathematical ellipse regardless of the ellipsoid choice.
8. Rendering backends#
The backend is selected automatically based on the coastlines parameter.
8.1 Fast path (default)#
Activated when coastlines=False (the default). cartopy is not imported.
The grid is sampled uniformly in Mollweide (x, y) space using the
analytic inverse formula. The resulting data_img array is therefore already
in display coordinates, and ax.imshow() requires no transform argument and
no render-time reprojection.
Uniform grid in Mollweide (x, y) space [n_lon × n_lat]
|
v _mollweide_inverse() — analytic, vectorised
(lon, lat) for each sample point [degrees]
|
v lonlat_to_healpix() — healpix-geo
cell_id
|
v hpx_map[cell_id]
data_img [n_lat × n_lon] — already in display space
|
v ax.imshow() — no transform, no reprojection
Final figure
The graticule is drawn by pre-projecting meridians and parallels with
_mollweide_forward() (Newton iteration) and calling ax.plot() directly.
This replaces ax.gridlines(), which alone costs ~400 ms in cartopy.
Typical time (nside=64, 1800×900): ~70 ms.
8.2 Cartopy path (coastlines=True)#
Activated when coastlines=True. cartopy is imported lazily at this point.
If cartopy is not installed, a clear ImportError is raised.
The grid must be sampled uniformly in PlateCarree (lon, lat) space so that
it matches the extent=[-180, 180, -90, 90] declared to
imshow(transform=PlateCarree()). Sampling in Mollweide space and passing a
geographic extent would misalign the raster with the coastline overlay (the
root cause of the artefact visible in earlier versions).
Uniform grid in PlateCarree (lon, lat) [n_lon × n_lat]
|
v lonlat_to_healpix() — healpix-geo
cell_id
|
v hpx_map[cell_id]
data_img [n_lat × n_lon] — in PlateCarree space
|
v GeoAxes.imshow(transform=PlateCarree())
cartopy reprojects to Mollweide at render time
|
v ax.add_feature(COASTLINE) — Natural Earth overlay
|
v pre-projected graticule — _mollweide_forward(), no ax.gridlines()
Final figure
Typical time (nside=64, 1800×900): ~500 ms (vs ~1.1 s for a naive
all-cartopy implementation that also calls ax.gridlines()).
The remaining overhead is unavoidable when coastlines are needed: GeoAxes
initialisation (~200–400 ms) and cartopy’s render-time reprojection of the
image (~200 ms) are both required by add_feature().
9. Algorithm — projection math#
9.1 Mollweide projection#
Inverse formula (Mollweide (x, y) → lon/lat, used for the fast-path grid):
The standard Mollweide ellipse spans x ∈ [-2√2, +2√2], y ∈ [-√2, +√2].
Points outside satisfy x²/8 + y²/2 > 1 and are masked as NaN.
The auxiliary angle θ = arcsin(y / √2), then:
sin(lat) = (2θ + sin(2θ)) / π
lon = lon_0 + π·x / (2√2·cos(θ))
Forward formula (lon/lat → Mollweide (x, y), used for graticule lines):
Requires Newton iteration to solve 2θ + sin(2θ) = π·sin(lat):
x = (2√2 / π) · (lon − lon_0) · cos(θ)
y = √2 · sin(θ)
Convergence is reached in fewer than 10 iterations to tol=1e-9.
9.2 Gnomonic projection#
The gnomonic (tangent-plane) projection is centred on (lon_center, lat_center).
Sampling coordinates (x, y) are in degrees of arc from the tangent point.
The inverse formula (tangent-plane → lon/lat):
c = arctan(rho), rho = sqrt(x^2 + y^2)
lat = arcsin( cos(c)·sin(lat_0) + y·sin(c)·cos(lat_0)/rho )
lon = lon_0 + arctan2( x·sin(c), rho·cos(lat_0)·cos(c) − y·sin(lat_0)·sin(c) )
For the fast path the gnomonic grid is also sampled directly in tangent-plane
space, so ax.imshow() requires no transform. The cartopy path samples in
PlateCarree space and uses a Gnomonic GeoAxes, for the same reason as
mollview (coastline alignment).
10. Internal helpers#
Function |
Signature |
Description |
|---|---|---|
|
|
Infers HEALPix depth. Raises |
|
|
Analytic inverse Mollweide. Returns a boolean mask for points inside the ellipse. |
|
|
Forward Mollweide via Newton iteration. Used for graticule drawing. |
|
|
Inverse gnomonic projection. |
|
|
HEALPix lookup for valid grid points. Returns |
|
|
Builds the colormap object and sets the bad-value colour. |
|
|
Builds the normalisation object (2nd/98th percentile defaults). |
|
|
Adds a horizontal colorbar. |
|
|
Creates a plain |
|
|
Creates a cartopy |
|
|
Pre-projected graticule on a plain Axes (fast path). |
|
|
Pre-projected graticule on a GeoAxes (cartopy path, avoids |
|
|
Adds the oval boundary, clips the image to the ellipse, and sets axis limits. |
|
|
Lazy cartopy import with a clear error message if not installed. |
11. Comparison with healpy#
Feature |
|
|
|---|---|---|
Pixel ordering |
RING by default |
RING by default ( |
Depth/nside |
inferred from map length |
inferred from map length |
Rotation |
|
|
Ellipsoid |
sphere only |
|
Image resolution |
fixed internal grid |
configurable: |
East/west default |
|
|
Return value |
|
|
|
supported |
supported |
Coastlines |
not supported |
|
healpy dependency |
required |
not required |
cartopy dependency |
not required |
optional (only for |
NaN handling |
|
|
Colour normalisation |
|
percentiles 2/98 by default; custom |
Migration from healpy#
# healpy
import healpy as hp
hp.mollview(m, title="My map", nest=False, cmap="RdBu_r", unit="K", min=-3, max=3)
# This module — drop-in equivalent
from mollview import mollview
mollview(m, title="My map", nest=False, cmap="RdBu_r", unit="K", vmin=-3, vmax=3)
Parameter renames: min → vmin, max → vmax, width_px/height_px → n_lon/n_lat.
The rot parameter accepts only a longitude scalar (not a 3-tuple).
The flip default is "geo" here (east to the right), whereas healpy defaults
to "astro" (east to the left). Add flip="astro" to reproduce the healpy
convention.
12. Common recipes#
Save to file (no display)#
import matplotlib
matplotlib.use("Agg")
from mollview import mollview
import matplotlib.pyplot as plt
mollview(m, title="My map", cmap="RdBu_r")
plt.savefig("map.png", dpi=150, bbox_inches="tight", facecolor="black")
plt.close()
Astronomical convention (east to the left)#
mollview(m, flip="astro", title="Astro convention")
With coastlines (geographic data)#
# Requires: pip install cartopy
mollview(
m,
flip="geo",
ellipsoid="WGS84",
coastlines=True,
coastline_kwargs={"linewidth": 0.8, "edgecolor": "white"},
title="Geographic — WGS84",
)
Custom colour normalisation (log scale)#
import matplotlib.colors as mcolors
from mollview import mollview
mollview(
m_positive,
norm=mcolors.LogNorm(vmin=1e-3, vmax=1.0),
title="Log scale",
cmap="inferno",
)
Symmetric diverging scale#
import numpy as np
from mollview import mollview
absmax = np.nanpercentile(np.abs(m), 98)
mollview(
m,
vmin=-absmax,
vmax=absmax,
cmap="RdBu_r",
title="Symmetric +/-{:.2f}".format(absmax),
)
Compare two maps side by side#
import matplotlib.pyplot as plt
from mollview import mollview
fig = plt.figure(figsize=(18, 5), facecolor="black")
mollview(m1, sub=(1, 2, 1), title="Map A", cmap="plasma", vmin=-3, vmax=3)
mollview(m2, sub=(1, 2, 2), title="Map B", cmap="plasma", vmin=-3, vmax=3)
plt.tight_layout()
plt.savefig("comparison.png", dpi=150, bbox_inches="tight", facecolor="black")
plt.close()
Full-sky + local zoom in one figure#
import matplotlib.pyplot as plt
from mollview import mollview, mollgnomview
fig = plt.figure(figsize=(18, 8), facecolor="black")
mollview(m, sub=(1, 2, 1), title="Full sky", cmap="RdBu_r")
mollgnomview(
m,
lon_center=45.0,
lat_center=30.0,
fov_deg=20.0,
sub=(1, 2, 2),
title="Zoom 20deg",
cmap="RdBu_r",
)
plt.tight_layout()
plt.show()
Increase raster resolution for a high-depth map#
# depth=8 -> nside=256 -> 786 432 pixels
# Default 1800x900 may be too coarse; use 3600x1800
mollview(m_high_res, n_lon=3600, n_lat=1800, title="High-res map (depth=8)")
13. Known limitations#
rotis a scalar longitude only. Unlikehealpy.mollviewwhich accepts a 3-tuple(lon, lat, psi)for full rotation, only longitude rotation is supported. Latitude rotation and roll are not implemented.No partial-sky maps. The input must be a full-sky map of exactly
12·4^depthpixels. Partial-sky maps must be zero-padded or filled withNaNto full size before plotting.Performance. The HEALPix lookup is vectorised over all
n_lon × n_latsample points with a singlelonlat_to_healpixcall. Forn_lon × n_lat >= 4 × 10^6this may take a few seconds on CPU. Reduce the resolution or pre-cache the index grid if you need to render many maps at the same resolution.Coastlines require cartopy. When
coastlines=Truethe cartopy backend adds ~430 ms of fixed overhead (GeoAxes init + render-time reprojection) that is unavoidable becauseadd_feature()requires aGeoAxes. This overhead is independent of the map resolution.flip="astro"on the cartopy path reverses the column order of the raster before passing it to cartopy. Cartopy is not aware of this flip, so the coastlines are drawn in the correct (geographic) orientation while the data is mirrored. Useflip="geo"(the default) withcoastlines=Truefor correct coastline alignment.