diff --git a/docs/api.rst b/docs/api.rst index 6b1f9fe..0e54599 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -29,3 +29,235 @@ Classes healpix_plot.sampling_grid.ParametrizedSamplingGrid healpix_plot.sampling_grid.AffineSamplingGrid healpix_plot.sampling_grid.ConcreteSamplingGrid + +mollview / mollgnomview +----------------------- + +.. autosummary:: + :toctree: generated + + healpix_plot.mollview + healpix_plot.mollgnomview + +mollview parameters +~~~~~~~~~~~~~~~~~~~ + +.. list-table:: + :header-rows: 1 + :widths: 20 25 15 40 + + * - Parameter + - Type + - Default + - Description + * - ``hpx_map`` + - ``np.ndarray``, shape ``(12·4^depth,)`` + - — + - Input HEALPix map. RING order by default; use ``nest=True`` for NESTED. + * - ``nest`` + - ``bool`` + - ``False`` + - Pixel ordering. ``False`` = RING (healpy default), ``True`` = NESTED. + * - ``title`` + - ``str`` + - ``""`` + - Title displayed above the map. + * - ``cmap`` + - ``str`` or ``Colormap`` + - ``"viridis"`` + - Matplotlib colormap. ``"RdBu_r"`` is recommended for CMB/temperature maps. + * - ``vmin`` + - ``float`` or ``None`` + - ``None`` + - Lower bound of the colour scale. Defaults to the 2nd percentile of finite values. + * - ``vmax`` + - ``float`` or ``None`` + - ``None`` + - Upper bound of the colour scale. Defaults to the 98th percentile of finite values. + * - ``rot`` + - ``float`` + - ``0.0`` + - Central longitude of the map in degrees. ``rot=180`` centres on the anti-meridian. + * - ``ellipsoid`` + - ``str`` + - ``"sphere"`` + - Reference ellipsoid for healpix-geo. ``"sphere"`` gives results identical to healpy. Other values: ``"WGS84"``, ``"GRS80"``. + * - ``graticule`` + - ``bool`` + - ``True`` + - Draw meridians and parallels. + * - ``graticule_step`` + - ``float`` + - ``30.0`` + - Spacing of graticule lines in degrees. + * - ``unit`` + - ``str`` + - ``""`` + - Unit string shown below the colorbar. + * - ``bgcolor`` + - ``str`` + - ``"black"`` + - Background colour outside the Mollweide ellipse. + * - ``n_lon`` + - ``int`` + - ``1800`` + - Number of sample columns in the internal raster grid. Increase for high-depth maps (depth >= 8). + * - ``n_lat`` + - ``int`` + - ``900`` + - Number of sample rows in the internal raster grid. + * - ``norm`` + - ``Normalize`` or ``None`` + - ``None`` + - Custom matplotlib normalisation (e.g. ``LogNorm()``). Overrides ``vmin``/``vmax``. + * - ``bad_color`` + - ``str`` + - ``"gray"`` + - Colour for ``NaN`` values. + * - ``flip`` + - ``str`` + - ``"geo"`` + - East/west convention. ``"geo"``: east to the right (default). ``"astro"``: east to the left (astronomical convention). + * - ``figsize`` + - ``(float, float)`` + - ``(14, 7)`` + - Figure size in inches. Only used when a new figure is created. + * - ``colorbar`` + - ``bool`` + - ``True`` + - Show a horizontal colorbar below the map. + * - ``hold`` + - ``bool`` + - ``False`` + - If ``True``, draw into the current axes. Ignored when ``sub`` is provided. + * - ``sub`` + - ``(int, int, int)`` or ``None`` + - ``None`` + - ``(nrows, ncols, index)`` subplot position. Overrides ``hold``. + * - ``coastlines`` + - ``bool`` + - ``False`` + - Overlay Natural Earth coastlines. Activates the cartopy backend — cartopy must be installed. + * - ``coastline_kwargs`` + - ``dict`` or ``None`` + - ``None`` + - Extra kwargs forwarded to ``ax.add_feature(COASTLINE, ...)``. Only used when ``coastlines=True``. + +**Returns:** ``None``. Access the current figure with ``plt.gcf()``. + +**Raises:** + +- ``ValueError`` — ``hpx_map.size`` is not of the form ``12·4^depth``. +- ``ValueError`` — ``flip`` is not ``"astro"`` or ``"geo"``. +- ``ImportError`` — ``coastlines=True`` but cartopy is not installed. + +mollgnomview additional parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``mollgnomview`` shares all parameters of ``mollview`` and adds: + +.. list-table:: + :header-rows: 1 + :widths: 20 15 15 50 + + * - Parameter + - Type + - Default + - Description + * - ``lon_center`` + - ``float`` + - — + - Longitude of the view centre in degrees. + * - ``lat_center`` + - ``float`` + - — + - Latitude of the view centre in degrees. + * - ``fov_deg`` + - ``float`` + - ``10.0`` + - Total field of view (square side) in degrees. + +Internal helpers +~~~~~~~~~~~~~~~~ + +.. list-table:: + :header-rows: 1 + :widths: 30 45 25 + + * - Function + - Signature + - Description + * - ``_depth_from_npix`` + - ``(npix) → int`` + - Infers HEALPix depth. Raises ``ValueError`` for invalid sizes. + * - ``_mollweide_inverse`` + - ``(x, y, central_lon_rad) → (lon_deg, lat_deg, valid)`` + - Analytic inverse Mollweide. Returns a boolean mask for points inside the ellipse. + * - ``_mollweide_forward`` + - ``(lon_deg, lat_deg, central_lon_rad) → (x, y)`` + - Forward Mollweide via Newton iteration. Used for graticule drawing. + * - ``_gnomonic_inverse`` + - ``(x, y, lon0_deg, lat0_deg) → (lon_deg, lat_deg)`` + - Inverse gnomonic projection. ``x``, ``y`` in degrees of arc. + * - ``_rasterise`` + - ``(hpx_map, depth, lon_grid, lat_grid, valid, nest, ellipsoid) → ndarray`` + - HEALPix lookup for valid grid points. Returns ``(H, W)`` float64 with NaN outside the mask. + * - ``_build_cmap`` + - ``(cmap, bad_color) → Colormap`` + - Builds the colormap object and sets the bad-value colour. + * - ``_build_norm`` + - ``(data_img, vmin, vmax, norm) → Normalize`` + - Builds the normalisation object (2nd/98th percentile defaults). + * - ``_add_colorbar`` + - ``(fig, ax, norm, cmap_obj, unit, text_color)`` + - Adds a horizontal colorbar. + * - ``_make_axes_plain`` + - ``(hold, sub, figsize, bgcolor) → (fig, ax)`` + - Creates a plain ``Axes`` (fast path). + * - ``_make_axes_geo`` + - ``(crs, hold, sub, figsize, bgcolor) → (fig, ax)`` + - Creates a cartopy ``GeoAxes`` (cartopy path). + * - ``_draw_graticule_moll`` + - ``(ax, step_deg, central_lon_rad, ...)`` + - Pre-projected graticule on a plain Axes (fast path). + * - ``_draw_graticule_on_geoaxes`` + - ``(ax, step_deg, central_lon_rad, ...)`` + - Pre-projected graticule on a GeoAxes (cartopy path, avoids ``ax.gridlines()``). + * - ``_finalize_moll_axes`` + - ``(ax)`` + - Adds the oval boundary, clips the image to the ellipse, and sets axis limits. + * - ``_require_cartopy`` + - ``() → (ccrs, cfeature)`` + - Lazy cartopy import with a clear error message if not installed. + +Projection math +~~~~~~~~~~~~~~~ + +**Mollweide — 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(θ)) + +**Mollweide — 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``. + +**Gnomonic — Inverse formula** (tangent-plane → lon/lat): + +The gnomonic projection is centred on ``(lon_center, lat_center)``. +Sampling coordinates ``(x, y)`` are in degrees of arc from the tangent point:: + + 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) ) diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index c24999c..0100490 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -8,6 +8,12 @@ :link-type: doc Install, understand the two core objects, make your first map. ::: + +:::{grid-item-card} Mollweide visualisation +:link: mollview +:link-type: doc +Full-sky and local-zoom plots with `mollview` and `mollgnomview`: recipes, layouts, coastlines, and custom normalisation. +::: :::: ```{toctree} @@ -15,4 +21,5 @@ Install, understand the two core objects, make your first map. :maxdepth: 1 quickstart +mollview ``` diff --git a/docs/tutorials/mollview.md b/docs/tutorials/mollview.md new file mode 100644 index 0000000..d131599 --- /dev/null +++ b/docs/tutorials/mollview.md @@ -0,0 +1,182 @@ +--- +jupytext: + formats: md:myst + text_representation: + extension: .md + format_name: myst +kernelspec: + name: python3 + display_name: Python 3 +--- + +# Mollweide visualisation tutorial + +This tutorial walks through the main use cases for `mollview` and `mollgnomview`, from a minimal first plot to advanced layouts and recipes. + +## Setup + +**Always required:** + +```{code-cell} python +pip install healpix-geo numpy matplotlib +``` + +**Optional — only needed when `coastlines=True`:** + +```{code-cell} python +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). + +```{code-cell} python +import numpy as np +import matplotlib.pyplot as plt +from mollview import mollview, mollgnomview +``` + +## Quick start + +```{code-cell} python +# --- 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() +``` + +```{code-cell} python +# With coastlines (requires cartopy) +mollview( + m, + title="With coastlines", + coastlines=True, + coastline_kwargs={"linewidth": 0.8, "edgecolor": "cyan"}, +) +plt.show() +``` + +```{code-cell} python +# 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() +``` + +--- + +## Recipes + +### Save to file (no display) + +```{code-cell} python +import matplotlib +matplotlib.use("Agg") + +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) + +```{code-cell} python +mollview(m, flip="astro", title="Astro convention") +``` + +### With coastlines (geographic data) + +```{code-cell} python +# 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) + +```{code-cell} python +import matplotlib.colors as mcolors + +mollview( + m_positive, + norm=mcolors.LogNorm(vmin=1e-3, vmax=1.0), + title="Log scale", + cmap="inferno", +) +``` + +### Symmetric diverging scale + +```{code-cell} python +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 + +```{code-cell} python +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 + +```{code-cell} python +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() +``` + +### Multi-panel layout (6 maps) + +```{code-cell} python +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() +``` + +### Increase raster resolution for a high-depth map + +```{code-cell} python +# 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)") +``` diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md index 6828478..240b274 100644 --- a/docs/user-guide/index.md +++ b/docs/user-guide/index.md @@ -25,6 +25,12 @@ All parameters explained: colormap, projection, interpolation, colorbar. How the output pixel grid is defined: dict shorthand, bounding box, or affine transform. ::: +:::{grid-item-card} Mollweide visualisation +:link: mollview +:link-type: doc +Rendering backends, pixel ordering, ellipsoid support, figure layouts, and healpy migration guide. +::: + :::: ```{toctree} diff --git a/docs/user-guide/mollview.md b/docs/user-guide/mollview.md index 05317b2..f5a2e6c 100644 --- a/docs/user-guide/mollview.md +++ b/docs/user-guide/mollview.md @@ -3,35 +3,7 @@ > A `healpy`-free replacement for `healpy.mollview` and `healpy.gnomview`, > built on top of **healpix-geo** for all coordinate-system geometry. ---- - -## Table of Contents - -1. [Overview](#overview) -2. [Installation & Dependencies](#installation-dependencies) -3. [Quick Start](#quick-start) -4. [Public API](#public-api) - - 4.1 [`mollview`](#mollview) - - 4.2 [`mollgnomview`](#mollgnomview) -5. [Figure Layout — `hold` and `sub`](#figure-layout-hold-and-sub) -6. [RING vs NESTED pixel ordering](#ring-vs-nested-pixel-ordering) -7. [Ellipsoid support](#ellipsoid-support) -8. [Rendering backends](#rendering-backends) - - 8.1 [Fast path (default)](#fast-path-default) - - 8.2 [Cartopy path (`coastlines=True`)](#cartopy-path-coastlinestrue) -9. [Algorithm — projection math](#algorithm--projection-math) - - 9.1 [Mollweide projection](#mollweide-projection) - - 9.2 [Gnomonic projection](#gnomonic-projection) -10. [Internal helpers](#internal-helpers) -11. [Comparison with `healpy`](#comparison-with-healpy) -12. [Common recipes](#common-recipes) -13. [Known limitations](#known-limitations) - ---- - -(overview)= - -## 1. Overview +## Overview `mollview.py` provides two visualisation functions for HEALPix sky/sphere maps: @@ -54,209 +26,9 @@ Key differences from healpy: 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. + - _Cartopy path_: activated by `coastlines=True`, ~500 ms.\* ---- - -(installation-dependencies)= - -## 2. Installation & Dependencies - -**Always required:** - -```bash -pip install healpix-geo numpy matplotlib -``` - -**Optional — only needed when `coastlines=True`:** - -```bash -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). - ---- - -(quick-start)= - -## 3. Quick Start - -```python -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() -``` - ---- - -(public-api)= - -## 4. Public API - -(mollview)= - -### 4.1 `mollview` - -```python -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**: - -| `coastlines` | Backend | cartopy required | Typical time | -| ----------------- | --------------- | ---------------- | ------------ | -| `False` (default) | Pure matplotlib | No | ~70 ms | -| `True` | Cartopy | Yes | ~500 ms | - -#### Parameters - -| Parameter | Type | Default | Description | -| ------------------ | ----------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| `hpx_map` | `np.ndarray`, shape `(12·4^depth,)` | — | Input HEALPix map. RING order by default; use `nest=True` for NESTED. | -| `nest` | `bool` | `False` | Pixel ordering. `False` = RING (healpy default), `True` = NESTED. | -| `title` | `str` | `""` | Title displayed above the map. | -| `cmap` | `str` or `Colormap` | `"viridis"` | Matplotlib colormap. `"RdBu_r"` is recommended for CMB/temperature maps. | -| `vmin` | `float` or `None` | `None` | Lower bound of the colour scale. Defaults to the 2nd percentile of finite values. | -| `vmax` | `float` or `None` | `None` | Upper bound of the colour scale. Defaults to the 98th percentile of finite values. | -| `rot` | `float` | `0.0` | Central longitude of the map in degrees. `rot=180` centres on the anti-meridian. | -| `ellipsoid` | `str` | `"sphere"` | Reference ellipsoid for healpix-geo. `"sphere"` gives results identical to healpy. Other values: `"WGS84"`, `"GRS80"`. | -| `graticule` | `bool` | `True` | Draw meridians and parallels. | -| `graticule_step` | `float` | `30.0` | Spacing of graticule lines in degrees. | -| `unit` | `str` | `""` | Unit string shown below the colorbar. | -| `bgcolor` | `str` | `"black"` | Background colour outside the Mollweide ellipse. | -| `n_lon` | `int` | `1800` | Number of sample columns in the internal raster grid. Increase for high-depth maps (depth >= 8). | -| `n_lat` | `int` | `900` | Number of sample rows in the internal raster grid. | -| `norm` | `Normalize` or `None` | `None` | Custom matplotlib normalisation (e.g. `LogNorm()`). Overrides `vmin`/`vmax`. | -| `bad_color` | `str` | `"gray"` | Colour for `NaN` values. | -| `flip` | `str` | `"geo"` | East/west convention. `"geo"`: east to the right (default). `"astro"`: east to the left (astronomical convention). | -| `figsize` | `(float, float)` | `(14, 7)` | Figure size in inches. Only used when a new figure is created. | -| `colorbar` | `bool` | `True` | Show a horizontal colorbar below the map. | -| `hold` | `bool` | `False` | If `True`, draw into the current axes. Ignored when `sub` is provided. | -| `sub` | `(int, int, int)` or `None` | `None` | `(nrows, ncols, index)` subplot position. Overrides `hold`. | -| `coastlines` | `bool` | `False` | Overlay Natural Earth coastlines. Activates the cartopy backend — cartopy must be installed. | -| `coastline_kwargs` | `dict` or `None` | `None` | Extra kwargs forwarded to `ax.add_feature(COASTLINE, ...)`. Only used when `coastlines=True`. Example: `{"linewidth": 0.8, "edgecolor": "cyan"}`. | - -#### Returns - -`None`. Access the current figure with `plt.gcf()`. - -#### Raises - -| Exception | Condition | -| ------------- | ----------------------------------------------- | -| `ValueError` | `hpx_map.size` is not of the form `12·4^depth`. | -| `ValueError` | `flip` is not `"astro"` or `"geo"`. | -| `ImportError` | `coastlines=True` but cartopy is not installed. | - ---- - -(mollgnomview)= - -### 4.2 `mollgnomview` - -```python -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 | -| ------------ | ------- | ------- | --------------------------------------------- | -| `lon_center` | `float` | — | Longitude of the view centre in degrees. | -| `lat_center` | `float` | — | Latitude of the view centre in degrees. | -| `fov_deg` | `float` | `10.0` | 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`. - ---- - -(figure-layout-hold-and-sub)= - -## 5. Figure Layout — `hold` and `sub` +## Figure Layout — `hold` and `sub` The three modes below are mutually exclusive, with priority order: **`sub` > `hold=True` > `hold=False`**. @@ -295,25 +67,7 @@ plt.savefig("comparison.png", dpi=150, bbox_inches="tight", facecolor="black") plt.close() ``` -More complex layouts: - -```python -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() -``` - ---- - -(ring-vs-nested-pixel-ordering)= - -## 6. RING vs NESTED pixel ordering +## RING vs NESTED pixel ordering HEALPix maps can be stored in two pixel orderings: @@ -322,8 +76,7 @@ HEALPix maps can be stored in two pixel orderings: | **RING** | Pixels ordered in iso-latitude rings, west to east | `healpy`, this module | | **NESTED** | Pixels ordered along a space-filling (Z-order) curve | `healpix-geo` | -This module always uses RING order by default (`nest=False`), matching -`healpy.mollview`. +This module always uses RING order by default (`nest=False`), matching `healpy.mollview`. ```python # RING map (healpy default) @@ -333,16 +86,9 @@ mollview(m_ring) mollview(m_nested, nest=True) ``` ---- - -(ellipsoid-support)= - -## 7. Ellipsoid support +## 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. +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. ```python # Geographic data referenced to WGS84 @@ -353,17 +99,11 @@ mollview(m_ring, ellipsoid="WGS84", title="WGS84 Mollweide") > image pixel maps to. The visual shape of the Mollweide projection is always > the same mathematical ellipse regardless of the ellipsoid choice. ---- - -(rendering-backends)= - -## 8. Rendering backends +## Rendering backends The backend is selected **automatically** based on the `coastlines` parameter. -(fast-path-default)= - -### 8.1 Fast path (default) +### Fast path (default) Activated when `coastlines=False` (the default). **cartopy is not imported.** @@ -394,9 +134,7 @@ This replaces `ax.gridlines()`, which alone costs ~400 ms in cartopy. Typical time (nside=64, 1800×900): **~70 ms**. -(cartopy-path-coastlinestrue)= - -### 8.2 Cartopy path (`coastlines=True`) +### 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. @@ -432,87 +170,7 @@ 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()`. ---- - -(algorithm--projection-math)= - -## 9. Algorithm — projection math - -(mollweide-projection)= - -### 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`. - -(gnomonic-projection)= - -### 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). - ---- - -(internal-helpers)= - -## 10. Internal helpers - -| Function | Signature | Description | -| ---------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | -| `_depth_from_npix` | `(npix) → int` | Infers HEALPix depth. Raises `ValueError` for invalid sizes. | -| `_mollweide_inverse` | `(x, y, central_lon_rad) → (lon_deg, lat_deg, valid)` | Analytic inverse Mollweide. Returns a boolean mask for points inside the ellipse. | -| `_mollweide_forward` | `(lon_deg, lat_deg, central_lon_rad) → (x, y)` | Forward Mollweide via Newton iteration. Used for graticule drawing. | -| `_gnomonic_inverse` | `(x, y, lon0_deg, lat0_deg) → (lon_deg, lat_deg)` | Inverse gnomonic projection. `x`, `y` in degrees of arc. | -| `_rasterise` | `(hpx_map, depth, lon_grid, lat_grid, valid, nest, ellipsoid) → ndarray` | HEALPix lookup for valid grid points. Returns `(H, W)` float64 with NaN outside the mask. | -| `_build_cmap` | `(cmap, bad_color) → Colormap` | Builds the colormap object and sets the bad-value colour. | -| `_build_norm` | `(data_img, vmin, vmax, norm) → Normalize` | Builds the normalisation object (2nd/98th percentile defaults). | -| `_add_colorbar` | `(fig, ax, norm, cmap_obj, unit, text_color)` | Adds a horizontal colorbar. | -| `_make_axes_plain` | `(hold, sub, figsize, bgcolor) → (fig, ax)` | Creates a plain `Axes` (fast path). | -| `_make_axes_geo` | `(crs, hold, sub, figsize, bgcolor) → (fig, ax)` | Creates a cartopy `GeoAxes` (cartopy path). | -| `_draw_graticule_moll` | `(ax, step_deg, central_lon_rad, ...)` | Pre-projected graticule on a plain Axes (fast path). | -| `_draw_graticule_on_geoaxes` | `(ax, step_deg, central_lon_rad, ...)` | Pre-projected graticule on a GeoAxes (cartopy path, avoids `ax.gridlines()`). | -| `_finalize_moll_axes` | `(ax)` | Adds the oval boundary, clips the image to the ellipse, and sets axis limits. | -| `_require_cartopy` | `() → (ccrs, cfeature)` | Lazy cartopy import with a clear error message if not installed. | - ---- - -(comparison-with-healpy)= - -## 11. Comparison with `healpy` +## Comparison with `healpy` | Feature | `healpy.mollview` | `mollview` (this module) | | -------------------- | ----------------------------- | ---------------------------------------------------- | @@ -552,125 +210,7 @@ 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. ---- - -(common-recipes)= - -## 12. Common recipes - -### Save to file (no display) - -```python -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) - -```python -mollview(m, flip="astro", title="Astro convention") -``` - -### With coastlines (geographic data) - -```python -# 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) - -```python -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 - -```python -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 - -```python -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 - -```python -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 - -```python -# 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)") -``` - ---- - -(known-limitations)= - -## 13. Known limitations +## Known limitations - **`rot` is a scalar longitude only.** Unlike `healpy.mollview` which accepts a 3-tuple `(lon, lat, psi)` for full rotation, only longitude rotation is