Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 76 additions & 19 deletions floodlight/models/kinematics.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from floodlight import XY
from floodlight.core.property import PlayerProperty
from floodlight.models.base import BaseModel, requires_fit
from floodlight.transforms.filter import _get_filterable_and_short_sequences


class DistanceModel(BaseModel):
Expand Down Expand Up @@ -83,28 +84,59 @@ def fit(self, xy: XY, difference: str = "central", axis: str = None):
calculated in both dimensions.
"""

# differentiate only sequences without NaNs
# shortest possible signal length to differentiate
min_signal_len = 2
# pe-allocate array for differences
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=0)
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],
)
to_differentiate = xy.xy
elif axis == "x":
distance_euclidean = np.gradient(xy.x, axis=0)
to_differentiate = xy.x
elif axis == "y":
distance_euclidean = np.gradient(xy.y, axis=0)
to_differentiate = xy.y
else:
raise ValueError(
f"Expected axis to be one of (None, 'x', 'y'), got {axis}."
)

differences_xy = np.full(to_differentiate.shape, np.NaN)
# loop through the xy-object columns
for i, column in enumerate(np.transpose(to_differentiate)):
# extract indices of filterable and short sequences
seqs_diff, seqs_short = _get_filterable_and_short_sequences(
column, min_signal_len
)
# pre-allocate space for filtered column
col_diff = np.full(column.shape, np.nan)

# loop through filterable sequences
for start, end in seqs_diff:
# apply filter to the sequence and enter filtered data to their
# respective indices in the data
if difference == "central":
col_diff[start:end] = np.gradient(column[start:end])
elif difference == "backward":
col_diff[start:end] = np.diff(
column[start:end], prepend=column[start]
)
else:
raise ValueError(
f"Expected differences to be one of ('central',"
f" 'backward', got {difference})"
)

# set single frames to 0 because differentiation is not possible
for start, end in seqs_short:
col_diff[start:end] = 0

# enter filtered data into respective column
differences_xy[:, i] = col_diff

distance_euclidean = np.hypot(
differences_xy[:, ::2],
differences_xy[:, 1::2],
)

self._distance_euclidean_ = PlayerProperty(
property=distance_euclidean, name="distance_covered", framerate=xy.framerate
)
Expand Down Expand Up @@ -323,12 +355,37 @@ def fit(
velocity_model.fit(xy, difference=difference, axis=axis)
velocity = velocity_model.velocity()

if difference == "central":
acceleration = np.gradient(velocity.property, axis=0) * velocity.framerate
else:
acceleration = (
np.diff(velocity.property, axis=0, append=0) * velocity.framerate
min_signal_len = 2
# pe-allocate array for differences
differences_vel = np.empty(velocity.property.shape)
# loop through the xy-object columns
for i, column in enumerate(np.transpose(velocity)):
# extract indices of filterable and short sequences
seqs_diff, seqs_short = _get_filterable_and_short_sequences(
column, min_signal_len
)
# pre-allocate space for filtered column
col_diff = np.full(column.shape, np.nan)

# loop through filterable sequences
for start, end in seqs_diff:
# apply filter to the sequence and enter filtered data to their
# respective indices in the data
if difference == "central":
col_diff[start:end] = np.gradient(column[start:end])
else:
col_diff[start:end] = np.diff(
column[start:end], prepend=column[start]
)

# set single frames to 0 because differentiation is not possible
for start, end in seqs_short:
col_diff[start:end] = 0

# enter filtered data into respective column
differences_vel[:, i] = col_diff

acceleration = differences_vel * velocity.framerate

self._acceleration_ = PlayerProperty(
property=acceleration,
Expand Down
20 changes: 10 additions & 10 deletions tests/test_models/test_kinematics.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def test_distance_model_fit_difference_central(example_xy_object_kinematics) ->
# Assert
assert np.array_equal(
np.round(distance_covered, 3),
np.array(((1, np.NaN), (1.118, 1.414), (1.414, np.NaN))),
np.array(((1, 0), (1.118, np.NaN), (1.414, 0))),
equal_nan=True,
)

Expand All @@ -42,7 +42,7 @@ def test_distance_model_fit_difference_backward(example_xy_object_kinematics) ->
# Assert
assert np.array_equal(
np.round(distance_covered, 3),
np.array(((0, 1.414), (1, np.NaN), (1.414, np.NaN))),
np.array(((0, 0), (1, np.NaN), (1.414, 0))),
equal_nan=True,
)

Expand All @@ -60,7 +60,7 @@ def test_distance_covered(example_xy_object_kinematics) -> None:
# Assert
assert np.array_equal(
np.round(distance_covered, 3),
np.array(((1, np.NaN), (1.118, 1.414), (1.414, np.NaN))),
np.array(((1, 0), (1.118, np.NaN), (1.414, 0))),
equal_nan=True,
)

Expand All @@ -78,7 +78,7 @@ def test_cumulative_distance_covered(example_xy_object_kinematics) -> None:
# Assert
assert np.array_equal(
np.round(distance_covered, 3),
np.array(((1, 0), (2.118, 1.414), (3.532, 1.414))),
np.array(((1, 0), (2.118, 0), (3.532, 0))),
equal_nan=True,
)

Expand All @@ -96,7 +96,7 @@ def test_velocity_model_fit_difference_central(example_xy_object_kinematics) ->
# Assert
assert np.array_equal(
np.round(velocity, 3),
np.array(((20, np.NaN), (22.361, 28.284), (28.284, np.NaN))),
np.array(((20, 0), (22.361, np.NaN), (28.284, 0))),
equal_nan=True,
)

Expand All @@ -114,7 +114,7 @@ def test_velocity_model_fit_difference_backward(example_xy_object_kinematics) ->
# Assert
assert np.array_equal(
np.round(velocity, 3),
np.array(((0, 28.284), (20, np.NaN), (28.284, np.NaN))),
np.array(((0, 0), (20, np.NaN), (28.284, 0))),
equal_nan=True,
)

Expand All @@ -132,7 +132,7 @@ def test_velocity(example_xy_object_kinematics) -> None:
# Assert
assert np.array_equal(
np.round(velocity, 3),
np.array(((20, np.NaN), (22.361, 28.284), (28.284, np.NaN))),
np.array(((20, 0), (22.361, np.NaN), (28.284, 0))),
equal_nan=True,
)

Expand All @@ -150,7 +150,7 @@ def test_acceleration_model_difference_central(example_xy_object_kinematics) ->
# Assert
assert np.array_equal(
np.round(acceleration, 3),
np.array(((47.214, np.NaN), (82.843, np.NaN), (118.472, np.NaN))),
np.array(((47.214, 0), (82.843, np.NaN), (118.472, 0))),
equal_nan=True,
)

Expand All @@ -168,7 +168,7 @@ def test_acceleration_model_difference_backward(example_xy_object_kinematics) ->
# Assert
assert np.array_equal(
np.round(acceleration, 3),
np.array(((400, np.NaN), (165.685, np.NaN), (-565.685, np.NaN))),
np.array(((0, 0), (400, np.NaN), (165.685, 0))),
equal_nan=True,
)

Expand All @@ -186,6 +186,6 @@ def test_acceleration(example_xy_object_kinematics) -> None:
# Assert
assert np.array_equal(
np.round(acceleration, 3),
np.array(((47.214, np.NaN), (82.843, np.NaN), (118.472, np.NaN))),
np.array(((47.214, 0), (82.843, np.NaN), (118.472, 0))),
equal_nan=True,
)