In [1]:
import re
import os
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
%matplotlib inline

In [2]:
def mean(values):
    return float(sum(values))/len(values)

In [3]:
import serial

def transfer_test(data, dev='/dev/ttyACM0'):
    """Send numpy array over serial, return bytes written
    # TODO: Time taken to send for quick benchmarking!"""
    with serial.Serial(dev, writeTimeout=0) as ser:
        return ser.write(data)

PJRC's receive test

(host in C, variable buffer size, receiving in 64 Byte chunks)

Anything below 64 bytes is not a full USB packet and waits for transmission. Above, full speed is achieved.


In [4]:
result_path = '../src/USB_Virtual_Serial_Rcv_Speed_Test/usb_serial_receive/host_software/'
print [f for f in os.listdir(result_path) if f.endswith('.txt')]


['result_readbytes_overhead.txt', 'result_standard.txt', 'result_readbytes_spi4teensy.txt', 'result_readbytes.txt']

In [5]:
def read_result(filename):
    results = {}
    current_blocksize = None
    with open(os.path.join(result_path, filename)) as f:
        for line in f.readlines():
            if line.startswith('port'):
                current_blocksize = int(re.search('(?:size.)(\d*)', line).groups()[0])
                results[current_blocksize] = []
            else:
                results[current_blocksize].append(int(line[:-4].strip())/1000.)
    return results

In [6]:
# Example:  
results = read_result('result_readbytes.txt')
for bs in sorted(results.keys()):
    speeds = results[bs]
    print "{bs:4d}B blocks: {avg:4.0f}±{sem:.0f} KB/s".format(bs=bs, avg=mean(speeds), sem=stats.sem(speeds))


   1B blocks:   16±0 KB/s
   2B blocks:   32±0 KB/s
   4B blocks:   64±0 KB/s
   8B blocks:  128±0 KB/s
  16B blocks:  256±0 KB/s
  32B blocks:  512±1 KB/s
  64B blocks:  959±2 KB/s
 128B blocks:  961±2 KB/s
 256B blocks:  961±2 KB/s
 512B blocks:  959±3 KB/s
1024B blocks:  961±2 KB/s
2048B blocks:  961±3 KB/s
4096B blocks:  961±3 KB/s
8192B blocks:  959±2 KB/s

In [7]:
# Standard
sizes, speeds_standard = zip(*[(k, mean(v)) for k, v in read_result('result_standard.txt').items()])

# ReadBytes
sizes, speeds_readbytes = zip(*[(k, mean(v)) for k, v in read_result('result_readbytes.txt').items()])

# Readbytes+8us overhead per transferred SPI packet (worst case scenario?)
sizes, speeds_readbytes_oh = zip(*[(k, mean(v)) for k, v in read_result('result_readbytes_overhead.txt').items()])

# ReadBytes+spi4teensy on 8 channels
sizes, speeds_readbytes_spi = zip(*[(k, mean(v)) for k, v in read_result('result_readbytes_spi4teensy.txt').items()])

In [8]:
fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(10, 5))

axes.semilogx(sizes, speeds_standard, 'gx', basex=2, label='Standard')
axes.semilogx(sizes, speeds_readbytes, 'rx', basex=2, label='ReadBytes')
axes.semilogx(sizes, speeds_readbytes_oh, 'bx', basex=2, label='ReadBytes+OH')
axes.semilogx(sizes, speeds_readbytes_spi, 'k+', basex=2, label='ReadBytes+spi4teensy@8channels')
axes.set_xlabel('Block size [B]')
axes.set_ylabel('Transfer speed [kB/s]')
axes.legend(loc=2)
axes.set_xlim((min(sizes)/2., max(sizes)*2))

fig.tight_layout()
#TODO: use individual values, make stats + error bars



In [9]:
n = int(1e6)
data = data=''.join([chr(i%256) for i in range(n)])
t = %timeit -o -q transfer_test(data)
print "{:.1f} KB, {:.2f} s, {:.1f} KB/s".format(len(data)/1000., mean(t.all_runs), len(data)/1000./mean(t.all_runs))


1000.0 KB, 1.81 s, 552.4 KB/s

Send arbitrary signals


In [10]:
n_val = 4096
max_val = 4096
# cosines
cosines = ((np.cos(np.linspace(-np.pi, np.pi, num=n_val))+1)*(max_val/2)).astype('uint16')

# noise
noise = (np.random.rand(n_val)*max_val).astype('uint16')

# ramps
ramps = np.linspace(0, max_val, n_val).astype('uint16')

# squares
hi = np.ones(n_val/4, dtype='uint16')*max_val-1
lo = np.zeros_like(hi)
squares = np.tile(np.hstack((hi, lo)), 2)

# all together
arr = np.dstack((cosines, noise, ramps, squares, \
                 cosines, noise, ramps, squares, \
                 cosines, noise, ramps, squares, \
                 cosines, noise, ramps, squares)).flatten()
fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(13, 8))
axes[0].set_xlim((0, cosines.size))
axes[0].plot(cosines, label='cosine');
axes[0].plot(noise, label='random');
axes[0].plot(ramps, label='ramp');
axes[0].plot(squares, label='square');
axes[0].legend()

