def mutual_information(histo):
    assert(histo.ndim == 2)
    assert(np.allclose(histo.sum(), 1.))
    zeros = (histo == 0) * 1  # To avoid nans
    return (histo * np.log((histo + zeros) / (
        histo.sum(axis=0)[np.newaxis, :] * histo.sum(axis=1)[:, np.newaxis]
        + zeros))).sum() class Logger(object):

    class NoFileError(Exception):
        pass

    def __init__(self, filename=None):
        self.glob = {}  # Global logs
        self.exps = []  # Logs of experiments
        self.exp_keys = set()
        self.result_keys = set()
        self.filename = filename

    # Does not handle correctly numpy arrays with several values
    def __eq__(self, other):
        return (isinstance(other, Logger)
                and self.filename == other.filename
                and self.glob == other.glob
                and self.exps == other.exps
                and self.exp_keys == other.exp_keys
                and self.result_keys == other.result_keys
                )

    def log(self, msg):
        print(msg)

    def new_run(self):
        self.exps.append({})

    def store_global(self, key, value):
        self.glob[key] = value

    def store(self, key, value):
        if key in self.exps[-1]:
            raise ValueError(
                "There is already a value for this experiment for key: %s."
                % key)
        else:
            self.exp_keys.add(key)
            self.exps[-1][key] = value

    def store_result(self, key, value, is_data=False):
        self.store(key, value)
        self.result_keys.add(key)

    def get_values(self, key):
        return [dic[key] if key in dic else None for dic in self.exps]

    def get_value(self, key):
        return self.glob[key]

    def get_last_value(self, key):
        return self.exps[-1][key]

    def get_stats(self, key):
        vals = self.get_values(key)
        return (np.average(vals), np.std(vals))

    def print_result(self, key, text=None):
        if text is None:
            text = key
        avg, std = self.get_stats(key)
        print("%s: %g (std dev.: %g)" % (text, avg, std))

    def print_all_results(self):
        for k in self.result_keys:
            self.print_result(k)

    def save(self, compress=True):
        if self.filename is None:
            raise self.NoFileError('No file set for this logger.')
        else:
            to_save = {'glob': {},
                       'exps': [{} for e in self.exps],
                       'exp_keys': list(self.exp_keys),
                       'result_keys': list(self.result_keys),
                       'has_np': False,
                       }
            to_save_np = {}
            _split_for_save(self.glob, to_save['glob'], to_save_np, 'glob')
            for i, e in enumerate(self.exps):
                _split_for_save(e, to_save['exps'][i], to_save_np,
                                "exp_%d" % i)
            if len(to_save_np) > 0:
                to_save['has_np'] = True
                if compress:
                    np.savez_compressed(self.filename, **to_save_np)
                else:
                    np.savez(self.filename, **to_save_np)
            with open(self.filename + '.json', 'w') as f:
                json.dump(to_save, f, indent=2)

    def append_exps_from_other(self, other):
        assert(self.exp_keys == other.exp_keys)
        self.exps.extend(other.exps)

    @classmethod
    def merge_experiments(cls, loggers):
        if len(loggers) == 0:
            raise ValueError()
        l0 = loggers[0]
        for l in loggers[1:]:
            l0.append_exps_from_other(l)
        return l0

    @classmethod
    def load(cls, filename, set_filename=False, load_np=True):
        logger = Logger()
        with open(filename + '.json', 'r') as f:
            data = json.loads(f.read())
        logger.glob = data['glob']
        logger.exps = data['exps']
        logger.exp_keys = set(data['exp_keys'])
        logger.exp_results = set(data['result_keys'])
        if data['has_np'] and load_np:
            data_np = np.load(filename + '.npz')
            for full_key in data_np.keys():
                p, k = full_key.split('_', 1)
                if p == 'glob':
                    logger.store_global(k, data_np[full_key])
                elif p == 'exp':
                    num, name = k.split('_', 1)
                    logger.exps[int(num)][name] = data_np[full_key]
        if set_filename:
            logger.filename = filename
        return logger class BasicTimeWindow(TimeWindow):
    """Associates a time window to a basic object.
    """

    def __init__(self, absolute_start, absolute_end, obj=None):
        self.absolute_start = absolute_start
        self.absolute_end = absolute_end
        self.obj = obj

    def __repr__(self):
        return "[{}->{}: {}]".format(self.absolute_start, self.absolute_end,
                                     repr(self.obj))

    def get_subwindow(self, t_start, t_end):
        self.check_time(t_start)
        self.check_time(t_end)
        new = self.copy()
        new.absolute_start = t_start
        new.absolute_end = t_end if t_start < t_end else t_start
        return new

    def copy(self):
        return self.__class__(self.absolute_start, self.absolute_end,
                              obj=self.obj) class ConcatTimeWindow(TimeWindow):
    """Sequence of consecutive time windows."""

    def __init__(self, windows):
        if len(windows) < 1:
            raise ValueError('At least one window should be given.')
        self.windows = windows
        # Check for alignment (i.e. no gap or overlap between windows)
        starts = [w.absolute_start for w in self.windows[1:]]
        ends = self._ends()[:-1]
        if max([abs(a - b) for a, b in zip(ends, starts)] + [0]) > TOL:
            raise ValueError('Windows are not consecutive. '
                             'Consider using ConcatTimeWindow.align.')

    @property
    def absolute_start(self):
        return self.windows[0].absolute_start

    @property
    def absolute_end(self):
        return self.windows[-1].absolute_end

    def move(self, delta_t):
        for w in self.windows:
            w.move(delta_t)

    def get_subwindow(self, t_start, t_end):
        i_start = self._get_file_index_from_time(t_start)
        i_end = self._get_file_index_from_time(t_end)
        new_windows = [w.copy() for w in self.windows[i_start:1 + i_end]]
        new_windows[0] = new_windows[0].get_subwindow(
            t_start, new_windows[0].absolute_end)
        new_windows[-1] = new_windows[-1].get_subwindow(
            new_windows[-1].absolute_start, t_end)
        return self.__class__(new_windows)

    def to_array_window(self):
        """Only when windows have a to_array_method."""
        array_windows = [win.to_array_window() for win in self.windows]
        return ArrayTimeWindow.concatenate(array_windows)

    def copy(self):
        return self.__class__([win.copy() for win in self.windows])

    def get_subwindow_at(self, t):
        return self.windows[self._get_file_index_from_time(t)]

    def _ends(self):
        return [win.absolute_end for win in self.windows]

    def _get_file_index_from_time(self, t):
        self.check_time(t)
        return bisect.bisect_left(self._ends(), t)

    @classmethod
    def align(cls, windows, start=None):
        """Align start windows so that they cover consecutive time intervals.
        Changes start times but not durations and window order.
        """
        if start is None:
            start = windows[0].absolute_start
        t = start
        for w in windows:
            w.move(t - w.absolute_start)
            t += w.duration()
        return windows def concat_from_list_of_wavs(files, start=None):
    file_windows = ConcatTimeWindow.align(
        [WavFileTimeWindow(f, 0.) for f in files], start=start)
    return ConcatTimeWindow(file_windows) def slider(t_start, t_end, width, shift, partial=False, tol=TOL):
    """Iterator over start/end time of time windows covering
    [t_start, t_end].

    partial: bool (False)
        If set returns partial window (i.e. of width < param),
        else stop at last full window.
    """
    end_start = t_end if partial else t_end - width
    # if partial: do not include t_end
    # else: include t_end - width
    starts = _arange(t_start, end_start, shift, not partial, tol=tol)
    return [(t, _snap_above(t + width, t_end, tol=tol)) for t in starts] TEN_COLORS = [
    "#3989d4",
    "#ffad4c",
    "#ff728d",
    "#a8efa0",
    "#a0c2fc",
    "#eba7f7",
    "#ffd1af",
    "#ffff2d",  # Darker from yellow: "#ffff90",
    "#5ab578",
    "#60708d",
] class Year1Loader(Loader):

    dataset_name = 'acorns'

    def __init__(self, speaker):
        super(Year1Loader, self).__init__()
        self.speaker = speaker
        self._db = None

    @property
    def db(self):
        if self._db is None:
            self._db = load(1, blacklist=True)
        return self._db

    @property
    def records(self):
        return self.db.records[self.speaker]

    def get_data(self):
        X = load_features(1, self.speaker, blacklist=True)
        self.check_n_samples(X.shape[0])
        return X

    def get_labels(self):
        self.check_n_samples(len(self.records))
        return [self.db.tags[r.tags[0]] for r in self.records]

    def serialize(self):
        return self.speaker

    @classmethod
    def get_loader(cls, spkr):
        return Year1Loader(spkr) class InteractivePlot(object):

    def __init__(self, record_wins, sliding_wins, example_labels,
                 similarities, is_test=None, plot_rc=None):
        self.score_plot = ScorePlot(record_wins, sliding_wins, example_labels,
                                    similarities, is_test=is_test,
                                    plot_rc=plot_rc)
        self.fig, self.main_ax = self.score_plot.fig, self.score_plot.main_ax
        plt.subplots_adjust(bottom=0.2)
        self.score_plot.draw()
        max_time = self.score_plot.records.absolute_end
        ax_s = plt.axes([0.15, 0.1, 0.75, 0.03])
        ax_w = plt.axes([0.15, 0.05, 0.75, 0.03])
        self.slider_start = Slider(ax_s, 'Start', 0., max_time, valinit=0.)
        self.slider_start.on_changed(self.update)
        self.slider_width = Slider(ax_w, 'Width', 0., 30., valinit=15.)
        self.slider_width.on_changed(self.update)
        self.fig.canvas.mpl_connect('key_press_event', self.on_key)

    def update(self, val):
        self.score_plot.current.absolute_start = self.slider_start.val
        width = self.slider_width.val
        self.score_plot.current.absolute_end = (
                self.score_plot.current.absolute_start + width)
        self.score_plot.draw()

    def on_key(self, event):
        if event.key == 'right':
            direction = 1
        elif event.key == 'left':
            direction = -1
        else:
            return
        self.slider_start.set_val(self.slider_start.val +
                                  direction * .5 * self.slider_width.val)
        self.update(None) def plot_one_sentence(record_win, sliding_wins, example_labels, similarities,
                      plot_rc, annotate=[]):
    """annotate: list of (text, (start, end))"""
    default_plot_rc = {'draw_sentence_boundaries': False,
                       'use_relative_time': True,
                       'print_sentences': False,
                       }
    default_plot_rc.update(plot_rc)
    score_plot = ScorePlot(ConcatTimeWindow([record_win]), sliding_wins,
                           example_labels, similarities,
                           plot_rc=default_plot_rc)
    score_plot.current.absolute_start = record_win.absolute_start
    score_plot.current.absolute_end = record_win.absolute_end
    score_plot.draw()
    score_plot.main_ax.set_title(record_win.obj.trans)
    for a in annotate:
        score_plot.annotate(a[1], a[0])
    return score_plot
import os
import numpy as np
import matplotlib as plt
from multimodal.lib.metrics import mutual_information
from multimodal.lib.logger import Logger
from multimodal.lib.window import (BasicTimeWindow, ConcatTimeWindow,
                                   concat_from_list_of_wavs,
                                   slider)
from multimodal.lib.plot import TEN_COLORS
from multimodal.db.acorns import Year1Loader as AcornsLoader
from multimodal.plots import InteractivePlot, plot_one_sentence




WIDTH = .5
SHIFT = .1

WORKDIR = os.path.expanduser('~/work/data/results/quick/')
COLORS = TEN_COLORS

sound_loader = AcornsLoader(1)


logger = Logger.load(os.path.join(WORKDIR, 'sliding'))