metrics

Image similarity metrics and geodesic distances for camera poses

Image similarity metrics

Used to quantify the similarity between ground truth X-rays (\(\mathbf I\)) and synthetic X-rays generated from estimated camera poses (\(\hat{\mathbf I}\)). If a metric is differentiable, it can be used to optimize camera poses with DiffDRR.

NCC and GradNCC are originally implemented in diffdrr.metrics. DiffPose provides torchmetrics wrappers for these functions.


source

GradientNormalizedCrossCorrelation

 GradientNormalizedCrossCorrelation (patch_size=None)

torchmetric wrapper for GradNCC.


source

MultiscaleNormalizedCrossCorrelation

 MultiscaleNormalizedCrossCorrelation (patch_sizes, patch_weights)

torchmetric wrapper for Multiscale NCC.


source

NormalizedCrossCorrelation

 NormalizedCrossCorrelation (patch_size=None)

torchmetric wrapper for NCC.

Geodesic distances for SO(3) and SE(3)

One can define geodesic pseudo-distances on SO(3) and SE(3).1 This let’s us measure registration error (in radians and millimeters, respectively) on poses, rather than needed to compute the projection of fiducials.

  • For SO(3), the geodesic distance between two rotation matrices \(\mathbf R_A\) and \(\mathbf R_B\) is \[\begin{equation} d_\theta(\mathbf R_A, \mathbf R_B; r) = r \left| \arccos \left( \frac{\mathrm{trace}(\mathbf R_A^* \mathbf R_B) - 1}{2} \right ) \right| \,, \end{equation}\] where \(r\), the source-to-detector radius, is used to convert radians to millimeters.

  • For SE(3), we decompose the transformation matrix into a rotation and a translation, i.e., \(\mathbf T = (\mathbf R, \mathbf t)\). Then, we compute the geodesic on translations (just Euclidean distance), \[\begin{equation} d_t(\mathbf t_A, \mathbf t_B) = \| \mathbf t_A - \mathbf t_B \|_2 \,. \end{equation}\] Finally, we compute the double geodesic on the rotations and translations: \[\begin{equation} d(\mathbf T_A, \mathbf T_B) = \sqrt{d_\theta(\mathbf R_A, \mathbf R_B)^2 + d_t(\mathbf t_A, \mathbf t_B)^2} \,. \end{equation}\]


source

GeodesicTranslation

 GeodesicTranslation ()

Calculate the angular distance between two rotations in SO(3).


source

GeodesicSO3

 GeodesicSO3 ()

Calculate the angular distance between two rotations in SO(3).


source

GeodesicSE3

 GeodesicSE3 ()

Calculate the distance between transforms in the log-space of SE(3).


source

DoubleGeodesic

 DoubleGeodesic (sdr:float, eps:float=0.0001)

Calculate the angular and translational geodesics between two SE(3) transformation matrices.

Type Default Details
sdr float Source-to-detector radius
eps float 0.0001 Avoid overflows in sqrt
# SO(3) distance
geodesic_so3 = GeodesicSO3()

pose_1 = RigidTransform(
    torch.tensor([[0.1, 1.0, torch.pi]]),
    torch.ones(1, 3),
    parameterization="euler_angles",
    convention="ZYX",
)
pose_2 = RigidTransform(
    torch.tensor([[0.1, 1.0, torch.pi]]),
    torch.ones(1, 3),
    parameterization="euler_angles",
    convention="ZYX",
)

print(geodesic_so3(pose_1, pose_2))  # Angular distance in radians

pose_1 = RigidTransform(
    torch.tensor([[0.1, 1.0, torch.pi]]),
    torch.ones(1, 3),
    parameterization="euler_angles",
    convention="ZYX",
)
pose_2 = RigidTransform(
    torch.tensor([[0.1, 1.1, torch.pi]]),
    torch.ones(1, 3),
    parameterization="euler_angles",
    convention="ZYX",
)

print(geodesic_so3(pose_1, pose_2))  # Angular distance in radians
tensor([0.])
tensor([0.1000])
# SE(3) distance
geodesic_se3 = GeodesicSE3()

pose_1 = RigidTransform(
    torch.tensor([[0.1, 1.0, torch.pi]]),
    torch.ones(1, 3),
    parameterization="euler_angles",
    convention="ZYX",
)
pose_2 = RigidTransform(
    torch.tensor([[0.1, 1.1, torch.pi]]),
    torch.zeros(1, 3),
    parameterization="euler_angles",
    convention="ZYX",
)

geodesic_se3(pose_1, pose_2)
tensor([1.7355])
# Angular distance and translational distance both in mm
double_geodesic = DoubleGeodesic(1020 / 2)

pose_1 = RigidTransform(
    torch.tensor([[0.1, 1.0, torch.pi]]),
    torch.ones(1, 3),
    parameterization="euler_angles",
    convention="ZYX",
)
pose_2 = RigidTransform(
    torch.tensor([[0.1, 1.1, torch.pi]]),
    torch.zeros(1, 3),
    parameterization="euler_angles",
    convention="ZYX",
)

double_geodesic(pose_1, pose_2)
(tensor([51.0000]), tensor([1.7321]), tensor([51.0294]))