Merge pull request #11 from romanz/py3

Python 3 support
This commit is contained in:
Roman Zeyde
2014-08-07 17:15:25 +03:00
17 changed files with 139 additions and 105 deletions

View File

@@ -1,6 +1,7 @@
language: python
python:
- "2.7"
- "3.3"
install:
- pip install .

View File

@@ -1,9 +1,10 @@
#!/usr/bin/env python
import numpy as np
import common
import config
import sigproc
import wave
from . import common
from . import config
from . import sigproc
from . import wave
Tsample = 1
t = np.arange(int(Tsample * config.Fs)) * config.Ts
@@ -22,6 +23,7 @@ def send():
except KeyboardInterrupt:
p.kill()
def recv():
p = wave.record('-', stdout=wave.sp.PIPE)
try:
@@ -36,13 +38,15 @@ def recv():
continue
x = x - np.mean(x)
c = np.abs(np.dot(x, sig)) / (np.sqrt(0.5 * len(x)) * sigproc.norm(x))
normalization_factor = np.sqrt(0.5 * len(x)) * sigproc.norm(x)
coherence = np.abs(np.dot(x, sig)) / normalization_factor
z = np.dot(x, sig.conj()) / (0.5 * len(x))
amp = np.abs(z)
amplitude = np.abs(z)
phase = np.angle(z)
peak = np.max(np.abs(x))
print('coherence={:.3f} amp={:.3f} phase={:.1f} peak={:.3f}'.format(
c, amp, phase * 180 / np.pi, peak))
fmt = 'coherence={:.3f} amplitude={:.3f} phase={:+.1f} peak={:.3f}'
print(fmt.format(coherence, amplitude, phase * 180 / np.pi, peak))
except KeyboardInterrupt:
p.kill()

View File

@@ -41,7 +41,7 @@ def load(fileobj):
def loads(data):
x = np.fromstring(data, dtype='int16')
x = np.frombuffer(data, dtype='int16')
x = x / scaling
return x
@@ -83,7 +83,7 @@ class Splitter(object):
while True:
if all(self.read):
try:
self.last = self.iterable.next()
self.last = next(self.iterable)
except StopIteration:
return
@@ -108,3 +108,9 @@ def icapture(iterable, result):
def take(iterable, n):
return np.array(list(itertools.islice(iterable, n)))
try:
izip = itertools.izip
except AttributeError:
izip = zip # Python 3

View File

@@ -1,7 +1,7 @@
''' Reed-Solomon CODEC. '''
from reedsolo import rs_encode_msg, rs_correct_msg
import common
from . import common
import logging
log = logging.getLogger(__name__)
@@ -17,7 +17,8 @@ def end_of_stream(size):
def encode(data, nsym=DEFAULT_NSYM):
chunk_size = BLOCK_SIZE - nsym - 1
for _, chunk in common.iterate(data, chunk_size, bytearray, truncate=False):
for _, chunk in common.iterate(data=data, size=chunk_size,
func=bytearray, truncate=False):
size = len(chunk)
if size < chunk_size:
padding = [0] * (chunk_size - size)

View File

@@ -1,8 +1,9 @@
import numpy as np
import itertools
import sampling
import sigproc
from . import sampling
from . import sigproc
from . import common
class Filter(object):
@@ -33,4 +34,4 @@ class FreqLoop(object):
self.gens.append(gen)
def __iter__(self):
return itertools.izip(*self.gens)
return common.izip(*self.gens)

View File

@@ -8,21 +8,24 @@ import time
import sys
import os
import bitarray
log = logging.getLogger(__name__)
import stream
import sigproc
import loop
import train
import common
import config
from . import stream
from . import sigproc
from . import loop
from . import train
from . import common
from . import config
from . import ecc
modem = sigproc.MODEM(config)
if os.environ.get('PYLAB') == '1':
import pylab
import show
from . import pylab
from . import show
WIDTH = np.floor(np.sqrt(len(modem.freqs)))
HEIGHT = np.ceil(len(modem.freqs) / float(WIDTH))
else:
@@ -190,7 +193,7 @@ def demodulate(symbols, filters, freqs, sampler):
stats['rx_start'] = time.time()
log.info('Demodulation started')
for i, block in enumerate(itertools.izip(*streams)): # block per frequency
for i, block in enumerate(common.izip(*streams)): # block per frequency
for bits in block:
stats['rx_bits'] = stats['rx_bits'] + len(bits)
yield bits
@@ -223,9 +226,6 @@ def receive(signal, freqs, gain=1.0):
def decode(bits_iterator):
import bitarray
import ecc
def blocks():
while True:
bits = itertools.islice(bits_iterator, 8 * ecc.BLOCK_SIZE)
@@ -238,11 +238,16 @@ def decode(bits_iterator):
return ecc.decode(blocks())
def iread(fd):
reader = stream.Reader(fd, data_type=common.loads)
return itertools.chain.from_iterable(reader)
def main(args):
log.info('Running MODEM @ {:.1f} kbps'.format(modem.modem_bps / 1e3))
signal = stream.iread(args.input)
signal = iread(args.input)
skipped = common.take(signal, args.skip)
log.debug('Skipping first %.3f seconds', len(skipped) / float(modem.baud))
@@ -282,8 +287,10 @@ if __name__ == '__main__':
p = argparse.ArgumentParser()
p.add_argument('--skip', type=int, default=100,
help='skip initial N samples, due to spurious spikes')
p.add_argument('-i', '--input', type=argparse.FileType('r'), default=sys.stdin)
p.add_argument('-o', '--output', type=argparse.FileType('w'), default=sys.stdout)
p.add_argument('-i', '--input', type=argparse.FileType('rb'),
default=sys.stdin)
p.add_argument('-o', '--output', type=argparse.FileType('wb'),
default=sys.stdout)
args = p.parse_args()
try:
main(args)

