Skip to content

Commit

Permalink
[manipulation_station] Optionally publish point clouds in sim (RobotL…
Browse files Browse the repository at this point in the history
…ocomotion#14991)

Add new drake_visualizer built-in script to show Drake point clouds.
  • Loading branch information
jwnimmer-tri authored Jun 15, 2021
1 parent 54ecf3e commit 0705661
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 0 deletions.
3 changes: 3 additions & 0 deletions examples/manipulation_station/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ drake_cc_library(
"//math:geometric_transform",
"//multibody/parsing",
"//multibody/plant",
"//perception:depth_image_to_point_cloud",
"//systems/controllers:inverse_dynamics_controller",
"//systems/framework",
"//systems/primitives",
"//systems/sensors:rgbd_sensor",
],
)
Expand Down Expand Up @@ -94,6 +96,7 @@ drake_cc_binary(
"//manipulation/kuka_iiwa:iiwa_command_receiver",
"//manipulation/kuka_iiwa:iiwa_status_sender",
"//manipulation/schunk_wsg:schunk_wsg_lcm",
"//perception:point_cloud_to_lcm",
"//systems/analysis:simulator",
"//systems/lcm",
"//systems/sensors:image_to_lcm_image_array_t",
Expand Down
20 changes: 20 additions & 0 deletions examples/manipulation_station/manipulation_station.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
#include "drake/multibody/parsing/parser.h"
#include "drake/multibody/tree/prismatic_joint.h"
#include "drake/multibody/tree/revolute_joint.h"
#include "drake/perception/depth_image_to_point_cloud.h"
#include "drake/systems/controllers/inverse_dynamics_controller.h"
#include "drake/systems/framework/diagram_builder.h"
#include "drake/systems/primitives/adder.h"
#include "drake/systems/primitives/constant_value_source.h"
#include "drake/systems/primitives/constant_vector_source.h"
#include "drake/systems/primitives/demultiplexer.h"
#include "drake/systems/primitives/discrete_derivative.h"
Expand Down Expand Up @@ -708,12 +710,30 @@ void ManipulationStation<T>::Finalize(
builder.Connect(scene_graph_->get_query_output_port(),
camera->query_object_input_port());

auto depth_to_cloud = builder.template AddSystem<
perception::DepthImageToPointCloud>(
camera->depth_camera_info(),
systems::sensors::PixelType::kDepth16U,
0.001f /* depth camera is in mm */,
perception::pc_flags::kXYZs |
perception::pc_flags::kRGBs);
auto x_pc_system = builder.template AddSystem<
systems::ConstantValueSource>(Value<RigidTransformd>(X_PC));
builder.Connect(camera->color_image_output_port(),
depth_to_cloud->color_image_input_port());
builder.Connect(camera->depth_image_16U_output_port(),
depth_to_cloud->depth_image_input_port());
builder.Connect(x_pc_system->get_output_port(),
depth_to_cloud->camera_pose_input_port());

builder.ExportOutput(camera->color_image_output_port(),
camera_name + "_rgb_image");
builder.ExportOutput(camera->depth_image_16U_output_port(),
camera_name + "_depth_image");
builder.ExportOutput(camera->label_image_output_port(),
camera_name + "_label_image");
builder.ExportOutput(depth_to_cloud->point_cloud_output_port(),
camera_name + "_point_cloud");
}
}

