Module compoelem.generate.bisection

Expand source code
import itertools
from typing import Tuple, cast

import numpy as np
import numpy.linalg as la
from shapely.geometry import Polygon

from compoelem.config import config
from compoelem.detect.converter import k, p
from compoelem.types import *

body_direction_counter = 0
normal_body_direction_counter = 0
fallback_body_direction_counter = 0

def get_centroids_for_bisection(keypoints: Sequence[Keypoint], fallback: bool) -> Tuple[Keypoint, Keypoint, Keypoint]:
    """Helper method for transforming COCO input in a way that we can calculate the bisection vector of upper,
    middle and lower keypoints. Therefore we calculate the centroid of the keypoint pairs specified in 
    the config bisection.left_pose_points and bisection.right_pose_points

    Args:
        keypoints (Pose.keypoints): transformed COCO output

    Raises:
        ValueError: is raised if one of the above keypoints is missing

    Returns:
        Tuple[Keypoint, Keypoint, Keypoint]: top_kp, middle_kp, bottom_kp
    """
    global body_direction_counter
    global normal_body_direction_counter
    global fallback_body_direction_counter
    body_direction_counter = body_direction_counter + 1

    keypoints_np = np.array(keypoints)
    left_kp = keypoints_np[config["bisection"]["left_pose_points"]]
    right_kp = keypoints_np[config["bisection"]["right_pose_points"]]

    left_shoulder_kp = keypoints_np[config["bisection"]["fallback"]["left_shoulder_kp"]]
    right_shoulder_kp = keypoints_np[config["bisection"]["fallback"]["right_shoulder_kp"]]
    left_eye_kp = keypoints_np[config["bisection"]["fallback"]["left_eye_kp"]]
    right_eye_kp = keypoints_np[config["bisection"]["fallback"]["right_eye_kp"]]
    with_fallback_kps: Sequence[Tuple[Keypoint,Keypoint]] = []
    if fallback:
        fb_used = False
        for idx, t in enumerate(zip(left_kp, right_kp)): # 0,1,8 & 11
            if t[0].isNone or t[1].isNone:
                fb_used = True
                if idx == 0: # top point fallback
                    with_fallback_kps.append((left_eye_kp, right_eye_kp))
                if idx == 1: # middle point fallback
                    with_fallback_kps.append((left_shoulder_kp, right_shoulder_kp))
                if idx == 2: # bottom point fallback
                    l_middle_point_pair, r_middle_point_pair = with_fallback_kps[-1]
                    left_bottom_fallback = Keypoint(l_middle_point_pair.x, l_middle_point_pair.y + 40, 1)
                    right_bottom_fallback = Keypoint(r_middle_point_pair.x, r_middle_point_pair.y + 40, 1)
                    with_fallback_kps.append((left_bottom_fallback, right_bottom_fallback)) #actually they can be the same. Since we calculate the centroid anyways
            else:
                with_fallback_kps.append(t)
        if fb_used:
            fallback_body_direction_counter = fallback_body_direction_counter + 1
        else:
            normal_body_direction_counter = normal_body_direction_counter + 1

    bisection_keypoint_pairs: Sequence[Tuple[Keypoint,Keypoint]] = list(
        filter(lambda x: not (x[0].isNone or x[1].isNone), with_fallback_kps if fallback else zip(left_kp, right_kp,))
    )
    if len(bisection_keypoint_pairs) != 3:
        raise ValueError('some keypoints for bisection calculation are missing!')
    keypoint_pairs = [
        Keypoint(*p(cast(Point, LineString([k(a),k(b)]).centroid)), np.mean([a.score, b.score]))
        for a,b in bisection_keypoint_pairs
    ]
    top_kp, middle_kp, bottom_kp = keypoint_pairs
    # print("body_direction_counter, normal_body_direction_counter, fallback_body_direction_counter", body_direction_counter, normal_body_direction_counter, fallback_body_direction_counter)
    return top_kp, middle_kp, bottom_kp

def keypoint_to_point(k: Keypoint) -> Point:
    return Point(k.x, k.y)

def keypoint_to_vector(a: Keypoint, b: Keypoint) -> np.ndarray:
    return keypoint_to_np(a) - keypoint_to_np(b)

def keypoint_to_np(keypoint: Keypoint) -> np.ndarray:
    return np.array([keypoint.x, keypoint.y])

