import numpy as np
from scipy.interpolate import splev, splprep
from scipy.spatial import transform
from fury.animation.helpers import (
euclidean_distances,
get_next_timestamp,
get_previous_timestamp,
get_time_tau,
get_timestamps_from_keyframes,
get_values_from_keyframes,
lerp,
)
from fury.colormap import hsv2rgb, lab2rgb, rgb2hsv, rgb2lab, rgb2xyz, xyz2rgb
[docs]def spline_interpolator(keyframes, degree):
"""N-th degree spline interpolator for keyframes.
This is a general n-th degree spline interpolator to be used for any shape
of keyframes data.
Parameters
----------
keyframes: dict
Keyframe data containing timestamps and values to form the spline
curve. Data should be on the following format:
>>> {1: {'value': np.array([...])}, 2: {'value': np.array([...])}}
Returns
-------
function
The interpolation function that take time and return interpolated
value at that time.
"""
if len(keyframes) < (degree + 1):
raise ValueError(
f'Minimum {degree + 1} '
f'keyframes must be set in order to use '
f'{degree}-degree spline'
)
timestamps = get_timestamps_from_keyframes(keyframes)
values = get_values_from_keyframes(keyframes)
distances = euclidean_distances(values)
distances_sum = sum(distances)
cumulative_dist_sum = np.cumsum([0] + distances)
tck = splprep(values.T, k=degree, full_output=1, s=0)[0][0]
def interpolate(t):
t0 = get_previous_timestamp(timestamps, t)
t1 = get_next_timestamp(timestamps, t)
mi_index = np.where(timestamps == t0)[0][0]
dt = get_time_tau(t, t0, t1)
section = cumulative_dist_sum[mi_index]
ts = (section + dt * distances[mi_index]) / distances_sum
return np.array(splev(ts, tck))
return interpolate
[docs]def cubic_spline_interpolator(keyframes):
"""Cubic spline interpolator for keyframes.
This is a general cubic spline interpolator to be used for any shape of
keyframes data.
Parameters
----------
keyframes: dict
Keyframe data containing timestamps and values to form the cubic spline
curve.
Returns
-------
function
The interpolation function that take time and return interpolated
value at that time.
See Also
--------
spline_interpolator
"""
return spline_interpolator(keyframes, degree=3)
[docs]def step_interpolator(keyframes):
"""Step interpolator for keyframes.
This is a simple step interpolator to be used for any shape of
keyframes data.
Parameters
----------
keyframes: dict
Keyframe data containing timestamps and values to form the spline
Returns
-------
function
The interpolation function that take time and return interpolated
value at that time.
"""
timestamps = get_timestamps_from_keyframes(keyframes)
def interpolate(t):
previous_t = get_previous_timestamp(timestamps, t, include_last=True)
return keyframes.get(previous_t).get('value')
return interpolate
[docs]def linear_interpolator(keyframes):
"""Linear interpolator for keyframes.
This is a general linear interpolator to be used for any shape of
keyframes data.
Parameters
----------
keyframes: dict
Keyframe data to be linearly interpolated.
Returns
-------
function
The interpolation function that take time and return interpolated
value at that time.
"""
timestamps = get_timestamps_from_keyframes(keyframes)
is_single = len(keyframes) == 1
def interpolate(t):
if is_single:
t = timestamps[0]
return keyframes.get(t).get('value')
t0 = get_previous_timestamp(timestamps, t)
t1 = get_next_timestamp(timestamps, t)
p0 = keyframes.get(t0).get('value')
p1 = keyframes.get(t1).get('value')
return lerp(p0, p1, t0, t1, t)
return interpolate
[docs]def cubic_bezier_interpolator(keyframes):
"""Cubic Bézier interpolator for keyframes.
This is a general cubic Bézier interpolator to be used for any shape of
keyframes data.
Parameters
----------
keyframes : dict
Keyframes to be interpolated at any time.
Returns
-------
function
The interpolation function that take time and return interpolated
value at that time.
Notes
-----
If no control points are set in the keyframes, The cubic
Bézier interpolator will almost behave as a linear interpolator.
"""
timestamps = get_timestamps_from_keyframes(keyframes)
for ts in timestamps:
# keyframe at timestamp
kf_ts = keyframes.get(ts)
if kf_ts.get('in_cp') is None:
kf_ts['in_cp'] = kf_ts.get('value')
if kf_ts.get('out_cp') is None:
kf_ts['out_cp'] = kf_ts.get('value')
def interpolate(t):
t0 = get_previous_timestamp(timestamps, t)
t1 = get_next_timestamp(timestamps, t)
k0 = keyframes.get(t0)
k1 = keyframes.get(t1)
p0 = k0.get('value')
p1 = k0.get('out_cp')
p2 = k1.get('in_cp')
p3 = k1.get('value')
dt = get_time_tau(t, t0, t1)
val = (
(1 - dt) ** 3 * p0
+ 3 * (1 - dt) ** 2 * dt * p1
+ 3 * (1 - dt) * dt**2 * p2
+ dt**3 * p3
)
return val
return interpolate
[docs]def slerp(keyframes):
"""Spherical based rotation keyframes interpolator.
A rotation interpolator to be used for rotation keyframes.
Parameters
----------
keyframes : dict
Rotation keyframes to be interpolated at any time.
Returns
-------
function
The interpolation function that take time and return interpolated
value at that time.
Notes
-----
Rotation keyframes must be in the form of quaternions.
"""
timestamps = get_timestamps_from_keyframes(keyframes)
quat_rots = []
for ts in timestamps:
quat_rots.append(keyframes.get(ts).get('value'))
rotations = transform.Rotation.from_quat(quat_rots)
# if only one keyframe specified, linear interpolator is used.
if len(timestamps) == 1:
return linear_interpolator(keyframes)
slerp_interp = transform.Slerp(timestamps, rotations)
min_t = timestamps[0]
max_t = timestamps[-1]
def interpolate(t):
t = min_t if t < min_t else max_t if t > max_t else t
v = slerp_interp(t)
q = v.as_quat()
return q
return interpolate
[docs]def color_interpolator(keyframes, rgb2space, space2rgb):
"""Custom-space color interpolator.
Interpolate values linearly inside a custom color space.
Parameters
----------
keyframes : dict
Rotation keyframes to be interpolated at any time.
rgb2space: function
A functions that take color value in rgb and return that color
converted to the targeted space.
space2rgb: function
A functions that take color value in the targeted space and returns
that color in rgb space.
Returns
-------
function
The interpolation function that take time and return interpolated
value at that time.
"""
timestamps = get_timestamps_from_keyframes(keyframes)
space_keyframes = {}
is_single = len(keyframes) == 1
for ts, keyframe in keyframes.items():
space_keyframes[ts] = rgb2space(keyframe.get('value'))
def interpolate(t):
if is_single:
t = timestamps[0]
return keyframes.get(t).get('value')
t0 = get_previous_timestamp(timestamps, t)
t1 = get_next_timestamp(timestamps, t)
c0 = space_keyframes.get(t0)
c1 = space_keyframes.get(t1)
space_color_val = lerp(c0, c1, t0, t1, t)
return space2rgb(space_color_val)
return interpolate
[docs]def hsv_color_interpolator(keyframes):
"""HSV interpolator for color keyframes
See Also
--------
color_interpolator
"""
return color_interpolator(keyframes, rgb2hsv, hsv2rgb)
[docs]def lab_color_interpolator(keyframes):
"""LAB interpolator for color keyframes
See Also
--------
color_interpolator
"""
return color_interpolator(keyframes, rgb2lab, lab2rgb)
[docs]def xyz_color_interpolator(keyframes):
"""XYZ interpolator for color keyframes
See Also
--------
color_interpolator
"""
return color_interpolator(keyframes, rgb2xyz, xyz2rgb)
[docs]def tan_cubic_spline_interpolator(keyframes):
"""Cubic spline interpolator for keyframes using tangents.
glTF contains additional tangent information for the cubic spline
interpolator.
Parameters
----------
keyframes: dict
Keyframe data containing timestamps and values to form the cubic spline
curve.
Returns
-------
function
The interpolation function that take time and return interpolated
value at that time.
"""
timestamps = get_timestamps_from_keyframes(keyframes)
for time in keyframes:
data = keyframes.get(time)
value = data.get('value')
if data.get('in_tangent') is None:
data['in_tangent'] = np.zeros_like(value)
if data.get('in_tangent') is None:
data['in_tangent'] = np.zeros_like(value)
def interpolate(t):
t0 = get_previous_timestamp(timestamps, t)
t1 = get_next_timestamp(timestamps, t)
dt = get_time_tau(t, t0, t1)
time_delta = t1 - t0
p0 = keyframes.get(t0).get('value')
tan_0 = keyframes.get(t0).get('out_tangent') * time_delta
p1 = keyframes.get(t1).get('value')
tan_1 = keyframes.get(t1).get('in_tangent') * time_delta
# cubic spline equation using tangents
t2 = dt * dt
t3 = t2 * dt
return (
(2 * t3 - 3 * t2 + 1) * p0
+ (t3 - 2 * t2 + dt) * tan_0
+ (-2 * t3 + 3 * t2) * p1
+ (t3 - t2) * tan_1
)
return interpolate