Module compoelem.generate.global_action

Expand source code
import itertools
from typing import cast

import numpy as np
from compoelem.generate.bisection import get_centroids_for_bisection, get_angle_in_respect_to_x
from compoelem.generate.pose_direction import get_pose_directions
from compoelem.types import *
from shapely.geometry.polygon import Polygon


def get_cone_combination_intersections(pose_directions: Sequence[PoseDirection]) -> Sequence[ConeIntersection]:
    """calculate every intersection of all combinations of input pose list.
    It will return all combination intersections which are not null.
    So we can later for example select the intersection with the highest combination_length as our target.

    Args:
        pose_directions (Sequence[PoseDirection]): Output of get_pose_directions(poses)

    Returns:
        Sequence[Tuple[Combination, int, Polygon]]: a ordered list of pairs (combination_length, combination, combi_intersection_result)
                                                    ordered with ascending combination_length
    """
    combination_intersections: Sequence[ConeIntersection] = []
    # we increase the length of the combinations of cones. Ending in a combination_length with all cones in one combination
    # len(pose_directions) == 7:
    #   combination_length = 1 => [(0,), (1,), (2,), (3,), (4,), (5,), (6,), (7,)]
    #   combination_length = 2 => [(0, 1), ..., (0, 7), (1, 2), ..., (1, 7), (2, 3), ...]   # no duplicate combinations live (1, 0) since intersection would be the same
    #   combination_length = 8 => [(0, 1, 2, 3, 4, 5, 6, 7)]
    # combinations can still get HUGGEE... for 21 poses: 2097151 combinations: sum([len(list(itertools.combinations(range(0,21), r))) for r in range(1,22)])
    # is the same as 2097151 2**21-1 => Loop has size: (2^len(pose_directions))-1

    # # old from 0 to len(poserange) => and calc all
    # # new from len(poserange) to 0 and break after first r is found where entries exist
    # for combination_length in list(range(1,len(pose_directions)+1))[::-1]:
    #     cone_combinations: Sequence[ConeCombination] = list(itertools.combinations(range(0,len(pose_directions)), combination_length))
    #     # generating all combinations of cones with combi length r
    #     for combination in cone_combinations:
    #         # start the recursive intersection calculation with the first entry of the combination tuple as the target cone
    #         combi_intersection_result = pose_directions[combination[0]].cone
    #         for i in combination[1:]:
    #             # iterate over the remaining combinations and recursivly call the intersection on the current intersection
    #             # this will slowly make the intersection smaller till all intersections from the combination are generated
    #             combi_intersection_result = cast(Polygon, combi_intersection_result.intersection(pose_directions[i].cone))
    #         if not combi_intersection_result.is_empty:
    #             # only append the final intersection if the target cone is not empty
    #             # also store the combination tuple
    #             combination_intersections.append(ConeIntersection(combi_intersection_result, combination))
    #     if len(combination_intersections) > 0:
    #         # new optimization, calc backwards (from many combinations) to less combinations
    #         # since length_cone_combinations with combination_length has pyramid form. we have potential to save a lot of calculations!!!
    #         print("cone_combination_length", map(lambda x: x.cone_combination_length, combination_intersections), "length_cone_combinations", len(cone_combinations))
    #         break
    # return combination_intersections


    # old from 0 to len(poserange) => and calc all
    # new from len(poserange) to 0 and break after first r is found where entries exist
    for combination_length in range(1,len(pose_directions)+1):
        cone_combinations: Sequence[ConeCombination] = list(itertools.combinations(range(0,len(pose_directions)), combination_length))
        # generating all combinations of cones with combi length r
        empty_round = True
        for combination in cone_combinations:
            # start the recursive intersection calculation with the first entry of the combination tuple as the target cone
            combi_intersection_result = pose_directions[combination[0]].cone
            for i in combination[1:]:
                # iterate over the remaining combinations and recursivly call the intersection on the current intersection
                # this will slowly make the intersection smaller till all intersections from the combination are generated
                combi_intersection_result = cast(Polygon, combi_intersection_result.intersection(pose_directions[i].cone))
            if not combi_intersection_result.is_empty:
                # only append the final intersection if the target cone is not empty
                # also store the combination tuple
                combination_intersections.append(ConeIntersection(combi_intersection_result, combination))
                empty_round = False
        if empty_round: # NEW add empty round as optimization
            # print("empty round at combi length", combination_length)
            # if we now have one round without any intersections, increasing the combination length by one will not have any intersections as well
            break
    return combination_intersections

