Skip to content

Commit

Permalink
Merge pull request #336 from UnravelSports/fix/tracab_meta
Browse files Browse the repository at this point in the history
Fix for older/incomplete/other versions of Tracab Meta data files
  • Loading branch information
koenvo authored Jul 12, 2024
2 parents 9ccbc77 + 50aee17 commit d001eb0
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 16 deletions.
49 changes: 33 additions & 16 deletions kloppy/infra/serializers/tracking/tracab/tracab_dat.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,18 @@ def _frame_from_line(cls, teams, period, line, frame_rate):
)

player = team.get_player_by_jersey_number(jersey_no)
if player:
players_data[player] = PlayerData(
coordinates=Point(float(x), float(y)), speed=float(speed)
)
else:
# continue
raise DeserializationError(
f"Player not found for player jersey no {jersey_no} of team: {team.name}"

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,
Expand Down Expand Up @@ -160,8 +163,12 @@ def deserialize(self, inputs: TRACABInputs) -> TrackingDataset:
meta_data = objectify.fromstring(inputs.meta_data.read())
match = meta_data.match
frame_rate = int(match.attrib["iFrameRateFps"])
pitch_size_width = float(match.attrib["fPitchXSizeMeters"])
pitch_size_height = float(match.attrib["fPitchYSizeMeters"])
pitch_size_width = float(
match.attrib["fPitchXSizeMeters"].replace(",", ".")
)
pitch_size_height = float(
match.attrib["fPitchYSizeMeters"].replace(",", ".")
)

periods = []
for period in match.iterchildren(tag="period"):
Expand All @@ -180,12 +187,22 @@ def deserialize(self, inputs: TRACABInputs) -> TrackingDataset:
)
)

home_team = self.create_team(
meta_data["HomeTeam"], Ground.HOME, start_frame_id
)
away_team = self.create_team(
meta_data["AwayTeam"], Ground.AWAY, start_frame_id
)
if hasattr(meta_data, "HomeTeam") and hasattr(
meta_data, "AwayTeam"
):
home_team = self.create_team(
meta_data["HomeTeam"], Ground.HOME, start_frame_id
)
away_team = self.create_team(
meta_data["AwayTeam"], Ground.AWAY, start_frame_id
)
else:
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]

with performance_logging("Loading data", logger=logger):
Expand Down
16 changes: 16 additions & 0 deletions kloppy/tests/files/tracab_meta_2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<TracabMetaData sVersion="1.0">
<match
iId="1"
dtDate="2023-12-15 20:32:20"
iFrameRateFps="25"
fPitchXSizeMeters="105.00"
fPitchYSizeMeters="68.00"
fTrackingAreaXSizeMeters="unknown"
fTrackingAreaYSizeMeters="unknown">
<period iId="1" iStartFrame="1848508" iEndFrame="1916408"/>
<period iId="2" iStartFrame="1942114" iEndFrame="2017933"/>
<period iId="3" iStartFrame="0" iEndFrame="0"/>
<period iId="4" iStartFrame="0" iEndFrame="0"/>
</match>
</TracabMetaData>
12 changes: 12 additions & 0 deletions kloppy/tests/files/tracab_meta_3.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<OptaMetaData sVersion="1.2">
<match
iId="1"
dtDate="2023-12-15 20:32:20"
iFrameRateFps="25"
fPitchXSizeMeters="105,00"
fPitchYSizeMeters="68,00"
>
<period iId="1" iStartFrame="1848508" iEndFrame="1916408"/>
<period iId="2" iStartFrame="1942114" iEndFrame="2017933"/>
</match>
</OptaMetaData>
132 changes: 132 additions & 0 deletions kloppy/tests/test_tracab.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ def xml_meta_data(base_dir: Path) -> Path:
return base_dir / "files" / "tracab_meta.xml"


@pytest.fixture(scope="session")
def xml_meta2_data(base_dir: Path) -> Path:
return base_dir / "files" / "tracab_meta_2.xml"


@pytest.fixture(scope="session")
def xml_meta3_data(base_dir: Path) -> Path:
return base_dir / "files" / "tracab_meta_3.xml"


