From: yomguy Date: Wed, 25 Aug 2010 15:20:29 +0000 (+0000) Subject: add smooth for joy, fix normalize bug X-Git-Tag: 0.3.2~117 X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=dff2d67154f7d9aca1e5cf6649a5f03f2d948fd5;p=timeside.git add smooth for joy, fix normalize bug --- diff --git a/timeside/grapher/core.py b/timeside/grapher/core.py index 8577871..539ba5b 100644 --- a/timeside/grapher/core.py +++ b/timeside/grapher/core.py @@ -255,57 +255,54 @@ class WaveformImage(object): class WaveformImageJoyContour(WaveformImage): - + def __init__(self, image_width, image_height, nframes, samplerate, fft_size, bg_color, color_scheme, filename=None): WaveformImage.__init__(self, image_width, image_height, nframes, samplerate, fft_size, bg_color, color_scheme, filename=filename) self.contour = numpy.zeros(self.image_width) self.centroids = numpy.zeros(self.image_width) self.ndiv = 6 self.x = numpy.r_[0:self.image_width-1:1] - #self.dx1 = self.x[1]-self.x[0] - self.dx2 = self.x[self.samples_per_pixel/(self.ndiv*10)]-self.x[0] + self.dx1 = self.x[1]-self.x[0] def get_peaks_contour(self, x, peaks, spectral_centroid=None): - """ draw 2 peaks at x using the spectral_centroid for color """ self.contour[x] = numpy.max(peaks) self.centroids[x] = spectral_centroid - + def draw_peaks_contour(self): - contour = cspline1d(self.contour.copy()) - #contour = cspline1d_eval(contour, self.x, dx=self.dx1, x0=self.x[0]) - contour = cspline1d_eval(contour, self.x, dx=self.dx2, x0=self.x[0]) - #print len(contour) - - l_min = min(self.contour) - l_max = max(self.contour) - l_range= l_max - l_min + #contour = self.contour.copy() + contour = smooth(self.contour, window_len=13) - self.contour = (contour-l_min)/l_range - #print contour + l_min = min(contour) + contour = (contour-l_min) + l_max = max(contour) + l_range= l_max - l_min + contour = contour/l_max + contour = cspline1d(contour) + contour = cspline1d_eval(contour, self.x, dx=self.dx1, x0=self.x[0]) # Multispline scales for i in range(0,self.ndiv): self.previous_x, self.previous_y = None, None - bright_color = int(255*(1-float(i)/self.ndiv)) + + #bright_color = 255 + bright_color = int(255*(1-float(i)/(self.ndiv*2))) line_color = (bright_color,bright_color,bright_color) - print line_color - + # Linear #contour = contour*(1.0-float(i)/self.ndiv) #contour = contour*(1-float(i)/self.ndiv) - + # Cosine contour = contour*numpy.arccos(float(i)/self.ndiv)*2/numpy.pi #contour = self.contour*(1-float(i)*numpy.arccos(float(i)/self.ndiv)*2/numpy.pi/self.ndiv) - + # Negative Sine #contour = contour + ((1-contour)*2/numpy.pi*numpy.arcsin(float(i)/self.ndiv)) for j in range(0,self.image_width-1): #line_color = self.color_lookup[int(self.centroids[j]*255.0)] x = self.x[j] - y = contour[j]*self.image_height - #print y + y = contour[j]*(self.image_height-1) if self.previous_y: self.draw.line([self.previous_x, self.previous_y, x, y], line_color) self.draw_anti_aliased_pixels(x, y, y, line_color) @@ -448,3 +445,72 @@ class Noise(object): self.seekpoint += will_read return numpy.random.random(will_read)*2 - 1 + +# TOOLS + +def downsample(vector, factor): + """ + downsample(vector, factor): + Downsample (by averaging) a vector by an integer factor. + """ + if (len(vector) % factor): + print "Length of 'vector' is not divisible by 'factor'=%d!" % factor + return 0 + vector.shape = (len(vector)/factor, factor) + return numpy.mean(vector, axis=1) + + +def smooth(x, window_len=10, window='hanning'): + """smooth the data using a window with requested size. + + This method is based on the convolution of a scaled window with the signal. + The signal is prepared by introducing reflected copies of the signal + (with the window size) in both ends so that transient parts are minimized + in the begining and end part of the output signal. + + input: + x: the input signal + window_len: the dimension of the smoothing window + window: the type of window from 'flat', 'hanning', 'hamming', 'bartlett', 'blackman' + flat window will produce a moving average smoothing. + + output: + the smoothed signal + + example: + + import numpy as np + t = numpy.linspace(-2,2,0.1) + x = numpy.sin(t)+numpy.random.randn(len(t))*0.1 + y = smooth(x) + + see also: + + numpy.hanning, numpy.hamming, numpy.bartlett, numpy.blackman, numpy.convolve + scipy.signal.lfilter + + TODO: the window parameter could be the window itself if an array instead of a string + """ + + if x.ndim != 1: + raise ValueError, "smooth only accepts 1 dimension arrays." + + if x.size < window_len: + raise ValueError, "Input vector needs to be bigger than window size." + + if window_len < 3: + return x + + if not window in ['flat', 'hanning', 'hamming', 'bartlett', 'blackman']: + raise ValueError, "Window is on of 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'" + + s=numpy.r_[2*x[0]-x[window_len:1:-1], x, 2*x[-1]-x[-1:-window_len:-1]] + #print(len(s)) + + if window == 'flat': #moving average + w = numpy.ones(window_len,'d') + else: + w = getattr(numpy, window)(window_len) + y = numpy.convolve(w/w.sum(), s, mode='same') + return y[window_len-1:-window_len+1] + diff --git a/timeside/tools/waveform_batch b/timeside/tools/waveform_batch deleted file mode 100644 index ea433c9..0000000 --- a/timeside/tools/waveform_batch +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright (c) 2009-2010 Guillaume Pellerin - -# 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: Guillaume Pellerin - -version = '0.1-beta' - -import os -import sys -import timeside - -class GrapherScheme: - - def __init__(self): - - self.color_scheme = { - 'waveform': [ # Four (R,G,B) tuples for three main color channels for the spectral centroid method - (50,0,200), (0,220,80), (255,224,0), (255,0,0) - ], - 'spectrogram': [ - (0, 0, 0), (58/4,68/4,65/4), (80/2,100/2,153/2), (90,180,100), (224,224,44), (255,60,30), (255,255,255) - ]} - - # Width of the image - self.width = 1024 - - # Height of the image - self.height = 320 - - # Background color - self.bg_color = (25,25,25) - - # Force computation. By default, the class doesn't overwrite existing image files. - self.force = True - - -class Media2Waveform(object): - - def __init__(self, media_dir, img_dir): - self.root_dir = media_dir - self.img_dir = img_dir - self.scheme = GrapherScheme() - self.width = self.scheme.width - self.height = self.scheme.height - self.bg_color = self.scheme.bg_color - self.color_scheme = self.scheme.color_scheme - self.force = self.scheme.force - - self.media_list = self.get_media_list() - if not os.path.exists(self.img_dir): - os.mkdir(self.img_dir) - self.path_dict = self.get_path_dict() - - def get_media_list(self): - media_list = [] - for root, dirs, files in os.walk(self.root_dir): - if root: - for file in files: - ext = file.split('.')[-1] - if ext == 'wav' or ext == 'WAV': - media_list.append(root+os.sep+file) - return media_list - - def get_path_dict(self): - path_dict = {} - for media in self.media_list: - name = os.path.splitext(media) - name = name[0].split(os.sep)[-1] - path_dict[media] = unicode(self.img_dir + os.sep + name + '.png') - return path_dict - - def process(self): - for source, image in self.path_dict.iteritems(): - if not os.path.exists(image) or self.force: - print 'Rendering ', source, ' to ', image, '...' - audio = os.path.join(os.path.dirname(__file__), source) - decoder = timeside.decoder.FileDecoder(audio) - waveform = timeside.grapher.WaveformJoyDiv(width=self.width, height=self.height, output=image, - bg_color=self.bg_color, color_scheme=self.color_scheme) - (decoder | waveform).run() - print 'frames per pixel = ', waveform.graph.samples_per_pixel - waveform.render() - - -if __name__ == '__main__': - if len(sys.argv) <= 2: - print """ - Usage : python waveform_batch /path/to/media_dir /path/to/img_dir - - Dependencies : timeside, python, python-numpy, python-gst0.10, gstreamer0.10-plugins-base - See http://code.google.com/p/timeside/ for more information. - """ - else: - media_dir = sys.argv[-2] - img_dir = sys.argv[-1] - m = Media2Waveform(media_dir, img_dir) - m.process() diff --git a/timeside/tools/waveform_batch.py b/timeside/tools/waveform_batch.py new file mode 100644 index 0000000..8bc8fe0 --- /dev/null +++ b/timeside/tools/waveform_batch.py @@ -0,0 +1,115 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2009-2010 Guillaume Pellerin + +# 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: Guillaume Pellerin + +version = '0.1-beta' + +import os +import sys +import timeside + +class GrapherScheme: + + def __init__(self): + + self.color_scheme = { + 'waveform': [ # Four (R,G,B) tuples for three main color channels for the spectral centroid method + (50,0,200), (0,220,80), (255,224,0), (255,0,0) + ], + 'spectrogram': [ + (0, 0, 0), (58/4,68/4,65/4), (80/2,100/2,153/2), (90,180,100), (224,224,44), (255,60,30), (255,255,255) + ]} + + # Width of the image + self.width = 1024 + + # Height of the image + self.height = 320 + + # Background color + self.bg_color = (25,25,25) + + # Force computation. By default, the class doesn't overwrite existing image files. + self.force = True + + +class Media2Waveform(object): + + def __init__(self, media_dir, img_dir): + self.root_dir = media_dir + self.img_dir = img_dir + self.scheme = GrapherScheme() + self.width = self.scheme.width + self.height = self.scheme.height + self.bg_color = self.scheme.bg_color + self.color_scheme = self.scheme.color_scheme + self.force = self.scheme.force + + self.media_list = self.get_media_list() + if not os.path.exists(self.img_dir): + os.mkdir(self.img_dir) + self.path_dict = self.get_path_dict() + + def get_media_list(self): + media_list = [] + for root, dirs, files in os.walk(self.root_dir): + if root: + for file in files: + ext = file.split('.')[-1] + media_list.append(root+os.sep+file) + return media_list + + def get_path_dict(self): + path_dict = {} + for media in self.media_list: + filename = media.split(os.sep)[-1] + name, ext = os.path.splitext(filename) + path_dict[media] = self.img_dir + os.sep + filename + '.png' + return path_dict + + def process(self): + for source, image in self.path_dict.iteritems(): + if not os.path.exists(image) or self.force: + print 'Rendering ', source, ' to ', image, '...' + audio = os.path.join(os.path.dirname(__file__), source) + decoder = timeside.decoder.FileDecoder(audio) + + waveform = timeside.grapher.WaveformJoyDiv(width=self.width, height=self.height, output=image, + bg_color=self.bg_color, color_scheme=self.color_scheme) + + (decoder | waveform).run() + print 'frames per pixel = ', waveform.graph.samples_per_pixel + waveform.render() + + +if __name__ == '__main__': + if len(sys.argv) <= 2: + print """ + Usage : python waveform_batch /path/to/media_dir /path/to/img_dir + + Dependencies : timeside, python, python-numpy, python-gst0.10, gstreamer0.10-plugins-base + See http://code.google.com/p/timeside/ for more information. + """ + else: + media_dir = sys.argv[-2] + img_dir = sys.argv[-1] + m = Media2Waveform(media_dir, img_dir) + m.process()