<gh_stars>10-100
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Visualization
==================
This is module contains a simple visualizer to show event lists along with the audio.
The visualizer can show multiple event lists for the same reference audio allowing the
comparison of the reference and estimated event lists.

 .. image:: visualization.png


.. autosummary::
    :toctree: generated/

    EventListVisualizer
    EventListVisualizer.show

"""
from __future__ import print_function, absolute_import
from sed_vis.util import AudioPlayer, AudioThread
import dcase_util
import numpy
import math
import time

import scipy.fftpack
import scipy.signal
from numpy.lib.stride_tricks import as_strided
from sys import platform as _platform
import matplotlib
if _platform == "darwin":
    # MAC OS X
    matplotlib.use('TkAgg')

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib.patches as patches
import matplotlib.colors as colors
import matplotlib.cm as cm

from matplotlib.widgets import Button, SpanSelector
from matplotlib.patches import Rectangle


class EventListVisualizer(object):
    """Event List visualizer.

    Examples
    --------
    >>> # Load audio signal first
    >>> audio_container = dcase_util.containers.AudioContainer().load('data/audio.wav')
    >>> # Load event lists
    >>> reference_event_list = dcase_util.containers.MetaDataContainer().load('data/reference.txt')
    >>> estimated_event_list = dcase_util.containers.MetaDataContainer().load('data/estimated.txt')
    >>> event_lists = {'reference': reference_event_list, 'estimated': estimated_event_list}
    >>> # Visualize the data
    >>> vis = sed_vis.visualization.EventListVisualizer(event_lists=event_lists,audio_signal=audio_container.data,sampling_rate=audio_container.fs)
    >>> vis.show()

    """

    def __init__(self, *args, **kwargs):
        """Constructor

        Parameters
        ----------
        event_lists : dict of event lists
            Dict of event lists

        event_list_order : list
            Order of event list, if None alphabetical order used
            (Default value=None)

        active_events : list
            List of active sound event classes, if None all used.
            (Default value=None)

        audio_signal : np.ndarray
            Audio signal

        sampling_rate : int
            Sampling rate [0:96000]

        mode: str
            Signal visualization mode ['spectrogram', 'time_domain']
            (Default value = 'spectrogram')

        spec_hop_size : int
            Spectrogram calculation hop length in samples
            (Default value=256)

        spec_win_size: int ,
            Spectrogram calculation window length in samples
            (Default value=1024)

        spec_fft_size: int
            FFT length
            (Default value=1024)

        spec_cmap : str
            Color map used for spectrogram, see examples: http://matplotlib.org/examples/color/colormaps_reference.html
            (Default value='magma')

        spec_interpolation : str
            Matrix interpolation method for spectrogram (e.g. nearest, bilear, bicubic, quadric, gaussian)
            (Default value='nearest')

        event_roll_cmap : str
            Color map used for spectrogram, see examples: http://matplotlib.org/examples/color/colormaps_reference.html
            (Default value='rainbow')

        minimum_event_length : float > 0.0
            Minimum event length in seconds, shorten than given are filtered out from the output.
            (Default value=None)

        minimum_event_gap : float > 0.0
            Minimum allowed gap between events in seconds from same event label class.
            (Default value=None)

        color : color hex
            Main color code used in highlighting things

        use_blit : bool
            Use blit
            (Default value=False)

        publication_mode : bool
            Strip visual elements, can be used to prepare figures for publications.
            (Default value=False)

        show_selector : bool
            Show highlight selector
            (Default value=True)

        labels: dict
            Text labels overrides

        button_color : dict
            Button color overrides

        Returns
        -------
        Nothing

        """

        if kwargs.get('event_lists', []):
            self._event_lists = kwargs.get('event_lists', [])

            if kwargs.get('event_list_order') is None:
                self._event_list_order = sorted(self._event_lists.keys())
            else:
                self._event_list_order = kwargs.get('event_list_order')

            events = dcase_util.containers.MetaDataContainer()
            for event_list_label in self._event_lists:
                events += self._event_lists[event_list_label]

            self.event_labels = sorted(events.unique_event_labels, reverse=True)
            self.event_label_count = events.event_label_count

            if kwargs.get('active_events') is None:
                self.active_events = self.event_labels

            else:
                self.active_events = sorted(kwargs.get('active_events'), reverse=True)

            for name in self._event_lists:
                self._event_lists[name] = self._event_lists[name].process_events(
                    minimum_event_length=kwargs.get('minimum_event_length'),
                    minimum_event_gap=kwargs.get('minimum_event_gap')
                )

        else:
            self._event_lists = None

        if kwargs.get('audio_signal') is not None and kwargs.get('sampling_rate') is not None:
            audio_signal = kwargs.get('audio_signal') / numpy.max(numpy.abs(kwargs.get('audio_signal')))
            self.audio = AudioPlayer(
                signal=audio_signal,
                sampling_rate=kwargs.get('sampling_rate')
            )

        if kwargs.get('mode') not in ['spectrogram', 'time_domain']:
            self.mode = 'spectrogram'
        else:
            self.mode = kwargs.get('mode')

        self.auto_play = kwargs.get('auto_play', False)

        self.spec_hop_size = kwargs.get('spec_hop_size', 256)
        self.spec_win_size = kwargs.get('spec_win_size', 1024)
        self.spec_fft_size = kwargs.get('spec_fft_size', 1024)
        self.spec_cmap = kwargs.get('spec_cmap', 'magma')
        self.spec_interpolation =  kwargs.get('spec_interpolation', 'nearest')

        self.color = kwargs.get('color', '#339933')

        self.button_color = {
            'off': 'grey',
            'on': 'red'
        }

        self.button_color.update(kwargs.get('button_color',{}))
        self.labels = {
            'close': 'Close',
            'play': 'Play',
            'stop': 'Stop',
            'quit': 'Quit',
            'selection': 'Selection',
            'waveform': 'Waveform',
            'spectrogram': 'Spectrogram',
            'verification': 'Verification',
            'verification_info': '',
            'info': '',
        }

        self.labels.update(kwargs.get('labels',{}))

        self.indicator_line_color = self.color

        # Initialize members
        self.fig = None

        # Panels
        self.ax1 = None
        self.ax2 = None
        self.ax3 = None

        self.D = None
        self.x = None
        self.timedomain_locations = None

        self.begin_time = None
        self.end_time = None
        self.playback_offset = 0

        # Play indicators
        self.animation_event_roll_panel = None
        self.animation_selector_panel = None
        self.animation_highlight_panel = None

        self.event_panel_indicator_line = None
        self.selector_panel_indicator_line = None
        self.highlight_panel_indicator_line = None

        self.slider_time = None

        # Buttons
        self.buttons = {
            'play': True,
            'pause': False,
            'stop': True,
            'close': True,
            'quit': False,
            'verification': False,
        }
        self.buttons.update(kwargs.get('buttons',{}))

        self.button_play = None
        self.button_pause = None
        self.button_stop = None
        self.button_close = None

        self.use_blit = kwargs.get('use_blit', False)

        self.publication_mode = kwargs.get('publication_mode', False)
        self.show_selector = kwargs.get('show_selector', True)

        self.panel_title_font_size = 14
        self.legend_font_size = 12
        self.event_roll_label_font_size = 14
        self.event_roll_time_font_size = 10

        self.waveform_selector_point_hop = kwargs.get('waveform_selector_point_hop', 1000)
        self.waveform_highlight_point_hop = 100
        self.waveform_highlight_color = self.color

        self.selector_panel_height = 10
        self.highlight_panel_height = 25
        self.event_roll_panel_height = 50

        self.selector_panel_loc = 0
        self.highlight_panel_loc = 17
        self.event_roll_panel_loc = 45

        self.event_roll_item_opacity = 0.5
        self.fig_shape = (14, 6)

        self._quit = False

        if self.publication_mode:
            self.panel_title_font_size = 14
            self.legend_font_size = 16
            self.event_roll_time_font_size = 12

            self.spec_cmap = 'magma_r'
            self.spec_interpolation = 'bicubic'
            if not self.waveform_selector_point_hop:
                self.waveform_selector_point_hop = 5000
            self.waveform_highlight_point_hop = 500
            self.waveform_highlight_color = 'black'
            if self.show_selector:
                if self.mode == 'time_domain':
                    self.fig_shape = (30, 4)

                elif self.mode == 'spectrogram':
                    self.fig_shape = (20, 5)

                if self._event_lists:
                    if self.event_label_count == 1:
                        self.selector_panel_height = 10
                        self.highlight_panel_height = 33
                        self.event_roll_panel_height = 33

                        self.selector_panel_loc = 0
                        self.highlight_panel_loc = 17
                        self.event_roll_panel_loc = 53

                        self.event_roll_time_font_size = 16

                    else:
                        self.selector_panel_height = 10
                        self.highlight_panel_height = 15
                        self.event_roll_panel_height = 60

                        self.selector_panel_loc = 0
                        self.highlight_panel_loc = 17
                        self.event_roll_panel_loc = 35

                else:
                    self.selector_panel_height = 30
                    self.highlight_panel_height = 66
                    self.event_roll_panel_height = 0

                    self.selector_panel_loc = 0
                    self.highlight_panel_loc = 37
                    self.event_roll_panel_loc = 0

                    self.event_roll_time_font_size = 16

            else:
                if self.mode == 'time_domain':
                    self.fig_shape = (30, 4)

                elif self.mode == 'spectrogram':
                    self.fig_shape = (20, 4)

                self.selector_panel_height = 0
                self.highlight_panel_height = 15
                self.event_roll_panel_height = 75

                self.selector_panel_loc = 0
                self.highlight_panel_loc = 0
                self.event_roll_panel_loc = 17

            self.event_roll_item_opacity = 1.0

        self.label_colormap = cm.get_cmap(name=kwargs.get('event_roll_cmap','rainbow'))

    def generate_GUI(self):
        """Generates the visualizer GUI.

        Parameters
        ----------
        Nothing

        Returns
        -------
        Nothing

        """

        self.fig = plt.figure(figsize=self.fig_shape)

        # Selector panel
        # ====================================
        if self.show_selector:
            self.ax1 = plt.subplot2grid(shape=(100, 1), loc=(self.selector_panel_loc, 0), rowspan=self.selector_panel_height, colspan=1)

            self.timedomain_locations = numpy.arange(0, self.audio.signal.shape[0])

            self.ax1.fill_between(
                self.timedomain_locations[::self.waveform_selector_point_hop],
                self.audio.signal[::self.waveform_selector_point_hop],
                -self.audio.signal[::self.waveform_selector_point_hop],
                color='0.5'
            )

            plt.yticks([])
            plt.axis('tight')

            self.ax1.set_xlim(self.timedomain_locations[0], self.timedomain_locations[-1])
            self.ax1.set_ylim(-1, 1)

            self.time_ticks(
                locations=self.timedomain_locations,
                n_ticks=10,
                sampling_rate=self.audio.fs
            )

            self.ax1.yaxis.grid(False, which='major')
            self.ax1.yaxis.grid(False, which='minor')
            self.ax1.xaxis.grid(True, which='major')
            self.ax1.xaxis.grid(True, which='minor')
            self.ax1.yaxis.set_label_position("right")

            plt.title(self.labels['selection'], fontsize=self.panel_title_font_size)

        # Highlight panel
        # ====================================
        self.ax2 = plt.subplot2grid(shape=(100, 1), loc=(self.highlight_panel_loc, 0), rowspan=self.highlight_panel_height, colspan=1)
        self.x = numpy.arange(0, self.audio.duration_samples)

        self.begin_time = self.x[0] / float(self.audio.fs)
        self.end_time = self.x[-1] / float(self.audio.fs)

        if self.mode == 'spectrogram':
            self.D = self.get_spectrogram(
                audio=self.audio.signal,
                n_fft=self.spec_fft_size,
                win_length=self.spec_win_size,
                hop_length=self.spec_hop_size
            )

            self.plot_spectrogram(
                self.D,
                sampling_rate=self.audio.fs,
                interpolation=self.spec_interpolation,
                cmap=self.spec_cmap
            )

            if not self.publication_mode:
                self.ax2.yaxis.grid(False, which='major')
                self.ax2.yaxis.grid(False, which='minor')
                self.ax2.xaxis.grid(False, which='major')
                self.ax2.xaxis.grid(False, which='minor')

                plt.ylabel(self.labels['spectrogram'], fontsize=self.panel_title_font_size)
            else:
                self.ax2.get_yaxis().set_visible(False)

        elif self.mode == 'time_domain':

            self.ax2.fill_between(
                self.x[::self.waveform_highlight_point_hop], self.audio.signal[::self.waveform_highlight_point_hop], -self.audio.signal[::self.waveform_highlight_point_hop],
                color=self.waveform_highlight_color
            )

            self.ax2.set_ylim(-1, 1)
            self.ax2.set_xlim(self.x[0], self.x[-1])

            segment_begin = self.x[0] / float(self.audio.fs)
            segment_end = self.x[-1] / float(self.audio.fs)

            locs = numpy.arange(segment_begin, segment_end)
            plt.xlim([locs[0], locs[-1]])
            self.time_ticks(locations=locs, n_ticks=20)

            plt.yticks([])
            plt.xticks([])
            self.ax2.yaxis.grid(False, which='major')
            self.ax2.yaxis.grid(False, which='minor')

            self.ax2.xaxis.grid(True, which='major')
            self.ax2.xaxis.grid(True, which='minor')
            if not self.publication_mode:
                plt.ylabel(self.labels['waveform'], fontsize=self.panel_title_font_size)

            self.ax2.set_xlim(self.timedomain_locations[0], self.timedomain_locations[-1])

        self.ax2.yaxis.set_label_position("right")

        # Event roll panel
        # ====================================
        if self._event_lists:
            event_list_count = len(self._event_lists)

            self.begin_time = 0
            self.end_time = self.audio.duration_seconds

            if event_list_count == 1:
                norm = colors.Normalize(
                    vmin=0,
                    vmax=self.event_label_count
                )
                self.ax3 = plt.subplot2grid(
                    shape=(100, 1),
                    loc=(self.event_roll_panel_loc, 0),
                    rowspan=self.event_roll_panel_height+10,
                    colspan=1
                )

            else:
                norm = colors.Normalize(
                    vmin=0,
                    vmax=event_list_count
                )
                self.ax3 = plt.subplot2grid(
                    shape=(100, 1),
                    loc=(self.event_roll_panel_loc, 0),
                    rowspan=self.event_roll_panel_height,
                    colspan=1
                )

            m = cm.ScalarMappable(norm=norm, cmap=self.label_colormap)

            line_margin = 0.1
            y = 0
            annotation_height = (1.0-line_margin*2)/event_list_count

            for label in self.active_events:
                for event_list_id, event_list_label in enumerate(self._event_list_order):
                    offset = (len(self._event_list_order)-1-event_list_id) * annotation_height

                    event_y = y - 0.5 + line_margin + offset

                    # grid line
                    line = plt.Rectangle(
                        (0, y-0.5),
                        height=0.001,
                        width=self.end_time,
                        edgecolor='black',
                        facecolor='black'
                    )

                    plt.gca().add_patch(line)

                    for event in self._event_lists[event_list_label]:
                        if event['event_label'] == label:
                            event_length = event['offset'] - event['onset']

                            if 'probability' in event:
                                if event_list_count == 1:
                                    color = m.to_rgba(x=y + offset, alpha=event['probability'])
                                else:
                                    color = m.to_rgba(x=event_list_id, alpha=event['probability'])

                                rectangle = plt.Rectangle(
                                    (event['onset'], event_y),
                                    height=annotation_height,
                                    width=event_length,
                                    edgecolor='black',
                                    facecolor=color,
                                    linewidth=0,
                                    picker=5
                                )

                            else:
                                if event_list_count == 1:
                                    color = m.to_rgba(x=y + offset)
                                else:
                                    color = m.to_rgba(x=event_list_id)

                                rectangle = plt.Rectangle(
                                    (event['onset'], event_y),
                                    height=annotation_height,
                                    width=event_length,
                                    edgecolor='black',
                                    facecolor=color,
                                    linewidth=0,
                                    alpha=self.event_roll_item_opacity,
                                    picker=5
                                )


                            plt.gca().add_patch(rectangle)

                y += 1

            # grid line
            line = plt.Rectangle((0, y - 0.5),
                                 height=0.001,
                                 width=self.end_time,
                                 edgecolor='black',
                                 facecolor='black')
            plt.gca().add_patch(line)
            # Axis
            plt.axis([0, self.audio.duration_seconds, -0.5, len(self.active_events) + 0.5])
            locs = numpy.arange(0, self.audio.duration_seconds, 0.00001)

            plt.xlim([locs[0], locs[-1]])
            plt.axis('tight')

            # X axis
            self.ax3.xaxis.grid(True, which='major')
            self.ax3.xaxis.grid(True, which='minor')
            plt.tick_params(axis='x', which='major', labelsize=self.event_roll_time_font_size)

            # Y axis
            plt.yticks(
                numpy.arange(len(self.active_events)),
                self.active_events,
                fontsize=self.event_roll_label_font_size
            )

            plt.ylabel('Event Roll', fontsize=self.panel_title_font_size)
            self.ax3.yaxis.set_label_position('right')
            self.ax3.yaxis.grid(False, which='major')
            self.ax3.yaxis.grid(False, which='minor')

            # Set event list legends panel
            self.ax3.set_xlim(self.begin_time, self.end_time)
            if event_list_count > 1:
                span = 0
                for event_list_id, event_list_label in enumerate(self._event_list_order):

                    ax_legend_color = plt.axes([0.225+span, 0.02, 0.02, 0.02])
                    Button(
                        ax_legend_color,
                        '',
                        color=m.to_rgba(event_list_id),
                        hovercolor=m.to_rgba(event_list_id)
                    )

                    ax_legend_label = plt.axes([0.225+0.025+span, 0.02, 0.10, 0.04])
                    ax_legend_label.axis('off')
                    ax_legend_label.text(0, 0, event_list_label, fontsize=self.legend_font_size)
                    span += 0.15

        if self.show_selector:
            self.slider_time = SpanSelector(
                ax=self.ax1,
                onselect=self.on_select,
                minspan=None,
                direction='horizontal',
                span_stays=True,
                useblit=self.use_blit,
                onmove_callback=None,
                rectprops=dict(alpha=0.15, facecolor=self.color)
            )

        if not self.publication_mode:
            ax_legend_label = plt.axes([0.92, 0.02, 0.10, 0.04])
            ax_legend_label.axis('off')
            ax_legend_label.text(0, 0, 'sed_vis', fontsize=16)

            # Buttons
            # ====================================
            ax_play = plt.axes([0.125, 0.93, 0.07, 0.04])
            ax_stop = plt.axes([0.205, 0.93, 0.07, 0.04])
            ax_close = plt.axes([0.92, 0.93, 0.07, 0.04])

            self.button_play = Button(
                ax_play,
                self.labels['play'],
                color=self.button_color['off'],
                hovercolor=self.button_color['on']
            )

            self.button_stop = Button(
                ax_stop,
                self.labels['stop'],
                color=self.button_color['off'],
                hovercolor=self.button_color['on']
            )

            self.button_close = Button(
                ax_close,
                self.labels['close'],
                color=self.button_color['off'],
                hovercolor=self.button_color['on']
            )

            self.button_play.on_clicked(self.on_play)
            self.button_stop.on_clicked(self.on_stop)
            self.button_close.on_clicked(self.on_close_window)

            self.fig.canvas.mpl_connect('pick_event', self.on_pick)
        else:
            plt.subplots_adjust(left=0.12, bottom=0.05, right=.97, top=0.95, wspace=0, hspace=0)

        if self.auto_play:
            self.on_play(None)

    def show(self):
        """Shows the visualizer.

        Parameters
        ----------
        Nothing

        Returns
        -------
        Nothing

        """
        self.generate_GUI()
        plt.show()

    def save(self, filename=None):
        if filename:
            self.generate_GUI()
            plt.savefig(filename, bbox_inches='tight')

    def on_close_window(self, event):
        if self.audio.playing:
            self.audio.stop()
            self.audio = None

        plt.close(self.fig)

    def on_pick(self, event):
        if isinstance(event.artist, Rectangle):
            if self.audio.playing:
                self.audio.stop()  # Stop current playback
                try:
                    self.event_panel_indicator_line.set_visible(False)
                    self.event_panel_indicator_line.remove()
                except:
                    pass

                if self.animation_event_roll_panel is not None:
                    self.animation_event_roll_panel._stop()
                    self.animation_event_roll_panel = None

                try:
                    self.selector_panel_indicator_line.set_visible(False)
                    self.selector_panel_indicator_line.remove()
                except:
                    pass

                if self.animation_selector_panel is not None:
                    self.animation_selector_panel._stop()
                    self.animation_selector_panel = None

                try:
                    self.highlight_panel_indicator_line.set_visible(False)
                    self.highlight_panel_indicator_line.remove()
                except:
                    pass

                if self.animation_highlight_panel is not None:
                    self.animation_highlight_panel._stop()
                    self.animation_highlight_panel = None

                self.fig.canvas.draw()

                time.sleep(0.25)  # Wait until playback has stopped

            self.playback_offset = event.artist.get_x()
            self.audio.play(
                offset=event.artist.get_x(),
                duration=event.artist.get_width()
            )

            # Set up play indicators animations
            self.animation_event_roll_panel = animation.FuncAnimation(
                self.fig,
                self.event_roll_panel_play_indicator_update,
                init_func=self.event_roll_panel_play_indicator_init,
                interval=10,
                blit=self.use_blit,
                repeat=False
            )

            self.animation_selector_panel = animation.FuncAnimation(
                self.fig,
                self.selector_panel_play_indicator_update,
                init_func=self.selector_panel_play_indicator_init,
                interval=10,
                blit=self.use_blit,
                repeat=False
            )

            self.animation_highlight_panel = animation.FuncAnimation(
                self.fig,
                self.highlight_panel_play_indicator_update,
                init_func=self.highlight_panel_play_indicator_init,
                interval=10,
                blit=self.use_blit,
                repeat=False
            )

            self.fig.canvas.draw()

    def on_select(self, x_min, x_max):
        x_min = int(x_min)
        x_max = int(x_max)
        if math.fabs(x_min-x_max) < 10:
            # Reset highlight
            self.begin_time = self.x[0] / float(self.audio.fs)
            self.end_time = self.x[-1] / float(self.audio.fs)
            if self.ax3:
                self.ax3.set_xlim(self.begin_time, self.end_time)

            # Set signal highlight panel
            if self.mode == 'spectrogram':
                self.ax2.set_xlim(0, self.D.shape[1])
            elif self.mode == 'time_domain':
                self.ax2.set_xlim(self.timedomain_locations[0], self.timedomain_locations[-1])

            self.slider_time.stay_rect.set_visible(False)

        else:
            # Set annotation panel
            self.begin_time = float(x_min) / self.audio.fs
            self.end_time = float(x_max) / self.audio.fs
            if self.ax3:
                self.ax3.set_xlim(self.begin_time, self.end_time)

            # Set signal highlight panel
            if self.mode == 'spectrogram':
                spec_min = int(x_min / float(self.spec_hop_size))
                spec_max = int(x_max / float(self.spec_hop_size))

                self.ax2.set_xlim(spec_min, spec_max)

            elif self.mode == 'time_domain':
                index_min, index_max = numpy.searchsorted(self.x, (x_min, x_max))
                index_max = min(len(self.x) - 1, index_max)
                this_x = self.timedomain_locations[index_min:index_max]
                self.ax2.set_xlim(this_x[0], this_x[-1])

            self.slider_time.stay_rect.set_visible(True)

        self.fig.canvas.draw()

    def on_play(self, event):
        if self.audio.playing:
            self.audio.stop()  # Stop current playback
            try:
                self.event_panel_indicator_line.set_visible(False)
                self.event_panel_indicator_line.remove()
            except:
                pass

            if self.animation_event_roll_panel is not None:
                self.animation_event_roll_panel._stop()
                self.animation_event_roll_panel = None

            try:
                self.selector_panel_indicator_line.set_visible(False)
                self.selector_panel_indicator_line.remove()
            except:
                pass

            if self.animation_selector_panel is not None:
                self.animation_selector_panel._stop()
                self.animation_selector_panel = None

            try:
                self.highlight_panel_indicator_line.set_visible(False)
                self.highlight_panel_indicator_line.remove()
            except:
                pass

            if self.animation_highlight_panel is not None:
                self.animation_highlight_panel._stop()
                self.animation_highlight_panel = None

            self.fig.canvas.draw()

            time.sleep(0.25)  # Wait until playback has stopped

        self.audio.play(
            offset=self.begin_time,
            duration=self.end_time-self.begin_time
        )

        self.button_play.color = self.button_color['on']
        self.button_play.hovercolor = self.button_color['on']

        self.button_stop.color = self.button_color['off']
        self.button_stop.hovercolor = self.button_color['off']

        self.playback_offset = self.begin_time

        self.animation_event_roll_panel = animation.FuncAnimation(
            self.fig,
            self.event_roll_panel_play_indicator_update,
            init_func=self.event_roll_panel_play_indicator_init,
            interval=50,
            blit=self.use_blit,
            repeat=False
        )

        self.animation_selector_panel = animation.FuncAnimation(
            self.fig,
            self.selector_panel_play_indicator_update,
            init_func=self.selector_panel_play_indicator_init,
            interval=50,
            blit=self.use_blit,
            repeat=False
        )

        self.animation_highlight_panel = animation.FuncAnimation(
            self.fig,
            self.highlight_panel_play_indicator_update,
            init_func=self.highlight_panel_play_indicator_init,
            interval=50,
            blit=self.use_blit,
            repeat=False
        )

        self.fig.canvas.draw()

    def on_pause(self, event):
        self.audio.pause()

        self.button_play.color = self.button_color['off']
        self.button_play.hovercolor = self.button_color['off']

        self.button_stop.color = self.button_color['off']
        self.button_stop.hovercolor = self.button_color['off']

        self.fig.canvas.draw()

    def on_stop(self, event):
        self.audio.stop()

        self.button_play.color = self.button_color['off']
        self.button_play.hovercolor = self.button_color['off']

        self.button_stop.color = self.button_color['off']
        self.button_stop.hovercolor = self.button_color['off']

        self.fig.canvas.draw()

    def on_quit(self, event):
        self._quit = True
        self.on_close_window(event=event)

    @property
    def quit(self):
        return self._quit

    def event_roll_panel_play_indicator_init(self):
        indicator_width = (self.end_time-self.begin_time) / 1000
        if indicator_width > 0.5:
            indicator_width = 0.5

        self.event_panel_indicator_line = patches.Rectangle(
            (self.playback_offset + self.audio.get_time(), -0.5),
            height=self.event_label_count,
            width=indicator_width,
            edgecolor=self.indicator_line_color,
            facecolor=self.indicator_line_color,
            alpha=0.8
        )

        self.ax3.add_patch(self.event_panel_indicator_line)
        return self.event_panel_indicator_line,

    def event_roll_panel_play_indicator_update(self, i):
        if self.audio.playing:
            self.event_panel_indicator_line.set_x(self.playback_offset + self.audio.get_time())
        else:
            self.event_panel_indicator_line.set_visible(False)
            if self.animation_event_roll_panel is not None:
                self.animation_event_roll_panel.event_source.stop()
                self.animation_event_roll_panel = None

            self.fig.canvas.draw()

        return self.event_panel_indicator_line,

    def selector_panel_play_indicator_init(self):
        self.selector_panel_indicator_line = patches.Rectangle(
            (0, -1),
            height=2,
            width=0.5,
            edgecolor=self.indicator_line_color,
            facecolor=self.indicator_line_color,
            alpha=0.8
        )

        self.ax1.add_patch(self.selector_panel_indicator_line)
        return self.selector_panel_indicator_line,

    def selector_panel_play_indicator_update(self, i):
        if self.audio.playing:
            self.selector_panel_indicator_line.set_x((self.playback_offset + self.audio.get_time())*self.audio.fs)
        else:
            self.selector_panel_indicator_line.set_visible(False)
            if self.animation_selector_panel is not None:
                self.animation_selector_panel.event_source.stop()
                self.animation_selector_panel = None

        return self.selector_panel_indicator_line,

    def highlight_panel_play_indicator_init(self):
        indicator_width = 0.5
        if self.mode == 'spectrogram':
            indicator_height = self.spec_fft_size
            indicator_y = 0

        elif self.mode == 'time_domain':
            indicator_height = 2
            indicator_y = -1

        else:
            indicator_height = 2
            indicator_y = -1

        self.highlight_panel_indicator_line = patches.Rectangle(
            (0, indicator_y),
            height=indicator_height,
            width=indicator_width,
            edgecolor=self.indicator_line_color,
            facecolor=self.indicator_line_color,
            alpha=0.8
        )

        self.ax2.add_patch(self.highlight_panel_indicator_line)
        return self.highlight_panel_indicator_line,

    def highlight_panel_play_indicator_update(self, i):
        if self.audio.playing:
            if self.mode == 'spectrogram':
                self.highlight_panel_indicator_line.set_x(
                    (self.playback_offset + self.audio.get_time()) * self.audio.fs / float(self.spec_hop_size)
                )

            elif self.mode == 'time_domain':
                self.highlight_panel_indicator_line.set_x(
                    (self.playback_offset + self.audio.get_time()) * self.audio.fs
                )

        else:
            self.highlight_panel_indicator_line.set_visible(False)
            if self.animation_highlight_panel is not None:
                self.animation_highlight_panel.event_source.stop()
                self.animation_highlight_panel = None

        return self.highlight_panel_indicator_line,

    def time_ticks(self, locations, n_ticks=10, sampling_rate=44100):
        times = self.samples_to_time(locations, sampling_rate=sampling_rate)
        positions = numpy.linspace(0, len(locations)-1, n_ticks, endpoint=True).astype(int)
        locations = locations[positions]
        times = times[positions]
        times = ['{:0.2f}s'.format(t) for t in times]

        return plt.xticks(locations, times)

    @staticmethod
    def time_to_samples(time, sampling_rate=44100):
        return (numpy.atleast_1d(time) * sampling_rate).astype(int)

    @staticmethod
    def samples_to_time(samples, sampling_rate=44100):
        return numpy.atleast_1d(samples) / float(sampling_rate)

    @staticmethod
    def get_spectrogram(audio, n_fft=256, win_length=1024, hop_length=1024):
        fft_window = scipy.signal.hann(win_length, sym=False).reshape((-1, 1))

        audio = numpy.pad(array=audio,
                          pad_width=int(n_fft // 2),
                          mode='reflect')

        n_frames = 1 + int((len(audio) - n_fft) / hop_length)
        y_frames = as_strided(x=audio,
                              shape=(n_fft, n_frames),
                              strides=(audio.itemsize, int(hop_length * audio.itemsize)))

        S = numpy.empty((int(1 + n_fft // 2), y_frames.shape[1]), dtype=numpy.complex64, order='F')

        max_memory_block = 2**8 * 2**10
        n_columns = int(max_memory_block / (S.shape[0] * S.itemsize))

        for bl_s in range(0, S.shape[1], n_columns):
            bl_t = min(bl_s + n_columns, S.shape[1])

            # RFFT and Conjugate here to match phase from DPWE code
            S[:, bl_s:bl_t] = scipy.fftpack.fft(fft_window * y_frames[:, bl_s:bl_t], axis=0)[:S.shape[0]].conj()

        magnitude = numpy.abs(S) ** 2

        ref = numpy.max(magnitude)
        amin=1e-10
        top_db = 80.0

        log_spec = 10.0 * numpy.log10(numpy.maximum(amin, magnitude))
        log_spec -= 10.0 * numpy.log10(numpy.maximum(amin, ref))

        log_spec = numpy.maximum(log_spec, log_spec.max() - top_db)

        return log_spec

    @staticmethod
    def plot_spectrogram(data, sampling_rate=44100, n_yticks=5, interpolation='nearest', cmap='magma'):

        axes = plt.imshow(data, aspect='auto', origin='lower', interpolation=interpolation, cmap=plt.get_cmap(cmap))

        # X axis
        plt.xticks([])

        # Y axis
        positions = numpy.linspace(0, data.shape[0]-1, n_yticks, endpoint=True).astype(int)
        values = numpy.linspace(0, 0.5 * sampling_rate, data.shape[0], endpoint=True).astype(int)

        t_log = (data.shape[0] * (1 - numpy.logspace(-numpy.log2(data.shape[0]), 0, data.shape[0], base=2, endpoint=True))[::-1]).astype(int)
        t_inv = numpy.arange(len(t_log))
        for i in range(len(t_log)-1):
            t_inv[t_log[i]:t_log[i+1]] = i

        plt.yticks(positions, values[t_inv[positions]])

        return axes


class EventListVerifier(EventListVisualizer):
    def __init__(self, *args, **kwargs):
        super(EventListVerifier, self).__init__(*args, **kwargs)
        self.verification_answer_id = None
        self.verification_answer_value = None
        self.verification_values = kwargs.get('verification_values', [
            'A',
            'B',
            'C'
        ])

        self.verification_button_colors = kwargs.get('verification_button_colors',[
            {
                'off': '#AAAAAA',
                'on':'#b32400',
            },
            {
                'off': '#AAAAAA',
                'on': '#996600',

            },
            {
                'off': '#AAAAAA',
                'on': '#009933',
            },
        ])

        self.button_color = kwargs.get('button_color', {
            'off': '#AAAAAA',
            'on': '#666666'
        })

        self.button_verification = {}
        self.button_verification_axis = {}

        if self.buttons['verification']:
            # Verification panel
            # ====================================
            self.selector_panel_loc = 20
            self.highlight_panel_loc = 37
            self.event_roll_panel_loc = 75

            self.selector_panel_height = 10
            self.highlight_panel_height = 35
            self.event_roll_panel_height = 20

    def generate_GUI(self):
        """Generates the visualizer GUI."""

        self.fig = plt.figure(figsize=self.fig_shape)

        # Selector panel
        # ====================================
        if self.show_selector:
            self.ax1 = plt.subplot2grid(
                shape=(100, 1),
                loc=(self.selector_panel_loc, 0),
                rowspan=self.selector_panel_height,
                colspan=1
            )

            self.timedomain_locations = numpy.arange(0, self.audio.signal.shape[0])

            self.ax1.fill_between(
                self.timedomain_locations[::self.waveform_selector_point_hop],
                self.audio.signal[::self.waveform_selector_point_hop],
                -self.audio.signal[::self.waveform_selector_point_hop],
                color='0.5'
            )

            plt.yticks([])
            plt.axis('tight')

            self.ax1.set_xlim(self.timedomain_locations[0], self.timedomain_locations[-1])
            self.ax1.set_ylim(-1, 1)

            self.time_ticks(
                locations=self.timedomain_locations,
                n_ticks=10,
                sampling_rate=self.audio.fs
            )

            self.ax1.yaxis.grid(False, which='major')
            self.ax1.yaxis.grid(False, which='minor')
            self.ax1.xaxis.grid(True, which='major')
            self.ax1.xaxis.grid(True, which='minor')
            self.ax1.yaxis.set_label_position("right")

            plt.title(self.labels['selection'], fontsize=self.panel_title_font_size)

        # Highlight panel
        # ====================================
        self.ax2 = plt.subplot2grid(
            shape=(100, 1),
            loc=(self.highlight_panel_loc, 0),
            rowspan=self.highlight_panel_height,
            colspan=1
        )

        self.x = numpy.arange(0, self.audio.duration_samples)

        self.begin_time = float(self.x[0]) / self.audio.fs
        self.end_time = float(self.x[-1]) / self.audio.fs

        if self.mode == 'spectrogram':
            self.D = self.get_spectrogram(
                audio=self.audio.signal,
                n_fft=self.spec_fft_size,
                win_length=self.spec_win_size,
                hop_length=self.spec_hop_size
            )

            self.plot_spectrogram(
                self.D,
                sampling_rate=self.audio.fs,
                interpolation=self.spec_interpolation,
                cmap=self.spec_cmap
            )

            if not self.publication_mode:
                self.ax2.yaxis.grid(False, which='major')
                self.ax2.yaxis.grid(False, which='minor')
                self.ax2.xaxis.grid(False, which='major')
                self.ax2.xaxis.grid(False, which='minor')

                plt.ylabel(self.labels['spectrogram'], fontsize=self.panel_title_font_size)
            else:
                self.ax2.get_yaxis().set_visible(False)

        elif self.mode == 'time_domain':

            self.ax2.fill_between(
                self.x[::self.waveform_highlight_point_hop],
                self.audio.signal[::self.waveform_highlight_point_hop],
                -self.audio.signal[::self.waveform_highlight_point_hop],
                color=self.waveform_highlight_color
            )

            self.ax2.set_ylim(-1, 1)
            self.ax2.set_xlim(self.x[0], self.x[-1])

            segment_begin = self.time_to_samples(time=0, sampling_rate=self.audio.fs)
            segment_end = self.time_to_samples(time=self.audio.duration_seconds, sampling_rate=self.audio.fs)
            locs = numpy.arange(segment_begin, segment_end)
            plt.xlim([locs[0], locs[-1]])
            self.time_ticks(locations=locs, n_ticks=20)

            plt.yticks([])
            plt.xticks([])
            self.ax2.yaxis.grid(False, which='major')
            self.ax2.yaxis.grid(False, which='minor')

            self.ax2.xaxis.grid(True, which='major')
            self.ax2.xaxis.grid(True, which='minor')

            if not self.publication_mode:
                plt.ylabel(self.labels['waveform'], fontsize=self.panel_title_font_size)

        self.ax2.yaxis.set_label_position("right")

        plt.axis('tight')

        # Event roll panel
        # ====================================
        if self._event_lists:
            event_list_count = len(self._event_lists)

            self.begin_time = 0
            self.end_time = self.audio.duration_seconds

            if event_list_count == 1:
                norm = colors.Normalize(
                    vmin=0,
                    vmax=self.event_label_count
                )
                self.ax3 = plt.subplot2grid(
                    shape=(100, 1),
                    loc=(self.event_roll_panel_loc, 0),
                    rowspan=self.event_roll_panel_height+10,
                    colspan=1
                )

            else:
                norm = colors.Normalize(
                    vmin=0,
                    vmax=event_list_count
                )
                self.ax3 = plt.subplot2grid(
                    shape=(100, 1),
                    loc=(self.event_roll_panel_loc, 0),
                    rowspan=self.event_roll_panel_height,
                    colspan=1
                )

            m = cm.ScalarMappable(
                norm=norm,
                cmap=self.label_colormap
            )

            line_margin = 0.1
            y = 0
            annotation_height = (1.0-line_margin*2)/event_list_count

            for label in self.active_events:
                for event_list_id, event_list_label in enumerate(self._event_list_order):
                    offset = (len(self._event_list_order)-1-event_list_id) * annotation_height

                    event_y = y - 0.5 + line_margin + offset

                    # grid line
                    line = plt.Rectangle(
                        (0, y-0.5),
                        height=0.001,
                        width=self.end_time,
                        edgecolor='black',
                        facecolor='black'
                    )

                    plt.gca().add_patch(line)

                    for event in self._event_lists[event_list_label]:
                        if event['event_label'] == label:
                            event_length = event['offset'] - event['onset']

                            if event_list_count == 1:
                                color = m.to_rgba(y + offset)
                            else:
                                color = m.to_rgba(event_list_id)

                            rectangle = plt.Rectangle(
                                (event['onset'], event_y),
                                height=annotation_height,
                                width=event_length,
                                edgecolor='black',
                                facecolor=color,
                                linewidth=0,
                                alpha=self.event_roll_item_opacity,
                                picker=5
                            )

                            plt.gca().add_patch(rectangle)
                y += 1

            # Grid line
            line = plt.Rectangle(
                (0, y - 0.5),
                height=0.001,
                width=self.end_time,
                edgecolor='black',
                facecolor='black'
            )

            plt.gca().add_patch(line)

            # Axis
            plt.axis([0, self.audio.duration_seconds, -0.5, len(self.active_events) + 0.5])
            locs = numpy.arange(0, self.audio.duration_seconds, 0.00001)

            plt.xlim([locs[0], locs[-1]])
            plt.axis('tight')

            # X axis
            self.ax3.xaxis.grid(True, which='major')
            self.ax3.xaxis.grid(True, which='minor')
            plt.tick_params(
                axis='x',
                which='major',
                labelsize=self.event_roll_time_font_size
            )

            # Y axis
            plt.yticks(
                numpy.arange(len(self.active_events)),
                self.active_events,
                fontsize=self.event_roll_label_font_size
            )

            plt.ylabel('Event Roll', fontsize=self.panel_title_font_size)
            self.ax3.yaxis.set_label_position('right')
            self.ax3.yaxis.grid(False, which='major')
            self.ax3.yaxis.grid(False, which='minor')

            # Set event list legends panel
            self.ax3.set_xlim(self.begin_time, self.end_time)
            if event_list_count > 1:
                span = 0
                for event_list_id, event_list_label in enumerate(self._event_list_order):

                    ax_legend_color = plt.axes([0.225+span, 0.02, 0.02, 0.02])
                    Button(
                        ax_legend_color,
                        '',
                        color=m.to_rgba(event_list_id),
                        hovercolor=m.to_rgba(event_list_id)
                    )

                    ax_legend_label = plt.axes([0.225+0.025+span, 0.02, 0.10, 0.04])
                    ax_legend_label.axis('off')
                    ax_legend_label.text(0, 0, event_list_label, fontsize=self.legend_font_size)
                    span += 0.15

        if self.show_selector:
            self.slider_time = SpanSelector(
                ax=self.ax1,
                onselect=self.on_select,
                minspan=None,
                direction='horizontal',
                span_stays=True,
                useblit=self.use_blit,
                onmove_callback=None,
                rectprops=dict(alpha=0.15, facecolor=self.color)
            )

        if not self.publication_mode:
            ax_legend_label = plt.axes([0.92, 0.02, 0.10, 0.04])
            ax_legend_label.axis('off')
            ax_legend_label.text(0, 0, 'sed_vis', fontsize=16)

            # Buttons
            # ====================================
            if self.buttons['play']:
                ax_play = plt.axes([0.125, 0.93, 0.07, 0.04])
                self.button_play = Button(
                    ax_play,
                    self.labels['play'],
                    color=self.button_color['off'],
                    hovercolor=self.button_color['on']
                )

                self.button_play.on_clicked(self.on_play)

            if self.buttons['stop']:
                ax_stop = plt.axes([0.205, 0.93, 0.07, 0.04])
                self.button_stop = Button(
                    ax_stop,
                    self.labels['stop'],
                    color=self.button_color['off'],
                    hovercolor=self.button_color['on']
                )

                self.button_stop.on_clicked(self.on_stop)

            if self.buttons['close']:
                ax_close = plt.axes([0.92, 0.93, 0.07, 0.04])
                self.button_close = Button(
                    ax_close,
                    self.labels['close'],
                    color=self.button_color['off'],
                    hovercolor=self.button_color['on']
                )

                self.button_close.on_clicked(self.on_close_window)

            if self.buttons['quit']:
                ax_quit = plt.axes([0.78, 0.93, 0.07, 0.04])
                self.button_quit = Button(
                    ax_quit,
                    self.labels['quit'],
                    color=self.button_color['off'],
                    hovercolor=self.button_color['on']
                )

                self.button_quit.on_clicked(self.on_quit)

            if self.buttons['verification']:
                start_x = 0.125 + 0.2 #25
                end_x = 0.125+0.775 -0.2 #1 - start_x
                width = (end_x - start_x)
                spacing = width / float(len(self.verification_values))

                # Verification text
                plt.axes([0.125, 0.88, 0.775, 0.04], frameon=False)
                plt.xticks([])
                plt.yticks([])
                plt.text(
                    0.5, 0.5,
                    self.labels['verification'],
                    fontsize=14,
                    horizontalalignment='center',
                    verticalalignment='center'
                )

                # Verification-info text
                plt.axes([0.00, 0.80, 0.125 + 0.18, 0.08], frameon=False)
                plt.xticks([])
                plt.yticks([])
                plt.text(
                    1, 0.5,
                    self.labels['verification_info'],
                    fontsize=18,
                    horizontalalignment='right',
                    verticalalignment='center'
                )

                for i, label in enumerate(self.verification_values):
                    self.button_verification_axis[i] = plt.axes([start_x+(i*spacing), 0.80, 0.12, 0.08])
                    self.button_verification[i] = Button(
                        self.button_verification_axis[i],
                        label,
                        color=self.verification_button_colors[i]['off'],
                        hovercolor=self.verification_button_colors[i]['off']
                    )

                    self.button_verification[i].label.set_fontsize(12)
                    self.button_verification[i].on_clicked(self.on_verification)

            # Info text
            plt.axes([start_x, 0.02, width, 0.04], frameon=False)
            plt.xticks([])
            plt.yticks([])
            plt.text(0.5, 0.5,
                     self.labels['info'],
                     fontsize=12,
                     horizontalalignment='center',
                     verticalalignment='center'
                     )

            self.fig.canvas.mpl_connect('pick_event', self.on_pick)
        else:
            plt.subplots_adjust(
                left=0.12,
                bottom=0.05,
                right=.97,
                top=0.95,
                wspace=0,
                hspace=0
            )

        if self.auto_play:
            self.on_play(None)

    def on_verification(self, event):
        for i in self.button_verification:
            if event.inaxes == self.button_verification_axis[i]:
                self.button_verification[i].color = self.verification_button_colors[i]['on']
                self.button_verification[i].hovercolor = self.verification_button_colors[i]['on']
                self.verification_answer_id =i
                self.verification_answer_value = self.verification_values[i]

            else:
                self.button_verification[i].color = self.verification_button_colors[i]['off']
                self.button_verification[i].hovercolor = self.verification_button_colors[i]['off']

    def get_answer(self):
        return self.verification_answer_id, self.verification_answer_value


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Visualization
==================
This is module contains a simple visualizer to show event lists along with the audio.
The visualizer can show multiple event lists for the same reference audio allowing the
comparison of the reference and estimated event lists.

 .. image:: visualization.png


.. autosummary::
    :toctree: generated/

    EventListVisualizer
    EventListVisualizer.show

"""
from __future__ import print_function, absolute_import
from sed_vis.util import AudioPlayer, AudioThread
import dcase_util
import numpy
import math
import time

