Provide parent/attribute content of Actors via python interface

While creating the new carla ros bridge some extensions became necessary
within CARLA:

The parent property of an actor via python interface is not yet filled.
Therefore, the parent_id of Actors has to be transferred from the CARLA
server via rpc interface.

In addition, actor attributes are published via python interface.

Changes in detail:

carla/rpc/Actor.h:
- add parent_id field to the Actor class for rpc transport

TheNewCarlaServer.cpp:
- fill the parent_id field with the appropriate value

client/ActorList:
- added GetActor() function to get an actor by id

client/ActorVariant:
- added actor_list optional parameter to Get() and MakeActor() function
which allows to query for the parent actor in case the actor_list is
available

client/ActorAttribute:
- solved problem of independent rpc::ActorAttribute* classes by
introduction of ActorAttributeValueAccess class, to be able to reuse
most of the functions for both ActorAttribueValues and ActorAttributes

ActorBlueprintFunctionLibrary:
- extended actor attributes by attribute 'role_name' having {autopilot,
    scenario, ego_vehicle} as recommended values for vehicles or
    {front,back,...} for sensors to be able to distiguish the different
    actors in a meaningful way when transferring to ROS topic names
- extended vehicle attributes by not-modifiable attribute 'object_type'
to be defined at blueprint creation time to provide ground truth
object classification type

PythonAPI:
- libcarla: provide the actor attributes within python as dictionary
- make use of role_name attribute to provide information required for
ROS bridge to distinguish ego vehicle from others
This commit is contained in:
Bernd Gassmann 2018-11-30 16:05:30 +01:00
parent fd54b3ccf7
commit bd710c339c
17 changed files with 276 additions and 110 deletions

View File

@ -90,6 +90,7 @@
- `parent`
- `semantic_tags`
- `is_alive`
- `attributes`
- `get_world()`
- `get_location()`
- `get_transform()`

View File

