Skip to content

Commit

Permalink
Merge pull request #26 from bmcfee/inverse-transform
Browse files Browse the repository at this point in the history
Inverse task transformers
  • Loading branch information
bmcfee committed Feb 20, 2017
2 parents 40b5435 + 6efb457 commit ceae0a2
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 26 deletions.
24 changes: 5 additions & 19 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ addons:
packages:
- ffmpeg


notifications:
email: false

Expand All @@ -19,28 +18,15 @@ python:

cache:
directories:
- $HOME/miniconda

env:
global:
# Directory where tests are run from
- CONDA_DEPS="pip nose numpy scipy scikit-learn"
- $HOME/env

before_install:
- export MINICONDA=$HOME/miniconda
- export PATH="$MINICONDA/bin:$PATH"
- hash -r
# Install conda only if necessary
- command -v conda >/dev/null || { wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh;
bash miniconda.sh -b -f -p $MINICONDA; }
- conda config --set always_yes yes
- conda update conda
- conda info -a
- conda install python=$TRAVIS_PYTHON_VERSION $CONDA_DEPS
- bash .travis_dependencies.sh
- export PATH="$HOME/env/miniconda$TRAVIS_PYTHON_VERSION/bin:$PATH";
- hash -r
- source activate test-environment

install:
- pip install python-coveralls pytest-faulthandler
- pip install git+https://github.com/marl/jams
- pip install -e .[docs,tests]

script:
Expand Down
44 changes: 44 additions & 0 deletions .travis_dependencies.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

#!/bin/sh

ENV_NAME="test-environment"
set -e

conda_create ()
{

hash -r
conda config --set always_yes yes --set changeps1 no
conda update -q conda
conda config --add channels pypi
conda info -a
deps='pip numpy scipy scikit-learn'

conda create -q -n $ENV_NAME "python=$TRAVIS_PYTHON_VERSION" $deps
conda update --all
}

src="$HOME/env/miniconda$TRAVIS_PYTHON_VERSION"
if [ ! -d "$src" ]; then
mkdir -p $HOME/env
pushd $HOME/env

# Download miniconda packages
wget http://repo.continuum.io/miniconda/Miniconda-3.16.0-Linux-x86_64.sh -O miniconda.sh;

# Install both environments
bash miniconda.sh -b -p $src

export PATH="$src/bin:$PATH"
conda_create

source activate $ENV_NAME

conda install -c conda-forge librosa
pip install python-coveralls pytest-faulthandler

source deactivate
popd
else
echo "Using cached dependencies"
fi
67 changes: 66 additions & 1 deletion pumpp/task/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
'''The base class for task transformer objects'''

import numpy as np
from librosa import time_to_frames
from librosa import time_to_frames, frames_to_time
import jams

from ..base import Scope
Expand Down Expand Up @@ -202,3 +202,68 @@ def encode_intervals(self, duration, intervals, values, dtype=np.bool):
target[interval[0]:interval[1]] += column

return target

def decode_events(self, encoded):
'''Decode labeled events into (time, value) pairs
Parameters
----------
encoded : np.ndarray, shape=(n_frames, m)
Frame-level annotation encodings as produced by ``encode_events``.
Returns
-------
[(time, value)] : iterable of tuples
where `time` is the event time and `value` is an
np.ndarray, shape=(m,) of the encoded value at that time
'''
times = frames_to_time(np.arange(encoded.shape[0]),
sr=self.sr,
hop_length=self.hop_length)

return zip(times, encoded)

def decode_intervals(self, encoded, duration=None):
'''Decode labeled intervals into (start, end, value) triples
Parameters
----------
encoded : np.ndarray, shape=(n_frames, m)
Frame-level annotation encodings as produced by
``encode_intervals``
duration : None or float > 0
The max duration of the annotation (in seconds)
Must be greater than the length of encoded array.
Returns
-------
[(start, end, value)] : iterable of tuples
where `start` and `end` are the interval boundaries (in seconds)
and `value` is an np.ndarray, shape=(m,) of the encoded value
for this interval.
'''
if duration is None:
# 1+ is fair here, because encode_intervals already pads
duration = 1 + encoded.shape[0]
else:
duration = 1 + time_to_frames(duration,
sr=self.sr,
hop_length=self.hop_length)

# [0, duration] inclusive
times = frames_to_time(np.arange(duration+1),
sr=self.sr,
hop_length=self.hop_length)

# Find the change-points of the rows
idx = np.unique(np.append(np.where(np.max(encoded[1:] != encoded[:-1],
axis=-1)),
encoded.shape[0]))
delta = np.diff(np.append(-1, idx))

