Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add symmetries publisher and publish empty messages on no detections #1

Merged
merged 34 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e8d705b
Untested symmetries publisher
Kotochleb Jul 16, 2024
d956f07
Add unittest basis
Kotochleb Jul 17, 2024
f840ba6
Update setup.py
Kotochleb Jul 18, 2024
58a8288
Fix thread cleanup
Kotochleb Jul 18, 2024
f6832ba
Fix typos | Change QOS in tests
Kotochleb Jul 18, 2024
46933c0
Fix typos in setup.py
Kotochleb Jul 18, 2024
d79df1c
Fix typos pointed during review
Kotochleb Jul 23, 2024
0ba5392
Add draft for continuous to discrete symmetries converter
Kotochleb Jul 23, 2024
e9e8e03
Add package dependencies
Kotochleb Jul 23, 2024
278047a
Fix formatting of package.xml
Kotochleb Jul 23, 2024
b4fc4e6
Add option to return ROS messages
Kotochleb Jul 23, 2024
31b280f
Rename function
Kotochleb Jul 23, 2024
1c35c18
Fix comment
Kotochleb Jul 23, 2024
69709fb
Add base for unittests
Kotochleb Jul 23, 2024
604184c
Smarter way to aviod `__pycache__`
Kotochleb Jul 24, 2024
5162234
Update happypose_msgs/msg/ObjectSymmetriesArray.msg
Kotochleb Jul 24, 2024
c15e794
Update happypose_msgs/msg/ObjectSymmetries.msg
Kotochleb Jul 24, 2024
89c0140
Update happypose_ros/.gitignore
Kotochleb Jul 24, 2024
b4eb1e0
Update happypose_ros/.gitignore
Kotochleb Jul 24, 2024
aa004fe
Merge branch 'main' of https://github.com/agimus-project/happypose_ro…
Kotochleb Jul 24, 2024
ee3f145
Cover all test cases | Fix bugs
Kotochleb Jul 24, 2024
adb790f
Merge branch 'main' of https://github.com/agimus-project/happypose_ro…
Kotochleb Jul 24, 2024
df8a165
Update package.xml
Kotochleb Jul 24, 2024
81be232
Extend time for unit tests
Kotochleb Jul 24, 2024
c6eefb5
Update .gitignore
Kotochleb Jul 24, 2024
052c273
FIx gitignore comment
Kotochleb Jul 24, 2024
bd5de32
Reduce workload on actions | Fix test order
Kotochleb Jul 24, 2024
11621b5
Hopefully reduce number of memory allocations
Kotochleb Jul 25, 2024
bf62c59
Update comment
Kotochleb Jul 25, 2024
0be9c5e
Fix pacakge version from 0.0.2 to 0.2.0
Kotochleb Jul 25, 2024
c1964f5
FIx comment typo
Kotochleb Jul 25, 2024
2341260
Fix rotations around symmetry axis
Kotochleb Aug 2, 2024
055cfd6
Use `np.linspace` to generate rotation steps | Add notebook with an e…
Kotochleb Aug 8, 2024
b3e4895
Update documentation
Kotochleb Sep 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/happypose_ros_build_and_test.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
name: "Humble: Build and Test"

on: [ push, pull_request ]
on:
push:
branches:
- "main"
pull_request:
branches:
- "*"

jobs:
test_happypose_ros:
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,16 @@ The node provides the following ROS topics:
- Single-view: bounding box is populated.
- Multi-view: no bounding box. Results are represented in the *leading camera* reference frame. Detections are the result of CosyPose multi-view algorithm.

Timestamp in the message's header is set to the moment it is published, after the pose etimation pipeline finished. Timestamps of each result are the same as the timestamp of the image used for the detection. In case of multiview see parameter **time_stamp_strategy** for more information.

- **happypose/vision_info** [vision_msgs/msg/VisionInfo]

Information about the used pose estimator (currently only CosyPose is supported) and URL with object database location.

- **happypose/object_symmetries** [happypose_msgs/msg/ObjectSymmetriesArray] (*QOS: TRANSIENT_LOCAL*)

Discrete and continuous symmetries of objects in the dataset.

- **happypose/markers** [visualization_msgs/msg/MarkerArray]

