python matplotlib correcting X and Y of np.meshgrid - python

I want to plot a function with 2 variables to a 2D plain. I also want some trial points in it. However, the result I get is not what I want. The X and Y values range from 0 to 40 (I wanted them to be between -1.5 and 2) and completely mismatch the function's actual values (with x = 15 and y = 15 it is definitely not 0).
However, the color map and the contour lines with numbers seem correct when I compare this to a top-down view of a 3D plot (it actually is between -1.5 and 2)
Lastly, the trail points (red dots in the first picture) are plotted according to the wrong X and Y values and are in the wrong place. How do I fix these problems? I am quite new to Numpy and matplotlib and I am guessing, that I used either np.arange or np.meshgrid wrong. I would like to either have a 2D image with the correct X and Y axis and trial points or a 3D plot with at least trial points that are always visible( the way I tried it, they seemed to be drawn before the 3D plot and would not be visible most of the time, having contour lines would be perfect). There is code I use to do calculations and plotting
import numpy as np
import matplotlib.pyplot as plt
def func(x: float, y: float) -> float:
return -( x * y * (1 - x - y) ) / 8
def partial_x_der(x: float, y: float) -> float:
return (-y * (-2 * x - y + 1)) / 8
def partial_y_der(x: float, y: float) -> float:
return (-x * (1 - x - 2 * y) ) / 8
def gradient(x: float, y: float):
return (partial_x_der(x, y), partial_y_der(x, y))
def tuple_minus(my_tuple1: tuple, my_tuple2: tuple) -> tuple:
return (my_tuple1[0] - my_tuple2[0], my_tuple1[1] - my_tuple2[1])
def tuple_multi_val(my_tuple: tuple, value: float) -> tuple:
return (my_tuple[0] * value, my_tuple[1] * value)
def gradiant_descent(start_point: tuple, learning_rate: float, epsilon: float) -> list:
max_iterations = 1000
cur_point = start_point
result_points = list()
for iter in range(max_iterations):
next_point = tuple_multi_val(gradient(cur_point[0], cur_point[1]), learning_rate)
if np.abs(next_point[0]) < epsilon and np.abs(next_point[1]) < epsilon:
print(f"With learning rate {learning_rate} minimum found at ({cur_point[0] - next_point[0]};{cur_point[1] - next_point[1]}) with value of {func(cur_point[0], cur_point[1])}, iterations needed: {iter}")
result_points.append((cur_point[0], cur_point[1], func(cur_point[0], cur_point[1])))
return result_points
cur_point = tuple_minus(cur_point, next_point)
if iter % 10 == 0:
result_points.append((cur_point[0], cur_point[1], func(cur_point[0], cur_point[1])))
print(f"Max iteraration count of {max_iterations} reached, Minimum value found at ({cur_point[0]};{cur_point[1]}) ")
result_points.append((cur_point[0], cur_point[1], func(cur_point[0], cur_point[1])))
return result_points
def show_gradient_descend(points_to_show: list):
x = np.arange(-1.5,2,0.1)
y = np.arange(-1.5,2,0.1)
X,Y = np.meshgrid(x, y)
Z = func(X, Y)
# 2D plot
im = plt.imshow(Z, 'GnBu', origin='lower') # drawing the function
# adding the Contour lines with labels
plt.cset = plt.contour(Z, np.arange(-0.5, 1, 0.1), linewidths=1, cmap=plt.cm.Set2)
plt.clabel(plt.cset, inline=True, fmt='%1.1f', fontsize=8)
plt.colorbar(im) # adding the colobar on the right
for point in points_to_show:
plt.scatter(point[0], point[1], color='r', marker='o')
# 3D plot
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='GnBu',linewidth=0, antialiased=False)
# title
plt.title('$z= -(x*y*(1-x-y)) / 8$')
plt.xlabel("X")
plt.ylabel("Y")
plt.show()
def main():
x0 = (0, 0)
x1 = (1, 1)
xm = (5/10, 9/10)
epsilon = 0.0001
points = [x0, x1, xm]
#for learning_rate in np.linspace(0,1,101):
# gradiant_descent(xm, learning_rate, epsilon)
for point in points:
points_to_show = gradiant_descent(point, 0.42, 0.0001)
show_gradient_descend(points_to_show)
if __name__ == "__main__":
main()
Thanks for any advice or help in advance.

Related

Modify surface code to solve for 4 dimensions instead of 3 [edited]