View File

@@ -49,6 +49,8 @@ class Sampler(object):
def next(self):
return self._sample() * self.gain
__next__ = next
def _sample(self):
offset = self.offset
# offset = k + (j / self.resolution)
@@ -58,7 +60,7 @@ class Sampler(object):
end = k + self.width
while self.index < end:
self.buff[:-1] = self.buff[1:]
self.buff[-1] = self.src.next() # throws StopIteration
self.buff[-1] = next(self.src) # throws StopIteration
self.index += 1
self.offset += self.freq

View File

@@ -7,19 +7,22 @@ import time
log = logging.getLogger(__name__)
import train
import wave
from . import train
from . import wave
import common
import config
import sigproc
from . import common
from . import config
from . import sigproc
from . import stream
from . import ecc
modem = sigproc.MODEM(config)
class Symbol(object):
t = np.arange(0, config.Nsym) * config.Ts
carrier = [np.exp(2j * np.pi * F * t) for F in modem.freqs]
def __init__(self):
t = np.arange(0, config.Nsym) * config.Ts
self.carrier = [np.exp(2j * np.pi * F * t) for F in modem.freqs]
sym = Symbol()
@@ -63,26 +66,7 @@ def modulate(fd, bits):
break
class Reader(object):
def __init__(self, fd, size):
self.fd = fd
self.size = size
self.total = 0
def next(self):
block = self.fd.read(self.size)
if block:
self.total += len(block)
return block
else:
raise StopIteration()
def __iter__(self):
return self
def main(args):
import ecc
log.info('Running MODEM @ {:.1f} kbps'.format(modem.modem_bps / 1e3))
# padding audio with silence
@@ -95,7 +79,7 @@ def main(args):
log.info('%.3f seconds of training audio',
training_size / wave.bytes_per_second)
reader = Reader(args.input, 64 << 10)
reader = stream.Reader(args.input, bufsize=(64 << 10), eof=True)
data = itertools.chain.from_iterable(reader)
encoded = itertools.chain.from_iterable(ecc.encode(data))
modulate(args.output, bits=common.to_bits(encoded))
@@ -115,7 +99,9 @@ if __name__ == '__main__':
p = argparse.ArgumentParser()
p.add_argument('--silence-start', type=float, default=1.0)
p.add_argument('--silence-stop', type=float, default=1.0)
p.add_argument('-i', '--input', type=argparse.FileType('r'), default=sys.stdin)
p.add_argument('-o', '--output', type=argparse.FileType('w'), default=sys.stdout)
p.add_argument('-i', '--input', type=argparse.FileType('rb'),
default=sys.stdin)
p.add_argument('-o', '--output', type=argparse.FileType('wb'),
default=sys.stdout)
args = p.parse_args()
main(args)

View File

@@ -1,8 +1,8 @@
import numpy as np
from numpy import linalg
import common
from config import Ts, Nsym
from . import common
from .config import Ts, Nsym
class Filter(object):

View File

@@ -1,43 +1,57 @@
import time
import itertools
import logging
import common
import wave
log = logging.getLogger(__name__)
class Timeout(Exception):
pass
class Reader(object):
SAMPLES = 4096
BUFSIZE = int(SAMPLES * wave.bytes_per_sample)
WAIT = 0.1
TIMEOUT = 2.0
def __init__(self, fd):
def __init__(self, fd, data_type=None, bufsize=4096,
eof=False, timeout=2.0, wait=0.2):
self.fd = fd
self.data_type = data_type if (data_type is not None) else lambda x: x
self.bufsize = bufsize
self.eof = eof
self.timeout = timeout
self.wait = wait
self.total = 0
self.check = None
def __iter__(self):
return self
def __next__(self):
return self.next()
def next(self):
block = bytearray()
finish_time = time.time() + self.TIMEOUT
if self.eof:
data = self.fd.read(self.bufsize)
if data:
self.total += len(data)
block.extend(data)
return block
else:
raise StopIteration()
finish_time = time.time() + self.timeout
while time.time() <= finish_time:
left = self.BUFSIZE - len(block)
left = self.bufsize - len(block)
data = self.fd.read(left)
if data:
self.total += len(data)
block.extend(data)
if len(block) == self.BUFSIZE:
values = common.loads(str(block))
if len(block) == self.bufsize:
values = self.data_type(block)
if self.check:
self.check(values)
return values
time.sleep(self.WAIT)
time.sleep(self.wait)
raise IOError('timeout')
def iread(fd):
return itertools.chain.from_iterable(Reader(fd))
raise Timeout(self.timeout)

