Source code for dispel.providers.generic.tasks.draw.tremor
"""A module dedicated to intentional tremors computation."""
import pandas as pd
from fastdtw import fastdtw
from dispel.providers.generic.tasks.draw.intersections import get_segment
from dispel.providers.generic.tasks.draw.shapes import get_segment_deceleration
from dispel.signal.core import euclidean_distance
#: The number of data point of the square-like model shapes.
_SQUARE_SHAPES_SIZE = 1038
def _get_segment_indexes(data: pd.DataFrame, segment: range, model_len: int):
"""Get the reference segment on which to compute intentional tremors.
Only for square-like shapes. Support also BDH format.
Parameters
----------
data
The pandas data frame containing all the minimum distance attributions
from Dynamic Time Warping between the user trajectory and the
reference.
segment
The range of indexes defining the segment on which to compute the
intentional tremors.
model_len
The length of the current reference trajectory.
Returns
-------
pandas.DataFrame
The minimum distance attributions from Dynamic Time Warping between the
user trajectory and the reference corresponding to the segment of
interest.
"""
if model_len == _SQUARE_SHAPES_SIZE:
segment_data = data.loc[data["ref"].isin(segment)]
else:
bdh_first_idx = int((segment[0] / _SQUARE_SHAPES_SIZE) * model_len)
bdh_last_idx = int((segment[-1] / _SQUARE_SHAPES_SIZE) * model_len)
segment_data = data[bdh_first_idx:bdh_last_idx]
return segment_data
[docs]
def get_deceleration_profile_data(
user: pd.DataFrame, ref: pd.DataFrame, level_id: str
) -> pd.DataFrame:
"""Transform `{level}_path` data frame to `{level}_deceleration` one.
It extracts the proper data to compute intentional tremors (`tsTouch` and
`minimal distance` pd.Series.).
Parameters
----------
user
The `{level}_user_paths` data frame.
ref
The `{level}_reference` data frame.
level_id
The given level on which to compute the data transformation.
Returns
-------
pandas.DataFrame
The `{level}_deceleration` data frame containing `tsTouch` and
`min_distance` pd.Series of the user points attributed to the specific
segment where the deceleration profile is relevant to compute
intentional tremors.
"""
new_user = user.join(get_segment(user))
new_ref = ref.join(get_segment(ref))
path_model = new_ref[["x", "y"]].to_numpy()
path_user = new_user[["x", "y"]].to_numpy()
# Apply DTW on the two paths.
_, matches = fastdtw(path_user, path_model, dist=2)
# Store matched attributions into a pandas data frame.
matches = pd.DataFrame(matches).rename(columns={0: "user", 1: "ref"})
# Compute the distance between each attributed points.
matches["min_distance"] = matches.apply(
lambda row: euclidean_distance(
new_user["seg"][row["user"]].segment[0],
new_ref["seg"][row["ref"]].segment[0],
),
axis=1,
)
# Keep only one attribution between a user point and a model point based on
# keeping the minimum distance.
min_matches = matches.groupby(by="user").min().reset_index()
# Get the specific segment on which to compute the intentional tremors.
deceleration_indexes = get_segment_deceleration(level_id)
reference_len = len(path_model)
segment_data = _get_segment_indexes(
min_matches, deceleration_indexes, reference_len
)
# Format the new data frame based on only the segment on which to compute
# intentional tremors.
segment_data = segment_data.join(
new_user.loc[segment_data["user"].values, ["tsTouch", "x", "y"]]
)
return segment_data.drop(["user", "ref"], axis=1).astype(
{"min_distance": float, "x": float, "y": float}
)