Module gonioanalysis.tkgui.examine

TODO

Features + video of the moving ROIs + no need for manual add to PythonPath on Windows - window to strecth to full screen - multiselect ROIs (if many separate recordings at the same site)

Polishing - specimens control title, retain name specimen - image folders control title as specimens - after movement measure, update MAnalyser - displacement plot Y axis label - highlight specimens/image_folders: red: no rois / movements yellow: rois but no movements - x-axis from frames to time?

Functions

def main()

Classes

class ExamineMenubar (parent)

Menubar class for the examine GUI.

Construct a frame widget with the parent MASTER.

Valid resource names: background, bd, bg, borderwidth, class, colormap, container, cursor, height, highlightbackground, highlightcolor, highlightthickness, relief, takefocus, visual, width.

Expand source code
class ExamineMenubar(tk.Frame):
    '''
    Menubar class for the examine GUI.
    '''

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)

        self.parent = parent
        self.root = parent.root
        self.core = parent.core
        self.menubar = tk.Menu(self)

        # File command and menu
        self.file_commands = FileCommands(self.parent, self.core, 'File')
        self.file_commands._connect(self.menubar, tearoff=0)
        
        self.menubar.add_command(label="|")

        # Imagefolder command and menu
        self.imagefolder_commands = ImageFolderCommands(self.parent, self.core, 'Image folder')
        self.imagefolder_commands._connect(self.menubar, tearoff=0)

        # Specimen commands and menu
        self.specimen_commands = SpecimenCommands(self.parent, self.core, 'Specimen')
        self.specimen_commands._connect(self.menubar, tearoff=0)
        
        #    Submenu: Add terminal commands
        self.terminal_commands = ModifiedMenuMaker(self.parent, self.core, 'Terminal interface commands')
        for name in ANALYSER_CMDS:
            setattr(self.terminal_commands, name, lambda name=name: self.core.adm_subprocess('current', "-A "+name) )
        self.terminal_commands._connect(self.specimen_commands.tkmenu)

        # Many specimen commands and menu
        self.many_specimen_commands = ManySpecimenCommands(self.parent, self.core, 'Many specimens')
        self.many_specimen_commands._connect(self.menubar, tearoff=0)
       
        self.menubar.add_command(label="|")
        
        # Other commands and menu
        self.other_commands = OtherCommands(self.parent, self.core, 'Other')
        self.other_commands._connect(self.menubar, tearoff=0)
        

        self.winfo_toplevel().config(menu=self.menubar)

Ancestors

  • tkinter.Frame
  • tkinter.Widget
  • tkinter.BaseWidget
  • tkinter.Misc
  • tkinter.Pack
  • tkinter.Place
  • tkinter.Grid
class ExamineView (parent)

The examine frame. Selection of - data directory - specimen - recording and plotting the intemediate result for each recording.

Construct a frame widget with the parent MASTER.

Valid resource names: background, bd, bg, borderwidth, class, colormap, container, cursor, height, highlightbackground, highlightcolor, highlightthickness, relief, takefocus, visual, width.

