Bicubic interpolation Python
Solution 1
Not sure why Wikipedia implementation is not working as expected. Probably, the reason is that these values might be approximated in a different way than what is explained in their site.
px00 = (f12 - f10)/2*deltax
px01 = (f22 - f20)/2*deltax
px10 = (f13 - f11)/2*deltax
px11 = (f23 - f21)/2*deltax
py00 = (f21 - f01)/2*deltay
py01 = (f22 - f02)/2*deltay
py10 = (f31 - f11)/2*deltay
py11 = (f32 - f12)/2*deltay
pxy00 = ((f22-f20) - (f02-f00))/4*deltax*deltay
pxy01 = ((f32-f30) - (f12-f10))/4*deltax*deltay
pxy10 = ((f23-f21) - (f03-f01))/4*deltax*deltay
pxy11 = ((f33-f31) - (f13-f11))/4*deltax*deltay
However, I found this document which have a different implementation and it is well explained and easier understood than Wikipedia. The result I obtain using this implementation are quite similar to what obtained by SciPy
.
def bicubic_interpolation2(xi, yi, zi, xnew, ynew):
# check sorting
if np.any(np.diff(xi) < 0) and np.any(np.diff(yi) < 0) and\
np.any(np.diff(xnew) < 0) and np.any(np.diff(ynew) < 0):
raise ValueError('data are not sorted')
if zi.shape != (xi.size, yi.size):
raise ValueError('zi is not set properly use np.meshgrid(xi, yi)')
z = np.zeros((xnew.size, ynew.size))
deltax = xi[1] - xi[0]
deltay = yi[1] - yi[0]
for n, x in enumerate(xnew):
for m, y in enumerate(ynew):
if xi.min() <= x <= xi.max() and yi.min() <= y <= yi.max():
i = np.searchsorted(xi, x) - 1
j = np.searchsorted(yi, y) - 1
x1 = xi[i]
x2 = xi[i+1]
y1 = yi[j]
y2 = yi[j+1]
px = (x-x1)/(x2-x1)
py = (y-y1)/(y2-y1)
f00 = zi[i-1, j-1] #row0 col0 >> x0,y0
f01 = zi[i-1, j] #row0 col1 >> x1,y0
f02 = zi[i-1, j+1] #row0 col2 >> x2,y0
f10 = zi[i, j-1] #row1 col0 >> x0,y1
f11 = p00 = zi[i, j] #row1 col1 >> x1,y1
f12 = p01 = zi[i, j+1] #row1 col2 >> x2,y1
f20 = zi[i+1,j-1] #row2 col0 >> x0,y2
f21 = p10 = zi[i+1,j] #row2 col1 >> x1,y2
f22 = p11 = zi[i+1,j+1] #row2 col2 >> x2,y2
if 0 < i < xi.size-2 and 0 < j < yi.size-2:
f03 = zi[i-1, j+2] #row0 col3 >> x3,y0
f13 = zi[i,j+2] #row1 col3 >> x3,y1
f23 = zi[i+1,j+2] #row2 col3 >> x3,y2
f30 = zi[i+2,j-1] #row3 col0 >> x0,y3
f31 = zi[i+2,j] #row3 col1 >> x1,y3
f32 = zi[i+2,j+1] #row3 col2 >> x2,y3
f33 = zi[i+2,j+2] #row3 col3 >> x3,y3
elif i<=0:
f03 = f02 #row0 col3 >> x3,y0
f13 = f12 #row1 col3 >> x3,y1
f23 = f22 #row2 col3 >> x3,y2
f30 = zi[i+2,j-1] #row3 col0 >> x0,y3
f31 = zi[i+2,j] #row3 col1 >> x1,y3
f32 = zi[i+2,j+1] #row3 col2 >> x2,y3
f33 = f32 #row3 col3 >> x3,y3
elif j<=0:
f03 = zi[i-1, j+2] #row0 col3 >> x3,y0
f13 = zi[i,j+2] #row1 col3 >> x3,y1
f23 = zi[i+1,j+2] #row2 col3 >> x3,y2
f30 = f20 #row3 col0 >> x0,y3
f31 = f21 #row3 col1 >> x1,y3
f32 = f22 #row3 col2 >> x2,y3
f33 = f23 #row3 col3 >> x3,y3
elif i == xi.size-2 or j == yi.size-2:
f03 = f02 #row0 col3 >> x3,y0
f13 = f12 #row1 col3 >> x3,y1
f23 = f22 #row2 col3 >> x3,y2
f30 = f20 #row3 col0 >> x0,y3
f31 = f21 #row3 col1 >> x1,y3
f32 = f22 #row3 col2 >> x2,y3
f33 = f23 #row3 col3 >> x3,y3
Z = np.array([f00, f01, f02, f03,
f10, f11, f12, f13,
f20, f21, f22, f23,
f30, f31, f32, f33]).reshape(4,4).transpose()
X = np.tile(np.array([-1, 0, 1, 2]), (4,1))
X[0,:] = X[0,:]**3
X[1,:] = X[1,:]**2
X[-1,:] = 1
Cr = [email protected](X)
R = [email protected]([px**3, px**2, px, 1])
Y = np.tile(np.array([-1, 0, 1, 2]), (4,1)).transpose()
Y[:,0] = Y[:,0]**3
Y[:,1] = Y[:,1]**2
Y[:,-1] = 1
Cc = np.linalg.inv(Y)@R
z[n,m]=([email protected]([py**3, py**2, py, 1]))
return z
def f(x,y):
return np.sin(np.sqrt(x ** 2 + y ** 2))
x = np.linspace(-6, 6, 11)
y = np.linspace(-6, 6, 11)
xx, yy = np.meshgrid(x, y)
z = f(xx, yy)
x_new = np.linspace(-6, 6, 100)
y_new = np.linspace(-6, 6, 100)
xx_new, yy_new = np.meshgrid(x_new, y_new)
z_new = bicubic_interpolation2(x, y, z, x_new, y_new)
z_true = f(xx_new, yy_new)
f_scipy = interpolate.interp2d(x, y, z, kind='cubic')
z_scipy = f_scipy(x_new, y_new)
fig, ax = plt.subplots(2, 2, sharey=True, figsize=(16,12))
img0 = ax[0, 0].scatter(xx, yy, c=z, s=100)
ax[0, 0].set_title('original points')
fig.colorbar(img0, ax=ax[0, 0], orientation='vertical', shrink=1, pad=0.01)
img1 = ax[0, 1].imshow(z_new, vmin=z_new.min(), vmax=z_new.max(), origin='lower',
extent=[x_new.min(), x_new.max(), y_new.max(), y_new.min()])
ax[0, 1].set_title('bicubic our code')
fig.colorbar(img1, ax=ax[0, 1], orientation='vertical', shrink=1, pad=0.01)
img2 = ax[1, 0].imshow(z_scipy, vmin=z_scipy.min(), vmax=z_scipy.max(), origin='lower',
extent=[x_new.min(), x_new.max(), y_new.max(), y_new.min()])
ax[1, 0].set_title('bicubic scipy')
fig.colorbar(img2, ax=ax[1, 0], orientation='vertical', shrink=1, pad=0.01)
img3 = ax[1, 1].imshow(z_true, vmin=z_true.min(), vmax=z_true.max(), origin='lower',
extent=[x_new.min(), x_new.max(), y_new.max(), y_new.min()])
ax[1, 1].set_title('true model')
fig.colorbar(img3, ax=ax[1, 1], orientation='vertical', shrink=1, pad=0.01)
plt.subplots_adjust(wspace=0.05, hspace=0.15)
plt.show()
fig, ax = plt.subplots(1, 2, sharey=True, figsize=(10, 6))
ax[0].plot(xx[0,:], z[5,:], 'or', label='original')
ax[0].plot(xx_new[0,:], z_true[int(100/10*5),:], label='true')
ax[0].plot(xx_new[0,:], z_new[int(100/10*5), :], label='our interpolation')
ax[1].plot(xx[0,:], z[5,:], 'or', label='original')
ax[1].plot(xx_new[0,:], z_true[int(100/10*5),:], label='true')
ax[1].plot(xx_new[0,:], z_scipy[int(100/10*5), :], label='scipy interpolation')
for axes in ax:
axes.legend()
axes.grid()
plt.show()
Solution 2
For future notice, I think the problem is that the algorithm detailed on Wikipedia is for bicubic interpolation on the unit square. If instead you are interpolating on a rectilinear grid, your vector x needs to be modified slightly. See the section 'Extension to rectilinear grids', now included on the Wikipedia page. https://en.wikipedia.org/wiki/Bicubic_interpolation#Extension_to_rectilinear_grids
Solution 3
Upgrade solution of Khalil Al Hooti. I hope it's better.
def BiCubicInterp(X, Y, Z, h = 0.01):
new_Z = []
new_X = []
new_Y = []
new_n_x = int((X[1] - X[0]) / h)
new_n_y = int((Y[1] - Y[0]) / h)
count_X = len(X)
count_Y = len(Y)
X_m = np.array([[-1, 0, 1, 8],
[1, 0, 1, 4],
[-1, 0, 1, 2],
[1, 1, 1, 1]])
Y_m = np.array([[-1, 1, -1, 1],
[0, 0, 0, 1],
[1, 1, 1, 1],
[8, 4, 2, 1]])
X_m = np.linalg.inv(X_m)
Y_m = np.linalg.inv(Y_m)
for i in range(1, count_X):
px = X[i - 1]
k = i - 1
for s in range(new_n_x):
for j in range(1, count_Y):
py = Y[j - 1]
l = j - 1
for r in range(new_n_y):
x1 = X[k]
x2 = X[k+1]
y1 = Y[l]
y2 = Y[l+1]
P_x = (px - x1)/(x2 - x1)
P_y = (py - y1)/(y2 - y1)
f00 = Z[(count_Y + l-1) % count_Y, (count_X + k - 1) % count_X]
f01 = Z[(count_Y + l-1) % count_Y, (count_X + k) % count_X]
f02 = Z[(count_Y + l-1) % count_Y, (count_X + k + 1) % count_X]
f03 = Z[(count_Y + l-1) % count_Y, (count_X + k + 2) % count_X]
f10 = Z[(count_Y + l) % count_Y, (count_X + k - 1) % count_X]
f11 = Z[(count_Y + l) % count_Y, (count_X + k) % count_X]
f12 = Z[(count_Y + l) % count_Y, (count_X + k + 1) % count_X]
f13 = Z[(count_Y + l) % count_Y, (count_X + k + 2) % count_X]
f20 = Z[(count_Y + l + 1) % count_Y, (count_X + k - 1) % count_X]
f21 = Z[(count_Y + l + 1) % count_Y, (count_X + k) % count_X]
f22 = Z[(count_Y + l + 1) % count_Y, (count_X + k + 1) % count_X]
f23 = Z[(count_Y + l + 1) % count_Y, (count_X + k + 2) % count_X]
f30 = Z[(count_Y + l + 2) % count_Y, (count_X + k - 1) % count_X]
f31 = Z[(count_Y + l + 2) % count_Y, (count_X + k) % count_X]
f32 = Z[(count_Y + l + 2) % count_Y, (count_X + k + 1) % count_X]
f33 = Z[(count_Y + l + 2) % count_Y, (count_X + k + 2) % count_X]
Z_m = np.array([[f00, f01, f02, f03],
[f10, f11, f12, f13],
[f20, f21, f22, f23],
[f30, f31, f32, f33]])
Cr = np.dot(Z_m, X_m)
R = np.dot(Cr, np.array([P_x**3, P_x**2, P_x, 1]).T)
Cc = np.dot(Y_m, R)
new_Z.append((np.dot(np.array([P_y**3, P_y**2, P_y, 1]), Cc)))
new_X.append(px)
new_Y.append(py)
py += h
py = round(py, 2)
px += h
px = round(px, 2)
return new_X, new_Y, new_Z
from scipy.interpolate import griddata
x = np.linspace(0, 3, 4)
y = np.linspace(0, 3, 4)
X_ = np.linspace(0, 3, 30)
Y_ = np.linspace(0, 3, 30)
xs2, ys2, zs2 = BiCubicInterp(x, y, cords)
zs = bicubic_interpolation2(x, y, cords, X_, Y_)
x, y = np.meshgrid(x, y)
zs3 = griddata(np.array([x.reshape(1, -1)[0], y.reshape(1,-1)[0]]).T, cords.reshape(1, -1)[0], (xs2, ys2), method='cubic')
xs2 = np.array(xs2)
ys2 = np.array(ys2)
zs2 = np.array(zs2)
fig, axs = plt.subplots(1, 4, figsize = (32, 10))
shape2 = floor(np.sqrt(len(zs2)))
X2 = np.reshape(xs2,(shape2, shape2))
Y2 = np.reshape(ys2,(shape2, shape2))
Z2 = np.reshape(zs2,(shape2, shape2))
shape3 = floor(np.sqrt(len(zs3)))
Z3 = np.reshape(zs3,(shape3, shape3))
vmax = max([np.amax(zs2), np.amax(zs3), np.amax(zs), np.amax(cords)])
vmin = max([np.amin(zs2), np.amin(zs3), np.amin(zs), np.amin(cords)])
pt = axs[0].contourf(X2, Y2, Z2, levels = 1000, label = 'Интерполируемые бикубически точки', cmap = 'jet', vmin = vmin, vmax = vmax)
axs[1].contourf(X2, Y2, Z3, levels = 1000, label = 'Интерполируемые треугольниками точки', cmap = 'jet', vmin = vmin, vmax = vmax)
axs[2].contourf(X_, Y_, zs, levels = 1000, label = 'Интерполируемые бикубически точки (начальное решение)', cmap = 'jet', vmin = vmin, vmax = vmax)
axs[3].contourf(x, y, cords, levels = 1000, label = 'Интерполируемые бикубически точки (начальное решение)', cmap = 'jet', vmin = vmin, vmax = vmax)
axs[0].set_ylabel('y')
axs[0].set_xlabel('x')
axs[1].set_ylabel('y')
axs[1].set_xlabel('x')
axs[2].set_ylabel('y')
axs[2].set_xlabel('x')
axs[3].set_ylabel('y')
axs[3].set_xlabel('x')
axs[0].set_title('JacKira')
axs[1].set_title('scipy.interpolate.griddata method=\"cubic\"')
axs[2].set_title('Khalil')
axs[3].set_title('Input Data')
Comments
-
Khalil Al Hooti almost 2 years
I have developed Bicubic interpolation for demonstration to some undergraduate students using Python Programming language.
The methodology is as explained in wikipedia, The code is working fine except the results I am getting are slightly different than what is obtained when using scipy library.
The interpolation code is shown below in the function
bicubic_interpolation
.import numpy as np import matplotlib.pyplot as plt from mpl_toolkits import mplot3d from scipy import interpolate import sympy as syp import pandas as pd pd.options.display.max_colwidth = 200 %matplotlib inline def bicubic_interpolation(xi, yi, zi, xnew, ynew): # check sorting if np.any(np.diff(xi) < 0) and np.any(np.diff(yi) < 0) and\ np.any(np.diff(xnew) < 0) and np.any(np.diff(ynew) < 0): raise ValueError('data are not sorted') if zi.shape != (xi.size, yi.size): raise ValueError('zi is not set properly use np.meshgrid(xi, yi)') z = np.zeros((xnew.size, ynew.size)) deltax = xi[1] - xi[0] deltay = yi[1] - yi[0] for n, x in enumerate(xnew): for m, y in enumerate(ynew): if xi.min() <= x <= xi.max() and yi.min() <= y <= yi.max(): i = np.searchsorted(xi, x) - 1 j = np.searchsorted(yi, y) - 1 x0 = xi[i-1] x1 = xi[i] x2 = xi[i+1] x3 = x1+2*deltax y0 = yi[j-1] y1 = yi[j] y2 = yi[j+1] y3 = y1+2*deltay px = (x-x1)/(x2-x1) py = (y-y1)/(y2-y1) f00 = zi[i-1, j-1] #row0 col0 >> x0,y0 f01 = zi[i-1, j] #row0 col1 >> x1,y0 f02 = zi[i-1, j+1] #row0 col2 >> x2,y0 f10 = zi[i, j-1] #row1 col0 >> x0,y1 f11 = p00 = zi[i, j] #row1 col1 >> x1,y1 f12 = p01 = zi[i, j+1] #row1 col2 >> x2,y1 f20 = zi[i+1,j-1] #row2 col0 >> x0,y2 f21 = p10 = zi[i+1,j] #row2 col1 >> x1,y2 f22 = p11 = zi[i+1,j+1] #row2 col2 >> x2,y2 if 0 < i < xi.size-2 and 0 < j < yi.size-2: f03 = zi[i-1, j+2] #row0 col3 >> x3,y0 f13 = zi[i,j+2] #row1 col3 >> x3,y1 f23 = zi[i+1,j+2] #row2 col3 >> x3,y2 f30 = zi[i+2,j-1] #row3 col0 >> x0,y3 f31 = zi[i+2,j] #row3 col1 >> x1,y3 f32 = zi[i+2,j+1] #row3 col2 >> x2,y3 f33 = zi[i+2,j+2] #row3 col3 >> x3,y3 elif i<=0: f03 = f02 #row0 col3 >> x3,y0 f13 = f12 #row1 col3 >> x3,y1 f23 = f22 #row2 col3 >> x3,y2 f30 = zi[i+2,j-1] #row3 col0 >> x0,y3 f31 = zi[i+2,j] #row3 col1 >> x1,y3 f32 = zi[i+2,j+1] #row3 col2 >> x2,y3 f33 = f32 #row3 col3 >> x3,y3 elif j<=0: f03 = zi[i-1, j+2] #row0 col3 >> x3,y0 f13 = zi[i,j+2] #row1 col3 >> x3,y1 f23 = zi[i+1,j+2] #row2 col3 >> x3,y2 f30 = f20 #row3 col0 >> x0,y3 f31 = f21 #row3 col1 >> x1,y3 f32 = f22 #row3 col2 >> x2,y3 f33 = f23 #row3 col3 >> x3,y3 elif i == xi.size-2 or j == yi.size-2: f03 = f02 #row0 col3 >> x3,y0 f13 = f12 #row1 col3 >> x3,y1 f23 = f22 #row2 col3 >> x3,y2 f30 = f20 #row3 col0 >> x0,y3 f31 = f21 #row3 col1 >> x1,y3 f32 = f22 #row3 col2 >> x2,y3 f33 = f23 #row3 col3 >> x3,y3 px00 = (f12 - f10)/2*deltax px01 = (f22 - f20)/2*deltax px10 = (f13 - f11)/2*deltax px11 = (f23 - f21)/2*deltax py00 = (f21 - f01)/2*deltay py01 = (f22 - f02)/2*deltay py10 = (f31 - f11)/2*deltay py11 = (f32 - f12)/2*deltay pxy00 = ((f22-f20) - (f02-f00))/4*deltax*deltay pxy01 = ((f32-f30) - (f12-f10))/4*deltax*deltay pxy10 = ((f23-f21) - (f03-f01))/4*deltax*deltay pxy11 = ((f33-f31) - (f13-f11))/4*deltax*deltay f = np.array([p00, p01, p10, p11, px00, px01, px10, px11, py00, py01, py10, py11, pxy00, pxy01, pxy10, pxy11]) a = A@f a = a.reshape(4,4).transpose() z[n,m] = np.array([1, px, px**2, px**3]) @ a @ np.array([1, py, py**2, py**3]) return z
In the function
bicubic_interpolation
the inputs arexi
= old x data range,yi
= old y range,zi
= old values at grids points (x,y),xnew
, andynew
are the new horizontal data ranges. All inputs are 1D numpy arrays exceptzi
which is 2D numpy array.The data I am testing the function on are shown below. I am comparing the results as well with
scipy
and true model (functionf
).def f(x,y): return np.sin(np.sqrt(x ** 2 + y ** 2)) x = np.linspace(-6, 6, 11) y = np.linspace(-6, 6, 11) xx, yy = np.meshgrid(x, y) z = f(xx, yy) x_new = np.linspace(-6, 6, 100) y_new = np.linspace(-6, 6, 100) xx_new, yy_new = np.meshgrid(x_new, y_new) z_new = bicubic_interpolation(x, y, z, x_new, y_new) z_true = f(xx_new, yy_new) f_scipy = interpolate.interp2d(x, y, z, kind='cubic') z_scipy = f_scipy(x_new, y_new) fig, ax = plt.subplots(2, 2, sharey=True, figsize=(16,12)) img0 = ax[0, 0].scatter(xx, yy, c=z, s=100) ax[0, 0].set_title('original points') fig.colorbar(img0, ax=ax[0, 0], orientation='vertical', shrink=1, pad=0.01) img1 = ax[0, 1].imshow(z_new, vmin=z_new.min(), vmax=z_new.max(), origin='lower', extent=[x_new.min(), x_new.max(), y_new.max(), y_new.min()]) ax[0, 1].set_title('bicubic our code') fig.colorbar(img1, ax=ax[0, 1], orientation='vertical', shrink=1, pad=0.01) img2 = ax[1, 0].imshow(z_scipy, vmin=z_scipy.min(), vmax=z_scipy.max(), origin='lower', extent=[x_new.min(), x_new.max(), y_new.max(), y_new.min()]) ax[1, 0].set_title('bicubic scipy') fig.colorbar(img2, ax=ax[1, 0], orientation='vertical', shrink=1, pad=0.01) img3 = ax[1, 1].imshow(z_true, vmin=z_true.min(), vmax=z_true.max(), origin='lower', extent=[x_new.min(), x_new.max(), y_new.max(), y_new.min()]) ax[1, 1].set_title('true model') fig.colorbar(img3, ax=ax[1, 1], orientation='vertical', shrink=1, pad=0.01) plt.subplots_adjust(wspace=0.05, hspace=0.15) plt.show()
The results are shown below:
The matrix A (inside funtion
bicubic_interpolation
) is as explained in the Wikipedia site and simply can be obtained using the following code:x = syp.Symbol('x') y = syp.Symbol('y') a00, a01, a02, a03, a10, a11, a12, a13 = syp.symbols('a00 a01 a02 a03 a10 a11 a12 a13') a20, a21, a22, a23, a30, a31, a32, a33 = syp.symbols('a20 a21 a22 a23 a30 a31 a32 a33') p = a00 + a01*y + a02*y**2 + a03*y**3\ + a10*x + a11*x*y + a12*x*y**2 + a13*x*y**3\ + a20*x**2 + a21*x**2*y + a22*x**2*y**2 + a23*x**2*y**3\ + a30*x**3 + a31*x**3*y + a32*x**3*y**2 + a33*x**3*y**3 px = syp.diff(p, x) py = syp.diff(p, y) pxy = syp.diff(p, x, y) df = pd.DataFrame(columns=['function', 'evaluation']) for i in range(2): for j in range(2): function = 'p({}, {})'.format(j,i) df.loc[len(df)] = [function, p.subs({x:j, y:i})] for i in range(2): for j in range(2): function = 'px({}, {})'.format(j,i) df.loc[len(df)] = [function, px.subs({x:j, y:i})] for i in range(2): for j in range(2): function = 'py({}, {})'.format(j,i) df.loc[len(df)] = [function, py.subs({x:j, y:i})] for i in range(2): for j in range(2): function = 'pxy({}, {})'.format(j,i) df.loc[len(df)] = [function, pxy.subs({x:j, y:i})] eqns = df['evaluation'].tolist() symbols = [a00,a01,a02,a03,a10,a11,a12,a13,a20,a21,a22,a23,a30,a31,a32,a33] A = syp.linear_eq_to_matrix(eqns, *symbols)[0] A = np.array(A.inv()).astype(np.float64) print(df) print(A)
I would like to know where the problem is with the
bicubic_interpolation
function and why it is slightly different than the result obtained byscipy
? Any help is greatly appreciated! -
Zhanwen Chen over 5 yearshow can this interpolation method apply to images, where there are no x and y, only z?
-
Khalil Al Hooti over 5 yearsI am not expert in image processing! I think what already is available in python scientific ecosystem is sufficient and definitely better than this implementation for that matter, they support parallel processing too. I was testing this for just educational purposes. I also recently modified the code and will update the answer here soon. Regards, @ZhanwenChen
-
Will.Evo almost 5 yearsI am having a weird result using your bi cubic function. When I try to interpolate an image from 512x512 to 1024x1024, there is an empty row at the bottom of the interpolated image and empty column on the right side of image. Any ideas as to why?
-
Eliot K almost 3 yearsCan you add some clarification as to why your answer is better and to help users understand what is different?
-
JacKira almost 3 yearsFor users this solution more comfortable, because new meshgrid returns from procedure. I was deciding task of bicubic interpolation for C# implementation in work project, but didn't find ready-made simple solution. The best way was to take code by Khalil and transform it to fit the capabilities of C#, and i tried made code more readable.
-
JacKira almost 3 yearsWhen i debugging Khalil solution, i noticed what Z matrix changes to the cyclic shift rule and just code it. I hope this solution more simple for understanding in the future.
-
Safi over 2 yearsthe above cited link for Bicubic is no more functional. I found the document over here. anyflip.com/lxkj/dlpa