Going from 2 Axes to 1? - python

How can I go down to one plot (axes) without getting the error "'AxesSubplot' object is not subscriptable" I tried changing this line <self.fig, axes = plt.subplots(2, 1, figsize=(11,7))> to <self.fig, axes = plt.subplots(1, 1, figsize=(11,7))>.....but I got the error.
Any help would be truly appreciated.
Just some background, this is a live EEG (Biosensor) Stream.
#############3 Create matplotlib plots
#self.figure = Figure() ???
self.fig, axes = plt.subplots(2, 1, figsize=(11,7)) #2 or 1?
prop_cycle = plt.rcParams['axes.prop_cycle'] #CHANGED
colors = prop_cycle.by_key()['color']
# Set the Axes Instance
# Add brainflow waveform and FFT
self.wave_ax = axes[0] #Location 1
# Set titles
self.wave_ax.set_title("Cyton Waveform") #Title
# Create line objects whose data we update in the animation
self.lines = [x[0] for x in
[ax.plot(self.data, self.data, color=colors[i]) for i,ax in enumerate(axes)]]
# Start animation
self.fig.tight_layout() #Creates spaces between titles :)
self.ani = matplotlib.animation.FuncAnimation(self.fig, self.updateFig,
interval=5, blit=True) #Animation
# Create list of objects that get animated
self.ani_objects = [self.wave_ax]
################################################# Animation helpers
def updateVars(self): #THIS IS WHERE YOU PICK THE FIRST CHANNEL
### What's the latest?? # Using the first channel
all_data = self.board.get_current_board_data(self.display_window)
self.curData = all_data[self.board_channel, :]
if (len(self.curData) < self.display_window):
self.data = self.curData
return
################################## Animation function
def updateFig(self, *args):
now = time.time()
# Update data from the brainflow
self.updateVars()
if (len(self.data) == self.display_window):
### Update plots with new data
self.lines[0].set_data(self.waveX, self.data)
else: # Set filler waveform data if the buffer hasn't filled yet
self.lines[0].set_data(list(range(len(self.data))), self.data)
## Reset limits of the waveform plot so it looks nice
self.wave_ax.relim()
self.wave_ax.autoscale_view(tight=True)
self.propagateChanges()
return self.ani_objects
def propagateChanges(self):
self.fig.stale = True #'stale' and needs to be re-drawn for the output to match the internal state
self.fig.canvas.draw() # Redraw the current figure. This is used to update a figure that has been altered, but not automatically re-drawn
self.fig.canvas.flush_events() # Flush the GUI events for the figure.
global stream
'''
i = 0
while i < 5:
print("starting loop")
i += 1
'''
try:
stream = CytonStream()
plt.show()
#except KeyboardInterrupt: # Dont understand incentive behind this???
# print('\nKeyboard interrupted, ending program')
# stream.board.release_session()
except Exception as e:
print("other exception,", e)
time.sleep(1)

When you can self.fig, axes = plt.subplots(2, 1, figsize=(11,7)), axes will be a numpy array of subplots that you can index.
When you call self.fig, axes = plt.subplots(1, 1, figsize=(11,7)), axes will be a single subplot that you can not index.
f1, a1 = plt.subplots(1, 1, figsize=(11,7))
f2, a2 = plt.subplots(2, 1, figsize=(11,7))
print(type(a1)) # <class 'matplotlib.axes._subplots.AxesSubplot'>
print(type(a2)) # <class 'numpy.ndarray'>

Related

How to update python plot with new data?

I would like to update python plot once new data arrive. Below is my example using timer to simulate data change.
index = 0
class RepeatTimer(Timer):
def run(self):
while not self.finished.wait(self.interval):
self.function(*self.args, **self.kwargs)
def LoopArray():
global index
if index >= 4: index =0
ArrayList = [
[85.0805, 85.0625, 85.094, 85.043, 84.9655, 84.9725, 84.93],
[0.896385, 0.89645, 0.896315, 0.89622, 0.89579, 0.896005, 0.896535],
[1.19046, 1.191145, 1.19125, 1.19071, 1.19086, 1.19122, 1.190185],
[162.1485, 162.2275, 162.285, 162.305, 162.3965, 162.562, 162.7135],
]
Array = np.array(ArrayList[index],float)
fig = plt.figure()
ax = fig.subplots()
ax.plot(Array)
ax.set_title('Symbol' )
ax.set_ylabel('Price')
ax.legend()
ax.grid()
plt.show()
index = index +1
timer_main = RepeatTimer(3, LoopArray)
timer_main.start()
But I got only first array is displayed. Any advice or guidance on this would be greatly appreciated, Thanks.

Animating heatmap with python seaborn

This is driving me crazy. I'm trying to create an animation of a heatmap where every frame shows a different time stamp. I have created the data structures, imported ffmpeg, and I tried another package too (celluloid that seems very good). I tried an example with a simple line plot and it does animate. The result is always the same: I get the last frame, not an animation. Here is my code in case anyone can spot the error... Thank you in advance.
------------- Code --------------
from matplotlib import animation, rc
from IPython.display import HTML
data2 = df[['ProducerName', 'MemOccupied']]
data2 = data2.sort_index().sort_values('ProducerName', kind='mergesort')
data2 = data2[['MemOccupied']]
fig = plt.figure()
camera = Camera(fig)
df_heatmap = pd.DataFrame(data2)
df_heatmap = df_heatmap.dropna()
df_heatmap = df_heatmap.resample('1T').mean()
count = df_heatmap.count()
index_series = df_heatmap.index
def init():
#plt.clf()
ax = sns.heatmap(data2[data2.index == index_series[1]], cbar_kws={'label': 'Memory Occupied (GB)'}, cmap='Blues_r')
ax.set(ylabel=None)
#return (ax,)
def animate(i):
plt.clf()
ax = sns.heatmap(data2[data2.index == index_series[i]], cbar_kws={'label': 'Memory Occupied (GB)'}, cmap='Blues_r')
ax.set(ylabel=None)
#return (ax,)
anim = animation.FuncAnimation(fig = fig, func = animate, init_func=init, frames=count-1,repeat = True, interval = 200)
HTML(anim.to_html5_video())

problem embedding matplotlib graph in tkinter

I'm trying to embed an animation graph into my GUI, however, whenever I try what i've seen according to tutorials regarding the use of canvas, I get a blank tkinter screen.
No errors, nothing.
However, when I use plt.show, it works fine but its not confined to the GUI. Which is the problem.
Its probably a quick fix, but this is what I have.
Any help would be appreciated!
class popupWindowOscil(tk.Frame):
def __init__(self,master,ser):
OscilTop= self.OscilTop= Toplevel(master)
tk.Frame.__init__(self)
self.ser = ser
self.fig = plt.figure()
self.ax = self.fig.add_subplot(1, 1, 1)
self.xs = []
self.ys = []
self.xval =0
self.OscilLoop()
def OscilLoop(self):
ani = animation.FuncAnimation(self.fig, self.Oscilliscope, fargs=(self.xs, self.ys))
#self.canvas = FigureCanvasTkAgg(self.fig, self)
#self.canvas.draw()
#self.canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
plt.show()
return
# The function that creates the values for the plot.
def Oscilliscope(self,i,xs,ys):
tryagain =1
while tryagain == 1:
try:
reading = self.ser.readline().decode()
tryagain = 0
except UnicodeDecodeError:
pass
Incominglist = str(reading).split(",")
try:
numbers = [float(x) for x in Incominglist]
except ValueError:
print ('Failure during string decode, Restart and Try again')
return
# Add x and y to lists
self.ys.extend(numbers)
for val in range(len(Incominglist)):
if self.xval == 0 and val ==0:
self.xs.append(self.xval) # or any arbitrary update to your figure's data
else:
self.xval += 0.005
self.xs.append(self.xval)
# Draw x and y lists
self.ax.clear()
self.ax.plot(self.xs, self.ys)
# Format plot
self.ax.yaxis.set_ticks(np.arange(0,5,0.25))
plt.subplots_adjust(bottom=0.30)

