diff --git a/examples/datasets/statsbomb.py b/examples/datasets/statsbomb.py index 9e1b1d8a..831f8b12 100644 --- a/examples/datasets/statsbomb.py +++ b/examples/datasets/statsbomb.py @@ -25,7 +25,7 @@ def main(): with performance_logging("transform", logger=logger): # convert to TRACAB coordinates dataset = dataset.transform( - to_orientation="FIXED_HOME_AWAY", + to_orientation="STATIC_HOME_AWAY", to_pitch_dimensions=[(-5500, 5500), (-3300, 3300)], ) diff --git a/kloppy/domain/models/common.py b/kloppy/domain/models/common.py index 485bd067..746d7412 100644 --- a/kloppy/domain/models/common.py +++ b/kloppy/domain/models/common.py @@ -232,25 +232,59 @@ def __repr__(self): return self.value -class AttackingDirection(Enum): +@dataclass +class Period: """ - AttackingDirection + Period Attributes: - HOME_AWAY (AttackingDirection): Home team is playing from left to right - AWAY_HOME (AttackingDirection): Home team is playing from right to left - NOT_SET (AttackingDirection): not set yet + id: `1` for first half, `2` for second half, `3` for first overtime, + `4` for second overtime, and `5` for penalty shootouts + start_timestamp: timestamp given by provider (can be unix timestamp or relative) + end_timestamp: timestamp given by provider (can be unix timestamp or relative) """ - HOME_AWAY = "home-away" - AWAY_HOME = "away-home" - NOT_SET = "not-set" + id: int + start_timestamp: float + end_timestamp: float - def __repr__(self): - return self.value + def contains(self, timestamp: float): + return self.start_timestamp <= timestamp <= self.end_timestamp + + @property + def duration(self): + return self.end_timestamp - self.start_timestamp + + def __eq__(self, other): + return isinstance(other, Period) and other.id == self.id class Orientation(Enum): + """ + The attacking direction of each team in a dataset. + + Attributes: + BALL_OWNING_TEAM: The team that is currently in possession of the ball + plays from left to right. + ACTION_EXECUTING_TEAM: The team that executes the action + plays from left to right. Used in event stream data only. Equivalent + to "BALL_OWNING_TEAM" for tracking data. + HOME_AWAY: The home team plays from left to right in the first period. + The away team plays from left to right in the second period. + AWAY_HOME: The away team plays from left to right in the first period. + The home team plays from left to right in the second period. + STATIC_HOME_AWAY: The home team plays from left to right in both periods. + STATIC_AWAY_HOME: The away team plays from left to right in both periods. + NOT_SET: The attacking direction is not defined. + + Notes: + The attacking direction is not defined for penalty shootouts in the + `HOME_AWAY`, `AWAY_HOME`, `STATIC_HOME_AWAY`, and `STATIC_AWAY_HOME` + orientations. This period is ignored in orientation transforms + involving one of these orientations and keeps its original + attacking direction. + """ + # change when possession changes BALL_OWNING_TEAM = "ball-owning-team" @@ -258,60 +292,119 @@ class Orientation(Enum): ACTION_EXECUTING_TEAM = "action-executing-team" # changes during half-time - HOME_TEAM = "home-team" - AWAY_TEAM = "away-team" + HOME_AWAY = "home-away" + AWAY_HOME = "away-home" # won't change during match - FIXED_HOME_AWAY = "fixed-home-away" - FIXED_AWAY_HOME = "fixed-away-home" + STATIC_HOME_AWAY = "fixed-home-away" + STATIC_AWAY_HOME = "fixed-away-home" # Not set in dataset NOT_SET = "not-set" - def get_orientation_factor( - self, - attacking_direction: AttackingDirection, - ball_owning_team: Team, - action_executing_team: Team, - ) -> int: - if self == Orientation.FIXED_HOME_AWAY: - return -1 - elif self == Orientation.FIXED_AWAY_HOME: - return 1 - elif self == Orientation.HOME_TEAM: - if attacking_direction == AttackingDirection.HOME_AWAY: - return -1 - elif attacking_direction == AttackingDirection.AWAY_HOME: - return 1 - else: - raise OrientationError("AttackingDirection not set") - elif self == Orientation.AWAY_TEAM: - if attacking_direction == AttackingDirection.AWAY_HOME: - return -1 - elif attacking_direction == AttackingDirection.HOME_AWAY: - return 1 - else: - raise OrientationError("AttackingDirection not set") - elif self == Orientation.BALL_OWNING_TEAM: - if ball_owning_team.ground == Ground.HOME: - return -1 - elif ball_owning_team.ground == Ground.AWAY: - return 1 - else: + def __repr__(self): + return self.value + + +class AttackingDirection(Enum): + """ + AttackingDirection + + Attributes: + LTR (AttackingDirection): Home team is playing from left to right + RTL (AttackingDirection): Home team is playing from right to left + NOT_SET (AttackingDirection): not set yet + """ + + LTR = "left-to-right" + RTL = "right-to-left" + NOT_SET = "not-set" + + @staticmethod + def from_orientation( + orientation: Orientation, + period: Optional[Period] = None, + ball_owning_team: Optional[Team] = None, + action_executing_team: Optional[Team] = None, + ) -> "AttackingDirection": + """Determines the attacking direction for a specific data record. + + Args: + orientation: The orientation of the dataset. + period: The period of the data record. + ball_owning_team: The team that is in possession of the ball. + action_executing_team: The team that executes the action. + + Raises: + OrientationError: If the attacking direction cannot be determined + from the given data. + + Returns: + The attacking direction for the given data record. + """ + if orientation == Orientation.STATIC_HOME_AWAY: + return AttackingDirection.LTR + if orientation == Orientation.STATIC_AWAY_HOME: + return AttackingDirection.RTL + if orientation == Orientation.HOME_AWAY: + if period is None: raise OrientationError( - f"Invalid ball_owning_team: {ball_owning_team}" + "You must provide a period to determine the attacking direction" ) - elif self == Orientation.ACTION_EXECUTING_TEAM: - if action_executing_team.ground == Ground.HOME: - return -1 - elif action_executing_team.ground == Ground.AWAY: - return 1 - else: + dirmap = { + 1: AttackingDirection.LTR, + 2: AttackingDirection.RTL, + 3: AttackingDirection.LTR, + 4: AttackingDirection.RTL, + } + if period.id in dirmap: + return dirmap[period.id] + raise OrientationError( + "This orientation is not defined for period %s" % period.id + ) + if orientation == Orientation.AWAY_HOME: + if period is None: raise OrientationError( - f"Invalid action_executing_team: {action_executing_team}" + "You must provide a period to determine the attacking direction" ) - else: - raise OrientationError(f"Unknown orientation: {self}") + dirmap = { + 1: AttackingDirection.RTL, + 2: AttackingDirection.LTR, + 3: AttackingDirection.RTL, + 4: AttackingDirection.LTR, + } + if period.id in dirmap: + return dirmap[period.id] + raise OrientationError( + "This orientation is not defined for period %s" % period.id + ) + if orientation == Orientation.BALL_OWNING_TEAM: + if ball_owning_team is None: + raise OrientationError( + "You must provide the ball owning team to determine the attacking direction" + ) + if ball_owning_team is not None: + if ball_owning_team.ground == Ground.HOME: + return AttackingDirection.LTR + if ball_owning_team.ground == Ground.AWAY: + return AttackingDirection.RTL + raise OrientationError( + "Invalid ball_owning_team: %s", ball_owning_team + ) + return AttackingDirection.NOT_SET + if orientation == Orientation.ACTION_EXECUTING_TEAM: + if action_executing_team is None: + raise ValueError( + "You must provide the action executing team to determine the attacking direction" + ) + if action_executing_team.ground == Ground.HOME: + return AttackingDirection.LTR + if action_executing_team.ground == Ground.AWAY: + return AttackingDirection.RTL + raise OrientationError( + "Invalid action_executing_team: %s", action_executing_team + ) + raise OrientationError("Unknown orientation: %s", orientation) def __repr__(self): return self.value @@ -325,43 +418,6 @@ class VerticalOrientation(Enum): BOTTOM_TO_TOP = "bottom-to-top" -@dataclass -class Period: - """ - Period - - Attributes: - id: `1` for first half, `2` for second half - start_timestamp: timestamp given by provider (can be unix timestamp or relative) - end_timestamp: timestamp given by provider (can be unix timestamp or relative) - attacking_direction: See [`AttackingDirection`][kloppy.domain.models.common.AttackingDirection] - """ - - id: int - start_timestamp: float - end_timestamp: float - attacking_direction: Optional[ - AttackingDirection - ] = AttackingDirection.NOT_SET - - def contains(self, timestamp: float): - return self.start_timestamp <= timestamp <= self.end_timestamp - - @property - def attacking_direction_set(self): - return self.attacking_direction != AttackingDirection.NOT_SET - - def set_attacking_direction(self, attacking_direction: AttackingDirection): - self.attacking_direction = attacking_direction - - @property - def duration(self): - return self.end_timestamp - self.start_timestamp - - def __eq__(self, other): - return isinstance(other, Period) and other.id == self.id - - class Origin(Enum): """ Attributes: @@ -788,6 +844,23 @@ def set_refs( self.prev_record = prev self.next_record = next_ + @property + def attacking_direction(self): + if ( + self.dataset + and self.dataset.metadata + and self.dataset.metadata.orientation is not None + ): + try: + return AttackingDirection.from_orientation( + self.dataset.metadata.orientation, + period=self.period, + ball_owning_team=self.ball_owning_team, + ) + except OrientationError: + return AttackingDirection.NOT_SET + return AttackingDirection.NOT_SET + def matches(self, filter_) -> bool: if filter_ is None: return True @@ -848,6 +921,11 @@ class Metadata: frame_rate: Optional[float] = None attributes: Optional[Dict] = field(default_factory=dict, compare=False) + def __post_init__(self): + if self.coordinate_system is not None: + # set the pitch dimensions from the coordinate system + self.pitch_dimensions = self.coordinate_system.pitch_dimensions + T = TypeVar("T", bound="DataRecord") diff --git a/kloppy/domain/models/event.py b/kloppy/domain/models/event.py index c40147d3..b25f0d08 100644 --- a/kloppy/domain/models/event.py +++ b/kloppy/domain/models/event.py @@ -12,7 +12,11 @@ TYPE_CHECKING, ) -from kloppy.domain.models.common import DatasetType +from kloppy.domain.models.common import ( + DatasetType, + AttackingDirection, + OrientationError, +) from kloppy.utils import ( camelcase_to_snakecase, removes_suffix, @@ -530,6 +534,24 @@ def event_type(self) -> EventType: def event_name(self) -> str: raise NotImplementedError + @property + def attacking_direction(self): + if ( + self.dataset + and self.dataset.metadata + and self.dataset.metadata.orientation is not None + ): + try: + return AttackingDirection.from_orientation( + self.dataset.metadata.orientation, + period=self.period, + ball_owning_team=self.ball_owning_team, + action_executing_team=self.team, + ) + except OrientationError: + return AttackingDirection.NOT_SET + return AttackingDirection.NOT_SET + def get_qualifier_value(self, qualifier_type: Type[Qualifier]): """ Returns the Qualifier of a certain type, or None if qualifier is not present. diff --git a/kloppy/domain/services/__init__.py b/kloppy/domain/services/__init__.py index 404a7f8c..d72375cc 100644 --- a/kloppy/domain/services/__init__.py +++ b/kloppy/domain/services/__init__.py @@ -15,7 +15,7 @@ def avg(items: List[float]) -> float: def attacking_direction_from_frame(frame: Frame) -> AttackingDirection: - """This method should only be called for the first frame of a""" + """This method should only be called for the first frame of a period.""" avg_x_home = avg( [ player_data.coordinates.x @@ -32,6 +32,6 @@ def attacking_direction_from_frame(frame: Frame) -> AttackingDirection: ) if avg_x_home < avg_x_away: - return AttackingDirection.HOME_AWAY + return AttackingDirection.LTR else: - return AttackingDirection.AWAY_HOME + return AttackingDirection.RTL diff --git a/kloppy/domain/services/transformers/dataset.py b/kloppy/domain/services/transformers/dataset.py index 3ddb1d46..11f44b52 100644 --- a/kloppy/domain/services/transformers/dataset.py +++ b/kloppy/domain/services/transformers/dataset.py @@ -7,10 +7,12 @@ AttackingDirection, Dataset, DatasetFlag, + DataRecord, EventDataset, Frame, Orientation, PitchDimensions, + Period, Point, Point3D, Team, @@ -90,6 +92,10 @@ def _needs_coordinate_system_change(self): def _needs_pitch_dimensions_change(self): return self._from_pitch_dimensions != self._to_pitch_dimensions + @property + def _needs_orientation_change(self): + return self._from_orientation != self._to_orientation + def change_point_dimensions( self, point: Union[Point, Point3D, None] ) -> Union[Point, Point3D, None]: @@ -130,7 +136,7 @@ def flip_point( def __needs_flip( self, ball_owning_team: Team, - attacking_direction: AttackingDirection, + period: Period, action_executing_team: Optional[Team] = None, ) -> bool: if ( @@ -143,37 +149,40 @@ def __needs_flip( if action_executing_team is None: action_executing_team = ball_owning_team - orientation_factor_from = ( - self._from_orientation.get_orientation_factor( - ball_owning_team=ball_owning_team, - attacking_direction=attacking_direction, - action_executing_team=action_executing_team, - ) + attacking_direction_from = AttackingDirection.from_orientation( + self._from_orientation, + period=period, + ball_owning_team=ball_owning_team, + action_executing_team=action_executing_team, ) - orientation_factor_to = ( - self._to_orientation.get_orientation_factor( - ball_owning_team=ball_owning_team, - attacking_direction=attacking_direction, - action_executing_team=action_executing_team, - ) + attacking_direction_to = AttackingDirection.from_orientation( + self._to_orientation, + period=period, + ball_owning_team=ball_owning_team, + action_executing_team=action_executing_team, + ) + flip = ( + attacking_direction_from != attacking_direction_to + and attacking_direction_to != AttackingDirection.NOT_SET ) - flip = orientation_factor_from != orientation_factor_to return flip def transform_frame(self, frame: Frame) -> Frame: # Change coordinate system if self._needs_coordinate_system_change: frame = self.__change_frame_coordinate_system(frame) + # Change dimensions elif self._needs_pitch_dimensions_change: frame = self.__change_frame_dimensions(frame) # Flip frame based on orientation - if self.__needs_flip( - ball_owning_team=frame.ball_owning_team, - attacking_direction=frame.period.attacking_direction, - ): - frame = self.__flip_frame(frame) + if self._needs_orientation_change: + if self.__needs_flip( + ball_owning_team=frame.ball_owning_team, + period=frame.period, + ): + frame = self.__flip_frame(frame) return frame @@ -281,20 +290,22 @@ def transform_event(self, event: Event) -> Event: # Change coordinate system if self._needs_coordinate_system_change: event = self.__change_event_coordinate_system(event) + # Change dimensions elif self._needs_pitch_dimensions_change: event = self.__change_event_dimensions(event) # Flip event based on orientation - if self.__needs_flip( - ball_owning_team=event.ball_owning_team, - attacking_direction=event.period.attacking_direction, - action_executing_team=event.team, - ): - event = self.__flip_event(event) - - if event.freeze_frame: - event.freeze_frame = self.transform_frame(event.freeze_frame) + if self._needs_orientation_change: + if self.__needs_flip( + ball_owning_team=event.ball_owning_team, + period=event.period, + action_executing_team=event.team, + ): + event = self.__flip_event(event) + + if event.freeze_frame: + event.freeze_frame = self.transform_frame(event.freeze_frame) return event diff --git a/kloppy/infra/serializers/event/datafactory/deserializer.py b/kloppy/infra/serializers/event/datafactory/deserializer.py index 2bac39c0..99d8d289 100644 --- a/kloppy/infra/serializers/event/datafactory/deserializer.py +++ b/kloppy/infra/serializers/event/datafactory/deserializer.py @@ -416,9 +416,6 @@ def deserialize(self, inputs: DatafactoryInputs) -> EventDataset: id=half, start_timestamp=start_ts[half], end_timestamp=end_ts, - attacking_direction=AttackingDirection.HOME_AWAY - if half % 2 == 1 - else AttackingDirection.AWAY_HOME, ) # exclude goals, already listed as shots too @@ -576,7 +573,7 @@ def deserialize(self, inputs: DatafactoryInputs) -> EventDataset: periods=sorted(periods.values(), key=lambda p: p.id), pitch_dimensions=transformer.get_to_coordinate_system().pitch_dimensions, frame_rate=None, - orientation=Orientation.HOME_TEAM, + orientation=Orientation.HOME_AWAY, flags=DatasetFlag.BALL_OWNING_TEAM, score=score, provider=Provider.DATAFACTORY, diff --git a/kloppy/infra/serializers/event/sportec/deserializer.py b/kloppy/infra/serializers/event/sportec/deserializer.py index fa29f753..5f43ff37 100644 --- a/kloppy/infra/serializers/event/sportec/deserializer.py +++ b/kloppy/infra/serializers/event/sportec/deserializer.py @@ -412,25 +412,10 @@ def deserialize(self, inputs: SportecEventDataInputs) -> EventDataset: team_left = event_chain[SPORTEC_EVENT_NAME_KICKOFF][ "TeamLeft" ] - if team_left == home_team.team_id: - # goal of home team is on the left side. - # this means they attack from left to right - orientation = Orientation.FIXED_HOME_AWAY - period.set_attacking_direction( - AttackingDirection.HOME_AWAY - ) - else: - orientation = Orientation.FIXED_AWAY_HOME - period.set_attacking_direction( - AttackingDirection.AWAY_HOME - ) - else: - last_period = periods[-1] - period.set_attacking_direction( - AttackingDirection.AWAY_HOME - if last_period.attacking_direction - == AttackingDirection.HOME_AWAY - else AttackingDirection.HOME_AWAY + orientation = ( + Orientation.HOME_AWAY + if team_left == home_team.team_id + else Orientation.AWAY_HOME ) periods.append(period) diff --git a/kloppy/infra/serializers/event/statsbomb/deserializer.py b/kloppy/infra/serializers/event/statsbomb/deserializer.py index 419e96ac..920774a0 100644 --- a/kloppy/infra/serializers/event/statsbomb/deserializer.py +++ b/kloppy/infra/serializers/event/statsbomb/deserializer.py @@ -228,7 +228,7 @@ def create_periods(self, raw_events): ::2 ] # recorded for each team, take every other periods = [] - for (start_event, end_event) in zip_longest( + for start_event, end_event in zip_longest( half_start_and_end_events[::2], half_start_and_end_events[1::2] ): if ( diff --git a/kloppy/infra/serializers/tracking/metrica_csv.py b/kloppy/infra/serializers/tracking/metrica_csv.py index fa61690c..163c8d30 100644 --- a/kloppy/infra/serializers/tracking/metrica_csv.py +++ b/kloppy/infra/serializers/tracking/metrica_csv.py @@ -1,4 +1,5 @@ import logging +import warnings from collections import namedtuple from typing import Tuple, Dict, Iterator, IO, NamedTuple @@ -204,13 +205,6 @@ def deserialize( if not periods or period.id != periods[-1].id: periods.append(period) - if not period.attacking_direction_set: - period.set_attacking_direction( - attacking_direction=attacking_direction_from_frame( - frame - ) - ) - if n == 0: teams = [home_partial_frame.team, away_partial_frame.team] @@ -218,11 +212,21 @@ def deserialize( if self.limit and n >= self.limit: break - orientation = ( - Orientation.FIXED_HOME_AWAY - if periods[0].attacking_direction == AttackingDirection.HOME_AWAY - else Orientation.FIXED_AWAY_HOME - ) + try: + first_frame = next( + frame for frame in frames if frame.period.id == 1 + ) + orientation = ( + Orientation.HOME_AWAY + if attacking_direction_from_frame(first_frame) + == AttackingDirection.LTR + else Orientation.AWAY_HOME + ) + except StopIteration: + warnings.warn( + "Could not determine orientation of dataset, defaulting to NOT_SET" + ) + orientation = Orientation.NOT_SET metadata = Metadata( teams=teams, diff --git a/kloppy/infra/serializers/tracking/metrica_epts/metadata.py b/kloppy/infra/serializers/tracking/metrica_epts/metadata.py index f87ffc46..4200fbe2 100644 --- a/kloppy/infra/serializers/tracking/metrica_epts/metadata.py +++ b/kloppy/infra/serializers/tracking/metrica_epts/metadata.py @@ -62,33 +62,26 @@ def _load_periods( ] periods = [] + start_attacking_direction = AttackingDirection.NOT_SET for idx, period_name in enumerate(period_names): # the attacking direction is only defined for the first period # and alternates between periods - invert = idx % 2 - if ( - provider_teams_params[Ground.HOME].get( - "attack_direction_first_half" - ) - == "left_to_right" - ): - attacking_direction = [ - AttackingDirection.HOME_AWAY, - AttackingDirection.AWAY_HOME, - ][invert] - elif ( - provider_teams_params[Ground.HOME].get( - "attack_direction_first_half" - ) - == "right_to_left" - ): - attacking_direction = [ - AttackingDirection.AWAY_HOME, - AttackingDirection.HOME_AWAY, - ][invert] - else: - attacking_direction = AttackingDirection.NOT_SET + if idx == 0: + if ( + provider_teams_params[Ground.HOME].get( + "attack_direction_first_half" + ) + == "left_to_right" + ): + start_attacking_direction = AttackingDirection.LTR + elif ( + provider_teams_params[Ground.HOME].get( + "attack_direction_first_half" + ) + == "right_to_left" + ): + start_attacking_direction = AttackingDirection.RTL start_key = f"{period_name}_start" end_key = f"{period_name}_end" @@ -99,14 +92,13 @@ def _load_periods( start_timestamp=float(provider_params[start_key]) / frame_rate, end_timestamp=float(provider_params[end_key]) / frame_rate, - attacking_direction=attacking_direction, ) ) else: # done break - return periods + return periods, start_attacking_direction def _load_players(players_elm, team: Team) -> List[Player]: @@ -293,24 +285,21 @@ def load_metadata( frame_rate = int(metadata.find("GlobalConfig").find("FrameRate")) pitch_dimensions = _load_pitch_dimensions(metadata, sensors) - periods = _load_periods(metadata, _team_map, frame_rate) + periods, start_attacking_direction = _load_periods( + metadata, _team_map, frame_rate + ) - if periods: - start_attacking_direction = periods[0].attacking_direction + if start_attacking_direction != AttackingDirection.NOT_SET: + orientation = ( + Orientation.HOME_AWAY + if start_attacking_direction == AttackingDirection.LTR + else Orientation.AWAY_HOME + ) else: - start_attacking_direction = None - - orientation = ( - ( - Orientation.HOME_TEAM - if start_attacking_direction == AttackingDirection.HOME_AWAY - else Orientation.AWAY_TEAM + warnings.warn( + "Could not determine orientation of dataset, defaulting to NOT_SET" ) - if start_attacking_direction != AttackingDirection.NOT_SET - else Orientation.NOT_SET - ) - - metadata.orientation = orientation + orientation = Orientation.NOT_SET if provider and pitch_dimensions: from_coordinate_system = build_coordinate_system( diff --git a/kloppy/infra/serializers/tracking/secondspectrum.py b/kloppy/infra/serializers/tracking/secondspectrum.py index e1f0dcb0..a5dfb1a0 100644 --- a/kloppy/infra/serializers/tracking/secondspectrum.py +++ b/kloppy/infra/serializers/tracking/secondspectrum.py @@ -1,5 +1,6 @@ import json import logging +import warnings from typing import Tuple, Dict, Optional, Union, NamedTuple, IO from lxml import objectify @@ -261,21 +262,24 @@ def _iter(): frame = transformer.transform_frame(frame) frames.append(frame) - if not period.attacking_direction_set: - period.set_attacking_direction( - attacking_direction=attacking_direction_from_frame( - frame - ) - ) - if self.limit and n + 1 >= self.limit: break - orientation = ( - Orientation.FIXED_HOME_AWAY - if periods[0].attacking_direction == AttackingDirection.HOME_AWAY - else Orientation.FIXED_AWAY_HOME - ) + try: + first_frame = next( + frame for frame in frames if frame.period.id == 1 + ) + orientation = ( + Orientation.HOME_AWAY + if attacking_direction_from_frame(first_frame) + == AttackingDirection.LTR + else Orientation.AWAY_HOME + ) + except StopIteration: + warnings.warn( + "Could not determine orientation of dataset, defaulting to NOT_SET" + ) + orientation = Orientation.NOT_SET metadata = Metadata( teams=teams, diff --git a/kloppy/infra/serializers/tracking/skillcorner.py b/kloppy/infra/serializers/tracking/skillcorner.py index a255e501..bbe3ca3d 100644 --- a/kloppy/infra/serializers/tracking/skillcorner.py +++ b/kloppy/infra/serializers/tracking/skillcorner.py @@ -1,4 +1,5 @@ import logging +import warnings from typing import List, Dict, Tuple, NamedTuple, IO, Optional, Union from enum import Enum, Flag from collections import Counter @@ -160,35 +161,33 @@ def _timestamp_from_timestring(cls, timestring): raise ValueError("Invalid timestring format") @classmethod - def _set_skillcorner_attacking_directions(cls, frames, periods): + def _get_skillcorner_attacking_directions(cls, frames, periods): """ with only partial tracking data we cannot rely on a single frame to infer the attacking directions as a simple average of only some players x-coords might not reflect the attacking direction. """ - attacking_directions = [] - - for frame in frames: - if len(frame.players_data) > 0: - attacking_directions.append( - attacking_direction_from_frame(frame) - ) - else: - attacking_directions.append(AttackingDirection.NOT_SET) - - frame_periods = np.array([_frame.period.id for _frame in frames]) + attacking_directions = {} + frame_period_ids = np.array([_frame.period.id for _frame in frames]) + frame_attacking_directions = np.array( + [ + attacking_direction_from_frame(frame) + if len(frame.players_data) > 0 + else AttackingDirection.NOT_SET + for frame in frames + ] + ) - for period in periods.keys(): - if period in frame_periods: + for period_id in periods.keys(): + if period_id in frame_period_ids: count = Counter( - np.array(attacking_directions)[frame_periods == period] + frame_attacking_directions[frame_period_ids == period_id] ) - att_direction = count.most_common()[0][0] - periods[period].attacking_direction = att_direction + attacking_directions[period_id] = count.most_common()[0][0] else: - periods[ - period - ].attacking_direction = AttackingDirection.NOT_SET + attacking_directions[period_id] = AttackingDirection.NOT_SET + + return attacking_directions def __load_json(self, file): if Path(file.name).suffix == ".jsonl": @@ -394,15 +393,18 @@ def _iter(): if self.limit and n_frames >= self.limit: break - self._set_skillcorner_attacking_directions(frames, periods) - - frame_rate = 10 - - orientation = ( - Orientation.HOME_TEAM - if periods[1].attacking_direction == AttackingDirection.HOME_AWAY - else Orientation.AWAY_TEAM + attacking_directions = self._get_skillcorner_attacking_directions( + frames, periods ) + if attacking_directions[1] == AttackingDirection.LTR: + orientation = Orientation.HOME_AWAY + elif attacking_directions[1] == AttackingDirection.RTL: + orientation = Orientation.AWAY_HOME + else: + warnings.warn( + "Could not determine orientation of dataset, defaulting to NOT_SET" + ) + orientation = Orientation.NOT_SET metadata = Metadata( teams=teams, @@ -412,7 +414,7 @@ def _iter(): home=metadata["home_team_score"], away=metadata["away_team_score"], ), - frame_rate=frame_rate, + frame_rate=10, orientation=orientation, provider=Provider.SKILLCORNER, flags=~(DatasetFlag.BALL_STATE | DatasetFlag.BALL_OWNING_TEAM), diff --git a/kloppy/infra/serializers/tracking/sportec/deserializer.py b/kloppy/infra/serializers/tracking/sportec/deserializer.py index 45b05b1f..3458edfd 100644 --- a/kloppy/infra/serializers/tracking/sportec/deserializer.py +++ b/kloppy/infra/serializers/tracking/sportec/deserializer.py @@ -1,4 +1,5 @@ import logging +import warnings from collections import defaultdict from typing import NamedTuple, Optional, Union, IO @@ -195,24 +196,26 @@ def _iter(): frames = [] for n, frame in enumerate(_iter()): frame = transformer.transform_frame(frame) - frames.append(frame) - if not frame.period.attacking_direction_set: - frame.period.set_attacking_direction( - attacking_direction=attacking_direction_from_frame( - frame - ) - ) - if self.limit and n >= self.limit: break - orientation = ( - Orientation.FIXED_HOME_AWAY - if periods[0].attacking_direction == AttackingDirection.HOME_AWAY - else Orientation.FIXED_AWAY_HOME - ) + try: + first_frame = next( + frame for frame in frames if frame.period.id == 1 + ) + orientation = ( + Orientation.HOME_AWAY + if attacking_direction_from_frame(first_frame) + == AttackingDirection.LTR + else Orientation.AWAY_HOME + ) + except StopIteration: + warnings.warn( + "Could not determine orientation of dataset, defaulting to NOT_SET" + ) + orientation = Orientation.NOT_SET metadata = Metadata( teams=teams, diff --git a/kloppy/infra/serializers/tracking/statsperform.py b/kloppy/infra/serializers/tracking/statsperform.py index 6c5d6687..dada36da 100644 --- a/kloppy/infra/serializers/tracking/statsperform.py +++ b/kloppy/infra/serializers/tracking/statsperform.py @@ -1,5 +1,6 @@ import json import logging +import warnings from typing import IO, Any, Dict, List, NamedTuple, Optional, Union from lxml import objectify @@ -309,21 +310,24 @@ def _iter(): frame = transformer.transform_frame(frame) frames.append(frame) - if not period.attacking_direction_set: - period.set_attacking_direction( - attacking_direction=attacking_direction_from_frame( - frame - ) - ) - if self.limit and n >= self.limit: break - orientation = ( - Orientation.FIXED_HOME_AWAY - if periods[1].attacking_direction == AttackingDirection.HOME_AWAY - else Orientation.FIXED_AWAY_HOME - ) + try: + first_frame = next( + frame for frame in frames if frame.period.id == 1 + ) + orientation = ( + Orientation.HOME_AWAY + if attacking_direction_from_frame(first_frame) + == AttackingDirection.LTR + else Orientation.AWAY_HOME + ) + except StopIteration: + warnings.warn( + "Could not determine orientation of dataset, defaulting to NOT_SET" + ) + orientation = Orientation.NOT_SET meta_data = Metadata( teams=teams_list, diff --git a/kloppy/infra/serializers/tracking/tracab.py b/kloppy/infra/serializers/tracking/tracab.py index f7a0e162..f9cd7729 100644 --- a/kloppy/infra/serializers/tracking/tracab.py +++ b/kloppy/infra/serializers/tracking/tracab.py @@ -1,4 +1,5 @@ import logging +import warnings from typing import Tuple, Dict, NamedTuple, IO, Optional, Union from lxml import objectify @@ -184,24 +185,26 @@ def _iter(): frame = self._frame_from_line(teams, period, line, frame_rate) frame = transformer.transform_frame(frame) - frames.append(frame) - if not period.attacking_direction_set: - period.set_attacking_direction( - attacking_direction=attacking_direction_from_frame( - frame - ) - ) - if self.limit and n >= self.limit: break - orientation = ( - Orientation.FIXED_HOME_AWAY - if periods[0].attacking_direction == AttackingDirection.HOME_AWAY - else Orientation.FIXED_AWAY_HOME - ) + try: + first_frame = next( + frame for frame in frames if frame.period.id == 1 + ) + orientation = ( + Orientation.HOME_AWAY + if attacking_direction_from_frame(first_frame) + == AttackingDirection.LTR + else Orientation.AWAY_HOME + ) + except StopIteration: + warnings.warn( + "Could not determine orientation of dataset, defaulting to NOT_SET" + ) + orientation = Orientation.NOT_SET metadata = Metadata( teams=teams, diff --git a/kloppy/tests/issues/issue_113/test_issue_113.py b/kloppy/tests/issues/issue_113/test_issue_113.py index ea60a709..b63d48ba 100644 --- a/kloppy/tests/issues/issue_113/test_issue_113.py +++ b/kloppy/tests/issues/issue_113/test_issue_113.py @@ -21,7 +21,7 @@ def kloppy_load_data(f7, f24): dataset = opta.load(f7_data=f7, f24_data=f24) events = dataset.transform( - to_orientation=Orientation.FIXED_HOME_AWAY + to_orientation=Orientation.STATIC_HOME_AWAY ).to_pandas( additional_columns={ "event_name": lambda event: str(getattr(event, "event_name", "")), diff --git a/kloppy/tests/test_datafactory.py b/kloppy/tests/test_datafactory.py index ba2c029e..731a5aab 100644 --- a/kloppy/tests/test_datafactory.py +++ b/kloppy/tests/test_datafactory.py @@ -30,7 +30,7 @@ def test_correct_deserialization(self, event_data: str): assert len(dataset.metadata.periods) == 2 assert dataset.events[10].ball_owning_team == dataset.metadata.teams[1] assert dataset.events[23].ball_owning_team == dataset.metadata.teams[0] - assert dataset.metadata.orientation == Orientation.HOME_TEAM + assert dataset.metadata.orientation == Orientation.HOME_AWAY assert dataset.metadata.teams[0].name == "Team A" assert dataset.metadata.teams[0].ground == Ground.HOME assert dataset.metadata.teams[1].name == "Team B" @@ -47,13 +47,11 @@ def test_correct_deserialization(self, event_data: str): id=1, start_timestamp=0, end_timestamp=2912, - attacking_direction=AttackingDirection.HOME_AWAY, ) assert dataset.metadata.periods[1] == Period( id=2, start_timestamp=2700, end_timestamp=5710, - attacking_direction=AttackingDirection.AWAY_HOME, ) assert dataset.events[0].coordinates == Point(0.01, 0.01) diff --git a/kloppy/tests/test_helpers.py b/kloppy/tests/test_helpers.py index ddf4c8a2..a8bc5fc5 100644 --- a/kloppy/tests/test_helpers.py +++ b/kloppy/tests/test_helpers.py @@ -44,21 +44,19 @@ def _get_tracking_dataset(self): id=1, start_timestamp=0.0, end_timestamp=10.0, - attacking_direction=AttackingDirection.HOME_AWAY, ), Period( id=2, start_timestamp=15.0, end_timestamp=25.0, - attacking_direction=AttackingDirection.AWAY_HOME, ), ] metadata = Metadata( - flags=~(DatasetFlag.BALL_OWNING_TEAM | DatasetFlag.BALL_STATE), + flags=(DatasetFlag.BALL_OWNING_TEAM), pitch_dimensions=PitchDimensions( x_dim=Dimension(0, 100), y_dim=Dimension(-50, 50) ), - orientation=Orientation.HOME_TEAM, + orientation=Orientation.HOME_AWAY, frame_rate=25, periods=periods, teams=teams, @@ -73,7 +71,7 @@ def _get_tracking_dataset(self): Frame( frame_id=1, timestamp=0.1, - ball_owning_team=None, + ball_owning_team=teams[0], ball_state=None, period=periods[0], players_data={}, @@ -83,9 +81,9 @@ def _get_tracking_dataset(self): Frame( frame_id=2, timestamp=0.2, - ball_owning_team=None, + ball_owning_team=teams[1], ball_state=None, - period=periods[0], + period=periods[1], players_data={ Player( team=home_team, player_id="home_1", jersey_no=1 @@ -108,7 +106,7 @@ def test_transform(self): # orientation change AND dimension scale transformed_dataset = tracking_data.transform( - to_orientation="AWAY_TEAM", + to_orientation="AWAY_HOME", to_pitch_dimensions=[[0, 1], [0, 1]], ) @@ -119,7 +117,7 @@ def test_transform(self): x=1, y=0, z=1 ) assert ( - transformed_dataset.metadata.orientation == Orientation.AWAY_TEAM + transformed_dataset.metadata.orientation == Orientation.AWAY_HOME ) assert transformed_dataset.metadata.coordinate_system is None assert ( @@ -129,19 +127,6 @@ def test_transform(self): ) ) - def test_transform_to_orientation(self): - tracking_data = self._get_tracking_dataset() - - transformed_dataset = tracking_data.transform( - to_orientation=Orientation.AWAY_TEAM, - ) - assert transformed_dataset.frames[0].ball_coordinates == Point3D( - x=0, y=50, z=0 - ) - assert ( - transformed_dataset.metadata.orientation == Orientation.AWAY_TEAM - ) - def test_transform_to_pitch_dimensions(self): tracking_data = self._get_tracking_dataset() @@ -164,6 +149,91 @@ def test_transform_to_pitch_dimensions(self): ) ) + def test_transform_to_orientation(self): + # Create a dataset with the KLOPPY pitch dimensions + # and HOME_AWAY orientation + original = self._get_tracking_dataset().transform( + to_pitch_dimensions=[[0, 1], [0, 1]], + ) + assert original.metadata.orientation == Orientation.HOME_AWAY + assert original.frames[0].ball_coordinates == Point3D(x=1, y=0, z=0) + assert original.frames[1].ball_coordinates == Point3D(x=0, y=1, z=1) + # the frames should have the correct attacking direction + assert original.frames[0].attacking_direction == AttackingDirection.LTR + assert original.frames[1].attacking_direction == AttackingDirection.RTL + + # Transform to AWAY_HOME orientation + transform1 = original.transform( + to_orientation=Orientation.AWAY_HOME, + to_pitch_dimensions=[[0, 1], [0, 1]], + ) + assert transform1.metadata.orientation == Orientation.AWAY_HOME + # all coordinates should be flipped + assert transform1.frames[0].ball_coordinates == Point3D(x=0, y=1, z=0) + assert transform1.frames[1].ball_coordinates == Point3D(x=1, y=0, z=1) + # the frames should have the correct attacking direction + assert ( + transform1.frames[0].attacking_direction == AttackingDirection.RTL + ) + assert ( + transform1.frames[1].attacking_direction == AttackingDirection.LTR + ) + + # Transform to STATIC_AWAY_HOME orientation + transform2 = transform1.transform( + to_orientation=Orientation.STATIC_AWAY_HOME, + to_pitch_dimensions=[[0, 1], [0, 1]], + ) + assert transform2.metadata.orientation == Orientation.STATIC_AWAY_HOME + # all coordintes in the second half should be flipped + assert transform2.frames[0].ball_coordinates == Point3D(x=0, y=1, z=0) + assert transform2.frames[1].ball_coordinates == Point3D(x=0, y=1, z=1) + # the frames should have the correct attacking direction + for frame in transform2.frames: + assert frame.attacking_direction == AttackingDirection.RTL + + # Transform to BALL_OWNING_TEAM orientation + transform3 = transform2.transform( + to_orientation=Orientation.BALL_OWNING_TEAM, + to_pitch_dimensions=[[0, 1], [0, 1]], + ) + assert transform3.metadata.orientation == Orientation.BALL_OWNING_TEAM + # the coordinates of frame 1 should be flipped + assert transform3.frames[0].ball_coordinates == Point3D(x=1, y=0, z=0) + assert transform3.frames[1].ball_coordinates == Point3D(x=0, y=1, z=1) + # the frames should have the correct attacking direction + assert ( + transform3.frames[0].attacking_direction == AttackingDirection.LTR + ) + assert ( + transform3.frames[1].attacking_direction == AttackingDirection.RTL + ) + + # Transform to ACTION_EXECUTING_TEAM orientation + # this should be identical to BALL_OWNING_TEAM for tracking data + transform4 = transform3.transform( + to_orientation=Orientation.ACTION_EXECUTING_TEAM, + to_pitch_dimensions=[[0, 1], [0, 1]], + ) + assert ( + transform4.metadata.orientation + == Orientation.ACTION_EXECUTING_TEAM + ) + assert transform4.frames[1].ball_coordinates == Point3D(x=0, y=1, z=1) + for frame_t3, frame_t4 in zip(transform3.frames, transform4.frames): + assert frame_t3.ball_coordinates == frame_t4.ball_coordinates + assert frame_t3.attacking_direction == frame_t4.attacking_direction + + # Transform back to the original HOME_AWAY orientation + transform5 = transform4.transform( + to_orientation=Orientation.HOME_AWAY, + to_pitch_dimensions=[[0, 1], [0, 1]], + ) + # we should be back at the original + for frame1, frame2 in zip(original.frames, transform5.frames): + assert frame1.ball_coordinates == frame2.ball_coordinates + assert frame1.attacking_direction == frame2.attacking_direction + def test_transform_to_coordinate_system(self, base_dir): dataset = tracab.load( meta_data=base_dir / "files/tracab_meta.xml", @@ -226,7 +296,7 @@ def test_transform_event_data(self, base_dir): assert receipt_event.ball_owning_team == home_team transformed_dataset = dataset.transform( - to_orientation="fixed_home_away" + to_orientation="static_home_away" ) transformed_pressure_event = transformed_dataset.get_event_by_id( pressure_event.event_id @@ -266,7 +336,7 @@ def test_transform_event_data_freeze_frame(self, base_dir): "65f16e50-7c5d-4293-b2fc-d20887a772f9" ) transformed_dataset = dataset.transform( - to_orientation="fixed_away_home" + to_orientation="static_away_home" ) shot_event_transformed = transformed_dataset.get_event_by_id( shot_event.event_id @@ -289,10 +359,10 @@ def test_to_pandas(self): expected_data_frame = DataFrame.from_dict( { "frame_id": {0: 1, 1: 2}, - "period_id": {0: 1, 1: 1}, + "period_id": {0: 1, 1: 2}, "timestamp": {0: 0.1, 1: 0.2}, "ball_state": {0: None, 1: None}, - "ball_owning_team_id": {0: None, 1: None}, + "ball_owning_team_id": {0: "home", 1: "away"}, "ball_x": {0: 100, 1: 0}, "ball_y": {0: -50, 1: 50}, "ball_z": {0: 0, 1: 1}, @@ -342,10 +412,10 @@ def test_to_pandas_additional_columns(self): expected_data_frame = DataFrame.from_dict( { "frame_id": [1, 2], - "period_id": [1, 1], + "period_id": [1, 2], "timestamp": [0.1, 0.2], "ball_state": [None, None], - "ball_owning_team_id": [None, None], + "ball_owning_team_id": ["home", "away"], "ball_x": [100, 0], "ball_y": [-50, 50], "ball_z": [0, 1], diff --git a/kloppy/tests/test_metrica_csv.py b/kloppy/tests/test_metrica_csv.py index fe4c741a..57ce3495 100644 --- a/kloppy/tests/test_metrica_csv.py +++ b/kloppy/tests/test_metrica_csv.py @@ -31,18 +31,16 @@ def test_correct_deserialization(self, home_data: str, away_data: str): assert dataset.dataset_type == DatasetType.TRACKING assert len(dataset.records) == 6 assert len(dataset.metadata.periods) == 2 - assert dataset.metadata.orientation == Orientation.FIXED_HOME_AWAY + assert dataset.metadata.orientation == Orientation.HOME_AWAY assert dataset.metadata.periods[0] == Period( id=1, start_timestamp=0.04, end_timestamp=0.12, - attacking_direction=AttackingDirection.HOME_AWAY, ) assert dataset.metadata.periods[1] == Period( id=2, start_timestamp=5800.16, end_timestamp=5800.24, - attacking_direction=AttackingDirection.AWAY_HOME, ) # make sure data is loaded correctly (including flip y-axis) diff --git a/kloppy/tests/test_metrica_epts.py b/kloppy/tests/test_metrica_epts.py index f1696460..e626be2c 100644 --- a/kloppy/tests/test_metrica_epts.py +++ b/kloppy/tests/test_metrica_epts.py @@ -115,7 +115,7 @@ def test_correct_deserialization(self, meta_data: str, raw_data: str): assert len(dataset.records) == 100 assert len(dataset.metadata.periods) == 2 - assert dataset.metadata.orientation is Orientation.HOME_TEAM + assert dataset.metadata.orientation is Orientation.HOME_AWAY assert dataset.records[0].players_data[ first_player diff --git a/kloppy/tests/test_metrica_events.py b/kloppy/tests/test_metrica_events.py index 08d2d0f3..fef37456 100644 --- a/kloppy/tests/test_metrica_events.py +++ b/kloppy/tests/test_metrica_events.py @@ -36,7 +36,7 @@ def test_metadata(self, dataset: EventDataset): """It should parse the metadata correctly.""" assert dataset.metadata.provider == Provider.METRICA assert len(dataset.metadata.periods) == 2 - assert dataset.metadata.orientation is Orientation.HOME_TEAM + assert dataset.metadata.orientation is Orientation.HOME_AWAY assert dataset.metadata.teams[0].name == "Team A" assert dataset.metadata.teams[1].name == "Team B" player = dataset.metadata.teams[0].players[10] @@ -49,13 +49,11 @@ def test_metadata(self, dataset: EventDataset): id=1, start_timestamp=14.44, end_timestamp=2783.76, - attacking_direction=AttackingDirection.NOT_SET, ) assert dataset.metadata.periods[1] == Period( id=2, start_timestamp=2803.6, end_timestamp=5742.12, - attacking_direction=AttackingDirection.NOT_SET, ) def test_coordinates(self, dataset: EventDataset): diff --git a/kloppy/tests/test_opta.py b/kloppy/tests/test_opta.py index e19a25a6..77a61bbc 100644 --- a/kloppy/tests/test_opta.py +++ b/kloppy/tests/test_opta.py @@ -146,7 +146,6 @@ def test_periods(self, dataset): assert period.id == i + 1 assert period.start_timestamp == period_starts[i] assert period.end_timestamp == period_ends[i] - assert period.attacking_direction == AttackingDirection.NOT_SET def test_pitch_dimensions(self, dataset): """It should set the correct pitch dimensions""" diff --git a/kloppy/tests/test_secondspectrum.py b/kloppy/tests/test_secondspectrum.py index f556bff9..b7116232 100644 --- a/kloppy/tests/test_secondspectrum.py +++ b/kloppy/tests/test_secondspectrum.py @@ -44,24 +44,16 @@ def test_correct_deserialization( assert dataset.dataset_type == DatasetType.TRACKING assert len(dataset.records) == 376 assert len(dataset.metadata.periods) == 2 - assert dataset.metadata.orientation == Orientation.FIXED_AWAY_HOME + assert dataset.metadata.orientation == Orientation.AWAY_HOME # Check the Periods assert dataset.metadata.periods[0].id == 1 assert dataset.metadata.periods[0].start_timestamp == 0 assert dataset.metadata.periods[0].end_timestamp == 2982240 - assert ( - dataset.metadata.periods[0].attacking_direction - == AttackingDirection.AWAY_HOME - ) assert dataset.metadata.periods[1].id == 2 assert dataset.metadata.periods[1].start_timestamp == 3907360 assert dataset.metadata.periods[1].end_timestamp == 6927840 - assert ( - dataset.metadata.periods[1].attacking_direction - == AttackingDirection.HOME_AWAY - ) # Check some timestamps assert dataset.records[0].timestamp == 0 # First frame diff --git a/kloppy/tests/test_skillcorner.py b/kloppy/tests/test_skillcorner.py index 0688f033..8b09dee8 100644 --- a/kloppy/tests/test_skillcorner.py +++ b/kloppy/tests/test_skillcorner.py @@ -33,18 +33,16 @@ def test_correct_deserialization(self, raw_data: Path, meta_data: Path): assert dataset.dataset_type == DatasetType.TRACKING assert len(dataset.records) == 34783 assert len(dataset.metadata.periods) == 2 - assert dataset.metadata.orientation == Orientation.AWAY_TEAM + assert dataset.metadata.orientation == Orientation.AWAY_HOME assert dataset.metadata.periods[0] == Period( id=1, start_timestamp=0.0, end_timestamp=2753.3, - attacking_direction=AttackingDirection.AWAY_HOME, ) assert dataset.metadata.periods[1] == Period( id=2, start_timestamp=2700.0, end_timestamp=5509.7, - attacking_direction=AttackingDirection.HOME_AWAY, ) # are frames with wrong camera views and pregame skipped? diff --git a/kloppy/tests/test_sportec.py b/kloppy/tests/test_sportec.py index d19613eb..e8990538 100644 --- a/kloppy/tests/test_sportec.py +++ b/kloppy/tests/test_sportec.py @@ -49,18 +49,16 @@ def test_correct_event_data_deserialization( assert len(dataset.events) == 29 assert dataset.events[28].result == ShotResult.OWN_GOAL - assert dataset.metadata.orientation == Orientation.FIXED_HOME_AWAY + assert dataset.metadata.orientation == Orientation.HOME_AWAY assert dataset.metadata.periods[0] == Period( id=1, start_timestamp=1591381800.21, end_timestamp=1591384584.0, - attacking_direction=AttackingDirection.HOME_AWAY, ) assert dataset.metadata.periods[1] == Period( id=2, start_timestamp=1591385607.01, end_timestamp=1591388598.0, - attacking_direction=AttackingDirection.AWAY_HOME, ) player = dataset.metadata.teams[0].players[0] diff --git a/kloppy/tests/test_statsbomb.py b/kloppy/tests/test_statsbomb.py index 4115f1f5..a2c6c8ec 100644 --- a/kloppy/tests/test_statsbomb.py +++ b/kloppy/tests/test_statsbomb.py @@ -155,10 +155,6 @@ def test_periods(self, dataset): assert dataset.metadata.periods[0].end_timestamp == parse_str_ts( "00:47:38.122" ) - assert ( - dataset.metadata.periods[0].attacking_direction - == AttackingDirection.NOT_SET - ) assert dataset.metadata.periods[1].id == 2 assert dataset.metadata.periods[1].start_timestamp == parse_str_ts( "00:47:38.122" @@ -166,10 +162,6 @@ def test_periods(self, dataset): assert dataset.metadata.periods[1].end_timestamp == parse_str_ts( "00:47:38.122" ) + parse_str_ts("00:50:29.638") - assert ( - dataset.metadata.periods[1].attacking_direction - == AttackingDirection.NOT_SET - ) def test_pitch_dimensions(self, dataset): """It should set the correct pitch dimensions""" diff --git a/kloppy/tests/test_statsperform.py b/kloppy/tests/test_statsperform.py index 68770e8b..b23f70f3 100644 --- a/kloppy/tests/test_statsperform.py +++ b/kloppy/tests/test_statsperform.py @@ -49,24 +49,16 @@ def test_correct_deserialization(self, meta_data: Path, raw_data: Path): assert dataset.dataset_type == DatasetType.TRACKING assert len(dataset.records) == 92 assert len(dataset.metadata.periods) == 2 - assert dataset.metadata.orientation == Orientation.FIXED_AWAY_HOME + assert dataset.metadata.orientation == Orientation.AWAY_HOME # Check the periods assert dataset.metadata.periods[1].id == 1 assert dataset.metadata.periods[1].start_timestamp == 0 assert dataset.metadata.periods[1].end_timestamp == 2500 - assert ( - dataset.metadata.periods[1].attacking_direction - == AttackingDirection.AWAY_HOME - ) assert dataset.metadata.periods[2].id == 2 assert dataset.metadata.periods[2].start_timestamp == 0 assert dataset.metadata.periods[2].end_timestamp == 6500 - assert ( - dataset.metadata.periods[2].attacking_direction - == AttackingDirection.HOME_AWAY - ) # Check some timestamps assert dataset.records[0].timestamp == 0 # First frame diff --git a/kloppy/tests/test_tracab.py b/kloppy/tests/test_tracab.py index a97d9d8f..100267a5 100644 --- a/kloppy/tests/test_tracab.py +++ b/kloppy/tests/test_tracab.py @@ -39,19 +39,17 @@ def test_correct_deserialization(self, meta_data: Path, raw_data: Path): assert dataset.dataset_type == DatasetType.TRACKING assert len(dataset.records) == 6 assert len(dataset.metadata.periods) == 2 - assert dataset.metadata.orientation == Orientation.FIXED_HOME_AWAY + assert dataset.metadata.orientation == Orientation.HOME_AWAY assert dataset.metadata.periods[0] == Period( id=1, start_timestamp=4.0, end_timestamp=4.08, - attacking_direction=AttackingDirection.HOME_AWAY, ) assert dataset.metadata.periods[1] == Period( id=2, start_timestamp=8.0, end_timestamp=8.08, - attacking_direction=AttackingDirection.AWAY_HOME, ) player_home_19 = dataset.metadata.teams[0].get_player_by_jersey_number(