Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make urdf plugable and revive urdf_parser_plugin #13

Merged
merged 36 commits into from
Sep 18, 2020

Conversation

sloretz
Copy link

@sloretz sloretz commented Jul 15, 2020

This makes the urdf package load parser plugins, so it can use collada or sdformat parser plugins in the future.
It also makes the urdf xml parser a plugin instead of a special case.

A new api might_handle() returns a score for how likely it is a given string is meant for the plugin.
Right now the implementation is very simple, and a bit fragile.
The URDF plugin uses the position of the text <robot as the score.

This breaks ABI with urdf::Model because it now stores the class
loader instance, so everything downstream of this change must be rebuilt.

@scpeters @IanTheEngineer FYI

Requires ros/pluginlib#199

This makes the urdf package load parser plugins.
It includes a new api might_handle() that returns a score for how likely
the plugin is to be the one the given file format is meant for.

This change also makes the urdf xml parser a plugin instead of a special
case that is called directly.

This breaks ABI with urdf::Model because the model now stores the class
loader instance.

Signed-off-by: Shane Loretz<sloretz@openrobotics.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
@ros-discourse
Copy link

This pull request has been mentioned on ROS Discourse. There might be relevant details there:

https://discourse.ros.org/t/ros-2-tsc-meeting-minutes-2020-07-16/15468/1

Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Copy link

@clalancette clalancette left a comment

Choose a reason for hiding this comment

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

I did a first pass of review, please take a look.

This package looks like it needs a lot of cleanup work in addition to what you've done here. It's all orthogonal, but while we are looking, can you open up follow-up issues to deal with:

Thanks.

urdf/include/urdf/model.h Outdated Show resolved Hide resolved
urdf/package.xml Outdated Show resolved Hide resolved
urdf/src/model.cpp Show resolved Hide resolved
urdf/src/model.cpp Show resolved Hide resolved
urdf/src/model.cpp Outdated Show resolved Hide resolved
urdf/src/urdf_plugin.cpp Show resolved Hide resolved
urdf_parser_plugin/CMakeLists.txt Outdated Show resolved Hide resolved
urdf_parser_plugin/package.xml Outdated Show resolved Hide resolved
urdf_parser_plugin/package.xml Outdated Show resolved Hide resolved
sloretz and others added 13 commits August 5, 2020 13:36
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>

Co-authored-by: Chris Lalancette <clalancette@openrobotics.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
@sloretz
Copy link
Author

sloretz commented Aug 5, 2020

@clalancette I've addressed all comments :) . Mind having a second look?

urdf/package.xml Outdated Show resolved Hide resolved
urdf/src/urdf_plugin.cpp Outdated Show resolved Hide resolved
urdf_parser_plugin/include/urdf_parser_plugin/parser.h Outdated Show resolved Hide resolved
urdf_parser_plugin/package.xml Outdated Show resolved Hide resolved
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
@sloretz
Copy link
Author

sloretz commented Aug 19, 2020

CI one more time after (hopefully) fixing test issue on OSX (build: --packages-up-to urdf urdf_parser_plugin test: --packages-select urdf urdf_parser_plugin)

  • Linux Build Status
  • Linux-aarch64 Build Status
  • macOS Build Status
  • Windows Build Status

Fixes OSX failing build
ros/pluginlib#200

Signed-off-by: Shane Loretz<sloretz@openrobotics.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
@sloretz
Copy link
Author

sloretz commented Aug 19, 2020

CI again, this time I'm sure I've fixed the OSX issue

  • Linux Build Status
  • Linux-aarch64 Build Status
  • macOS Build Status
  • Windows Build Status

@sloretz
Copy link
Author

sloretz commented Aug 19, 2020

@clalancette Mind having a look at the changes since your last review? 🙇

Copy link

@clalancette clalancette left a comment

Choose a reason for hiding this comment

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

One question here about the performance test.

Comment on lines 90 to 113
# Write files to enable pluginlib to find urdf_xml_parser in build folder
set(fake_urdf_prefix "${CMAKE_CURRENT_BINARY_DIR}/fake_urdf_install")
# Copy urdf_xml_parser to fake prefix lib/ and bin/
file(MAKE_DIRECTORY "${fake_urdf_prefix}/lib")
file(MAKE_DIRECTORY "${fake_urdf_prefix}/bin")
add_custom_command(TARGET urdf_xml_parser
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:urdf_xml_parser>
"${fake_urdf_prefix}/lib/"
)
add_custom_command(TARGET urdf_xml_parser
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:urdf_xml_parser>
"${fake_urdf_prefix}/bin/"
)
# Fake install plugin description file
file(COPY "urdf_parser_description.xml" DESTINATION "${fake_urdf_prefix}/share/urdf/")
# Fake install package.xml
configure_file("package.xml" "${fake_urdf_prefix}/share/urdf/package.xml" COPYONLY)
# Fake install entry in ament_index telling pluginlib where to find plugin description
file(WRITE "${fake_urdf_prefix}/share/ament_index/resource_index/urdf_parser_plugin__pluginlib__plugin/urdf"
"share/urdf/urdf_parser_description.xml\n")
# Fake install entry in ament_index declaring this a package
file(WRITE "${fake_urdf_prefix}/share/ament_index/resource_index/packages/urdf" "")

