Skip to content

Commit

Permalink
Added new event types to Opta serializer
Browse files Browse the repository at this point in the history
  • Loading branch information
Bruno committed Nov 5, 2020
1 parent e180914 commit 4419cc3
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 106 deletions.
281 changes: 180 additions & 101 deletions kloppy/infra/serializers/event/opta/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,133 @@
Metadata,
Player,
Position,
RecoveryEvent,
BallOutEvent,
FoulCommittedEvent,
Qualifier,
SetPieceQualifier,
SetPieceType,
)
from kloppy.infra.serializers.event import EventDataSerializer
from kloppy.utils import Readable, performance_logging

logger = logging.getLogger(__name__)

EVENT_TYPE_START_PERIOD = 32
EVENT_TYPE_END_PERIOD = 30

EVENT_TYPE_PASS = 1
EVENT_TYPE_OFFSIDE_PASS = 2
EVENT_TYPE_TAKE_ON = 3
EVENT_TYPE_SHOT_MISS = 13
EVENT_TYPE_SHOT_POST = 14
EVENT_TYPE_SHOT_SAVED = 15
EVENT_TYPE_SHOT_GOAL = 16
EVENT_TYPE_BALL_OUT = 5
EVENT_TYPE_CORNER_AWARDED = 6
EVENT_TYPE_FOUL_COMMITTED = 4
EVENT_TYPE_RECOVERY = 49

BALL_OUT_EVENTS = [EVENT_TYPE_BALL_OUT, EVENT_TYPE_CORNER_AWARDED]

BALL_OWNING_EVENTS = (
EVENT_TYPE_PASS,
EVENT_TYPE_OFFSIDE_PASS,
EVENT_TYPE_TAKE_ON,
EVENT_TYPE_SHOT_MISS,
EVENT_TYPE_SHOT_POST,
EVENT_TYPE_SHOT_SAVED,
EVENT_TYPE_SHOT_GOAL,
EVENT_TYPE_RECOVERY,
)

EVENT_QUALIFIER_GOAL_KICK = 124
EVENT_QUALIFIER_FREE_KICK = 5
EVENT_QUALIFIER_THROW_IN = 107
EVENT_QUALIFIER_CORNER_KICK = 6
EVENT_QUALIFIER_PENALTY = 9
EVENT_QUALIFIER_KICK_OFF = 279

event_type_names = {
1: "pass",
2: "offside pass",
3: "take on",
4: "foul",
5: "out",
6: "corner awarded",
7: "tackle",
8: "interception",
9: "turnover",
10: "save",
11: "claim",
12: "clearance",
13: "miss",
14: "post",
15: "attempt saved",
16: "goal",
17: "card",
18: "player off",
19: "player on",
20: "player retired",
21: "player returns",
22: "player becomes goalkeeper",
23: "goalkeeper becomes player",
24: "condition change",
25: "official change",
26: "unknown26",
27: "start delay",
28: "end delay",
29: "unknown29",
30: "end",
31: "unknown31",
32: "start",
33: "unknown33",
34: "team set up",
35: "player changed position",
36: "player changed jersey number",
37: "collection end",
38: "temp_goal",
39: "temp_attempt",
40: "formation change",
41: "punch",
42: "good skill",
43: "deleted event",
44: "aerial",
45: "challenge",
46: "unknown46",
47: "rescinded card",
48: "unknown46",
49: "ball recovery",
50: "dispossessed",
51: "error",
52: "keeper pick-up",
53: "cross not claimed",
54: "smother",
55: "offside provoked",
56: "shield ball opp",
57: "foul throw in",
58: "penalty faced",
59: "keeper sweeper",
60: "chance missed",
61: "ball touch",
62: "unknown62",
63: "temp_save",
64: "resume",
65: "contentious referee decision",
66: "possession data",
67: "50/50",
68: "referee drop ball",
69: "failed to block",
70: "injury time announcement",
71: "coach setup",
72: "caught offside",
73: "other ball contact",
74: "blocked pass",
75: "delayed start",
76: "early end",
77: "player off pitch",
}


def _parse_f24_datetime(dt_str: str) -> float:
return (
Expand All @@ -45,30 +166,35 @@ def _parse_f24_datetime(dt_str: str) -> float:
)