Expand Down
5 changes: 5 additions & 0 deletions examples/manipulation_station/manipulation_station.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ enum class Setup { kNone, kManipulationClass, kClutterClearing, kPlanarIiwa };
/// - camera_[NAME]_rgb_image
/// - camera_[NAME]_depth_image
/// - <b style="color:orange">camera_[NAME]_label_image</b>
/// - <b style="color:orange">camera_[NAME]_point_cloud</b>
/// - ...
/// - camera_[NAME]_rgb_image
/// - camera_[NAME]_depth_image
/// - <b style="color:orange">camera_[NAME]_label_image</b>
/// - <b style="color:orange">camera_[NAME]_point_cloud</b>
/// - <b style="color:orange">pose_bundle</b>
/// - <b style="color:orange">query_object</b>
/// - <b style="color:orange">contact_results</b>
Expand All @@ -88,6 +90,9 @@ enum class Setup { kNone, kManipulationClass, kClutterClearing, kPlanarIiwa };
/// positions directly, but we have estimated v in our code that wraps the
/// FRI.
///
/// @warning The "camera_[NAME]_point_cloud" data currently has registration
/// errors per issue https://github.com/RobotLocomotion/drake/issues/12125.
///
/// Consider the robot dynamics
/// M(q)vdot + C(q,v)v = τ_g(q) + τ_commanded + τ_joint_friction + τ_external,
/// where q == position, v == velocity, and τ == torque.
Expand Down
22 changes: 22 additions & 0 deletions examples/manipulation_station/mock_station_simulation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "drake/geometry/drake_visualizer.h"
#include "drake/lcmt_iiwa_command.hpp"
#include "drake/lcmt_iiwa_status.hpp"
#include "drake/lcmt_point_cloud.hpp"
#include "drake/lcmt_schunk_wsg_command.hpp"
#include "drake/lcmt_schunk_wsg_status.hpp"
#include "drake/manipulation/kuka_iiwa/iiwa_command_receiver.h"
Expand All @@ -17,6 +18,7 @@
#include "drake/math/rigid_transform.h"
#include "drake/math/rotation_matrix.h"
#include "drake/multibody/parsing/parser.h"
#include "drake/perception/point_cloud_to_lcm.h"
#include "drake/systems/analysis/simulator.h"
#include "drake/systems/framework/diagram.h"
#include "drake/systems/framework/diagram_builder.h"
Expand Down Expand Up @@ -48,6 +50,10 @@ DEFINE_string(setup, "manipulation_class",
"Can be {manipulation_class, clutter_clearing}");
DEFINE_bool(publish_cameras, false,
"Whether to publish camera images to LCM");
DEFINE_bool(publish_point_cloud, false,
"Whether to publish point clouds to LCM. Note that per issue "
"https://github.com/RobotLocomotion/drake/issues/12125 the "
"simulated point cloud data will have registration errors.");

