tl;dr
This exposes a way to efficiently sample points from spline curves and 2-sphere homeomorphic splines using linear algebra operations and trivial yet useful caching operations.
First, a definition of spline curves as well as two ways of sampling it is given. Secondly, a definition of 2-sphere homeomorphic splines as well as a matricial scheme to sample several of them efficiently are given.
Finally, some tricks for the implementations are also covered briefly. I think those schemes benefit from a nearly optimal arithmetic intensity.
Notation
In the following, we clarify the following notations:
- \([\![n]\!] \triangleq \{0, 1, \cdots, n - 1\}\)
- As to clarify the manipulation of vectors as rows, for \(n \in \mathbb{N}\), \(\mathbb{R}^{n}\) is identified to \(\mathbb{R}^{1 \times n}\):
Open or closed spline curves
Expression of the curve \(\gamma\)
A 2D open or closed Spline is \(\gamma\) is defined as follows:
where \(t\) is the parameter, where \(\mathbf{c} \in \mathbb{R}^{M \times 2}\) are the spline coefficients also referred to in this 2D case as control points, and where \(\varphi_{\text{per}}\) is the periodisation of \(\varphi\), a given basis function.
Periodisation using wrapping
Periodisation is needed in the case of closed spline to meet boundary conditions.
One can define a wrapping function \(\text{wrap}_{M}\) to easily make \(\varphi\) periodic, that is:
Wrapping will be used in all the coming details.
Sampling using matrices
One can efficiently, for \(t \in [0, 1]\) sample a point \(\gamma(t) \in \mathbb{R}^2 \approxeq \mathbb{R}^{1\times 2}\) using matrices multiplication.
One can compute the vector:
and get:
Thus, for a given number of points \(N\), one can easily extend this scheme to sample a set \(\mathbf{X}\) of points:
By computing the matrix:
one gets:
Sampling evenly on the curve \(\gamma\)
A naive sampling consider a range of \(N\) evenly spread parameters \((t_i)_{i=0}^{N-1} \in [0, 1]^N\), that is:
This generally does not give evenly sampled points \((\gamma(t_i))_{i=0}^{N-1}\) on the curve, in the sense that:
where
for:
To sample \((\gamma(t_i))_{i=0}^{N-1}\) evenly, one can define \((t_i)_{i=0}^{N-1} \in [0, 1]^N\) as follows:
In practice, one does not compute \(\text{length}_\gamma^{-1}\) but binary-searches for preimages of \(\text{length}_\gamma\) after having it computed on a discretisation of \([0, 1]\) using \(N_{\text{oversample}} = c \ N\) points, for a given integer \(c\), for instance \(c \triangleq 5\).
This can be done efficiently for all the points using using numerical integration and a schema with the exact same matricial structure as presented above that considers \(\varphi'\) instead of \(\varphi\) for sampling \(\gamma'\).
2-Sphere homeomorphic Spline
Expression of the surface \(\eta\)
A 2-Sphere homeomorphic Spline parametrisation is given by:
where the following parameters are considered:
- \(s\in [0,1]\), for the longitude,
- \(t\in [0,1]\), for the latitude,
where the following spline coefficients are considered:
and where the exponential spline \(\phi_a\) for \(\alpha \in \left\{\frac{2\pi}{M_t}, \frac{\pi}{M_s - 1}\right\}\) is chosen
On coefficients and control points
The coefficients shown above contain more than the actual control points.
Indeed, the parametrisation is fully determined by \(M_t(M_s-2)+6\) points:
- the north pole \(\mathbf{c}_\mathrm{N}\)
- the south pole \(\mathbf{c}_\mathrm{S}\)
- the tangents at the north pole \(\mathbf{T}_{1,\mathrm{N}}\), \(\mathbf{T}_{2,\mathrm{N}}\)
- the tangents at the south pole \(\mathbf{T}_{1,\mathrm{S}}\), \(\mathbf{T}_{2,\mathrm{S}}\)
- \((M_s - 2)\times M_t\) actual control points (excluding \(\mathbf{c_N}\) and \(\mathbf{c_S}\), the north and south pole):
Moreover, \(4\times M_t\) additional control points at the extremities of each open longitude are considered to ensure correct behaviour at the boundaries are used for continuity and smoothness conditions due to the support size of the exponential spline basis:
Their expression is given, for \(k \in [\![M_t]\!]\), by:
Periodising latitudes using argument wrapping
As in the 2D case, one can define a wrapping function \(\text{wrap}_{M_t}\) to make \(\varphi_{\frac{2\pi}{M_t}}\) periodic, that is
Wrapping is used in this implementation.
Matricial expression of the surface
We can rewrite the expression
as a matricial product.
To do so, we first define the following matrix:
This way we have:
To combine both sums in a sole one, we define \(\tilde\Phi(s, t)\) and \(\mathbf{\tilde c}\), which are flattened version of \(\Phi(s, t)\) and \(\mathbf{c}\):
So that we get the nice matricial expression:
Efficiently sampling using the matricial expression
Now, for some sampling parameters \(N_s, N_t\), how to obtain:
?
And how to do it efficiently for several splines when \(N_s, N_t\) and \(\varphi\) are fixed? One can proceed as follows:
- Compute the tensor resulting from the concatenation of the points \(\Phi\) matrices:
- Flatten it on its two first axis and on its two lasts, that is:
- Cache \(\mathbf{\tilde\Phi}\)
- Use \(\mathbf{\tilde\Phi}\) on several splines’ flattened coefficients \(\mathbf{\tilde c}\):
$$ \ \mathbf{X} = \mathbf{\tilde \Phi} \mathbf{\tilde c} \in \mathbb{R}^{N_sN_t \times 3} $$
Computing \(\Phi\) efficiently: some tensor wizardry
First note that \(\Phi(s, t) \in \mathbb{R}^{(M_s + 2) \times M_t}\) can be computed as the outer product of two vectors
where
This can be generalised on all the points \((i, j) \in {[\![N_s]\!] \times [\![N_t]\!]}\). Indeed suppose that we have the two following matrices which concatenates the vectors for each point:
\(\mathbf{\Phi}\) can be computed by performing the outer product with respect to the two last axis of those:
This can be computed in a call of numpy.einsum
with the proper subscripts.
See this small numpy snippet to understand the subscripts used for Einstein notations.
About the implementation of \(\text{flatten}\)
\(\text{flatten}\) is implemented using reshaping (e.g np.reshape
) which generally is a constant time and memory operation as solely the tensors strides are being modified when reshaping. For more information, see the this
comment.
Further work
This Python implementation allows sampling 10 million surface splines’ points in a few second which should be fine for most application.
Still, the slow part of constructing \(\mathbf{\Phi}\) can be further speeded up, especially using parallelization because np.einsum
is single-threaded.
Might Python transpilers like Numba or Pythran help in this regards as they can translate numpy instructions into efficient machine code?
Finally and more importantly, some time is needed to reorder the implementation, release it and port it to use GPUs.
References
R. Delgado-Gonzalo, P. Thévenaz, M. Unser, Exponential splines and minimal-support bases for curve representation,
Computer Aided Geometric Design, Volume 29, Issue 2, 2012, Pages 109-128, ISSN 0167-8396,
https://doi.org/10.1016/j.cagd.2011.10.005