Expand source code
class ExamineView(tk.Frame):
    '''
    The examine frame. Selection of
    - data directory
    - specimen
    - recording
    and plotting the intemediate result for each recording.
    
    '''
    
    def __init__(self, parent):

        self.last_saveplotter_dir = GONIODIR

        tk.Frame.__init__(self, parent)
        
        self.core = Core()
        self.core.update_gui = self.update_specimen
        
        self.root = self.winfo_toplevel()
        
        # Make canvas plotter to stretch
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=3)
        self.grid_columnconfigure(0, weight=1)
        self.grid_columnconfigure(0, minsize=400)


        #tk.Button(self, text='Set data directory...', command=self.set_data_directory).grid(row=0, column=0)
        
        # Uncomment to have the menubar
        self.menu = ExamineMenubar(self)

        # LEFTSIDE frame
        self.leftside_frame = tk.Frame(self)
        self.leftside_frame.grid(row=0, column=0, sticky='NSWE') 
        self.leftside_frame.grid_rowconfigure(4, weight=1) 
        self.leftside_frame.grid_columnconfigure(0, weight=1)
        self.leftside_frame.grid_columnconfigure(1, weight=1)



        # The 1st buttons frame, selecting root data directory
        self.buttons_frame_1 = ButtonsFrame(self.leftside_frame, ['Set data directory'],
                [self.menu.file_commands.set_data_directory, self.menu.file_commands.set_data_directory])
        self.buttons_frame_1.grid(row=0, column=0, sticky='NW', columnspan=2)

 
        self.specimen_control_frame = tk.LabelFrame(self.leftside_frame, text='Specimen')
        self.specimen_control_frame.grid(row=1, column=0, sticky='NWES', columnspan=2)

       
        # The 2nd buttons frame, ROIs and movements
        self.buttons_frame_2 = ButtonsFrame(self.specimen_control_frame,
                ['Select ROIs', 'Measure movement'],
                [self.menu.specimen_commands.select_ROIs, self.menu.specimen_commands.measure_movement])
        self.buttons_frame_2.grid(row=1, column=0, sticky='NW', columnspan=2)
        self.button_rois, self.button_measure = self.buttons_frame_2.get_buttons()
        
        # Subframe for 2nd buttons frame
        #self.status_frame = tk.Frame(self.leftside_frame)
        #self.status_frame.grid(row=2)

        self.status_rois = tk.Label(self.specimen_control_frame, text='ROIs selected 0/0', font=('system', 8))
        self.status_rois.grid(row=2, column=0, sticky='W')
        
        self.status_antenna_level = tk.Label(self.specimen_control_frame, text='Zero correcter N/A', font=('system', 8))
        #self.status_antenna_level.grid(row=3, column=0, sticky='W')
        
        self.status_active_analysis = tk.Label(self.specimen_control_frame, text='Active analysis: default', font=('system', 8), justify=tk.LEFT)
        self.status_active_analysis.grid(row=4, column=0, sticky='W')
       
        self.tickbox_analyses = TickboxFrame(self.specimen_control_frame, [], ncols=4)
        self.tickbox_analyses.grid(row=5, column=0, sticky='W')


        # Image folder manipulations
        self.folder_control_frame = tk.LabelFrame(self.leftside_frame, text='Image folder')
        self.folder_control_frame.grid(row=2, column=0, sticky='NWES', columnspan=2)
       
        self.buttons_frame_3 = ButtonsFrame(self.folder_control_frame,
                ['Reselect ROI', 'Remeasure'],
                [self.menu.imagefolder_commands.select_ROIs, self.menu.imagefolder_commands.measure_movement])
        self.buttons_frame_3.grid(row=1, column=0, sticky='NW', columnspan=2)
        self.button_one_roi = self.buttons_frame_2.get_buttons()[0]
        

        self.status_horizontal = tk.Label(self.folder_control_frame, text='Horizontal angle N/A', font=('system', 8))
        self.status_horizontal.grid(row=2, column=0, sticky='W')
        
        self.status_vertical = tk.Label(self.folder_control_frame, text='Vertical angle N/A', font=('system', 8))
        self.status_vertical.grid(row=3, column=0, sticky='W')
        

        # Selecting the specimen 
        tk.Label(self.leftside_frame, text='Specimens').grid(row=3, column=0)
        self.specimen_box = Listbox(self.leftside_frame, ['(select directory)'], self.on_specimen_selection)
        self.specimen_box.grid(row=4, column=0, sticky='NSEW')

       
        # Selecting the recording
        tk.Label(self.leftside_frame, text='Image folders').grid(row=3, column=1)
        self.recording_box = Listbox(self.leftside_frame, [''], self.on_recording_selection)
        self.recording_box.grid(row=4, column=1, sticky='NSEW')

        
        # Add color explanation frame in the bottom
        ColorExplanation(self.leftside_frame, ['white', 'green', 'yellow'],
                ['Movements measured', 'ROIs selected', 'No ROIs']).grid(row=5, column=0, sticky='NW')


        self.default_button_bg = self.button_rois.cget('bg')
        
        self.plot_view = PlotView(self, self.core)
        self.plot_view.grid(row=0, column=1, sticky='NSWE')


    def _color_specimens(self, specimens):
        '''
        See _color_recording for reference.
        '''
        colors = []
        for specimen in specimens:
            analyser = self.core.get_manalyser(specimen, no_data_load=True)
            color = 'yellow'
            if analyser.are_rois_selected():
                color = 'green'
                if analyser.is_measured():
                    color = 'white'
            
            colors.append(color)
        return colors



    def copy_to_csv(self, formatted): 
        with open(os.path.join(GONIODIR, 'clipboard.csv'), 'w') as fp:
            fp.write(formatted.replace('\t', ','))


    def specimen_traces_to_clipboard(self, mean=False):
        '''
        If mean==True, copy only the average trace.
        Otherwise, copy all the traces of the fly.
        '''

        formatted = '' 
        
        # Always first clear clipboard; If something goes wrong, the user
        # doesn't want to keep pasting old data thinking it's new.
        self.root.clipboard_clear()

        if self.core.selected_recording is None:
            return None

        data = []

        self.root.clipboard_append(formatted)
        
        for pos_folder in self.core.analyser.list_imagefolders():
            all_movements = self.core.analyser.get_movements_from_folder(pos_folder)
            
            for eye, movements in all_movements.items():
                for repetition in range(len(movements)):
                    mag = np.sqrt(np.array(movements[repetition]['x'])**2 + np.array(movements[repetition]['y'])**2)
                    data.append(mag)

        if mean:
            data = [np.mean(data, axis=0)]
        
        for i_frame in range(len(data[0])):
            formatted += '\t'.join([str(data[i_repeat][i_frame]) for i_repeat in range(len(data)) ]) + '\n'
        
        self.root.clipboard_append(formatted)
        self.copy_to_csv(formatted)       


     


    def _color_recordings(self, recordings):
        '''
        Returns a list of colours, each corresponding to a recording
        in recordings, based on wheter the ROIs have been selected or
        movements measured for the recording.

        yellow      No ROIs, no movements
        green       ROIs, no movements
        white       ROIs and movements
        '''
        colors = []
        for recording in recordings:
            color = 'yellow'
            if self.core.analyser.folder_has_rois(recording):
                color = 'green'
                if self.core.analyser.folder_has_movements(recording):
                    color = 'white'
            
            colors.append(color)
        return colors


    def on_specimen_selection(self, specimen):
        '''
        When a selection happens in the specimens listbox.
        '''
        self.specimen_control_frame.config(text=specimen)

        self.core.set_current_specimen(specimen)
        

        # Recordings box
        recordings = self.core.analyser.list_imagefolders()
        self.recording_box.enable()
        self.recording_box.set_selections(recordings, colors=self._color_recordings(recordings))
         
        
        # Logick to set buttons inactive/active and their texts
        if self.core.analyser.are_rois_selected(): 
  
            self.button_rois.config(text='Reselect ROIs')
            self.button_rois.config(bg=self.default_button_bg)
            
            self.button_measure.config(state=tk.NORMAL)
            
            self.button_one_roi.config(state=tk.NORMAL)
            
            # Enable image_folder buttons
            for button in self.buttons_frame_3.get_buttons():
                button.config(state=tk.NORMAL)

            if self.core.analyser.is_measured():
                self.button_measure.config(text='Remeasure movement')
                self.button_measure.config(bg=self.default_button_bg)
            else:
                self.button_measure.config(text='Measure movement')
                self.button_measure.config(bg='green')
        else:
            #self.recording_box.disable()

            self.button_rois.config(text='Select ROIs')
            self.button_rois.config(bg='yellow')

            self.button_measure.config(state=tk.DISABLED)
            self.button_measure.config(text='Measure movement')
            self.button_measure.config(bg=self.default_button_bg)

            self.button_one_roi.config(state=tk.DISABLED)
            
            # Disable image_folder buttons
            for button in self.buttons_frame_3.get_buttons():
                button.config(state=tk.DISABLED)



        if self.core.analyser.are_rois_selected():
            self.core.analyser.load_ROIs()

        # Loading cached analyses and setting the recordings listbox

        if self.core.analyser.is_measured():
            self.core.analyser.load_analysed_movements()
            #self.recording_box.enable()
            
        
        N_rois = self.core.analyser.count_roi_selected_folders()
        N_image_folders = len(self.core.analyser.list_imagefolders())
        self.status_rois.config(text='ROIs selected {}/{}'.format(N_rois, N_image_folders))
        
        try:
            self.correction = self.core.analyser.get_antenna_level_correction()
        except:
            self.correction = False
        if self.correction is not False:
            self.status_antenna_level.config(text='Zero corrected, {:.2f} degrees'.format(self.correction))
        else:
            self.status_antenna_level.config(text='Zero corrected FALSE')

        self.status_active_analysis.config(text='Active analysis: {}'.format(self.core.analyser.active_analysis))

        
        # FIXME Instead of destroyign tickbox, make changes to tk_steroids
        # so that the selections can be reset
        self.tickbox_analyses.grid_forget()
        self.tickbox_analyses.destroy()
        self.tickbox_analyses = TickboxFrame(self.specimen_control_frame, self.core.analyser.list_analyses(),
                defaults=[self.core.analyser.active_analysis == an for an in self.core.analyser.list_analyses()],
                ncols=4, callback=lambda: self.update_plot(None))
        self.tickbox_analyses.grid(row=5, column=0, sticky='W')

        self.button_rois.config(state=tk.NORMAL)



    def on_recording_selection(self, selected_recording):
        '''
        When a selection happens in the recordings listbox.
        
        selected_recording      Name of the recording. If 'current', keeps the current
        '''
        if selected_recording == 'current':
            selected_recording = self.core.selected_recording
        else:
            self.core.set_selected_recording(selected_recording)

        
        print(self.core.analyser.get_recording_time(selected_recording))
        
        angles = [list(angles_from_fn(selected_recording))]
        to_degrees(angles)
        horizontal, vertical = angles[0]
        self.status_horizontal.config(text='Horizontal angle {:.2f} degrees'.format(horizontal))
        self.status_vertical.config(text='Vertical angle {:.2f} degrees'.format(vertical))
        

        # Plotting only the view we have currently open
        self.plot_view.update_plot()
        



    def update_specimen(self, changed_specimens=False):
        '''
        Updates GUI colors, button states etc. to right values.
        
        Call this if there has been changes to specimens/image_folders by an
        external process or similar.
        '''        
        if changed_specimens:
            specimens = self.core.list_specimens()
            self.specimen_box.set_selections(specimens, self._color_specimens(specimens))

        if self.core.current_specimen is not None:
            self.on_specimen_selection(self.core.current_specimen)

Ancestors

  • tkinter.Frame
  • tkinter.Widget
  • tkinter.BaseWidget
  • tkinter.Misc
  • tkinter.Pack
  • tkinter.Place
  • tkinter.Grid

Methods

def copy_to_csv(self, formatted)
def on_recording_selection(self, selected_recording)

When a selection happens in the recordings listbox.

selected_recording Name of the recording. If 'current', keeps the current

def on_specimen_selection(self, specimen)

When a selection happens in the specimens listbox.

def specimen_traces_to_clipboard(self, mean=False)

If mean==True, copy only the average trace. Otherwise, copy all the traces of the fly.

def update_specimen(self, changed_specimens=False)

Updates GUI colors, button states etc. to right values.

Call this if there has been changes to specimens/image_folders by an external process or similar.

class PlotView (examine, core)

The plot showing part of the examine view (the old rightside_frame).

Construct a frame widget with the parent MASTER.

Valid resource names: background, bd, bg, borderwidth, class, colormap, container, cursor, height, highlightbackground, highlightcolor, highlightthickness, relief, takefocus, visual, width.

Expand source code
class PlotView(tk.Frame):
    '''The plot showing part of the examine view (the old rightside_frame).
    '''
    def __init__(self, examine, core):

        tk.Frame.__init__(self, examine)
        self.examine = examine
        self.core = core

        tab_kwargs = [{}, {}, {}, {'projection': '3d'}]
        tab_names = ['ROI', 'Displacement', 'XY', '3D']
        canvas_constructors = [lambda parent, kwargs=kwargs: CanvasPlotter(parent, visibility_button=False, **kwargs) for kwargs in tab_kwargs]
        self.tabs = Tabs(self, tab_names, canvas_constructors,
                on_select_callback=self.update_plot)
        
        self.tabs.grid(row=0, column=0, sticky='NWES')


        # Make canvas plotter to stretch
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)


        self.canvases = self.tabs.get_elements()
        
        # Controls for displacement plot (means etc)
        displacementplot_options, displacementplot_defaults = inspect_booleans(
                plot_1d_magnitude, exclude_keywords=['mean_imagefolders'])
        self.displacement_ticks = TickboxFrame(self.canvases[1], displacementplot_options,
                defaults=displacementplot_defaults, callback=lambda:self.update_plot(1))
        self.displacement_ticks.grid()

        xyplot_options, xyplot_defaults = inspect_booleans(
                plot_xy_trajectory)
        self.xy_ticks = TickboxFrame(self.canvases[2], xyplot_options,
                defaults=xyplot_defaults, callback=lambda:self.update_plot(2))
        self.xy_ticks.grid()



        # Controls for the vector plot
        # Controls for displacement plot (means etc)
        vectorplot_options, vectorplot_defaults = inspect_booleans(
                plot_3d_vectormap, exclude_keywords=[])
        self.vectorplot_ticks = TickboxFrame(self.canvases[3], vectorplot_options,
                defaults=vectorplot_defaults, callback=lambda:self.update_plot(3))
        self.vectorplot_ticks.grid()
        
        tk.Button(self.canvases[3], text='Save animation', command=self.save_3d_animation).grid()



        self.plotter = RecordingPlotter(self.core)
                
        # Add buttons for selecting single repeats from a recording
        self.repetition_selector = RepetitionSelector(self, self.plotter, self.core,
                update_command=lambda: self.examine.on_recording_selection('current'))
        self.repetition_selector.grid(row=1, column=0)
        

        tk.Button(self.repetition_selector, text='Copy data',
                command=self.copy_to_clipboard).grid(row=0, column=5)

        tk.Button(self.repetition_selector, text='Save view...',
                command=self.save_view).grid(row=0, column=6)

    
    def update_plot(self, i_plot=None):
        '''
        i_plot : int or None
            Index of the plot (from 0 to N-1 tabs) or None just to update the current open tab.
        '''
        if self.core.selected_recording is None:
            return None
        
        if i_plot is None:
            i_plot = self.tabs.i_current

        fig, ax = self.canvases[i_plot].get_figax()

        if i_plot == 0:
            self.plotter.ROI(self.canvases[i_plot])
        else:
            
            ax.clear()

            remember_analysis = self.core.analyser.active_analysis

            for analysis in [name for name, state in self.examine.tickbox_analyses.states.items() if state == True]:
                
                self.core.analyser.active_analysis = analysis

                if i_plot == 1:
                    self.plotter.magnitude(ax, **self.displacement_ticks.states)
                elif i_plot == 2:  
                    self.plotter.xy(ax, **self.xy_ticks.states) 
                elif i_plot == 3:
                    self.plotter.vectormap(ax, **self.vectorplot_ticks.states)
            
            self.core.active_analysis = remember_analysis


        self.canvases[i_plot].update()
        
        self.repetition_selector.update_text()


    def copy_to_clipboard(self, force_i_tab=None):
        '''
        Copies data currently visible on theopen plotter tab to the clipboard.
        
        force_i_tab         Copy from the specified tab index, instead of
                            the currently opened tab
        '''
        formatted = ''
        
        # Always first clear clipboard; If something goes wrong, the user
        # doesn't want to keep pasting old data thinking it's new.
        self.examine.root.clipboard_clear()
        
        if self.core.selected_recording is None:
            return None

        if force_i_tab is not None:
            i_tab = int(force_i_tab)
        else:
            i_tab = self.tabs.i_current
        
        # Make sure we have the correct data in the plot by reissuing
        # the plotting command
        self.update_plot(i_tab)
        
        # Select data based on where we want to copy
        if i_tab == 0:
            data = self.plotter.image
        elif i_tab == 1:
            data = self.plotter.magnitudes
        elif i_tab == 2:
            data = self.plotter.xys
            data = list(itertools.chain(*data))
        elif i_tab == 3:
            raise NotImplementedError('Cannot yet cliboard vectormap data')

        # Format the data for tkinter clipboard copy
        for i_frame in range(len(data[0])):
            formatted += '\t'.join([str(data[i_repeat][i_frame]) for i_repeat in range(len(data)) ]) + '\n'
        
        self.examine.root.clipboard_append(formatted)
        self.examine.copy_to_csv(formatted)


    def save_view(self):
        '''
        Launches a save dialog for the current plotter view.
        '''
        fig, ax = self.canvases[self.tabs.i_current].get_figax()
        
        dformats = fig.canvas.get_supported_filetypes()
        formats = [(value, '*.'+key) for key, value in sorted(dformats.items())]
        
        # Make png first
        if 'png' in dformats.keys():
            i = formats.index((dformats['png'], '*.png'))
            formats.insert(0, formats.pop(i))

        fn = filedialog.asksaveasfilename(title='Save current view',
                initialdir=self.examine.last_saveplotter_dir,
                filetypes=formats)

        if fn:
            self.last_saveplotter_dir = os.path.dirname(fn)

            fig.savefig(fn, dpi=1200)
        

    def save_3d_animation(self):
        
        def callback():
            self.canvases[3].update()
        
        fig, ax = self.canvases[3].get_figax()
        save_3d_animation(self.core.analyser, ax=ax, interframe_callback=callback)

Ancestors

  • tkinter.Frame
  • tkinter.Widget
  • tkinter.BaseWidget
  • tkinter.Misc
  • tkinter.Pack
  • tkinter.Place
  • tkinter.Grid

Methods

def copy_to_clipboard(self, force_i_tab=None)

Copies data currently visible on theopen plotter tab to the clipboard.

force_i_tab Copy from the specified tab index, instead of the currently opened tab

def save_3d_animation(self)
def save_view(self)

Launches a save dialog for the current plotter view.

def update_plot(self, i_plot=None)

i_plot : int or None Index of the plot (from 0 to N-1 tabs) or None just to update the current open tab.