atomic_criteria.py 80 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045
  1. #!/usr/bin/env python
  2. # Copyright (c) 2018-2020 Intel Corporation
  3. #
  4. # This work is licensed under the terms of the MIT license.
  5. # For a copy, see <https://opensource.org/licenses/MIT>.
  6. """
  7. This module provides all atomic evaluation criteria required to analyze if a
  8. scenario was completed successfully or failed.
  9. Criteria should run continuously to monitor the state of a single actor, multiple
  10. actors or environmental parameters. Hence, a termination is not required.
  11. The atomic criteria are implemented with py_trees.
  12. """
  13. import weakref
  14. import math
  15. import numpy as np
  16. import py_trees
  17. import shapely.geometry
  18. import carla
  19. from srunner.scenariomanager.carla_data_provider import CarlaDataProvider
  20. from srunner.scenariomanager.timer import GameTime
  21. from srunner.scenariomanager.traffic_events import TrafficEvent, TrafficEventType
  22. class Criterion(py_trees.behaviour.Behaviour):
  23. """
  24. Base class for all criteria used to evaluate a scenario for success/failure
  25. Important parameters (PUBLIC):
  26. - name: Name of the criterion
  27. - expected_value_success: Result in case of success
  28. (e.g. max_speed, zero collisions, ...)
  29. - expected_value_acceptable: Result that does not mean a failure,
  30. but is not good enough for a success
  31. - actual_value: Actual result after running the scenario
  32. - test_status: Used to access the result of the criterion
  33. - optional: Indicates if a criterion is optional (not used for overall analysis)
  34. """
  35. def __init__(self,
  36. name,
  37. actor,
  38. expected_value_success,
  39. expected_value_acceptable=None,
  40. optional=False,
  41. terminate_on_failure=False):
  42. super(Criterion, self).__init__(name)
  43. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  44. self._terminate_on_failure = terminate_on_failure
  45. self.name = name
  46. self.actor = actor
  47. self.test_status = "INIT"
  48. self.expected_value_success = expected_value_success
  49. self.expected_value_acceptable = expected_value_acceptable
  50. self.actual_value = 0
  51. self.optional = optional
  52. self.list_traffic_events = []
  53. def initialise(self):
  54. """
  55. Initialise the criterion. Can be extended by the user-derived class
  56. """
  57. self.logger.debug("%s.initialise()" % (self.__class__.__name__))
  58. def terminate(self, new_status):
  59. """
  60. Terminate the criterion. Can be extended by the user-derived class
  61. """
  62. if self.test_status in ('RUNNING', 'INIT'):
  63. self.test_status = "SUCCESS"
  64. self.logger.debug("%s.terminate()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  65. class MaxVelocityTest(Criterion):
  66. """
  67. This class contains an atomic test for maximum velocity.
  68. Important parameters:
  69. - actor: CARLA actor to be used for this test
  70. - max_velocity_allowed: maximum allowed velocity in m/s
  71. - optional [optional]: If True, the result is not considered for an overall pass/fail result
  72. """
  73. def __init__(self, actor, max_velocity_allowed, optional=False, name="CheckMaximumVelocity"):
  74. """
  75. Setup actor and maximum allowed velovity
  76. """
  77. super(MaxVelocityTest, self).__init__(name, actor, max_velocity_allowed, None, optional)
  78. def update(self):
  79. """
  80. Check velocity
  81. """
  82. new_status = py_trees.common.Status.RUNNING
  83. if self.actor is None:
  84. return new_status
  85. velocity = CarlaDataProvider.get_velocity(self.actor)
  86. self.actual_value = max(velocity, self.actual_value)
  87. if velocity > self.expected_value_success:
  88. self.test_status = "FAILURE"
  89. else:
  90. self.test_status = "SUCCESS"
  91. if self._terminate_on_failure and (self.test_status == "FAILURE"):
  92. new_status = py_trees.common.Status.FAILURE
  93. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  94. return new_status
  95. class DrivenDistanceTest(Criterion):
  96. """
  97. This class contains an atomic test to check the driven distance
  98. Important parameters:
  99. - actor: CARLA actor to be used for this test
  100. - distance_success: If the actor's driven distance is more than this value (in meters),
  101. the test result is SUCCESS
  102. - distance_acceptable: If the actor's driven distance is more than this value (in meters),
  103. the test result is ACCEPTABLE
  104. - optional [optional]: If True, the result is not considered for an overall pass/fail result
  105. """
  106. def __init__(self,
  107. actor,
  108. distance_success,
  109. distance_acceptable=None,
  110. optional=False,
  111. name="CheckDrivenDistance"):
  112. """
  113. Setup actor
  114. """
  115. super(DrivenDistanceTest, self).__init__(name, actor, distance_success, distance_acceptable, optional)
  116. self._last_location = None
  117. def initialise(self):
  118. self._last_location = CarlaDataProvider.get_location(self.actor)
  119. super(DrivenDistanceTest, self).initialise()
  120. def update(self):
  121. """
  122. Check distance
  123. """
  124. new_status = py_trees.common.Status.RUNNING
  125. if self.actor is None:
  126. return new_status
  127. location = CarlaDataProvider.get_location(self.actor)
  128. if location is None:
  129. return new_status
  130. if self._last_location is None:
  131. self._last_location = location
  132. return new_status
  133. self.actual_value += location.distance(self._last_location)
  134. self._last_location = location
  135. if self.actual_value > self.expected_value_success:
  136. self.test_status = "SUCCESS"
  137. elif (self.expected_value_acceptable is not None and
  138. self.actual_value > self.expected_value_acceptable):
  139. self.test_status = "ACCEPTABLE"
  140. else:
  141. self.test_status = "RUNNING"
  142. if self._terminate_on_failure and (self.test_status == "FAILURE"):
  143. new_status = py_trees.common.Status.FAILURE
  144. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  145. return new_status
  146. def terminate(self, new_status):
  147. """
  148. Set final status
  149. """
  150. if self.test_status != "SUCCESS":
  151. self.test_status = "FAILURE"
  152. self.actual_value = round(self.actual_value, 2)
  153. super(DrivenDistanceTest, self).terminate(new_status)
  154. class AverageVelocityTest(Criterion):
  155. """
  156. This class contains an atomic test for average velocity.
  157. Important parameters:
  158. - actor: CARLA actor to be used for this test
  159. - avg_velocity_success: If the actor's average velocity is more than this value (in m/s),
  160. the test result is SUCCESS
  161. - avg_velocity_acceptable: If the actor's average velocity is more than this value (in m/s),
  162. the test result is ACCEPTABLE
  163. - optional [optional]: If True, the result is not considered for an overall pass/fail result
  164. """
  165. def __init__(self,
  166. actor,
  167. avg_velocity_success,
  168. avg_velocity_acceptable=None,
  169. optional=False,
  170. name="CheckAverageVelocity"):
  171. """
  172. Setup actor and average velovity expected
  173. """
  174. super(AverageVelocityTest, self).__init__(name, actor,
  175. avg_velocity_success,
  176. avg_velocity_acceptable,
  177. optional)
  178. self._last_location = None
  179. self._distance = 0.0
  180. def initialise(self):
  181. self._last_location = CarlaDataProvider.get_location(self.actor)
  182. super(AverageVelocityTest, self).initialise()
  183. def update(self):
  184. """
  185. Check velocity
  186. """
  187. new_status = py_trees.common.Status.RUNNING
  188. if self.actor is None:
  189. return new_status
  190. location = CarlaDataProvider.get_location(self.actor)
  191. if location is None:
  192. return new_status
  193. if self._last_location is None:
  194. self._last_location = location
  195. return new_status
  196. self._distance += location.distance(self._last_location)
  197. self._last_location = location
  198. elapsed_time = GameTime.get_time()
  199. if elapsed_time > 0.0:
  200. self.actual_value = self._distance / elapsed_time
  201. if self.actual_value > self.expected_value_success:
  202. self.test_status = "SUCCESS"
  203. elif (self.expected_value_acceptable is not None and
  204. self.actual_value > self.expected_value_acceptable):
  205. self.test_status = "ACCEPTABLE"
  206. else:
  207. self.test_status = "RUNNING"
  208. if self._terminate_on_failure and (self.test_status == "FAILURE"):
  209. new_status = py_trees.common.Status.FAILURE
  210. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  211. return new_status
  212. def terminate(self, new_status):
  213. """
  214. Set final status
  215. """
  216. if self.test_status == "RUNNING":
  217. self.test_status = "FAILURE"
  218. super(AverageVelocityTest, self).terminate(new_status)
  219. class CollisionTest(Criterion):
  220. """
  221. This class contains an atomic test for collisions.
  222. Args:
  223. - actor (carla.Actor): CARLA actor to be used for this test
  224. - other_actor (carla.Actor): only collisions with this actor will be registered
  225. - other_actor_type (str): only collisions with actors including this type_id will count.
  226. Additionally, the "miscellaneous" tag can also be used to include all static objects in the scene
  227. - terminate_on_failure [optional]: If True, the complete scenario will terminate upon failure of this test
  228. - optional [optional]: If True, the result is not considered for an overall pass/fail result
  229. """
  230. MIN_AREA_OF_COLLISION = 3 # If closer than this distance, the collision is ignored
  231. MAX_AREA_OF_COLLISION = 5 # If further than this distance, the area is forgotten
  232. MAX_ID_TIME = 5 # Amount of time the last collision if is remembered
  233. def __init__(self, actor, other_actor=None, other_actor_type=None,
  234. optional=False, name="CollisionTest", terminate_on_failure=False):
  235. """
  236. Construction with sensor setup
  237. """
  238. super(CollisionTest, self).__init__(name, actor, 0, None, optional, terminate_on_failure)
  239. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  240. world = self.actor.get_world()
  241. blueprint = world.get_blueprint_library().find('sensor.other.collision')
  242. self._collision_sensor = world.spawn_actor(blueprint, carla.Transform(), attach_to=self.actor)
  243. self._collision_sensor.listen(lambda event: self._count_collisions(weakref.ref(self), event))
  244. self.other_actor = other_actor
  245. self.other_actor_type = other_actor_type
  246. self.registered_collisions = []
  247. self.last_id = None
  248. self.collision_time = None
  249. def update(self):
  250. """
  251. Check collision count
  252. """
  253. new_status = py_trees.common.Status.RUNNING
  254. if self._terminate_on_failure and (self.test_status == "FAILURE"):
  255. new_status = py_trees.common.Status.FAILURE
  256. actor_location = CarlaDataProvider.get_location(self.actor)
  257. new_registered_collisions = []
  258. # Loops through all the previous registered collisions
  259. for collision_location in self.registered_collisions:
  260. # Get the distance to the collision point
  261. distance_vector = actor_location - collision_location
  262. distance = math.sqrt(math.pow(distance_vector.x, 2) + math.pow(distance_vector.y, 2))
  263. # If far away from a previous collision, forget it
  264. if distance <= self.MAX_AREA_OF_COLLISION:
  265. new_registered_collisions.append(collision_location)
  266. self.registered_collisions = new_registered_collisions
  267. if self.last_id and GameTime.get_time() - self.collision_time > self.MAX_ID_TIME:
  268. self.last_id = None
  269. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  270. return new_status
  271. def terminate(self, new_status):
  272. """
  273. Cleanup sensor
  274. """
  275. if self._collision_sensor is not None:
  276. self._collision_sensor.destroy()
  277. self._collision_sensor = None
  278. super(CollisionTest, self).terminate(new_status)
  279. @staticmethod
  280. def _count_collisions(weak_self, event): # pylint: disable=too-many-return-statements
  281. """
  282. Callback to update collision count
  283. """
  284. self = weak_self()
  285. if not self:
  286. return
  287. actor_location = CarlaDataProvider.get_location(self.actor)
  288. # Ignore the current one if it is the same id as before
  289. if self.last_id == event.other_actor.id:
  290. return
  291. # Filter to only a specific actor
  292. if self.other_actor and self.other_actor.id != event.other_actor.id:
  293. return
  294. # Filter to only a specific type
  295. if self.other_actor_type:
  296. if self.other_actor_type == "miscellaneous":
  297. if "traffic" not in event.other_actor.type_id \
  298. and "static" not in event.other_actor.type_id:
  299. return
  300. else:
  301. if self.other_actor_type not in event.other_actor.type_id:
  302. return
  303. # Ignore it if its too close to a previous collision (avoid micro collisions)
  304. for collision_location in self.registered_collisions:
  305. distance_vector = actor_location - collision_location
  306. distance = math.sqrt(math.pow(distance_vector.x, 2) + math.pow(distance_vector.y, 2))
  307. if distance <= self.MIN_AREA_OF_COLLISION:
  308. return
  309. if ('static' in event.other_actor.type_id or 'traffic' in event.other_actor.type_id) \
  310. and 'sidewalk' not in event.other_actor.type_id:
  311. actor_type = TrafficEventType.COLLISION_STATIC
  312. elif 'vehicle' in event.other_actor.type_id:
  313. actor_type = TrafficEventType.COLLISION_VEHICLE
  314. elif 'walker' in event.other_actor.type_id:
  315. actor_type = TrafficEventType.COLLISION_PEDESTRIAN
  316. else:
  317. return
  318. collision_event = TrafficEvent(event_type=actor_type)
  319. collision_event.set_dict({
  320. 'type': event.other_actor.type_id,
  321. 'id': event.other_actor.id,
  322. 'x': actor_location.x,
  323. 'y': actor_location.y,
  324. 'z': actor_location.z})
  325. collision_event.set_message(
  326. "Agent collided against object with type={} and id={} at (x={}, y={}, z={})".format(
  327. event.other_actor.type_id,
  328. event.other_actor.id,
  329. round(actor_location.x, 3),
  330. round(actor_location.y, 3),
  331. round(actor_location.z, 3)))
  332. self.test_status = "FAILURE"
  333. self.actual_value += 1
  334. self.collision_time = GameTime.get_time()
  335. self.registered_collisions.append(actor_location)
  336. self.list_traffic_events.append(collision_event)
  337. # Number 0: static objects -> ignore it
  338. if event.other_actor.id != 0:
  339. self.last_id = event.other_actor.id
  340. class ActorSpeedAboveThresholdTest(Criterion):
  341. """
  342. This test will fail if the actor has had its linear velocity lower than a specific value for
  343. a specific amount of time
  344. Important parameters:
  345. - actor: CARLA actor to be used for this test
  346. - speed_threshold: speed required
  347. - below_threshold_max_time: Maximum time (in seconds) the actor can remain under the speed threshold
  348. - terminate_on_failure [optional]: If True, the complete scenario will terminate upon failure of this test
  349. """
  350. def __init__(self, actor, speed_threshold, below_threshold_max_time,
  351. name="ActorSpeedAboveThresholdTest", terminate_on_failure=False):
  352. """
  353. Class constructor.
  354. """
  355. super(ActorSpeedAboveThresholdTest, self).__init__(name, actor, 0, terminate_on_failure=terminate_on_failure)
  356. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  357. self._actor = actor
  358. self._speed_threshold = speed_threshold
  359. self._below_threshold_max_time = below_threshold_max_time
  360. self._time_last_valid_state = None
  361. def update(self):
  362. """
  363. Check if the actor speed is above the speed_threshold
  364. """
  365. new_status = py_trees.common.Status.RUNNING
  366. linear_speed = CarlaDataProvider.get_velocity(self._actor)
  367. if linear_speed is not None:
  368. if linear_speed < self._speed_threshold and self._time_last_valid_state:
  369. if (GameTime.get_time() - self._time_last_valid_state) > self._below_threshold_max_time:
  370. # Game over. The actor has been "blocked" for too long
  371. self.test_status = "FAILURE"
  372. # record event
  373. vehicle_location = CarlaDataProvider.get_location(self._actor)
  374. blocked_event = TrafficEvent(event_type=TrafficEventType.VEHICLE_BLOCKED)
  375. ActorSpeedAboveThresholdTest._set_event_message(blocked_event, vehicle_location)
  376. ActorSpeedAboveThresholdTest._set_event_dict(blocked_event, vehicle_location)
  377. self.list_traffic_events.append(blocked_event)
  378. else:
  379. self._time_last_valid_state = GameTime.get_time()
  380. if self._terminate_on_failure and (self.test_status == "FAILURE"):
  381. new_status = py_trees.common.Status.FAILURE
  382. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  383. return new_status
  384. @staticmethod
  385. def _set_event_message(event, location):
  386. """
  387. Sets the message of the event
  388. """
  389. event.set_message('Agent got blocked at (x={}, y={}, z={})'.format(round(location.x, 3),
  390. round(location.y, 3),
  391. round(location.z, 3)))
  392. @staticmethod
  393. def _set_event_dict(event, location):
  394. """
  395. Sets the dictionary of the event
  396. """
  397. event.set_dict({
  398. 'x': location.x,
  399. 'y': location.y,
  400. 'z': location.z,
  401. })
  402. class KeepLaneTest(Criterion):
  403. """
  404. This class contains an atomic test for keeping lane.
  405. Important parameters:
  406. - actor: CARLA actor to be used for this test
  407. - optional [optional]: If True, the result is not considered for an overall pass/fail result
  408. """
  409. def __init__(self, actor, optional=False, name="CheckKeepLane"):
  410. """
  411. Construction with sensor setup
  412. """
  413. super(KeepLaneTest, self).__init__(name, actor, 0, None, optional)
  414. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  415. world = self.actor.get_world()
  416. blueprint = world.get_blueprint_library().find('sensor.other.lane_invasion')
  417. self._lane_sensor = world.spawn_actor(blueprint, carla.Transform(), attach_to=self.actor)
  418. self._lane_sensor.listen(lambda event: self._count_lane_invasion(weakref.ref(self), event))
  419. def update(self):
  420. """
  421. Check lane invasion count
  422. """
  423. new_status = py_trees.common.Status.RUNNING
  424. if self.actual_value > 0:
  425. self.test_status = "FAILURE"
  426. else:
  427. self.test_status = "SUCCESS"
  428. if self._terminate_on_failure and (self.test_status == "FAILURE"):
  429. new_status = py_trees.common.Status.FAILURE
  430. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  431. return new_status
  432. def terminate(self, new_status):
  433. """
  434. Cleanup sensor
  435. """
  436. if self._lane_sensor is not None:
  437. self._lane_sensor.destroy()
  438. self._lane_sensor = None
  439. super(KeepLaneTest, self).terminate(new_status)
  440. @staticmethod
  441. def _count_lane_invasion(weak_self, event):
  442. """
  443. Callback to update lane invasion count
  444. """
  445. self = weak_self()
  446. if not self:
  447. return
  448. self.actual_value += 1
  449. class ReachedRegionTest(Criterion):
  450. """
  451. This class contains the reached region test
  452. The test is a success if the actor reaches a specified region
  453. Important parameters:
  454. - actor: CARLA actor to be used for this test
  455. - min_x, max_x, min_y, max_y: Bounding box of the checked region
  456. """
  457. def __init__(self, actor, min_x, max_x, min_y, max_y, name="ReachedRegionTest"):
  458. """
  459. Setup trigger region (rectangle provided by
  460. [min_x,min_y] and [max_x,max_y]
  461. """
  462. super(ReachedRegionTest, self).__init__(name, actor, 0)
  463. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  464. self._actor = actor
  465. self._min_x = min_x
  466. self._max_x = max_x
  467. self._min_y = min_y
  468. self._max_y = max_y
  469. def update(self):
  470. """
  471. Check if the actor location is within trigger region
  472. """
  473. new_status = py_trees.common.Status.RUNNING
  474. location = CarlaDataProvider.get_location(self._actor)
  475. if location is None:
  476. return new_status
  477. in_region = False
  478. if self.test_status != "SUCCESS":
  479. in_region = (location.x > self._min_x and location.x < self._max_x) and (
  480. location.y > self._min_y and location.y < self._max_y)
  481. if in_region:
  482. self.test_status = "SUCCESS"
  483. else:
  484. self.test_status = "RUNNING"
  485. if self.test_status == "SUCCESS":
  486. new_status = py_trees.common.Status.SUCCESS
  487. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  488. return new_status
  489. class OffRoadTest(Criterion):
  490. """
  491. Atomic containing a test to detect when an actor deviates from the driving lanes. This atomic can
  492. fail when actor has spent a specific time outside driving lanes (defined by OpenDRIVE). Simplified
  493. version of OnSidewalkTest, and doesn't relly on waypoints with *Sidewalk* lane types
  494. Args:
  495. actor (carla.Actor): CARLA actor to be used for this test
  496. duration (float): Time spent at sidewalks before the atomic fails.
  497. If terminate_on_failure isn't active, this is ignored.
  498. optional (bool): If True, the result is not considered for an overall pass/fail result
  499. when using the output argument
  500. terminate_on_failure (bool): If True, the atomic will fail when the duration condition has been met.
  501. """
  502. def __init__(self, actor, duration=0, optional=False, terminate_on_failure=False, name="OffRoadTest"):
  503. """
  504. Setup of the variables
  505. """
  506. super(OffRoadTest, self).__init__(name, actor, 0, None, optional, terminate_on_failure)
  507. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  508. self._map = CarlaDataProvider.get_map()
  509. self._offroad = False
  510. self._duration = duration
  511. self._prev_time = None
  512. self._time_offroad = 0
  513. def update(self):
  514. """
  515. First, transforms the actor's current position to its corresponding waypoint. This is
  516. filtered to only use waypoints of type Driving or Parking. Depending on these results,
  517. the actor will be considered to be outside (or inside) driving lanes.
  518. returns:
  519. py_trees.common.Status.FAILURE: when the actor has spent a given duration outside driving lanes
  520. py_trees.common.Status.RUNNING: the rest of the time
  521. """
  522. new_status = py_trees.common.Status.RUNNING
  523. current_location = CarlaDataProvider.get_location(self.actor)
  524. # Get the waypoint at the current location to see if the actor is offroad
  525. drive_waypoint = self._map.get_waypoint(
  526. current_location,
  527. project_to_road=False
  528. )
  529. park_waypoint = self._map.get_waypoint(
  530. current_location,
  531. project_to_road=False,
  532. lane_type=carla.LaneType.Parking
  533. )
  534. if drive_waypoint or park_waypoint:
  535. self._offroad = False
  536. else:
  537. self._offroad = True
  538. # Counts the time offroad
  539. if self._offroad:
  540. if self._prev_time is None:
  541. self._prev_time = GameTime.get_time()
  542. else:
  543. curr_time = GameTime.get_time()
  544. self._time_offroad += curr_time - self._prev_time
  545. self._prev_time = curr_time
  546. else:
  547. self._prev_time = None
  548. if self._time_offroad > self._duration:
  549. self.test_status = "FAILURE"
  550. if self._terminate_on_failure and self.test_status == "FAILURE":
  551. new_status = py_trees.common.Status.FAILURE
  552. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  553. return new_status
  554. class EndofRoadTest(Criterion):
  555. """
  556. Atomic containing a test to detect when an actor has changed to a different road
  557. Args:
  558. actor (carla.Actor): CARLA actor to be used for this test
  559. duration (float): Time spent after ending the road before the atomic fails.
  560. If terminate_on_failure isn't active, this is ignored.
  561. optional (bool): If True, the result is not considered for an overall pass/fail result
  562. when using the output argument
  563. terminate_on_failure (bool): If True, the atomic will fail when the duration condition has been met.
  564. """
  565. def __init__(self, actor, duration=0, optional=False, terminate_on_failure=False, name="EndofRoadTest"):
  566. """
  567. Setup of the variables
  568. """
  569. super(EndofRoadTest, self).__init__(name, actor, 0, None, optional, terminate_on_failure)
  570. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  571. self._map = CarlaDataProvider.get_map()
  572. self._end_of_road = False
  573. self._duration = duration
  574. self._start_time = None
  575. self._time_end_road = 0
  576. self._road_id = None
  577. def update(self):
  578. """
  579. First, transforms the actor's current position to its corresponding waypoint. Then the road id
  580. is compared with the initial one and if that's the case, a time is started
  581. returns:
  582. py_trees.common.Status.FAILURE: when the actor has spent a given duration outside driving lanes
  583. py_trees.common.Status.RUNNING: the rest of the time
  584. """
  585. new_status = py_trees.common.Status.RUNNING
  586. current_location = CarlaDataProvider.get_location(self.actor)
  587. current_waypoint = self._map.get_waypoint(current_location)
  588. # Get the current road id
  589. if self._road_id is None:
  590. self._road_id = current_waypoint.road_id
  591. else:
  592. # Wait until the actor has left the road
  593. if self._road_id != current_waypoint.road_id or self._start_time:
  594. # Start counting
  595. if self._start_time is None:
  596. self._start_time = GameTime.get_time()
  597. return new_status
  598. curr_time = GameTime.get_time()
  599. self._time_end_road = curr_time - self._start_time
  600. if self._time_end_road > self._duration:
  601. self.test_status = "FAILURE"
  602. self.actual_value += 1
  603. return py_trees.common.Status.SUCCESS
  604. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  605. return new_status
  606. class OnSidewalkTest(Criterion):
  607. """
  608. Atomic containing a test to detect sidewalk invasions of a specific actor. This atomic can
  609. fail when actor has spent a specific time outside driving lanes (defined by OpenDRIVE).
  610. Args:
  611. actor (carla.Actor): CARLA actor to be used for this test
  612. duration (float): Time spent at sidewalks before the atomic fails.
  613. If terminate_on_failure isn't active, this is ignored.
  614. optional (bool): If True, the result is not considered for an overall pass/fail result
  615. when using the output argument
  616. terminate_on_failure (bool): If True, the atomic will fail when the duration condition has been met.
  617. """
  618. def __init__(self, actor, duration=0, optional=False, terminate_on_failure=False, name="OnSidewalkTest"):
  619. """
  620. Construction with sensor setup
  621. """
  622. super(OnSidewalkTest, self).__init__(name, actor, 0, None, optional, terminate_on_failure)
  623. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  624. self._actor = actor
  625. self._map = CarlaDataProvider.get_map()
  626. self._onsidewalk_active = False
  627. self._outside_lane_active = False
  628. self._actor_location = self._actor.get_location()
  629. self._wrong_sidewalk_distance = 0
  630. self._wrong_outside_lane_distance = 0
  631. self._sidewalk_start_location = None
  632. self._outside_lane_start_location = None
  633. self._duration = duration
  634. self._prev_time = None
  635. self._time_outside_lanes = 0
  636. def update(self):
  637. """
  638. First, transforms the actor's current position as well as its four corners to their
  639. corresponding waypoints. Depending on their lane type, the actor will be considered to be
  640. outside (or inside) driving lanes.
  641. returns:
  642. py_trees.common.Status.FAILURE: when the actor has spent a given duration outside
  643. driving lanes and terminate_on_failure is active
  644. py_trees.common.Status.RUNNING: the rest of the time
  645. """
  646. new_status = py_trees.common.Status.RUNNING
  647. if self._terminate_on_failure and self.test_status == "FAILURE":
  648. new_status = py_trees.common.Status.FAILURE
  649. # Some of the vehicle parameters
  650. current_tra = CarlaDataProvider.get_transform(self._actor)
  651. current_loc = current_tra.location
  652. current_wp = self._map.get_waypoint(current_loc, lane_type=carla.LaneType.Any)
  653. # Case 1) Car center is at a sidewalk
  654. if current_wp.lane_type == carla.LaneType.Sidewalk:
  655. if not self._onsidewalk_active:
  656. self._onsidewalk_active = True
  657. self._sidewalk_start_location = current_loc
  658. # Case 2) Not inside allowed zones (Driving and Parking)
  659. elif current_wp.lane_type not in (carla.LaneType.Driving, carla.LaneType.Parking):
  660. # Get the vertices of the vehicle
  661. heading_vec = current_tra.get_forward_vector()
  662. heading_vec.z = 0
  663. heading_vec = heading_vec / math.sqrt(math.pow(heading_vec.x, 2) + math.pow(heading_vec.y, 2))
  664. perpendicular_vec = carla.Vector3D(-heading_vec.y, heading_vec.x, 0)
  665. extent = self.actor.bounding_box.extent
  666. x_boundary_vector = heading_vec * extent.x
  667. y_boundary_vector = perpendicular_vec * extent.y
  668. bbox = [
  669. current_loc + carla.Location(x_boundary_vector - y_boundary_vector),
  670. current_loc + carla.Location(x_boundary_vector + y_boundary_vector),
  671. current_loc + carla.Location(-1 * x_boundary_vector - y_boundary_vector),
  672. current_loc + carla.Location(-1 * x_boundary_vector + y_boundary_vector)]
  673. bbox_wp = [
  674. self._map.get_waypoint(bbox[0], lane_type=carla.LaneType.Any),
  675. self._map.get_waypoint(bbox[1], lane_type=carla.LaneType.Any),
  676. self._map.get_waypoint(bbox[2], lane_type=carla.LaneType.Any),
  677. self._map.get_waypoint(bbox[3], lane_type=carla.LaneType.Any)]
  678. lane_type_list = [bbox_wp[0].lane_type, bbox_wp[1].lane_type, bbox_wp[2].lane_type, bbox_wp[3].lane_type]
  679. # Case 2.1) Not quite outside yet
  680. if bbox_wp[0].lane_type == (carla.LaneType.Driving or carla.LaneType.Parking) \
  681. or bbox_wp[1].lane_type == (carla.LaneType.Driving or carla.LaneType.Parking) \
  682. or bbox_wp[2].lane_type == (carla.LaneType.Driving or carla.LaneType.Parking) \
  683. or bbox_wp[3].lane_type == (carla.LaneType.Driving or carla.LaneType.Parking):
  684. self._onsidewalk_active = False
  685. self._outside_lane_active = False
  686. # Case 2.2) At the mini Shoulders between Driving and Sidewalk
  687. elif carla.LaneType.Sidewalk in lane_type_list:
  688. if not self._onsidewalk_active:
  689. self._onsidewalk_active = True
  690. self._sidewalk_start_location = current_loc
  691. else:
  692. distance_vehicle_wp = current_loc.distance(current_wp.transform.location)
  693. # Case 2.3) Outside lane
  694. if distance_vehicle_wp >= current_wp.lane_width / 2:
  695. if not self._outside_lane_active:
  696. self._outside_lane_active = True
  697. self._outside_lane_start_location = current_loc
  698. # Case 2.4) Very very edge case (but still inside driving lanes)
  699. else:
  700. self._onsidewalk_active = False
  701. self._outside_lane_active = False
  702. # Case 3) Driving and Parking conditions
  703. else:
  704. # Check for false positives at junctions
  705. if current_wp.is_junction:
  706. distance_vehicle_wp = math.sqrt(
  707. math.pow(current_wp.transform.location.x - current_loc.x, 2) +
  708. math.pow(current_wp.transform.location.y - current_loc.y, 2))
  709. if distance_vehicle_wp <= current_wp.lane_width / 2:
  710. self._onsidewalk_active = False
  711. self._outside_lane_active = False
  712. # Else, do nothing, the waypoint is too far to consider it a correct position
  713. else:
  714. self._onsidewalk_active = False
  715. self._outside_lane_active = False
  716. # Counts the time offroad
  717. if self._onsidewalk_active or self._outside_lane_active:
  718. if self._prev_time is None:
  719. self._prev_time = GameTime.get_time()
  720. else:
  721. curr_time = GameTime.get_time()
  722. self._time_outside_lanes += curr_time - self._prev_time
  723. self._prev_time = curr_time
  724. else:
  725. self._prev_time = None
  726. if self._time_outside_lanes > self._duration:
  727. self.test_status = "FAILURE"
  728. # Update the distances
  729. distance_vector = CarlaDataProvider.get_location(self._actor) - self._actor_location
  730. distance = math.sqrt(math.pow(distance_vector.x, 2) + math.pow(distance_vector.y, 2))
  731. if distance >= 0.02: # Used to avoid micro-changes adding to considerable sums
  732. self._actor_location = CarlaDataProvider.get_location(self._actor)
  733. if self._onsidewalk_active:
  734. self._wrong_sidewalk_distance += distance
  735. elif self._outside_lane_active:
  736. # Only add if car is outside the lane but ISN'T in a junction
  737. self._wrong_outside_lane_distance += distance
  738. # Register the sidewalk event
  739. if not self._onsidewalk_active and self._wrong_sidewalk_distance > 0:
  740. self.actual_value += 1
  741. onsidewalk_event = TrafficEvent(event_type=TrafficEventType.ON_SIDEWALK_INFRACTION)
  742. self._set_event_message(
  743. onsidewalk_event, self._sidewalk_start_location, self._wrong_sidewalk_distance)
  744. self._set_event_dict(
  745. onsidewalk_event, self._sidewalk_start_location, self._wrong_sidewalk_distance)
  746. self._onsidewalk_active = False
  747. self._wrong_sidewalk_distance = 0
  748. self.list_traffic_events.append(onsidewalk_event)
  749. # Register the outside of a lane event
  750. if not self._outside_lane_active and self._wrong_outside_lane_distance > 0:
  751. self.actual_value += 1
  752. outsidelane_event = TrafficEvent(event_type=TrafficEventType.OUTSIDE_LANE_INFRACTION)
  753. self._set_event_message(
  754. outsidelane_event, self._outside_lane_start_location, self._wrong_outside_lane_distance)
  755. self._set_event_dict(
  756. outsidelane_event, self._outside_lane_start_location, self._wrong_outside_lane_distance)
  757. self._outside_lane_active = False
  758. self._wrong_outside_lane_distance = 0
  759. self.list_traffic_events.append(outsidelane_event)
  760. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  761. return new_status
  762. def terminate(self, new_status):
  763. """
  764. If there is currently an event running, it is registered
  765. """
  766. # If currently at a sidewalk, register the event
  767. if self._onsidewalk_active:
  768. self.actual_value += 1
  769. onsidewalk_event = TrafficEvent(event_type=TrafficEventType.ON_SIDEWALK_INFRACTION)
  770. self._set_event_message(
  771. onsidewalk_event, self._sidewalk_start_location, self._wrong_sidewalk_distance)
  772. self._set_event_dict(
  773. onsidewalk_event, self._sidewalk_start_location, self._wrong_sidewalk_distance)
  774. self._onsidewalk_active = False
  775. self._wrong_sidewalk_distance = 0
  776. self.list_traffic_events.append(onsidewalk_event)
  777. # If currently outside of our lane, register the event
  778. if self._outside_lane_active:
  779. self.actual_value += 1
  780. outsidelane_event = TrafficEvent(event_type=TrafficEventType.OUTSIDE_LANE_INFRACTION)
  781. self._set_event_message(
  782. outsidelane_event, self._outside_lane_start_location, self._wrong_outside_lane_distance)
  783. self._set_event_dict(
  784. outsidelane_event, self._outside_lane_start_location, self._wrong_outside_lane_distance)
  785. self._outside_lane_active = False
  786. self._wrong_outside_lane_distance = 0
  787. self.list_traffic_events.append(outsidelane_event)
  788. super(OnSidewalkTest, self).terminate(new_status)
  789. def _set_event_message(self, event, location, distance):
  790. """
  791. Sets the message of the event
  792. """
  793. if event.get_type() == TrafficEventType.ON_SIDEWALK_INFRACTION:
  794. message_start = 'Agent invaded the sidewalk'
  795. else:
  796. message_start = 'Agent went outside the lane'
  797. event.set_message(
  798. '{} for about {} meters, starting at (x={}, y={}, z={})'.format(
  799. message_start,
  800. round(distance, 3),
  801. round(location.x, 3),
  802. round(location.y, 3),
  803. round(location.z, 3)))
  804. def _set_event_dict(self, event, location, distance):
  805. """
  806. Sets the dictionary of the event
  807. """
  808. event.set_dict({
  809. 'x': location.x,
  810. 'y': location.y,
  811. 'z': location.z,
  812. 'distance': distance})
  813. class OutsideRouteLanesTest(Criterion):
  814. """
  815. Atomic to detect if the vehicle is either on a sidewalk or at a wrong lane. The distance spent outside
  816. is computed and it is returned as a percentage of the route distance traveled.
  817. Args:
  818. actor (carla.ACtor): CARLA actor to be used for this test
  819. route (list [carla.Location, connection]): series of locations representing the route waypoints
  820. optional (bool): If True, the result is not considered for an overall pass/fail result
  821. """
  822. ALLOWED_OUT_DISTANCE = 1.3 # At least 0.5, due to the mini-shoulder between lanes and sidewalks
  823. MAX_ALLOWED_VEHICLE_ANGLE = 120.0 # Maximum angle between the yaw and waypoint lane
  824. MAX_ALLOWED_WAYPOINT_ANGLE = 150.0 # Maximum change between the yaw-lane angle between frames
  825. WINDOWS_SIZE = 3 # Amount of additional waypoints checked (in case the first on fails)
  826. def __init__(self, actor, route, optional=False, name="OutsideRouteLanesTest"):
  827. """
  828. Constructor
  829. """
  830. super(OutsideRouteLanesTest, self).__init__(name, actor, 0, None, optional)
  831. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  832. self._actor = actor
  833. self._route = route
  834. self._current_index = 0
  835. self._route_length = len(self._route)
  836. self._waypoints, _ = zip(*self._route)
  837. self._map = CarlaDataProvider.get_map()
  838. self._pre_ego_waypoint = self._map.get_waypoint(self._actor.get_location())
  839. self._outside_lane_active = False
  840. self._wrong_lane_active = False
  841. self._last_road_id = None
  842. self._last_lane_id = None
  843. self._total_distance = 0
  844. self._wrong_distance = 0
  845. def update(self):
  846. """
  847. Transforms the actor location and its four corners to waypoints. Depending on its types,
  848. the actor will be considered to be at driving lanes, sidewalk or offroad.
  849. returns:
  850. py_trees.common.Status.FAILURE: when the actor has left driving and terminate_on_failure is active
  851. py_trees.common.Status.RUNNING: the rest of the time
  852. """
  853. new_status = py_trees.common.Status.RUNNING
  854. if self._terminate_on_failure and (self.test_status == "FAILURE"):
  855. new_status = py_trees.common.Status.FAILURE
  856. # Some of the vehicle parameters
  857. location = CarlaDataProvider.get_location(self._actor)
  858. if location is None:
  859. return new_status
  860. # 1) Check if outside route lanes
  861. self._is_outside_driving_lanes(location)
  862. self._is_at_wrong_lane(location)
  863. if self._outside_lane_active or self._wrong_lane_active:
  864. self.test_status = "FAILURE"
  865. # 2) Get the traveled distance
  866. for index in range(self._current_index + 1,
  867. min(self._current_index + self.WINDOWS_SIZE + 1, self._route_length)):
  868. # Get the dot product to know if it has passed this location
  869. index_location = self._waypoints[index]
  870. index_waypoint = self._map.get_waypoint(index_location)
  871. wp_dir = index_waypoint.transform.get_forward_vector() # Waypoint's forward vector
  872. wp_veh = location - index_location # vector waypoint - vehicle
  873. dot_ve_wp = wp_veh.x * wp_dir.x + wp_veh.y * wp_dir.y + wp_veh.z * wp_dir.z
  874. if dot_ve_wp > 0:
  875. # Get the distance traveled
  876. index_location = self._waypoints[index]
  877. current_index_location = self._waypoints[self._current_index]
  878. new_dist = current_index_location.distance(index_location)
  879. # Add it to the total distance
  880. self._current_index = index
  881. self._total_distance += new_dist
  882. # And to the wrong one if outside route lanes
  883. if self._outside_lane_active or self._wrong_lane_active:
  884. self._wrong_distance += new_dist
  885. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  886. return new_status
  887. def _is_outside_driving_lanes(self, location):
  888. """
  889. Detects if the ego_vehicle is outside driving lanes
  890. """
  891. current_driving_wp = self._map.get_waypoint(location, lane_type=carla.LaneType.Driving, project_to_road=True)
  892. current_parking_wp = self._map.get_waypoint(location, lane_type=carla.LaneType.Parking, project_to_road=True)
  893. driving_distance = location.distance(current_driving_wp.transform.location)
  894. if current_parking_wp is not None: # Some towns have no parking
  895. parking_distance = location.distance(current_parking_wp.transform.location)
  896. else:
  897. parking_distance = float('inf')
  898. if driving_distance >= parking_distance:
  899. distance = parking_distance
  900. lane_width = current_parking_wp.lane_width
  901. else:
  902. distance = driving_distance
  903. lane_width = current_driving_wp.lane_width
  904. self._outside_lane_active = bool(distance > (lane_width / 2 + self.ALLOWED_OUT_DISTANCE))
  905. def _is_at_wrong_lane(self, location):
  906. """
  907. Detects if the ego_vehicle has invaded a wrong lane
  908. """
  909. current_waypoint = self._map.get_waypoint(location, lane_type=carla.LaneType.Driving, project_to_road=True)
  910. current_lane_id = current_waypoint.lane_id
  911. current_road_id = current_waypoint.road_id
  912. # Lanes and roads are too chaotic at junctions
  913. if current_waypoint.is_junction:
  914. self._wrong_lane_active = False
  915. elif self._last_road_id != current_road_id or self._last_lane_id != current_lane_id:
  916. # Route direction can be considered continuous, except after exiting a junction.
  917. if self._pre_ego_waypoint.is_junction:
  918. yaw_waypt = current_waypoint.transform.rotation.yaw % 360
  919. yaw_actor = self._actor.get_transform().rotation.yaw % 360
  920. vehicle_lane_angle = (yaw_waypt - yaw_actor) % 360
  921. if vehicle_lane_angle < self.MAX_ALLOWED_VEHICLE_ANGLE \
  922. or vehicle_lane_angle > (360 - self.MAX_ALLOWED_VEHICLE_ANGLE):
  923. self._wrong_lane_active = False
  924. else:
  925. self._wrong_lane_active = True
  926. else:
  927. # Check for a big gap in waypoint directions.
  928. yaw_pre_wp = self._pre_ego_waypoint.transform.rotation.yaw % 360
  929. yaw_cur_wp = current_waypoint.transform.rotation.yaw % 360
  930. waypoint_angle = (yaw_pre_wp - yaw_cur_wp) % 360
  931. if waypoint_angle >= self.MAX_ALLOWED_WAYPOINT_ANGLE \
  932. and waypoint_angle <= (360 - self.MAX_ALLOWED_WAYPOINT_ANGLE): # pylint: disable=chained-comparison
  933. # Is the ego vehicle going back to the lane, or going out? Take the opposite
  934. self._wrong_lane_active = not bool(self._wrong_lane_active)
  935. else:
  936. # Changing to a lane with the same direction
  937. self._wrong_lane_active = False
  938. # Remember the last state
  939. self._last_lane_id = current_lane_id
  940. self._last_road_id = current_road_id
  941. self._pre_ego_waypoint = current_waypoint
  942. def terminate(self, new_status):
  943. """
  944. If there is currently an event running, it is registered
  945. """
  946. if self._wrong_distance > 0:
  947. percentage = self._wrong_distance / self._total_distance * 100
  948. outside_lane = TrafficEvent(event_type=TrafficEventType.OUTSIDE_ROUTE_LANES_INFRACTION)
  949. outside_lane.set_message(
  950. "Agent went outside its route lanes for about {} meters "
  951. "({}% of the completed route)".format(
  952. round(self._wrong_distance, 3),
  953. round(percentage, 2)))
  954. outside_lane.set_dict({
  955. 'distance': self._wrong_distance,
  956. 'percentage': percentage
  957. })
  958. self._wrong_distance = 0
  959. self.list_traffic_events.append(outside_lane)
  960. self.actual_value = round(percentage, 2)
  961. super(OutsideRouteLanesTest, self).terminate(new_status)
  962. class WrongLaneTest(Criterion):
  963. """
  964. This class contains an atomic test to detect invasions to wrong direction lanes.
  965. Important parameters:
  966. - actor: CARLA actor to be used for this test
  967. - optional [optional]: If True, the result is not considered for an overall pass/fail result
  968. """
  969. MAX_ALLOWED_ANGLE = 120.0
  970. MAX_ALLOWED_WAYPOINT_ANGLE = 150.0
  971. def __init__(self, actor, optional=False, name="WrongLaneTest"):
  972. """
  973. Construction with sensor setup
  974. """
  975. super(WrongLaneTest, self).__init__(name, actor, 0, None, optional)
  976. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  977. self._actor = actor
  978. self._map = CarlaDataProvider.get_map()
  979. self._last_lane_id = None
  980. self._last_road_id = None
  981. self._in_lane = True
  982. self._wrong_distance = 0
  983. self._actor_location = self._actor.get_location()
  984. self._previous_lane_waypoint = self._map.get_waypoint(self._actor.get_location())
  985. self._wrong_lane_start_location = None
  986. def update(self):
  987. """
  988. Check lane invasion count
  989. """
  990. new_status = py_trees.common.Status.RUNNING
  991. if self._terminate_on_failure and (self.test_status == "FAILURE"):
  992. new_status = py_trees.common.Status.FAILURE
  993. lane_waypoint = self._map.get_waypoint(self._actor.get_location())
  994. current_lane_id = lane_waypoint.lane_id
  995. current_road_id = lane_waypoint.road_id
  996. if (self._last_road_id != current_road_id or self._last_lane_id != current_lane_id) \
  997. and not lane_waypoint.is_junction:
  998. next_waypoint = lane_waypoint.next(2.0)[0]
  999. if not next_waypoint:
  1000. return new_status
  1001. # The waypoint route direction can be considered continuous.
  1002. # Therefore just check for a big gap in waypoint directions.
  1003. previous_lane_direction = self._previous_lane_waypoint.transform.get_forward_vector()
  1004. current_lane_direction = lane_waypoint.transform.get_forward_vector()
  1005. p_lane_vector = np.array([previous_lane_direction.x, previous_lane_direction.y])
  1006. c_lane_vector = np.array([current_lane_direction.x, current_lane_direction.y])
  1007. waypoint_angle = math.degrees(
  1008. math.acos(np.clip(np.dot(p_lane_vector, c_lane_vector) /
  1009. (np.linalg.norm(p_lane_vector) * np.linalg.norm(c_lane_vector)), -1.0, 1.0)))
  1010. if waypoint_angle > self.MAX_ALLOWED_WAYPOINT_ANGLE and self._in_lane:
  1011. self.test_status = "FAILURE"
  1012. self._in_lane = False
  1013. self.actual_value += 1
  1014. self._wrong_lane_start_location = self._actor_location
  1015. else:
  1016. # Reset variables
  1017. self._in_lane = True
  1018. # Continuity is broken after a junction so check vehicle-lane angle instead
  1019. if self._previous_lane_waypoint.is_junction:
  1020. vector_wp = np.array([next_waypoint.transform.location.x - lane_waypoint.transform.location.x,
  1021. next_waypoint.transform.location.y - lane_waypoint.transform.location.y])
  1022. vector_actor = np.array([math.cos(math.radians(self._actor.get_transform().rotation.yaw)),
  1023. math.sin(math.radians(self._actor.get_transform().rotation.yaw))])
  1024. vehicle_lane_angle = math.degrees(
  1025. math.acos(np.clip(np.dot(vector_actor, vector_wp) / (np.linalg.norm(vector_wp)), -1.0, 1.0)))
  1026. if vehicle_lane_angle > self.MAX_ALLOWED_ANGLE:
  1027. self.test_status = "FAILURE"
  1028. self._in_lane = False
  1029. self.actual_value += 1
  1030. self._wrong_lane_start_location = self._actor.get_location()
  1031. # Keep adding "meters" to the counter
  1032. distance_vector = self._actor.get_location() - self._actor_location
  1033. distance = math.sqrt(math.pow(distance_vector.x, 2) + math.pow(distance_vector.y, 2))
  1034. if distance >= 0.02: # Used to avoid micro-changes adding add to considerable sums
  1035. self._actor_location = CarlaDataProvider.get_location(self._actor)
  1036. if not self._in_lane and not lane_waypoint.is_junction:
  1037. self._wrong_distance += distance
  1038. # Register the event
  1039. if self._in_lane and self._wrong_distance > 0:
  1040. wrong_way_event = TrafficEvent(event_type=TrafficEventType.WRONG_WAY_INFRACTION)
  1041. self._set_event_message(wrong_way_event, self._wrong_lane_start_location,
  1042. self._wrong_distance, current_road_id, current_lane_id)
  1043. self._set_event_dict(wrong_way_event, self._wrong_lane_start_location,
  1044. self._wrong_distance, current_road_id, current_lane_id)
  1045. self.list_traffic_events.append(wrong_way_event)
  1046. self._wrong_distance = 0
  1047. # Remember the last state
  1048. self._last_lane_id = current_lane_id
  1049. self._last_road_id = current_road_id
  1050. self._previous_lane_waypoint = lane_waypoint
  1051. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  1052. return new_status
  1053. def terminate(self, new_status):
  1054. """
  1055. If there is currently an event running, it is registered
  1056. """
  1057. if not self._in_lane:
  1058. lane_waypoint = self._map.get_waypoint(self._actor.get_location())
  1059. current_lane_id = lane_waypoint.lane_id
  1060. current_road_id = lane_waypoint.road_id
  1061. wrong_way_event = TrafficEvent(event_type=TrafficEventType.WRONG_WAY_INFRACTION)
  1062. self._set_event_message(wrong_way_event, self._wrong_lane_start_location,
  1063. self._wrong_distance, current_road_id, current_lane_id)
  1064. self._set_event_dict(wrong_way_event, self._wrong_lane_start_location,
  1065. self._wrong_distance, current_road_id, current_lane_id)
  1066. self._wrong_distance = 0
  1067. self._in_lane = True
  1068. self.list_traffic_events.append(wrong_way_event)
  1069. super(WrongLaneTest, self).terminate(new_status)
  1070. def _set_event_message(self, event, location, distance, road_id, lane_id):
  1071. """
  1072. Sets the message of the event
  1073. """
  1074. event.set_message(
  1075. "Agent invaded a lane in opposite direction for {} meters, starting at (x={}, y={}, z={}). "
  1076. "road_id={}, lane_id={}".format(
  1077. round(distance, 3),
  1078. round(location.x, 3),
  1079. round(location.y, 3),
  1080. round(location.z, 3),
  1081. road_id,
  1082. lane_id))
  1083. def _set_event_dict(self, event, location, distance, road_id, lane_id):
  1084. """
  1085. Sets the dictionary of the event
  1086. """
  1087. event.set_dict({
  1088. 'x': location.x,
  1089. 'y': location.y,
  1090. 'z': location.y,
  1091. 'distance': distance,
  1092. 'road_id': road_id,
  1093. 'lane_id': lane_id})
  1094. class InRadiusRegionTest(Criterion):
  1095. """
  1096. The test is a success if the actor is within a given radius of a specified region
  1097. Important parameters:
  1098. - actor: CARLA actor to be used for this test
  1099. - x, y, radius: Position (x,y) and radius (in meters) used to get the checked region
  1100. """
  1101. def __init__(self, actor, x, y, radius, name="InRadiusRegionTest"):
  1102. """
  1103. """
  1104. super(InRadiusRegionTest, self).__init__(name, actor, 0)
  1105. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  1106. self._actor = actor
  1107. self._x = x # pylint: disable=invalid-name
  1108. self._y = y # pylint: disable=invalid-name
  1109. self._radius = radius
  1110. def update(self):
  1111. """
  1112. Check if the actor location is within trigger region
  1113. """
  1114. new_status = py_trees.common.Status.RUNNING
  1115. location = CarlaDataProvider.get_location(self._actor)
  1116. if location is None:
  1117. return new_status
  1118. if self.test_status != "SUCCESS":
  1119. in_radius = math.sqrt(((location.x - self._x)**2) + ((location.y - self._y)**2)) < self._radius
  1120. if in_radius:
  1121. route_completion_event = TrafficEvent(event_type=TrafficEventType.ROUTE_COMPLETED)
  1122. route_completion_event.set_message("Destination was successfully reached")
  1123. self.list_traffic_events.append(route_completion_event)
  1124. self.test_status = "SUCCESS"
  1125. else:
  1126. self.test_status = "RUNNING"
  1127. if self.test_status == "SUCCESS":
  1128. new_status = py_trees.common.Status.SUCCESS
  1129. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  1130. return new_status
  1131. class InRouteTest(Criterion):
  1132. """
  1133. The test is a success if the actor is never outside route. The actor can go outside of the route
  1134. but only for a certain amount of distance
  1135. Important parameters:
  1136. - actor: CARLA actor to be used for this test
  1137. - route: Route to be checked
  1138. - offroad_max: Maximum distance (in meters) the actor can deviate from the route
  1139. - offroad_min: Maximum safe distance (in meters). Might eventually cause failure
  1140. - terminate_on_failure [optional]: If True, the complete scenario will terminate upon failure of this test
  1141. """
  1142. MAX_ROUTE_PERCENTAGE = 30 # %
  1143. WINDOWS_SIZE = 5 # Amount of additional waypoints checked
  1144. def __init__(self, actor, route, offroad_min=-1, offroad_max=30, name="InRouteTest", terminate_on_failure=False):
  1145. """
  1146. """
  1147. super(InRouteTest, self).__init__(name, actor, 0, terminate_on_failure=terminate_on_failure)
  1148. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  1149. self._actor = actor
  1150. self._route = route
  1151. self._offroad_max = offroad_max
  1152. # Unless specified, halve of the max value
  1153. if offroad_min == -1:
  1154. self._offroad_min = self._offroad_max / 2
  1155. else:
  1156. self._offroad_min = self._offroad_min
  1157. self._world = CarlaDataProvider.get_world()
  1158. self._waypoints, _ = zip(*self._route)
  1159. self._route_length = len(self._route)
  1160. self._current_index = 0
  1161. self._out_route_distance = 0
  1162. self._in_safe_route = True
  1163. self._accum_meters = []
  1164. prev_wp = self._waypoints[0]
  1165. for i, wp in enumerate(self._waypoints):
  1166. d = wp.distance(prev_wp)
  1167. if i > 0:
  1168. accum = self._accum_meters[i - 1]
  1169. else:
  1170. accum = 0
  1171. self._accum_meters.append(d + accum)
  1172. prev_wp = wp
  1173. # Blackboard variable
  1174. blackv = py_trees.blackboard.Blackboard()
  1175. _ = blackv.set("InRoute", True)
  1176. def update(self):
  1177. """
  1178. Check if the actor location is within trigger region
  1179. """
  1180. new_status = py_trees.common.Status.RUNNING
  1181. location = CarlaDataProvider.get_location(self._actor)
  1182. if location is None:
  1183. return new_status
  1184. if self._terminate_on_failure and (self.test_status == "FAILURE"):
  1185. new_status = py_trees.common.Status.FAILURE
  1186. elif self.test_status in ('RUNNING', 'INIT'):
  1187. off_route = True
  1188. shortest_distance = float('inf')
  1189. closest_index = -1
  1190. # Get the closest distance
  1191. for index in range(self._current_index,
  1192. min(self._current_index + self.WINDOWS_SIZE + 1, self._route_length)):
  1193. ref_waypoint = self._waypoints[index]
  1194. distance = math.sqrt(((location.x - ref_waypoint.x) ** 2) + ((location.y - ref_waypoint.y) ** 2))
  1195. if distance <= shortest_distance:
  1196. closest_index = index
  1197. shortest_distance = distance
  1198. if closest_index == -1 or shortest_distance == float('inf'):
  1199. return new_status
  1200. # Check if the actor is out of route
  1201. if shortest_distance < self._offroad_max:
  1202. off_route = False
  1203. self._in_safe_route = bool(shortest_distance < self._offroad_min)
  1204. # If actor advanced a step, record the distance
  1205. if self._current_index != closest_index:
  1206. new_dist = self._accum_meters[closest_index] - self._accum_meters[self._current_index]
  1207. # If too far from the route, add it and check if its value
  1208. if not self._in_safe_route:
  1209. self._out_route_distance += new_dist
  1210. out_route_percentage = 100 * self._out_route_distance / self._accum_meters[-1]
  1211. if out_route_percentage > self.MAX_ROUTE_PERCENTAGE:
  1212. off_route = True
  1213. self._current_index = closest_index
  1214. if off_route:
  1215. # Blackboard variable
  1216. blackv = py_trees.blackboard.Blackboard()
  1217. _ = blackv.set("InRoute", False)
  1218. route_deviation_event = TrafficEvent(event_type=TrafficEventType.ROUTE_DEVIATION)
  1219. route_deviation_event.set_message(
  1220. "Agent deviated from the route at (x={}, y={}, z={})".format(
  1221. round(location.x, 3),
  1222. round(location.y, 3),
  1223. round(location.z, 3)))
  1224. route_deviation_event.set_dict({
  1225. 'x': location.x,
  1226. 'y': location.y,
  1227. 'z': location.z})
  1228. self.list_traffic_events.append(route_deviation_event)
  1229. self.test_status = "FAILURE"
  1230. self.actual_value += 1
  1231. new_status = py_trees.common.Status.FAILURE
  1232. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  1233. return new_status
  1234. class RouteCompletionTest(Criterion):
  1235. """
  1236. Check at which stage of the route is the actor at each tick
  1237. Important parameters:
  1238. - actor: CARLA actor to be used for this test
  1239. - route: Route to be checked
  1240. - terminate_on_failure [optional]: If True, the complete scenario will terminate upon failure of this test
  1241. """
  1242. DISTANCE_THRESHOLD = 10.0 # meters
  1243. WINDOWS_SIZE = 2
  1244. def __init__(self, actor, route, name="RouteCompletionTest", terminate_on_failure=False):
  1245. """
  1246. """
  1247. super(RouteCompletionTest, self).__init__(name, actor, 100, terminate_on_failure=terminate_on_failure)
  1248. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  1249. self._actor = actor
  1250. self._route = route
  1251. self._map = CarlaDataProvider.get_map()
  1252. self._wsize = self.WINDOWS_SIZE
  1253. self._current_index = 0
  1254. self._route_length = len(self._route)
  1255. self._waypoints, _ = zip(*self._route)
  1256. self.target = self._waypoints[-1]
  1257. self._accum_meters = []
  1258. prev_wp = self._waypoints[0]
  1259. for i, wp in enumerate(self._waypoints):
  1260. d = wp.distance(prev_wp)
  1261. if i > 0:
  1262. accum = self._accum_meters[i - 1]
  1263. else:
  1264. accum = 0
  1265. self._accum_meters.append(d + accum)
  1266. prev_wp = wp
  1267. self._traffic_event = TrafficEvent(event_type=TrafficEventType.ROUTE_COMPLETION)
  1268. self.list_traffic_events.append(self._traffic_event)
  1269. self._percentage_route_completed = 0.0
  1270. def update(self):
  1271. """
  1272. Check if the actor location is within trigger region
  1273. """
  1274. new_status = py_trees.common.Status.RUNNING
  1275. location = CarlaDataProvider.get_location(self._actor)
  1276. if location is None:
  1277. return new_status
  1278. if self._terminate_on_failure and (self.test_status == "FAILURE"):
  1279. new_status = py_trees.common.Status.FAILURE
  1280. elif self.test_status in ('RUNNING', 'INIT'):
  1281. for index in range(self._current_index, min(self._current_index + self._wsize + 1, self._route_length)):
  1282. # Get the dot product to know if it has passed this location
  1283. ref_waypoint = self._waypoints[index]
  1284. wp = self._map.get_waypoint(ref_waypoint)
  1285. wp_dir = wp.transform.get_forward_vector() # Waypoint's forward vector
  1286. wp_veh = location - ref_waypoint # vector waypoint - vehicle
  1287. dot_ve_wp = wp_veh.x * wp_dir.x + wp_veh.y * wp_dir.y + wp_veh.z * wp_dir.z
  1288. if dot_ve_wp > 0:
  1289. # good! segment completed!
  1290. self._current_index = index
  1291. self._percentage_route_completed = 100.0 * float(self._accum_meters[self._current_index]) \
  1292. / float(self._accum_meters[-1])
  1293. self._traffic_event.set_dict({
  1294. 'route_completed': self._percentage_route_completed})
  1295. self._traffic_event.set_message(
  1296. "Agent has completed > {:.2f}% of the route".format(
  1297. self._percentage_route_completed))
  1298. if self._percentage_route_completed > 99.0 and location.distance(self.target) < self.DISTANCE_THRESHOLD:
  1299. route_completion_event = TrafficEvent(event_type=TrafficEventType.ROUTE_COMPLETED)
  1300. route_completion_event.set_message("Destination was successfully reached")
  1301. self.list_traffic_events.append(route_completion_event)
  1302. self.test_status = "SUCCESS"
  1303. self._percentage_route_completed = 100
  1304. elif self.test_status == "SUCCESS":
  1305. new_status = py_trees.common.Status.SUCCESS
  1306. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  1307. return new_status
  1308. def terminate(self, new_status):
  1309. """
  1310. Set test status to failure if not successful and terminate
  1311. """
  1312. self.actual_value = round(self._percentage_route_completed, 2)
  1313. if self.test_status == "INIT":
  1314. self.test_status = "FAILURE"
  1315. super(RouteCompletionTest, self).terminate(new_status)
  1316. class RunningRedLightTest(Criterion):
  1317. """
  1318. Check if an actor is running a red light
  1319. Important parameters:
  1320. - actor: CARLA actor to be used for this test
  1321. - terminate_on_failure [optional]: If True, the complete scenario will terminate upon failure of this test
  1322. """
  1323. DISTANCE_LIGHT = 15 # m
  1324. def __init__(self, actor, name="RunningRedLightTest", terminate_on_failure=False):
  1325. """
  1326. Init
  1327. """
  1328. super(RunningRedLightTest, self).__init__(name, actor, 0, terminate_on_failure=terminate_on_failure)
  1329. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  1330. self._actor = actor
  1331. self._world = actor.get_world()
  1332. self._map = CarlaDataProvider.get_map()
  1333. self._list_traffic_lights = []
  1334. self._last_red_light_id = None
  1335. self.actual_value = 0
  1336. self.debug = False
  1337. all_actors = self._world.get_actors()
  1338. for _actor in all_actors:
  1339. if 'traffic_light' in _actor.type_id:
  1340. center, waypoints = self.get_traffic_light_waypoints(_actor)
  1341. self._list_traffic_lights.append((_actor, center, waypoints))
  1342. # pylint: disable=no-self-use
  1343. def is_vehicle_crossing_line(self, seg1, seg2):
  1344. """
  1345. check if vehicle crosses a line segment
  1346. """
  1347. line1 = shapely.geometry.LineString([(seg1[0].x, seg1[0].y), (seg1[1].x, seg1[1].y)])
  1348. line2 = shapely.geometry.LineString([(seg2[0].x, seg2[0].y), (seg2[1].x, seg2[1].y)])
  1349. inter = line1.intersection(line2)
  1350. return not inter.is_empty
  1351. def update(self):
  1352. """
  1353. Check if the actor is running a red light
  1354. """
  1355. new_status = py_trees.common.Status.RUNNING
  1356. transform = CarlaDataProvider.get_transform(self._actor)
  1357. location = transform.location
  1358. if location is None:
  1359. return new_status
  1360. veh_extent = self._actor.bounding_box.extent.x
  1361. tail_close_pt = self.rotate_point(carla.Vector3D(-0.8 * veh_extent, 0.0, location.z), transform.rotation.yaw)
  1362. tail_close_pt = location + carla.Location(tail_close_pt)
  1363. tail_far_pt = self.rotate_point(carla.Vector3D(-veh_extent - 1, 0.0, location.z), transform.rotation.yaw)
  1364. tail_far_pt = location + carla.Location(tail_far_pt)
  1365. for traffic_light, center, waypoints in self._list_traffic_lights:
  1366. if self.debug:
  1367. z = 2.1
  1368. if traffic_light.state == carla.TrafficLightState.Red:
  1369. color = carla.Color(155, 0, 0)
  1370. elif traffic_light.state == carla.TrafficLightState.Green:
  1371. color = carla.Color(0, 155, 0)
  1372. else:
  1373. color = carla.Color(155, 155, 0)
  1374. self._world.debug.draw_point(center + carla.Location(z=z), size=0.2, color=color, life_time=0.01)
  1375. for wp in waypoints:
  1376. text = "{}.{}".format(wp.road_id, wp.lane_id)
  1377. self._world.debug.draw_string(
  1378. wp.transform.location + carla.Location(x=1, z=z), text, color=color, life_time=0.01)
  1379. self._world.debug.draw_point(
  1380. wp.transform.location + carla.Location(z=z), size=0.1, color=color, life_time=0.01)
  1381. center_loc = carla.Location(center)
  1382. if self._last_red_light_id and self._last_red_light_id == traffic_light.id:
  1383. continue
  1384. if center_loc.distance(location) > self.DISTANCE_LIGHT:
  1385. continue
  1386. if traffic_light.state != carla.TrafficLightState.Red:
  1387. continue
  1388. for wp in waypoints:
  1389. tail_wp = self._map.get_waypoint(tail_far_pt)
  1390. # Calculate the dot product (Might be unscaled, as only its sign is important)
  1391. ve_dir = CarlaDataProvider.get_transform(self._actor).get_forward_vector()
  1392. wp_dir = wp.transform.get_forward_vector()
  1393. dot_ve_wp = ve_dir.x * wp_dir.x + ve_dir.y * wp_dir.y + ve_dir.z * wp_dir.z
  1394. # Check the lane until all the "tail" has passed
  1395. if tail_wp.road_id == wp.road_id and tail_wp.lane_id == wp.lane_id and dot_ve_wp > 0:
  1396. # This light is red and is affecting our lane
  1397. yaw_wp = wp.transform.rotation.yaw
  1398. lane_width = wp.lane_width
  1399. location_wp = wp.transform.location
  1400. lft_lane_wp = self.rotate_point(carla.Vector3D(0.4 * lane_width, 0.0, location_wp.z), yaw_wp + 90)
  1401. lft_lane_wp = location_wp + carla.Location(lft_lane_wp)
  1402. rgt_lane_wp = self.rotate_point(carla.Vector3D(0.4 * lane_width, 0.0, location_wp.z), yaw_wp - 90)
  1403. rgt_lane_wp = location_wp + carla.Location(rgt_lane_wp)
  1404. # Is the vehicle traversing the stop line?
  1405. if self.is_vehicle_crossing_line((tail_close_pt, tail_far_pt), (lft_lane_wp, rgt_lane_wp)):
  1406. self.test_status = "FAILURE"
  1407. self.actual_value += 1
  1408. location = traffic_light.get_transform().location
  1409. red_light_event = TrafficEvent(event_type=TrafficEventType.TRAFFIC_LIGHT_INFRACTION)
  1410. red_light_event.set_message(
  1411. "Agent ran a red light {} at (x={}, y={}, z={})".format(
  1412. traffic_light.id,
  1413. round(location.x, 3),
  1414. round(location.y, 3),
  1415. round(location.z, 3)))
  1416. red_light_event.set_dict({
  1417. 'id': traffic_light.id,
  1418. 'x': location.x,
  1419. 'y': location.y,
  1420. 'z': location.z})
  1421. self.list_traffic_events.append(red_light_event)
  1422. self._last_red_light_id = traffic_light.id
  1423. break
  1424. if self._terminate_on_failure and (self.test_status == "FAILURE"):
  1425. new_status = py_trees.common.Status.FAILURE
  1426. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  1427. return new_status
  1428. def rotate_point(self, point, angle):
  1429. """
  1430. rotate a given point by a given angle
  1431. """
  1432. x_ = math.cos(math.radians(angle)) * point.x - math.sin(math.radians(angle)) * point.y
  1433. y_ = math.sin(math.radians(angle)) * point.x + math.cos(math.radians(angle)) * point.y
  1434. return carla.Vector3D(x_, y_, point.z)
  1435. def get_traffic_light_waypoints(self, traffic_light):
  1436. """
  1437. get area of a given traffic light
  1438. """
  1439. base_transform = traffic_light.get_transform()
  1440. base_rot = base_transform.rotation.yaw
  1441. area_loc = base_transform.transform(traffic_light.trigger_volume.location)
  1442. # Discretize the trigger box into points
  1443. area_ext = traffic_light.trigger_volume.extent
  1444. x_values = np.arange(-0.9 * area_ext.x, 0.9 * area_ext.x, 1.0) # 0.9 to avoid crossing to adjacent lanes
  1445. area = []
  1446. for x in x_values:
  1447. point = self.rotate_point(carla.Vector3D(x, 0, area_ext.z), base_rot)
  1448. point_location = area_loc + carla.Location(x=point.x, y=point.y)
  1449. area.append(point_location)
  1450. # Get the waypoints of these points, removing duplicates
  1451. ini_wps = []
  1452. for pt in area:
  1453. wpx = self._map.get_waypoint(pt)
  1454. # As x_values are arranged in order, only the last one has to be checked
  1455. if not ini_wps or ini_wps[-1].road_id != wpx.road_id or ini_wps[-1].lane_id != wpx.lane_id:
  1456. ini_wps.append(wpx)
  1457. # Advance them until the intersection
  1458. wps = []
  1459. for wpx in ini_wps:
  1460. while not wpx.is_intersection:
  1461. next_wp = wpx.next(0.5)[0]
  1462. if next_wp and not next_wp.is_intersection:
  1463. wpx = next_wp
  1464. else:
  1465. break
  1466. wps.append(wpx)
  1467. return area_loc, wps
  1468. class RunningStopTest(Criterion):
  1469. """
  1470. Check if an actor is running a stop sign
  1471. Important parameters:
  1472. - actor: CARLA actor to be used for this test
  1473. - terminate_on_failure [optional]: If True, the complete scenario will terminate upon failure of this test
  1474. """
  1475. PROXIMITY_THRESHOLD = 50.0 # meters
  1476. SPEED_THRESHOLD = 0.1
  1477. WAYPOINT_STEP = 1.0 # meters
  1478. def __init__(self, actor, name="RunningStopTest", terminate_on_failure=False):
  1479. """
  1480. """
  1481. super(RunningStopTest, self).__init__(name, actor, 0, terminate_on_failure=terminate_on_failure)
  1482. self.logger.debug("%s.__init__()" % (self.__class__.__name__))
  1483. self._actor = actor
  1484. self._world = CarlaDataProvider.get_world()
  1485. self._map = CarlaDataProvider.get_map()
  1486. self._list_stop_signs = []
  1487. self._target_stop_sign = None
  1488. self._stop_completed = False
  1489. self._affected_by_stop = False
  1490. self.actual_value = 0
  1491. all_actors = self._world.get_actors()
  1492. for _actor in all_actors:
  1493. if 'traffic.stop' in _actor.type_id:
  1494. self._list_stop_signs.append(_actor)
  1495. @staticmethod
  1496. def point_inside_boundingbox(point, bb_center, bb_extent):
  1497. """
  1498. X
  1499. :param point:
  1500. :param bb_center:
  1501. :param bb_extent:
  1502. :return:
  1503. """
  1504. # pylint: disable=invalid-name
  1505. A = carla.Vector2D(bb_center.x - bb_extent.x, bb_center.y - bb_extent.y)
  1506. B = carla.Vector2D(bb_center.x + bb_extent.x, bb_center.y - bb_extent.y)
  1507. D = carla.Vector2D(bb_center.x - bb_extent.x, bb_center.y + bb_extent.y)
  1508. M = carla.Vector2D(point.x, point.y)
  1509. AB = B - A
  1510. AD = D - A
  1511. AM = M - A
  1512. am_ab = AM.x * AB.x + AM.y * AB.y
  1513. ab_ab = AB.x * AB.x + AB.y * AB.y
  1514. am_ad = AM.x * AD.x + AM.y * AD.y
  1515. ad_ad = AD.x * AD.x + AD.y * AD.y
  1516. return am_ab > 0 and am_ab < ab_ab and am_ad > 0 and am_ad < ad_ad # pylint: disable=chained-comparison
  1517. def is_actor_affected_by_stop(self, actor, stop, multi_step=20):
  1518. """
  1519. Check if the given actor is affected by the stop
  1520. """
  1521. affected = False
  1522. # first we run a fast coarse test
  1523. current_location = actor.get_location()
  1524. stop_location = stop.get_transform().location
  1525. if stop_location.distance(current_location) > self.PROXIMITY_THRESHOLD:
  1526. return affected
  1527. stop_t = stop.get_transform()
  1528. transformed_tv = stop_t.transform(stop.trigger_volume.location)
  1529. # slower and accurate test based on waypoint's horizon and geometric test
  1530. list_locations = [current_location]
  1531. waypoint = self._map.get_waypoint(current_location)
  1532. for _ in range(multi_step):
  1533. if waypoint:
  1534. next_wps = waypoint.next(self.WAYPOINT_STEP)
  1535. if not next_wps:
  1536. break
  1537. waypoint = next_wps[0]
  1538. if not waypoint:
  1539. break
  1540. list_locations.append(waypoint.transform.location)
  1541. for actor_location in list_locations:
  1542. if self.point_inside_boundingbox(actor_location, transformed_tv, stop.trigger_volume.extent):
  1543. affected = True
  1544. return affected
  1545. def _scan_for_stop_sign(self):
  1546. target_stop_sign = None
  1547. ve_tra = CarlaDataProvider.get_transform(self._actor)
  1548. ve_dir = ve_tra.get_forward_vector()
  1549. wp = self._map.get_waypoint(ve_tra.location)
  1550. wp_dir = wp.transform.get_forward_vector()
  1551. dot_ve_wp = ve_dir.x * wp_dir.x + ve_dir.y * wp_dir.y + ve_dir.z * wp_dir.z
  1552. if dot_ve_wp > 0: # Ignore all when going in a wrong lane
  1553. for stop_sign in self._list_stop_signs:
  1554. if self.is_actor_affected_by_stop(self._actor, stop_sign):
  1555. # this stop sign is affecting the vehicle
  1556. target_stop_sign = stop_sign
  1557. break
  1558. return target_stop_sign
  1559. def update(self):
  1560. """
  1561. Check if the actor is running a red light
  1562. """
  1563. new_status = py_trees.common.Status.RUNNING
  1564. location = self._actor.get_location()
  1565. if location is None:
  1566. return new_status
  1567. if not self._target_stop_sign:
  1568. # scan for stop signs
  1569. self._target_stop_sign = self._scan_for_stop_sign()
  1570. else:
  1571. # we were in the middle of dealing with a stop sign
  1572. if not self._stop_completed:
  1573. # did the ego-vehicle stop?
  1574. current_speed = CarlaDataProvider.get_velocity(self._actor)
  1575. if current_speed < self.SPEED_THRESHOLD:
  1576. self._stop_completed = True
  1577. if not self._affected_by_stop:
  1578. stop_location = self._target_stop_sign.get_location()
  1579. stop_extent = self._target_stop_sign.trigger_volume.extent
  1580. if self.point_inside_boundingbox(location, stop_location, stop_extent):
  1581. self._affected_by_stop = True
  1582. if not self.is_actor_affected_by_stop(self._actor, self._target_stop_sign):
  1583. # is the vehicle out of the influence of this stop sign now?
  1584. if not self._stop_completed and self._affected_by_stop:
  1585. # did we stop?
  1586. self.actual_value += 1
  1587. self.test_status = "FAILURE"
  1588. stop_location = self._target_stop_sign.get_transform().location
  1589. running_stop_event = TrafficEvent(event_type=TrafficEventType.STOP_INFRACTION)
  1590. running_stop_event.set_message(
  1591. "Agent ran a stop with id={} at (x={}, y={}, z={})".format(
  1592. self._target_stop_sign.id,
  1593. round(stop_location.x, 3),
  1594. round(stop_location.y, 3),
  1595. round(stop_location.z, 3)))
  1596. running_stop_event.set_dict({
  1597. 'id': self._target_stop_sign.id,
  1598. 'x': stop_location.x,
  1599. 'y': stop_location.y,
  1600. 'z': stop_location.z})
  1601. self.list_traffic_events.append(running_stop_event)
  1602. # reset state
  1603. self._target_stop_sign = None
  1604. self._stop_completed = False
  1605. self._affected_by_stop = False
  1606. if self._terminate_on_failure and (self.test_status == "FAILURE"):
  1607. new_status = py_trees.common.Status.FAILURE
  1608. self.logger.debug("%s.update()[%s->%s]" % (self.__class__.__name__, self.status, new_status))
  1609. return new_status