#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# CREATED:2016-02-16 15:40:04 by Brian McFee <brian.mcfee@nyu.edu>
r"""
Namespace conversion
--------------------
.. autosummary::
:toctree: generated
convert
"""
import numpy as np
from copy import deepcopy
from collections import defaultdict
from .exceptions import NamespaceError
# The structure that handles all conversion mappings
__CONVERSION__ = defaultdict(defaultdict)
__all__ = ["convert", "can_convert"]
def _conversion(target, source):
"""Decorate a function to register namespace conversions.
Examples
--------
>>> @conversion('tag_open', 'tag_.*')
... def tag_to_open(annotation):
... annotation.namespace = 'tag_open'
... return annotation
"""
def register(func):
"""Register func as mapping source to target"""
__CONVERSION__[target][source] = func
return func
return register
[docs]
def convert(annotation, target_namespace):
"""Convert a given annotation to the target namespace.
Parameters
----------
annotation : jams.Annotation
An annotation object
target_namespace : str
The target namespace
Returns
-------
mapped_annotation : jams.Annotation
if `annotation` already belongs to `target_namespace`, then
it is returned directly.
otherwise, `annotation` is copied and automatically converted
to the target namespace.
Raises
------
SchemaError
if the input annotation fails to validate
NamespaceError
if no conversion is possible
Examples
--------
Convert frequency measurements in Hz to MIDI
>>> ann_midi = jams.convert(ann_hz, 'note_midi')
And back to Hz
>>> ann_hz2 = jams.convert(ann_midi, 'note_hz')
"""
# First, validate the input. If this fails, we can't auto-convert.
annotation.validate(strict=True)
# If we're already in the target namespace, do nothing
if annotation.namespace == target_namespace:
return annotation
if target_namespace in __CONVERSION__:
# Otherwise, make a copy to mangle
annotation = deepcopy(annotation)
# Look for a way to map this namespace to the target
for source in __CONVERSION__[target_namespace]:
if annotation.search(namespace=source):
return __CONVERSION__[target_namespace][source](annotation)
# No conversion possible
raise NamespaceError(
"Unable to convert annotation from namespace="
'"{0}" to "{1}"'.format(annotation.namespace, target_namespace)
)
def can_convert(annotation, target_namespace):
"""Test if an annotation can be mapped to a target namespace
Parameters
----------
annotation : jams.Annotation
An annotation object
target_namespace : str
The target namespace
Returns
-------
True
if `annotation` can be automatically converted to
`target_namespace`
False
otherwise
"""
# If we're already in the target namespace, do nothing
if annotation.namespace == target_namespace:
return True
if target_namespace in __CONVERSION__:
# Look for a way to map this namespace to the target
for source in __CONVERSION__[target_namespace]:
if annotation.search(namespace=source):
return True
return False
@_conversion("pitch_contour", "pitch_hz")
def pitch_hz_to_contour(annotation):
"""Convert a pitch_hz annotation to a contour"""
annotation.namespace = "pitch_contour"
data = annotation.pop_data()
for obs in data:
annotation.append(
time=obs.time,
duration=obs.duration,
confidence=obs.confidence,
value=dict(index=0, frequency=np.abs(obs.value), voiced=obs.value > 0),
)
return annotation
@_conversion("pitch_contour", "pitch_midi")
def pitch_midi_to_contour(annotation):
"""Convert a pitch_hz annotation to a contour"""
annotation = pitch_midi_to_hz(annotation)
return pitch_hz_to_contour(annotation)
@_conversion("note_hz", "note_midi")
def note_midi_to_hz(annotation):
"""Convert a pitch_midi annotation to pitch_hz"""
annotation.namespace = "note_hz"
data = annotation.pop_data()
for obs in data:
annotation.append(
time=obs.time,
duration=obs.duration,
confidence=obs.confidence,
value=440 * (2.0 ** ((obs.value - 69.0) / 12.0)),
)
return annotation
@_conversion("note_midi", "note_hz")
def note_hz_to_midi(annotation):
"""Convert a pitch_hz annotation to pitch_midi"""
annotation.namespace = "note_midi"
data = annotation.pop_data()
for obs in data:
annotation.append(
time=obs.time,
duration=obs.duration,
confidence=obs.confidence,
value=12 * (np.log2(obs.value) - np.log2(440.0)) + 69,
)
return annotation
@_conversion("pitch_hz", "pitch_midi")
def pitch_midi_to_hz(annotation):
"""Convert a pitch_midi annotation to pitch_hz"""
annotation.namespace = "pitch_hz"
data = annotation.pop_data()
for obs in data:
annotation.append(
time=obs.time,
duration=obs.duration,
confidence=obs.confidence,
value=440 * (2.0 ** ((obs.value - 69.0) / 12.0)),
)
return annotation
@_conversion("pitch_midi", "pitch_hz")
def pitch_hz_to_midi(annotation):
"""Convert a pitch_hz annotation to pitch_midi"""
annotation.namespace = "pitch_midi"
data = annotation.pop_data()
for obs in data:
annotation.append(
time=obs.time,
duration=obs.duration,
confidence=obs.confidence,
value=12 * (np.log2(obs.value) - np.log2(440.0)) + 69,
)
return annotation
@_conversion("segment_open", "segment_.*")
def segment_to_open(annotation):
"""Convert any segmentation to open label space"""
annotation.namespace = "segment_open"
return annotation
@_conversion("tag_open", "tag_.*")
def tag_to_open(annotation):
"""Convert any tag annotation to open label space"""
annotation.namespace = "tag_open"
return annotation
@_conversion("tag_open", "scaper")
def scaper_to_tag(annotation):
"""Convert scaper annotations to tag_open"""
annotation.namespace = "tag_open"
data = annotation.pop_data()
for obs in data:
annotation.append(
time=obs.time,
duration=obs.duration,
confidence=obs.confidence,
value=obs.value["label"],
)
return annotation
@_conversion("beat", "beat_position")
def beat_position(annotation):
"""Convert beat_position to beat"""
annotation.namespace = "beat"
data = annotation.pop_data()
for obs in data:
annotation.append(
time=obs.time,
duration=obs.duration,
confidence=obs.confidence,
value=obs.value["position"],
)
return annotation
@_conversion("chord", "chord_harte")
def chordh_to_chord(annotation):
"""Convert Harte annotation to chord"""
annotation.namespace = "chord"
return annotation