Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matplotlib Updating slider widget range

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()
like image 854
moadeep Avatar asked Nov 21 '12 12:11

moadeep


2 Answers

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()
like image 73
ImportanceOfBeingErnest Avatar answered Oct 17 '22 15:10

ImportanceOfBeingErnest


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

like image 40
tacaswell Avatar answered Oct 17 '22 16:10

tacaswell