def get_bisection_point(top_kp: Keypoint, middle_kp: Keypoint, bottom_kp: Keypoint) -> Keypoint:
    """Returns the end point of the bisection vector of three input points.
    The length of the vector is the double of the length from top_kp to middle_kp.

    Args:
        top_kp (Keypoint): Some keypoint from head region
        middle_kp (Keypoint): Some keypoint from upper body region
        bottom_kp (Keypoint): Some keypoint from lower body region

    Returns:
        Keypoint: Endpoint of bisection vector
    """
    phi = get_angle(top_kp, middle_kp, bottom_kp) - np.deg2rad(int(config["bisection"]["correction_angle"])) #TODO: check if this correction_angle is correct for both directions (bisection vector show to the left or right (clockwise/counterclockwise rotation))
    return get_bisection_point_from_angle(top_kp, middle_kp, phi)

def get_bisection_point_from_angle(top_kp: Keypoint, middle_kp: Keypoint, phi: float, scale: float = 1) -> Keypoint:
    theta = phi / 2 * -1 
    x, y = keypoint_to_vector(top_kp, middle_kp)
    x_new = x * np.cos(theta) - y * np.sin(theta)
    y_new = x * np.sin(theta) + y * np.cos(theta)
    return Keypoint(x_new * scale + middle_kp.x, y_new * scale + middle_kp.y)

def get_angle(a: Keypoint, b: Keypoint, c: Keypoint) -> float:
    """Get angle between vector b->a and b->c by calculating the scalarproduct.
    Because the scalarproduct looses track of the orientation of the angle we further
    calculate the orientation with the help of the crossproduct and return it with the sign of the returned angle

    Args:
        a (Keypoint): first keypoint
        b (Keypoint): middle keypoint
        c (Keypoint): last keypoint

    Returns:
        float: angle in radians. Positive if angle is left rotating and positiv if right rotating
    """
    # get a vector with origin in (0,0) from points a and b by substracting Point a from Point b
    vector_a = keypoint_to_vector(a, b)
    vector_c = keypoint_to_vector(c, b)
    # https://de.wikipedia.org/wiki/Skalarprodukt => winkel phi = arccos(...)
    phi = np.arccos(np.dot(vector_a, vector_c) / (np.linalg.norm(vector_a) * np.linalg.norm(vector_c)))
    angle_left_opening = np.cross(vector_a, vector_c) < 0
    return phi if angle_left_opening else -phi

def get_horizantal_b_reference(top_kp: Keypoint, middle_kp: Keypoint, bottom_kp: Keypoint) -> Keypoint:
    """Returns an reference Keypoint by adding or substracting 100px from middle_kp x-coordinate.
    If the smaller angle between the three keypoints is on the left side we substract 100px else we add 100px.
    Using this keypoint together with the middle_kp to create a parallel line to the x axis

    Args:
        top_kp (Keypoint): Some keypoint from head region
        middle_kp (Keypoint): Some keypoint from upper body region
        bottom_kp (Keypoint): Some keypoint from lower body region

    Returns:
        Keypoint: returns an reference Keypoint.
    """
    vector_a = keypoint_to_vector(top_kp, middle_kp)
    vector_c = keypoint_to_vector(bottom_kp, middle_kp)
    # NOTE: checking angle_left_opening has the same effect as to check if biscetion point is positioned to the left or right of the neck point
    angle_left_opening = np.cross(vector_a, vector_c) < 0
    return Keypoint(middle_kp.x - 100, middle_kp.y) if angle_left_opening else Keypoint(middle_kp.x + 100, middle_kp.y)

def get_bisection_cone(top_kp: Keypoint, middle_kp: Keypoint, bottom_kp: Keypoint) -> Polygon:
    cone_offset_angle = np.deg2rad(int(config["bisection"]["cone_opening_angle"])/2)
    cone_scale_factor = float(config["bisection"]["cone_scale_factor"])
    cone_base_scale_factor = float(config["bisection"]["cone_base_scale_factor"])
    # print(cone_offset_angle, cone_scale_factor)
    phi = get_angle(top_kp, middle_kp, bottom_kp) - np.deg2rad(int(config["bisection"]["correction_angle"]))
    # cone points
    cone_endpoint1 = get_bisection_point_from_angle(top_kp, middle_kp, phi + cone_offset_angle, cone_scale_factor)
    cone_endpoint2 = get_bisection_point_from_angle(top_kp, middle_kp, phi - cone_offset_angle, cone_scale_factor)
    cone_startpoint1 = get_bisection_point_from_angle(top_kp, middle_kp, phi + np.deg2rad(config["bisection"]["cone_base_angle"]), cone_base_scale_factor)
    cone_startpoint2 = get_bisection_point_from_angle(top_kp, middle_kp, phi - np.deg2rad(config["bisection"]["cone_base_angle"]), cone_base_scale_factor)
    cone = Polygon([keypoint_to_np(cone_endpoint1), keypoint_to_np(cone_endpoint2), keypoint_to_np(cone_startpoint2), keypoint_to_np(cone_startpoint1)])
    # if phi > 0:
    #     cone = Polygon([keypoint_to_np(cone1), keypoint_to_np(cone2), [middle_kp.x, middle_kp.y - 20], [middle_kp.x, middle_kp.y + 20]]) # new
    # else:
    #     cone = Polygon([keypoint_to_np(cone1), keypoint_to_np(cone2), [middle_kp.x, middle_kp.y + 20], [middle_kp.x, middle_kp.y - 20]]) # new
    return cone

