My goal is to create 3 images, one is an anti-aliased image of a random spline and then two others that scaled between 0 and 1 for how "horizontal" or "vertical" the spline is at each point.
from scipy.interpolate import CubicSpline, griddata
import numpy as np
import matplotlib.pyplot as plt
def create_random_line():
# Create random spline
x = np.array([1, 15, 30, 49])
y = np.random.uniform(1, 50, 4)
f = CubicSpline(x, y, bc_type='natural')
x_new = np.linspace(0, 49, 100)
y_new = f(x_new)
y_new_deriv = f(x_new, 1)
y_angles = np.array([math.atan2(tt, 1) for tt in y_new_deriv])
# Plot the spline, derivative and angle
plt.figure(2)
plt.clf()
plt.subplot(3,1,1)
plt.plot(x, y, 'x')
plt.xlim((0, 50))
plt.ylim((0, 50))
plt.plot(x_new, y_new)
plt.subplot(3,1,2)
plt.plot(x_new, y_new_deriv)
plt.subplot(3,1,3)
plt.plot(x_new, np.rad2deg(y_angles))
plt.ylim((-90, 90))
plt.show()
# Create image of spline
image = np.zeros((50, 50))
scaled_angle_maps = np.zeros((50, 50, 2))
for xx, yy, rr in zip(y_new, x_new, np.rad2deg(y_angles)):
image[int(np.round(xx)), int(np.round(yy))] = 1
scaled_angle_maps[int(np.round(xx)), int(np.round(yy)), 0] = np.clip(1 - (np.abs(rr)/90), 0, 1)
scaled_angle_maps[int(np.round(xx)), int(np.round(yy)), 1] = np.clip(np.mod(np.abs(rr),90)/90, 0, 1)
return image, scaled_angle_maps
# Create random spline image
image, scaled_angle_maps = create_random_line()
# Plot
plt.figure(1)
plt.clf()
plt.subplot(2,2,1)
plt.imshow(image)
plt.gray()
plt.colorbar()
plt.ylim((0,50))
plt.subplot(2,2,3)
plt.imshow(scaled_angle_maps[:,:,0])
plt.ylim((0,50))
plt.colorbar()
plt.title('horizontal')
plt.subplot(2,2,4)
plt.imshow(scaled_angle_maps[:,:,1])
plt.ylim((0,50))
plt.colorbar()
plt.title('vertical')
plt.show()
But, I would like this anti-aliased. I have been reading about Wu's algorithm, but most implementations appear to be for straight lines. I tried creating it from the matplotlib canvas, but that did not work out well.
Then, second, I would like to have an arbitrary thickness to the spline in the image, though I suppose I could just scipy.ndimage.grey_dilation after the image is created.
So, am I missing an easy method of creating a random spline image? It feels like there should be a simpler method to do this.
Related
I have two 3D-points, for example a = (100, 100, 10) and b = (0, 100, 60), and would like to fit a line through those points.
I know, the 3D line equation can have different shapes:
Vector-form:
(x,y,z)=(x0,y0,z0)+t(a,b,c)
Parameter-form:
x=x0+ta
y=y0+tb
z=z0+tc
But I have a problem getting the data in the right shape for a numerical function.
The following code should work
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.axes(projection ='3d')
# defining coordinates for the 2 points.
x = np.array([100, 0])
y = np.array([100, 100])
z = np.array([10, 60])
# plotting
ax.plot3D(x, y, z)
plt.show()
Here the ax.plot3D() plots a curve that joins the points (x[i], y[i], z[i]) with straight lines.
I would like to make a 3d plot of a surface parametrised by a function, and I would like the surface to be of one color (say white) where it is above some value a, and of another color (say black) where it is below a.
Here is the code to generate and plot the surface (the way the surface is generated is not important, it could be a much simpler function):
from __future__ import division
import numpy as np
import time,random
random.seed(-2)
def build_spden(N,M, alpha):
#computes the spectral density in momentum space
sp_den = np.zeros((N,M))
for k1 in prange(-N//2, N//2):
for k2 in prange(-M//2, M//2):
sp_den[k1,k2] = np.abs(2*(np.cos(2*np.pi*k1/N)+np.cos(2*np.pi*k2/M)-2))
sp_den[0,0]=1
return 1/sp_den**(alpha/2)
def gaussian_field(N,M,alpha):
'''Builds a correlated gaussian field on a surface NxM'''
spectral_density = build_spden(N,M, alpha)
# FFT of gaussian noise:
noise_real = np.random.normal(0, 1, size = (N,M))
noise_fourier = np.fft.fft2(noise_real)
# Add correlations by Fourier Filtering Method:
convolution = noise_fourier*np.sqrt(spectral_density)
# Take IFFT and exclude residual complex part
correlated_noise = np.fft.ifft2(convolution).real
# Return normalized field
return correlated_noise * (np.sqrt(N*M)/np.sqrt(np.sum(spectral_density)) )
#PLOT
N = 2**5
alpha = .75
a = -.1985
surf = gaussian_field(N,N,alpha)
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
x = np.outer(np.arange(0, N), np.ones(N))
y = x.copy().T # transpose
z = surf
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.plot_surface(x, y, z,alpha=.4) #plot the surface
z2 = a*np.ones((N,N))
ax.plot_surface(x, y, z2, alpha=0.9) #plot a plane z = a.
plt.show()
The output is:
I would therefore like the surface to be white above the plane and black below.
Many thanks !
You can define a custom color map and pass to plot_surface:
from matplotlib.colors import ListedColormap, BoundaryNorm
cmap = ListedColormap(['r', 'b'])
norm = BoundaryNorm([z.min(), a, z.max()], cmap.N)
ax.plot_surface(x, y, z, cmap=cmap, norm=norm, alpha=.4) #plot the surface
z2 = a*np.ones((N,N))
ax.plot_surface(x, y, z2, colalpha=0.9) #plot a plane z = a.
plt.show()
Output:
I have a loss function of two variables W1, W2 and an output z = F(W1,W2).
Now I plot the contour map of this loss function. Now say, I have calculated gradient vector at two points, therefore I have two gradient vectors now. I want to plot these gradient vector on my contour plot but I have no idea how to procces. Any help is appreciated
enter code here
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
feature_x = np.arange(-50, 50, 2)
feature_y = np.arange(-50, 50, 3)
# Creating 2-D grid of features
[X, Y] = np.meshgrid(feature_x, feature_y)
fig, ax = plt.subplots(1, 1)
z = 0.5*np.array((Y-X)*(Y-X) + 0.5*(1-X)*(1-X))
# plots contour lines
ax.contour(X, Y, z, 10, cmap = 'jet')
ax.grid(True)
ax.axis('scaled')
#ax.clabel(cp, inline=1, fontsize=10)
ax.set_title('Contour Plot')
ax.set_xlabel('feature_x')
ax.set_ylabel('feature_y')
plt.show()
You could use FancyArrowPatch to draw the gradients at a few selected positions.
from matplotlib.patches import FancyArrowPatch
x1 = -20 # position of the gradient
y1 = 10
dz1_dx = 10 # value of the gradient at that position
dz1_dy = -5
arrow = FancyArrowPatch((x1, y1), (x1+dz1_dx, y1+dz1_dy),
arrowstyle='simple', color='k', mutation_scale=10)
ax.add_patch(arrow)
Otherwise if you want to plot the whole vector field quiver might be an option:
feature_x = np.arange(-50, 50, 2)
feature_y = np.arange(-50, 50, 2)
x, y = np.meshgrid(feature_x, feature_y)
z = 0.5*(y-x)**2 + 0.5*(1-x)**2
u = 2*x - y - 1
v = y - x
# Normalize all gradients to focus on the direction not the magnitude
norm = np.linalg.norm(np.array((u, v)), axis=0)
u = u / norm
v = v / norm
fig, ax = plt.subplots(1, 1)
ax.set_aspect(1)
ax.plot(feature_x, feature_y, c='k')
ax.quiver(x, y, u, v, units='xy', scale=0.5, color='gray')
ax.contour(x, y, z, 10, cmap='jet', lw=2)
arrow = FancyArrowPatch((35, 35), (35+34*0.2, 35+0), arrowstyle='simple',
color='r', mutation_scale=10)
ax.add_patch(arrow) # NOTE: this gradient is scaled to make it better visible
I added the line y = x in this plot and marked the point where this lines intersects with a contour line. Here you can see clearly
that:
Gradients are orthogonal to level surfaces
So for your point (80, 80) the gradient (79, 0) is correct even so the general shape of isolines maybe suggest that there should be be a part in y-direction.
But if you look along the line y=x you see that the gradients there are always only in x-direction.
I have two heatmaps which are based on 2d histograms that I am trying to overlay on a single graph. The limits of their axes (extent_L and extent_H) do not necessarily coincide exactly. I can make the individual plots satisfactorily if needed, but when trying to show both heatmaps on a single graph nicely, only the most recent one is displayed.
import numpy as np
import numpy.random
import matplotlib.pyplot as plt
# Generate some test data
x_L = np.random.randn(8873)
y_L = np.random.randn(8873)
x_H = np.random.randn(1000)
y_H = np.random.randn(1000)
heatmap_L, xedges_L, yedges_L = np.histogram2d(x_L, y_L, bins=50)
extent_L = [xedges_L[0], xedges_L[-1], yedges_L[0], yedges_L[-1]]
heatmap_H, xedges_H, yedges_H = np.histogram2d(x_H, y_H, bins=50)
extent_H = [xedges_H[0], xedges_H[-1], yedges_H[0], yedges_H[-1]]
plt.clf()
im1 = plt.imshow(heatmap_L.T, extent=extent_L, origin='lower', cmap='Blues')
im2 = plt.imshow(heatmap_H.T, extent=extent_H, origin='lower', cmap='Greens')
plt.show()
Edit: If I'm not mistaken, all points are not in exactly the proper location
import numpy as np
import numpy.random
import matplotlib.pyplot as plt
# Generate some test data
x_L = np.random.randn(8873)
y_L = np.random.randn(8873)
x_H = np.random.randn(1000)
y_H = np.random.randn(1000)
heatmap_L, xedges_L, yedges_L = np.histogram2d(x_L, y_L, bins=50)
extent_L = np.array([xedges_L[0], xedges_L[-1], yedges_L[0], yedges_L[-1]])
heatmap_H, xedges_H, yedges_H = np.histogram2d(x_H, y_H, bins=50)
extent_H = np.array([xedges_H[0], xedges_H[-1], yedges_H[0], yedges_H[-1]])
plt.clf()
im1 = plt.imshow(heatmap_L.T, extent=extent_L, origin='lower', cmap='Blues')
im2 = plt.imshow(heatmap_H.T, extent=extent_H, origin='lower', cmap='Greens')
plt.autoscale()
plt.show()
flatHMH = np.reshape(heatmap_H, 2500) # flatten the 2D arrays
flatHML = np.reshape(heatmap_L, 2500)
maxHMH = flatHMH.max() # Find the maximum in each
maxHML = flatHML.max()
# Now for each value in the flat array build an RGBA tuple using
# 1 for the colour we want - either green or blue, and then scaling
# the value by the maximum, finally reshaping back to a 50x50 array
augHMH = np.array([(0, 1, 0, x/maxHMH) for x in flatHMH]).reshape((50, 50, 4))
augHML = np.array([(0, 0, 1, x/maxHML) for x in flatHML]).reshape((50, 50, 4))
plt.clf()
# Plot without cmap as colours are now part of the data array passed.
im1 = plt.imshow(augHML, extent=extent_L, origin='lower')
im2 = plt.imshow(augHMH, extent=extent_H, origin='lower')
plt.autoscale()
plt.show()
If you look closely at the points in the last plot, for example the clustering of points at the edge, you'll notice they are not the same as in the plot above.
You are displaying both plots, the problem is that you are drawing one on top of the other. To see this in action you can shift one of the plots as in:
import numpy as np
import numpy.random
import matplotlib.pyplot as plt
# Generate some test data
x_L = np.random.randn(8873)
y_L = np.random.randn(8873)
x_H = np.random.randn(1000)
y_H = np.random.randn(1000)
heatmap_L, xedges_L, yedges_L = np.histogram2d(x_L, y_L, bins=50)
extent_L = np.array([xedges_L[0], xedges_L[-1], yedges_L[0], yedges_L[-1]])
heatmap_H, xedges_H, yedges_H = np.histogram2d(x_H, y_H, bins=50)
extent_H = np.array([xedges_H[0], xedges_H[-1], yedges_H[0], yedges_H[-1]])
plt.clf()
im1 = plt.imshow(heatmap_L.T, extent=extent_L, origin='lower', cmap='Blues')
im2 = plt.imshow(heatmap_H.T+2, extent=extent_H+2, origin='lower', cmap='Greens')
plt.autoscale()
plt.show()
You also need the plt.autoscale() call in there as otherwise the limits are not adjusted correctly.
One way to show the two plots on top of each other is to use the argument alpha=X to the imshow call (where 0 < X < 1) in order to set transparency on the plot call. Another, possibly clearer way is to transform each value from the histogram2D to an RGBA value. See the imshow docs for both alternatives to displaying the plots on top of each other.
One way of transforming the values would be to flatten the data, and augment it with the colours you want.
# imports and test data generation as before, removed for clarity...
flatHMH = np.reshape(heatmap_H, 2500) # flatten the 2D arrays
flatHML = np.reshape(heatmap_L, 2500)
maxHMH = flatHMH.max() # Find the maximum in each
maxHML = flatHML.max()
# Now for each value in the flat array build an RGBA tuple using
# 1 for the colour we want - either green or blue, and then scaling
# the value by the maximum, finally reshaping back to a 50x50 array
augHMH = np.array([(0, 1, 0, x/maxHMH) for x in flatHMH]).reshape((50, 50, 4))
augHML = np.array([(0, 0, 1, x/maxHML) for x in flatHML]).reshape((50, 50, 4))
plt.clf()
# Plot without cmap as colours are now part of the data array passed.
im1 = plt.imshow(augHML, extent=extent_L, origin='lower')
im2 = plt.imshow(augHMH, extent=extent_H, origin='lower')
plt.autoscale()
plt.show()
You can call
plt.autoscale()
such that the limits are adjusted to the content of the axes.
Example:
import numpy as np
import matplotlib.pyplot as plt
def get(offs=0):
# Generate some test data
x = np.random.randn(8873)+offs
y = np.random.randn(8873)+offs
heatmap, xedges, yedges = np.histogram2d(x, y, bins=50)
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
heatmap, xedges, yedges = np.histogram2d(x, y, bins=50)
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
return heatmap, extent
h1,e1 = get(-3)
h2,e2 = get(+3)
plt.imshow(h1, extent=e1, origin='lower', cmap="RdBu")
plt.imshow(h2, extent=e2, origin='lower', cmap="YlGnBu")
plt.autoscale()
plt.show()
I want to fit a set of points on a image with a smooth curve in python. The curve may be open or closed. Moreover, I want to get the curve plotted over the image as a mask that has the same size as the image. Is there any modules or functions I can refer to? Thanks.
This is generally called Parametric Interpolation. There is a scipy function that does just that called splprep. The steps you are requesting are:
1) Smooth a shape (built with ordered points, if not use convex hull first, check this question).
NOTE: for more on how to create a shape over an image check this question.
2) Use the smooth shape to build a mask over an image (lets say 2D array).
The following recipe does that:
import numpy as np
from scipy.interpolate import splprep, splev
import matplotlib.pyplot as plt
from matplotlib import path
# Building a shape with scattered points.
x = np.array([10, 10, 0, 0, 10, 10, 20, 20, 30, 30, 20, 20, 10]) + 10
y = np.array([0, 10, 10, 20, 20, 30, 30, 20, 20, 10, 10, 0, 0]) + 10
image = np.random.randint(0, 10, (50, 50))
# Smoothing the shape.
# spline parameters
s = 3.0 # smoothness parameter
k = 2 # spline order
nest = -1 # estimate of number of knots needed (-1 = maximal)
t, u = splprep([x, y], s=s, k=k, nest=-1)
xn, yn = splev(np.linspace(0, 1, 500), t)
# Showing the original shape
plt.imshow(image.T, origin='lower', interpolation='nearest', cmap='gray')
plt.plot(x, y, color='b', linewidth=3)
plt.xlim(0, 50)
plt.ylim(0, 50)
plt.show()
# Showing the original shape vs smooth shape
plt.imshow(image.T, origin='lower', interpolation='nearest', cmap='gray')
plt.plot(x, y, color='b', linewidth=3)
plt.plot(xn, yn, color='r', linewidth=3)
plt.xlim(0, 50)
plt.ylim(0, 50)
plt.show()
# Changing values inside the shape (and outside). Building a boolean mask.
image1 = image.copy()
image2 = image.copy()
mask = np.zeros(image.shape)
xx, yy = np.meshgrid(range(image.shape[0]),range(image.shape[1]))
shapes = np.hstack((xn[:, np.newaxis], yn[:, np.newaxis]))
p = path.Path(shapes)
for i in range(image.shape[0]):
for j in range(image.shape[1]):
if not p.contains_point((i, j)):
image1[i, j] = 0
mask[i, j] = True
else:
image2[i, j] = 0
# Showing changes in image for values outside shape.
plt.imshow(image1.T, origin='lower', interpolation='nearest', cmap='gray')
plt.plot(xn, yn, color='r', linewidth=3)
plt.xlim(0, 50)
plt.ylim(0, 50)
plt.show()
# Showing changes in image for values inside shape.
plt.imshow(image2.T, origin='lower', interpolation='nearest', cmap='gray')
plt.plot(xn, yn, color='r', linewidth=3)
plt.xlim(0, 50)
plt.ylim(0, 50)
plt.show()
The code is commented for you to know what is happening at each of the plots of the results:
1. Simple Shape over image
2. Simple vs Smooth shape over image
3. Use values inside shape
4. Use values outside shape