Array of markers used to visualize detections with their meshes in software like RViz 2.
Expand Down
2 changes: 1 addition & 1 deletion happypose_examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.5.0)
cmake_minimum_required(VERSION 3.10)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the version of CMake that is the default on Ubuntu LTS 22.04?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

project(happypose_examples)

find_package(ament_cmake REQUIRED)
Expand Down
2 changes: 1 addition & 1 deletion happypose_examples/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>happypose_examples</name>
<version>0.0.0</version>
<version>0.2.0</version>
<description>Examples for happypose_ros package</description>
<author email="[email protected]">Krzysztof Wojciechowski</author>
<maintainer email="[email protected]">Guilhem Saurel</maintainer>
Expand Down
45 changes: 45 additions & 0 deletions happypose_msgs/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
cmake_minimum_required(VERSION 3.10)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CMake version on Ubuntu 22.04?
This code targets this version at the minimum so it should ask for this CMake version at the minimum.

project(happypose_msgs)

find_package(ament_cmake REQUIRED)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For future reference, concerning pure ament package like this please consider ament_cmake_auto.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to an additional package happypose_msgs_py being created on a build time, I decided to resort to default ament

find_package(ament_cmake_python REQUIRED)
find_package(builtin_interfaces REQUIRED)
find_package(std_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)

find_package(rclpy REQUIRED)

find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(
${PROJECT_NAME}
msg/ContinuousSymmetry.msg
msg/ObjectSymmetries.msg
msg/ObjectSymmetriesArray.msg
DEPENDENCIES
builtin_interfaces
std_msgs
geometry_msgs)

# Install Python modules
ament_python_install_package(${PROJECT_NAME}_py)
install(FILES package.xml DESTINATION share/${PROJECT_NAME})

if(BUILD_TESTING)
find_package(ament_cmake_pytest REQUIRED)
set(_pytest_tests
test/test_discretize_symmetries.py
)
foreach(_test_path ${_pytest_tests})
get_filename_component(_test_name ${_test_path} NAME_WE)
ament_add_pytest_test(${_test_name} ${_test_path}
APPEND_ENV PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR}
TIMEOUT 60
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
endforeach()
endif()


ament_export_dependencies(rosidl_default_runtime)
ament_package()
136 changes: 136 additions & 0 deletions happypose_msgs/examples/meshcat_viz.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Simple example of usage of symmetries discretization\n",
"\n",
"This example will walk you through the usage of the discretization function found in `happypose_msgs_py`.\n",
"Note, this example requires additional dependency in a form of [MeshCat](https://pypi.org/project/meshcat/) which has to be installed manually.\n",
"\n",
"Additionally, the user has to update `PYTHONPATH` variable used inside Jupyter notebook to account for dependencies found in their ROS 2 installation and build dependencies of `happypose_msgs` build in their Colcon workspace. Code cell will help to make those changes."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Add ROS 2 install path and your Colcon workspace to PYTHONPATH in Jupyter\n",
"import os\n",
"import sys\n",
"from pathlib import Path\n",
"\n",
"# Modify this path to mach you Colcon workspace. The path has to be global\n",
"my_colcon_ws_path = Path(\"/home/gepetto/ros2_ws\")\n",
"\n",
"python_version = f\"python{sys.version_info.major}.{sys.version_info.minor}\"\n",
"dist_package_path = Path(\"local\") / \"lib\" / python_version / \"dist-packages\"\n",
"ros_path = Path(\"/opt\") / \"ros\" / os.environ['ROS_DISTRO'] / dist_package_path\n",
"colson_ws_path = my_colcon_ws_path / \"install\" / \"happypose_msgs\" / dist_package_path\n",
"sys.path.append(ros_path.as_posix())\n",
"sys.path.append(colson_ws_path.as_posix())"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"\n",
"import meshcat\n",
"import meshcat.geometry as g\n",
"\n",
"from geometry_msgs.msg import Vector3\n",
"from happypose_msgs_py.symmetries import discretize_symmetries\n",
"from happypose_msgs.msg import ContinuousSymmetry, ObjectSymmetries\n",
"\n",
"# Generate input ROS message with symmetries\n",
"input_msg = ObjectSymmetries(\n",
" symmetries_discrete=[],\n",
" symmetries_continuous=[\n",
" ContinuousSymmetry(\n",
" axis=Vector3(x=0.0, y=0.0, z=1.0),\n",
" offset=Vector3(x=0.0, y=0.0, z=0.0),\n",
" )\n",
" ],\n",
")\n",
"\n",
"# Discretize symmetries from the message\n",
"res = discretize_symmetries(input_msg, n_symmetries_continuous=64)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Create MeshCat window to display simple mesh rotating around our symmetries"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"vis = meshcat.Visualizer()\n",
"vis.jupyter_cell()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Load mesh of Valkyrie robot head and spin it around our symmetry axis"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"assests_path = Path(meshcat.viewer_assets_path()) / \"data\"\n",
"\n",
"vis[\"robots/valkyrie/head\"].set_object(\n",
" g.ObjMeshGeometry.from_file(assests_path / \"head_multisense.obj\"),\n",
" g.MeshLambertMaterial(\n",
" map=g.ImageTexture(\n",
" image=g.PngImage.from_file(assests_path / \"HeadTextureMultisense.png\")\n",
" )\n",
" ),\n",
")\n",
"\n",
"for r in res:\n",
" # Apply our symmetry transformation in a form of matrix\n",
" vis[\"robots/valkyrie/head\"].set_transform(r)\n",
" time.sleep(0.1)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
103 changes: 103 additions & 0 deletions happypose_msgs/happypose_msgs_py/symmetries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from copy import copy
import numpy as np
import numpy.typing as npt
import transforms3d
from typing import List, Union