def _parse_pass(qualifiers: Dict[int, str], outcome: int) -> Dict:
def _parse_pass(raw_qualifiers: Dict[int, str], outcome: int) -> Dict:
if outcome:
receiver_coordinates = Point(
x=float(qualifiers[140]), y=float(qualifiers[141])
x=float(raw_qualifiers[140]), y=float(raw_qualifiers[141])
)
result = PassResult.COMPLETE
else:
result = PassResult.INCOMPLETE
receiver_coordinates = None

qualifiers = _get_event_qualifiers(raw_qualifiers)

return dict(
result=result,
receiver_coordinates=receiver_coordinates,
receiver_player=None,
receive_timestamp=None,
qualifiers=qualifiers,
)


def _parse_offside_pass() -> Dict:
def _parse_offside_pass(raw_qualifiers: List) -> Dict:
qualifiers = _get_event_qualifiers(raw_qualifiers)
return dict(
result=PassResult.OFFSIDE,
receiver_coordinates=None,
receiver_player=None,
receive_timestamp=None,
qualifiers=qualifiers,
)


Expand All @@ -81,16 +207,18 @@ def _parse_take_on(outcome: int) -> Dict:


def _parse_shot(
qualifiers: Dict[int, str], type_id: int, coordinates: Point
raw_qualifiers: Dict[int, str], type_id: int, coordinates: Point
) -> Dict:
if type_id == EVENT_TYPE_SHOT_GOAL:
if 28 in qualifiers:
if 28 in raw_qualifiers:
coordinates = Point(x=100 - coordinates.x, y=100 - coordinates.y)
result = ShotResult.GOAL
else:
result = None

return dict(coordinates=coordinates, result=result)
qualifiers = _get_event_qualifiers(raw_qualifiers)

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


