-
Notifications
You must be signed in to change notification settings - Fork 1
Pose distance metrics from Ham2Pose #4
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
Pose distance metrics from Ham2Pose #4
Conversation
Oh yes, when accepted this will fix #3 |
Spent the day messing around with this, thinking about how to implement this. Essentially the Ham2Pose Metrics https://github.com/J22Melody/iict-eval-private/blob/text2pose/metrics/metrics.py#L94 have the following variables, various combinations of which constitute different metrics
Also, a number of these work along joint-point trajectories. For example sequence of thumb positions from hyp and ref. For this, the pose.body.points_perspective() (https://github.com/sign-language-processing/pose/blob/master/src/python/pose_format/pose_body.py) function seems useful. For example it makes APE easier to implement: https://github.com/rotem-shalev/Ham2Pose/blob/25a5cd7221dfb81a24088e4f38bca868d8e896fc/metrics.py#L101 A couple conundrums to solve:
|
A base class could contain many optional basic pose operations (normalize, pad, etc.), but at the moment, a In general, I lean towards whatever can be quickly built. Then, we can optimize as we use it and develop other metrics. We will know better what should be shared among classes and what not. Let's see @AmitMY's opinions. |
I think a base metric, with options, is a good idea. class DTW_MJE(DistanceMetric):
def __init__():
super().__init__(normalize=False, hide_legs=True, etc...) That way, we can have a generic approach, but still "keep" the old names through basic inheritance. It will also allow creating all possible setups of Finally, maybe it will be a really good section of writing: Everyone is inventing metrics left and right, giving them names, there are so many metrics with the same name, etc. We go with the |
OK, it's still very WIP, but I pushed some changes along the lines of making this more like sacrebleu:
TODO: something similar for distancecalculators, so they also have a str |
I'm trying to think about how to organize and implement the Distance Calculations in a good way so that we can generate the signatures consistently. And I'm trying to understand the implementations from ham2pose. It seems there's two main distance categories:
For example, Average Position Error simply subtracts the two trajectories from each other (absolute difference), and then takes the mean. It could have a signature like:
But notionally you could have a "distance" that didn't do a point-distance. For example you could imagine a (terrible) Distance which would just be DifferenceOfSums
In which case I would want the signature to be something like I don't think we need to worry about a case where there's no traj distance. The whole point here is that we put in two pose trajectories and get one float out. I think the answer here is to have a separate DistanceCalculator class that knows its own signature, so that we can have an APEDistance which gives us |
"distances_aggregator": args.get("distances_aggregator", None), | ||
"mask_strategy":args.get("mask_strategy", 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.
class PowerDistance(PointwiseDistance):
def __init__(self, power: int = 2, default_distance=0):
self.power = power
self.default_distance = default_distance
def __call__(self, p1: MaskedArray, p2: MaskedArray):
return (p1 - p2).pow(self.power).abs().filled(self.default_distance).mean()
L2Distance = PowerDistance(power=2)
L1Distance = PowerDistance(power=1)
class APEDistance(PointwiseDistance):
def __call__(self, p1: MaskedArray, p2: MaskedArray):
return (p1 - p2).pow(2).sum(-1).sqrt().filled(default_distance).mean()
# Example usage
DistanceMetric(distance=L2Distance, alignment_strategy="pad"|"truncate"|"by-reference")
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.
class DTWMetric(BaseMetric):
def __init__(self, distance: Distance, trajectory:"keypoints"|"frames", processors=List[Processor])
self.__super__...
def score(self, pose1: Pose, pose2: Pose):
tensor1 = self.process(pose1)
tensor2 = self.process(pose2)
if self. trajectory == "keypoints":
tensor1 = points_perspective(tensor1)
tensor2 = points_perspective(tensor2)
distance = 0
for trajectory1, trajectory2 in zip(tensor1, tensor2):
distance += fastdtw(metric=self.distance, seq1=trajectory1, seq2=trajectory2)
return distance
return fastdtw(metric=self.distance, seq1=tensor1, seq2=tensor2)
def PoseSetMaskedToOrigin(pose):
return pose.filled(0)
dtw_metric = DTWMetric(
distance=L2Distance(),
trajectory="keypoints",
processors=[
PoseNormalize(),
HideLegs(),
PoseSetMaskedToOrigin(),
ReduceToCommonKeypoints(),
],
)
# Usage signature: dtw(distance=str(distance), processors="|".join(processors))
Had a design discussion with Amit.
|
One issue I'm running into is: if we calculate a separate distance for every keypoint's trajectory, we need to figure out how to aggregate those. |
As of PR #19, this one is rendered moot! |
Initial commit is just to copy code. Next we need to rewrite to subclass DistanceMetric, etc.
Tasks include: