Animated arrows in matplotlib gives IndexError - python

I was trying to animate arrow and used the script from the following answer. Plus my animation has scatter-points and text too.
This is the script that I am using:-
from my_func import Pitch
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
## Pitch for plotting the football-pitch in the background
pitch = Pitch(line_color='grey', pitch_color='#121212', orientation='horizontal')
fig, ax = pitch.create_pitch()
x_start, y_start = (50, 35)
x_end, y_end = (90, 45)
x_1, y_1, x_2, y_2 = 50.55, 35.1375, 89.45, 44.8625
x = np.linspace(x_1, x_2, 20)
y = np.linspace(y_1, y_2, 20)
sc_1 = ax.scatter([], [], color="crimson", zorder=4, s=150, alpha=0.7, edgecolor="w")
sc_2 = ax.scatter([], [], color="crimson", zorder=4, s=150, alpha=0.7, edgecolor="w")
title = ax.text(50, 65, "", bbox={'facecolor':'w', 'alpha':0.5, 'pad':5}, ha="center")
def animate(i):
if i == 1:
sc_1.set_offsets([x_start, y_start])
title.set_text("Start of Action 01")
if i == 2:
plt.pause(0.5)
title.set_text("Action 02")
## plot arrow
## haven't included ax.cla() as it was removing the pitch in the background
if i <= len(x):
patch = plt.Arrow(x_start, y_start, x[i] - x_start, y[i] - y_start)
ax.add_patch(patch)
## plot scatter point
if i > len(x):
plt.pause(0.2)
title.set_text("Action 03")
sc_2.set_offsets([x_end, y_end])
return sc_1, patch, sc_2, title,
ani = animation.FuncAnimation(
fig=fig, func=animate, interval=50, blit=True)
plt.show()
The result is showing a little bit of animation and then giving me the following error:
File "test.py", line 33, in animate
patch = plt.Arrow(x_start, y_start, x[i] - x_start, y[i] - y_start)
IndexError: index 20 is out of bounds for axis 0 with size 20
[1] 14116 abort (core dumped) python test.py
I am a begineer in matplotlib animations and don't know how to solve this error what should I change in my code to remove the error and generate the animated output.

The modifications are as follows: 1) plt.axes() is added to ax. 2) The return value of the animation function is set to ax. 3) The number of objects to be animated is 20, so frames=20 is used.
# from my_func import Pitch
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
## Pitch for plotting the football-pitch in the background
# pitch = Pitch(line_color='grey', pitch_color='#121212', orientation='horizontal')
# fig, ax = pitch.create_pitch()
fig,ax = plt.subplots()
x_start, y_start = 50, 35
x_end, y_end = 90, 45
x_1, y_1, x_2, y_2 = 50.55, 35.1375, 89.45, 44.8625
x = np.linspace(x_1, x_2, 20)
y = np.linspace(y_1, y_2, 20)
ax = plt.axes(xlim=(50, 90), ylim=(35, 45))
sc_1 = ax.scatter([], [], color="crimson", zorder=1, s=150, alpha=0.7, edgecolor="w")
sc_2 = ax.scatter([], [], color="crimson", zorder=1, s=150, alpha=0.7, edgecolor="w")
title = ax.text(50, 65, "", bbox={'facecolor':'w', 'alpha':0.5, 'pad':5}, ha="center")
def animate(i):
if i == 1:
sc_1.set_offsets([x_start, y_start])
title.set_text("Start of Action 01")
if i == 2:
plt.pause(0.5)
title.set_text("Action 02")
## plot arrow
## haven't included ax.cla() as it was removing the pitch in the background
if i <= len(x):
ax.clear()
#patch = plt.Arrow(x_start, y_start, x[i] - x_start, y[i] - y_start)
#ax.add_patch(patch)
ax.arrow(x_start, y_start, x[i]-x_start, y[i]-y_start, head_width=1, head_length=1, fc='black', ec='black')
ax.set(xlim=(50,90), ylim=(35,45))
## plot scatter point
if i == len(x)-1:
plt.pause(0.2)
title.set_text("Action 03")
sc_2.set_offsets([x_end, y_end])
return sc_1, sc_2, title, ax
ani = animation.FuncAnimation(fig=fig, func=animate, frames=20, interval=200, repeat=False, blit=True)
plt.show()

Related

matplotlib - How to get the Y value for each axis if I have the X value from an event for one axis

I have typed a class for the matplotlib chart markers, everything works fine if I add markers to specific axes. But I want to add markers to all axes at once. Then I don't know how to get the Y value for the axis other than the one I clicked on.
class MplMarkers():
def __init__(self, fig, enable=None):
self.mpl_fig = fig
self.mpl_markers = []
self.mpl_markers_event = []
self.mpl_bbox_props = dict(boxstyle="round,pad=0.5", fc="w", ec="k", lw=1)
if enable:
self.enable()
def add(self, event):
if event.inaxes:
# add marker for current ax ######################
if event.button is MouseButton.RIGHT and event.key is None:
an = event.inaxes.annotate(bbox=self.mpl_bbox_props, text=' X={}\n Y={:4f}'.format(matplotlib.dates.num2date(event.xdata, tz=None).strftime("%H:%M:%S.%f"), event.ydata), xy=(event.xdata,0.98),
color='black', fontsize=8, alpha=0.5, xycoords=('data', 'axes fraction'), annotation_clip=False, verticalalignment='top', horizontalalignment='left' , rotation = 0)
vl = event.inaxes.axvline(x=event.xdata, linewidth=1.0, alpha=0.5, color='b', label='M-' + str(event.xdata))
self.mpl_markers.append({'x': event.xdata, 'y':event.ydata, 'an_cid':an, 'vl_cid':vl})
event.inaxes.figure.canvas.draw_idle()
# add marker for all ax ######################
if event.button is MouseButton.RIGHT and event.key == 'shift':
if len(self.mpl_fig.axes) > 1:
for a in self.mpl_fig.axes:
an = a.annotate(bbox=self.mpl_bbox_props, text=' X={}\n Y={:4f}'.format(matplotlib.dates.num2date(event.xdata, tz=None).strftime("%H:%M:%S.%f"), event.ydata), xy=(event.xdata,0.98),
color='black', fontsize=8, alpha=0.5, xycoords=('data', 'axes fraction'), annotation_clip=False, verticalalignment='top', horizontalalignment='left' , rotation = 0)
vl = a.axvline(x=event.xdata, linewidth=1.0, alpha=0.5, color='b', label='M-' + str(event.xdata))
self.mpl_markers.append({'x': event.xdata, 'y':event.ydata, 'an_cid':an, 'vl_cid':vl})
a.figure.canvas.draw_idle()
def remove(self, event):
if event.inaxes:
# remove all markers ##################
if event.button is MouseButton.RIGHT and event.key == 'control':
for m in self.mpl_markers:
m['an_cid'].remove()
m['vl_cid'].remove()
del m
self.mpl_markers=[]
for a in self.mpl_fig.axes:
a.figure.canvas.draw_idle()
def enable(self):
self.mpl_markers_event.append(self.mpl_fig.canvas.mpl_connect('button_press_event', self.add))
self.mpl_markers_event.append(self.mpl_fig.canvas.mpl_connect('button_press_event', self.remove))
def disable(self):
if self.mpl_markers_event:
for me in self.mpl_markers_event:
self.mpl_fig.canvas.mpl_disconnect(me)
How can I get the Y value for the rest of the axes?
In the code part - "# add marker for all ax ######################"
In this form, I get the Y value of the clicked axis on all axes
screenshot

Python plt.show shows a blank figure

