Skip to content

Commit

Permalink
feat: VelocityVectorModel
Browse files Browse the repository at this point in the history
  • Loading branch information
manuba95 committed Jul 4, 2023
1 parent e1de087 commit 96b2162
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 12 deletions.
117 changes: 106 additions & 11 deletions floodlight/models/kinematics.py
Expand Up @@ -156,11 +156,11 @@ def cumulative_distance_covered(self) -> PlayerProperty:
class VelocityModel(BaseModel):
"""Computations for velocities of all players.
Upon calling the :func:`~VelocityModel.fit`-method, this model calculates the
frame-wise velocity for each player. The calculation can subsequently be queried by
calling the corresponding method:
Upon calling the :func:`~SpeedModel.fit`-method, this model calculates the
frame-wise magnitude of the velocity vector for each player. The
calculation can subsequently be queried by calling the corresponding method:
- Frame-wise velocity --> :func:`~VelocityModel.velocity`
- Frame-wise velocity --> :func:`~VelocityModel.speed`
Notes
-----
Expand Down Expand Up @@ -193,10 +193,10 @@ class VelocityModel(BaseModel):
>>> xy = XY(np.array(((0, 0), (0, 1), (1, 1), (2, 2))), framerate=20)
>>> vm = VelocityModel()
>>> vm.fit(xy)
>>> sm = VelocityModel()
>>> sm.fit(xy)
>>> vm.velocity()
>>> sm.velocity()
PlayerProperty(property=array([[20. ],
[14.14213562],
[22.36067977],
Expand All @@ -213,7 +213,7 @@ def fit(
difference: str = "central",
axis: str = None,
):
"""Fits a model calculating velocities of each player to an XY object.
"""Fits a model calculating velocity of each player to an XY object.
Parameters
----------
Expand Down Expand Up @@ -247,16 +247,16 @@ def velocity(self) -> PlayerProperty:
Returns
-------
velocity: PlayerProperty
speed: PlayerProperty
A PlayerProperty object of shape (T, N), where T is the total number of
frames and N is the number of players. The columns contain the frame-wise
velocity.
speed.
"""
return self._velocity_