def get_filtered_cone_intersections(poses, fallback) -> Sequence[ConeIntersection]:
    """get the filtered cone intersections from the poses. We filter by selecting the intersections with the most cones participating. Therfore it could happen that the amount of intersections is greater than 1.

    Args:
        poses ([type]): converted HRNet output

    Returns:
        Sequence[Polygon]: Each entry is a cone intersection represented by a Polygon
    """
    pose_directions = get_pose_directions(poses, fallback)
    combination_intersections = get_cone_combination_intersections(pose_directions)
    if len(combination_intersections) == 0:
        return []
    # since combination_intersections is ordered. The combi_length of the last entry will always be the highest combi_length
    filtered_combi_length = combination_intersections[-1].cone_combination_length
    filtered_cone_intersections = [v for v in combination_intersections if v.cone_combination_length == filtered_combi_length]
    return filtered_cone_intersections

def get_combined_angle(poses, fallback) -> float:
    """calculates and combines the bisection angle of all input poses.

    Args:
        poses ([type]): Openpose transformed output poses

    Returns:
        float: angle in radians
    """
    angles: Sequence[float] = []
    for pose in poses:
        try:
            angles.append(get_angle_in_respect_to_x(*get_centroids_for_bisection(pose.keypoints, fallback)))
        except ValueError as e:
            #print(e)
            pass
    return np.mean(angles) * -1

def get_global_action_lines(poses, fallback=False) -> Sequence[GlobalActionLine]:
    """calculate global action lines. Therefore we calculate the intersecting pose direction cones and take the centroid of
    the intersection with the most cones participating. From these participating cones we also calculate the angle from the
    bisection vector and average it together to calculate the angle for the global action line. The line is then drawn trough
    the intersection area centroid with this newly calculated centroid.

    Args:
        poses ([type]): Openpose transformed output poses

    Returns:
        Sequence[GlobalActionLine]: a single or multiple global action lines. Mostly a single line but if there are multiple 
        different intersection areas with the same amount of cones participating we take all of them.
    """
    cone_intersections = get_filtered_cone_intersections(poses, fallback)
    global_action_lines = []
    for cone_intersection in cone_intersections:
        filtered_participating_poses = np.array(poses)[np.array(cone_intersection.cone_combination)]
        if len(filtered_participating_poses) > 0:
            combined_angle = get_combined_angle(filtered_participating_poses, fallback)
            global_action_lines.append(
                GlobalActionLine(
                    cast(Point, cone_intersection.shape.centroid),
                    combined_angle,
                    cone_intersection.shape.area,
                    cone_intersection.shape
                )
            )
    return global_action_lines

Functions

def get_combined_angle(poses, fallback) ‑> float

calculates and combines the bisection angle of all input poses.

Args

poses : [type]
Openpose transformed output poses

Returns

float
angle in radians
Expand source code
def get_combined_angle(poses, fallback) -> float:
    """calculates and combines the bisection angle of all input poses.

    Args:
        poses ([type]): Openpose transformed output poses

    Returns:
        float: angle in radians
    """
    angles: Sequence[float] = []
    for pose in poses:
        try:
            angles.append(get_angle_in_respect_to_x(*get_centroids_for_bisection(pose.keypoints, fallback)))
        except ValueError as e:
            #print(e)
            pass
    return np.mean(angles) * -1