Using a matplotlib button to alternate/switch between plots I created

So I've created several charts using the matplotlib library in python 3.5, but I want to be able to have the flexibility to utilize a button to alternate between the views I created within a single window. I've been trying to experiment with an example here, but have not succeeded in doing so. I was curious in how to have the flexibility to click through different views that I created.
My code is sort of organized like this:
def plot1(data1, 'name1'):
...
ax.plot(x,y)
plt.draw()
def plot2(data2, 'name2'):
...
ax2.plot(x,y)
plt.draw()
def plot3(data3, 'name3'):
...
ax3.plot(x,y)
plt.draw()
plot1(data1,'name1')
plot2(data2,'name2')
plot3(data3,'name3')
plt.show()
Currently it will show up in three different windows. Now when I try to make this all into one view accessible via buttons, I'm unable to do so because quite frankly I'm unfamiliar with how to pass on the variables in my methods to create my desired subplots with the callback function. Is there a way to sort of structure my code to have them all run under one matplotlib window?
The following would be a class that uses the functions that you create. Those would not actually plot anything, but provide the required data. They should be put in a list called funcs, and when you click next or prev the corresponding graph would pop up. This should get you started.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.2)
x = range(-50,50)
y = range(-50,50)
l, = plt.plot(x, y, lw=2)
ax.title.set_text('y = x')
class Index(object):
ind = 0
global funcs # used so yu can access local list, funcs, here
def next(self, event):
self.ind += 1
i = self.ind %(len(funcs))
x,y,name = funcs[i]() # unpack tuple data
l.set_xdata(x) #set x value data
l.set_ydata(y) #set y value data
ax.title.set_text(name) # set title of graph
plt.draw()
def prev(self, event):
self.ind -= 1
i = self.ind %(len(funcs))
x,y, name = funcs[i]() #unpack tuple data
l.set_xdata(x) #set x value data
l.set_ydata(y) #set y value data
ax.title.set_text(name) #set title of graph
plt.draw()
def plot1():
x = range(-20,20)
y = x
name = "y = x"
return (x,y, name)
def plot2():
x = range(-20,20)
y = np.power(x, 2)
name = "y = x^2"
return (x,y,name)
def plot3():
x = range(-20,20) # sample data
y = np.power(x, 3)
name = "y = x^3"
return (x,y, name)
funcs = [plot1, plot2, plot3] # functions in a list so you can interate over
callback = Index()
axprev = plt.axes([0.7, 0.05, 0.1, 0.075])
axnext = plt.axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(callback.next)
bprev = Button(axprev, 'Previous')
bprev.on_clicked(callback.prev)
plt.show()

updating a Slider min - max range in runtime in matplotlib [duplicate]