# Starting positions can be integrated from changes
position = np.cumsum(np.append(0, delta))

return [(times[p], times[p + d], encoded[p])
for (p, d) in zip(position, delta)]
7 changes: 7 additions & 0 deletions pumpp/task/chord.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ def transform_annotation(self, ann, duration):
'root': _pad_nochord(target_root),
'bass': _pad_nochord(target_bass)}

def inverse(self, pitch, root, bass, duration=None):

raise NotImplementedError('Chord cannot be inverted')


class SimpleChordTransformer(ChordTransformer):
'''Simplified chord transformations. Only pitch class activity is encoded.
Expand Down Expand Up @@ -209,3 +213,6 @@ def transform_annotation(self, ann, duration):
data.pop('root', None)
data.pop('bass', None)
return data

def inverse(self, *args, **kwargs):
raise NotImplementedError('SimpleChord cannot be inverted')
27 changes: 27 additions & 0 deletions pumpp/task/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import numpy as np

import jams

from .base import BaseTaskTransformer

__all__ = ['BeatTransformer']
Expand Down Expand Up @@ -84,3 +86,28 @@ def transform_annotation(self, ann, duration):
return {'beat': target_beat,
'downbeat': target_downbeat,
'mask_downbeat': mask_downbeat}

def inverse(self, encoded, downbeat=None, duration=None):
'''Inverse transformation for beats and optional downbeats'''

ann = jams.Annotation(namespace=self.namespace, duration=duration)

beat_times = [t for t, _ in self.decode_events(encoded) if _]
if downbeat is not None:
downbeat_times = set([t for t, _ in self.decode_events(downbeat)
if _])
pickup_beats = len([t for t in beat_times
if t < min(downbeat_times)])
else:
downbeat_times = set()
pickup_beats = 0

value = - pickup_beats - 1
for beat in beat_times:
if beat in downbeat_times:
value = 1
else:
value += 1
ann.append(time=beat, duration=0, value=value)

return ann
15 changes: 14 additions & 1 deletion pumpp/task/regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

import numpy as np

import jams

from .base import BaseTaskTransformer
from ..exceptions import *
from ..exceptions import DataError

__all__ = ['VectorTransformer']

Expand Down Expand Up @@ -87,3 +89,14 @@ def transform_annotation(self, ann, duration):
.format(len(vector), self.dimension))

return {'vector': vector}

def inverse(self, vector, duration=None):
'''Inverse vector transformer'''

ann = jams.Annotation(namespace=self.namespace, duration=duration)

if duration is None:
duration = 0
ann.append(time=0, duration=duration, value=vector)

return ann
25 changes: 24 additions & 1 deletion pumpp/task/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def __init__(self, name, namespace, labels=None, sr=22050, hop_length=512):
self.encoder = MultiLabelBinarizer()
self.encoder.fit([labels])
self._classes = set(self.encoder.classes_)

self.register('tags', [None, len(self._classes)], np.bool)

def empty(self, duration):
Expand Down Expand Up @@ -100,6 +100,19 @@ def transform_annotation(self, ann, duration):

return {'tags': target}

def inverse(self, encoded, duration=None):
'''Inverse transformation'''

ann = jams.Annotation(namespace=self.namespace, duration=duration)
for start, end, value in self.decode_intervals(encoded,
duration=duration):
value_dec = self.encoder.inverse_transform(np.atleast_2d(value))[0]

for vd in value_dec:
ann.append(time=start, duration=end-start, value=vd)

return ann


class StaticLabelTransformer(BaseTaskTransformer):
'''Static label transformer.
Expand Down Expand Up @@ -165,3 +178,13 @@ def transform_annotation(self, ann, duration):
target = np.zeros(len(self._classes), dtype=np.bool)

return {'tags': target}

def inverse(self, encoded, duration=None):
'''Inverse static tag transformation'''

ann = jams.Annotation(namespace=self.namespace, duration=duration)

for vd in self.encoder.inverse_transform(np.atleast_2d(encoded))[0]:
ann.append(time=0, duration=duration, value=vd)

return ann
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[pytest]
[tool:pytest]
addopts = -v --cov-report term-missing --cov pumpp
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@
keywords='audio music learning',
license='ISC',
install_requires=['six',
'librosa>=0.4.3',
'jams>=0.2.1',
'librosa>=0.5.0',
'jams>=0.2.2',
'scikit-learn>=0.17',
'mir_eval>=0.2'],
'mir_eval>=0.4'],
extras_require={
'docs': ['numpydoc'],
'tests': ['pytest', 'pytest-cov']
Expand Down

0 comments on commit ceae0a2

Please sign in to comment.