Created
December 24, 2015 14:11
-
-
Save dmishin/91dd2553c3d7dfc1478a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
#This is Python3 executable | |
import os.path | |
from numpy import array, hstack, vstack, clip | |
from PIL import Image | |
import numpy as np | |
import subprocess | |
import re | |
#SOX_EXEC = r"C:\Program Files\sox-14-4-2\sox.exe" | |
SOX_EXEC = r"sox" | |
def hilbert_indices(N): | |
m = array([[0]], dtype=np.int32) | |
for i in range(N): | |
d = 4**i | |
m = vstack( (hstack( (m.T, m.T[::-1, ::-1] + 3*d )), | |
hstack( (m+d, m+2*d) ) ) ) | |
return m | |
def palette_gray(): | |
return [x for x in range(256) for _chan in range(3) ] | |
def palette_colored(gamma): | |
r = [(i/127.0)**gamma for i in range(1,128)] | |
palette = [(p, p*0.7, 0) for p in r][::-1] + [(0,0,0)] + \ | |
[(0, p*0.7, p) for p in r] | |
palette.append((0,0.7,1)) #make 256 samples | |
return [int(p*255) for rgb in palette for p in rgb ] | |
def data2image(img, palette): | |
img = Image.fromarray( np.squeeze(img).astype(np.uint8)+127, "P") | |
img.putpalette(palette) | |
return img | |
def soxversion(): | |
return subprocess.check_output([SOX_EXEC, "--version"]).strip().decode('mbcs','ignore') | |
def soxinfo( filename ): | |
info_args = [SOX_EXEC, | |
"--info", | |
filename] | |
info = subprocess.check_output(info_args) | |
channels = int(re.search(br"Channels\s*:\s*(\d+)", info ).group(1)) | |
bits = int(re.search(br"Precision\s*:\s*(\d+)-bit", info ).group(1)) | |
rate = int(re.search(br"Sample Rate\s*:\s*(\d+)", info ).group(1)) | |
samples = int(re.search(br"(\d+)\s+samples").group(1)) | |
return channels, bits, rate, samples | |
def read_sound( filename, out_byps=1, trim=None ): | |
"""Read sound file, using SOX""" | |
cmd = [SOX_EXEC, | |
filename, # input filename | |
'-t','raw', # output file type raw | |
'-e','signed-integer', # output encode as signed ints | |
'-L', # output little endin | |
'-b',str(out_byps*8), # output bytes per sample | |
'-c', '1', # mono sound | |
'-', # output to stdout | |
] | |
if trim is not None: | |
# only extract requested part | |
cmd.extend(['trim', str(trim[0])+'s', str(trim[1])+'s'] ) | |
return np.fromstring(subprocess.check_output(cmd),'<i%d'%(out_byps)) | |
def ilog2(x): | |
log = 0 | |
while x > 1: | |
log += 1 | |
x = x // 2 | |
return log | |
def normalize_int8_array( data ): | |
"""Scale int8 array to span at least +- 127""" | |
if data.dtype != np.int8: | |
raise TypeError("Wrong data type, must be int8") | |
amplitude = max( data.max(), -data.min() ) | |
if amplitude < 127: | |
return (data.astype(np.float32)*(1.0/amplitude*127)).astype(np.int8) | |
else: | |
return data | |
def data_array2hilbert_image(data, palette): | |
"""Bend linear data array to a Hilbert curve, apply palette and return image. Data must have type int8""" | |
size = max(data.shape) | |
print ("Read sound samples:", size) | |
n = ilog2(size)//2 | |
print ("Hilbert curve order:", n) | |
#now n contains hilbert curve order | |
return data2image( data[hilbert_indices( n )], | |
palette=palette) | |
if __name__=="__main__": | |
import sys | |
import argparse | |
PALETTES = { | |
'gray': lambda: palette_gray(), | |
'color-bright': lambda: palette_colored(0.5), | |
'color': lambda: palette_colored(1.0), | |
'color-dark': lambda: palette_colored(1.5) | |
} | |
parser = argparse.ArgumentParser() | |
parser.add_argument("audiofile", | |
help="Input audio file, any fomat supported by SOX.") | |
parser.add_argument("imgfile", | |
nargs='?', | |
help="Output image file. If not specified, image is shown. Image format must be PNG or GIF, image will be saved in indexed color mode.") | |
parser.add_argument("-t", "--trim", | |
metavar="SAMPLE", | |
help="Start and end samples to take from audio.", type=int, nargs=2) | |
parser.add_argument("-p", "--palette", | |
default="color", | |
choices=PALETTES.keys(), | |
help="Name of the palette") | |
parser.add_argument("--sox", metavar="PATH", help="path to the SOX executable", default=SOX_EXEC) | |
args = parser.parse_args() | |
SOX_EXEC = args.sox | |
try: | |
print("Detecting SOX version...") | |
print(soxversion()) | |
except Exception as err: | |
print("Failed to run {SOX_EXEC}".format(SOX_EXEC=SOX_EXEC)) | |
print("If SOX is installed, edit this script and put correct path. If not, install it.") | |
print(err) | |
exit(1) | |
#read data and convert it to image | |
image = data_array2hilbert_image\ | |
( normalize_int8_array(read_sound( args.audiofile, | |
trim = args.trim or None, | |
out_byps=1 )), | |
palette=PALETTES[args.palette]()) | |
if args.imgfile is None: | |
image.show() | |
else: | |
image.save(args.imgfile) | |
################################################################################ | |
# Tests | |
################################################################################ | |
if __name__!="__main__": | |
import unittest | |
class TestHilbertIndex(unittest.TestCase): | |
def test_hilbert_indices0(self): | |
m = hilbert_indices(0) | |
me = array( [[0]], dtype=np.int ) | |
self.assertEqual( m, me ) | |
def test_hilbert_indices1(self): | |
m = hilbert_indices(1) | |
me = array( [[0,3], | |
[1,2]], | |
dtype=np.int ) | |
self.assertTrue( (m == me).all() ) | |
def test_hilbert_indices2(self): | |
m = hilbert_indices(2) | |
me = array( [[0,1,14,15], | |
[3,2,13,12], | |
[4,7,8,11], | |
[5,6,9,10]], | |
dtype=np.int ) | |
self.assertTrue( (m == me).all(), "returned: \n{m}, expected: \n{me}".format(m=m,me=me) ) | |
def test_hilbert_indicesN(self): | |
N = 6 | |
m = hilbert_indices(N) | |
x,y = 0,0 | |
for i in range(4**N-1): | |
self.assertEqual( m[x,y], i, "m[{x},{y}]=={i}".format(**locals())) | |
found = False | |
for dx, dy in [(-1,0),(1,0),(0,1),(0,-1)]: | |
x1 = x+dx | |
y1 = y+dy | |
if x1 < 0 or x1 >= 2**N: continue | |
if y1 < 0 or y1 >= 2**N: continue | |
if m[x1,y1] == i+1: | |
found = True | |
break | |
self.assertTrue(found, "not found neighbor {i} for {x}, {y}".format(**locals())) | |
x,y = x1,y1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Script to convert audio files to images, by wrapping wave profile along Hilbert curve.
Requires