sccn / lsl_archived

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

Sending binary in python #167

Open botransfer opened 7 years ago

botransfer commented 7 years ago

Hello. Is it possible to send binary data using the Python module of LSL? If I try to send a binary data as in a script following (as 'string'), I get an error shown in the last part. Right now, I am avoiding this issue by modifying the pylsl.py code so that it does not perform the unicode conversion whenever the "type" is specified as "xxx/raw" and directly passing the data to the C library, but if there is a better way, I would like to know how. Below are outputs on Windows 10 with Anaconda 4.2/Python3, with pylsl-1.10.5 installed by pip. My usage is to send/receive FFmpeg encoded audio/video data, EEG/NIRS data and so on (I'm using my own library which gathers data and stores to HDF5 file using Windows, Intel Edison and others).

Thank you for your kind response.

NISHIO Shuichi

from pylsl import StreamInfo, StreamOutlet info = StreamInfo('BioSemi', 'EEG', 1, 100, 'string', 'myuid34234') outlet = StreamOutlet(info) while True: mysample = [b'\x00\x0a'] outlet.push_sample(mysample) time.sleep(0.01)

(C:\Anaconda3) C:\home\Desktop\t>python test_send.py Traceback (most recent call last): File "test_send.py", line 6, in outlet.push_sample(mysample) File "C:\Anaconda3\lib\site-packages\pylsl\pylsl.py", line 432, in push_sample x = [v.encode('utf-8') for v in x] File "C:\Anaconda3\lib\site-packages\pylsl\pylsl.py", line 432, in x = [v.encode('utf-8') for v in x] AttributeError: 'bytes' object has no attribute 'encode'

cboulay commented 7 years ago

For the EEG and fNIRS data, is there a need to send it as binary instead of using floats? You may one day want to use another provided LSL-aware app (e.g. signal viewer, or some out of the box signal processing) and it would be good if the stream were in a typical format that the app knew how to deal with. If it needs to be in binary for storing in your data file then that encoding should probably happen on the Inlet->storage end.

As for the encoded video, I don't know enough about FFmpeg to know if it makes sense to use a different format (e.g. 3 ints per pixel) or to use binary. I assume it is variable-length. If that's the case then using a string stream is the recommended way. The fact that pylsl can't handle binary data is an oversight.

I think the channel_format (is this what you mean by specifying the 'type'?) should still be cf_string, because the channel_format variable is integrated into so many things. Other possible solutions are to check if the sample is a string on every push (or is this what you meant?) before attempting to encode, to use a keyword argument to push_sample to tell it not to encode strings, or to create a new member variable that is a flag determining whether or not to encode.

Before we decide, I think you'll have to make sure that your StreamInfo explains that the data are binary. This can be done with the type variable (or is this what you meant?). Then, upon creation of the Outlet, info.type() can be parsed and if it's 'xxx/raw' then a flag can be sent to not encode the strings to binary.

I'm not sure which of these you opted for. If the string type check is too slow then the flag that is automatically set from info.type() is probably the best way to do it. If the string type check is really fast then that would be the most robust way (e.g., user enters the wrong 'type' in StreamInfo).

This problem still exists in the latest version of pylsl from this repository but if you do make a pull request then please base it on the latest version, not the version in the pypi.

botransfer commented 7 years ago

Dear Mr Boulay,

Thank you for your reply.

I know about sending data as float32 etc., and in some cases (for me, such as sending motion capture or gyro data), I'm sending data as floats. However in some cases I wanted to have the data sent in binary and have them processed later, especially when I'm using a low-power embedded system with wireless connection (I know TCP isn't suitable for such case, but anyway).

For the details, perhaps it's better to show the diff. Please find my changes below. Although my modified version is based on somewhat old version of pylsl, the diff is made with the latest master branch from github. By the way, I'm now only using push/pull_sample() so the chunk send/receive part may not be working properly.

As you suggest, perhaps it's better to check the Python data type if it's a string or bytes/bytearrays.

And lastly, I'm not familiar with github; I made an account just for posting my issue. So I would appreciate if you would use the following diff to merge, if necessary.

Best Regards,

NISHIO Shuichi

--- labstreaminglayer/LSL/liblsl-Python/pylsl/pylsl.py 2017-01-21 00:25:05.746897900 +0900 +++ pylsl_mod.py 2017-01-12 14:22:55.000000000 +0900 @@ -21,6 +21,7 @@ import struct from ctypes import CDLL, util, byref, c_char_p, c_void_p, c_double, c_int, \ c_long, c_float, c_short, c_byte, c_longlong, cast, POINTER +import ctypes

all = ['IRREGULAR_RATE', 'DEDUCED_TIMESTAMP', 'FOREVER', 'cf_float32', 'cf_double64', 'cf_string', 'cf_int32', 'cf_int16', 'cf_int8', @@ -164,7 +165,7 @@ experimenters or data analysts). Cannot be empty. type -- Content type of the stream. By convention LSL uses the content types defined in the XDF file format specification where

From: Chadwick Boulay notifications@github.com Subject: Re: [sccn/labstreaminglayer] Sending binary in python (#167) Date: Fri, 20 Jan 2017 09:39:24 -0800 Message-ID: sccn/labstreaminglayer/issues/167/274132397@github.com

For the EEG and fNIRS data, is there a need to send it as binary instead of using floats? You may one day want to use another provided LSL-aware app (e.g. signal viewer, or some out of the box signal processing) and it would be good if the stream were in a typical format that the app knew how to deal with. If it needs to be in binary for storing in your data file then that encoding should probably happen on the Inlet->storage end.

As for the encoded video, I don't know enough about FFmpeg to know if it makes sense to use a different format (e.g. 3 ints per pixel) or to use binary. I assume it is variable-length. If that's the case then using a string stream is the recommended way. The fact that pylsl can't handle binary data is an oversight.

I think the channel_format (is this what you mean by specifying the 'type'?) should still be cf_string, because the channel_format variable is integrated into so many things. Other possible solutions are to check if the sample is a string on every push (or is this what you meant?) before attempting to encode, to use a keyword argument to push_sample to tell it not to encode strings, or to create a new member variable that is a flag determining whether or not to encode.

Before we decide, I think you'll have to make sure that your StreamInfo explains that the data are binary. This can be done with the type variable (or is this what you meant?). Then, upon creation of the Outlet, info.type() can be parsed and if it's 'xxx/raw' then a flag can be sent to not encode the strings to binary.

I'm not sure which of these you opted for. If the string type check is too slow then the flag that is automatically set from info.type() is probably the best way to do it. If the string type check is really fast then that would be the most robust way (e.g., user enters the wrong 'type' in StreamInfo).

This problem still exists in the latest version of pylsl from this repository but if you do make a pull request then please base it on the latest version, not the version in the pypi.

-- You are receiving this because you authored the thread. Reply to this email directly or view it on GitHub: https://github.com/sccn/labstreaminglayer/issues/167#issuecomment-274132397

cboulay commented 7 years ago

Ah, this is slightly more complicated than I had hoped. I see now that the push_sample_buf[tp] has a different signature than the other push_sample functions because it also expects a lengths param. Therefore, we cannot use the same function pointer variable for these data as we do for any other data type.

Fixing this up and testing will take a little time. Does your current solution work for you?

botransfer commented 7 years ago

Dear Mr Boulay,

Yes, it seems to be working fine. Please find attached a small sample for sending/receiving raw audio data using PyAudio.

Best Regards,

NISHIO Shuichi

From: Chadwick Boulay notifications@github.com Subject: Re: [sccn/labstreaminglayer] Sending binary in python (#167) Date: Fri, 20 Jan 2017 19:09:01 -0800 Message-ID: sccn/labstreaminglayer/issues/167/274229416@github.com

Ah, this is slightly more complicated than I had hoped. I see now that the push_sample_buf[tp] has a different signature than the other push_sample functions because it also expects a lengths param. Therefore, we cannot use the same function pointer variable for these data as we do for any other data type.

Fixing this up and testing will take a little time. Does your current solution work for you?

-- You are receiving this because you authored the thread. Reply to this email directly or view it on GitHub: https://github.com/sccn/labstreaminglayer/issues/167#issuecomment-274229416