Choose a reason for hiding this comment

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

Well, yuck. If I'm understanding this properly, this is more-or-less recreating the install directory structure under build. I guess its OK, but it seems pretty fragile to me; if we change the ament structure for any reason, this is going to break.

How valuable do you think this performance test is? I'm wondering if it would be more straightforward to not include it at all.

Copy link
Author

Choose a reason for hiding this comment

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

Well, yuck. If I'm understanding this properly, this is more-or-less recreating the install directory structure under build. I guess its OK, but it seems pretty fragile to me; if we change the ament structure for any reason, this is going to break.

Yeah. It will break if how pluginlib uses the ament index is changed. I copied this logic from pluginlib's own tests. A cmake function provided by pluginlib that implemented this would eliminate the duplication if something does change

https://github.com/ros/pluginlib/blob/7c3e7d5b7af6d776079e2e9cba2ac9a5581777b0/pluginlib/CMakeLists.txt#L86-L94

How valuable do you think this performance test is? I'm wondering if it would be more straightforward to not include it at all.

It's straight forward to delete it, though it does give valuable info about the overhead of the plugin infrastructure, and it does prove the plugin infrastructure works. I would rather robustify it than delete it.

Choose a reason for hiding this comment

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

That's a valid point @clalancette, these performance tests we just a guide to make sure that the new plugin-based file type check weren't introducing large amounts of overhead. While no one should be parsing URDF/SDF inside their control loop, we don't want this plugin structure to have a substantial impact on existing code execution times.

I think it would be fine to refrain from including this install deployment. This code was more-or-less intended as a one-off verification that the plugin checking times were reasonable. @sloretz can you recap the results of the tests here? Would they be impacted at all by the size of the URDF/SDFormat file being parsed?

Copy link
Author

Choose a reason for hiding this comment

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

I think it would be fine to refrain from including this install deployment. This code was more-or-less intended as a one-off verification that the plugin checking times were reasonable.

I would recommend keeping it as it's the only test that makes sure the urdf plugin is loadable.

@sloretz can you recap the results of the tests here?

Linux amd64

  • direct - 0.015 ms
  • plugin - 0.743 ms

Linux aarch64

  • direct - 0.045 ms
  • plugin - 3.009 ms

OSX

  • direct - 0.012 ms
  • plugin - 2.371 ms

Windows

  • direct - 0.017 ms
  • plugin - 1.970 ms

Would they be impacted at all by the size of the URDF/SDFormat file being parsed?

Yes since cb348db asks tinyxml2 to parse the whole document to decide the confidence. See discussion on #13 (comment) . The size dependent overhead would apply times every plugin with a similar score plugin - ros/sdformat_urdf#1 (comment)

Copy link
Author

Choose a reason for hiding this comment

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

3341988 uses a new pluginlib CMake function that enables testing plugins in the same package that built them: ros/pluginlib#201

Copy link

@IanTheEngineer IanTheEngineer left a comment

Choose a reason for hiding this comment

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

I think this is a valuable addition for the community @sloretz. Thanks for the PR. I'm sure @gavanderhoorn would appreciate the fact that you implemented his confidence suggestion from the original SDFormat support proposal: ros#34 (comment)

Comment on lines 90 to 113
# Write files to enable pluginlib to find urdf_xml_parser in build folder
set(fake_urdf_prefix "${CMAKE_CURRENT_BINARY_DIR}/fake_urdf_install")
# Copy urdf_xml_parser to fake prefix lib/ and bin/
file(MAKE_DIRECTORY "${fake_urdf_prefix}/lib")
file(MAKE_DIRECTORY "${fake_urdf_prefix}/bin")
add_custom_command(TARGET urdf_xml_parser
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:urdf_xml_parser>
"${fake_urdf_prefix}/lib/"
)
add_custom_command(TARGET urdf_xml_parser
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:urdf_xml_parser>
"${fake_urdf_prefix}/bin/"
)
# Fake install plugin description file
file(COPY "urdf_parser_description.xml" DESTINATION "${fake_urdf_prefix}/share/urdf/")
# Fake install package.xml
configure_file("package.xml" "${fake_urdf_prefix}/share/urdf/package.xml" COPYONLY)
# Fake install entry in ament_index telling pluginlib where to find plugin description
file(WRITE "${fake_urdf_prefix}/share/ament_index/resource_index/urdf_parser_plugin__pluginlib__plugin/urdf"
"share/urdf/urdf_parser_description.xml\n")
# Fake install entry in ament_index declaring this a package
file(WRITE "${fake_urdf_prefix}/share/ament_index/resource_index/packages/urdf" "")

Choose a reason for hiding this comment

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

That's a valid point @clalancette, these performance tests we just a guide to make sure that the new plugin-based file type check weren't introducing large amounts of overhead. While no one should be parsing URDF/SDF inside their control loop, we don't want this plugin structure to have a substantial impact on existing code execution times.

