Source code for curve_matcher.shape_similarity
import math
from curve_matcher.frechet_distance import frechet_distance
from curve_matcher.geometry import Curve, curve_length, rotate_curve
from curve_matcher.procrustes_analysis import (
find_procrustes_rotation_angle,
procrustes_normalize_curve,
)
[docs]
def shape_similarity(
curve1: Curve,
curve2: Curve,
estimation_points: int = 50,
rotations: int = 10,
restrict_rotation_angle: float = math.pi,
check_rotations: bool = True,
) -> float:
if abs(restrict_rotation_angle) > math.pi:
raise ValueError("restrict_rotation_angle cannot be larger than PI")
normalized_curve1 = procrustes_normalize_curve(
curve1, estimation_points=estimation_points
)
normalized_curve2 = procrustes_normalize_curve(
curve2, estimation_points=estimation_points
)
geo_avg_curve_len = math.sqrt(
curve_length(normalized_curve1) * curve_length(normalized_curve2)
)
thetas_to_check = [0.0]
if check_rotations:
procrustes_theta = find_procrustes_rotation_angle(
normalized_curve1, normalized_curve2
)
if procrustes_theta > math.pi:
procrustes_theta = procrustes_theta - 2 * math.pi
if procrustes_theta != 0 and abs(procrustes_theta) < restrict_rotation_angle:
thetas_to_check.append(procrustes_theta)
for i in range(rotations):
theta = -1 * restrict_rotation_angle + (2 * i * restrict_rotation_angle) / (
rotations - 1
)
if theta != 0 and theta != math.pi:
thetas_to_check.append(theta)
min_frechet_dist = float("inf")
for theta in thetas_to_check:
rotated_curve1 = rotate_curve(normalized_curve1, theta)
dist = frechet_distance(rotated_curve1, normalized_curve2)
if dist < min_frechet_dist:
min_frechet_dist = dist
return max(1 - min_frechet_dist / (geo_avg_curve_len / math.sqrt(2)), 0)