axes[1].set_xlim((0, arr.size))
axes[1].plot(arr);
fig.tight_layout()



In [21]:
n = 500
data = np.tile(arr, n).view(np.uint8)
t = %timeit -o -q -n 1 -r 1 tx = transfer_test(data)
print "{:.1f} KB, {:.2f} s, {:.1f} KB/s".format(arr.nbytes/1000.*n, mean(t.all_runs), arr.nbytes/1000.*n/mean(t.all_runs))


---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-21-e3f5dd7d7471> in <module>()
      1 n = 500
      2 data = np.tile(arr, n).view(np.uint8)
----> 3 t = get_ipython().magic(u'timeit -o -q -n 1 -r 1 tx = transfer_test(data)')
      4 print "{:.1f} KB, {:.2f} s, {:.1f} KB/s".format(arr.nbytes/1000.*n, mean(t.all_runs), arr.nbytes/1000.*n/mean(t.all_runs))

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/IPython/core/interactiveshell.pyc in magic(self, arg_s)
   2302         magic_name, _, magic_arg_s = arg_s.partition(' ')
   2303         magic_name = magic_name.lstrip(prefilter.ESC_MAGIC)
-> 2304         return self.run_line_magic(magic_name, magic_arg_s)
   2305 
   2306     #-------------------------------------------------------------------------

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/IPython/core/interactiveshell.pyc in run_line_magic(self, magic_name, line)
   2223                 kwargs['local_ns'] = sys._getframe(stack_depth).f_locals
   2224             with self.builtin_trap:
-> 2225                 result = fn(*args,**kwargs)
   2226             return result
   2227 

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/IPython/core/magics/execution.pyc in timeit(self, line, cell)

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/IPython/core/magic.pyc in <lambda>(f, *a, **k)
    191     # but it's overkill for just that one bit of state.
    192     def magic_deco(arg):
--> 193         call = lambda f, *a, **k: f(*a, **k)
    194 
    195         if callable(arg):

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/IPython/core/magics/execution.pyc in timeit(self, line, cell)
   1039                     break
   1040                 number *= 10
-> 1041         all_runs = timer.repeat(repeat, number)
   1042         best = min(all_runs) / number
   1043         if not quiet :

/usr/lib/python2.7/timeit.py in repeat(self, repeat, number)
    221         r = []
    222         for i in range(repeat):
--> 223             t = self.timeit(number)
    224             r.append(t)
    225         return r

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/IPython/core/magics/execution.pyc in timeit(self, number)
    130         gc.disable()
    131         try:
--> 132             timing = self.inner(it, self.timer)
    133         finally:
    134             if gcold:

<magic-timeit> in inner(_it, _timer)

<ipython-input-3-29f58f7269d3> in transfer_test(data, dev)
      5     # TODO: Time taken to send for quick benchmarking!"""
      6     with serial.Serial(dev) as ser:
----> 7         return ser.write(data)

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/serial/serialposix.pyc in write(self, data)
    489         """Output the given string over the serial port."""
    490         if not self._isOpen: raise portNotOpenError
--> 491         d = to_bytes(data)
    492         tx_len = len(d)
    493         if self._writeTimeout is not None and self._writeTimeout > 0:

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/serial/serialutil.pyc in to_bytes(seq)
     74         b = bytearray()
     75         for item in seq:
---> 76             b.append(item)  # this one handles int and str for our emulation and ints for Python 3.x
     77         return bytes(b)
     78 

KeyboardInterrupt: 

In [19]:
t = %timeit -o -q -n 1 -r 1 tx = transfer_test(data)
print "{:.1f} KB, {:.2f} s, {:.1f} KB/s".format(arr.nbytes/1000.*n, mean(t.all_runs), arr.nbytes/1000.*n/mean(t.all_runs))


13107.2 KB, 32.66 s, 401.3 KB/s

Send "neural" data

Using Lynn's data set from the Klusters2 example


In [ ]:
data_path = "../data/lynn/lynn.dat"
data_float = np.fromfile(data_path, dtype='(64,)i2').astype(np.float)

In [ ]:
# normalize the array to 12bit
data_float -= data_float.min()
data_float /= data_float.max()
data_float *= (2**12-1)
data_scaled = data_float.astype(np.uint16)
print data_scaled.min(), data_scaled.max()

In [ ]:
fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(13, 7))
for n in range(0, 64, 4):
    axes.plot(data_scaled[0:20000, n]+n*70, label="Channel %d"%n);
plt.legend()
fig.tight_layout()

In [ ]:
print "first channel :", data_scaled[0,0:3]
print "second channel:", data_scaled[8,0:3]
print "interleaved   :", data_scaled[(0, 8), 0:3].transpose().flatten()

In [136]:
n = 5
data = np.tile(data_scaled[:, 0:64:4].transpose().flatten(), n).tobytes()
len(data)
transfer_test(data)


