Skip to content
Permalink
Browse files

eml-window-function: Tool for generating FFT/filter windows tables

  • Loading branch information...
jonnor committed Mar 1, 2019
1 parent a619b8b commit 89a280f70dd6e50f58d0692ffd5c9dfc30581546
Showing with 205 additions and 25 deletions.
  1. +21 −0 emlearn/cgen.py
  2. +8 −25 emlearn/net.py
  3. +76 −0 examples/window-function.py
  4. +3 −0 setup.py
  5. +1 −0 test/.gitignore
  6. +96 −0 test/test_window_function.py
@@ -0,0 +1,21 @@


def struct_init(*args):
return '{ ' + ', '.join(str(a) for a in args) + ' }'


def constant(val, dtype='float'):
if dtype == 'float':
return "{:f}f".format(val)
else:
return str(val)


def array_declare(name, size, dtype='float', modifiers='static const',
values=None, end='', indent=''):
init = ''
if values is not None:
init_values = ', '.join(constant(v, dtype) for v in values)
init = ' = {{ {init_values} }}'.format(**locals())

return '{indent}{modifiers} {dtype} {name}[{size}]{init};{end}'.format(**locals())
@@ -1,5 +1,5 @@

from . import common
from . import common, cgen
import eml_net

import numpy
@@ -56,31 +56,14 @@ def save(self, name=None, file=None):

return code

def c_struct_init(*args):
return '{ ' + ', '.join(str(a) for a in args) + ' }'

def c_constant(val, dtype='float'):
if dtype == 'float':
return str(val)+'f'
else:
return str(val)

def c_array_declare(name, size, dtype='float', modifiers='static const',
values=None, end='', indent=''):
init = ''
if values is not None:
init_values = ', '.join(c_constant(v, dtype) for v in values)
init = ' = {{ {init_values} }}'.format(**locals())

return '{indent}{modifiers} {dtype} {name}[{size}]{init};{end}'.format(**locals())

def c_generate_net(activations, weights, biases, prefix):
def init_net(name, n_layers, layers_name, buf1_name, buf2_name, buf_length):
init = c_struct_init(n_layers, layers_name, buf1_name, buf2_name, buf_length)
init = cgen.struct_init(n_layers, layers_name, buf1_name, buf2_name, buf_length)
o = 'static EmlNet {name} = {init};'.format(**locals())
return o
def init_layer(name, n_outputs, n_inputs, weigths_name, biases_name, activation_func):
init = c_struct_init(n_outputs, n_inputs, weights_name, biases_name, activation_func)
init = cgen.struct_init(n_outputs, n_inputs, weights_name, biases_name, activation_func)
return init

