import rospy import subprocess import time import os import numpy as np from sensor_msgs.msg import CameraInfo from sensor_msgs.msg import Image from gazebo_msgs.msg import ModelState from geometry_msgs.msg import Pose, Point, Quaternion import tf from cv_bridge import CvBridge import copy class ShapeParams : def __init__(self, shape_type=None, shape_scale=1.0, shape_pose=None, shape_color=None): self.shape_type = shape_type self.shape_scale = shape_scale self.shape_pose = shape_pose self.shape_color = shape_color self.shape_name = None def getPose(self) : return self.shape_pose def getType(self) : return self.shape_type def setName(self,name) : self.shape_name = name def getName(self): return self.shape_name class ShapeParamsRanges : def __init__(self, shape_type, shape_scale_max=1.0, shape_pose_min=None, shape_pose_max=None, shape_color_list=['White']) : self.shape_type = shape_type self.shape_scale_max= shape_scale_max self.shape_pose_min = shape_pose_min self.shape_pose_max = shape_pose_max self.shape_color_list = shape_color_list class ShapeFactory : ''' Factory of shapes... In : ShapeParamsranges Out : ShapeParams ''' def __init__(self, params) : assert(isinstance(params,ShapeParamsRanges)) self.params = params def generate(self) : ''' Generate a new shape with respect to the ShapeParamsRanges information provided. Return ShapeParams. ''' newshape_info = ShapeParams(shape_type=self.params.shape_type) #choose scale : scale = np.random.random_sample()*(self.params.shape_scale_max-0.25)+0.25 newshape_info.shape_scale = scale #choose pose : newshape_info.shape_pose = np.concatenate( [ self.randomPose(), self.randomOrientation() ], axis=0).reshape((1,6)) #choose color : nbrcolors = len( self.params.shape_color_list ) newshape_info.shape_color = self.params.shape_color_list[ np.random.randint( nbrcolors) ] return newshape_info def randomPose(self) : interval = self.params.shape_pose_max-self.params.shape_pose_min return interval[0,:3]*np.random.random_sample((1,3))+self.params.shape_pose_min[0,:3] def randomOrientation(self) : interval = self.params.shape_pose_max-self.params.shape_pose_min return interval[0,3:]*np.random.random_sample((1,3))+self.params.shape_pose_min[0,3:] def changePose(self,shape_param) : shape_param.shape_pose[0,:3] = self.randomPose() return shape_param def changeOrientation(self,shape_param) : shape_param.shape_pose[0,3:] = self.randomOrientation() return shape_param class Configuration : def __init__(self, shape_list=[],factory_list=[]) : self.shape_list = copy.deepcopy(shape_list) self.factory_list = copy.deepcopy(factory_list) def append(self, newshape_info,factory=None) : assert(isinstance(newshape_info,ShapeParams)) self.shape_list.append( newshape_info) self.factory_list.append(factory) def getShapeList(self) : return self.shape_list def changePose(self) : for idx in range(len(self.shape_list) ) : self.shape_list[idx] = self.factory_list[idx].changePose( self.shape_list[idx] ) def changeOrientation(self) : for idx in range(len(self.shape_list) ) : self.shape_list[idx] = self.factory_list[idx].changeOrientation( self.shape_list[idx]) class OccParamsRanges : def __init__(self, occ_min=0, occ_max=2 ) : self.occ_min = occ_min self.occ_max = occ_max class ConfigurationFactory : ''' Factory of Configuration... In : list of tuples : ( ShapeFactory , OccParamsRanges ) Out : Configuration ''' def __init__(self, info=[], port=11311 ) : self.spawned = False self.info = info self.port = port self.env = os.environ self.env["ROS_MASTER_URI"] = 'http://localhost:'+str(self.port) self.env["GAZEBO_MASTER_URI"]='http://localhost:'+str(self.port+40) self.currentConfiguration = None def init_roscore(self): self.launcher_roscore = subprocess.Popen(['roscore -p '+str(self.port)+' '],shell=True,env=self.env) time.sleep(1) def init_gazebo(self) : command = ('roslaunch -p '+str(self.port)+' GazeboDomainRandom empty_world.launch') self.launcher_gazebo = subprocess.Popen( command, shell=True, env=self.env) rospy.loginfo("GAZEBO Domain Random : ENVIRONMENT "+str(self.port)+" : launching...") def init_node(self) : rospy.init_node('GazeboDomainRandom_node', anonymous=False)#, xmlrpc_port=self.port)#,tcpros_port=self.port) rospy.on_shutdown(self.close) def init_subpub(self) : self.pub_modelstate = rospy.Publisher( '/gazebo/set_model_state', ModelState, queue_size=10) def close(self) : #TODO : # end services... self.launcher_gazebo.kill() self.launcher_roscore.kill() command = 'pkill roslaunch' subprocess.Popen( command.split()) rospy.loginfo("GAZEBO Domain Random : ENVIRONMENT "+str(self.port)+" : CLOSED.") def init(self) : ''' Generate the Gazebo environment (launch roscore, gzserver and so on...) ''' self.init_roscore() self.init_gazebo() self.init_node() self.init_subpub() def generate(self) : newconfig = Configuration(shape_list=[]) for (shapefactory,occ) in self.info : nbrocc = np.random.randint( low=occ.occ_min, high=occ.occ_max) for i in range(nbrocc) : newconfig.append( shapefactory.generate(), shapefactory ) return newconfig def _erase(self) : counters = dict() for el in self.currentConfiguration.shape_list : stype = el.getType() if stype in counters : counters[stype] += 1 else : counters[stype] = 0 #rospy.loginfo('ERASE : {}'.format(stype) ) command = "rosservice call gazebo/delete_model \"{model_name: "+stype+str(counters[stype])+"}\"" #rospy.loginfo('COMMAND : ERASE : {}'.format(command) ) subprocess.Popen(command, shell=True, env=self.env) def spawn(self, config) : assert(isinstance(config,Configuration)) if self.spawned : self._erase() self.currentConfiguration = copy.deepcopy(config) shape_it = dict() for idx, elem in enumerate( config.shape_list) : eltype = elem.shape_type elscale = elem.shape_scale elpose = elem.shape_pose.tolist()[0] elposeX = elpose[0] elposeY = elpose[1] elposeZ = elpose[2] elposeRoll = elpose[3] elposePitch = elpose[4] elposeYaw = elpose[5] elcolor = elem.shape_color if eltype in shape_it : shape_it[eltype] += 1 else : shape_it[eltype] = 0 elname = eltype+str(shape_it[eltype]) self.currentConfiguration.shape_list[idx].setName( str(elname) ) command = 'roslaunch -p '+str(self.port)+' GazeboDomainRandom {}.spawn.launch name:={} color:={} scale:={} X:={} Y:={} Z:={}'.format( eltype, elname, elcolor, elscale, elposeX, elposeY, elposeZ) #rospy.loginfo('CONFIGURATION SPAWN : COMMAND : {}'.format(command) ) subprocess.Popen( command, shell=True, env=self.env) #time.sleep(1.0) self.spawned = True time.sleep(3.0) def changeOrientation(self) : self.currentConfiguration.changeOrientation() def changePose(self) : self.currentConfiguration.changePose() def changeSpawned(self, config=None) : if config is None : config = self.currentConfiguration for elem in config.shape_list : modelstate = ModelState() elpose = elem.shape_pose.tolist()[0] pose = Point() quat = Quaternion() pose.x = elpose[0] pose.y = elpose[1] pose.z = elpose[2] modelstate.pose.position = pose quaternion = tf.transformations.quaternion_from_euler( elpose[3], elpose[4], elpose[5] ) modelstate.pose.orientation.x = quaternion[0] modelstate.pose.orientation.y = quaternion[1] modelstate.pose.orientation.z = quaternion[2] modelstate.pose.orientation.w = quaternion[3] elname = elem.getName() modelstate.model_name = elname self.pub_modelstate.publish(modelstate) #rospy.loginfo('CONFIGURATION SPAWNED : making changes... :\n {}'.format(modelstate) ) #time.sleep(0.1) def getCurrentConfiguration(self): return self.currentConfiguration class Camera : def __init__(self, fovy=75, altitude=1.0, port=11311, env=None) : self.fovy = fovy self.altitude = altitude self.spawned = False self.port = port self.env = env if self.env is None : self.env = os.environ self.K = np.zeros((3,3)) self.K[0][0] = self.fovy self.K[1][1] = self.fovy self.K[2][2] = 1.0 self.K = np.matrix(self.K) self.P = np.matrix( np.concatenate( [self.K, np.zeros((3,1)) ], axis=1) ) self.camerainfo = None self.listener_camera_info = rospy.Subscriber( '/CAMERA/camera_info', CameraInfo, self.callbackCAMERAINFO ) self.image = None self.bridge = CvBridge() self.listener_image = rospy.Subscriber( '/CAMERA/image_raw', Image, self.callbackIMAGE ) def ros2np(self,img) : return self.bridge.imgmsg_to_cv2(img, "bgr8") def callbackIMAGE(self, image) : self.image = self.ros2np(image) def callbackCAMERAINFO(self, camerainfo) : self.camerainfo = camerainfo if self.camerainfo is not None : self.K = np.matrix( np.reshape( self.camerainfo.K, (3,3)) ) self.P = np.matrix( np.reshape( self.camerainfo.P, (3,4)) ) def getImage(self) : if self.image is not None : return self.image.copy() else : return None def setFovy(self, fovy) : self.fovy = fovy self.spawn() def setAltitude(self, altitude) : self.altitude = altitude self.spawn() def spawn(self,port=None,env=None) : if port is not None : self.port = port if env is not None : self.env = env if self.spawned : self._erase() command = 'roslaunch -p '+str(self.port)+' GazeboDomainRandom camera.spawn.launch fovy:={} Z:={}'.format( self.fovy, self.altitude) subprocess.Popen( command, shell=True, env=self.env) #time.sleep(1.0) self.spawned = True rospy.loginfo('CAMERA SPAWNED : fovy={} // Z={}'.format(self.fovy, self.altitude) ) def _erase(self) : command = "rosservice call gazebo/delete_model \"{model_name: CAMERA}\"" subprocess.Popen(command, shell=True, env=self.env) #time.sleep(1.0) def project(self, x) : #homogenization : if x.shape[0] == 3 : x = np.concatenate( [x, np.ones((1,1)) ], axis=0 ) x = np.matrix(x) #P = self.K * np.matrix( np.concatenate( [ np.ones((3,3)), np.reshape( [ -self.altitude, 0.0, 0.0 ], newshape=(3,1) )], axis=1) ) Rt = np.matrix( [ [1, 0, 0, 0], [0, 1, 0, self.altitude+0.1], [0, 0, 1, 0] ] ) P = self.K * Rt ''' Rt = np.matrix( [ [1, 0, 0, 0], [0, 1, 0, self.altitude+0.1], [0, 0, 1, 0], [0, 0, 0, 1.0] ] ) P = self.P * Rt ''' projx = P * x #dehomogenization ; for i in range(3) : projx[i] /= projx[2]+1e-6 return projx def setPause(self,pause=True) : if pause==True : subprocess.Popen(['rosservice call /gazebo/pause_physics'], shell=True, env=self.env) rospy.loginfo("ENVIRONMENT "+str(self.port)+" PHYSICS : PAUSED.\n") else : subprocess.Popen(['rosservice call /gazebo/unpause_physics'], shell=True, env=self.env) rospy.loginfo("ENVIRONMENT "+str(self.port)+" PHYSICS : UNPAUSED.\n")