View File

@@ -6,7 +6,7 @@ import logging
log = logging.getLogger(__name__)
import config
from . import config
Fs = int(config.Fs) # sampling rate
bits_per_sample = 16
@@ -27,7 +27,7 @@ def record(fname, **kwargs):
def launch(*args, **kwargs):
args = map(str, args)
args = list(map(str, args))
log.debug('$ %s', ' '.join(args))
p = sp.Popen(args=args, **kwargs)
p.stop = lambda: os.kill(p.pid, signal.SIGKILL)

View File

@@ -1,7 +1,7 @@
#!/bin/bash
killall -q aplay arecord
./calib.py send &
python -m amodem.calib send &
SENDER_PID=$!
./calib.py recv
python -m amodem.calib recv
kill -INT $SENDER_PID

View File

@@ -32,20 +32,20 @@ run_src dd if=/dev/urandom of=$TEST_DIR/data.send bs=125kB count=1 status=none
SRC_HASH=`run_src sha256sum $TEST_DIR/data.send`
# modulate data into audio file
run_src "./send.py <$TEST_DIR/data.send >$TEST_DIR/audio.send"
run_src "python -m amodem.send -i $TEST_DIR/data.send -o $TEST_DIR/audio.send"
# stop old recording and start a new one
run_src killall -q aplay || true
run_dst killall -q arecord || true
run_dst "./wave.py record $TEST_DIR/audio.recv" &
run_dst "python -m amodem.wave record $TEST_DIR/audio.recv" &
sleep 1 # let audio.recv be filled
# play the modulated data
run_src ./wave.py play $TEST_DIR/audio.send &
run_src "python -m amodem.wave play $TEST_DIR/audio.send" &
# start the receiever
run_dst "./recv.py <$TEST_DIR/audio.recv >$TEST_DIR/data.recv"
run_dst "python -m amodem.recv -i $TEST_DIR/audio.recv -o $TEST_DIR/data.recv"
# stop recording after playing is over
run_src killall -q aplay || true

View File

@@ -25,13 +25,13 @@ def test_iterate():
def test_split():
L = [(i*2, i*2+1) for i in range(10)]
iters = common.split(L, n=2)
assert zip(*iters) == L
assert list(zip(*iters)) == L
for i in [0, 1]:
iters = common.split(L, n=2)
iters[i].next()
next(iters[i])
try:
iters[i].next()
next(iters[i])
assert False
except IndexError as e:
assert e.args == (i,)
@@ -43,8 +43,8 @@ def test_icapture():
z = []
for i in common.icapture(x, result=y):
z.append(i)
assert x == y
assert x == z
assert list(x) == y
assert list(x) == z
def test_dumps_loads():

View File

@@ -1,5 +1,9 @@
import os
from cStringIO import StringIO
try:
from cStringIO import StringIO as BytesIO
except ImportError:
from io import BytesIO # Python 3
import numpy as np
from amodem import send
@@ -10,40 +14,48 @@ import logging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)-12s %(message)s')
class Args(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def run(size, chan):
tx_data = os.urandom(size)
tx_audio = StringIO()
send.main(Args(silence_start=1, silence_stop=1, input=StringIO(tx_data), output=tx_audio))
tx_audio = BytesIO()
send.main(Args(silence_start=1, silence_stop=1,
input=BytesIO(tx_data), output=tx_audio))
data = tx_audio.getvalue()
data = common.loads(data)
data = chan(data)
data = common.dumps(data * 1j)
rx_audio = StringIO(data)
rx_audio = BytesIO(data)
rx_data = StringIO()
rx_data = BytesIO()
recv.main(Args(skip=100, input=rx_audio, output=rx_data))
rx_data = rx_data.getvalue()
assert rx_data == tx_data
def test_small():
run(1024, lambda x: x)
def test_large():
run(54321, lambda x: x)
def test_attenuation():
run(5120, lambda x: x * 0.1)
def test_low_noise():
r = np.random.RandomState(seed=0)
run(5120, lambda x: x + r.normal(size=len(x), scale=0.0001))
def test_medium_noise():
r = np.random.RandomState(seed=0)
run(5120, lambda x: x + r.normal(size=len(x), scale=0.001))

View File

@@ -1,14 +1,14 @@
from amodem import stream
import subprocess as sp
script = r"""
script = br"""
import sys
import time
import os
while True:
time.sleep(0.1)
sys.stdout.write(b'\x00' * 6400)
sys.stdout.write('\x00' * 6400)
sys.stderr.write('.')
"""
@@ -19,17 +19,17 @@ def test_read():
p.stdin.close()
f = stream.Reader(p.stdout)
result = zip(range(10), f)
result = list(zip(range(10), f))
p.kill()
j = 0
for i, buf in result:
assert i == j
assert len(buf) == f.SAMPLES
assert len(buf) == f.bufsize
j += 1
try:
for buf in f:
pass
except IOError as e:
assert str(e) == 'timeout'
except stream.Timeout as e:
assert e.args == (f.timeout,)