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
Related
Not at all sure what I'm doing wrong besides perhaps the order that I am plotting the ocean in. I am trying to get the ocean feature in to mask the data in the ocean. I am trying to get data to not appear in the ocean and to get the ax.add_feature(cfeature.OCEAN) to be on top of the temperature data I am plotting so I see ocean and no data. Similar to what is happening in the great lakes region where you see lakes and no temperature data.
proj_map = ccrs.Mercator(central_longitude=cLon)
proj_data = ccrs.PlateCarree()
fig = plt.figure(figsize=(30,20))
ax = fig.add_subplot(1,1,1, projection=proj_map)
ax.set_extent([-84,-66,37,47.5])
CT = ax.contourf(Tlat, Tlon, tempF, transform=temp.metpy.cartopy_crs, levels=clevs,
cmap=cmap)
ax.add_feature(cfeature.COASTLINE.with_scale('10m'), linewidth=0.5)
ax.add_feature(cfeature.OCEAN)
ax.add_feature(cfeature.LAKES)
ax.add_feature(cfeature.BORDERS, linewidth=0.5)
ax.add_feature(cfeature.STATES.with_scale('10m'), linewidth=0.5)
ax.add_feature(USCOUNTIES.with_scale('20m'), linewidth=0.25)
cbar = fig.colorbar(CT, orientation='horizontal', shrink=0.5, pad=0.05)
cbar.ax.tick_params(labelsize=14)
cbar.set_ticks([-50, -40, -30, -20, -10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100,
110, 120])
cbar.ax.set_xlabel("Temp ($^\circ$F)",fontsize=20)
Here is what the image looks like
You need to use zorder option to specify proper orders of the plot on the map. Features with largers values of zorder will be plotted on top of those with lower values. In your case, you need zorder of the OCEAN larger than the filled-contour.
Here is a runnable demo code and its sample plot. Read comments in the code for explanation.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import numpy as np
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(projection=ccrs.PlateCarree()))
extent = [-84, -66, 37, 47.5]
# generate (x, y), centered at the middle of the `extent`
mean = [(extent[0]+extent[1])/2, (extent[2]+extent[3])/2] #mean
cov = [[7, 3.5], [3.5, 6]] #co-variance matrix
x, y = np.random.multivariate_normal(mean, cov, 4000).T
# make a 2D histogram
# set the edges of the bins in x and y directions
bin_size = 40
lonrange = np.linspace(extent[0], extent[1], bin_size)
latrange = np.linspace(extent[2], extent[3], bin_size)
# the cell sizes of the bins:
dx = (lonrange[1]- lonrange[0])/2
dy = (latrange[3]- latrange[2])/2
# compute array of center points of the bins' grid
# the dimensions of mesh-grid < the edges by 1
lonrange2 = np.linspace(extent[0]+dx, extent[1]-dx, bin_size-1)
latrange2 = np.linspace(extent[2]+dy, extent[3]-dy, bin_size-1)
x2d, y2d = np.meshgrid(lonrange2, latrange2)
# create 2d-histogram
# zorder is set = 10
h = ax.hist2d(x, y, bins=[lonrange, latrange], zorder=10, alpha=0.75)
#h: (counts, xedges, yedges, image)
ax.add_feature(cfeature.OCEAN, zorder=12) #zorder > 10
ax.add_feature(cfeature.BORDERS, linewidth=0.5)
ax.gridlines(draw_labels=True, xlocs=list(range(-85, -60, 5)), ylocs=list(range(35, 50, 5)),
linewidth=1.8, color='gray', linestyle='--', alpha=0.8, zorder=20)
# plot colorbar, using image from hist2d's result
plt.colorbar(h[3], ax=ax, shrink=0.45)
# finally, show the plot.
plt.show()
The output plot:
If zorder option is not specified:
ax.add_feature(cfeature.OCEAN)
the plot will be:
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.
I want to achieve the following. I have a x-y plot with a function y dependent on x. The plot consists of a mesh of squares. When a function point is inside the function block, the block changes color. I have attached an example:
I want to make an simillar figure, but I want to be able to make the gridsize variable.
I know how to make a plot with plt.plot(), but I'm not familliar with making a grid and filling in that grid if the function point falls in a square. Can somebody refer to numpy or mathplotlib functions that can help?
Thanks
here is a piece of code that should work for graphs centered around (min(X)-max(X)) and (min(Y),max(Y)) :
import numpy as np
def grid_plot(X,Y,resx,resy) :
d_x=resx/(np.max(X)-np.min(X))
d_y=resy/(np.max(Y)-np.min(Y))
mat=np.zeros((resy,resx))
for i in range(len(X)) :
mat[int((Y[i]-np.min(Y))*d_y),resx-int((X[i]-np.min(X))*d_x)]=1
return mat
You can use np.histogram2d to create a 2D histogram and plot the locations with count greater than zero with imshow:
import numpy as np
import matplotlib.pyplot as plt
# Input data
x = np.linspace(-2, 2, 300)
y = np.sin(x)
# Plot limits
x_min, x_max = -3, 3
y_min, y_max = -2, 2
plt.figure(figsize=(8, 3))
# First plot
resolution = 25
xg = np.linspace(x_min, x_max, resolution)
yg = np.linspace(y_min, y_max, resolution)
h, _, _ = np.histogram2d(x, y, (xg, yg))
plt.subplot(121)
# Transpose because imshow swaps X and Y axes
plt.imshow(h.T > 0, origin='lower', extent=(xg[0], xg[-1], yg[0], yg[-1]))
# Show grid
plt.gca().set_xticks([], minor=False)
plt.gca().set_xticks(xg, minor=True)
plt.gca().set_yticks([], minor=False)
plt.gca().set_yticks(yg, minor=True)
plt.grid(True, 'minor')
# Second plot
resolution = 50
xg = np.linspace(x_min, x_max, resolution)
yg = np.linspace(y_min, y_max, resolution)
h, _, _ = np.histogram2d(x, y, (xg, yg))
plt.subplot(122)
plt.imshow(h.T > 0, origin='lower', extent=(xg[0], xg[-1], yg[0], yg[-1]))
plt.gca().set_xticks([], minor=False)
plt.gca().set_xticks(xg, minor=True)
plt.gca().set_yticks([], minor=False)
plt.gca().set_yticks(yg, minor=True)
plt.grid(True, 'minor')
# Show plot
plt.tight_layout()
plt.show()
Result:
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()