def get_angle_in_respect_to_x(top_kp: Keypoint, middle_kp: Keypoint, bottom_kp: Keypoint) -> float:
    """Calculates the angle from the three input keypoints in respect to the x-axis.
    A positive value indicates a increasing slope and a negative a decreasing slope.

    Args:
        top_kp (Keypoint): Some keypoint from head region
        middle_kp (Keypoint): Some keypoint from upper body region
        bottom_kp (Keypoint): Some keypoint from lower body region

    Returns:
        float: angle in radians in respect to x-axis
    """
    bisect_point = get_bisection_point(top_kp, middle_kp, bottom_kp)
    horizontal_middle_kp_reference = get_horizantal_b_reference(top_kp, middle_kp, bottom_kp)
    gamma = get_angle(horizontal_middle_kp_reference, middle_kp, bisect_point)
    return gamma

def keypoint_to_np(keypoint: Keypoint) -> np.ndarray:
    return np.array([keypoint.x, keypoint.y])

# #previuosly angleMapper
# def get_mapped_angle(top_kp: Keypoint, middle_kp: Keypoint, bottom_kp: Keypoint) -> float:
#     a = np.array([top_kp.x, top_kp.y])
#     b = np.array([middle_kp.x, middle_kp.y])
#     c = np.array([bottom_kp.x, bottom_kp.y])
#     angle = getAngleGroundNormed(a,b,c)
#      #map all angles to one direction, so the mean does not get influences by left/right direction
#     if(angle > np.deg2rad(180)):
#         mapped = angle - np.deg2rad(180)
#         return mapped - np.deg2rad(180) if mapped > np.deg2rad(90) else mapped
#     else:
#         mapped = angle
#         return mapped - np.deg2rad(180) if mapped > np.deg2rad(90) else mapped

# def angleMapper(pose):
#     angle = getAngleGroundNormed(*pose[[0,1,8]][:,:2])
#      #map all angles to one direction, so the mean does not get influences by left/right direction
#     if(angle > np.deg2rad(180)):
#         mapped = angle - np.deg2rad(180)
#         return mapped - np.deg2rad(180) if mapped > np.deg2rad(90) else mapped
#     else:
#         mapped = angle
#         return mapped - np.deg2rad(180) if mapped > np.deg2rad(90) else mapped

# def getGlobalLineAngle(poses):
#     return np.mean([angleMapper(pose) for pose in poses if not 0.0 in pose[[0,1,8]][:,2:]])

# def getBisecPoint(a,b,c) -> Tuple[int, int]:
#     angle = getAngleGroundNormed(a,b,c)
#     dist = la.norm(a-b)*2
#     d = (int(dist * np.cos(angle)), int(dist * np.sin(angle))) #with origin zero
#     out = (b[0]+d[0],b[1]-d[1])
#     return out #with origin b

# def poseToBisectVector(pose):
#     points = pose[[0,1,8]]
#     if(0.0 in points[:,2:]): #if one point has confidence zero, we can not generate the vector
#         return None
#     a,b,c = points[:,:2] # cut of confidence score so we have normal coordinate points
#     bisecPoint = getBisecPoint(a,b,c)
#     return np.array([bisecPoint,b])

Functions

def get_angle(a: Keypoint, b: Keypoint, c: Keypoint) ‑> float

Get angle between vector b->a and b->c by calculating the scalarproduct. Because the scalarproduct looses track of the orientation of the angle we further calculate the orientation with the help of the crossproduct and return it with the sign of the returned angle

Args

a : Keypoint
first keypoint
b : Keypoint
middle keypoint
c : Keypoint
last keypoint