I found this great question with some concise code that, with a couple of tweaks, fits a 3D polynomial surface onto a set of points of in space.
Python 3D polynomial surface fit, order dependent
My version is below.
Ultimately, I've realized that I need to fit a surface over time, i.e. I need to solve for a 4 dimensional surface, and I've struggled with it.
I came up with a very hacky and computationally intensive work-around. I create a surface for each time interval. Then I create a grid of points and find the Z value for each point on each surface. So now I have a bunch of x,y points and each one has a list of z values that need to flow smoothly from one interval to the next. So we do a regression on the z values. Now that the z-flow is smooth, I refit a surface for each time interval based on the x,y points and whatever their smoothed Z value is for the relevant time interval.
Its what it sounds like. Clunky and suboptimal. The resulting surfaces flow more smoothly and still perform okay but there's gotta be a way to cut out the middle man and solve for that 4th dimension directly in the fitSurface function.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import itertools
# Start black magic
def xy_powers(order):
powers = itertools.product(range(order + 1), range(order + 1))
return [tup for tup in powers if sum(tup) <= order]
def fitSurface(x, y, z, order):
ncols = (order + 1)**2
G = np.zeros((x.size, ncols))
ij = xy_powers(order)
for k, (i,j) in enumerate(ij):
G[:,k] = x**i * y**j
m, _, _, _ = np.linalg.lstsq(G, z, rcond=None)
return m
def getZValuesForXYInputs(surface, order, x, y):
order = int(np.sqrt(len(surface))) - 1
ij = xy_powers(order)
z = np.zeros_like(x)
for a, (i,j) in zip(surface, ij):
z += a * x**i * y**j
return z
# End black magic
def showRender_3D(x_raw, y_raw, z_raw, xx, yy, zz):
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(x_raw, y_raw, z_raw, color='red', zorder=0)
ax.plot_surface(xx, yy, zz, zorder=10, alpha=0.4)
ax.set_xlabel('X data')
ax.set_ylabel('Y data')
ax.set_zlabel('Z data')
plt.show()
def main(order):
# Make generic data
numdata = 100
x = np.random.random(numdata)
y = np.random.random(numdata)
z = x**2 + y**2 + 3*x**3 + y + np.random.random(numdata)
t = np.random.randint(1, 4, numdata) # Break the data into
# Fit the surface
m = fitSurface(x, y, z, order)
# Sample the surface at regular points so we can more easily plot the surface
nx, ny = 40, 40
xx, yy = np.meshgrid(np.linspace(x.min(), x.max(), nx),
np.linspace(y.min(), y.max(), ny))
zz = getZValuesForXYInputs(m, order, xx, yy)
# Plot it
showRender_3D(x, y, z, xx, yy, zz)
orderForSurfaceFit = 3
main(orderForSurfaceFit)
Alright so I think I got this dialed in. I wont go over the how, other than to say that once you study the code enough the black magic doesn't go away but patterns do emerge. I just extended those patterns and it looks like it works.
End result
Admittedly this is so low res that it look like its not changing from C=1 to C=2 but it is. Load it up and you'll see. The gif should show the flow more cleary now.
First the methodology behind the proof. I found a funky surface equation and added a third input variable C (in-effect creating a 4D surface), then studied the surface shape with fixed C values using the original 3D fitter/renderer as a point of trusted reference.
When C is 1, you get a half pipe from hell. A slightly lopsided downsloping halfpipe.
Whence C is 2, you get much the same but the lopsidedness is even more exaggerated.
When C is 3, you get a very different shape. Like the exaggerated half pipe from above was cut in half, reversed, and glued back together.
When you run the below code, you get a 3D render with a slider that allows you to flow through the C values from 1 to 3. The values at 1, 2, and 3 look like solid matches to the references. I also added a randomizer to the data to see how it would perform at approximating a surface from imperfect noisy data and I like what I see there too.
Props to the below questions for their code and ideas.
Python 3D polynomial surface fit, order dependent
python visualize 4d data with surface plot and slider for 4th variable
import itertools
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.widgets import Slider
class Surface4D:
def __init__(self, order, a, b, c, z):
# Setting initial attributes
self.order = order
self.a = a
self.b = b
self.c = c
self.z = z
# Setting surface attributes
self.surface = self._fit_surface()
self.aa = None
self.bb = None
self._sample_surface_grid()
# Setting graph attributes
self.surface_render = None
self.axis_3d = None
# Start black magic math
def _abc_powers(self):
powers = itertools.product(range(self.order + 1), range(self.order + 1), range(self.order + 1))
return [tup for tup in powers if sum(tup) <= self.order]
def _fit_surface(self):
ncols = (self.order + 1)**3
G = np.zeros((self.a.size, ncols))
ijk = self._abc_powers()
for idx, (i,j,k) in enumerate(ijk):
G[:,idx] = self.a**i * self.b**j * self.c**k
m, _, _, _ = np.linalg.lstsq(G, self.z, rcond=None)
return m
def get_z_values(self, a, b, c):
ijk = self._abc_powers()
z = np.zeros_like(a)
for s, (i,j,k) in zip(self.surface, ijk):
z += s * a**i * b**j * c**k
return z
# End black magic math
def render_4d_flow(self):
# Set up the layout of the graph
fig = plt.figure()
self.axis_3d = Axes3D(fig, rect=[0.1,0.2,0.8,0.7])
slider_ax = fig.add_axes([0.1,0.1,0.8,0.05])
self.axis_3d.set_xlabel('X data')
self.axis_3d.set_ylabel('Y data')
self.axis_3d.set_zlabel('Z data')
# Plot the point cloud and initial surface
self.axis_3d.scatter(self.a, self.b, self.z, color='red', zorder=0)
zz = self.get_z_values(self.aa, self.bb, 1)
self.surface_render = self.axis_3d.plot_surface(self.aa, self.bb, zz, zorder=10, alpha=0.4, color="b")
# Setup the slider behavior
slider_start_step = self.c.min()
slider_max_steps = self.c.max()
slider = Slider(slider_ax, 'time', slider_start_step, slider_max_steps , valinit=slider_start_step)
slider.on_changed(self._update)
plt.show()
input("Once youre done, hit any enter to continue.")
def _update(self, val):
self.surface_render.remove()
zz = self.get_z_values(self.aa, self.bb, val)
self.surface_render = self.axis_3d.plot_surface(self.aa, self.bb, zz, zorder=10, alpha=0.4, color="b")
def _sample_surface_grid(self):
na, nb = 40, 40
aa, bb = np.meshgrid(np.linspace(self.a.min(), self.a.max(), na),
np.linspace(self.b.min(), self.b.max(), nb))
self.aa = aa
self.bb = bb
def noisify_array(one_dim_array, randomness_multiplier):
listOfNewValues = []
range = abs(one_dim_array.min()-one_dim_array.max())
for item in one_dim_array:
# What percentage are we shifting the point dimension by
shift = np.random.randint(0, 101)
shiftPercent = shift/100
shiftPercent = shiftPercent * randomness_multiplier
# Is that shift positive or negative
shiftSignIndex = np.random.randint(0, 2)
shifSignOption = [-1, 1]
shiftSign = shifSignOption[shiftSignIndex]
# Shift it
newNoisyPosition = item + (range * (shiftPercent * shiftSign))
listOfNewValues.append(newNoisyPosition)
return np.array(listOfNewValues)
def generate_data():
# Define our range for each dimension
x = np.linspace(-6, 6, 20)
y = np.linspace(-6, 6, 20)
w = [1, 2, 3]
# Populate each dimension
a,b,c,z = [],[],[],[]
for X in x:
for Y in y:
for W in w:
a.append(X)
b.append(Y)
c.append(W)
z.append((1 * X ** 4) + (2 * Y ** 3) + (X * Y ** W) + (4 * X) + (5 * Y))
# Convert them to arrays
a = np.array(a)
b = np.array(b)
c = np.array(c)
z = np.array(z)
return [a, b, c, z]
def main(order):
# Make the data
a,b,c,z = generate_data()
# Show the pure data and best fit
surface_pure_data = Surface4D(order, a, b, c, z)
surface_pure_data.render_4d_flow()
# Add some noise to the data
a = noisify_array(a, 0.10)
b = noisify_array(b, 0.10)
c = noisify_array(c, 0.10)
z = noisify_array(z, 0.10)
# Show the noisy data and best fit
surface_noisy_data = Surface4D(order, a, b, c, z)
surface_noisy_data.render_4d_flow()
# ----------------------------------------------------------------
orderForSurfaceFit = 5
main(orderForSurfaceFit)
[Edit 1: I've started to incorporate this code into my real projects and I found some tweaks to make things ore sensible. Also there's a scope problem where the runtime needs to be paused while it's still in the scope of the render_4d_flow function in order for the slider to work.]
[Edit 2: Higher resolution gif that shows the flow from c=2 to c=3]

Interpolating non-uniformly distributed points on a 3D sphere

I have several points on the unit sphere that are distributed according to the algorithm described in https://www.cmu.edu/biolphys/deserno/pdf/sphere_equi.pdf (and implemented in the code below). On each of these points, I have a value that in my particular case represents 1 minus a small error. The errors are in [0, 0.1] if this is important, so my values are in [0.9, 1].
Sadly, computing the errors is a costly process and I cannot do this for as many points as I want. Still, I want my plots to look like I am plotting something "continuous".
So I want to fit an interpolation function to my data, to be able to sample as many points as I want.
After a little bit of research I found scipy.interpolate.SmoothSphereBivariateSpline which seems to do exactly what I want. But I cannot make it work properly.
Question: what can I use to interpolate (spline, linear interpolation, anything would be fine for the moment) my data on the unit sphere? An answer can be either "you misused scipy.interpolation, here is the correct way to do this" or "this other function is better suited to your problem".
Sample code that should be executable with numpy and scipy installed:
import typing as ty
import numpy
import scipy.interpolate
def get_equidistant_points(N: int) -> ty.List[numpy.ndarray]:
"""Generate approximately n points evenly distributed accros the 3-d sphere.
This function tries to find approximately n points (might be a little less
or more) that are evenly distributed accros the 3-dimensional unit sphere.
The algorithm used is described in
https://www.cmu.edu/biolphys/deserno/pdf/sphere_equi.pdf.
"""
# Unit sphere
r = 1
points: ty.List[numpy.ndarray] = list()
a = 4 * numpy.pi * r ** 2 / N
d = numpy.sqrt(a)
m_v = int(numpy.round(numpy.pi / d))
d_v = numpy.pi / m_v
d_phi = a / d_v
for m in range(m_v):
v = numpy.pi * (m + 0.5) / m_v
m_phi = int(numpy.round(2 * numpy.pi * numpy.sin(v) / d_phi))
for n in range(m_phi):
phi = 2 * numpy.pi * n / m_phi
points.append(
numpy.array(
[
numpy.sin(v) * numpy.cos(phi),
numpy.sin(v) * numpy.sin(phi),
numpy.cos(v),
]
)
)
return points
def cartesian2spherical(x: float, y: float, z: float) -> numpy.ndarray:
r = numpy.linalg.norm([x, y, z])
theta = numpy.arccos(z / r)
phi = numpy.arctan2(y, x)
return numpy.array([r, theta, phi])
n = 100
points = get_equidistant_points(n)
# Random here, but costly in real life.
errors = numpy.random.rand(len(points)) / 10
# Change everything to spherical to use the interpolator from scipy.
ideal_spherical_points = numpy.array([cartesian2spherical(*point) for point in points])
r_interp = 1 - errors
theta_interp = ideal_spherical_points[:, 1]
phi_interp = ideal_spherical_points[:, 2]
# Change phi coordinate from [-pi, pi] to [0, 2pi] to please scipy.
phi_interp[phi_interp < 0] += 2 * numpy.pi
# Create the interpolator.
interpolator = scipy.interpolate.SmoothSphereBivariateSpline(
theta_interp, phi_interp, r_interp
)
# Creating the finer theta and phi values for the final plot
theta = numpy.linspace(0, numpy.pi, 100, endpoint=True)
phi = numpy.linspace(0, numpy.pi * 2, 100, endpoint=True)
# Creating the coordinate grid for the unit sphere.
X = numpy.outer(numpy.sin(theta), numpy.cos(phi))
Y = numpy.outer(numpy.sin(theta), numpy.sin(phi))
Z = numpy.outer(numpy.cos(theta), numpy.ones(100))
thetas, phis = numpy.meshgrid(theta, phi)
heatmap = interpolator(thetas, phis)
Issue with the code above:
With the code as-is, I have a
ValueError: The required storage space exceeds the available storage space: nxest or nyest too small, or s too small. The weighted least-squares spline corresponds to the current set of knots.
that is raised when initialising the interpolator instance.
The issue above seems to say that I should change the value of s that is one on the parameters of scipy.interpolate.SmoothSphereBivariateSpline. I tested different values of s ranging from 0.0001 to 100000, the code above always raise, either the exception described above or:
ValueError: Error code returned by bispev: 10
Edit: I am including my findings here. They can't really be considered as a solution, that is why I am editing and not posting as an answer.
With more research I found this question Using Radial Basis Functions to Interpolate a Function on a Sphere. The author has exactly the same problem as me and use a different interpolator: scipy.interpolate.Rbf. I changed the above code by replacing the interpolator and plotting:
# Create the interpolator.
interpolator = scipy.interpolate.Rbf(theta_interp, phi_interp, r_interp)
# Creating the finer theta and phi values for the final plot
plot_points = 100
theta = numpy.linspace(0, numpy.pi, plot_points, endpoint=True)
phi = numpy.linspace(0, numpy.pi * 2, plot_points, endpoint=True)
# Creating the coordinate grid for the unit sphere.
X = numpy.outer(numpy.sin(theta), numpy.cos(phi))
Y = numpy.outer(numpy.sin(theta), numpy.sin(phi))
Z = numpy.outer(numpy.cos(theta), numpy.ones(plot_points))
thetas, phis = numpy.meshgrid(theta, phi)
heatmap = interpolator(thetas, phis)
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib import cm
colormap = cm.inferno
normaliser = mpl.colors.Normalize(vmin=numpy.min(heatmap), vmax=1)
scalar_mappable = cm.ScalarMappable(cmap=colormap, norm=normaliser)
scalar_mappable.set_array([])
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.plot_surface(
X,
Y,
Z,
facecolors=colormap(normaliser(heatmap)),
alpha=0.7,
cmap=colormap,
)
plt.colorbar(scalar_mappable)
plt.show()
This code runs smoothly and gives the following result:
The interpolation seems OK except on one line that is discontinuous, just like in the question that led me to this class. One of the answer give the idea of using a different distance, more adapted the the spherical coordinates: the Haversine distance.
def haversine(x1, x2):
theta1, phi1 = x1
theta2, phi2 = x2
return 2 * numpy.arcsin(
numpy.sqrt(
numpy.sin((theta2 - theta1) / 2) ** 2
+ numpy.cos(theta1) * numpy.cos(theta2) * numpy.sin((phi2 - phi1) / 2) ** 2
)
)
# Create the interpolator.
interpolator = scipy.interpolate.Rbf(theta_interp, phi_interp, r_interp, norm=haversine)
which, when executed, gives a warning:
LinAlgWarning: Ill-conditioned matrix (rcond=1.33262e-19): result may not be accurate.
self.nodes = linalg.solve(self.A, self.di)
and a result that is not at all the one expected: the interpolated function have values that may go up to -1 which is clearly wrong.
You can use Cartesian coordinate instead of Spherical coordinate.
The default norm parameter ('euclidean') used by Rbf is sufficient
# interpolation
x, y, z = numpy.array(points).T
interpolator = scipy.interpolate.Rbf(x, y, z, r_interp)
# predict
heatmap = interpolator(X, Y, Z)
Here the result:
ax.plot_surface(
X, Y, Z,
rstride=1, cstride=1,
# or rcount=50, ccount=50,
facecolors=colormap(normaliser(heatmap)),
cmap=colormap,
alpha=0.7, shade=False
)
ax.set_xlabel('x axis')
ax.set_ylabel('y axis')
ax.set_zlabel('z axis')
You can also use a cosine distance if you want (norm parameter):
def cosine(XA, XB):
if XA.ndim == 1:
XA = numpy.expand_dims(XA, axis=0)
if XB.ndim == 1:
XB = numpy.expand_dims(XB, axis=0)
return scipy.spatial.distance.cosine(XA, XB)
In order to better see the differences,
I stacked the two images, substracted them and inverted the layer.

Problems using curve_fit for multivariate gaussian fit

I am so stuck trying to fit 3D gaussians, and I am hoping someone can see some silly mistake I am making, because I have spent hours debugging to no avail.
I have a 3d image stored in an array called "data", where data[x, y, z] gives the grayscale intensity at the point (x, y, z). I know that this 3d image follows a 3D Gaussian distribution, with a peak near the center of the image, but I am interested in the amplitude and spread. I am trying to fit this 3d array to a gaussian of the form My function in Python is:
def gaussian_3d(X, A, x0, y0, z0, sigx, sigy, sigz, offset):
x, y, z = X
return offset + A*np.exp(-(x-x0)**2/(2*sigx**2) - \
(y-y0)**2/(2*sigy**2) - (z-z0)**2/(2*sigz**2))
And the way I am doing this is as follows: if my image is of size 3 x 4 x 5, then I create a meshgrid (0...2) x (0...3) x (0...4), and then try to fit the intensity values to the function above.
My code looks like this:
def fit_gauss_3d(data):
dim = data.shape
# Step 1: set up meshgrid
x, y, z = np.arange(0, dim[0]), np.arange(0, dim[1]), np.arange(0, dim[2])
X, Y, Z = np.meshgrid(x, y, z)
data_in = np.vstack((X.ravel(),Y.ravel(),Z.ravel()))
data_out = data.ravel()
# Step 2: make good guess of the center "peak" point of the gaussian (x0, y0, z0)
# by using slices along the middle and finding the position of the maxes
mid1, mid2, mid3 = dim[0]//2, dim[1]//2, dim[2]//2
x0, y0, z0 = np.argmax(data[:, mid2, mid3]), np.argmax(data[mid1, :, mid3]), np.argmax(data[mid1, mid2, :])
# Step 3: Set lower/upper bounds for parameter search
delta = 0.5 # I am saying that the fit peak must be within +/- 0.5 of the initial guess
p0 = (data_max + 0.05, x0, y0, z0, 0.9, 0.9, 0.9, 0.05)
# Note: I know that sigmas are between 0.7 and 2.5, and offset is between 0 and 5
lower_bound = [data_max * 0.9, x0 - delta, y0 - delta, z0 - delta, 0.7, 0.7, 0.7, 0]
upper_bound = [data_max*1.1 + 0.1, x0 + delta, y0 + delta, z0 + delta, 2.5, 2.5, 2.5, 5]
# Step 4: Fit
p_param, p_cov = opt.curve_fit(gauss_3d, data_in, data_out, p0=p0, maxfev=50000, bounds=(lower_bound, upper_bound))
return p_param
def predict_gauss_3d(params, dims):
x = np.arange(0, dims[0])
y = np.arange(0, dims[1])
z = np.arange(0, dims[2])
XX, YY, ZZ = np.meshgrid(x, y, z)
X = np.vstack((XX.ravel(),YY.ravel(),ZZ.ravel()))
return gaussian_3d(X, *params).reshape(dims)
def plot_results(orig, sec):
''' Plot original and second fitted image'''
mid1, mid2, mid3 = dim[0]//2, dim[1]//2, dim[2]//2
fig = plt.figure()
ax1 = fig.add_subplot(3, 1, 1)
ax1.plot(orig[:, mid2, mid3], label='orig')
ax1.plot(sec[:, mid2, mid3], label='fitted')
ax1.legend(loc="upper left")
ax2 = fig.add_subplot(3, 1, 2)
ax2.plot(orig[mid1, :, mid3], label='orig')
ax2.plot(sec[mid1, :, mid3], label='fitted')
ax2.legend(loc="upper left")
ax3 = fig.add_subplot(3, 1, 3)
ax3.plot(orig[mid1, mid2], label='orig')
ax3.plot(sec[mid1, mid2], label='fitted')
ax3.legend(loc="upper left")
plt.tight_layout()
plt.show()
I plotted the projection of the fits along the middle axes. The first pic is varying x and keeping y, z at their midpoints, the second is varying y and keeping x, z at their midpoints, and so forth.
Some of my fits are reasonable, something like this:
While most are insanely bad, and not even Gaussian looking! For the below image, it chose the following parameters: . Clearly, I am either plotting wrong or fitting wrong. Can someone help me out? Is my meshgridding messed up somehow?

Python: How to interpolate angles on a grid?

I have a grid with some given data. This data is given by its angle (from 0 to π).
Within this grid I have another smaller grid.
This might look like this:
Now I want to interpolate the angles on that grid.
I tried this by using scipy.interpolate.griddata what gives a good result. But there is a problem when the angles change from almost 0 to almost π (because the middle is π/2 ...)
Here is the result and it is easy to see what's going wrong.
How can I deal with that problem? Thank you! :)
Here is the code to reproduce:
import numpy as np
from matplotlib import pyplot as plt
from scipy.interpolate import griddata
ax = plt.subplot()
ax.set_aspect(1)
# Simulate some given data.
x, y = np.meshgrid(np.linspace(-10, 10, 20), np.linspace(-10, 10, 20))
data = np.arctan(y / 10) % np.pi
u = np.cos(data)
v = np.sin(data)
ax.quiver(x, y, u, v, headlength=0.01, headaxislength=0, pivot='middle', units='xy')
# Create a smaller grid within.
x1, y1 = np.meshgrid(np.linspace(-1, 5, 15), np.linspace(-6, 2, 20))
# ax.plot(x1, y1, '.', color='red', markersize=2)
# Interpolate data on grid.
interpolation = griddata((x.flatten(), y.flatten()), data.flatten(), (x1.flatten(), y1.flatten()))
u1 = np.cos(interpolation)
v1 = np.sin(interpolation)
ax.quiver(x1, y1, u1, v1, headlength=0.01, headaxislength=0, pivot='middle', units='xy',
color='red', scale=3, width=0.03)
plt.show()
Edit:
Thanks to #bubble, there is a way to adjust the given angles before interpolation such that the result will be as desired.
Therefore:
Define a rectifying function:
def RectifyData(data):
for j in range(len(data)):
step = data[j] - data[j - 1]
if abs(step) > np.pi / 2:
data[j] += np.pi * (2 * (step < 0) - 1)
return data
Interpolate as follows:
interpolation = griddata((x.flatten(), y.flatten()),
RectifyData(data.flatten()),
(x1.flatten(), y1.flatten()))
u1 = np.cos(interpolation)
v1 = np.sin(interpolation)
I tried direct interpolation of cos(angle) and sin(angle) values, but this still yielded to discontinues, that cause wrong line directions. The main idea consist in reducing discontinues, e.g. [2.99,3.01, 0.05,0.06] should be transformed to something like this: [2.99, 3.01, pi+0.05, pi+0.06]. This is needed to apply 2D interpolation algorithm correctly. Almost the same problem raises in the following post.
def get_rectified_angles(u, v):
angles = np.arcsin(v)
inds = u < 0
angles[inds] *= -1
# Direct approach of removing discontinues
# for j in range(len(angles[1:])):
# if abs(angles[j] - angles[j - 1]) > np.pi / 2:
# sel = [abs(angles[j] + np.pi - angles[j - 1]), abs(angles[j] - np.pi - angles[j-1])]
# if np.argmin(sel) == 0:
# angles[j] += np.pi
# else:
# angles[j] -= np.pi
return angles
ax.quiver(x, y, u, v, headlength=0.01, headaxislength=0, pivot='middle', units='xy')
# # Create a smaller grid within.
x1, y1 = np.meshgrid(np.linspace(-1, 5, 15), np.linspace(-6, 2, 20))
angles = get_rectified_angles(u.flatten(), v.flatten())
interpolation = griddata((x.flatten(), y.flatten()), angles, (x1.flatten(), y1.flatten()))
u1 = np.cos(interpolation)
v1 = np.sin(interpolation)
ax.quiver(x1, y1, u1, v1, headlength=0.01, headaxislength=0, pivot='middle', units='xy',
color='red', scale=3, width=0.03)
Probably, numpy.unwrap function could be used to fix discontinues. In case of 1d data, numpy.interp has keyword period to handle periodic data.