from geometry_msgs.msg import Transform, Vector3, Quaternion

from happypose_msgs.msg import ObjectSymmetries


def discretize_symmetries(
object_symmetries: ObjectSymmetries,
n_symmetries_continuous: int = 8,
return_ros_msg: bool = False,
) -> Union[npt.NDArray[np.float64], List[Transform]]:
"""Converts discrete and continuous symmetries to a list of discrete symmetries.

:param object_symmetries: ROS message containing symmetries of a given object.
:type object_symmetries: happypose_msgs.msg.ObjectSymmetries
:param n_symmetries_continuous: Number of segments to discretize continuous symmetries.
:type n_symmetries_continuous: int
:param return_ros_msg: Whether to return ROS message or numpy array
with 4x4 matrices, defaults to False.
:type return_ros_msg: bool, optional
:return: If ``return_ros_msg`` is False returns array of a shape (n, 4, 4) with ``n``
SE3 transformation matrices representing symmetries.
Otherwise list of ROS Transform messages.
:rtype: Union[npt.NDArray[np.float64], List[geometry_msgs.msg.Transform]]
"""

# If there are no continuous symmetries and ROS message is expected skip computations
if return_ros_msg and len(object_symmetries.symmetries_continuous) == 0:
return copy(object_symmetries.symmetries_discrete)

n_con = len(object_symmetries.symmetries_continuous) * n_symmetries_continuous
n_disc = len(object_symmetries.symmetries_discrete)
n_mix = n_con * n_disc

# Preallocate memory for results
out = np.zeros((n_con + n_disc + n_mix, 4, 4))

# Precompute steps of rotations
angles = np.linspace(0.0, 2.0 * np.pi, n_symmetries_continuous, endpoint=False)

# Discretize continuous symmetries
for i, sym_c in enumerate(object_symmetries.symmetries_continuous):
axis = np.array([sym_c.axis.x, sym_c.axis.y, sym_c.axis.z])
if not np.isclose(np.linalg.norm(axis), 1.0):
raise ValueError(
f"Continuous symmetry at index {i} has non unitary rotation axis!"
)
# Compute begin and end indices
begin = i * n_symmetries_continuous
end = (i + 1) * n_symmetries_continuous

# Compute T @ R @ int(T)
# Discrete rotations around axis, generating matrices R
out[begin:end, :3, :3] = np.array(
[transforms3d.axangles.axangle2mat(axis, a) for a in angles]
)
# Compute T @ R
offset = np.array([sym_c.offset.x, sym_c.offset.y, sym_c.offset.z, 1.0])
out[begin:end, :, -1] = offset