class AccelerationModel(BaseModel):
"""Computations for velocities of all players.
"""Computations for accelerations of all players.
Upon calling the :func:`~AccelerationModel.fit`-method, this model calculates the
frame-wise acceleration for each player. The calculation can subsequently be queried
Expand Down Expand Up @@ -367,3 +367,98 @@ def acceleration(self) -> PlayerProperty:
acceleration.
"""
return self._acceleration_


class VelocityVectorModel(BaseModel):
"""Computations for velocities of all players as a vector.
Upon calling the :func:`~VelocityVectorModel.fit`-method, this model calculates the
frame-wise velocity-vector for each player relative to their position. The
calculation can subsequently be queried by calling the corresponding method:
- Frame-wise velocity vector --> :func:`~VelocityVectorModel.velocityvector`
Notes
-----
For input data in metrical units, the output equals the input unit.
Differences between frames can be calculated with two different methods:
*Central difference method* (recommended) allows for differenciation without
temporal shift:
.. math::
y^{\\prime}(t_{0}) = \\frac{y_{1}-y_{-1}}{t_{1} - t_{-1}}
The first and last frame are padded with linear extrapolation.\n
*Backward difference method* calculates the difference between each consecutive
frame:
.. math::
y^{\\prime}(t_{0}) = \\frac{y_{0}-y_{-1}}{t_{0} - t_{-1}}
The first frame is padded prepending a '0' at the beginning of the array along
axis=1.
Examples
--------
>>> import numpy as np
>>> from floodlight import XY
>>> from floodlight.models.kinematics import VelocityVectorModel
>>> xy = XY(np.array(((0, 0), (0, 1), (1, 1), (2, 2))), framerate=20)
>>> vm = VelocityVectorModel()
>>> vm.fit(xy)
>>> vm.velocityvector()
PlayerProperty(property=array([[[ 0., 20.]],
[[10., 10.]],
[[20., 10.]],
[[20., 20.]]]), name='velocityvector', framerate=20)
"""

def __init__(self):
super().__init__()
self._velocityvector_ = None

def fit(self, xy: XY, difference: str = "central"):
"""Fits a model calculating velocitie vectors of each player to an XY object.
Parameters
----------
xy: XY
Floodlight XY Data object.
difference: {'central', 'backward'}, optional
The method of differentiation. 'central' will differentiate using the
central difference method, 'backward' will differentiate using the backward
difference method as described in the Notes.
"""

distance_model = DistanceModel()
distance_model.fit(xy, difference=difference, axis="x")
x_distance = distance_model.distance_covered()
distance_model.fit(xy, difference=difference, axis="y")
y_distance = distance_model.distance_covered()

distance_vector = np.stack((x_distance, y_distance), axis=2)

velocityvector = np.multiply(distance_vector, xy.framerate)

self._velocityvector_ = PlayerProperty(
property=velocityvector, name="velocity", framerate=xy.framerate
)

@requires_fit
def velocityvector(self) -> PlayerProperty:
"""Returns the frame-wise velocity vector as computed by the fit method.
Returns
-------
velocityvector: PlayerProperty
A PlayerProperty object of shape (T, N), where T is the total number of
frames and N is the number of players. The columns contain the frame-wise
velocity.
"""
return self._velocityvector_
83 changes: 82 additions & 1 deletion tests/test_models/test_kinematics.py
@@ -1,7 +1,12 @@
import pytest
import numpy as np

from floodlight.models.kinematics import DistanceModel, VelocityModel, AccelerationModel
from floodlight.models.kinematics import (
DistanceModel,
VelocityModel,
VelocityVectorModel,
AccelerationModel,
)


# Differences in the kinematic models can be calculated via central or backward
Expand Down Expand Up @@ -137,6 +142,82 @@ def test_velocity(example_xy_object_kinematics) -> None:
)


@pytest.mark.unit
def test_velocityvector_model_fit_difference_central(
example_xy_object_kinematics,
) -> None:
# Arrange
xy = example_xy_object_kinematics

# Act
velvec_model = VelocityVectorModel()
velvec_model.fit(xy)
velocityvector = velvec_model._velocityvector_

# Assert
assert np.array_equal(
np.round(velocityvector, 3),
np.array(
(
((0, 20), (np.NaN, np.NaN)),
((10, 20), (20, -20)),
((20, 20), (np.NaN, np.NaN)),
)
),
equal_nan=True,
)


@pytest.mark.unit
def test_velocityvector_model_fit_difference_backward(
example_xy_object_kinematics,
) -> None:
# Arrange
xy = example_xy_object_kinematics

# Act
velvec_model = VelocityVectorModel()
velvec_model.fit(xy, difference="backward")
velocityvector = velvec_model._velocityvector_

# Assert
assert np.array_equal(
np.round(velocityvector, 3),
np.array(
(
((0, 0), (0, 0)),
((0, 20), (np.NaN, np.NaN)),
((20, 20), (np.NaN, np.NaN)),
)
),
equal_nan=True,
)


@pytest.mark.unit
def test_velocityvector(example_xy_object_kinematics) -> None:
# Arrange
xy = example_xy_object_kinematics

# Act
velvec_model = VelocityVectorModel()
velvec_model.fit(xy)
velocityvector = velvec_model.velocityvector()

# Assert
assert np.array_equal(
np.round(velocityvector, 3),
np.array(
(
((0, 20), (np.NaN, np.NaN)),
((10, 20), (20, -20)),
((20, 20), (np.NaN, np.NaN)),
)
),
equal_nan=True,
)


@pytest.mark.unit
def test_acceleration_model_difference_central(example_xy_object_kinematics) -> None:
# Arrange
Expand Down

0 comments on commit 96b2162

Please sign in to comment.