I would like to make an image of a ring of inner radius r1 and outter radius r2 with values modulated by a cosinus.
I've already made this and tried several options (comments in the code) :
import numpy as np
import matplotlib.pyplot as plt
def circle(x_center,y_center,r):
# theta = np.linspace(0, 2*np.pi, 500)
# x = r*np.cos(theta) - x_center
# y = r*np.sin(theta) - y_center
xv = np.linspace(-r-1,r+1,500)
X,Y = np.meshgrid(xv, xv)
profilegrid = np.ones(X.shape, float)
for i, a in enumerate(X[0, :]):
for k, z in enumerate(Y[:, 0]):
theta = np.arctan(z/a)
current_radius = np.sqrt(a**2 + z**2)
cond1 = current_radius <= r
# cond2 = np.logical_and(np.abs(theta)>=0,np.abs(theta)<=pi)
cond2 = a==a
if np.logical_and(cond1,cond2)==False :
profilegrid[i, k] = 0
else :
profilegrid[i, k] = np.cos(theta)
return xv,profilegrid
xv1,circle_big = circle(0,0,1)
xv2,circle_small = circle(0,0,0.5)
new = circle_big - circle_small
plt.imshow(new, interpolation="bicubic",
origin="lower", extent=[min(xv1),max(xv1),min(xv1),max(xv1)])
plt.colorbar()
plt.show()
But the output image gives me :
I should have values between -1 and 1 and not between 0 and 1. Furthermore, as you can see I've asked a circle of radius r1=1 substracted to a circle of radius r2=0.5 in order to simulate the ring but the circle seems to be a little bit bigger.
Any idea about the origin of those issues?
EDIT :
Found the issue concerning the radius problem thanks to #tgrtim :
xv = np.linspace(-r-1,r+1,500) should be fixed and replaced by :
xv = np.linspace(-2,2,500)
Furthermore the problem of the values interval [0:1] instead of [-1:1] comes from the arctan boundaries. So do you have any idea how to change this in order to have only one maximum and no mirror pattern like this?
The problem for the mirror pattern can be resolved involving the function math.atan2(y,x) instead of np.arctan(y/x).
Related
I'm trying to plot the angle vs. time plot for the output angle of a four-bar linkage (angle fi4 in the image below). This angle is calculated using the solution from the https://scholar.cu.edu.eg/?q=anis/files/week04-mdp206-position_analysis-draft.pdf, page 23.
I'm now trying to plot the fi_4(t) plot and am getting some strange results. The diagram displays the input angle fi2 as blue and output angle fi4 as red. Why is the fi2 fluctuating over time? Shouldn't the fi4 have some sort of sine curve?
Am I missing something here?
Four-bar linkage:
The code:
from __future__ import division
import math
import numpy as np
import matplotlib.pyplot as plt
# Input
#lengths of links (tube testing machine actual lengths)
a = 45.5 #mm
b = 250 #mm
c = 140 #mm
d = 244.244 #mm
# Solution for fi2 being a time function, f(time) = angle
f = 16.7/60 #/s
omega = 2 * np.pi * f #rad/s
t = np.linspace(0, 50, 100)
y = a * np.sin(omega * t)
x = a * np.cos(omega * t)
fi2 = np.arctan(y/x)
# Solution of the vector loop equation
#https://scholar.cu.edu.eg/?q=anis/files/week04-mdp206-position_analysis-draft.pdf
K1 = d/a
K2 = d/c
K3 = (a**2 - b**2 + c**2 + d**2)/(2*a*c)
A = np.cos(fi2) - K1 - K2*np.cos(fi2) + K3
B = -2*np.sin(fi2)
C = K1 - (K2+1)*np.cos(fi2) + K3
fi4_1 = 2*np.arctan((-B+np.sqrt(B**2 - 4*A*C))/(2*A))
fi4_2 = 2*np.arctan((-B-np.sqrt(B**2 - 4*A*C))/(2*A))
# Plot the fi2 time diagram and fi4 time diagram
plt.plot(t, np.degrees(fi2), color = 'blue')
plt.plot(t, np.degrees(fi4_2), color = 'red')
plt.show()
Diagram:
The linespace(0, 50, 100) is too fast. Replacing it with:
t = np.linspace(0, 5, 100)
Second, all the calculations involving the bare np.arctan() are incorrect. You should use np.arctan2(y, x), which determines the correct quadrant (unlike anything based on y/x where the respective signs of x and y are lost). So:
fi2 = np.arctan2(y, x) # not: np.arctan(y/x)
...
fi4_1 = 2 * np.arctan2(-B + np.sqrt(B**2 - 4*A*C), 2*A)
fi4_2 = 2 * np.arctan2(-B - np.sqrt(B**2 - 4*A*C), 2*A)
Putting some labels on your plots and showing both solutions for θ_4:
plt.plot(t, np.degrees(fi2) % 360, color = 'k', label=r'$θ_2$')
plt.plot(t, np.degrees(fi4_1) % 360, color = 'b', label=r'$θ_{4_1}$')
plt.plot(t, np.degrees(fi4_2) % 360, color = 'r', label=r'$θ_{4_2}$')
plt.xlabel('t [s]')
plt.ylabel('degrees')
plt.legend()
plt.show()
With these mods, we get:
BTW, do you want to see an amazingly lazy way of solving problems like these? Much more inefficient than your code, but much easier to derive (e.g. for other structures) without trying to express the closed form of your solution:
from scipy.optimize import fsolve
def polar(r, theta):
return r * np.array((np.cos(theta), np.sin(theta)))
def f(th34, th2):
th3, th4 = th34 # solve simultaneously for theta_3 and theta_4
pb_23 = polar(a, th2) + polar(b, th3) # point B based on links a, b
pb_14 = polar(d, 0) + polar(c, th4) # point B based on links d, c
return pb_23 - pb_14 # error: difference of the two
def solve(th2):
th4_1 = np.array([fsolve(f, [0, -1.5], args=(th2_k,))[1] for th2_k in th2])
th4_2 = np.array([fsolve(f, [0, 1.5], args=(th2_k,))[1] for th2_k in th2])
return th4_1, th4_2
Application:
t = np.linspace(0, 5, 100)
th2 = omega * t
th4_1, th4_2 = solve(th2)
twopi = 2 * np.pi
np.allclose(th4_1 % twopi, fi4_1 % twopi)
# True
np.allclose(th4_2 % twopi, fi4_2 % twopi)
# True
Depending on the structure of your mechanism (e.g. 5 links), you may have more than two solutions, and of course more angles, so you'd have to adapt the code above. But you get the idea.
Be warned: fsolve iterates to find a suitable (close enough) solution, so as I said, it is much slower than your closed form.
Update (some clarification/explanation):
The function f computes the position of the point B in two different ways (via R2-R3 and via R1-R4) and returns the difference (as a vector). We solve for the difference to be zero.
That function takes two arguments: one 2-dimensional variable (th34, which is an array [th3, th4]) and one parameter th2; the parameter is constant during one run of fsolve.
The values [0, -1.5] and [0, 1.5] are initialization values (guesses) for th34 (th3 and th4). We call fsolve twice to get the two possible solutions.
All angles refer to your figure. I use th for θ (theta, not phi), but I kept along the original fi4_1 and fi4_2 for comparison.
Modulo 2*pi, th4_1 should be equal to fi4_1 etc., which is tested by np.allclose to account for numerical rounding errors.
I would like to create an array with values that range from 0.0 to 1.0 as shown here:
weighting matrix
Basically, the left and top edges should remain close to 1.0 but slowly decay to 0.5 in the corners.
The bottom and right edges should remain close to 0.0
The middle region should be mostly 0.5, and the values should be decaying diagonally from 1.0 to 0.0.
This is what I've tried but it doesn't give me exactly what I would like.
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
y = np.zeros(len(x))
for i in range(len(x)):
y[i] = 1 / (1 + math.exp(-x[i]))
return y
sigmoid_ = sigmoid(np.linspace(20, 2.5, 30))
temp1 = np.repeat(sigmoid_.reshape((1,len(sigmoid_))), repeats=10, axis=0)
sigmoid_ = sigmoid(np.linspace(6, 3, 10))
temp2 = np.repeat(sigmoid_.reshape((len(sigmoid_),1)), repeats=30, axis=1)
alpha1 = temp1 + temp2
sigmoid_ = sigmoid(np.linspace(-2.5, -20, 30))
temp1 = np.repeat(sigmoid_.reshape((1,len(sigmoid_))), repeats=10, axis=0)
sigmoid_ = sigmoid(np.linspace(-3, -6, 10))
temp2 = np.repeat(sigmoid_.reshape((len(sigmoid_),1)), repeats=30, axis=1)
alpha2 = temp1 + temp2
alpha = alpha1 + alpha2
alpha = alpha - np.min(alpha)
alpha = alpha / np.max(alpha)
plt.matshow(alpha)
Which gives me this: results
Can someone help me?
This is the simplest function I can think of:
tune_me = 101
x = np.linspace(0, 1, tune_me)
y = np.linspace(0, 1, tune_me)
xv, yv = np.meshgrid(x, y)
sig = 1/(1 + np.exp(tune_me - xv - yv))
plt.matshow(sig)
But if you want something specific, you should probably figure out your math (maybe on the math stack exchange) before you try to implement it.
I'd use the same function for all parts of the weighting matrix area, if not otherwise required. A sigmoid function (which changes rapidly near the center and slowly away from it) is indeed suitable after appropriate translation and scaling. For the sigmoid function's argument I'd choose the taxicab distance from a corner of the area.
import math
import numpy as np
import matplotlib.pyplot as plt
alpha = np.ndarray((10, 30))
ymax, xmax = alpha.shape[0]-1, alpha.shape[1]-1
for y in range(alpha.shape[0]):
for x in range(alpha.shape[1]):
M = x/xmax+y/ymax # Manhattan distance, range [0;2]
M -= 1 # make range [-1;1], so that inflection point is in the middle
M *= 3.0 # the higher this factor, the steeper the sigmoid curve at the flex
s = 1 / (1 + math.exp(-M)) # sigmoid function, range ]0;1[
alpha[y, x] = 1-s # decay from 1 to 0
plt.matshow(alpha)
plt.show()
# show the values close to 0.5
h = [(y, x) for y in range(alpha.shape[0])
for x in range(alpha.shape[1]) if .4 < alpha[y, x] and alpha[y, x] < .6]
hy, hx = zip(*h)
plt.plot(hx, hy, 'o')
plt.gca().invert_yaxis()
plt.show()
Illustration of values in center region close to 0.5, e. g. in ]0.4;0.6[:
I have plotted a 'tear drop' shaped cylinder in matplotlib. To obtain the tear drop shape I plotted a normal cylinder from theta = 0 to theta = pi and an ellipse from theta = pi to theta = 2pi. However I am now trying to 'spin' the cylinder around it's axis which here is given conveniently by the z-axis.
I tried using the rotation matrix for rotating around the z-axis which Wikipedia gives as:
However when I try to rotate through -pi/3 radians, the cylinder becomes very disfigured.
Is there anyway to prevent this from happening?
Here is my code:
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from math import sin, cos, pi
import math
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
theta = np.linspace(0,2*pi, 1200)
Z = np.linspace(0,5,1000+600)
Z,theta = np.meshgrid(Z, theta)
X = []
Y = []
R = 0.003
#calculate the x and y values
for i in theta:
cnt = 0
tempX = []
tempY = []
for j in i:
#circle
if(i[0]<=pi):
tempX.append(R*cos(j))
tempY.append(R*sin(j))
cnt+=1
#ellipse
else:
tempX.append(R*cos(j))
tempY.append(0.006*sin(j))
X.append(tempX)
Y.append(tempY)
X1 = np.array(X)
Y1 = np.array(Y)
#rotate around the Z axis
a = -pi/3
for i in range(len(X)):
X1[i] = cos(a)*X1[i]-sin(a)*Y1[i]
Y1[i] = sin(a)*X1[i]+cos(a)*Y1[i]
#plot
ax.plot_surface(X1,Y1,Z,linewidth = 0, shade = True, alpha = 0.3)
ax.set_xlim(-0.01,0.01)
ax.set_ylim(-0.01, 0.01)
azimuth = 173
elevation = 52
ax.view_init(elevation, azimuth)
plt.show()
Your rotating is flawed: To calculate Y1[i] you need the old value of X1[i], but you already updated it. You can try something like
X1[i], Y1[i] = cos(a)*X1[i]-sin(a)*Y1[i], sin(a)*X1[i]+cos(a)*Y1[i]
if you want to make the matrix multiplication a bit more obvious (and fix the bug) you could also do the following (please doublecheck that the matrix is correct and that the multiplication is in the right order, I did not test this):
rotation_matrix = np.array([[cos(a), -sin(a)], [sin(a), cos(a)]])
x, y = zip(*[(x,y) # rotation_matrix for x,y in zip(x,y)])
the # is new in 3.5 and for numpy array it's defined to be the matrix multiplication. If you are on a version below 3.5 you can use np.dot.
The zip(*...) is necessary to get a pair of lists instead of a list of pairs. See also this answer
I have a function called func(mu, gamma) . For each combination of mu and gamma, the function will return a value, let's call it return_value.
Now I have set range for mu and gamma:
mu = np.linspace(0,1,100)
gamma = np.linspace(0,1,100)
Now we have 1e4 combinations and each combinations corresponds to a return_value. I want to plot a heatmap for return_value.
I have tried to use pcolor in Python. However, from the example in the documentation:
import matplotlib.pyplot as plt
import numpy as np
# make these smaller to increase the resolution
dx, dy = 0.15, 0.05
# generate 2 2d grids for the x & y bounds
y, x = np.mgrid[slice(-3, 3 + dy, dy),
slice(-3, 3 + dx, dx)]
z = (1 - x / 2. + x ** 5 + y ** 3) * np.exp(-x ** 2 - y ** 2)
# x and y are bounds, so z should be the value *inside* those bounds.
# Therefore, remove the last value from the z array.
z = z[:-1, :-1]
z_min, z_max = -np.abs(z).max(), np.abs(z).max()
because the defined function fun in my script can not take array as input, it does not work and I get this message if I follow the example:
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Below is my code for func:
def fun(mu, gamma2):
Isolation_Ratio = []
kappa1 = gamma2
kappa2 = gamma2
gamma1 = gamma2
g0 = gamma2 + kappa2 + gamma1 + kappa1
gammag = kappa1/2. + gamma1/2.
gamma = gamma2/2. + kappa2/2.
for ii in range(len(rangedeltaw)):
deltaw = rangedeltaw[ii]
Forward_delta = forward_delta(mu, deltaw)
Backward_delta = backward_delta(mu, deltaw)
forward_root1, forward_root2, forward_root3 = forward_root(mu, deltaw)
test_D, backward_root1, backward_root2, backward_root3 = backward_root(mu, deltaw)
Root1.append(backward_root1)
Root2.append(backward_root2)
Root3.append(backward_root3)
root1.append(forward_root1)
root2.append(forward_root2)
root3.append(forward_root3)
if Forward_delta >= 0 and Backward_delta >= 0:
a2sq = [max([forward_root1.real, forward_root2.real, forward_root3.real])]
b1sq = [max([backward_root1.real, backward_root2.real, backward_root3.real])]
A2sq.append(max([forward_root1.real, forward_root2.real, forward_root3.real]))
B1sq.append(max([backward_root1.real, backward_root2.real, backward_root3.real]))
for ii in range(len(a2sq)):
for jj in range(len(b1sq)):
Isolation_Ratio.append(kappa2*a2sq[ii]/(kappa1*b1sq[jj]))
elif Forward_delta >= 0 and Backward_delta < 0:
a2sq = [max([forward_root1.real, forward_root2.real, forward_root3.real])]
b1sq = [backward_root1.real]
A2sq.append(max([forward_root1.real, forward_root2.real, forward_root3.real]))
B1sq.append(backward_root1.real)
for ii in range(len(a2sq)):
for jj in range(len(b1sq)):
Isolation_Ratio.append(kappa2*a2sq[ii]/(kappa1*b1sq[jj]))
elif Forward_delta < 0 and Backward_delta >= 0:
a2sq = [forward_root1.real]
b1sq = [max([backward_root1.real, backward_root2.real, backward_root3.real])]
A2sq.append(forward_root1.real)
B1sq.append(max([backward_root1.real, backward_root2.real, backward_root3.real]))
for ii in range(len(a2sq)):
for jj in range(len(b1sq)):
Isolation_Ratio.append(kappa2*a2sq[ii]/(kappa1*b1sq[jj]))
else:
A2sq.append(forward_root1.real)
B1sq.append(backward_root1.real)
Isolation_Ratio.append(kappa2*forward_root1.real/(kappa1*backward_root1.real))
x = Isolation_RangeDeltaw
y = Isolation_Ratio
return max(y)
So, first, how to obtain the heatmap. fun() is still not self-contained (forward_delta() etc are not defined), so I cannot execute it, and you didn't specify at which line the error occurs, but I can guess that the offender is
if Forward_delta >= 0 and Backward_delta >= 0:
meaning that forward_delta() etc functions work with arrays. By the look of it, it may be possible to fully vectorize the function, but it is a non-trivial task and is a question of its own (and if you ever ask it, make sure to make a self contained example). A simpler, although a less efficient solution is to just fill the heatmap value by value:
import matplotlib.pyplot as plt
import numpy
def fun(mu, gamma):
# your function
mu = numpy.linspace(0,1,100)
gamma = numpy.linspace(0,1,100)
# filling the heatmap, value by value
fun_map = numpy.empty((mu.size, gamma.size))
for i in range(mu.size):
for j in range(gamma.size):
fun_map[i,j] = fun(mu[i], gamma[j])
Now that you have the array, the second part of your question is how to plot it. pcolor() is used to visualize discreet arrays; imshow() suits your purpose better:
fig = plt.figure()
s = fig.add_subplot(1, 1, 1, xlabel='$\\gamma$', ylabel='$\\mu$')
im = s.imshow(
fun_map,
extent=(gamma[0], gamma[-1], mu[0], mu[-1]),
origin='lower')
fig.colorbar(im)
fig.savefig('t.png')
Note that in the array the X dimension is last (corresponds to gamma), but imshow puts the X dimension first.
The result for a simple function
def fun(mu, gamma):
return numpy.sin(mu) + numpy.cos(gamma)
will look like
I have 4-dimensional data, say for the temperature, in an numpy.ndarray.
The shape of the array is (ntime, nheight_in, nlat, nlon).
I have corresponding 1D arrays for each of the dimensions that tell me which time, height, latitude, and longitude a certain value corresponds to, for this example I need height_in giving the height in metres.
Now I need to bring it onto a different height dimension, height_out, with a different length.
The following seems to do what I want:
ntime, nheight_in, nlat, nlon = t_in.shape
nheight_out = len(height_out)
t_out = np.empty((ntime, nheight_out, nlat, nlon))
for time in range(ntime):
for lat in range(nlat):
for lon in range(nlon):
t_out[time, :, lat, lon] = np.interp(
height_out, height_in, t[time, :, lat, lon]
)
But with 3 nested loops, and lots of switching between python and numpy, I don't think this is the best way to do it.
Any suggestions on how to improve this? Thanks
scipy's interp1d can help:
import numpy as np
from scipy.interpolate import interp1d
ntime, nheight_in, nlat, nlon = (10, 20, 30, 40)
heights = np.linspace(0, 1, nheight_in)
t_in = np.random.normal(size=(ntime, nheight_in, nlat, nlon))
f_out = interp1d(heights, t_in, axis=1)
nheight_out = 50
new_heights = np.linspace(0, 1, nheight_out)
t_out = f_out(new_heights)
I was looking for a similar function that works with irregularly spaced coordinates, and ended up writing my own function. As far as I see, the interpolation is handled nicely and the performance in terms of memory and speed is also quite good. I thought I'd share it here in case anyone else comes across this question looking for a similar function:
import numpy as np
import warnings
def interp_along_axis(y, x, newx, axis, inverse=False, method='linear'):
""" Interpolate vertical profiles, e.g. of atmospheric variables
using vectorized numpy operations
This function assumes that the x-xoordinate increases monotonically
ps:
* Updated to work with irregularly spaced x-coordinate.
* Updated to work with irregularly spaced newx-coordinate
* Updated to easily inverse the direction of the x-coordinate
* Updated to fill with nans outside extrapolation range
* Updated to include a linear interpolation method as well
(it was initially written for a cubic function)
Peter Kalverla
March 2018
--------------------
More info:
Algorithm from: http://www.paulinternet.nl/?page=bicubic
It approximates y = f(x) = ax^3 + bx^2 + cx + d
where y may be an ndarray input vector
Returns f(newx)
The algorithm uses the derivative f'(x) = 3ax^2 + 2bx + c
and uses the fact that:
f(0) = d
f(1) = a + b + c + d
f'(0) = c
f'(1) = 3a + 2b + c
Rewriting this yields expressions for a, b, c, d:
a = 2f(0) - 2f(1) + f'(0) + f'(1)
b = -3f(0) + 3f(1) - 2f'(0) - f'(1)
c = f'(0)
d = f(0)
These can be evaluated at two neighbouring points in x and
as such constitute the piecewise cubic interpolator.
"""
# View of x and y with axis as first dimension
if inverse:
_x = np.moveaxis(x, axis, 0)[::-1, ...]
_y = np.moveaxis(y, axis, 0)[::-1, ...]
_newx = np.moveaxis(newx, axis, 0)[::-1, ...]
else:
_y = np.moveaxis(y, axis, 0)
_x = np.moveaxis(x, axis, 0)
_newx = np.moveaxis(newx, axis, 0)
# Sanity checks
if np.any(_newx[0] < _x[0]) or np.any(_newx[-1] > _x[-1]):
# raise ValueError('This function cannot extrapolate')
warnings.warn("Some values are outside the interpolation range. "
"These will be filled with NaN")
if np.any(np.diff(_x, axis=0) < 0):
raise ValueError('x should increase monotonically')
if np.any(np.diff(_newx, axis=0) < 0):
raise ValueError('newx should increase monotonically')
# Cubic interpolation needs the gradient of y in addition to its values
if method == 'cubic':
# For now, simply use a numpy function to get the derivatives
# This produces the largest memory overhead of the function and
# could alternatively be done in passing.
ydx = np.gradient(_y, axis=0, edge_order=2)
# This will later be concatenated with a dynamic '0th' index
ind = [i for i in np.indices(_y.shape[1:])]
# Allocate the output array
original_dims = _y.shape
newdims = list(original_dims)
newdims[0] = len(_newx)
newy = np.zeros(newdims)
# set initial bounds
i_lower = np.zeros(_x.shape[1:], dtype=int)
i_upper = np.ones(_x.shape[1:], dtype=int)
x_lower = _x[0, ...]
x_upper = _x[1, ...]
for i, xi in enumerate(_newx):
# Start at the 'bottom' of the array and work upwards
# This only works if x and newx increase monotonically
# Update bounds where necessary and possible
needs_update = (xi > x_upper) & (i_upper+1<len(_x))
# print x_upper.max(), np.any(needs_update)
while np.any(needs_update):
i_lower = np.where(needs_update, i_lower+1, i_lower)
i_upper = i_lower + 1
x_lower = _x[[i_lower]+ind]
x_upper = _x[[i_upper]+ind]
# Check again
needs_update = (xi > x_upper) & (i_upper+1<len(_x))
# Express the position of xi relative to its neighbours
xj = (xi-x_lower)/(x_upper - x_lower)
# Determine where there is a valid interpolation range
within_bounds = (_x[0, ...] < xi) & (xi < _x[-1, ...])
if method == 'linear':
f0, f1 = _y[[i_lower]+ind], _y[[i_upper]+ind]
a = f1 - f0
b = f0
newy[i, ...] = np.where(within_bounds, a*xj+b, np.nan)
elif method=='cubic':
f0, f1 = _y[[i_lower]+ind], _y[[i_upper]+ind]
df0, df1 = ydx[[i_lower]+ind], ydx[[i_upper]+ind]
a = 2*f0 - 2*f1 + df0 + df1
b = -3*f0 + 3*f1 - 2*df0 - df1
c = df0
d = f0
newy[i, ...] = np.where(within_bounds, a*xj**3 + b*xj**2 + c*xj + d, np.nan)
else:
raise ValueError("invalid interpolation method"
"(choose 'linear' or 'cubic')")
if inverse:
newy = newy[::-1, ...]
return np.moveaxis(newy, 0, axis)
And this is a small example to test it:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d as scipy1d
# toy coordinates and data
nx, ny, nz = 25, 30, 10
x = np.arange(nx)
y = np.arange(ny)
z = np.tile(np.arange(nz), (nx,ny,1)) + np.random.randn(nx, ny, nz)*.1
testdata = np.random.randn(nx,ny,nz) # x,y,z
# Desired z-coordinates (must be between bounds of z)
znew = np.tile(np.linspace(2,nz-2,50), (nx,ny,1)) + np.random.randn(nx, ny, 50)*0.01
# Inverse the coordinates for testing
z = z[..., ::-1]
znew = znew[..., ::-1]
# Now use own routine
ynew = interp_along_axis(testdata, z, znew, axis=2, inverse=True)
# Check some random profiles
for i in range(5):
randx = np.random.randint(nx)
randy = np.random.randint(ny)
checkfunc = scipy1d(z[randx, randy], testdata[randx,randy], kind='cubic')
checkdata = checkfunc(znew)
fig, ax = plt.subplots()
ax.plot(testdata[randx, randy], z[randx, randy], 'x', label='original data')
ax.plot(checkdata[randx, randy], znew[randx, randy], label='scipy')
ax.plot(ynew[randx, randy], znew[randx, randy], '--', label='Peter')
ax.legend()
plt.show()
Following the criteria of numpy.interp, one can assign the left/right bounds to the points outside the range adding this lines after within_bounds = ...
out_lbound = (xi <= _x[0,...])
out_rbound = (_x[-1,...] <= xi)
and
newy[i, out_lbound] = _y[0, out_lbound]
newy[i, out_rbound] = _y[-1, out_rbound]
after newy[i, ...] = ....
If I understood well the strategy used by #Peter9192, I think the changes are in the same line. I've checked a little bit, but maybe some strange case could not work properly.