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

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).

Related

Going from 2 Axes to 1?

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'>

How do I define a floatSlider using python2.7 in maya?

very new to scripting with python in maya so excuse my limited knowledge.
I need help figuring out how to define the variable for a floatSlider. I need two float sliders for the assignment I'm doing. I need one that will change the size of the selected or specified objects, and I need another that will use MASH to change the count of that object.
I have script with those sliders and a Distribute button laid out. I'm not sure what I need to include to link the scale of the object to the slider I have.
This is the code I have so far:
from maya import cmds
if cmds.window('mainUI2', exists=True):
cmds.deleteUI
win = cmds.window("mainUI2", title="Bush Generator", widthHeight=(300, 300))
# Layout
cmds.columnLayout(adjustableColumn=True)
cmds.text(label='Bush Generator')
cmds.button(label='Distribute', command='DistributeMesh()')
cmds.text(label=' ')
# need help defining Leaf_size
Leaf_size = cmds.floatSlider(min=0, max=100, value=0, step=1)
# I tried another type of slider
LeafScale = cmds.intSliderGrp(min=0, max=100, f=True)
cmds.text(label='Leaf Size')
# need defining Leaf_amount and linking to mash count
Leaf_amount = cmds.floatSlider(min=0, max=100, value=0, step=1)
cmds.text(label='Leaf Amount')
# Bush tool
def DistributeMesh():
cmds.loadPlugin("MASH", quiet=True)
import MASH.api as mapi
count = 3000
source_mesh = "pCube2"
scatter_mesh = "pSphere1"
source_shape = cmds.listRelatives(scatter_mesh, children=True)[0]
cmds.select(source_mesh)
mash_network = mapi.Network()
mash_network.createNetwork(name="Test", geometry="Instancer")
# set to use meshes to scatter
cmds.setAttr(mash_network.distribute + ".arrangement", 4)
cmds.setAttr(mash_network.distribute + ".pointCount", count)
# connect mesh
cmds.connectAttr(
source_shape + ".worldMesh[0]",
mash_network.distribute + ".inputMesh",
force=True)
cmds.showWindow(win)
Scale is a float value so you can use cmds.floatSliderGrp to set the source mesh's scale. First you have to define a separate function that will be triggered when you change the value of floatSliderGrp, then in floatSliderGrp set its changeCommand parameter to that function:
from maya import cmds
# Define a function that will be called when the slider changes values.
def on_size_slider_changed(value):
source_mesh = "pCube2"
if cmds.objExists(source_mesh): # Check if it exists.
cmds.setAttr("{}.scale".format(source_mesh), value, value, value) # Set its scale.
if cmds.window('mainUI2', exists=True):
cmds.deleteUI
win = cmds.window("mainUI2", title="Bush Generator", widthHeight=(300, 300))
# Layout
cmds.columnLayout(adjustableColumn=True)
cmds.text(label='Bush Generator')
cmds.button(label='Distribute', command='DistributeMesh()')
# Use `changeCommand` to define what function it should call.
leaf_size_slider = cmds.floatSliderGrp(label="Size", field=True, min=0, max=100, value=1, changeCommand=on_size_slider_changed)
# Bush tool
def DistributeMesh():
cmds.loadPlugin("MASH", quiet=True)
import MASH.api as mapi
count = 3000
source_mesh = "pCube2"
scatter_mesh = "pSphere1"
source_shape = cmds.listRelatives(scatter_mesh, children=True)[0]
cmds.select(source_mesh)
mash_network = mapi.Network()
mash_network.createNetwork(name="Test", geometry="Instancer")
# set to use meshes to scatter
cmds.setAttr(mash_network.distribute + ".arrangement", 4)
cmds.setAttr(mash_network.distribute + ".pointCount", count)
# connect mesh
cmds.connectAttr(
source_shape + ".worldMesh[0]",
mash_network.distribute + ".inputMesh",
force=True)
cmds.showWindow(win)
Dragging the slider will now set the scale of the cube. Though to be honest the structure of the code here is very messy and a bit too hard-coded (think about how it would work with the current selection instead of explicitly using the object's names)

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)

AttributeError: 'DataFrame' object has no attribute 'add_root' bokeh

