Source code for geomfum.refine

"""Functional map refinement machinery."""

import abc
import logging

import geomstats.backend as gs
import scipy

import geomfum.backend as xgs
from geomfum.convert import (
    FmFromP2pBijectiveConverter,
    FmFromP2pConverter,
    P2pFromFmConverter,
    SinkhornP2pFromFmConverter,
    P2pFromNamConverter,
    NamFromP2pConverter,
)


[docs] class Refiner(abc.ABC): """Functional map refiner.""" @abc.abstractmethod def __call__(self, fmap_matrix, basis_a, basis_b): """Apply refiner. Parameters ---------- fmap_matrix : array-like, shape=[spectrum_size_b, spectrum_size_a] Functional map matrix. basis_a : Eigenbasis. Basis. basis_b: Eigenbasis. Basis. Returns ------- fmap_matrix : array-like, shape=[spectrum_size_b, spectrum_size_a] Refined functional map matrix. """
[docs] class IdentityRefiner(Refiner): """A dummy refiner.""" def __call__(self, fmap_matrix, basis_a=None, basis_b=None): """Apply refiner. Parameters ---------- fmap_matrix : array-like, shape=[spectrum_size_b, spectrum_size_a] Functional map matrix. basis_a : Eigenbasis. Basis. Ignored. basis_b: Eigenbasis. Basis. Ignored. Returns ------- fmap_matrix : array-like, shape=[spectrum_size_b, spectrum_size_a] Refined functional map matrix. """ return fmap_matrix
[docs] class OrthogonalRefiner(Refiner): """Refinement using singular value decomposition. Parameters ---------- flip_neg_det : bool Whether to flip negative determinant for square matrices. References ---------- .. [OCSBG2012] Maks Ovsjanikov, Mirela Ben-Chen, Justin Solomon, Adrian Butscher, and Leonidas Guibas. “Functional Maps: A Flexible Representation of Maps between Shapes.” ACM Transactions on Graphics 31, no. 4 (2012): 30:1-30:11. https://doi.org/10.1145/2185520.2185526. """ def __init__(self, flip_neg_det=True): self.flip_neg_det = flip_neg_det def __call__(self, fmap_matrix, basis_a=None, basis_b=None): """Apply refiner. Parameters ---------- fmap_matrix : array-like, shape=[spectrum_size_b, spectrum_size_a] Functional map matrix. basis_a : Eigenbasis. Basis. Ignored. basis_b: Eigenbasis. Basis. Ignored. Returns ------- fmap_matrix : array-like, shape=[spectrum_size_b, spectrum_size_a] Refined functional map matrix. """ k2, k1 = fmap_matrix.shape U, _, VT = scipy.linalg.svd(fmap_matrix) if k1 != k2 or not self.flip_neg_det: return gs.asarray(U @ gs.to_numpy(gs.eye(k2, k1)) @ VT) opt_rot = gs.asarray(gs.matmul(U, VT)) if gs.linalg.det(opt_rot) < 0.0: diag_sign = xgs.diag(gs.ones(VT.shape[0])) diag_sign[-1, -1] = -1 opt_rot = gs.matmul(U, gs.matmul(diag_sign, VT)) return opt_rot
[docs] class ProperRefiner(Refiner): """Refinement projecting the functional map to the proper functional map space. Parameters ---------- p2p_from_fm_converter : P2pFromFmConverter Pointwise map from functional map. fm_from_p2p_converter : FmFromP2pConverter Functional map from pointwise map. """ def __init__( self, p2p_from_fm_converter=None, fm_from_p2p_converter=None, ): super().__init__() if p2p_from_fm_converter is None: p2p_from_fm_converter = P2pFromFmConverter() if fm_from_p2p_converter is None: fm_from_p2p_converter = FmFromP2pConverter() self.p2p_from_fm_converter = p2p_from_fm_converter self.fm_from_p2p_converter = fm_from_p2p_converter def __call__(self, fmap_matrix, basis_a, basis_b): """Apply refiner. Parameters ---------- fmap_matrix : array-like, shape=[spectrum_size_b, spectrum_size_a] Functional map matrix. basis_a : Eigenbasis. Basis. basis_b: Eigenbasis. Basis. Returns ------- fmap_matrix : array-like, shape=[spectrum_size_b, spectrum_size_a] Refined functional map matrix. """ p2p_21 = self.p2p_from_fm_converter(fmap_matrix, basis_a, basis_b) return self.fm_from_p2p_converter(p2p_21, basis_a, basis_b)
[docs] class IterativeRefiner(Refiner): """Iterative refinement of functional map. At each iteration, it computes a pointwise map, converts it back to a functional map, and (optionally) furthers refines it. Parameters ---------- nit : int Number of iterations. step : int or tuple[2, int] How much to increase each basis per iteration. atol : float Convergence tolerance. Ignored if step different than 1. p2p_from_fm_converter : P2pFromFmConverter Pointwise map from functional map. fm_from_p2p_converter : FmFromP2pConverter Functional map from pointwise map. iter_refiner : Refiner Refinement algorithm that runs within each iteration. """ def __init__( self, nit=10, step=0, atol=None, p2p_from_fm_converter=None, fm_from_p2p_converter=None, iter_refiner=None, ): super().__init__() if p2p_from_fm_converter is None: p2p_from_fm_converter = P2pFromFmConverter() if fm_from_p2p_converter is None: fm_from_p2p_converter = FmFromP2pConverter() if iter_refiner is None: iter_refiner = IdentityRefiner() self.nit = nit self.step = step self.atol = atol self.p2p_from_fm_converter = p2p_from_fm_converter self.fm_from_p2p_converter = fm_from_p2p_converter self.iter_refiner = iter_refiner if self._step_a != self._step_b != 0 and atol is not None: raise ValueError("`atol` can't be used with step different than 0.") @property def step(self): """How much to increase each basis per iteration. Returns ------- step : tuple[2, int] Step. """ return self._step_a, self._step_b @step.setter def step(self, step): """Set step. Parameters ---------- step : int or tuple[2, int] How much to increase each basis per iteration. """ if isinstance(step, int): self._step_a = self._step_b = step else: self._step_a, self._step_b = step
[docs] def iter(self, fmap_matrix, basis_a, basis_b): """Refiner iteration. Parameters ---------- fmap_matrix : array-like, shape=[spectrum_size_b, spectrum_size_a] Functional map matrix. basis_a : Eigenbasis. Basis. basis_b: Eigenbasis. Basis. Returns ------- fmap_matrix : array-like, shape=[spectrum_size_b + step_b, spectrum_size_a + step_a] Refined functional map matrix. """ k2, k1 = fmap_matrix.shape new_k1, new_k2 = k1 + self._step_a, k2 + self._step_b p2p_21 = self.p2p_from_fm_converter(fmap_matrix, basis_a, basis_b) fmap_matrix = self.fm_from_p2p_converter( p2p_21, basis_a.truncate(new_k1), basis_b.truncate(new_k2) ) return self.iter_refiner(fmap_matrix, basis_a, basis_b)
def __call__(self, fmap_matrix, basis_a, basis_b): """Apply refiner. Parameters ---------- fmap_matrix : array-like, shape=[spectrum_size_b, spectrum_size_a] Functional map matrix. basis_a : Eigenbasis. Basis. basis_b: Eigenbasis. Basis. Returns ------- fmap_matrix : array-like, shape=[spectrum_size_b, spectrum_size_a] Refined functional map matrix. """ k2, k1 = fmap_matrix.shape nit = self.nit if nit is None: nit = min( (k1 - basis_a.full_spectrum_size) // self._step_a, (k2 - basis_b.full_spectrum_size) // self._step_b, ) else: msg = [] if k1 + nit * self._step_a > basis_a.full_spectrum_size: msg.append("`basis_a`") if k2 + nit * self._step_b > basis_b.full_spectrum_size: msg.append("`basis_b`") if msg: raise ValueError(f"Not enough eigenvectors on {', '.join(msg)}.") for _ in range(nit): new_fmap_matrix = self.iter(fmap_matrix, basis_a, basis_b) if ( self.atol is not None and gs.amax(gs.abs(new_fmap_matrix - fmap_matrix)) < self.atol ): break fmap_matrix = new_fmap_matrix else: if self.atol is not None: logging.warning(f"Maximum number of iterations reached: {nit}") return new_fmap_matrix
[docs] class IcpRefiner(IterativeRefiner): """Iterative refinement of functional map using SVD. Parameters ---------- nit : int Number of iterations. atol : float Convergence tolerance. p2p_from_fm_converter : P2pFromFmConverter Pointwise map from functional map. fm_from_p2p_converter : FmFromP2pConverter Functional map from pointwise map. References ---------- .. [OCSBG2012] Maks Ovsjanikov, Mirela Ben-Chen, Justin Solomon, Adrian Butscher, and Leonidas Guibas. “Functional Maps: A Flexible Representation of Maps between Shapes.” ACM Transactions on Graphics 31, no. 4 (2012): 30:1-30:11. https://doi.org/10.1145/2185520.2185526. """ def __init__( self, nit=10, atol=1e-4, p2p_from_fm_converter=None, fm_from_p2p_converter=None, ): super().__init__( nit=nit, step=0, atol=atol, p2p_from_fm_converter=p2p_from_fm_converter, fm_from_p2p_converter=fm_from_p2p_converter, iter_refiner=OrthogonalRefiner(), )
[docs] class ZoomOut(IterativeRefiner): """Zoomout algorithm. Parameters ---------- nit : int Number of iterations. step : int or tuple[2, int] How much to increase each basis per iteration. p2p_from_fm_converter : P2pFromFmConverter Pointwise map from functional map. fm_from_p2p_converter : FmFromP2pConverter Functional map from pointwise map. References ---------- .. [MRRSWO2019] Simone Melzi, Jing Ren, Emanuele Rodolà, Abhishek Sharma, Peter Wonka, and Maks Ovsjanikov. “ZoomOut: Spectral Upsampling for Efficient Shape Correspondence.” arXiv, September 12, 2019. http://arxiv.org/abs/1904.07865 """ def __init__( self, nit=10, step=1, p2p_from_fm_converter=None, fm_from_p2p_converter=None, ): super().__init__( nit=nit, step=step, p2p_from_fm_converter=p2p_from_fm_converter, fm_from_p2p_converter=fm_from_p2p_converter, iter_refiner=None, )
[docs] class AdjointBijectiveZoomOut(ZoomOut): """Adjoint bijective zoomout algorithm. Parameters ---------- nit : int Number of iterations. step : int or tuple[2, int] How much to increase each basis per iteration. References ---------- :cite:`VM2024` """ def __init__( self, nit=10, step=1, ): super().__init__( nit=nit, step=step, p2p_from_fm_converter=P2pFromFmConverter(adjoint=True, bijective=True), fm_from_p2p_converter=FmFromP2pBijectiveConverter(), )
[docs] class FastSinkhornFilters(ZoomOut): """Fast Sinkhorn filters. Parameters ---------- nit : int Number of iterations. step : int or tuple[2, int] How much to increase each basis per iteration. neighbor_finder : SinkhornKNeighborsFinder Nearest neighbor finder. References ---------- .. [PRMWO2021] Gautam Pai, Jing Ren, Simone Melzi, Peter Wonka, and Maks Ovsjanikov. "Fast Sinkhorn Filters: Using Matrix Scaling for Non-Rigid Shape Correspondence with Functional Maps." Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR), 2021, pp. 11956-11965. https://hal.science/hal-03184936/document """ def __init__( self, nit=10, step=1, neighbor_finder=None, ): super().__init__( nit=nit, step=step, p2p_from_fm_converter=SinkhornP2pFromFmConverter(neighbor_finder), fm_from_p2p_converter=FmFromP2pConverter(), )
[docs] class NeuralZoomOut(ZoomOut): """Neural zoomout algorithm. Parameters ---------- nit : int Number of iterations. step : int or tuple[2, int] How much to increase each basis per iteration. References ---------- .. [VOM2025] Giulio Viganò, Maks Ovsjanikov, Simone Melzi. "NAM: Neural Adjoint Maps for refining shape correspondences". """ def __init__( self, nit=10, step=1, device="cpu", ): super().__init__( nit=nit, step=step, p2p_from_fm_converter=P2pFromNamConverter(), fm_from_p2p_converter=NamFromP2pConverter(device=device), )