<commit_before># CREATED:2013-08-13 12:02:42 by <NAME> <<EMAIL>>
'''Structural segmentation evaluation, following the protocols of MIREX2012:
    - Boundary detection
        - (precision, recall, f-measure)
        - median distance to nearest boundary
    - Frame clustering
'''

import numpy as np
import scipy.stats
import sklearn.metrics.cluster as metrics

def boundary_detection(annotated_boundaries, predicted_boundaries, window=0.5, beta=1.0):
    '''Boundary detection hit-rate.  

    A hit is counted whenever an annotated boundary is within `window` of a predicted
    boundary.

    :parameters:
    - annotated_boundaries : list-like, float
        ground-truth segment boundary times (in seconds)

    - predicted_boundaries : list-like, float
        predicted segment boundary times (in seconds)

    - window : float > 0
        size of the window of 'correctness' around ground-truth beats (in seconds)

    - beta : float > 0
        'beta' for F-measure.

    :returns:
    - P : float
        precision of predictions
    - R : float
        recall of ground-truth beats
    - F : float
        F-measure (harmonic mean of P, R)
    '''

    # Compute the hits
    D = np.abs( np.subtract.outer(annotated_boundaries, predicted_boundaries)) <= window

    # Precision: how many predicted boundaries were hits?
    P = np.mean(D.max(axis=0))

    # Recall: how many of the boundaries did we catch?
    R = np.mean(D.max(axis=1))

    # And the f-measure
    F = 0.0

    if P > 0 or R > 0:
        F = (1 + beta**2) * P * R / ((beta**2) * P + R)

    return P, R, F

def boundary_deviation(annotated_boundaries, predicted_boundaries):
    '''Compute the median deviations between annotated and predicted boundary times.

    :parameters:
    - annotated_boundaries : list-like, float
        ground-truth segment boundary times (in seconds)

    - predicted_boundaries : list-like, float
        predicted segment boundary times (in seconds)

    :returns:
    - true_to_predicted : float
        median time from each true boundary to the closest predicted boundary

    - predicted_to_true : float
        median time from each predicted boundary to the closest true boundary
    '''

    D = np.abs( np.subtract.outer(annotated_boundaries, predicted_boundaries) )

    true_to_predicted = np.median(np.sort(D, axis=1)[:, 0])
    predicted_to_true = np.median(np.sort(D, axis=0)[0, :])

    return true_to_predicted, predicted_to_true


def boundaries_to_frames(boundaries, frame_size=0.1):
    '''Convert a sequence of boundaries to frame-level segment annotations.
    
    :parameters:
    - boundaries : list-like float
        segment boundary times (in seconds).

    - frame_size : float > 0
        duration of each frame (in seconds)

    :returns:
    - y : np.array, dtype=int
        array of segment labels for each frame

    ..note::
        It is assumed that `boundaries[-1] == length of song`

    ..note::
        Segment boundaries will be rounded down to the nearest multiple 
        of frame_size.
    '''
    
    boundaries = np.sort(boundaries - np.mod(boundaries, frame_size))
    boundaries = np.unique(np.concatenate(([0], boundaries)))

    # Build the frame label array
    y = np.zeros(int(boundaries[-1] / frame_size))

    for (i, (start, end)) in enumerate(zip(boundaries[:-1], boundaries[1:])):
        y[int(start / frame_size):int(end / frame_size)] = i

    return y

def frame_clustering_pairwise(annotated_boundaries, predicted_boundaries, frame_size=0.1, beta=1.0):
    '''Frame-clustering segmentation evaluation by pair-wise agreement.

    :parameters:
    - annotated_boundaries : list-like, float
        ground-truth segment boundary times (in seconds)
<commit_msg>fixed some framing logic in segment eval<commit_after># CREATED:2013-08-13 12:02:42 by <NAME> <<EMAIL>>
'''Structural segmentation evaluation, following the protocols of MIREX2012:
    - Boundary detection
        - (precision, recall, f-measure)
        - median distance to nearest boundary
    - Frame clustering
'''

import numpy as np
import scipy.stats
import sklearn.metrics.cluster as metrics

def boundary_detection(annotated_boundaries, predicted_boundaries, window=0.5, beta=1.0):
    '''Boundary detection hit-rate.  

    A hit is counted whenever an annotated boundary is within `window` of a predicted
    boundary.

    :parameters:
    - annotated_boundaries : list-like, float
        ground-truth segment boundary times (in seconds)

    - predicted_boundaries : list-like, float
        predicted segment boundary times (in seconds)

    - window : float > 0
        size of the window of 'correctness' around ground-truth beats (in seconds)

    - beta : float > 0
        'beta' for F-measure.

    :returns:
    - P : float
        precision of predictions
    - R : float
        recall of ground-truth beats
    - F : float
        F-measure (harmonic mean of P, R)
    '''

    # Compute the hits
    D = np.abs( np.subtract.outer(annotated_boundaries, predicted_boundaries)) <= window

    # Precision: how many predicted boundaries were hits?
    P = np.mean(D.max(axis=0))

    # Recall: how many of the boundaries did we catch?
    R = np.mean(D.max(axis=1))

    # And the f-measure
    F = 0.0

    if P > 0 or R > 0:
        F = (1 + beta**2) * P * R / ((beta**2) * P + R)

    return P, R, F

def boundary_deviation(annotated_boundaries, predicted_boundaries):
    '''Compute the median deviations between annotated and predicted boundary times.

    :parameters:
    - annotated_boundaries : list-like, float
        ground-truth segment boundary times (in seconds)

    - predicted_boundaries : list-like, float
        predicted segment boundary times (in seconds)

    :returns:
    - true_to_predicted : float
        median time from each true boundary to the closest predicted boundary

    - predicted_to_true : float
        median time from each predicted boundary to the closest true boundary
    '''

    D = np.abs( np.subtract.outer(annotated_boundaries, predicted_boundaries) )

    true_to_predicted = np.median(np.sort(D, axis=1)[:, 0])
    predicted_to_true = np.median(np.sort(D, axis=0)[0, :])

    return true_to_predicted, predicted_to_true


def boundaries_to_frames(boundaries, frame_size=0.1):
    '''Convert a sequence of boundaries to frame-level segment annotations.
    
    :parameters:
    - boundaries : list-like float
        segment boundary times (in seconds).

    - frame_size : float > 0
        duration of each frame (in seconds)

    :returns:
    - y : np.array, dtype=int
        array of segment labels for each frame

    ..note::
        It is assumed that `boundaries[-1] == length of song`

    ..note::
        Segment boundaries will be rounded down to the nearest multiple 
        of frame_size.
    '''
    
    boundaries = np.sort(frame_size * np.round(boundaries / frame_size))
    boundaries = np.unique(np.concatenate(([0], boundaries)))

    # Build the frame label array
    y = np.zeros(int(boundaries[-1] / frame_size))

    for (i, (start, end)) in enumerate(zip(boundaries[:-1], boundaries[1:])):
        y[int(start / frame_size):int(end / frame_size)] = i

    return y

def frame_clustering_pairwise(annotated_boundaries, predicted_boundaries, frame_size=0.1, beta=1.0):
    '''Frame-clustering segmentation evaluation by pair-wise agreement.

    :parameters:
    - annotated_boundaries : list-like, float
        ground-truth segment boundary times (in seconds)

{'fraction_non_alphanumeric': 0.0967741935483871, 'fraction_numerical': 0.013063183151159691, 'mean_word_length': 2.8956386292834893, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '875659', 'n_tokens_mistral': 2315, 'n_tokens_neox': 2157, 'n_words': 714}