def test_correct_deserialization(self): base_dir = os.path.dirname(__file__) serializer = EPTSSerializer() with open(f"{base_dir}/files/epts_meta.xml", "rb") as metadata, open(f"{base_dir}/files/epts_raw.txt", "rb") as raw_data: dataset = serializer.deserialize(inputs={ "metadata": metadata, "raw_data": raw_data }) first_player = next(iter(dataset.records[0].players_coordinates)) assert len(dataset.records) == 2 assert len(dataset.metadata.periods) == 1 assert dataset.metadata.orientation is None assert dataset.records[0].players_coordinates[first_player] == Point( x=-769, y=-2013) assert dataset.records[0].ball_coordinates == Point3D(x=-2656, y=367, z=100)
def _frame_from_row(row: dict, metadata: EPTSMetadata) -> Frame: timestamp = row["timestamp"] if metadata.periods and row["period_id"]: # might want to search for it instead period = metadata.periods[row["period_id"] - 1] else: period = None players_coordinates = {} for team in metadata.teams: for player in team.players: if f"player_{player.player_id}_x" in row: players_coordinates[player] = Point( x=row[f"player_{player.player_id}_x"], y=row[f"player_{player.player_id}_y"], ) return Frame( frame_id=row["frame_id"], timestamp=timestamp, ball_owning_team=None, ball_state=None, period=period, players_coordinates=players_coordinates, ball_coordinates=Point3D(x=row["ball_x"], y=row["ball_y"], z=row["ball_z"]), )
def __change_point_coordinate_system(self, point: Union[Point, Point3D]): if not point: return None x = self._from_coordinate_system.pitch_dimensions.x_dim.to_base( point.x ) y = self._from_coordinate_system.pitch_dimensions.y_dim.to_base( point.y ) if ( self._from_coordinate_system.vertical_orientation != self._to_coordinate_system.vertical_orientation ): y = 1 - y if not self._to_coordinate_system.normalized: x = self._to_coordinate_system.pitch_dimensions.x_dim.from_base(x) y = self._to_coordinate_system.pitch_dimensions.y_dim.from_base(y) if isinstance(point, Point3D): return Point3D(x=x, y=y, z=point.z) else: return Point(x=x, y=y)
def test_correct_deserialization(self, meta_data: str, raw_data: str, additional_meta_data: str): dataset = secondspectrum.load( meta_data=meta_data, raw_data=raw_data, additional_meta_data=additional_meta_data, only_alive=False, coordinates="secondspectrum", ) # Check provider, type, shape, etc assert dataset.metadata.provider == Provider.SECONDSPECTRUM 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 # 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 assert dataset.records[20].timestamp == 320.0 # Later frame # Check some players home_player = dataset.metadata.teams[0].players[2] assert home_player.player_id == "8xwx2" assert dataset.records[0].players_coordinates[home_player] == Point( x=-8.943903672572427, y=-28.171654132650364) away_player = dataset.metadata.teams[1].players[3] assert away_player.player_id == "2q0uv" assert dataset.records[0].players_coordinates[away_player] == Point( x=-45.11871334915762, y=-20.06459030559596) # Check the ball assert dataset.records[1].ball_coordinates == Point3D( x=-23.147073918432426, y=13.69367399756424, z=0.0) # Check pitch dimensions pitch_dimensions = dataset.metadata.pitch_dimensions assert pitch_dimensions.x_dim.min == -52.425 assert pitch_dimensions.x_dim.max == 52.425 assert pitch_dimensions.y_dim.min == -33.985 assert pitch_dimensions.y_dim.max == 33.985
def _frame_from_row(row: dict, metadata: EPTSMetadata, transformer: DatasetTransformer) -> Frame: timestamp = row["timestamp"] if metadata.periods and row["period_id"]: # might want to search for it instead period = metadata.periods[row["period_id"] - 1] else: period = None other_sensors = [] for sensor in metadata.sensors: if sensor.sensor_id not in ["position", "distance", "speed"]: other_sensors.append(sensor) players_data = {} for team in metadata.teams: for player in team.players: other_data = {} for sensor in other_sensors: other_data.update({ sensor.sensor_id: row[f"player_{player.player_id}_{sensor.channels[0].channel_id}"] }) players_data[player] = PlayerData( coordinates=Point( x=row[f"player_{player.player_id}_x"], y=row[f"player_{player.player_id}_y"], ) if f"player_{player.player_id}_x" in row else None, speed=row[f"player_{player.player_id}_s"] if f"player_{player.player_id}_s" in row else None, distance=row[f"player_{player.player_id}_d"] if f"player_{player.player_id}_d" in row else None, other_data=other_data, ) frame = Frame( frame_id=row["frame_id"], timestamp=timestamp, ball_owning_team=None, ball_state=None, period=period, players_data=players_data, other_data={}, ball_coordinates=Point3D(x=row["ball_x"], y=row["ball_y"], z=row.get("ball_z")), ) if transformer: frame = transformer.transform_frame(frame) return frame
def test_transform(self): tracking_data = self._get_tracking_dataset() # orientation change AND dimension scale transformed_dataset = tracking_data.transform( to_orientation="AWAY_TEAM", to_pitch_dimensions=[[0, 1], [0, 1]], ) assert transformed_dataset.frames[0].ball_coordinates == Point3D(x=0, y=1, z=0) assert transformed_dataset.frames[1].ball_coordinates == Point3D(x=1, y=0, z=1) assert ( transformed_dataset.metadata.orientation == Orientation.AWAY_TEAM) assert transformed_dataset.metadata.coordinate_system is None assert ( transformed_dataset.metadata.pitch_dimensions == PitchDimensions( x_dim=Dimension(min=0, max=1), y_dim=Dimension(min=0, max=1)))
def change_point_dimensions(self, point: Union[Point, Point3D]) -> Point: if point is None: return None x_base = self._from_pitch_dimensions.x_dim.to_base(point.x) y_base = self._from_pitch_dimensions.y_dim.to_base(point.y) x = self._to_pitch_dimensions.x_dim.from_base(x_base) y = self._to_pitch_dimensions.y_dim.from_base(y_base) if isinstance(point, Point3D): return Point3D(x=x, y=y, z=point.z) else: return Point(x=x, y=y)
def _frame_from_framedata(cls, teams, period, frame_data): frame_id = frame_data["frameIdx"] frame_timestamp = frame_data["gameClock"] if frame_data["ball"]["xyz"]: ball_x, ball_y, ball_z = frame_data["ball"]["xyz"] ball_coordinates = Point3D( float(ball_x), float(ball_y), float(ball_z) ) else: ball_coordinates = None ball_state = BallState.ALIVE if frame_data["live"] else BallState.DEAD ball_owning_team = ( teams[0] if frame_data["lastTouch"] == "home" else teams[1] ) players_data = {} for team, team_str in zip(teams, ["homePlayers", "awayPlayers"]): for player_data in frame_data[team_str]: jersey_no = player_data["number"] x, y, _ = player_data["xyz"] player = team.get_player_by_jersey_number(jersey_no) if not player: player = Player( player_id=player_data["playerId"], team=team, jersey_no=int(jersey_no), ) team.players.append(player) players_data[player] = PlayerData( coordinates=Point(float(x), float(y)) ) return Frame( frame_id=frame_id, timestamp=frame_timestamp, ball_coordinates=ball_coordinates, ball_state=ball_state, ball_owning_team=ball_owning_team, players_data=players_data, period=period, other_data={}, )
def test_correct_deserialization(self, meta_data: str, raw_data: str): dataset = metrica.load_tracking_epts( meta_data=meta_data, raw_data=raw_data ) first_player = next(iter(dataset.records[0].players_data)) assert len(dataset.records) == 100 assert len(dataset.metadata.periods) == 2 assert dataset.metadata.orientation is None assert dataset.records[0].players_data[ first_player ].coordinates == Point(x=0.30602, y=0.97029) assert dataset.records[0].ball_coordinates == Point3D( x=0.52867, y=0.7069, z=None )
def flip_point(self, point: Union[Point, Point3D]): if not point: return None x_base = self._to_pitch_dimensions.x_dim.to_base(point.x) y_base = self._to_pitch_dimensions.y_dim.to_base(point.y) x_base = 1 - x_base y_base = 1 - y_base x = self._to_pitch_dimensions.x_dim.from_base(x_base) y = self._to_pitch_dimensions.y_dim.from_base(y_base) if isinstance(point, Point3D): return Point3D(x=x, y=y, z=point.z) else: return Point(x=x, y=y)
def _frame_from_line(cls, teams, period, line, frame_rate): line = str(line) frame_id, players, ball = line.strip().split(":")[:3] players_data = {} for player_data in players.split(";")[:-1]: team_id, target_id, jersey_no, x, y, speed = player_data.split(",") team_id = int(team_id) if team_id == 1: team = teams[0] elif team_id == 0: team = teams[1] else: # it's probably -1, but make sure it doesn't crash continue player = team.get_player_by_jersey_number(jersey_no) if not player: player = Player( player_id=f"{team.ground}_{jersey_no}", team=team, jersey_no=int(jersey_no), ) team.players.append(player) players_data[player] = PlayerData( coordinates=Point(float(x), float(y)), speed=float(speed) ) ( ball_x, ball_y, ball_z, ball_speed, ball_owning_team, ball_state, ) = ball.rstrip(";").split(",")[:6] frame_id = int(frame_id) if ball_owning_team == "H": ball_owning_team = teams[0] elif ball_owning_team == "A": ball_owning_team = teams[1] else: raise DeserializationError( f"Unknown ball owning team: {ball_owning_team}" ) if ball_state == "Alive": ball_state = BallState.ALIVE elif ball_state == "Dead": ball_state = BallState.DEAD else: raise DeserializationError(f"Unknown ball state: {ball_state}") return Frame( frame_id=frame_id, timestamp=frame_id / frame_rate - period.start_timestamp, ball_coordinates=Point3D( float(ball_x), float(ball_y), float(ball_z) ), ball_state=ball_state, ball_owning_team=ball_owning_team, players_data=players_data, period=period, other_data={}, )
def _get_frame_data( cls, teams, teamdict, players, player_id_to_team_dict, periods, player_dict, anon_players, ball_id, referee_dict, frame, ): frame_period = frame["period"] frame_id = frame["frame"] frame_time = cls._timestamp_from_timestring(frame["time"]) ball_coordinates = None players_data = {} # ball_carrier = frame["possession"].get("trackable_object") ball_owning_team = frame["possession"].get("group") if ball_owning_team == "home team": ball_owning_team = teams[0] elif ball_owning_team == "away team": ball_owning_team = teams[1] else: ball_owning_team = None for frame_record in frame["data"]: # containing x, y, trackable_object, track_id, group_name x = frame_record.get("x") y = frame_record.get("y") trackable_object = frame_record.get("trackable_object", None) track_id = frame_record.get("track_id", None) group_name = frame_record.get("group_name", None) if trackable_object == ball_id: group_name = "ball" z = frame_record.get("z") if z is not None: z = float(z) ball_coordinates = Point3D(x=float(x), y=float(y), z=z) continue elif trackable_object in referee_dict.keys(): group_name = "referee" continue # Skip Referee Coords if group_name is None: group_name = teamdict.get( player_id_to_team_dict.get(trackable_object)) if group_name == "home_team": player = players["HOME"][trackable_object] elif group_name == "away_team": player = players["AWAY"][trackable_object] if trackable_object is None: player_id = str(track_id) if group_name == "home team": if f"anon_{player_id}" not in anon_players["HOME"].keys(): player = cls.__create_anon_player(teams, frame_record) anon_players["HOME"][f"anon_home_{player_id}"] = player else: player = anon_players["HOME"][f"anon_home_{player_id}"] elif group_name == "away team": if f"anon_{player_id}" not in anon_players["AWAY"].keys(): player = cls.__create_anon_player(teams, frame_record) anon_players["AWAY"][f"anon_away_{player_id}"] = player else: player = anon_players["AWAY"][f"anon_away_{player_id}"] players_data[player] = PlayerData(coordinates=Point(x, y)) return Frame( frame_id=frame_id, timestamp=frame_time, ball_coordinates=ball_coordinates, players_data=players_data, period=periods[frame_period], ball_state=None, ball_owning_team=ball_owning_team, other_data={}, )
def _get_tracking_dataset(self): home_team = Team(team_id="home", name="home", ground=Ground.HOME) away_team = Team(team_id="away", name="away", ground=Ground.AWAY) teams = [home_team, away_team] periods = [ Period( 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), pitch_dimensions=PitchDimensions(x_dim=Dimension(0, 100), y_dim=Dimension(-50, 50)), orientation=Orientation.HOME_TEAM, frame_rate=25, periods=periods, teams=teams, score=None, provider=None, coordinate_system=None, ) tracking_data = TrackingDataset( metadata=metadata, records=[ Frame( frame_id=1, timestamp=0.1, ball_owning_team=None, ball_state=None, period=periods[0], players_data={}, other_data=None, ball_coordinates=Point3D(x=100, y=-50, z=0), ), Frame( frame_id=2, timestamp=0.2, ball_owning_team=None, ball_state=None, period=periods[0], players_data={ Player(team=home_team, player_id="home_1", jersey_no=1): PlayerData( coordinates=Point(x=15, y=35), distance=0.03, speed=10.5, other_data={"extra_data": 1}, ) }, other_data={"extra_data": 1}, ball_coordinates=Point3D(x=0, y=50, z=1), ), ], ) return tracking_data
def test_correct_deserialization(self, meta_data: str, raw_data: str): dataset = tracab.load( meta_data=meta_data, raw_data=raw_data, coordinates="tracab", only_alive=False, ) assert dataset.metadata.provider == Provider.TRACAB 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.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( 19 ) assert dataset.records[0].players_data[ player_home_19 ].coordinates == Point(x=-1234.0, y=-294.0) player_away_19 = dataset.metadata.teams[1].get_player_by_jersey_number( 19 ) assert dataset.records[0].players_data[ player_away_19 ].coordinates == Point(x=8889, y=-666) assert dataset.records[0].ball_coordinates == Point3D(x=-27, y=25, z=0) assert dataset.records[0].ball_state == BallState.ALIVE assert dataset.records[0].ball_owning_team == Team( team_id="home", name="home", ground=Ground.HOME ) assert dataset.records[1].ball_owning_team == Team( team_id="away", name="away", ground=Ground.AWAY ) assert dataset.records[2].ball_state == BallState.DEAD # make sure player data is only in the frame when the player is at the pitch assert "away_1337" not in [ player.player_id for player in dataset.records[0].players_data.keys() ] assert "away_1337" in [ player.player_id for player in dataset.records[3].players_data.keys() ]
def test_correct_deserialization(self, raw_data: str, meta_data: str): dataset = skillcorner.load( meta_data=meta_data, raw_data=raw_data, coordinates="skillcorner" ) assert dataset.metadata.provider == Provider.SKILLCORNER 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.periods[1] == Period( id=1, start_timestamp=0.0, end_timestamp=2753.3, attacking_direction=AttackingDirection.AWAY_HOME, ) assert dataset.metadata.periods[2] == 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? assert dataset.records[0].timestamp == 11.2 # make sure data is loaded correctly home_player = dataset.metadata.teams[0].players[2] assert dataset.records[0].players_data[ home_player ].coordinates == Point(x=33.8697315398, y=-9.55742259253) away_player = dataset.metadata.teams[1].players[9] assert dataset.records[0].players_data[ away_player ].coordinates == Point(x=25.9863082795, y=27.3013598578) assert dataset.records[1].ball_coordinates == Point3D( x=30.5914728131, y=35.3622277834, z=2.24371228757 ) # check that missing ball-z_coordinate is identified as None assert dataset.records[38].ball_coordinates == Point3D( x=11.6568802848, y=24.7214038909, z=None ) # check that 'ball_z' column is included in to_pandas dataframe # frame = _frame_to_pandas_row_converter(dataset.records[38]) # assert "ball_z" in frame.keys() # make sure player data is only in the frame when the player is in view assert "home_1" not in [ player.player_id for player in dataset.records[0].players_data.keys() ] assert "away_1" not in [ player.player_id for player in dataset.records[0].players_data.keys() ] # are anonymous players loaded correctly? home_anon_75 = [ player for player in dataset.records[87].players_data if player.player_id == "home_anon_75" ] assert home_anon_75 == [ player for player in dataset.records[88].players_data if player.player_id == "home_anon_75" ] # is pitch dimension set correctly? pitch_dimensions = dataset.metadata.pitch_dimensions assert pitch_dimensions.x_dim.min == -52.5 assert pitch_dimensions.x_dim.max == 52.5 assert pitch_dimensions.y_dim.min == -34 assert pitch_dimensions.y_dim.max == 34
def test_correct_deserialization(self): base_dir = os.path.dirname(__file__) serializer = TRACABSerializer() with open(f"{base_dir}/files/tracab_meta.xml", "rb") as metadata, open(f"{base_dir}/files/tracab_raw.dat", "rb") as raw_data: dataset = serializer.deserialize( inputs={ "metadata": metadata, "raw_data": raw_data }, options={"only_alive": False}, ) assert dataset.metadata.provider == Provider.TRACAB 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.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( "19") assert dataset.records[0].players_coordinates[player_home_19] == Point( x=-1234.0, y=-294.0) player_away_19 = dataset.metadata.teams[1].get_player_by_jersey_number( "19") assert dataset.records[0].players_coordinates[player_away_19] == Point( x=8889, y=-666) assert dataset.records[0].ball_coordinates == Point3D(x=-27, y=25, z=0) assert dataset.records[0].ball_state == BallState.ALIVE assert dataset.records[0].ball_owning_team == Team(team_id="home", name="home", ground=Ground.HOME) assert dataset.records[1].ball_owning_team == Team(team_id="away", name="away", ground=Ground.AWAY) assert dataset.records[2].ball_state == BallState.DEAD # make sure player data is only in the frame when the player is at the pitch assert "away_1337" not in [ player.player_id for player in dataset.records[0].players_coordinates.keys() ] assert "away_1337" in [ player.player_id for player in dataset.records[3].players_coordinates.keys() ]