sccn / lsl_archived

Multi-modal time-synched data transmission over local network
242 stars 134 forks source link

How to stream multi-channel time-frequency data (a.k.a. pls define "multiplexing") #108

Closed cboulay closed 8 years ago

cboulay commented 8 years ago

Using pylsl, I'm trying to send time-frequency data through an outlet. My current approach is to create nFreqs_by_nChannels outlet channels and then collapse my data across those dimensions before sending.

However, the pylsl docstring for push_chunk says that the data argument can be a "list of multiplexed values". What is a multiplexed value?

chkothe commented 8 years ago

Hi, basically this push_chunk function is just a more efficient way to send a whole chunk at once instead of sample by sample to reduce interpreter and memalloc overhead. Multiplexed here means that for N samples and M channels you send a list of length M*N that has first all channel values for sample 1, then all channel values for sample 2, etc. On Apr 7, 2016 1:17 PM, "Chadwick Boulay" notifications@github.com wrote:

Using pylsl, I'm trying to send time-frequency data through an outlet. My current approach is to create nFreqs_by_nChannels outlet channels and then collapse my data across those dimensions before sending.

However, the pylsl docstring for push_chunk https://github.com/sccn/labstreaminglayer/blob/master/LSL/liblsl-Python/pylsl/pylsl.py#L441-L445 says that the data argument can be a "list of multiplexed values". What is a multiplexed value?

— You are receiving this because you are subscribed to this thread. Reply to this email directly or view it on GitHub https://github.com/sccn/labstreaminglayer/issues/108

cboulay commented 8 years ago

Ah, I understand now. Thanks. I guess I was hoping it meant a list of MultiPlexedSample objects or something.

Did you ever try calling lsl_push_chunk_buf from Python, passing in a pointer to the nparray.data buffer? (or something similar?) I guess this would be faster than the copy made during .flatten() (or .tolist() for ndim<=2 arrays).

chkothe commented 8 years ago

I haven't. If you're running into throughput issues with these pylsl calls, please go ahead and optimize the hell out of it! ;)

On Thu, Apr 7, 2016 at 5:44 PM, Chadwick Boulay notifications@github.com wrote:

Ah, I understand now. Thanks. I guess I was hoping it meant a list of MultiPlexedSample objects or something.

Did you ever try calling lsl_push_chunk_buf from Python, passing in a pointer to the nparray.data buffer? (or something similar?) I guess this would be faster than the copy made during .flatten() (or .tolist() for ndim<=2 arrays).

— You are receiving this because you commented. Reply to this email directly or view it on GitHub https://github.com/sccn/labstreaminglayer/issues/108#issuecomment-207150327

chkothe commented 8 years ago

(though when you do that you may need something like an np.asndarray(..., order='C') to ensure that the memory layout of .data is really what you think it is (the asndarray would then hopefully only make a copy if it isn't already 'C')

On Thu, Apr 7, 2016 at 5:57 PM, Christian Kothe christiankothe@gmail.com wrote:

I haven't. If you're running into throughput issues with these pylsl calls, please go ahead and optimize the hell out of it! ;)

On Thu, Apr 7, 2016 at 5:44 PM, Chadwick Boulay notifications@github.com wrote:

Ah, I understand now. Thanks. I guess I was hoping it meant a list of MultiPlexedSample objects or something.

Did you ever try calling lsl_push_chunk_buf from Python, passing in a pointer to the nparray.data buffer? (or something similar?) I guess this would be faster than the copy made during .flatten() (or .tolist() for ndim<=2 arrays).

— You are receiving this because you commented. Reply to this email directly or view it on GitHub https://github.com/sccn/labstreaminglayer/issues/108#issuecomment-207150327

cboulay commented 8 years ago

It's not the pylsl calls that are the problem, but the data copying during numpy operations. I'm sampling at 16384 Hz and then visualizing at several different steps of my signal processing pipeline so the data copies are an issue. However, I'm not going to optimize too much because then I should just be writing Cython or C directly.

I'll spend a few minutes on this tonight, then post my results. After that I'll close this issue.

cboulay commented 8 years ago

For pulling...

#current way:
data = np.asarray([[buffer[0][s * num_channels + c] for c in range(num_channels)]
           for s in range(int(num_samples))])
#numpy buffer way:
data = np.frombuffer(buffer[0], dtype=np.float32).reshape(num_samples, num_channels)

Numpy buffer way is about 50x faster (1.07 msec vs 53 msec for 1000x iterations on my macbook) than the current way.

On the push side...

# Current way:
x = input.flatten()
constructor = c_float*len(x)
lib.lsl_push_chunk_ftp(outlet.obj, constructor(*x), c_long(len(x)), c_double(0.0), c_int(True))
# Numpy buffer way:
lib.lsl_push_chunk_ftp(outlet.obj, c_void_p(input.ctypes.data), c_long(input.size), c_double(0.0), c_int(True))

Numpy buffer way is about 8x faster (7.45 msec vs 61 msec for 1000x iterations on my macbook) than the current way.

All in all, I'm doing about 100 pushes per second from Python (many different outlets, high chunk rate), so I stand to gain about 0.5% of my processing time by doing this. There's big savings to be had in the pulls except those are in a separate visualization process that is not time-sensitive, or at least the visualization itself is the large chunk of processing time.

I may revisit this if brain signal processing ever gets into the realm of HFT.