python - How to get the spline basis used by scipy.interpolate.splev -


i need evaluate b-splines in python. wrote code below works well.

import numpy np import scipy.interpolate si  def scipy_bspline(cv,n,degree):     """ bspline basis function         c        = list of control points.         n        = number of points on curve.         degree   = curve degree     """     # create range of u values     c = len(cv)     kv = np.array([0]*degree + range(c-degree+1) + [c-degree]*degree,dtype='int')     u  = np.linspace(0,c-degree,n)      # calculate result     arange = np.arange(n)     points = np.zeros((n,cv.shape[1]))     in xrange(cv.shape[1]):         points[arange,i] = si.splev(u, (kv,cv[:,i],degree))      return points 

giving 6 control points , asking evaluate 100k points on curve breeze:

# control points cv = np.array([[ 50.,  25., 0.],        [ 59.,  12., 0.],        [ 50.,  10., 0.],        [ 57.,   2., 0.],        [ 40.,   4., 0.],        [ 40.,   14., 0.]])  n = 100000  # 100k points degree = 3 # curve degree points_scipy = scipy_bspline(cv,n,degree) #cprofile clocks @ 0.012 seconds 

here's plot of "points_scipy": enter image description here

now, make faster compute basis 100k points on curve, store in memory, , when need draw curve need multiply new control point positions stored basis new curve. prove point wrote function uses deboor's algorithm compute basis:

def basis(c, n, degree):     """ bspline basis function         c        = number of control points.         n        = number of points on curve.         degree   = curve degree     """     # create knot vector , range of samples on curve     kv = np.array([0]*degree + range(c-degree+1) + [c-degree]*degree,dtype='int') # knot vector     u  = np.linspace(0,c-degree,n) # samples range      # cox - deboor recursive function calculate basis     def coxdeboor(u, k, d):         # test end conditions         if (d == 0):             if (kv[k] <= u , u < kv[k+1]):                 return 1             return 0          den1 = kv[k+d] - kv[k]         den2 = 0         eq1  = 0         eq2  = 0          if den1 > 0:             eq1 = ((u-kv[k]) / den1) * coxdeboor(u,k,(d-1))          try:             den2 = kv[k+d+1] - kv[k+1]             if den2 > 0:                 eq2 = ((kv[k+d+1]-u) / den2) * coxdeboor(u,(k+1),(d-1))         except:             pass          return eq1 + eq2       # compute basis each point     b = np.zeros((n,c))     in xrange(n):         k in xrange(c):             b[i][k%c] += coxdeboor(u[i],k,degree)      b[n-1][-1] = 1      return b 

now lets use compute new basis, multiply control points , confirm we're getting same results splev:

b = basis(len(cv),n,degree) #5600011 function calls (600011 primitive calls) in 10.975 seconds points_basis = np.dot(b,cv) #3 function calls in 0.002 seconds print np.allclose(points_basis,points_scipy) # returns true 

my extremely slow function returned 100k basis values in 11 seconds, since these values need computed once, calculating points on curve ended being 6 times faster way doing via splev.

the fact able exact same results both method , splev leads me believe internally splev calculates basis do, except faster.

so goal find out how calculate basis fast, store in memory , use np.dot() compute new points on curve, , question is: possible use spicy.interpolate basis values (i presume) splev uses compute it's result? , if so, how?

[addendum]

following unutbu's , ev-br's useful insight on how scipy calculates spline's basis, looked fortran code , wrote equivalent best of abilities:

def fitpack_basis(c, n=100, d=3, rminoffset=0, rmaxoffset=0):     """ fitpack's spline basis function         c = number of control points.         n = number of points on curve.         d = curve degree     """     # create knot vector     kv = np.array([0]*d + range(c-d+1) + [c-d]*d, dtype='int')      # create sample range     u = np.linspace(rminoffset, rmaxoffset + c - d, n)  # samples range      # create buffers     b  = np.zeros((n,c)) # basis     bb = np.zeros((n,c)) # basis buffer     left  = np.clip(np.floor(u),0,c-d-1).astype(int)   # left  knot vector indices     right = left+d+1 # right knot vector indices      # go!     nrange = np.arange(n)     b[nrange,left] = 1.0     j in xrange(1, d+1):         crange = np.arange(j)[:,none]         bb[nrange,left+crange] = b[nrange,left+crange]                 b[nrange,left] = 0.0         in xrange(j):             f = bb[nrange,left+i] / (kv[right+i] - kv[right+i-j])             b[nrange,left+i] = b[nrange,left+i] + f * (kv[right+i] - u)             b[nrange,left+i+1] = f * (u - kv[right+i-j])      return b 

testing against unutbu's version of original basis function:

