Skip to content

geometry::OrientedBoundingBox::Transform() always throws, even for a rigid transform #7558

Description

@simonschlaepfer

Checklist

Describe the issue

geometry::OrientedBoundingBox::Transform() always raises, even for a plain rigid transform:

OrientedBoundingBox& OrientedBoundingBox::Transform(
const Eigen::Matrix4d& transformation) {
utility::LogError(
"A general transform of an OrientedBoundingBox is not implemented. "
"Call Translate, Scale, and Rotate.");
return *this;
}

An oriented bounding box is stored as a center, a rotation matrix and an extent, so it is closed under any similarity transform (rotation, uniform scale, translation): only the center and the rotation move, and the extent scales. That is exactly the case that matters in practice, where a box is carried between coordinate frames next to the point cloud it bounds:

cloud.Transform(target_T_source);
box.Transform(target_T_source);  // throws

Three things make the current behavior surprising:

  1. Every other Geometry3D accepts Transform(), so a box cannot be moved through generic code that transforms a geometry.
  2. The tensor API already implements it, as Rotate() followed by Translate(), so the legacy and tensor APIs disagree on the same operation:
    OrientedBoundingBox &OrientedBoundingBox::Transform(
    const core::Tensor &transformation) {
    core::AssertTensorDevice(transformation, GetDevice());
    core::AssertTensorShape(transformation, {4, 4});
    core::AssertTensorDtypes(transformation, {core::Float32, core::Float64});
    const core::Tensor transformation_d = transformation.To(GetDtype());
    Rotate(transformation_d.GetItem({core::TensorKey::Slice(0, 3, 1),
    core::TensorKey::Slice(0, 3, 1)}));
    Translate(transformation_d
    .GetItem({core::TensorKey::Slice(0, 3, 1),
    core::TensorKey::Index(3)})
    .Flatten());
    return *this;
    }
  3. The suggested workaround ("Call Translate, Scale, and Rotate") is easy to get wrong, because Rotate(R) rotates about the box center by default while Transform() rotates about the origin. How to transform pointcloud and bounding box in the same way? #4875 is exactly that mistake, and the usual fix people land on is to rebuild the box by hand:
    OrientedBoundingBox transformed(transform * box.center_,
                                    transform.rotation() * box.R_, box.extent_);

geometry::OrientedBoundingEllipsoid::Transform() on main has the same body and the same limitation.

A general affine transform genuinely cannot be supported, since shear or non-uniform scale turns the box into a parallelepiped and the ellipsoid into a general quadric. Those cases should keep raising. The similarity case should not.

Steps to reproduce the bug

import numpy as np
import open3d as o3d

box = o3d.geometry.OrientedBoundingBox([1, 2, 3], np.eye(3), [2, 4, 6])

transformation = np.eye(4)
transformation[:3, 3] = [1, 0, 0]  # translate by 1 along x

box.transform(transformation)

Error message

Traceback (most recent call last):
  File "<string>", line 5, in <module>
RuntimeError: [Open3D Error] (virtual open3d::geometry::OrientedBoundingBox& open3d::geometry::OrientedBoundingBox::Transform(const Matrix4d&)) /root/Open3D/cpp/open3d/geometry/BoundingVolume.cpp:58: A general transform of an OrientedBoundingBox is not implemented. Call Translate, Scale, and Rotate.

Expected behavior

A similarity transform is applied to the box: center_ is mapped by the transform, R_ is premultiplied by its rotation part and extent_ is multiplied by its uniform scale. The corners of the result are then the transformed corners of the original box, which is what transforming a point cloud of those corners with the same matrix gives.

A transform with shear, non-uniform scale, mirroring or a projective part keeps raising, with a message that names which part is not supported.

Open3D, Python and System information

- Operating system: Ubuntu 24.04 64-bit
- Python version: 3.12.3
- Open3D version: 0.19.0, and the source of `main` at 1a9eb990f9a20936c30c428568c602bdef760744
- System architecture: x86_64
- Is this a remote workstation?: no
- How did you install Open3D?: pip, and separately built from source for the C++ side
- Compiler version (if built from source): gcc 13.3

Additional information

I have a fix ready and will open a PR that implements Transform() for similarity transforms on both OrientedBoundingBox and OrientedBoundingEllipsoid, reusing the existing Rotate(), Scale() and Translate() primitives, with C++ and Python unit tests for the supported and the rejected cases.

Activity

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