Skip to content

Commit

Permalink
minor changes
Browse files Browse the repository at this point in the history
  • Loading branch information
UnravelSports [JB] committed Jul 22, 2024
1 parent f06049c commit 8eda590
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 26 deletions.
43 changes: 35 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,49 @@ The **unravelsports** package aims to aid researchers, analysts and enthusiasts
🌀 Features
-----

- ⚽ Converting **positional soccer data** into graphs to train **graph neural networks** by leveraging the powerful [**Kloppy**](https://github.com/PySport/kloppy/tree/master) data conversion standard and [**Spektral**](https://github.com/danielegrattarola/spektral) - a flexible framework for creating graph neural networks.
- ⚽ Randomizing and splitting data into **train, test and validation sets** along matches, sequences or possessions to avoid leakage and improve model quality.
- ⚽ Due to the power of **Kloppy**, **unravelsports** supports these actions for _Metrica_, _Sportec_, _Tracab (CyronHego)_, _SecondSpectrum_, _SkillCorner_ and _StatsPerform_ tracking data.
<ul style="list-style: none; padding: 0; margin-left: 1.2em;">
<li style="margin-bottom: 8px;">
<span style="display: inline-block; width: 1.2em; margin-right: 0.5em;">⚽</span>
Convert <strong>positional soccer data</strong> into graphs to train <strong>graph neural networks</strong> by leveraging the powerful <a href="https://github.com/PySport/kloppy/tree/master"><strong>Kloppy</strong></a> data conversion standard and <a href="https://github.com/danielegrattarola/spektral"><strong>Spektral</strong></a> - a flexible framework for creating GNNs.
</li>
<li style="margin-bottom: 8px;">
<span style="display: inline-block; width: 1.2em; margin-right: 0.5em;">⚽</span>
Randomize and split data into <strong>train, test and validation sets</strong> along matches, sequences or possessions to avoid leakage and improve model quality.
</li>
<li style="margin-bottom: 8px;">
<span style="display: inline-block; width: 1.2em; margin-right: 0.5em;">⚽</span>
Due to the power of <strong>Kloppy</strong>, <strong>unravelsports</strong> supports these actions for <em>Metrica</em>, <em>Sportec</em>, <em>Tracab (CyronHego)</em>, <em>SecondSpectrum</em>, <em>SkillCorner</em> and <em>StatsPerform</em> tracking data.
</li>
<li style="margin-bottom: 8px;">
<span style="display: inline-block; width: 1.2em; margin-right: 0.5em;">⏳</span>
<strong>More to come...</strong>
</li>
</ul>

🌀 Getting Started
-----
📖 The [**Getting Started Jupyter Notebook**](examples/0_getting_started.ipynb) explains how to convert any positional tracking data from **Kloppy** to **Spektral GNN** in a few easy steps while walking you through the most important features and documentation.

📖 The [**Graph Converter Tutorial Jupyter Notebook**](examples/1_tutorial_graph_converter.ipynb) gives an in-depth walkthrough.
<ul style="list-style: none; padding: 0; margin-left: 1.2em;">
<li style="margin-bottom: 8px;">
<span style="display: inline-block; width: 1.2em; margin-right: 0.5em;">📖</span>
The <a href="examples/0_getting_started.ipynb"><strong>Getting Started Jupyter Notebook</strong></a> explains how to convert any positional tracking data from <strong>Kloppy</strong> to <strong>Spektral GNN</strong> in a few easy steps while walking you through the most important functionality.
</li>
<li style="margin-bottom: 8px;">
<span style="display: inline-block; width: 1.2em; margin-right: 0.5em;">📖</span>
The <a href="examples/1_tutorial_graph_converter.ipynb"><strong>Graph Converter Tutorial Jupyter Notebook</strong></a> gives an in-depth walkthrough.
</li>
</ul>

🌀 Documentation
-----
For now, follow the [**Graph Converter Tutorial**](examples/1_tutorial_graph_converter.ipynb), more documentation will follow!

Additional reading:
- 📖 [A Graph Neural Network Deep-dive into Successful Counterattacks {A. Sahasrabudhe & J. Bekkers, 2023}](https://github.com/USSoccerFederation/ussf_ssac_23_soccer_gnn/tree/main)
<ul style="list-style: none; padding: 0; margin-left: 1.2em;">
<li style="margin-bottom: 8px;">
<span style="display: inline-block; width: 1.2em; margin-right: 0.5em;">📖</span>
<a href="https://github.com/USSoccerFederation/ussf_ssac_23_soccer_gnn/tree/main"><strong>A Graph Neural Network Deep-dive into Successful Counterattacks {A. Sahasrabudhe & J. Bekkers, 2023}</strong></a>
</li>
</ul>

🌀 Installation
----
Expand All @@ -44,7 +71,7 @@ pip install unravelsports

🌀 Contributing
----
All contributions, bug reports, bug fixes, documentation improvements, enhancements, and ideas are welcome.
All contributions, bug reports, bug fixes, documentation improvements, enhancements, and ideas are welcome. Feel free to create a Pull Request for any improvements you make that do not contribute to winning more games!

An overview on how to contribute can be found in the [**contributing guide**](CONTRIBUTING.md).

Expand Down
18 changes: 10 additions & 8 deletions tests/test_kloppy.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,19 @@ def test_conversion(self, gnnc: GraphConverter):
assert data.orientation == Orientation.STATIC_HOME_AWAY
assert data.attacking_players == data.home_players
assert data.defending_players == data.away_players

hp = data.home_players[3]
assert -19.582426479899993 == pytest.approx(hp.x1, abs=1e-5)
assert 24.3039460863 == pytest.approx(hp.y1, abs=1e-5)
assert -19.6022318885 == pytest.approx(hp.x2, abs=1e-5)
assert 24.1632567814 == pytest.approx(hp.y2, abs=1e-5)
assert hp.position.shape == (2, )
np.testing.assert_allclose(hp.position, np.asarray([hp.x1, hp.y1]), rtol=1e-4, atol=1e-4)
assert -19.582426479899993 == pytest.approx(hp.x1, abs=1e-5)
assert 24.3039460863 == pytest.approx(hp.y1, abs=1e-5)
assert -19.6022318885 == pytest.approx(hp.x2, abs=1e-5)
assert 24.1632567814 == pytest.approx(hp.y2, abs=1e-5)
assert hp.position.shape == (2,)
np.testing.assert_allclose(
hp.position, np.asarray([hp.x1, hp.y1]), rtol=1e-4, atol=1e-4
)
assert hp.is_gk == False
assert hp.next_position[0] - hp.position[0]

assert data.ball_carrier_idx == 1
assert len(data.home_players) == 6
assert len(data.away_players) == 4
Expand Down
7 changes: 4 additions & 3 deletions unravel/utils/objects/default_ball.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ def __post_init__(self):

def set_velocity(self):
delta_time = 1.0 / self.fps

if not (np.any(np.isnan(self.next_position3D)) or np.any(np.isnan(self.position3D))):

if not (
np.any(np.isnan(self.next_position3D)) or np.any(np.isnan(self.position3D))
):
vx = (self.next_position3D[0] - self.position3D[0]) / delta_time
vy = (self.next_position3D[1] - self.position3D[1]) / delta_time
vz = (self.next_position3D[2] - self.position3D[2]) / delta_time
Expand All @@ -43,7 +45,6 @@ def set_velocity(self):

self.speed = np.sqrt(vx**2 + vy**2 + vz**2)


def invert_position(self):
self.next_position = self.next_position * -1.0
self.position = self.position * -1.0
Expand Down
9 changes: 5 additions & 4 deletions unravel/utils/objects/default_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ class DefaultPlayer(object):
) # velocity vector
speed: float = 0.0 # actual speed in m/s
is_gk: bool = False


def __post_init__(self):
self.next_position = np.asarray([self.x2, self.y2], dtype=float)
Expand All @@ -42,17 +41,19 @@ def invert_position(self):

def set_velocity(self):
dt = 1.0 / self.fps
if not (np.any(np.isnan(self.next_position)) or np.any(np.isnan(self.position))):
if not (
np.any(np.isnan(self.next_position)) or np.any(np.isnan(self.position))
):
vx = (self.next_position[0] - self.position[0]) / dt
vy = (self.next_position[1] - self.position[1]) / dt
else:
vx = 0
vy = 0

self.velocity = np.asarray([vx, vy], dtype=float)

# Re-check if any component of velocity is NaN and set to zero if it is
if np.any(np.isnan(self.velocity)):
self.velocity = np.asarray([0.0, 0.0], dtype=float)

self.speed = np.sqrt(vx**2 + vy**2)
3 changes: 1 addition & 2 deletions unravel/utils/objects/default_tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ class DefaultTrackingModel:
ball_carrier_treshold: bool = 25.0
verbose: bool = False
pad_n_players: bool = None


def __post_init__(self):
self.home_players: List[DefaultPlayer] = list()
Expand Down Expand Up @@ -207,7 +206,7 @@ def set_objects_from_frame(
y1=coords.y,
y2=next_coords.y,
is_visible=True,
fps=self.fps
fps=self.fps,
)

if pid.team.ground == Ground.HOME:
Expand Down
2 changes: 1 addition & 1 deletion unravel/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def dummy_graph_ids(dataset: TrackingDataset) -> Dict:
"""
if not isinstance(dataset, TrackingDataset):
raise TypeError("dataset should be of type TrackingDataset (from kloppy)")

from uuid import uuid4

graph_ids = dict()
Expand Down

0 comments on commit 8eda590

Please sign in to comment.