]> git.parisson.com Git - timeside.git/commitdiff
TimeSide Ccore ProcessPipe: Solve #8 by providing a more user-friendly implementation...
authorThomas Fillon <thomas@parisson.com>
Fri, 13 Dec 2013 19:46:28 +0000 (20:46 +0100)
committerThomas Fillon <thomas@parisson.com>
Fri, 13 Dec 2013 19:46:28 +0000 (20:46 +0100)
- The frames can be stored in the `ProcessPipe` frames_stack attribute if the pipe is run with argument 'stack=True' (default stack=False)
- The ProcessPipe is now linked to its processors through the processor self.process_pipe attribute (this avoid confusion with `decoder.pipe` or `decoder.pipeline` (-> reflect this change in all analyzers)
- After the process, during the release every processors of the pipe are removed from the pipe (except the decoder)

19 files changed:
doc/source/tutorial/ArrayDecoder.rst [deleted file]
doc/source/tutorial/frames_stack.rst [new file with mode: 0644]
doc/source/tutorial/index.rst
tests/test_AnalyzerResult.py
timeside/analyzer/aubio_melenergy.py
timeside/analyzer/aubio_mfcc.py
timeside/analyzer/aubio_pitch.py
timeside/analyzer/aubio_specdesc.py
timeside/analyzer/aubio_temporal.py
timeside/analyzer/core.py
timeside/analyzer/dc.py
timeside/analyzer/level.py
timeside/analyzer/odf.py
timeside/analyzer/spectrogram.py
timeside/analyzer/vamp_plugin.py
timeside/analyzer/waveform.py
timeside/analyzer/yaafe.py
timeside/core.py
timeside/decoder/core.py

diff --git a/doc/source/tutorial/ArrayDecoder.rst b/doc/source/tutorial/ArrayDecoder.rst
deleted file mode 100644 (file)
index 6a37158..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-.. This file is part of TimeSide
-   @author: Thomas Fillon
-
-===============================================
- Running a pipe with previously decoded frames
-===============================================
-
-Example of use of the  :class:`ArrayDecoder <timeside.decoder.core.ArrayDecoder>` and  :class:`Waveform analyzer <timeside.analyzer.waveform.Waveform>` to run a pipe with previously frames from memory on a second pass
-
-First, setup a  :class:`FileDecoder <timeside.decoder.core.FileDecoder>` on an audio file:
-
->>> import timeside
->>> import numpy as np
->>> 
->>> audio_file = 'http://github.com/yomguy/timeside-samples/raw/master/samples/sweep.mp3'
->>> 
->>> file_decoder = timeside.decoder.FileDecoder(audio_file)
-
-Then, setup an arbitrary analyzer to check that both decoding process are equivalent and a :class:`Waveform analyzer <timeside.analyzer.waveform.Waveform>` which result will store the decoded frames:
-
->>> pitch_on_file = timeside.analyzer.AubioPitch()
->>> waveform = timeside.analyzer.Waveform()
-
-And run the pipe:
-
->>> (file_decoder | pitch_on_file | waveform).run()
-
-To run the second pass, we need to get back the decoded samples and the original samplerate and pass them to :class:`ArrayDecoder <timeside.decoder.core.ArrayDecoder>`:
-
->>> samples = waveform.results['waveform_analyzer'].data
->>> samplerate = waveform.results['waveform_analyzer'].frame_metadata.samplerate
->>> array_decoder = timeside.decoder.ArrayDecoder(samples=samples, samplerate=samplerate)
-
-Then we can run a second pipe with the previously decoded frames and pass the frames to the same analyzer:
-
->>> pitch_on_array = timeside.analyzer.AubioPitch()
->>> (array_decoder | pitch_on_array).run()
-
-To assert that the frames passed to the two analyzers are the same, we check that the results of these analyzers are equivalent:
-
->>> np.allclose(pitch_on_file.results['aubio_pitch.pitch'].data,
-...             pitch_on_array.results['aubio_pitch.pitch'].data)
-True
-
diff --git a/doc/source/tutorial/frames_stack.rst b/doc/source/tutorial/frames_stack.rst
new file mode 100644 (file)
index 0000000..9201b0c
--- /dev/null
@@ -0,0 +1,65 @@
+.. This file is part of TimeSide
+   @author: Thomas Fillon
+
+===============================================
+ Running a pipe with previously decoded frames
+===============================================
+
+Example of use of the `stack` option in :func:`timeside.core.ProcessPipe.run` to run a pipe with previously decoded frames stacked in memory on a second pass.
+
+>>> import timeside
+>>> import numpy as np
+>>> audio_file = 'http://github.com/yomguy/timeside-samples/raw/master/samples/sweep.mp3'
+>>> decoder = timeside.decoder.FileDecoder(audio_file)
+
+Setup an arbitrary analyzer to check that decoding process from file and from stack are equivalent:
+
+>>> pitch_on_file = timeside.analyzer.AubioPitch()
+>>> myPipe = (decoder | pitch_on_file)
+>>> print myPipe.processors #doctest: +ELLIPSIS
+[<timeside.decoder.core.FileDecoder object at 0x...>, <timeside.analyzer.aubio_pitch.AubioPitch object at 0x...>]
+
+If the pipe is run with the default argument `stack=False`, the other processes of the pipe are released from the pipe after the run and only the  :class:`fileDecoder <timeside.decoder.core.FileDecoder>` is kept in the pipe:
+
+>>> myPipe.run()
+>>> print myPipe.processors #doctest: +ELLIPSIS
+[<timeside.decoder.core.FileDecoder object at 0x...>]
+
+
+If the pipe is run with the argument `stack=True`, the processed frames are stored in the pipe attribute `frames_stack`.
+The other processes of the pipe are also released from the pipe after the run but the :class:`fileDecoder <timeside.decoder.core.FileDecoder>` is replaced by an :class:`ArrayDecoder <timeside.decoder.core.ArrayDecoder>`:
+
+>>> myPipe = (decoder | pitch_on_file)
+>>> myPipe.run(stack=True)
+>>> print myPipe.processors #doctest: +ELLIPSIS
+[<timeside.decoder.core.ArrayDecoder object at 0x...>]
+
+The stack
+
+>>> myPipe.frames_stack #doctest: +ELLIPSIS
+array([[...]], dtype=float32)
+
+
+Then we can run a second pipe with the previously decoded frames and pass the frames to the same analyzer.
+
+Define a second analyzer equivalent to the previous one:
+
+>>> pitch_on_stack = timeside.analyzer.AubioPitch()
+
+Add it to the pipe:
+
+>>> myPipe |= pitch_on_stack
+>>> print myPipe.processors #doctest: +ELLIPSIS
+[<timeside.decoder.core.ArrayDecoder object at 0x...>, <timeside.analyzer.aubio_pitch.AubioPitch object at 0x...>]
+
+
+Run the pipe:
+
+>>> myPipe.run()
+
+Assert that the frames passed to the two analyzers are the same, we check that the results of these analyzers are equivalent:
+
+>>> np.allclose(pitch_on_file.results['aubio_pitch.pitch'].data,
+...                    pitch_on_stack.results['aubio_pitch.pitch'].data)
+True
+
index 333e314515c3ef2687b79fdfc960eb4a1d75abfb..5b6d893d613d2449d68f2eb69ab3c8944e687185 100644 (file)
@@ -13,6 +13,6 @@ Contents:
 
       Quick start <quick_start>
       Usage of AnalyzerResult <AnalyzerResult>
-      Running a pipe with previously decoded frames <ArrayDecoder>
+      Running a pipe with previously decoded frames <frames_stack>
 
 
index 45cc2c7cc0d60ed733276f09f01cb54d6c2e18de..e0dd58dd8cdf347566940e7bce49cf15e4ea33d7 100755 (executable)
@@ -236,4 +236,4 @@ class TestAnalyzerResultAsDict(TestAnalyzerResult):
                               self.result.as_dict().keys())
 
 if __name__ == '__main__':
-    unittest.main(testRunner=TestRunner())
\ No newline at end of file
+    unittest.main(testRunner=TestRunner())
index 451a0ae37d65c093005fa34ab258b50ce8f4b855..23f277db590f5f8759a9d86a3e361214c239a354 100644 (file)
@@ -78,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.pipe.results.add(melenergy)
+        self.process_pipe.results.add(melenergy)
index 0b44368d1cbefed83b38471b603533e574f5e4fe..1d43c3d7210e096dd00003eb124765300b5528d9 100644 (file)
@@ -80,4 +80,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.pipe.results.add(mfcc)
+        self.process_pipe.results.add(mfcc)
index 580b994b61098b2617d2db4b58d6c3faeecd72bb..c280d06e6047ceffef89209f9320a2d93c330b72 100644 (file)
@@ -85,11 +85,11 @@ class AubioPitch(Analyzer):
         pitch.id_metadata.name += ' ' + "pitch"
         pitch.id_metadata.unit = "Hz"
         pitch.data_object.value = self.pitches
-        self.pipe.results.add(pitch)
+        self.process_pipe.results.add(pitch)
 
         pitch_confidence = self.new_result(data_mode='value', time_mode='framewise')
         pitch_confidence.id_metadata.id += '.' + "pitch_confidence"
         pitch_confidence.id_metadata.name += ' ' + "pitch confidence"
         pitch_confidence.id_metadata.unit = None
         pitch_confidence.data_object.value = self.pitch_confidences
-        self.pipe.results.add(pitch_confidence)
+        self.process_pipe.results.add(pitch_confidence)
index 931969d21b436ed3717873b92218365483cb6234..2b9cf8214eac608341fd363e5ef26fd230bbd699 100644 (file)
@@ -92,4 +92,4 @@ class AubioSpecdesc(Analyzer):
             res_specdesc.id_metadata.name = ' ' + method
             res_specdesc.data_object.value = self.specdesc_results[method]
 
-            self.pipe.results.add(res_specdesc)
+            self.process_pipe.results.add(res_specdesc)
index 59e7c4ab31989d960bcb943693215ad636b89aa9..b1e3746a5a51e136fae5787940a83ca4e866365f 100644 (file)
@@ -96,7 +96,7 @@ class AubioTemporal(Analyzer):
         onsets.data_object.label = numpy.ones(len(self.onsets))
         onsets.label_metadata.label = {1: 'Onset'}
 
-        self.pipe.results.add(onsets)
+        self.process_pipe.results.add(onsets)
 
         #---------------------------------
         #  Onset Rate: Segment (time, duration, value)
@@ -115,7 +115,7 @@ class AubioTemporal(Analyzer):
             onsetrate.data_object.value = []
             onsetrate.data_object.time = []
 
-        self.pipe.results.add(onsetrate)
+        self.process_pipe.results.add(onsetrate)
 
         #---------------------------------
         #  Beats: Event (time, "Beat")
@@ -128,7 +128,7 @@ class AubioTemporal(Analyzer):
         beats.data_object.label = numpy.ones(len(self.beats))
         beats.label_metadata.label = {1: 'Beat'}
 
-        self.pipe.results.add(beats)
+        self.process_pipe.results.add(beats)
 
         #---------------------------------
         #  Beat confidences: Event (time, value)
@@ -140,7 +140,7 @@ class AubioTemporal(Analyzer):
         beat_confidences.data_object.time = self.beats
         beat_confidences.data_object.value = self.beat_confidences
 
-        self.pipe.results.add(beat_confidences)
+        self.process_pipe.results.add(beat_confidences)
 
         #---------------------------------
         #  BPM: Segment (time, duration, value)
@@ -158,4 +158,4 @@ class AubioTemporal(Analyzer):
         else:
             bpm.data_object.value = []
 
-        self.pipe.results.add(bpm)
+        self.process_pipe.results.add(bpm)
index c48e0ff8f3a9e0c4555eeb416ea7856c6eac80b0..9da30496f3497098bae8a124fbffca8ab16baad2 100644 (file)
@@ -984,7 +984,7 @@ class Analyzer(Processor):
     def results(self):
 
         return AnalyzerResultContainer(
-            [self.pipe.results[key] for key in self.pipe.results.keys()
+            [self.process_pipe.results[key] for key in self.process_pipe.results.keys()
              if key.split('.')[0] == self.id()])
 
     @staticmethod
index bee0a212290b524636085f312fd711c44ecf1cbb..446d23ed57e7b48ff8026439d8a03e3e8db1ae96 100644 (file)
@@ -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.pipe.results.add(dc_result)
+        self.process_pipe.results.add(dc_result)
index 007d526f640692a8a5ae933d05c6c3133dd7899f..7151ab9e01d45d28959494ada76f24fed40faed1 100644 (file)
@@ -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.pipe.results.add(max_level)
+        self.process_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.pipe.results.add(rms_level)
+        self.process_pipe.results.add(rms_level)
 
index 42eb0bce5c3a49ff1492d8805a44061c5632887d..91cd1aeaa9f8e0e6a61d57a67c252e6de0c464ec 100644 (file)
@@ -70,7 +70,7 @@ class OnsetDetectionFunction(Analyzer):
     def post_process(self):
 
         #spectrogram = self.parents()[0]['spectrogram_analyzer'].data
-        spectrogram = self.pipe.results['spectrogram_analyzer'].data
+        spectrogram = self.process_pipe.results['spectrogram_analyzer'].data
         #spectrogram = self.pipe._results[self.parents()[0].id]
 
         # Low-pass filtering of the spectrogram amplitude along the time axis
@@ -108,4 +108,4 @@ class OnsetDetectionFunction(Analyzer):
         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)
+        self.process_pipe.results.add(odf)
index 9728c6aa2ac58dd974adab5ffc5b0fa0c3283c01..587d7f4c99f15016b191fa0fb7c27aae9fc8de56 100644 (file)
@@ -72,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.pipe.results.add(spectrogram)
+        self.process_pipe.results.add(spectrogram)
index 68d1eb931625aa6be7aa0f3b5da7e7b952be4c9d..7f5f65b84894e76dc988bfc64023c434f4053f01 100644 (file)
@@ -110,7 +110,7 @@ class VampSimpleHost(Analyzer):
             plugin_res.id_metadata.name += ' ' + \
                 ' '.join(plugin_line[1:])
 
-            self.pipe.results.add(plugin_res)
+            self.process_pipe.results.add(plugin_res)
 
     @staticmethod
     def vamp_plugin(plugin, wavfile):
index e1289f0bb098534015a0f29b96e26333f4d53327..0a14c76b0f35a5f237eca0a1989b0e6649077267 100644 (file)
@@ -67,4 +67,4 @@ class Waveform(Analyzer):
     def post_process(self):
         waveform = self.new_result(data_mode='value', time_mode='framewise')
         waveform.data_object.value = np.vstack(self.values)
-        self.pipe.results.add(waveform)
+        self.process_pipe.results.add(waveform)
index 45af5172f2e97aeca55dcb84f94f89e932a5fc52..5e00a08dcb8e87e5289bde2f0f90d2d3b522307a 100644 (file)
@@ -114,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.pipe.results.add(result)
+                self.process_pipe.results.add(result)
index b937de683bafedef87044ec7ccbed74ea203b66c..4f5a54b5c9176d9d13ee4680c99a0d59d664eee7 100644 (file)
@@ -21,6 +21,8 @@
 from timeside.component import *
 from timeside.api import IProcessor
 from timeside.exceptions import Error, ApiError
+
+
 import re
 import time
 import numpy
@@ -244,18 +246,15 @@ class ProcessPipe(object):
         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):
-            parents = other.parents
-            for parent in parents:
+            for parent in other.parents:
                 self |= parent
             self.processors.append(other)
+            other.process_pipe = self
         elif isinstance(other, ProcessPipe):
             self.processors.extend(other.processors)
         else:
@@ -277,13 +276,22 @@ class ProcessPipe(object):
                 pipe += ' | '
         return pipe
 
-    def run(self, channels = None, samplerate = None, blocksize = None):
+    def run(self, channels=None, samplerate=None, blocksize=None, stack=None):
         """Setup/reset all processors in cascade and stream audio data along
         the pipe. Also returns the pipe itself."""
 
         source = self.processors[0]
         items = self.processors[1:]
-        source.setup(channels = channels, samplerate = samplerate, blocksize = blocksize)
+        source.setup(channels=channels, samplerate=samplerate,
+                     blocksize=blocksize)
+
+        if stack is None:
+                self.stack = False
+        else:
+            self.stack = stack
+
+        if self.stack:
+            self.frames_stack = []
 
         last = source
 
@@ -300,11 +308,27 @@ class ProcessPipe(object):
         eod = False
         while not eod:
             frames, eod = source.process()
+            if self.stack:
+                self.frames_stack.append(frames)
             for item in items:
                 frames, eod = item.process(frames, eod)
 
+        # Post-processing
         for item in items:
             item.post_process()
 
+        # Release processors
+        if self.stack:
+            if not isinstance(self.frames_stack, numpy.ndarray):
+                self.frames_stack = numpy.vstack(self.frames_stack)
+            from timeside.decoder.core import ArrayDecoder
+            new_source = ArrayDecoder(samples=self.frames_stack,
+                                      samplerate=source.samplerate())
+            new_source.setup(channels=source.channels(),
+                             samplerate=source.samplerate(),
+                             blocksize=source.blocksize())
+            self.processors[0] = new_source
+
         for item in items:
             item.release()
+            self.processors.remove(item)
index 9fa1f7e2c06321a3c9424d821241c7492bf08c6f..dbee2a0187598c51a330e995f4fd35bf729748e3 100644 (file)
@@ -62,7 +62,7 @@ class FileDecoder(Processor):
     def id():
         return "gst_dec"
 
-    def __init__(self, uri, start = 0, duration = None):
+    def __init__(self, uri, start=0, duration=None):
 
         """
         Construct a new FileDecoder
@@ -520,4 +520,3 @@ if __name__ == "__main__":
     from tests import test_decoding, test_array_decoding
 
     run_test_module([test_decoding, test_array_decoding])
-