def get_cone_combination_intersections(pose_directions: Sequence[PoseDirection]) ‑> Sequence[ConeIntersection]

calculate every intersection of all combinations of input pose list. It will return all combination intersections which are not null. So we can later for example select the intersection with the highest combination_length as our target.

Args

pose_directions : Sequence[PoseDirection]
Output of get_pose_directions(poses)

Returns

Sequence[Tuple[Combination, int, Polygon]]
a ordered list of pairs (combination_length, combination, combi_intersection_result) ordered with ascending combination_length
Expand source code
def get_cone_combination_intersections(pose_directions: Sequence[PoseDirection]) -> Sequence[ConeIntersection]:
    """calculate every intersection of all combinations of input pose list.
    It will return all combination intersections which are not null.
    So we can later for example select the intersection with the highest combination_length as our target.

    Args:
        pose_directions (Sequence[PoseDirection]): Output of get_pose_directions(poses)

    Returns:
        Sequence[Tuple[Combination, int, Polygon]]: a ordered list of pairs (combination_length, combination, combi_intersection_result)
                                                    ordered with ascending combination_length
    """
    combination_intersections: Sequence[ConeIntersection] = []
    # we increase the length of the combinations of cones. Ending in a combination_length with all cones in one combination
    # len(pose_directions) == 7:
    #   combination_length = 1 => [(0,), (1,), (2,), (3,), (4,), (5,), (6,), (7,)]
    #   combination_length = 2 => [(0, 1), ..., (0, 7), (1, 2), ..., (1, 7), (2, 3), ...]   # no duplicate combinations live (1, 0) since intersection would be the same
    #   combination_length = 8 => [(0, 1, 2, 3, 4, 5, 6, 7)]
    # combinations can still get HUGGEE... for 21 poses: 2097151 combinations: sum([len(list(itertools.combinations(range(0,21), r))) for r in range(1,22)])
    # is the same as 2097151 2**21-1 => Loop has size: (2^len(pose_directions))-1

    # # old from 0 to len(poserange) => and calc all
    # # new from len(poserange) to 0 and break after first r is found where entries exist
    # for combination_length in list(range(1,len(pose_directions)+1))[::-1]:
    #     cone_combinations: Sequence[ConeCombination] = list(itertools.combinations(range(0,len(pose_directions)), combination_length))
    #     # generating all combinations of cones with combi length r
    #     for combination in cone_combinations:
    #         # start the recursive intersection calculation with the first entry of the combination tuple as the target cone
    #         combi_intersection_result = pose_directions[combination[0]].cone
    #         for i in combination[1:]:
    #             # iterate over the remaining combinations and recursivly call the intersection on the current intersection
    #             # this will slowly make the intersection smaller till all intersections from the combination are generated
    #             combi_intersection_result = cast(Polygon, combi_intersection_result.intersection(pose_directions[i].cone))
    #         if not combi_intersection_result.is_empty:
    #             # only append the final intersection if the target cone is not empty
    #             # also store the combination tuple
    #             combination_intersections.append(ConeIntersection(combi_intersection_result, combination))
    #     if len(combination_intersections) > 0:
    #         # new optimization, calc backwards (from many combinations) to less combinations
    #         # since length_cone_combinations with combination_length has pyramid form. we have potential to save a lot of calculations!!!
    #         print("cone_combination_length", map(lambda x: x.cone_combination_length, combination_intersections), "length_cone_combinations", len(cone_combinations))
    #         break
    # return combination_intersections


    # old from 0 to len(poserange) => and calc all
    # new from len(poserange) to 0 and break after first r is found where entries exist
    for combination_length in range(1,len(pose_directions)+1):
        cone_combinations: Sequence[ConeCombination] = list(itertools.combinations(range(0,len(pose_directions)), combination_length))
        # generating all combinations of cones with combi length r
        empty_round = True
        for combination in cone_combinations:
            # start the recursive intersection calculation with the first entry of the combination tuple as the target cone
            combi_intersection_result = pose_directions[combination[0]].cone
            for i in combination[1:]:
                # iterate over the remaining combinations and recursivly call the intersection on the current intersection
                # this will slowly make the intersection smaller till all intersections from the combination are generated
                combi_intersection_result = cast(Polygon, combi_intersection_result.intersection(pose_directions[i].cone))
            if not combi_intersection_result.is_empty:
                # only append the final intersection if the target cone is not empty
                # also store the combination tuple
                combination_intersections.append(ConeIntersection(combi_intersection_result, combination))
                empty_round = False
        if empty_round: # NEW add empty round as optimization
            # print("empty round at combi length", combination_length)
            # if we now have one round without any intersections, increasing the combination length by one will not have any intersections as well
            break
    return combination_intersections
