Reputation: 6284
Given a masked NumPy array of dimensionality (frames, points, 2)
representing a video of frames
frames, where in each frame points
(x, y)
points are being tracked.
I would like to interpolate this video from frames
frames to any number of frames, very fast, hopefully with a cubic spline, but any other continuous interpolation will work OK too.
The naive solution I have implemented splits the array into 2 arrays of dimensions (frames, points)
for the X
array and Y
array.
Then, I transpose the array to (points, frames)
. For each row (single point over-time) I map it to an index and value, so the array [5, 6, --, 7]
becomes:
[{"x": 0, "y": 5}, {"x": 1, "y": 6}, {"x": 3, "y": 7}]
I feed this to a scipy.interp1d
, and run on my new array which is for example [0, 0.5, 1, 1.5, 2, 2.5, 3]
and get a new x, y
array that I then convert back to NumPy.
This process removes the masks for intermittent frames (which is fine with me).
Current performance: Small array of shape (9 frames, 140 points, 2)
to (24 frames, 140 points, 2)
This is a masked array, like [5, 6, --, 7]
. so it is important to incorporate the masks in the interpolation in order to not have 0 values (the data array looks like [5, 6, 0, 7]
!
Here is a toy example with data on the desired and undesired behaviors:
import numpy as np
import numpy.ma as ma
from scipy.interpolate import interp1d
points = np.array([1, 2, 3, 4, 0, 6])
mask = [0, 0, 0, 0, 1, 0]
points = ma.array(points, mask=mask)
print(points) # [1 2 3 4 -- 6]
lin = np.linspace(0, 1, 6)
# Undesired behavior
f = interp1d(lin, points, axis=0, kind='cubic')
print(f(lin)) # [1 2 3 4 -8.8817842e-16 6]
# Desired behavior
compressed_lin = [0, 0.2, 0.4, 0.6, 1]
compressed_points = np.array([1,2,3,4,6])
f = interp1d(compressed_lin, compressed_points, axis=0, kind='cubic')
print(f(lin)) # [1 2 3 4 5 6]
Upvotes: 4
Views: 2151
Reputation: 26030
If you need it fast, 1) avoid masked arrays, 2) avoid interp1d. If CubicSpline is not fast enough, you can code up your own cubic interpolation taking into account that your points are equidistant.
EDIT: if you really cannot avoid masked arrays, beware that nothing in scipy.interpolate is numpy.ma-aware, so you'll need to DIY. Alternatively, consider doing some data imputation to fill the masked values.
Upvotes: 1
Reputation: 150725
I'm not sure you are using scipy.interpolate.interp1d
as I do, but my computer shows about 662 µs
:
np.random.seed(1)
points = np.random.rand(9,140,2)
f = interp1d(np.linspace(0,1,9), points, axis=0, kind='cubic')
f(np.linspace(0,1,24))
Performance (timeit -n 100
):
662 µs ± 111 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Upvotes: 2