I am trying to write a small bit of code that interactively deletes selected slices in an image series using matplotlib. I have created a button 'delete' which stores a number of indices to be deleted when the button 'update' is selected. However, I am currently unable to reset the range of my slider widget, i.e. removing the number of deleted slices from valmax. What is the pythonic solution to this problem?
Here is my code:
import dicom
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
frame = 0
#store indices of slices to be deleted
delete_list = []
def main():
data = np.random.rand(16,256,256)
nframes = data.shape[0]
raw_dicom_stack = []
for x in range (nframes):
raw_dicom_stack.append(data[x,:,:])
#yframe = 0
# Visualize it
viewer = VolumeViewer(raw_dicom_stack, nframes)
viewer.show()
class VolumeViewer(object):
def __init__(self, raw_dicom_stack, nframes):
global delete_list
self.raw_dicom_stack = raw_dicom_stack
self.nframes = nframes
self.delete_list = delete_list
# Setup the axes.
self.fig, self.ax = plt.subplots()
self.slider_ax = self.fig.add_axes([0.2, 0.03, 0.65, 0.03])
self.delete_ax = self.fig.add_axes([0.85,0.84,0.1,0.04])
self.update_ax = self.fig.add_axes([0.85,0.78,0.1,0.04])
self.register_ax = self.fig.add_axes([0.85,0.72,0.1,0.04])
self.add_ax = self.fig.add_axes([0.85,0.66,0.1,0.04])
# Make the slider
self.slider = Slider(self.slider_ax, 'Frame', 1, self.nframes,
valinit=1, valfmt='%1d/{}'.format(self.nframes))
self.slider.on_changed(self.update)
#Make the buttons
self.del_button = Button(self.delete_ax, 'Delete')
self.del_button.on_clicked(self.delete)
self.upd_button = Button(self.update_ax, 'Update')
self.upd_button.on_clicked(self.img_update)
self.reg_button = Button(self.register_ax, 'Register')
self.add_button = Button(self.add_ax, "Add")
# Plot the first slice of the image
self.im = self.ax.imshow(np.array(raw_dicom_stack[0]))
def update(self, value):
global frame
frame = int(np.round(value - 1))
# Update the image data
dat = np.array(self.raw_dicom_stack[frame])
self.im.set_data(dat)
# Reset the image scaling bounds (this may not be necessary for you)
self.im.set_clim([dat.min(), dat.max()])
# Redraw the plot
self.fig.canvas.draw()
def delete(self,event):
global frame
global delete_list
delete_list.append(frame)
print 'Frame %s has been added to list of slices to be deleted' %str(frame+1)
print 'Please click update to delete these slices and show updated image series \n'
#Remove duplicates from delete list
def img_update(self,event):
#function deletes image stacks and updates viewer
global delete_list
#Remove duplicates from list and sort into numerical order
delete_list = list(set(delete_list))
delete_list.sort()
#Make sure delete_list is not empty
if not delete_list:
print "Delete list is empty, no slices to delete"
#Loop through delete list in reverse numerical order and remove slices from series
else:
for i in reversed(delete_list):
self.raw_dicom_stack.pop(i)
print 'Slice %i removed from dicom series \n' %(i+1)
#Can now remove contents from delete_list
del delete_list[:]
#Update slider range
self.nframes = len(self.raw_dicom_stack)
def show(self):
plt.show()
if __name__ == '__main__':
main()
In order to update a slider range you may set the min and max value of it directly,
slider.valmin = 3
slider.valmax = 7
In order to reflect this change in the slider axes you need to set the limits of the axes,
slider.ax.set_xlim(slider.valmin,slider.valmax)
A complete example, where typing in any digit changes the valmin of the slider to that value.
import matplotlib.pyplot as plt
import matplotlib.widgets
fig, (ax,sliderax) = plt.subplots(nrows=2,gridspec_kw=dict(height_ratios=[1,.05]))
ax.plot(range(11))
ax.set_xlim(5,None)
ax.set_title("Type number to set minimum slider value")
def update_range(val):
ax.set_xlim(val,None)
def update_slider(evt):
print(evt.key)
try:
val = int(evt.key)
slider.valmin = val
slider.ax.set_xlim(slider.valmin,None)
if val > slider.val:
slider.val=val
update_range(val)
fig.canvas.draw_idle()
except:
pass
slider=matplotlib.widgets.Slider(sliderax,"xlim",0,10,5)
slider.on_changed(update_range)
fig.canvas.mpl_connect('key_press_event', update_slider)
plt.show()
It looks like the slider does not have a way to update the range (api). I would suggest setting the range of the slider to be [0,1] and doing
frame = int(self.nframes * value)
On a somewhat related note, I would have made frame an instance variable a data attribute instead of a global variable (tutorial).

Categories

Resources