sccn / lsl_archived

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

g.tec nautilus -- EEG channels incorrect #334

Closed fd301 closed 5 years ago

fd301 commented 5 years ago

I built labstreaming for a g.tec nautilus device (16 EEG channels) based on the code in the apps. However the information of the EEG channels are not correct, though the auxiliary channels are ok. Bellow is a copy of the first three lines of a csv file I create with the input LSL streamed data. The value of the first channel is always 0. I have also used the device with openvibe and it seems that openvibe acquires more reasonable values. It seems that LSL is not able to tell correctly the type of each channels, ie. float32 vs float64 etc? Or is sth else? Is there any way around this?

time-fs 250.0 Ch 1 EEG uV Ch 2 EEG uV Ch 3 EEG uV Ch 4 EEG uV Ch 5 EEG uV Ch 6 EEG uV Ch 7 EEG uV Ch 8 EEG uV Ch 9 EEG uV Ch 10 EEG uV Ch 11 EEG uV Ch 12 EEG uV Ch 13 EEG uV Ch 14 EEG uV Ch 15 EEG uV Ch 16 EEG uV Ch ACC X Accelerometer g Ch ACC Y Accelerometer g Ch ACC Z Accelerometer g Ch Counter Counter samples Ch Link Quality LinkQuality unknown Ch Battery Level BatteryLevel unknown Ch Validation Indicator ValidationIndicator unknown
0 0 -33982.2 -160494 -41696.3 -160532 -73008.3 -160735 -160520 -139109 -21472.5 -90066.8 -48933.4 -15293.1 -88741.3 -67169.8 -160485 0.07822 0.657046 -1.01686 356505 57.83133 100 1
4 0 -34001.5 -160411 -41659.1 -160449 -73010.3 -160651 -159900 -137022 -21442 -90036.9 -48897.5 -15247.5 -88717.8 -67154.4 -160401 0.07822 0.657046 -1.0325 356506 57.83133 100 1
cboulay commented 5 years ago

I built labstreaming for a g.tec nautilus device (16 EEG channels) based on the code in the apps.

Can you please tell me exactly what you mean?

fd301 commented 5 years ago

I compiled labstreaminglayer with enabled the gtec app. This created a g.needaccess app with LSL.

cboulay commented 5 years ago

Ok thanks. When you said "based on" that made me think that you wrote your own app that was merely based on example code.

Sorry I no longer have a g.nautilus to test. However, all 3 devices use the same callback function here, and I've been testing g.USBamps recently and they work fine.

The only differences between the devices are in how the API is used to configure them. Does everything look correct in the config GUI for the g.nautilus?

Can you point me to the OpenVibe code that seems to work? Make sure that the data aren't being processed at all (i.e., no filters, no standardization, etc) before evaluating whether or not OpenVibe works as expected.

You can try joining Slack and having a real-time conversation with me there, though I'm leaving for home now and I honestly don't know how I could debug this without a device.

fd301 commented 5 years ago

In addition to the tests above with data acquired via labstreaminglayer in python, I tried the following scenarios:

  1. I used the openvibe acquisition server to acquire data directly from g.needAccess and then the openvibe designer to display the raw and filtered data
  2. I used openvibe server to acquire data via LSL streaming while I used the LSL-g.needAccess app.

In the second case, the first channel was always flat and I didn't manage to get any blinking to show up on the display. In the first case channels were noisy (dry cap) but I was able to get signal from all channels and blinks were obvious.

fd301 commented 5 years ago

According to openvibe: https://gitlab.inria.fr/openvibe/extras/blob/master/contrib/plugins/server-drivers/gtec-gnautilus/src/ovasCDrivergNautilusInterface.cpp

I see that number of scans is set before the acquisition loop:

m_ui32AvailableScans = m_oNautilusDeviceCfg.NumberOfScans;
m_ui32BufferSize = l_ui32AcquiredChannelCount*m_oNautilusDeviceCfg.NumberOfScans;
m_oGdsResult = GDS_GetData(m_pDevice,&m_ui32AvailableScans,m_pBuffer,m_ui32BufferSize);

whereas in lsl-g.needaccess:

size_t bufferSize = thisWin->m_devInfo.scans_per_block * thisWin->m_devInfo.nsamples_per_scan;
size_t scans_available = 0;  // Read as many scans as possible, up to as many will fit in m_dataBuffer
GDS_RESULT res = GDS_GetData(thisWin->m_connectionHandle, &scans_available, &thisWin ->m_dataBuffer[0], bufferSize);

These two pieces of code are not equivalent, are they?

cboulay commented 5 years ago

No, they aren't. I'll take a look at the API docs tomorrow to see if what I did was strictly wrong. My impression from my // comment is that passing 0 will fetch as many scans as possible, at least as many as is possible without overflowing the buffer whose size is determined by the last argument.

In OpenVibe, m_ui32BufferSize is the product of the number of channels (including extra channels) and the number of scans.

In the LSL app, bufferSize is equal to the product of scans_per_block, which I fully expect to be the same as NumberOfScans, and nsamples_per_scan which I am less confident is equal to l_ui32AcquiredChannelCount; maybe it's missing auxiliary channels. I'll check tomorrow.

By the way, it would be tremendously helpful if you could print the contents of the data buffer before this line before it gets pushed to LSL.

fd301 commented 5 years ago

Thanks for looking into this.

I added some line into the code to print the values:

success &= handleResult("GDS_GetDataInfo",
GDS_GetDataInfo(m_connectionHandle, &n_scans, NULL,
&n_devices_with_channels, &m_devInfo.nsamples_per_scan));
std::cout << "info-n_devices_with_channels: " << n_devices_with_channels <<
std::endl;
std::cout << "info-nsamples_per_scan: " << m_devInfo.nsamples_per_scan <<
std::endl;
...