@ -12,10 +12,10 @@
namespace carla {
namespace client {
#define LIBCARLA_THROW_INVALID_VALUE(message) throw InvalidAttributeValue(_attribute.id + ": " + message);
#define LIBCARLA_THROW_INVALID_VALUE(message) throw InvalidAttributeValue(GetId() + ": " + message);
#define LIBCARLA_THROW_BAD_VALUE_CAST(type) \
if (GetType() != rpc::ActorAttributeType:: type) { \
throw BadAttributeCast(_attribute.id + ": bad attribute cast: cannot convert to " #type); \
throw BadAttributeCast(GetId() + ": bad attribute cast: cannot convert to " #type); \
}
void ActorAttribute::Set(std::string value) {
@ -29,28 +29,29 @@ namespace client {
Validate();
}
template <>
bool ActorAttribute::As<bool>() const {
bool ActorAttributeValueAccess::As<bool>() const {
LIBCARLA_THROW_BAD_VALUE_CAST(Bool);
auto value = StringUtil::ToLowerCopy(_attribute.value);
auto value = StringUtil::ToLowerCopy(GetValue());
if (value == "true") {
return true;
} else if (value == "false") {
return false;
}
LIBCARLA_THROW_INVALID_VALUE("invalid bool: " + _attribute.value);
LIBCARLA_THROW_INVALID_VALUE("invalid bool: " + GetValue());
}
template<>
int ActorAttribute::As<int>() const {
int ActorAttributeValueAccess::As<int>() const {
LIBCARLA_THROW_BAD_VALUE_CAST(Int);
return std::atoi(_attribute.value.c_str());
return std::atoi(GetValue().c_str());
}
template<>
float ActorAttribute::As<float>() const {
float ActorAttributeValueAccess::As<float>() const {
LIBCARLA_THROW_BAD_VALUE_CAST(Float);
double x = std::atof(_attribute.value.c_str());
double x = std::atof(GetValue().c_str());
if ((x > std::numeric_limits<float>::max()) ||
(x < std::numeric_limits<float>::lowest())) {
LIBCARLA_THROW_INVALID_VALUE("float overflow");
@ -59,19 +60,19 @@ namespace client {
}
template <>
std::string ActorAttribute::As<std::string>() const {
std::string ActorAttributeValueAccess::As<std::string>() const {
LIBCARLA_THROW_BAD_VALUE_CAST(String);
return _attribute.value;
return GetValue();
}
template <>
sensor::data::Color ActorAttribute::As<sensor::data::Color>() const {
sensor::data::Color ActorAttributeValueAccess::As<sensor::data::Color>() const {
LIBCARLA_THROW_BAD_VALUE_CAST(RGBColor);
std::vector<std::string> channels;
StringUtil::Split(channels, _attribute.value, ",");
StringUtil::Split(channels, GetValue(), ",");
if (channels.size() != 3u) {
log_error("invalid color", _attribute.value);
log_error("invalid color", GetValue());
LIBCARLA_THROW_INVALID_VALUE("colors must have 3 channels (R,G,B)");
}
@ -86,8 +87,8 @@ namespace client {
return {to_int(channels[0u]), to_int(channels[1u]), to_int(channels[2u])};
}
void ActorAttribute::Validate() const {
switch (_attribute.type) {
void ActorAttributeValueAccess::Validate() const {
switch (GetType()) {
case rpc::ActorAttributeType::Bool: As<rpc::ActorAttributeType::Bool>(); break;
case rpc::ActorAttributeType::Int: As<rpc::ActorAttributeType::Int>(); break;
case rpc::ActorAttributeType::Float: As<rpc::ActorAttributeType::Float>(); break;

View File

@ -44,36 +44,20 @@ namespace client {
// -- ActorAttribute ---------------------------------------------------------
// ===========================================================================
/// An attribute of an ActorBlueprint.
class ActorAttribute {
class ActorAttributeValueAccess
{
public:
ActorAttributeValueAccess() = default;
ActorAttributeValueAccess(ActorAttributeValueAccess const &) = default;
ActorAttributeValueAccess(ActorAttributeValueAccess &&) = default;
virtual ~ActorAttributeValueAccess() = default;
ActorAttribute(rpc::ActorAttribute attribute)
: _attribute(std::move(attribute)) {
Validate();
}
ActorAttributeValueAccess & operator= (ActorAttributeValueAccess const & ) = default;
ActorAttributeValueAccess & operator= (ActorAttributeValueAccess && ) = default;
const std::string &GetId() const {
return _attribute.id;
}
virtual const std::string &GetId() const = 0;
rpc::ActorAttributeType GetType() const {
return _attribute.type;
}
const std::vector<std::string> &GetRecommendedValues() const {
return _attribute.recommended_values;
}
bool IsModifiable() const {
return _attribute.is_modifiable;
}
/// Set the value of this attribute.
///
/// @throw InvalidAttributeValue if attribute is not modifiable.
/// @throw InvalidAttributeValue if format does not match this type.
void Set(std::string value);
virtual rpc::ActorAttributeType GetType() const = 0;
/// Cast the value to the given type.
///
@ -96,68 +80,162 @@ namespace client {
return !(*this == rhs);
}
protected:
virtual const std::string &GetValue() const = 0;
void Validate() const;
};
template <>
bool ActorAttributeValueAccess::As<bool>() const;
template <>
int ActorAttributeValueAccess::As<int>() const;
template <>
float ActorAttributeValueAccess::As<float>() const;
template <>
std::string ActorAttributeValueAccess::As<std::string>() const;
template <>
sensor::data::Color ActorAttributeValueAccess::As<sensor::data::Color>() const;
template <>
inline auto ActorAttributeValueAccess::As<rpc::ActorAttributeType::Bool>() const {
return As<bool>();
}
template <>
inline auto ActorAttributeValueAccess::As<rpc::ActorAttributeType::Int>() const {
return As<int>();
}
template <>
inline auto ActorAttributeValueAccess::As<rpc::ActorAttributeType::Float>() const {
return As<float>();
}
template <>
inline auto ActorAttributeValueAccess::As<rpc::ActorAttributeType::String>() const {
return As<std::string>();
}
template <>
inline auto ActorAttributeValueAccess::As<rpc::ActorAttributeType::RGBColor>() const {
return As<sensor::data::Color>();
}
template <typename T>
inline bool ActorAttributeValueAccess::operator==(const T &rhs) const {
return As<T>() == rhs;
}
template <>
inline bool ActorAttributeValueAccess::operator==(const ActorAttributeValueAccess &rhs) const {
return
(GetType() == rhs.GetType()) &&
(GetValue() == rhs.GetValue());
}
class ActorAttributeValue: public ActorAttributeValueAccess {
public:
ActorAttributeValue(rpc::ActorAttributeValue attribute):
_attribute(std::move(attribute))
{
Validate();
}
ActorAttributeValue(ActorAttributeValue const &) = default;
ActorAttributeValue(ActorAttributeValue &&) = default;
virtual ~ActorAttributeValue() = default;
ActorAttributeValue & operator= (ActorAttributeValue const & ) = default;
ActorAttributeValue & operator= (ActorAttributeValue && ) = default;
virtual const std::string &GetId() const override {
return _attribute.id;
}
virtual rpc::ActorAttributeType GetType() const override {
return _attribute.type;
}
/// Serialize this object as a carla::rpc::ActorAttributeValue.
operator rpc::ActorAttributeValue() const{
return _attribute;
}
virtual const std::string &GetValue() const override {
return _attribute.value;
}
private:
rpc::ActorAttributeValue _attribute;
};
template <>
inline bool ActorAttributeValueAccess::operator==(const ActorAttributeValue &rhs) const {
return rhs.operator==(*this);
}
/// An attribute of an ActorBlueprint.
class ActorAttribute: public ActorAttributeValueAccess {
public:
ActorAttribute(rpc::ActorAttribute attribute)
: ActorAttributeValueAccess(),
_attribute(std::move(attribute)) {
Validate();
}
ActorAttribute(ActorAttribute const &) = default;
ActorAttribute(ActorAttribute &&) = default;
virtual ~ActorAttribute() = default;
ActorAttribute & operator= (ActorAttribute const & ) = default;
ActorAttribute & operator= (ActorAttribute && ) = default;
virtual const std::string &GetId() const override {
return _attribute.id;
}
virtual rpc::ActorAttributeType GetType() const override {
return _attribute.type;
}
const std::vector<std::string> &GetRecommendedValues() const {
return _attribute.recommended_values;
}
bool IsModifiable() const {
return _attribute.is_modifiable;
}
/// Set the value of this attribute.
///
/// @throw InvalidAttributeValue if attribute is not modifiable.
/// @throw InvalidAttributeValue if format does not match this type.
void Set(std::string value);
/// Serialize this object as a carla::rpc::ActorAttributeValue.
operator rpc::ActorAttributeValue() const {
return _attribute;
}
protected:
virtual const std::string &GetValue() const override {
return _attribute.value;
}
private:
void Validate() const;
rpc::ActorAttribute _attribute;
};
template <>
bool ActorAttribute::As<bool>() const;
template <>
int ActorAttribute::As<int>() const;
template <>
float ActorAttribute::As<float>() const;
template <>
std::string ActorAttribute::As<std::string>() const;
template <>
sensor::data::Color ActorAttribute::As<sensor::data::Color>() const;
template <>
inline auto ActorAttribute::As<rpc::ActorAttributeType::Bool>() const {
return As<bool>();
}
template <>
inline auto ActorAttribute::As<rpc::ActorAttributeType::Int>() const {
return As<int>();
}
template <>
inline auto ActorAttribute::As<rpc::ActorAttributeType::Float>() const {
return As<float>();
}
template <>
inline auto ActorAttribute::As<rpc::ActorAttributeType::String>() const {
return As<std::string>();
}
template <>
inline auto ActorAttribute::As<rpc::ActorAttributeType::RGBColor>() const {
return As<sensor::data::Color>();
}
template <typename T>
inline bool ActorAttribute::operator==(const T &rhs) const {
return As<T>() == rhs;
}
template <>
inline bool ActorAttribute::operator==(const ActorAttribute &rhs) const {
return
(_attribute.type == rhs._attribute.type) &&
(_attribute.value == rhs._attribute.value);
inline bool ActorAttributeValueAccess::operator==(const ActorAttribute &rhs) const {
return rhs.operator==(*this);
}
} // namespace client

View File

@ -56,9 +56,7 @@ namespace client {
description.id = _id;
description.attributes.reserve(_attributes.size());
for (const auto &attribute : *this) {
if (attribute.IsModifiable()) {
description.attributes.push_back(attribute);
}
description.attributes.push_back(attribute);
}
return description;
}

View File

@ -20,6 +20,18 @@ namespace client {
: _episode(std::move(episode)),
_actors(std::make_move_iterator(actors.begin()), std::make_move_iterator(actors.end())) {}
SharedPtr<Actor> ActorList::GetActor(actor_id_type const actor_id) const
{
for (auto &actor: _actors)
{
if (actor_id == actor.GetId())
{
return actor.Get(_episode, shared_from_this());
}
}
return nullptr;
}
ActorList ActorList::Filter(const std::string &wildcard_pattern) const {
ActorList filtered{_episode, {}};
for (auto &&actor : _actors) {

View File

@ -21,7 +21,7 @@ namespace client {
template <typename It>
auto MakeIterator(It it) const {
return boost::make_transform_iterator(it, [this](auto &v) {
return v.Get(_episode);
return v.Get(_episode, shared_from_this());
});
}
@ -31,11 +31,11 @@ namespace client {
ActorList Filter(const std::string &wildcard_pattern) const;
SharedPtr<Actor> operator[](size_t pos) const {
return _actors[pos].Get(_episode);
return _actors[pos].Get(_episode, shared_from_this());
}
SharedPtr<Actor> at(size_t pos) const {
return _actors.at(pos).Get(_episode);
return _actors.at(pos).Get(_episode, shared_from_this());
}
auto begin() const {
@ -54,6 +54,8 @@ namespace client {
return _actors.size();
}
SharedPtr<Actor> GetActor(actor_id_type const actor_id) const;
private:
friend class World;

View File

@ -7,6 +7,7 @@
#include "carla/client/detail/ActorState.h"
#include <string>
#include <iterator>
namespace carla {
namespace client {
@ -25,7 +26,9 @@ namespace detail {
"Actor "s +
std::to_string(desc.id) +
" (" + desc.description.id + ')';
}(_description)) {}
}(_description)),
_attributes(_description.description.attributes.begin(), _description.description.attributes.end())
{}
} // namespace detail
} // namespace client

View File

@ -8,6 +8,7 @@
#include "carla/NonCopyable.h"
#include "carla/client/World.h"
#include "carla/client/ActorAttribute.h"
#include "carla/client/detail/EpisodeProxy.h"
#include "carla/rpc/Actor.h"
@ -49,6 +50,11 @@ namespace detail {
return World{_episode};
}
const std::vector<ActorAttributeValue> &GetAttributes() const
{
return _attributes;
}
protected:
const rpc::Actor &GetActorDescription() const {
@ -79,6 +85,8 @@ namespace detail {
SharedPtr<Actor> _parent;
std::string _display_id;
std::vector<ActorAttributeValue> _attributes;
};
} // namespace detail

View File

@ -7,16 +7,24 @@
#include "carla/client/detail/ActorVariant.h"
#include "carla/client/detail/ActorFactory.h"
#include "carla/client/ActorList.h"
namespace carla {
namespace client {
namespace detail {
void ActorVariant::MakeActor(EpisodeProxy episode) const {
void ActorVariant::MakeActor(EpisodeProxy episode, SharedPtr<const client::ActorList> actor_list) const {
auto const parent_id = GetParentId();
SharedPtr<client::Actor> parent = nullptr;
if ( (actor_list != nullptr) && (parent_id != 0) )
{
// in case we have an actor list as context, we are able to actually create the parent actor
parent = actor_list->GetActor(parent_id);
}
_value = detail::ActorFactory::MakeActor(
episode,
boost::get<rpc::Actor>(std::move(_value)),
nullptr, /// @todo We need to create the parent too.
parent,
GarbageCollectionPolicy::Disabled);
}

View File

@ -38,9 +38,9 @@ namespace detail {
return *this;
}
SharedPtr<client::Actor> Get(EpisodeProxy episode) const {
SharedPtr<client::Actor> Get(EpisodeProxy episode, SharedPtr<const client::ActorList> actor_list = nullptr) const {
if (_value.which() == 0u) {
MakeActor(episode);
MakeActor(episode, actor_list);
}
DEBUG_ASSERT(_value.which() == 1u);
return boost::get<SharedPtr<client::Actor>>(_value);
@ -54,6 +54,10 @@ namespace detail {
return Serialize().id;
}
actor_id_type GetParentId() const {
return Serialize().parent_id;
}
const std::string &GetTypeId() const {
return Serialize().description.id;
}
@ -72,12 +76,12 @@ namespace detail {
const rpc::Actor &operator()(const rpc::Actor &actor) const {
return actor;
}
const rpc::Actor &operator()(const SharedPtr<client::Actor> &actor) const {
const rpc::Actor &operator()(const SharedPtr<const client::Actor> &actor) const {
return actor->Serialize();
}
};
void MakeActor(EpisodeProxy episode) const;
void MakeActor(EpisodeProxy episode, SharedPtr<const client::ActorList> actor_list) const;
mutable boost::variant<rpc::Actor, SharedPtr<client::Actor>> _value;
};

View File

@ -24,6 +24,8 @@ namespace rpc {
actor_id_type id = 0u;
actor_id_type parent_id;
ActorDescription description;
geom::BoundingBox bounding_box;
@ -49,7 +51,7 @@ namespace rpc {
/// @}
MSGPACK_DEFINE_ARRAY(id, description, bounding_box, semantic_tags, stream_token);
MSGPACK_DEFINE_ARRAY(id, parent_id, description, bounding_box, semantic_tags, stream_token);
};
} // namespace rpc

View File

@ -133,6 +133,7 @@ class World(object):
blueprint = self._get_random_blueprint()
spawn_points = self.world.get_map().get_spawn_points()
spawn_point = random.choice(spawn_points) if spawn_points else carla.Transform()
blueprint.set_attribute('role_name', 'hero')
self.vehicle = self.world.spawn_actor(blueprint, spawn_point)
self.collision_sensor = CollisionSensor(self.vehicle, self.hud)
self.lane_invasion_sensor = LaneInvasionSensor(self.vehicle, self.hud)
@ -150,6 +151,8 @@ class World(object):
start_pose.rotation.roll = 0.0
start_pose.rotation.pitch = 0.0
blueprint = self._get_random_blueprint()
blueprint.set_attribute('role_name', 'hero')
self.destroy()
self.vehicle = self.world.spawn_actor(blueprint, start_pose)
self.collision_sensor = CollisionSensor(self.vehicle, self.hud)

View File

@ -47,6 +47,13 @@ void export_actor() {
.add_property("parent", CALL_RETURNING_COPY(cc::Actor, GetParent))
.add_property("semantic_tags", &GetSemanticTags)
.add_property("is_alive", CALL_RETURNING_COPY(cc::Actor, IsAlive))
.add_property("attributes", +[](const cc::Actor &self) {
boost::python::dict atttribute_dict;
for (auto &&attribute_value : self.GetAttributes()) {
atttribute_dict[attribute_value.GetId()] = attribute_value.GetValue();
}
return atttribute_dict;
})
.def("get_world", CALL_RETURNING_COPY(cc::Actor, GetWorld))
.def("get_location", &cc::Actor::GetLocation)
.def("get_transform", &cc::Actor::GetTransform)

View File

@ -77,6 +77,7 @@ def main():
if blueprint.has_attribute('color'):
color = random.choice(blueprint.get_attribute('color').recommended_values)
blueprint.set_attribute('color', color)
blueprint.set_attribute('role_name', 'autopilot')
vehicle = world.try_spawn_actor(blueprint, transform)
if vehicle is not None:
actor_list.append(vehicle)

View File

@ -192,6 +192,30 @@ static void FillIdAndTags(FActorDefinition &Def, TStrs &&... Strings)
{
Def.Id = JoinStrings(TEXT("."), std::forward<TStrs>(Strings)...).ToLower();
Def.Tags = JoinStrings(TEXT(","), std::forward<TStrs>(Strings)...).ToLower();
// each actor gets an actor role name attribute (empty by default)
FActorVariation ActorRole;
ActorRole.Id = TEXT("role_name");
ActorRole.Type = EActorAttributeType::String;
ActorRole.RecommendedValues = { TEXT("default") };
ActorRole.bRestrictToRecommended = false;
Def.Variations.Emplace(ActorRole);
}
static void AddRecommendedValuesForActorRoleName(FActorDefinition &Definition, TArray<FString> &&RecommendedValues)
{
for (auto &&ActorVariation: Definition.Variations)
{
if ( ActorVariation.Id == "role_name" )
{
ActorVariation.RecommendedValues = RecommendedValues;
return;
}
}
}
static void AddRecommendedValuesForSensorRoleNames(FActorDefinition &Definition)
{
AddRecommendedValuesForActorRoleName(Definition, {TEXT("front"), TEXT("back"), TEXT("left"), TEXT("right"), TEXT("front_left"), TEXT("front_right"), TEXT("back_left"), TEXT("back_right")});
}
FActorDefinition UActorBlueprintFunctionLibrary::MakeGenericSensorDefinition(
@ -200,6 +224,7 @@ FActorDefinition UActorBlueprintFunctionLibrary::MakeGenericSensorDefinition(
{
FActorDefinition Definition;
FillIdAndTags(Definition, TEXT("sensor"), Type, Id);
AddRecommendedValuesForSensorRoleNames(Definition);
return Definition;
}
@ -221,6 +246,7 @@ void UActorBlueprintFunctionLibrary::MakeCameraDefinition(
FActorDefinition &Definition)
{
FillIdAndTags(Definition, TEXT("sensor"), TEXT("camera"), Id);
AddRecommendedValuesForSensorRoleNames(Definition);
// FOV.
FActorVariation FOV;
FOV.Id = TEXT("fov");
@ -239,7 +265,7 @@ void UActorBlueprintFunctionLibrary::MakeCameraDefinition(
ResY.RecommendedValues = { TEXT("600") };
ResY.bRestrictToRecommended = false;
Definition.Variations = {ResX, ResY, FOV};
Definition.Variations.Append({ResX, ResY, FOV});
if (bEnableModifyingPostProcessEffects)
{
@ -270,6 +296,7 @@ void UActorBlueprintFunctionLibrary::MakeLidarDefinition(
FActorDefinition &Definition)
{
FillIdAndTags(Definition, TEXT("sensor"), TEXT("lidar"), Id);
AddRecommendedValuesForSensorRoleNames(Definition);
// Number of channels.
FActorVariation Channels;
Channels.Id = TEXT("channels");
@ -301,7 +328,7 @@ void UActorBlueprintFunctionLibrary::MakeLidarDefinition(
LowerFOV.Type = EActorAttributeType::Float;
LowerFOV.RecommendedValues = { TEXT("-30.0") };
Definition.Variations = {Channels, Range, PointsPerSecond, Frequency, UpperFOV, LowerFOV};
Definition.Variations.Append({Channels, Range, PointsPerSecond, Frequency, UpperFOV, LowerFOV});
Success = CheckActorDefinition(Definition);
}
@ -313,6 +340,7 @@ void UActorBlueprintFunctionLibrary::MakeVehicleDefinition(
{
/// @todo We need to validate here the params.
FillIdAndTags(Definition, TEXT("vehicle"), Parameters.Make, Parameters.Model);
AddRecommendedValuesForActorRoleName(Definition, {TEXT("autopilot"), TEXT("scenario"), TEXT("ego_vehicle")});
Definition.Class = Parameters.Class;
if (Parameters.RecommendedColors.Num() > 0)
{
@ -326,6 +354,12 @@ void UActorBlueprintFunctionLibrary::MakeVehicleDefinition(
}
Definition.Variations.Emplace(Colors);
}
Definition.Attributes.Emplace(FActorAttribute{
TEXT("object_type"),
EActorAttributeType::String,
Parameters.ObjectType});
Definition.Attributes.Emplace(FActorAttribute{
TEXT("number_of_wheels"),
EActorAttributeType::Int,

View File

@ -34,6 +34,9 @@ struct CARLA_API FVehicleParameters
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 NumberOfWheels = 4;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString ObjectType;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<FColor> RecommendedColors;
};

View File

@ -133,6 +133,7 @@ public:
Actor.id = ActorView.GetActorId();
if (ActorView.IsValid())
{
Actor.parent_id = Episode->GetActorRegistry().Find(ActorView.GetActor()->GetOwner()).GetActorId();
Actor.description = *ActorView.GetActorDescription();
Actor.bounding_box = GetActorBoundingBox(*ActorView.GetActor());
Actor.semantic_tags.reserve(ActorView.GetSemanticTags().Num());