Skip to content

Commit

Permalink
Merge branch 'master' of github.com:PySport/kloppy
Browse files Browse the repository at this point in the history
  • Loading branch information
koenvossen committed Mar 1, 2021
2 parents b7168f9 + e0fad6e commit 34ce674
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 4 deletions.
25 changes: 22 additions & 3 deletions kloppy/domain/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,15 +263,34 @@ class BodyPart(Enum):
BodyPart
Attributes:
RIGHT_FOOT (BodyPart):
LEFT_FOOT (BodyPart):
HEAD (BodyPart):
RIGHT_FOOT (BodyPart): Pass or Shot with right foot, save with right foot (for goalkeepers).
LEFT_FOOT (BodyPart): Pass or Shot with leftt foot, save with left foot (for goalkeepers).
HEAD (BodyPart): Pass or Shot with head, save with head (for goalkeepers).
BOTH_HANDS (BodyPart): Goalkeeper only. Save with both hands.
CHEST (BodyPart): Goalkeeper only. Save with chest.
LEFT_HAND (BodyPart): Goalkeeper only. Save with left hand.
RIGHT_HAND (BodyPart): Goalkeeper only. Save with right hand.
DROP_KICK (BodyPart): Pass is a keeper drop kick.
KEEPER_ARM (BodyPart): Pass thrown from keepers hands.
OTHER (BodyPart): Other body part (chest, back, etc.), for Pass and Shot.
NO_TOUCH (BodyPart): Pass only. A player deliberately let the pass go past him
instead of receiving it to deliver to a teammate behind him.
(Also known as a "dummy").
"""

RIGHT_FOOT = "RIGHT_FOOT"
LEFT_FOOT = "LEFT_FOOT"
HEAD = "HEAD"

BOTH_HANDS = "BOTH_HANDS"
CHEST = "CHEST"
LEFT_HAND = "LEFT_HAND"
RIGHT_HAND = "RIGHT_HAND"
DROP_KICK = "DROP_KICK"
KEEPER_ARM = "KEEPER_ARM"
OTHER = "OTHER"
NO_TOUCH = "NO_TOUCH"


@dataclass
class BodyPartQualifier(EnumQualifier):
Expand Down
52 changes: 51 additions & 1 deletion kloppy/infra/serializers/event/statsbomb/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
FoulCommittedEvent,
BallOutEvent,
Event,
BodyPart,
BodyPartQualifier,
)
from kloppy.infra.serializers.event import EventDataSerializer
from kloppy.utils import Readable, performance_logging
Expand Down Expand Up @@ -83,6 +85,18 @@

OUT_EVENT_RESULTS = [PassResult.OUT, TakeOnResult.OUT]

SB_BODYPART_BOTH_HANDS = 35
SB_BODYPART_CHEST = 36
SB_BODYPART_HEAD = 37
SB_BODYPART_LEFT_FOOT = 38
SB_BODYPART_LEFT_HAND = 39
SB_BODYPART_RIGHT_FOOT = 40
SB_BODYPART_RIGHT_HAND = 41
SB_BODYPART_DROP_KICK = 68
SB_BODYPART_KEEPER_ARM = 69
SB_BODYPART_OTHER = 70
SB_BODYPART_NO_TOUCH = 106


def parse_str_ts(timestamp: str) -> float:
h, m, s = timestamp.split(":")
Expand All @@ -107,6 +121,36 @@ def _parse_coordinates(
)


def _parse_bodypart(event_dict: Dict) -> BodyPart:
bodypart_id = event_dict["body_part"]["id"]
if bodypart_id == SB_BODYPART_BOTH_HANDS:
body_part = BodyPart.BOTH_HANDS
elif bodypart_id == SB_BODYPART_CHEST:
body_part = BodyPart.CHEST
elif bodypart_id == SB_BODYPART_HEAD:
body_part = BodyPart.HEAD
elif bodypart_id == SB_BODYPART_LEFT_FOOT:
body_part = BodyPart.LEFT_FOOT
elif bodypart_id == SB_BODYPART_LEFT_HAND:
body_part = BodyPart.LEFT_HAND
elif bodypart_id == SB_BODYPART_RIGHT_FOOT:
body_part = BodyPart.RIGHT_FOOT
elif bodypart_id == SB_BODYPART_RIGHT_HAND:
body_part = BodyPart.RIGHT_HAND
elif bodypart_id == SB_BODYPART_DROP_KICK:
body_part = BodyPart.DROP_KICK
elif bodypart_id == SB_BODYPART_KEEPER_ARM:
body_part = BodyPart.KEEPER_ARM
elif bodypart_id == SB_BODYPART_OTHER:
body_part = BodyPart.OTHER
elif bodypart_id == SB_BODYPART_NO_TOUCH:
body_part = BodyPart.NO_TOUCH
else:
raise Exception(f"Unknown body part: {body_part_id}")

return body_part


def _parse_pass(pass_dict: Dict, team: Team, fidelity_version: int) -> Dict:
if "outcome" in pass_dict:
outcome_id = pass_dict["outcome"]["id"]
Expand Down Expand Up @@ -160,6 +204,9 @@ def _get_event_qualifiers(qualifiers_dict: Dict) -> List[Qualifier]:
elif qualifiers_dict["type"]["id"] == SB_EVENT_TYPE_GOAL_KICK:
qualifiers.append(SetPieceQualifier(value=SetPieceType.GOAL_KICK))

if "body_part" in qualifiers_dict:
qualifiers.append(BodyPartQualifier(_parse_bodypart(qualifiers_dict)))

return qualifiers


Expand All @@ -182,7 +229,10 @@ def _parse_shot(shot_dict: Dict) -> Dict:

qualifiers = _get_event_qualifiers(shot_dict)

return dict(result=result, qualifiers=qualifiers)
return dict(
result=result,
qualifiers=qualifiers,
)


def _parse_carry(carry_dict: Dict, fidelity_version: int) -> Dict:
Expand Down
16 changes: 16 additions & 0 deletions kloppy/tests/test_statsbomb.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
Orientation,
Provider,
EventType,
BodyPartQualifier,
BodyPart,
)
from kloppy.domain.models.common import DatasetType

Expand Down Expand Up @@ -68,6 +70,20 @@ def test_correct_deserialization(self):
attacking_direction=AttackingDirection.NOT_SET,
)

assert (
dataset.events[791].get_qualifier_value(BodyPartQualifier)
== BodyPart.HEAD
)

assert (
dataset.events[2231].get_qualifier_value(BodyPartQualifier)
== BodyPart.RIGHT_FOOT
)

assert (
dataset.events[195].get_qualifier_value(BodyPartQualifier) is None
)

def test_substitution(self):
"""
Test substitution events
Expand Down

0 comments on commit 34ce674

Please sign in to comment.