Skip to content

svdtf reflection correction can return a non-minimizing rigid alignment #436

Closed

Description

@Afloat16

Describe the bug

svdtf documents an SE(3) transformation that minimizes mean squared error between associated point clouds. When the unconstrained SVD rotation has determinant -1, the current implementation negates the whole rotation. This makes the determinant positive, but it need not minimize the rigid-alignment objective.

A six-point, centered example:

import torch
import pypose as pp

source = torch.tensor([
    [3., 0., 0.], [-3., 0., 0.],
    [0., 2., 0.], [0., -2., 0.],
    [0., 0., 1.], [0., 0., -1.],
], dtype=torch.float64)
target = source * torch.tensor([1., 1., -1.])
transform = pp.svdtf(source, target)
print(transform.matrix())
print("returned SSE:", (transform.Act(source) - target).square().sum().item())
print("identity SSE:", (source - target).square().sum().item())

Observed on unchanged main revision e37ed16688176596bfb60b1b9c525ba117e0688c:

tensor([[-1.,  0.,  0.,  0.],
        [ 0., -1.,  0.,  0.],
        [ 0.,  0.,  1.,  0.],
        [ 0.,  0.,  0.,  1.]], dtype=torch.float64)
returned SSE: 112.0
identity SSE: 8.0

The same result occurs with float32. Identity is a valid rigid transformation with a strictly smaller error. It is also globally optimal here: the centered cross-covariance is diag(18, 8, -2), with singular values (18, 8, 2). The determinant-constrained Procrustes bound gives minimum SSE 28 + 28 - 2*(18 + 8 - 2) = 8, attained by identity.

The current reflection branch in pypose/function/geometry.py uses R[mask] = -R[mask]. The determinant correction should change only the least-significant singular direction, rather than all three. This concerns the rigid svdtf helper, not the separate svdstf scale-alignment implementation or the earlier pixel2point camera-broadcast fix.

Following the contribution guide, I intend to contribute a minimal determinant-correction fix and regression tests covering the objective, proper rotations, translations, batching/broadcasting, and ordinary exact-rigid controls. Project code has not been modified yet. This report establishes the native CPU baseline and mathematical counterexample; it does not claim a passing full test suite or an end-to-end ICP/EPnP improvement.

Versions

Source revision: e37ed16688176596bfb60b1b9c525ba117e0688c.
The upstream environment-info helper produced the following output. Its unrelated crash-dump directory scan was not invoked.

PyTorch version: 2.7.1+cpu
Is debug build: False
CUDA used to build PyTorch: None
ROCM used to build PyTorch: N/A

OS: Debian GNU/Linux 13 (trixie) (x86_64)
GCC version: (Debian 14.2.0-19) 14.2.0
Clang version: Could not collect
CMake version: Could not collect
Libc version: glibc-2.41

Python version: 3.12.14 (main, Aug 25 2026, 14:00:49) [Clang 22.1.3 ] (64-bit runtime)
Python platform: Linux-6.18.55-x86_64-with-glibc2.41
Is CUDA available: False
CUDA runtime version: No CUDA
GPU models and configuration: No CUDA
Nvidia driver version: No CUDA
cuDNN version: No CUDA
HIP runtime version: N/A
MIOpen runtime version: N/A
Is XNNPACK available: True

Versions of relevant libraries:
[pip3] numpy==2.3.5
[pip3] torch==2.7.1+cpu
[conda] Could not collect

Activity

  1. added a commit that references this issue on Nov 14, 2026
    f9b0882
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions