From 4cda4d7a5a0636e42cf39e761b2344e8c18f8c3f Mon Sep 17 00:00:00 2001 From: Thomas Fillon Date: Thu, 31 Oct 2013 17:49:01 +0100 Subject: [PATCH] Add a parents attribute to Processor. A Processor can access its parents results through the pipe in which it runs --- timeside/analyzer/__init__.py | 1 + timeside/analyzer/aubio_melenergy.py | 3 +- timeside/analyzer/aubio_mfcc.py | 3 +- timeside/analyzer/aubio_pitch.py | 3 +- timeside/analyzer/aubio_specdesc.py | 3 +- timeside/analyzer/aubio_temporal.py | 9 +-- timeside/analyzer/core.py | 11 ++-- timeside/analyzer/dc.py | 2 +- timeside/analyzer/level.py | 4 +- timeside/analyzer/odf.py | 99 ++++++++++++++++++++++++++++ timeside/analyzer/spectrogram.py | 15 +++-- timeside/analyzer/vamp_plugin.py | 4 +- timeside/analyzer/waveform.py | 2 +- timeside/analyzer/yaafe.py | 4 +- timeside/api.py | 9 ++- timeside/component.py | 2 +- timeside/core.py | 53 +++++++++------ timeside/decoder/core.py | 1 + timeside/grapher/core.py | 1 + 19 files changed, 184 insertions(+), 45 deletions(-) create mode 100644 timeside/analyzer/odf.py diff --git a/timeside/analyzer/__init__.py b/timeside/analyzer/__init__.py index 663ce64..4c293f1 100644 --- a/timeside/analyzer/__init__.py +++ b/timeside/analyzer/__init__.py @@ -13,3 +13,4 @@ from waveform import Waveform from vamp_plugin import VampSimpleHost from irit_speech_entropy import IRITSpeechEntropy from irit_speech_4hz import IRITSpeech4Hz +from odf import OnsetDetectionFunction diff --git a/timeside/analyzer/aubio_melenergy.py b/timeside/analyzer/aubio_melenergy.py index b353079..e862934 100644 --- a/timeside/analyzer/aubio_melenergy.py +++ b/timeside/analyzer/aubio_melenergy.py @@ -32,6 +32,7 @@ class AubioMelEnergy(Analyzer): implements(IAnalyzer) def __init__(self): + super(AubioMelEnergy, self).__init__() self.input_blocksize = 1024 self.input_stepsize = self.input_blocksize / 4 @@ -77,4 +78,4 @@ class AubioMelEnergy(Analyzer): melenergy.parameters = dict(n_filters=self.n_filters, n_coeffs=self.n_coeffs) melenergy.data_object.value = self.melenergy_results - self._results.add(melenergy) + self.pipe.results.add(melenergy) diff --git a/timeside/analyzer/aubio_mfcc.py b/timeside/analyzer/aubio_mfcc.py index 3697b81..2fa8c23 100644 --- a/timeside/analyzer/aubio_mfcc.py +++ b/timeside/analyzer/aubio_mfcc.py @@ -32,6 +32,7 @@ class AubioMfcc(Analyzer): implements(IAnalyzer) def __init__(self): + super(AubioMfcc, self).__init__() self.input_blocksize = 1024 self.input_stepsize = self.input_blocksize / 4 @@ -78,4 +79,4 @@ class AubioMfcc(Analyzer): mfcc.parameters = dict(n_filters=self.n_filters, n_coeffs=self.n_coeffs) mfcc.data_object.value = self.mfcc_results - self._results.add(mfcc) + self.pipe.results.add(mfcc) diff --git a/timeside/analyzer/aubio_pitch.py b/timeside/analyzer/aubio_pitch.py index 899d64d..b358bb3 100644 --- a/timeside/analyzer/aubio_pitch.py +++ b/timeside/analyzer/aubio_pitch.py @@ -30,6 +30,7 @@ class AubioPitch(Analyzer): implements(IAnalyzer) # TODO check if needed with inheritance def __init__(self): + super(AubioPitch, self).__init__() self.input_blocksize = 2048 self.input_stepsize = self.input_blocksize / 2 @@ -78,4 +79,4 @@ class AubioPitch(Analyzer): # setup pitch.data_object.value = self.pitches - self._results.add(pitch) + self.pipe.results.add(pitch) diff --git a/timeside/analyzer/aubio_specdesc.py b/timeside/analyzer/aubio_specdesc.py index b23d58b..ce4d048 100644 --- a/timeside/analyzer/aubio_specdesc.py +++ b/timeside/analyzer/aubio_specdesc.py @@ -31,6 +31,7 @@ class AubioSpecdesc(Analyzer): implements(IAnalyzer) def __init__(self): + super(AubioSpecdesc, self).__init__() self.input_blocksize = 1024 self.input_stepsize = self.input_blocksize / 4 @@ -90,4 +91,4 @@ class AubioSpecdesc(Analyzer): res_specdesc.id_metadata.name = ' ' + method res_specdesc.data_object.value = self.specdesc_results[method] - self._results.add(res_specdesc) + self.pipe.results.add(res_specdesc) diff --git a/timeside/analyzer/aubio_temporal.py b/timeside/analyzer/aubio_temporal.py index e6c0a9c..71d5f60 100644 --- a/timeside/analyzer/aubio_temporal.py +++ b/timeside/analyzer/aubio_temporal.py @@ -32,6 +32,7 @@ class AubioTemporal(Analyzer): implements(IAnalyzer) def __init__(self): + super(AubioTemporal, self).__init__() self.input_blocksize = 1024 self.input_stepsize = 256 @@ -96,7 +97,7 @@ class AubioTemporal(Analyzer): onsets.data_object.time = self.onsets onsets.label_metadata.label = {1: 'Onset'} - self._results.add(onsets) + self.pipe.results.add(onsets) #--------------------------------- # Onset Rate @@ -116,7 +117,7 @@ class AubioTemporal(Analyzer): else: onsetrate.data_object.value = [] - self._results.add(onsetrate) + self.pipe.results.add(onsetrate) #--------------------------------- # Beats @@ -140,7 +141,7 @@ class AubioTemporal(Analyzer): beats.label_metadata.label = {1: 'Beat'} - self._results.add(beats) + self.pipe.results.add(beats) #--------------------------------- # BPM @@ -163,4 +164,4 @@ class AubioTemporal(Analyzer): else: bpm.data_object.value = [] - self._results.add(bpm) + self.pipe.results.add(bpm) diff --git a/timeside/analyzer/core.py b/timeside/analyzer/core.py index 58a415d..d42f15c 100644 --- a/timeside/analyzer/core.py +++ b/timeside/analyzer/core.py @@ -518,7 +518,7 @@ class AnalyzerResult(MetadataObject): data_mode_child = root.find('data_mode') time_mode_child = root.find('time_mode') result = analyzer_result_factory(data_mode=data_mode_child.text, - time_mode=time_mode_child.text) + time_mode=time_mode_child.text) for child in root: key = child.tag if key not in ['data_mode', 'time_mode']: @@ -560,7 +560,6 @@ class AnalyzerResult(MetadataObject): return self.id_metadata.unit - class ValueObject(AnalyzerResult): def __init__(self): @@ -955,6 +954,9 @@ class Analyzer(Processor): Generic class for the analyzers ''' + def __init__(self): + super(Analyzer, self).__init__() + def setup(self, channels=None, samplerate=None, blocksize=None, totalframes=None): super(Analyzer, self).setup(channels, samplerate, @@ -971,7 +973,7 @@ class Analyzer(Processor): def results(self): return AnalyzerResultContainer( - [self._results[key] for key in self._results.keys() + [self.pipe.results[key] for key in self.pipe.results.keys() if key.split('.')[0] == self.id()]) @staticmethod @@ -1003,7 +1005,8 @@ class Analyzer(Processor): from datetime import datetime - result = analyzer_result_factory(data_mode=data_mode, time_mode=time_mode) + result = analyzer_result_factory(data_mode=data_mode, + time_mode=time_mode) # Automatically write known metadata result.id_metadata.date = datetime.now().replace( diff --git a/timeside/analyzer/dc.py b/timeside/analyzer/dc.py index 8ce3290..bee0a21 100644 --- a/timeside/analyzer/dc.py +++ b/timeside/analyzer/dc.py @@ -61,4 +61,4 @@ class MeanDCShift(Analyzer): dc_result = self.new_result(data_mode='value', time_mode='global') dc_result.data_object.value = numpy.round( numpy.mean(100 * self.values), 3) - self._results.add(dc_result) + self.pipe.results.add(dc_result) diff --git a/timeside/analyzer/level.py b/timeside/analyzer/level.py index 29f0237..007d526 100644 --- a/timeside/analyzer/level.py +++ b/timeside/analyzer/level.py @@ -72,7 +72,7 @@ class Level(Analyzer): max_level.id_metadata.name += ' ' + "Max" max_level.data_object.value = np.round(20*np.log10(self.max_value), 3) - self._results.add(max_level) + self.pipe.results.add(max_level) # RMS level rms_level = self.new_result(data_mode='value', time_mode='global') @@ -81,5 +81,5 @@ class Level(Analyzer): rms_level.data_object.value = np.round(20*np.log10( np.sqrt(np.mean(self.mean_values))), 3) - self._results.add(rms_level) + self.pipe.results.add(rms_level) diff --git a/timeside/analyzer/odf.py b/timeside/analyzer/odf.py new file mode 100644 index 0000000..06c91ae --- /dev/null +++ b/timeside/analyzer/odf.py @@ -0,0 +1,99 @@ +# -*- 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.analyzer import Spectrogram +from timeside.api import IAnalyzer +import numpy as np +from numpy import pi as Pi +from scipy import signal + + +class OnsetDetectionFunction(Analyzer): + implements(IAnalyzer) + + def __init__(self, blocksize=1024, stepsize=None): + super(OnsetDetectionFunction, self).__init__() + + self.input_blocksize = blocksize + if stepsize: + self.input_stepsize = stepsize + else: + self.input_stepsize = blocksize / 2 + + self.parents.append(Spectrogram(blocksize=self.input_blocksize, + stepsize=self.input_stepsize)) + + @interfacedoc + def setup(self, channels=None, samplerate=None, + blocksize=None, totalframes=None): + super(OnsetDetectionFunction, self).setup(channels, samplerate, + blocksize, totalframes) + + @staticmethod + @interfacedoc + def id(): + return "odf" + + @staticmethod + @interfacedoc + def name(): + return "Onset Detection Function" + + @staticmethod + @interfacedoc + def unit(): + return "" + + def process(self, frames, eod=False): + return frames, eod + + def post_process(self): + + #spectrogram = self.parents()[0]['spectrogram_analyzer'].data + spectrogram = self.pipe._results['spectrogram_analyzer'].data + #spectrogram = self.pipe._results[self.parents()[0].id] + + # Low-pass filtering of the spectrogram amplitude along the time axis + S = signal.lfilter(signal.hann(15), 1, abs(spectrogram), axis=0) + # Clip small value to a minimal threshold + np.maximum(S, 1e-9, out=S) + + S = np.log10(S) + # S[S<1e-3]=0 + np.maximum(S, 1e-3, out=S) + + # Differentiator filter + df_filter = signal.fir_filter_design.remez(31, [0, 0.5], [Pi], + type='differentiator') + + S_diff = signal.lfilter(df_filter, 1, S, axis=0) + S_diff[S_diff < 1e-10] = 0 + + # Summation along the frequency axis + odf_diff = S_diff.sum(axis=1) + odf_diff = odf_diff / np.median(odf_diff) # Normalize + + odf = self.new_result(data_mode='value', time_mode='framewise') + #odf.parameters = {'FFT_SIZE': self.FFT_SIZE} + odf.data_object.value = odf_diff + self.pipe.results.add(odf) diff --git a/timeside/analyzer/spectrogram.py b/timeside/analyzer/spectrogram.py index 0597e31..adc2e55 100644 --- a/timeside/analyzer/spectrogram.py +++ b/timeside/analyzer/spectrogram.py @@ -27,11 +27,16 @@ import numpy as np class Spectrogram(Analyzer): - implements(IAnalyzer) # TODO check if needed with inheritance + implements(IAnalyzer) - def __init__(self): - self.input_blocksize = 2048 - self.input_stepsize = self.input_blocksize / 2 + def __init__(self, blocksize=2048, stepsize=None): + super(Spectrogram, self).__init__() + + self.input_blocksize = blocksize + if stepsize: + self.input_stepsize = stepsize + else: + self.input_stepsize = blocksize / 2 @interfacedoc def setup(self, channels=None, samplerate=None, @@ -67,4 +72,4 @@ class Spectrogram(Analyzer): spectrogram = self.new_result(data_mode='value', time_mode='framewise') spectrogram.parameters = {'FFT_SIZE': self.FFT_SIZE} spectrogram.data_object.value = self.values - self._results.add(spectrogram) + self.pipe.results.add(spectrogram) diff --git a/timeside/analyzer/vamp_plugin.py b/timeside/analyzer/vamp_plugin.py index 057ffa5..68d1eb9 100644 --- a/timeside/analyzer/vamp_plugin.py +++ b/timeside/analyzer/vamp_plugin.py @@ -31,7 +31,7 @@ class VampSimpleHost(Analyzer): implements(IAnalyzer) def __init__(self, plugin_list=None): - + super(VampSimpleHost, self).__init__() if plugin_list is None: plugin_list = self.get_plugins_list() #plugin_list = [['vamp-example-plugins', 'percussiononsets', 'detectionfunction']] @@ -110,7 +110,7 @@ class VampSimpleHost(Analyzer): plugin_res.id_metadata.name += ' ' + \ ' '.join(plugin_line[1:]) - self._results.add(plugin_res) + self.pipe.results.add(plugin_res) @staticmethod def vamp_plugin(plugin, wavfile): diff --git a/timeside/analyzer/waveform.py b/timeside/analyzer/waveform.py index ace57c8..eb8b09f 100644 --- a/timeside/analyzer/waveform.py +++ b/timeside/analyzer/waveform.py @@ -65,4 +65,4 @@ class Waveform(Analyzer): def post_process(self): waveform = self.new_result(data_mode='value', time_mode='framewise') waveform.data_object.value = np.asarray(self.values).flatten() - self._results.add(waveform) + self.pipe.results.add(waveform) diff --git a/timeside/analyzer/yaafe.py b/timeside/analyzer/yaafe.py index 0ad8b3a..45af517 100644 --- a/timeside/analyzer/yaafe.py +++ b/timeside/analyzer/yaafe.py @@ -35,6 +35,8 @@ class Yaafe(Analyzer): implements(IAnalyzer) def __init__(self, yaafeSpecification=None): + super(Yaafe,self).__init__() + # Check arguments if yaafeSpecification is None: yaafeSpecification = FeaturePlan(sample_rate=32000) @@ -112,4 +114,4 @@ class Yaafe(Analyzer): result.data_object.value = self.yaafe_engine.readOutput(featName) # Store results in Container if len(result.data_object.value): - self._results.add(result) + self.pipe.results.add(result) diff --git a/timeside/api.py b/timeside/api.py index ad2b374..04d86d8 100644 --- a/timeside/api.py +++ b/timeside/api.py @@ -88,7 +88,7 @@ class IProcessor(Interface): # implementations should always call the parent method - def mediainfo(): + def mediainfo(self): """ Information about the media object uri @@ -96,6 +96,13 @@ class IProcessor(Interface): duration """ + @property + def parents(self): + """ + Return the processor's parents + """ + + class IEncoder(IProcessor): """Encoder driver interface. Each encoder is expected to support a specific format.""" diff --git a/timeside/component.py b/timeside/component.py index 25ddec6..d5daa3f 100644 --- a/timeside/component.py +++ b/timeside/component.py @@ -19,7 +19,7 @@ # This file defines a generic object interface mechanism and -# a way to determine which components implements a given interface. +# a way to determine which components implements a given interface. # # For example, the following defines the Music class as implementing the # listenable interface. diff --git a/timeside/core.py b/timeside/core.py index 06323cb..ef7f4ea 100644 --- a/timeside/core.py +++ b/timeside/core.py @@ -62,12 +62,26 @@ class MetaProcessor(MetaComponent): class Processor(Component): - """Base component class of all processors""" + """Base component class of all processors + + + Attributes: + parents : List of parent Processors that must be processed + before the current Processor + pipe : The current ProcessPipe in which the Processor will run + """ __metaclass__ = MetaProcessor abstract() implements(IProcessor) + def __init__(self): + super(Processor, self).__init__() + + self.parents = [] + self.source_mediainfo = None + self.pipe = None + @interfacedoc def setup(self, channels=None, samplerate=None, blocksize=None, totalframes=None): @@ -204,23 +218,35 @@ def get_processor(processor_id): """Return a processor by its id""" if not _processors.has_key(processor_id): raise Error("No processor registered with id: '%s'" - % processor_id) + % processor_id) return _processors[processor_id] class ProcessPipe(object): - """Handle a pipe of processors""" + """Handle a pipe of processors + + Attributes: + processor: List of all processors in the Process pipe + results : Results Container for all the analyzers of the Pipe process +""" def __init__(self, *others): self.processors = [] self |= others + from timeside.analyzer.core import AnalyzerResultContainer + self.results = AnalyzerResultContainer() + + for proc in self.processors: + proc.pipe = self + def __or__(self, other): return ProcessPipe(self, other) def __ior__(self, other): if isinstance(other, Processor): + self |= other.parents self.processors.append(other) elif isinstance(other, ProcessPipe): self.processors.extend(other.processors) @@ -248,22 +274,18 @@ class ProcessPipe(object): the pipe. Also returns the pipe itself.""" source = self.processors[0] - items = self.processors[1:] + items = self.processors[1:] source.setup() last = source - from timeside.analyzer.core import AnalyzerResultContainer - self._results = AnalyzerResultContainer() - # setup/reset processors and configure properties throughout the pipe for item in items: - item.setup(channels = last.channels(), - samplerate = last.samplerate(), - blocksize = last.blocksize(), - totalframes = last.totalframes()) + item.setup(channels=last.channels(), + samplerate=last.samplerate(), + blocksize=last.blocksize(), + totalframes=last.totalframes()) item.source_mediainfo = source.mediainfo() - item._results = self._results last = item # now stream audio data along the pipe @@ -279,10 +301,3 @@ class ProcessPipe(object): for item in items: item.release() - @property - def results(self): - """ - Results Container for all the analyzers of the Pipe process - """ - if hasattr(self, '_results'): - return self._results \ No newline at end of file diff --git a/timeside/decoder/core.py b/timeside/decoder/core.py index bb7af5e..d4cff1c 100644 --- a/timeside/decoder/core.py +++ b/timeside/decoder/core.py @@ -69,6 +69,7 @@ class FileDecoder(Processor): duration : float duration of the segment in seconds ''' + super(FileDecoder, self).__init__() # is this a file? import os.path diff --git a/timeside/grapher/core.py b/timeside/grapher/core.py index 38c5f81..822fa21 100644 --- a/timeside/grapher/core.py +++ b/timeside/grapher/core.py @@ -106,6 +106,7 @@ class Grapher(Processor): lower_freq = 20 def __init__(self, width=1024, height=256, bg_color=None, color_scheme='default'): + super(Grapher, self).__init__() self.bg_color = bg_color self.color_scheme = color_scheme self.graph = None -- 2.39.5