]> git.parisson.com Git - timeside.git/commitdiff
add grapher utils and color schemes
authorGuillaume Pellerin <yomguy@parisson.com>
Tue, 22 Oct 2013 16:54:48 +0000 (18:54 +0200)
committerGuillaume Pellerin <yomguy@parisson.com>
Tue, 22 Oct 2013 16:54:48 +0000 (18:54 +0200)
timeside/grapher/color_schemes.py [new file with mode: 0644]
timeside/grapher/core.py
timeside/grapher/utils.py [new file with mode: 0644]

diff --git a/timeside/grapher/color_schemes.py b/timeside/grapher/color_schemes.py
new file mode 100644 (file)
index 0000000..8f41d0c
--- /dev/null
@@ -0,0 +1,24 @@
+
+
+default_color_schemes = {
+    'default': {
+        'waveform': [(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)]
+    },
+    'iso': {
+        'waveform': [(0,0,255), (0,255,255), (255,255,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)]
+    },
+    'purple': {
+        'waveform': [(173,173,173), (147,149,196), (77,80,138), (108,66,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)]
+    },
+    'awdio': {
+        'waveform': [(255,255,255), (255,255,255), (255,255,255), (255,255,255)],
+        '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)]
+    },
+}
index ebc2c2e3d972aaa62c1083a7a248e2975fdc8226..354e60c4cc0603ff020f87f5f59b5ba6c37638ab 100644 (file)
@@ -30,7 +30,7 @@ except ImportError:
     import ImageFilter, ImageChops, Image, ImageDraw, ImageColor, ImageEnhance
 
 from timeside.core import FixedSizeInputAdapter
-from color_schemes import default_color_schemes
+from timeside.grapher.color_schemes import default_color_schemes
 from utils import *
 
 class Spectrum(object):
diff --git a/timeside/grapher/utils.py b/timeside/grapher/utils.py
new file mode 100644 (file)
index 0000000..36b6973
--- /dev/null
@@ -0,0 +1,213 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009-2013 Parisson SARL
+# Copyright (c) 2009-2012 Guillaume Pellerin <pellerin@parisson.com>
+# Copyright (C) 2008 MUSIC TECHNOLOGY GROUP (MTG)
+#                    UNIVERSITAT POMPEU FABRA
+
+# 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/>.
+
+
+# Authors:
+#   Bram de Jong <bram.dejong at domain.com where domain in gmail>
+#   Guillaume Pellerin <yomguy@parisson.com>
+
+try:
+    from PIL import ImageFilter, ImageChops, Image, ImageDraw, ImageColor, ImageEnhance
+except ImportError:
+    import ImageFilter, ImageChops, Image, ImageDraw, ImageColor, ImageEnhance
+
+import numpy
+
+
+def interpolate_colors(colors, flat=False, num_colors=256):
+    """ Given a list of colors, create a larger list of colors interpolating
+    the first one. If flatten is True a list of numers will be returned. If
+    False, a list of (r,g,b) tuples. num_colors is the number of colors wanted
+    in the final list """
+
+    palette = []
+
+    for i in range(num_colors):
+        index = (i * (len(colors) - 1))/(num_colors - 1.0)
+        index_int = int(index)
+        alpha = index - float(index_int)
+
+        if alpha > 0:
+            r = (1.0 - alpha) * colors[index_int][0] + alpha * colors[index_int + 1][0]
+            g = (1.0 - alpha) * colors[index_int][1] + alpha * colors[index_int + 1][1]
+            b = (1.0 - alpha) * colors[index_int][2] + alpha * colors[index_int + 1][2]
+        else:
+            r = (1.0 - alpha) * colors[index_int][0]
+            g = (1.0 - alpha) * colors[index_int][1]
+            b = (1.0 - alpha) * colors[index_int][2]
+
+        if flat:
+            palette.extend((int(r), int(g), int(b)))
+        else:
+            palette.append((int(r), int(g), int(b)))
+
+    return palette
+
+
+class Noise(object):
+    """A class that mimics audiolab.sndfile but generates noise instead of reading
+    a wave file. Additionally it can be told to have a "broken" header and thus crashing
+    in the middle of the file. Also useful for testing ultra-short files of 20 samples."""
+
+    def __init__(self, num_frames, has_broken_header=False):
+        self.seekpoint = 0
+        self.num_frames = num_frames
+        self.has_broken_header = has_broken_header
+
+    def seek(self, seekpoint):
+        self.seekpoint = seekpoint
+
+    def get_nframes(self):
+        return self.num_frames
+
+    def get_samplerate(self):
+        return 44100
+
+    def get_channels(self):
+        return 1
+
+    def read_frames(self, frames_to_read):
+        if self.has_broken_header and self.seekpoint + frames_to_read > self.num_frames / 2:
+            raise IOError()
+
+        num_frames_left = self.num_frames - self.seekpoint
+        if num_frames_left < frames_to_read:
+            will_read = num_frames_left
+        else:
+            will_read = frames_to_read
+        self.seekpoint += will_read
+        return numpy.random.random(will_read)*2 - 1
+
+
+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.
+
+    Parameters
+    ----------
+    x : numpy.array
+        the input signal
+    window_len : int
+        the dimension of the smoothing window
+    window : str
+        the type of window from 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'
+        flat window will produce a moving average smoothing.
+
+    Returns
+    -------
+    The smoothed signal
+
+    See Also
+    -------
+
+    numpy.hanning, numpy.hamming, numpy.bartlett, numpy.blackman, numpy.convolve
+    scipy.signal.lfilter
+
+
+    Examples
+    --------
+
+    >>> import numpy as np
+    >>> from timeside.grapher import smooth
+    >>> t = np.arange(-2,2,0.1)
+    >>> x = np.sin(t)+np.random.randn(len(t))*0.1
+    >>> y = smooth(x)
+    >>> import matplotlib.pyplot as plt
+    >>> plt.plot(x) # doctest: +ELLIPSIS
+    [<matplotlib.lines.Line2D object at 0x...>]
+    >>> plt.plot(y) # doctest: +ELLIPSIS
+    [<matplotlib.lines.Line2D object at 0x...>]
+    >>> plt.legend(['Source signal', 'Smoothed signal']) # doctest: +ELLIPSIS
+    <matplotlib.legend.Legend object at 0x...>
+    >>> plt.show() # doctest: +SKIP
+    """
+
+    # 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]]
+
+    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]
+
+
+def reduce_opacity(im, opacity):
+    """Returns an image with reduced opacity."""
+    assert opacity >= 0 and opacity <= 1
+    if im.mode != 'RGBA':
+        im = im.convert('RGBA')
+    else:
+        im = im.copy()
+    alpha = im.split()[3]
+    alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
+    im.putalpha(alpha)
+    return im
+
+
+def im_watermark(im, inputtext, font=None, color=None, opacity=.6, margin=(30,30)):
+    """
+    imprints a PIL image with the indicated text in lower-right corner
+    """
+    if im.mode != "RGBA":
+        im = im.convert("RGBA")
+    textlayer = Image.new("RGBA", im.size, (0,0,0,0))
+    textdraw = ImageDraw.Draw(textlayer)
+    textsize = textdraw.textsize(inputtext, font=font)
+    textpos = [im.size[i]-textsize[i]-margin[i] for i in [0,1]]
+    textdraw.text(textpos, inputtext, font=font, fill=color)
+    if opacity != 1:
+        textlayer = reduce_opacity(textlayer,opacity)
+    return Image.composite(textlayer, im, textlayer)