From: Thomas Fillon Date: Fri, 11 Oct 2013 19:13:00 +0000 (+0200) Subject: Add Vamp Plugin analyzer based on vamp-simple-host X-Git-Tag: 0.5.0~34 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=a749a136ffa6fa4170617d586c5b42b960b1c0c8;p=timeside.git Add Vamp Plugin analyzer based on vamp-simple-host - VampSimpleHost analyzer requires vamp-plugin-sdk installation - add exemple to use it in test/api" (requires vamp-examples installation) --- diff --git a/doc/source/examples/AnalyzerResult.rst b/doc/source/examples/AnalyzerResult.rst new file mode 100644 index 0000000..7470d2d --- /dev/null +++ b/doc/source/examples/AnalyzerResult.rst @@ -0,0 +1,117 @@ +.. This file is part of TimeSide + @author: Thomas Fillon + +============================= + New analyzer Result example +============================= + +Example of use of the new analyzerResult structure + +Usage : AnalyzerResult(data_mode=None, time_mode=None) + +See : :class:`timeside.analyzer.core.AnalyzerResult` + +Default +======= + +Create a new analyzer result without arguments + + >>> import timeside.analyzer.core as coreA + >>> res = coreA.AnalyzerResult() + +This default result has all the metadata and dataObject attribute + + >>> res.keys() + ['data_mode', 'time_mode', 'id_metadata', 'data', 'audio_metadata', 'frame_metadata', 'label_metadata', 'parameters'] + + >>> for key,value in res.items(): + ... print '%s : %s' % (key, value) + ... + data_mode : None + time_mode : None + id_metadata : {'description': '', 'author': '', 'version': '', 'date': '', 'id': '', 'unit': '', 'name': ''} + dataObject : {'duration': array([], dtype=float64), 'time': array([], dtype=float64), 'value': None, 'label': array([], dtype=int64)} + audio_metadata : {'duration': None, 'start': 0, 'channelsManagement': '', 'uri': '', 'channels': None} + frame_metadata : {'blocksize': None, 'samplerate': None, 'stepsize': None} + label_metadata : {'label_type': 'mono', 'description': None, 'label': None} + parameters : {} + + +Specification of time_mode +========================= +Four different time_mode can be specified : + +- 'framewise' : Data are returned on a frame basis (i.e. with specified blocksize, stepsize and framerate) +- 'global' : A global data value is return for the entire audio item +- 'segment' : Data are returned on a segmnet basis (i.e. with specified start time and duration) +- 'event' : Data are returned on a segment basis (i.e. with specified start time) + + +Framewise +--------- + +>>> res = coreA.AnalyzerResult(time_mode='framewise') +>>> res.keys() +['data_mode', 'time_mode', 'id_metadata', 'data', 'audio_metadata', 'frame_metadata', 'label_metadata', 'parameters'] + +Global +------ + +No frame metadata information is needed for these modes. +The 'frame_metadata' key/attribute is deleted. + +>>> res = coreA.AnalyzerResult(time_mode='global') +>>> res.keys() +['data_mode', 'time_mode', 'id_metadata', 'data', 'audio_metadata', 'label_metadata', 'parameters'] +>>> res.data +DataObject(value=None, label=array([], dtype=int64)) + +Segment +------- + +>>> res = coreA.AnalyzerResult(time_mode='segment') +>>> res.keys() +['data_mode', 'time_mode', 'id_metadata', 'data', 'audio_metadata', 'label_metadata', 'parameters'] +>>> res.data +DataObject(value=None, label=array([], dtype=int64), time=array([], dtype=float64), duration=array([], dtype=float64)) + +Event +----- + +>>> res = coreA.AnalyzerResult(time_mode='event') +>>> res.keys() +['data_mode', 'time_mode', 'id_metadata', 'data', 'audio_metadata', 'label_metadata', 'parameters'] +>>> res.data +DataObject(value=None, label=array([], dtype=int64), time=array([], dtype=float64)) + +Specification of data_mode +========================= +Two different data_mode can be specified : + +- 'value' : Data are returned as numpy Array of arbitrary type +- 'label' : Data are returned as label indexes (specified by the label_metadata key) + +Value +----- +The label_metadata key is deleted. + +>>> res = coreA.AnalyzerResult(data_mode='value') +>>> res.keys() +['data_mode', 'time_mode', 'id_metadata', 'data', 'audio_metadata', 'frame_metadata', 'parameters'] + +In the dataObject key, the 'value' key is kept and the 'label' key is deleted. + +>>> res.data +DataObject(value=None, time=array([], dtype=float64), duration=array([], dtype=float64)) + +Label +----- +>>> res = coreA.AnalyzerResult(data_mode='label') +>>> res.keys() +['data_mode', 'time_mode', 'id_metadata', 'data', 'audio_metadata', 'frame_metadata', 'label_metadata', 'parameters'] + +In the dataObject key, the 'label' key is kept and the 'value' key is deleted. + + +>>> res.data +DataObject(label=array([], dtype=int64), time=array([], dtype=float64), duration=array([], dtype=float64)) diff --git a/tests/api/exempleCMMR_vamp.py b/tests/api/exempleCMMR_vamp.py new file mode 100644 index 0000000..0f0746a --- /dev/null +++ b/tests/api/exempleCMMR_vamp.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Oct 11 13:22:37 2013 + +@author: thomas +""" + +from __future__ import division +import timeside.decoder +import timeside.encoder +import timeside.grapher +import timeside.analyzer +import matplotlib.pyplot as plt +import numpy as np + +wavFile = 'TimeSide/tests/samples/sweep.mp3' +wavFile = '/home/thomas/data/CNRSMH_E_1985_001_001_001_04.wav' +# normal +d = timeside.decoder.FileDecoder(wavFile, start=10, duration=15) + +specgram = timeside.analyzer.Spectrogram() +waveform = timeside.analyzer.Waveform() + +# Get available Vamp plugins list +from timeside.analyzer.vamp_plugin import VampSimpleHost +plugins_list = VampSimpleHost.get_plugins_list() + +# Display avalaible plugins +print 'index \t soname \t \t identifier \t output ' +print '------ \t \t ---------- \t ------ ' +for index, line in zip(xrange(len(plugins_list)),plugins_list): + print '%d : %s \t %s \t %s' % (index,line[0],line[1],line[2]) + +# Let's choose #7 +my_plugin = plugins_list[7] +print my_plugin + +# +# Vamp plugin Analyzer +vamp = timeside.analyzer.VampSimpleHost(my_plugin) + +# +myPipe = (d | vamp | specgram | waveform).run() + +# Get spectrogram result and plot the spectrogram +spec_res = specgram.results['spectrogram_analyzer'] +N = spec_res.parameters['FFT_SIZE'] +max_freq = (N // 2 + 1) / N * spec_res.frame_metadata.samplerate + + + +# Get the vamp plugin result and plot it +vamp.results.keys() + +res_vamp = vamp.results['vamp_simple_host.percussiononsets.detectionfunction'] + +plt.figure(1) + +plt.subplot(2,1,1) +plt.plot(res_vamp.time, res_vamp.data) +plt.xlabel('time in s') +plt.grid +plt.title(res_vamp.name) + +plt.subplot(2,1,2) +plt.imshow(20 * np.log10(spec_res.data.T), + origin='lower', + extent=[spec_res.time[0], spec_res.time[-1], 0, + max_freq], + aspect='auto') + +data = res_vamp.data - res_vamp.data.mean() +plt.plot(res_vamp.time, abs(data / data.max() * max_freq)) + + +plt.xlabel('time in s') +plt.show() \ No newline at end of file diff --git a/timeside/analyzer/__init__.py b/timeside/analyzer/__init__.py index e97a095..6f13282 100644 --- a/timeside/analyzer/__init__.py +++ b/timeside/analyzer/__init__.py @@ -9,4 +9,5 @@ from aubio_melenergy import * from aubio_specdesc import * from yaafe import * # TF : add Yaafe analyzer from spectrogram import Spectrogram -from waveform import Waveform \ No newline at end of file +from waveform import Waveform +from vamp_plugin import VampSimpleHost \ No newline at end of file diff --git a/timeside/analyzer/core.py b/timeside/analyzer/core.py index 71f9f37..4e97793 100644 --- a/timeside/analyzer/core.py +++ b/timeside/analyzer/core.py @@ -600,7 +600,7 @@ class AnalyzerResult(MetadataObject): if self.time_mode == 'global': return self.audio_metadata.duration elif self.time_mode == 'framewise': - return (self.frame_metadata.blockwise / + return (self.frame_metadata.blocksize / self.frame_metadata.samplerate * numpy.ones(len(self))) elif self.time_mode == 'event': diff --git a/timeside/analyzer/vamp_plugin.py b/timeside/analyzer/vamp_plugin.py new file mode 100644 index 0000000..33390a6 --- /dev/null +++ b/timeside/analyzer/vamp_plugin.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2013 Paul Brossier + +# This file is part of TimeSide. + +# TimeSide is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. + +# TimeSide is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with TimeSide. If not, see . + +# Author: Paul Brossier + +from timeside.core import implements, interfacedoc +from timeside.analyzer.core import Analyzer +from timeside.api import IAnalyzer + +import subprocess +import numpy as np + + +class VampSimpleHost(Analyzer): + implements(IAnalyzer) + + def __init__(self, plugin): + self.plugin = ':'.join(plugin) + + @interfacedoc + def setup(self, channels=None, samplerate=None, + blocksize=None, totalframes=None): + super(VampSimpleHost, self).setup( + channels, samplerate, blocksize, totalframes) + + @staticmethod + @interfacedoc + def id(): + return "vamp_simple_host" + + @staticmethod + @interfacedoc + def name(): + return "Vamp Plugins host" + + @staticmethod + @interfacedoc + def unit(): + return "" + + def process(self, frames, eod=False): + pass + return frames, eod + + def release(self): + #plugin = 'vamp-example-plugins:amplitudefollower:amplitude' + + wavfile = self.mediainfo()['uri'].split('file://')[-1] + + (blocksize, stepsize, values) = self.vamp_plugin(self.plugin, wavfile) + + self.result_blocksize = blocksize + self.result_stepsize = stepsize + self.result_samplerate = self.mediainfo()['samplerate'] + + plugin_res = self.new_result(data_mode='value', time_mode='framewise') + + # Fix strat, duration issues if audio is a segment + if self.mediainfo()['is_segment']: + start_index = np.floor(self.mediainfo()['start'] * + self.result_samplerate / + self.result_stepsize) + new_start = start_index * self.result_stepsize + + stop_index = np.ceil((self.mediainfo()['start'] + + self.mediainfo()['duration']) * + self.result_samplerate / + self.result_stepsize) + + fixed_start = (start_index * self.result_stepsize / + self.result_samplerate) + fixed_duration = ((stop_index - start_index) * self.result_stepsize / + self.result_samplerate) + + plugin_res.audio_metadata.start = fixed_start + plugin_res.audio_metadata.duration = fixed_duration + + values = values[start_index:stop_index + 1] + + plugin_res.id_metadata.id += '.' + '.'.join(self.plugin.split(':')[1:]) + plugin_res.id_metadata.name += ' ' + \ + ' '.join(self.plugin.split(':')[1:]) + plugin_res.data_object.value = values + + self._results.add(plugin_res) + + @staticmethod + def vamp_plugin(plugin, wavfile): + + args = [plugin, wavfile] + + stdout = VampSimpleHost.SimpleHostProcess(args) # run vamp-simple-host + + stderr = stdout[0:8] # stderr containing file and process information + res = stdout[8:] # stdout containg the feature data + + # Parse stderr to get blocksize and stepsize + blocksize_info = stderr[4] + + import re + # Match agianst pattern 'Using block size = %d, step size = %d' + m = re.match( + 'Using block size = (\d+), step size = (\d+)', blocksize_info) + + blocksize = int(m.groups()[0]) + stepsize = int(m.groups()[1]) + + # Get the results + values = np.asfarray([line.split(': ')[1] for line in res]) + # TODO int support ? + + return (blocksize, stepsize, values) + + @staticmethod + def get_plugins_list(): + arg = ['--list-outputs'] + stdout = VampSimpleHost.SimpleHostProcess(arg) + + return [line.split(':')[1:] for line in stdout] + + @staticmethod + def SimpleHostProcess(argslist): + """Call vamp-simple-host""" + + vamp_host = 'vamp-simple-host' + command = [vamp_host] + command.extend(argslist) + # try ? + stdout = subprocess.check_output( + command, stderr=subprocess.STDOUT).splitlines() + + return stdout diff --git a/timeside/decoder/core.py b/timeside/decoder/core.py index d0939d8..c812dd8 100644 --- a/timeside/decoder/core.py +++ b/timeside/decoder/core.py @@ -328,7 +328,8 @@ class FileDecoder(Processor): return dict(uri=self.uri, duration=self.uri_duration, start=self.uri_start, - is_segment=self.is_segment) + is_segment=self.is_segment, + samplerate=self.input_samplerate) def __del__(self): self.release()