import scipy.fftpack
import scipy.signal
from numpy.lib.stride_tricks import as_strided
from sys import platform as _platform
import matplotlib
if _platform == "darwin":
    # MAC OS X
    matplotlib.use('TkAgg')

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib.patches as patches
import matplotlib.colors as colors
import matplotlib.cm as cm

from matplotlib.widgets import Button, SpanSelector
from matplotlib.patches import Rectangle


class EventListVisualizer(object):
    """Event List visualizer.

    Examples
    --------
    >>> # Load audio signal first
    >>> audio_container = dcase_util.containers.AudioContainer().load('data/audio.wav')
    >>> # Load event lists
    >>> reference_event_list = dcase_util.containers.MetaDataContainer().load('data/reference.txt')
    >>> estimated_event_list = dcase_util.containers.MetaDataContainer().load('data/estimated.txt')
    >>> event_lists = {'reference': reference_event_list, 'estimated': estimated_event_list}
    >>> # Visualize the data
    >>> vis = sed_vis.visualization.EventListVisualizer(event_lists=event_lists,audio_signal=audio_container.data,sampling_rate=audio_container.fs)
    >>> vis.show()

    """

    def __init__(self, *args, **kwargs):
        """Constructor

        Parameters
        ----------
        event_lists : dict of event lists
            Dict of event lists

        event_list_order : list
            Order of event list, if None alphabetical order used
            (Default value=None)

        active_events : list
            List of active sound event classes, if None all used.
            (Default value=None)

        audio_signal : np.ndarray
            Audio signal

        sampling_rate : int
            Sampling rate [0:96000]

        mode: str
            Signal visualization mode ['spectrogram', 'time_domain']
            (Default value = 'spectrogram')

        spec_hop_size : int
            Spectrogram calculation hop length in samples
            (Default value=256)

        spec_win_size: int ,
            Spectrogram calculation window length in samples
            (Default value=1024)

        spec_fft_size: int
            FFT length
            (Default value=1024)

        spec_cmap : str
            Color map used for spectrogram, see examples: http://matplotlib.org/examples/color/colormaps_reference.html
            (Default value='magma')

        spec_interpolation : str
            Matrix interpolation method for spectrogram (e.g. nearest, bilear, bicubic, quadric, gaussian)
            (Default value='nearest')

        event_roll_cmap : str
            Color map used for spectrogram, see examples: http://matplotlib.org/examples/color/colormaps_reference.html
            (Default value='rainbow')

        minimum_event_length : float > 0.0
            Minimum event length in seconds, shorten than given are filtered out from the output.
            (Default value=None)

        minimum_event_gap : float > 0.0
            Minimum allowed gap between events in seconds from same event label class.
            (Default value=None)

        color : color hex
            Main color code used in highlighting things

        use_blit : bool
            Use blit
            (Default value=False)

        publication_mode : bool
            Strip visual elements, can be used to prepare figures for publications.
            (Default value=False)

        show_selector : bool
            Show highlight selector
            (Default value=True)

        labels: dict
            Text labels overrides

        button_color : dict
            Button color overrides

        Returns
        -------
        Nothing

        """

        if kwargs.get('event_lists', []):
            self._event_lists = kwargs.get('event_lists', [])

            if kwargs.get('event_list_order') is None:
                self._event_list_order = sorted(self._event_lists.keys())
            else:
                self._event_list_order = kwargs.get('event_list_order')

            events = dcase_util.containers.MetaDataContainer()
            for event_list_label in self._event_lists:
                events += self._event_lists[event_list_label]

            self.event_labels = sorted(events.unique_event_labels, reverse=True)
            self.event_label_count = events.event_label_count

            if kwargs.get('active_events') is None:
                self.active_events = self.event_labels

            else:
                self.active_events = sorted(kwargs.get('active_events'), reverse=True)

            for name in self._event_lists:
                self._event_lists[name] = self._event_lists[name].process_events(
                    minimum_event_length=kwargs.get('minimum_event_length'),
                    minimum_event_gap=kwargs.get('minimum_event_gap')
                )

        else:
            self._event_lists = None

        if kwargs.get('audio_signal') is not None and kwargs.get('sampling_rate') is not None:
            audio_signal = kwargs.get('audio_signal') / numpy.max(numpy.abs(kwargs.get('audio_signal')))
            self.audio = AudioPlayer(
                signal=audio_signal,
                sampling_rate=kwargs.get('sampling_rate')
            )

        if kwargs.get('mode') not in ['spectrogram', 'time_domain']:
            self.mode = 'spectrogram'
        else:
            self.mode = kwargs.get('mode')

        self.auto_play = kwargs.get('auto_play', False)

        self.spec_hop_size = kwargs.get('spec_hop_size', 256)
        self.spec_win_size = kwargs.get('spec_win_size', 1024)
        self.spec_fft_size = kwargs.get('spec_fft_size', 1024)
        self.spec_cmap = kwargs.get('spec_cmap', 'magma')
        self.spec_interpolation =  kwargs.get('spec_interpolation', 'nearest')

        self.color = kwargs.get('color', '#339933')

        self.button_color = {
            'off': 'grey',
            'on': 'red'
        }

        self.button_color.update(kwargs.get('button_color',{}))
        self.labels = {
            'close': 'Close',
            'play': 'Play',
            'stop': 'Stop',
            'quit': 'Quit',
            'selection': 'Selection',
            'waveform': 'Waveform',
            'spectrogram': 'Spectrogram',
            'verification': 'Verification',
            'verification_info': '',
            'info': '',
        }

        self.labels.update(kwargs.get('labels',{}))

        self.indicator_line_color = self.color

        # Initialize members
        self.fig = None

        # Panels
        self.ax1 = None
        self.ax2 = None
        self.ax3 = None

        self.D = None
        self.x = None
        self.timedomain_locations = None

        self.begin_time = None
        self.end_time = None
        self.playback_offset = 0

        # Play indicators
        self.animation_event_roll_panel = None
        self.animation_selector_panel = None
        self.animation_highlight_panel = None

        self.event_panel_indicator_line = None
        self.selector_panel_indicator_line = None
        self.highlight_panel_indicator_line = None

        self.slider_time = None

        # Buttons
        self.buttons = {
            'play': True,
            'pause': False,
            'stop': True,
            'close': True,
            'quit': False,
            'verification': False,
        }
        self.buttons.update(kwargs.get('buttons',{}))

        self.button_play = None
        self.button_pause = None
        self.button_stop = None
        self.button_close = None

        self.use_blit = kwargs.get('use_blit', False)

        self.publication_mode = kwargs.get('publication_mode', False)
        self.show_selector = kwargs.get('show_selector', True)

        self.panel_title_font_size = 14
        self.legend_font_size = 12
        self.event_roll_label_font_size = 14
        self.event_roll_time_font_size = 10

        self.waveform_selector_point_hop = kwargs.get('waveform_selector_point_hop', 1000)
        self.waveform_highlight_point_hop = 100
        self.waveform_highlight_color = self.color

        self.selector_panel_height = 10
        self.highlight_panel_height = 25
        self.event_roll_panel_height = 50

        self.selector_panel_loc = 0
        self.highlight_panel_loc = 17
        self.event_roll_panel_loc = 45

        self.event_roll_item_opacity = 0.5
        self.fig_shape = (14, 6)

        self._quit = False

        if self.publication_mode:
            self.panel_title_font_size = 14
            self.legend_font_size = 16
            self.event_roll_time_font_size = 12

            self.spec_cmap = 'magma_r'
            self.spec_interpolation = 'bicubic'
            if not self.waveform_selector_point_hop:
                self.waveform_selector_point_hop = 5000
            self.waveform_highlight_point_hop = 500
            self.waveform_highlight_color = 'black'
            if self.show_selector:
                if self.mode == 'time_domain':
                    self.fig_shape = (30, 4)

                elif self.mode == 'spectrogram':
                    self.fig_shape = (20, 5)

                if self._event_lists:
                    if self.event_label_count == 1:
                        self.selector_panel_height = 10
                        self.highlight_panel_height = 33
                        self.event_roll_panel_height = 33

                        self.selector_panel_loc = 0
                        self.highlight_panel_loc = 17
                        self.event_roll_panel_loc = 53

                        self.event_roll_time_font_size = 16

                    else:
                        self.selector_panel_height = 10
                        self.highlight_panel_height = 15
                        self.event_roll_panel_height = 60

                        self.selector_panel_loc = 0
                        self.highlight_panel_loc = 17
                        self.event_roll_panel_loc = 35

                else:
                    self.selector_panel_height = 30
                    self.highlight_panel_height = 66
                    self.event_roll_panel_height = 0

                    self.selector_panel_loc = 0
                    self.highlight_panel_loc = 37
                    self.event_roll_panel_loc = 0

                    self.event_roll_time_font_size = 16

            else:
                if self.mode == 'time_domain':
                    self.fig_shape = (30, 4)

                elif self.mode == 'spectrogram':
                    self.fig_shape = (20, 4)

                self.selector_panel_height = 0
                self.highlight_panel_height = 15
                self.event_roll_panel_height = 75

                self.selector_panel_loc = 0
                self.highlight_panel_loc = 0
                self.event_roll_panel_loc = 17

            self.event_roll_item_opacity = 1.0

        self.label_colormap = cm.get_cmap(name=kwargs.get('event_roll_cmap','rainbow'))

    def generate_GUI(self):
        """Generates the visualizer GUI.

        Parameters
        ----------
        Nothing

        Returns
        -------
        Nothing

        """

        self.fig = plt.figure(figsize=self.fig_shape)

        # Selector panel
        # ====================================
        if self.show_selector:
            self.ax1 = plt.subplot2grid(shape=(100, 1), loc=(self.selector_panel_loc, 0), rowspan=self.selector_panel_height, colspan=1)

            self.timedomain_locations = numpy.arange(0, self.audio.signal.shape[0])

            self.ax1.fill_between(
                self.timedomain_locations[::self.waveform_selector_point_hop],
                self.audio.signal[::self.waveform_selector_point_hop],
                -self.audio.signal[::self.waveform_selector_point_hop],
                color='0.5'
            )

            plt.yticks([])
            plt.axis('tight')

            self.ax1.set_xlim(self.timedomain_locations[0], self.timedomain_locations[-1])
            self.ax1.set_ylim(-1, 1)

            self.time_ticks(
                locations=self.timedomain_locations,
                n_ticks=10,
                sampling_rate=self.audio.fs
            )

            self.ax1.yaxis.grid(False, which='major')
            self.ax1.yaxis.grid(False, which='minor')
            self.ax1.xaxis.grid(True, which='major')
            self.ax1.xaxis.grid(True, which='minor')
            self.ax1.yaxis.set_label_position("right")

            plt.title(self.labels['selection'], fontsize=self.panel_title_font_size)

        # Highlight panel
        # ====================================
        self.ax2 = plt.subplot2grid(shape=(100, 1), loc=(self.highlight_panel_loc, 0), rowspan=self.highlight_panel_height, colspan=1)
        self.x = numpy.arange(0, self.audio.duration_samples)

        self.begin_time = self.x[0] / float(self.audio.fs)
        self.end_time = self.x[-1] / float(self.audio.fs)

        if self.mode == 'spectrogram':
            self.D = self.get_spectrogram(
                audio=self.audio.signal,
                n_fft=self.spec_fft_size,
                win_length=self.spec_win_size,
                hop_length=self.spec_hop_size
            )

            self.plot_spectrogram(
                self.D,
                sampling_rate=self.audio.fs,
                interpolation=self.spec_interpolation,
                cmap=self.spec_cmap
            )

            if not self.publication_mode:
                self.ax2.yaxis.grid(False, which='major')
                self.ax2.yaxis.grid(False, which='minor')
                self.ax2.xaxis.grid(False, which='major')
                self.ax2.xaxis.grid(False, which='minor')

                plt.ylabel(self.labels['spectrogram'], fontsize=self.panel_title_font_size)
            else:
                self.ax2.get_yaxis().set_visible(False)

        elif self.mode == 'time_domain':

            self.ax2.fill_between(
                self.x[::self.waveform_highlight_point_hop], self.audio.signal[::self.waveform_highlight_point_hop], -self.audio.signal[::self.waveform_highlight_point_hop],
                color=self.waveform_highlight_color
            )

            self.ax2.set_ylim(-1, 1)
            self.ax2.set_xlim(self.x[0], self.x[-1])

            segment_begin = self.x[0] / float(self.audio.fs)
            segment_end = self.x[-1] / float(self.audio.fs)

            locs = numpy.arange(segment_begin, segment_end)
            plt.xlim([locs[0], locs[-1]])
            self.time_ticks(locations=locs, n_ticks=20)

            plt.yticks([])
            plt.xticks([])
            self.ax2.yaxis.grid(False, which='major')
            self.ax2.yaxis.grid(False, which='minor')

            self.ax2.xaxis.grid(True, which='major')
            self.ax2.xaxis.grid(True, which='minor')
            if not self.publication_mode:
                plt.ylabel(self.labels['waveform'], fontsize=self.panel_title_font_size)

            self.ax2.set_xlim(self.timedomain_locations[0], self.timedomain_locations[-1])

        self.ax2.yaxis.set_label_position("right")

        # Event roll panel
        # ====================================
        if self._event_lists:
            event_list_count = len(self._event_lists)

            self.begin_time = 0
            self.end_time = self.audio.duration_seconds

            if event_list_count == 1:
                norm = colors.Normalize(
                    vmin=0,
                    vmax=self.event_label_count
                )
                self.ax3 = plt.subplot2grid(
                    shape=(100, 1),
                    loc=(self.event_roll_panel_loc, 0),
                    rowspan=self.event_roll_panel_height+10,
                    colspan=1
                )

            else:
                norm = colors.Normalize(
                    vmin=0,
                    vmax=event_list_count
                )
                self.ax3 = plt.subplot2grid(
                    shape=(100, 1),
                    loc=(self.event_roll_panel_loc, 0),
                    rowspan=self.event_roll_panel_height,
                    colspan=1
                )

            m = cm.ScalarMappable(norm=norm, cmap=self.label_colormap)

            line_margin = 0.1
            y = 0
            annotation_height = (1.0-line_margin*2)/event_list_count

            for label in self.active_events:
                for event_list_id, event_list_label in enumerate(self._event_list_order):
                    offset = (len(self._event_list_order)-1-event_list_id) * annotation_height

                    event_y = y - 0.5 + line_margin + offset

                    # grid line
                    line = plt.Rectangle(
                        (0, y-0.5),
                        height=0.001,
                        width=self.end_time,
                        edgecolor='black',
                        facecolor='black'
                    )

                    plt.gca().add_patch(line)

                    for event in self._event_lists[event_list_label]:
                        if event['event_label'] == label:
                            event_length = event['offset'] - event['onset']

                            if 'probability' in event:
                                if event_list_count == 1:
                                    color = m.to_rgba(x=y + offset, alpha=event['probability'])
                                else:
                                    color = m.to_rgba(x=event_list_id, alpha=event['probability'])

                                rectangle = plt.Rectangle(
                                    (event['onset'], event_y),
                                    height=annotation_height,
                                    width=event_length,
                                    edgecolor='black',
                                    facecolor=color,
                                    linewidth=0,
                                    picker=5
                                )

                            else:
                                if event_list_count == 1:
                                    color = m.to_rgba(x=y + offset)
                                else:
                                    color = m.to_rgba(x=event_list_id)

                                rectangle = plt.Rectangle(
                                    (event['onset'], event_y),
                                    height=annotation_height,
                                    width=event_length,
                                    edgecolor='black',
                                    facecolor=color,
                                    linewidth=0,
                                    alpha=self.event_roll_item_opacity,
                                    picker=5
                                )


                            plt.gca().add_patch(rectangle)

                y += 1

            # grid line
            line = plt.Rectangle((0, y - 0.5),
                                 height=0.001,
                                 width=self.end_time,
                                 edgecolor='black',
                                 facecolor='black')
            plt.gca().add_patch(line)
            # Axis
            plt.axis([0, self.audio.duration_seconds, -0.5, len(self.active_events) + 0.5])
            locs = numpy.arange(0, self.audio.duration_seconds, 0.00001)

            plt.xlim([locs[0], locs[-1]])
            plt.axis('tight')

            # X axis
            self.ax3.xaxis.grid(True, which='major')
            self.ax3.xaxis.grid(True, which='minor')
            plt.tick_params(axis='x', which='major', labelsize=self.event_roll_time_font_size)

            # Y axis
            plt.yticks(
                numpy.arange(len(self.active_events)),
                self.active_events,
                fontsize=self.event_roll_label_font_size
            )

            plt.ylabel('Event Roll', fontsize=self.panel_title_font_size)
            self.ax3.yaxis.set_label_position('right')
            self.ax3.yaxis.grid(False, which='major')
            self.ax3.yaxis.grid(False, which='minor')

            # Set event list legends panel
            self.ax3.set_xlim(self.begin_time, self.end_time)
            if event_list_count > 1:
                span = 0
                for event_list_id, event_list_label in enumerate(self._event_list_order):

                    ax_legend_color = plt.axes([0.225+span, 0.02, 0.02, 0.02])
                    Button(
                        ax_legend_color,
                        '',
                        color=m.to_rgba(event_list_id),
                        hovercolor=m.to_rgba(event_list_id)
                    )

                    ax_legend_label = plt.axes([0.225+0.025+span, 0.02, 0.10, 0.04])
                    ax_legend_label.axis('off')
                    ax_legend_label.text(0, 0, event_list_label, fontsize=self.legend_font_size)
                    span += 0.15

        if self.show_selector:
            self.slider_time = SpanSelector(
                ax=self.ax1,
                onselect=self.on_select,
                minspan=None,
                direction='horizontal',
                span_stays=True,
                useblit=self.use_blit,
                onmove_callback=None,
                rectprops=dict(alpha=0.15, facecolor=self.color)
            )

        if not self.publication_mode:
            ax_legend_label = plt.axes([0.92, 0.02, 0.10, 0.04])
            ax_legend_label.axis('off')
            ax_legend_label.text(0, 0, 'sed_vis', fontsize=16)

            # Buttons
            # ====================================
            ax_play = plt.axes([0.125, 0.93, 0.07, 0.04])
            ax_stop = plt.axes([0.205, 0.93, 0.07, 0.04])
            ax_close = plt.axes([0.92, 0.93, 0.07, 0.04])

            self.button_play = Button(
                ax_play,
                self.labels['play'],
                color=self.button_color['off'],
                hovercolor=self.button_color['on']
            )

            self.button_stop = Button(
                ax_stop,
                self.labels['stop'],
                color=self.button_color['off'],
                hovercolor=self.button_color['on']
            )

            self.button_close = Button(
                ax_close,
                self.labels['close'],
                color=self.button_color['off'],
                hovercolor=self.button_color['on']
            )

            self.button_play.on_clicked(self.on_play)
            self.button_stop.on_clicked(self.on_stop)
            self.button_close.on_clicked(self.on_close_window)

            self.fig.canvas.mpl_connect('pick_event', self.on_pick)
        else:
            plt.subplots_adjust(left=0.12, bottom=0.05, right=.97, top=0.95, wspace=0, hspace=0)

        if self.auto_play:
            self.on_play(None)

    def show(self):
        """Shows the visualizer.

        Parameters
        ----------
        Nothing

        Returns
        -------
        Nothing

        """
        self.generate_GUI()
        plt.show()

    def save(self, filename=None):
        if filename:
            self.generate_GUI()
            plt.savefig(filename, bbox_inches='tight')

    def on_close_window(self, event):
        if self.audio.playing:
            self.audio.stop()
            self.audio = None

        plt.close(self.fig)

    def on_pick(self, event):
        if isinstance(event.artist, Rectangle):
            if self.audio.playing:
                self.audio.stop()  # Stop current playback
                try:
                    self.event_panel_indicator_line.set_visible(False)
                    self.event_panel_indicator_line.remove()
                except:
                    pass

                if self.animation_event_roll_panel is not None:
                    self.animation_event_roll_panel._stop()
                    self.animation_event_roll_panel = None

                try:
                    self.selector_panel_indicator_line.set_visible(False)
                    self.selector_panel_indicator_line.remove()
                except:
                    pass

                if self.animation_selector_panel is not None:
                    self.animation_selector_panel._stop()
                    self.animation_selector_panel = None

                try:
                    self.highlight_panel_indicator_line.set_visible(False)
                    self.highlight_panel_indicator_line.remove()
                except:
                    pass

                if self.animation_highlight_panel is not None:
                    self.animation_highlight_panel._stop()
                    self.animation_highlight_panel = None

                self.fig.canvas.draw()

                time.sleep(0.25)  # Wait until playback has stopped

            self.playback_offset = event.artist.get_x()
            self.audio.play(
                offset=event.artist.get_x(),
                duration=event.artist.get_width()
            )

            # Set up play indicators animations
            self.animation_event_roll_panel = animation.FuncAnimation(
                self.fig,
                self.event_roll_panel_play_indicator_update,
                init_func=self.event_roll_panel_play_indicator_init,
                interval=10,
                blit=self.use_blit,
                repeat=False
            )

            self.animation_selector_panel = animation.FuncAnimation(
                self.fig,
                self.selector_panel_play_indicator_update,
                init_func=self.selector_panel_play_indicator_init,
                interval=10,
                blit=self.use_blit,
                repeat=False
            )

            self.animation_highlight_panel = animation.FuncAnimation(
                self.fig,
                self.highlight_panel_play_indicator_update,
                init_func=self.highlight_panel_play_indicator_init,
                interval=10,
                blit=self.use_blit,
                repeat=False
            )

            self.fig.canvas.draw()

    def on_select(self, x_min, x_max):
        x_min = int(x_min)
        x_max = int(x_max)
        if math.fabs(x_min-x_max) < 10:
            # Reset highlight
            self.begin_time = self.x[0] / float(self.audio.fs)
            self.end_time = self.x[-1] / float(self.audio.fs)
            if self.ax3:
                self.ax3.set_xlim(self.begin_time, self.end_time)

            # Set signal highlight panel
            if self.mode == 'spectrogram':
                self.ax2.set_xlim(0, self.D.shape[1])
            elif self.mode == 'time_domain':
                self.ax2.set_xlim(self.timedomain_locations[0], self.timedomain_locations[-1])

            self.slider_time.stay_rect.set_visible(False)

        else:
            # Set annotation panel
            self.begin_time = float(x_min) / self.audio.fs
            self.end_time = float(x_max) / self.audio.fs
            if self.ax3:
                self.ax3.set_xlim(self.begin_time, self.end_time)

            # Set signal highlight panel
            if self.mode == 'spectrogram':
                spec_min = int(x_min / float(self.spec_hop_size))
                spec_max = int(x_max / float(self.spec_hop_size))

                self.ax2.set_xlim(spec_min, spec_max)

            elif self.mode == 'time_domain':
                index_min, index_max = numpy.searchsorted(self.x, (x_min, x_max))
                index_max = min(len(self.x) - 1, index_max)
                this_x = self.timedomain_locations[index_min:index_max]
                self.ax2.set_xlim(this_x[0], this_x[-1])

            self.slider_time.stay_rect.set_visible(True)

        self.fig.canvas.draw()

    def on_play(self, event):
        if self.audio.playing:
            self.audio.stop()  # Stop current playback
            try:
                self.event_panel_indicator_line.set_visible(False)
                self.event_panel_indicator_line.remove()
            except:
                pass

            if self.animation_event_roll_panel is not None:
                self.animation_event_roll_panel._stop()
                self.animation_event_roll_panel = None

            try:
                self.selector_panel_indicator_line.set_visible(False)
                self.selector_panel_indicator_line.remove()
            except:
                pass

            if self.animation_selector_panel is not None:
                self.animation_selector_panel._stop()
                self.animation_selector_panel = None

            try:
                self.highlight_panel_indicator_line.set_visible(False)
                self.highlight_panel_indicator_line.remove()
            except:
                pass

            if self.animation_highlight_panel is not None:
                self.animation_highlight_panel._stop()
                self.animation_highlight_panel = None

            self.fig.canvas.draw()

            time.sleep(0.25)  # Wait until playback has stopped

        self.audio.play(
            offset=self.begin_time,
            duration=self.end_time-self.begin_time
        )

        self.button_play.color = self.button_color['on']
        self.button_play.hovercolor = self.button_color['on']

        self.button_stop.color = self.button_color['off']
        self.button_stop.hovercolor = self.button_color['off']

        self.playback_offset = self.begin_time

        self.animation_event_roll_panel = animation.FuncAnimation(
            self.fig,
            self.event_roll_panel_play_indicator_update,
            init_func=self.event_roll_panel_play_indicator_init,
            interval=50,
            blit=self.use_blit,
            repeat=False
        )

        self.animation_selector_panel = animation.FuncAnimation(
            self.fig,
            self.selector_panel_play_indicator_update,
            init_func=self.selector_panel_play_indicator_init,
            interval=50,
            blit=self.use_blit,
            repeat=False
        )

        self.animation_highlight_panel = animation.FuncAnimation(
            self.fig,
            self.highlight_panel_play_indicator_update,
            init_func=self.highlight_panel_play_indicator_init,
            interval=50,
            blit=self.use_blit,
            repeat=False
        )

        self.fig.canvas.draw()

    def on_pause(self, event):
        self.audio.pause()

        self.button_play.color = self.button_color['off']
        self.button_play.hovercolor = self.button_color['off']

        self.button_stop.color = self.button_color['off']
        self.button_stop.hovercolor = self.button_color['off']

        self.fig.canvas.draw()

    def on_stop(self, event):
        self.audio.stop()

        self.button_play.color = self.button_color['off']
        self.button_play.hovercolor = self.button_color['off']

        self.button_stop.color = self.button_color['off']
        self.button_stop.hovercolor = self.button_color['off']

        self.fig.canvas.draw()

    def on_quit(self, event):
        self._quit = True
        self.on_close_window(event=event)

    @property
    def quit(self):
        return self._quit

    def event_roll_panel_play_indicator_init(self):
        indicator_width = (self.end_time-self.begin_time) / 1000
        if indicator_width > 0.5:
            indicator_width = 0.5

        self.event_panel_indicator_line = patches.Rectangle(
            (self.playback_offset + self.audio.get_time(), -0.5),
            height=self.event_label_count,
            width=indicator_width,
            edgecolor=self.indicator_line_color,
            facecolor=self.indicator_line_color,
            alpha=0.8
        )

        self.ax3.add_patch(self.event_panel_indicator_line)
        return self.event_panel_indicator_line,

    def event_roll_panel_play_indicator_update(self, i):
        if self.audio.playing:
            self.event_panel_indicator_line.set_x(self.playback_offset + self.audio.get_time())
        else:
            self.event_panel_indicator_line.set_visible(False)
            if self.animation_event_roll_panel is not None:
                self.animation_event_roll_panel.event_source.stop()
                self.animation_event_roll_panel = None

            self.fig.canvas.draw()

        return self.event_panel_indicator_line,

    def selector_panel_play_indicator_init(self):
        self.selector_panel_indicator_line = patches.Rectangle(
            (0, -1),
            height=2,
            width=0.5,
            edgecolor=self.indicator_line_color,
            facecolor=self.indicator_line_color,
            alpha=0.8
        )

        self.ax1.add_patch(self.selector_panel_indicator_line)
        return self.selector_panel_indicator_line,

    def selector_panel_play_indicator_update(self, i):
        if self.audio.playing:
            self.selector_panel_indicator_line.set_x((self.playback_offset + self.audio.get_time())*self.audio.fs)
        else:
            self.selector_panel_indicator_line.set_visible(False)
            if self.animation_selector_panel is not None:
                self.animation_selector_panel.event_source.stop()
                self.animation_selector_panel = None

        return self.selector_panel_indicator_line,

    def highlight_panel_play_indicator_init(self):
        indicator_width = 0.5
        if self.mode == 'spectrogram':
            indicator_height = self.spec_fft_size
            indicator_y = 0

        elif self.mode == 'time_domain':
            indicator_height = 2
            indicator_y = -1

        else:
            indicator_height = 2
            indicator_y = -1

        self.highlight_panel_indicator_line = patches.Rectangle(
            (0, indicator_y),
            height=indicator_height,
            width=indicator_width,
            edgecolor=self.indicator_line_color,
            facecolor=self.indicator_line_color,
            alpha=0.8
        )

        self.ax2.add_patch(self.highlight_panel_indicator_line)
        return self.highlight_panel_indicator_line,

    def highlight_panel_play_indicator_update(self, i):
        if self.audio.playing:
            if self.mode == 'spectrogram':
                self.highlight_panel_indicator_line.set_x(
                    (self.playback_offset + self.audio.get_time()) * self.audio.fs / float(self.spec_hop_size)
                )

            elif self.mode == 'time_domain':
                self.highlight_panel_indicator_line.set_x(
                    (self.playback_offset + self.audio.get_time()) * self.audio.fs
                )

        else:
            self.highlight_panel_indicator_line.set_visible(False)
            if self.animation_highlight_panel is not None:
                self.animation_highlight_panel.event_source.stop()
                self.animation_highlight_panel = None

        return self.highlight_panel_indicator_line,

    def time_ticks(self, locations, n_ticks=10, sampling_rate=44100):
        times = self.samples_to_time(locations, sampling_rate=sampling_rate)
        positions = numpy.linspace(0, len(locations)-1, n_ticks, endpoint=True).astype(int)
        locations = locations[positions]
        times = times[positions]
        times = ['{:0.2f}s'.format(t) for t in times]

        return plt.xticks(locations, times)

    @staticmethod
    def time_to_samples(time, sampling_rate=44100):
        return (numpy.atleast_1d(time) * sampling_rate).astype(int)

    @staticmethod
    def samples_to_time(samples, sampling_rate=44100):
        return numpy.atleast_1d(samples) / float(sampling_rate)

    @staticmethod
    def get_spectrogram(audio, n_fft=256, win_length=1024, hop_length=1024):
        fft_window = scipy.signal.hann(win_length, sym=False).reshape((-1, 1))

        audio = numpy.pad(array=audio,
                          pad_width=int(n_fft // 2),
                          mode='reflect')

        n_frames = 1 + int((len(audio) - n_fft) / hop_length)
        y_frames = as_strided(x=audio,
                              shape=(n_fft, n_frames),
                              strides=(audio.itemsize, int(hop_length * audio.itemsize)))

        S = numpy.empty((int(1 + n_fft // 2), y_frames.shape[1]), dtype=numpy.complex64, order='F')

        max_memory_block = 2**8 * 2**10
        n_columns = int(max_memory_block / (S.shape[0] * S.itemsize))

        for bl_s in range(0, S.shape[1], n_columns):
            bl_t = min(bl_s + n_columns, S.shape[1])

            # RFFT and Conjugate here to match phase from DPWE code
            S[:, bl_s:bl_t] = scipy.fftpack.fft(fft_window * y_frames[:, bl_s:bl_t], axis=0)[:S.shape[0]].conj()

        magnitude = numpy.abs(S) ** 2

        ref = numpy.max(magnitude)
        amin=1e-10
        top_db = 80.0

        log_spec = 10.0 * numpy.log10(numpy.maximum(amin, magnitude))
        log_spec -= 10.0 * numpy.log10(numpy.maximum(amin, ref))

        log_spec = numpy.maximum(log_spec, log_spec.max() - top_db)

        return log_spec

    @staticmethod
    def plot_spectrogram(data, sampling_rate=44100, n_yticks=5, interpolation='nearest', cmap='magma'):

        axes = plt.imshow(data, aspect='auto', origin='lower', interpolation=interpolation, cmap=plt.get_cmap(cmap))

        # X axis
        plt.xticks([])

        # Y axis
        positions = numpy.linspace(0, data.shape[0]-1, n_yticks, endpoint=True).astype(int)
        values = numpy.linspace(0, 0.5 * sampling_rate, data.shape[0], endpoint=True).astype(int)

        t_log = (data.shape[0] * (1 - numpy.logspace(-numpy.log2(data.shape[0]), 0, data.shape[0], base=2, endpoint=True))[::-1]).astype(int)
        t_inv = numpy.arange(len(t_log))
        for i in range(len(t_log)-1):
            t_inv[t_log[i]:t_log[i+1]] = i

        plt.yticks(positions, values[t_inv[positions]])

        return axes


class EventListVerifier(EventListVisualizer):
    def __init__(self, *args, **kwargs):
        super(EventListVerifier, self).__init__(*args, **kwargs)
        self.verification_answer_id = None
        self.verification_answer_value = None
        self.verification_values = kwargs.get('verification_values', [
            'A',
            'B',
            'C'
        ])

        self.verification_button_colors = kwargs.get('verification_button_colors',[
            {
                'off': '#AAAAAA',
                'on':'#b32400',
            },
            {
                'off': '#AAAAAA',
                'on': '#996600',

            },
            {
                'off': '#AAAAAA',
                'on': '#009933',
            },
        ])

        self.button_color = kwargs.get('button_color', {
            'off': '#AAAAAA',
            'on': '#666666'
        })

        self.button_verification = {}
        self.button_verification_axis = {}

        if self.buttons['verification']:
            # Verification panel
            # ====================================
            self.selector_panel_loc = 20
            self.highlight_panel_loc = 37
            self.event_roll_panel_loc = 75

            self.selector_panel_height = 10
            self.highlight_panel_height = 35
            self.event_roll_panel_height = 20

    def generate_GUI(self):
        """Generates the visualizer GUI."""

        self.fig = plt.figure(figsize=self.fig_shape)

        # Selector panel
        # ====================================
        if self.show_selector:
            self.ax1 = plt.subplot2grid(
                shape=(100, 1),
                loc=(self.selector_panel_loc, 0),
                rowspan=self.selector_panel_height,
                colspan=1
            )

            self.timedomain_locations = numpy.arange(0, self.audio.signal.shape[0])

            self.ax1.fill_between(
                self.timedomain_locations[::self.waveform_selector_point_hop],
                self.audio.signal[::self.waveform_selector_point_hop],
                -self.audio.signal[::self.waveform_selector_point_hop],
                color='0.5'
            )

            plt.yticks([])
            plt.axis('tight')

            self.ax1.set_xlim(self.timedomain_locations[0], self.timedomain_locations[-1])
            self.ax1.set_ylim(-1, 1)

            self.time_ticks(
                locations=self.timedomain_locations,
                n_ticks=10,
                sampling_rate=self.audio.fs
            )

            self.ax1.yaxis.grid(False, which='major')
            self.ax1.yaxis.grid(False, which='minor')
            self.ax1.xaxis.grid(True, which='major')
            self.ax1.xaxis.grid(True, which='minor')
            self.ax1.yaxis.set_label_position("right")

            plt.title(self.labels['selection'], fontsize=self.panel_title_font_size)

        # Highlight panel
        # ====================================
        self.ax2 = plt.subplot2grid(
            shape=(100, 1),
            loc=(self.highlight_panel_loc, 0),
            rowspan=self.highlight_panel_height,
            colspan=1
        )

        self.x = numpy.arange(0, self.audio.duration_samples)

        self.begin_time = float(self.x[0]) / self.audio.fs
        self.end_time = float(self.x[-1]) / self.audio.fs

        if self.mode == 'spectrogram':
            self.D = self.get_spectrogram(
                audio=self.audio.signal,
                n_fft=self.spec_fft_size,
                win_length=self.spec_win_size,
                hop_length=self.spec_hop_size
            )

            self.plot_spectrogram(
                self.D,
                sampling_rate=self.audio.fs,
                interpolation=self.spec_interpolation,
                cmap=self.spec_cmap
            )

            if not self.publication_mode:
                self.ax2.yaxis.grid(False, which='major')
                self.ax2.yaxis.grid(False, which='minor')
                self.ax2.xaxis.grid(False, which='major')
                self.ax2.xaxis.grid(False, which='minor')

                plt.ylabel(self.labels['spectrogram'], fontsize=self.panel_title_font_size)
            else:
                self.ax2.get_yaxis().set_visible(False)

        elif self.mode == 'time_domain':

            self.ax2.fill_between(
                self.x[::self.waveform_highlight_point_hop],
                self.audio.signal[::self.waveform_highlight_point_hop],
                -self.audio.signal[::self.waveform_highlight_point_hop],
                color=self.waveform_highlight_color
            )

            self.ax2.set_ylim(-1, 1)
            self.ax2.set_xlim(self.x[0], self.x[-1])

            segment_begin = self.time_to_samples(time=0, sampling_rate=self.audio.fs)
            segment_end = self.time_to_samples(time=self.audio.duration_seconds, sampling_rate=self.audio.fs)
            locs = numpy.arange(segment_begin, segment_end)
            plt.xlim([locs[0], locs[-1]])
            self.time_ticks(locations=locs, n_ticks=20)

            plt.yticks([])
            plt.xticks([])
            self.ax2.yaxis.grid(False, which='major')
            self.ax2.yaxis.grid(False, which='minor')

            self.ax2.xaxis.grid(True, which='major')
            self.ax2.xaxis.grid(True, which='minor')

            if not self.publication_mode:
                plt.ylabel(self.labels['waveform'], fontsize=self.panel_title_font_size)

        self.ax2.yaxis.set_label_position("right")

        plt.axis('tight')

        # Event roll panel
        # ====================================
        if self._event_lists:
            event_list_count = len(self._event_lists)

            self.begin_time = 0
            self.end_time = self.audio.duration_seconds

            if event_list_count == 1:
                norm = colors.Normalize(
                    vmin=0,
                    vmax=self.event_label_count
                )
                self.ax3 = plt.subplot2grid(
                    shape=(100, 1),
                    loc=(self.event_roll_panel_loc, 0),
                    rowspan=self.event_roll_panel_height+10,
                    colspan=1
                )

            else:
                norm = colors.Normalize(
                    vmin=0,
                    vmax=event_list_count
                )
                self.ax3 = plt.subplot2grid(
                    shape=(100, 1),
                    loc=(self.event_roll_panel_loc, 0),
                    rowspan=self.event_roll_panel_height,
                    colspan=1
                )

            m = cm.ScalarMappable(
                norm=norm,
                cmap=self.label_colormap
            )

            line_margin = 0.1
            y = 0
            annotation_height = (1.0-line_margin*2)/event_list_count

            for label in self.active_events:
                for event_list_id, event_list_label in enumerate(self._event_list_order):
                    offset = (len(self._event_list_order)-1-event_list_id) * annotation_height

                    event_y = y - 0.5 + line_margin + offset

                    # grid line
                    line = plt.Rectangle(
                        (0, y-0.5),
                        height=0.001,
                        width=self.end_time,
                        edgecolor='black',
                        facecolor='black'
                    )

                    plt.gca().add_patch(line)

                    for event in self._event_lists[event_list_label]:
                        if event['event_label'] == label:
                            event_length = event['offset'] - event['onset']

                            if event_list_count == 1:
                                color = m.to_rgba(y + offset)
                            else:
                                color = m.to_rgba(event_list_id)

                            rectangle = plt.Rectangle(
                                (event['onset'], event_y),
                                height=annotation_height,
                                width=event_length,
                                edgecolor='black',
                                facecolor=color,
                                linewidth=0,
                                alpha=self.event_roll_item_opacity,
                                picker=5
                            )

                            plt.gca().add_patch(rectangle)
                y += 1

            # Grid line
            line = plt.Rectangle(
                (0, y - 0.5),
                height=0.001,
                width=self.end_time,
                edgecolor='black',
                facecolor='black'
            )

            plt.gca().add_patch(line)

            # Axis
            plt.axis([0, self.audio.duration_seconds, -0.5, len(self.active_events) + 0.5])
            locs = numpy.arange(0, self.audio.duration_seconds, 0.00001)

            plt.xlim([locs[0], locs[-1]])
            plt.axis('tight')

            # X axis
            self.ax3.xaxis.grid(True, which='major')
            self.ax3.xaxis.grid(True, which='minor')
            plt.tick_params(
                axis='x',
                which='major',
                labelsize=self.event_roll_time_font_size
            )

            # Y axis
            plt.yticks(
                numpy.arange(len(self.active_events)),
                self.active_events,
                fontsize=self.event_roll_label_font_size
            )

            plt.ylabel('Event Roll', fontsize=self.panel_title_font_size)
            self.ax3.yaxis.set_label_position('right')
            self.ax3.yaxis.grid(False, which='major')
            self.ax3.yaxis.grid(False, which='minor')

            # Set event list legends panel
            self.ax3.set_xlim(self.begin_time, self.end_time)
            if event_list_count > 1:
                span = 0
                for event_list_id, event_list_label in enumerate(self._event_list_order):

                    ax_legend_color = plt.axes([0.225+span, 0.02, 0.02, 0.02])
                    Button(
                        ax_legend_color,
                        '',
                        color=m.to_rgba(event_list_id),
                        hovercolor=m.to_rgba(event_list_id)
                    )

                    ax_legend_label = plt.axes([0.225+0.025+span, 0.02, 0.10, 0.04])
                    ax_legend_label.axis('off')
                    ax_legend_label.text(0, 0, event_list_label, fontsize=self.legend_font_size)
                    span += 0.15

        if self.show_selector:
            self.slider_time = SpanSelector(
                ax=self.ax1,
                onselect=self.on_select,
                minspan=None,
                direction='horizontal',
                span_stays=True,
                useblit=self.use_blit,
                onmove_callback=None,
                rectprops=dict(alpha=0.15, facecolor=self.color)
            )

        if not self.publication_mode:
            ax_legend_label = plt.axes([0.92, 0.02, 0.10, 0.04])
            ax_legend_label.axis('off')
            ax_legend_label.text(0, 0, 'sed_vis', fontsize=16)

            # Buttons
            # ====================================
            if self.buttons['play']:
                ax_play = plt.axes([0.125, 0.93, 0.07, 0.04])
                self.button_play = Button(
                    ax_play,
                    self.labels['play'],
                    color=self.button_color['off'],
                    hovercolor=self.button_color['on']
                )

                self.button_play.on_clicked(self.on_play)

            if self.buttons['stop']:
                ax_stop = plt.axes([0.205, 0.93, 0.07, 0.04])
                self.button_stop = Button(
                    ax_stop,
                    self.labels['stop'],
                    color=self.button_color['off'],
                    hovercolor=self.button_color['on']
                )

                self.button_stop.on_clicked(self.on_stop)

            if self.buttons['close']:
                ax_close = plt.axes([0.92, 0.93, 0.07, 0.04])
                self.button_close = Button(
                    ax_close,
                    self.labels['close'],
                    color=self.button_color['off'],
                    hovercolor=self.button_color['on']
                )

                self.button_close.on_clicked(self.on_close_window)

            if self.buttons['quit']:
                ax_quit = plt.axes([0.78, 0.93, 0.07, 0.04])
                self.button_quit = Button(
                    ax_quit,
                    self.labels['quit'],
                    color=self.button_color['off'],
                    hovercolor=self.button_color['on']
                )

                self.button_quit.on_clicked(self.on_quit)

            if self.buttons['verification']:
                start_x = 0.125 + 0.2 #25
                end_x = 0.125+0.775 -0.2 #1 - start_x
                width = (end_x - start_x)
                spacing = width / float(len(self.verification_values))

                # Verification text
                plt.axes([0.125, 0.88, 0.775, 0.04], frameon=False)
                plt.xticks([])
                plt.yticks([])
                plt.text(
                    0.5, 0.5,
                    self.labels['verification'],
                    fontsize=14,
                    horizontalalignment='center',
                    verticalalignment='center'
                )

                # Verification-info text
                plt.axes([0.00, 0.80, 0.125 + 0.18, 0.08], frameon=False)
                plt.xticks([])
                plt.yticks([])
                plt.text(
                    1, 0.5,
                    self.labels['verification_info'],
                    fontsize=18,
                    horizontalalignment='right',
                    verticalalignment='center'
                )

                for i, label in enumerate(self.verification_values):
                    self.button_verification_axis[i] = plt.axes([start_x+(i*spacing), 0.80, 0.12, 0.08])
                    self.button_verification[i] = Button(
                        self.button_verification_axis[i],
                        label,
                        color=self.verification_button_colors[i]['off'],
                        hovercolor=self.verification_button_colors[i]['off']
                    )

                    self.button_verification[i].label.set_fontsize(12)
                    self.button_verification[i].on_clicked(self.on_verification)

            # Info text
            plt.axes([start_x, 0.02, width, 0.04], frameon=False)
            plt.xticks([])
            plt.yticks([])
            plt.text(0.5, 0.5,
                     self.labels['info'],
                     fontsize=12,
                     horizontalalignment='center',
                     verticalalignment='center'
                     )

            self.fig.canvas.mpl_connect('pick_event', self.on_pick)
        else:
            plt.subplots_adjust(
                left=0.12,
                bottom=0.05,
                right=.97,
                top=0.95,
                wspace=0,
                hspace=0
            )

        if self.auto_play:
            self.on_play(None)

    def on_verification(self, event):
        for i in self.button_verification:
            if event.inaxes == self.button_verification_axis[i]:
                self.button_verification[i].color = self.verification_button_colors[i]['on']
                self.button_verification[i].hovercolor = self.verification_button_colors[i]['on']
                self.verification_answer_id =i
                self.verification_answer_value = self.verification_values[i]

            else:
                self.button_verification[i].color = self.verification_button_colors[i]['off']
                self.button_verification[i].hovercolor = self.verification_button_colors[i]['off']

    def get_answer(self):
        return self.verification_answer_id, self.verification_answer_value
#!/usr/bin/env python # -*- coding: utf-8 -*- Visualization ================== This is module contains a simple visualizer to show event lists along with the audio. The visualizer can show multiple event lists for the same reference audio allowing the comparison of the reference and estimated event lists.   .. image:: visualization.png   .. autosummary::     :toctree: generated/      EventListVisualizer     EventListVisualizer.show # MAC OS X Event List visualizer.      Examples     --------     >>> # Load audio signal first     >>> audio_container = dcase_util.containers.AudioContainer().load('data/audio.wav')     >>> # Load event lists     >>> reference_event_list = dcase_util.containers.MetaDataContainer().load('data/reference.txt')     >>> estimated_event_list = dcase_util.containers.MetaDataContainer().load('data/estimated.txt')     >>> event_lists = {'reference': reference_event_list, 'estimated': estimated_event_list}     >>> # Visualize the data     >>> vis = sed_vis.visualization.EventListVisualizer(event_lists=event_lists,audio_signal=audio_container.data,sampling_rate=audio_container.fs)     >>> vis.show() Constructor          Parameters         ----------         event_lists : dict of event lists             Dict of event lists          event_list_order : list             Order of event list, if None alphabetical order used             (Default value=None)          active_events : list             List of active sound event classes, if None all used.             (Default value=None)          audio_signal : np.ndarray             Audio signal          sampling_rate : int             Sampling rate [0:96000]          mode: str             Signal visualization mode ['spectrogram', 'time_domain']             (Default value = 'spectrogram')          spec_hop_size : int             Spectrogram calculation hop length in samples             (Default value=256)          spec_win_size: int ,             Spectrogram calculation window length in samples             (Default value=1024)          spec_fft_size: int             FFT length             (Default value=1024)          spec_cmap : str             Color map used for spectrogram, see examples: http://matplotlib.org/examples/color/colormaps_reference.html             (Default value='magma')          spec_interpolation : str             Matrix interpolation method for spectrogram (e.g. nearest, bilear, bicubic, quadric, gaussian)             (Default value='nearest')          event_roll_cmap : str             Color map used for spectrogram, see examples: http://matplotlib.org/examples/color/colormaps_reference.html             (Default value='rainbow')          minimum_event_length : float > 0.0             Minimum event length in seconds, shorten than given are filtered out from the output.             (Default value=None)          minimum_event_gap : float > 0.0             Minimum allowed gap between events in seconds from same event label class.             (Default value=None)          color : color hex             Main color code used in highlighting things          use_blit : bool             Use blit             (Default value=False)          publication_mode : bool             Strip visual elements, can be used to prepare figures for publications.             (Default value=False)          show_selector : bool             Show highlight selector             (Default value=True)          labels: dict             Text labels overrides          button_color : dict             Button color overrides          Returns         -------         Nothing # Initialize members # Panels # Play indicators # Buttons Generates the visualizer GUI.          Parameters         ----------         Nothing          Returns         -------         Nothing # Selector panel # ==================================== # Highlight panel # ==================================== # Event roll panel # ==================================== # grid line # grid line # Axis # X axis # Y axis # Set event list legends panel # Buttons # ==================================== Shows the visualizer.          Parameters         ----------         Nothing          Returns         -------         Nothing # Stop current playback # Wait until playback has stopped # Set up play indicators animations # Reset highlight # Set signal highlight panel # Set annotation panel # Set signal highlight panel # Stop current playback # Wait until playback has stopped # RFFT and Conjugate here to match phase from DPWE code # X axis # Y axis # Verification panel # ==================================== Generates the visualizer GUI. # Selector panel # ==================================== # Highlight panel # ==================================== # Event roll panel # ==================================== # grid line # Grid line # Axis # X axis # Y axis # Set event list legends panel # Buttons # ==================================== #25 #1 - start_x # Verification text # Verification-info text # Info text