Returns

float
angle in radians. Positive if angle is left rotating and positiv if right rotating
Expand source code
def get_angle(a: Keypoint, b: Keypoint, c: Keypoint) -> float:
    """Get angle between vector b->a and b->c by calculating the scalarproduct.
    Because the scalarproduct looses track of the orientation of the angle we further
    calculate the orientation with the help of the crossproduct and return it with the sign of the returned angle

    Args:
        a (Keypoint): first keypoint
        b (Keypoint): middle keypoint
        c (Keypoint): last keypoint

    Returns:
        float: angle in radians. Positive if angle is left rotating and positiv if right rotating
    """
    # get a vector with origin in (0,0) from points a and b by substracting Point a from Point b
    vector_a = keypoint_to_vector(a, b)
    vector_c = keypoint_to_vector(c, b)
    # https://de.wikipedia.org/wiki/Skalarprodukt => winkel phi = arccos(...)
    phi = np.arccos(np.dot(vector_a, vector_c) / (np.linalg.norm(vector_a) * np.linalg.norm(vector_c)))
    angle_left_opening = np.cross(vector_a, vector_c) < 0
    return phi if angle_left_opening else -phi
def get_angle_in_respect_to_x(top_kp: Keypoint, middle_kp: Keypoint, bottom_kp: Keypoint) ‑> float

Calculates the angle from the three input keypoints in respect to the x-axis. A positive value indicates a increasing slope and a negative a decreasing slope.

Args

top_kp : Keypoint
Some keypoint from head region
middle_kp : Keypoint
Some keypoint from upper body region
bottom_kp : Keypoint
Some keypoint from lower body region

Returns

float
angle in radians in respect to x-axis
Expand source code
def get_angle_in_respect_to_x(top_kp: Keypoint, middle_kp: Keypoint, bottom_kp: Keypoint) -> float:
    """Calculates the angle from the three input keypoints in respect to the x-axis.
    A positive value indicates a increasing slope and a negative a decreasing slope.

    Args:
        top_kp (Keypoint): Some keypoint from head region
        middle_kp (Keypoint): Some keypoint from upper body region
        bottom_kp (Keypoint): Some keypoint from lower body region

    Returns:
        float: angle in radians in respect to x-axis
    """
    bisect_point = get_bisection_point(top_kp, middle_kp, bottom_kp)
    horizontal_middle_kp_reference = get_horizantal_b_reference(top_kp, middle_kp, bottom_kp)
    gamma = get_angle(horizontal_middle_kp_reference, middle_kp, bisect_point)
    return gamma
def get_bisection_cone(top_kp: Keypoint, middle_kp: Keypoint, bottom_kp: Keypoint) ‑> shapely.geometry.polygon.Polygon
Expand source code
def get_bisection_cone(top_kp: Keypoint, middle_kp: Keypoint, bottom_kp: Keypoint) -> Polygon:
    cone_offset_angle = np.deg2rad(int(config["bisection"]["cone_opening_angle"])/2)
    cone_scale_factor = float(config["bisection"]["cone_scale_factor"])
    cone_base_scale_factor = float(config["bisection"]["cone_base_scale_factor"])
    # print(cone_offset_angle, cone_scale_factor)
    phi = get_angle(top_kp, middle_kp, bottom_kp) - np.deg2rad(int(config["bisection"]["correction_angle"]))
    # cone points
    cone_endpoint1 = get_bisection_point_from_angle(top_kp, middle_kp, phi + cone_offset_angle, cone_scale_factor)
    cone_endpoint2 = get_bisection_point_from_angle(top_kp, middle_kp, phi - cone_offset_angle, cone_scale_factor)
    cone_startpoint1 = get_bisection_point_from_angle(top_kp, middle_kp, phi + np.deg2rad(config["bisection"]["cone_base_angle"]), cone_base_scale_factor)
    cone_startpoint2 = get_bisection_point_from_angle(top_kp, middle_kp, phi - np.deg2rad(config["bisection"]["cone_base_angle"]), cone_base_scale_factor)
    cone = Polygon([keypoint_to_np(cone_endpoint1), keypoint_to_np(cone_endpoint2), keypoint_to_np(cone_startpoint2), keypoint_to_np(cone_startpoint1)])
    # if phi > 0:
    #     cone = Polygon([keypoint_to_np(cone1), keypoint_to_np(cone2), [middle_kp.x, middle_kp.y - 20], [middle_kp.x, middle_kp.y + 20]]) # new
    # else:
    #     cone = Polygon([keypoint_to_np(cone1), keypoint_to_np(cone2), [middle_kp.x, middle_kp.y + 20], [middle_kp.x, middle_kp.y - 20]]) # new
    return cone
