Skip to content

Commit

Permalink
Merge pull request #2 from PySport/metrica-tracking-serializer
Browse files Browse the repository at this point in the history
Metrica tracking serializer
  • Loading branch information
koenvo committed May 5, 2020
2 parents b390550 + 2dcbffa commit a979fd7
Show file tree
Hide file tree
Showing 28 changed files with 1,701 additions and 314 deletions.
5 changes: 4 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
v0.1, 2020-04-23 -- Initial release.
v0.1.0, 2020-04-23 -- Initial release.
v0.2.0, 2020-05-05 -- Change interface of TrackingDataSerializer
Add Metrica Tracking Serializer including automated tests
Cleanup some import statements
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ from kloppy import TRACABSerializer

serializer = TRACABSerializer()

with open("tracab_data.dat", "rb") as data, \
with open("tracab_data.dat", "rb") as raw, \
open("tracab_metadata.xml", "rb") as meta:

data_set = serializer.deserialize(
data=data,
metadata=meta,
inputs={
'raw_data': raw,
'meta_data': meta
},
options={
"sample_rate": 1 / 12
}
Expand All @@ -59,6 +61,29 @@ with open("tracab_data.dat", "rb") as data, \
# start working with data_set
```

or Metrica data
```python
from kloppy import MetricaTrackingSerializer

serializer = MetricaTrackingSerializer()

with open("Sample_Game_1_RawTrackingData_Away_Team.csv", "rb") as raw_home, \
open("Sample_Game_1_RawTrackingData_Home_Team.csv", "rb") as raw_away:

data_set = serializer.deserialize(
inputs={
'raw_data_home': raw_home,
'raw_data_away': raw_away
},
options={
"sample_rate": 1 / 12
}
)

# start working with data_set
```


### <a name="pitch-dimensions"></a>Transform the pitch dimensions
Data providers use their own pitch dimensions. Some use actual meters while others use 100x100. Use the Transformer to get from one pitch dimensions to another one.
```python
Expand Down Expand Up @@ -114,9 +139,9 @@ Data models
- [ ] Event

Tracking data (de)serializers
- [ ] Automated tests
- [x] Automated tests
- [x] TRACAB
- [ ] MetricaSports
- [x] MetricaSports
- [ ] BallJames
- [ ] FIFA EPTS

Expand Down
2 changes: 1 addition & 1 deletion kloppy/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .infra.serializers import TRACABSerializer
from .infra.serializers import *
2 changes: 1 addition & 1 deletion kloppy/domain/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .models import *
from .services.transformers import *
from .services import *
5 changes: 4 additions & 1 deletion kloppy/domain/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
from .tracking import *
from .common import *
from .pitch import *
from .tracking import *
# NOT YET: from .event import *

105 changes: 105 additions & 0 deletions kloppy/domain/models/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from abc import ABC
from dataclasses import dataclass
from enum import Enum, Flag
from typing import Optional, List

from .pitch import PitchDimensions


class Team(Enum):
HOME = "home"
AWAY = "away"


class BallState(Enum):
ALIVE = "alive"
DEAD = "dead"


class AttackingDirection(Enum):
HOME_AWAY = "home-away" # home L -> R, away R -> L
AWAY_HOME = "away-home" # home R -> L, away L -> R
NOT_SET = "not-set" # not set yet


class Orientation(Enum):
# change when possession changes
BALL_OWNING_TEAM = "ball-owning-team"

# changes during half-time
HOME_TEAM = "home-team"
AWAY_TEAM = "away-team"

# won't change during match
FIXED_HOME_AWAY = "fixed-home-away"
FIXED_AWAY_HOME = "fixed-away-home"

def get_orientation_factor(self,
attacking_direction: AttackingDirection,
ball_owning_team: Team):
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
else:
return 1
elif self == Orientation.AWAY_TEAM:
if attacking_direction == AttackingDirection.AWAY_HOME:
return -1
else:
return 1
elif self == Orientation.BALL_OWNING_TEAM:
if ((ball_owning_team == Team.HOME
and attacking_direction == AttackingDirection.HOME_AWAY)
or
(ball_owning_team == Team.AWAY
and attacking_direction == AttackingDirection.AWAY_HOME)):
return -1
else:
return 1


@dataclass
class Period:
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


class DataSetFlag(Flag):
BALL_OWNING_TEAM = 1
BALL_STATE = 2


@dataclass
class DataRecord(ABC):
timestamp: float
ball_owning_team: Team
ball_state: BallState

period: Period


@dataclass
class DataSet(ABC):
flags: DataSetFlag
pitch_dimensions: PitchDimensions
orientation: Orientation
periods: List[Period]
records: List[DataRecord]


Loading

0 comments on commit a979fd7

Please sign in to comment.