-
Notifications
You must be signed in to change notification settings - Fork 1
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
[sw-2167] fix inconsistent specification of arm upon upload animation #20
base: main
Are you sure you want to change the base?
[sw-2167] fix inconsistent specification of arm upon upload animation #20
Conversation
fixed indentation
…D's spot-sdk choreo client to fail to upload animation
…is operating on self as opposed to a temp copy
@@ -519,6 +520,25 @@ def validate(self) -> Tuple[bool, str]: | |||
return False, f"Keyframe timestamps must be monotonically increasing. Error at keyframe {idx}" | |||
last_time_seen = keyframe.time | |||
|
|||
necessary_joint_angles_fields = ["shoulder_0", "shoulder_1", "elbow_0", "elbow_1", "wrist_0", "wrist_1"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As written this will probably cause issues when playing animations on a spot without an arm. It would also be good to keep this function as a pure validation check, and do the fix in a separate function like _remove_duplicate_timestamp.
Since the BD requirement is any joint angle that exists in one keyframe must exist in all keyframes, you could grab the set of keys in the first keyframe, and then for every other frame you can fail if there are any missing/extra keys.
To do the automatic fix you could do it in two loops (since validation check is fast no major need to that optimize this to a single loop)
- Loop 1, collect a set of joint_angle fields that exist in the animation (similar to what you have hardcoded here)
- Loop 2, for any fields that are missing in any keyframe, set to near-zero value
Some links that might be relevant:
Lines 282 to 302 in 105dbd9
def flatten_keyframe_to_dictionary(keyframe: AnimationKeyframe) -> dict[str, float]: | |
""" | |
Recursively walks through the keyframe to extract all of the set values | |
""" | |
keyframe_to_proto_attrs = joint_angle_to_protobuf_attrs_map() | |
flattened_map = {} | |
for animation_name, nested_proto_attrs in keyframe_to_proto_attrs.items(): | |
active_proto = keyframe | |
extraction_success = True | |
for attribute in nested_proto_attrs: | |
if check_if_protobuf_field_set(active_proto, attribute): | |
active_proto = getattr(active_proto, attribute) | |
else: | |
extraction_success = False | |
if extraction_success: | |
flattened_map[animation_name] = active_proto | |
else: | |
flattened_map[animation_name] = 0 | |
return flattened_map |
https://github.com/bdaiinstitute/spot_choreo_utils/blob/main/spot_choreo_utils/spot_choreo_utils/choreo_creation/choreo_builders/spot_properties.py#L40-L71
Lines 20 to 26 in 105dbd9
def protobuf_zero_omission_check(value: float) -> bool: | |
""" | |
Returns whether the value would be omitted by protobuf when sent over the wire | |
""" | |
if value == 0.0 and math.copysign(1, value) == 1.0: | |
return True | |
return False |
Lines 196 to 226 in 105dbd9
def perform_keyframe_interpolation( | |
start: AnimationKeyframe, | |
stop: AnimationKeyframe, | |
modified: AnimationKeyframe, | |
percent: float, | |
logger: Optional[Logger] = None, | |
) -> bool: | |
"""Interpolates between two animation keyframes by the requested percent""" | |
is_protobuf = hasattr(start, "ListFields") | |
if is_protobuf: | |
properties = [descriptor.name for descriptor, _ in start.ListFields()] | |
for property in properties: | |
start_ref = getattr(start, property, None) | |
stop_ref = getattr(stop, property, None) | |
modified_ref = getattr(modified, property, None) | |
result = perform_keyframe_interpolation(start_ref, stop_ref, modified_ref, percent) | |
# If interpolation not performed at nested level, use set attribute at this | |
# level to update the interpolation | |
if not result: | |
if isinstance(stop_ref, float) and isinstance(start_ref, float): | |
interpolated_value = (stop_ref - start_ref) * percent + start_ref | |
elif isinstance(stop_ref, bool) and isinstance(start_ref, bool): | |
interpolated_value = start_ref | |
else: | |
if logger: | |
logger.error(f"Unknown type {type(stop_ref)} for interpolation") | |
raise AssertionError() | |
setattr(modified, property, interpolated_value) | |
return is_protobuf |
Also, Gripper values need to be between -1 and 0, so for that field you have to set -1e-6 instead of 1e-6
https://github.com/bdaiinstitute/spot_choreo_utils/blob/main/spot_choreo_utils/spot_choreo_utils/choreo_creation/choreo_builders/animation_builder.py#L467
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks for this! I am taking a look now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed most of this here @kkarol-bdai
The scope of this issue is just the joint_angles fields of the "arm" field, and the gripper_angle is part of its own "gripper" field. If you think we should include that as well, though, let me know!
spot_choreo_utils/spot_choreo_utils/choreo_creation/choreo_builders/animation_builder.py
Show resolved
Hide resolved
spot_choreo_utils/spot_choreo_utils/choreo_creation/choreo_builders/animation_builder.py
Outdated
Show resolved
Hide resolved
Alright, I redid the implementation to to find and fill in all fields, not just arm joint_angle fields. Two things to note:
Any other thoughts and suggestions welcome! |
@@ -281,6 +281,8 @@ def extract_paramaters(body_section: str) -> Optional[Dict[str, float]]: | |||
# Set the Keyframe time | |||
if "start_time" in keyframe_angles.keys(): | |||
keyframe_proto.time = keyframe_angles["start_time"] | |||
if "time" in keyframe_angles.keys(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are this and the spot_properties updates to account for naming in the web_animator? Could we update the web animator to use start_time instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The update to spot_properties I believe was because there previously was no "time" field returned in the attribute map, which would propagate through until joint_angle_to_keyframe_proto() where the lack of a correct "time" field would cause the keyframe to be skipped altogether
# Set the Keyframe time if "start_time" in keyframe_angles.keys(): keyframe_proto.time = keyframe_angles["start_time"] if "time" in keyframe_angles.keys(): keyframe_proto.time = keyframe_angles["time"] elif start_time is not None: keyframe_proto.time = start_time else: print("No start time specified, skipping joint angle keyframe") return None
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the reason I had to include "time" was because that is what spot-sdk's AnimationKeyframe currently uses: https://dev.bostondynamics.com/protos/bosdyn/api/proto_reference.html#bosdyn-api-spot-AnimationKeyframe.
I can try to go through web_animator and choreo_utils and rename all "start_time" to "time", would that work?
spot_choreo_utils/spot_choreo_utils/choreo_creation/choreo_builders/animation_builder.py
Show resolved
Hide resolved
spot_choreo_utils/spot_choreo_utils/choreo_creation/choreo_builders/animation_builder.py
Outdated
Show resolved
Hide resolved
spot_choreo_utils/spot_choreo_utils/choreo_creation/choreo_builders/animation_builder.py
Show resolved
Hide resolved
Looking good, added a couple final cleanup questions.
|
@kkarol-bdai To answer your questions:
I will say, the AnimationKeyframe type is defined to always have an arm field. Do you think I should make it so that we fill in the arm fields with 1e-06 iff this is an armed spot? if so, it is not immediately clear to me what is the best way to check if this is an armed spot; perhaps if at least one arm field in one keyframe is non-zero we can fill in, otherwise omit. What do you think? |
|
Previously, animation uploads to BD's choreo client would occasionally fail as the animation keyframe protobuf would omit arm joint_angles fields (probably due to value = 0). In this PR, I add a step in AnimationBuilder validate() which fills in missing joint_angle fields in the protobuf, ensuring that this issue won't occur so long as you build() the animation prior to uploading.
One point of note is that in the build() step, I moved the validate() to be prior to the
output_animation = copy.deepcopy(self._animation)
so that my code that fixes the omitted pb fields actually affects the animation that is outputted by build().Additionally, while this fixes the Omitted joint angle issue, it does not verify all of the other non-joint angle fields. Though I haven't yet encountered issues with those fields, they may be worth validating further down the road.