#!/usr/bin/env python
# -*- coding: utf-8 -*-

import numpy


def event_detection(feature_data, model_container, hop_length_seconds=0.01, smoothing_window_length_seconds=1.0, decision_threshold=0.0, minimum_event_length=0.1, minimum_event_gap=0.1):
    """Sound event detection

    Parameters
    ----------
    feature_data : numpy.ndarray [shape=(n_features, t)]
        Feature matrix

    model_container : dict
        Sound event model pairs [positive and negative] in dict

    hop_length_seconds : float > 0.0
        Feature hop length in seconds, used to convert feature index into time-stamp
        (Default value=0.01)

    smoothing_window_length_seconds : float > 0.0
        Accumulation window (look-back) length, withing the window likelihoods are accumulated.
        (Default value=1.0)

    decision_threshold : float > 0.0
        Likelihood ratio threshold for making the decision.
        (Default value=0.0)

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

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

    Returns
    -------
    results : list (event dicts)
        Detection result, event list

    """

    smoothing_window = int(smoothing_window_length_seconds / hop_length_seconds)

    results = []
    for event_label in model_container['models']:
        positive = model_container['models'][event_label]['positive'].score_samples(feature_data)[0]
        negative = model_container['models'][event_label]['negative'].score_samples(feature_data)[0]

        # Lets keep the system causal and use look-back while smoothing (accumulating) likelihoods
        for stop_id in range(0, feature_data.shape[0]):
            start_id = stop_id - smoothing_window
            if start_id < 0:
                start_id = 0
            positive[start_id] = sum(positive[start_id:stop_id])
            negative[start_id] = sum(negative[start_id:stop_id])

        likelihood_ratio = positive - negative
        event_activity = likelihood_ratio > decision_threshold

        # Find contiguous segments and convert frame-ids into times
        event_segments = contiguous_regions(event_activity) * hop_length_seconds

        # Preprocess the event segments
        event_segments = postprocess_event_segments(event_segments=event_segments,
                                                   minimum_event_length=minimum_event_length,
                                                   minimum_event_gap=minimum_event_gap)

        for event in event_segments:
            results.append((event[0], event[1], event_label))

    return results


def contiguous_regions(activity_array):
    """Find contiguous regions from bool valued numpy.array.
    Transforms boolean values for each frame into pairs of onsets and offsets.

    Parameters
    ----------
    activity_array : numpy.array [shape=(t)]
        Event activity array, bool values

    Returns
    -------
    change_indices : numpy.ndarray [shape=(2, number of found changes)]
        Onset and offset indices pairs in matrix

    """

    # Find the changes in the activity_array
    change_indices = numpy.diff(activity_array).nonzero()[0]

    # Shift change_index with one, focus on frame after the change.
    change_indices += 1

    if activity_array[0]:
        # If the first element of activity_array is True add 0 at the beginning
        change_indices = numpy.r_[0, change_indices]

    if activity_array[-1]:
        # If the last element of activity_array is True, add the length of the array
        change_indices = numpy.r_[change_indices, activity_array.size]

    # Reshape the result into two columns
    return change_indices.reshape((-1, 2))


def postprocess_event_segments(event_segments, minimum_event_length=0.1, minimum_event_gap=0.1):
    """Post process event segment list. Makes sure that minimum event length and minimum event gap conditions are met.

    Parameters
    ----------
    event_segments : numpy.ndarray [shape=(2, number of event)]
        Event segments, first column has the onset, second has the offset.

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

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

    Returns
    -------
    event_results : numpy.ndarray [shape=(2, number of event)]
        postprocessed event segments

    """

    # 1. remove short events
    event_results_1 = []
    for event in event_segments:
        if event[1]-event[0] >= minimum_event_length:
            event_results_1.append((event[0], event[1]))

    if len(event_results_1):
        # 2. remove small gaps between events
        event_results_2 = []

        # Load first event into event buffer
        buffered_event_onset = event_results_1[0][0]
        buffered_event_offset = event_results_1[0][1]
        for i in range(1,len(event_results_1)):
            if event_results_1[i][0] - buffered_event_offset > minimum_event_gap:
                # The gap between current event and the buffered is bigger than minimum event gap,
                # store event, and replace buffered event
                event_results_2.append((buffered_event_onset, buffered_event_offset))
                buffered_event_onset = event_results_1[i][0]
                buffered_event_offset = event_results_1[i][1]
            else:
                # The gap between current event and the buffered is smalle than minimum event gap,
                # extend the buffered event until the current offset
                buffered_event_offset = event_results_1[i][1]

        # Store last event from buffer
        event_results_2.append((buffered_event_onset, buffered_event_offset))

        return event_results_2
    else:
        return event_results_1

@@ -5173,16 +5173,17 @@
 range(1,
+ 
 len(even

Missing whitespace