# Multiply by inv(T)
T = np.eye(4)
T[:3, 3] = -offset[:3]
out[begin:end, :, :] = out[begin:end, :, :] @ T

# Convert discrete symmetries to matrix format
for i, sym_d in enumerate(object_symmetries.symmetries_discrete):
begin = n_con + i
out[begin, :3, :3] = transforms3d.quaternions.quat2mat(
[sym_d.rotation.w, sym_d.rotation.x, sym_d.rotation.y, sym_d.rotation.z]
)
out[begin, :, -1] = np.array(
[sym_d.translation.x, sym_d.translation.y, sym_d.translation.z, 1.0]
)

sym_c_d_end = n_con + n_disc
symmetries_continuous = out[:n_con]
# Combine discrete symmetries with possible continuous rotations
# TODO @MedericFourmy we should ensure this operation is valid for all object
# and not only objects with offset being at the origin of the coordinate system.
for i in range(n_disc):
begin = sym_c_d_end + i * n_symmetries_continuous
end = sym_c_d_end + (i + 1) * n_symmetries_continuous
symmetry_discrete = out[n_con + i]
# Multiply batch of continuous symmetries onto single discrete symmetry
out[begin:end] = symmetry_discrete @ symmetries_continuous

if not return_ros_msg:
return out

def _mat_to_msg(M: npt.NDArray[np.float64]) -> Transform:
q = transforms3d.quaternions.mat2quat(M[:3, :3])
return Transform(
translation=Vector3(**dict(zip("xyz", M[:, -1]))),
rotation=Quaternion(**dict(zip("wxyz", q))),
)

return [_mat_to_msg(M) for M in out]
6 changes: 6 additions & 0 deletions happypose_msgs/msg/ContinuousSymmetry.msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Definition of continuous symmetry.
# Consists of rotation axis and offset.
# Symilarly to HappyPose object ContinuousSymmetry

geometry_msgs/Vector3 offset
geometry_msgs/Vector3 axis
10 changes: 10 additions & 0 deletions happypose_msgs/msg/ObjectSymmetries.msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Class id for which symmetries are considered
string class_id
# Lists discrete and continuous symmetries of considered object.
# If no symmetries of a given type, list is left empty.

# In HappyPose discrete symmetries are represented as
# transformation matrices. Those matrices are directly
# converted to the Transform message.
geometry_msgs/Transform[] symmetries_discrete
ContinuousSymmetry[] symmetries_continuous
5 changes: 5 additions & 0 deletions happypose_msgs/msg/ObjectSymmetriesArray.msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Time stamp at which the message was sent
std_msgs/Header header

# List of objects detected by the HappyPose node
ObjectSymmetries[] objects
37 changes: 37 additions & 0 deletions happypose_msgs/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0"?>
<package format="3">
<name>happypose_msgs</name>
<version>0.2.0</version>
<description>Custom messages used by happypose_ros package.</description>
<author email="[email protected]">Krzysztof Wojciechowski</author>
<maintainer email="[email protected]">Guilhem Saurel</maintainer>
<license>BSD</license>

<url type="website">https://github.com/agimus-project/happypose_ros</url>
<url type="bugtracker">https://github.com/agimus-project/happypose_ros/issues</url>
<url type="repository">https://github.com/agimus-project/happypose_ros</url>

<buildtool_depend>ament_cmake</buildtool_depend>
<buildtool_depend>ament_cmake_python</buildtool_depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>

<build_depend>builtin_interfaces</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>geometry_msgs</build_depend>

<exec_depend>builtin_interfaces</exec_depend>
<exec_depend>std_msgs</exec_depend>
<exec_depend>geometry_msgs</exec_depend>
<exec_depend>rclpy</exec_depend>
<exec_depend>python3-numpy</exec_depend>
<exec_depend>python3-transforms3d</exec_depend>
<exec_depend>rosidl_default_runtime</exec_depend>

<test_depend>pinocchio</test_depend>

<member_of_group>rosidl_interface_packages</member_of_group>

<export>
<build_type>ament_cmake</build_type>
</export>
</package>
Loading