Akima spline interpolation


#pragma rtGlobals=3		// Use modern global access method and strict wave access.

// This code was translated into IGOR from the Python code available at
// http://www.lfd.uci.edu/~gohlke/code/akima.py.html

// Copyright (c) 2007-2015, Christoph Gohlke
// Copyright (c) 2007-2015, The Regents of the University of California
// Produced at the Laboratory for Fluorescence Dynamics
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright
//   notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
//   notice, this list of conditions and the following disclaimer in the
//   documentation and/or other materials provided with the distribution.
// * Neither the name of the copyright holders nor the names of any
//   contributors may be used to endorse or promote products derived
//   from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

//Akima's interpolation method uses a continuously differentiable sub-spline
//built from piecewise cubic polynomials. The resultant curve passes through
//the given data points and will appear smooth and natural.
//
//:Author:
//  `Christoph Gohlke <http://www.lfd.uci.edu/~gohlke/>`_
//
//:Organization:
//  Laboratory for Fluorescence Dynamics, University of California, Irvine
//
//References
//----------
//(1) A new method of interpolation and smooth curve fitting based
//    on local procedures. Hiroshi Akima, J. ACM, October 1970, 17(4), 589-602.

    
Function akima(x, y, x_new, y_new)
//	Return interpolated data using Akima's method.
//
//	Parameters
//	----------
//	x_new : wave
//	1D array of monotonically increasing real values.
//	y : wave
//	N-D array of real values. y's length along the interpolation
//	axis must be equal to the length of x.
//	x_new : wave
//	New independent variables.
//	y_new : wave
//	Wave to receive results. The number of points must be the same as x_new

//    make/d/n=2 y_new
//    akima({0, 1, 2}, {0, 0, 1}, {0.5, 1.5}, y_new)
//    print y_new
//    y_new[0]= {-0.125,0.375}
    
	Wave x, y, x_new, y_new

	variable n, mm, mmm, mp, mpp, ii, wm

	duplicate/free x_new, x_i

	n = numpnts(x)

	make/d/n=(numpnts(x) - 1)/free dx, dy, m, m1
	dx = x[p + 1] - x[p]
	dy = y[p + 1] - y[p]

	m = dy / dx
	mm = 2 * m[0] - m[1]
	mmm = 2 * mm - m[0]
	mp = 2 * m[n - 2] - m[n - 3]
	mpp = 2 * mp - m[n - 2]

	m1 = m
	insertpoints 0, 2, m1
	m1[0] = mmm
	m1[1] = mm
	insertpoints numpnts(m1), 2, m1
	m1[numpnts(m1) - 2] = mp
	m1[numpnts(m1) - 1] = mpp

	make/d/free/n=(numpnts(m1) - 1) dm
	dm = abs(m1[p + 1] - m1[p])

	duplicate/r=[2, n + 1]/free dm, f1
	duplicate/r=[0, n - 1]/free dm, f2
	duplicate/free f1, f12
	f12 = f1 + f2

	duplicate/free f12, ids
	ids = f12[p] > 1e-9 * wavemax(f12) ? p : -1

	duplicate/free/r=[1, n] m1, b

	b = m1[p + 1]
	b = ids[p] >= 0 ? (f1[p] * m1[p + 1] + f2[p] * m1[p + 2]) / f12[p]: 0

	duplicate/free m, c, d
	c = (3.0 * m[p] - 2.0 * b[p] - b[p + 1]) / dx[p]
	d = (b[p] + b[p + 1] - 2.0 * m) / dx ^ 2

	duplicate/free x_i, bins
	bins = binarysearch(x, x_i)
	bins = min(bins[p], n - 2)

	duplicate/free/r=[0, numpnts(x_i) - 1] bins, bb
	
	duplicate/free x_i, wj
	wj = x_i - x[bb]
	
	duplicate/free bb, d_p, c_p, b_p
	b_p = b[bb[p]]
	c_p = c[bb[p]]
	d_p = d[bb[p]]
	
	y_new = ((wj * d_p + c_p) * wj + b_p) * wj + y[bb]
End
kravlor
Here is an alternate implementation based on the same reference. It has been tested against the published test cases for the algorithm.

First, call calcIota to generate interpolation information; then you can interpolate using Akima's spline method with the akima() function.


#pragma rtGlobals=3	

// Akima.ipf: Routines to implement Akima-spline fitting, based on
// H. Akima, Journ. ACM, Vol 17, No 4, 1970 p 589-602
// M. Bongard, 11/17/09

#pragma IgorVersion=6.1 // Use of free waves

ThreadSafe Function calcIota(knotX, knotY[, dWave])
	WAVE knotX // knot X locations
	WAVE knotY // knot Y locations
	WAVE dWave // Destination wave reference for iotas
	
	if(!WaveExists(knotX) || !WaveExists(knotY))
		Print "calcIota: ERROR -- requisite waves do not exist! Aborting..."
		return -1
	endif
	
	if(numpnts(knotX) != numpnts(knotY))
		Print "calcIota: ERROR -- knot waves must have same number of points! Aborting..."
		return -1
	endif
	
	Variable numKnots = numpnts(knotX)
	
	if(numKnots < 5)
		Print "calcIota: ERROR -- Akima spline algorithm requires at least 5 knots. Aborting..."
		return -1
	endif
	
	// Make intermediate ai, bi, mi arrays
	Make/D/FREE/N=(numKnots + 4)  kX, kY
	
	Variable i, j
	for(i = 2, j = 0; j < numKnots; i += 1, j += 1)
		kX[i] = knotX[j]
		kY[i] = knotY[j]
	endfor
	
	// Handle end-point extrapolation
	Make/D/FREE/N=5 endX, endY
	// RHS: end points are last three in dataset
	Variable endStartPt
	// RHS
	endStartPt = numPnts(kX) - 5
	endX = kX[p + endStartPt]
	endY = kY[p + endStartPt]
	
	calcEndPoints(endX, endY)
	
	kX[numpnts(kX)-2] = endX[3]
	kX[numpnts(kX)-1] = endX[4]
	kY[numpnts(kX)-2] = endY[3]
	kY[numpnts(kX)-1] = endY[4]
	
	// LHS: end points are first three in dataset, but reversed in ordering
	// (i.e. point 3 in Akima's notation == index 0)
	endX = 0
	endY = 0
	
	for(i = 0, j = 2; i < 3; i += 1, j -= 1)
		endX[j] = knotX[i]
		endY[j] = knotY[i]
	endfor
	
	calcEndPoints(endX, endY)
	
	kX[1] = endX[3]
	kX[0] = endX[4]
	kY[1] = endY[3]
	kY[0] = endY[4]
	
	// kX, kY are now properly populated, along with all necessary extrapolated endpoints
	// computed as specified in Akima1970
	
	Make/D/FREE/N=(numKnots + 4 - 1)  mK
	mK = (kY[p + 1] - kY[p]) / (kX[p + 1] - kX[p])	
	
	Make/O/N=(numKnots) knotIota
	
	Variable denom, m1,m2,m3,m4
	for(i = 2, j = 0; j < numKnots; i += 1, j += 1)
		m1 = mK[i - 2]
		m2 = mK[i - 1]
		m3 = mK[i]
		m4 = mK[i + 1]
		
		denom = abs(m4 - m3) + abs(m2 - m1)
		if(denom == 0)
			knotIota[j] = 0.5 * (m2 + m3)
			continue
		endif
		
		knotIota[j] = ( abs(m4 - m3)*m2 + abs(m2 - m1)*m3 ) / denom
	endfor

	if(!ParamIsDefault(dWave))
		// Overwrite input destination wave with new iotas
		Duplicate/O knotIota, dWave
		Killwaves/Z knotIota
	endif
End

// Given: 5-point knot wave knotX, knotY, with i=[0,2] representing the last three
// knot locations from data, compute end knots i=[3,4] appropriately.
ThreadSafe Function calcEndPoints(kX, kY)
	WAVE kX, kY // knot X,Y coordinate locations, respectively
	
	// Sanity checks
	if( (numPnts(kX) != numpnts(kY)) || numpnts(kX) != 5)
		Print "calcEndPoints: ERROR -- must have 5 points in knot wave! Aborting..."
		return -1
	endif
	
	// First, compute X locations of knots, according to relations in eq. 8 of Akima1970:
	kX[3] = kX[1] + kX[2] - kX[0]
	kX[4] = 2*kX[2] - kX[0]
	
	// Now all kX are known, so let's set up the line segment slope waves
	// ai, bi, mi of eq. (12)-(14).
	Make/N=4/FREE ai, bi, mi
	
	ai = kX[p+1] - kX[p]
	// ai is now determined completely
	
	bi = kY[p + 1] - kY[p]
	mi = bi/ai
	// bi, mi determined on i=[0,1]	
	
	// Determine remainder of quantities by applying solutions of eq (9)
	kY[3] = (2*mi[1] - mi[0])*(kX[3] - kX[2]) + kY[2]
	mi[2] = (kY[3] - kY[2]) / (kX[3] - kX[2])
	
	kY[4] = (2*mi[2] - mi[1])*(kX[4] - kX[3]) + kY[3]
	mi[3] = (kY[4] - kY[3]) / (kX[4] - kX[3])
End

ThreadSafe Function akima(x, knotX, knotY, knotIota)
	Variable x
	WAVE knotX, knotY, knotIota
	
	Variable i1, i2
	
	// Find where x
	Variable done = 0
	i2 = -1
	Variable numKnots = numpnts(knotX)
	do
		i2 += 1
		
		if(knotX[i2] == x)
			return knotY[i2]
		endif
		
		done = (knotX[i2] > x) ? 1 : 0
		
	while(!done && (i2 < numKnots))
	i1 = i2 - 1
	
	Variable x1, x2, y1, y2, iota1, iota2
	x1 = knotX[i1]
	y1 = knotY[i1]
	iota1 = knotIota[i1]
	
	x2 = knotX[i2]
	y2 = knotY[i2]
	iota2 = knotIota[i2]
	
	Variable p0, p1, p2, p3, tmp
	p0 = y1
	p1 = iota1
	p2 = ( 3 * (y2 - y1)/(x2 - x1) - 2*iota1 - iota2 ) / (x2 - x1)
	p3 = (iota1 + iota2 - 2 * (y2 - y1)/(x2 - x1)) / (x2 - x1)^2
	
	tmp = x - x1
	
	return p0 + p1 * tmp + p2 * tmp^2 + p3*tmp^3
End

Forum

Support

Gallery

Igor Pro 10

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More