def get_bisection_point(top_kp: Keypoint, middle_kp: Keypoint, bottom_kp: Keypoint) ‑> Keypoint

Returns the end point of the bisection vector of three input points. The length of the vector is the double of the length from top_kp to middle_kp.

Args

top_kp : Keypoint
Some keypoint from head region
middle_kp : Keypoint
Some keypoint from upper body region
bottom_kp : Keypoint
Some keypoint from lower body region

Returns

Keypoint
Endpoint of bisection vector
Expand source code
def get_bisection_point(top_kp: Keypoint, middle_kp: Keypoint, bottom_kp: Keypoint) -> Keypoint:
    """Returns the end point of the bisection vector of three input points.
    The length of the vector is the double of the length from top_kp to middle_kp.

    Args:
        top_kp (Keypoint): Some keypoint from head region
        middle_kp (Keypoint): Some keypoint from upper body region
        bottom_kp (Keypoint): Some keypoint from lower body region

    Returns:
        Keypoint: Endpoint of bisection vector
    """
    phi = get_angle(top_kp, middle_kp, bottom_kp) - np.deg2rad(int(config["bisection"]["correction_angle"])) #TODO: check if this correction_angle is correct for both directions (bisection vector show to the left or right (clockwise/counterclockwise rotation))
    return get_bisection_point_from_angle(top_kp, middle_kp, phi)
def get_bisection_point_from_angle(top_kp: Keypoint, middle_kp: Keypoint, phi: float, scale: float = 1) ‑> Keypoint
Expand source code
def get_bisection_point_from_angle(top_kp: Keypoint, middle_kp: Keypoint, phi: float, scale: float = 1) -> Keypoint:
    theta = phi / 2 * -1 
    x, y = keypoint_to_vector(top_kp, middle_kp)
    x_new = x * np.cos(theta) - y * np.sin(theta)
    y_new = x * np.sin(theta) + y * np.cos(theta)
    return Keypoint(x_new * scale + middle_kp.x, y_new * scale + middle_kp.y)
def get_centroids_for_bisection(keypoints: Sequence[Keypoint], fallback: bool) ‑> Tuple[KeypointKeypointKeypoint]

Helper method for transforming COCO input in a way that we can calculate the bisection vector of upper, middle and lower keypoints. Therefore we calculate the centroid of the keypoint pairs specified in the config bisection.left_pose_points and bisection.right_pose_points

Args

keypoints : Pose.keypoints
transformed COCO output

Raises

ValueError
is raised if one of the above keypoints is missing

Returns