GDS_RESULT res = GDS_GetData(thisWin->m_connectionHandle, &scans_available,
&thisWin->m_dataBuffer[0], bufferSize);
if (scans_available > 0)
{

std::cout << "scans_available: " << scans_available << std::endl;
std::cout << "scans per block: " << thisWin->m_devInfo.scans_per_block <<
std::endl;
std::cout << "samples per scan: " << scans_available <<
thisWin->m_devInfo.nsamples_per_scan << std::endl;
for (int i = 0; i < thisWin->m_devInfo.nsamples_per_scan; i++) {
std::cout << thisWin->m_dataBuffer[i] << " ";
}
std::cout << std::endl;

The output is attached as a picture. buffer size is 184, which I guess it is fine since it corresponds to 23channelsx8scans.

lslgtec_cmd_2 1

I hope this helps.

On Tue, Aug 14, 2018 at 1:41 AM, Chadwick Boulay notifications@github.com wrote:

No, they aren't. I'll take a look at the API docs tomorrow to see if what I did was strictly wrong. My impression from my // comment is that passing 0 will fetch as many scans as possible, at least as many as is possible without overflowing the buffer whose size is determined by the last argument.

In OpenVibe, m_ui32BufferSize is the product of the number of channels (including extra channels) and the number of scans.

In the LSL app, bufferSize is equal to the product of scans_per_block, which I fully expect to be the same as NumberOfScans, and nsamples_per_scan which I am less confident is equal to l_ui32AcquiredChannelCount; maybe it's missing auxiliary channels. I'll check tomorrow.

By the way, it would be tremendously helpful if you could print the contents of the data buffer before this line https://github.com/labstreaminglayer/App-g.Tec/blob/master/g.NEEDaccess/src/mainwindow.cpp#L46 before it gets pushed to LSL.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/sccn/labstreaminglayer/issues/334#issuecomment-412714051, or mute the thread https://github.com/notifications/unsubscribe-auth/Akph7316I7Bzt1QGGOYrq-AhjTEePDKUks5uQhzNgaJpZM4V7Hbz .

fd301 commented 5 years ago

I realise that I forgot to print a space here: std::cout << "samples per scan: " << scans_available << thisWin->m_devInfo.nsamples_per_scan << std::endl;

So the output 823 corresponds to 8scansx23channels. This also looks ok, I guess.

cboulay commented 5 years ago

So, as you saw from the printout, LSL itself isn't doing anything wrong; it's relaying the buffer accurately but the contents of the data buffer are wrong. Off the top of my head, there are at least two things to check.

First, maybe the data buffer isn't getting filled by the g.NEEDaccess server. Can you try modifying the buffer resize line here and passing a second argument to resize of 4.567, so that all elements are filled with that value? Then when you print the buffer later these values should be changed. If they are not then that tells us something.

Second, maybe there's some configuration that I missed. Can you please inspect m_devconfigs in this line and make sure that everything seems configured correctly?

fd301 commented 5 years ago

I tried to fill the buffer with a specific value as you recommended but this didn't show any clear results. The dll still pulls some data. Some of these values are reasonable but not all of them. The problem is reproducible across nautilus devices. I tried another one with 32 channels and results are attached.

I'll look into the configuration settings as well.

lslgtec_cmd_3 1

On Tue, Aug 14, 2018 at 3:12 PM, Chadwick Boulay notifications@github.com wrote:

So, as you saw from the printout, LSL itself isn't doing anything wrong; it's relaying the buffer accurately but the contents of the data buffer are wrong. Off the top of my head, there are at least two things to check.

First, maybe the data buffer isn't getting filled by the g.NEEDaccess server. Can you try modifying the buffer resize line here https://github.com/labstreaminglayer/App-g.Tec/blob/master/g.NEEDaccess/src/mainwindow.cpp#L672 and passing a second argument to resize of 4.567, so that all elements are filled with that value? Then when you print the buffer later these values should be changed. If they are not then that tells us something.

Second, maybe there's some configuration that I missed. Can you please inspect m_devconfigs in this line https://github.com/labstreaminglayer/App-g.Tec/blob/master/g.NEEDaccess/src/mainwindow.cpp#L327 and make sure that everything seems configured correctly?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/sccn/labstreaminglayer/issues/334#issuecomment-412886107, or mute the thread https://github.com/notifications/unsubscribe-auth/Akph75EgX7KCcte1ErO4J6zcVk0bGbLsks5uQtrngaJpZM4V7Hbz .

fd301 commented 5 years ago

By the way, the piece of code below that is commented out seems to work if you replace the device_names with &device_names[0] and then copy the names to the std::vector of strings: This code receives the names of the channels from the nautilus device and later pass them to lsl.

            // First determine how many channel names there are.
            uint32_t mountedModulesCount = 0;
            size_t electrodeNamesCount = 0;
            success &= handleResult("GDS_GNAUTILUS_GetChannelNames",
                GDS_GNAUTILUS_GetChannelNames(m_connectionHandle, &device_names[0], &mountedModulesCount, NULL, &electrodeNamesCount));
            // Allocate memory to store the channel names.
            char(*electrode_names)[GDS_GNAUTILUS_ELECTRODE_NAME_LENGTH_MAX] = new char[electrodeNamesCount][GDS_GNAUTILUS_ELECTRODE_NAME_LENGTH_MAX];
            success &= handleResult("GDS_GNAUTILUS_GetChannelNames",
                GDS_GNAUTILUS_GetChannelNames(m_connectionHandle, &device_names[0], &mountedModulesCount, electrode_names, &electrodeNamesCount));

for (int i = 0; i < electrodeNamesCount; i++) m_chanLabels[i].assign(electrode_names[i]);

Perhaps, you need also to check if channels are enabled before copying.

cboulay commented 5 years ago

I tried to fill the buffer with a specific value as you recommended but this didn't show any clear results. The dll still pulls some data. Some of these values are reasonable but not all of them.

OK that's helpful. So the data returned by the g.NEEDaccess server are incorrect, or the data layout of the buffer is somehow incorrect.

here and here:

std::vector<float> m_dataBuffer = {};
m_dataBuffer.clear();
m_dataBuffer.resize(m_devInfo.scans_per_block * m_devInfo.nsamples_per_scan);

and it is passed as: GDS_GetData(thisWin->m_connectionHandle, &scans_available, &thisWin->m_dataBuffer[0], bufferSize);

In OpenVibe it is: m_pBuffer = new float[l_ui32AcquiredChannelCount*m_oNautilusDeviceCfg.NumberOfScans]; and it is passed as: GDS_GetData(m_pDevice,&m_ui32AvailableScans,m_pBuffer,m_ui32BufferSize);.

Without testing, these seem equivalent to me. I guess I would like to change &thisWin->m_dataBuffer[0] to thisWin->m_dataBuffer.data(), just to eliminate the ambiguity as to what the & is operating on. Please go ahead and try, though it shouldn't change anything.

The other thing you can try is initializing scans_available as size_t scans_available = thisWin->m_devInfo.scans_per_block;. It's possible that using 0 here doesn't work with g.Nautilus, though it seems to work well for g.USBamps. This would be a shame, because by using 0 I was trying to always fetch whatever data is available, without waiting, to try and push the most recent data through as fast as possible.

fd301 commented 5 years ago

I think the problem is with the configuration lines:

success &= handleResult("GDS_SetConfiguration", GDS_SetConfiguration(m_connectionHandle, &m_devConfigs[0], m_devConfigs.size()));

Gtec api client demo uses this code to initialise the configuration:

GDS_GNAUTILUS_CONFIGURATION* cfg_nautilus = new GDS_GNAUTILUS_CONFIGURATION;
setup_config( cfg_nautilus, SAMPLE_RATE );
GDS_CONFIGURATION_BASE* cfg = new GDS_CONFIGURATION_BASE[1];
cfg[0].DeviceInfo.DeviceType = GDS_DEVICE_TYPE_GNAUTILUS;
strcpy( cfg[0].DeviceInfo.Name, device_list[idx_selection].c_str() );
cfg[0].Configuration = cfg_nautilus;

Attached are the visual studio project of the client api provided by gtec. (Edit by Chad: I don't know if you can share that project, and I have it anyway, so I deleted it from here)

In the lsl version of the driver, I cannot find whether the struct GDS_GNAUTILUS_CONFIGURATION is initialised. Also the GDS_CONFIGURATION_BASE is not used? I'm a bit confused on how this is done.

I tried to apply the configuration and indeed changed the output of the eeg channels. However, I'm not convinced that what I get is correct.

tstenner commented 5 years ago

Without testing, these seem equivalent to me. I guess I would like to change &thisWin->m_dataBuffer[0] to thisWin->m_dataBuffer.data(), just to eliminate the ambiguity as to what the & is operating on.

For a vector, it's exactly the same, but the second is C++11.

cboulay commented 5 years ago

@fd301 For configuration...

The header has std::vector<GDS_CONFIGURATION_BASE> m_devConfigs;.

The configuration code is here:

GDS_CONFIGURATION_BASE *deviceConfigurations = NULL;
    size_t deviceConfigurationsCount = 0;
    success &= handleResult("GDS_GetConfiguration",
        GDS_GetConfiguration(m_connectionHandle, &deviceConfigurations, &deviceConfigurationsCount));
    // Note: API allocates memory during GDS_GetConfiguration. We must free it with GDS_FreeConfigurationList below.

    // Copy device configs to our local map.
    clear_dev_configs();
    for (size_t dev_ix = 0; dev_ix < deviceConfigurationsCount; dev_ix++)
    {
        m_devConfigs.push_back(deviceConfigurations[dev_ix]);
        // We must also copy the data pointed to by the device-specific .Configuration
        size_t copy_bytes = 0;
        switch (deviceConfigurations[dev_ix].DeviceInfo.DeviceType)
        {
        case GDS_DEVICE_TYPE_NOT_SUPPORTED:
            break;
        case GDS_DEVICE_TYPE_GUSBAMP:
            copy_bytes = sizeof(GDS_GUSBAMP_CONFIGURATION);
            m_devConfigs[dev_ix].Configuration = new GDS_GUSBAMP_CONFIGURATION();  // Allocate memory.
            break;
        case GDS_DEVICE_TYPE_GHIAMP:
            copy_bytes = sizeof(GDS_GHIAMP_CONFIGURATION);
            m_devConfigs[dev_ix].Configuration = new GDS_GHIAMP_CONFIGURATION();  // Allocate memory.
            break;
        case GDS_DEVICE_TYPE_GNAUTILUS:
            copy_bytes = sizeof(GDS_GNAUTILUS_CONFIGURATION);
            m_devConfigs[dev_ix].Configuration = new GDS_GNAUTILUS_CONFIGURATION();  // Allocate memory.
            break;
        default:
            break;
        }
        memcpy(m_devConfigs[dev_ix].Configuration, deviceConfigurations[dev_ix].Configuration, copy_bytes);
    }

    // Free the resources used by the config
    success &= handleResult("GDS_FreeConfigurationList",
GDS_FreeConfigurationList(&deviceConfigurations, deviceConfigurationsCount));

I can't look into this in any more detail right now. For that I'll have to reboot into Windows and I can't do that just yet. I'll try to get to this later.

cboulay commented 5 years ago

Comparing to the gNautilusDemo, I have a couple ideas as to what should change.

  1. Don't release configuration memory (GDS_FreeConfigurationList) until after GDS_SetConfiguration. You can try commenting out here. This will leak memory but for now that's ok.
  2. I manually set a few cfg arguments that were missing when compared to the gNautilusDemo. You can see the changes here. I haven't merged this into master or anything so it's probably easiest if you just copy-paste for now and let me know if it worked.
fd301 commented 5 years ago

Setting the scans to zero for the configuration did not work. I got a direct GDS error and the program crashed. I'll try to hard code the scans to 4, which most probably is a safe option.

Another problem I have when a crash with gtec drivers happens is that I cannot reconnect. I get an error: 'GDS_BecomeCreator - the data acquistion session associated with the specified connection handle does already have a creator.' Then I need to restart the computer. Perhaps, we can leave this problem at the side for the moment, since it happens with the company's proprietary code as well.

It is disappointing that gtec does not offer any support. As far as I know all the major EEG companies support LSL directly.

cboulay commented 5 years ago

It is disappointing that gtec does not offer any support. As far as I know all the major EEG companies support LSL directly.

I'm sort of their indirect support. They sent me hardware, they answered my questions, (I thought) I finished and sent them the hardware back.

Another problem I have when a crash with gtec drivers happens is that I cannot reconnect. I get an error: 'GDS_BecomeCreator - the data acquistion session associated with the specified connection handle does already have a creator.'

You probably don't have to restart the computer, but you probably do have to restart the GDS service.

fd301 commented 5 years ago

How I can restart the GDS service? I do restart the wireless device and I unplug the mini 'transmitting' device connected to the PC.

Best Regards, Fani

On Thu, 16 Aug 2018 21:22 Chadwick Boulay, notifications@github.com wrote:

It is disappointing that gtec does not offer any support. As far as I know all the major EEG companies support LSL directly.

I'm sort of their indirect support. They sent me hardware, they answered my questions, (I thought) I finished and sent them the hardware back. I still have USBamps but I don't have the HIamp or the Nautilus anymore :/

Another problem I have when a crash with gtec drivers happens is that I cannot reconnect. I get an error: 'GDS_BecomeCreator - the data acquistion session associated with the specified connection handle does already have a creator.'

You probably don't have to restart the computer, but you probably do have to restart the GDS service.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sccn/labstreaminglayer/issues/334#issuecomment-413672584, or mute the thread https://github.com/notifications/unsubscribe-auth/Akph79B-vL5K59x5RGd3eUJNJz3vDmF-ks5uRdRqgaJpZM4V7Hbz .

cboulay commented 5 years ago

How I can restart the GDS service?

Right click on the taskbar, choose Task Manager, click on the Services tab, in the list find GDS, right click on it then choose Restart.

cboulay commented 5 years ago

@fd301 Did you make any progress on this? Just now I sent an e-mail to Fan Cao at g.Tec to ask him to take a look at this thread.

fd301 commented 5 years ago

No, I haven't make much progress. I did try what we discussed but without any noticeable difference. I would like to look again into this but it won't happen for a week or so. I have also contacted g.tec support long time ago but I didn't receive any response. Thanks for moving this forward!

On Mon, Sep 10, 2018 at 6:13 PM Chadwick Boulay notifications@github.com wrote:

@fd301 https://github.com/fd301 Did you make any progress on this? Just now I sent an e-mail to Fan Cao at g.Tec to ask him to take a look at this thread.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sccn/labstreaminglayer/issues/334#issuecomment-419989545, or mute the thread https://github.com/notifications/unsubscribe-auth/Akph70lHw15nq6zBcMEbC0ftT_5-xXOMks5uZp2vgaJpZM4V7Hbz .

fd301 commented 5 years ago

@cboulay have you heard back from g.Tec? What do they think about this issue?

cboulay commented 5 years ago

I asked g.tec but I asked several questions at once and I guess this question got overlooked. I'll ask again.

cboulay commented 5 years ago

OK they are sending me a g.Nautilus so I can fix the problem. I'll let you know how it goes.

fd301 commented 5 years ago

Hi, sorry to bug you again. Did you have any chance to check this out?

cboulay commented 5 years ago

This issue was moved to labstreaminglayer/App-g.Tec#7