I think it would be fine to refrain from including this install deployment. This code was more-or-less intended as a one-off verification that the plugin checking times were reasonable. @sloretz can you recap the results of the tests here? Would they be impacted at all by the size of the URDF/SDFormat file being parsed?

urdf/include/urdf/model.h Outdated Show resolved Hide resolved
urdf/src/model.cpp Outdated Show resolved Hide resolved
urdf/src/model.cpp Show resolved Hide resolved
urdf/src/model.cpp Show resolved Hide resolved
/// \brief Indicate if data is meant to be parsed by this parser
/// \return confidence that the data is meant for this parser, where smaller
/// values mean more confidence, 0 is absolute certainty, and
/// std::numeric_limits<size_t>::max() is no confidence.

Choose a reason for hiding this comment

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

My intuition would be that a score of 1.0 is absolute certainty and 0.0 is no confidence (treating this as a probability), but that would require floating point comparisons. Is there a reason to avoid floating point?

I would have trouble thinking what a score like 1000 would mean in this context. To back out a probability from this score would be something like 1-(x/std::numeric_limits<size_t>::max()) and plugging in our hypothetical 1000 in for x?

Copy link
Author

@sloretz sloretz Aug 19, 2020

Choose a reason for hiding this comment

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

Is there a reason to avoid floating point?

No reason to avoid floating point.

(treating this as a probability)

I don't think the score can be treated as a probability. One could take a bunch of robot descriptions, run them through the might_handle() function, and count the number of URDF or non-URDF files in a range of scores. Then one could say "documents that scored between 29 and 42 were URDF files 50% of the time".

I would have trouble thinking what a score like 1000 would mean in this context.

I don't think 1000 would mean anything on its own; it only has meaning when compared against other scores. If someone scores 10 free throws in a minute, are they an NBA basketball player? I don't think that has an answer. Now say there are two people and one scores 10 free throws in a minute while the other scores 2 - which one is more likely to be an NBA player? The latter is more like what's happening here (with a caveat that the free throw test isn't the same for each plugin since each has it's own might_handle() implementation).

Choose a reason for hiding this comment

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

(with a caveat that the free throw test isn't the same for each plugin since each has it's own might_handle() implementation).

I think this is where the intuition for a probability comes in. What seems most likely is that plugins are either going to score a 0 for "yes" or std::numeric_limits<size_t>::max() for "no", at which point anything in between will be a best guess.

Copy link
Author

Choose a reason for hiding this comment

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

What seems most likely is that plugins are either going to score a 0 for "yes" or std::numeric_limits<size_t>::max() for "no", at which point anything in between will be a best guess.

Yeah, the scores less than std::numeric_limits<size_t>::max() are a bit arbitrary. Maybe it would be more intuitive if the documentation was "return the position in the string where you became sure this document was for you"?

Choose a reason for hiding this comment

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

I think this is where the intuition for a probability comes in. What seems most likely is that plugins are either going to score a 0 for "yes" or std::numeric_limits<size_t>::max() for "no", at which point anything in between will be a best guess.

Yeah, and not only that, the writer of a new plugin won't know whether they'll be scored "better" than the other plugins without reading all other available plugins. In which case, this regresses towards the binary 0/1 . I'm now rethinking the whole plan to return anything but a boolean :). That being said, it isn't something I would block on, though a more constrained range and/or better documentation on what exactly to return would be helpful.

Copy link
Author

Choose a reason for hiding this comment

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

69b3d49 defines the score range being the position in the document the parser became confident the format was for it, and 025f816 makes the urdf plugin return data.size() instead of the max size_t value.

From offline conversation on this topic, 31014c0 makes urdf::Model try the urdf plugin anyways if no plugin things the file is for it.

Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz<sloretz@openrobotics.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Copy link

@clalancette clalancette left a comment

Choose a reason for hiding this comment

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

I've got a few more open comments.


namespace urdf
{
class ModelImplementation

Choose a reason for hiding this comment

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

Nit: this can be a final class, since nothing will be deriving from it.

Copy link
Author

Choose a reason for hiding this comment

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

Final in 06474e8

ROS_ERROR_STREAM("Exception while creating planning plugin loader " << ex.what() << ". Will not parse Collada file.");
}
size_t best_score = std::numeric_limits<size_t>::max();
pluginlib::UniquePtr<urdf::URDFParser> best_plugin;

Choose a reason for hiding this comment

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

There is some possibility that no plugin matches, in which case we wouldn't assign anything to best_plugin until after the for loop. I think the "no confidence" logic below that currently prevents us from accessing anything bad, but I'd still feel better if we explicitly assigned this to nullptr here.

Copy link
Author

Choose a reason for hiding this comment

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

Explicit initialize to nullptr in f9fe6f9

urdf/src/model.cpp Show resolved Hide resolved
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
@sloretz
Copy link
Author

sloretz commented Sep 18, 2020

@clalancette May I ask for another look? There are new changes since your last review 🙇

@sloretz
Copy link
Author

sloretz commented Sep 18, 2020

CI (build: --packages-up-to urdf urdf_parser_plugin test: --packages-select urdf urdf_parser_plugin)

  • Linux Build Status
  • Linux-aarch64 Build Status
  • macOS Build Status
  • Windows Build Status

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants