def plot_tree(T, res=None, title=None, cmap_id="Pastel2"):
    """Plots a given tree, containing hierarchical segmentation.

    Parameters
    ----------
    T: mir_eval.segment.tree
        A tree object containing the hierarchical segmentation.
    res: float
        Frame-rate resolution of the tree (None to use seconds).
    title: str
        Title for the plot. `None` for no title.
    cmap_id: str
        Color Map ID
    """
    import matplotlib.pyplot as plt
    def round_time(t, res=0.1):
        v = int(t / float(res)) * res
        return v

    # Get color map
    cmap = plt.get_cmap(cmap_id)

    # Get segments by level
    level_bounds = []
    for level in T.levels:
        if level == "root":
            continue
        segments = T.get_segments_in_level(level)
        level_bounds.append(segments)

    # Plot axvspans for each segment
    B = float(len(level_bounds))
    #plt.figure(figsize=figsize)
    for i, segments in enumerate(level_bounds):
        labels = utils.segment_labels_to_floats(segments)
        for segment, label in zip(segments, labels):
            #print i, label, cmap(label)
            if res is None:
                start = segment.start
                end = segment.end
                xlabel = "Time (seconds)"
            else:
                start = int(round_time(segment.start, res=res) / res)
                end = int(round_time(segment.end, res=res) / res)
                xlabel = "Time (frames)"
            plt.axvspan(start, end,
                        ymax=(len(level_bounds) - i) / B,
                        ymin=(len(level_bounds) - i - 1) / B,
                        facecolor=cmap(label))

    # Plot labels
    L = float(len(T.levels) - 1)
    plt.yticks(np.linspace(0, (L - 1) / L, num=L) + 1 / L / 2.,
               T.levels[1:][::-1])
    plt.xlabel(xlabel)
    if title is not None:
        plt.title(title)
    plt.gca().set_xlim([0, end])

def
plot_tree
(
T
,
res
=
,
title
=
,
cmap_id
=
"Pastel2"
)
:
import
matplotlib
.
pyplot
as
plt
def
round_time
(
t
,
res
=
0.1
)
:
v
=
int
(
t
/
float
(
res
)
)
*
res
return
v
# Get color map
cmap
=
plt
.
get_cmap
(
cmap_id
)
# Get segments by level
level_bounds
=
[
]
for
level
in
T
.
levels
:
if
level
==
"root"
:
continue
segments
=
T
.
get_segments_in_level
(
level
)
level_bounds
.
append
(
segments
)
# Plot axvspans for each segment
B
=
float
(
len
(
level_bounds
)
)
#plt.figure(figsize=figsize)
for
i
,
segments
in
enumerate
(
level_bounds
)
:
labels
=
utils
.
segment_labels_to_floats
(
segments
)
for
segment
,
label
in
zip
(
segments
,
labels
)
:
#print i, label, cmap(label)
if
res
is
:
start
=
segment
.
start
end
=
segment
.
end
xlabel
=
"Time (seconds)"
else
:
start
=
int
(
round_time
(
segment
.
start
,
res
=
res
)
/
res
)
end
=
int
(
round_time
(
segment
.
end
,
res
=
res
)
/
res
)
xlabel
=
"Time (frames)"
plt
.
axvspan
(
start
,
end
,
ymax
=
(
len
(
level_bounds
)
i
)
/
B
,
ymin
=
(
len
(
level_bounds
)
i
1
)
/
B
,
facecolor
=
cmap
(
label
)
)
# Plot labels
L
=
float
(
len
(
T
.
levels
)
1
)
plt
.
yticks
(
np
.
linspace
(
0
,
(
L
1
)
/
L
,
num
=
L
)
+
1
/
L
/
2.
,
T
.
levels
[
1
:
]
[
:
:
1
]
)
plt
.
xlabel
(
xlabel
)
if
title
is
not
:
plt
.
title
(
title
)
plt
.
gca
(
)
.
set_xlim
(
[
0
,
end
]
)

Plots a given tree containing hierarchical segmentation .


def get_feat_segments(F, bound_idxs):
    """Returns a set of segments defined by the bound_idxs.

    Parameters
    ----------
    F: np.ndarray
        Matrix containing the features, one feature vector per row.
    bound_idxs: np.ndarray
        Array with boundary indeces.

    Returns
    -------
    feat_segments: list
        List of segments, one for each boundary interval.
    """
    # Make sure bound_idxs are not empty
    assert len(bound_idxs) > 0, "Boundaries can't be empty"

    # Make sure that boundaries are sorted
    bound_idxs = np.sort(bound_idxs)

    # Make sure we're not out of bounds
    assert bound_idxs[0] >= 0 and bound_idxs[-1] < F.shape[0], \
        "Boundaries are not correct for the given feature dimensions."

    # Obtain the segments
    feat_segments = []
    for i in range(len(bound_idxs) - 1):
        feat_segments.append(F[bound_idxs[i]:bound_idxs[i + 1], :])
    return feat_segments

Returns a set of segments defined by the bound_idxs .