buffer_sizes = [ w.shape[0] for w in weights ] + [ w.shape[1] for w in weights ]
@@ -109,19 +92,19 @@ def init_layer(name, n_outputs, n_inputs, weigths_name, biases_name, activation_
layer_name = '{prefix}_layer{layer_no}'.format(**locals())

weight_values = numpy.array(l_weights).flatten(order='C')
weights_arr = c_array_declare(weights_name, n_in*n_out, values=weight_values)
weights_arr = cgen.array_declare(weights_name, n_in * n_out, values=weight_values)
layer_lines.append(weights_arr)
bias_values = l_bias
biases_arr = c_array_declare(biases_name, len(l_bias), values=bias_values)
biases_arr = cgen.array_declare(biases_name, len(l_bias), values=bias_values)
layer_lines.append(biases_arr)

l = init_layer(layer_name, n_out, n_in, weights_name, biases_name, activation_func)
layers.append('\n'+l)

net_lines = [
c_array_declare(buf1_name, buffer_size, modifiers='static'),
c_array_declare(buf2_name, buffer_size, modifiers='static'),
c_array_declare(layers_name, n_layers, dtype='EmlNetLayer', values=layers),
cgen.array_declare(buf1_name, buffer_size, modifiers='static'),
cgen.array_declare(buf2_name, buffer_size, modifiers='static'),
cgen.array_declare(layers_name, n_layers, dtype='EmlNetLayer', values=layers),
init_net(prefix, n_layers, layers_name, buf1_name, buf2_name, buffer_size),
]

@@ -0,0 +1,76 @@

"""eml-window-function: Generating C code for window functions
Part of the emlearn project: https://emlearn.org
Redistributable under the MIT license
"""

import argparse
import textwrap

import scipy.signal

import emlearn.cgen

# Supports everything without parameters in scipy.signal.get_window
_known = 'boxcar, triang, blackman, hamming, hann, bartlett, flattop, parzen, bohman, blackmanharris, nuttall, barthann'
known_window_types = tuple(_known.split(', '))


def parse(args=None):
parser = argparse.ArgumentParser(description='Generate lookup table for window functions')
a = parser.add_argument

a('--window', type=str, default='hann',
help='Window function to use. Supported: \n' + '|'.join(known_window_types))
a('--length', type=int, default=1024,
help='Number of coefficients in window')
a('--symmetric', default=False, action='store_true',
help='Whether to use a symmetric window. Defaults to False, normal for FFT')
a('--name', type=str, default='',
help='Name of the generate C array')
a('--out', type=str, default='',
help='Output file. Default: $name.h')
a('--linewrap', type=int, default=70,
help='Maximum width of lines')

parsed = parser.parse_args(args)
return parsed


def window_function(name, window_type, length, fft_mode, linewrap):
window = scipy.signal.get_window(window_type, length, fftbins=fft_mode)
gen = emlearn.cgen.array_declare(name, length, values=window)
w = textwrap.wrap(gen, linewrap)
wrapped = '\n'.join(w)
return wrapped


def main():
args = parse()

window_type = args.window
length = args.length
fft_mode = not args.symmetric
name = args.name
out = args.out
if not name:
name = '_'.join([window_type, str(length), 'lut'])
if not out:
out = name+'.h'

if window_type not in known_window_types:
print('Warning: Unknown window type {}. Known:\n {}'.format(window_type, known_window_types))

preamble = '// This file was generated with emlearn using eml-window-function\n\n'

wrapped = window_function(name, window_type, length, fft_mode, args.linewrap)
wrapped = preamble + wrapped

with open(out, 'w') as f:
f.write(wrapped)
print('Wrote to', out)


if __name__ == '__main__':
main()
@@ -159,6 +159,9 @@ def read_readme():
long_description=read_readme(),
long_description_content_type="text/markdown",
packages=['emlearn'],
scripts={
'examples/window-function.py': 'eml-window-function',
},
ext_modules=ext_modules,
include_package_data=True,
package_data = {
@@ -0,0 +1 @@
out/
@@ -0,0 +1,96 @@

import os.path
import subprocess
import json

import numpy

examples_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../examples'))


def run_window_function(options):
path = os.path.join(examples_dir, 'window-function.py')
args = ['python3', path]

for key, value in options.items():
args.append('--{}={}'.format(key, value))

stdout = subprocess.check_output(' '.join(args), shell=True)
return stdout.decode('utf-8')


def run_extract(include, name, length, workdir):
template_prog = """
#include <stdio.h>
#include "{include}"
void print_json(const float *arr, int length) {{
// write as JSON array
printf("[");
for (int i=0; i<length; i++) {{
printf("%f%s", arr[i], (i != length-1) ? ", " : "");
}}
printf("]");
}}
int main() {{
const float *arr = {name};
const int length = {length};
print_json(arr, length);
}}
"""

prog_path = os.path.join(workdir, 'test_' + name)
code_path = prog_path + '.c'

params = dict(include=include, name=name, length=length)
prog = template_prog.format(**params)
with open(code_path, 'w') as f:
f.write(prog)

# compile
subprocess.check_call(['gcc', code_path, '-o', prog_path])

stdout = subprocess.check_output([prog_path])
arr = json.loads(stdout)
return arr


def window_function_test(file_path, args):
out_dir = os.path.dirname(file_path)
if not os.path.exists(out_dir):
os.makedirs(out_dir)
if os.path.exists(file_path):
os.remove(file_path)

stdout = run_window_function(args)

assert file_path in stdout, 'output filename should be mentioned on stdout'
assert os.path.exists(file_path), 'output file should have been written'

with open(file_path, 'r') as f:
contents = f.read()
assert args['name'] in contents
assert str(args['length']) in contents

# check it compiles
arr = run_extract(os.path.abspath(file_path), args['name'], args['length'], out_dir)
return arr


def test_window_function_hann():

file_path = 'tests/out/window_func.h'
args = dict(
window='hann',
length=512,
name='some_name_for_array',
out=file_path,
)

arr = window_function_test(file_path, args)
# Hann has sum of coefficients == half of N
numpy.testing.assert_allclose(numpy.sum(arr), args['length']/2)

0 comments on commit 89a280f

Please sign in to comment.
You can’t perform that action at this time.