Tuple[Keypoint, Keypoint, Keypoint]
top_kp, middle_kp, bottom_kp
Expand source code
def get_centroids_for_bisection(keypoints: Sequence[Keypoint], fallback: bool) -> Tuple[Keypoint, Keypoint, Keypoint]:
    """Helper method for transforming COCO input in a way that we can calculate the bisection vector of upper,
    middle and lower keypoints. Therefore we calculate the centroid of the keypoint pairs specified in 
    the config bisection.left_pose_points and bisection.right_pose_points

    Args:
        keypoints (Pose.keypoints): transformed COCO output

    Raises:
        ValueError: is raised if one of the above keypoints is missing

    Returns:
        Tuple[Keypoint, Keypoint, Keypoint]: top_kp, middle_kp, bottom_kp
    """
    global body_direction_counter
    global normal_body_direction_counter
    global fallback_body_direction_counter
    body_direction_counter = body_direction_counter + 1

    keypoints_np = np.array(keypoints)
    left_kp = keypoints_np[config["bisection"]["left_pose_points"]]
    right_kp = keypoints_np[config["bisection"]["right_pose_points"]]

    left_shoulder_kp = keypoints_np[config["bisection"]["fallback"]["left_shoulder_kp"]]
    right_shoulder_kp = keypoints_np[config["bisection"]["fallback"]["right_shoulder_kp"]]
    left_eye_kp = keypoints_np[config["bisection"]["fallback"]["left_eye_kp"]]
    right_eye_kp = keypoints_np[config["bisection"]["fallback"]["right_eye_kp"]]
    with_fallback_kps: Sequence[Tuple[Keypoint,Keypoint]] = []
    if fallback:
        fb_used = False
        for idx, t in enumerate(zip(left_kp, right_kp)): # 0,1,8 & 11
            if t[0].isNone or t[1].isNone:
                fb_used = True
                if idx == 0: # top point fallback
                    with_fallback_kps.append((left_eye_kp, right_eye_kp))
                if idx == 1: # middle point fallback
                    with_fallback_kps.append((left_shoulder_kp, right_shoulder_kp))
                if idx == 2: # bottom point fallback
                    l_middle_point_pair, r_middle_point_pair = with_fallback_kps[-1]
                    left_bottom_fallback = Keypoint(l_middle_point_pair.x, l_middle_point_pair.y + 40, 1)
                    right_bottom_fallback = Keypoint(r_middle_point_pair.x, r_middle_point_pair.y + 40, 1)
                    with_fallback_kps.append((left_bottom_fallback, right_bottom_fallback)) #actually they can be the same. Since we calculate the centroid anyways
            else:
                with_fallback_kps.append(t)
        if fb_used:
            fallback_body_direction_counter = fallback_body_direction_counter + 1
        else:
            normal_body_direction_counter = normal_body_direction_counter + 1

    bisection_keypoint_pairs: Sequence[Tuple[Keypoint,Keypoint]] = list(
        filter(lambda x: not (x[0].isNone or x[1].isNone), with_fallback_kps if fallback else zip(left_kp, right_kp,))
    )
    if len(bisection_keypoint_pairs) != 3:
        raise ValueError('some keypoints for bisection calculation are missing!')
    keypoint_pairs = [
        Keypoint(*p(cast(Point, LineString([k(a),k(b)]).centroid)), np.mean([a.score, b.score]))
        for a,b in bisection_keypoint_pairs
    ]
    top_kp, middle_kp, bottom_kp = keypoint_pairs
    # print("body_direction_counter, normal_body_direction_counter, fallback_body_direction_counter", body_direction_counter, normal_body_direction_counter, fallback_body_direction_counter)
    return top_kp, middle_kp, bottom_kp
def get_horizantal_b_reference(top_kp: Keypoint, middle_kp: Keypoint, bottom_kp: Keypoint) ‑> Keypoint

Returns an reference Keypoint by adding or substracting 100px from middle_kp x-coordinate. If the smaller angle between the three keypoints is on the left side we substract 100px else we add 100px. Using this keypoint together with the middle_kp to create a parallel line to the x axis

Args

top_kp : Keypoint
Some keypoint from head region
middle_kp : Keypoint
Some keypoint from upper body region
bottom_kp : Keypoint
Some keypoint from lower body region

Returns

Keypoint
returns an reference Keypoint.
Expand source code
def get_horizantal_b_reference(top_kp: Keypoint, middle_kp: Keypoint, bottom_kp: Keypoint) -> Keypoint:
    """Returns an reference Keypoint by adding or substracting 100px from middle_kp x-coordinate.
    If the smaller angle between the three keypoints is on the left side we substract 100px else we add 100px.
    Using this keypoint together with the middle_kp to create a parallel line to the x axis

    Args:
        top_kp (Keypoint): Some keypoint from head region
        middle_kp (Keypoint): Some keypoint from upper body region
        bottom_kp (Keypoint): Some keypoint from lower body region

    Returns:
        Keypoint: returns an reference Keypoint.
    """
    vector_a = keypoint_to_vector(top_kp, middle_kp)
    vector_c = keypoint_to_vector(bottom_kp, middle_kp)
    # NOTE: checking angle_left_opening has the same effect as to check if biscetion point is positioned to the left or right of the neck point
    angle_left_opening = np.cross(vector_a, vector_c) < 0
    return Keypoint(middle_kp.x - 100, middle_kp.y) if angle_left_opening else Keypoint(middle_kp.x + 100, middle_kp.y)
def keypoint_to_np(keypoint: Keypoint) ‑> numpy.ndarray
Expand source code
def keypoint_to_np(keypoint: Keypoint) -> np.ndarray:
    return np.array([keypoint.x, keypoint.y])
def keypoint_to_point(k: Keypoint) ‑> shapely.geometry.point.Point
Expand source code
def keypoint_to_point(k: Keypoint) -> Point:
    return Point(k.x, k.y)
def keypoint_to_vector(a: Keypoint, b: Keypoint) ‑> numpy.ndarray
Expand source code
def keypoint_to_vector(a: Keypoint, b: Keypoint) -> np.ndarray:
    return keypoint_to_np(a) - keypoint_to_np(b)