labstreaminglayer / LSL4Unity

A integration approach of the LabStreamingLayer Framework for Unity3D
Other
85 stars 39 forks source link

Asynchronous sample/chunk aquisistion #14

Open xfleckx opened 7 years ago

xfleckx commented 7 years ago

Problem:

I can never get out of the while ((lastTimeStamp = inlet.pull_sample(sample, 0.0f)) != 0) loop because I'm Processing the samples slower than they are coming in.

Solution A: do not push sample processing logic into Unity - heavy data post-processing could be done in more efficient environments due to data processing - e.g. matlab\Julia\Python ❓ This might result in feature streams with lower frequencies (30 - 100Hz) which could be comfortable processed by Update rates of 30/60/90 Hz of a game loop.

Soultion B: Using a thread based wrapper for a LSL inlet.


void Start(){
     // contains all the thread logic
     mySampleAquisitionJob = new FloatSampleAquisition(streamInlet);
     mySampleAquisitionJob.Start(); //start the thread and try pulling
}

void Update(){
    if(mySampleAquisitionJob.HasData){
        // this takes care of thread locking etc.
        float[] data = mySampleAquisitionJob.GetData();
        // data got copied in the unity main thread 
    }
}
cboulay commented 7 years ago

I do all my signal processing in Python (mostly using NeuroPype). It streams the result out via a 'type="Control"' Outlet. These streams are always between 10 and 80 Hz.

I was able to escape the while loop by commenting out my line in Process that printed the value via Debug.Log. I guess Debug.Log is too slow to be called multiple times in each game tick.

I've since modified my Process function to simply take the value and store it as a member variable and toggle a hasNewData switch. Then at the end of Update (i.e., after pullSamples()), if (hasNewData) {do something with lastSample;}.

I don't know if you've ever encountered this, but sometimes when I start up a program that gets an LSL Inlet, it seems to get lots of data really quickly at the beginning, much faster than it is being sent out via the outlet, then eventually it catches up and the inlet data rate matches the outlet data rate. I assume this has something to do with LSL's data buffering and my application's new inlet assuming the role of a recently-destroyed inlet and picking up all the data since the last inlet was destroyed.

xfleckx commented 7 years ago

to this:

I was able to escape the while loop by commenting out my line in Process that printed the value via Debug.Log. I guess Debug.Log is too slow to be called multiple times in each game tick.

Instead of using the Debug.Log, I would suggest to use an custom inspector with a value field showing the latest value of your stream within the inspector of your components. Code looks like this:


public class YourScript : MonoBehaviour{

    [HideInspector] // to get a read only field
    public float latestLSLSample = 0f;

    void Update(){
                // Update the value from your lsl values.... 
        latestSample = GetTheLSLValue();
    }
}

[CustomInspector(typeof(YourScript))]
public class InspectorForYourScript : Editor{

    public override OnInspectorGUI()
    {
        var yourScript = target as YourScript;

        // Show the default Inspector
        base.OnInspectorGUI();

        // shows the hidden property as a read only field
        EditorGUILayout.FloatField(yourScript.latestValue, "Latest Value from LSL");
    }

}

this seems to be more efficient than a debug log call. For the future, I'm planning to provide a little chart implementation which can be used within the Editor and the Game logic for visualisation purpose.

But this solves the issue just for the case, you got this issue whenever you got a bit more processing logic - which takes also a bit more.

The second issue:

it seems to get lots of data really quickly at the beginning, much faster than it is being sent out via the outlet, then eventually it catches up and the inlet data rate matches the outlet data rate

Naive solution: I would pull all samples at the start of the application and throw them away since we don't want to record something.

Interesting phenomena, actually intended, I guess their should be an option for that.... I will take a look. I thought their was a config file for LSL which could be used to define things like how much data should be cached...