I would like to plot a multivariate function f_ab using the axes3d method with a and b as "variables". For some reason, the method throws an error saying that z is not a 2d array. If I check the shape of f_ab, I get (50,50)
import numpy as np
a = np.linspace(5,10,50)
b = np.linspace(3,8,50)
f_ab = np.zeros((len(a),len(b)))
for i in range(len(a)):
for j in range(len(b)):
f_ab[i,j] = (a[i]**0.5)*(b[j])
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
ax = plt.figure().add_subplot(projection='3d')
X, Y, Z = a, b, f_ab
# Plot the 3D surface
ax.plot_surface(X, Y, Z, edgecolor='royalblue', lw=0.5, rstride=8, cstride=8,
alpha=0.3)
# Plot projections of the contours for each dimension. By choosing offsets
# that match the appropriate axes limits, the projected contours will sit on
# the 'walls' of the graph.
ax.contour(X, Y, Z, zdir='z', offset=np.min(f_ab), cmap='coolwarm')
ax.contour(X, Y, Z, zdir='x', offset=50, cmap='coolwarm')
ax.contour(X, Y, Z, zdir='y', offset=50, cmap='coolwarm')
ax.set(xlim=(0,50), ylim=(0,50), zlim=(np.min(f_ab),np.max(f_ab)),
xlabel='a', ylabel='b', zlabel='z')
The error looks like this:
File ~\AppData\Roaming\Python\Python310\site-packages\matplotlib\contour.py:1501, in QuadContourSet._check_xyz(self, args, kwargs)
1498 z = ma.asarray(args[2], dtype=np.float64)
1500 if z.ndim != 2:
-> 1501 raise TypeError(f"Input z must be 2D, not {z.ndim}D")
1502 if z.shape[0] < 2 or z.shape[1] < 2:
1503 raise TypeError(f"Input z must be at least a (2, 2) shaped array, "
1504 f"but has shape {z.shape}")
TypeError: Input z must be 2D, not 1D
I tried changing the zdir parameter in the ax.contour, but could not get the desired result.
How can I resolve the error?
Related
I am currently trying to write my own 2D Gaussian function as a coding exercise, and have been able to create the following script:
import numpy as np
import matplotlib.pyplot as plt
def Gaussian2D_v1(coords=None, # x and y coordinates for each image.
amplitude=1, # Highest intensity in image.
xo=0, # x-coordinate of peak centre.
yo=0, # y-coordinate of peak centre.
sigma_x=1, # Standard deviation in x.
sigma_y=1, # Standard deviation in y.
rho=0, # Correlation coefficient.
offset=0): # Offset from zero (background radiation).
x, y = coords
xo = float(xo)
yo = float(yo)
# Create covariance matrix
mat_cov = [[sigma_x**2, rho * sigma_x * sigma_y],
[rho * sigma_x * sigma_y, sigma_y**2]]
mat_cov = np.asarray(mat_cov)
# Find its inverse
mat_cov_inv = np.linalg.inv(mat_cov)
G_array = []
# Calculate pixel by pixel
# Iterate through row last
for i in range(0, np.shape(y)[0]):
# Iterate through column first
for j in range(0, np.shape(x)[1]):
mat_coords = np.asarray([[x[i, j]-xo],
[y[i, j]-xo]])
G = (amplitude * np.exp(-0.5*np.matmul(np.matmul(mat_coords.T,
mat_cov_inv),
mat_coords)) + offset)
G_array.append(G)
G_array = np.asarray(G_array)
G_array = G_array.reshape(64, 64)
return G_array.ravel()
coords = np.meshgrid(np.arange(0, 64), np.arange(0, 64))
model_1 = Gaussian2D_v1(coords,
amplitude=20,
xo=32,
yo=32,
sigma_x=6,
sigma_y=3,
rho=0.8,
offset=20).reshape(64, 64)
plt.figure(figsize=(5, 5)).add_axes([0,
0,
1,
1])
plt.contourf(model_1)
The code as it is works, but as you can see, I am currently iterating through the mesh grid one point at a time, and appending each point to a list, which is then converted to an array and re-shaped to give the 2D Gaussian distribution.
How can I modify the script to forgo using a nested "for" loop and have the program consider the whole meshgrid for matrix calculations? Is such a method possible?
Thanks!
Of course there is a solution, numpy is all about array operations and vectorization of the code! np.matmul can take args with more than 2 dimensions and apply the matrix multiplication on the last two axes only (and this calculation in parallel over the others axes). However, making sure of the right axes order can get tricky.
Here is your edited code:
import numpy as np
import matplotlib.pyplot as plt
def Gaussian2D_v1(coords, # x and y coordinates for each image.
amplitude=1, # Highest intensity in image.
xo=0, # x-coordinate of peak centre.
yo=0, # y-coordinate of peak centre.
sigma_x=1, # Standard deviation in x.
sigma_y=1, # Standard deviation in y.
rho=0, # Correlation coefficient.
offset=0): # Offset from zero (background radiation).
x, y = coords
xo = float(xo)
yo = float(yo)
# Create covariance matrix
mat_cov = [[sigma_x**2, rho * sigma_x * sigma_y],
[rho * sigma_x * sigma_y, sigma_y**2]]
mat_cov = np.asarray(mat_cov)
# Find its inverse
mat_cov_inv = np.linalg.inv(mat_cov)
# PB We stack the coordinates along the last axis
mat_coords = np.stack((x - xo, y - yo), axis=-1)
G = amplitude * np.exp(-0.5*np.matmul(np.matmul(mat_coords[:, :, np.newaxis, :],
mat_cov_inv),
mat_coords[..., np.newaxis])) + offset
return G.squeeze()
coords = np.meshgrid(np.arange(0, 64), np.arange(0, 64))
model_1 = Gaussian2D_v1(coords,
amplitude=20,
xo=32,
yo=32,
sigma_x=6,
sigma_y=3,
rho=0.8,
offset=20)
plt.figure(figsize=(5, 5)).add_axes([0, 0, 1, 1])
plt.contourf(model_1)
So, the equation is exp(-0.5 * (X - µ)' Cinv (X - µ) ), where X is our coordinate matrix, µ the mean (x0, y0) and Cinv the inverse covariance matrix (and ' is a transpose). In the code, I stack both meshgrids to a new matrix so that: mat_coords has a shape of (Ny, Nx, 2). In the first np.matmul call, I add a new axis so that the shapes go like :(Ny, Nx, 1, 2) * (2, 2) = (Ny, Nx, 1, 2). As you see, the matrix multiplication is done on the two last axes, in parallel on the other. Then, I add a new axis so that: (Ny, Nx, 1, 2) * (Ny, Nx, 2, 1) = (Ny, Nx, 1, 1).
The np.squeeze() call returns a version without the two last singleton axes.
So I tried to plot a contour in Julia by interpolating a 2D function, using the following code:
using Interpolations
using Plots
gr()
xs = 1:0.5:5
ys = 1:0.5:8
# The function to be plotted
f(x, y) = (3x + y ^ 2)
g = Float64[f(x,y) for x in xs, y in ys]
# Interpolate the function
g_int = interpolate(g, BSpline(Quadratic(Line(OnCell()))))
# Scale the interpolated function to the correct grid
gs_int = scale(g_int, xs, ys)
xc = 1:0.1:5
yc = 1:0.1:5
# Compare the real value and the interpolated value of the function at an arbitrary point
println("gs_int(3.2, 3.2) = ", gs_int(3.2, 3.2))
println("f(3.2, 3.2) = ", f(3.2, 3.2))
# Contour of the interpolated plot
p1 = contour(xs, ys, gs_int(xs, ys), fill=true)
# Real contour of the function
p2 = contour(xc, yc, f, fill=true)
plot(p1, p2)
And this obviously didn't give the correct contour, although the interpolation was seemingly correct:
The problem was fixed by transposing gs_int(xs, ys):
p1 = contour(xs, ys, gs_int(xs, ys)', fill=true)
Then I randomly generated some points in 2D space, and repeated the same procedures:
using DelimitedFiles
using Interpolations
using Plots
gr()
data = readdlm("./random_points.txt", Float64)
# Create a dictionary to test different orders of interpolations.
inter = Dict("constant" => BSpline(Constant()),
"linear" => BSpline(Linear()),
"quadratic" => BSpline(Quadratic(Line(OnCell()))),
"cubic" => BSpline(Cubic(Line(OnCell())))
)
x = range(-10, length=64, stop=10)
y = range(-10, length=64, stop=10)
v_unscaled = interpolate(data, inter["cubic"])
v = scale(v_unscaled, x, y)
# The contour of the data points
p0 = contour(x, y, data, fill=true)
display(p0)
# The contour of the interpolated function
p_int = contour(x, y, v(x,y)', fill=true)
display(p_int)
However the two contour plots don't look the same.
As I removed the apostrophe after v(x,y), this worked:
p_int = contour(x, y, v(x,y), fill=true)
Now I don't get it. When should I apply transposition, and when shouldn't I do so?
That's because in your first example you plot a function, in the second example you plot two arrays. The two arrays don't need to be transposed as they are oriented the same way. But in the first example, the way you generate the array is transposed relative to the way Plots generates an array from the 2-d function you're passing.
When you plot a function, Plots will calculate the outcome as g = Float64[f(x,y) for y in ys, x in xs] not the other way around, like you did in your code. For a good discussion of transposes in plotting, again refer to https://github.com/JuliaPlots/Makie.jl/issues/205
I want to the same algorithm to repeat over a range of DX, DY values instead of me setting the values inside the translate function.
The image I used is a simple binary image of an abstract shape. I want an array of distances which are calculated over different values of dx,dy
I believe what I am trying to do is some form of brute force in order to find the most optimum dx,dy that gives the best distance
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from scipy.spatial import distance
import scipy.misc
im = scipy.misc.imread(r'C:\Users\mbore\Pictures\irregular1.png', flatten=False, mode='L')
def ellipse(x, y):
value = (x*x) + (y*y)/3
if (value >= 600):
return 0
else:
return 1
def translate(x, y):
DX = 45
DY = 75
return (x- DX, y - DY)
def rotate(x, y):
theta = np.radians(45)
matrix = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])
return np.dot(matrix, (x,y))
data = np.zeros((100,100))
for i in range(0, 100):
for j in range(0, 100):
(x, y) = translate(i,j)
(x, y) = rotate(x, y)
data[i,j] = ellipse(x, y)
#np.append(data,ellipse(x,y))
plt.imshow(data, cmap="gray")
plt.show()
plt.imshow(im)
plt.show()
counter = 0 #tracking white
counter1 = 0 #tracking black
#getting the dimensions of the image -> y
yDim = im.shape[0]
#getting the dimensions of the image -> x
xDim = im.shape[1]
for i in range(yDim):
for j in range (xDim):
if np.any(im[i,j]) == 0:
counter += 1
else:
counter1 += 1
#initialize empty array this array will receive all the white pixels
a = np.empty([100,100])
for i in range(yDim):
for j in range (xDim):
if np.any(im[i,j]) == 0:
np.append(a,im[i,j],axis=None)
#spatial distance
a = a.flatten()
data = data.flatten()
distance = distance.hamming(data,a)
print (distance)
I dont understand you question fully (and maybe you could ask it more generally as for instance the plotting part has nothing to do with the core problem) but if you just want to run that code for different parameters you could expand your function to generall DX and DY:
def translate(x, y, DX, DY):
return (x- DX, y - DY)
DX_DY_list = [(1,1),(2,3),(3,4)]
for DX, DY in DX_DY_list:
result = translate(1, 2, DX, DY)
print(result)
a other (bit more advance) way would be to general you code and using argparse as I find it a very useful tool for parameter studies.
Im new to programming and im trying to plot a graph where i want my "y" function to differ with a specific condition of x. This is how i tried to do it:
import matplotlib.pyplot as plt
import numpy as np
x= np.arange(0,10,0.1)
if x(x<5):
y=x
else:
y=0
plt.plot(x,y)
plt.show()
plt.plot(x, [x_ if x_ < 5 else 0 for x_ in x])
The plot method plots x versus y as lines and/or markers. The y array is created based on the values of x array using list comprehension
y = [x_ if x_ < 5 else 0 for x_ in x]
It is exactly same as
y = list()
for i in range(len(x)):
if x[i] < 5:
y.append(x[i])
else:
y.append(0)
The error originates from x(x<5). Python parses the first x as a function and try to call it for the argument x<5, thus triggering an error, because x is not a function that you can call, it's an numpy.ndarray, thus explaining the numpy.ndarray object is not callable error.
Try this:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0,10,0.1)
y = [(xx if xx<5 else 0) for xx in x]
plt.plot(x,y)
plt.show()
I am trying to create a matrix with that is q by 3. In this case, q = 10. or each row I want the three values to be the results of the trigonometric functions described in my code below.
The problem is that I keep getting an error saying that the list index is out of range. I don't understand why it is saying it is out of range. To my eyes, my loop seems correct. Can anyone tell me what I'm overlooking/doing wrong?
# Input az matrix
az = [142.243258152,116.039625836,80.1585056414,139.614063776,87.2093336287,94.1433825229,35.5599100744,11.0328982848,177.717968103,19.0072693362]
# Construct frame of X matrix
X = [[0 for x in range(10)] for y in range(3)]
# Use az matrix to complete X matrix
f=0
for bear in az:
X[f][0] = (M.cos(bear))**2
X[f][1] = 2*M.cos(bear)*M.sin(bear)
X[f][2] = (M.sin(bear))**2
f=f+1
print X
OP's input list az has 10 elements, not 8 as supposed and the ranges of the matrix should be swapped.
Besides, sin and cos functions usually take radians as input, while az seems to contain angles misured in degrees.
This snippet:
from math import radians, cos, sin
# Input az matrix
az = [142.243258152, 116.039625836, 80.1585056414, 139.614063776, 87.2093336287, 94.1433825229, 35.5599100744, 11.0328982848, 177.717968103, 19.0072693362]
# Construct frame of X matrix
X = [[0 for x in range(3)] for y in range(10)]
# Use az matrix to complete X matrix
f=0
for bear in az:
r = radians(bear)
c = cos(r)
s = sin(r)
X[f][0] = c**2
X[f][1] = 2*c*s
X[f][2] = s**2
f=f+1
print(X)
Gives this output:
[[0.6250760791021176, -0.9682065367191874, 0.37492392089788235], [0.19271454590900655, -0.7888615840667916, 0.8072854540909934], [0.029214706063653385, 0.3368157182393228, 0.9707852939363467], [0.5801828858777331, -0.9870576575100736, 0.41981711412226685], [0.0023704299165554724, 0.09725864441922212, 0.9976295700834447], [0.0052204459914281754, -0.14412762309951216, 0.9947795540085718], [0.6617950612456389, 0.9461973539521655, 0.33820493875436103], [0.9633765287676627, 0.3756710933102597, 0.0366234712323373], [0.9984144917844932, -0.07957372378380607, 0.001585508215506806], [0.893927252777247, 0.615861411421014, 0.10607274722275291]]