def _parse_team_players(
Expand Down Expand Up @@ -155,98 +283,22 @@ def _team_from_xml_elm(team_elm, f7_root) -> Team:
return team


EVENT_TYPE_START_PERIOD = 32
EVENT_TYPE_END_PERIOD = 30
def _get_event_qualifiers(raw_qualifiers: List) -> List[Qualifier]:
qualifiers = []
if EVENT_QUALIFIER_CORNER_KICK in raw_qualifiers:
qualifiers.append(SetPieceQualifier(value=SetPieceType.CORNER_KICK))
elif EVENT_QUALIFIER_FREE_KICK in raw_qualifiers:
qualifiers.append(SetPieceQualifier(value=SetPieceType.FREE_KICK))
elif EVENT_QUALIFIER_PENALTY in raw_qualifiers:
qualifiers.append(SetPieceQualifier(value=SetPieceType.PENALTY))
elif EVENT_QUALIFIER_THROW_IN in raw_qualifiers:
qualifiers.append(SetPieceQualifier(value=SetPieceType.THROW_IN))
elif EVENT_QUALIFIER_KICK_OFF in raw_qualifiers:
qualifiers.append(SetPieceQualifier(value=SetPieceType.KICK_OFF))
elif EVENT_QUALIFIER_GOAL_KICK in raw_qualifiers:
qualifiers.append(SetPieceQualifier(value=SetPieceType.GOAL_KICK))

EVENT_TYPE_PASS = 1
EVENT_TYPE_OFFSIDE_PASS = 1
EVENT_TYPE_TAKE_ON = 3
EVENT_TYPE_SHOT_MISS = 13
EVENT_TYPE_SHOT_POST = 14
EVENT_TYPE_SHOT_SAVED = 15
EVENT_TYPE_SHOT_GOAL = 16

event_type_names = {
1: "pass",
2: "offside pass",
3: "take on",
4: "foul",
5: "out",
6: "corner awarded",
7: "tackle",
8: "interception",
9: "turnover",
10: "save",
11: "claim",
12: "clearance",
13: "miss",
14: "post",
15: "attempt saved",
16: "goal",
17: "card",
18: "player off",
19: "player on",
20: "player retired",
21: "player returns",
22: "player becomes goalkeeper",
23: "goalkeeper becomes player",
24: "condition change",
25: "official change",
26: "unknown26",
27: "start delay",
28: "end delay",
29: "unknown29",
30: "end",
31: "unknown31",
32: "start",
33: "unknown33",
34: "team set up",
35: "player changed position",
36: "player changed jersey number",
37: "collection end",
38: "temp_goal",
39: "temp_attempt",
40: "formation change",
41: "punch",
42: "good skill",
43: "deleted event",
44: "aerial",
45: "challenge",
46: "unknown46",
47: "rescinded card",
48: "unknown46",
49: "ball recovery",
50: "dispossessed",
51: "error",
52: "keeper pick-up",
53: "cross not claimed",
54: "smother",
55: "offside provoked",
56: "shield ball opp",
57: "foul throw in",
58: "penalty faced",
59: "keeper sweeper",
60: "chance missed",
61: "ball touch",
62: "unknown62",
63: "temp_save",
64: "resume",
65: "contentious referee decision",
66: "possession data",
67: "50/50",
68: "referee drop ball",
69: "failed to block",
70: "injury time announcement",
71: "coach setup",
72: "caught offside",
73: "other ball contact",
74: "blocked pass",
75: "delayed start",
76: "early end",
77: "player off pitch",
}

BALL_OWNING_EVENTS = (1, 2, 3, 13, 14, 15, 16, 49)
return qualifiers


def _get_event_type_name(type_id: int) -> str:
Expand Down Expand Up @@ -402,7 +454,7 @@ def deserialize(
x = float(event_elm.attrib["x"])
y = float(event_elm.attrib["y"])
outcome = int(event_elm.attrib["outcome"])
qualifiers = {
raw_qualifiers = {
int(
qualifier_elm.attrib["qualifier_id"]
): qualifier_elm.attrib.get("value")
Expand Down Expand Up @@ -432,20 +484,23 @@ def deserialize(
)

if type_id == EVENT_TYPE_PASS:
pass_event_kwargs = _parse_pass(qualifiers, outcome)
pass_event_kwargs = _parse_pass(
raw_qualifiers, outcome
)
event = PassEvent.create(
**pass_event_kwargs,
**generic_event_kwargs,
)
elif type_id == EVENT_TYPE_OFFSIDE_PASS:
pass_event_kwargs = _parse_offside_pass()
pass_event_kwargs = _parse_offside_pass(raw_qualifiers)
event = PassEvent.create(
**pass_event_kwargs,
**generic_event_kwargs,
)
elif type_id == EVENT_TYPE_TAKE_ON:
take_on_event_kwargs = _parse_take_on(outcome)
event = TakeOnEvent.create(
qualifiers=None,
**take_on_event_kwargs,
**generic_event_kwargs,
)
Expand All @@ -456,18 +511,42 @@ def deserialize(
EVENT_TYPE_SHOT_GOAL,
):
shot_event_kwargs = _parse_shot(
qualifiers,
raw_qualifiers,
type_id,
coordinates=generic_event_kwargs["coordinates"],
)
kwargs = {}
kwargs.update(generic_event_kwargs)
kwargs.update(shot_event_kwargs)
event = ShotEvent.create(**kwargs)

elif type_id == EVENT_TYPE_RECOVERY:
event = RecoveryEvent.create(
result=None,
qualifiers=None,
**generic_event_kwargs,
)

elif type_id == EVENT_TYPE_FOUL_COMMITTED:
event = FoulCommittedEvent.create(
result=None,
qualifiers=None,
**generic_event_kwargs,
)

elif type_id in BALL_OUT_EVENTS:
generic_event_kwargs["ball_state"] = BallState.DEAD
event = BallOutEvent.create(
result=None,
qualifiers=None,
**generic_event_kwargs,
)

else:
event = GenericEvent.create(
**generic_event_kwargs,
result=None,
qualifiers=None,
event_name=_get_event_type_name(type_id),
)

Expand Down
2 changes: 1 addition & 1 deletion kloppy/infra/serializers/event/statsbomb/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def _get_event_qualifiers(qualifiers_dict: Dict) -> List[Qualifier]:
qualifiers.append(
SetPieceQualifier(value=SetPieceType.CORNER_KICK)
)
elif qualifiers_dict["type"]["id"] == SB_EVENT_TYPE_CORNER_KICK:
elif qualifiers_dict["type"]["id"] == SB_EVENT_TYPE_FREE_KICK:
qualifiers.append(SetPieceQualifier(value=SetPieceType.FREE_KICK))
elif qualifiers_dict["type"]["id"] == SB_EVENT_TYPE_PENALTY:
qualifiers.append(SetPieceQualifier(value=SetPieceType.PENALTY))
Expand Down
Loading

0 comments on commit 4419cc3

Please sign in to comment.