def feat_segments_to_2dfmc_max(feat_segments, offset=4):
    """From a list of feature segments, return a list of 2D-Fourier Magnitude
    Coefs using the maximum segment size as main size and zero pad the rest.

    Parameters
    ----------
    feat_segments: list
        List of segments, one for each boundary interval.
    offset: int >= 0
        Number of frames to ignore from beginning and end of each segment.

    Returns
    -------
    fmcs: np.ndarray
        Tensor containing the 2D-FMC matrices, one matrix per segment.
    """
    if len(feat_segments) == 0:
        return []

    # Get maximum segment size
    max_len = max([feat_segment.shape[0] for feat_segment in feat_segments])

    fmcs = []
    for feat_segment in feat_segments:
        # Zero pad if needed
        X = np.zeros((max_len, feat_segment.shape[1]))

        # Remove a set of frames in the beginning an end of the segment
        if feat_segment.shape[0] <= offset or offset == 0:
            X[:feat_segment.shape[0], :] = feat_segment
        else:
            X[:feat_segment.shape[0] - offset, :] = \
                feat_segment[offset // 2:-offset // 2, :]

        # Compute the 2D-FMC
        try:
            fmcs.append(utils2d.compute_ffmc2d(X))
        except:
            logging.warning("Couldn't compute the 2D Fourier Transform")
            fmcs.append(np.zeros((X.shape[0] * X.shape[1]) // 2 + 1))

        # Normalize
        # fmcs[-1] = fmcs[-1] / float(fmcs[-1].max())

    return np.asarray(fmcs)

From a list of feature segments return a list of 2D - Fourier Magnitude Coefs using the maximum segment size as main size and zero pad the rest .


def compute_similarity(F, bound_idxs, dirichlet=False, xmeans=False, k=5,
                       offset=4):
    """Main function to compute the segment similarity of file file_struct.

    Parameters
    ----------
    F: np.ndarray
        Matrix containing one feature vector per row.
    bound_idxs: np.ndarray
        Array with the indeces of the segment boundaries.
    dirichlet: boolean
        Whether to use the dirichlet estimator of the number of unique labels.
    xmeans: boolean
        Whether to use the xmeans estimator of the number of unique labels.
    k: int > 0
        If the other two predictors are `False`, use fixed number of labels.
    offset: int >= 0
        Number of frames to ignore from beginning and end of each segment.

    Returns
    -------
    labels_est: np.ndarray
        Estimated labels, containing integer identifiers.
    """
    # Get the feature segments
    feat_segments = get_feat_segments(F, bound_idxs)

    # Get the 2D-FMCs segments
    fmcs = feat_segments_to_2dfmc_max(feat_segments, offset)
    if len(fmcs) == 0:
        return np.arange(len(bound_idxs) - 1)

    # Compute the labels using kmeans
    if dirichlet:
        k_init = np.min([fmcs.shape[0], k])
        # Only compute the dirichlet method if the fmc shape is small enough
        if fmcs.shape[1] > 500:
            labels_est = compute_labels_kmeans(fmcs, k=k)
        else:
            dpgmm = mixture.DPGMM(n_components=k_init, covariance_type='full')
            # dpgmm = mixture.VBGMM(n_components=k_init, covariance_type='full')
            dpgmm.fit(fmcs)
            k = len(dpgmm.means_)
            labels_est = dpgmm.predict(fmcs)
            # print("Estimated with Dirichlet Process:", k)
    if xmeans:
        xm = XMeans(fmcs, plot=False)
        k = xm.estimate_K_knee(th=0.01, maxK=8)
        labels_est = compute_labels_kmeans(fmcs, k=k)
        # print("Estimated with Xmeans:", k)
    else:
        labels_est = compute_labels_kmeans(fmcs, k=k)

    return labels_est

Main function to compute the segment similarity of file file_struct .


def fit(self, X, Y):
        '''Fit the OLDA model

        Parameters
        ----------
        X : array-like, shape [n_samples]
            Training data: each example is an n_features-by-* data array

        Y : array-like, shape [n_samples]
            Training labels: each label is an array of change-points
                             (eg, a list of segment boundaries)

        Returns
        -------
        self : object
        '''

        # Re-initialize the scatter matrices
        self.scatter_ordinal_ = None
        self.scatter_within_  = None

        # Reduce to partial-fit
        self.partial_fit(X, Y)

        return self

def
fit
(
self
,
X
,
Y
)
:
# Re-initialize the scatter matrices
self
.
scatter_ordinal_
=
self
.
scatter_within_
=
# Reduce to partial-fit
self
.
partial_fit
(
X
,
Y
)
return
self


def partial_fit(self, X, Y):
        '''Partial-fit the OLDA model

        Parameters
        ----------
        X : array-like, shape [n_samples]
            Training data: each example is an n_features-by-* data array

        Y : array-like, shape [n_samples]
            Training labels: each label is an array of change-points
                             (eg, a list of segment boundaries)

        Returns
        -------
        self : object
        '''

        for (xi, yi) in itertools.izip(X, Y):

            prev_mean       = None
            prev_length     = None

            if self.scatter_within_ is None:
                # First round: initialize
                d, n = xi.shape

                if yi[0] > 0:
                    yi = np.concatenate([np.array([0]), yi])
                if yi[-1] < n:
                    yi = np.concatenate([yi, np.array([n])])

                self.scatter_within_  = self.sigma * np.eye(d)
                self.scatter_ordinal_ = np.zeros(d)

            # iterate over segments
            for (seg_start, seg_end) in zip(yi[:-1], yi[1:]):

                seg_length = seg_end - seg_start

                if seg_length < 2:
                    continue

                seg_mean = np.mean(xi[:, seg_start:seg_end], axis=1, keepdims=True)
                seg_cov  = np.cov(xi[:, seg_start:seg_end])
                self.scatter_within_ = self.scatter_within_ + seg_length * seg_cov

                if prev_mean is not None:
                    diff_ord = seg_mean - (prev_length * prev_mean + seg_length * seg_mean) / (prev_length + seg_length)
                    self.scatter_ordinal_ = self.scatter_ordinal_ + seg_length * np.dot(diff_ord, diff_ord.T)

                    diff_ord = prev_mean - (prev_length * prev_mean + seg_length * seg_mean) / (prev_length + seg_length)
                    self.scatter_ordinal_ = self.scatter_ordinal_ + prev_length * np.dot(diff_ord, diff_ord.T)

                prev_mean = seg_mean
                prev_length = seg_length

        e_vals, e_vecs = scipy.linalg.eig(self.scatter_ordinal_, self.scatter_within_)
        self.e_vals_ = e_vals
        self.e_vecs_ = e_vecs
        self.components_ = e_vecs.T
        return self

def
partial_fit
(
self
,
X
,
Y
)
:
for
(
xi
,
yi
)
in
itertools
.
izip
(
X
,
Y
)
:
prev_mean
=
prev_length
=
if
self
.
scatter_within_
is
:
# First round: initialize
d
,
n
=
xi
.
shape
if
yi
[
0
]
>
0
:
yi
=
np
.
concatenate
(
[
np
.
array
(
[
0
]
)
,
yi
]
)
if
yi
[
1
]
<
n
:
yi
=
np
.
concatenate
(
[
yi
,
np
.
array
(
[
n
]
)
]
)
self
.
scatter_within_
=
self
.
sigma
*
np
.
eye
(
d
)
self
.
scatter_ordinal_
=
np
.
zeros
(
d
)
# iterate over segments
for
(
seg_start
,
seg_end
)
in
zip
(
yi
[
:
1
]
,
yi
[
1
:
]
)
:
seg_length
=
seg_end
seg_start
if
seg_length
<
2
:
continue
seg_mean
=
np
.
mean
(
xi
[
:
,
seg_start
:
seg_end
]
,
axis
=
1
,
keepdims
=
True
)
seg_cov
=
np
.
cov
(
xi
[
:
,
seg_start
:
seg_end
]
)
self
.
scatter_within_
=
self
.
scatter_within_
+
seg_length
*
seg_cov
if
prev_mean
is
not
:
diff_ord
=
seg_mean
(
prev_length
*
prev_mean
+
seg_length
*
seg_mean
)
/
(
prev_length
+
seg_length
)
self
.
scatter_ordinal_
=
self
.
scatter_ordinal_
+
seg_length
*
np
.
dot
(
diff_ord
,
diff_ord
.
T
)
diff_ord
=
prev_mean
(
prev_length
*
prev_mean
+
seg_length
*
seg_mean
)
/
(
prev_length
+
seg_length
)
self
.
scatter_ordinal_
=
self
.
scatter_ordinal_
+
prev_length
*
np
.
dot
(
diff_ord
,
diff_ord
.
T
)
prev_mean
=
seg_mean
prev_length
=
seg_length
e_vals
,
e_vecs
=
scipy
.
linalg
.
eig
(
self
.
scatter_ordinal_
,
self
.
scatter_within_
)
self
.
e_vals_
=
e_vals
self
.
e_vecs_
=
e_vecs
self
.
components_
=
e_vecs
.
T
return
self


def read_references(audio_path, annotator_id=0):
    """Reads the boundary times and the labels.

    Parameters
    ----------
    audio_path : str
        Path to the audio file

    Returns
    -------
    ref_times : list
        List of boundary times
    ref_labels : list
        List of labels

    Raises
    ------
    IOError: if `audio_path` doesn't exist.
    """
    # Dataset path
    ds_path = os.path.dirname(os.path.dirname(audio_path))

    # Read references
    jam_path = os.path.join(ds_path, ds_config.references_dir,
                            os.path.basename(audio_path)[:-4] +
                            ds_config.references_ext)

    jam = jams.load(jam_path, validate=False)
    ann = jam.search(namespace='segment_.*')[annotator_id]
    ref_inters, ref_labels = ann.to_interval_values()

    # Intervals to times
    ref_times = utils.intervals_to_times(ref_inters)

    return ref_times, ref_labels

def
read_references
(
audio_path
,
annotator_id
=
0
)
:
# Dataset path
ds_path
=
os
.
path
.
dirname
(
os
.
path
.
dirname
(
audio_path
)
)
# Read references
jam_path
=
os
.
path
.
join
(
ds_path
,
ds_config
.
references_dir
,
os
.
path
.
basename
(
audio_path
)
[
:
4
]
+
ds_config
.
references_ext
)
jam
=
jams
.
load
(
jam_path
,
validate
=
False
)
ann
=
jam
.
search
(
namespace
=
'segment_.*'
)
[
annotator_id
]
ref_inters
,
ref_labels
=
ann
.
to_interval_values
(
)
# Intervals to times
ref_times
=
utils
.
intervals_to_times
(
ref_inters
)
return
ref_times
,
ref_labels

Reads the boundary times and the labels .


def find_estimation(jam, boundaries_id, labels_id, params):
    """Finds the correct estimation from all the estimations contained in a
    JAMS file given the specified arguments.

    Parameters
    ----------
    jam : jams.JAMS
        JAMS object.
    boundaries_id : str
        Identifier of the algorithm used to compute the boundaries.
    labels_id : str
        Identifier of the algorithm used to compute the labels.
    params : dict
        Additional search parameters. E.g. {"feature" : "pcp"}.

    Returns
    -------
    ann : jams.Annotation
        Found estimation.
        `None` if it couldn't be found.
    """
    # Use handy JAMS search interface
    namespace = "multi_segment" if params["hier"] else "segment_open"
    # TODO: This is a workaround to issue in JAMS. Should be
    # resolved in JAMS 0.2.3, but for now, this works too.
    ann = jam.search(namespace=namespace).\
        search(**{"Sandbox.boundaries_id": boundaries_id}).\
        search(**{"Sandbox.labels_id": lambda x:
                  (isinstance(x, six.string_types) and
                   re.match(labels_id, x) is not None) or x is None})
    for key, val in zip(params.keys(), params.values()):
        if isinstance(val, six.string_types):
            ann = ann.search(**{"Sandbox.%s" % key: val})
        else:
            ann = ann.search(**{"Sandbox.%s" % key: lambda x: x == val})

    # Check estimations found
    if len(ann) > 1:
        logging.warning("More than one estimation with same parameters.")

    if len(ann) > 0:
        ann = ann[0]

    # If we couldn't find anything, let's return None
    if not ann:
        ann = None

    return ann

Finds the correct estimation from all the estimations contained in a JAMS file given the specified arguments .


def save_estimations(file_struct, times, labels, boundaries_id, labels_id,
                     **params):
    """Saves the segment estimations in a JAMS file.

    Parameters
    ----------
    file_struct : FileStruct
        Object with the different file paths of the current file.
    times : np.array or list
        Estimated boundary times.
        If `list`, estimated hierarchical boundaries.
    labels : np.array(N, 2)
        Estimated labels (None in case we are only storing boundary
        evaluations).
    boundaries_id : str
        Boundary algorithm identifier.
    labels_id : str
        Labels algorithm identifier.
    params : dict
        Dictionary with additional parameters for both algorithms.
    """
    # Remove features if they exist
    params.pop("features", None)

    # Get duration
    dur = get_duration(file_struct.features_file)

    # Convert to intervals and sanity check
    if 'numpy' in str(type(times)):
        # Flat check
        inters = utils.times_to_intervals(times)
        assert len(inters) == len(labels), "Number of boundary intervals " \
            "(%d) and labels (%d) do not match" % (len(inters), len(labels))
        # Put into lists to simplify the writing process later
        inters = [inters]
        labels = [labels]
    else:
        # Hierarchical check
        inters = []
        for level in range(len(times)):
            est_inters = utils.times_to_intervals(times[level])
            inters.append(est_inters)
            assert len(inters[level]) == len(labels[level]), \
                "Number of boundary intervals (%d) and labels (%d) do not " \
                "match in level %d" % (len(inters[level]), len(labels[level]),
                                       level)

    # Create new estimation
    namespace = "multi_segment" if params["hier"] else "segment_open"
    ann = jams.Annotation(namespace=namespace)

    # Find estimation in file
    if os.path.isfile(file_struct.est_file):
        jam = jams.load(file_struct.est_file, validate=False)
        curr_ann = find_estimation(jam, boundaries_id, labels_id, params)
        if curr_ann is not None:
            curr_ann.data = ann.data  # cleanup all data
            ann = curr_ann  # This will overwrite the existing estimation
        else:
            jam.annotations.append(ann)
    else:
        # Create new JAMS if it doesn't exist
        jam = jams.JAMS()
        jam.file_metadata.duration = dur
        jam.annotations.append(ann)

    # Save metadata and parameters
    ann.annotation_metadata.version = msaf.__version__
    ann.annotation_metadata.data_source = "MSAF"
    sandbox = {}
    sandbox["boundaries_id"] = boundaries_id
    sandbox["labels_id"] = labels_id
    sandbox["timestamp"] = \
        datetime.datetime.today().strftime("%Y/%m/%d %H:%M:%S")
    for key in params:
        sandbox[key] = params[key]
    ann.sandbox = sandbox

    # Save actual data
    for i, (level_inters, level_labels) in enumerate(zip(inters, labels)):
        for bound_inter, label in zip(level_inters, level_labels):
            dur = float(bound_inter[1]) - float(bound_inter[0])
            label = chr(int(label) + 65)
            if params["hier"]:
                value = {"label": label, "level": i}
            else:
                value = label
            ann.append(time=bound_inter[0], duration=dur,
                       value=value)

    # Write results
    jam.save(file_struct.est_file)

Saves the segment estimations in a JAMS file .


def get_all_boundary_algorithms():
    """Gets all the possible boundary algorithms in MSAF.

    Returns
    -------
    algo_ids : list
        List of all the IDs of boundary algorithms (strings).
    """
    algo_ids = []
    for name in msaf.algorithms.__all__:
        module = eval(msaf.algorithms.__name__ + "." + name)
        if module.is_boundary_type:
            algo_ids.append(module.algo_id)
    return algo_ids

def get_all_boundary_algorithms ( ) : algo_ids = [ ] for name in msaf . algorithms . __all__ : module = eval ( msaf . algorithms . __name__ + "." + name ) if module . is_boundary_type : algo_ids . append ( module . algo_id ) return algo_ids

Gets all the possible boundary algorithms in MSAF .


def get_configuration(feature, annot_beats, framesync, boundaries_id,
                      labels_id):
    """Gets the configuration dictionary from the current parameters of the
    algorithms to be evaluated."""
    config = {}
    config["annot_beats"] = annot_beats
    config["feature"] = feature
    config["framesync"] = framesync
    bound_config = {}
    if boundaries_id != "gt":
        bound_config = \
            eval(msaf.algorithms.__name__ + "." + boundaries_id).config
        config.update(bound_config)
    if labels_id is not None:
        label_config = \
            eval(msaf.algorithms.__name__ + "." + labels_id).config

        # Make sure we don't have parameter name duplicates
        if labels_id != boundaries_id:
            overlap = set(bound_config.keys()). \
                intersection(set(label_config.keys()))
            assert len(overlap) == 0, \
                "Parameter %s must not exist both in %s and %s algorithms" % \
                (overlap, boundaries_id, labels_id)
        config.update(label_config)
    return config

Gets the configuration dictionary from the current parameters of the algorithms to be evaluated .


def get_dataset_files(in_path):
    """Gets the files of the given dataset."""
    # Get audio files
    audio_files = []
    for ext in ds_config.audio_exts:
        audio_files += glob.glob(
            os.path.join(in_path, ds_config.audio_dir, "*" + ext))

    # Make sure directories exist
    utils.ensure_dir(os.path.join(in_path, ds_config.features_dir))
    utils.ensure_dir(os.path.join(in_path, ds_config.estimations_dir))
    utils.ensure_dir(os.path.join(in_path, ds_config.references_dir))

    # Get the file structs
    file_structs = []
    for audio_file in audio_files:
        file_structs.append(FileStruct(audio_file))

    # Sort by audio file name
    file_structs = sorted(file_structs,
                          key=lambda file_struct: file_struct.audio_file)

    return file_structs

def
get_dataset_files
(
in_path
)
:
# Get audio files
audio_files
=
[
]
for
ext
in
ds_config
.
audio_exts
:
audio_files
+=
glob
.
glob
(
os
.
path
.
join
(
in_path
,
ds_config
.
audio_dir
,
"*"
+
ext
)
)
# Make sure directories exist
utils
.
ensure_dir
(
os
.
path
.
join
(
in_path
,
ds_config
.
features_dir
)
)
utils
.
ensure_dir
(
os
.
path
.
join
(
in_path
,
ds_config
.
estimations_dir
)
)
utils
.
ensure_dir
(
os
.
path
.
join
(
in_path
,
ds_config
.
references_dir
)
)
# Get the file structs
file_structs
=
[
]
for
audio_file
in
audio_files
:
file_structs
.
append
(
FileStruct
(
audio_file
)
)
# Sort by audio file name
file_structs
=
sorted
(
file_structs
,
key
=
lambda
file_struct
:
file_struct
.
audio_file
)
return
file_structs


def read_hier_references(jams_file, annotation_id=0, exclude_levels=[]):
    """Reads hierarchical references from a jams file.

    Parameters
    ----------
    jams_file : str
        Path to the jams file.
    annotation_id : int > 0
        Identifier of the annotator to read from.
    exclude_levels: list
        List of levels to exclude. Empty list to include all levels.

    Returns
    -------
    hier_bounds : list
        List of the segment boundary times in seconds for each level.
    hier_labels : list
        List of the segment labels for each level.
    hier_levels : list
        List of strings for the level identifiers.
    """
    hier_bounds = []
    hier_labels = []
    hier_levels = []
    jam = jams.load(jams_file)
    namespaces = ["segment_salami_upper", "segment_salami_function",
                  "segment_open", "segment_tut", "segment_salami_lower"]

    # Remove levels if needed
    for exclude in exclude_levels:
        if exclude in namespaces:
            namespaces.remove(exclude)

    # Build hierarchy references
    for ns in namespaces:
        ann = jam.search(namespace=ns)
        if not ann:
            continue
        ref_inters, ref_labels = ann[annotation_id].to_interval_values()
        hier_bounds.append(utils.intervals_to_times(ref_inters))
        hier_labels.append(ref_labels)
        hier_levels.append(ns)

    return hier_bounds, hier_labels, hier_levels

def
read_hier_references
(
jams_file
,
annotation_id
=
0
,
exclude_levels
=
[
]
)
:
hier_bounds
=
[
]
hier_labels
=
[
]
hier_levels
=
[
]
jam
=
jams
.
load
(
jams_file
)
namespaces
=
[
"segment_salami_upper"
,
"segment_salami_function"
,
"segment_open"
,
"segment_tut"
,
"segment_salami_lower"
]
# Remove levels if needed
for
exclude
in
exclude_levels
:
if
exclude
in
namespaces
:
namespaces
.
remove
(
exclude
)
# Build hierarchy references
for
ns
in
namespaces
:
ann
=
jam
.
search
(
namespace
=
ns
)
if
not
ann
:
continue
ref_inters
,
ref_labels
=
ann
[
annotation_id
]
.
to_interval_values
(
)
hier_bounds
.
append
(
utils
.
intervals_to_times
(
ref_inters
)
)
hier_labels
.
append
(
ref_labels
)
hier_levels
.
append
(
ns
)
return
hier_bounds
,
hier_labels
,
hier_levels

Reads hierarchical references from a jams file .


def get_duration(features_file):
    """Reads the duration of a given features file.

    Parameters
    ----------
    features_file: str
        Path to the JSON file containing the features.

    Returns
    -------
    dur: float
        Duration of the analyzed file.
    """
    with open(features_file) as f:
        feats = json.load(f)
    return float(feats["globals"]["dur"])

def get_duration ( features_file ) : with open ( features_file ) as f : feats = json . load ( f ) return float ( feats [ "globals" ] [ "dur" ] )

Reads the duration of a given features file .


def write_mirex(times, labels, out_file):
    """Writes results to file using the standard MIREX format.

    Parameters
    ----------
    times: np.array
        Times in seconds of the boundaries.
    labels: np.array
        Labels associated to the segments defined by the boundaries.
    out_file: str
        Output file path to save the results.
    """
    inters = msaf.utils.times_to_intervals(times)
    assert len(inters) == len(labels)
    out_str = ""
    for inter, label in zip(inters, labels):
        out_str += "%.3f\t%.3f\t%s\n" % (inter[0], inter[1], label)
    with open(out_file, "w") as f:
        f.write(out_str[:-1])

def write_mirex ( times , labels , out_file ) : inters = msaf . utils . times_to_intervals ( times ) assert len ( inters ) == len ( labels ) out_str = "" for inter , label in zip ( inters , labels ) : out_str += "%.3f\t%.3f\t%s\n" % ( inter [ 0 ] , inter [ 1 ] , label ) with open ( out_file , "w" ) as f : f . write ( out_str [ : - 1 ] )

Writes results to file using the standard MIREX format .


def _get_dataset_file(self, dir, ext):
        """Gets the desired dataset file."""
        audio_file_ext = "." + self.audio_file.split(".")[-1]
        base_file = os.path.basename(self.audio_file).replace(
            audio_file_ext, ext)
        return os.path.join(self.ds_path, dir, base_file)

def _get_dataset_file ( self , dir , ext ) : audio_file_ext = "." + self . audio_file . split ( "." ) [ - 1 ] base_file = os . path . basename ( self . audio_file ) . replace ( audio_file_ext , ext ) return os . path . join ( self . ds_path , dir , base_file )


def align_segmentation(beat_times, song):
    '''Load a ground-truth segmentation, and align times to the nearest
    detected beats.

    Arguments:
        beat_times -- array
        song -- path to the audio file

    Returns:
        segment_beats -- array
            beat-aligned segment boundaries

        segment_times -- array
            true segment times

        segment_labels -- array
            list of segment labels
    '''
    try:
        segment_times, segment_labels = msaf.io.read_references(song)
    except:
        return None, None, None
    segment_times = np.asarray(segment_times)

    # Map to intervals
    segment_intervals = msaf.utils.times_to_intervals(segment_times)

    # Map beats to intervals
    beat_intervals = np.asarray(zip(beat_times[:-1], beat_times[1:]))

    # Map beats to segments
    beat_segment_ids = librosa.util.match_intervals(beat_intervals,
                                                    segment_intervals)

    segment_beats = []
    segment_times_out = []
    segment_labels_out = []

    # print segment_times, beat_segment_ids, len(beat_times),
    # len(beat_segment_ids)
    for i in range(segment_times.shape[0]):
        hits = np.argwhere(beat_segment_ids == i)
        if len(hits) > 0 and i < len(segment_intervals) and \
                i < len(segment_labels):
            segment_beats.extend(hits[0])
            segment_times_out.append(segment_intervals[i, :])
            segment_labels_out.append(segment_labels[i])

    # Pull out the segment start times
    segment_beats = list(segment_beats)
    # segment_times_out = np.asarray(
    #  segment_times_out)[:, 0].squeeze().reshape((-1, 1))

    # if segment_times_out.ndim == 0:
    #    segment_times_out = segment_times_out[np.newaxis]
    segment_times_out = segment_times

    return segment_beats, segment_times_out, segment_labels_out

Load a ground - truth segmentation and align times to the nearest detected beats .


def estimate_beats(self):
        """Estimates the beats using librosa.

        Returns
        -------
        times: np.array
            Times of estimated beats in seconds.
        frames: np.array
            Frame indeces of estimated beats.
        """
        # Compute harmonic-percussive source separation if needed
        if self._audio_percussive is None:
            self._audio_harmonic, self._audio_percussive = self.compute_HPSS()

        # Compute beats
        tempo, frames = librosa.beat.beat_track(
            y=self._audio_percussive, sr=self.sr,
            hop_length=self.hop_length)

        # To times
        times = librosa.frames_to_time(frames, sr=self.sr,
                                       hop_length=self.hop_length)

        # TODO: Is this really necessary?
        if len(times) > 0 and times[0] == 0:
            times = times[1:]
            frames = frames[1:]

        return times, frames


def read_ann_beats(self):
        """Reads the annotated beats if available.

        Returns
        -------
        times: np.array
            Times of annotated beats in seconds.
        frames: np.array
            Frame indeces of annotated beats.
        """
        times, frames = (None, None)

        # Read annotations if they exist in correct folder
        if os.path.isfile(self.file_struct.ref_file):
            try:
                jam = jams.load(self.file_struct.ref_file)
            except TypeError:
                logging.warning(
                    "Can't read JAMS file %s. Maybe it's not "
                    "compatible with current JAMS version?" %
                    self.file_struct.ref_file)
                return times, frames
            beat_annot = jam.search(namespace="beat.*")

            # If beat annotations exist, get times and frames
            if len(beat_annot) > 0:
                beats_inters, _ = beat_annot[0].to_interval_values()
                times = beats_inters[:, 0]
                frames = librosa.time_to_frames(times, sr=self.sr,
                                                hop_length=self.hop_length)
        return times, frames

Reads the annotated beats if available .


def compute_beat_sync_features(self, beat_frames, beat_times, pad):
        """Make the features beat-synchronous.

        Parameters
        ----------
        beat_frames: np.array
            The frame indeces of the beat positions.
        beat_times: np.array
            The time points of the beat positions (in seconds).
        pad: boolean
            If `True`, `beat_frames` is padded to span the full range.

        Returns
        -------
        beatsync_feats: np.array
            The beat-synchronized features.
            `None` if the beat_frames was `None`.
        beatsync_times: np.array
            The beat-synchronized times.
            `None` if the beat_frames was `None`.
        """
        if beat_frames is None:
            return None, None

        # Make beat synchronous
        beatsync_feats = librosa.util.utils.sync(self._framesync_features.T,
                                                 beat_frames, pad=pad).T

        # Assign times (and add last time if padded)
        beatsync_times = np.copy(beat_times)
        if beatsync_times.shape[0] != beatsync_feats.shape[0]:
            beatsync_times = np.concatenate((beatsync_times,
                                             [self._framesync_times[-1]]))
        return beatsync_feats, beatsync_times


def read_features(self, tol=1e-3):
        """Reads the features from a file and stores them in the current
        object.

        Parameters
        ----------
        tol: float
            Tolerance level to detect duration of audio.
        """
        try:
            # Read JSON file
            with open(self.file_struct.features_file) as f:
                feats = json.load(f)

            # Store duration
            if self.dur is None:
                self.dur = float(feats["globals"]["dur"])

            # Check that we have the correct global parameters
            assert(np.isclose(
                self.dur, float(feats["globals"]["dur"]), rtol=tol))
            assert(self.sr == int(feats["globals"]["sample_rate"]))
            assert(self.hop_length == int(feats["globals"]["hop_length"]))
            assert(os.path.basename(self.file_struct.audio_file) ==
                   os.path.basename(feats["globals"]["audio_file"]))

            # Check for specific features params
            feat_params_err = FeatureParamsError(
                "Couldn't find features for %s id in file %s" %
                (self.get_id(), self.file_struct.features_file))
            if self.get_id() not in feats.keys():
                raise feat_params_err
            for param_name in self.get_param_names():
                value = getattr(self, param_name)
                if hasattr(value, '__call__'):
                    # Special case of functions
                    if value.__name__ != \
                            feats[self.get_id()]["params"][param_name]:
                        raise feat_params_err
                else:
                    if str(value) != \
                            feats[self.get_id()]["params"][param_name]:
                        raise feat_params_err

            # Store actual features
            self._est_beats_times = np.array(feats["est_beats"])
            self._est_beatsync_times = np.array(feats["est_beatsync_times"])
            self._est_beats_frames = librosa.core.time_to_frames(
                self._est_beats_times, sr=self.sr, hop_length=self.hop_length)
            self._framesync_features = \
                np.array(feats[self.get_id()]["framesync"])
            self._est_beatsync_features = \
                np.array(feats[self.get_id()]["est_beatsync"])

            # Read annotated beats if available
            if "ann_beats" in feats.keys():
                self._ann_beats_times = np.array(feats["ann_beats"])
                self._ann_beatsync_times = np.array(feats["ann_beatsync_times"])
                self._ann_beats_frames = librosa.core.time_to_frames(
                    self._ann_beats_times, sr=self.sr,
                    hop_length=self.hop_length)
                self._ann_beatsync_features = \
                    np.array(feats[self.get_id()]["ann_beatsync"])
        except KeyError:
            raise WrongFeaturesFormatError(
                "The features file %s is not correctly formatted" %
                self.file_struct.features_file)
        except AssertionError:
            raise FeaturesNotFound(
                "The features for the given parameters were not found in "
                "features file %s" % self.file_struct.features_file)
        except IOError:
            raise NoFeaturesFileError("Could not find features file %s",
                                      self.file_struct.features_file)

Reads the features from a file and stores them in the current object .


def write_features(self):
        """Saves features to file."""
        out_json = collections.OrderedDict()
        try:
            # Only save the necessary information
            self.read_features()
        except (WrongFeaturesFormatError, FeaturesNotFound,
                NoFeaturesFileError):
            # We need to create the file or overwite it
            # Metadata
            out_json = collections.OrderedDict({"metadata": {
                "versions": {"librosa": librosa.__version__,
                             "msaf": msaf.__version__,
                             "numpy": np.__version__},
                "timestamp": datetime.datetime.today().strftime(
                    "%Y/%m/%d %H:%M:%S")}})

            # Global parameters
            out_json["globals"] = {
                "dur": self.dur,
                "sample_rate": self.sr,
                "hop_length": self.hop_length,
                "audio_file": self.file_struct.audio_file
            }

            # Beats
            out_json["est_beats"] = self._est_beats_times.tolist()
            out_json["est_beatsync_times"] = self._est_beatsync_times.tolist()
            if self._ann_beats_times is not None:
                out_json["ann_beats"] = self._ann_beats_times.tolist()
                out_json["ann_beatsync_times"] = self._ann_beatsync_times.tolist()
        except FeatureParamsError:
            # We have other features in the file, simply add these ones
            with open(self.file_struct.features_file) as f:
                out_json = json.load(f)
        finally:
            # Specific parameters of the current features
            out_json[self.get_id()] = {}
            out_json[self.get_id()]["params"] = {}
            for param_name in self.get_param_names():
                value = getattr(self, param_name)
                # Check for special case of functions
                if hasattr(value, '__call__'):
                    value = value.__name__
                else:
                    value = str(value)
                out_json[self.get_id()]["params"][param_name] = value

            # Actual features
            out_json[self.get_id()]["framesync"] = \
                self._framesync_features.tolist()
            out_json[self.get_id()]["est_beatsync"] = \
                self._est_beatsync_features.tolist()
            if self._ann_beatsync_features is not None:
                out_json[self.get_id()]["ann_beatsync"] = \
                    self._ann_beatsync_features.tolist()

            # Save it
            with open(self.file_struct.features_file, "w") as f:
                json.dump(out_json, f, indent=2)


def get_param_names(self):
        """Returns the parameter names for these features, avoiding
        the global parameters."""
        return [name for name in vars(self) if not name.startswith('_') and
                name not in self._global_param_names]

def get_param_names ( self ) : return [ name for name in vars ( self ) if not name . startswith ( '_' ) and name not in self . _global_param_names ]

Returns the parameter names for these features avoiding the global parameters .


def _compute_framesync_times(self):
        """Computes the framesync times based on the framesync features."""
        self._framesync_times = librosa.core.frames_to_time(
            np.arange(self._framesync_features.shape[0]), self.sr,
            self.hop_length)

def _compute_framesync_times ( self ) : self . _framesync_times = librosa . core . frames_to_time ( np . arange ( self . _framesync_features . shape [ 0 ] ) , self . sr , self . hop_length )

Computes the framesync times based on the framesync features .


def frame_times(self):
        """This getter returns the frame times, for the corresponding type of
        features."""
        frame_times = None
        # Make sure we have already computed the features
        self.features
        if self.feat_type is FeatureTypes.framesync:
            self._compute_framesync_times()
            frame_times = self._framesync_times
        elif self.feat_type is FeatureTypes.est_beatsync:
            frame_times = self._est_beatsync_times
        elif self.feat_type is FeatureTypes.ann_beatsync:
            frame_times = self._ann_beatsync_times

        return frame_times

This getter returns the frame times for the corresponding type of features .


def features(self):
        """This getter will compute the actual features if they haven't
        been computed yet.

        Returns
        -------
        features: np.array
            The actual features. Each row corresponds to a feature vector.
        """
        # Compute features if needed
        if self._features is None:
            try:
                self.read_features()
            except (NoFeaturesFileError, FeaturesNotFound,
                    WrongFeaturesFormatError, FeatureParamsError) as e:
                try:
                    self._compute_all_features()
                    self.write_features()
                except IOError:
                    if isinstance(e, FeaturesNotFound) or \
                            isinstance(e, FeatureParamsError):
                        msg = "Computation of the features is needed for " \
                            "current parameters but no audio file was found." \
                            "Please, change your parameters or add the audio" \
                            " file in %s"
                    else:
                        msg = "Couldn't find audio file in %s"
                    raise NoAudioFileError(msg % self.file_struct.audio_file)

        # Choose features based on type
        if self.feat_type is FeatureTypes.framesync:
            self._features = self._framesync_features
        elif self.feat_type is FeatureTypes.est_beatsync:
            self._features = self._est_beatsync_features
        elif self.feat_type is FeatureTypes.ann_beatsync:
            if self._ann_beatsync_features is None:
                raise FeatureTypeNotFound(
                    "Feature type %s is not valid because no annotated beats "
                    "were found" % self.feat_type)
            self._features = self._ann_beatsync_features
        else:
            raise FeatureTypeNotFound("Feature type %s is not valid." %
                                      self.feat_type)

        return self._features

This getter will compute the actual features if they haven t been computed yet .


def select_features(cls, features_id, file_struct, annot_beats, framesync):
        """Selects the features from the given parameters.

        Parameters
        ----------
        features_id: str
            The identifier of the features (it must be a key inside the
            `features_registry`)
        file_struct: msaf.io.FileStruct
            The file struct containing the files to extract the features from
        annot_beats: boolean
            Whether to use annotated (`True`) or estimated (`False`) beats
        framesync: boolean
            Whether to use framesync (`True`) or beatsync (`False`) features

        Returns
        -------
        features: obj
            The actual features object that inherits from `msaf.Features`
        """
        if not annot_beats and framesync:
            feat_type = FeatureTypes.framesync
        elif annot_beats and not framesync:
            feat_type = FeatureTypes.ann_beatsync
        elif not annot_beats and not framesync:
            feat_type = FeatureTypes.est_beatsync
        else:
            raise FeatureTypeNotFound("Type of features not valid.")

        # Select features with default parameters
        if features_id not in features_registry.keys():
            raise FeaturesNotFound(
                "The features '%s' are invalid (valid features are %s)"
                % (features_id, features_registry.keys()))

        return features_registry[features_id](file_struct, feat_type)

Selects the features from the given parameters .


def _preprocess(self, valid_features=["pcp", "tonnetz", "mfcc",
                                          "cqt", "tempogram"]):
        """This method obtains the actual features."""
        # Use specific feature
        if self.feature_str not in valid_features:
            raise RuntimeError("Feature %s in not valid for algorithm: %s "
                               "(valid features are %s)." %
                               (self.feature_str, __name__, valid_features))
        else:
            try:
                F = self.features.features
            except KeyError:
                raise RuntimeError("Feature %s in not supported by MSAF" %
                                   (self.feature_str))

        return F

This method obtains the actual features .


def _postprocess(self, est_idxs, est_labels):
        """Post processes the estimations from the algorithm, removing empty
        segments and making sure the lenghts of the boundaries and labels
        match."""
        # Make sure we are using the previously input bounds, if any
        if self.in_bound_idxs is not None:
            F = self._preprocess()
            est_labels = U.synchronize_labels(self.in_bound_idxs, est_idxs,
                                              est_labels, F.shape[0])
            est_idxs = self.in_bound_idxs

        # Remove empty segments if needed
        est_idxs, est_labels = U.remove_empty_segments(est_idxs, est_labels)

        assert len(est_idxs) - 1 == len(est_labels), "Number of boundaries " \
            "(%d) and number of labels(%d) don't match" % (len(est_idxs),
                                                           len(est_labels))

        # Make sure the indeces are integers
        est_idxs = np.asarray(est_idxs, dtype=int)

        return est_idxs, est_labels

Post processes the estimations from the algorithm removing empty segments and making sure the lenghts of the boundaries and labels match .


def main():
    """Main function to sweep parameters of a certain algorithm."""
    parser = argparse.ArgumentParser(
        description="Runs the speficied algorithm(s) on the MSAF "
        "formatted dataset.",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("in_path",
                        action="store",
                        help="Input dataset")
    parser.add_argument("-f",
                        action="store",
                        dest="feature",
                        default="pcp",
                        type=str,
                        help="Type of features",
                        choices=["pcp", "tonnetz", "mfcc", "cqt", "tempogram"])
    parser.add_argument("-b",
                        action="store_true",
                        dest="annot_beats",
                        help="Use annotated beats",
                        default=False)
    parser.add_argument("-fs",
                        action="store_true",
                        dest="framesync",
                        help="Use frame-synchronous features",
                        default=False)
    parser.add_argument("-bid",
                        action="store",
                        help="Boundary algorithm identifier",
                        dest="boundaries_id",
                        default="gt",
                        choices=["gt"] +
                        io.get_all_boundary_algorithms())
    parser.add_argument("-lid",
                        action="store",
                        help="Label algorithm identifier",
                        dest="labels_id",
                        default=None,
                        choices=io.get_all_label_algorithms())
    parser.add_argument("-j",
                        action="store",
                        dest="n_jobs",
                        default=4,
                        type=int,
                        help="The number of threads to use")
    args = parser.parse_args()
    start_time = time.time()

    # Run the algorithm(s)
    process(args.in_path, annot_beats=args.annot_beats, feature=args.feature,
            framesync=args.framesync, boundaries_id=args.boundaries_id,
            labels_id=args.labels_id, n_jobs=args.n_jobs)

    # Done!
    logging.info("Done! Took %.2f seconds." % (time.time() - start_time))

Main function to sweep parameters of a certain algorithm .


def print_results(results):
    """Print all the results.

    Parameters
    ----------
    results: pd.DataFrame
        Dataframe with all the results
    """
    if len(results) == 0:
        logging.warning("No results to print!")
        return
    res = results.mean()
    logging.info("Results:\n%s" % res)

def
print_results
(
results
)
:
if
len
(
results
)
==
0
:
logging
.
warning
(
"No results to print!"
)
return
res
=
results
.
mean
(
)
logging
.
info
(
"Results:\n%s"
%
res
)


def compute_gt_results(est_file, ref_file, boundaries_id, labels_id, config,
                       bins=251, annotator_id=0):
    """Computes the results by using the ground truth dataset identified by
    the annotator parameter.

    Return
    ------
    results : dict
        Dictionary of the results (see function compute_results).
    """
    if config["hier"]:
        ref_times, ref_labels, ref_levels = \
            msaf.io.read_hier_references(
                ref_file, annotation_id=annotator_id,
                exclude_levels=["segment_salami_function"])
    else:
        jam = jams.load(ref_file, validate=False)
        ann = jam.search(namespace='segment_.*')[annotator_id]
        ref_inter, ref_labels = ann.to_interval_values()

    # Read estimations with correct configuration
    est_inter, est_labels = io.read_estimations(est_file, boundaries_id,
                                                labels_id, **config)

    # Compute the results and return
    logging.info("Evaluating %s" % os.path.basename(est_file))
    if config["hier"]:
        # Hierarchical
        assert len(est_inter) == len(est_labels), "Same number of levels " \
            "are required in the boundaries and labels for the hierarchical " \
            "evaluation."
        est_times = []
        est_labels = []

        # Sort based on how many segments per level
        est_inter = sorted(est_inter, key=lambda level: len(level))

        for inter in est_inter:
            est_times.append(msaf.utils.intervals_to_times(inter))
            # Add fake labels (hierarchical eval does not use labels --yet--)
            est_labels.append(np.ones(len(est_times[-1]) - 1) * -1)

        # Align the times
        utils.align_end_hierarchies(est_times, ref_times, thres=1)

        # To intervals
        est_hier = [utils.times_to_intervals(times) for times in est_times]
        ref_hier = [utils.times_to_intervals(times) for times in ref_times]

        # Compute evaluations
        res = {}
        res["t_recall10"], res["t_precision10"], res["t_measure10"] = \
            mir_eval.hierarchy.tmeasure(ref_hier, est_hier, window=10)
        res["t_recall15"], res["t_precision15"], res["t_measure15"] = \
            mir_eval.hierarchy.tmeasure(ref_hier, est_hier, window=15)

        res["track_id"] = os.path.basename(est_file)[:-5]
        return res
    else:
        # Flat
        return compute_results(ref_inter, est_inter, ref_labels, est_labels,
                               bins, est_file)

Computes the results by using the ground truth dataset identified by the annotator parameter .


def compute_information_gain(ann_inter, est_inter, est_file, bins):
    """Computes the information gain of the est_file from the annotated
    intervals and the estimated intervals."""
    ann_times = utils.intervals_to_times(ann_inter)
    est_times = utils.intervals_to_times(est_inter)
    return mir_eval.beat.information_gain(ann_times, est_times, bins=bins)

def compute_information_gain ( ann_inter , est_inter , est_file , bins ) : ann_times = utils . intervals_to_times ( ann_inter ) est_times = utils . intervals_to_times ( est_inter ) return mir_eval . beat . information_gain ( ann_times , est_times , bins = bins )

Computes the information gain of the est_file from the annotated intervals and the estimated intervals .


def process_track(file_struct, boundaries_id, labels_id, config,
                  annotator_id=0):
    """Processes a single track.

    Parameters
    ----------
    file_struct : object (FileStruct) or str
        File struct or full path of the audio file to be evaluated.
    boundaries_id : str
        Identifier of the boundaries algorithm.
    labels_id : str
        Identifier of the labels algorithm.
    config : dict
        Configuration of the algorithms to be evaluated.
    annotator_id : int
        Number identifiying the annotator.

    Returns
    -------
    one_res : dict
        Dictionary of the results (see function compute_results).
    """
    # Convert to file_struct if string is passed
    if isinstance(file_struct, six.string_types):
        file_struct = io.FileStruct(file_struct)

    est_file = file_struct.est_file
    ref_file = file_struct.ref_file

    # Sanity check
    assert os.path.basename(est_file)[:-4] == \
        os.path.basename(ref_file)[:-4], "File names are different %s --- %s" \
        % (os.path.basename(est_file)[:-4], os.path.basename(ref_file)[:-4])

    if not os.path.isfile(ref_file):
        raise NoReferencesError("Reference file %s does not exist. You must "
                                "have annotated references to run "
                                "evaluations." % ref_file)

    one_res = compute_gt_results(est_file, ref_file, boundaries_id, labels_id,
                                 config, annotator_id=annotator_id)

    return one_res


def get_results_file_name(boundaries_id, labels_id, config,
                          annotator_id):
    """Based on the config and the dataset, get the file name to store the
    results."""
    utils.ensure_dir(msaf.config.results_dir)
    file_name = os.path.join(msaf.config.results_dir, "results")
    file_name += "_boundsE%s_labelsE%s" % (boundaries_id, labels_id)
    file_name += "_annotatorE%d" % (annotator_id)
    sorted_keys = sorted(config.keys(), key=str.lower)
    for key in sorted_keys:
        file_name += "_%sE%s" % (key, str(config[key]).replace("/", "_"))

    # Check for max file length
    if len(file_name) > 255 - len(msaf.config.results_ext):
        file_name = file_name[:255 - len(msaf.config.results_ext)]

    return file_name + msaf.config.results_ext

def
get_results_file_name
(
boundaries_id
,
labels_id
,
config
,
annotator_id
)
:
utils
.
ensure_dir
(
msaf
.
config
.
results_dir
)
file_name
=
os
.
path
.
join
(
msaf
.
config
.
results_dir
,
"results"
)
file_name
+=
"_boundsE%s_labelsE%s"
%
(
boundaries_id
,
labels_id
)
file_name
+=
"_annotatorE%d"
%
(
annotator_id
)
sorted_keys
=
sorted
(
config
.
keys
(
)
,
key
=
str
.
lower
)
for
key
in
sorted_keys
:
file_name
+=
"_%sE%s"
%
(
key
,
str
(
config
[
key
]
)
.
replace
(
"/"
,
"_"
)
)
# Check for max file length
if
len
(
file_name
)
>
255
len
(
msaf
.
config
.
results_ext
)
:
file_name
=
file_name
[
:
255
len
(
msaf
.
config
.
results_ext
)
]
return
file_name
+
msaf
.
config
.
results_ext

Based on the config and the dataset get the file name to store the results .


def process(in_path, boundaries_id=msaf.config.default_bound_id,
            labels_id=msaf.config.default_label_id, annot_beats=False,
            framesync=False, feature="pcp", hier=False, save=False,
            out_file=None, n_jobs=4, annotator_id=0, config=None):
    """Main process to evaluate algorithms' results.

    Parameters
    ----------
    in_path : str
        Path to the dataset root folder.
    boundaries_id : str
        Boundaries algorithm identifier (e.g. siplca, cnmf)
    labels_id : str
        Labels algorithm identifier (e.g. siplca, cnmf)
    ds_name : str
        Name of the dataset to be evaluated (e.g. SALAMI). * stands for all.
    annot_beats : boolean
        Whether to use the annotated beats or not.
    framesync: str
        Whether to use framesync features or not (default: False -> beatsync)
    feature: str
        String representing the feature to be used (e.g. pcp, mfcc, tonnetz)
    hier : bool
        Whether to compute a hierarchical or flat segmentation.
    save: boolean
        Whether to save the results into the `out_file` csv file.
    out_file: str
        Path to the csv file to save the results (if `None` and `save = True`
        it will save the results in the default file name obtained by
        calling `get_results_file_name`).
    n_jobs: int
        Number of processes to run in parallel. Only available in collection
        mode.
    annotator_id : int
        Number identifiying the annotator.
    config: dict
        Dictionary containing custom configuration parameters for the
        algorithms.  If None, the default parameters are used.

    Return
    ------
    results : pd.DataFrame
        DataFrame containing the evaluations for each file.
    """

    # Set up configuration based on algorithms parameters
    if config is None:
        config = io.get_configuration(feature, annot_beats, framesync,
                                      boundaries_id, labels_id)

    # Hierarchical segmentation
    config["hier"] = hier

    # Remove actual features
    config.pop("features", None)

    # Get out file in case we want to save results
    if out_file is None:
        out_file = get_results_file_name(boundaries_id, labels_id, config,
                                         annotator_id)

    # If out_file already exists, read and return them
    if os.path.exists(out_file):
        logging.warning("Results already exists, reading from file %s" %
                        out_file)
        results = pd.read_csv(out_file)
        print_results(results)
        return results

    # Perform actual evaluations
    if os.path.isfile(in_path):
        # Single File mode
        evals = [process_track(in_path, boundaries_id, labels_id, config,
                               annotator_id=annotator_id)]
    else:
        # Collection mode
        # Get files
        file_structs = io.get_dataset_files(in_path)

        # Evaluate in parallel
        logging.info("Evaluating %d tracks..." % len(file_structs))
        evals = Parallel(n_jobs=n_jobs)(delayed(process_track)(
            file_struct, boundaries_id, labels_id, config,
            annotator_id=annotator_id) for file_struct in file_structs[:])

    # Aggregate evaluations in pandas format
    results = pd.DataFrame()
    for e in evals:
        if e != []:
            results = results.append(e, ignore_index=True)
    logging.info("%d tracks analyzed" % len(results))

    # Print results
    print_results(results)

    # Save all results
    if save:
        logging.info("Writing results in %s" % out_file)
        results.to_csv(out_file)

    return results

Main process to evaluate algorithms results .


def AddConfigVar(name, doc, configparam, root=config):
    """Add a new variable to msaf.config

    Parameters
    ----------
    name: str
        String of the form "[section0.[section1.[etc]]]option", containing the
        full name for this configuration variable.
    string: str
        What does this variable specify?
    configparam: `ConfigParam`
        An object for getting and setting this configuration parameter.
    root: object
        Used for recursive calls -- do not provide an argument for this
        parameter.
    """

    # This method also performs some of the work of initializing ConfigParam
    # instances

    if root is config:
        # only set the name in the first call, not the recursive ones
        configparam.fullname = name
    sections = name.split('.')
    if len(sections) > 1:
        # set up a subobject
        if not hasattr(root, sections[0]):
            # every internal node in the config tree is an instance of its own
            # unique class
            class SubObj(object):
                _i_am_a_config_class = True
            setattr(root.__class__, sections[0], SubObj())
        newroot = getattr(root, sections[0])
        if (not getattr(newroot, '_i_am_a_config_class', False) or
                isinstance(newroot, type)):
            raise TypeError(
                'Internal config nodes must be config class instances',
                newroot)
        return AddConfigVar('.'.join(sections[1:]), doc, configparam,
                            root=newroot)
    else:
        if hasattr(root, name):
            raise AttributeError('This name is already taken',
                                 configparam.fullname)
        configparam.doc = doc
        # Trigger a read of the value from config files and env vars
        # This allow to filter wrong value from the user.
        if not callable(configparam.default):
            configparam.__get__(root, type(root), delete_key=True)
        else:
            # We do not want to evaluate now the default value
            # when it is a callable.
            try:
                fetch_val_for_key(configparam.fullname)
                # The user provided a value, filter it now.
                configparam.__get__(root, type(root), delete_key=True)
            except KeyError:
                pass
        setattr(root.__class__, sections[0], configparam)
        _config_var_list.append(configparam)


def compute_all_features(file_struct, framesync):
    """Computes all features for the given file."""
    for feature_id in msaf.features_registry:
        logging.info("Computing %s for file %s" % (feature_id,
                                                   file_struct.audio_file))
        feats = Features.select_features(feature_id, file_struct, False, framesync)
        feats.features

def
compute_all_features
(
file_struct
,
framesync
)
:
for
feature_id
in
msaf
.
features_registry
:
logging
.
info
(
"Computing %s for file %s"
%
(
feature_id
,
file_struct
.
audio_file
)
)
feats
=
Features
.
select_features
(
feature_id
,
file_struct
,
False
,
framesync
)
feats
.
features

Computes all features for the given file .


def process(in_path, out_file, n_jobs, framesync):
    """Computes the features for the selected dataset or file."""
    if os.path.isfile(in_path):
        # Single file mode
        # Get (if they exitst) or compute features
        file_struct = msaf.io.FileStruct(in_path)
        file_struct.features_file = out_file
        compute_all_features(file_struct, framesync)
    else:
        # Collection mode
        file_structs = msaf.io.get_dataset_files(in_path)

        # Call in parallel
        return Parallel(n_jobs=n_jobs)(delayed(compute_all_features)(
            file_struct, framesync) for file_struct in file_structs)

Computes the features for the selected dataset or file .


def gaussian_cost(X):
    '''Return the average log-likelihood of data under a standard normal
    '''

    d, n = X.shape

    if n < 2:
        return 0

    sigma = np.var(X, axis=1, ddof=1)

    cost = -0.5 * d * n * np.log(2. * np.pi) - 0.5 * (n - 1.) * np.sum(sigma)
    return cost

def gaussian_cost ( X ) : d , n = X . shape if n < 2 : return 0 sigma = np . var ( X , axis = 1 , ddof = 1 ) cost = - 0.5 * d * n * np . log ( 2. * np . pi ) - 0.5 * ( n - 1. ) * np . sum ( sigma ) return cost

Return the average log - likelihood of data under a standard normal


def lognormalize(F, floor=0.1, min_db=-80):
    """Log-normalizes features such that each vector is between min_db to 0."""
    assert min_db < 0
    F = min_max_normalize(F, floor=floor)
    F = np.abs(min_db) * np.log10(F)  # Normalize from min_db to 0
    return F

def
lognormalize
(
F
,
floor
=
0.1
,
min_db
=
80
)
:
assert
min_db
<
0
F
=
min_max_normalize
(
F
,
floor
=
floor
)
F
=
np
.
abs
(
min_db
)
*
np
.
log10
(
F
)
# Normalize from min_db to 0
return
F

Log - normalizes features such that each vector is between min_db to 0 .


def min_max_normalize(F, floor=0.001):
    """Normalizes features such that each vector is between floor to 1."""
    F += -F.min() + floor
    F = F / F.max(axis=0)
    return F

def min_max_normalize ( F , floor = 0.001 ) : F += - F . min ( ) + floor F = F / F . max ( axis = 0 ) return F

Normalizes features such that each vector is between floor to 1 .


def normalize(X, norm_type, floor=0.0, min_db=-80):
    """Normalizes the given matrix of features.

    Parameters
    ----------
    X: np.array
        Each row represents a feature vector.
    norm_type: {"min_max", "log", np.inf, -np.inf, 0, float > 0, None}
        - `"min_max"`: Min/max scaling is performed
        - `"log"`: Logarithmic scaling is performed
        - `np.inf`: Maximum absolute value
        - `-np.inf`: Mininum absolute value
        - `0`: Number of non-zeros
        - float: Corresponding l_p norm.
        - None : No normalization is performed

    Returns
    -------
    norm_X: np.array
        Normalized `X` according the the input parameters.
    """
    if isinstance(norm_type, six.string_types):
        if norm_type == "min_max":
            return min_max_normalize(X, floor=floor)
        if norm_type == "log":
            return lognormalize(X, floor=floor, min_db=min_db)
    return librosa.util.normalize(X, norm=norm_type, axis=1)

def normalize ( X , norm_type , floor = 0.0 , min_db = - 80 ) : if isinstance ( norm_type , six . string_types ) : if norm_type == "min_max" : return min_max_normalize ( X , floor = floor ) if norm_type == "log" : return lognormalize ( X , floor = floor , min_db = min_db ) return librosa . util . normalize ( X , norm = norm_type , axis = 1 )

Normalizes the given matrix of features .


def get_time_frames(dur, anal):
    """Gets the time frames and puts them in a numpy array."""
    n_frames = get_num_frames(dur, anal)
    return np.linspace(0, dur, num=n_frames)

def get_time_frames ( dur , anal ) : n_frames = get_num_frames ( dur , anal ) return np . linspace ( 0 , dur , num = n_frames )

Gets the time frames and puts them in a numpy array .


def remove_empty_segments(times, labels):
    """Removes empty segments if needed."""
    assert len(times) - 1 == len(labels)
    inters = times_to_intervals(times)
    new_inters = []
    new_labels = []
    for inter, label in zip(inters, labels):
        if inter[0] < inter[1]:
            new_inters.append(inter)
            new_labels.append(label)
    return intervals_to_times(np.asarray(new_inters)), new_labels

def remove_empty_segments ( times , labels ) : assert len ( times ) - 1 == len ( labels ) inters = times_to_intervals ( times ) new_inters = [ ] new_labels = [ ] for inter , label in zip ( inters , labels ) : if inter [ 0 ] < inter [ 1 ] : new_inters . append ( inter ) new_labels . append ( label ) return intervals_to_times ( np . asarray ( new_inters ) ) , new_labels


def sonify_clicks(audio, clicks, out_file, fs, offset=0):
    """Sonifies the estimated times into the output file.

    Parameters
    ----------
    audio: np.array
        Audio samples of the input track.
    clicks: np.array
        Click positions in seconds.
    out_file: str
        Path to the output file.
    fs: int
        Sample rate.
    offset: float
        Offset of the clicks with respect to the audio.
    """
    # Generate clicks (this should be done by mir_eval, but its
    # latest release is not compatible with latest numpy)
    times = clicks + offset
    # 1 kHz tone, 100ms
    click = np.sin(2 * np.pi * np.arange(fs * .1) * 1000 / (1. * fs))
    # Exponential decay
    click *= np.exp(-np.arange(fs * .1) / (fs * .01))
    length = int(times.max() * fs + click.shape[0] + 1)
    audio_clicks = mir_eval.sonify.clicks(times, fs, length=length)

    # Create array to store the audio plus the clicks
    out_audio = np.zeros(max(len(audio), len(audio_clicks)))

    # Assign the audio and the clicks
    out_audio[:len(audio)] = audio
    out_audio[:len(audio_clicks)] += audio_clicks

    # Write to file
    scipy.io.wavfile.write(out_file, fs, out_audio)

Sonifies the estimated times into the output file .


def synchronize_labels(new_bound_idxs, old_bound_idxs, old_labels, N):
    """Synchronizes the labels from the old_bound_idxs to the new_bound_idxs.

    Parameters
    ----------
    new_bound_idxs: np.array
        New indeces to synchronize with.
    old_bound_idxs: np.array
        Old indeces, same shape as labels + 1.
    old_labels: np.array
        Labels associated to the old_bound_idxs.
    N: int
        Total number of frames.

    Returns
    -------
    new_labels: np.array
        New labels, synchronized to the new boundary indeces.
    """
    assert len(old_bound_idxs) - 1 == len(old_labels)

    # Construct unfolded labels array
    unfold_labels = np.zeros(N)
    for i, (bound_idx, label) in enumerate(
            zip(old_bound_idxs[:-1], old_labels)):
        unfold_labels[bound_idx:old_bound_idxs[i + 1]] = label

    # Constuct new labels
    new_labels = np.zeros(len(new_bound_idxs) - 1)
    for i, bound_idx in enumerate(new_bound_idxs[:-1]):
        new_labels[i] = np.median(
            unfold_labels[bound_idx:new_bound_idxs[i + 1]])

    return new_labels

def
synchronize_labels
(
new_bound_idxs
,
old_bound_idxs
,
old_labels
,
N
)
:
assert
len
(
old_bound_idxs
)
1
==
len
(
old_labels
)
# Construct unfolded labels array
unfold_labels
=
np
.
zeros
(
N
)
for
i
,
(
bound_idx
,
label
)
in
enumerate
(
zip
(
old_bound_idxs
[
:
1
]
,
old_labels
)
)
:
unfold_labels
[
bound_idx
:
old_bound_idxs
[
i
+
1
]
]
=
label
# Constuct new labels
new_labels
=
np
.
zeros
(
len
(
new_bound_idxs
)
1
)
for
i
,
bound_idx
in
enumerate
(
new_bound_idxs
[
:
1
]
)
:
new_labels
[
i
]
=
np
.
median
(
unfold_labels
[
bound_idx
:
new_bound_idxs
[
i
+
1
]
]
)
return
new_labels

Synchronizes the labels from the old_bound_idxs to the new_bound_idxs .


def process_segmentation_level(est_idxs, est_labels, N, frame_times, dur):
    """Processes a level of segmentation, and converts it into times.

    Parameters
    ----------
    est_idxs: np.array
        Estimated boundaries in frame indeces.
    est_labels: np.array
        Estimated labels.
    N: int
        Number of frames in the whole track.
    frame_times: np.array
        Time stamp for each frame.
    dur: float
        Duration of the audio track.

    Returns
    -------
    est_times: np.array
        Estimated segment boundaries in seconds.
    est_labels: np.array
        Estimated labels for each segment.
    """
    assert est_idxs[0] == 0 and est_idxs[-1] == N - 1
    assert len(est_idxs) - 1 == len(est_labels)

    # Add silences, if needed
    est_times = np.concatenate(([0], frame_times[est_idxs], [dur]))
    silence_label = np.max(est_labels) + 1
    est_labels = np.concatenate(([silence_label], est_labels, [silence_label]))

    # Remove empty segments if needed
    est_times, est_labels = remove_empty_segments(est_times, est_labels)

    # Make sure that the first and last times are 0 and duration, respectively
    assert np.allclose([est_times[0]], [0]) and \
        np.allclose([est_times[-1]], [dur])

    return est_times, est_labels

Processes a level of segmentation and converts it into times .


def align_end_hierarchies(hier1, hier2, thres=0.5):
    """Align the end of the hierarchies such that they end at the same exact
    second as long they have the same duration within a certain threshold.

    Parameters
    ----------
    hier1: list
        List containing hierarchical segment boundaries.
    hier2: list
        List containing hierarchical segment boundaries.
    thres: float > 0
        Threshold to decide whether two values are the same.
    """
    # Make sure we have correctly formatted hierarchies
    dur_h1 = hier1[0][-1]
    for hier in hier1:
        assert hier[-1] == dur_h1, "hier1 is not correctly " \
            "formatted {} {}".format(hier[-1], dur_h1)
    dur_h2 = hier2[0][-1]
    for hier in hier2:
        assert hier[-1] == dur_h2, "hier2 is not correctly formatted"

    # If durations are different, do nothing
    if abs(dur_h1 - dur_h2) > thres:
        return

    # Align h1 with h2
    for hier in hier1:
        hier[-1] = dur_h2

Align the end of the hierarchies such that they end at the same exact second as long they have the same duration within a certain threshold .


def _distance(self, idx):
        """ compute distances of a specific data point to all other samples"""

        if scipy.sparse.issparse(self.data):
            step = self.data.shape[1]
        else:
            step = 50000

        d = np.zeros((self.data.shape[1]))
        if idx == -1:
            # set vec to origin if idx=-1
            vec = np.zeros((self.data.shape[0], 1))
            if scipy.sparse.issparse(self.data):
                vec = scipy.sparse.csc_matrix(vec)
        else:
            vec = self.data[:, idx:idx+1]

        self._logger.info('compute distance to node ' + str(idx))

        # slice data into smaller chunks
        for idx_start in range(0, self.data.shape[1], step):
            if idx_start + step > self.data.shape[1]:
                idx_end = self.data.shape[1]
            else:
                idx_end = idx_start + step

            d[idx_start:idx_end] = self._distfunc(
                self.data[:,idx_start:idx_end], vec)
            self._logger.info('completed:' +
                str(idx_end/(self.data.shape[1]/100.0)) + "%")
        return d

def
_distance
(
self
,
idx
)
:
if
scipy
.
sparse
.
issparse
(
self
.
data
)
:
step
=
self
.
data
.
shape
[
1
]
else
:
step
=
50000
d
=
np
.
zeros
(
(
self
.
data
.
shape
[
1
]
)
)
if
idx
==
1
:
# set vec to origin if idx=-1
vec
=
np
.
zeros
(
(
self
.
data
.
shape
[
0
]
,
1
)
)
if
scipy
.
sparse
.
issparse
(
self
.
data
)
:
vec
=
scipy
.
sparse
.
csc_matrix
(
vec
)
else
:
vec
=
self
.
data
[
:
,
idx
:
idx
+
1
]
self
.
_logger
.
info
(
'compute distance to node '
+
str
(
idx
)
)
# slice data into smaller chunks
for
idx_start
in
range
(
0
,
self
.
data
.
shape
[
1
]
,
step
)
:
if
idx_start
+
step
>
self
.
data
.
shape
[
1
]
:
idx_end
=
self
.
data
.
shape
[
1
]
else
:
idx_end
=
idx_start
+
step
d
[
idx_start
:
idx_end
]
=
self
.
_distfunc
(
self
.
data
[
:
,
idx_start
:
idx_end
]
,
vec
)
self
.
_logger
.
info
(
'completed:'
+
str
(
idx_end
/
(
self
.
data
.
shape
[
1
]
/
100.0
)
)
+
"%"
)
return
d


def estimate_K_knee(self, th=.015, maxK=12):
        """Estimates the K using K-means and BIC, by sweeping various K and
            choosing the optimal BIC."""
        # Sweep K-means
        if self.X.shape[0] < maxK:
            maxK = self.X.shape[0]
        if maxK < 2:
            maxK = 2
        K = np.arange(1, maxK)
        bics = []
        for k in K:
            means, labels = self.run_kmeans(self.X, k)
            bic = self.compute_bic(self.X, means, labels, K=k,
                                   R=self.X.shape[0])
            bics.append(bic)
        diff_bics = np.diff(bics)
        finalK = K[-1]

        if len(bics) == 1:
            finalK = 2
        else:
            # Normalize
            bics = np.asarray(bics)
            bics -= bics.min()
            #bics /= bics.max()
            diff_bics -= diff_bics.min()
            #diff_bics /= diff_bics.max()

            #print bics, diff_bics

            # Find optimum K
            for i in range(len(K[:-1])):
                #if bics[i] > diff_bics[i]:
                if diff_bics[i] < th and K[i] != 1:
                    finalK = K[i]
                    break

        #print "Estimated K: ", finalK
        if self.plot:
            plt.subplot(2, 1, 1)
            plt.plot(K, bics, label="BIC")
            plt.plot(K[:-1], diff_bics, label="BIC diff")
            plt.legend(loc=2)
            plt.subplot(2, 1, 2)
            plt.scatter(self.X[:, 0], self.X[:, 1])
            plt.show()

        return finalK

def
estimate_K_knee
(
self
,
th
=
.015
,
maxK
=
12
)
:
# Sweep K-means
if
self
.
X
.
shape
[
0
]
<
maxK
:
maxK
=
self
.
X
.
shape
[
0
]
if
maxK
<
2
:
maxK
=
2
K
=
np
.
arange
(
1
,
maxK
)
bics
=
[
]
for
k
in
K
:
means
,
labels
=
self
.
run_kmeans
(
self
.
X
,
k
)
bic
=
self
.
compute_bic
(
self
.
X
,
means
,
labels
,
K
=
k
,
R
=
self
.
X
.
shape
[
0
]
)
bics
.
append
(
bic
)
diff_bics
=
np
.
diff
(
bics
)
finalK
=
K
[
1
]
if
len
(
bics
)
==
1
:
finalK
=
2
else
:
# Normalize
bics
=
np
.
asarray
(
bics
)
bics
-=
bics
.
min
(
)
#bics /= bics.max()
diff_bics
-=
diff_bics
.
min
(
)
#diff_bics /= diff_bics.max()
#print bics, diff_bics
# Find optimum K
for
i
in
range
(
len
(
K
[
:
1
]
)
)
:
#if bics[i] > diff_bics[i]:
if
diff_bics
[
i
]
<
th
and
K
[
i
]
!=
1
:
finalK
=
K
[
i
]
break
#print "Estimated K: ", finalK
if
self
.
plot
:
plt
.
subplot
(
2
,
1
,
1
)
plt
.
plot
(
K
,
bics
,
label
=
"BIC"
)
plt
.
plot
(
K
[
:
1
]
,
diff_bics
,
label
=
"BIC diff"
)
plt
.
legend
(
loc
=
2
)
plt
.
subplot
(
2
,
1
,
2
)
plt
.
scatter
(
self
.
X
[
:
,
0
]
,
self
.
X
[
:
,
1
]
)
plt
.
show
(
)
return
finalK

Estimates the K using K - means and BIC by sweeping various K and choosing the optimal BIC .


def get_clustered_data(self, X, labels, label_index):
        """Returns the data with a specific label_index, using the previously
         learned labels."""
        D = X[np.argwhere(labels == label_index)]
        return D.reshape((D.shape[0], D.shape[-1]))

def get_clustered_data ( self , X , labels , label_index ) : D = X [ np . argwhere ( labels == label_index ) ] return D . reshape ( ( D . shape [ 0 ] , D . shape [ - 1 ] ) )

Returns the data with a specific label_index using the previously learned labels .


def run_kmeans(self, X, K):
        """Runs k-means and returns the labels assigned to the data."""
        wX = vq.whiten(X)
        means, dist = vq.kmeans(wX, K, iter=100)
        labels, dist = vq.vq(wX, means)
        return means, labels

def run_kmeans ( self , X , K ) : wX = vq . whiten ( X ) means , dist = vq . kmeans ( wX , K , iter = 100 ) labels , dist = vq . vq ( wX , means ) return means , labels

Runs k - means and returns the labels assigned to the data .


def compute_bic(self, D, means, labels, K, R):
        """Computes the Bayesian Information Criterion."""
        D = vq.whiten(D)
        Rn = D.shape[0]
        M = D.shape[1]

        if R == K:
            return 1

        # Maximum likelihood estimate (MLE)
        mle_var = 0
        for k in range(len(means)):
            X = D[np.argwhere(labels == k)]
            X = X.reshape((X.shape[0], X.shape[-1]))
            for x in X:
                mle_var += distance.euclidean(x, means[k])
                #print x, means[k], mle_var
        mle_var /= float(R - K)

        # Log-likelihood of the data
        l_D = - Rn/2. * np.log(2*np.pi) - (Rn * M)/2. * np.log(mle_var) - \
            (Rn - K) / 2. + Rn * np.log(Rn) - Rn * np.log(R)

        # Params of BIC
        p = (K-1) + M * K + mle_var

        #print "BIC:", l_D, p, R, K

        # Return the bic
        return l_D - p / 2. * np.log(R)

def
compute_bic
(
self
,
D
,
means
,
labels
,
K
,
R
)
:
D
=
vq
.
whiten
(
D
)
Rn
=
D
.
shape
[
0
]
M
=
D
.
shape
[
1
]
if
R
==
K
:
return
1
# Maximum likelihood estimate (MLE)
mle_var
=
0
for
k
in
range
(
len
(
means
)
)
:
X
=
D
[
np
.
argwhere
(
labels
==
k
)
]
X
=
X
.
reshape
(
(
X
.
shape
[
0
]
,
X
.
shape
[
1
]
)
)
for
x
in
X
:
mle_var
+=
distance
.
euclidean
(
x
,
means
[
k
]
)
#print x, means[k], mle_var
mle_var
/=
float
(
R
K
)
# Log-likelihood of the data
l_D
=
Rn
/
2.
*
np
.
log
(
2
*
np
.
pi
)
(
Rn
*
M
)
/
2.
*
np
.
log
(
mle_var
)
(
Rn
K
)
/
2.
+
Rn
*
np
.
log
(
Rn
)
Rn
*
np
.
log
(
R
)
# Params of BIC
p
=
(
K
1
)
+
M
*
K
+
mle_var
#print "BIC:", l_D, p, R, K
# Return the bic
return
l_D
p
/
2.
*
np
.
log
(
R
)

Computes the Bayesian Information Criterion .


def magnitude(X):
    """Magnitude of a complex matrix."""
    r = np.real(X)
    i = np.imag(X)
    return np.sqrt(r * r + i * i);

def magnitude ( X ) : r = np . real ( X ) i = np . imag ( X ) return np . sqrt ( r * r + i * i )


def json_to_bounds(segments_json):
    """Extracts the boundaries from a json file and puts them into
        an np array."""
    f = open(segments_json)
    segments = json.load(f)["segments"]
    bounds = []
    for segment in segments:
        bounds.append(segment["start"])
    bounds.append(bounds[-1] + segments[-1]["duration"]) # Add last boundary
    f.close()
    return np.asarray(bounds)

def
json_to_bounds
(
segments_json
)
:
f
=
open
(
segments_json
)
segments
=
json
.
load
(
f
)
[
"segments"
]
bounds
=
[
]
for
segment
in
segments
:
bounds
.
append
(
segment
[
"start"
]
)
bounds
.
append
(
bounds
[
1
]
+
segments
[
1
]
[
"duration"
]
)
# Add last boundary
f
.
close
(
)
return
np
.
asarray
(
bounds
)

Extracts the boundaries from a json file and puts them into an np array .


def json_bounds_to_bounds(bounds_json):
    """Extracts the boundaries from a bounds json file and puts them into
        an np array."""
    f = open(bounds_json)
    segments = json.load(f)["bounds"]
    bounds = []
    for segment in segments:
        bounds.append(segment["start"])
    f.close()
    return np.asarray(bounds)

def json_bounds_to_bounds ( bounds_json ) : f = open ( bounds_json ) segments = json . load ( f ) [ "bounds" ] bounds = [ ] for segment in segments : bounds . append ( segment [ "start" ] ) f . close ( ) return np . asarray ( bounds )

Extracts the boundaries from a bounds json file and puts them into an np array .


def json_to_labels(segments_json):
    """Extracts the labels from a json file and puts them into
        an np array."""
    f = open(segments_json)
    segments = json.load(f)["segments"]
    labels = []
    str_labels = []
    for segment in segments:
        if not segment["label"] in str_labels:
            str_labels.append(segment["label"])
            labels.append(len(str_labels)-1)
        else:
            label_idx = np.where(np.asarray(str_labels) == segment["label"])[0][0]
            labels.append(label_idx)
    f.close()
    return np.asarray(labels)

def json_to_labels ( segments_json ) : f = open ( segments_json ) segments = json . load ( f ) [ "segments" ] labels = [ ] str_labels = [ ] for segment in segments : if not segment [ "label" ] in str_labels : str_labels . append ( segment [ "label" ] ) labels . append ( len ( str_labels ) - 1 ) else : label_idx = np . where ( np . asarray ( str_labels ) == segment [ "label" ] ) [ 0 ] [ 0 ] labels . append ( label_idx ) f . close ( ) return np . asarray ( labels )

Extracts the labels from a json file and puts them into an np array .


def json_to_beats(beats_json_file):
    """Extracts the beats from the beats_json_file and puts them into
        an np array."""
    f = open(beats_json_file, "r")
    beats_json = json.load(f)
    beats = []
    for beat in beats_json["beats"]:
        beats.append(beat["start"])
    f.close()
    return np.asarray(beats)

def json_to_beats ( beats_json_file ) : f = open ( beats_json_file , "r" ) beats_json = json . load ( f ) beats = [ ] for beat in beats_json [ "beats" ] : beats . append ( beat [ "start" ] ) f . close ( ) return np . asarray ( beats )

Extracts the beats from the beats_json_file and puts them into an np array .


def compute_ffmc2d(X):
    """Computes the 2D-Fourier Magnitude Coefficients."""
    # 2d-fft
    fft2 = scipy.fftpack.fft2(X)

    # Magnitude
    fft2m = magnitude(fft2)

    # FFTshift and flatten
    fftshift = scipy.fftpack.fftshift(fft2m).flatten()

    #cmap = plt.cm.get_cmap('hot')
    #plt.imshow(np.log1p(scipy.fftpack.fftshift(fft2m)).T, interpolation="nearest",
    #    aspect="auto", cmap=cmap)
    #plt.show()

    # Take out redundant components
    return fftshift[:fftshift.shape[0] // 2 + 1]

Computes the 2D - Fourier Magnitude Coefficients .


def compute_labels(X, rank, R, bound_idxs, niter=300):
    """Computes the labels using the bounds."""

    try:
        F, G = cnmf(X, rank, niter=niter, hull=False)
    except:
        return [1]

    label_frames = filter_activation_matrix(G.T, R)
    label_frames = np.asarray(label_frames, dtype=int)

    #labels = [label_frames[0]]
    labels = []
    bound_inters = zip(bound_idxs[:-1], bound_idxs[1:])
    for bound_inter in bound_inters:
        if bound_inter[1] - bound_inter[0] <= 0:
            labels.append(np.max(label_frames) + 1)
        else:
            labels.append(most_frequent(
                label_frames[bound_inter[0]: bound_inter[1]]))
        #print bound_inter, labels[-1]
    #labels.append(label_frames[-1])

    return labels

def
compute_labels
(
X
,
rank
,
R
,
bound_idxs
,
niter
=
300
)
:
try
:
F
,
G
=
cnmf
(
X
,
rank
,
niter
=
niter
,
hull
=
False
)
except
:
return
[
1
]
label_frames
=
filter_activation_matrix
(
G
.
T
,
R
)
label_frames
=
np
.
asarray
(
label_frames
,
dtype
=
int
)
#labels = [label_frames[0]]
labels
=
[
]
bound_inters
=
zip
(
bound_idxs
[
:
1
]
,
bound_idxs
[
1
:
]
)
for
bound_inter
in
bound_inters
:
if
bound_inter
[
1
]
bound_inter
[
0
]
<=
0
:
labels
.
append
(
np
.
max
(
label_frames
)
+
1
)
else
:
labels
.
append
(
most_frequent
(
label_frames
[
bound_inter
[
0
]
:
bound_inter
[
1
]
]
)
)
#print bound_inter, labels[-1]
#labels.append(label_frames[-1])
return
labels


def filter_activation_matrix(G, R):
    """Filters the activation matrix G, and returns a flattened copy."""

    #import pylab as plt
    #plt.imshow(G, interpolation="nearest", aspect="auto")
    #plt.show()

    idx = np.argmax(G, axis=1)
    max_idx = np.arange(G.shape[0])
    max_idx = (max_idx, idx.flatten())
    G[:, :] = 0
    G[max_idx] = idx + 1

    # TODO: Order matters?
    G = np.sum(G, axis=1)
    G = median_filter(G[:, np.newaxis], R)

    return G.flatten()

Filters the activation matrix G and returns a flattened copy .


def get_boundaries_module(boundaries_id):
    """Obtains the boundaries module given a boundary algorithm identificator.

    Parameters
    ----------
    boundaries_id: str
        Boundary algorithm identificator (e.g., foote, sf).

    Returns
    -------
    module: object
        Object containing the selected boundary module.
        None for "ground truth".
    """
    if boundaries_id == "gt":
        return None
    try:
        module = eval(algorithms.__name__ + "." + boundaries_id)
    except AttributeError:
        raise RuntimeError("Algorithm %s can not be found in msaf!" %
                           boundaries_id)
    if not module.is_boundary_type:
        raise RuntimeError("Algorithm %s can not identify boundaries!" %
                           boundaries_id)
    return module

Obtains the boundaries module given a boundary algorithm identificator .


def get_labels_module(labels_id):
    """Obtains the label module given a label algorithm identificator.

    Parameters
    ----------
    labels_id: str
        Label algorithm identificator (e.g., fmc2d, cnmf).

    Returns
    -------
    module: object
        Object containing the selected label module.
        None for not computing the labeling part of music segmentation.
    """
    if labels_id is None:
        return None
    try:
        module = eval(algorithms.__name__ + "." + labels_id)
    except AttributeError:
        raise RuntimeError("Algorithm %s can not be found in msaf!" %
                           labels_id)
    if not module.is_label_type:
        raise RuntimeError("Algorithm %s can not label segments!" %
                           labels_id)
    return module

Obtains the label module given a label algorithm identificator .


def run_hierarchical(audio_file, bounds_module, labels_module, frame_times,
                     config, annotator_id=0):
    """Runs hierarchical algorithms with the specified identifiers on the
    audio_file. See run_algorithm for more information.
    """
    # Sanity check
    if bounds_module is None:
        raise NoHierBoundaryError("A boundary algorithm is needed when using "
                                  "hierarchical segmentation.")

    # Get features to make code nicer
    features = config["features"].features

    # Compute boundaries
    S = bounds_module.Segmenter(audio_file, **config)
    est_idxs, est_labels = S.processHierarchical()

    # Compute labels if needed
    if labels_module is not None and \
            bounds_module.__name__ != labels_module.__name__:
        # Compute labels for each level in the hierarchy
        flat_config = deepcopy(config)
        flat_config["hier"] = False
        for i, level_idxs in enumerate(est_idxs):
            S = labels_module.Segmenter(audio_file,
                                        in_bound_idxs=level_idxs,
                                        **flat_config)
            est_labels[i] = S.processFlat()[1]

    # Make sure the first and last boundaries are included for each
    # level in the hierarchy
    est_times = []
    cleaned_est_labels = []
    for level in range(len(est_idxs)):
        est_level_times, est_level_labels = \
            utils.process_segmentation_level(
                est_idxs[level], est_labels[level], features.shape[0],
                frame_times, config["features"].dur)
        est_times.append(est_level_times)
        cleaned_est_labels.append(est_level_labels)
    est_labels = cleaned_est_labels

    return est_times, est_labels

Runs hierarchical algorithms with the specified identifiers on the audio_file . See run_algorithm for more information .


def run_flat(file_struct, bounds_module, labels_module, frame_times, config,
             annotator_id):
    """Runs the flat algorithms with the specified identifiers on the
    audio_file. See run_algorithm for more information.
    """
    # Get features to make code nicer
    features = config["features"].features

    # Segment using the specified boundaries and labels
    # Case when boundaries and labels algorithms are the same
    if bounds_module is not None and labels_module is not None and \
            bounds_module.__name__ == labels_module.__name__:
        S = bounds_module.Segmenter(file_struct, **config)
        est_idxs, est_labels = S.processFlat()
    # Different boundary and label algorithms
    else:
        # Identify segment boundaries
        if bounds_module is not None:
            S = bounds_module.Segmenter(file_struct, in_labels=[], **config)
            est_idxs, est_labels = S.processFlat()
        else:
            try:
                # Ground-truth boundaries
                est_times, est_labels = io.read_references(
                    file_struct.audio_file, annotator_id=annotator_id)
                est_idxs = io.align_times(est_times, frame_times)
                if est_idxs[0] != 0:
                    est_idxs = np.concatenate(([0], est_idxs))
            except IOError:
                logging.warning("No references found for file: %s" %
                                file_struct.audio_file)
                return [], []

        # Label segments
        if labels_module is not None:
            if len(est_idxs) == 2:
                est_labels = np.array([0])
            else:
                S = labels_module.Segmenter(file_struct,
                                            in_bound_idxs=est_idxs,
                                            **config)
                est_labels = S.processFlat()[1]

    # Make sure the first and last boundaries are included
    est_times, est_labels = utils.process_segmentation_level(
        est_idxs, est_labels, features.shape[0], frame_times,
        config["features"].dur)

    return est_times, est_labels

Runs the flat algorithms with the specified identifiers on the audio_file . See run_algorithm for more information .


def run_algorithms(file_struct, boundaries_id, labels_id, config,
                   annotator_id=0):
    """Runs the algorithms with the specified identifiers on the audio_file.

    Parameters
    ----------
    file_struct: `msaf.io.FileStruct`
        Object with the file paths.
    boundaries_id: str
        Identifier of the boundaries algorithm to use ("gt" for ground truth).
    labels_id: str
        Identifier of the labels algorithm to use (None for not labeling).
    config: dict
        Dictionary containing the custom parameters of the algorithms to use.
    annotator_id: int
        Annotator identificator in the ground truth.

    Returns
    -------
    est_times: np.array or list
        List of estimated times for the segment boundaries.
        If `list`, it will be a list of np.arrays, sorted by segmentation
        layer.
    est_labels: np.array or list
        List of all the labels associated segments.
        If `list`, it will be a list of np.arrays, sorted by segmentation
        layer.
    """
    # Check that there are enough audio frames
    if config["features"].features.shape[0] <= msaf.config.minimum_frames:
        logging.warning("Audio file too short, or too many few beats "
                        "estimated. Returning empty estimations.")
        return np.asarray([0, config["features"].dur]), \
            np.asarray([0], dtype=int)

    # Get the corresponding modules
    bounds_module = get_boundaries_module(boundaries_id)
    labels_module = get_labels_module(labels_id)

    # Get the correct frame times
    frame_times = config["features"].frame_times

    # Segment audio based on type of segmentation
    run_fun = run_hierarchical if config["hier"] else run_flat
    est_times, est_labels = run_fun(file_struct, bounds_module, labels_module,
                                    frame_times, config, annotator_id)

    return est_times, est_labels

Runs the algorithms with the specified identifiers on the audio_file .


def process_track(file_struct, boundaries_id, labels_id, config,
                  annotator_id=0):
    """Prepares the parameters, runs the algorithms, and saves results.

    Parameters
    ----------
    file_struct: `msaf.io.FileStruct`
        FileStruct containing the paths of the input files (audio file,
        features file, reference file, output estimation file).
    boundaries_id: str
        Identifier of the boundaries algorithm to use ("gt" for ground truth).
    labels_id: str
        Identifier of the labels algorithm to use (None for not labeling).
    config: dict
        Dictionary containing the custom parameters of the algorithms to use.
    annotator_id: int
        Annotator identificator in the ground truth.

    Returns
    -------
    est_times: np.array
        List of estimated times for the segment boundaries.
    est_labels: np.array
        List of all the labels associated segments.
    """
    logging.info("Segmenting %s" % file_struct.audio_file)

    # Get features
    config["features"] = Features.select_features(
        config["feature"], file_struct, config["annot_beats"],
        config["framesync"])

    # Get estimations
    est_times, est_labels = run_algorithms(file_struct,
                                           boundaries_id, labels_id, config,
                                           annotator_id=annotator_id)

    # Save
    logging.info("Writing results in: %s" % file_struct.est_file)
    io.save_estimations(file_struct, est_times, est_labels,
                        boundaries_id, labels_id, **config)

    return est_times, est_labels

def
process_track
(
file_struct
,
boundaries_id
,
labels_id
,
config
,
annotator_id
=
0
)
:
logging
.
info
(
"Segmenting %s"
%
file_struct
.
audio_file
)
# Get features
config
[
"features"
]
=
Features
.
select_features
(
config
[
"feature"
]
,
file_struct
,
config
[
"annot_beats"
]
,
config
[
"framesync"
]
)
# Get estimations
est_times
,
est_labels
=
run_algorithms
(
file_struct
,
boundaries_id
,
labels_id
,
config
,
annotator_id
=
annotator_id
)
# Save
logging
.
info
(
"Writing results in: %s"
%
file_struct
.
est_file
)
io
.
save_estimations
(
file_struct
,
est_times
,
est_labels
,
boundaries_id
,
labels_id
,
*
*
config
)
return
est_times
,
est_labels

Prepares the parameters runs the algorithms and saves results .