def get_filtered_cone_intersections(poses, fallback) ‑> Sequence[ConeIntersection]

get the filtered cone intersections from the poses. We filter by selecting the intersections with the most cones participating. Therfore it could happen that the amount of intersections is greater than 1.

Args

poses : [type]
converted HRNet output

Returns

Sequence[Polygon]
Each entry is a cone intersection represented by a Polygon
Expand source code
def get_filtered_cone_intersections(poses, fallback) -> Sequence[ConeIntersection]:
    """get the filtered cone intersections from the poses. We filter by selecting the intersections with the most cones participating. Therfore it could happen that the amount of intersections is greater than 1.

    Args:
        poses ([type]): converted HRNet output

    Returns:
        Sequence[Polygon]: Each entry is a cone intersection represented by a Polygon
    """
    pose_directions = get_pose_directions(poses, fallback)
    combination_intersections = get_cone_combination_intersections(pose_directions)
    if len(combination_intersections) == 0:
        return []
    # since combination_intersections is ordered. The combi_length of the last entry will always be the highest combi_length
    filtered_combi_length = combination_intersections[-1].cone_combination_length
    filtered_cone_intersections = [v for v in combination_intersections if v.cone_combination_length == filtered_combi_length]
    return filtered_cone_intersections
def get_global_action_lines(poses, fallback=False) ‑> Sequence[GlobalActionLine]

calculate global action lines. Therefore we calculate the intersecting pose direction cones and take the centroid of the intersection with the most cones participating. From these participating cones we also calculate the angle from the bisection vector and average it together to calculate the angle for the global action line. The line is then drawn trough the intersection area centroid with this newly calculated centroid.

Args

poses : [type]
Openpose transformed output poses

Returns

Sequence[GlobalActionLine]
a single or multiple global action lines. Mostly a single line but if there are multiple

different intersection areas with the same amount of cones participating we take all of them.

Expand source code
def get_global_action_lines(poses, fallback=False) -> Sequence[GlobalActionLine]:
    """calculate global action lines. Therefore we calculate the intersecting pose direction cones and take the centroid of
    the intersection with the most cones participating. From these participating cones we also calculate the angle from the
    bisection vector and average it together to calculate the angle for the global action line. The line is then drawn trough
    the intersection area centroid with this newly calculated centroid.

    Args:
        poses ([type]): Openpose transformed output poses

    Returns:
        Sequence[GlobalActionLine]: a single or multiple global action lines. Mostly a single line but if there are multiple 
        different intersection areas with the same amount of cones participating we take all of them.
    """
    cone_intersections = get_filtered_cone_intersections(poses, fallback)
    global_action_lines = []
    for cone_intersection in cone_intersections:
        filtered_participating_poses = np.array(poses)[np.array(cone_intersection.cone_combination)]
        if len(filtered_participating_poses) > 0:
            combined_angle = get_combined_angle(filtered_participating_poses, fallback)
            global_action_lines.append(
                GlobalActionLine(
                    cast(Point, cone_intersection.shape.centroid),
                    combined_angle,
                    cone_intersection.shape.area,
                    cone_intersection.shape
                )
            )
    return global_action_lines