sccn / labstreaminglayer

LabStreamingLayer super repository comprising submodules for LSL and associated apps.
Other
555 stars 164 forks source link

Intended use cases of channel_format and functions like lsl_push_sample_* #22

Open maltesen opened 5 years ago

maltesen commented 5 years ago

Hello.

I do not understand the intended use of specifiing a channel_format while creating the streaminfo for an outlet and the function groups for different types like lsl_pushsample*.

there is the channel_format enum

typedef enum {
    cft_float32 = 1,    /* For up to 24-bit precision measurements in the appropriate physical unit */
                        /* (e.g., microvolts). Integers from -16777216 to 16777216 are represented accurately. */
                        /* Labview: 4 Byte single
                        /* lsl_push_sample_f
    cft_double64 = 2,   /* For universal numeric data as long as permitted by network & disk budget. */
                        /* The largest representable integer is 53-bit. */
                        /* Labview: 8 Byte double
                        /* lsl_push_sample_d
    cft_string = 3,     /* For variable-length ASCII strings or data blobs, such as video frames, */
                        /* complex event descriptions, etc. */
    cft_int32 = 4,      /* For high-rate digitized formats that require 32-bit precision. Depends critically on */
                        /* meta-data to represent meaningful units. Useful for application event codes or other coded data. */
                        /* Labview:I32
                        /* lsl_push_sample_i
    cft_int16 = 5,      /* For very high rate signals (40Khz+) or consumer-grade audio */ 
                        /* (for professional audio float is recommended). */
                        /* Labview:I16
                        /* lsl_push_sample_s
    cft_int8 = 6,       /* For binary signals or other coded data. */
                        /* Not recommended for encoding string data. */
                        /* Labview:I8
                        /* lsl_push_sample_???
    cft_int64 = 7,      /* For now only for future compatibility. Support for this type is not yet exposed in all languages. */
                        /* Also, some builds of liblsl will not be able to send or receive data of this type. */
                        /* Labview:I64
                        /* lsl_push_sample_v ?
    cft_undefined = 0   /* Can not be transmitted. */
} lsl_channel_format_t;

to define which data type the outlet would use. Then there are the individual functions to push that type of data into the outlet.:

lsl_push_sample_f lsl_push_sample_d lsl_push_sample_s lsl_push_sample_i lsl_push_sample_l lsl_push_sample_c lsl_push_sample_str lsl_push_sample_buf lsl_push_sample_v

1) I dont understand why there is not only lsl_push_sample which maps its input data to the type defined by the channel_format defined for the corresponding streaminfo. This seems redundant to me. It could be intended, that i can i.e. define the channel format as cft_int16 but instead put uint16 data to perform a typecast. I doubt that this is the intended use case though, as it would be easier and cleaner to just cast uint16 to int16 directly before putting the data into the outlet.

2) I dont understand which channel_format is to be used with which lsl_push_sample function. There are 7+ channel formats but 9 different lsl_push_sample functions. A table of the mapping as i think it is correct.:

lsl_push_sample_f; cft_float32 lsl_push_sample_d; cft_double64 lsl_push_sample_s; cft_int16 lsl_push_sample_i; cft_int32 lsl_push_sample_l; cft_int64 lsl_push_sample_c; unknown lsl_push_sample_str; cft_string lsl_push_sample_buf; unknown lsl_push_sample_v; unknown unknown; cft_int8

3) I dont understand how i would transfer the basic types uint8, uint16, uint32, and uint64 (,and int8). There seems to be no channel format and no function for the unsigned integers and no functions (apart from maybe lsl_push_sample_c) for the int8 type. I could just use signed int and assume that the receiver knows that he has to cast it to unsigned int after pulling the data from the inlet, but that would be not very clean.

This should probably be one of the main chapters in the developers guide, but i couldn't find it.

Thank you very much for your time!

tstenner commented 5 years ago

I dont understand why there is not only lsl_push_sample which maps its input data to the type defined by the channel_format defined for the corresponding streaminfo.

The push functions have to be typed and C doesn't allow function overloading. The alternative (push_sample(void* data, …)) would be error-prone and hard to use when you want to push a different data type. The data types are all signed, but I can't recall the exact reason for it.

Your mapping is mostly correct:

lsl_push_sample_c; cft_int8 lsl_push_sample_str; cft_string lsl_push_sample_buf; cft_string lsl_push_sample_v; cft_string

maltesen commented 5 years ago

Thank you very much @tstenner! This works for me as is.

However, transmitting basic data types over a serial interface is the core functionality of the library. Also typecasting is a topic where it can easily occure that errors slip through unnoticed until data is lost. Imo one of the main reasons to use a library like this is the intention to avoid bugs during typecast, serialization, de-serialization, and typecast again.

Without wanting to create much hassle and thankful that a unified protocol for lab environments like this is available at all i would respectfully like to suggest to tidy this part up (redundancy and possibility to use mismatches of channel format and push_sample function types; implementation of unsigned types; implementation of separate char, unit8 and int8 types), if time is available.

Thank you very much and have a nice day!

tstenner commented 5 years ago

Just to clarify: the different functions are there to let the compiler prevent you from giving the wrong data type to a single push_sample(void*) function. The push (and pull) functions will still check what's the stream's data type and convert, so in theory you could have an int64 stream, push a double array with push_sample_d and retrieve the data as string array with pull_sample_s at the other end.

maltesen commented 5 years ago

Hmm i fail to see the benefit of this. To me it appears you are calling a feature what i would call a bug.

I would have expected it to work this way.: I define the type of the stream during creation of the outlet using the channel_format field. If later on i try to push a different type into the stream it throws an error. On the inlet side a 2nd party can tell by looking at the channel_format field what kind of stream this is. Pulling any other type from it is an error.

In order to add the functionality of arbitrary type casting you mentioned above to my interpretation of how it should work the user would have to typecast before and after using liblsl at his own risk. Youre implementation however does not provide the benefit that the user of the inlet can be SURE that the datatype of the stream is as advertized in the header of the stream (the channel_format field). So, in a wider sense, the inlet user can not be sure about the transmission protocol being used without knowing the sourcecode of the sender. But exactly that should be the job of the stream header, to define the protocol, the type being used, the version of the protocol, the lexicale being used.

If arbitrary typecasts at inlet and outlet inside liblsl are possible, as you describe it (that functionality is already implemented by the system using libls as mentioned above), whats the use of the channel_format field? An internal buffer size? Why would the user want to tamper with that? Is there any reason to not set it to a 64Bit type apart from maybe increasing the load of the transmission line? What happens if channel_format is set to a 8bit type and the outlet is then pushed with int32s? are the remaining 24 bits lost or does that result in 4 times as many samples? You said the push and pull functions still check the streams data type, so that would be an error if the pushed type is bitwise larger than the channel_format field?