int do_main(int argc, char* argv[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
Expand Down Expand Up @@ -172,6 +178,22 @@ int do_main(int argc, char* argv[]) {
image_publisher->get_input_port());
}

// Publish the point clouds.
if (FLAGS_publish_point_cloud) {
for (const auto& camera_name : station->get_camera_names()) {
const std::string cloud_name = "camera_" + camera_name + "_point_cloud";
auto cloud_encoder = builder.AddSystem<perception::PointCloudToLcm>();
const double fps = 5.0;
auto cloud_publisher = builder.AddSystem(
systems::lcm::LcmPublisherSystem::Make<drake::lcmt_point_cloud>(
"DRAKE_POINT_CLOUD_" + camera_name, lcm, 1.0 / fps));
builder.Connect(station->GetOutputPort(cloud_name),
cloud_encoder->get_input_port());
builder.Connect(cloud_encoder->get_output_port(),
cloud_publisher->get_input_port());
}
}

auto diagram = builder.Build();

systems::Simulator<double> simulator(*diagram);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ py_library(
"show_frame.py",
"show_hydroelastic_contact.py",
"show_image.py",
"show_point_cloud.py",
"show_point_pair_contact.py",
"show_time.py",
"use_builtin_scripts.py",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"frame",
"hydroelastic_contact",
"image",
"point_cloud",
"point_pair_contact",
"time",
"grid_wireframe",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Note that this script runs in the main context of drake-visulizer,
# where many modules and variables already exist in the global scope.

import numpy as np

from director import lcmUtils
from director import applogic
from director import objectmodel as om
from director import visualization as vis
from director import vtkNumpy as vnp

from drake import lcmt_point_cloud, lcmt_point_cloud_field

from _drake_visualizer_builtin_scripts import scoped_singleton_func


class PointCloudChannel:

def __init__(self, parent_folder, channel):
self._parent_folder = parent_folder
self._name = channel

def handle_message(self, msg):
# Check that the message's metadata is only and exactly what we want.
if msg.flags != lcmt_point_cloud.IS_STRICTLY_FINITE:
return
fields = (
# (name, byte_offset, datatype, count)
("x", 0, lcmt_point_cloud_field.FLOAT32, 1),
("y", 4, lcmt_point_cloud_field.FLOAT32, 1),
("z", 8, lcmt_point_cloud_field.FLOAT32, 1),
("rgb", 12, lcmt_point_cloud_field.UINT32, 1),
)
if msg.num_fields != len(fields):
return
for i, (name, byte_offset, datatype, count) in enumerate(fields):
if msg.fields[i].name != name:
return
if msg.fields[i].byte_offset != byte_offset:
return
if msg.fields[i].datatype != datatype:
return
if msg.fields[i].count != count:
return

# Swizzle the channel data into ndarrays. The msg.data is formatted
# as "xxxx yyyy zzzz rgb_" where "xxxx", "yyyy", and "zzzz" are floats,
# "r", "g", and "b" are uint8, and "_" is one byte of padding.
points = np.frombuffer(msg.data, dtype=np.float32)
points.shape = (-1, 4)
xyz = points[:, 0:3]
rgb_and_padding_as_float = np.delete(points, range(3), 1)
rgb_and_padding = rgb_and_padding_as_float.view(dtype=np.uint8)
rgb = rgb_and_padding[:, 0:3]
self._update_cloud(xyz, rgb)

def _update_cloud(self, xyz, rgb):
poly_data = vnp.numpyToPolyData(xyz)
vnp.addNumpyToVtk(poly_data, rgb, "rgb")
item = self._parent_folder.findChild(self._name)
if item is not None:
item.setPolyData(poly_data)
else:
view = applogic.getCurrentRenderView()
item = vis.PolyDataItem(self._name, poly_data, view=view)
item.setProperty("Color By", "rgb")
om.addToObjectModel(item, parentObj=self._parent_folder)
item._updateColorByProperty()

def _get_folder(self):
return om.getOrCreateContainer(
self._name, parentObj=self._parent_folder)

def remove_folder(self):
om.removeFromObjectModel(self._get_folder())


class PointCloudsVisualizer:

def __init__(self):
self._name = "Point Clouds Visualizer"
self._subscriber = None
self._children = {}
self.set_enabled(True)

def _add_subscriber(self):
if (self._subscriber is not None):
return
self._subscriber = lcmUtils.addSubscriber(
'DRAKE_POINT_CLOUD_.*',
messageClass=lcmt_point_cloud,
callback=self._handle_message,
callbackNeedsChannel=True)
self._subscriber.setNotifyAllMessagesEnabled(True)

def _get_folder(self):
return om.getOrCreateContainer(self._name)

def _remove_subscriber(self):
if (self._subscriber is None):
return
lcmUtils.removeSubscriber(self._subscriber)
for child in self._children:
child.remove_folder()
self._children.clear()
self._subscriber = None
om.removeFromObjectModel(self._get_folder())

def is_enabled(self):
return self._subscriber is not None

def set_enabled(self, enable):
if enable:
self._add_subscriber()
else:
self._remove_subscriber()

def _handle_message(self, msg, channel):
child = self._children.get(channel)
if not child:
child = PointCloudChannel(
parent_folder=self._get_folder(), channel=channel)
self._children[channel] = child
child.handle_message(msg)


@scoped_singleton_func
def init_visualizer():
point_cloud_viz = PointCloudsVisualizer()

# Adds to the "Tools" menu.
applogic.MenuActionToggleHelper(
'Tools', point_cloud_viz._name,
point_cloud_viz.is_enabled, point_cloud_viz.set_enabled)
return point_cloud_viz


# Activate the plugin if this script is run directly; store the results to keep
# the plugin objects in scope.
if __name__ == "__main__":
point_cloud_viz = init_visualizer()
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
show_frame,
show_hydroelastic_contact,
show_image,
show_point_cloud,
show_point_pair_contact,
show_time,
)
Expand All @@ -27,6 +28,7 @@ def init_visualizer():
("frame", show_frame.init_visualizer),
("hydroelastic_contact", show_hydroelastic_contact.init_visualizer),
("image", show_image.init_visualizer),
("point_cloud", show_point_cloud.init_visualizer),
("point_pair_contact", show_point_pair_contact.init_visualizer),
("time", show_time.init_visualizer),
("grid_wireframe", grid_wireframe.activate),
Expand Down

0 comments on commit 0705661

Please sign in to comment.