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

Added Chisel to the perception module #281

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
32908ae
Hacked module for Chisel
Feb 1, 2016
d9869ec
Merge branch 'master' of https://github.com/personalrobotics/prpy int…
Feb 9, 2016
17b4c4f
For chisel module
Feb 26, 2016
9aca27c
Merge branch 'master' into perception/chisel
jeking04 Feb 26, 2016
199d542
Updating package to work with single call to DetectObject
jeking04 Feb 26, 2016
bde5ebf
Working on transforming chisel mesh to herb frame
jeking04 Feb 26, 2016
13227e5
Flipping to correct transform
jeking04 Feb 26, 2016
2457645
Cleaning up chisel class. Adding kinbody_helper class. Currently cont…
jeking04 Mar 1, 2016
3e3c93f
Updating vncc to also use the kinbody_helper.
jeking04 Mar 1, 2016
f38ad0e
Updating simtrack to use kinbody_helper
jeking04 Mar 17, 2016
abfd92e
Working on integrating simtrack object tracking with prpy
jeking04 Mar 18, 2016
debef02
Adding ability to bypass tf lookup during tracking
jeking04 Mar 18, 2016
86a9259
Some serialization fixes
Mar 22, 2016
ee1c333
Merge branch 'perception/chisel' of https://github.com/personalroboti…
Mar 22, 2016
3f54fe9
fixing bugs in chisel refactor. adding ability to transform kinbodies…
jeking04 Mar 22, 2016
ba940f2
Merge branch 'master' into perception/chisel
jeking04 Mar 26, 2016
1b4af44
Adding some comments to clarify logic
jeking04 Mar 26, 2016
0c70585
Merge branch 'master' into perception/chisel
mkoval Mar 30, 2016
f47f066
Merge branch 'perception/chisel' of https://github.com/personalroboti…
Apr 13, 2016
06a31b5
Merge branch 'master' into perception/chisel
Apr 13, 2016
99e2cbe
Merge branch 'master' into perception/chisel
Apr 22, 2016
076191f
Around 70% of Mike's original comments
Apr 22, 2016
d5afd97
Basic tracking interface
Apr 27, 2016
8fbdccb
Merge branch 'master' into perception/chisel
May 6, 2016
c62e2d3
Addressed most of Mike's other comments
May 6, 2016
b15e9f5
Merge branch 'master' into perception/chisel
jeking04 Jun 2, 2016
ea0b584
Merge branch 'perception/simtrack' into perception/chisel
jeking04 Jun 2, 2016
9193519
Small changes to get simtrack working correctly and ignoring failed p…
jeking04 Jun 2, 2016
68449f2
Fixing merge error in kinbody_detectory. Changing default chisel dete…
jeking04 Jun 2, 2016
5aec63b
Modifying transform logic to put objects in map frame for offscreen_r…
jeking04 Jun 2, 2016
bbbf4f5
adding logic to add clones of the original body to the camera in orde…
jeking04 Jun 13, 2016
cac5a96
Using 0.05 for pose offset
Jun 14, 2016
97931d3
Merge branch 'master' into perception/chisel
Jun 14, 2016
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
139 changes: 139 additions & 0 deletions src/prpy/perception/chisel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import logging
from base import PerceptionModule, PerceptionMethod

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

class ChiselModule(PerceptionModule):

def __init__(self, env, mesh_client=None, service_namespace=None,
detection_frame=None, destination_frame=None, reference_link=None):
Copy link
Member

Choose a reason for hiding this comment

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

What is detection_frame? It's missing a docstring.

If this is the camera frame, please replace this with the frame_id from the CameraInfo message.

"""
Copy link
Member

Choose a reason for hiding this comment

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

Add a description of what ChiselModule does here.

@param env The OpenRAVE environment
@param mesh_client An instance of the Chisel sensor system (if None, one is created)
@param service_namespace The namespace to use for Chisel service calls (default: Chisel)
@param destination_frame The tf frame the detected chisel kinbody should be transformed to
(default map)
@param reference_link A link on an OpenRAVE kinbody that corresponds to the tf frame
specified by the destination_frame parameter. If not provided it is assumed that the
transform from the destination_frame tf frame to the OpenRAVE environment is the identity
"""
import rospy

try:
rospy.init_node('chisel_detector',anonymous=True)
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't call init_node here because it may only be called once per process. I prefer calling init_node only once, in the top-level script, to prevent this from happening twice.

except rospy.exceptions.ROSException:
pass

if service_namespace is None:
service_namespace = 'Chisel'
Copy link
Member

Choose a reason for hiding this comment

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

Python strings are immutable, so it's fine to move this default into the argument list. Ditto with map.


if mesh_client is None:
import openravepy
Copy link
Member

Choose a reason for hiding this comment

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

Make this a file-level import. It's not like this script would be useful without openravepy installed. 😄

mesh_client = openravepy.RaveCreateSensorSystem(env, 'mesh_marker')
Copy link
Member

Choose a reason for hiding this comment

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

  1. Raise an exception if this fails (i.e. returns None).
  2. Check that mesh_client.GetEnv() == env; this may not be true if the user passes one in.
  3. What package is the mesh_marker sensor system from? We should add this as a dependency in the package.xml file.
  4. What is the motivation behind allowing a user to pass in the sensor system? We don't allow this for the camera client.


self.mesh_client = mesh_client
self.serv_ns = service_namespace

# Create a ROS camera used to generate a synthetic point cloud
# to mask known kinbodies from the chisel mesh
from offscreen_render import ros_camera_sim
Copy link
Member

Choose a reason for hiding this comment

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

Add this as a dependency in the package.xml file.

self.camera = ros_camera_sim.RosCameraSim(env)
self.camera.start('/head/kinect2/qhd')
Copy link
Member

Choose a reason for hiding this comment

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

What is this topic used for? Isn't this a simulated camera?

self.camera_bodies = []

self.reset_detection_srv = None

if detection_frame is None:
# By default, the Chisel kinbody is put in map frame
# not camera frame
detection_frame='/map'
self.detection_frame = detection_frame

if destination_frame is None:
destination_frame = '/map'
self.destination_frame = destination_frame
self.reference_link = reference_link

@PerceptionMethod
def DetectObject(self, robot, ignored_bodies=None, **kw_args):
"""
Copy link
Member

Choose a reason for hiding this comment

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

Add a docstring that explains what this does.

@param robot Required by the PerceptionMethod interface
@param ignored_bodies A list of known kinbodies to be masked out
of the chisel mesh
"""
env = robot.GetEnv()

if ignored_bodies is None:
ignored_bodies = []

# Dictionary mapping ignored bodies to start transform
orig_bodies = {}

#Check if chisel kinbody in env
already_in_env = False
for body in env.GetBodies():
if body.GetName() == 'Chisel/full_mesh':
Copy link
Member

Choose a reason for hiding this comment

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

  1. Where does the name Chisel/full_mesh come from? I believe this only has to match the name passed to SendCommand below. If so, we should make this a parameter to __init__.
  2. All of this logic could be replaced by chisel_kb = env.GetKinBody('Chisel/full_mesh').

already_in_env = True
break

#Remove chisel kinbody if present
if already_in_env:
chisel_kb = env.GetKinBody('Chisel/full_mesh')
env.Remove(chisel_kb)
Copy link
Member

Choose a reason for hiding this comment

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

It's a bit odd that we create a new KinBody with the same name each time we call DetectObjects. This will invalidate any references that the user has to the KinBody. Could we update the mesh in existing KinBody, instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

The or_mesh_marker plugin was designed so as to directly add a KinBody to the current env and it throws an error if there is an existing one, so we worked around it this way. We can have it like this for now, pending a change to 'or_mesh_marker` which I might do after this.


