Source code for floodlight.models.kinematics

import numpy as np

from floodlight import XY
from floodlight.core.property import PlayerProperty
from floodlight.models.base import BaseModel, requires_fit


[docs]class DistanceModel(BaseModel): """Computations for Euclidean distances of all players. Upon calling the :func:`~DistanceModel.fit`-method, this model calculates the frame-wise Euclidean distance for each player. The following calculations can subsequently be queried by calling the corresponding methods: - Frame-wise Distance Covered --> :func:`~DistanceModel.distance_covered` - Cumulative Distance Covered --> :func:`~DistanceModel.cumulative_distance_\ covered` 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 DistanceModel >>> xy = XY(np.array(((0, 0), (0, 1), (1, 1), (2, 2)))) >>> dm = DistanceModel() >>> dm.fit(xy) >>> dm.distance_covered() PlayerProperty(property=array([[1. ], [0.70710678], [1.11803399], [1.41421356]]), name='distance_covered'0) >>> dm.cumulative_distance_covered() PlayerProperty(property=array([[1. ], [0.70710678], [1.11803399], [1.41421356]]), name='distance_covered') """ def __init__(self): super().__init__() self._distance_euclidean_ = None
[docs] def fit(self, xy: XY, difference: str = "central", axis: str = None): """Fits a model calculating Euclidean distances 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. axis: {None, 'x', 'y'}, optional Optional argument that restricts distance calculation to either the x- or y-dimension of the data. If set to None (default), distances are calculated in both dimensions. """ if axis is None: if difference == "central": differences_xy = np.gradient(xy.xy, axis=0) elif difference == "backward": differences_xy = np.diff(xy.xy, axis=0, prepend=xy.xy[0].reshape(1, -1)) else: raise ValueError( f"Expected axis to be one of (None, 'x', 'y'), got {axis}." ) distance_euclidean = np.hypot( differences_xy[:, ::2], differences_xy[:, 1::2], ) elif axis == "x": if difference == "central": distance_euclidean = np.gradient(xy.x, axis=0) elif difference == "backward": distance_euclidean = np.diff( xy.x, axis=0, prepend=xy.x[0].reshape(1, -1) ) elif axis == "y": if difference == "central": distance_euclidean = np.gradient(xy.y, axis=0) if difference == "backward": distance_euclidean = np.diff( xy.y, axis=0, prepend=xy.y[0].reshape(1, -1) ) else: raise ValueError( f"Expected axis to be one of (None, 'x', 'y'), got {axis}." ) self._distance_euclidean_ = PlayerProperty( property=distance_euclidean, name="distance_covered", framerate=xy.framerate )
[docs] @requires_fit def distance_covered(self) -> PlayerProperty: """Returns the frame-wise distance covered as computed by the fit method. Returns ------- distance_euclidean: 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 Euclidean distance covered. """ return self._distance_euclidean_
[docs] @requires_fit def cumulative_distance_covered(self) -> PlayerProperty: """Returns the cumulative distance covered. Returns ------- cumulative_distance_euclidean: 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 cumulative Euclidean distance covered calculated by numpy.nancumsum() over axis=0. """ dist = self._distance_euclidean_.property cum_dist = np.nancumsum(dist, axis=0) cumulative_distance = PlayerProperty( property=cum_dist, name="cumulative_distance_covered", framerate=self._distance_euclidean_.framerate, ) return cumulative_distance
[docs]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: - Frame-wise velocity --> :func:`~VelocityModel.velocity` 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 VelocityModel >>> xy = XY(np.array(((0, 0), (0, 1), (1, 1), (2, 2))), framerate=20) >>> vm = VelocityModel() >>> vm.fit(xy) >>> vm.velocity() PlayerProperty(property=array([[20. ], [14.14213562], [22.36067977], [28.28427125]]), name='velocity', framerate=20) """ def __init__(self): super().__init__() self._velocity_ = None
[docs] def fit( self, xy: XY, difference: str = "central", axis: str = None, ): """Fits a model calculating velocities 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. axis: {None, 'x', 'y'}, optional Optional argument that restricts distance calculation to either the x- or y-dimension of the data. If set to None (default), distances are calculated in both dimensions. """ distance_model = DistanceModel() distance_model.fit(xy, difference=difference, axis=axis) distance_euclidean = distance_model.distance_covered() velocity = np.multiply( distance_euclidean.property, distance_euclidean.framerate ) self._velocity_ = PlayerProperty( property=velocity, name="velocity", framerate=xy.framerate )
[docs] @requires_fit def velocity(self) -> PlayerProperty: """Returns the frame-wise velocity as computed by the fit method. Returns ------- velocity: 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._velocity_
[docs]class AccelerationModel(BaseModel): """Computations for velocities 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 by calling the corresponding method: - Frame-wise acceleration --> :func:`~AccelerationModel.acceleration` 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 AccelerationModel >>> xy = XY(np.array(((0, 0), (0, 1), (1, 1), (2, 2))), framerate=20) >>> am = AccelerationModel() >>> am.fit(xy) >>> am.acceleration() PlayerProperty(property=array([[-117.15728753], [ 23.60679775], [ 141.42135624], [ 118.47182945]]), name='acceleration', framerate=20) """ def __init__(self): super().__init__() self._acceleration_ = None
[docs] def fit( self, xy: XY, difference: str = "central", axis: str = None, ): """Fits a model calculating accelerations 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. axis: {None, 'x', 'y'}, optional Optional argument that restricts distance calculation to either the x- or y-dimension of the data. If set to None (default), distances are calculated in both dimensions. """ velocity_model = VelocityModel() velocity_model.fit(xy, difference=difference, axis=axis) velocity = velocity_model.velocity() if difference == "central": acceleration = np.multiply( np.gradient(velocity.property, axis=0), velocity.framerate ) else: acceleration = np.multiply( np.diff( velocity.property, axis=0, prepend=velocity.property[0].reshape(1, -1), ), velocity.framerate, ) self._acceleration_ = PlayerProperty( property=acceleration, name="acceleration", framerate=xy.framerate, )
[docs] @requires_fit def acceleration(self) -> PlayerProperty: """Returns the frame-wise acceleration as computed by the fit method. Returns ------- acceleration: 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 acceleration. """ return self._acceleration_