healpix_analyse.minkowski ========================= .. py:module:: healpix_analyse.minkowski .. autoapi-nested-parse:: minkowski.py ------------ Differentiable Minkowski functionals for 2D images [B, N, N]. The three 2D Minkowski functionals are: W0 – Area : fraction of "active" pixels W1 – Perimeter : normalised boundary length W2 – Euler characteristic : topological invariant (components − holes) All operations are differentiable; gradients flow back through `img` and optionally through `threshold` (when it is a learned tensor). Functions --------- .. autoapisummary:: healpix_analyse.minkowski.minkowski_functionals healpix_analyse.minkowski.minkowski_curves healpix_analyse.minkowski.build_healpix_adjacency healpix_analyse.minkowski.minkowski_functionals_healpix healpix_analyse.minkowski.minkowski_curves_healpix Module Contents --------------- .. py:function:: minkowski_functionals(img: torch.Tensor, threshold: float | torch.Tensor | None = None, temperature: float | torch.Tensor = 20.0) -> dict[str, torch.Tensor] Compute the three 2D Minkowski functionals in a differentiable manner. :Parameters: * **img** (:py:class:`torch.Tensor`, :py:class:`shape [B`, :py:class:`N`, :py:class:`N]`) -- Batch of square 2D images with values in [0, 1]. * **threshold** (:py:class:`float | torch.Tensor | None`) -- Optional soft threshold applied via a sigmoid before computation. Accepted shapes — see :func:`_as_threshold` for full details: - ``None`` – no thresholding; ``img`` used as soft membership - ``float`` – scalar, same threshold for the whole batch - ``Tensor []`` – scalar tensor (may carry ``requires_grad``) - ``Tensor [B]`` – one threshold per image in the batch - ``Tensor [B,N,N]``– per-pixel spatial threshold (e.g. learned) - Any tensor broadcastable onto ``[B, N, N]`` * **temperature** (:py:class:`float | torch.Tensor`) -- Sharpness of the sigmoid threshold. Higher → closer to hard binary. :returns: :py:class:`dict[str`, :py:class:`Tensor]` -- Keys ``'W0'``, ``'W1'``, ``'W2'``, each of shape ``[B]``. Fully differentiable w.r.t. both ``img`` and ``threshold``. .. admonition:: Notes Mathematical definitions (pixel-complex formulation): .. math:: W_0 = \frac{1}{N^2} \sum_{i,j} f_{ij} W_1 = \frac{1}{N^2} \sum_{i,j} |\nabla f|_1 W_2 = \frac{Q_1 - Q_h - Q_v + Q_f}{N^2} where :math:`Q_1` = pixel sum, :math:`Q_h` / :math:`Q_v` = horizontal / vertical adjacent-pair products (soft AND), :math:`Q_f` = 2×2 block product (4-connectivity formula, Hadwiger 1957). .. py:function:: minkowski_curves(img: torch.Tensor, thresholds: torch.Tensor, temperature: float | torch.Tensor = 20.0) -> dict[str, torch.Tensor] Compute Minkowski functionals at multiple thresholds (Minkowski curves). Useful for cosmological field analysis, texture description, or as rich topological feature vectors for machine learning. :Parameters: * **img** (:py:class:`torch.Tensor`, :py:class:`shape [B`, :py:class:`N`, :py:class:`N]`) -- Batch of square 2D images. * **thresholds** (:py:class:`torch.Tensor`) -- - Shape ``[T]`` – same threshold grid for every image in the batch. - Shape ``[B, T]`` – a different threshold grid per image (e.g. per-sample learned thresholds). * **temperature** (:py:class:`float | torch.Tensor`) -- Sigmoid sharpness. Scalar or broadcastable onto ``[B, T, N, N]``. :returns: :py:class:`dict[str`, :py:class:`Tensor]` -- Keys ``'W0'``, ``'W1'``, ``'W2'``, each of shape ``[B, T]``. Fully differentiable w.r.t. both ``img`` and ``thresholds``. .. py:function:: build_healpix_adjacency(level: int, cell_ids=None, nest: bool = True, device=None) -> tuple[torch.Tensor, torch.Tensor] Pre-compute the edge and triangle adjacency for a HEALPix map. This is a **one-time setup** call. Pass the returned tensors to :func:`minkowski_functionals_healpix` and :func:`minkowski_curves_healpix`. The computation uses healpy and is **not** differentiable, but the Minkowski functions that consume the output **are** differentiable. :Parameters: * **level** (:py:class:`int`) -- Grid4Earth/HEALPix level. The internal resolution is ``nside = 2**level``. * **cell_ids** (:py:class:`array-like` or :py:obj:`None`) -- Global HEALPix pixel indices of the map. ``None`` = full sky (all ``12 * nside²`` pixels). * **nest** (:py:class:`bool`) -- ``True`` for NESTED ordering (default), ``False`` for RING. * **device** (:py:class:`torch.device` or :py:obj:`None`) -- Target device for the returned tensors. :returns: * **edges** (:py:class:`LongTensor [E`, :py:class:`2]`) -- Unique pairs of adjacent **local** pixel indices (i < j). "Adjacent" = sharing a boundary in the HEALPix tessellation (up to 8 neighbours per pixel). * **triangles** (:py:class:`LongTensor [F`, :py:class:`3]`) -- Unique triples of mutually adjacent local indices (i < j < k). Used to compute the Euler characteristic. .. admonition:: Notes The Euler characteristic formula on the pixel complex is: .. math:: \chi = Q_1 - Q_{\text{edges}} + Q_{\text{triangles}} For the full sphere with all pixels active this recovers :math:`\chi(\mathbb{S}^2) = 2` (Euler characteristic of the sphere). .. py:function:: minkowski_functionals_healpix(img: torch.Tensor, edges: torch.Tensor, triangles: torch.Tensor, threshold: float | torch.Tensor | None = None, temperature: float | torch.Tensor = 20.0) -> dict[str, torch.Tensor] Differentiable Minkowski functionals for HEALPix maps. Analogue of :func:`minkowski_functionals` for the spherical pixel graph instead of a 2D regular grid. :Parameters: * **img** (:py:class:`torch.Tensor`, :py:class:`shape [...`, :py:class:`Npix]`) -- Batch of HEALPix maps. Any number of leading dimensions is accepted. * **edges** (:py:class:`LongTensor [E`, :py:class:`2]`) -- Adjacent pixel-pair indices from :func:`build_healpix_adjacency`. * **triangles** (:py:class:`LongTensor [F`, :py:class:`3]`) -- Adjacent pixel-triple indices from :func:`build_healpix_adjacency`. * **threshold** (:py:class:`float | torch.Tensor | None`) -- Soft threshold applied via sigmoid. Accepted shapes (with B = product of leading dims of ``img``): - ``None`` – no thresholding - ``float`` – scalar - ``Tensor []`` – scalar tensor (may carry ``requires_grad``) - ``Tensor [B]`` – one threshold per sample - ``Tensor [B, Npix]`` – spatial (pixel-wise) threshold - Any tensor broadcastable on ``[B, Npix]`` * **temperature** (:py:class:`float | torch.Tensor`) -- Sigmoid sharpness. :returns: :py:class:`dict[str`, :py:class:`Tensor]` -- Keys ``'W0'``, ``'W1'``, ``'W2'``, each of shape ``[...]`` (same leading dims as ``img``). Fully differentiable w.r.t. ``img`` and ``threshold``. .. admonition:: Notes Formulas on the pixel graph: .. math:: W_0 = \frac{1}{N_{\text{pix}}} \sum_i f_i W_1 = \frac{1}{N_{\text{pix}}} \sum_{(i,j)\in E} |f_i - f_j| W_2 = \frac{Q_1 - Q_E + Q_F}{N_{\text{pix}}} where :math:`Q_1`, :math:`Q_E`, :math:`Q_F` are the sums of soft-AND over vertices, edges, and triangular faces respectively. .. py:function:: minkowski_curves_healpix(img: torch.Tensor, edges: torch.Tensor, triangles: torch.Tensor, thresholds: torch.Tensor, temperature: float | torch.Tensor = 20.0) -> dict[str, torch.Tensor] Minkowski curves for HEALPix maps — functionals at multiple thresholds. :Parameters: * **img** (:py:class:`torch.Tensor`, :py:class:`shape [...`, :py:class:`Npix]`) * **edges** (:py:class:`LongTensor [E`, :py:class:`2]`) * **triangles** (:py:class:`LongTensor [F`, :py:class:`3]`) * **thresholds** (:py:class:`Tensor [T]` or :py:class:`[B`, :py:class:`T]`) -- - ``[T]`` – same grid for every sample in the batch - ``[B, T]`` – one grid per sample (B = product of leading dims) * **temperature** (:py:class:`float | torch.Tensor`) :returns: :py:class:`dict[str`, :py:class:`Tensor]` -- Keys ``'W0'``, ``'W1'``, ``'W2'``, each of shape ``[..., T]``. Differentiable w.r.t. ``img`` and ``thresholds``.