I'm trying to figure out why an extra blank figure is shown using this code, there should be only two figures but three are shown where one is empty. Here is an image showing what happens:
Here is the code I am using:
f1 = plt.figure(1)
img=mpimg.imread(image_path)
imgplot = plt.imshow(img)
original_image = Image.open(image_path)
bw_image = original_image.convert('1', dither=Image.NONE)
bw_image_array = np.array(bw_image, dtype=np.int)
black_indices = np.argwhere(bw_image_array == 0)
chosen_black_indices = black_indices[np.random.choice(black_indices.shape[0], replace=False, size=3000)]
distances = pdist(chosen_black_indices)
distance_matrix = squareform(distances)
optimized_path = solve_tsp(distance_matrix)
optimized_path_points = [chosen_black_indices[x] for x in optimized_path]
f2 = plt.figure(2)
plt.xlim(0, 600)
plt.ylim(0, 800)
plt.gca().invert_yaxis()
plt.xticks([])
plt.yticks([])
xstartdata = optimized_path_points[0][0]
ystartdata = optimized_path_points[0][1]
fig, ax = plt.subplots()
ln, = plt.plot([], [], color='black', marker='o', linestyle='dashed', markersize=1, animated=True)
plt.plot([xstartdata], [ystartdata], color='black', marker='o', linestyle='dashed', markersize=1, lw=0.1)
plt.gca().invert_yaxis()
x = []
y = []
def init():
ln.set_data([],[])
return ln,
def animate(i):
x.append(optimized_path_points[i][1])
y.append(optimized_path_points[i][0])
ln.set_data(x, y)
plt.plot(x , y , lw=0.1,animated=True)
return ln,
anim = animation.FuncAnimation(fig, animate, init_func=init,frames=range(len(optimized_path_points)), interval=5, blit=True)
plt.show()

Animated Scatter Plot