import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook, push_notebook
from bokeh.plotting import figure
from bokeh.models import CategoricalColorMapper, HoverTool, ColumnDataSource, Panel
from bokeh.models.widgets import CheckboxGroup, Slider, RangeSlider, Tabs
from bokeh.layouts import column, row, WidgetBox
from bokeh.palettes import Category20_16
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
output_notebook()
def histogram_tab(webs):
def make_dataset(params_list, range_start = 0.0, range_end = 1, bin_width = 0.005):
#check to make sure the start is less than the end
assert range_start < range_end, "Start must be less than end!"
#by_params = pd.DataFrame(columns=[ ,'Max', 'Avarage', 'Min','color'])
by_params = pd.DataFrame(columns=[ 'left','right', 'proportion', 'p_proportion','p_interval', 'name', 'w_name','color'])
#
range_extent = range_end - range_start
values = ['Min', "Avarage", 'Max']
# Iterate through all the parameters
for i, para_name in enumerate(params_list):
#print para_name
# Subset to the parameter
subset = webs[para_name]
# note: subset have to be a list of values
# [webs.columns[i%6]]
# Create a histogram with specified bins and range
arr_hist, edges = np.histogram(subset,
bins = int(range_extent / bin_width),
range = [range_start, range_end])
# Divide the counts by the total to get a proportion and create df
arr_df= pd.DataFrame({'proportion': arr_hist ,
'left': edges[:-1], 'right': edges[1:]}) #/ np.sum(arr_hist)
# Format the proportion
arr_df['p_proportion'] = ['%0.00005f' % proportion for proportion in arr_df['proportion']]
# Format the interval
arr_df['p_interval'] = ['%d to %d scale' % (left, right) for left,
right in zip(arr_df['left'], arr_df['right'])]
# Assign the parameter for labels
arr_df['name'] = para_name
arr_df['w_name'] = webs['Site name']
# Color each parametr differently
arr_df['color'] = Category20_16[i]
# Add to the overall dataframe
by_params = by_params.append(arr_df)
# Overall dataframe
by_params = by_params.sort_values(['name','left'])
return ColumnDataSource(by_params)
def style(p):
# Title
p.title.align = 'center'
p.title.text_font_size ='20pt'
p.title.text_font = 'serif'
# Axis titles
p.xaxis.axis_label_text_font_size = '14pt'
p.xaxis.axis_label_text_font_style = 'bold'
p.yaxis.axis_label_text_font_size = '14pt'
p.yaxis.axis_label_text_font_style = 'bold'
# Tick labels
p.xaxis.major_label_text_font_size = '12pt'
p.yaxis.major_label_text_font_size = '12pt'
return p
def make_plot(src):
# Blank plot with correct labels
p = figure(plot_width = 700, plot_height = 700,
title = "Histogram of Parametes for the websites",
x_axis_label = 'parameters', y_axis_label = "values")
# Quad glyphs to create a histogram
p.quad(source=src, bottom =0,left = 'left', right = 'right', color ='color', top= 'proportion',fill_alpha = 0.7, hover_fill_color = 'color', legend = 'name',
hover_fill_alpha = 1.0, line_color = 'white') #top='proportion',
# Hover tool with vline mode
hover = HoverTool(tooltips=[('Parameter','#name'),
('Website','#w_name'),
('Proportion','p_proportion')
],
mode='vline')
p.add_tools(hover)
# Stypling
p = style(p)
return p
# Update function takes three default parameters
def update(attr, old, new):
# Get the list of parameter for the graph
parameter_to_plot = [para_selection.labels[i] for i in para_selection.active]
# Make a new dataset based on the selected parameter and the
# make_dataset function defined earlier
new_src = make_dataset(parameter_to_plot, range_start = 0, range_end = 1, bin_width = 0.005) # note range are not specified
# Convert dataframe to column data source
new_src = ColumnDataSource(new_src)
# Update the source used the quad glpyhs
src.data.update(new_src.data)
list_of_params = list(webs.columns[1:].unique())
list_of_params.sort()
para_selection = CheckboxGroup(labels=list_of_params, active = [0,1])
para_selection.on_change('active',update)
binwidth_select = Slider(start =0, end = 1,
step = 0.00025, value = 0.0005,
title = 'Change in parameter')
binwidth_select.on_change('value', update)
range_select = RangeSlider(start=0, end=1, value =(0,1),
step=0.00025, title = 'Change in range')
range_select.on_change('value', update)
initial_params = [para_selection.labels[i] for i in para_selection.active]
src = make_dataset(initial_params,
range_start = range_select.value[0],
range_end = range_select.value[1],
bin_width = binwidth_select.value)
p = make_plot(src)
#show(p)
# Put controls in a single element
controls = WidgetBox(para_selection, binwidth_select, range_select)
# Create a row layout
layout = row(controls, p)
# Make a tab with the layout
tab = Panel(child = layout, title = 'Histogram')
#return tab
tabs = Tabs(tabs=[tab])
webs.add_root(tabs)
# Set up an application
handler = FunctionHandler(histogram_tab(webs))
app = Application(handler)
add_root is a method on Document, you are trying to call it on a DataFrame called webs, apparently, which is why you get that message. The structure of a Bokeh app in a notebook should look like this:
# create a function to define the app, must accept "doc" as the parameter
def myfunc(doc):
# make Bokeh objects
# add stuff to doc
doc.add_root(stuff)
# pass the function, but *don't* execute it
handler = FunctionHandler(myfunc)
app = Application(handler)
Note that the last two lines are not necessary in recent version of Bokeh, you can just call:
show(myfunc)
directly. There is a full example in the repo:
https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb
Your code should be structured very similarly to that.

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()

Categories

Resources