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 ) : audio_files = [ ] for ext in ds_config . audio_exts : audio_files += glob . glob ( os . path . join ( in_path , ds_config . audio_dir , "*" + ext ) ) 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 ) ) file_structs = [ ] for audio_file in audio_files : file_structs . append ( FileStruct ( audio_file ) ) 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" ] for exclude in exclude_levels : if exclude in namespaces : namespaces . remove ( exclude ) 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

def align_segmentation ( beat_times , song ) : try : segment_times , segment_labels = msaf . io . read_references ( song ) except : return None , None , None segment_times = np . asarray ( segment_times ) segment_intervals = msaf . utils . times_to_intervals ( segment_times ) beat_intervals = np . asarray ( zip ( beat_times [ : - 1 ] , beat_times [ 1 : ] ) ) beat_segment_ids = librosa . util . match_intervals ( beat_intervals , segment_intervals ) segment_beats = [ ] segment_times_out = [ ] segment_labels_out = [ ] 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 ] ) segment_beats = list ( segment_beats ) 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 estimate_beats ( self ) : if self . _audio_percussive is None : self . _audio_harmonic , self . _audio_percussive = self . compute_HPSS ( ) tempo , frames = librosa . beat . beat_track ( y = self . _audio_percussive , sr = self . sr , hop_length = self . hop_length ) times = librosa . frames_to_time ( frames , sr = self . sr , hop_length = self . hop_length ) 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 compute_beat_sync_features ( self , beat_frames , beat_times , pad ) : if beat_frames is None : return None , None beatsync_feats = librosa . util . utils . sync ( self . _framesync_features . T , beat_frames , pad = pad ) . T 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
write_features
(
self
)
:
out_json
=
collections
.
OrderedDict
(
)
try
:
self
.
read_features
(
)
except
(
WrongFeaturesFormatError
,
FeaturesNotFound
,
NoFeaturesFileError
)
:
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"
)
}
}
)
out_json
[
"globals"
]
=
{
"dur"
:
self
.
dur
,
"sample_rate"
:
self
.
sr
,
"hop_length"
:
self
.
hop_length
,
"audio_file"
:
self
.
file_struct
.
audio_file
}
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
:
out_json
[
"ann_beats"
]
=
self
.
_ann_beats_times
.
tolist
(
)
out_json
[
"ann_beatsync_times"
]
=
self
.
_ann_beatsync_times
.
tolist
(
)
except
FeatureParamsError
:
with
open
(
self
.
file_struct
.
features_file
)
as
f
:
out_json
=
json
.
load
(
f
)
finally
:
out_json
[
self
.
get_id
(
)
]
=
{
}
out_json
[
self
.
get_id
(
)
]
[
"params"
]
=
{
}
for
param_name
in
self
.
get_param_names
(
)
:
value
=
getattr
(
self
,
param_name
)
if
hasattr
(
value
,
'__call__'
)
:
value
=
value
.
__name__
else
:
value
=
str
(
value
)
out_json
[
self
.
get_id
(
)
]
[
"params"
]
[
param_name
]
=
value
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
:
out_json
[
self
.
get_id
(
)
]
[
"ann_beatsync"
]
=
self
.
_ann_beatsync_features
.
tolist
(
)
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

def frame_times ( self ) : frame_times = None 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 ( "/" , "_" ) ) 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)

def process ( in_path , out_file , n_jobs , framesync ) : if os . path . isfile ( in_path ) : file_struct = msaf . io . FileStruct ( in_path ) file_struct . features_file = out_file compute_all_features ( file_struct , framesync ) else : file_structs = msaf . io . get_dataset_files ( in_path ) 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 ) 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)

def sonify_clicks ( audio , clicks , out_file , fs , offset = 0 ) : times = clicks + offset click = np . sin ( 2 * np . pi * np . arange ( fs * .1 ) * 1000 / ( 1. * fs ) ) 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 ) out_audio = np . zeros ( max ( len ( audio ) , len ( audio_clicks ) ) ) out_audio [ : len ( audio ) ] = audio out_audio [ : len ( audio_clicks ) ] += audio_clicks 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 ) 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 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

def process_segmentation_level ( est_idxs , est_labels , N , frame_times , dur ) : assert est_idxs [ 0 ] == 0 and est_idxs [ - 1 ] == N - 1 assert len ( est_idxs ) - 1 == len ( est_labels ) 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 ] ) ) est_times , est_labels = remove_empty_segments ( est_times , est_labels ) 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

def
align_end_hierarchies
(
hier1
,
hier2
,
thres
=
0.5
)
:
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
abs
(
dur_h1
dur_h2
)
>
thres
:
return
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
:
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
)
)
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
)
:
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
:
bics
=
np
.
asarray
(
bics
)
bics
-=
bics
.
min
(
)
diff_bics
-=
diff_bics
.
min
(
)
for
i
in
range
(
len
(
K
[
:
1
]
)
)
:
if
diff_bics
[
i
]
<
th
and
K
[
i
]
!=
1
:
finalK
=
K
[
i
]
break
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 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 ] ) mle_var /= float ( R - K ) 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 ) p = ( K - 1 ) + M * K + mle_var 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" ] ) 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]

def compute_ffmc2d ( X ) : fft2 = scipy . fftpack . fft2 ( X ) fft2m = magnitude ( fft2 ) fftshift = scipy . fftpack . fftshift ( fft2m ) . flatten ( ) 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 = [ ] 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 ] ] ) ) 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()

def filter_activation_matrix ( G , R ) : 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 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

def
run_flat
(
file_struct
,
bounds_module
,
labels_module
,
frame_times
,
config
,
annotator_id
)
:
features
=
config
[
"features"
]
.
features
if
bounds_module
is
not
and
labels_module
is
not
and
bounds_module
.
__name__
==
labels_module
.
__name__
:
S
=
bounds_module
.
Segmenter
(
file_struct
,
**
config
)
est_idxs
,
est_labels
=
S
.
processFlat
(
)
else
:
if
bounds_module
is
not
:
S
=
bounds_module
.
Segmenter
(
file_struct
,
in_labels
=
[
]
,
**
config
)
est_idxs
,
est_labels
=
S
.
processFlat
(
)
else
:
try
:
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
[
]
,
[
]
if
labels_module
is
not
:
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
]
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
)
config
[
"features"
]
=
Features
.
select_features
(
config
[
"feature"
]
,
file_struct
,
config
[
"annot_beats"
]
,
config
[
"framesync"
]
)
est_times
,
est_labels
=
run_algorithms
(
file_struct
,
boundaries_id
,
labels_id
,
config
,
annotator_id
=
annotator_id
)
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 .


def process(in_path, annot_beats=False, feature="pcp", framesync=False,
            boundaries_id=msaf.config.default_bound_id,
            labels_id=msaf.config.default_label_id, hier=False,
            sonify_bounds=False, plot=False, n_jobs=4, annotator_id=0,
            config=None, out_bounds="out_bounds.wav", out_sr=22050):
    """Main process to segment a file or a collection of files.

    Parameters
    ----------
    in_path: str
        Input path. If a directory, MSAF will function in collection mode.
        If audio file, MSAF will be in single file mode.
    annot_beats: bool
        Whether to use annotated beats or not.
    feature: str
        String representing the feature to be used (e.g. pcp, mfcc, tonnetz)
    framesync: str
        Whether to use framesync features or not (default: False -> beatsync)
    boundaries_id: str
        Identifier of the boundaries algorithm (use "gt" for groundtruth)
    labels_id: str
        Identifier of the labels algorithm (use None to not compute labels)
    hier : bool
        Whether to compute a hierarchical or flat segmentation.
    sonify_bounds: bool
        Whether to write an output audio file with the annotated boundaries
        or not (only available in Single File Mode).
    plot: bool
        Whether to plot the boundaries and labels against the ground truth.
    n_jobs: int
        Number of processes to run in parallel. Only available in collection
        mode.
    annotator_id: int
        Annotator identificator in the ground truth.
    config: dict
        Dictionary containing custom configuration parameters for the
        algorithms.  If None, the default parameters are used.
    out_bounds: str
        Path to the output for the sonified boundaries (only in single file
        mode, when sonify_bounds is True.
    out_sr : int
        Sampling rate for the sonified bounds.

    Returns
    -------
    results : list
        List containing tuples of (est_times, est_labels) of estimated
        boundary times and estimated labels.
        If labels_id is None, est_labels will be a list of -1.
    """
    # Seed random to reproduce results
    np.random.seed(123)

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

    # Save multi-segment (hierarchical) configuration
    config["hier"] = hier
    if not os.path.exists(in_path):
        raise NoAudioFileError("File or directory does not exists, %s" %
                               in_path)
    if os.path.isfile(in_path):
        # Single file mode
        # Get (if they exitst) or compute features
        file_struct = msaf.io.FileStruct(in_path)

        # Use temporary file in single mode
        file_struct.features_file = msaf.config.features_tmp_file

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

        # And run the algorithms
        est_times, est_labels = run_algorithms(file_struct, boundaries_id,
                                               labels_id, config,
                                               annotator_id=annotator_id)

        if sonify_bounds:
            logging.info("Sonifying boundaries in %s..." % out_bounds)
            audio_hq, sr = librosa.load(in_path, sr=out_sr)
            utils.sonify_clicks(audio_hq, est_times, out_bounds, out_sr)

        if plot:
            plotting.plot_one_track(file_struct, est_times, est_labels,
                                    boundaries_id, labels_id)

        # TODO: Only save if needed
        # Save estimations
        msaf.utils.ensure_dir(os.path.dirname(file_struct.est_file))
        io.save_estimations(file_struct, est_times, est_labels,
                            boundaries_id, labels_id, **config)

        return est_times, est_labels
    else:
        # Collection mode
        file_structs = io.get_dataset_files(in_path)

        return 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[:])

def
process
(
in_path
,
annot_beats
=
False
,
feature
=
"pcp"
,
framesync
=
False
,
boundaries_id
=
msaf
.
config
.
default_bound_id
,
labels_id
=
msaf
.
config
.
default_label_id
,
hier
=
False
,
sonify_bounds
=
False
,
plot
=
False
,
n_jobs
=
4
,
annotator_id
=
0
,
config
=
,
out_bounds
=
"out_bounds.wav"
,
out_sr
=
22050
)
:
np
.
random
.
seed
(
123
)
if
config
is
:
config
=
io
.
get_configuration
(
feature
,
annot_beats
,
framesync
,
boundaries_id
,
labels_id
)
config
[
"features"
]
=
config
[
"hier"
]
=
hier
if
not
os
.
path
.
exists
(
in_path
)
:
raise
NoAudioFileError
(
"File or directory does not exists, %s"
%
in_path
)
if
os
.
path
.
isfile
(
in_path
)
:
file_struct
=
msaf
.
io
.
FileStruct
(
in_path
)
file_struct
.
features_file
=
msaf
.
config
.
features_tmp_file
config
[
"features"
]
=
Features
.
select_features
(
feature
,
file_struct
,
annot_beats
,
framesync
)
est_times
,
est_labels
=
run_algorithms
(
file_struct
,
boundaries_id
,
labels_id
,
config
,
annotator_id
=
annotator_id
)
if
sonify_bounds
:
logging
.
info
(
"Sonifying boundaries in %s..."
%
out_bounds
)
audio_hq
,
sr
=
librosa
.
load
(
in_path
,
sr
=
out_sr
)
utils
.
sonify_clicks
(
audio_hq
,
est_times
,
out_bounds
,
out_sr
)
if
plot
:
plotting
.
plot_one_track
(
file_struct
,
est_times
,
est_labels
,
boundaries_id
,
labels_id
)
msaf
.
utils
.
ensure_dir
(
os
.
path
.
dirname
(
file_struct
.
est_file
)
)
io
.
save_estimations
(
file_struct
,
est_times
,
est_labels
,
boundaries_id
,
labels_id
,
**
config
)
return
est_times
,
est_labels
else
:
file_structs
=
io
.
get_dataset_files
(
in_path
)
return
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
[
:
]
)

Main process to segment a file or a collection of files .


def update_w(self):
        """ alternating least squares step, update W under the convexity
        constraint """
        def update_single_w(i):
            """ compute single W[:,i] """
            # optimize beta     using qp solver from cvxopt
            FB = base.matrix(np.float64(np.dot(-self.data.T, W_hat[:,i])))
            be = solvers.qp(HB, FB, INQa, INQb, EQa, EQb)
            self.beta[i,:] = np.array(be['x']).reshape((1, self._num_samples))

        # float64 required for cvxopt
        HB = base.matrix(np.float64(np.dot(self.data[:,:].T, self.data[:,:])))
        EQb = base.matrix(1.0, (1, 1))
        W_hat = np.dot(self.data, pinv(self.H))
        INQa = base.matrix(-np.eye(self._num_samples))
        INQb = base.matrix(0.0, (self._num_samples, 1))
        EQa = base.matrix(1.0, (1, self._num_samples))

        for i in range(self._num_bases):
            update_single_w(i)

        self.W = np.dot(self.beta, self.data.T).T

def update_w ( self ) : def update_single_w ( i ) : FB = base . matrix ( np . float64 ( np . dot ( - self . data . T , W_hat [ : , i ] ) ) ) be = solvers . qp ( HB , FB , INQa , INQb , EQa , EQb ) self . beta [ i , : ] = np . array ( be [ 'x' ] ) . reshape ( ( 1 , self . _num_samples ) ) HB = base . matrix ( np . float64 ( np . dot ( self . data [ : , : ] . T , self . data [ : , : ] ) ) ) EQb = base . matrix ( 1.0 , ( 1 , 1 ) ) W_hat = np . dot ( self . data , pinv ( self . H ) ) INQa = base . matrix ( - np . eye ( self . _num_samples ) ) INQb = base . matrix ( 0.0 , ( self . _num_samples , 1 ) ) EQa = base . matrix ( 1.0 , ( 1 , self . _num_samples ) ) for i in range ( self . _num_bases ) : update_single_w ( i ) self . W = np . dot ( self . beta , self . data . T ) . T

alternating least squares step update W under the convexity constraint


def main():
    '''
    Main Entry point for translator and argument parser
    '''
    args      = command_line()
    translate = partial(translator, args.source, args.dest,
                        version=' '.join([__version__, __build__]))

    return source(spool(set_task(translate, translit=args.translit)), args.text)

def
main
(
)
:
args
=
command_line
(
)
translate
=
partial
(
translator
,
args
.
source
,
args
.
dest
,
version
=
' '
.
join
(
[
__version__
,
__build__
]
)
)
return
source
(
spool
(
set_task
(
translate
,
translit
=
args
.
translit
)
)
,
args
.
text
)


def coroutine(func):
    """
    Initializes coroutine essentially priming it to the yield statement.
    Used as a decorator over functions that generate coroutines.

    .. code-block:: python

        # Basic coroutine producer/consumer pattern
        from translate import coroutine

        @coroutine
        def coroutine_foo(bar):
            try:
                while True:
                    baz = (yield)
                    bar.send(baz)

            except GeneratorExit:
                bar.close()

    :param func: Unprimed Generator
    :type func: Function

    :return: Initialized Coroutine
    :rtype: Function
    """

    @wraps(func)
    def initialization(*args, **kwargs):

        start = func(*args, **kwargs)
        next(start)

        return start

    return initialization

def coroutine ( func ) : @ wraps ( func ) def initialization ( * args , ** kwargs ) : start = func ( * args , ** kwargs ) next ( start ) return start return initialization

Initializes coroutine essentially priming it to the yield statement . Used as a decorator over functions that generate coroutines .


def accumulator(init, update):
    """
    Generic accumulator function.

    .. code-block:: python

        # Simplest Form
        >>> a = 'this' + ' '
        >>> b = 'that'
        >>> c = functools.reduce(accumulator, a, b)
        >>> c
        'this that'

        # The type of the initial value determines output type.
        >>> a = 5
        >>> b = Hello
        >>> c = functools.reduce(accumulator, a, b)
        >>> c
        10

    :param init:  Initial Value
    :param update: Value to accumulate

    :return: Combined Values
    """
    return (
        init + len(update)
            if isinstance(init, int) else
        init + update
    )

def accumulator ( init , update ) : return ( init + len ( update ) if isinstance ( init , int ) else init + update )