I would like to do an animation in python representing each point, dot by dot. I am doing it as I always have done it, but it does not work. Any help?
I tried 2 different ways.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
%matplotlib nbagg
def mcd(a, b):
resto = 0
while(b > 0):
resto = b
b = a % b
a = resto
return a
N = 1200
n = list (range (N))
an = [1,1]
for i in range (2,N):
k = i-1
if mcd (n[i], an[k]) == 1:
an.append (n[i] + 1 + an[k])
else:
an.append (an[k]/mcd (n[i], an[k]))
fig = plt.figure ()
ax = fig.add_subplot (111)
ax.grid (True)
ax.set_xlim(0, N*1.1)
pt, = ax.plot ([],[],'ko', markersize=2)
ax.plot (n,an, 'ko', markersize=2)
def init ():
pt.set_data([],[])
return (pt)
def animate (i,pt):
pt.set_data (n[:i],an[:i])
return (pt)
ani = FuncAnimation (fig, animate, fargs = (pt), frames=N, init_func=init, interval=50, blit = True)
plt.show ()
And the second way:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
%matplotlib nbagg
def mcd(a, b):
resto = 0
while(b > 0):
resto = b
b = a % b
a = resto
return a
N = 1200
n = list (range (N))
an = [1,1]
for i in range (2,N):
k = i-1
if mcd (n[i], an[k]) == 1:
an.append (n[i] + 1 + an[k])
else:
an.append (an[k]/mcd (n[i], an[k]))
xdata, ydata = [],[]
fig = plt.figure ()
ax = fig.add_subplot(111)
ax.grid (True)
pt, = ax.plot ([],[],'ko', markersize=2)
ax.plot (n,an, 'ko', markersize=2)
def init ():
ax.set_xlim(0, N*1.1)
pt.set_data([],[])
return (pt)
def animate (pt):
xdata.append (n[i])
ydata.append (an[i])
pt.set_data (xdata,ydata)
return (pt)
ani = FuncAnimation (fig, animate, fargs = (pt), frames=N, init_func=init, interval=50, blit = True)
plt.show ()
Using those codes, I get the entire figure with all the points. I would like to fill the graph point by point in an animated way.
The following will work
%matplotlib nbagg
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def mcd(a, b):
resto = 0
while(b > 0):
resto = b
b = a % b
a = resto
return a
N = 1200
n = list (range (N))
an = [1,1]
for i in range (2,N):
k = i-1
if mcd (n[i], an[k]) == 1:
an.append (n[i] + 1 + an[k])
else:
an.append (an[k]/mcd (n[i], an[k]))
fig = plt.figure ()
ax = fig.add_subplot (111)
ax.grid (True)
ax.set_xlim(0, N*1.1)
ax.set_ylim(min(an), max(an))
pt, = ax.plot([],[],'ko', markersize=2)
def init ():
pt.set_data([], [])
return pt,
def animate(i):
pt.set_data (n[:i], an[:i])
return pt,
ani = FuncAnimation (fig, animate, frames=N, init_func=init, interval=50, blit = True)
plt.show ()
EDIT: I tested it in normal Python Shell and it draws black dots on red dots but Jupyter draws black dots hidden behind red dots so it needs these lines in different order - first red dots, next empty plot for black dots.
ax.plot(n, an, 'ro', markersize=2) # red dots
pt, = ax.plot([], [], 'ko', markersize=2)
First: I get error message
TypeError: 'Line2D' object is not iterable.
All because () doesn't create tuple - you have to use comma , to create tuple in
return pt, and fargs=(pt,)
Problem is because you draw all point at start using
ax.plot(n, an, 'ko', markersize=2)
and later it draws dots in the same places so you don't see animation.
If you use different color - ie. red
ax.plot(n, an, 'ro', markersize=2)
then you will see animation of black points on red points.
Or remove this line and it will draw dots in empty window.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
#%matplotlib nbagg
def mcd(a, b):
resto = 0
while(b > 0):
resto = b
b = a % b
a = resto
return a
N = 1200
n = list(range(N))
an = [1, 1]
for i in range(2, N):
k = i-1
if mcd(n[i], an[k]) == 1:
an.append(n[i] + 1 + an[k])
else:
an.append(an[k]/mcd(n[i], an[k]))
fig = plt.figure()
ax = fig.add_subplot(111)
ax.grid(True)
ax.set_xlim(0, N*1.1)
pt, = ax.plot([], [], 'ko', markersize=2)
ax.plot(n, an, 'ro', markersize=2) # red dots
def init():
pt.set_data([], [])
return (pt,)
def animate(i, pt):
pt.set_data(n[:i], an[:i])
return (pt,)
ani = FuncAnimation(fig, animate, fargs=(pt,), frames=N, init_func=init, interval=50, blit=True)
plt.show ()
In second code you have the same problems and you also forgot i in def animate(i, pt):
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
#%matplotlib nbagg
def mcd(a, b):
resto = 0
while(b > 0):
resto = b
b = a % b
a = resto
return a
N = 1200
n = list(range (N))
an = [1, 1]
for i in range(2, N):
k = i-1
if mcd(n[i], an[k]) == 1:
an.append(n[i] + 1 + an[k])
else:
an.append(an[k]/mcd (n[i], an[k]))
xdata, ydata = [], []
fig = plt.figure()
ax = fig.add_subplot(111)
ax.grid(True)
pt, = ax.plot([], [], 'ko', markersize=2)
ax.plot(n, an, 'ro', markersize=2)
def init():
ax.set_xlim(0, N*1.1)
pt.set_data([], [])
return (pt,)
def animate(i, pt):
xdata.append(n[i])
ydata.append(an[i])
pt.set_data(xdata, ydata)
return (pt,)
ani = FuncAnimation(fig, animate, fargs=(pt,), frames=N, init_func=init, interval=50, blit=True)
plt.show ()

update matplotlib path interactively on figure and extract actual data for recalculation