---------------------------------------------------------------------------
OSError                                   Traceback (most recent call last)
<ipython-input-136-88d76161a5bf> in <module>()
      2 data = np.tile(data_scaled[:, 0:64:4].transpose().flatten(), n).tobytes()
      3 len(data)
----> 4 transfer_test(data)

<ipython-input-129-29f58f7269d3> in transfer_test(data, dev)
      4     """Send numpy array over serial, return bytes written
      5     # TODO: Time taken to send for quick benchmarking!"""
----> 6     with serial.Serial(dev) as ser:
      7         return ser.write(data)

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/serial/serialutil.pyc in __init__(self, port, baudrate, bytesize, parity, stopbits, timeout, xonxoff, rtscts, writeTimeout, dsrdtr, interCharTimeout)
    280 
    281         if port is not None:
--> 282             self.open()
    283 
    284     def isOpen(self):

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/serial/serialposix.pyc in open(self)
    287         # open
    288         try:
--> 289             self.fd = os.open(self.portstr, os.O_RDWR|os.O_NOCTTY|os.O_NONBLOCK)
    290         except IOError, msg:
    291             self.fd = None

OSError: [Errno 2] No such file or directory: '/dev/ttyACM0'

In [104]:
t = %timeit -q -o -n 1 -r 1 transfer_test(data);
print "{:.1f} KB, {:.2f} s, {:.1f} KB/s".format(data_scaled[:, 0:64:4].nbytes/1000.*n,
                                                mean(t.all_runs),
                                                data_scaled[:, 0:64:4].nbytes/1000.*n/mean(t.all_runs))


6912.0 KB, 22.14 s, 312.2 KB/s

In [18]:
type(data)


Out[18]:
numpy.ndarray

In [126]:
data


---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-126-6137cde4893c> in <module>()
----> 1 data

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/IPython/core/displayhook.pyc in __call__(self, result)
    236                 self.write_format_data(format_dict, md_dict)
    237                 self.log_output(format_dict)
--> 238             self.finish_displayhook()
    239 
    240     def cull_cache(self):

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/IPython/kernel/zmq/displayhook.pyc in finish_displayhook(self)
     70         sys.stderr.flush()
     71         if self.msg['content']['data']:
---> 72             self.session.send(self.pub_socket, self.msg, ident=self.topic)
     73         self.msg = None
     74 

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/IPython/kernel/zmq/session.pyc in send(self, stream, msg_or_type, content, parent, ident, buffers, track, header, metadata)
    647         if self.adapt_version:
    648             msg = adapt(msg, self.adapt_version)
--> 649         to_send = self.serialize(msg, ident)
    650         to_send.extend(buffers)
    651         longest = max([ len(s) for s in to_send ])

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/IPython/kernel/zmq/session.pyc in serialize(self, msg, ident)
    551             content = self.none
    552         elif isinstance(content, dict):
--> 553             content = self.pack(content)
    554         elif isinstance(content, bytes):
    555             # content is already packed, as in a relayed message

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/IPython/kernel/zmq/session.pyc in <lambda>(obj)
     83 # disallow nan, because it's not actually valid JSON
     84 json_packer = lambda obj: jsonapi.dumps(obj, default=date_default,
---> 85     ensure_ascii=False, allow_nan=False,
     86 )
     87 json_unpacker = lambda s: jsonapi.loads(s)

/home/reichler/.virtualenvs/scipy_base/local/lib/python2.7/site-packages/zmq/utils/jsonapi.pyc in dumps(o, **kwargs)
     38         kwargs['separators'] = (',', ':')
     39 
---> 40     s = jsonmod.dumps(o, **kwargs)
     41 
     42     if isinstance(s, unicode):

/usr/lib/python2.7/json/__init__.pyc in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, encoding, default, sort_keys, **kw)
    248         check_circular=check_circular, allow_nan=allow_nan, indent=indent,
    249         separators=separators, encoding=encoding, default=default,
--> 250         sort_keys=sort_keys, **kw).encode(obj)
    251 
    252 

/usr/lib/python2.7/json/encoder.pyc in encode(self, o)
    205         # exceptions aren't as detailed.  The list call should be roughly
    206         # equivalent to the PySequence_Fast that ''.join() would do.
--> 207         chunks = self.iterencode(o, _one_shot=True)
    208         if not isinstance(chunks, (list, tuple)):
    209             chunks = list(chunks)

/usr/lib/python2.7/json/encoder.pyc in iterencode(self, o, _one_shot)
    268                 self.key_separator, self.item_separator, self.sort_keys,
    269                 self.skipkeys, _one_shot)
--> 270         return _iterencode(o, 0)
    271 
    272 def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,

/usr/lib/python2.7/json/encoder.pyc in encode_basestring(s)
     37     def replace(match):
     38         return ESCAPE_DCT[match.group(0)]
---> 39     return '"' + ESCAPE.sub(replace, s) + '"'
     40 
     41 

/usr/lib/python2.7/json/encoder.pyc in replace(match)
     36     """
     37     def replace(match):
---> 38         return ESCAPE_DCT[match.group(0)]
     39     return '"' + ESCAPE.sub(replace, s) + '"'
     40 

KeyboardInterrupt: 

In [ ]: