nbv.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 '''
4 Copyright (c) 2016, Allgeyer Tobias, Aumann Florian, Borella Jocelyn, Hutmacher Robin, Karrenbauer Oliver, Marek Felix, Meissner Pascal, Trautmann Jeremias, Wittenbeck Valerij
5 
6 All rights reserved.
7 
8 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
9 
10 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
11 
12 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
13 
14 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
15 
16 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17 '''
18 
19 import roslib
20 import rospy
21 import smach
22 import smach_ros
23 import numpy
24 import tf
25 import math
26 import pickle
27 import os
28 
29 from asr_next_best_view.srv import *
30 from asr_next_best_view.msg import RobotStateMessage, NormalsInfo
31 from asr_world_model.srv import GetViewportList, FilterViewportDependingOnAlreadyVisitedViewports
32 from asr_msgs.msg import (AsrAttributedPointCloud, AsrAttributedPoint, AsrViewport)
33 from geometry_msgs.msg import (Pose, PoseWithCovariance,
34  PoseWithCovarianceStamped, Point, Quaternion, Twist)
35 
36 from common.evaluation_decorators import *
37 import __builtin__
38 import common.state_acquisition as state_acquisition
39 
40 """
41 States and methods for using the Next-Best-View package.
42 """
43 
44 global rejectedPointCloud
45 rejectedPointCloud = []
46 global numSetPointCloud
47 numSetPointCloud = 0
48 global useGoalCameraPoseAtNextNBVUpdatePointCloud
49 useGoalCameraPoseAtNextNBVUpdatePointCloud = False
50 
51 class NBVSetPointCloud(smach.State):
52  """
53  Initializes the point cloud for next_best_view.
54  If there is already a point cloud set, this state overwrites it.
55  """
56 
57  def __init__(self):
58  smach.State.__init__(
59  self, outcomes=['succeeded', 'aborted', 'too_many_deactivated_normals'],
60  input_keys=['object_pointcloud'])
61  self.isInitDone = 0
62 
63  def initNode(self):
64  rospy.loginfo('Init NBV_SET_POINT_CLOUD')
65  self.isInitDone = 1
66  self.percentage_too_many_deactivated = rospy.get_param('/scene_exploration_sm/percentageTooManyDeactivatedNormals')
67  rospy.loginfo("percentageTooManyDeactivatedNormals: " + str(self.percentage_too_many_deactivated))
68  if rospy.get_param("~SavePointClouds"):
69  os.makedirs(__builtin__.log_dir + "/PointCloud")
70 
71  @log
72  @timed
73  def execute(self, userdata):
74  rospy.loginfo('Executing NBV_SET_POINT_CLOUD')
75  if self.isInitDone == 0:
76  self.initNode()
77 
78  try:
79  rospy.wait_for_service('/nbv/set_point_cloud', timeout=5)
80  rospy.wait_for_service('/nbv/remove_objects', timeout=5)
81  rospy.wait_for_service('/env/asr_world_model/get_viewport_list', timeout=5)
82  except rospy.exceptions.ROSException, e:
83  rospy.loginfo("Couldn't reach service")
84  return 'aborted'
85  try:
87 
88  # services
89  set_pc = rospy.ServiceProxy('/nbv/set_point_cloud', SetAttributedPointCloud)
90  invalidate_point_cloud = rospy.ServiceProxy('/nbv/remove_objects', RemoveObjects)
91  get_viewports = rospy.ServiceProxy('/env/asr_world_model/get_viewport_list', GetViewportList)
92 
93  # used to get points in nbv service call
94  pointCloud = userdata.object_pointcloud
95  # append previously removed hypothesis
96  if rospy.get_param("~recoverLostHypothesis"):
97  global rejectedPointCloud
98  pointCloud.elements += rejectedPointCloud
99  if (len(rejectedPointCloud) > 0):
100  rospy.loginfo("added " + str(len(rejectedPointCloud)) + " old points to pc")
101  rejectedPointCloud = []
102  viewports = get_viewports('all').viewport_list
103  set_pc_rsp = set_pc(pointCloud, viewports)
104 
105  # save pointcloud to debug later
106  if rospy.get_param("~SavePointClouds"):
107  global numSetPointCloud
108  pointCloudFile = open(__builtin__.log_dir + "/PointCloud/PointCloud_" + str(numSetPointCloud), "wb")
109  viewportsFile = open(__builtin__.log_dir + "/PointCloud/Viewports_" + str(numSetPointCloud), "wb")
110  pickle.dump(pointCloud, pointCloudFile)
111  pickle.dump(viewports, viewportsFile)
112  pointCloudFile.close()
113  viewportsFile.close()
114  numSetPointCloud += 1
115 
116  # print if too many object hypothesis were removed from earlier viewports per object
117  # check if all hypothesis are covered by earlier viewports
118  all_percentages_too_low = True
119 
120  for normalInfo in set_pc_rsp.normals_per_object:
121  rospy.loginfo("Number of active object normals in original point cloud for type " + \
122  normalInfo.type + " and id " + normalInfo.identifier + ": " + str(normalInfo.active_normals))
123  rospy.loginfo("Number of deactivated normals for type " + \
124  normalInfo.type + " and id " + normalInfo.identifier + ": " + str(normalInfo.deactivated_object_normals))
125 
126  if normalInfo.active_normals == 0:
127  quotient = 0.0
128  else:
129  quotient = 1.0 * normalInfo.deactivated_object_normals / normalInfo.active_normals
130  rospy.loginfo("Quotient of deactivated_object_normals / set_pc_rsp.active_normals for type " + \
131  normalInfo.type + " and id " + normalInfo.identifier + ": " + str(quotient))
132  if quotient < self.percentage_too_many_deactivated:
133  all_percentages_too_low = False
134  break
135 
136  # whole pc will be removed because earlier viewports cover most hypothesis
137  if all_percentages_too_low == True:
138  rospy.logwarn("Percentage of active normals in cloud too low for all types and ids. " + \
139  "Calculating different point cloud for NBV estimation instead.")
140  if rospy.get_param("~recoverLostHypothesis"):
141  get_pc = rospy.ServiceProxy("/nbv/get_point_cloud", GetAttributedPointCloud)
142  rejectedPointCloud += get_pc().point_cloud.elements
143  return 'too_many_deactivated_normals'
144  else:
145  # only some or none hypothesis are covered by earlier hypothesis
146  if rospy.get_param("~recoverLostHypothesis"):
147  get_pc = rospy.ServiceProxy("/nbv/get_point_cloud", GetAttributedPointCloud)
148  filteredPC = get_pc()
149  for normalInfo in set_pc_rsp.normals_per_object:
150  if normalInfo.active_normals == 0:
151  quotient = 0.0
152  else:
153  quotient = 1.0 * normalInfo.deactivated_object_normals / normalInfo.active_normals
154  if quotient >= self.percentage_too_many_deactivated :
155  # objects will be removed
156  rospy.logwarn("Percentage of active normals in cloud too low for type " + \
157  normalInfo.type + " and id " + normalInfo.identifier + ". Erase all normals for type and id in point_cloud.")
158 
159  # save objects that will be removed
160  if rospy.get_param("~recoverLostHypothesis"):
161  # recover hypothesis that we invalidate later
162  for obj in filteredPC.point_cloud.elements:
163  if (obj.type == normalInfo.type and obj.identifier == normalInfo.identifier):
164  rejectedPointCloud.append(obj)
165  # remove objects from pc
166  result = invalidate_point_cloud(normalInfo.type, normalInfo.identifier)
167  if not result.is_valid:
168  # PC is empty
169  return 'too_many_deactivated_normals'
170 
171  if set_pc_rsp.is_valid == False:
172  return 'aborted'
173  else:
174  return 'succeeded'
175 
176  except rospy.ServiceException, e:
177  rospy.logwarn(str(e))
178  return 'aborted'
179 
180 
181 class NextBestView(smach.State):
182  """
183  Calculates NextBestView given a pose
184  """
185 
186  def __init__(self):
187  smach.State.__init__(
188  self,
189  outcomes=['found_next_best_view', 'aborted', 'no_nbv_found', 'nbv_update_point_cloud'],
190  output_keys=['goal_camera_pose', 'goal_robot_pose', 'goal_ptu_position', 'searched_object_types'])
191 
192  @log
193  @timed
194  def execute(self, userdata):
195  rospy.loginfo('Executing NEXT_BEST_VIEW')
196 
197  rospy.wait_for_service('/nbv/next_best_view')
198  try:
199  setRobotState()
200 
201  get_nbv = rospy.ServiceProxy('/nbv/next_best_view', GetNextBestView)
202  min_utility_for_moving = rospy.get_param("/scene_exploration_sm/min_utility_for_moving")
203 
204  # get_camera_pose_cpp() gets actual pose of the camera of the robot.
205  current_camera_pose = state_acquisition.get_camera_pose_cpp()
206  rospy.loginfo("This is the current camera pose, given to NBV: " + str(current_camera_pose))
207 
208  get_nbv_rsp = get_nbv(current_camera_pose)
209 
210  #If no nbv found at all, get next estimation from pose prediction
211  if get_nbv_rsp.found == False:
212  rospy.logwarn("Could not find any pose with rating > 0 in next best view estimation, aborting...")
213  if rospy.get_param("~recoverLostHypothesis"):
214  get_pc = rospy.ServiceProxy("/nbv/get_point_cloud", GetAttributedPointCloud)
215  global rejectedPointCloud
216  rejectedPointCloud += get_pc().point_cloud.elements
217  return 'no_nbv_found'
218 
219  rospy.loginfo("This is the camera pose, returned by NBV: " + str(get_nbv_rsp))
220 
221  # This is the camera pose calculated by the nbv but most certainly
222  # it won't be reached due to uncertainties
223  userdata.goal_camera_pose = get_nbv_rsp.viewport.pose
224 
225  # objects to search in this nbv pose
226  userdata.searched_object_types = get_nbv_rsp.viewport.object_type_name_list
227 
228  # construct goal for moving base and ptu from nbv result
229  move_pose = Pose()
230  quaternion = tf.transformations.quaternion_from_euler(0, 0, get_nbv_rsp.robot_state.rotation)
231  move_pose.position = Point(get_nbv_rsp.robot_state.x,
232  get_nbv_rsp.robot_state.y,
233  0)
234  move_pose.orientation = Quaternion(*quaternion)
235 
236  userdata.goal_robot_pose = move_pose
237 
238  # rad to degree
239  pan = math.trunc(get_nbv_rsp.robot_state.pan * (180 / numpy.pi))
240  tilt = math.trunc(get_nbv_rsp.robot_state.tilt * (180 / numpy.pi))
241  rospy.logdebug("PTU from nbv robot state in deg - pan: " + str(pan) + "| tilt: " + str(tilt))
242  userdata.goal_ptu_position = [pan, tilt]
243 
244  viewport = get_nbv_rsp.viewport
245 
246  if rospy.get_param("/scene_exploration_sm/filter_viewports"):
247  rospy.wait_for_service('/env/asr_world_model/filter_viewport_depending_on_already_visited_viewports')
248  filterViewportServ = rospy.ServiceProxy('/env/asr_world_model/filter_viewport_depending_on_already_visited_viewports',
249  FilterViewportDependingOnAlreadyVisitedViewports)
250  filterViewportResp = filterViewportServ(viewport)
251 
252  if filterViewportResp.isBeenFiltered:
253  rospy.logwarn("Viewport was already visited -> filtered objectTypes to search: " + str(filterViewportResp.filteredViewport.object_type_name_list))
254  if len(filterViewportResp.filteredViewport.object_type_name_list) == 0:
255  rospy.logwarn("No objects to search -> nbv_update_point_cloud")
256  global useGoalCameraPoseAtNextNBVUpdatePointCloud
257  useGoalCameraPoseAtNextNBVUpdatePointCloud = True
258  #the normals deleted in nbv_update_point_cloud get lost and are not in rejectedPointCloud
259  return 'nbv_update_point_cloud'
260  else:
261  viewport = filterViewportResp.filteredViewport
262  userdata.searched_object_types = filterViewportResp.filteredViewport.object_type_name_list
263 
264 
265  if (min_utility_for_moving > get_nbv_rsp.utility):
266  rospy.logwarn("Utility in found next best view estimate too low for moving to it, aborting...")
267  if rospy.get_param("~recoverLostHypothesis"):
268  get_pc = rospy.ServiceProxy("/nbv/get_point_cloud", GetAttributedPointCloud)
269  global rejectedPointCloud
270  rejectedPointCloud += get_pc().point_cloud.elements
271  return 'no_nbv_found'
272 
273  # trigger nbv visualization
274  trigger_nbv_vis = rospy.ServiceProxy('/nbv/trigger_frustums_and_point_cloud_visualization', TriggerFrustumsAndPointCloudVisualization)
275  trigger_nbv_vis(viewport)
276 
277  # write nbv results into log folder if log_dir is specified
278  if hasattr(__builtin__, 'log_dir'):
279  with open(__builtin__.log_dir + '/nbv_results.csv', 'a') as log:
280  logwriter = csv.writer(log, delimiter=' ',
281  quotechar='|', quoting=csv.QUOTE_MINIMAL)
282  logwriter.writerow([time(), str(get_nbv_rsp)])
283 
284  return 'found_next_best_view'
285  except rospy.ServiceException, e:
286  rospy.logwarn("ServiceException from NextBestView: " + str(e))
287  rospy.logwarn(e)
288  return 'aborted'
289 
290 
291 class NextBestViewUpdate(smach.State):
292  """
293  Updates the pointcloud for next_best_view with the actual pose.
294 
295  Note that the actual pose differs from the calculated nbv pose, so
296  that we have to get the actual camera pose to update here.
297  """
298 
299  def __init__(self):
300  smach.State.__init__(
301  self,
302  outcomes=['succeeded', 'aborted', 'no_nbv_found'],
303  input_keys=['goal_camera_pose', 'searched_object_types'],
304  output_keys=['deactivated_object_normals_count'])
306  self.isInitDone = 0
307 
308  def initNode(self):
309  rospy.loginfo('Init NEXT_BEST_VIEW_UPDATE')
310  self.isInitDone = 1
311  self.mode = rospy.get_param("/scene_exploration_sm/mode")
313 
315  # In cropbox record we allow more remaining_attempts, because we have only one PointCloud to set
316  # It is possible that there will be a cycle of views which NBV trys to reach but not successfully
317  if self.mode == 6:
319  else:
320  #Required for checking if we drive around hypotheses without deactivating normals.
321  #We only accept to do this twice. Otherwise we need new predicted poses.
322  self.remaining_attempts = 1
323  #Required for checking if search for objects at the same view.
325  rospy.loginfo("Reset remaining attempts to: " + str(self.remaining_attempts))
326 
327 
328  @log
329  @timed
330  @key_press
331  def execute(self, userdata):
332  rospy.loginfo('Executing NEXT_BEST_VIEW_UPDATE')
333  if self.isInitDone == 0:
334  self.initNode()
335 
336  rospy.wait_for_service('/nbv/update_point_cloud')
337  setRobotState()
338 
339  # In Cropbox search/record we have to update the normals for the placeholder object, because the point_cloud consists only of it
340  if self.mode == 5 or self.mode == 6:
341  self.searched_object_types = [rospy.get_param('/scene_exploration_sm/PlaceholderObjectTypeInCropboxSearch')]
342  else:
343  #check if searched_object_types is only one object and transform to list
344  if not isinstance(userdata.searched_object_types, list):
345  self.searched_object_types = [userdata.searched_object_types]
346  else:
347  self.searched_object_types = userdata.searched_object_types
348 
349  try:
350  nbv_update = rospy.ServiceProxy('/nbv/update_point_cloud', UpdatePointCloud)
351 
352  # We get stuck in a loop here, when searching for objects at hypotheses, that are erroneous.
353  # Problem 1: Our navigation does not exactly reach a nbv. It tries to reach it without success over and over.
354  # Problem 2: NBV calculation produces views in which no normals can be deleted and the robot repeatedly tries out views around existing hypotheses.
355  global useGoalCameraPoseAtNextNBVUpdatePointCloud
356  if useGoalCameraPoseAtNextNBVUpdatePointCloud:
357  useGoalCameraPoseAtNextNBVUpdatePointCloud = False
358  current_camera_pose = userdata.goal_camera_pose
359  else:
360  current_camera_pose = state_acquisition.get_camera_pose()
361 
362  rospy.loginfo("Deactivating normals in predicted cloud based on camera pose : " + str(current_camera_pose) + " and on objects " + str(list(self.searched_object_types)))
363  rsp = nbv_update(current_camera_pose, self.searched_object_types)
364  rospy.loginfo("Number of deactivated normals: " + str(rsp.deactivated_object_normals))
365  userdata.deactivated_object_normals_count = rsp.deactivated_object_normals
366 
367  #We need to update differently since deactivating no normals risks to end up in infinite loop.
368  if rsp.deactivated_object_normals == 0:
369  rospy.logwarn("View, reached by robot, differs from NBV and allows no update.")
370  #In order not to update from the same pose over and over again, we remember the old pose and check for approximate equality.
371  if state_acquisition.approx_equal_pose(userdata.goal_camera_pose, self.last_calculated_pose, 0.2):
372  rospy.loginfo("Robot did not move enough after NBV estimation. Updating cloud with NBV estimate: " + str(userdata.goal_camera_pose))
373  #We only delete those normals in the nbv to have the chance to search for objects at other places with the same pose predictions.
374  rsp = nbv_update(userdata.goal_camera_pose, self.searched_object_types)
375  rospy.loginfo("Number of deactivated normals: " + str(rsp.deactivated_object_normals))
377  else:
378  #If remaining_attempts == 1: We still have a chance to be successful in deleting normals in the next view.
379  self.remaining_attempts -= 1
380  self.last_calculated_pose = userdata.goal_camera_pose
381  rospy.loginfo("Remaining attempts till no_nbv_found: " + str(self.remaining_attempts))
382 
383  if self.remaining_attempts <= 0:
384  #Driving around hypotheses is a good sign for having no good data in the given pose predictions.
385  rospy.loginfo("Two NBV estimations deactivated no normals. Aborting object search loop.")
387  if rospy.get_param("~recoverLostHypothesis"):
388  get_pc = rospy.ServiceProxy("/nbv/get_point_cloud", GetAttributedPointCloud)
389  global rejectedPointCloud
390  rejectedPointCloud += get_pc().point_cloud.elements
391  return 'no_nbv_found';
392  else :
394  return 'succeeded'
395  except rospy.ServiceException, e:
396  rospy.logwarn(str(e))
397  return 'aborted'
398 
399 
401  # try to get current robot config and set it in the nbv
402  getRobotState = state_acquisition.GetRobotState()
403  robotState = getRobotState.get_robot_state()
404  rospy.loginfo("Transmitting current robot state to NBV: (Pan: " + str(robotState.pan) + ", Tilt: " + str(robotState.tilt) + ", Rotation: " + str(robotState.rotation) + ", X: " + str(robotState.x) + ", Y: " + str(robotState.y) + ")")
405 
406  rospy.wait_for_service('/nbv/set_init_robot_state')
407  set_init_state = rospy.ServiceProxy('/nbv/set_init_robot_state', SetInitRobotState)
408  set_init_state(robotState)
def execute(self, userdata)
Definition: nbv.py:73
def execute(self, userdata)
Definition: nbv.py:194


asr_state_machine
Author(s): Allgeyer Tobias, Aumann Florian, Borella Jocelyn, Hutmacher Robin, Karrenbauer Oliver, Marek Felix, Meißner Pascal, Trautmann Jeremias, Wittenbeck Valerij
autogenerated on Mon Feb 28 2022 21:53:50