draw a smooth polygon around data points in a scatter plot, in matplotlib

I have a bunch of cross plots with two sets of data and have been looking for a matploltib way of highlighting their plotted regions with smoothed polygon outlines.
At the moment i just use Adobe Illustrator and amend saved plot, but this is not ideal. Example:
I'd be grateful for any pointers/links to examples.
Cheers
Here, you have an example. I was written the main ideas, but obviously, you could do it better.
A short explanations:
1) You need to compute the convex-hull (http://en.wikipedia.org/wiki/Convex_hull)
2) With the hull, you could scale it to keep all your data inside.
3) You must to interpolate the resulting curve.
The first part was done in http://wiki.scipy.org/Cookbook/Finding_Convex_Hull. The second one is trivial. The third one is very general, and you could perform any method, there are a lot of different ways to do the same. I took the #Jaime's approach (Smooth spline representation of an arbitrary contour, f(length) --> x,y), which I think it's a very good method.
I hope it help you...
#Taken from http://wiki.scipy.org/Cookbook/Finding_Convex_Hull
import numpy as n, pylab as p, time
def _angle_to_point(point, centre):
'''calculate angle in 2-D between points and x axis'''
delta = point - centre
res = n.arctan(delta[1] / delta[0])
if delta[0] < 0:
res += n.pi
return res
def _draw_triangle(p1, p2, p3, **kwargs):
tmp = n.vstack((p1,p2,p3))
x,y = [x[0] for x in zip(tmp.transpose())]
p.fill(x,y, **kwargs)
def area_of_triangle(p1, p2, p3):
'''calculate area of any triangle given co-ordinates of the corners'''
return n.linalg.norm(n.cross((p2 - p1), (p3 - p1)))/2.
def convex_hull(points, graphic=False, smidgen=0.0075):
'''
Calculate subset of points that make a convex hull around points
Recursively eliminates points that lie inside two neighbouring points until only convex hull is remaining.
:Parameters:
points : ndarray (2 x m)
array of points for which to find hull
graphic : bool
use pylab to show progress?
smidgen : float
offset for graphic number labels - useful values depend on your data range
:Returns:
hull_points : ndarray (2 x n)
convex hull surrounding points
'''
if graphic:
p.clf()
p.plot(points[0], points[1], 'ro')
n_pts = points.shape[1]
assert(n_pts > 5)
centre = points.mean(1)
if graphic: p.plot((centre[0],),(centre[1],),'bo')
angles = n.apply_along_axis(_angle_to_point, 0, points, centre)
pts_ord = points[:,angles.argsort()]
if graphic:
for i in xrange(n_pts):
p.text(pts_ord[0,i] + smidgen, pts_ord[1,i] + smidgen, \
'%d' % i)
pts = [x[0] for x in zip(pts_ord.transpose())]
prev_pts = len(pts) + 1
k = 0
while prev_pts > n_pts:
prev_pts = n_pts
n_pts = len(pts)
if graphic: p.gca().patches = []
i = -2
while i < (n_pts - 2):
Aij = area_of_triangle(centre, pts[i], pts[(i + 1) % n_pts])
Ajk = area_of_triangle(centre, pts[(i + 1) % n_pts], \
pts[(i + 2) % n_pts])
Aik = area_of_triangle(centre, pts[i], pts[(i + 2) % n_pts])
if graphic:
_draw_triangle(centre, pts[i], pts[(i + 1) % n_pts], \
facecolor='blue', alpha = 0.2)
_draw_triangle(centre, pts[(i + 1) % n_pts], \
pts[(i + 2) % n_pts], \
facecolor='green', alpha = 0.2)
_draw_triangle(centre, pts[i], pts[(i + 2) % n_pts], \
facecolor='red', alpha = 0.2)
if Aij + Ajk < Aik:
if graphic: p.plot((pts[i + 1][0],),(pts[i + 1][1],),'go')
del pts[i+1]
i += 1
n_pts = len(pts)
k += 1
return n.asarray(pts)
if __name__ == "__main__":
import scipy.interpolate as interpolate
# fig = p.figure(figsize=(10,10))
theta = 2*n.pi*n.random.rand(1000)
r = n.random.rand(1000)**0.5
x,y = r*p.cos(theta),r*p.sin(theta)
points = n.ndarray((2,len(x)))
points[0,:],points[1,:] = x,y
scale = 1.03
hull_pts = scale*convex_hull(points)
p.plot(x,y,'ko')
x,y = [],[]
convex = scale*hull_pts
for point in convex:
x.append(point[0])
y.append(point[1])
x.append(convex[0][0])
y.append(convex[0][1])
x,y = n.array(x),n.array(y)
#Taken from https://stackoverflow.com/questions/14344099/numpy-scipy-smooth-spline-representation-of-an-arbitrary-contour-flength
nt = n.linspace(0, 1, 100)
t = n.zeros(x.shape)
t[1:] = n.sqrt((x[1:] - x[:-1])**2 + (y[1:] - y[:-1])**2)
t = n.cumsum(t)
t /= t[-1]
x2 = interpolate.spline(t, x, nt)
y2 = interpolate.spline(t, y, nt)
p.plot(x2, y2,'r--',linewidth=2)
p.show()
There are some useful papers, eg.:
http://repositorium.sdum.uminho.pt/bitstream/1822/6429/1/ConcaveHull_ACM_MYS.pdf
Also, you could try with: http://resources.arcgis.com/en/help/main/10.1/index.html#//007000000013000000
I don't know nothing about arcgis, but it looks fine.
I came across this and implemented easy to use functions as well as a couple of alternatives/improvements.
Improvements:
use a periodic interpolation which ensures smooth
use quadratic interpolation
now works for only positive points as well
using an alternative to the deprecated scipy.interpolate.spline function
Alternatives:
many different and configurable interpolation schemes
a rounded-corner convex hull version
Hope this helps someone along the way.
import sklearn.preprocessing
import sklearn.pipeline
import scipy.spatial
import numpy as np
def calculate_hull(
X,
scale=1.1,
padding="scale",
n_interpolate=100,
interpolation="quadratic_periodic",
return_hull_points=False):
"""
Calculates a "smooth" hull around given points in `X`.
The different settings have different drawbacks but the given defaults work reasonably well.
Parameters
----------
X : np.ndarray
2d-array with 2 columns and `n` rows
scale : float, optional
padding strength, by default 1.1
padding : str, optional
padding mode, by default "scale"
n_interpolate : int, optional
number of interpolation points, by default 100
interpolation : str or callable(ix,iy,x), optional
interpolation mode, by default "quadratic_periodic"
Inspired by: https://stackoverflow.com/a/17557853/991496
"""
if padding == "scale":
# scaling based padding
scaler = sklearn.pipeline.make_pipeline(
sklearn.preprocessing.StandardScaler(with_std=False),
sklearn.preprocessing.MinMaxScaler(feature_range=(-1,1)))
points_scaled = scaler.fit_transform(X) * scale
hull_scaled = scipy.spatial.ConvexHull(points_scaled, incremental=True)
hull_points_scaled = points_scaled[hull_scaled.vertices]
hull_points = scaler.inverse_transform(hull_points_scaled)
hull_points = np.concatenate([hull_points, hull_points[:1]])
elif padding == "extend" or isinstance(padding, (float, int)):
# extension based padding
# TODO: remove?
if padding == "extend":
add = (scale - 1) * np.max([
X[:,0].max() - X[:,0].min(),
X[:,1].max() - X[:,1].min()])
else:
add = padding
points_added = np.concatenate([
X + [0,add],
X - [0,add],
X + [add, 0],
X - [add, 0]])
hull = scipy.spatial.ConvexHull(points_added)
hull_points = points_added[hull.vertices]
hull_points = np.concatenate([hull_points, hull_points[:1]])
else:
raise ValueError(f"Unknown padding mode: {padding}")
# number of interpolated points
nt = np.linspace(0, 1, n_interpolate)
x, y = hull_points[:,0], hull_points[:,1]
# ensures the same spacing of points between all hull points
t = np.zeros(x.shape)
t[1:] = np.sqrt((x[1:] - x[:-1])**2 + (y[1:] - y[:-1])**2)
t = np.cumsum(t)
t /= t[-1]
# interpolation types
if interpolation is None or interpolation == "linear":
x2 = scipy.interpolate.interp1d(t, x, kind="linear")(nt)
y2 = scipy.interpolate.interp1d(t, y, kind="linear")(nt)
elif interpolation == "quadratic":
x2 = scipy.interpolate.interp1d(t, x, kind="quadratic")(nt)
y2 = scipy.interpolate.interp1d(t, y, kind="quadratic")(nt)
elif interpolation == "quadratic_periodic":
x2 = scipy.interpolate.splev(nt, scipy.interpolate.splrep(t, x, per=True, k=4))
y2 = scipy.interpolate.splev(nt, scipy.interpolate.splrep(t, y, per=True, k=4))
elif interpolation == "cubic":
x2 = scipy.interpolate.CubicSpline(t, x, bc_type="periodic")(nt)
y2 = scipy.interpolate.CubicSpline(t, y, bc_type="periodic")(nt)
else:
x2 = interpolation(t, x, nt)
y2 = interpolation(t, y, nt)
X_hull = np.concatenate([x2.reshape(-1,1), y2.reshape(-1,1)], axis=1)
if return_hull_points:
return X_hull, hull_points
else:
return X_hull
def draw_hull(
X,
scale=1.1,
padding="scale",
n_interpolate=100,
interpolation="quadratic_periodic",
plot_kwargs=None,
ax=None):
"""Uses `calculate_hull` to draw a hull around given points.
Parameters
----------
X : np.ndarray
2d-array with 2 columns and `n` rows
scale : float, optional
padding strength, by default 1.1
padding : str, optional
padding mode, by default "scale"
n_interpolate : int, optional
number of interpolation points, by default 100
interpolation : str or callable(ix,iy,x), optional
interpolation mode, by default "quadratic_periodic"
plot_kwargs : dict, optional
`matplotlib.pyplot.plot` kwargs, by default None
ax : `matplotlib.axes.Axes`, optional
[description], by default None
"""
if plot_kwargs is None:
plot_kwargs = {}
X_hull = calculate_hull(
X, scale=scale, padding=padding, n_interpolate=n_interpolate, interpolation=interpolation)
if ax is None:
ax= plt.gca()
plt.plot(X_hull[:,0], X_hull[:,1], **plot_kwargs)
def draw_rounded_hull(X, padding=0.1, line_kwargs=None, ax=None):
"""Plots a convex hull around points with rounded corners and a given padding.
Parameters
----------
X : np.array
2d array with two columns and n rows
padding : float, optional
padding between hull and points, by default 0.1
line_kwargs : dict, optional
line kwargs (used for `matplotlib.pyplot.plot` and `matplotlib.patches.Arc`), by default None
ax : matplotlib.axes.Axes, optional
axes to plat on, by default None
"""
default_line_kwargs = dict(
color="black",
linewidth=1
)
if line_kwargs is None:
line_kwargs = default_line_kwargs
else:
line_kwargs = {**default_line_kwargs, **line_kwargs}
if ax is None:
ax = plt.gca()
hull = scipy.spatial.ConvexHull(X)
hull_points = X[hull.vertices]
hull_points = np.concatenate([hull_points[[-1]], hull_points, hull_points[[0]]])
diameter = padding * 2
for i in range(1, hull_points.shape[0] - 1):
# line
# source: https://stackoverflow.com/a/1243676/991496
norm_next = np.flip(hull_points[i] - hull_points[i + 1]) * [-1, 1]
norm_next /= np.linalg.norm(norm_next)
norm_prev = np.flip(hull_points[i - 1] - hull_points[i]) * [-1, 1]
norm_prev /= np.linalg.norm(norm_prev)
# plot line
line = hull_points[i:i+2] + norm_next * diameter / 2
ax.plot(line[:,0], line[:,1], **line_kwargs)
# arc
angle_next = np.rad2deg(np.arccos(np.dot(norm_next, [1,0])))
if norm_next[1] < 0:
angle_next = 360 - angle_next
angle_prev = np.rad2deg(np.arccos(np.dot(norm_prev, [1,0])))
if norm_prev[1] < 0:
angle_prev = 360 - angle_prev
arc = patches.Arc(
hull_points[i],
diameter, diameter,
angle=0, fill=False, theta1=angle_prev, theta2=angle_next,
**line_kwargs)
ax.add_patch(arc)
if __name__ == '__main__':
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import patches
# np.random.seed(42)
X = np.random.random((20,2))
fig, ax = plt.subplots(1,1, figsize=(10,10))
ax.scatter(X[:,0], X[:,1])
draw_rounded_hull(X, padding=0.1)
draw_hull(X)
ax.set(xlim=[-1,2], ylim= [-1,2])
fig.savefig("_out/test.png")

Categories

Resources