I'm trying to draw elliptical lines using matplotlib to connect two circles but would like to make it so the elliptical lines do not intersect either circle.
Currently my design has resulted in this:
which as you can see has lines going through both circle A and B.
I decided to use matplotlib.patches.Arc since I didn't want it filled and it allowed me to draw a left and right part. Here is what I have:
from matplotlib import pyplot
from matplotlib.patches import Arc
import math
def calculate_perimeter(a, b):
perimeter = math.pi * (3*(a+b) - math.sqrt( (3*a + b) * (a + 3*b) ))
return perimeter
def draw_circle(xy, radius, text):
circle = pyplot.Circle(xy, radius=radius, fill=False)
pyplot.gca().add_patch(circle)
pyplot.gca().annotate(text, xy=xy, fontsize=10, ha='center', va='center')
def draw_arc(xy1, xy2, a, b, theta1, theta2):
# Calculate center of the elliptical arc
center = (xy1[0], (xy1[1] + xy2[1])/2.0)
arc = Arc(center, a, b, theta1=theta1, theta2=theta2)
pyplot.gca().add_patch(arc)
if __name__ == '__main__':
pyplot.figure()
center_circle1 = (5, 5)
center_circle2 = (5, 20)
dist_y = center_circle2[1] - center_circle1[1]
adjustment = 5.3 # #TODO: How do I calculate what this needs to be?
# Circles
draw_circle(center_circle1, 1, 'A')
draw_circle(center_circle2, 1, 'B')
# Draw right side of arc
theta1 = 270.0 + adjustment
theta2 = 90.0 - adjustment
draw_arc(center_circle1, center_circle2, 3, dist_y, theta1, theta2)
# Draw left side of arc
theta1 = 90.0 + adjustment
theta2 = 270.0 - adjustment
draw_arc(center_circle1, center_circle2, 3, dist_y, theta1, theta2)
pyplot.axis('scaled')
pyplot.axis('off')
pyplot.show()
For instance when I put adjustment = 5.3 I get:
If I zoom in on this area though it's easy to see it does not line up:
My question then becomes, how do I calculate what adjustment should be?
I thought I would be able to calculate the perimeter if I consider it a complete ellipse and subtract the amount that overlaps in one of the circles and use that to get the adjustment, but I'm not sure if that would work or how to calculate how much overlaps inside. Any help on this would be appreciated.
Rather than adjusting the figure manually, consider using the zorder in the Patch constructor.
The various artists on a plot are stacked upon each other vertically, with those with the highest zorder on top. By setting the zorder, therefore, you will cause the circles to be drawn over the ellipse, obscuring it.
Example code:
from matplotlib import pyplot as plt
from matplotlib.patches import Circle, Arc
fig, ax = plt.subplots(figsize=(6, 6))
ax.add_patch(Circle((0.5, 0.75), 0.05, edgecolor='black', facecolor='white', zorder=3))
ax.add_patch(Circle((0.5, 0.25), 0.05, edgecolor='black', facecolor='white', zorder=3))
ax.add_patch(Arc((0.5, 0.5), 0.1, 0.5))
That generates
.
Related
I would like to plot circles which are tangents to the interior of a curve in Python as shown below:
I tried the following approach.
I created an inverted exponential curve to get the curve as shown below.
x = np.arange(0, 2, 0.1)
y = [1/np.exp(i) for i in x]
plt.plot(x, y, marker = "o")
To add a circle as tangent to the curve, I am adding a circle manually as follows:
fig, ax = plt.subplots()
#Plot exponential curve
ax.plot(x, y)
#Add tangential circle manually
pi = np.pi
c1 = plt.Circle((x[1]+ np.sin(pi/4) * 0.1, y[1] + np.sin(pi/4) * 0.1),
radius = 0.1, facecolor = "black", ec = "white")
ax.add_patch(c1)
Here my assumption is that the angle denoted by the shaded part in the figure below (between the normal radius and horizontal x-axis is 45 degrees).
However, I think this is an incorrect assumption and incorrect way to do it.
When I add more circles, they are not exactly tangential to the curve as shown below.
What would be the correct approach to draw the circles as tangents to the curve? Is it possible to do it in Python?
Using trigonometry (there is likely a way to simplify but I'm rusty+tired :p).
fig, ax = plt.subplots()
X = np.arange(0, 5, 0.1)
Y = np.sin(X)
#Plot exponential curve
ax.plot(X, Y)
ax.set_aspect('equal')
#Add tangential circles
for i in np.arange(1,len(X),5):
ax.plot(X[i], Y[i], marker='o', color='r')
pi = np.pi
radius = 0.3
# slope of tangent
dydx = np.gradient(Y)[i]/np.gradient(X)[i]
# slope of perpendicular
slope = 1/abs(dydx)
# angle to horizontal
angle = np.arctan(slope)
c1 = plt.Circle((X[i] - np.sign(dydx)*np.cos(angle) * radius,
Y[i] + np.sin(angle) * radius),
radius = radius, facecolor = "black", ec = "white")
ax.add_patch(c1)
output:
Your function is exp(-x)
It's derivative is -exp(-x), so tangent vector at point x, exp(-x) is (1, -exp(-x)) and normal vector is (exp(-x), 1) (note sign change).
Normalize it
n_len = np.hypot((y)[i], 1)
nx = y[i] / n_len
ny = 1 / n_len
center for radius R is
c1 = plt.Circle((x[i] + R * nx, y[i] + R * ny, ...
For the circle to be tangent, its center must be located along the normal to the curve (this is a straight-line with slope -δx/δy).
You did not specify how the radius varies, so there are two options:
the radius is a function of x or y or s (curvilinear abscissa) that you supply; then place the center at the desired distance from the contact point, on the normal;
the radius is such that it matches the local curvature; then what you want is the osculating circle; there is a formula to compute the radius of curvature.
https://en.wikipedia.org/wiki/Osculating_circle
I'm trying to generate a right triangle with hypotenuse = 1, interior angle 25, with its base rotated 30 degrees. I've entered the trig formulas correctly to my understanding, and I suspect some kind of rounding error. Because the triangle produced in matplot is slightly off from a right triangle.
import math
import matplotlib.pyplot as plt
from annotation import label
angle_b = math.radians(25) # the interior 25 degree angle of our right triangle
angle_a = math.radians(30) # 30 degree angle between the plotted angle_b triangle base and the x axis.
point_A = (0,0)
point_B = (math.cos(angle_a + angle_b), math.sin(angle_a + angle_b))
point_C = (math.cos(angle_a) * math.cos(angle_b), math.sin(angle_a) * math.cos(angle_b))
# Label our points
label(plt, 'A', point_A)
label(plt, 'B', point_B)
label(plt, 'C', point_C)
# Draw the right triangle between our points.
plt.plot(*zip(point_A, point_B, point_C, point_A), marker='o', color='black')
As you can see, the angle ACB is not a right angle as trigonometry would predict.
#Mad Physicist pointed out a the comment that ABC does appear to be a right triangle. I would like to get ACB to be a right angle, like this example.
Here is with the right triangle formed by angle_a below the previous one:
point_D = (math.cos(angle_a) * math.cos(angle_b), 0)
plt.plot(*zip(point_A, point_C, point_D, point_A), marker='o', color='black')
label(plt, 'D', point_D)
Now solved. I finally got it working thanks to the line ax.set_aspect('equal')
The problem is that by default, matplotlib doesn't use the same distances in the x and in the y direction. Instead, matplotlib tries to fit everything nicely into the given bounds.
These uneven distances distort angles, and also deforms circles.
You can force an equal aspect ratio via ax.set_aspect('equal').
To calculate the positions via the angles, and have the right corner at point B, you need to take into account that the length AC is cos(b) times the length of AC. AC can be chosen to have 1 as length. Alternatively, you could divide both B and C by cos(b) to have a larger triangle, where the length of AB would be 1.
import matplotlib.pyplot as plt
import math
angle_b = math.radians(25) # the interior 25 degree angle of our right triangle
angle_a = math.radians(30) # 30 degree angle between the plotted angle_b triangle and the x axis.
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(14, 5), sharey=True)
for ax in (ax1, ax2):
point_A = (0, 0)
if ax == ax1:
point_B = (math.cos(angle_a + angle_b) * math.cos(angle_b), math.sin(angle_a + angle_b) * math.cos(angle_b))
point_C = (math.cos(angle_a), math.sin(angle_a))
ax.set_title('length AC is 1')
else:
point_B = (math.cos(angle_a + angle_b), math.sin(angle_a + angle_b))
point_C = (math.cos(angle_a) / math.cos(angle_b), math.sin(angle_a) / math.cos(angle_b))
ax.set_title('length AB is 1')
point_M = ((point_A[0] + point_C[0]) / 2, (point_A[1] + point_C[1]) / 2)
# Draw the right triangle between our points.
ax.plot(*zip(point_A, point_B, point_C, point_A), marker='o', color='black')
# draw a circle around the 3 points
ax.add_patch(plt.Circle(point_M, math.sqrt((point_M[0] - point_A[0]) ** 2 + (point_M[1] - point_A[1]) ** 2),
ec='r', ls='--', fc='none'))
ax.set_aspect('equal', 'datalim')
plt.show()
The same calculation works for any angle. Here is how 12 rotations in steps of 30 degrees look together:
The following code shows the effect of ax.set_aspect('equal') for the original points.
import matplotlib.pyplot as plt
import math
angle_b = math.radians(25) # the interior 25 degree angle of our right triangle
angle_a = math.radians(30) # 30 degree angle between the plotted angle_b triangle and the x axis.
point_A = (0, 0)
point_B = (math.cos(angle_a + angle_b), math.sin(angle_a + angle_b))
point_C = (math.cos(angle_a) * math.cos(angle_b), math.sin(angle_a) * math.cos(angle_b))
point_M = ((point_A[0] + point_B[0]) / 2, (point_A[1] + point_B[1]) / 2)
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(16, 4), gridspec_kw={'width_ratios': [2, 1]})
for ax in (ax1, ax2):
# Draw the right triangle between our points.
ax.plot(*zip(point_A, point_B, point_C, point_A), marker='o', color='black')
# draw a circle around the 3 points
ax.add_patch(plt.Circle(point_M, math.sqrt((point_M[0] - point_A[0]) ** 2 + (point_M[1] - point_A[1]) ** 2),
ec='r', ls='--', fc='none'))
ax1.set_title('Default aspect ratio, deforms the angles')
ax2.set_aspect('equal') # or plt.axis('equal')
ax2.set_title('Equal aspect ratio')
plt.tight_layout()
plt.show()
You appear to have jumbled your trigonometry. I'm going to suggest moving slowly step-by-step, and using variable names that help you remember what is going on.
Let's start with the original triangle. If interior_angle = np.deg2rad(25) and the hypotenuse has length 1, then the right angle on the x-axis is at (np.cos(interior_angle), 0) and the other interior angle is at (np.cos(interior_angle), np.sin(interior_angle)). Neither of the points in your diagram corresponds.
Now let's express the triangle as a matrix whose columns are the vertices:
interior_angle = np.deg2rad(25)
vertices = np.array([
[0, np.cos(interior_angle), np.cos(interior_angle), 0],
[0, 0, np.sin(interior_angle), 0],
])
The last vertex is a repeat of the origin to make plotting easier.
Now let's look at the rotation. For rotation_angle = np.deg2rad(30), point (x, y) rotates to (np.cos(rotation_angle) * x - np.sin(rotation_angle) * y, np.sin(rotation_angle) * x + np.cos(rotation_angle) * y). This can be expressed as a matrix equation:
rotation_matrix = np.array([
[np.cos(rotation_angle), -np.sin(rotation_angle)],
[np.sin(rotation_angle), np.cos(rotation_angle)]])
p_out = rotation_matrix # p_in
The array vertices is so constructed that it can be directly multiplied by a rotation matrix. You can therefore write
rotation_angle = np.deg2rad(30)
rotation_matrix = np.array([
[np.cos(rotation_angle), -np.sin(rotation_angle)],
[np.sin(rotation_angle), np.cos(rotation_angle)]])
rotated_vertices = rotation_matrix # vertices
The plotted image should make more sense now:
plt.plot(*vertices)
plt.plot(*rotated_vertices)
plt.axis('equal')
plt.show()
I want to plot an ellipse that based on two foci f1 and f2 and a third point. But for some reason the orientation of my ellipse for the given angle I calculated is not displayed correctly.
Am I missing something basic? Why is bokeh not orienting the ellipse with the given angle of pi/4?
Here is my example code:
import numpy as np
from bokeh.io import output_notebook, show
from bokeh.plotting import figure
from bokeh.models import Range1d
output_notebook()
f1 = np.array([0,0])
f2 = np.array([3,3])
point = np.array([1,3])
tools = "hover, box_zoom, undo, crosshair"
p = figure(tools=tools)
p.circle(f1[0], f1[1], radius = 0.05, alpha=0.5)
p.circle(f2[0], f2[1], radius = 0.05, alpha=0.5)
p.circle(point[0], point[1], radius = 0.05, color='red')
center = (f2 - f1)/2 + f1
print(center)
angle = np.arctan2((f2 - f1)[1], (f2 - f1)[0])
print(np.rad2deg(angle))
# minor axis
a = (np.linalg.norm(f2-point) + np.linalg.norm(f1-point))/2
# major axis
c = np.linalg.norm((f2 - f1)/2)
# b = np.sqrt(a**2 - c**2)/2
b = 0.2
p.circle(center[0], center[1], radius = 0.05, color='orange')
p.ellipse(x=center[0], y=center[1], width=2*a, height=2*b, angle=angle, fill_color="yellow", fill_alpha = 0.4)
p.x_range = Range1d(-2, 4)
p.y_range = Range1d(-2, 4)
show(p)
It's a pretty hard problem that has not yet been solved: https://github.com/bokeh/bokeh/issues/7965
tl;dr: the angle basically uses screen pixel ratio that doesn't correspond to the data range ratio. In your particular case, it's the toolbar and the axes that "squish" the main plot area. If you remove them, then the area will be perfectly square and the angle will be correct.
I had a similar problem and solved it by changing the axis ranges. It does not make any sense, but for this example, the ellipse is aligned when extending the y_range like that:
from
p.y_range = Range1d(-2, 4)
to
p.y_range = Range1d(-2, 4.35)
I am trying to draw circles at a given geographical coordinate with a certain radius using cartopy. I want to draw using an orthographic projection, which is centred at the centre of the circle.
I use the following python code for testing:
import numpy as np
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
# example: draw circle with 45 degree radius around the North pole
lon = 0
lat = 90
r = 45
# find map ranges (with 5 degree margin)
minLon = lon - r - 5
maxLon = lon + r + 5
minLat = lat - r - 5
maxLat = lat + r + 5
# define image properties
width = 800
height = 800
dpi = 96
resolution = '50m'
# create figure
fig = plt.figure(figsize=(width / dpi, height / dpi), dpi=dpi)
ax = fig.add_subplot(1, 1, 1, projection=ccrs.Orthographic(central_longitude=lon, central_latitude=lat))
ax.set_extent([minLon, maxLon, minLat, maxLat])
ax.imshow(np.tile(np.array([[cfeature.COLORS['water'] * 255]], dtype=np.uint8), [2, 2, 1]), origin='upper', transform=ccrs.PlateCarree(), extent=[-180, 180, -180, 180])
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', resolution, edgecolor='black', facecolor=cfeature.COLORS['land']))
ax.add_feature(cfeature.NaturalEarthFeature('cultural', 'admin_0_countries', resolution, edgecolor='black', facecolor='none'))
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'lakes', resolution, edgecolor='none', facecolor=cfeature.COLORS['water']), alpha=0.5)
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'rivers_lake_centerlines', resolution, edgecolor=cfeature.COLORS['water'], facecolor='none'))
ax.add_feature(cfeature.NaturalEarthFeature('cultural', 'admin_1_states_provinces_lines', resolution, edgecolor='gray', facecolor='none'))
ax.add_patch(mpatches.Circle(xy=[lon, lat], radius=r, color='red', alpha=0.3, transform=ccrs.PlateCarree(), zorder=30))
fig.tight_layout()
plt.savefig('CircleTest.png', dpi=dpi)
plt.show()
I get a correct result at the equator (set lat to 0 in example above):
But when I move towards a pole the shape is distorted (lat = 45):
At the pole I only see one quarter of the circle:
I would expect to always see a perfect circle in orthographic projection, if the view is centred correctly. I also tried to use a different transform in the add_patch method, but then the circle completely vanishes!
You approach of defining the circle in PlateCarree coordinates is not going to work, because this is a cartesian projection and a circle drawn on it is not necessarily circular in spherical geometry (unless the circle is at (0, 0) as you saw).
Since you want the result to be circular in the Orthographic projection, you could draw the circle in native coordinates. This requires first defining your Orthographic projection centred on the centre of your circle, then computing what the radius of the circle (which you specify in degrees) would be in projection coordinates (distance from the centre of the projection). Doing it this way is convenient because it also gives you a neat way of determining the correct map extents. The example below computes the radius in orthographic coordinates by transforming a point 45 degrees north (or south if more convenient) away from the centre of the projection and gives the following:
The full code is below:
import numpy as np
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
# example: draw circle with 45 degree radius around the North pole
lat = 51.4198101
lon = -0.950854653584
r = 45
# Define the projection used to display the circle:
proj = ccrs.Orthographic(central_longitude=lon, central_latitude=lat)
def compute_radius(ortho, radius_degrees):
phi1 = lat + radius_degrees if lat <= 0 else lat - radius_degrees
_, y1 = ortho.transform_point(lon, phi1, ccrs.PlateCarree())
return abs(y1)
# Compute the required radius in projection native coordinates:
r_ortho = compute_radius(proj, r)
# We can now compute the correct plot extents to have padding in degrees:
pad_radius = compute_radius(proj, r + 5)
# define image properties
width = 800
height = 800
dpi = 96
resolution = '50m'
# create figure
fig = plt.figure(figsize=(width / dpi, height / dpi), dpi=dpi)
ax = fig.add_subplot(1, 1, 1, projection=proj)
# Deliberately avoiding set_extent because it has some odd behaviour that causes
# errors for this case. However, since we already know our extents in native
# coordinates we can just use the lower-level set_xlim/set_ylim safely.
ax.set_xlim([-pad_radius, pad_radius])
ax.set_ylim([-pad_radius, pad_radius])
ax.imshow(np.tile(np.array([[cfeature.COLORS['water'] * 255]], dtype=np.uint8), [2, 2, 1]), origin='upper', transform=ccrs.PlateCarree(), extent=[-180, 180, -180, 180])
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', resolution, edgecolor='black', facecolor=cfeature.COLORS['land']))
ax.add_feature(cfeature.NaturalEarthFeature('cultural', 'admin_0_countries', resolution, edgecolor='black', facecolor='none'))
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'lakes', resolution, edgecolor='none', facecolor=cfeature.COLORS['water']), alpha=0.5)
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'rivers_lake_centerlines', resolution, edgecolor=cfeature.COLORS['water'], facecolor='none'))
ax.add_feature(cfeature.NaturalEarthFeature('cultural', 'admin_1_states_provinces_lines', resolution, edgecolor='gray', facecolor='none'))
ax.add_patch(mpatches.Circle(xy=[lon, lat], radius=r_ortho, color='red', alpha=0.3, transform=proj, zorder=30))
fig.tight_layout()
plt.savefig('CircleTest.png', dpi=dpi)
plt.show()
This might be a little late, but there is a convient function in Cartopy for this.
We can use Cartopy's .circle function (documentation) to generate a ring of points with a specified radius from a particular (longitude & latitude) in the Geodesic coordinate frame and then plot a polygon with those points using Shapely.
This would look something like the following
circle_points = cartopy.geodesic.Geodesic().circle(lon=lon, lat=lat, radius=radius_in_meters, n_samples=n_points, endpoint=False)
geom = shapely.geometry.Polygon(circle_points)
ax.add_geometries((geom,), crs=cartopy.crs.PlateCarree(), facecolor='red', edgecolor='none', linewidth=0)
Specifying the crs as PlateCarree does not matter and merely avoids a warning with Shapely. You will keep your desired projection. However, if you are plotting directly with the circle center on the pole, you might still have an issue and may need to do some fancy transformations (Haven't tested it recently, but recall from a few months ago it being a little wonky).
You could also manually compute these points using the pyproj library Cartopy makes use of, specifically the Geod class. Pick a point with a radius and loop through the azmoths for however fine you want your circle to be with the .inv or .fwd function similar to the suggestion in https://stackoverflow.com/a/57002776/2430454. I don't recommend this method, but used it a long while back to accomplish the same thing.
I want to superimpose Circles on top of a 2D plot in Matplotlib as position markers. Currently they look very much like what they are, colored Circles:
import matplotlib.pyplot as plt
plt.axes()
circle = plt.Circle((0, 0), radius=0.3,fc='b')
circle1 = plt.Circle((1, 1), radius=0.3, fc='y')
circle2 = plt.Circle((1, 0), radius=0.3, fc='r')
plt.gca().add_patch(circle)
plt.gca().add_patch(circle1)
plt.gca().add_patch(circle2)
plt.axis('scaled')
plt.show()
Is there a way to give theses circles the appearance of a 3D object without using mayavi?
These are examples of my goal:
EDIT
With the information in the link supplied by user3419537 and the idea presented here Custom color maps i created the following idea, that lets me somehow plot circles filled with a gradient:
import numpy as np
import matplotlib.colors as mcolors
def make_colormap(seq):
"""Return a LinearSegmentedColormap
seq: a sequence of floats and RGB-tuples. The floats should be increasing
and in the interval (0,1).
"""
seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3]
cdict = {'red': [], 'green': [], 'blue': []}
for i, item in enumerate(seq):
if isinstance(item, float):
r1, g1, b1 = seq[i - 1]
r2, g2, b2 = seq[i + 1]
cdict['red'].append([item, r1, r2])
cdict['green'].append([item, g1, g2])
cdict['blue'].append([item, b1, b2])
return mcolors.LinearSegmentedColormap('CustomMap', cdict)
def gauplot(centers, radiuses, xr=None, yr=None, P_color='black'):
c = mcolors.ColorConverter().to_rgb
# Maybe it is possible to change the values to get a better gradient?
current_cmap = make_colormap([c(P_color),0.05,c(P_color),0.1,c(P_color), c('white')])
nx, ny = 1000.,1000.
xgrid, ygrid = np.mgrid[xr[0]:xr[1]:(xr[1]-xr[0])/nx,yr[0]:yr[1]:(yr[1]-yr[0])/ny]
im = xgrid*0 + np.nan
xs = np.array([np.nan])
ys = np.array([np.nan])
fis = np.concatenate((np.linspace(-np.pi,np.pi,100), [np.nan]) )
#cmap = plt.cm.gray
cmap = current_cmap
cmap.set_bad('white')
thresh = 2.8
for curcen,currad in zip(centers,radiuses):
curim=(((xgrid-curcen[0])**2+(ygrid-curcen[1])**2)**.5)/currad*thresh
im[curim<thresh]=np.exp(-.5*curim**2)[curim<thresh]
xs = np.append(xs, curcen[0] + currad * np.cos(fis))
ys = np.append(ys, curcen[1] + currad * np.sin(fis))
plt.imshow(im.T, cmap=cmap, extent=xr+yr)
plt.plot(xs, ys, 'r-')
gauplot([(0,0), (2,3), (5,1), (6, 7), (6.1, 6.1)], [.3,.4, .5, 1, .4], [-1,10], [-1,10],P_color="#75507b")
plt.show()
Unfortunately only the red circles appear at the right position:
I would appreciate a tip what might be the cause of this.
At the moment i am plotting my circles in the script in question like this:
circle = Circle(x,y,*kwargs)
plt.gca().add_patch(circle)
would it be possible to adapt the above solution in a way that it can plot circles with different colors at the position (x,y) and superimpose them to an existing plot as well?
The imshow plot is mirrored against the desired plot. This is because the imshow plot has its origin in the upper left corner, but the plot to show the circles has the origin in the lower left corner.
The solution would be to set the origin of the imshow in the lower left corner
plt.imshow(im.T, cmap=cmap, extent=xr+yr, origin="lower")