fb = fitpack_basis(c,n,d) #22 function calls in 0.044 seconds b = basis(c,n,d) #81 function calls (45 primitive calls) in 0.013 seconds  ~5 times faster print np.allclose(b,fb) # returns true 

my function 5 times slower, still relatively fast. lets me use sample ranges go beyond boundaries, of use in application. example:

print fitpack_basis(c,5,d,rminoffset=-0.1,rmaxoffset=.2) [[ 1.331  -0.3468  0.0159 -0.0002  0.      0.    ] [ 0.0208  0.4766  0.4391  0.0635  0.      0.    ] [ 0.      0.0228  0.4398  0.4959  0.0416  0.    ] [ 0.      0.      0.0407  0.3621  0.5444  0.0527] [ 0.      0.     -0.0013  0.0673 -0.794   1.728 ]] 

so reason use fitpack_basis since it's relatively fast. love suggestions improving performance , closer unutbu's version of original basis function wrote.

fitpack_basis uses double loop iteratively modifies elements in bb , b. don't see way use numpy vectorize these loops since value of bb , b @ each stage of iteration depends on values previous iterations. in situations cython can used improve performance of loops.

here cython-ized version of fitpack_basis, runs fast bspline_basis. main ideas used boost speed using cython declare type of every variable, , rewrite uses of numpy fancy indexing loops using plain integer indexing.

see this page instructions on how build code , run python.

import numpy np cimport numpy np cimport cython  ctypedef np.float64_t dtype_f ctypedef np.int64_t dtype_i  @cython.boundscheck(false) @cython.wraparound(false) @cython.nonecheck(false) def cython_fitpack_basis(int c, int n=100, int d=3,                           double rminoffset=0, double rmaxoffset=0):     """ fitpack's spline basis function         c = number of control points.         n = number of points on curve.         d = curve degree     """     cdef py_ssize_t i, j, k, l     cdef double f      # create knot vector     cdef np.ndarray[dtype_i, ndim=1] kv = np.array(         [0]*d + range(c-d+1) + [c-d]*d, dtype=np.int64)      # create sample range     cdef np.ndarray[dtype_f, ndim=1] u = np.linspace(         rminoffset, rmaxoffset + c - d, n)      # basis     cdef np.ndarray[dtype_f, ndim=2] b  = np.zeros((n,c))      # basis buffer     cdef np.ndarray[dtype_f, ndim=2] bb = np.zeros((n,c))      # left  knot vector indices     cdef np.ndarray[dtype_i, ndim=1] left = np.clip(np.floor(u), 0, c-d-1).astype(np.int64)        # right knot vector indices     cdef np.ndarray[dtype_i, ndim=1] right = left+d+1       k in range(n):         b[k, left[k]] = 1.0      j in range(1, d+1):         l in range(j):             k in range(n):                 bb[k, left[k] + l] = b[k, left[k] + l]                  b[k, left[k]] = 0.0         in range(j):             k in range(n):                 f = bb[k, left[k]+i] / (kv[right[k]+i] - kv[right[k]+i-j])                 b[k, left[k]+i] = b[k, left[k]+i] + f * (kv[right[k]+i] - u[k])                 b[k, left[k]+i+1] = f * (u[k] - kv[right[k]+i-j])     return b 

using timeit code benchmark it's performance,

import timeit import numpy np import cython_bspline cb import numpy_bspline nb  c = 6 n = 10**5 d = 3  fb = nb.fitpack_basis(c, n, d) bb = nb.bspline_basis(c, n, d)  cfb = cb.cython_fitpack_basis(c,n,d)   assert np.allclose(bb, fb)  assert np.allclose(cfb, fb)  # print(nb.fitpack_basis(c,5,d,rminoffset=-0.1,rmaxoffset=.2))  timing = dict() timing['nb.fitpack_basis'] = timeit.timeit(     stmt='nb.fitpack_basis(c, n, d)',      setup='from __main__ import nb, c, n, d',      number=10) timing['nb.bspline_basis'] = timeit.timeit(     stmt='nb.bspline_basis(c, n, d)',      setup='from __main__ import nb, c, n, d',      number=10) timing['cb.cython_fitpack_basis'] = timeit.timeit(     stmt='cb.cython_fitpack_basis(c, n, d)',      setup='from __main__ import cb, c, n, d',      number=10)  func_name, t in timing.items():     print "{:>25}: {:.4f}".format(func_name, t) 

it appears cython can make fitpack_basis code run fast (and perhaps bit faster) bspline_basis:

         nb.bspline_basis: 0.3322   cb.cython_fitpack_basis: 0.2939          nb.fitpack_basis: 0.9182 

Comments

Popular posts from this blog

sublimetext3 - what keyboard shortcut is to comment/uncomment for this script tag in sublime -

java - No use of nillable="0" in SOAP Webservice -

ubuntu - Laravel 5.2 quickstart guide gives Not Found Error -