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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With