try:
from kinbody_helper import transform_from_or
Copy link
Member

Choose a reason for hiding this comment

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

Add this dependency to the package.xml file.

Copy link
Contributor

Choose a reason for hiding this comment

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

kinbody_helper is inside prpy.


# Add any ignored bodies to the camera
# We need to transform all these bodies from their pose
# in OpenRAVE to the equivalent pose in TF so that
# chisel can perform the appropriate masking
# Then we will transform them all back after the server
# performs the detection
for b in ignored_bodies:
if b not in self.camera_bodies:
with env:
orig_bodies[b] = b.GetTransform()
Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer to lock around this entire loop and the SendCommand call. Otherwise, the objects that need to be masked out may move before we do the render.

transform_from_or(kinbody=b,
detection_frame=self.detection_frame,
destination_frame=self.destination_frame,
reference_link=self.reference_link)
Copy link
Member

Choose a reason for hiding this comment

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

It looks like the return value from transform_from_or is being ignored. What's the purpose of this?

Copy link
Contributor

Choose a reason for hiding this comment

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

The kinbody is being changed inside transform_from_or. Is that the right way to do it?

self.camera.add_body(b)
self.camera_bodies.append(b)

#Reset Chisel
if self.reset_detection_srv is None:
Copy link
Member

Choose a reason for hiding this comment

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

Why do we lazily initialize reset_detection_srv? I suggest either: (1) initializing it once in __init__ or (2) initializing in every function call unless there is a compelling reason to do this.

import rospy, chisel_msgs.srv
Copy link
Member

Choose a reason for hiding this comment

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

Add chisel_msgs as a package.xml file.

Copy link
Member

Choose a reason for hiding this comment

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

srv_nm = self.serv_ns+'/Reset'
rospy.wait_for_service(srv_nm)
Copy link
Member

Choose a reason for hiding this comment

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

I suggest passing a timeout here. That way we can raise an exception with an informative error message if the Chisel service is not running.

self.reset_detection_srv = rospy.ServiceProxy(srv_nm,
chisel_msgs.srv.ResetService)
self.reset_detection_srv()

#Get Kinbody and load into env
self.mesh_client.SendCommand('GetMesh Chisel/full_mesh')
chisel_mesh = env.GetKinBody('Chisel/full_mesh')

finally:

# Remove any previous bodies in the camera
for b in self.camera_bodies:
self.camera.remove_body(b)
self.camera_bodies = []
Copy link
Member

Choose a reason for hiding this comment

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

Why are these on self? It seems like a local variable would suffice.

Copy link
Contributor

Choose a reason for hiding this comment

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

camera_bodies? It is the list of bodies that the camera has added since being started, and needs to have state between calls to DetectObject


with env:
# Restore all bodies to their original pose
for b, orig_pose in orig_bodies.iteritems():
b.SetTransform(orig_pose)

if chisel_mesh is not None and self.reference_link is not None:
# Apply a transform to the chisel kinbody to put it in the correct
# location in the OpenRAVE environment
from kinbody_helper import transform_to_or
transform_to_or(kinbody=chisel_mesh,
detection_frame=self.detection_frame,
destination_frame=self.destination_frame,
reference_link=self.reference_link)

return chisel_mesh
87 changes: 87 additions & 0 deletions src/prpy/perception/kinbody_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@

def transform_to_or(kinbody, detection_frame, destination_frame,
reference_link=None):
"""
Transform the pose of a kinbody from a pose determined using TF to
the correct relative pose in OpenRAVE. This transformation is performed
by providing a link in OpenRAVE that corresponds directly to a frame in TF.

@param kinbody The kinbody to transform
@param detection_frame The tf frame the kinbody was originally detected in
@param destination_frame A tf frame that has a direct correspondence with
a link on an OpenRAVE Kinbody
@param reference_link The OpenRAVE link that corresponds to the tf frame
given by the destination_frame parameter (if None it is assumed
that the transform between the OpenRAVE world and the destination_frame
tf frame is the identity)
"""

import numpy, tf, rospy
Copy link
Member

Choose a reason for hiding this comment

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

Add tf as a dependency.

Copy link
Member

Choose a reason for hiding this comment

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

FYI guys, multiple imports on a single line is not PEP8 :neckbeard:
https://www.python.org/dev/peps/pep-0008/#imports

listener = tf.TransformListener()

listener.waitForTransform(
detection_frame, destination_frame,
rospy.Time(),
Copy link
Member

Choose a reason for hiding this comment

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

You should use the timestamp from the data used by Chisel here.

Copy link
Contributor

@Shushman Shushman May 6, 2016

Choose a reason for hiding this comment

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

That's not easily available. Chisel listens to a bunch of image topics which we don't want to assume the names of. Does it make sense to use rospy.Time.now() ?

rospy.Duration(10))

frame_trans, frame_rot = listener.lookupTransform(
destination_frame, detection_frame,
rospy.Time(0))

from tf.transformations import quaternion_matrix
detection_in_destination = numpy.array(numpy.matrix(quaternion_matrix(frame_rot)))
detection_in_destination[:3,3] = frame_trans

body_in_destination = numpy.dot(detection_in_destination, kinbody.GetTransform())

if reference_link is not None:
destination_in_or = reference_link.GetTransform()
else:
destination_in_or = numpy.eye(4)

body_in_or= numpy.dot(destination_in_or, body_in_destination)
kinbody.SetTransform(body_in_or)

def transform_from_or(kinbody, detection_frame, destination_frame,
reference_link=None):
"""
Transform the pose of a kinbody from a OpenRAVE pose to the correct
relative pose in TF.This transformation is performed
by providing a link in OpenRAVE that corresponds directly to a frame in TF.

@param kinbody The kinbody to transform
@param detection_frame The tf frame the kinbody was originally detected in
@param destination_frame A tf frame that has a direct correspondence with
a link on an OpenRAVE Kinbody
@param reference_link The OpenRAVE link that corresponds to the tf frame
given by the destination_frame parameter (if None it is assumed
that the transform between the OpenRAVE world and the destination_frame
tf frame is the identity)
"""

import numpy, tf, rospy
Copy link
Member

Choose a reason for hiding this comment

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

listener = tf.TransformListener()

listener.waitForTransform(
detection_frame, destination_frame,
rospy.Time(),
rospy.Duration(10))

frame_trans, frame_rot = listener.lookupTransform(
detection_frame, destination_frame,
rospy.Time(0))
Copy link
Member

Choose a reason for hiding this comment

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

Same comments as above.


from tf.transformations import quaternion_matrix
destination_in_detection = numpy.array(numpy.matrix(quaternion_matrix(frame_rot)))
destination_in_detection[:3,3] = frame_trans

with kinbody.GetEnv():
body_in_or = kinbody.GetTransform()

or_in_destination = numpy.linalg.inv(reference_link.GetTransform())
Copy link
Member

Choose a reason for hiding this comment

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

Missing lock.

Copy link
Contributor

Choose a reason for hiding this comment

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

Put lock around entire thing there and in transform_to_or

body_in_destination = numpy.dot(or_in_destination, body_in_or)

body_in_detection = numpy.dot(destination_in_detection, body_in_destination)

with kinbody.GetEnv():
Copy link
Member

Choose a reason for hiding this comment

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

You should use the same lock for all of this to avoid synchronization problems.

kinbody.SetTransform(body_in_detection)
61 changes: 26 additions & 35 deletions src/prpy/perception/simtrack.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@

class SimtrackModule(PerceptionModule):

def __init__(self, kinbody_path, detection_frame, world_frame,
def __init__(self, kinbody_path, detection_frame,
destination_frame=None, reference_link=None,
service_namespace=None):
"""
This initializes a simtrack detector.

@param kinbody_path The path to the folder where kinbodies are stored
@param detection_frame The TF frame of the camera
@param world_frame The desired world TF frame
@param destination_frame The tf frame that the kinbody should be transformed to
@param reference_link The OpenRAVE link that corresponds to the tf destination_frame
@param service_namespace The namespace for the simtrack service (default: /simtrack)
"""
import rospy
Expand All @@ -34,10 +36,13 @@ def __init__(self, kinbody_path, detection_frame, world_frame,

if service_namespace is None:
service_namespace='/simtrack'
self.service_namespace = service_namespace

self.detection_frame = detection_frame
self.world_frame = world_frame
self.service_namespace = service_namespace
if destination_frame is None:
destination_frame='/map'
self.destination_frame = destination_frame
self.reference_link = reference_link
Copy link
Member

Choose a reason for hiding this comment

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

Same comments as above about the defaults and detection_frame.


self.kinbody_path = kinbody_path

Expand Down Expand Up @@ -65,31 +70,6 @@ def _MsgToPose(msg):

return pose

def _LocalToWorld(self,pose):
"""
Transform a pose from local frame to world frame
@param pose The 4x4 transformation matrix containing the pose to transform
@return The 4x4 transformation matrix describing the pose in world frame
"""
import rospy
#Get pose w.r.t world frame
self.listener.waitForTransform(self.world_frame,self.detection_frame,
rospy.Time(),rospy.Duration(10))
t, r = self.listener.lookupTransform(self.world_frame,self.detection_frame,
rospy.Time(0))

#Get relative transform between frames
offset_to_world = numpy.matrix(transformations.quaternion_matrix(r))
offset_to_world[0,3] = t[0]
offset_to_world[1,3] = t[1]
offset_to_world[2,3] = t[2]

#Compose with pose to get pose in world frame
result = numpy.array(numpy.dot(offset_to_world, pose))

return result


def _GetDetections(self, obj_names):
import simtrack_msgs.srv
"""
Expand All @@ -111,8 +91,6 @@ def _GetDetections(self, obj_names):
obj_name = detect_resp.detected_models[i];
obj_pose = detect_resp.detected_poses[i];
obj_pose_tf = self._MsgToPose(obj_pose);
if (self.detection_frame is not None and self.world_frame is not None):
obj_pose_tf = self._LocalToWorld(obj_pose_tf)
detections.append((obj_name, obj_pose_tf));

return detections
Expand Down Expand Up @@ -157,7 +135,15 @@ def DetectObject(self, robot, obj_name, **kw_args):
os.path.join(self.kinbody_path, kinbody_file))

body = env.GetKinBody(obj_name)
body.SetTransform(obj_pose)

# Apply a transform to the kinbody to put it in the
# desired location in OpenRAVE
from kinbody_helper import transform_to_or
transform_to_or(kinbody=body,
detection_frame=self.detection_frame,
destination_frame=self.destination_frame,
reference_link=self.reference_link)

return body

@PerceptionMethod
Expand All @@ -166,6 +152,8 @@ def DetectObjects(self, robot, **kw_args):
Overriden method for detection_frame
"""
from prpy.perception.base import PerceptionException
from kinbody_helper import transform_to_or

env = robot.GetEnv()
# Detecting empty list will detect all possible objects
detections = self._GetDetections([])
Expand All @@ -175,16 +163,19 @@ def DetectObjects(self, robot, **kw_args):
continue

kinbody_name = self.query_to_kinbody_map[obj_name]

if env.GetKinBody(kinbody_name) is None:
from prpy.rave import add_object
kinbody_file = '%s.kinbody.xml' % kinbody_name
new_body = add_object(
env,
kinbody_name,
os.path.join(self.kinbody_path, kinbody_file))
print kinbody_name


body = env.GetKinBody(kinbody_name)
body.SetTransform(obj_pose)
transform_to_or(kinbody=body,
detection_frame=self.detection_frame,
destination_frame=self.destination_frame,
reference_link=self.reference_link)


Loading