I have a plot where I want to interactively adjust the position of a patch and use the interactive update to be used in recalculating a variable.
I have plotted and image and added a closed contour (represented using PathPatch)
Here is an example image. example image with contour where information is calculated from the pixels enclosed by the contour. The pixel mask needs to be updated so that plots on right can also be updated.
Currently I plot everything and then use an event to move the contour, I tried using transforms and this worked to move the contour but when I tried using the transform to update the binary mask needed to recalculate the plots on the right side, the transformation it returns is incorrect. On top of this the transforms caused the contour to move when zooming in and out from the interactive window.
I need to figure out how to shift the contour and re-plot it and then extract the shift and apply to extracting a new mask of enclosed points to update the calculation.
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.path as mplpath
import matplotlib.patches as patches
from matplotlib.widgets import Button, Slider
# GENERATE SOME DATA TO PLOT
# event functions
def press(event):
print('press', event.key)
sys.stdout.flush()
if (event.key == 'left'):
shift[0] -= 1.0
elif (event.key == 'right'):
shift[0] += 1.0
elif (event.key == 'up'):
shift[1] += 1
elif (event.key == "down"):
shift[1] -= 1.0
# this is where I don't know how to properly transform and update[!
fig.canvas.draw()
class Recalculate(object):
def update(self, event):
x_update = [x_ for x_ in x_pts]
y_update = [y_ for y_ in y_pts]
verts_test = np.column_stack((x_update, y_update))
new_contour = mplpath.Path(verts_test, closed=True)
# create mask
new_grid = getMask(im_vol_resize, new_contour)
# calculate the flow
q_sum = []
for i in range(N): #assume size doesn't change
q_mask_sum = np.sum(test_q[:,:,i]*new_grid.astype(image_dtype))
q_sum.append(q_mask_sum*np.prod(spacing[0:2])/(100.0*scale**2)) # cm^3
print("total flow {0} cm^3/beat".format(np.trapz( q_sum, dx = np.mean(mean_diff)/1000)))
q_sum_roll = np.roll(q_sum, -16)
#q_interp = interpolate.CubicSpline(new_time, q_sum_roll, bc_type='periodic')
q_interp = interpolate.InterpolatedUnivariateSpline(new_time, q_sum_roll, k=3, ext=0)
q_test = q_interp(t_interp)
#plotting update
grid_handle.set_data(new_grid)
#print(new_time)
scat_data = np.stack((new_time, q_sum), axis=1)
#ax2.relim() # make sure all the data fits
#ax2.autoscale()
#print(scat_data.shape)
scatter_handle.set_offsets(scat_data)
interp_handle[-1].set_ydata(q_test)
ax3.relim() # make sure all the data fits
ax3.autoscale()
ax2.set_xlim(ax3.get_xlim())
ax2.set_ylim(ax3.get_ylim())
fig.canvas.draw()
def update_cine(val):
ind = int(slider.val)
s = [slice(ind, ind + 1) if i == 2 else slice(None)
for i in range(3)]
for sd in xyz_keys:
im = cubes[sd][s].squeeze()
cine_ax_dict[sd].set_data(im)
ax6.set_title("time: {0}".format(new_time[ind]))
ax2.scatter(new_time, q_sum, c='b', label='flowRate')
ax2.scatter(new_time[ind], q_sum[ind], c='r', label='time={0}'.format(new_time[ind]))
fig.canvas.draw()
#figures
gs = plt.GridSpec(6, 6, wspace=0.2, hspace=0.2)
fig = plt.figure(figsize=(17, 9))
fig.canvas.mpl_connect('key_press_event', press)
ax = fig.add_subplot(gs[3:,0:3])
#xl = ax.set_xlabel('easy come, easy go')
#ax.set_title('Press a key')
patch = patches.PathPatch(contour, facecolor=(1.0,165./255.0,0,0.25), lw=1 )#, alpha=0.5)
patch_handle = ax.add_patch(patch)
#plt.plot(x_pts, y_pts)
x_bounds = [round((bounds[0]-4)*scale), round((bounds[1]+4)*scale)]
y_bounds = [round((bounds[2]-4)*scale), round((bounds[3]+4)*scale)]
ax.set_xlim(x_bounds)
ax.set_ylim(y_bounds)
ax.imshow(im_vol_resize, interpolation='bilinear', cmap="gray", alpha=1.0)
ax5 = fig.add_subplot(gs[0:3,0:3])
patch2 = patches.PathPatch(contour, facecolor='none', linewidth=0.8, edgecolor=(1.0,165./255.0,0,0.50))#, alpha=0.5)
ax5.imshow(im_vol_resize, interpolation='bilinear', cmap="gray", alpha=1.0)
patch_handle2 = ax5.add_patch(patch2)
#new_contour = patch_.get_path()
ax5.set_xlim(x_bounds)
ax5.set_ylim(y_bounds)
# show ROI mask
grid_handle = ax.imshow(grid, interpolation='None', cmap="gray", alpha=0.1)
#print(dir(grid_handle))
ax2 = fig.add_subplot(gs[0:3, 4:])
scatter_handle = ax2.scatter(new_time, q_sum, c='b', label='flowRate')
ax2.scatter(new_time[0], q_sum[0], c='r', label='cine time')
#print(dir(scatter_handle))
#ax2.scatter(dict_time["y"], q_sum, label='flowRatey')
#ax2.scatter(dict_time["z"], q_sum, label='flowRatez')
#ax2.plot(dict_time["x"], q_sum)
#ax2.set_xlabel(r'$t$', fontsize=20)
ax2.set_title('terminal ICA Waveform', fontsize=20)
ax2.set_ylabel(r'Q, $Q(t)$ $cm^3/min$', fontsize=20)
ax2.xaxis.set_ticks_position('none')
ax2.xaxis.set_ticklabels([])
ax2.legend(loc='center left', bbox_to_anchor=(1, 0.9))
ax3 = fig.add_subplot(gs[3:, 4:])
ax3.set_xlabel(r'time $t$ $milliseconds$', fontsize=20)
ax3.set_ylabel(r'Flowrate $cm^3/min$', fontsize=20)
ax3.set_xlim(ax2.get_xlim())
ax3.set_ylim(ax2.get_ylim())
interp_handle = ax3.plot(t_interp, q_test, c='b', linestyle='-', label='interp')
#print(dir(interp_handle[0]))
ax3.legend(loc='center left', bbox_to_anchor=(1, 0.9))
cine_ax_dict = {}
ax6 = fig.add_subplot(gs[0:2,2:4])
ax6.set_title("time: {0}".format(new_time[0]))
cine_ax_dict["x"] = ax6.imshow(cubes['x'][:,:,0], interpolation='bilinear', cmap="viridis", alpha=1.0)
patch6 = patches.PathPatch(contour, facecolor='none', linewidth=0.8, edgecolor=(1.0,165./255.0,0,0.50))
ax6.add_patch(patch6)
ax6.set_xlim(x_bounds)
ax6.set_ylim(y_bounds)
ax7 = fig.add_subplot(gs[2:4,2:4])
cine_ax_dict["y"] = ax7.imshow(cubes['y'][:,:,0], interpolation='bilinear', cmap="viridis", alpha=1.0)
patch7 = patches.PathPatch(contour, facecolor='none', linewidth=0.8, edgecolor=(1.0,165./255.0,0,0.50))
ax7.add_patch(patch7)
ax7.set_xlim(x_bounds)
ax7.set_ylim(y_bounds)
ax8 = fig.add_subplot(gs[4:,2:4])
cine_ax_dict["z"] = ax8.imshow(cubes['z'][:,:,0], interpolation='bilinear', cmap="viridis", alpha=1.0)
patch8 = patches.PathPatch(contour, facecolor='none', linewidth=0.8, edgecolor=(1.0,165./255.0,0,0.50))
ax8.add_patch(patch8)
ax8.set_xlim(x_bounds)
ax8.set_ylim(y_bounds)
# define slider
axcolor = 'lightslategray'
ax_c = fig.add_axes([0.25, 0.05, 0.60, 0.03], aspect="auto", facecolor=axcolor)
slider = Slider(ax_c, 'Axis %i index' % 2, 0, cubes["x"].shape[2] - 1,
valinit=0, valfmt='%i')

How to draw only annotation instead of whole canvas in matplotlib?

The event.canvas.draw() seems to take a long time in below code, how can I just draw the annotation instead of the whole canvas?
import random
import matplotlib.pyplot as plt
def on_move(event):
if event.inaxes is not None:
x = event.xdata
y = event.ydata
annotation.xy = (x, y)
annotation.set_text("Test")
event.canvas.draw()
fig = plt.figure()
ax = plt.gca()
ax.plot(random.sample(xrange(100000), 50000), random.sample(xrange(100000), 50000), 'bo')
ax.plot(random.sample(xrange(100000), 50000), random.sample(xrange(100000), 50000), 'go')
annotation = ax.annotate(
'',
xy=(0,0),
xycoords='data',
ha = 'right',
xytext = (-20, 20),
textcoords = 'offset points',
va = 'bottom',
bbox = dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.75),
arrowprops = dict(
arrowstyle='->',
connectionstyle='arc3,rad=0',
relpos=(1., 0.))
)
fig.canvas.mpl_connect('motion_notify_event', on_move)
plt.show()

Categories

Resources