]> git.parisson.com Git - timeside.git/commitdiff
Add Vamp Plugin analyzer based on vamp-simple-host
authorThomas Fillon <thomas@parisson.com>
Fri, 11 Oct 2013 19:13:00 +0000 (21:13 +0200)
committerThomas Fillon <thomas@parisson.com>
Fri, 11 Oct 2013 19:16:30 +0000 (21:16 +0200)
- VampSimpleHost analyzer requires vamp-plugin-sdk  installation
- add exemple to use it in test/api" (requires vamp-examples installation)

doc/source/examples/AnalyzerResult.rst [new file with mode: 0644]
tests/api/exempleCMMR_vamp.py [new file with mode: 0644]
timeside/analyzer/__init__.py
timeside/analyzer/core.py
timeside/analyzer/vamp_plugin.py [new file with mode: 0644]
timeside/decoder/core.py

diff --git a/doc/source/examples/AnalyzerResult.rst b/doc/source/examples/AnalyzerResult.rst
new file mode 100644 (file)
index 0000000..7470d2d
--- /dev/null
@@ -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 (file)
index 0000000..0f0746a
--- /dev/null
@@ -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
index e97a0950b9ea987950f22f2ad63f4fa93d212fb8..6f13282ae0be2b4a0d168b36d27a435e017744ab 100644 (file)
@@ -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
index 71f9f3798c9c49875b31cae6c7f699bcded99292..4e9779379f21c6920707165955face1828148687 100644 (file)
@@ -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 (file)
index 0000000..33390a6
--- /dev/null
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2013 Paul Brossier <piem@piem.org>
+
+# 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 <http://www.gnu.org/licenses/>.
+
+# Author: Paul Brossier <piem@piem.org>
+
+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
index d0939d8d78a94c8712093776b6d68ddfc2ad62f7..c812dd8697b16e9fc787f205c8ed160cb8ead039 100644 (file)
@@ -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()