foscat.HealBili =============== .. py:module:: foscat.HealBili .. autoapi-nested-parse:: HealBili: Bilinear weights from a curvilinear (theta, phi) source grid to arbitrary HEALPix targets. This module provides a class `HealBili` that, given a *curvilinear* source grid of angular coordinates (theta[y,x], phi[y,x]) on the sphere (e.g., a tangent-plane grid built on an ellipsoid), computes **bilinear interpolation weights** to map values from that grid onto arbitrary target directions specified by HEALPix angles (heal_theta[n], heal_phi[n]). Key idea -------- Because the source grid is not rectilinear in index-space, we cannot assume a simple affine mapping from (i,j) to angles. Instead, for each target direction (theta_h, phi_h), we: 1) locate a nearby source cell (seed) by nearest neighbor search on the unit sphere; 2) consider up to 4 candidate quads around the seed: [(i0,j0),(i0+1,j0),(i0,j0+1),(i0+1,j0+1)]; 3) project the 4 corner unit vectors and the target onto a **local tangent plane** built at the quad barycenter; 4) *invert* the bilinear mapping f(s,t) from the quad corners to the plane point using Newton, retrieving (s,t) in [0,1]^2; 5) build the 4 bilinear weights [(1-s)(1-t), s(1-t), (1-s)t, st] and the 4 linear indices into the source image (row-major, j*W + i). If no candidate quad cleanly contains the point, we choose the one with the smallest residual in the plane and clamp (s,t) to [0,1]. The code is NumPy-only by default, but can optionally use `scipy.spatial.cKDTree` for a faster nearest neighbor seed search by setting `prefer_kdtree=True` (falls back automatically if SciPy is absent). Usage ----- >>> hb = HealBili(src_theta, src_phi, prefer_kdtree=True) >>> I, W = hb.compute_weights(heal_theta, heal_phi) >>> # Apply to a source image `img` of shape (H,W): >>> vals = hb.apply_weights(img, I, W) # shape (N,) All angles must be in **radians**. theta is colatitude (0 at north pole), phi is longitude in [0, 2*pi). Classes ------- .. autoapisummary:: foscat.HealBili.HealBili Module Contents --------------- .. py:class:: HealBili(src_theta: numpy.ndarray, src_phi: numpy.ndarray, *, prefer_kdtree: bool = False) Compute bilinear interpolation weights from a curvilinear (theta, phi) grid to HEALPix targets. :Parameters: * **src_theta** (:py:class:`np.ndarray`, :py:class:`shape (H`, :py:class:`W)`) -- Source **colatitude** (radians) at each grid node. * **src_phi** (:py:class:`np.ndarray`, :py:class:`shape (H`, :py:class:`W)`) -- Source **longitude** (radians) at each grid node. * **prefer_kdtree** (:py:class:`bool`, *default* :py:obj:`False`) -- If True and SciPy is available, use cKDTree on unit vectors for a faster nearest-neighbor seed. Falls back to blocked brute-force dot-product search otherwise. .. py:attribute:: src_theta .. py:attribute:: src_phi .. py:attribute:: prefer_kdtree :value: False .. py:method:: compute_weights(level, cell_ids: numpy.ndarray) -> Tuple[numpy.ndarray, numpy.ndarray] Compute bilinear weights/indices for target HEALPix angles. :Parameters: **cell_ids** (:py:class:`np.ndarray`, :py:class:`shape (N,)`) -- Target **cell_ids** . :returns: * **I** (:py:class:`np.ndarray`, :py:class:`shape (4`, :py:class:`N)`, :py:class:`dtype=int64`) -- Linear indices of the 4 source corners per target (row-major: j*W + i). Invalid corners are -1. * **W** (:py:class:`np.ndarray`, :py:class:`shape (4`, :py:class:`N)`, :py:class:`dtype=float64`) -- Bilinear weights aligned with `I`. Weights are set to 0.0 for invalid corners and normalized to sum to 1 when at least one corner is valid. .. py:method:: apply_weights(img: numpy.ndarray, I: numpy.ndarray, W: numpy.ndarray) -> numpy.ndarray Apply precomputed (I, W) to a source image to obtain values at the HEALPix targets. :Parameters: * **img** (:py:class:`np.ndarray`, :py:class:`shape (H`, :py:class:`W)`) -- Source image values defined on the same grid as (src_theta, src_phi). * **I** (:py:class:`np.ndarray`, :py:class:`shape (4`, :py:class:`N)`, :py:class:`dtype=int64`) -- Linear indices (row-major) of corner samples; -1 for invalid corners. * **W** (:py:class:`np.ndarray`, :py:class:`shape (4`, :py:class:`N)`, :py:class:`dtype=float64`) -- Bilinear weights aligned with I. :returns: **vals** (:py:class:`np.ndarray`, :py:class:`shape (N,)`) -- Interpolated values at the target directions.