@pytest.fixture(scope="session")
def dat_raw_data(base_dir: Path) -> Path:
return base_dir / "files" / "tracab_raw.dat"
Expand Down Expand Up @@ -223,3 +233,125 @@ def test_correct_normalized_deserialization(
assert dataset.records[0].players_data[
player_home_1
].coordinates == Point(x=1.0019047619047619, y=0.49602941176470583)


class TestTracabMeta2:
def test_correct_deserialization(
self, xml_meta2_data: Path, dat_raw_data: Path
):
dataset = tracab.load(
meta_data=xml_meta2_data,
raw_data=dat_raw_data,
coordinates="tracab",
only_alive=False,
)

# Check metadata
assert dataset.metadata.provider == Provider.TRACAB
assert dataset.dataset_type == DatasetType.TRACKING
assert len(dataset.records) == 7
assert len(dataset.metadata.periods) == 2
assert dataset.metadata.orientation == Orientation.AWAY_HOME
assert dataset.metadata.periods[0].id == 1
assert dataset.metadata.periods[0].start_timestamp == timedelta(
seconds=73940, microseconds=320000
)
assert dataset.metadata.periods[0].end_timestamp == timedelta(
seconds=76656, microseconds=320000
)
assert dataset.metadata.periods[1].id == 2
assert dataset.metadata.periods[1].start_timestamp == timedelta(
seconds=77684, microseconds=560000
)
assert dataset.metadata.periods[1].end_timestamp == timedelta(
seconds=80717, microseconds=320000
)

# No need to check frames, since we do that in TestTracabDATTracking
# The only difference in this test is the meta data file structure

# make sure player data is only in the frame when the player is at the pitch
assert "home_20" in [
player.player_id
for player in dataset.records[0].players_data.keys()
]
assert "home_20" not in [
player.player_id
for player in dataset.records[6].players_data.keys()
]

def test_correct_normalized_deserialization(
self, xml_meta2_data: Path, dat_raw_data: Path
):
dataset = tracab.load(
meta_data=xml_meta2_data, raw_data=dat_raw_data, only_alive=False
)

player_home_1 = dataset.metadata.teams[0].get_player_by_jersey_number(
1
)

assert dataset.records[0].players_data[
player_home_1
].coordinates == Point(x=1.0019047619047619, y=0.49602941176470583)


class TestTracabMeta3:
def test_correct_deserialization(
self, xml_meta3_data: Path, dat_raw_data: Path
):
dataset = tracab.load(
meta_data=xml_meta3_data,
raw_data=dat_raw_data,
coordinates="tracab",
only_alive=False,
)

# Check metadata
assert dataset.metadata.provider == Provider.TRACAB
assert dataset.dataset_type == DatasetType.TRACKING
assert len(dataset.records) == 7
assert len(dataset.metadata.periods) == 2
assert dataset.metadata.orientation == Orientation.AWAY_HOME
assert dataset.metadata.periods[0].id == 1
assert dataset.metadata.periods[0].start_timestamp == timedelta(
seconds=73940, microseconds=320000
)
assert dataset.metadata.periods[0].end_timestamp == timedelta(
seconds=76656, microseconds=320000
)
assert dataset.metadata.periods[1].id == 2
assert dataset.metadata.periods[1].start_timestamp == timedelta(
seconds=77684, microseconds=560000
)
assert dataset.metadata.periods[1].end_timestamp == timedelta(
seconds=80717, microseconds=320000
)

# No need to check frames, since we do that in TestTracabDATTracking
# The only difference in this test is the meta data file structure

# make sure player data is only in the frame when the player is at the pitch
assert "home_20" in [
player.player_id
for player in dataset.records[0].players_data.keys()
]
assert "home_20" not in [
player.player_id
for player in dataset.records[6].players_data.keys()
]

def test_correct_normalized_deserialization(
self, xml_meta3_data: Path, dat_raw_data: Path
):
dataset = tracab.load(
meta_data=xml_meta3_data, raw_data=dat_raw_data, only_alive=False
)

player_home_1 = dataset.metadata.teams[0].get_player_by_jersey_number(
1
)

assert dataset.records[0].players_data[
player_home_1
].coordinates == Point(x=1.0019047619047619, y=0.49602941176470583)

0 comments on commit d001eb0

Please sign in to comment.