diff --git a/Apps/BrainProducts/ActiChamp/ActiChamp.vcproj b/Apps/BrainProducts/ActiChamp/ActiChamp.vcproj index 4644eda8..9ea0b91b 100644 --- a/Apps/BrainProducts/ActiChamp/ActiChamp.vcproj +++ b/Apps/BrainProducts/ActiChamp/ActiChamp.vcproj @@ -1,9 +1,10 @@ @@ -229,7 +230,7 @@ -032102falsetrue \ No newline at end of file +032102falsetruefalsetruefalse \ No newline at end of file diff --git a/Apps/BrainProducts/ActiChamp/explanation_of_trigger_marker_types.pdf b/Apps/BrainProducts/ActiChamp/explanation_of_trigger_marker_types.pdf new file mode 100644 index 00000000..13053e0d Binary files /dev/null and b/Apps/BrainProducts/ActiChamp/explanation_of_trigger_marker_types.pdf differ diff --git a/Apps/BrainProducts/ActiChamp/mainwindow.cpp b/Apps/BrainProducts/ActiChamp/mainwindow.cpp index 35daeb47..905bf552 100644 --- a/Apps/BrainProducts/ActiChamp/mainwindow.cpp +++ b/Apps/BrainProducts/ActiChamp/mainwindow.cpp @@ -573,6 +573,12 @@ void MainWindow::load_config(const std::string &filename) { setMinChunk(ui->samplingRate->currentIndex()); ui->useAUX->setCheckState(pt.get("settings.useaux",false) ? Qt::Checked : Qt::Unchecked); ui->activeShield->setCheckState(pt.get("settings.activeshield",true) ? Qt::Checked : Qt::Unchecked); + ui->unsampledMarkers->setCheckState(pt.get("settings.unsampledmarkers",false) ? Qt::Checked : Qt::Unchecked); + ui->sampledMarkers->setCheckState(pt.get("settings.sampledmarkers",true) ? Qt::Checked : Qt::Unchecked); + ui->sampledMarkersEEG->setCheckState(pt.get("settings.sampledmarkersEEG",false) ? Qt::Checked : Qt::Unchecked); + ui->unsampledMarkers->setCheckState(pt.get("settings.unsampledmarkers",false) ? Qt::Checked : Qt::Unchecked); + ui->sampledMarkers->setCheckState(pt.get("settings.sampledmarkers",true) ? Qt::Checked : Qt::Unchecked); + ui->sampledMarkersEEG->setCheckState(pt.get("settings.sampledmarkersEEG",false) ? Qt::Checked : Qt::Unchecked); ui->channelLabels->clear(); BOOST_FOREACH(ptree::value_type &v, pt.get_child("channels.labels")) ui->channelLabels->appendPlainText(v.second.data().c_str()); @@ -594,6 +600,12 @@ void MainWindow::save_config(const std::string &filename) { pt.put("settings.samplingrate",ui->samplingRate->currentIndex()); pt.put("settings.useaux",ui->useAUX->checkState()==Qt::Checked); pt.put("settings.activeshield",ui->activeShield->checkState()==Qt::Checked); + pt.put("settings.unsampledmarkers",ui->unsampledMarkers->checkState()==Qt::Checked); + pt.put("settings.sampledmarkers",ui->sampledMarkers->checkState()==Qt::Checked); + pt.put("settings.sampledmarkersEEG",ui->sampledMarkersEEG->checkState()==Qt::Checked); + pt.put("settings.unsampledmarkers",ui->unsampledMarkers->checkState()==Qt::Checked); + pt.put("settings.sampledmarkers",ui->sampledMarkers->checkState()==Qt::Checked); + pt.put("settings.sampledmarkersEEG",ui->sampledMarkersEEG->checkState()==Qt::Checked); std::vector channelLabels; boost::algorithm::split(channelLabels,ui->channelLabels->toPlainText().toStdString(),boost::algorithm::is_any_of("\n")); BOOST_FOREACH(std::string &v, channelLabels) @@ -638,6 +650,11 @@ void MainWindow::link() { int samplingRate = sampling_rates[ui->samplingRate->currentIndex()]; bool useAUX = ui->useAUX->checkState()==Qt::Checked; bool activeShield = ui->activeShield->checkState()==Qt::Checked; + + g_unsampledMarkers = ui->unsampledMarkers->checkState()==Qt::Checked; + g_sampledMarkers = ui->sampledMarkers->checkState()==Qt::Checked; + g_sampledMarkersEEG = ui->sampledMarkersEEG->checkState()==Qt::Checked; + std::vector channelLabels; boost::algorithm::split(channelLabels,ui->channelLabels->toPlainText().toStdString(),boost::algorithm::is_any_of("\n")); if (channelLabels.size() != channelCount) @@ -726,12 +743,18 @@ void MainWindow::link() { } } -bool _resample = false; + // background data reader thread void MainWindow::read_thread(int deviceNumber, int channelCount, int chunkSize, int samplingRate, bool useAUX, bool activeShield, std::vector channelLabels) { HANDLE hDevice = NULL; bool started = false; + bool _resample = false; + + // set thread priority to high in order to ensure we don't lose data during sleep periods + int res = SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); + std::string threadId = boost::lexical_cast(boost::this_thread::get_id()); + try { // try to open the device again (we're doing everything in the same thread to not confuse the driver) hDevice = champOpen(deviceNumber); @@ -783,12 +806,17 @@ void MainWindow::read_thread(int deviceNumber, int channelCount, int chunkSize, // reserve buffers to receive and send data int buffer_bytes = chunkSize*sampleSize; char *recv_buffer = new char[buffer_bytes*10]; - std::vector > temp_buffer(chunkSize,std::vector(channelCount)); - std::vector > send_buffer(chunkSize,std::vector(channelCount)); + std::vector > temp_buffer(chunkSize,std::vector(channelCount+(g_sampledMarkersEEG?1:0))); + std::vector > send_buffer(chunkSize,std::vector(channelCount+(g_sampledMarkersEEG?1:0))); std::vector trigger_buffer(chunkSize); std::vector> rs_temp_in,rs_temp_out; + // containers for the marker streams + std::vector> marker_buffer(chunkSize, std::vector(1)); + std::vector s_mrkr; + float f_mrkr; + // allocate resampler //Resampler *resampler = NULL; //switch (samplingRate) { @@ -803,7 +831,7 @@ void MainWindow::read_thread(int deviceNumber, int channelCount, int chunkSize, _resample = true; std::vector*> resamplers; - for (unsigned c=0;c(1,80,coeffs_10000_to_125,sizeof(coeffs_10000_to_125)/sizeof(coeffs_10000_to_125[0]))); @@ -821,13 +849,20 @@ void MainWindow::read_thread(int deviceNumber, int channelCount, int chunkSize, } // create data streaminfo and append some meta-data - lsl::stream_info data_info("ActiChamp-" + boost::lexical_cast(deviceNumber),"EEG",channelCount,samplingRate,lsl::cf_float32,"ActiChamp_" + boost::lexical_cast(deviceNumber)); + lsl::stream_info data_info("ActiChamp-" + boost::lexical_cast(deviceNumber),"EEG",channelCount+(g_sampledMarkersEEG?1:0),samplingRate,lsl::cf_float32,"ActiChamp_" + boost::lexical_cast(deviceNumber)); lsl::xml_element channels = data_info.desc().append_child("channels"); for (std::size_t k=0;k(version.Dll).c_str()) @@ -838,22 +873,47 @@ void MainWindow::read_thread(int deviceNumber, int channelCount, int chunkSize, // make a data outlet lsl::stream_outlet data_outlet(data_info); + + lsl::stream_outlet *marker_outlet; + if(g_unsampledMarkers) { + lsl::stream_info marker_info("ActiChamp-" + boost::lexical_cast(deviceNumber) + "-Markers","Markers", 1, 0, lsl::cf_string,"ActiChamp_" + boost::lexical_cast(deviceNumber) + "_markers"); + marker_outlet = new lsl::stream_outlet(marker_info); + } + + lsl::stream_outlet *s_marker_outlet; + if(g_sampledMarkers) { + lsl::stream_info s_marker_info("ActiChamp-" + boost::lexical_cast(deviceNumber) + "-Sampled-Markers","sampledMarkers", 1, (samplingRate > 1000 ? samplingRate : 10000), lsl::cf_string,"ActiChamp_" + boost::lexical_cast(deviceNumber) + "_sampled_markers"); + s_marker_outlet = new lsl::stream_outlet(s_marker_info); + // ditch the outlet if we don't need it (need to do it this way in order to trick C++ compiler into using this object conditionally) + } + // create marker streaminfo and outlet - lsl::stream_info marker_info("ActiChamp-" + boost::lexical_cast(deviceNumber) + "-Markers","Markers",1,0,lsl::cf_string,"ActiChamp_" + boost::lexical_cast(deviceNumber) + "_markers"); - lsl::stream_outlet marker_outlet(marker_info); + //lsl::stream_info marker_info("ActiChamp-" + boost::lexical_cast(deviceNumber) + "-Markers","Markers",1,0,lsl::cf_string,"ActiChamp_" + boost::lexical_cast(deviceNumber) + "_markers"); + //lsl::stream_outlet marker_outlet(marker_info); // enter transmission loop + + // for keeping track of changes to the trigger signal int last_mrk = 0; + int prev_markerSampled = 0; + int prev_markerEEG = 0; + int bytes_read, samples_read; while (!stop_) { // read chunk into recv_buffer bytes_read = champGetDataBlocking(hDevice,recv_buffer,buffer_bytes); samples_read = bytes_read/sampleSize; + + if (samples_read <= 0 && samplingRate < 50000){ + boost::this_thread::sleep(boost::posix_time::milliseconds(1)); + continue; + } + if (samples_read > 0) { double now = lsl::local_clock(); // copy data into trigger_buffer and temp_buffer, and scale to microvolts trigger_buffer.resize(samples_read); - temp_buffer.resize(samples_read,std::vector(channelCount)); + temp_buffer.resize(samples_read,std::vector(channelCount+(g_sampledMarkersEEG?1:0))); switch(sampleSize) { case sizeof(t_champDataModelAux): { @@ -927,6 +987,28 @@ void MainWindow::read_thread(int deviceNumber, int channelCount, int chunkSize, } } + for (int s=0;s(trigger_buffer[s]) << std::endl; + f_mrkr = (trigger_buffer[s] == prev_markerEEG ? 0.0 : boost::lexical_cast(trigger_buffer[s])); + prev_markerEEG = trigger_buffer[s]; + temp_buffer[s][channelCount] = f_mrkr; + } + + if (g_sampledMarkers) { + s_mrkr.clear(); + //if(trigger_buffer[s] != prev_markerSampled) + // std::cout << " smrkr: " << now << " " << s << " " << boost::lexical_cast(trigger_buffer[s]) << std::endl; + s_mrkr.push_back(trigger_buffer[s] == prev_markerSampled ? "" : boost::lexical_cast(trigger_buffer[s])); + marker_buffer.at(s) = s_mrkr; + //std::cout << "s: " << s << std::endl; + prev_markerSampled = trigger_buffer[s]; + } + } + // this never gets resampled, do it now + if (g_sampledMarkers) s_marker_outlet->push_chunk(marker_buffer, now); + // optionally resample if (_resample) { //resampler->apply_multichannel(temp_buffer,send_buffer); @@ -967,17 +1049,23 @@ void MainWindow::read_thread(int deviceNumber, int channelCount, int chunkSize, } // push markers into outlet - for (int s=0;s(mrk); - marker_outlet.push_sample(&mrk_string,now + (s + 1 - samples_read)/samplingRate); - last_mrk = mrk; + if(g_unsampledMarkers) { + for (int s=0;s(mrk); + //std::cout << " mrkr: " << now << " " << s << " " << boost::lexical_cast(trigger_buffer[s]) << std::endl; + marker_outlet->push_sample(&mrk_string,now + (s + 1 - samples_read)/samplingRate); + last_mrk = mrk; + } } } } } } + // need to explicitly delete these objects + if(g_unsampledMarkers)delete(marker_outlet); + if(g_sampledMarkers)delete(s_marker_outlet); } catch(boost::thread_interrupted &) { // thread was interrupted: no error diff --git a/Apps/BrainProducts/ActiChamp/mainwindow.h b/Apps/BrainProducts/ActiChamp/mainwindow.h index d2f67c7b..393c4e2e 100644 --- a/Apps/BrainProducts/ActiChamp/mainwindow.h +++ b/Apps/BrainProducts/ActiChamp/mainwindow.h @@ -58,6 +58,10 @@ private slots: bool stop_; // whether the reader thread is supposed to stop boost::shared_ptr reader_thread_; // our reader thread + bool g_unsampledMarkers; + bool g_sampledMarkers; + bool g_sampledMarkersEEG; + Ui::MainWindow *ui; }; diff --git a/Apps/BrainProducts/ActiChamp/mainwindow.ui b/Apps/BrainProducts/ActiChamp/mainwindow.ui index 062a1b44..3b100886 100644 --- a/Apps/BrainProducts/ActiChamp/mainwindow.ui +++ b/Apps/BrainProducts/ActiChamp/mainwindow.ui @@ -6,13 +6,16 @@ 0 0 - 357 - 274 + 360 + 411 ActiChamp Connector + + <html><head/><body><p>For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.</p></body></html> + @@ -237,6 +240,72 @@ PO10 + + + + LSL Trigger Output Style + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Unsampled String Markers + + + + + + + Sampled String Markers + + + + + + + <html><head/><body><p>For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.</p></body></html> + + + (check) + + + true + + + + + + + Floating Point EEG Channel + + + + + + + <html><head/><body><p>For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.</p></body></html> + + + (check) + + + + + + + <html><head/><body><p>For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.</p></body></html> + + + (check) + + + + + + @@ -286,8 +355,8 @@ PO10 0 0 - 357 - 18 + 360 + 21 diff --git a/Apps/BrainProducts/ActiChamp/ui_mainwindow.h b/Apps/BrainProducts/ActiChamp/ui_mainwindow.h new file mode 100644 index 00000000..2a0818b1 --- /dev/null +++ b/Apps/BrainProducts/ActiChamp/ui_mainwindow.h @@ -0,0 +1,394 @@ +/******************************************************************************** +** Form generated from reading UI file 'mainwindow.ui' +** +** Created by: Qt User Interface Compiler version 4.8.6 +** +** WARNING! All changes made in this file will be lost when recompiling UI file! +********************************************************************************/ + +#ifndef UI_MAINWINDOW_H +#define UI_MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Ui_MainWindow +{ +public: + QAction *actionLoad_Configuration; + QAction *actionSave_Configuration; + QAction *actionQuit; + QAction *actionQuit_2; + QWidget *centralWidget; + QHBoxLayout *horizontalLayout_2; + QGroupBox *groupBox_2; + QGridLayout *gridLayout; + QPlainTextEdit *channelLabels; + QVBoxLayout *verticalLayout; + QGroupBox *groupBox; + QFormLayout *formLayout; + QLabel *label_4; + QSpinBox *deviceNumber; + QLabel *label_2; + QSpinBox *channelCount; + QLabel *label; + QSpinBox *chunkSize; + QLabel *label_3; + QComboBox *samplingRate; + QLabel *label_7; + QCheckBox *useAUX; + QLabel *label_8; + QCheckBox *activeShield; + QGroupBox *groupBox_3; + QFormLayout *formLayout_2; + QLabel *label_5; + QLabel *label_6; + QCheckBox *sampledMarkers; + QLabel *label_9; + QCheckBox *sampledMarkersEEG; + QCheckBox *unsampledMarkers; + QSpacerItem *verticalSpacer; + QHBoxLayout *horizontalLayout; + QSpacerItem *horizontalSpacer; + QPushButton *linkButton; + QMenuBar *menuBar; + QMenu *menuFile; + QStatusBar *statusBar; + + void setupUi(QMainWindow *MainWindow) + { + if (MainWindow->objectName().isEmpty()) + MainWindow->setObjectName(QString::fromUtf8("MainWindow")); + MainWindow->resize(360, 411); + actionLoad_Configuration = new QAction(MainWindow); + actionLoad_Configuration->setObjectName(QString::fromUtf8("actionLoad_Configuration")); + actionSave_Configuration = new QAction(MainWindow); + actionSave_Configuration->setObjectName(QString::fromUtf8("actionSave_Configuration")); + actionQuit = new QAction(MainWindow); + actionQuit->setObjectName(QString::fromUtf8("actionQuit")); + actionQuit_2 = new QAction(MainWindow); + actionQuit_2->setObjectName(QString::fromUtf8("actionQuit_2")); + centralWidget = new QWidget(MainWindow); + centralWidget->setObjectName(QString::fromUtf8("centralWidget")); + horizontalLayout_2 = new QHBoxLayout(centralWidget); + horizontalLayout_2->setSpacing(6); + horizontalLayout_2->setContentsMargins(11, 11, 11, 11); + horizontalLayout_2->setObjectName(QString::fromUtf8("horizontalLayout_2")); + groupBox_2 = new QGroupBox(centralWidget); + groupBox_2->setObjectName(QString::fromUtf8("groupBox_2")); + gridLayout = new QGridLayout(groupBox_2); + gridLayout->setSpacing(6); + gridLayout->setContentsMargins(11, 11, 11, 11); + gridLayout->setObjectName(QString::fromUtf8("gridLayout")); + channelLabels = new QPlainTextEdit(groupBox_2); + channelLabels->setObjectName(QString::fromUtf8("channelLabels")); + + gridLayout->addWidget(channelLabels, 0, 0, 1, 1); + + + horizontalLayout_2->addWidget(groupBox_2); + + verticalLayout = new QVBoxLayout(); + verticalLayout->setSpacing(6); + verticalLayout->setObjectName(QString::fromUtf8("verticalLayout")); + groupBox = new QGroupBox(centralWidget); + groupBox->setObjectName(QString::fromUtf8("groupBox")); + formLayout = new QFormLayout(groupBox); + formLayout->setSpacing(6); + formLayout->setContentsMargins(11, 11, 11, 11); + formLayout->setObjectName(QString::fromUtf8("formLayout")); + formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + label_4 = new QLabel(groupBox); + label_4->setObjectName(QString::fromUtf8("label_4")); + + formLayout->setWidget(0, QFormLayout::LabelRole, label_4); + + deviceNumber = new QSpinBox(groupBox); + deviceNumber->setObjectName(QString::fromUtf8("deviceNumber")); + deviceNumber->setMinimum(0); + deviceNumber->setMaximum(256); + deviceNumber->setValue(0); + + formLayout->setWidget(0, QFormLayout::FieldRole, deviceNumber); + + label_2 = new QLabel(groupBox); + label_2->setObjectName(QString::fromUtf8("label_2")); + + formLayout->setWidget(1, QFormLayout::LabelRole, label_2); + + channelCount = new QSpinBox(groupBox); + channelCount->setObjectName(QString::fromUtf8("channelCount")); + channelCount->setMinimum(1); + channelCount->setMaximum(256); + channelCount->setSingleStep(1); + channelCount->setValue(32); + + formLayout->setWidget(1, QFormLayout::FieldRole, channelCount); + + label = new QLabel(groupBox); + label->setObjectName(QString::fromUtf8("label")); + + formLayout->setWidget(2, QFormLayout::LabelRole, label); + + chunkSize = new QSpinBox(groupBox); + chunkSize->setObjectName(QString::fromUtf8("chunkSize")); + chunkSize->setMinimum(1); + chunkSize->setMaximum(1024); + chunkSize->setValue(10); + + formLayout->setWidget(2, QFormLayout::FieldRole, chunkSize); + + label_3 = new QLabel(groupBox); + label_3->setObjectName(QString::fromUtf8("label_3")); + + formLayout->setWidget(3, QFormLayout::LabelRole, label_3); + + samplingRate = new QComboBox(groupBox); + samplingRate->setObjectName(QString::fromUtf8("samplingRate")); + + formLayout->setWidget(3, QFormLayout::FieldRole, samplingRate); + + label_7 = new QLabel(groupBox); + label_7->setObjectName(QString::fromUtf8("label_7")); + + formLayout->setWidget(4, QFormLayout::LabelRole, label_7); + + useAUX = new QCheckBox(groupBox); + useAUX->setObjectName(QString::fromUtf8("useAUX")); + useAUX->setChecked(false); + + formLayout->setWidget(4, QFormLayout::FieldRole, useAUX); + + label_8 = new QLabel(groupBox); + label_8->setObjectName(QString::fromUtf8("label_8")); + + formLayout->setWidget(5, QFormLayout::LabelRole, label_8); + + activeShield = new QCheckBox(groupBox); + activeShield->setObjectName(QString::fromUtf8("activeShield")); + activeShield->setChecked(true); + + formLayout->setWidget(5, QFormLayout::FieldRole, activeShield); + + groupBox_3 = new QGroupBox(groupBox); + groupBox_3->setObjectName(QString::fromUtf8("groupBox_3")); + formLayout_2 = new QFormLayout(groupBox_3); + formLayout_2->setSpacing(6); + formLayout_2->setContentsMargins(11, 11, 11, 11); + formLayout_2->setObjectName(QString::fromUtf8("formLayout_2")); + formLayout_2->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + label_5 = new QLabel(groupBox_3); + label_5->setObjectName(QString::fromUtf8("label_5")); + + formLayout_2->setWidget(0, QFormLayout::LabelRole, label_5); + + label_6 = new QLabel(groupBox_3); + label_6->setObjectName(QString::fromUtf8("label_6")); + + formLayout_2->setWidget(1, QFormLayout::LabelRole, label_6); + + sampledMarkers = new QCheckBox(groupBox_3); + sampledMarkers->setObjectName(QString::fromUtf8("sampledMarkers")); + sampledMarkers->setChecked(true); + + formLayout_2->setWidget(1, QFormLayout::FieldRole, sampledMarkers); + + label_9 = new QLabel(groupBox_3); + label_9->setObjectName(QString::fromUtf8("label_9")); + + formLayout_2->setWidget(2, QFormLayout::LabelRole, label_9); + + sampledMarkersEEG = new QCheckBox(groupBox_3); + sampledMarkersEEG->setObjectName(QString::fromUtf8("sampledMarkersEEG")); + + formLayout_2->setWidget(2, QFormLayout::FieldRole, sampledMarkersEEG); + + unsampledMarkers = new QCheckBox(groupBox_3); + unsampledMarkers->setObjectName(QString::fromUtf8("unsampledMarkers")); + + formLayout_2->setWidget(0, QFormLayout::FieldRole, unsampledMarkers); + + + formLayout->setWidget(6, QFormLayout::SpanningRole, groupBox_3); + + + verticalLayout->addWidget(groupBox); + + verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); + + verticalLayout->addItem(verticalSpacer); + + horizontalLayout = new QHBoxLayout(); + horizontalLayout->setSpacing(6); + horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout")); + horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + + horizontalLayout->addItem(horizontalSpacer); + + linkButton = new QPushButton(centralWidget); + linkButton->setObjectName(QString::fromUtf8("linkButton")); + + horizontalLayout->addWidget(linkButton); + + + verticalLayout->addLayout(horizontalLayout); + + + horizontalLayout_2->addLayout(verticalLayout); + + MainWindow->setCentralWidget(centralWidget); + menuBar = new QMenuBar(MainWindow); + menuBar->setObjectName(QString::fromUtf8("menuBar")); + menuBar->setGeometry(QRect(0, 0, 360, 21)); + menuFile = new QMenu(menuBar); + menuFile->setObjectName(QString::fromUtf8("menuFile")); + MainWindow->setMenuBar(menuBar); + statusBar = new QStatusBar(MainWindow); + statusBar->setObjectName(QString::fromUtf8("statusBar")); + MainWindow->setStatusBar(statusBar); + + menuBar->addAction(menuFile->menuAction()); + menuFile->addAction(actionLoad_Configuration); + menuFile->addAction(actionSave_Configuration); + menuFile->addSeparator(); + menuFile->addAction(actionQuit_2); + + retranslateUi(MainWindow); + + samplingRate->setCurrentIndex(2); + + + QMetaObject::connectSlotsByName(MainWindow); + } // setupUi + + void retranslateUi(QMainWindow *MainWindow) + { + MainWindow->setWindowTitle(QApplication::translate("MainWindow", "ActiChamp Connector", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + MainWindow->setToolTip(QApplication::translate("MainWindow", "

For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.

", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + actionLoad_Configuration->setText(QApplication::translate("MainWindow", "Load Configuration", 0, QApplication::UnicodeUTF8)); + actionSave_Configuration->setText(QApplication::translate("MainWindow", "Save Configuration", 0, QApplication::UnicodeUTF8)); + actionQuit->setText(QApplication::translate("MainWindow", "Quit", 0, QApplication::UnicodeUTF8)); + actionQuit_2->setText(QApplication::translate("MainWindow", "Quit", 0, QApplication::UnicodeUTF8)); + groupBox_2->setTitle(QApplication::translate("MainWindow", "Channel Labels", 0, QApplication::UnicodeUTF8)); + channelLabels->setPlainText(QApplication::translate("MainWindow", "Fp1\n" +"Fp2\n" +"F7\n" +"F3\n" +"Fz\n" +"F4\n" +"F8\n" +"FC5\n" +"FC1\n" +"FC2\n" +"FC6\n" +"T7\n" +"C3\n" +"Cz\n" +"C4\n" +"T8\n" +"TP9\n" +"CP5\n" +"CP1\n" +"CP2\n" +"CP6\n" +"TP10\n" +"P7\n" +"P3\n" +"Pz\n" +"P4\n" +"P8\n" +"PO9\n" +"O1\n" +"Oz\n" +"O2\n" +"PO10 ", 0, QApplication::UnicodeUTF8)); + groupBox->setTitle(QApplication::translate("MainWindow", "Device Settings", 0, QApplication::UnicodeUTF8)); + label_4->setText(QApplication::translate("MainWindow", "Device Number", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + deviceNumber->setToolTip(QApplication::translate("MainWindow", "The number of the USB device (if multiple); the first one is #0.", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + label_2->setText(QApplication::translate("MainWindow", "Number of Channels", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + channelCount->setToolTip(QApplication::translate("MainWindow", "This must match the number of entries in the channel list", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + label->setText(QApplication::translate("MainWindow", "Chunk Size", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + chunkSize->setToolTip(QApplication::translate("MainWindow", "The number of samples per chunk emitted by the driver -- a small value will lead to lower overall latency but causes more CPU load", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + label_3->setText(QApplication::translate("MainWindow", "Sampling Rate", 0, QApplication::UnicodeUTF8)); + samplingRate->clear(); + samplingRate->insertItems(0, QStringList() + << QApplication::translate("MainWindow", "125 (resampled)", 0, QApplication::UnicodeUTF8) + << QApplication::translate("MainWindow", "250 (resampled)", 0, QApplication::UnicodeUTF8) + << QApplication::translate("MainWindow", "500 (resampled)", 0, QApplication::UnicodeUTF8) + << QApplication::translate("MainWindow", "1000 (resampled)", 0, QApplication::UnicodeUTF8) + << QApplication::translate("MainWindow", "10000 (native)", 0, QApplication::UnicodeUTF8) + << QApplication::translate("MainWindow", "50000 (native)", 0, QApplication::UnicodeUTF8) + << QApplication::translate("MainWindow", "100000 (native)", 0, QApplication::UnicodeUTF8) + ); +#ifndef QT_NO_TOOLTIP + samplingRate->setToolTip(QApplication::translate("MainWindow", "The sampling rate to use; higher sampling rates require more network bandwidth (and storage space if recording), particularly the very high rates of 10KHz+. The native rates are those that are natively supported by the hardware and the resampled rates are resampled in software (using a linear-phase sinc resampler that delays the output signal by 5 samples).", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + label_7->setText(QApplication::translate("MainWindow", "Use AUX Channels", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + useAUX->setToolTip(QApplication::translate("MainWindow", "If this is checked then the last 8 channels will hold the AUX signals; make sure to increase your channel count accordingly.", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + useAUX->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + label_8->setText(QApplication::translate("MainWindow", "Enable Active Shield", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + activeShield->setToolTip(QApplication::translate("MainWindow", "This will enable the active shielding of the cap; recommended.", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + activeShield->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + groupBox_3->setTitle(QApplication::translate("MainWindow", "LSL Trigger Output Style", 0, QApplication::UnicodeUTF8)); + label_5->setText(QApplication::translate("MainWindow", "Unsampled String Markers", 0, QApplication::UnicodeUTF8)); + label_6->setText(QApplication::translate("MainWindow", "Sampled String Markers", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + sampledMarkers->setToolTip(QApplication::translate("MainWindow", "

For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.

", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + sampledMarkers->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + label_9->setText(QApplication::translate("MainWindow", "Floating Point EEG Channel", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + sampledMarkersEEG->setToolTip(QApplication::translate("MainWindow", "

For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.

", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + sampledMarkersEEG->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + unsampledMarkers->setToolTip(QApplication::translate("MainWindow", "

For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.

", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + unsampledMarkers->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + linkButton->setText(QApplication::translate("MainWindow", "Link", 0, QApplication::UnicodeUTF8)); + menuFile->setTitle(QApplication::translate("MainWindow", "File", 0, QApplication::UnicodeUTF8)); + } // retranslateUi + +}; + +namespace Ui { + class MainWindow: public Ui_MainWindow {}; +} // namespace Ui + +QT_END_NAMESPACE + +#endif // UI_MAINWINDOW_H diff --git a/Apps/BrainProducts/ActiChamp/vc90.idb b/Apps/BrainProducts/ActiChamp/vc90.idb new file mode 100644 index 00000000..fd2309fe Binary files /dev/null and b/Apps/BrainProducts/ActiChamp/vc90.idb differ diff --git a/Apps/BrainProducts/BrainAmpSeries/BrainAmpSeries.vcproj b/Apps/BrainProducts/BrainAmpSeries/BrainAmpSeries.vcproj index b580fe24..74d10204 100644 --- a/Apps/BrainProducts/BrainAmpSeries/BrainAmpSeries.vcproj +++ b/Apps/BrainProducts/BrainAmpSeries/BrainAmpSeries.vcproj @@ -1,9 +1,10 @@ diff --git a/Apps/BrainProducts/BrainAmpSeries/BrainAmpSeries_config.cfg b/Apps/BrainProducts/BrainAmpSeries/BrainAmpSeries_config.cfg index bf261484..6aef4329 100644 --- a/Apps/BrainProducts/BrainAmpSeries/BrainAmpSeries_config.cfg +++ b/Apps/BrainProducts/BrainAmpSeries/BrainAmpSeries_config.cfg @@ -1,2 +1,2 @@ -13200032false \ No newline at end of file +13200032falsefalsetruefalse \ No newline at end of file diff --git a/Apps/BrainProducts/BrainAmpSeries/explanation_of_trigger_marker_types.pdf b/Apps/BrainProducts/BrainAmpSeries/explanation_of_trigger_marker_types.pdf new file mode 100644 index 00000000..13053e0d Binary files /dev/null and b/Apps/BrainProducts/BrainAmpSeries/explanation_of_trigger_marker_types.pdf differ diff --git a/Apps/BrainProducts/BrainAmpSeries/mainwindow-rev.cpp b/Apps/BrainProducts/BrainAmpSeries/mainwindow-rev.cpp new file mode 100644 index 00000000..e716d6cc --- /dev/null +++ b/Apps/BrainProducts/BrainAmpSeries/mainwindow-rev.cpp @@ -0,0 +1,359 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include +#include +#include +#include +#include +#include +#include + +const double sampling_rate = 5000.0; +const char *error_messages[] = {"No error.","Loss lock.","Low power.","Can't establish communication at start.","Synchronisation error"}; + +MainWindow::MainWindow(QWidget *parent, const std::string &config_file): QMainWindow(parent),ui(new Ui::MainWindow),hDevice(NULL) +{ + ui->setupUi(this); + + // parse startup config file + load_config(config_file); + + // make GUI connections + QObject::connect(ui->actionQuit, SIGNAL(triggered()), this, SLOT(close())); + QObject::connect(ui->linkButton, SIGNAL(clicked()), this, SLOT(link())); + QObject::connect(ui->actionLoad_Configuration, SIGNAL(triggered()), this, SLOT(load_config_dialog())); + QObject::connect(ui->actionSave_Configuration, SIGNAL(triggered()), this, SLOT(save_config_dialog())); + + g_unsampledMarkers = false; + g_sampledMarkers = true; + g_sampledMarkersEEG = false; +} + + +void MainWindow::load_config_dialog() { + QString sel = QFileDialog::getOpenFileName(this,"Load Configuration File","","Configuration Files (*.cfg)"); + if (!sel.isEmpty()) + load_config(sel.toStdString()); +} + +void MainWindow::save_config_dialog() { + QString sel = QFileDialog::getSaveFileName(this,"Save Configuration File","","Configuration Files (*.cfg)"); + if (!sel.isEmpty()) + save_config(sel.toStdString()); +} + +void MainWindow::closeEvent(QCloseEvent *ev) { + if (reader_thread_) + ev->ignore(); +} + +void MainWindow::load_config(const std::string &filename) { + using boost::property_tree::ptree; + ptree pt; + + // parse file + try { + read_xml(filename, pt); + } catch(std::exception &e) { + QMessageBox::information(this,"Error",(std::string("Cannot read config file: ")+= e.what()).c_str(),QMessageBox::Ok); + return; + } + + // get config values + try { + ui->deviceNumber->setValue(pt.get("settings.devicenumber",1)); + ui->channelCount->setValue(pt.get("settings.channelcount",32)); + ui->impedanceMode->setCurrentIndex(pt.get("settings.impedancemode",0)); + ui->resolution->setCurrentIndex(pt.get("settings.resolution",0)); + ui->dcCoupling->setCurrentIndex(pt.get("settings.dccoupling",0)); + ui->chunkSize->setValue(pt.get("settings.chunksize",32)); + ui->usePolyBox->setCheckState(pt.get("settings.usepolybox",false) ? Qt::Checked : Qt::Unchecked); + ui->unsampledMarkers->setCheckState(pt.get("settings.unsampledmarkers",false) ? Qt::Checked : Qt::Unchecked); + ui->sampledMarkers->setCheckState(pt.get("settings.sampledmarkers",true) ? Qt::Checked : Qt::Unchecked); + ui->sampledMarkersEEG->setCheckState(pt.get("settings.sampledmarkersEEG",false) ? Qt::Checked : Qt::Unchecked); + ui->channelLabels->clear(); + BOOST_FOREACH(ptree::value_type &v, pt.get_child("channels.labels")) + ui->channelLabels->appendPlainText(v.second.data().c_str()); + } catch(std::exception &) { + QMessageBox::information(this,"Error in Config File","Could not read out config parameters.",QMessageBox::Ok); + return; + } +} + +void MainWindow::save_config(const std::string &filename) { + using boost::property_tree::ptree; + ptree pt; + + // transfer UI content into property tree + try { + pt.put("settings.devicenumber",ui->deviceNumber->value()); + pt.put("settings.channelcount",ui->channelCount->value()); + pt.put("settings.impedancemode",ui->impedanceMode->currentIndex()); + pt.put("settings.resolution",ui->resolution->currentIndex()); + pt.put("settings.dccoupling",ui->dcCoupling->currentIndex()); + pt.put("settings.chunksize",ui->chunkSize->value()); + pt.put("settings.usepolybox",ui->usePolyBox->checkState()==Qt::Checked); + pt.put("settings.unsampledmarkers",ui->unsampledMarkers->checkState()==Qt::Checked); + pt.put("settings.sampledmarkers",ui->sampledMarkers->checkState()==Qt::Checked); + pt.put("settings.sampledmarkersEEG",ui->sampledMarkersEEG->checkState()==Qt::Checked); + + std::vector channelLabels; + boost::algorithm::split(channelLabels,ui->channelLabels->toPlainText().toStdString(),boost::algorithm::is_any_of("\n")); + BOOST_FOREACH(std::string &v, channelLabels) + pt.add("channels.labels.label", v); + } catch(std::exception &e) { + QMessageBox::critical(this,"Error",(std::string("Could not prepare settings for saving: ")+=e.what()).c_str(),QMessageBox::Ok); + } + + // write to disk + try { + write_xml(filename, pt); + } catch(std::exception &e) { + QMessageBox::critical(this,"Error",(std::string("Could not write to config file: ")+=e.what()).c_str(),QMessageBox::Ok); + } +} + + +// start/stop the BrainAmpSeries connection +void MainWindow::link() { + DWORD bytes_returned; + if (reader_thread_) { + // === perform unlink action === + try { + stop_ = true; + reader_thread_->interrupt(); + reader_thread_->join(); + reader_thread_.reset(); + if (hDevice>0) { + DeviceIoControl(hDevice, IOCTL_BA_STOP, NULL, 0, NULL, 0, &bytes_returned, NULL); + CloseHandle(hDevice); + hDevice = NULL; + } + } catch(std::exception &e) { + QMessageBox::critical(this,"Error",(std::string("Could not stop the background processing: ")+=e.what()).c_str(),QMessageBox::Ok); + return; + } + + // indicate that we are now successfully unlinked + ui->linkButton->setText("Link"); + } else { + // === perform link action === + + try { + // get the UI parameters... + int deviceNumber = ui->deviceNumber->value(); + int channelCount = ui->channelCount->value(); + int impedanceMode = ui->impedanceMode->currentIndex(); + int resolution = ui->resolution->currentIndex(); + int dcCoupling = ui->dcCoupling->currentIndex(); + int chunkSize = ui->chunkSize->value(); + bool usePolyBox = ui->usePolyBox->checkState()==Qt::Checked; + + g_unsampledMarkers = ui->unsampledMarkers->checkState()==Qt::Checked; + g_sampledMarkers = ui->sampledMarkers->checkState()==Qt::Checked; + g_sampledMarkersEEG = ui->sampledMarkersEEG->checkState()==Qt::Checked; + + std::vector channelLabels; + boost::algorithm::split(channelLabels,ui->channelLabels->toPlainText().toStdString(),boost::algorithm::is_any_of("\n")); + if (channelLabels.size() != channelCount) + throw std::runtime_error("The number of channels labels does not match the channel count device setting."); + + // try to open the device + std::string deviceName = "\\\\.\\BrainAmpUSB" + boost::lexical_cast(deviceNumber); + hDevice = CreateFileA(deviceName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, NULL); + if (hDevice == INVALID_HANDLE_VALUE) + throw std::runtime_error("Could not open USB device. Please make sure that the device is plugged in, turned on, and that the driver is installed correctly."); + + // get serial number + ULONG serialNumber=0; + if(!DeviceIoControl(hDevice, IOCTL_BA_GET_SERIALNUMBER, NULL, 0, &serialNumber, sizeof(serialNumber), &bytes_returned, NULL)) + std::cerr << "Could not get device serial number." << std::endl; + + // set up device parameters + BA_SETUP setup = {0}; + setup.nChannels = channelCount; + for (int c=0;c0) { + long error_code = 0; + if (DeviceIoControl(hDevice, IOCTL_BA_ERROR_STATE, NULL, 0, &error_code, sizeof(error_code), &bytes_returned, NULL) && bytes_returned) + msg = ((error_code&0xFFFF)>=0 && (error_code&0xFFFF)<=4) ? error_messages[error_code&0xFFFF] : "Unknown error (your driver version might not yet be supported)."; + else + msg = "Could not retrieve error message because the device is closed"; + CloseHandle(hDevice); + hDevice = NULL; + } + QMessageBox::critical(this,"Error",("Could not initialize the BrainAmpSeries interface: "+(e.what()+(" (driver message: "+msg+")"))).c_str(),QMessageBox::Ok); + return; + } + + // done, all successful + ui->linkButton->setText("Unlink"); + } +} + +// background data reader thread +void MainWindow::read_thread(int deviceNumber, ULONG serialNumber, int impedanceMode, int resolution, int dcCoupling, int chunkSize, int channelCount, std::vector channelLabels) { + + const float unit_scales[] = {0.1,0.5,10,152.6}; + const char *unit_strings[] = {"100 nV","500 nV","10 µV","152.6 µV"}; + + // reserve buffers to receive and send data + int chunk_words = chunkSize*(channelCount+1); + boost::int16_t *recv_buffer = new boost::int16_t[chunk_words]; + std::vector > send_buffer(chunkSize,std::vector(channelCount)); + + std::vector trigger_buffer(chunkSize); + std::vector> marker_buffer(chunkSize, std::vector(1)); + std::vector s_mrkr; + + try { + // create data streaminfo and append some meta-data + lsl::stream_info data_info("BrainAmpSeries-" + boost::lexical_cast(deviceNumber),"EEG",channelCount + (g_sampledMarkersEEG ? 1 : 0),sampling_rate,lsl::cf_float32,"BrainAmpSeries_" + boost::lexical_cast(deviceNumber) + "_" + boost::lexical_cast(serialNumber)); + lsl::xml_element channels = data_info.desc().append_child("channels"); + for (int k=0;k(serialNumber).c_str()); + // make a data outlet + lsl::stream_outlet data_outlet(data_info); + + // create unsampled marker streaminfo and outlet + lsl::stream_outlet *marker_outlet; + if(g_unsampledMarkers) { + lsl::stream_info marker_info("BrainAmpSeries-" + boost::lexical_cast(deviceNumber) + "-Markers","Markers",1,0,lsl::cf_string,"BrainAmpSeries_" + boost::lexical_cast(deviceNumber) + "_" + boost::lexical_cast(serialNumber) + "_markers"); + marker_outlet = new lsl::stream_outlet(marker_info); + } + + // create sampled marker streaminfo and outlet + lsl::stream_outlet *s_marker_outlet; + if(g_sampledMarkers) { + lsl::stream_info marker_info("BrainAmpSeries-" + boost::lexical_cast(deviceNumber) + "-Sampled-Markers","Markers",1,sampling_rate,lsl::cf_string,"BrainAmpSeries_" + boost::lexical_cast(deviceNumber) + "_" + boost::lexical_cast(serialNumber) + "_sampled_markers"); + s_marker_outlet = new lsl::stream_outlet(marker_info); + } + + + // enter transmission loop + DWORD bytes_read; + int last_mrk = 0; + int prev_markerSampled = 0; + int prev_markerEEG = 0; + float f_mrkr; + + float scale = unit_scales[resolution]; + while (!stop_) { + // read chunk into recv_buffer + if(!ReadFile(hDevice,recv_buffer,2*chunk_words,&bytes_read, NULL)) + throw std::runtime_error(("Could not read data, error code " + boost::lexical_cast(GetLastError())).c_str()); + if (bytes_read == 2*chunk_words) { + + double now = lsl::local_clock(); + + // reformat into send_buffer + for (int s=0;s(trigger_buffer[s])); + prev_markerEEG = trigger_buffer[s]; + send_buffer[channelCount] = f_mrkr; + } + + if (g_sampledMarkers) { + s_mrkr.clear(); + s_mrkr.push_back(trigger_buffer[s] == prev_markerSampled ? "" : boost::lexical_cast(trigger_buffer[s])); + marker_buffer.at(s) = s_mrkr; + //std::cout << "s: " << s << std::endl; + prev_markerSampled = trigger_buffer[s]; + } + + + } + + // push data chunk into the outlet + data_outlet.push_chunk(send_buffer,now); + + if(g_sampledMarkers) + s_marker_outlet->push_chunk(marker_buffer,now); + + // push markers into outlet + if(g_unsampledMarkers) { + // push markers into outlet + for (int s=0;s(mrk); + marker_outlet->push_sample(&mrk_string,now + (s + 1 - chunkSize)/sampling_rate); + last_mrk = mrk; + } + } + } + + } else { + // check for errors + long error_code=0; + if (DeviceIoControl(hDevice, IOCTL_BA_ERROR_STATE, NULL, 0, &error_code, sizeof(error_code), &bytes_read, NULL) && error_code) + throw std::runtime_error(((error_code&0xFFFF)>=0 && (error_code&0xFFFF)<=4) ? error_messages[error_code&0xFFFF] : "Unknown error (your driver version might not yet be supported)."); + boost::thread::yield(); + } + } + // need to explicitly delete these outlets + if(g_unsampledMarkers)delete(marker_outlet); + if(g_sampledMarkers)delete(s_marker_outlet); + } + catch(boost::thread_interrupted &) { + // thread was interrupted: no error + } + catch(std::exception &e) { + // any other error + QMessageBox::critical(this,"Error",(std::string("Error during processing: ")+=e.what()).c_str(),QMessageBox::Ok); + } + delete recv_buffer; +} + +MainWindow::~MainWindow() { + delete ui; +} diff --git a/Apps/BrainProducts/BrainAmpSeries/mainwindow.cpp b/Apps/BrainProducts/BrainAmpSeries/mainwindow.cpp index 54736b6b..4cf04999 100644 --- a/Apps/BrainProducts/BrainAmpSeries/mainwindow.cpp +++ b/Apps/BrainProducts/BrainAmpSeries/mainwindow.cpp @@ -23,6 +23,10 @@ MainWindow::MainWindow(QWidget *parent, const std::string &config_file): QMainWi QObject::connect(ui->linkButton, SIGNAL(clicked()), this, SLOT(link())); QObject::connect(ui->actionLoad_Configuration, SIGNAL(triggered()), this, SLOT(load_config_dialog())); QObject::connect(ui->actionSave_Configuration, SIGNAL(triggered()), this, SLOT(save_config_dialog())); + + g_unsampledMarkers = false; + g_sampledMarkers = true; + g_sampledMarkersEEG = false; } @@ -64,6 +68,9 @@ void MainWindow::load_config(const std::string &filename) { ui->dcCoupling->setCurrentIndex(pt.get("settings.dccoupling",0)); ui->chunkSize->setValue(pt.get("settings.chunksize",32)); ui->usePolyBox->setCheckState(pt.get("settings.usepolybox",false) ? Qt::Checked : Qt::Unchecked); + ui->unsampledMarkers->setCheckState(pt.get("settings.unsampledmarkers",false) ? Qt::Checked : Qt::Unchecked); + ui->sampledMarkers->setCheckState(pt.get("settings.sampledmarkers",true) ? Qt::Checked : Qt::Unchecked); + ui->sampledMarkersEEG->setCheckState(pt.get("settings.sampledmarkersEEG",false) ? Qt::Checked : Qt::Unchecked); ui->channelLabels->clear(); BOOST_FOREACH(ptree::value_type &v, pt.get_child("channels.labels")) ui->channelLabels->appendPlainText(v.second.data().c_str()); @@ -86,6 +93,9 @@ void MainWindow::save_config(const std::string &filename) { pt.put("settings.dccoupling",ui->dcCoupling->currentIndex()); pt.put("settings.chunksize",ui->chunkSize->value()); pt.put("settings.usepolybox",ui->usePolyBox->checkState()==Qt::Checked); + pt.put("settings.unsampledmarkers",ui->unsampledMarkers->checkState()==Qt::Checked); + pt.put("settings.sampledmarkers",ui->sampledMarkers->checkState()==Qt::Checked); + pt.put("settings.sampledmarkersEEG",ui->sampledMarkersEEG->checkState()==Qt::Checked); std::vector channelLabels; boost::algorithm::split(channelLabels,ui->channelLabels->toPlainText().toStdString(),boost::algorithm::is_any_of("\n")); BOOST_FOREACH(std::string &v, channelLabels) @@ -113,10 +123,12 @@ void MainWindow::link() { reader_thread_->interrupt(); reader_thread_->join(); reader_thread_.reset(); + int res = SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); if (hDevice>0) { DeviceIoControl(hDevice, IOCTL_BA_STOP, NULL, 0, NULL, 0, &bytes_returned, NULL); CloseHandle(hDevice); hDevice = NULL; + } } catch(std::exception &e) { QMessageBox::critical(this,"Error",(std::string("Could not stop the background processing: ")+=e.what()).c_str(),QMessageBox::Ok); @@ -137,6 +149,11 @@ void MainWindow::link() { int dcCoupling = ui->dcCoupling->currentIndex(); int chunkSize = ui->chunkSize->value(); bool usePolyBox = ui->usePolyBox->checkState()==Qt::Checked; + + g_unsampledMarkers = ui->unsampledMarkers->checkState()==Qt::Checked; + g_sampledMarkers = ui->sampledMarkers->checkState()==Qt::Checked; + g_sampledMarkersEEG = ui->sampledMarkersEEG->checkState()==Qt::Checked; + std::vector channelLabels; boost::algorithm::split(channelLabels,ui->channelLabels->toPlainText().toStdString(),boost::algorithm::is_any_of("\n")); if (channelLabels.size() != channelCount) @@ -165,6 +182,13 @@ void MainWindow::link() { for (int c=0;c > send_buffer(chunkSize,std::vector(channelCount)); + std::vector > send_buffer(chunkSize,std::vector(channelCount+(g_sampledMarkersEEG?1:0))); + + std::vector> marker_buffer(chunkSize, std::vector(1)); + std::vector s_mrkr; + std::vectortrigger_buffer(chunkSize); + + int res = SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); + + // for keeping track of sampled marker stream data + USHORT mrkr=0; + USHORT prev_mrkr = 0; + + // for keeping track of unsampled markers + USHORT us_prev_mrkr = 0; + + lsl::stream_outlet *marker_outlet; + lsl::stream_outlet *s_marker_outlet; try { // create data streaminfo and append some meta-data - lsl::stream_info data_info("BrainAmpSeries-" + boost::lexical_cast(deviceNumber),"EEG",channelCount,sampling_rate,lsl::cf_float32,"BrainAmpSeries_" + boost::lexical_cast(deviceNumber) + "_" + boost::lexical_cast(serialNumber)); + lsl::stream_info data_info("BrainAmpSeries-" + boost::lexical_cast(deviceNumber),"EEG",channelCount+(g_sampledMarkersEEG?1:0),sampling_rate,lsl::cf_float32,"BrainAmpSeries_" + boost::lexical_cast(deviceNumber) + "_" + boost::lexical_cast(serialNumber)); lsl::xml_element channels = data_info.desc().append_child("channels"); for (int k=0;k(deviceNumber) + "-Markers","Markers",1,0,lsl::cf_string,"BrainAmpSeries_" + boost::lexical_cast(deviceNumber) + "_" + boost::lexical_cast(serialNumber) + "_markers"); - lsl::stream_outlet marker_outlet(marker_info); + //// create marker streaminfo and outlet + //lsl::stream_info marker_info("BrainAmpSeries-" + boost::lexical_cast(deviceNumber) + "-Markers","Markers",1,0,lsl::cf_string,"BrainAmpSeries_" + boost::lexical_cast(deviceNumber) + "_" + boost::lexical_cast(serialNumber) + "_markers"); + //lsl::stream_outlet marker_outlet(marker_info); + + // create unsampled marker streaminfo and outlet + + if(g_unsampledMarkers) { + lsl::stream_info marker_info("BrainAmpSeries-" + boost::lexical_cast(deviceNumber) + "-Markers","Markers",1,0,lsl::cf_string,"BrainAmpSeries_" + boost::lexical_cast(deviceNumber) + "_" + boost::lexical_cast(serialNumber) + "_markers"); + marker_outlet = new lsl::stream_outlet(marker_info); + } + + // create sampled marker streaminfo and outlet + if(g_sampledMarkers) { + lsl::stream_info marker_info("BrainAmpSeries-" + boost::lexical_cast(deviceNumber) + "-Sampled-Markers","sampledMarkers",1,sampling_rate,lsl::cf_string,"BrainAmpSeries_" + boost::lexical_cast(deviceNumber) + "_" + boost::lexical_cast(serialNumber) + "_sampled_markers"); + s_marker_outlet = new lsl::stream_outlet(marker_info); + } // enter transmission loop DWORD bytes_read; - int last_mrk = 0; + + float scale = unit_scales[resolution]; while (!stop_) { // read chunk into recv_buffer if(!ReadFile(hDevice,recv_buffer,2*chunk_words,&bytes_read, NULL)) throw std::runtime_error(("Could not read data, error code " + boost::lexical_cast(GetLastError())).c_str()); + + if (bytes_read <= 0){ + // CPU saver, this is ok even at higher sampling rates + boost::this_thread::sleep(boost::posix_time::milliseconds(1)); + continue; + } + if (bytes_read == 2*chunk_words) { double now = lsl::local_clock(); + + + // reformat into send_buffer - for (int s=0;s(mrkr)); + + if (g_sampledMarkers || g_unsampledMarkers) { + s_mrkr.clear(); + s_mrkr.push_back(mrkr==prev_mrkr ? "" : boost::lexical_cast(mrkr)); + if(mrkr!=prev_mrkr){std::cout << "s: " << s << " mrkr: " << s_mrkr[0] << std::endl; + if(g_unsampledMarkers)marker_outlet->push_sample(&s_mrkr[0],now + (s + 1 - chunkSize)/sampling_rate); + } + marker_buffer.at(s) = s_mrkr; + } + prev_mrkr = mrkr; + + } + // push data chunk into the outlet data_outlet.push_chunk(send_buffer,now); + + if(g_sampledMarkers) + s_marker_outlet->push_chunk(marker_buffer,now); + // push markers into outlet - for (int s=0;s(mrk); - marker_outlet.push_sample(&mrk_string,now + (s + 1 - chunkSize)/sampling_rate); - last_mrk = mrk; - } - } + //if(g_unsampledMarkers) { + //// push markers into outlet + // for (int s=0;s(us_mrkr); + // marker_outlet->push_sample(&str,now + (s + 1 - chunkSize)/sampling_rate); + // us_prev_mrkr = us_mrkr; + // } + // } + // } + //} + } else { // check for errors long error_code=0; @@ -262,7 +365,9 @@ void MainWindow::read_thread(int deviceNumber, ULONG serialNumber, int impedance throw std::runtime_error(((error_code&0xFFFF)>=0 && (error_code&0xFFFF)<=4) ? error_messages[error_code&0xFFFF] : "Unknown error (your driver version might not yet be supported)."); boost::thread::yield(); } + int foo = 1; } + } catch(boost::thread_interrupted &) { // thread was interrupted: no error @@ -272,6 +377,8 @@ void MainWindow::read_thread(int deviceNumber, ULONG serialNumber, int impedance QMessageBox::critical(this,"Error",(std::string("Error during processing: ")+=e.what()).c_str(),QMessageBox::Ok); } delete recv_buffer; + if(g_unsampledMarkers)delete(marker_outlet); + if(g_sampledMarkers)delete(s_marker_outlet); } MainWindow::~MainWindow() { diff --git a/Apps/BrainProducts/BrainAmpSeries/mainwindow.h b/Apps/BrainProducts/BrainAmpSeries/mainwindow.h index 45dda04e..9be74898 100644 --- a/Apps/BrainProducts/BrainAmpSeries/mainwindow.h +++ b/Apps/BrainProducts/BrainAmpSeries/mainwindow.h @@ -54,6 +54,14 @@ private slots: bool stop_; // whether the reader thread is supposed to stop boost::shared_ptr reader_thread_; // our reader thread + bool g_unsampledMarkers; + bool g_sampledMarkers; + bool g_sampledMarkersEEG; + + bool pullUpHiBits; + bool pullUpLowBits; + USHORT g_pull_dir; + Ui::MainWindow *ui; }; diff --git a/Apps/BrainProducts/BrainAmpSeries/mainwindow.ui b/Apps/BrainProducts/BrainAmpSeries/mainwindow.ui index 6c21fe32..eaf91b06 100644 --- a/Apps/BrainProducts/BrainAmpSeries/mainwindow.ui +++ b/Apps/BrainProducts/BrainAmpSeries/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 317 - 302 + 407 + 406 @@ -238,6 +238,66 @@ PO10
+ + + + LSL Trigger Output Style + + + + + + Unsampled String Markers + + + + + + + <html><head/><body><p>For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.</p></body></html> + + + (check) + + + + + + + Sampled String Markers + + + + + + + <html><head/><body><p>For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.</p></body></html> + + + (check) + + + + + + + Floating Point EEG Channel + + + + + + + <html><head/><body><p>For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.</p></body></html> + + + (check) + + + + + + @@ -287,8 +347,8 @@ PO10 0 0 - 317 - 18 + 407 + 21 diff --git a/Apps/BrainProducts/BrainAmpSeries/ui_mainwindow.h b/Apps/BrainProducts/BrainAmpSeries/ui_mainwindow.h new file mode 100644 index 00000000..d190009b --- /dev/null +++ b/Apps/BrainProducts/BrainAmpSeries/ui_mainwindow.h @@ -0,0 +1,404 @@ +/******************************************************************************** +** Form generated from reading UI file 'mainwindow.ui' +** +** Created by: Qt User Interface Compiler version 4.8.6 +** +** WARNING! All changes made in this file will be lost when recompiling UI file! +********************************************************************************/ + +#ifndef UI_MAINWINDOW_H +#define UI_MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Ui_MainWindow +{ +public: + QAction *actionLoad_Configuration; + QAction *actionSave_Configuration; + QAction *actionQuit; + QAction *actionQuit_2; + QWidget *centralWidget; + QHBoxLayout *horizontalLayout_2; + QGroupBox *groupBox_2; + QGridLayout *gridLayout; + QPlainTextEdit *channelLabels; + QVBoxLayout *verticalLayout; + QGroupBox *groupBox; + QFormLayout *formLayout; + QLabel *label_4; + QSpinBox *deviceNumber; + QLabel *label_2; + QSpinBox *channelCount; + QLabel *label_3; + QComboBox *impedanceMode; + QLabel *label_5; + QComboBox *resolution; + QLabel *label_6; + QComboBox *dcCoupling; + QLabel *label; + QSpinBox *chunkSize; + QLabel *label_7; + QCheckBox *usePolyBox; + QGroupBox *groupBox_3; + QFormLayout *formLayout_2; + QLabel *label_8; + QCheckBox *unsampledMarkers; + QLabel *label_9; + QCheckBox *sampledMarkers; + QLabel *label_10; + QCheckBox *sampledMarkersEEG; + QSpacerItem *verticalSpacer; + QHBoxLayout *horizontalLayout; + QSpacerItem *horizontalSpacer; + QPushButton *linkButton; + QMenuBar *menuBar; + QMenu *menuFile; + QStatusBar *statusBar; + + void setupUi(QMainWindow *MainWindow) + { + if (MainWindow->objectName().isEmpty()) + MainWindow->setObjectName(QString::fromUtf8("MainWindow")); + MainWindow->resize(407, 406); + actionLoad_Configuration = new QAction(MainWindow); + actionLoad_Configuration->setObjectName(QString::fromUtf8("actionLoad_Configuration")); + actionSave_Configuration = new QAction(MainWindow); + actionSave_Configuration->setObjectName(QString::fromUtf8("actionSave_Configuration")); + actionQuit = new QAction(MainWindow); + actionQuit->setObjectName(QString::fromUtf8("actionQuit")); + actionQuit_2 = new QAction(MainWindow); + actionQuit_2->setObjectName(QString::fromUtf8("actionQuit_2")); + centralWidget = new QWidget(MainWindow); + centralWidget->setObjectName(QString::fromUtf8("centralWidget")); + horizontalLayout_2 = new QHBoxLayout(centralWidget); + horizontalLayout_2->setSpacing(6); + horizontalLayout_2->setContentsMargins(11, 11, 11, 11); + horizontalLayout_2->setObjectName(QString::fromUtf8("horizontalLayout_2")); + groupBox_2 = new QGroupBox(centralWidget); + groupBox_2->setObjectName(QString::fromUtf8("groupBox_2")); + gridLayout = new QGridLayout(groupBox_2); + gridLayout->setSpacing(6); + gridLayout->setContentsMargins(11, 11, 11, 11); + gridLayout->setObjectName(QString::fromUtf8("gridLayout")); + channelLabels = new QPlainTextEdit(groupBox_2); + channelLabels->setObjectName(QString::fromUtf8("channelLabels")); + + gridLayout->addWidget(channelLabels, 0, 0, 1, 1); + + + horizontalLayout_2->addWidget(groupBox_2); + + verticalLayout = new QVBoxLayout(); + verticalLayout->setSpacing(6); + verticalLayout->setObjectName(QString::fromUtf8("verticalLayout")); + groupBox = new QGroupBox(centralWidget); + groupBox->setObjectName(QString::fromUtf8("groupBox")); + formLayout = new QFormLayout(groupBox); + formLayout->setSpacing(6); + formLayout->setContentsMargins(11, 11, 11, 11); + formLayout->setObjectName(QString::fromUtf8("formLayout")); + formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + label_4 = new QLabel(groupBox); + label_4->setObjectName(QString::fromUtf8("label_4")); + + formLayout->setWidget(0, QFormLayout::LabelRole, label_4); + + deviceNumber = new QSpinBox(groupBox); + deviceNumber->setObjectName(QString::fromUtf8("deviceNumber")); + deviceNumber->setMinimum(1); + deviceNumber->setMaximum(256); + + formLayout->setWidget(0, QFormLayout::FieldRole, deviceNumber); + + label_2 = new QLabel(groupBox); + label_2->setObjectName(QString::fromUtf8("label_2")); + + formLayout->setWidget(1, QFormLayout::LabelRole, label_2); + + channelCount = new QSpinBox(groupBox); + channelCount->setObjectName(QString::fromUtf8("channelCount")); + channelCount->setMinimum(1); + channelCount->setMaximum(256); + channelCount->setValue(32); + + formLayout->setWidget(1, QFormLayout::FieldRole, channelCount); + + label_3 = new QLabel(groupBox); + label_3->setObjectName(QString::fromUtf8("label_3")); + + formLayout->setWidget(3, QFormLayout::LabelRole, label_3); + + impedanceMode = new QComboBox(groupBox); + impedanceMode->setObjectName(QString::fromUtf8("impedanceMode")); + + formLayout->setWidget(3, QFormLayout::FieldRole, impedanceMode); + + label_5 = new QLabel(groupBox); + label_5->setObjectName(QString::fromUtf8("label_5")); + + formLayout->setWidget(4, QFormLayout::LabelRole, label_5); + + resolution = new QComboBox(groupBox); + resolution->setObjectName(QString::fromUtf8("resolution")); + + formLayout->setWidget(4, QFormLayout::FieldRole, resolution); + + label_6 = new QLabel(groupBox); + label_6->setObjectName(QString::fromUtf8("label_6")); + + formLayout->setWidget(5, QFormLayout::LabelRole, label_6); + + dcCoupling = new QComboBox(groupBox); + dcCoupling->setObjectName(QString::fromUtf8("dcCoupling")); + + formLayout->setWidget(5, QFormLayout::FieldRole, dcCoupling); + + label = new QLabel(groupBox); + label->setObjectName(QString::fromUtf8("label")); + + formLayout->setWidget(2, QFormLayout::LabelRole, label); + + chunkSize = new QSpinBox(groupBox); + chunkSize->setObjectName(QString::fromUtf8("chunkSize")); + chunkSize->setMinimum(1); + chunkSize->setMaximum(1024); + chunkSize->setValue(32); + + formLayout->setWidget(2, QFormLayout::FieldRole, chunkSize); + + label_7 = new QLabel(groupBox); + label_7->setObjectName(QString::fromUtf8("label_7")); + + formLayout->setWidget(6, QFormLayout::LabelRole, label_7); + + usePolyBox = new QCheckBox(groupBox); + usePolyBox->setObjectName(QString::fromUtf8("usePolyBox")); + + formLayout->setWidget(6, QFormLayout::FieldRole, usePolyBox); + + groupBox_3 = new QGroupBox(groupBox); + groupBox_3->setObjectName(QString::fromUtf8("groupBox_3")); + formLayout_2 = new QFormLayout(groupBox_3); + formLayout_2->setSpacing(6); + formLayout_2->setContentsMargins(11, 11, 11, 11); + formLayout_2->setObjectName(QString::fromUtf8("formLayout_2")); + label_8 = new QLabel(groupBox_3); + label_8->setObjectName(QString::fromUtf8("label_8")); + + formLayout_2->setWidget(0, QFormLayout::LabelRole, label_8); + + unsampledMarkers = new QCheckBox(groupBox_3); + unsampledMarkers->setObjectName(QString::fromUtf8("unsampledMarkers")); + + formLayout_2->setWidget(0, QFormLayout::FieldRole, unsampledMarkers); + + label_9 = new QLabel(groupBox_3); + label_9->setObjectName(QString::fromUtf8("label_9")); + + formLayout_2->setWidget(1, QFormLayout::LabelRole, label_9); + + sampledMarkers = new QCheckBox(groupBox_3); + sampledMarkers->setObjectName(QString::fromUtf8("sampledMarkers")); + + formLayout_2->setWidget(1, QFormLayout::FieldRole, sampledMarkers); + + label_10 = new QLabel(groupBox_3); + label_10->setObjectName(QString::fromUtf8("label_10")); + + formLayout_2->setWidget(2, QFormLayout::LabelRole, label_10); + + sampledMarkersEEG = new QCheckBox(groupBox_3); + sampledMarkersEEG->setObjectName(QString::fromUtf8("sampledMarkersEEG")); + + formLayout_2->setWidget(2, QFormLayout::FieldRole, sampledMarkersEEG); + + + formLayout->setWidget(7, QFormLayout::SpanningRole, groupBox_3); + + + verticalLayout->addWidget(groupBox); + + verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); + + verticalLayout->addItem(verticalSpacer); + + horizontalLayout = new QHBoxLayout(); + horizontalLayout->setSpacing(6); + horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout")); + horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + + horizontalLayout->addItem(horizontalSpacer); + + linkButton = new QPushButton(centralWidget); + linkButton->setObjectName(QString::fromUtf8("linkButton")); + + horizontalLayout->addWidget(linkButton); + + + verticalLayout->addLayout(horizontalLayout); + + + horizontalLayout_2->addLayout(verticalLayout); + + MainWindow->setCentralWidget(centralWidget); + menuBar = new QMenuBar(MainWindow); + menuBar->setObjectName(QString::fromUtf8("menuBar")); + menuBar->setGeometry(QRect(0, 0, 407, 21)); + menuFile = new QMenu(menuBar); + menuFile->setObjectName(QString::fromUtf8("menuFile")); + MainWindow->setMenuBar(menuBar); + statusBar = new QStatusBar(MainWindow); + statusBar->setObjectName(QString::fromUtf8("statusBar")); + MainWindow->setStatusBar(statusBar); + + menuBar->addAction(menuFile->menuAction()); + menuFile->addAction(actionLoad_Configuration); + menuFile->addAction(actionSave_Configuration); + menuFile->addSeparator(); + menuFile->addAction(actionQuit_2); + + retranslateUi(MainWindow); + + QMetaObject::connectSlotsByName(MainWindow); + } // setupUi + + void retranslateUi(QMainWindow *MainWindow) + { + MainWindow->setWindowTitle(QApplication::translate("MainWindow", "BrainAmpSeries Connector", 0, QApplication::UnicodeUTF8)); + actionLoad_Configuration->setText(QApplication::translate("MainWindow", "Load Configuration", 0, QApplication::UnicodeUTF8)); + actionSave_Configuration->setText(QApplication::translate("MainWindow", "Save Configuration", 0, QApplication::UnicodeUTF8)); + actionQuit->setText(QApplication::translate("MainWindow", "Quit", 0, QApplication::UnicodeUTF8)); + actionQuit_2->setText(QApplication::translate("MainWindow", "Quit", 0, QApplication::UnicodeUTF8)); + groupBox_2->setTitle(QApplication::translate("MainWindow", "Channel Labels", 0, QApplication::UnicodeUTF8)); + channelLabels->setPlainText(QApplication::translate("MainWindow", "Fp1\n" +"Fp2\n" +"F7\n" +"F3\n" +"Fz\n" +"F4\n" +"F8\n" +"FC5\n" +"FC1\n" +"FC2\n" +"FC6\n" +"T7\n" +"C3\n" +"Cz\n" +"C4\n" +"T8\n" +"TP9\n" +"CP5\n" +"CP1\n" +"CP2\n" +"CP6\n" +"TP10\n" +"P7\n" +"P3\n" +"Pz\n" +"P4\n" +"P8\n" +"PO9\n" +"O1\n" +"Oz\n" +"O2\n" +"PO10 ", 0, QApplication::UnicodeUTF8)); + groupBox->setTitle(QApplication::translate("MainWindow", "Device Settings", 0, QApplication::UnicodeUTF8)); + label_4->setText(QApplication::translate("MainWindow", "Device Number", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + deviceNumber->setToolTip(QApplication::translate("MainWindow", "The number of the USB device (if multiple); the first one is #1.", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + label_2->setText(QApplication::translate("MainWindow", "Number of Channels", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + channelCount->setToolTip(QApplication::translate("MainWindow", "This must match the number of entries in the channel list", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + label_3->setText(QApplication::translate("MainWindow", "Impedance Mode", 0, QApplication::UnicodeUTF8)); + impedanceMode->clear(); + impedanceMode->insertItems(0, QStringList() + << QApplication::translate("MainWindow", "High", 0, QApplication::UnicodeUTF8) + << QApplication::translate("MainWindow", "Low", 0, QApplication::UnicodeUTF8) + ); +#ifndef QT_NO_TOOLTIP + impedanceMode->setToolTip(QApplication::translate("MainWindow", "The default setting is to operate in high-impedance mode (less need for perfect electrode contact)", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + label_5->setText(QApplication::translate("MainWindow", "Resolution", 0, QApplication::UnicodeUTF8)); + resolution->clear(); + resolution->insertItems(0, QStringList() + << QApplication::translate("MainWindow", "100 nV", 0, QApplication::UnicodeUTF8) + << QApplication::translate("MainWindow", "500 nV", 0, QApplication::UnicodeUTF8) + << QApplication::translate("MainWindow", "10 \302\265V", 0, QApplication::UnicodeUTF8) + << QApplication::translate("MainWindow", "152.6 \302\265V", 0, QApplication::UnicodeUTF8) + ); +#ifndef QT_NO_TOOLTIP + resolution->setToolTip(QApplication::translate("MainWindow", "Resolution of the measured signal", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + label_6->setText(QApplication::translate("MainWindow", "DC Coupling", 0, QApplication::UnicodeUTF8)); + dcCoupling->clear(); + dcCoupling->insertItems(0, QStringList() + << QApplication::translate("MainWindow", "AC", 0, QApplication::UnicodeUTF8) + << QApplication::translate("MainWindow", "DC", 0, QApplication::UnicodeUTF8) + ); +#ifndef QT_NO_TOOLTIP + dcCoupling->setToolTip(QApplication::translate("MainWindow", "The default is AC", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + label->setText(QApplication::translate("MainWindow", "Chunk Size", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + chunkSize->setToolTip(QApplication::translate("MainWindow", "The number of samples per chunk emitted by the driver -- a small value will lead to lower overall latency but causes more CPU load", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + label_7->setText(QApplication::translate("MainWindow", "Use PolyBox", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + usePolyBox->setToolTip(QApplication::translate("MainWindow", "If this is checked then the first 8 channels will hold the polybox signals; make sure to increase your channel count appropriately.", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + usePolyBox->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + groupBox_3->setTitle(QApplication::translate("MainWindow", "LSL Trigger Output Style", 0, QApplication::UnicodeUTF8)); + label_8->setText(QApplication::translate("MainWindow", "Unsampled String Markers", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + unsampledMarkers->setToolTip(QApplication::translate("MainWindow", "

For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.

", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + unsampledMarkers->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + label_9->setText(QApplication::translate("MainWindow", "Sampled String Markers", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + sampledMarkers->setToolTip(QApplication::translate("MainWindow", "

For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.

", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + sampledMarkers->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + label_10->setText(QApplication::translate("MainWindow", "Floating Point EEG Channel", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + sampledMarkersEEG->setToolTip(QApplication::translate("MainWindow", "

For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.

", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + sampledMarkersEEG->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + linkButton->setText(QApplication::translate("MainWindow", "Link", 0, QApplication::UnicodeUTF8)); + menuFile->setTitle(QApplication::translate("MainWindow", "File", 0, QApplication::UnicodeUTF8)); + } // retranslateUi + +}; + +namespace Ui { + class MainWindow: public Ui_MainWindow {}; +} // namespace Ui + +QT_END_NAMESPACE + +#endif // UI_MAINWINDOW_H diff --git a/Apps/BrainProducts/BrainAmpSeries/vc90.idb b/Apps/BrainProducts/BrainAmpSeries/vc90.idb new file mode 100644 index 00000000..56aff9cb Binary files /dev/null and b/Apps/BrainProducts/BrainAmpSeries/vc90.idb differ diff --git a/Apps/BrainProducts/LiveAmp/LiveAmp.cpp b/Apps/BrainProducts/LiveAmp/LiveAmp.cpp new file mode 100644 index 00000000..fbd06504 --- /dev/null +++ b/Apps/BrainProducts/LiveAmp/LiveAmp.cpp @@ -0,0 +1,447 @@ +#include "LiveAmp.h" +#include + + +// TODO: overload the constructor so that +// live amp objects can be initialized by serial number etc. +// +LiveAmp::LiveAmp(std::string serialNumberIn, float samplingRateIn, bool useSim, int recordingModeIn) { +//LiveAmp::LiveAmp(){ + int res; + char HWI[20]; + + + if(useSim) + strcpy_s(HWI, "SIM"); + else + strcpy_s(HWI, "ANY"); // todo: parameterize this + + res = ampEnumerateDevices(HWI, sizeof(HWI), "LiveAmp", 0); + + if (res <= 0) + throw std::runtime_error("No LiveAmp connected"); + else { + for(int i=0;i(i).c_str()); + msg.append(" error= " ); + msg.append(boost::lexical_cast(res).c_str()); // TODO: error enumeration from liveamp driver + throw std::runtime_error(msg); + } + char sVar[20]; + result = ampGetProperty(hndl, PG_DEVICE, i, DPROP_CHR_SerialNumber, sVar, sizeof(sVar)); + + // got a hit! + if(!(strcmp(sVar, serialNumberIn.c_str()))) { + + // set the sampling rate on the device + result = ampSetProperty(hndl, PG_DEVICE, 0, DPROP_F32_BaseSampleRate, &samplingRateIn, sizeof(samplingRateIn)); + if(result != AMP_OK){ + throw std::runtime_error(("Error setting sampling rate, error code: " + boost::lexical_cast(result)).c_str()); + return; + } + samplingRate = samplingRateIn; + + // set the device mode to recording + result = ampSetProperty(hndl, PG_DEVICE, 0, DPROP_I32_RecordingMode, &recordingModeIn, sizeof(recordingModeIn)); + if(result != AMP_OK){ + throw std::runtime_error(("Error setting acquisition mode, error code: " + boost::lexical_cast(result)).c_str()); + return; + } + recordingMode = recordingModeIn; + + // set the handle + h = hndl; + + // set the serial number + serialNumber = std::string(sVar); + + // set the available channels + result = ampGetProperty(h, PG_DEVICE, 0, DPROP_I32_AvailableChannels, &availableChannels, sizeof(availableChannels)); + if(result != AMP_OK) + throw std::runtime_error(("Error getting available channel count, error code: " + boost::lexical_cast(result)).c_str()); + + + + + break; + } + + } + } + +} + +LiveAmp::~LiveAmp(void){ +// +// close(); +//// int res = ampCloseDevice(h); +} + +void LiveAmp::enumerate(std::vector> &Data, bool useSim){ + + int res; + char HWI[20]; + + if(!ampData.empty()){ + throw std::runtime_error("Input ampData vector isn't empty"); + return; + } + + + if(useSim) + strcpy_s(HWI, "SIM"); + else + strcpy_s(HWI, "ANY"); + + res = ampEnumerateDevices(HWI, sizeof(HWI), "LiveAmp", 0); + + if (res <= 0) + throw std::runtime_error("No LiveAmp connected"); + else { + for(int i=0;i(i).c_str()); + msg.append(" error= " ); + msg.append(boost::lexical_cast(res).c_str()); // TODO: error enumeration from liveamp driver + throw std::runtime_error(msg); + } + + char sVar[20]; + result = ampGetProperty(hndl, PG_DEVICE, i, DPROP_CHR_SerialNumber, sVar, sizeof(sVar)); + if(result != AMP_OK) { + std::string msg = "Cannot get device serial number: "; + msg.append (boost::lexical_cast(i).c_str()); + msg.append(" error= " ); + msg.append(boost::lexical_cast(res).c_str()); // TODO: error enumeration from liveamp driver + throw std::runtime_error(msg); + } + else { + + int32_t iVar; + result = ampGetProperty(hndl, PG_DEVICE, i, DPROP_I32_AvailableChannels, &iVar, sizeof(iVar)); + if(result != AMP_OK) { + std::string msg = "Cannot get device channel count: "; + msg.append (boost::lexical_cast(i).c_str()); + msg.append(" error= " ); + msg.append(boost::lexical_cast(res).c_str()); // TODO: error enumeration from liveamp driver + throw std::runtime_error(msg); + } + else + ampData.push_back(std::make_pair(std::string(sVar), iVar)); + + } + + // immediately close the device??? + //result = ampCloseDevice(&hndl); + //if(result != AMP_OK) { + // std::string msg = "Cannot close device: "; + // msg.append(" error= " ); + // msg.append(boost::lexical_cast(result).c_str()); // TODO: error enumeration from liveamp driver + // throw std::runtime_error(msg); + //} + } + } +} + + +void LiveAmp::close(void){ + + int result = ampCloseDevice(h); + if(result != AMP_OK) { + std::string msg = "Cannot close device: "; + msg.append (serialNumber.c_str()); + msg.append(" error= " ); + msg.append(boost::lexical_cast(result).c_str()); // TODO: error enumeration from liveamp driver + throw std::runtime_error(msg); + } +} + +void LiveAmp::enableChannels(std::vector eegIndecesIn, bool auxEnable, bool accEnable, bool bipolarEnable) { + + int res; + int type; + int i; + int enable, wasEnabled; + int bipType = CT_BIP; + char cValue[20]; // for determining if the aux channel is an accelerometer or not + + if(!eegIndeces.empty())eegIndeces.clear(); + if(!auxIndeces.empty())auxIndeces.clear(); + if(!accIndeces.empty())accIndeces.clear(); + if(!trigIndeces.empty())trigIndeces.clear(); + enabledChannelCnt = 0; + int passCnt = 0; + + // make sure the liveamp has valid channels available + if(availableChannels == -1) { + throw std::runtime_error((std::string("Invalid number of available channels on device ") + serialNumber).c_str()); + return; + } + + // go through the available channels and enable them if they are chosen + for(i=0;i(res)).c_str()); + + if (type == CT_EEG || type == CT_BIP) { + // go through the requested eeg channel vector and enable on match + passCnt = 0; + for(std::vector::iterator it=eegIndecesIn.begin(); it!=eegIndecesIn.end();++it) { + if(*it==i){ + enable = true; + eegIndeces.push_back(i); + ++enabledChannelCnt; + passCnt++; + // I'm not sure if this check is necessary, but it's in the old code, so I am assuming it is + res = ampGetProperty(h, PG_CHANNEL, i, CPROP_B32_RecordingEnabled, &wasEnabled, sizeof(wasEnabled)); + if (res != AMP_OK) + throw std::runtime_error(("Error GetProperty enable for EEG channels, error: " + boost::lexical_cast(res)).c_str()); + + if(wasEnabled!=enable) { + res = ampSetProperty(h, PG_CHANNEL, i, CPROP_B32_RecordingEnabled, &enable, sizeof(enable)); + if (res != AMP_OK) + throw std::runtime_error(("Error SetProperty enable for EEG channels, error: " + boost::lexical_cast(res)).c_str()); + } + + if(type == CT_BIP && bipolarEnable == true){ + res = ampSetProperty(h, PG_CHANNEL, i, CPROP_I32_Type, &bipType, sizeof(bipType)); + if (res != AMP_OK) + throw std::runtime_error(("Error SetProperty enable BIPOLAR EEG channels, error: " + boost::lexical_cast(res)).c_str()); + } + } + } + } + + // if it is an aux channel see if it can/should be enabled and if it is an acc channel + if(type == CT_AUX){ + res = ampGetProperty(h, PG_CHANNEL, i, CPROP_CHR_Function, &cValue, sizeof(cValue)); + + if (res != AMP_OK) + throw std::runtime_error(("Error GetProperty CPROP_CHR_Function error: " + boost::lexical_cast(res)).c_str()); + + // check that this aux channel is an acc channel + if(cValue[0] == 'X' || cValue[0] == 'Y' ||cValue[0] == 'Z' || cValue[0] == 'x' ||cValue[0] == 'y' ||cValue[0] == 'z') { + + + enable = accEnable; + if(enable == true){ + accIndeces.push_back(i); + ++enabledChannelCnt; + } + res = ampGetProperty(h, PG_CHANNEL, i, CPROP_B32_RecordingEnabled, &wasEnabled, sizeof(wasEnabled)); + if (res != AMP_OK) + throw std::runtime_error(("Error SetProperty enable for ACC channels, error: " + boost::lexical_cast(res)).c_str()); + + // if requested, enable it + if(wasEnabled!=enable){ + res = ampSetProperty(h, PG_CHANNEL, i, CPROP_B32_RecordingEnabled, &enable, sizeof(enable)); + if (res != AMP_OK) + throw std::runtime_error(("Error SetProperty enable for ACC channels, error: " + boost::lexical_cast(res)).c_str()); + } + + } + + else { // this is a non-acc aux channel + + + enable = auxEnable; + if(enable==true) { + accIndeces.push_back(i); + ++enabledChannelCnt; + } + res = ampGetProperty(h, PG_CHANNEL, i, CPROP_B32_RecordingEnabled, &wasEnabled, sizeof(wasEnabled)); + if (res != AMP_OK) + throw std::runtime_error(("Error SetProperty enable for AUX channels, error: " + boost::lexical_cast(res)).c_str()); + + // if requested, enable it + if(wasEnabled!=enable){ + res = ampSetProperty(h, PG_CHANNEL, i, CPROP_B32_RecordingEnabled, &enable, sizeof(enable)); + if (res != AMP_OK) + throw std::runtime_error(("Error SetProperty enable for AUX channels, error: " + boost::lexical_cast(res)).c_str()); + } + + } + } + + // the triggers are always enabled, but we need to keep track of them for channel labelling purposes + if(type == CT_TRG || type == CT_DIG) { + trigIndeces.push_back(i); + ++enabledChannelCnt; + } + } + + // get the sample size in bytes and make an array of sample types + int datatype; + int cnt = 0; + sampleSize=0; + int enabled; + + for(int i=0;i(res)).c_str()); + +} + +void LiveAmp::stopAcquisition(void){ + + int res = ampStopAcquisition(h); + if(res != AMP_OK) + throw std::runtime_error(("Error stopping acquisition, error code: " + boost::lexical_cast(res)).c_str()); + +} + +int64_t LiveAmp::pullAmpData(BYTE* buffer, int bufferSize){ + int64_t samplesRead = ampGetData(h, buffer, bufferSize, 0); + return samplesRead; +} + +// TODO: overload this function to support other data types, maybe use a std::vector for this? +void LiveAmp::pushAmpData(BYTE* buffer, int bufferSize, int64_t samplesRead, std::vector> &outData) +{ + uint64_t sc; + + int offset = 0; + float sample = 0; + + + int64_t numSamples = samplesRead / sampleSize; + + std::vector tmpData; + + for (int s = 0; s < numSamples; s++) + { + offset = 0; + sc = *(uint64_t*)&buffer[s*sampleSize + offset]; + offset += 8; // sample counter offset + + tmpData.resize(enabledChannelCnt); + + for (int i=0; i < enabledChannelCnt; i++) + { + switch (dataTypeArray[i]) + { + case DT_INT16: + { + int16_t tmp = *(int16_t*)&buffer[s*sampleSize + offset]; + sample = (float) tmp; + offset += 2; + break; + } + case DT_UINT16: + { + uint16_t tmp = *(uint16_t*)&buffer[s*sampleSize + offset]; + sample = (float) tmp; + offset += 2; + break; + } + case DT_INT32: + { + int32_t tmp = *(int32_t*)&buffer[s*sampleSize + offset]; + sample = (float) tmp; + offset += 4; + break; + } + case DT_UINT32: + { + uint32_t tmp = *(uint32_t*)&buffer[s*sampleSize + offset]; + sample = (float) tmp; + offset += 4; + break; + } + case DT_FLOAT32: + { + float tmp = *(float*)&buffer[s*sampleSize + offset]; + sample = (float) tmp; + offset += 4; + break; + } + case DT_INT64: + { + int64_t tmp = *(int64_t*)&buffer[s*sampleSize + offset]; + sample = (float) tmp; + offset += 8; + break; + } + case DT_UINT64: + { + uint64_t tmp = *(uint64_t*)&buffer[s*sampleSize + offset]; + sample = (float) tmp; + offset += 8; + break; + } + case DT_FLOAT64: + { + + float tmp = *(float*)&buffer[s*sampleSize + offset]; + sample = (float) tmp; + offset += 8; + break; + } + default: + break; + } + + tmpData[i] = sample; + } + outData.push_back(tmpData); + } +} + diff --git a/Apps/BrainProducts/LiveAmp/LiveAmp.h b/Apps/BrainProducts/LiveAmp/LiveAmp.h new file mode 100644 index 00000000..2d9d2a62 --- /dev/null +++ b/Apps/BrainProducts/LiveAmp/LiveAmp.h @@ -0,0 +1,85 @@ +#ifndef LiveAmp_H +#define LiveAmp_H + +// Header file for higher level LiveAmp connection functions + +// LiveAmp API +#include "Amplifier_LIB.h" +#include +#include + + + + +class LiveAmp { + +private: + // data get from device (defined in construction) + // these are initialized in the constructor + HANDLE h; // device handle + std::string serialNumber; // serial number + int availableChannels; // number of available devices + + // these are set later on by the user + float samplingRate; + int recordingMode; // defaults to record mode + + // set during configuration + int dataTypeArray[100]; + int sampleSize; + + + // flexible? channel access arrays + std::vector channelIndexes; + std::vector eegIndeces; + std::vector auxIndeces; + std::vector accIndeces; + std::vector trigIndeces; + + int enabledChannelCnt; + +public: + // constructor + // initialize by at least the serial number and a container for the enumerated devices + LiveAmp::LiveAmp(std::string serialNumberIn, float samplingRateIn = 500, bool useSim = false, int recordingModeIn = RM_NORMAL); + //LiveAmp::LiveAmp (void); + // destructor + ~LiveAmp(); + + // get the serial numbers and channel counts of all available liveamps + static void enumerate(std::vector> &Data, bool useSim=false); + + // close live amp device + void close(); + + // enable requested channels: for now acc and aux are all or nothing, triggers are always on, and eeg channels can be selected + void enableChannels(std::vector eegIndecesIn, bool auxEnable, bool accEnable, bool bipolarEnable); + + // activate the configured device with enabled channels + void startAcquisition(void); + + // activate the configured device with enabled channels + void stopAcquisition(void); + + // get data from device + int64_t pullAmpData(BYTE* buffer, int bufferSize); + + // push it into a vector TODO: make this a template to support any buffer type + void pushAmpData(BYTE* buffer, int bufferSize, int64_t samplesRead, std::vector> &outData); + + // public data access methods + inline float getSamplingRate(void){return samplingRate;} + inline HANDLE getHandle(void){return h;} + inline std::string& getSerialNumber(void){return serialNumber;} + inline int getAvailableChannels(void){return availableChannels;} + inline int getRecordingMode(void){return recordingMode;} + inline std::vector& getEEGIndeces(void){return eegIndeces;} + inline std::vector& getAuxIndeces(void){return auxIndeces;} + inline std::vector& getAccIndeces(void){return accIndeces;} + inline std::vector& getTrigIndeces(void){return trigIndeces;} + inline int getEnabledChannelCnt(void){return enabledChannelCnt;} + inline int* getDataTypeArray(void){return dataTypeArray;} + inline int getSampleSize(void){return sampleSize;} +}; + +#endif //LiveAmp_H \ No newline at end of file diff --git a/Apps/BrainProducts/LiveAmp/LiveAmp2010.sln b/Apps/BrainProducts/LiveAmp/LiveAmp.sln similarity index 89% rename from Apps/BrainProducts/LiveAmp/LiveAmp2010.sln rename to Apps/BrainProducts/LiveAmp/LiveAmp.sln index 61b3d094..0a226cc4 100644 --- a/Apps/BrainProducts/LiveAmp/LiveAmp2010.sln +++ b/Apps/BrainProducts/LiveAmp/LiveAmp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LiveAmp2010", "LiveAmp2010.vcxproj", "{519985BC-76B9-4A27-A175-F16F22D7394D}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LiveAmp", "LiveAmp.vcxproj", "{519985BC-76B9-4A27-A175-F16F22D7394D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Apps/BrainProducts/LiveAmp/LiveAmp2010.vcxproj b/Apps/BrainProducts/LiveAmp/LiveAmp.vcxproj similarity index 96% rename from Apps/BrainProducts/LiveAmp/LiveAmp2010.vcxproj rename to Apps/BrainProducts/LiveAmp/LiveAmp.vcxproj index 143a723e..1b23a1b8 100644 --- a/Apps/BrainProducts/LiveAmp/LiveAmp2010.vcxproj +++ b/Apps/BrainProducts/LiveAmp/LiveAmp.vcxproj @@ -19,6 +19,7 @@ + @@ -31,7 +32,7 @@ $(QTSDK)\bin\moc.exe;mainwindow.h $(QTSDK)\bin\moc.exe -DUNICODE -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_DLL -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_HAVE_MMX -DQT_HAVE_3DNOW -DQT_HAVE_SSE -DQT_HAVE_MMXEXT -DQT_HAVE_SSE2 -DQT_THREAD_SUPPORT -I"(QTSDK)\include\QtCore" -I"(QTSDK)\include\QtGui" -I"(QTSDK)\include" -I"(BOOST_ROOT)" -I"c:\DEVEL\LSL\repo\Apps\PhaseSpace\..\..\LSL\include" -I"(QTSDK)\include\ActiveQt" -I"release" -I"." -I(QTSDK)\mkspecs\default -D_MSC_VER=1500 -DWIN32 mainwindow.h -o moc_mainwindow.cpp MOC mainwindow.h - ..\moc_mainwindow.h + moc_mainwindow.cpp $(QTSDK)\bin\moc.exe;mainwindow.h $(QT5SDK)\bin\moc.exe -DUNICODE -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_DLL -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_HAVE_MMX -DQT_HAVE_3DNOW -DQT_HAVE_SSE -DQT_HAVE_MMXEXT -DQT_HAVE_SSE2 -DQT_THREAD_SUPPORT -I"$(QT5SDK)\include\QtCore" -I"$(QT5SDK)\include\QtGui" -I"$(QT5SDK)\include\QyWidgets" -I"$(QT5SDK)\include\QtMultimedia" -I"$(QT5SDK)\include" -I"..\..\LSL\liblsl\include" -I"..\..\LSL\liblsl\external" -I"$(BOOST_ROOT)" -I"$(QT5SDK)\include\ActiveQt" -I"release" -I"." -I$(QT5SDK)\mkspecs\default -D_MSC_VER=1500 -DWIN32 mainwindow.h -o moc_mainwindow.cpp MOC mainwindow.h @@ -43,6 +44,7 @@ $(QT5SDK)\bin\moc.exe;mainwindow.h + @@ -70,8 +72,8 @@ {519985BC-76B9-4A27-A175-F16F22D7394D} Win32Proj - ActiChamp2010 - LiveAmp2010 + LiveAmp + LiveAmp @@ -107,7 +109,7 @@ false - debug\ + .\ debug\ LiveAmpD true @@ -123,7 +125,7 @@ debug\ - ActiChamp.exe + LiveAmp.exe false @@ -131,7 +133,7 @@ release\ - ActiChamp.exe + LiveAmp.exe false @@ -163,9 +165,9 @@ Console true - $(OutDir)\ActiChampD.exe - $(QTSDK)\lib;$(LSL_LIB)\bin;$(BOOST_ROOT)\stage\lib;%(AdditionalLibraryDirectories) - $(QTSDK)\lib\qtmaind.lib;$(QTSDK)\lib\QtGuid4.lib;$(QTSDK)\lib\QtCored4.lib;ActiChamp_x86.lib;%(AdditionalDependencies) + $(OutDir)\LiveAmpD.exe + $(QTSDK)\lib;$(LSL_LIB)\bin;$(BOOST_ROOT)\lib;%(AdditionalLibraryDirectories) + $(QTSDK)\lib\qtmaind.lib;$(QTSDK)\lib\QtGuid4.lib;$(QTSDK)\lib\QtCored4.lib;LiveAmpLib2.lib;%(AdditionalDependencies) false 0 0 @@ -204,10 +206,10 @@ Windows - true + false true true - $(LSL_LIB)\bin;$(QTSDK)\lib;$(BOOST_ROOT)\stage\lib;%(AdditionalLibraryDirectories) + $(LSL_LIB)\bin;$(QTSDK)\lib;$(BOOST_ROOT)\lib;%(AdditionalLibraryDirectories) $(QTSDK)\lib\qtmain.lib;$(QTSDK)\lib\QtGui4.lib;$(QTSDK)\lib\QtCore4.lib;LiveAmpLib2.lib;%(AdditionalDependencies) false 0 @@ -235,7 +237,7 @@ CompileAsCpp - $(OutDir)\debug\ActiChamp64.exe + $(OutDir)\LiveAmp64.exe $(BOOST_ROOT)\lib64;$(QT5SDK)\lib;..\..\..\..\LSL\liblsl\bin;%(AdditionalLibraryDirectories) $(QT5SDK)\lib\qtmain.lib;$(QT5SDK)\lib\Qt5Gui.lib;$(QT5SDK)\lib\Qt5Core.lib;$(QT5SDK)\lib\Qt5Widgets.lib;$(QT5SDK)\lib\Qt5Network.lib;ActiChamp_x64.lib;%(AdditionalDependencies) false diff --git a/Apps/BrainProducts/LiveAmp/LiveAmp2010.vcxproj.filters b/Apps/BrainProducts/LiveAmp/LiveAmp.vcxproj.filters similarity index 57% rename from Apps/BrainProducts/LiveAmp/LiveAmp2010.vcxproj.filters rename to Apps/BrainProducts/LiveAmp/LiveAmp.vcxproj.filters index 0e4d54a6..a2683e84 100644 --- a/Apps/BrainProducts/LiveAmp/LiveAmp2010.vcxproj.filters +++ b/Apps/BrainProducts/LiveAmp/LiveAmp.vcxproj.filters @@ -1,47 +1,50 @@  - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {e95d3bd8-73b2-4103-93d7-d48e43663475} - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - + + Source + - Source Files + Source - Source Files + Source Generated Files + + Headers + + + Headers + Generated Files - - Header Files - - Header Files + Headers Form Files + + + {384ea93a-7d18-43ef-9295-255bdadbc034} + + + {55fdd8af-1c65-414c-b6a0-d0a104ef47e5} + + + {e2ec2d5a-8acb-4461-9a0f-b699d881bc82} + + + {abe8ec52-0ea9-4301-a129-10d711a75259} + + \ No newline at end of file diff --git a/Apps/BrainProducts/LiveAmp/LiveAmpHelperFuncs-ratko.cpp b/Apps/BrainProducts/LiveAmp/LiveAmpHelperFuncs-ratko.cpp new file mode 100644 index 00000000..d8fb3e6f --- /dev/null +++ b/Apps/BrainProducts/LiveAmp/LiveAmpHelperFuncs-ratko.cpp @@ -0,0 +1,525 @@ +#include "LiveAmpHelperFuncs.h" + +// configure LiveAmp amplifier, sampling rate, mode, channels etc... +bool MainWindow::ConfigureLiveAmp(void) +{ + try + { + // amplifier configuration + float fVar = (mSamplingRate); + int res = ampSetProperty(mDevice, PG_DEVICE, 0, DPROP_F32_BaseSampleRate, &fVar, sizeof(fVar)); + if(res != AMP_OK) + throw std::runtime_error(("Error setting sampling rate, error code: " + boost::lexical_cast(res)).c_str()); + + int mode = RM_NORMAL; + res = ampSetProperty(mDevice, PG_DEVICE, 0, DPROP_I32_RecordingMode, &mode, sizeof(mode)); + if(res != AMP_OK) + throw std::runtime_error(("Error setting acquisition mode, error code: " + boost::lexical_cast(res)).c_str()); + + } + catch(std::exception &e) { + // generate error message + std::string msg = "ConfigureLiveAmp error. "; + + QMessageBox::critical(this,"Error", (std::string("ConfigureLiveAmp error: ") + e.what()).c_str() ,QMessageBox::Ok); + return false; + } + + return true; +} + +bool MainWindow::InitializeLiveAmp() +{ + HANDLE hlocDevice = NULL; + int res; + char HWI[20]; + strcpy_s(HWI, "ANY"); // use word SIM to simulate the LiveAmp + + mDevice = NULL; + std::string serialN = ui->deviceSerialNumber->text().toStdString(); + + QMessageBox *ms = new QMessageBox(this); + ms->setWindowTitle("connecting to LiveAmp amplifier...it takes some seconds..."); + ms->setText("Connecting to LiveAmp amplifier(s)..."); + ms->show(); + + res = ampEnumerateDevices(HWI, sizeof(HWI), "LiveAmp", 0); + + ms->close(); + delete ms; + + if (res <= 0) + throw std::runtime_error("No LiveAmp connected"); + else + { + int numDevices = res; + for (int i = 0; i < numDevices; i++) + { + hlocDevice = NULL; + res = ampOpenDevice(i, &hlocDevice); + if(res != AMP_OK) + { + std::string msg = "Cannot open device: "; + msg.append (boost::lexical_cast(i).c_str()); + msg.append(" error= " ); + msg.append(boost::lexical_cast(res).c_str()); + throw std::runtime_error(msg); + } + + char sVar[20]; + res = ampGetProperty(hlocDevice, PG_DEVICE, i, DPROP_CHR_SerialNumber, sVar, sizeof(sVar)); + if(res != AMP_OK) + { + std::string msg = "Cannot read Serial number, "; + msg.append (boost::lexical_cast(i).c_str()); + msg.append(" error= " ); + msg.append(boost::lexical_cast(res).c_str()); + throw std::runtime_error(msg); + } + + //QMessageBox::information(this,"Information", sVar, QMessageBox::Ok); + + int check = strcmp(sVar, serialN.c_str()); + if(check == 0) + { + QMessageBox::information(this,"Information", std::string("LiveAmp SN: " + serialN + " successfully connected!").c_str(), QMessageBox::Ok); + + mDevice = hlocDevice; // save device handler + return true; + } + else + { + res = ampCloseDevice(hlocDevice); + if(res != AMP_OK) + { + std::string msg = "Cannot close device: "; + msg.append (boost::lexical_cast(i).c_str()); + msg.append(" error= " ); + msg.append(boost::lexical_cast(res).c_str()); + throw std::runtime_error(msg); + } + } + } + } + + if(mDevice == NULL) + { + std::string msg = "There is no LiveAmp with serial number: " + serialN + " detected!"; + throw std::runtime_error(msg); + } + + return false; +} + + + +// enables physical channels on LiveAmp +bool EnableLiveAmpChannels(bool enableAUX, bool enableACC) +{ + int avlbchannels; + bool useBipolar = ui->useBipolar->checkState()==Qt::Checked; + + try + { + int res = ampGetProperty(mDevice, PG_DEVICE, 0, DPROP_I32_AvailableChannels, &avlbchannels, sizeof(avlbchannels)); + //QMessageBox::information(this, "Information", std::string("avlbchannels= " + boost::lexical_cast(avlbchannels)).c_str(), QMessageBox::Ok); + + for (int c = 0; c < avlbchannels; c++) + { + int type; + + res = ampGetProperty(mDevice, PG_CHANNEL, c, CPROP_I32_Type, &type, sizeof(type)); + if(res != AMP_OK) + throw std::runtime_error(("Error getting property for channel type: error code: " + boost::lexical_cast(res)).c_str()); + + //QMessageBox::information(this, "Information", std::string("channel #' " + boost::lexical_cast(c)).c_str(), QMessageBox::Ok); + if (type == CT_AUX) + { + char cValue[20]; + int enabled; + + res = ampGetProperty(mDevice, PG_CHANNEL, c, CPROP_CHR_Function, &cValue, sizeof(cValue)); + if (res != AMP_OK) + throw std::runtime_error(("Error GetProperty CPROP_CHR_Function error: " + boost::lexical_cast(res)).c_str()); + + res = ampGetProperty(mDevice, PG_CHANNEL, c, CPROP_B32_RecordingEnabled, &enabled, sizeof(enabled)); + if (res != AMP_OK) + { + //QMessageBox::information(this, "Information", ("Error GetProperty enable for ACC channels, error: " + boost::lexical_cast(res)).c_str() , QMessageBox::Ok); + throw std::runtime_error(("Error SetProperty enable for ACC channels, error: " + boost::lexical_cast(res)).c_str()); + } + + if(cValue == NULL || cValue[0] == NULL || !(cValue[0] == 'X' || cValue[0] == 'Y' ||cValue[0] == 'Z' || cValue[0] == 'x' ||cValue[0] == 'y' ||cValue[0] == 'z')) + { + if(enabled != enableAUX) + { + enabled = enableAUX; + + // Enables/disables AUX channels + res = ampSetProperty(mDevice, PG_CHANNEL, c, CPROP_B32_RecordingEnabled, &enabled, sizeof(enabled)); + if (res != AMP_OK) + throw std::runtime_error(("Error SetProperty enable for ACC channels, error: " + boost::lexical_cast(res)).c_str()); + } + } + else + { + if(enabled != enableACC) + { + enabled = enableACC; + + // Enables/disables ACC channels + res = ampSetProperty(mDevice, PG_CHANNEL, c, CPROP_B32_RecordingEnabled, &enabled, sizeof(enabled)); + if (res != AMP_OK) + throw std::runtime_error(("Error SetProperty enable for ACC channels, error: " + boost::lexical_cast(res)).c_str()); + } + } + + } + + + else if (type == CT_EEG || type == CT_BIP) + { + int enableEEG = true; + // match indexes of input channel list (from UI List control) + if(c >= mChannelCount) + { + enableEEG = false; + res = ampSetProperty(mDevice, PG_CHANNEL, c, CPROP_B32_RecordingEnabled, &enableEEG, sizeof(enableEEG)); + if (res != AMP_OK) + throw std::runtime_error(("Error SetProperty disable for EEG channels, error: " + boost::lexical_cast(res)).c_str()); + } + else + { + res = ampSetProperty(mDevice, PG_CHANNEL, c, CPROP_B32_RecordingEnabled, &enableEEG, sizeof(enableEEG)); + if (res != AMP_OK) + throw std::runtime_error(("Error SetProperty enable for EEG channels, error: " + boost::lexical_cast(res)).c_str()); + + + if(useBipolar && c > 23 && c < 32 ) // set last 8 channels to bipolar + { + int bipType = CT_BIP; + res = ampSetProperty(mDevice, PG_CHANNEL, c, CPROP_I32_Type, &bipType, sizeof(bipType)); + if (res != AMP_OK) + throw std::runtime_error(("Error SetProperty enable BIPOLAR EEG channels, error: " + boost::lexical_cast(res)).c_str()); + + } + + } + } + + // Trigger channel cannot be enabled disabled + else if (type == CT_TRG || type == CT_DIG) + continue; + } + } + catch(std::exception &e) { + QMessageBox::critical(this,"Error", (std::string("ConfigureLiveAmp error. ") + e.what()).c_str() ,QMessageBox::Ok); + return false; + } + + return true; +} + +bool GenerateUsedPhysicalChannelIndexes(void) +{ + int enableCh; + int type; + int allEnabledChannel; // only used==enabled channel + + eegIndexes.clear(); + auxIndexes.clear(); + accIndexes.clear(); + mTrigInIndexes.clear(); + mChannelIndexes.clear(); + + // after enabling and disabling physical channels by LibveAmp, the order (indexing) changes... it must be checked again the channel types and indexing. + int avlbchannels; + + try + { + int res = ampGetProperty(mDevice, PG_DEVICE, 0, DPROP_I32_AvailableChannels, &avlbchannels, sizeof(avlbchannels)); + for (int c = 0; c < avlbchannels; c++) + { + res = ampGetProperty(mDevice, PG_CHANNEL, c, CPROP_B32_RecordingEnabled, &enableCh, sizeof(enableCh)); + if(res != AMP_OK) + throw std::runtime_error(("Error getting property for channel CPROP_B32_RecordingEnabled: error code: " + boost::lexical_cast(res)).c_str()); + + if(!enableCh) + continue; + + res = ampGetProperty(mDevice, PG_CHANNEL, c, CPROP_I32_Type, &type, sizeof(type)); + if(res != AMP_OK) + throw std::runtime_error(("Error getting property for channel type: error code: " + boost::lexical_cast(res)).c_str()); + + if (type == CT_AUX) + { + char cValue[20]; + res = ampGetProperty(mDevice, PG_CHANNEL, c, CPROP_CHR_Function, &cValue, sizeof(cValue)); + if (res != AMP_OK) + throw std::runtime_error(("Error GetProperty CPROP_CHR_Function error: " + boost::lexical_cast(res)).c_str()); + + if(cValue[0] == 'X' || cValue[0] == 'Y' ||cValue[0] == 'Z' || cValue[0] == 'x' ||cValue[0] == 'y' ||cValue[0] == 'z') + accIndexes.push_back(c); + + else + auxIndexes.push_back(c); + } + + else if (type == CT_EEG || type == CT_BIP) + eegIndexes.push_back(c); + + + else if (type == CT_TRG || type == CT_DIG) + { + char cValue[20]; + res = ampGetProperty(mDevice, PG_CHANNEL, c, CPROP_CHR_Function, &cValue, sizeof(cValue)); + if (res != AMP_OK) + throw std::runtime_error(("Error GetProperty CPROP_CHR_Function for CT_TRG, error: " + boost::lexical_cast(res)).c_str()); + + // we are interested only on Trigger Inputs... + if(strcmp("Trigger Input", cValue) == 0 ) + mTrigInIndexes.push_back(c); + } + + allEnabledChannel++; + + if (type == CT_EEG || type == CT_BIP || type == CT_AUX) + { + float gain = 2; + res = ampSetProperty(mDevice, PG_CHANNEL, c, CPROP_F32_Gain, &gain, sizeof(gain)); + if (res != AMP_OK) + throw std::runtime_error(("Error SetProperty CPROP_F32_Gain, error: " + boost::lexical_cast(res)).c_str()); + } + } + } + + catch(std::exception &e) { + QMessageBox::critical(this,"Error", (std::string("GenerateUsedPhysicalChannelIndexes error. ") + e.what()).c_str() ,QMessageBox::Ok); + return false; + } + + //QMessageBox::information(this, "Information", std::string("eegIndexes # " + boost::lexical_cast(eegIndexes.size())).c_str(), QMessageBox::Ok); + + // copy indexes of eeg, aux and acc physical channels + for (int i=0; i < eegIndexes.size(); i++) + mChannelIndexes.push_back(eegIndexes[i]); + + //QMessageBox::information(this, "Information", std::string("auxIndexes # " + boost::lexical_cast(auxIndexes.size())).c_str(), QMessageBox::Ok); + for (int i=0; i < auxIndexes.size(); i++) + mChannelIndexes.push_back(auxIndexes[i]); + + //QMessageBox::information(this, "Information", std::string("accIndexes # " + boost::lexical_cast(accIndexes.size())).c_str(), QMessageBox::Ok); + for (int i=0; i < accIndexes.size(); i++) + mChannelIndexes.push_back(accIndexes[i]); + + return true; + +} + + +// adjust channel labels, in case that there are more channels send to LSL (like aux or acc) +void AdjustChannelLabels(std::vector& inpuChannelLabels, std::vector &adjustedChannelLabels) +{ + // there is order of channels by LiveAmp : EEG + AUX + ACC + if(inpuChannelLabels.size() != eegIndexes.size()) + QMessageBox::critical(this,"Error", "AdjustChannelLabels error, number of elements in input channel labels doesn't match!" ,QMessageBox::Ok); + + adjustedChannelLabels.clear(); + adjustedChannelLabels.resize(inpuChannelLabels.size()); + + for (int i = 0; i < inpuChannelLabels.size(); i++) + adjustedChannelLabels[i] = inpuChannelLabels[i]; + + + for (int i = 0; i < auxIndexes.size(); i++) + { + std::string aux = "AUX"; + aux += boost::lexical_cast(i); + adjustedChannelLabels.push_back (aux); + } + + for (int i = 0; i < accIndexes.size(); i++) + { + std::string acc; + std::string ix = boost::lexical_cast(i/3 + 1); + + if (i%3 == 0) + acc = "X" + ix; + else if (i%3 == 1) + acc = "Y" + ix; + else if (i%3 == 2) + acc = "Z" + ix; + + //QMessageBox::information(this, "Information", (std::string("ACC= ") + acc).c_str(), QMessageBox::Ok); + + adjustedChannelLabels.push_back (acc); + } + +} + + + +// extracts recorded samples form LiveAMp structure and copies to LSL buffer structure, ready to send over LSL +void PrepareDataToSendOverLSL(std::vector> &LiveAmptData, std::vector> &LSLData, std::vector &trigger_buffer) +{ + int numSamples = LiveAmptData.size(); + + if(numSamples <= 0) + return; + + int lslChannleCount = mChannelIndexes.size(); + + + for (int s = 0; s < numSamples; s++) + { + std::vector locBuffer (lslChannleCount); + for (int i=0; i < lslChannleCount; i++) + locBuffer[i] = LiveAmptData[s][mChannelIndexes[i]]; + + LSLData.push_back(locBuffer); + trigger_buffer.push_back(LiveAmptData[s][mTrigInIndexes[0]]); // for now we use only the first Trigger In channel! + } +} + +void ExtractLiveAmpData(BYTE* buffer, int seamples_read, int samplesize, int *dataTypes, int usedChannelsCnt, std::vector> &extractData) +{ + uint64_t sc; + int numSamples = seamples_read / samplesize; + int offset = 0; + double sample = 0; + + extractData.clear(); + extractData.resize(numSamples); + + for (int s = 0; s < numSamples; s++) + { + offset = 0; + sc = *(uint64_t*)&buffer[s*samplesize + offset]; + offset += 8; // sample counter offset + + extractData[s].resize(usedChannelsCnt); + + for (int i=0; i < usedChannelsCnt; i++) + { + switch (dataTypes[i]) + { + case DT_INT16: + { + int16_t tmp = *(int16_t*)&buffer[s*samplesize + offset]; + sample = (double) tmp; + offset += 2; + break; + } + case DT_UINT16: + { + uint16_t tmp = *(uint16_t*)&buffer[s*samplesize + offset]; + sample = (double) tmp; + offset += 2; + break; + } + case DT_INT32: + { + int32_t tmp = *(int32_t*)&buffer[s*samplesize + offset]; + sample = (double) tmp; + offset += 4; + break; + } + case DT_UINT32: + { + uint32_t tmp = *(uint32_t*)&buffer[s*samplesize + offset]; + sample = (double) tmp; + offset += 4; + break; + } + case DT_FLOAT32: + { + float tmp = *(float*)&buffer[s*samplesize + offset]; + sample = (double) tmp; + offset += 4; + break; + } + case DT_INT64: + { + int64_t tmp = *(int64_t*)&buffer[s*samplesize + offset]; + sample = (double) tmp; + offset += 8; + break; + } + case DT_UINT64: + { + uint64_t tmp = *(uint64_t*)&buffer[s*samplesize + offset]; + sample = (double) tmp; + offset += 8; + break; + } + case DT_FLOAT64: + { + + double tmp = *(double*)&buffer[s*samplesize + offset]; + sample = (double) tmp; + offset += 8; + break; + } + default: + break; + } + + extractData[s][i] = sample; + } + } +} + +// Get the current sample size in byte +int LiveAmp_SampleSize(HANDLE hDevice, int *typeArray, int* usedChannelsCnt) +{ + int res; + int channels, datatype; + BOOL enabled; + int bytesize = 0; + int cntUsedCh = 0; + + // iterate through all enabled channels + res = ampGetProperty(hDevice, PG_DEVICE, 0, DPROP_I32_AvailableChannels, &channels, sizeof(channels)); + for (int c = 0; c < channels; c++) + { + res = ampGetProperty(hDevice, PG_CHANNEL, c, CPROP_B32_RecordingEnabled, &enabled, sizeof(enabled)); + if (enabled) + { + res = ampGetProperty(hDevice, PG_CHANNEL, c, CPROP_I32_DataType, &datatype, sizeof(datatype)); + + typeArray[cntUsedCh] = datatype; + cntUsedCh++; + + switch (datatype) + { + case DT_INT16: + case DT_UINT16: + { + bytesize += 2; + } + break; + case DT_INT32: + case DT_UINT32: + case DT_FLOAT32: + { + bytesize += 4; + + } + break; + case DT_INT64: + case DT_UINT64: + case DT_FLOAT64: + { + bytesize += 8; + } + break; + default: + break; + } + } + } + // add the sample counter size + bytesize += 8; + *usedChannelsCnt = cntUsedCh; + return bytesize; +} \ No newline at end of file diff --git a/Apps/BrainProducts/LiveAmp/LiveAmp_config.cfg b/Apps/BrainProducts/LiveAmp/LiveAmp_config.cfg index a0fc1008..b40d6c86 100644 --- a/Apps/BrainProducts/LiveAmp/LiveAmp_config.cfg +++ b/Apps/BrainProducts/LiveAmp/LiveAmp_config.cfg @@ -1,2 +1,2 @@ -054203-007732200falsefalsetrue \ No newline at end of file +054203-007732200falsefalsetruefalsetruefalsetruefalsefalse \ No newline at end of file diff --git a/Apps/BrainProducts/LiveAmp/explanation_of_trigger_marker_types.pdf b/Apps/BrainProducts/LiveAmp/explanation_of_trigger_marker_types.pdf new file mode 100644 index 00000000..13053e0d Binary files /dev/null and b/Apps/BrainProducts/LiveAmp/explanation_of_trigger_marker_types.pdf differ diff --git a/Apps/BrainProducts/LiveAmp/mainwindow.cpp b/Apps/BrainProducts/LiveAmp/mainwindow.cpp index 9615dbb5..30fcb502 100644 --- a/Apps/BrainProducts/LiveAmp/mainwindow.cpp +++ b/Apps/BrainProducts/LiveAmp/mainwindow.cpp @@ -9,23 +9,16 @@ #include #include +#include "LiveAmp.h" + #define VERSIONSTREAM(version) version.Major << "." << version.Minor << "." << version.Build << "." << version.Revision -HANDLE mDevice; -const double sampling_rates[] = {250,500,1000}; -int mSamplingRate; -int mChannelCount = 0; -std::vector mChannelIndexes; -std::vector eegIndexes; -std::vector auxIndexes; -std::vector accIndexes; -std::vector mTrigInIndexes; +const int sampling_rates[] = {250,500,1000}; int LiveAmp_SampleSize(HANDLE hDevice, int *typeArray, int* usedChannelsCnt); -MainWindow::MainWindow(QWidget *parent, const std::string &config_file): QMainWindow(parent),ui(new Ui::MainWindow) -{ +MainWindow::MainWindow(QWidget *parent, const std::string &config_file): QMainWindow(parent),ui(new Ui::MainWindow) { ui->setupUi(this); // parse startup config file @@ -36,8 +29,14 @@ MainWindow::MainWindow(QWidget *parent, const std::string &config_file): QMainWi QObject::connect(ui->linkButton, SIGNAL(clicked()), this, SLOT(link())); QObject::connect(ui->actionLoad_Configuration, SIGNAL(triggered()), this, SLOT(load_config_dialog())); QObject::connect(ui->actionSave_Configuration, SIGNAL(triggered()), this, SLOT(save_config_dialog())); + QObject::connect(ui->refreshDevices,SIGNAL(clicked()),this,SLOT(refresh_devices())); + QObject::connect(ui->deviceCb,SIGNAL(currentIndexChanged(int)),this,SLOT(choose_device(int))); + + unsampledMarkers = false; + sampledMarkers = true; + sampledMarkersEEG = false; - mDevice = NULL; + } @@ -80,6 +79,12 @@ void MainWindow::load_config(const std::string &filename) { ui->useBipolar->setCheckState(pt.get("settings.useBipolar",false) ? Qt::Checked : Qt::Unchecked); ui->useAUX->setCheckState(pt.get("settings.useAux",false) ? Qt::Checked : Qt::Unchecked); ui->useACC->setCheckState(pt.get("settings.useACC",true) ? Qt::Checked : Qt::Unchecked); + ui->unsampledMarkers->setCheckState(pt.get("settings.unsampledmarkers",false) ? Qt::Checked : Qt::Unchecked); + ui->sampledMarkers->setCheckState(pt.get("settings.sampledmarkers",true) ? Qt::Checked : Qt::Unchecked); + ui->sampledMarkersEEG->setCheckState(pt.get("settings.sampledmarkersEEG",false) ? Qt::Checked : Qt::Unchecked); + ui->inTrigger->setCheckState(pt.get("settings.intrigger",true) ? Qt::Checked : Qt::Unchecked); + ui->outTrigger->setCheckState(pt.get("settings.outtrigger",false) ? Qt::Checked : Qt::Unchecked); + ui->digiTrigger->setCheckState(pt.get("settings.digitrigger",false) ? Qt::Checked : Qt::Unchecked); ui->channelLabels->clear(); BOOST_FOREACH(ptree::value_type &v, pt.get_child("channels.labels")) ui->channelLabels->appendPlainText(v.second.data().c_str()); @@ -102,6 +107,13 @@ void MainWindow::save_config(const std::string &filename) { pt.put("settings.useBipolar",ui->useBipolar->checkState()==Qt::Checked); pt.put("settings.useAux",ui->useAUX->checkState()==Qt::Checked); pt.put("settings.activeshield",ui->useACC->checkState()==Qt::Checked); + pt.put("settings.unsampledmarkers",ui->unsampledMarkers->checkState()==Qt::Checked); + pt.put("settings.sampledmarkers",ui->sampledMarkers->checkState()==Qt::Checked); + pt.put("settings.sampledmarkersEEG",ui->sampledMarkersEEG->checkState()==Qt::Checked); + pt.put("settings.intrigger",ui->inTrigger->checkState()==Qt::Checked); + pt.put("settings.outtrigger",ui->outTrigger->checkState()==Qt::Checked); + pt.put("settings.digitrigger",ui->digiTrigger->checkState()==Qt::Checked); + std::vector channelLabels; boost::algorithm::split(channelLabels,ui->channelLabels->toPlainText().toStdString(),boost::algorithm::is_any_of("\n")); BOOST_FOREACH(std::string &v, channelLabels) @@ -119,14 +131,55 @@ void MainWindow::save_config(const std::string &filename) { } +void MainWindow::refresh_devices(){ + + ampData.clear(); + + this->setCursor(Qt::WaitCursor); + + LiveAmp::enumerate(ampData, ui->useSim->checkState()); + + this->setCursor(Qt::ArrowCursor); + + if(!live_amp_sns.empty()) + live_amp_sns.clear(); + // if we have liveamps, enumerate them in the gui: + if(!ampData.empty()) { + ui->deviceCb->clear(); + std::stringstream ss; + int i=0; + QStringList qsl; + for(std::vector>::iterator it=ampData.begin(); it!=ampData.end();++it){ + ss.clear(); + ss << it->first << " (" << it->second << ")"; + auto x = ss.str(); // oh, c++... + qsl << QString(x.c_str()); // oh, Qt... + live_amp_sns.push_back(it->first); + } + ui->deviceCb->addItems(qsl); + choose_device(0); + } + +} + + +// handle changes in chosen device +void MainWindow::choose_device(int which){ + + ui->deviceSerialNumber->setText(QString(live_amp_sns[which].c_str())); + +} + // start/stop the LiveAmp connection void MainWindow::link() { + if (reader_thread_) { // === perform unlink action === try { stop_ = true; reader_thread_->join(); reader_thread_.reset(); + int res = SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); } catch(std::exception &e) { QMessageBox::critical(this,"Error",(std::string("Could not stop the background processing: ")+=e.what()).c_str(),QMessageBox::Ok); return; @@ -135,205 +188,333 @@ void MainWindow::link() { // indicate that we are now successfully unlinked ui->linkButton->setText("Link"); } else { + // === perform link action === try { // get the UI parameters... std::string serialN = ui->deviceSerialNumber->text().toStdString(); - mChannelCount = ui->channelCount->value(); + + //mChannelCount = ui->channelCount->value(); int chunkSize = ui->chunkSize->value(); - mSamplingRate = sampling_rates[ui->samplingRate->currentIndex()]; + //mSamplingRate + int samplingRate = sampling_rates[ui->samplingRate->currentIndex()]; bool useAUX = ui->useAUX->checkState()==Qt::Checked; bool useACC = ui->useACC->checkState()==Qt::Checked; + bool useBipolar = ui->useBipolar->checkState()==Qt::Checked; + bool useSim = ui->useSim->checkState()==Qt::Checked; + + // query gui which trigger channles to record + trigger_indeces.clear(); + if(ui->inTrigger->checkState()==Qt::Checked)trigger_indeces.push_back(0); + if(ui->outTrigger->checkState()==Qt::Checked)trigger_indeces.push_back(1); + if(ui->digiTrigger->checkState()==Qt::Checked)trigger_indeces.push_back(2); + + unsampledMarkers = ui->unsampledMarkers->checkState()==Qt::Checked; + sampledMarkers = ui->sampledMarkers->checkState()==Qt::Checked; + sampledMarkersEEG = ui->sampledMarkersEEG->checkState()==Qt::Checked; + + std::vector eegChannelLabels; + boost::algorithm::split(eegChannelLabels,ui->channelLabels->toPlainText().toStdString(),boost::algorithm::is_any_of("\n")); + if (eegChannelLabels.size() != ui->channelCount->value()){ + QMessageBox::critical(this,"Error","The number of eeg channels labels does not match the channel count device setting.",QMessageBox::Ok); + return; + } - std::vector channelLabels; - boost::algorithm::split(channelLabels,ui->channelLabels->toPlainText().toStdString(),boost::algorithm::is_any_of("\n")); - if (channelLabels.size() != mChannelCount) - throw std::runtime_error("The number of channels labels does not match the channel count device setting."); - // try to verify that the requested device is available + // print library version t_VersionNumber version; GetLibraryVersion(&version); std::cout << "Library Version " << VERSIONSTREAM(version) << std::endl; - if(!InitializeLiveAmp()) - return; + // set the sampling rate and serial number + float fSamplingRate = (float) samplingRate; + std::string strSerialNumber = ui->deviceSerialNumber->text().toStdString(); + + // prepare the liveamp object + liveAmp = NULL; + + // change GUI + this->setCursor(Qt::WaitCursor); + + // construct + liveAmp = new LiveAmp(strSerialNumber, fSamplingRate, useSim, RM_NORMAL); + + // change GUI + this->setCursor(Qt::ArrowCursor); + + // report + if(liveAmp!=NULL) + QMessageBox::information( this,"Connected!", "Click OK to proceed",QMessageBox::Button::Ok); + + else { + QMessageBox::critical( this,"Error", "Could not connect to LiveAmp. Please restart the device and check connections.",QMessageBox::Button::Ok); + ui->linkButton->setText("Link"); + return; + } + + + // for now this is hard coded + // we simply enumerate eegchannels via labels + // this should be flexible and settable via the GUI + // i.e. the user can map channel labels to eegIndeces + std::vectoreegIndeces; + eegIndeces.clear(); + for(int i=0;ienableChannels(eegIndeces, useAUX, useACC, useBipolar); // start reader thread stop_ = false; - int deviceNumber=0; - reader_thread_.reset(new boost::thread(&MainWindow::read_thread, this, chunkSize, mSamplingRate, useAUX, useACC,channelLabels)); - } + reader_thread_.reset(new boost::thread(&MainWindow::read_thread, this, chunkSize, samplingRate, useAUX, useACC, useBipolar, eegChannelLabels)); + + } catch(std::exception &e) { - catch(std::exception &e) { // generate error message std::string msg = "Could not retrieve driver error message"; int errorcode=0; - if (mDevice) { - ampCloseDevice(mDevice); - mDevice = NULL; - } + delete liveAmp; QMessageBox::critical(this,"Error",("Could not initialize the LiveAmp interface: "+(e.what()+(" (driver message: "+msg+")"))).c_str(),QMessageBox::Ok); + ui->linkButton->setEnabled(true); + ui->linkButton->setText("Link"); + this->setCursor(Qt::ArrowCursor); return; } // done, all successful + ui->linkButton->setEnabled(true); ui->linkButton->setText("Unlink"); } } -bool _resample = false; -int numbresSamples = 0; + // background data reader thread -void MainWindow::read_thread(int chunkSize, int samplingRate, bool useAUX, bool useACC, std::vector channelLabels) { - - int res = 0; +void MainWindow::read_thread(int chunkSize, int samplingRate, bool useAUX, bool useACC, bool useBipolar, std::vector eegChannelLabels){ + + int res = SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); + bool started = false; BYTE *buffer = NULL; - std::string serialNumber = ui->deviceSerialNumber->text().toStdString(); + // for chunk storage + int sampleCount; - - try { - - if(!ConfigureLiveAmp()) // configure LiveAMp parameters - return; - - if(!EnableLiveAmpChannels(useAUX, useACC)) - return; + // number of output triggers, input is always 3 + int triggerCount = trigger_indeces.size(); + int inputTriggerCount = 3; - if(!GenerateUsedPhysicalChannelIndexes()) - return; + // number of eeg+aux+acc channels (nothing special to do here, just shove it into a vector of vectors for lsl push) + int eegAuxAccChannelCount = eegChannelLabels.size(); + if(useAUX) eegAuxAccChannelCount += liveAmp->getAuxIndeces().size(); + if(useACC) eegAuxAccChannelCount += liveAmp->getAccIndeces().size(); + // these are just used for local loop storage + std::vector eeg_buffer(eegAuxAccChannelCount); + std::vector str_marker_buffer(triggerCount); + std::vector int_marker_buffer(triggerCount); + + // this one has the enabled channels for incoming data + std::vector> liveamp_buffer(chunkSize,std::vector(liveAmp->getEnabledChannelCnt())); - int dataTypeArray[80]; - int usedChannelsCounter=0; - int sampleSize = LiveAmp_SampleSize(mDevice, dataTypeArray, &usedChannelsCounter); + // this one has thr full signal for lsl push + std::vector> eeg_aux_acc_buffer(chunkSize,std::vector(eegAuxAccChannelCount + (sampledMarkersEEG ? triggerCount : 0))); + + // declarations for sampled/unsampled trigger marker record + // declare this anyway, tho not always used + std::vector> sampled_marker_buffer(chunkSize, std::vector(triggerCount)); + std::vector> unsampled_marker_buffer(chunkSize, std::vector(triggerCount)); - res = ampStartAcquisition(mDevice); - if(res != AMP_OK) - throw std::runtime_error(("Error starting acquisition, error code: " + boost::lexical_cast(res)).c_str()); + // for keeping track of trigger signal changes, which is all we are interested in + float f_mrkr; + std::vector prev_marker_float(triggerCount); + for(std::vector::iterator it=prev_marker_float.begin(); it!=prev_marker_float.end(); ++it) + *it = 0.0; - started = true; + try { - std::vector> liveamp_buffer(chunkSize,std::vector(usedChannelsCounter)); - std::vector> lslsend_buffer(chunkSize,std::vector(usedChannelsCounter)); - std::vector trigger_buffer(1); + // start data streaming + liveAmp->startAcquisition(); - std::vector adjustedChannelLabels; - AdjustChannelLabels(channelLabels, adjustedChannelLabels); + // get a local copy of this so we don't have to copy on each loop iteration + std::vectortriggerIndeces = liveAmp->getTrigIndeces(); // create data streaminfo and append some meta-data - lsl::stream_info data_info("LiveAmpSN- " + serialNumber,"EEG", adjustedChannelLabels.size(), samplingRate,lsl::cf_float32,"LiveAmpSN-" + serialNumber); + lsl::stream_info data_info("LiveAmpSN-" + liveAmp->getSerialNumber(),"EEG", eegAuxAccChannelCount + (sampledMarkersEEG ? triggerCount : 0), samplingRate,lsl::cf_float32,"LiveAmpSN-" + liveAmp->getSerialNumber()); lsl::xml_element channels = data_info.desc().append_child("channels"); - for (std::size_t k=0;kgetAuxIndeces().size();k++) + channels.append_child("channel") + .append_child_value("type","AUX") + .append_child_value("unit","microvolts"); + + // append the accelerometer channel metadata + for (std::size_t k=0;kgetAccIndeces().size();k++) + channels.append_child("channel") + .append_child_value("type","ACC") + .append_child_value("unit","microvolts") + .append_child_value("direction",(k%3==0 ? "X":(k%3 ? "Y":"Z")) ); + + if(sampledMarkersEEG){ + // append the trigger channel metadata + for (std::size_t k=0;kgetTrigIndeces().size();k++) + channels.append_child("channel") + .append_child_value("type","Trigger") + .append_child_value("unit","microvolts"); + } + data_info.desc().append_child("acquisition") .append_child_value("manufacturer","Brain Products"); + // make a data outlet lsl::stream_outlet data_outlet(data_info); // create marker streaminfo and outlet - lsl::stream_info marker_info("LiveAmpSN- " + serialNumber + "-Markers","Markers",1,0,lsl::cf_string,"LiveAmpSN-" + serialNumber + "_markers"); - lsl::stream_outlet marker_outlet(marker_info); + lsl::stream_outlet *marker_outlet; + if(unsampledMarkers) { + lsl::stream_info marker_info("LiveAmpSN-" + liveAmp->getSerialNumber() + "-Markers","Markers", 1, 0, lsl::cf_string,"LiveAmpSN-" + liveAmp->getSerialNumber() + "_markers"); + marker_outlet = new lsl::stream_outlet(marker_info); + } + + // sampled trigger stream as string + lsl::stream_outlet *s_marker_outlet; + if(sampledMarkers) { + lsl::stream_info s_marker_info("LiveAmpSN-" + liveAmp->getSerialNumber() + "-Sampled-Markers","sampledMarkers", triggerCount, samplingRate, lsl::cf_string,"LiveAmpSN-" + liveAmp->getSerialNumber() + "_sampled_markers"); + s_marker_outlet = new lsl::stream_outlet(s_marker_info); + // ditch the outlet if we don't need it (need to do it this way in order to trick C++ compiler into using this object conditionally) + } // enter transmission loop int last_mrk = 0; - int bytes_read, samples_read; + int bytes_read; // read data stream from amplifier - int32_t BufferSize = (chunkSize + 10) * sampleSize; - buffer = new BYTE[BufferSize]; + int32_t bufferSize = (chunkSize + 10) * liveAmp->getSampleSize(); + buffer = new BYTE[bufferSize]; - trigger_buffer.clear(); - lslsend_buffer.clear(); - //QMessageBox::information(this,"Information", std::string("start thread size()").c_str(), QMessageBox::Ok); + sampled_marker_buffer.clear(); + eeg_aux_acc_buffer.clear(); + int64_t samples_read; while (!stop_) { - samples_read = ampGetData(mDevice, buffer, BufferSize, 0); - if (samples_read <= 0) - continue; - //QMessageBox::information(this,"Information", std::string("samples_read = " + boost::lexical_cast(samples_read)).c_str(), QMessageBox::Ok); + // pull the data from the amplifier + samples_read = liveAmp->pullAmpData(buffer, bufferSize); + + if (samples_read <= 0){ + // CPU saver, this is ok even at higher sampling rates + boost::this_thread::sleep(boost::posix_time::milliseconds(1)); + continue; + } - ExtractLiveAmpData(buffer, samples_read, sampleSize, dataTypeArray, usedChannelsCounter, liveamp_buffer); - PrepareDataToSendOverLSL(liveamp_buffer, lslsend_buffer, trigger_buffer); - //QMessageBox::information(this,"Information", std::string("liveamp_buffer size()" + boost::lexical_cast(lslsend_buffer.size())).c_str(), QMessageBox::Ok); + // push the data into a vector of vector of doubles (note: TODO: template-ify this so that it takes any vector type) + liveAmp->pushAmpData(buffer, bufferSize, samples_read, liveamp_buffer); + + // check to see that we got any samples + sampleCount = liveamp_buffer.size(); - if(lslsend_buffer.size() >= chunkSize) - { - //QMessageBox::information(this,"Information", std::string("lslsend_buffer size()" + boost::lexical_cast(lslsend_buffer.size())).c_str(), QMessageBox::Ok); - double now = lsl::local_clock(); - int sendChannels = lslsend_buffer[0].size(); - std::vector> send_buffer;//(chunkSize, std::vector(sendChannels)); - std::vector send_trig(chunkSize); - - send_buffer.resize(chunkSize); - //QMessageBox::information(this,"Information", std::string("sendChannels " + boost::lexical_cast(sendChannels)).c_str(), QMessageBox::Ok); - for (int s = 0; s < chunkSize; s++) - { - send_buffer[s].resize(sendChannels); + if(sampleCount >= chunkSize){ + + int k; + int j; + int i; + // transpose the data into lsl buffers + for (i=0;i(send_trig.size())).c_str(), QMessageBox::Ok); - } + // if the index of the input channel matches the index of the requested output channel... + if (k==trigger_indeces[j]){ + + // if the trigger is a new value, record it, else it is 0.0 + int trigger_channel = k+eegAuxAccChannelCount; + float mrkr_tmp = (liveamp_buffer[i][k+trigger_channel]); + f_mrkr = (mrkr_tmp == prev_marker_float[k] ? -1.0 : liveamp_buffer[i][k+trigger_channel]); + prev_marker_float[j] = mrkr_tmp; + + // if we want the trigger in the EEG signal: + if(sampledMarkersEEG) + eeg_buffer.push_back(f_mrkr); + + // if we want either type of string markers, record it as well + // this is not optimized because later we have to cast a string as an int + if(sampledMarkers || unsampledMarkers) + str_marker_buffer.push_back(f_mrkr == -1.0 ? "" : boost::lexical_cast(liveamp_buffer[i][k+eegAuxAccChannelCount])); + + // lastly, increment the local index of which channel we are outputting + j++; + } + } - //QMessageBox::information(this,"Information", std::string("send_buffer size()" + boost::lexical_cast(send_buffer.size())).c_str(), QMessageBox::Ok); + // now complete the signal buffer for lsl push + eeg_aux_acc_buffer.push_back(eeg_buffer); - data_outlet.push_chunk(send_buffer, now); + // and the marker buffer + sampled_marker_buffer.push_back(str_marker_buffer); + } - // push markers into outlet - for (int s=0; s(mrk); - marker_outlet.push_sample(&mrk_string,now + (s + 1 - samples_read)/samplingRate); - last_mrk = mrk; + double now = lsl::local_clock(); + + // push the eeg chunk + data_outlet.push_chunk(eeg_aux_acc_buffer, now); + // clear our data buffers + eeg_aux_acc_buffer.clear(); + liveamp_buffer.clear(); + + // push the unsampled markers one at a time, appending the trigger channel number + // TODO: change this to the label of the trigger channel + if(unsampledMarkers) { + std::stringstream ss; + for(i=0;ipush_sample(&(ss.str()),now + (i + 1 - sampleCount)/samplingRate); + } } } } - - int difSamp = lslsend_buffer.size() - chunkSize; - send_buffer.clear(); - send_buffer.resize(difSamp); - send_trig.clear(); - send_trig.resize(difSamp); - - for (int s = 0; s < difSamp; s++) - { - send_buffer[s].resize(sendChannels); - for (int ch = 0; ch < sendChannels; ch++ ) - send_buffer[s][ch] = lslsend_buffer[difSamp + s][ch]; - - send_trig [s] = trigger_buffer[difSamp + s]; + // push the sampled markers + if(sampledMarkers) { + s_marker_outlet->push_chunk(sampled_marker_buffer, now); + sampled_marker_buffer.clear(); + } + } + } - lslsend_buffer.clear(); - lslsend_buffer.resize(difSamp); - trigger_buffer.clear(); - trigger_buffer.resize(difSamp); - - for (int s = 0; s < difSamp; s++) - { - lslsend_buffer[s].resize(sendChannels); - for (int ch = 0; ch < sendChannels; ch++ ) - lslsend_buffer[s][ch] = send_buffer[s][ch]; + // cleanup (if necessary) + if(unsampledMarkers)delete(marker_outlet); + if(sampledMarkers)delete(s_marker_outlet); - trigger_buffer [s] = send_trig[s]; - } - } - } } catch(boost::thread_interrupted &) { // thread was interrupted: no error @@ -346,540 +527,20 @@ void MainWindow::read_thread(int chunkSize, int samplingRate, bool useAUX, bool if(buffer != NULL) delete[] buffer; buffer = NULL; - - if (mDevice) { - if (started) - ampStopAcquisition(mDevice); - ampCloseDevice(mDevice); - mDevice = NULL; + try{ + liveAmp->stopAcquisition(); + liveAmp->close(); + delete liveAmp; + }catch(std::exception &e) { + // any problem closing liveamp + std::cerr << e.what() << std::endl; } } MainWindow::~MainWindow() { - delete ui; -} - - -bool MainWindow::InitializeLiveAmp() -{ - HANDLE hlocDevice = NULL; - int res; - char HWI[20]; - strcpy_s(HWI, "ANY"); // use word SIM to simulate the LiveAmp - - mDevice = NULL; - std::string serialN = ui->deviceSerialNumber->text().toStdString(); - - QMessageBox *ms = new QMessageBox(this); - ms->setWindowTitle("connecting to LiveAmp amplifier...it takes some seconds..."); - ms->setText("Connecting to LiveAmp amplifier(s)..."); - ms->show(); - - res = ampEnumerateDevices(HWI, sizeof(HWI), "LiveAmp", 0); - - ms->close(); - delete ms; - - if (res <= 0) - throw std::runtime_error("No LiveAmp connected"); - else - { - int numDevices = res; - for (int i = 0; i < numDevices; i++) - { - hlocDevice = NULL; - res = ampOpenDevice(i, &hlocDevice); - if(res != AMP_OK) - { - std::string msg = "Cannot open device: "; - msg.append (boost::lexical_cast(i).c_str()); - msg.append(" error= " ); - msg.append(boost::lexical_cast(res).c_str()); - throw std::runtime_error(msg); - } - - char sVar[20]; - res = ampGetProperty(hlocDevice, PG_DEVICE, i, DPROP_CHR_SerialNumber, sVar, sizeof(sVar)); - if(res != AMP_OK) - { - std::string msg = "Cannot read Serial number, "; - msg.append (boost::lexical_cast(i).c_str()); - msg.append(" error= " ); - msg.append(boost::lexical_cast(res).c_str()); - throw std::runtime_error(msg); - } - - //QMessageBox::information(this,"Information", sVar, QMessageBox::Ok); - - int check = strcmp(sVar, serialN.c_str()); - if(check == 0) - { - QMessageBox::information(this,"Information", std::string("LiveAmp SN: " + serialN + " successfully connected!").c_str(), QMessageBox::Ok); - - mDevice = hlocDevice; // save device handler - return true; - } - else - { - res = ampCloseDevice(hlocDevice); - if(res != AMP_OK) - { - std::string msg = "Cannot close device: "; - msg.append (boost::lexical_cast(i).c_str()); - msg.append(" error= " ); - msg.append(boost::lexical_cast(res).c_str()); - throw std::runtime_error(msg); - } - } - } - } - - if(mDevice == NULL) - { - std::string msg = "There is no LiveAmp with serial number: " + serialN + " detected!"; - throw std::runtime_error(msg); - } - - return false; -} - -// configure LiveAmp amplifier, sampling rate, mode, channels etc... -bool MainWindow::ConfigureLiveAmp(void) -{ - try - { - // amplifier configuration - float fVar = (mSamplingRate); - int res = ampSetProperty(mDevice, PG_DEVICE, 0, DPROP_F32_BaseSampleRate, &fVar, sizeof(fVar)); - if(res != AMP_OK) - throw std::runtime_error(("Error setting sampling rate, error code: " + boost::lexical_cast(res)).c_str()); - - int mode = RM_NORMAL; - res = ampSetProperty(mDevice, PG_DEVICE, 0, DPROP_I32_RecordingMode, &mode, sizeof(mode)); - if(res != AMP_OK) - throw std::runtime_error(("Error setting acquisition mode, error code: " + boost::lexical_cast(res)).c_str()); - - } - catch(std::exception &e) { - // generate error message - std::string msg = "ConfigureLiveAmp error. "; - - QMessageBox::critical(this,"Error", (std::string("ConfigureLiveAmp error: ") + e.what()).c_str() ,QMessageBox::Ok); - return false; - } - - return true; -} - -// enables physical channels on LiveAmp -bool MainWindow::EnableLiveAmpChannels(bool enableAUX, bool enableACC) -{ - int avlbchannels; - bool useBipolar = ui->useBipolar->checkState()==Qt::Checked; - - try - { - int res = ampGetProperty(mDevice, PG_DEVICE, 0, DPROP_I32_AvailableChannels, &avlbchannels, sizeof(avlbchannels)); - //QMessageBox::information(this, "Information", std::string("avlbchannels= " + boost::lexical_cast(avlbchannels)).c_str(), QMessageBox::Ok); - - for (int c = 0; c < avlbchannels; c++) - { - int type; - - res = ampGetProperty(mDevice, PG_CHANNEL, c, CPROP_I32_Type, &type, sizeof(type)); - if(res != AMP_OK) - throw std::runtime_error(("Error getting property for channel type: error code: " + boost::lexical_cast(res)).c_str()); - - //QMessageBox::information(this, "Information", std::string("channel #' " + boost::lexical_cast(c)).c_str(), QMessageBox::Ok); - if (type == CT_AUX) - { - char cValue[20]; - int enabled; - - res = ampGetProperty(mDevice, PG_CHANNEL, c, CPROP_CHR_Function, &cValue, sizeof(cValue)); - if (res != AMP_OK) - throw std::runtime_error(("Error GetProperty CPROP_CHR_Function error: " + boost::lexical_cast(res)).c_str()); - - res = ampGetProperty(mDevice, PG_CHANNEL, c, CPROP_B32_RecordingEnabled, &enabled, sizeof(enabled)); - if (res != AMP_OK) - { - //QMessageBox::information(this, "Information", ("Error GetProperty enable for ACC channels, error: " + boost::lexical_cast(res)).c_str() , QMessageBox::Ok); - throw std::runtime_error(("Error SetProperty enable for ACC channels, error: " + boost::lexical_cast(res)).c_str()); - } - - if(cValue == NULL || cValue[0] == NULL || !(cValue[0] == 'X' || cValue[0] == 'Y' ||cValue[0] == 'Z' || cValue[0] == 'x' ||cValue[0] == 'y' ||cValue[0] == 'z')) - { - if(enabled != enableAUX) - { - enabled = enableAUX; - - // Enables/disables AUX channels - res = ampSetProperty(mDevice, PG_CHANNEL, c, CPROP_B32_RecordingEnabled, &enabled, sizeof(enabled)); - if (res != AMP_OK) - throw std::runtime_error(("Error SetProperty enable for ACC channels, error: " + boost::lexical_cast(res)).c_str()); - } - } - else - { - if(enabled != enableACC) - { - enabled = enableACC; - - // Enables/disables ACC channels - res = ampSetProperty(mDevice, PG_CHANNEL, c, CPROP_B32_RecordingEnabled, &enabled, sizeof(enabled)); - if (res != AMP_OK) - throw std::runtime_error(("Error SetProperty enable for ACC channels, error: " + boost::lexical_cast(res)).c_str()); - } - } - - } - - - else if (type == CT_EEG || type == CT_BIP) - { - int enableEEG = true; - // match indexes of input channel list (from UI List control) - if(c >= mChannelCount) - { - enableEEG = false; - res = ampSetProperty(mDevice, PG_CHANNEL, c, CPROP_B32_RecordingEnabled, &enableEEG, sizeof(enableEEG)); - if (res != AMP_OK) - throw std::runtime_error(("Error SetProperty disable for EEG channels, error: " + boost::lexical_cast(res)).c_str()); - } - else - { - res = ampSetProperty(mDevice, PG_CHANNEL, c, CPROP_B32_RecordingEnabled, &enableEEG, sizeof(enableEEG)); - if (res != AMP_OK) - throw std::runtime_error(("Error SetProperty enable for EEG channels, error: " + boost::lexical_cast(res)).c_str()); - - - if(useBipolar && c > 23 && c < 32 ) // set last 8 channels to bipolar - { - int bipType = CT_BIP; - res = ampSetProperty(mDevice, PG_CHANNEL, c, CPROP_I32_Type, &bipType, sizeof(bipType)); - if (res != AMP_OK) - throw std::runtime_error(("Error SetProperty enable BIPOLAR EEG channels, error: " + boost::lexical_cast(res)).c_str()); - - } - - } - } - - // Trigger channel cannot be enabled disabled - else if (type == CT_TRG || type == CT_DIG) - continue; - } - } - catch(std::exception &e) { - QMessageBox::critical(this,"Error", (std::string("ConfigureLiveAmp error. ") + e.what()).c_str() ,QMessageBox::Ok); - return false; - } - - return true; -} - -bool MainWindow::GenerateUsedPhysicalChannelIndexes(void) -{ - int enableCh; - int type; - int allEnabledChannel; // only used==enabled channel - - eegIndexes.clear(); - auxIndexes.clear(); - accIndexes.clear(); - mTrigInIndexes.clear(); - mChannelIndexes.clear(); - - // after enabling and disabling physical channels by LibveAmp, the order (indexing) changes... it must be checked again the channel types and indexing. - int avlbchannels; - - try - { - int res = ampGetProperty(mDevice, PG_DEVICE, 0, DPROP_I32_AvailableChannels, &avlbchannels, sizeof(avlbchannels)); - for (int c = 0; c < avlbchannels; c++) - { - res = ampGetProperty(mDevice, PG_CHANNEL, c, CPROP_B32_RecordingEnabled, &enableCh, sizeof(enableCh)); - if(res != AMP_OK) - throw std::runtime_error(("Error getting property for channel CPROP_B32_RecordingEnabled: error code: " + boost::lexical_cast(res)).c_str()); - - if(!enableCh) - continue; - - res = ampGetProperty(mDevice, PG_CHANNEL, c, CPROP_I32_Type, &type, sizeof(type)); - if(res != AMP_OK) - throw std::runtime_error(("Error getting property for channel type: error code: " + boost::lexical_cast(res)).c_str()); - - if (type == CT_AUX) - { - char cValue[20]; - res = ampGetProperty(mDevice, PG_CHANNEL, c, CPROP_CHR_Function, &cValue, sizeof(cValue)); - if (res != AMP_OK) - throw std::runtime_error(("Error GetProperty CPROP_CHR_Function error: " + boost::lexical_cast(res)).c_str()); - - if(cValue[0] == 'X' || cValue[0] == 'Y' ||cValue[0] == 'Z' || cValue[0] == 'x' ||cValue[0] == 'y' ||cValue[0] == 'z') - accIndexes.push_back(c); - - else - auxIndexes.push_back(c); - } - - else if (type == CT_EEG || type == CT_BIP) - eegIndexes.push_back(c); - - - else if (type == CT_TRG || type == CT_DIG) - { - char cValue[20]; - res = ampGetProperty(mDevice, PG_CHANNEL, c, CPROP_CHR_Function, &cValue, sizeof(cValue)); - if (res != AMP_OK) - throw std::runtime_error(("Error GetProperty CPROP_CHR_Function for CT_TRG, error: " + boost::lexical_cast(res)).c_str()); - - // we are interested only on Trigger Inputs... - if(strcmp("Trigger Input", cValue) == 0 ) - mTrigInIndexes.push_back(c); - } - - allEnabledChannel++; - - if (type == CT_EEG || type == CT_BIP || type == CT_AUX) - { - float gain = 2; - res = ampSetProperty(mDevice, PG_CHANNEL, c, CPROP_F32_Gain, &gain, sizeof(gain)); - if (res != AMP_OK) - throw std::runtime_error(("Error SetProperty CPROP_F32_Gain, error: " + boost::lexical_cast(res)).c_str()); - } - } - } - - catch(std::exception &e) { - QMessageBox::critical(this,"Error", (std::string("GenerateUsedPhysicalChannelIndexes error. ") + e.what()).c_str() ,QMessageBox::Ok); - return false; - } - - //QMessageBox::information(this, "Information", std::string("eegIndexes # " + boost::lexical_cast(eegIndexes.size())).c_str(), QMessageBox::Ok); - - // copy indexes of eeg, aux and acc physical channels - for (int i=0; i < eegIndexes.size(); i++) - mChannelIndexes.push_back(eegIndexes[i]); - - //QMessageBox::information(this, "Information", std::string("auxIndexes # " + boost::lexical_cast(auxIndexes.size())).c_str(), QMessageBox::Ok); - for (int i=0; i < auxIndexes.size(); i++) - mChannelIndexes.push_back(auxIndexes[i]); - - //QMessageBox::information(this, "Information", std::string("accIndexes # " + boost::lexical_cast(accIndexes.size())).c_str(), QMessageBox::Ok); - for (int i=0; i < accIndexes.size(); i++) - mChannelIndexes.push_back(accIndexes[i]); - - return true; - -} - -// adjust channel labels, in case that there are more channels send to LSL (like aux or acc) -void MainWindow::AdjustChannelLabels(std::vector& inpuChannelLabels, std::vector &adjustedChannelLabels) -{ - // there is order of channels by LiveAmp : EEG + AUX + ACC - if(inpuChannelLabels.size() != eegIndexes.size()) - QMessageBox::critical(this,"Error", "AdjustChannelLabels error, number of elements in input channel labels doesn't match!" ,QMessageBox::Ok); - - adjustedChannelLabels.clear(); - adjustedChannelLabels.resize(inpuChannelLabels.size()); - - for (int i = 0; i < inpuChannelLabels.size(); i++) - adjustedChannelLabels[i] = inpuChannelLabels[i]; - - - for (int i = 0; i < auxIndexes.size(); i++) - { - std::string aux = "AUX"; - aux += boost::lexical_cast(i); - adjustedChannelLabels.push_back (aux); - } - - for (int i = 0; i < accIndexes.size(); i++) - { - std::string acc; - std::string ix = boost::lexical_cast(i/3 + 1); - - if (i%3 == 0) - acc = "X" + ix; - else if (i%3 == 1) - acc = "Y" + ix; - else if (i%3 == 2) - acc = "Z" + ix; - - //QMessageBox::information(this, "Information", (std::string("ACC= ") + acc).c_str(), QMessageBox::Ok); - - adjustedChannelLabels.push_back (acc); - } - -} - - - -// extracts recorded samples form LiveAMp structure and copies to LSL buffer structure, ready to send over LSL -void MainWindow::PrepareDataToSendOverLSL(std::vector> &LiveAmptData, std::vector> &LSLData, std::vector &trigger_buffer) -{ - int numSamples = LiveAmptData.size(); - - if(numSamples <= 0) - return; - - int lslChannleCount = mChannelIndexes.size(); - - - for (int s = 0; s < numSamples; s++) - { - std::vector locBuffer (lslChannleCount); - for (int i=0; i < lslChannleCount; i++) - locBuffer[i] = LiveAmptData[s][mChannelIndexes[i]]; - - LSLData.push_back(locBuffer); - trigger_buffer.push_back(LiveAmptData[s][mTrigInIndexes[0]]); // for now we use only the first Trigger In channel! - } -} - -void MainWindow::ExtractLiveAmpData(BYTE* buffer, int seamples_read, int samplesize, int *dataTypes, int usedChannelsCnt, std::vector> &extractData) -{ - uint64_t sc; - int numSamples = seamples_read / samplesize; - int offset = 0; - double sample = 0; - - extractData.clear(); - extractData.resize(numSamples); - - for (int s = 0; s < numSamples; s++) - { - offset = 0; - sc = *(uint64_t*)&buffer[s*samplesize + offset]; - offset += 8; // sample counter offset - - extractData[s].resize(usedChannelsCnt); - - for (int i=0; i < usedChannelsCnt; i++) - { - switch (dataTypes[i]) - { - case DT_INT16: - { - int16_t tmp = *(int16_t*)&buffer[s*samplesize + offset]; - sample = (double) tmp; - offset += 2; - break; - } - case DT_UINT16: - { - uint16_t tmp = *(uint16_t*)&buffer[s*samplesize + offset]; - sample = (double) tmp; - offset += 2; - break; - } - case DT_INT32: - { - int32_t tmp = *(int32_t*)&buffer[s*samplesize + offset]; - sample = (double) tmp; - offset += 4; - break; - } - case DT_UINT32: - { - uint32_t tmp = *(uint32_t*)&buffer[s*samplesize + offset]; - sample = (double) tmp; - offset += 4; - break; - } - case DT_FLOAT32: - { - float tmp = *(float*)&buffer[s*samplesize + offset]; - sample = (double) tmp; - offset += 4; - break; - } - case DT_INT64: - { - int64_t tmp = *(int64_t*)&buffer[s*samplesize + offset]; - sample = (double) tmp; - offset += 8; - break; - } - case DT_UINT64: - { - uint64_t tmp = *(uint64_t*)&buffer[s*samplesize + offset]; - sample = (double) tmp; - offset += 8; - break; - } - case DT_FLOAT64: - { - - double tmp = *(double*)&buffer[s*samplesize + offset]; - sample = (double) tmp; - offset += 8; - break; - } - default: - break; - } - - extractData[s][i] = sample; - } - } + delete ui; } -// Get the current sample size in byte -int LiveAmp_SampleSize(HANDLE hDevice, int *typeArray, int* usedChannelsCnt) -{ - int res; - int channels, datatype; - BOOL enabled; - int bytesize = 0; - int cntUsedCh = 0; - - // iterate through all enabled channels - res = ampGetProperty(hDevice, PG_DEVICE, 0, DPROP_I32_AvailableChannels, &channels, sizeof(channels)); - for (int c = 0; c < channels; c++) - { - res = ampGetProperty(hDevice, PG_CHANNEL, c, CPROP_B32_RecordingEnabled, &enabled, sizeof(enabled)); - if (enabled) - { - res = ampGetProperty(hDevice, PG_CHANNEL, c, CPROP_I32_DataType, &datatype, sizeof(datatype)); - - typeArray[cntUsedCh] = datatype; - cntUsedCh++; - - switch (datatype) - { - case DT_INT16: - case DT_UINT16: - { - bytesize += 2; - } - break; - case DT_INT32: - case DT_UINT32: - case DT_FLOAT32: - { - bytesize += 4; - - } - break; - case DT_INT64: - case DT_UINT64: - case DT_FLOAT64: - { - bytesize += 8; - } - break; - default: - break; - } - } - } - // add the sample counter size - bytesize += 8; - *usedChannelsCnt = cntUsedCh; - return bytesize; -} diff --git a/Apps/BrainProducts/LiveAmp/mainwindow.h b/Apps/BrainProducts/LiveAmp/mainwindow.h index ae409f5f..d1cd06b6 100644 --- a/Apps/BrainProducts/LiveAmp/mainwindow.h +++ b/Apps/BrainProducts/LiveAmp/mainwindow.h @@ -12,13 +12,13 @@ // LSL API #define LSL_DEBUG_BINDINGS -#include +#include "../../../LSL/liblsl/include/lsl_cpp.h" -// BrainAmp API #define WIN32_LEAN_AND_MEAN #include #include -#include "Amplifier_LIB.h" + +#include "LiveAmp.h" @@ -38,36 +38,57 @@ private slots: // config file dialog ops (from main menu) void load_config_dialog(); void save_config_dialog(); + + // get list of available devices + void refresh_devices(); - // start the ActiChamp connection - void link(); + // link to selected device + void link(); // close event (potentially disabled) void closeEvent(QCloseEvent *ev); + + // if the device combo box item changes + void choose_device(int which); + + private: // background data reader thread - void read_thread(int chunkSize, int samplingRate, bool useAUX, bool activeShield, std::vector channelLabels); + void read_thread(int chunkSize, int samplingRate, bool useAUX, bool useACC, bool useBipolar, std::vector eegChannelLabels); + + // container for amplifier enumeration + std::vector> ampData; // raw config file IO void load_config(const std::string &filename); void save_config(const std::string &filename); - bool InitializeLiveAmp(void); bool ConfigureLiveAmp(void); - + bool InitializeLiveAmp(void); + void ExtractLiveAmpData(BYTE* buffer, int size, int samplesize, int *dataTypes, int usedChannelsCnt, std::vector> &extractData); - bool EnableLiveAmpChannels(bool enableAUX, bool enableACC); bool GenerateUsedPhysicalChannelIndexes(void); void PrepareDataToSendOverLSL(std::vector> &LiveAmptData, std::vector> &LSLData, std::vector &trigger_buffer); void AdjustChannelLabels(std::vector& inpuChannelLabels, std::vector &adjustedChannelLabels); + + bool unsampledMarkers; + bool sampledMarkers; + bool sampledMarkersEEG; + bool useSim; - boost::shared_ptr reader_thread_; // our reader thread + LiveAmp *liveAmp; + std::vector trigger_indeces; // this is just on the LSL side, the liveamp has all 3 trigger channels enabled + std::vector live_amp_sns; // live amp serial number container + boost::shared_ptr reader_thread_; // our reader thread + Ui::MainWindow *ui; + bool use_simulators; bool stop_; // whether the reader thread is supposed to stop + }; #endif // MAINWINDOW_H diff --git a/Apps/BrainProducts/LiveAmp/mainwindow.ui b/Apps/BrainProducts/LiveAmp/mainwindow.ui index e84f76fc..b541cad8 100644 --- a/Apps/BrainProducts/LiveAmp/mainwindow.ui +++ b/Apps/BrainProducts/LiveAmp/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 511 - 299 + 400 + 687 @@ -71,42 +71,81 @@ PO10 - + + + + - Device Settings + LiveAmpDevices - - - QFormLayout::AllNonFixedFieldsGrow - + - + - Device Number + Available Devices - - - The number of the USB device (if multiple); the first one is #0. + + + + + + true - - + + Refresh + + + + - + (check) - + + + + Use Simulator + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Device Settings + + + + QFormLayout::AllNonFixedFieldsGrow + + Number of EEG channels - + This must match the number of entries in the channel list @@ -125,37 +164,58 @@ PO10 - + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Use bipolar channels</span></p></body></html> + + + + + + + + + + false + + + + Chunk Size - + - The number of samples per chunk emitted by the driver -- a small value will lead to lower overall latency but causes more CPU load + <html><head/><body><p>The number of samples per chunk emitted by the driver -- a small value will lead to lower overall latency but causes more CPU load, max value is 20</p></body></html> 1 - 1024 + 20 10 - + Sampling Rate - + The sampling rate to use; higher sampling rates require more network bandwidth (and storage space if recording), particularly the very high rates of 10KHz+. The native rates are those that are natively supported by the hardware and the resampled rates are resampled in software (using a linear-phase sinc resampler that delays the output signal by 5 samples). @@ -180,14 +240,14 @@ PO10 - + Use AUX Channels - + If this is checked then the last 8 channels will hold the AUX signals; make sure to increase your channel count accordingly. @@ -200,14 +260,14 @@ PO10 - + Enable ACC sensors - + This will enable Acceleration sensors X, Y and Z. @@ -220,27 +280,160 @@ PO10 - - + + + + LSL Trigger Output Style + + + + + + Unsampled String Markers + + + + + + + <html><head/><body><p>For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.</p></body></html> + + + (check) + + + + + + + Sampled String Markers + + + + + + + <html><head/><body><p>For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.</p></body></html> + + + (check) + + + true + + + + + + + <html><head/><body><p>For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.</p></body></html> + + + (check) + + + + + + + Include in EEG Stream + + + + + + + + + + The number of the USB device (if multiple); the first one is #0. + + + + - - false + + + + + + Device Number - - + + - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Use bipolar channels</span></p></body></html> + Enable Bipolar (last 8 only) + + + + (check) + + + + + + + Select Trigger Channels to Output + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Trigger In + + + + + + + (check) + + + true + + + + + + + Trigger Out + + + + + + + (check) + + + + + + + Digital + + + + + + + (check) + + + + + +
@@ -274,6 +467,9 @@ p, li { white-space: pre-wrap; } + + true + Link @@ -290,7 +486,7 @@ p, li { white-space: pre-wrap; } 0 0 - 511 + 400 21 diff --git a/Apps/BrainProducts/LiveAmp/ui_mainwindow.h b/Apps/BrainProducts/LiveAmp/ui_mainwindow.h index 13bfac50..e8e546c7 100644 --- a/Apps/BrainProducts/LiveAmp/ui_mainwindow.h +++ b/Apps/BrainProducts/LiveAmp/ui_mainwindow.h @@ -1,8 +1,7 @@ /******************************************************************************** ** Form generated from reading UI file 'mainwindow.ui' ** -** Created: Fri 27. Jan 14:29:52 2017 -** by: Qt User Interface Compiler version 4.8.1 +** Created by: Qt User Interface Compiler version 4.8.6 ** ** WARNING! All changes made in this file will be lost when recompiling UI file! ********************************************************************************/ @@ -49,12 +48,21 @@ class Ui_MainWindow QGridLayout *gridLayout; QPlainTextEdit *channelLabels; QVBoxLayout *verticalLayout; + QHBoxLayout *horizontalLayout_4; + QGroupBox *groupBox_4; + QFormLayout *formLayout_3; + QLabel *label_10; + QComboBox *deviceCb; + QPushButton *refreshDevices; + QCheckBox *useSim; + QLabel *label_11; + QSpacerItem *verticalSpacer_2; QGroupBox *groupBox; QFormLayout *formLayout; - QLabel *label_4; - QLineEdit *deviceSerialNumber; QLabel *label_2; QSpinBox *channelCount; + QLabel *lblUseBipolar; + QCheckBox *useBipolar; QLabel *label; QSpinBox *chunkSize; QLabel *label_3; @@ -63,8 +71,26 @@ class Ui_MainWindow QCheckBox *useAUX; QLabel *label_8; QCheckBox *useACC; - QCheckBox *useBipolar; - QLabel *lblUseBipolar; + QGroupBox *groupBox_3; + QFormLayout *formLayout_2; + QLabel *label_5; + QCheckBox *unsampledMarkers; + QLabel *label_6; + QCheckBox *sampledMarkers; + QCheckBox *sampledMarkersEEG; + QLabel *label_9; + QLineEdit *deviceSerialNumber; + QLabel *label_4; + QLabel *label_12; + QCheckBox *useBipolar_2; + QGroupBox *groupBox_5; + QFormLayout *formLayout_4; + QLabel *label_13; + QCheckBox *inTrigger; + QLabel *label_14; + QCheckBox *outTrigger; + QLabel *label_15; + QCheckBox *digiTrigger; QSpacerItem *verticalSpacer; QHBoxLayout *horizontalLayout; QSpacerItem *horizontalSpacer; @@ -77,7 +103,7 @@ class Ui_MainWindow { if (MainWindow->objectName().isEmpty()) MainWindow->setObjectName(QString::fromUtf8("MainWindow")); - MainWindow->resize(511, 299); + MainWindow->resize(400, 687); actionLoad_Configuration = new QAction(MainWindow); actionLoad_Configuration->setObjectName(QString::fromUtf8("actionLoad_Configuration")); actionSave_Configuration = new QAction(MainWindow); @@ -114,6 +140,51 @@ class Ui_MainWindow verticalLayout = new QVBoxLayout(); verticalLayout->setSpacing(6); verticalLayout->setObjectName(QString::fromUtf8("verticalLayout")); + horizontalLayout_4 = new QHBoxLayout(); + horizontalLayout_4->setSpacing(6); + horizontalLayout_4->setObjectName(QString::fromUtf8("horizontalLayout_4")); + + verticalLayout->addLayout(horizontalLayout_4); + + groupBox_4 = new QGroupBox(centralWidget); + groupBox_4->setObjectName(QString::fromUtf8("groupBox_4")); + formLayout_3 = new QFormLayout(groupBox_4); + formLayout_3->setSpacing(6); + formLayout_3->setContentsMargins(11, 11, 11, 11); + formLayout_3->setObjectName(QString::fromUtf8("formLayout_3")); + label_10 = new QLabel(groupBox_4); + label_10->setObjectName(QString::fromUtf8("label_10")); + + formLayout_3->setWidget(0, QFormLayout::LabelRole, label_10); + + deviceCb = new QComboBox(groupBox_4); + deviceCb->setObjectName(QString::fromUtf8("deviceCb")); + + formLayout_3->setWidget(0, QFormLayout::FieldRole, deviceCb); + + refreshDevices = new QPushButton(groupBox_4); + refreshDevices->setObjectName(QString::fromUtf8("refreshDevices")); + refreshDevices->setEnabled(true); + + formLayout_3->setWidget(1, QFormLayout::FieldRole, refreshDevices); + + useSim = new QCheckBox(groupBox_4); + useSim->setObjectName(QString::fromUtf8("useSim")); + + formLayout_3->setWidget(2, QFormLayout::FieldRole, useSim); + + label_11 = new QLabel(groupBox_4); + label_11->setObjectName(QString::fromUtf8("label_11")); + + formLayout_3->setWidget(2, QFormLayout::LabelRole, label_11); + + + verticalLayout->addWidget(groupBox_4); + + verticalSpacer_2 = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); + + verticalLayout->addItem(verticalSpacer_2); + groupBox = new QGroupBox(centralWidget); groupBox->setObjectName(QString::fromUtf8("groupBox")); formLayout = new QFormLayout(groupBox); @@ -121,20 +192,10 @@ class Ui_MainWindow formLayout->setContentsMargins(11, 11, 11, 11); formLayout->setObjectName(QString::fromUtf8("formLayout")); formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); - label_4 = new QLabel(groupBox); - label_4->setObjectName(QString::fromUtf8("label_4")); - - formLayout->setWidget(0, QFormLayout::LabelRole, label_4); - - deviceSerialNumber = new QLineEdit(groupBox); - deviceSerialNumber->setObjectName(QString::fromUtf8("deviceSerialNumber")); - - formLayout->setWidget(0, QFormLayout::FieldRole, deviceSerialNumber); - label_2 = new QLabel(groupBox); label_2->setObjectName(QString::fromUtf8("label_2")); - formLayout->setWidget(1, QFormLayout::LabelRole, label_2); + formLayout->setWidget(5, QFormLayout::LabelRole, label_2); channelCount = new QSpinBox(groupBox); channelCount->setObjectName(QString::fromUtf8("channelCount")); @@ -143,63 +204,164 @@ class Ui_MainWindow channelCount->setSingleStep(1); channelCount->setValue(32); - formLayout->setWidget(1, QFormLayout::FieldRole, channelCount); + formLayout->setWidget(5, QFormLayout::FieldRole, channelCount); + + lblUseBipolar = new QLabel(groupBox); + lblUseBipolar->setObjectName(QString::fromUtf8("lblUseBipolar")); + + formLayout->setWidget(6, QFormLayout::LabelRole, lblUseBipolar); + + useBipolar = new QCheckBox(groupBox); + useBipolar->setObjectName(QString::fromUtf8("useBipolar")); + useBipolar->setChecked(false); + + formLayout->setWidget(6, QFormLayout::FieldRole, useBipolar); label = new QLabel(groupBox); label->setObjectName(QString::fromUtf8("label")); - formLayout->setWidget(3, QFormLayout::LabelRole, label); + formLayout->setWidget(7, QFormLayout::LabelRole, label); chunkSize = new QSpinBox(groupBox); chunkSize->setObjectName(QString::fromUtf8("chunkSize")); chunkSize->setMinimum(1); - chunkSize->setMaximum(1024); + chunkSize->setMaximum(20); chunkSize->setValue(10); - formLayout->setWidget(3, QFormLayout::FieldRole, chunkSize); + formLayout->setWidget(7, QFormLayout::FieldRole, chunkSize); label_3 = new QLabel(groupBox); label_3->setObjectName(QString::fromUtf8("label_3")); - formLayout->setWidget(4, QFormLayout::LabelRole, label_3); + formLayout->setWidget(8, QFormLayout::LabelRole, label_3); samplingRate = new QComboBox(groupBox); samplingRate->setObjectName(QString::fromUtf8("samplingRate")); - formLayout->setWidget(4, QFormLayout::FieldRole, samplingRate); + formLayout->setWidget(8, QFormLayout::FieldRole, samplingRate); label_7 = new QLabel(groupBox); label_7->setObjectName(QString::fromUtf8("label_7")); - formLayout->setWidget(5, QFormLayout::LabelRole, label_7); + formLayout->setWidget(9, QFormLayout::LabelRole, label_7); useAUX = new QCheckBox(groupBox); useAUX->setObjectName(QString::fromUtf8("useAUX")); useAUX->setChecked(false); - formLayout->setWidget(5, QFormLayout::FieldRole, useAUX); + formLayout->setWidget(9, QFormLayout::FieldRole, useAUX); label_8 = new QLabel(groupBox); label_8->setObjectName(QString::fromUtf8("label_8")); - formLayout->setWidget(6, QFormLayout::LabelRole, label_8); + formLayout->setWidget(10, QFormLayout::LabelRole, label_8); useACC = new QCheckBox(groupBox); useACC->setObjectName(QString::fromUtf8("useACC")); useACC->setChecked(true); - formLayout->setWidget(6, QFormLayout::FieldRole, useACC); + formLayout->setWidget(10, QFormLayout::FieldRole, useACC); - useBipolar = new QCheckBox(groupBox); - useBipolar->setObjectName(QString::fromUtf8("useBipolar")); - useBipolar->setChecked(false); + groupBox_3 = new QGroupBox(groupBox); + groupBox_3->setObjectName(QString::fromUtf8("groupBox_3")); + formLayout_2 = new QFormLayout(groupBox_3); + formLayout_2->setSpacing(6); + formLayout_2->setContentsMargins(11, 11, 11, 11); + formLayout_2->setObjectName(QString::fromUtf8("formLayout_2")); + label_5 = new QLabel(groupBox_3); + label_5->setObjectName(QString::fromUtf8("label_5")); - formLayout->setWidget(2, QFormLayout::FieldRole, useBipolar); + formLayout_2->setWidget(0, QFormLayout::LabelRole, label_5); + + unsampledMarkers = new QCheckBox(groupBox_3); + unsampledMarkers->setObjectName(QString::fromUtf8("unsampledMarkers")); + + formLayout_2->setWidget(0, QFormLayout::FieldRole, unsampledMarkers); + + label_6 = new QLabel(groupBox_3); + label_6->setObjectName(QString::fromUtf8("label_6")); + + formLayout_2->setWidget(1, QFormLayout::LabelRole, label_6); + + sampledMarkers = new QCheckBox(groupBox_3); + sampledMarkers->setObjectName(QString::fromUtf8("sampledMarkers")); + sampledMarkers->setChecked(true); + + formLayout_2->setWidget(1, QFormLayout::FieldRole, sampledMarkers); + + sampledMarkersEEG = new QCheckBox(groupBox_3); + sampledMarkersEEG->setObjectName(QString::fromUtf8("sampledMarkersEEG")); + + formLayout_2->setWidget(2, QFormLayout::FieldRole, sampledMarkersEEG); + + label_9 = new QLabel(groupBox_3); + label_9->setObjectName(QString::fromUtf8("label_9")); + + formLayout_2->setWidget(2, QFormLayout::LabelRole, label_9); - lblUseBipolar = new QLabel(groupBox); - lblUseBipolar->setObjectName(QString::fromUtf8("lblUseBipolar")); - formLayout->setWidget(2, QFormLayout::LabelRole, lblUseBipolar); + formLayout->setWidget(12, QFormLayout::SpanningRole, groupBox_3); + + deviceSerialNumber = new QLineEdit(groupBox); + deviceSerialNumber->setObjectName(QString::fromUtf8("deviceSerialNumber")); + + formLayout->setWidget(4, QFormLayout::LabelRole, deviceSerialNumber); + + label_4 = new QLabel(groupBox); + label_4->setObjectName(QString::fromUtf8("label_4")); + + formLayout->setWidget(3, QFormLayout::LabelRole, label_4); + + label_12 = new QLabel(groupBox); + label_12->setObjectName(QString::fromUtf8("label_12")); + + formLayout->setWidget(11, QFormLayout::LabelRole, label_12); + + useBipolar_2 = new QCheckBox(groupBox); + useBipolar_2->setObjectName(QString::fromUtf8("useBipolar_2")); + + formLayout->setWidget(11, QFormLayout::FieldRole, useBipolar_2); + + groupBox_5 = new QGroupBox(groupBox); + groupBox_5->setObjectName(QString::fromUtf8("groupBox_5")); + formLayout_4 = new QFormLayout(groupBox_5); + formLayout_4->setSpacing(6); + formLayout_4->setContentsMargins(11, 11, 11, 11); + formLayout_4->setObjectName(QString::fromUtf8("formLayout_4")); + formLayout_4->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + label_13 = new QLabel(groupBox_5); + label_13->setObjectName(QString::fromUtf8("label_13")); + + formLayout_4->setWidget(1, QFormLayout::LabelRole, label_13); + + inTrigger = new QCheckBox(groupBox_5); + inTrigger->setObjectName(QString::fromUtf8("inTrigger")); + inTrigger->setChecked(true); + + formLayout_4->setWidget(1, QFormLayout::FieldRole, inTrigger); + + label_14 = new QLabel(groupBox_5); + label_14->setObjectName(QString::fromUtf8("label_14")); + + formLayout_4->setWidget(2, QFormLayout::LabelRole, label_14); + + outTrigger = new QCheckBox(groupBox_5); + outTrigger->setObjectName(QString::fromUtf8("outTrigger")); + + formLayout_4->setWidget(2, QFormLayout::FieldRole, outTrigger); + + label_15 = new QLabel(groupBox_5); + label_15->setObjectName(QString::fromUtf8("label_15")); + + formLayout_4->setWidget(3, QFormLayout::LabelRole, label_15); + + digiTrigger = new QCheckBox(groupBox_5); + digiTrigger->setObjectName(QString::fromUtf8("digiTrigger")); + + formLayout_4->setWidget(3, QFormLayout::FieldRole, digiTrigger); + + + formLayout->setWidget(13, QFormLayout::SpanningRole, groupBox_5); verticalLayout->addWidget(groupBox); @@ -217,6 +379,7 @@ class Ui_MainWindow linkButton = new QPushButton(centralWidget); linkButton->setObjectName(QString::fromUtf8("linkButton")); + linkButton->setEnabled(true); horizontalLayout->addWidget(linkButton); @@ -229,7 +392,7 @@ class Ui_MainWindow MainWindow->setCentralWidget(centralWidget); menuBar = new QMenuBar(MainWindow); menuBar->setObjectName(QString::fromUtf8("menuBar")); - menuBar->setGeometry(QRect(0, 0, 511, 21)); + menuBar->setGeometry(QRect(0, 0, 400, 21)); menuFile = new QMenu(menuBar); menuFile->setObjectName(QString::fromUtf8("menuFile")); MainWindow->setMenuBar(menuBar); @@ -291,20 +454,25 @@ class Ui_MainWindow "Oz\n" "O2\n" "PO10 ", 0, QApplication::UnicodeUTF8)); + groupBox_4->setTitle(QApplication::translate("MainWindow", "LiveAmpDevices", 0, QApplication::UnicodeUTF8)); + label_10->setText(QApplication::translate("MainWindow", "Available Devices", 0, QApplication::UnicodeUTF8)); + refreshDevices->setText(QApplication::translate("MainWindow", "Refresh", 0, QApplication::UnicodeUTF8)); + useSim->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + label_11->setText(QApplication::translate("MainWindow", "Use Simulator", 0, QApplication::UnicodeUTF8)); groupBox->setTitle(QApplication::translate("MainWindow", "Device Settings", 0, QApplication::UnicodeUTF8)); - label_4->setText(QApplication::translate("MainWindow", "Device Number", 0, QApplication::UnicodeUTF8)); -#ifndef QT_NO_TOOLTIP - deviceSerialNumber->setToolTip(QApplication::translate("MainWindow", "The number of the USB device (if multiple); the first one is #0.", 0, QApplication::UnicodeUTF8)); -#endif // QT_NO_TOOLTIP - deviceSerialNumber->setInputMask(QString()); - deviceSerialNumber->setText(QString()); label_2->setText(QApplication::translate("MainWindow", "Number of EEG channels", 0, QApplication::UnicodeUTF8)); #ifndef QT_NO_TOOLTIP channelCount->setToolTip(QApplication::translate("MainWindow", "This must match the number of entries in the channel list", 0, QApplication::UnicodeUTF8)); #endif // QT_NO_TOOLTIP + lblUseBipolar->setText(QApplication::translate("MainWindow", "\n" +"\n" +"

Use bipolar channels

", 0, QApplication::UnicodeUTF8)); + useBipolar->setText(QString()); label->setText(QApplication::translate("MainWindow", "Chunk Size", 0, QApplication::UnicodeUTF8)); #ifndef QT_NO_TOOLTIP - chunkSize->setToolTip(QApplication::translate("MainWindow", "The number of samples per chunk emitted by the driver -- a small value will lead to lower overall latency but causes more CPU load", 0, QApplication::UnicodeUTF8)); + chunkSize->setToolTip(QApplication::translate("MainWindow", "

The number of samples per chunk emitted by the driver -- a small value will lead to lower overall latency but causes more CPU load, max value is 20

", 0, QApplication::UnicodeUTF8)); #endif // QT_NO_TOOLTIP label_3->setText(QApplication::translate("MainWindow", "Sampling Rate", 0, QApplication::UnicodeUTF8)); samplingRate->clear(); @@ -326,12 +494,37 @@ class Ui_MainWindow useACC->setToolTip(QApplication::translate("MainWindow", "This will enable Acceleration sensors X, Y and Z.", 0, QApplication::UnicodeUTF8)); #endif // QT_NO_TOOLTIP useACC->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); - useBipolar->setText(QString()); - lblUseBipolar->setText(QApplication::translate("MainWindow", "\n" -"\n" -"

Use bipolar channels

", 0, QApplication::UnicodeUTF8)); + groupBox_3->setTitle(QApplication::translate("MainWindow", "LSL Trigger Output Style", 0, QApplication::UnicodeUTF8)); + label_5->setText(QApplication::translate("MainWindow", "Unsampled String Markers", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + unsampledMarkers->setToolTip(QApplication::translate("MainWindow", "

For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.

", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + unsampledMarkers->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + label_6->setText(QApplication::translate("MainWindow", "Sampled String Markers", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + sampledMarkers->setToolTip(QApplication::translate("MainWindow", "

For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.

", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + sampledMarkers->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + sampledMarkersEEG->setToolTip(QApplication::translate("MainWindow", "

For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.

", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + sampledMarkersEEG->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + label_9->setText(QApplication::translate("MainWindow", "Include in EEG Stream", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + deviceSerialNumber->setToolTip(QApplication::translate("MainWindow", "The number of the USB device (if multiple); the first one is #0.", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + deviceSerialNumber->setInputMask(QString()); + deviceSerialNumber->setText(QString()); + label_4->setText(QApplication::translate("MainWindow", "Device Number", 0, QApplication::UnicodeUTF8)); + label_12->setText(QApplication::translate("MainWindow", "Enable Bipolar (last 8 only) ", 0, QApplication::UnicodeUTF8)); + useBipolar_2->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + groupBox_5->setTitle(QApplication::translate("MainWindow", "Select Trigger Channels to Output", 0, QApplication::UnicodeUTF8)); + label_13->setText(QApplication::translate("MainWindow", "Trigger In", 0, QApplication::UnicodeUTF8)); + inTrigger->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + label_14->setText(QApplication::translate("MainWindow", "Trigger Out", 0, QApplication::UnicodeUTF8)); + outTrigger->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + label_15->setText(QApplication::translate("MainWindow", "Digital", 0, QApplication::UnicodeUTF8)); + digiTrigger->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); linkButton->setText(QApplication::translate("MainWindow", "Link", 0, QApplication::UnicodeUTF8)); menuFile->setTitle(QApplication::translate("MainWindow", "File", 0, QApplication::UnicodeUTF8)); } // retranslateUi diff --git a/Apps/BrainProducts/LiveAmp/vc100.idb b/Apps/BrainProducts/LiveAmp/vc100.idb new file mode 100644 index 00000000..a3a3f073 Binary files /dev/null and b/Apps/BrainProducts/LiveAmp/vc100.idb differ diff --git a/Apps/BrainProducts/VAmp/README.txt b/Apps/BrainProducts/VAmp/README.txt new file mode 100644 index 00000000..8dc7977d --- /dev/null +++ b/Apps/BrainProducts/VAmp/README.txt @@ -0,0 +1,17 @@ +== Usage == + +1. Make sure that you have correctly installed the drivers for your amplifier, and that the amplifier is plugged in and ready to use (see also official brochure). + +2. If you have multiple amplifiers plugged in, make sure that you pick the correct one under Device Number (0 is the first one according to USB port numbering). Select the number of channels that you want to record from. The maximum number of channels is determined by the number of modules that are installed in your amplifier. Channels always count from the first module (the one closest to the front of the box). Enter the channel labels according to your cap design; make sure that the number of channel labels matches the selected number of channels. + +3. For most EEG experiments you can ignore the Chunk Size setting, but if you are developing a latency-critical real-time application (e.g., a P300 speller BCI), you can lower this setting to reduce the latency of your system. + +4. Use whatever sampling rate is appropriate for your experiment; you can use a lower rate if necessary to save network bandwidth (when transmitting on a slow network) or to reduce the disk space taken up by your recordings. If your analysis requires data to be unfiltered, particularly when you perform causal analysis (e.g., effective connectivity), you should strongly consider using a native sampling rate (e.g., 10KHz). + +5. If you use AUX channels, check the according box and append 8 channel labels at the end of the channel list (even if you only use a subset of them). + +6. Click the "Link" button. If all goes well you should now have a stream on your lab network that has name "ActiChamp-0" (if you used device 0) and type "EEG", and a second one named "ActiChamp-0-Markers" with type "Markers" that holds the event markers. Note that you cannot close the app while it is linked. + +== Optional == + +The configuration settings can be saved to a .cfg file (see File / Save Configuration) and subsequently loaded from such a file (via File / Load Configuration). Importantly, the program can be started with a command-line argument of the form "ActiChamp.exe -c myconfig.cfg", which allows to load the config automatically at start-up. The recommended procedure to use the app in production experiments is to make a shortcut on the experimenter's desktop which points to a previously saved configuration customized to the study being recorded to minimize the chance of operator error. \ No newline at end of file diff --git a/Apps/BrainProducts/VAmp/Resampler.h b/Apps/BrainProducts/VAmp/Resampler.h new file mode 100644 index 00000000..1bb88377 --- /dev/null +++ b/Apps/BrainProducts/VAmp/Resampler.h @@ -0,0 +1,271 @@ +/* +Copyright (c) 2009, Motorola, Inc + +All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +* Neither the name of Motorola nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef RESAMPLER_H +#define RESAMPLER_H + + +#include +#include +#include + +using namespace std; + +template +class Resampler { +public: + typedef S1 inputType; + typedef S2 outputType; + typedef C coefType; + + // create new resampler + Resampler(int upRate, int downRate, const C *coefs, int coefCount, S1 *initConds=0,int t=0,int xoff=0); + virtual ~Resampler(); + + // resample a chunk of multi-channel samples + void apply_multichannel(const vector > &in, vector > &out); + + // process input samples + int apply(S1* in, int inCount, S2* out, int outCount); + // number of output samples produced for given input + int neededOutCount(int inCount); + + // length of initial/final conditions + int coefsPerPhase() const { return _coefsPerPhase; } + + // final conditions + inputType *finalConds() const { return _state; } + + // pointer into fractional data stream + int get_t() const { return _t; } + int get_xoff() const {return _xOffset; } + +//private: + vector > _temp_in,_temp_out; + int _tempChannels; + + int _upRate; + int _downRate; + + coefType *_transposedCoefs; // reorganized coefficient matrix + inputType *_state; // state vector (in case past data is needed for next sample) + inputType *_stateEnd; // end ptr for state vector + + int _paddedCoefCount; // ceil(len(coefs)/upRate)*upRate + int _coefsPerPhase; // _paddedCoefCount / upRate + + int _t; // "time" (modulo upRate) + int _xOffset; // offset into the fractional data stream + +}; + + +#include +#include + +/* +using std::cout; +using std::endl; + +using std::fill; +using std::copy; +*/ + +using std::invalid_argument; + +template +Resampler::Resampler(int upRate, int downRate, const C *coefs, + int coefCount, S1 *initConds, int t, int xoff): + _upRate(upRate), _downRate(downRate), _t(t), _xOffset(xoff) +/* + The coefficients are copied into local storage in a transposed, flipped + arrangement. For example, suppose upRate is 3, and the input number + of coefficients coefCount = 10, represented as h[0], ..., h[9]. + Then the internal buffer will look like this: + h[9], h[6], h[3], h[0], // flipped phase 0 coefs + 0, h[7], h[4], h[1], // flipped phase 1 coefs (zero-padded) + 0, h[8], h[5], h[2], // flipped phase 2 coefs (zero-padded) +*/ +{ + _paddedCoefCount = coefCount; + while (_paddedCoefCount % _upRate) { + _paddedCoefCount++; + } + _coefsPerPhase = _paddedCoefCount / _upRate; + + /* initialize state */ + _state = new inputType[_coefsPerPhase - 1]; + _stateEnd = _state + _coefsPerPhase - 1; + if (initConds) { + /* if initial conditions have been specified, use them */ + copy(initConds,initConds + _coefsPerPhase - 1, _state); + } else { + /* otherwise assume that they are zero... */ + fill(_state, _stateEnd, 0.); + } + + /* This both transposes, and "flips" each phase, while + * copying the defined coefficients into local storage. + * There is probably a faster way to do this + */ + _transposedCoefs = new coefType[_paddedCoefCount]; + fill(_transposedCoefs, _transposedCoefs + _paddedCoefCount, 0.); + for (int i=0; i<_upRate; ++i) { + for (int j=0; j<_coefsPerPhase; ++j) { + if (j*_upRate + i < coefCount) + _transposedCoefs[(_coefsPerPhase-1-j) + i*_coefsPerPhase] = + coefs[j*_upRate + i]; + } + } +} + +template +Resampler::~Resampler() { + delete [] _transposedCoefs; + delete [] _state; +} + +template +int Resampler::neededOutCount(int inCount) +/* compute how many outputs will be generated for inCount inputs */ +{ + int np = inCount * _upRate; + int need = np / _downRate; + if ((_t + _upRate * _xOffset) < (np % _downRate)) + need++; + return need; +} + +// resample a chunk of multi-channel samples +template +void Resampler::apply_multichannel(const vector > &in, vector > &out) { + int insamples = in.size(); + if (insamples > 0) { + int outchannels = in[0].size(); + int outsamples = neededOutCount(insamples); + + // copy to transposed input buffer _temp_in + _temp_in.resize(outchannels); + for (int c=0,e=outchannels;c +int Resampler::apply(S1* in, int inCount, + S2* out, int outCount) { + if (outCount < neededOutCount(inCount)) + throw invalid_argument("Not enough output samples"); + + inputType *x = in + _xOffset; // points to the latest processed input sample + outputType *y = out; // points to the next output sample + inputType *end = in + inCount; // end of the input + + // as long as there is input... + while (x < end) { + // init accumulator + outputType acc = 0.; + // pointer to current filter phase + coefType *h = _transposedCoefs + _t*_coefsPerPhase; + // pointer from where we read (may actually be before the beginning of the signal) + inputType *xPtr = x - _coefsPerPhase + 1; + // offset relative to beginning of the signal + int offset = in - xPtr; + // if we need samples from the past... (note: sometimes we don't!) + if (offset > 0) { + // pointer into state + inputType *statePtr = _stateEnd - offset; + // as long as we need to read from state... + while (statePtr < _stateEnd) { + // do so. + acc += *statePtr++ * *h++; + } + // done; now increment the data pointer appropriately + xPtr += offset; + } + // as long as we have to catch up... + while (xPtr <= x) { + // do so + acc += *xPtr++ * *h++; + } + // and write out the result + *y++ = acc; + // also, advance phase time + _t += _downRate; + // and advance our position in X + int advanceAmount = _t / _upRate; + x += advanceAmount; + // do the modulo computation + _t %= _upRate; + } + // finally, remember where we are (in the data) + _xOffset = x - end; + + // manage _state buffer + // find number of samples retained in buffer: + int retain = (_coefsPerPhase - 1) - inCount; + if (retain > 0) { + // for inCount smaller than state buffer, copy end of buffer + // to beginning: + copy(_stateEnd - retain, _stateEnd, _state); + // Then, copy the entire (short) input to end of buffer + copy(in, end, _stateEnd - inCount); + } else { + // just copy last input samples into state buffer + copy(end - (_coefsPerPhase - 1), end, _state); + } + // number of samples computed + return y - out; +} + +#endif diff --git a/Apps/BrainProducts/VAmp/VAmp2010.sln b/Apps/BrainProducts/VAmp/VAmp.sln similarity index 55% rename from Apps/BrainProducts/VAmp/VAmp2010.sln rename to Apps/BrainProducts/VAmp/VAmp.sln index 7e4ba03e..4b5b651f 100644 --- a/Apps/BrainProducts/VAmp/VAmp2010.sln +++ b/Apps/BrainProducts/VAmp/VAmp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VAmp2010", "VAmp2010.vcxproj", "{F2B30B90-374A-4D05-9061-0EFA75651E33}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VAmp", "VAmp.vcxproj", "{B3837C3F-9CAB-4078-8E2F-07C837E22C95}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -9,10 +9,10 @@ Global Release|Win32 = Release|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F2B30B90-374A-4D05-9061-0EFA75651E33}.Debug|Win32.ActiveCfg = Debug|Win32 - {F2B30B90-374A-4D05-9061-0EFA75651E33}.Debug|Win32.Build.0 = Debug|Win32 - {F2B30B90-374A-4D05-9061-0EFA75651E33}.Release|Win32.ActiveCfg = Release|Win32 - {F2B30B90-374A-4D05-9061-0EFA75651E33}.Release|Win32.Build.0 = Release|Win32 + {B3837C3F-9CAB-4078-8E2F-07C837E22C95}.Debug|Win32.ActiveCfg = Debug|Win32 + {B3837C3F-9CAB-4078-8E2F-07C837E22C95}.Debug|Win32.Build.0 = Debug|Win32 + {B3837C3F-9CAB-4078-8E2F-07C837E22C95}.Release|Win32.ActiveCfg = Release|Win32 + {B3837C3F-9CAB-4078-8E2F-07C837E22C95}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Apps/BrainProducts/VAmp/VAmp2010.vcxproj b/Apps/BrainProducts/VAmp/VAmp.vcxproj similarity index 57% rename from Apps/BrainProducts/VAmp/VAmp2010.vcxproj rename to Apps/BrainProducts/VAmp/VAmp.vcxproj index e88ed22a..1b1cc639 100644 --- a/Apps/BrainProducts/VAmp/VAmp2010.vcxproj +++ b/Apps/BrainProducts/VAmp/VAmp.vcxproj @@ -11,26 +11,20 @@ - {F2B30B90-374A-4D05-9061-0EFA75651E33} - Win32Proj - VAmp2010 + {B3837C3F-9CAB-4078-8E2F-07C837E22C95} + VAmp Application true - Unicode - v100 - Static - false + NotSet Application false false - Unicode - v100 - Static + NotSet @@ -43,74 +37,76 @@ - true $(SolutionDir) debug - VAmp2010 + VAmp - false $(SolutionDir) - release - VAmp2010 - $(VCInstallDir)bin;$(WindowsSdkDir)bin\NETFX 4.0 Tools;$(WindowsSdkDir)bin;$(VSInstallDir)Common7\Tools\bin;$(VSInstallDir)Common7\tools;$(VSInstallDir)Common7\ide;$(ProgramFiles)\HTML Help Workshop;$(FrameworkSDKDir)\bin;$(MSBuildToolsPath32);$(VSInstallDir);$(SystemRoot)\SysWow64;$(FxCopDir);$(PATH); - $(VCInstallDir)atlmfc\lib;$(VCInstallDir)lib - $(VCInstallDir)atlmfc\src\mfc;$(VCInstallDir)atlmfc\src\mfcm;$(VCInstallDir)atlmfc\src\atl;$(VCInstallDir)crt\src; - $(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)\include;$(MSBuildToolsPath32);$(VCInstallDir)atlmfc\lib;$(VCInstallDir)lib; + release\ + VAmp + false + true - NotUsing Level3 Disabled - WIN32;_DEBUG;_WINDOWS;_MBS;WIN32;NDEBUG;_QT_LARGEFILE_SUPPORT;QT_DLL;QT_NO_DEBUG;QT_GUI_LIB;QT_CORE_LIB;QT_HAVE_MMX;QT_HAVE_3DNOW;QT_HAVE_SSE;QT_HAVE_MMXEXT;QT_HAVE_SSE2;QT_THREAD_SUPPORT;_WINDOWS;UNICODE;%(PreprocessorDefinitions) - $(BOOST_ROOT);$(QTSDK)\include;$(QTSDK)\include\QtGui;$(QTSDK)\include\QtCore;$(QTSDK)\include\ActiveQt;$(QTSDK)\mkspecs\default;.;release;$(LSL_LIB)\include;%(AdditionalIncludeDirectories) - ProgramDatabase - true - false - false - false - false + $(BOOST_ROOT);$(QTSDK)\include;$(QTSDK)\include\QtGui;$(QTSDK)\include\QtCore;$(QTSDK)\include\ActiveQt;.;debug;%(AdditionalIncludeDirectories) + false - Windows true - $(LSL_LIB)\bin;$(QTSDK)\lib;$(BOOST_ROOT)\stage\lib;%(AdditionalLibraryDirectories) - FilterButterworth64D.lib;Winmm.lib;$(QTSDK)\lib\qtmaind.lib;$(QTSDK)\lib\QtGuid4.lib;$(QTSDK)\lib\QtCored4.lib;%(AdditionalDependencies) + $(BOOST_ROOT)\lib;$(QTSDK)\lib;%(AdditionalLibraryDirectories) + FilterButterworth64D.lib;Winmm.lib;$(QTSDK)\lib\qtmaind.lib;$(QTSDK)\lib\QtGui4.lib;$(QTSDK)\lib\QtCore4.lib;%(AdditionalDependencies) Level3 - NotUsing MaxSpeed false - true - _MBS;WIN32;NDEBUG;_QT_LARGEFILE_SUPPORT;QT_DLL;QT_NO_DEBUG;QT_GUI_LIB;QT_CORE_LIB;QT_HAVE_MMX;QT_HAVE_3DNOW;QT_HAVE_SSE;QT_HAVE_MMXEXT;QT_HAVE_SSE2;QT_THREAD_SUPPORT;_WINDOWS;UNICODE;%(PreprocessorDefinitions) - $(BOOST_ROOT);$(QTSDK)\include;$(QTSDK)\include\QtGui;$(QTSDK)\include\QtCore;$(QTSDK)\include\ActiveQt;$(QTSDK)\mkspecs\default;.;release;$(LSL_LIB)\include;%(AdditionalIncludeDirectories) + false + $(QTSDK)\mkspecs\default;$(BOOST_ROOT);$(QTSDK)\include;$(QTSDK)\include\QtGui;$(QTSDK)\include\QtCore;$(QTSDK)\include\ActiveQt;.;%(AdditionalIncludeDirectories) + _WINDOWS;WIN32;QT_LARGEFILE_SUPPORT;QT_DLL;QT_NO_DEBUG;QT_GUI_LIB;QT_CORE_LIB;QT_HAVE_MMX;QT_HAVE_3DNOW;QT_HAVE_SSE;QT_HAVE_MMXEXT;QT_HAVE_SSE2;QT_THREAD_SUPPORT;NDEBUG;_SCL_SECURE_NO_WARNINGS false false false - false - false + true + true false release\ release\ - .\ CompileAsCpp Prompt stdafx.h - Windows - true + false true true - $(LSL_LIB)\bin;$(QTSDK)\lib;$(BOOST_ROOT)\stage\lib;%(AdditionalLibraryDirectories) + $(BOOST_ROOT)\lib;$(QTSDK)\lib;%(AdditionalLibraryDirectories) FilterButterworth64R.lib;Winmm.lib;$(QTSDK)\lib\qtmain.lib;$(QTSDK)\lib\QtGui4.lib;$(QTSDK)\lib\QtCore4.lib;%(AdditionalDependencies) + $(OutDir)VAmp.exe false + + + false + Windows + + + 0 + 0 + 0 + 0 + false + false + false + false NotSet DefaultThreadingAttribute + false + PromptImmediately @@ -118,12 +114,21 @@ - - NotUsing - - - Create - + + + + + + Document + $(QTSDK)\bin\uic.exe mainwindow.ui -o ui_mainwindow.h + UIC mainwindow.ui + ui_mainwindow.h;%(Outputs) + $(QTSDK)\bin\uic.exe;mainwindow.ui;%(AdditionalInputs) + $(QTSDK)\bin\uic.exe mainwindow.ui -o ui_mainwindow.h + UIC mainwindow.ui + ui_mainwindow.h;%(Outputs) + $(QTSDK)\bin\uic.exe;mainwindow.ui;%(AdditionalInputs) + @@ -131,23 +136,17 @@ + + - $(QTSDK)\bin\moc.exe -DUNICODE -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_DLL -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_HAVE_MMX -DQT_HAVE_3DNOW -DQT_HAVE_SSE -DQT_HAVE_MMXEXT -DQT_HAVE_SSE2 -DQT_THREAD_SUPPORT -I"(QTSDK)\include\QtCore" -I"(QTSDK)\include\QtGui" -I"(QTSDK)\include" -I"(BOOST_ROOT)" -I"c:\DEVEL\LSL\repo\Apps\PhaseSpace\..\..\LSL\include" -I"(QTSDK)\include\ActiveQt" -I"release" -I"." -I(QTSDK)\mkspecs\default -D_MSC_VER=1500 -DWIN32 mainwindow.h -o moc_mainwindow.cpp + $(QTSDK)\bin\moc.exe -DUNICODE -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_DLL -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_HAVE_MMX -DQT_HAVE_3DNOW -DQT_HAVE_SSE -DQT_HAVE_MMXEXT -DQT_HAVE_SSE2 -DQT_THREAD_SUPPORT -I"(QTSDK)\include\QtCore" -I"(QTSDK)\include\QtGui" -I"(QTSDK)\include" -I"(BOOST_ROOT)" -I"c:..\..\..\LSL\liblsl\include" -I"(QTSDK)\include\ActiveQt" -I"release" -I"." -I(QTSDK)\mkspecs\default -D_MSC_VER=1500 -DWIN32 mainwindow.h -o release\moc_mainwindow.cpp MOC mainwindow.h - ..\moc_mainwindow.h + release\moc_mainwindow.cpp $(QTSDK)\bin\moc.exe;mainwindow.h - - - - - - - Document - $(QTSDK)\bin\uic.exe mainwindow.ui -o ui_mainwindow.h - UIC mainwindow.ui - ui_mainwindow.h - $(QTSDK)\bin\uic.exe;mainwindow.ui - Designer + $(QTSDK)\bin\moc.exe -DUNICODE -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_DLL -DQT_GUI_LIB -DQT_CORE_LIB -DQT_HAVE_MMX -DQT_HAVE_3DNOW -DQT_HAVE_SSE -DQT_HAVE_MMXEXT -DQT_HAVE_SSE2 -DQT_THREAD_SUPPORT -I"(QTSDK)\include\QtCore" -I"(QTSDK)\include\QtGui" -I"(QTSDK)\include" -I"(BOOST_ROOT)" -I"..\..\..\LSL\liblsl\include" -I"(QTSDK)\include\ActiveQt" -I"debug" -I"." -I(QTSDK)\mkspecs\default -D_MSC_VER=1500 -DWIN32 mainwindow.h -o release\moc_mainwindow.cpp + MOC mainwindow.h + release\moc_mainwindow.cpp + $(QTSDK)\bin\moc.exe;mainwindow.h diff --git a/Apps/BrainProducts/VAmp/VAmp2010.vcxproj.filters b/Apps/BrainProducts/VAmp/VAmp.vcxproj.filters similarity index 82% rename from Apps/BrainProducts/VAmp/VAmp2010.vcxproj.filters rename to Apps/BrainProducts/VAmp/VAmp.vcxproj.filters index f565cd40..ef17d51a 100644 --- a/Apps/BrainProducts/VAmp/VAmp2010.vcxproj.filters +++ b/Apps/BrainProducts/VAmp/VAmp.vcxproj.filters @@ -5,26 +5,27 @@ {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {6f4f8a81-b86b-4a72-ad3b-2b5b1eb86064} + {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd - - {0b41471e-c521-4dcd-8a94-d8231739fb4f} - - - {6efbbaff-7e18-4cf8-b58c-d2f7b4f64f9b} - - + Source Files - + Source Files - - Gereated Files + + Generated Files Source Files @@ -38,12 +39,12 @@ - Gereated Files + Generated Files Header Files - + Header Files @@ -55,7 +56,7 @@ Header Files - + Header Files diff --git a/Apps/BrainProducts/VAmp/VAmp2010.rc b/Apps/BrainProducts/VAmp/VAmp2010.rc deleted file mode 100644 index 8cea0c8f..00000000 Binary files a/Apps/BrainProducts/VAmp/VAmp2010.rc and /dev/null differ diff --git a/Apps/BrainProducts/VAmp/VAmp_config.cfg b/Apps/BrainProducts/VAmp/VAmp_config.cfg index 901a09de..031efcd9 100644 --- a/Apps/BrainProducts/VAmp/VAmp_config.cfg +++ b/Apps/BrainProducts/VAmp/VAmp_config.cfg @@ -1,2 +1,2 @@ -016204false \ No newline at end of file +016202falsefalsetruefalse \ No newline at end of file diff --git a/Apps/BrainProducts/VAmp/explanation_of_trigger_marker_types.pdf b/Apps/BrainProducts/VAmp/explanation_of_trigger_marker_types.pdf new file mode 100644 index 00000000..13053e0d Binary files /dev/null and b/Apps/BrainProducts/VAmp/explanation_of_trigger_marker_types.pdf differ diff --git a/Apps/BrainProducts/VAmp/main.cpp b/Apps/BrainProducts/VAmp/main.cpp index 15408dfd..2cb8fa9d 100644 --- a/Apps/BrainProducts/VAmp/main.cpp +++ b/Apps/BrainProducts/VAmp/main.cpp @@ -2,14 +2,18 @@ #include #else #include -#endif +#endif #include "stdafx.h" #include "mainwindow.h" #include #include +#include int main(int argc, char *argv[]) { + + + // determine the startup config file... std::string config_file = "VAmp_config.cfg"; for (int k=1;k #include #include +#include #include "FirstAmp.h" #include "Device.h" @@ -16,482 +22,6 @@ const double sampling_rates[] = {100, 250, 500, 1000, 2000, 5000, 10000, 20000}; -const double coeffs_10000_to_125[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1.899468e-035, - -1.957509e-008, -7.843559e-008, -1.766936e-007, -3.143392e-007, -4.912400e-007, -7.071401e-007, -9.616602e-007, - -1.254298e-006, -1.584427e-006, -1.951300e-006, -2.354046e-006, -2.791676e-006, -3.263080e-006, - -3.767033e-006, -4.302193e-006, -4.867107e-006, -5.460211e-006, -6.079835e-006, -6.724205e-006, - -7.391445e-006, -8.079584e-006, -8.786557e-006, -9.510213e-006, -1.024831e-005, -1.099855e-005, - -1.175852e-005, -1.252577e-005, -1.329777e-005, -1.407196e-005, -1.484568e-005, -1.561627e-005, -1.638100e-005, - -1.713711e-005, -1.788183e-005, -1.861234e-005, -1.932583e-005, -2.001948e-005, -2.069045e-005, - -2.133592e-005, -2.195308e-005, -2.253915e-005, -2.309135e-005, -2.360696e-005, -2.408330e-005, - -2.451771e-005, -2.490762e-005, -2.525050e-005, -2.554390e-005, -2.578543e-005, -2.597280e-005, - -2.610380e-005, -2.617632e-005, -2.618835e-005, -2.613799e-005, -2.602345e-005, -2.584305e-005, -2.559526e-005, - -2.527868e-005, -2.489201e-005, -2.443414e-005, -2.390407e-005, -2.330098e-005, -2.262418e-005, - -2.187315e-005, -2.104754e-005, -2.014716e-005, -1.917198e-005, -1.812216e-005, -1.699802e-005, - -1.580006e-005, -1.452897e-005, -1.318561e-005, -1.177102e-005, -1.028642e-005, -8.733213e-006, - -7.112994e-006, -5.427526e-006, -3.678757e-006, -1.868814e-006, 5.325507e-020, 1.925206e-006, 3.904152e-006, - 5.934018e-006, 8.011816e-006, 1.013439e-005, 1.229844e-005, 1.450050e-005, 1.673695e-005, - 1.900404e-005, 2.129787e-005, 2.361441e-005, 2.594950e-005, 2.829887e-005, 3.065812e-005, 3.302276e-005, - 3.538819e-005, 3.774974e-005, 4.010262e-005, 4.244200e-005, 4.476296e-005, 4.706055e-005, 4.932976e-005, - 5.156552e-005, 5.376277e-005, 5.591641e-005, 5.802133e-005, 6.007242e-005, 6.206460e-005, - 6.399278e-005, 6.585194e-005, 6.763708e-005, 6.934326e-005, 7.096558e-005, 7.249927e-005, 7.393958e-005, - 7.528190e-005, 7.652172e-005, 7.765464e-005, 7.867637e-005, 7.958279e-005, 8.036992e-005, 8.103391e-005, - 8.157111e-005, 8.197803e-005, 8.225138e-005, 8.238806e-005, 8.238515e-005, 8.223999e-005, - 8.195011e-005, 8.151327e-005, 8.092749e-005, 8.019100e-005, 7.930232e-005, 7.826020e-005, 7.706367e-005, - 7.571202e-005, 7.420483e-005, 7.254195e-005, 7.072352e-005, 6.874996e-005, 6.662200e-005, 6.434065e-005, - 6.190723e-005, 5.932335e-005, 5.659093e-005, 5.371218e-005, 5.068963e-005, 4.752610e-005, - 4.422472e-005, 4.078892e-005, 3.722240e-005, 3.352921e-005, 2.971365e-005, 2.578031e-005, 2.173409e-005, - 1.758016e-005, 1.332394e-005, 8.971155e-006, 4.527766e-006, -1.139593e-019, -4.605674e-006, -9.282546e-006, - -1.402368e-005, -1.882191e-005, -2.366988e-005, -2.856001e-005, -3.348454e-005, -3.843555e-005, - -4.340492e-005, -4.838440e-005, -5.336560e-005, -5.833999e-005, -6.329893e-005, -6.823368e-005, - -7.313542e-005, -7.799525e-005, -8.280422e-005, -8.755333e-005, -9.223356e-005, -9.683587e-005, - -1.013513e-004, -1.057707e-004, -1.100852e-004, -1.142859e-004, -1.183640e-004, -1.223106e-004, -1.261172e-004, - -1.297751e-004, -1.332761e-004, -1.366118e-004, -1.397742e-004, -1.427555e-004, -1.455478e-004, - -1.481439e-004, -1.505364e-004, -1.527183e-004, -1.546830e-004, -1.564241e-004, -1.579353e-004, - -1.592109e-004, -1.602453e-004, -1.610335e-004, -1.615706e-004, -1.618522e-004, -1.618741e-004, - -1.616327e-004, -1.611248e-004, -1.603474e-004, -1.592982e-004, -1.579750e-004, -1.563764e-004, -1.545011e-004, - -1.523486e-004, -1.499185e-004, -1.472112e-004, -1.442274e-004, -1.409682e-004, -1.374355e-004, - -1.336312e-004, -1.295582e-004, -1.252194e-004, -1.206187e-004, -1.157601e-004, -1.106481e-004, - -1.052880e-004, -9.968527e-005, -9.384598e-005, -8.777668e-005, -8.148436e-005, -7.497650e-005, - -6.826101e-005, -6.134628e-005, -5.424111e-005, -4.695473e-005, -3.949682e-005, -3.187743e-005, -2.410702e-005, - -1.619645e-005, -8.156920e-006, 1.792589e-019, 8.262406e-006, 1.661807e-005, 2.505446e-005, - 3.355876e-005, 4.211787e-005, 5.071846e-005, 5.934695e-005, 6.798957e-005, 7.663233e-005, 8.526109e-005, - 9.386158e-005, 1.024194e-004, 1.109199e-004, 1.193487e-004, 1.276910e-004, 1.359322e-004, - 1.440575e-004, 1.520524e-004, 1.599021e-004, 1.675921e-004, 1.751079e-004, 1.824353e-004, 1.895599e-004, - 1.964678e-004, 2.031450e-004, 2.095780e-004, 2.157534e-004, 2.216580e-004, 2.272791e-004, 2.326041e-004, - 2.376208e-004, 2.423175e-004, 2.466828e-004, 2.507057e-004, 2.543756e-004, 2.576824e-004, - 2.606165e-004, 2.631688e-004, 2.653307e-004, 2.670942e-004, 2.684517e-004, 2.693965e-004, 2.699220e-004, - 2.700228e-004, 2.696937e-004, 2.689302e-004, 2.677287e-004, 2.660861e-004, 2.640000e-004, 2.614686e-004, - 2.584910e-004, 2.550670e-004, 2.511970e-004, 2.468821e-004, 2.421243e-004, 2.369264e-004, - 2.312916e-004, 2.252242e-004, 2.187291e-004, 2.118120e-004, 2.044793e-004, 1.967381e-004, 1.885964e-004, - 1.800629e-004, 1.711468e-004, 1.618583e-004, 1.522083e-004, 1.422082e-004, 1.318703e-004, 1.212074e-004, - 1.102332e-004, 9.896179e-005, 8.740811e-005, 7.558758e-005, 6.351629e-005, 5.121089e-005, - 3.868856e-005, 2.596705e-005, 1.306462e-005, -2.458534e-019, -1.320761e-005, -2.653857e-005, -3.997285e-005, - -5.349000e-005, -6.706927e-005, -8.068956e-005, -9.432949e-005, -1.079674e-004, -1.215815e-004, - -1.351496e-004, -1.486496e-004, -1.620591e-004, -1.753557e-004, -1.885169e-004, -2.015203e-004, - -2.143433e-004, -2.269635e-004, -2.393587e-004, -2.515065e-004, -2.633850e-004, -2.749724e-004, -2.862471e-004, - -2.971877e-004, -3.077733e-004, -3.179833e-004, -3.277974e-004, -3.371958e-004, -3.461591e-004, - -3.546686e-004, -3.627058e-004, -3.702531e-004, -3.772933e-004, -3.838098e-004, -3.897870e-004, - -3.952096e-004, -4.000633e-004, -4.043343e-004, -4.080100e-004, -4.110782e-004, -4.135279e-004, - -4.153487e-004, -4.165313e-004, -4.170672e-004, -4.169488e-004, -4.161698e-004, -4.147245e-004, -4.126084e-004, - -4.098179e-004, -4.063507e-004, -4.022054e-004, -3.973815e-004, -3.918798e-004, -3.857022e-004, - -3.788516e-004, -3.713321e-004, -3.631487e-004, -3.543077e-004, -3.448165e-004, -3.346836e-004, - -3.239185e-004, -3.125320e-004, -3.005358e-004, -2.879428e-004, -2.747669e-004, -2.610232e-004, - -2.467277e-004, -2.318976e-004, -2.165510e-004, -2.007070e-004, -1.843857e-004, -1.676082e-004, -1.503966e-004, - -1.327736e-004, -1.147633e-004, -9.639005e-005, -7.767949e-005, -5.865784e-005, -3.935211e-005, - -1.979003e-005, 3.102067e-019, 1.998892e-005, 4.014708e-005, 6.044427e-005, 8.084976e-005, - 1.013323e-004, 1.218604e-004, 1.424018e-004, 1.629244e-004, 1.833954e-004, 2.037820e-004, 2.240510e-004, - 2.441694e-004, 2.641037e-004, 2.838207e-004, 3.032870e-004, 3.224695e-004, 3.413350e-004, 3.598505e-004, - 3.779833e-004, 3.957010e-004, 4.129716e-004, 4.297633e-004, 4.460450e-004, 4.617857e-004, - 4.769554e-004, 4.915245e-004, 5.054640e-004, 5.187458e-004, 5.313423e-004, 5.432270e-004, 5.543740e-004, - 5.647586e-004, 5.743567e-004, 5.831455e-004, 5.911032e-004, 5.982090e-004, 6.044434e-004, 6.097877e-004, - 6.142250e-004, 6.177392e-004, 6.203157e-004, 6.219411e-004, 6.226036e-004, 6.222926e-004, - 6.209989e-004, 6.187149e-004, 6.154344e-004, 6.111527e-004, 6.058668e-004, 5.995749e-004, 5.922771e-004, - 5.839749e-004, 5.746714e-004, 5.643714e-004, 5.530812e-004, 5.408088e-004, 5.275638e-004, 5.133574e-004, - 4.982023e-004, 4.821131e-004, 4.651057e-004, 4.471978e-004, 4.284087e-004, 4.087590e-004, - 3.882711e-004, 3.669689e-004, 3.448777e-004, 3.220245e-004, 2.984375e-004, 2.741464e-004, 2.491824e-004, - 2.235780e-004, 1.973669e-004, 1.705842e-004, 1.432663e-004, 1.154507e-004, 8.717597e-005, 5.848192e-005, - 2.940933e-005, -3.687801e-019, -2.970336e-005, -5.965715e-005, -8.981695e-005, -1.201376e-004, - -1.505731e-004, -1.810772e-004, -2.116026e-004, -2.421018e-004, -2.725268e-004, -3.028293e-004, - -3.329606e-004, -3.628719e-004, -3.925142e-004, -4.218384e-004, -4.507954e-004, -4.793363e-004, -5.074123e-004, - -5.349748e-004, -5.619755e-004, -5.883667e-004, -6.141008e-004, -6.391312e-004, -6.634114e-004, - -6.868961e-004, -7.095405e-004, -7.313007e-004, -7.521337e-004, -7.719977e-004, -7.908518e-004, - -8.086562e-004, -8.253725e-004, -8.409635e-004, -8.553935e-004, -8.686279e-004, -8.806341e-004, - -8.913806e-004, -9.008377e-004, -9.089776e-004, -9.157740e-004, -9.212024e-004, -9.252403e-004, -9.278672e-004, - -9.290642e-004, -9.288148e-004, -9.271043e-004, -9.239204e-004, -9.192526e-004, -9.130927e-004, - -9.054349e-004, -8.962755e-004, -8.856129e-004, -8.734481e-004, -8.597843e-004, -8.446270e-004, - -8.279841e-004, -8.098658e-004, -7.902847e-004, -7.692559e-004, -7.467967e-004, -7.229268e-004, - -6.976684e-004, -6.710460e-004, -6.430864e-004, -6.138187e-004, -5.832745e-004, -5.514874e-004, -5.184934e-004, - -4.843308e-004, -4.490401e-004, -4.126637e-004, -3.752464e-004, -3.368349e-004, -2.974780e-004, - -2.572263e-004, -2.161326e-004, -1.742512e-004, -1.316385e-004, -8.835238e-005, -4.445249e-005, - 4.182708e-019, 4.494239e-005, 9.031056e-005, 1.360391e-004, 1.820612e-004, 2.283091e-004, 2.747138e-004, - 3.212054e-004, 3.677130e-004, 4.141649e-004, 4.604889e-004, 5.066120e-004, 5.524608e-004, - 5.979615e-004, 6.430398e-004, 6.876216e-004, 7.316325e-004, 7.749981e-004, 8.176442e-004, 8.594970e-004, - 9.004827e-004, 9.405285e-004, 9.795617e-004, 1.017511e-003, 1.054304e-003, 1.089873e-003, 1.124147e-003, - 1.157059e-003, 1.188542e-003, 1.218532e-003, 1.246964e-003, 1.273776e-003, 1.298909e-003, - 1.322303e-003, 1.343902e-003, 1.363652e-003, 1.381499e-003, 1.397394e-003, 1.411289e-003, 1.423138e-003, - 1.432898e-003, 1.440529e-003, 1.445993e-003, 1.449255e-003, 1.450283e-003, 1.449049e-003, 1.445525e-003, - 1.439688e-003, 1.431520e-003, 1.421003e-003, 1.408123e-003, 1.392871e-003, 1.375240e-003, - 1.355227e-003, 1.332831e-003, 1.308056e-003, 1.280910e-003, 1.251403e-003, 1.219550e-003, 1.185368e-003, - 1.148880e-003, 1.110110e-003, 1.069088e-003, 1.025846e-003, 9.804196e-004, 9.328497e-004, 8.831794e-004, - 8.314559e-004, 7.777298e-004, 7.220553e-004, 6.644903e-004, 6.050960e-004, 5.439371e-004, - 4.810817e-004, 4.166014e-004, 3.505707e-004, 2.830677e-004, 2.141734e-004, 1.439721e-004, 7.255096e-005, - -4.558372e-019, -7.358780e-005, -1.481168e-004, -2.234886e-004, -2.996024e-004, -3.763549e-004, - -4.536404e-004, -5.313512e-004, -6.093772e-004, -6.876064e-004, -7.659252e-004, -8.442179e-004, -9.223675e-004, - -1.000255e-003, -1.077761e-003, -1.154764e-003, -1.231143e-003, -1.306773e-003, -1.381531e-003, - -1.455292e-003, -1.527933e-003, -1.599327e-003, -1.669349e-003, -1.737873e-003, -1.804776e-003, - -1.869930e-003, -1.933213e-003, -1.994501e-003, -2.053670e-003, -2.110599e-003, -2.165166e-003, - -2.217253e-003, -2.266740e-003, -2.313511e-003, -2.357453e-003, -2.398450e-003, -2.436394e-003, -2.471175e-003, - -2.502687e-003, -2.530826e-003, -2.555492e-003, -2.576586e-003, -2.594013e-003, -2.607681e-003, - -2.617501e-003, -2.623387e-003, -2.625259e-003, -2.623037e-003, -2.616647e-003, -2.606018e-003, - -2.591084e-003, -2.571782e-003, -2.548054e-003, -2.519846e-003, -2.487109e-003, -2.449798e-003, - -2.407872e-003, -2.361297e-003, -2.310040e-003, -2.254076e-003, -2.193385e-003, -2.127950e-003, -2.057760e-003, - -1.982810e-003, -1.903098e-003, -1.818630e-003, -1.729415e-003, -1.635468e-003, -1.536809e-003, - -1.433464e-003, -1.325464e-003, -1.212845e-003, -1.095648e-003, -9.739207e-004, -8.477147e-004, - -7.170873e-004, -5.821011e-004, -4.428238e-004, -2.993284e-004, -1.516928e-004, 4.792956e-019, - 1.556619e-004, 3.152001e-004, 4.785167e-004, 6.455091e-004, 8.160703e-004, 9.900882e-004, 1.167447e-003, - 1.348025e-003, 1.531698e-003, 1.718337e-003, 1.907809e-003, 2.099975e-003, 2.294697e-003, 2.491828e-003, - 2.691221e-003, 2.892725e-003, 3.096184e-003, 3.301441e-003, 3.508336e-003, 3.716704e-003, - 3.926381e-003, 4.137196e-003, 4.348981e-003, 4.561562e-003, 4.774765e-003, 4.988412e-003, 5.202327e-003, - 5.416331e-003, 5.630242e-003, 5.843880e-003, 6.057063e-003, 6.269608e-003, 6.481332e-003, 6.692052e-003, - 6.901585e-003, 7.109748e-003, 7.316359e-003, 7.521235e-003, 7.724196e-003, 7.925063e-003, - 8.123655e-003, 8.319795e-003, 8.513309e-003, 8.704022e-003, 8.891761e-003, 9.076358e-003, 9.257644e-003, - 9.435455e-003, 9.609629e-003, 9.780007e-003, 9.946432e-003, 1.010875e-002, 1.026682e-002, 1.042048e-002, - 1.056960e-002, 1.071404e-002, 1.085367e-002, 1.098835e-002, 1.111797e-002, 1.124239e-002, - 1.136150e-002, 1.147520e-002, 1.158338e-002, 1.168592e-002, 1.178275e-002, 1.187376e-002, 1.195888e-002, - 1.203802e-002, 1.211110e-002, 1.217806e-002, 1.223884e-002, 1.229337e-002, 1.234161e-002, 1.238352e-002, - 1.241904e-002, 1.244815e-002, 1.247082e-002, 1.248702e-002, 1.249676e-002, 1.250000e-002, - 1.249676e-002, 1.248702e-002, 1.247082e-002, 1.244815e-002, 1.241904e-002, 1.238352e-002, 1.234161e-002, - 1.229337e-002, 1.223884e-002, 1.217806e-002, 1.211110e-002, 1.203802e-002, 1.195888e-002, 1.187376e-002, - 1.178275e-002, 1.168592e-002, 1.158338e-002, 1.147520e-002, 1.136150e-002, 1.124239e-002, - 1.111797e-002, 1.098835e-002, 1.085367e-002, 1.071404e-002, 1.056960e-002, 1.042048e-002, 1.026682e-002, - 1.010875e-002, 9.946432e-003, 9.780007e-003, 9.609629e-003, 9.435455e-003, 9.257644e-003, 9.076358e-003, - 8.891761e-003, 8.704022e-003, 8.513309e-003, 8.319795e-003, 8.123655e-003, 7.925063e-003, - 7.724196e-003, 7.521235e-003, 7.316359e-003, 7.109748e-003, 6.901585e-003, 6.692052e-003, 6.481332e-003, - 6.269608e-003, 6.057063e-003, 5.843880e-003, 5.630242e-003, 5.416331e-003, 5.202327e-003, 4.988412e-003, - 4.774765e-003, 4.561562e-003, 4.348981e-003, 4.137196e-003, 3.926381e-003, 3.716704e-003, - 3.508336e-003, 3.301441e-003, 3.096184e-003, 2.892725e-003, 2.691221e-003, 2.491828e-003, 2.294697e-003, - 2.099975e-003, 1.907809e-003, 1.718337e-003, 1.531698e-003, 1.348025e-003, 1.167447e-003, 9.900882e-004, - 8.160703e-004, 6.455091e-004, 4.785167e-004, 3.152001e-004, 1.556619e-004, 4.792956e-019, - -1.516928e-004, -2.993284e-004, -4.428238e-004, -5.821011e-004, -7.170873e-004, -8.477147e-004, -9.739207e-004, - -1.095648e-003, -1.212845e-003, -1.325464e-003, -1.433464e-003, -1.536809e-003, -1.635468e-003, - -1.729415e-003, -1.818630e-003, -1.903098e-003, -1.982810e-003, -2.057760e-003, -2.127950e-003, - -2.193385e-003, -2.254076e-003, -2.310040e-003, -2.361297e-003, -2.407872e-003, -2.449798e-003, - -2.487109e-003, -2.519846e-003, -2.548054e-003, -2.571782e-003, -2.591084e-003, -2.606018e-003, -2.616647e-003, - -2.623037e-003, -2.625259e-003, -2.623387e-003, -2.617501e-003, -2.607681e-003, -2.594013e-003, - -2.576586e-003, -2.555492e-003, -2.530826e-003, -2.502687e-003, -2.471175e-003, -2.436394e-003, - -2.398450e-003, -2.357453e-003, -2.313511e-003, -2.266740e-003, -2.217253e-003, -2.165166e-003, - -2.110599e-003, -2.053670e-003, -1.994501e-003, -1.933213e-003, -1.869930e-003, -1.804776e-003, -1.737873e-003, - -1.669349e-003, -1.599327e-003, -1.527933e-003, -1.455292e-003, -1.381531e-003, -1.306773e-003, - -1.231143e-003, -1.154764e-003, -1.077761e-003, -1.000255e-003, -9.223675e-004, -8.442179e-004, - -7.659252e-004, -6.876064e-004, -6.093772e-004, -5.313512e-004, -4.536404e-004, -3.763549e-004, - -2.996024e-004, -2.234886e-004, -1.481168e-004, -7.358780e-005, -4.558372e-019, 7.255096e-005, 1.439721e-004, - 2.141734e-004, 2.830677e-004, 3.505707e-004, 4.166014e-004, 4.810817e-004, 5.439371e-004, - 6.050960e-004, 6.644903e-004, 7.220553e-004, 7.777298e-004, 8.314559e-004, 8.831794e-004, 9.328497e-004, - 9.804196e-004, 1.025846e-003, 1.069088e-003, 1.110110e-003, 1.148880e-003, 1.185368e-003, 1.219550e-003, - 1.251403e-003, 1.280910e-003, 1.308056e-003, 1.332831e-003, 1.355227e-003, 1.375240e-003, - 1.392871e-003, 1.408123e-003, 1.421003e-003, 1.431520e-003, 1.439688e-003, 1.445525e-003, 1.449049e-003, - 1.450283e-003, 1.449255e-003, 1.445993e-003, 1.440529e-003, 1.432898e-003, 1.423138e-003, 1.411289e-003, - 1.397394e-003, 1.381499e-003, 1.363652e-003, 1.343902e-003, 1.322303e-003, 1.298909e-003, - 1.273776e-003, 1.246964e-003, 1.218532e-003, 1.188542e-003, 1.157059e-003, 1.124147e-003, 1.089873e-003, - 1.054304e-003, 1.017511e-003, 9.795617e-004, 9.405285e-004, 9.004827e-004, 8.594970e-004, 8.176442e-004, - 7.749981e-004, 7.316325e-004, 6.876216e-004, 6.430398e-004, 5.979615e-004, 5.524608e-004, - 5.066120e-004, 4.604889e-004, 4.141649e-004, 3.677130e-004, 3.212054e-004, 2.747138e-004, 2.283091e-004, - 1.820612e-004, 1.360391e-004, 9.031056e-005, 4.494239e-005, 4.182708e-019, -4.445249e-005, -8.835238e-005, - -1.316385e-004, -1.742512e-004, -2.161326e-004, -2.572263e-004, -2.974780e-004, -3.368349e-004, - -3.752464e-004, -4.126637e-004, -4.490401e-004, -4.843308e-004, -5.184934e-004, -5.514874e-004, - -5.832745e-004, -6.138187e-004, -6.430864e-004, -6.710460e-004, -6.976684e-004, -7.229268e-004, - -7.467967e-004, -7.692559e-004, -7.902847e-004, -8.098658e-004, -8.279841e-004, -8.446270e-004, -8.597843e-004, - -8.734481e-004, -8.856129e-004, -8.962755e-004, -9.054349e-004, -9.130927e-004, -9.192526e-004, - -9.239204e-004, -9.271043e-004, -9.288148e-004, -9.290642e-004, -9.278672e-004, -9.252403e-004, - -9.212024e-004, -9.157740e-004, -9.089776e-004, -9.008377e-004, -8.913806e-004, -8.806341e-004, - -8.686279e-004, -8.553935e-004, -8.409635e-004, -8.253725e-004, -8.086562e-004, -7.908518e-004, -7.719977e-004, - -7.521337e-004, -7.313007e-004, -7.095405e-004, -6.868961e-004, -6.634114e-004, -6.391312e-004, - -6.141008e-004, -5.883667e-004, -5.619755e-004, -5.349748e-004, -5.074123e-004, -4.793363e-004, - -4.507954e-004, -4.218384e-004, -3.925142e-004, -3.628719e-004, -3.329606e-004, -3.028293e-004, - -2.725268e-004, -2.421018e-004, -2.116026e-004, -1.810772e-004, -1.505731e-004, -1.201376e-004, -8.981695e-005, - -5.965715e-005, -2.970336e-005, -3.687801e-019, 2.940933e-005, 5.848192e-005, 8.717597e-005, - 1.154507e-004, 1.432663e-004, 1.705842e-004, 1.973669e-004, 2.235780e-004, 2.491824e-004, 2.741464e-004, - 2.984375e-004, 3.220245e-004, 3.448777e-004, 3.669689e-004, 3.882711e-004, 4.087590e-004, - 4.284087e-004, 4.471978e-004, 4.651057e-004, 4.821131e-004, 4.982023e-004, 5.133574e-004, 5.275638e-004, - 5.408088e-004, 5.530812e-004, 5.643714e-004, 5.746714e-004, 5.839749e-004, 5.922771e-004, 5.995749e-004, - 6.058668e-004, 6.111527e-004, 6.154344e-004, 6.187149e-004, 6.209989e-004, 6.222926e-004, - 6.226036e-004, 6.219411e-004, 6.203157e-004, 6.177392e-004, 6.142250e-004, 6.097877e-004, 6.044434e-004, - 5.982090e-004, 5.911032e-004, 5.831455e-004, 5.743567e-004, 5.647586e-004, 5.543740e-004, 5.432270e-004, - 5.313423e-004, 5.187458e-004, 5.054640e-004, 4.915245e-004, 4.769554e-004, 4.617857e-004, - 4.460450e-004, 4.297633e-004, 4.129716e-004, 3.957010e-004, 3.779833e-004, 3.598505e-004, 3.413350e-004, - 3.224695e-004, 3.032870e-004, 2.838207e-004, 2.641037e-004, 2.441694e-004, 2.240510e-004, 2.037820e-004, - 1.833954e-004, 1.629244e-004, 1.424018e-004, 1.218604e-004, 1.013323e-004, 8.084976e-005, - 6.044427e-005, 4.014708e-005, 1.998892e-005, 3.102067e-019, -1.979003e-005, -3.935211e-005, -5.865784e-005, - -7.767949e-005, -9.639005e-005, -1.147633e-004, -1.327736e-004, -1.503966e-004, -1.676082e-004, - -1.843857e-004, -2.007070e-004, -2.165510e-004, -2.318976e-004, -2.467277e-004, -2.610232e-004, - -2.747669e-004, -2.879428e-004, -3.005358e-004, -3.125320e-004, -3.239185e-004, -3.346836e-004, -3.448165e-004, - -3.543077e-004, -3.631487e-004, -3.713321e-004, -3.788516e-004, -3.857022e-004, -3.918798e-004, - -3.973815e-004, -4.022054e-004, -4.063507e-004, -4.098179e-004, -4.126084e-004, -4.147245e-004, - -4.161698e-004, -4.169488e-004, -4.170672e-004, -4.165313e-004, -4.153487e-004, -4.135279e-004, - -4.110782e-004, -4.080100e-004, -4.043343e-004, -4.000633e-004, -3.952096e-004, -3.897870e-004, -3.838098e-004, - -3.772933e-004, -3.702531e-004, -3.627058e-004, -3.546686e-004, -3.461591e-004, -3.371958e-004, - -3.277974e-004, -3.179833e-004, -3.077733e-004, -2.971877e-004, -2.862471e-004, -2.749724e-004, - -2.633850e-004, -2.515065e-004, -2.393587e-004, -2.269635e-004, -2.143433e-004, -2.015203e-004, - -1.885169e-004, -1.753557e-004, -1.620591e-004, -1.486496e-004, -1.351496e-004, -1.215815e-004, -1.079674e-004, - -9.432949e-005, -8.068956e-005, -6.706927e-005, -5.349000e-005, -3.997285e-005, -2.653857e-005, - -1.320761e-005, -2.458534e-019, 1.306462e-005, 2.596705e-005, 3.868856e-005, 5.121089e-005, - 6.351629e-005, 7.558758e-005, 8.740811e-005, 9.896179e-005, 1.102332e-004, 1.212074e-004, 1.318703e-004, - 1.422082e-004, 1.522083e-004, 1.618583e-004, 1.711468e-004, 1.800629e-004, 1.885964e-004, 1.967381e-004, - 2.044793e-004, 2.118120e-004, 2.187291e-004, 2.252242e-004, 2.312916e-004, 2.369264e-004, - 2.421243e-004, 2.468821e-004, 2.511970e-004, 2.550670e-004, 2.584910e-004, 2.614686e-004, 2.640000e-004, - 2.660861e-004, 2.677287e-004, 2.689302e-004, 2.696937e-004, 2.700228e-004, 2.699220e-004, 2.693965e-004, - 2.684517e-004, 2.670942e-004, 2.653307e-004, 2.631688e-004, 2.606165e-004, 2.576824e-004, - 2.543756e-004, 2.507057e-004, 2.466828e-004, 2.423175e-004, 2.376208e-004, 2.326041e-004, 2.272791e-004, - 2.216580e-004, 2.157534e-004, 2.095780e-004, 2.031450e-004, 1.964678e-004, 1.895599e-004, 1.824353e-004, - 1.751079e-004, 1.675921e-004, 1.599021e-004, 1.520524e-004, 1.440575e-004, 1.359322e-004, - 1.276910e-004, 1.193487e-004, 1.109199e-004, 1.024194e-004, 9.386158e-005, 8.526109e-005, 7.663233e-005, - 6.798957e-005, 5.934695e-005, 5.071846e-005, 4.211787e-005, 3.355876e-005, 2.505446e-005, 1.661807e-005, - 8.262406e-006, 1.792589e-019, -8.156920e-006, -1.619645e-005, -2.410702e-005, -3.187743e-005, - -3.949682e-005, -4.695473e-005, -5.424111e-005, -6.134628e-005, -6.826101e-005, -7.497650e-005, - -8.148436e-005, -8.777668e-005, -9.384598e-005, -9.968527e-005, -1.052880e-004, -1.106481e-004, -1.157601e-004, - -1.206187e-004, -1.252194e-004, -1.295582e-004, -1.336312e-004, -1.374355e-004, -1.409682e-004, - -1.442274e-004, -1.472112e-004, -1.499185e-004, -1.523486e-004, -1.545011e-004, -1.563764e-004, - -1.579750e-004, -1.592982e-004, -1.603474e-004, -1.611248e-004, -1.616327e-004, -1.618741e-004, - -1.618522e-004, -1.615706e-004, -1.610335e-004, -1.602453e-004, -1.592109e-004, -1.579353e-004, -1.564241e-004, - -1.546830e-004, -1.527183e-004, -1.505364e-004, -1.481439e-004, -1.455478e-004, -1.427555e-004, - -1.397742e-004, -1.366118e-004, -1.332761e-004, -1.297751e-004, -1.261172e-004, -1.223106e-004, - -1.183640e-004, -1.142859e-004, -1.100852e-004, -1.057707e-004, -1.013513e-004, -9.683587e-005, - -9.223356e-005, -8.755333e-005, -8.280422e-005, -7.799525e-005, -7.313542e-005, -6.823368e-005, -6.329893e-005, - -5.833999e-005, -5.336560e-005, -4.838440e-005, -4.340492e-005, -3.843555e-005, -3.348454e-005, - -2.856001e-005, -2.366988e-005, -1.882191e-005, -1.402368e-005, -9.282546e-006, -4.605674e-006, - -1.139593e-019, 4.527766e-006, 8.971155e-006, 1.332394e-005, 1.758016e-005, 2.173409e-005, 2.578031e-005, - 2.971365e-005, 3.352921e-005, 3.722240e-005, 4.078892e-005, 4.422472e-005, 4.752610e-005, - 5.068963e-005, 5.371218e-005, 5.659093e-005, 5.932335e-005, 6.190723e-005, 6.434065e-005, 6.662200e-005, - 6.874996e-005, 7.072352e-005, 7.254195e-005, 7.420483e-005, 7.571202e-005, 7.706367e-005, 7.826020e-005, - 7.930232e-005, 8.019100e-005, 8.092749e-005, 8.151327e-005, 8.195011e-005, 8.223999e-005, - 8.238515e-005, 8.238806e-005, 8.225138e-005, 8.197803e-005, 8.157111e-005, 8.103391e-005, 8.036992e-005, - 7.958279e-005, 7.867637e-005, 7.765464e-005, 7.652172e-005, 7.528190e-005, 7.393958e-005, 7.249927e-005, - 7.096558e-005, 6.934326e-005, 6.763708e-005, 6.585194e-005, 6.399278e-005, 6.206460e-005, - 6.007242e-005, 5.802133e-005, 5.591641e-005, 5.376277e-005, 5.156552e-005, 4.932976e-005, 4.706055e-005, - 4.476296e-005, 4.244200e-005, 4.010262e-005, 3.774974e-005, 3.538819e-005, 3.302276e-005, 3.065812e-005, - 2.829887e-005, 2.594950e-005, 2.361441e-005, 2.129787e-005, 1.900404e-005, 1.673695e-005, - 1.450050e-005, 1.229844e-005, 1.013439e-005, 8.011816e-006, 5.934018e-006, 3.904152e-006, 1.925206e-006, - 5.325507e-020, -1.868814e-006, -3.678757e-006, -5.427526e-006, -7.112994e-006, -8.733213e-006, - -1.028642e-005, -1.177102e-005, -1.318561e-005, -1.452897e-005, -1.580006e-005, -1.699802e-005, -1.812216e-005, - -1.917198e-005, -2.014716e-005, -2.104754e-005, -2.187315e-005, -2.262418e-005, -2.330098e-005, - -2.390407e-005, -2.443414e-005, -2.489201e-005, -2.527868e-005, -2.559526e-005, -2.584305e-005, - -2.602345e-005, -2.613799e-005, -2.618835e-005, -2.617632e-005, -2.610380e-005, -2.597280e-005, - -2.578543e-005, -2.554390e-005, -2.525050e-005, -2.490762e-005, -2.451771e-005, -2.408330e-005, -2.360696e-005, - -2.309135e-005, -2.253915e-005, -2.195308e-005, -2.133592e-005, -2.069045e-005, -2.001948e-005, - -1.932583e-005, -1.861234e-005, -1.788183e-005, -1.713711e-005, -1.638100e-005, -1.561627e-005, - -1.484568e-005, -1.407196e-005, -1.329777e-005, -1.252577e-005, -1.175852e-005, -1.099855e-005, - -1.024831e-005, -9.510213e-006, -8.786557e-006, -8.079584e-006, -7.391445e-006, -6.724205e-006, -6.079835e-006, - -5.460211e-006, -4.867107e-006, -4.302193e-006, -3.767033e-006, -3.263080e-006, -2.791676e-006, - -2.354046e-006, -1.951300e-006, -1.584427e-006, -1.254298e-006, -9.616602e-007, -7.071401e-007, - -4.912400e-007, -3.143392e-007, -1.766936e-007, -7.843559e-008, -1.957509e-008, -1.899468e-035}; - -const double coeffs_10000_to_250[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -3.798936e-035, -1.568712e-007, -6.286784e-007, - -1.414280e-006, -2.508596e-006, -3.902599e-006, -5.583351e-006, -7.534065e-006, -9.734214e-006, - -1.215967e-005, -1.478289e-005, -1.757311e-005, -2.049663e-005, -2.351703e-005, -2.659555e-005, - -2.969137e-005, -3.276200e-005, -3.576365e-005, -3.865167e-005, -4.138090e-005, -4.390616e-005, - -4.618270e-005, -4.816660e-005, -4.981524e-005, -5.108780e-005, -5.194560e-005, -5.235265e-005, -5.227598e-005, - -5.168610e-005, -5.055735e-005, -4.886828e-005, -4.660196e-005, -4.374631e-005, -4.029432e-005, - -3.624431e-005, -3.160012e-005, -2.637122e-005, -2.057283e-005, -1.422599e-005, -7.357515e-006, - 1.065101e-019, 7.808305e-006, 1.602363e-005, 2.459689e-005, 3.347391e-005, 4.259574e-005, 5.189900e-005, - 6.131624e-005, 7.077639e-005, 8.020523e-005, 8.952593e-005, 9.865952e-005, 1.075255e-004, - 1.160427e-004, 1.241292e-004, 1.317039e-004, 1.386865e-004, 1.449985e-004, 1.505638e-004, 1.553093e-004, - 1.591656e-004, 1.620678e-004, 1.639561e-004, 1.647761e-004, 1.644800e-004, 1.630265e-004, 1.603820e-004, - 1.565204e-004, 1.514240e-004, 1.450839e-004, 1.374999e-004, 1.286813e-004, 1.186467e-004, - 1.074244e-004, 9.505221e-005, 8.157783e-005, 6.705842e-005, 5.156062e-005, 3.516031e-005, 1.794231e-005, - -2.279186e-019, -1.856509e-005, -3.764383e-005, -5.712002e-005, -7.687109e-005, -9.676880e-005, - -1.166800e-004, -1.364674e-004, -1.559905e-004, -1.751067e-004, -1.936717e-004, -2.115414e-004, -2.285719e-004, - -2.446212e-004, -2.595502e-004, -2.732236e-004, -2.855109e-004, -2.962878e-004, -3.054366e-004, - -3.128481e-004, -3.184218e-004, -3.220671e-004, -3.237043e-004, -3.232654e-004, -3.206949e-004, - -3.159500e-004, -3.090022e-004, -2.998371e-004, -2.884548e-004, -2.748709e-004, -2.591163e-004, - -2.412374e-004, -2.212963e-004, -1.993705e-004, -1.755534e-004, -1.499530e-004, -1.226926e-004, -9.390946e-005, - -6.375485e-005, -3.239290e-005, 3.585178e-019, 3.323614e-005, 6.711752e-005, 1.014369e-004, - 1.359791e-004, 1.705222e-004, 2.048387e-004, 2.386974e-004, 2.718644e-004, 3.041047e-004, 3.351842e-004, - 3.648706e-004, 3.929355e-004, 4.191560e-004, 4.433160e-004, 4.652081e-004, 4.846351e-004, - 5.014114e-004, 5.153647e-004, 5.263376e-004, 5.341884e-004, 5.387929e-004, 5.400456e-004, 5.378605e-004, - 5.321722e-004, 5.229372e-004, 5.101340e-004, 4.937642e-004, 4.738527e-004, 4.504484e-004, 4.236240e-004, - 3.934762e-004, 3.601257e-004, 3.237166e-004, 2.844164e-004, 2.424148e-004, 1.979236e-004, - 1.511752e-004, 1.024218e-004, 5.193411e-005, -4.917068e-019, -5.307715e-005, -1.069800e-004, -1.613791e-004, - -2.159348e-004, -2.702992e-004, -3.241182e-004, -3.770338e-004, -4.286866e-004, -4.787173e-004, - -5.267701e-004, -5.724942e-004, -6.155467e-004, -6.555948e-004, -6.923182e-004, -7.254116e-004, - -7.545865e-004, -7.795740e-004, -8.001265e-004, -8.160200e-004, -8.270558e-004, -8.330625e-004, -8.338977e-004, - -8.294489e-004, -8.196359e-004, -8.044107e-004, -7.837597e-004, -7.577033e-004, -7.262974e-004, - -6.896331e-004, -6.478371e-004, -6.010716e-004, -5.495338e-004, -4.934555e-004, -4.331020e-004, - -3.687714e-004, -3.007931e-004, -2.295265e-004, -1.553590e-004, -7.870422e-005, 6.204133e-019, - 8.029417e-005, 1.616995e-004, 2.437207e-004, 3.258488e-004, 4.075639e-004, 4.883387e-004, 5.676414e-004, - 6.449390e-004, 7.197009e-004, 7.914021e-004, 8.595267e-004, 9.235714e-004, 9.830490e-004, 1.037492e-003, - 1.086454e-003, 1.129517e-003, 1.166291e-003, 1.196418e-003, 1.219575e-003, 1.235478e-003, - 1.243882e-003, 1.244585e-003, 1.237430e-003, 1.222305e-003, 1.199150e-003, 1.167950e-003, 1.128743e-003, - 1.081618e-003, 1.026715e-003, 9.642262e-004, 8.943957e-004, 8.175179e-004, 7.339378e-004, 6.440490e-004, - 5.482928e-004, 4.471559e-004, 3.411684e-004, 2.309014e-004, 1.169638e-004, -7.375602e-019, - -1.193143e-004, -2.402751e-004, -3.621543e-004, -4.842036e-004, -6.056587e-004, -7.257439e-004, -8.436767e-004, - -9.586726e-004, -1.069950e-003, -1.176733e-003, -1.278262e-003, -1.373792e-003, -1.462601e-003, - -1.543995e-003, -1.617312e-003, -1.681927e-003, -1.737256e-003, -1.782761e-003, -1.817955e-003, - -1.842405e-003, -1.855734e-003, -1.857630e-003, -1.847841e-003, -1.826185e-003, -1.792551e-003, - -1.746896e-003, -1.689254e-003, -1.619732e-003, -1.538512e-003, -1.445854e-003, -1.342092e-003, -1.227637e-003, - -1.102975e-003, -9.686617e-004, -8.253274e-004, -6.736698e-004, -5.144526e-004, -3.485024e-004, - -1.767048e-004, 8.365415e-019, 1.806211e-004, 3.641224e-004, 5.494276e-004, 7.354259e-004, - 9.209779e-004, 1.104922e-003, 1.286080e-003, 1.463265e-003, 1.635288e-003, 1.800965e-003, 1.959123e-003, - 2.108609e-003, 2.248293e-003, 2.377084e-003, 2.493928e-003, 2.597818e-003, 2.687805e-003, 2.762999e-003, - 2.822578e-003, 2.865796e-003, 2.891986e-003, 2.900567e-003, 2.891049e-003, 2.863040e-003, - 2.816247e-003, 2.750480e-003, 2.665661e-003, 2.561820e-003, 2.439100e-003, 2.297760e-003, 2.138176e-003, - 1.960839e-003, 1.766359e-003, 1.555460e-003, 1.328981e-003, 1.087874e-003, 8.332027e-004, 5.661354e-004, - 2.879443e-004, -9.116745e-019, -2.962335e-004, -5.992048e-004, -9.072809e-004, -1.218754e-003, - -1.531850e-003, -1.844735e-003, -2.155523e-003, -2.462285e-003, -2.763062e-003, -3.055866e-003, - -3.338697e-003, -3.609551e-003, -3.866427e-003, -4.107340e-003, -4.330332e-003, -4.533480e-003, -4.714905e-003, - -4.872788e-003, -5.005374e-003, -5.110984e-003, -5.188026e-003, -5.235002e-003, -5.250518e-003, - -5.233293e-003, -5.182167e-003, -5.096108e-003, -4.974219e-003, -4.815745e-003, -4.620080e-003, - -4.386770e-003, -4.115521e-003, -3.806197e-003, -3.458830e-003, -3.073618e-003, -2.650928e-003, - -2.191296e-003, -1.695429e-003, -1.164202e-003, -5.986568e-004, 9.585913e-019, 6.304002e-004, 1.291018e-003, - 1.980176e-003, 2.696050e-003, 3.436674e-003, 4.199951e-003, 4.983656e-003, 5.785449e-003, - 6.602882e-003, 7.433408e-003, 8.274393e-003, 9.123124e-003, 9.976825e-003, 1.083266e-002, 1.168776e-002, - 1.253922e-002, 1.338410e-002, 1.421950e-002, 1.504247e-002, 1.585013e-002, 1.663959e-002, 1.740804e-002, - 1.815272e-002, 1.887091e-002, 1.956001e-002, 2.021750e-002, 2.084096e-002, 2.142809e-002, - 2.197670e-002, 2.248478e-002, 2.295040e-002, 2.337185e-002, 2.374753e-002, 2.407603e-002, 2.435612e-002, - 2.458675e-002, 2.476703e-002, 2.489629e-002, 2.497405e-002, 2.500000e-002, 2.497405e-002, 2.489629e-002, - 2.476703e-002, 2.458675e-002, 2.435612e-002, 2.407603e-002, 2.374753e-002, 2.337185e-002, - 2.295040e-002, 2.248478e-002, 2.197670e-002, 2.142809e-002, 2.084096e-002, 2.021750e-002, 1.956001e-002, - 1.887091e-002, 1.815272e-002, 1.740804e-002, 1.663959e-002, 1.585013e-002, 1.504247e-002, 1.421950e-002, - 1.338410e-002, 1.253922e-002, 1.168776e-002, 1.083266e-002, 9.976825e-003, 9.123124e-003, - 8.274393e-003, 7.433408e-003, 6.602882e-003, 5.785449e-003, 4.983656e-003, 4.199951e-003, 3.436674e-003, - 2.696050e-003, 1.980176e-003, 1.291018e-003, 6.304002e-004, 9.585913e-019, -5.986568e-004, -1.164202e-003, - -1.695429e-003, -2.191296e-003, -2.650928e-003, -3.073618e-003, -3.458830e-003, -3.806197e-003, - -4.115521e-003, -4.386770e-003, -4.620080e-003, -4.815745e-003, -4.974219e-003, -5.096108e-003, - -5.182167e-003, -5.233293e-003, -5.250518e-003, -5.235002e-003, -5.188026e-003, -5.110984e-003, - -5.005374e-003, -4.872788e-003, -4.714905e-003, -4.533480e-003, -4.330332e-003, -4.107340e-003, -3.866427e-003, - -3.609551e-003, -3.338697e-003, -3.055866e-003, -2.763062e-003, -2.462285e-003, -2.155523e-003, - -1.844735e-003, -1.531850e-003, -1.218754e-003, -9.072809e-004, -5.992048e-004, -2.962335e-004, - -9.116745e-019, 2.879443e-004, 5.661354e-004, 8.332027e-004, 1.087874e-003, 1.328981e-003, 1.555460e-003, - 1.766359e-003, 1.960839e-003, 2.138176e-003, 2.297760e-003, 2.439100e-003, 2.561820e-003, - 2.665661e-003, 2.750480e-003, 2.816247e-003, 2.863040e-003, 2.891049e-003, 2.900567e-003, 2.891986e-003, - 2.865796e-003, 2.822578e-003, 2.762999e-003, 2.687805e-003, 2.597818e-003, 2.493928e-003, 2.377084e-003, - 2.248293e-003, 2.108609e-003, 1.959123e-003, 1.800965e-003, 1.635288e-003, 1.463265e-003, - 1.286080e-003, 1.104922e-003, 9.209779e-004, 7.354259e-004, 5.494276e-004, 3.641224e-004, 1.806211e-004, - 8.365415e-019, -1.767048e-004, -3.485024e-004, -5.144526e-004, -6.736698e-004, -8.253274e-004, - -9.686617e-004, -1.102975e-003, -1.227637e-003, -1.342092e-003, -1.445854e-003, -1.538512e-003, -1.619732e-003, - -1.689254e-003, -1.746896e-003, -1.792551e-003, -1.826185e-003, -1.847841e-003, -1.857630e-003, - -1.855734e-003, -1.842405e-003, -1.817955e-003, -1.782761e-003, -1.737256e-003, -1.681927e-003, - -1.617312e-003, -1.543995e-003, -1.462601e-003, -1.373792e-003, -1.278262e-003, -1.176733e-003, - -1.069950e-003, -9.586726e-004, -8.436767e-004, -7.257439e-004, -6.056587e-004, -4.842036e-004, -3.621543e-004, - -2.402751e-004, -1.193143e-004, -7.375602e-019, 1.169638e-004, 2.309014e-004, 3.411684e-004, - 4.471559e-004, 5.482928e-004, 6.440490e-004, 7.339378e-004, 8.175179e-004, 8.943957e-004, 9.642262e-004, - 1.026715e-003, 1.081618e-003, 1.128743e-003, 1.167950e-003, 1.199150e-003, 1.222305e-003, - 1.237430e-003, 1.244585e-003, 1.243882e-003, 1.235478e-003, 1.219575e-003, 1.196418e-003, 1.166291e-003, - 1.129517e-003, 1.086454e-003, 1.037492e-003, 9.830490e-004, 9.235714e-004, 8.595267e-004, 7.914021e-004, - 7.197009e-004, 6.449390e-004, 5.676414e-004, 4.883387e-004, 4.075639e-004, 3.258488e-004, - 2.437207e-004, 1.616995e-004, 8.029417e-005, 6.204133e-019, -7.870422e-005, -1.553590e-004, -2.295265e-004, - -3.007931e-004, -3.687714e-004, -4.331020e-004, -4.934555e-004, -5.495338e-004, -6.010716e-004, - -6.478371e-004, -6.896331e-004, -7.262974e-004, -7.577033e-004, -7.837597e-004, -8.044107e-004, - -8.196359e-004, -8.294489e-004, -8.338977e-004, -8.330625e-004, -8.270558e-004, -8.160200e-004, -8.001265e-004, - -7.795740e-004, -7.545865e-004, -7.254116e-004, -6.923182e-004, -6.555948e-004, -6.155467e-004, - -5.724942e-004, -5.267701e-004, -4.787173e-004, -4.286866e-004, -3.770338e-004, -3.241182e-004, - -2.702992e-004, -2.159348e-004, -1.613791e-004, -1.069800e-004, -5.307715e-005, -4.917068e-019, - 5.193411e-005, 1.024218e-004, 1.511752e-004, 1.979236e-004, 2.424148e-004, 2.844164e-004, 3.237166e-004, - 3.601257e-004, 3.934762e-004, 4.236240e-004, 4.504484e-004, 4.738527e-004, 4.937642e-004, 5.101340e-004, - 5.229372e-004, 5.321722e-004, 5.378605e-004, 5.400456e-004, 5.387929e-004, 5.341884e-004, - 5.263376e-004, 5.153647e-004, 5.014114e-004, 4.846351e-004, 4.652081e-004, 4.433160e-004, 4.191560e-004, - 3.929355e-004, 3.648706e-004, 3.351842e-004, 3.041047e-004, 2.718644e-004, 2.386974e-004, 2.048387e-004, - 1.705222e-004, 1.359791e-004, 1.014369e-004, 6.711752e-005, 3.323614e-005, 3.585178e-019, - -3.239290e-005, -6.375485e-005, -9.390946e-005, -1.226926e-004, -1.499530e-004, -1.755534e-004, -1.993705e-004, - -2.212963e-004, -2.412374e-004, -2.591163e-004, -2.748709e-004, -2.884548e-004, -2.998371e-004, - -3.090022e-004, -3.159500e-004, -3.206949e-004, -3.232654e-004, -3.237043e-004, -3.220671e-004, - -3.184218e-004, -3.128481e-004, -3.054366e-004, -2.962878e-004, -2.855109e-004, -2.732236e-004, - -2.595502e-004, -2.446212e-004, -2.285719e-004, -2.115414e-004, -1.936717e-004, -1.751067e-004, -1.559905e-004, - -1.364674e-004, -1.166800e-004, -9.676880e-005, -7.687109e-005, -5.712002e-005, -3.764383e-005, - -1.856509e-005, -2.279186e-019, 1.794231e-005, 3.516031e-005, 5.156062e-005, 6.705842e-005, - 8.157783e-005, 9.505221e-005, 1.074244e-004, 1.186467e-004, 1.286813e-004, 1.374999e-004, 1.450839e-004, - 1.514240e-004, 1.565204e-004, 1.603820e-004, 1.630265e-004, 1.644800e-004, 1.647761e-004, 1.639561e-004, - 1.620678e-004, 1.591656e-004, 1.553093e-004, 1.505638e-004, 1.449985e-004, 1.386865e-004, - 1.317039e-004, 1.241292e-004, 1.160427e-004, 1.075255e-004, 9.865952e-005, 8.952593e-005, 8.020523e-005, - 7.077639e-005, 6.131624e-005, 5.189900e-005, 4.259574e-005, 3.347391e-005, 2.459689e-005, 1.602363e-005, - 7.808305e-006, 1.065101e-019, -7.357515e-006, -1.422599e-005, -2.057283e-005, -2.637122e-005, - -3.160012e-005, -3.624431e-005, -4.029432e-005, -4.374631e-005, -4.660196e-005, -4.886828e-005, - -5.055735e-005, -5.168610e-005, -5.227598e-005, -5.235265e-005, -5.194560e-005, -5.108780e-005, -4.981524e-005, - -4.816660e-005, -4.618270e-005, -4.390616e-005, -4.138090e-005, -3.865167e-005, -3.576365e-005, - -3.276200e-005, -2.969137e-005, -2.659555e-005, -2.351703e-005, -2.049663e-005, -1.757311e-005, - -1.478289e-005, -1.215967e-005, -9.734214e-006, -7.534065e-006, -5.583351e-006, -3.902599e-006, - -2.508596e-006, -1.414280e-006, -6.286784e-007, -1.568712e-007, -3.798936e-035}; - -const double coeffs_10000_to_500[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - -7.597872e-035, -1.257357e-006, -5.017191e-006, -1.116670e-005, -1.946843e-005, -2.956578e-005, - -4.099326e-005, -5.319110e-005, -6.552399e-005, -7.730334e-005, -8.781233e-005, -9.633319e-005, -1.021756e-004, - -1.047053e-004, -1.033722e-004, -9.773656e-005, -8.749262e-005, -7.248863e-005, -5.274244e-005, - -2.845198e-005, 2.130203e-019, 3.204726e-005, 6.694782e-005, 1.037980e-004, 1.415528e-004, - 1.790519e-004, 2.150511e-004, 2.482584e-004, 2.773730e-004, 3.011276e-004, 3.183312e-004, 3.279121e-004, - 3.289600e-004, 3.207640e-004, 3.028481e-004, 2.749999e-004, 2.372934e-004, 1.901044e-004, 1.341168e-004, - 7.032063e-005, -4.558372e-019, -7.528765e-005, -1.537422e-004, -2.333599e-004, -3.119810e-004, - -3.873435e-004, -4.571437e-004, -5.191005e-004, -5.710219e-004, -6.108732e-004, -6.368435e-004, - -6.474086e-004, -6.413897e-004, -6.180045e-004, -5.769096e-004, -5.182326e-004, -4.425925e-004, -3.511067e-004, - -2.453851e-004, -1.275097e-004, 7.170356e-019, 1.342350e-004, 2.719583e-004, 4.096775e-004, - 5.437287e-004, 6.703684e-004, 7.858711e-004, 8.866321e-004, 9.692701e-004, 1.030729e-003, 1.068377e-003, - 1.080091e-003, 1.064344e-003, 1.020268e-003, 9.477055e-004, 8.472480e-004, 7.202514e-004, - 5.688328e-004, 3.958472e-004, 2.048435e-004, -9.834135e-019, -2.139600e-004, -4.318696e-004, -6.482363e-004, - -8.573731e-004, -1.053540e-003, -1.231093e-003, -1.384636e-003, -1.509173e-003, -1.600253e-003, - -1.654112e-003, -1.667795e-003, -1.639272e-003, -1.567519e-003, -1.452595e-003, -1.295674e-003, - -1.099068e-003, -8.662040e-004, -6.015863e-004, -3.107180e-004, 1.240827e-018, 3.233990e-004, 6.516976e-004, - 9.766774e-004, 1.289878e-003, 1.582804e-003, 1.847143e-003, 2.074983e-003, 2.259034e-003, - 2.392836e-003, 2.470957e-003, 2.489170e-003, 2.444611e-003, 2.335899e-003, 2.163235e-003, 1.928452e-003, - 1.635036e-003, 1.288098e-003, 8.943118e-004, 4.618027e-004, -1.475120e-018, -4.805503e-004, -9.684072e-004, - -1.451488e-003, -1.917345e-003, -2.353467e-003, -2.747584e-003, -3.087991e-003, -3.363854e-003, - -3.565522e-003, -3.684810e-003, -3.715259e-003, -3.652371e-003, -3.493793e-003, -3.239463e-003, - -2.891707e-003, -2.455275e-003, -1.937323e-003, -1.347340e-003, -6.970049e-004, 1.673083e-018, - 7.282449e-004, 1.470852e-003, 2.209843e-003, 2.926530e-003, 3.601931e-003, 4.217217e-003, 4.754168e-003, - 5.195636e-003, 5.525997e-003, 5.731592e-003, 5.801133e-003, 5.726080e-003, 5.500961e-003, 5.123639e-003, - 4.595520e-003, 3.921678e-003, 3.110919e-003, 2.175748e-003, 1.132271e-003, -1.823349e-018, - -1.198410e-003, -2.437509e-003, -3.689470e-003, -4.924571e-003, -6.111731e-003, -7.219102e-003, -8.214681e-003, - -9.066959e-003, -9.745576e-003, -1.022197e-002, -1.047000e-002, -1.046659e-002, -1.019222e-002, - -9.631490e-003, -8.773541e-003, -7.612394e-003, -6.147236e-003, -4.382592e-003, -2.328404e-003, - 1.917183e-018, 2.582037e-003, 5.392100e-003, 8.399902e-003, 1.157090e-002, 1.486682e-002, 1.824625e-002, - 2.166532e-002, 2.507843e-002, 2.843899e-002, 3.170025e-002, 3.481609e-002, 3.774182e-002, - 4.043500e-002, 4.285617e-002, 4.496955e-002, 4.674369e-002, 4.815206e-002, 4.917349e-002, 4.979259e-002, - 5.000000e-002, 4.979259e-002, 4.917349e-002, 4.815206e-002, 4.674369e-002, 4.496955e-002, 4.285617e-002, - 4.043500e-002, 3.774182e-002, 3.481609e-002, 3.170025e-002, 2.843899e-002, 2.507843e-002, - 2.166532e-002, 1.824625e-002, 1.486682e-002, 1.157090e-002, 8.399902e-003, 5.392100e-003, 2.582037e-003, - 1.917183e-018, -2.328404e-003, -4.382592e-003, -6.147236e-003, -7.612394e-003, -8.773541e-003, - -9.631490e-003, -1.019222e-002, -1.046659e-002, -1.047000e-002, -1.022197e-002, -9.745576e-003, -9.066959e-003, - -8.214681e-003, -7.219102e-003, -6.111731e-003, -4.924571e-003, -3.689470e-003, -2.437509e-003, - -1.198410e-003, -1.823349e-018, 1.132271e-003, 2.175748e-003, 3.110919e-003, 3.921678e-003, - 4.595520e-003, 5.123639e-003, 5.500961e-003, 5.726080e-003, 5.801133e-003, 5.731592e-003, 5.525997e-003, - 5.195636e-003, 4.754168e-003, 4.217217e-003, 3.601931e-003, 2.926530e-003, 2.209843e-003, 1.470852e-003, - 7.282449e-004, 1.673083e-018, -6.970049e-004, -1.347340e-003, -1.937323e-003, -2.455275e-003, - -2.891707e-003, -3.239463e-003, -3.493793e-003, -3.652371e-003, -3.715259e-003, -3.684810e-003, - -3.565522e-003, -3.363854e-003, -3.087991e-003, -2.747584e-003, -2.353467e-003, -1.917345e-003, -1.451488e-003, - -9.684072e-004, -4.805503e-004, -1.475120e-018, 4.618027e-004, 8.943118e-004, 1.288098e-003, - 1.635036e-003, 1.928452e-003, 2.163235e-003, 2.335899e-003, 2.444611e-003, 2.489170e-003, 2.470957e-003, - 2.392836e-003, 2.259034e-003, 2.074983e-003, 1.847143e-003, 1.582804e-003, 1.289878e-003, - 9.766774e-004, 6.516976e-004, 3.233990e-004, 1.240827e-018, -3.107180e-004, -6.015863e-004, -8.662040e-004, - -1.099068e-003, -1.295674e-003, -1.452595e-003, -1.567519e-003, -1.639272e-003, -1.667795e-003, - -1.654112e-003, -1.600253e-003, -1.509173e-003, -1.384636e-003, -1.231093e-003, -1.053540e-003, - -8.573731e-004, -6.482363e-004, -4.318696e-004, -2.139600e-004, -9.834135e-019, 2.048435e-004, 3.958472e-004, - 5.688328e-004, 7.202514e-004, 8.472480e-004, 9.477055e-004, 1.020268e-003, 1.064344e-003, - 1.080091e-003, 1.068377e-003, 1.030729e-003, 9.692701e-004, 8.866321e-004, 7.858711e-004, 6.703684e-004, - 5.437287e-004, 4.096775e-004, 2.719583e-004, 1.342350e-004, 7.170356e-019, -1.275097e-004, -2.453851e-004, - -3.511067e-004, -4.425925e-004, -5.182326e-004, -5.769096e-004, -6.180045e-004, -6.413897e-004, - -6.474086e-004, -6.368435e-004, -6.108732e-004, -5.710219e-004, -5.191005e-004, -4.571437e-004, - -3.873435e-004, -3.119810e-004, -2.333599e-004, -1.537422e-004, -7.528765e-005, -4.558372e-019, - 7.032063e-005, 1.341168e-004, 1.901044e-004, 2.372934e-004, 2.749999e-004, 3.028481e-004, 3.207640e-004, - 3.289600e-004, 3.279121e-004, 3.183312e-004, 3.011276e-004, 2.773730e-004, 2.482584e-004, 2.150511e-004, - 1.790519e-004, 1.415528e-004, 1.037980e-004, 6.694782e-005, 3.204726e-005, 2.130203e-019, - -2.845198e-005, -5.274244e-005, -7.248863e-005, -8.749262e-005, -9.773656e-005, -1.033722e-004, -1.047053e-004, - -1.021756e-004, -9.633319e-005, -8.781233e-005, -7.730334e-005, -6.552399e-005, -5.319110e-005, - -4.099326e-005, -2.956578e-005, -1.946843e-005, -1.116670e-005, -5.017191e-006, -1.257357e-006, - -7.597872e-035}; - -const double coeffs_10000_to_1000[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1.519574e-034, -1.003438e-005, - -3.893685e-005, -8.198651e-005, -1.310480e-004, -1.756247e-004, -2.043512e-004, -2.067444e-004, - -1.749852e-004, -1.054849e-004, 4.260406e-019, 1.338956e-004, 2.831055e-004, 4.301022e-004, 5.547460e-004, - 6.366624e-004, 6.579199e-004, 6.056962e-004, 4.745868e-004, 2.682337e-004, -9.116745e-019, - -3.074844e-004, -6.239620e-004, -9.142875e-004, -1.142044e-003, -1.273687e-003, -1.282779e-003, -1.153819e-003, - -8.851850e-004, -4.907702e-004, 1.434071e-018, 5.439165e-004, 1.087457e-003, 1.571742e-003, - 1.938540e-003, 2.136754e-003, 2.128689e-003, 1.895411e-003, 1.440503e-003, 7.916943e-004, -1.966827e-018, - -8.637393e-004, -1.714746e-003, -2.462187e-003, -3.018346e-003, -3.308223e-003, -3.278543e-003, - -2.905189e-003, -2.198135e-003, -1.203173e-003, 2.481653e-018, 1.303395e-003, 2.579756e-003, - 3.694286e-003, 4.518068e-003, 4.941914e-003, 4.889222e-003, 4.326471e-003, 3.270072e-003, 1.788624e-003, - -2.950241e-018, -1.936814e-003, -3.834690e-003, -5.495169e-003, -6.727708e-003, -7.369619e-003, - -7.304742e-003, -6.478926e-003, -4.910550e-003, -2.694679e-003, 3.346166e-018, 2.941704e-003, 5.853060e-003, - 8.434434e-003, 1.039127e-002, 1.146318e-002, 1.145216e-002, 1.024728e-002, 7.843357e-003, - 4.351497e-003, -3.646698e-018, -4.875017e-003, -9.849141e-003, -1.443820e-002, -1.813392e-002, -2.044394e-002, - -2.093317e-002, -1.926298e-002, -1.522479e-002, -8.765185e-003, 3.834365e-018, 1.078420e-002, - 2.314180e-002, 3.649250e-002, 5.015686e-002, 6.340050e-002, 7.548364e-002, 8.571235e-002, 9.348739e-002, - 9.834699e-002, 1.000000e-001, 9.834699e-002, 9.348739e-002, 8.571235e-002, 7.548364e-002, - 6.340050e-002, 5.015686e-002, 3.649250e-002, 2.314180e-002, 1.078420e-002, 3.834365e-018, -8.765185e-003, - -1.522479e-002, -1.926298e-002, -2.093317e-002, -2.044394e-002, -1.813392e-002, -1.443820e-002, - -9.849141e-003, -4.875017e-003, -3.646698e-018, 4.351497e-003, 7.843357e-003, 1.024728e-002, 1.145216e-002, - 1.146318e-002, 1.039127e-002, 8.434434e-003, 5.853060e-003, 2.941704e-003, 3.346166e-018, - -2.694679e-003, -4.910550e-003, -6.478926e-003, -7.304742e-003, -7.369619e-003, -6.727708e-003, -5.495169e-003, - -3.834690e-003, -1.936814e-003, -2.950241e-018, 1.788624e-003, 3.270072e-003, 4.326471e-003, - 4.889222e-003, 4.941914e-003, 4.518068e-003, 3.694286e-003, 2.579756e-003, 1.303395e-003, 2.481653e-018, - -1.203173e-003, -2.198135e-003, -2.905189e-003, -3.278543e-003, -3.308223e-003, -3.018346e-003, - -2.462187e-003, -1.714746e-003, -8.637393e-004, -1.966827e-018, 7.916943e-004, 1.440503e-003, - 1.895411e-003, 2.128689e-003, 2.136754e-003, 1.938540e-003, 1.571742e-003, 1.087457e-003, 5.439165e-004, - 1.434071e-018, -4.907702e-004, -8.851850e-004, -1.153819e-003, -1.282779e-003, -1.273687e-003, - -1.142044e-003, -9.142875e-004, -6.239620e-004, -3.074844e-004, -9.116745e-019, 2.682337e-004, 4.745868e-004, - 6.056962e-004, 6.579199e-004, 6.366624e-004, 5.547460e-004, 4.301022e-004, 2.831055e-004, - 1.338956e-004, 4.260406e-019, -1.054849e-004, -1.749852e-004, -2.067444e-004, -2.043512e-004, -1.756247e-004, - -1.310480e-004, -8.198651e-005, -3.893685e-005, -1.003438e-005, -1.519574e-034}; void Transpose(const vector > &in, vector > &out); @@ -505,8 +35,13 @@ MainWindow::MainWindow(QWidget *parent, const std::string &config_file): QMainWi // make GUI connections QObject::connect(ui->actionQuit, SIGNAL(triggered()), this, SLOT(close())); QObject::connect(ui->linkButton, SIGNAL(clicked()), this, SLOT(link())); + QObject::connect(ui->channelCount, SIGNAL(valueChanged(int)),this, SLOT(update_channel_labels(int))); + QObject::connect(ui->samplingRate, SIGNAL(currentIndexChanged(int)),this, SLOT(update_channels_for_sr(int))); QObject::connect(ui->actionLoad_Configuration, SIGNAL(triggered()), this, SLOT(load_config_dialog())); QObject::connect(ui->actionSave_Configuration, SIGNAL(triggered()), this, SLOT(save_config_dialog())); + + + } @@ -522,6 +57,43 @@ void MainWindow::save_config_dialog() { save_config(sel.toStdString()); } +void MainWindow::update_channel_counter() { + int i=0; + std::vector channelLabels; + boost::algorithm::split(channelLabels,ui->channelLabels->toPlainText().toStdString(),boost::algorithm::is_any_of("\n")); + BOOST_FOREACH(std::string &v, channelLabels)++i; + //std::cout << i << std::endl; + ui->channelCount->setValue(i); + //ui->channelCount->setMaximum(max_channels); + int channelCount = ui->channelCount->value(); +} + + + +void MainWindow::update_channel_labels(int n) { + + std::string str; + ui->channelLabels->clear(); + long long i; + for(i=1;i<=n;i++) { + str = std::to_string(i); + ui->channelLabels->appendPlainText(str.c_str()); + //ui->channelLabels->appendPlainText("\n"); + } +} + +void MainWindow::update_channels_for_sr(int n) { + + const double sr = sampling_rates[n]; + if(sr>2000 && ui->channelCount->value()>4){ + ui->channelCount->setValue(4); + update_channel_labels(4); + ui->useAUX->setEnabled(false); + ui->useAUX->setChecked(false); + } + if(sr<=2000)ui->useAUX->setEnabled(true); +} + void MainWindow::closeEvent(QCloseEvent *ev) { if (reader_thread_) ev->ignore(); @@ -542,13 +114,21 @@ void MainWindow::load_config(const std::string &filename) { // get config values try { ui->deviceNumber->setValue(pt.get("settings.devicenumber",0)); - ui->channelCount->setValue(pt.get("settings.channelcount",32)); - ui->chunkSize->setValue(pt.get("settings.chunksize",10)); + ui->channelCount->setValue(pt.get("settings.channelcount",16)); ui->samplingRate->setCurrentIndex(pt.get("settings.samplingrate",2)); ui->useAUX->setCheckState(pt.get("settings.useaux",false) ? Qt::Checked : Qt::Unchecked); - ui->channelLabels->clear(); - BOOST_FOREACH(ptree::value_type &v, pt.get_child("channels.labels")) + ui->unsampledMarkers->setCheckState(pt.get("settings.unsampledmarkers",false) ? Qt::Checked : Qt::Unchecked); + ui->sampledMarkers->setCheckState(pt.get("settings.sampledmarkers",true) ? Qt::Checked : Qt::Unchecked); + ui->sampledMarkersEEG->setCheckState(pt.get("settings.sampledmarkersEEG",false) ? Qt::Checked : Qt::Unchecked); + + //std::cout<channelLabels->toPlainText().toStdString()<channelLabels->clear(); + int i=0; + BOOST_FOREACH(ptree::value_type &v, pt.get_child("channels.labels")) { + ++i; ui->channelLabels->appendPlainText(v.second.data().c_str()); + } + } catch(std::exception &) { QMessageBox::information(this,"Error in Config File","Could not read out config parameters.",QMessageBox::Ok); return; @@ -563,13 +143,18 @@ void MainWindow::save_config(const std::string &filename) { try { pt.put("settings.devicenumber",ui->deviceNumber->value()); pt.put("settings.channelcount",ui->channelCount->value()); - pt.put("settings.chunksize",ui->chunkSize->value()); pt.put("settings.samplingrate",ui->samplingRate->currentIndex()); pt.put("settings.useaux",ui->useAUX->checkState()==Qt::Checked); + pt.put("settings.unsampledmarkers",ui->unsampledMarkers->checkState()==Qt::Checked); + pt.put("settings.sampledmarkers",ui->sampledMarkers->checkState()==Qt::Checked); + pt.put("settings.sampledmarkersEEG",ui->sampledMarkersEEG->checkState()==Qt::Checked); + + std::vector channelLabels; boost::algorithm::split(channelLabels,ui->channelLabels->toPlainText().toStdString(),boost::algorithm::is_any_of("\n")); BOOST_FOREACH(std::string &v, channelLabels) pt.add("channels.labels.label", v); + update_channel_counter(); } catch(std::exception &e) { QMessageBox::critical(this,"Error",(std::string("Could not prepare settings for saving: ")+=e.what()).c_str(),QMessageBox::Ok); } @@ -583,17 +168,18 @@ void MainWindow::save_config(const std::string &filename) { } -// start/stop the VAmp connection +// start/stop the ActiChamp connection void MainWindow::link() { CDevice *l_pDevice = NULL; // Instance of device if (reader_thread_) { - // === perform unlink action === + //// === perform unlink action === try { stop_ = true; reader_thread_->join(); reader_thread_.reset(); + int res = SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); } catch(std::exception &e) { QMessageBox::critical(this,"Error",(std::string("Could not stop the background processing: ")+=e.what()).c_str(),QMessageBox::Ok); return; @@ -602,23 +188,26 @@ void MainWindow::link() { // indicate that we are now successfully unlinked ui->linkButton->setText("Link"); } else { - // === perform link action === - - HANDLE hDevice = NULL; + HANDLE hDevice = NULL; try { // get the UI parameters... int deviceNumber = ui->deviceNumber->value(); - int channelCount = ui->channelCount->value(); - int chunkSize = ui->chunkSize->value(); int samplingRate = sampling_rates[ui->samplingRate->currentIndex()]; + //std::cout << samplingRate << std::endl; bool useAUX = ui->useAUX->checkState()==Qt::Checked; - std::vector channelLabels; - boost::algorithm::split(channelLabels,ui->channelLabels->toPlainText().toStdString(),boost::algorithm::is_any_of("\n")); - + bool unsampledMarkers = ui->unsampledMarkers->checkState()==Qt::Checked; + bool sampledMarkers = ui->sampledMarkers->checkState()==Qt::Checked; + bool sampledMarkersEEG = ui->sampledMarkersEEG->checkState()==Qt::Checked; + update_channel_counter(); - + int i=0; + std::vector channelLabels; + boost::algorithm::split(channelLabels,ui->channelLabels->toPlainText().toStdString(),boost::algorithm::is_any_of("\n")); + BOOST_FOREACH(std::string &v, channelLabels)++i; + //std::cout << i << std::endl; + int channelCount = ui->channelCount->value(); // try to connect with VAmp device l_pDevice = CDevice::GetInstance(); @@ -639,7 +228,6 @@ void MainWindow::link() { nDataMode = dm20kHz4Channels; /* fast - 20 kHz, 4 Channels, no auxiliary */ } - // check settings: if(nDataMode == dmNormal) { @@ -667,11 +255,14 @@ void MainWindow::link() { throw std::runtime_error("The number of channels labels does not match the channel count device setting."); } - int channelsToSendLSL = channelCount; + //int channelsToSendLSL = channelCount; if(useAUX && nDataMode == dmNormal) - channelsToSendLSL += 2; // add two AUX channels + channelCount += 2; // add two AUX channels + + if(sampledMarkersEEG) + channelCount += 1; - if(channelsToSendLSL == 0) + if(channelCount == 0) QMessageBox::information(this,"Information", std::string("There is no analog channel selected ! \n Only triggers will be recorded. ").c_str(), QMessageBox::Ok); /* @@ -688,7 +279,7 @@ void MainWindow::link() { // start reader thread stop_ = false; - reader_thread_.reset(new boost::thread(&MainWindow::read_thread,this,deviceNumber,channelsToSendLSL,chunkSize,samplingRate,useAUX,channelLabels)); + reader_thread_.reset(new boost::thread(&MainWindow::read_thread,this,deviceNumber,channelCount,samplingRate,useAUX,unsampledMarkers,sampledMarkers,sampledMarkersEEG,channelLabels)); } catch(std::exception &e) { @@ -712,9 +303,19 @@ void MainWindow::link() { bool _resample = false; // background data reader thread -void MainWindow::read_thread(int deviceNumber, int channelsToSendLSL, int chunkSize, int samplingRate, bool useAUX, std::vector channelLabels) { +void MainWindow::read_thread(int deviceNumber, int channelCount, int samplingRate, bool useAUX, bool unsampledMarkers, bool sampledMarkers, bool sampledMarkersEEG, std::vector channelLabels) { HANDLE hDevice = NULL; CDevice *l_pDevice = NULL; // Instance of device + const int chunkSize = 20; // Only available chunck size for VAmp + + + int res = SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); + std::string threadId = boost::lexical_cast(boost::this_thread::get_id()); + unsigned long threadNumber = 0; + sscanf(threadId.c_str(), "%lx", &threadNumber); + std::cout << res << ":" << threadNumber << std::endl; + + bool started = false; try { // try to open the device again (we're doing everything in the same thread to not confuse the driver) @@ -766,17 +367,24 @@ void MainWindow::read_thread(int deviceNumber, int channelsToSendLSL, int chunkS started = true; - std::vector > send_buffer(chunkSize,std::vector(channelsToSendLSL)); + std::vector> send_buffer(chunkSize,std::vector(channelCount)); + + std::vector> marker_buffer(chunkSize, std::vector(1)); + std::vector s_mrkr; std::vector trig_buffer(10); _resample = false; if(samplingRate != 2000 && samplingRate != 20000) _resample = true; + //std::cout << "channelCount: " << channelCount << std::endl; + // create data streaminfo and append some meta-data - lsl::stream_info data_info("VAmp-" + boost::lexical_cast(deviceNumber) + "-EEG","EEG",channelsToSendLSL, samplingRate, lsl::cf_float32,"VAmp_" + boost::lexical_cast(deviceNumber) + "_EEG"); + lsl::stream_info data_info("VAmp-" + boost::lexical_cast(deviceNumber) + "-EEG","EEG",channelCount, samplingRate, lsl::cf_float32,"VAmp_" + boost::lexical_cast(deviceNumber) + "_EEG"); lsl::xml_element channels = data_info.desc().append_child("channels"); - for (std::size_t k=0; k < channelsToSendLSL;k++) + int count = channelCount - (sampledMarkersEEG ? 0 : 1); + //if(!regularizeMarkers)count--; + for (std::size_t k=0; k < count;k++) { if(k < channelLabels.size()) { @@ -793,29 +401,53 @@ void MainWindow::read_thread(int deviceNumber, int channelsToSendLSL, int chunkS .append_child_value("unit","microvolts"); } } + if(sampledMarkersEEG) { + channels.append_child("channel") + .append_child_value("label", "triggerStream") + .append_child_value("type","EEG") + .append_child_value("unit","code"); + } data_info.desc().append_child("acquisition").append_child_value("manufacturer","Brain Products"); - // make a data outlet lsl::stream_outlet data_outlet(data_info); - + + // trick C++ compiler into having a conditional object + lsl::stream_outlet *marker_outlet; // create marker streaminfo and outlet - lsl::stream_info marker_info("VAmp-" + boost::lexical_cast(deviceNumber) + "-Markers","Markers", 1, 0, lsl::cf_string,"VAmp_" + boost::lexical_cast(deviceNumber) + "_marker"); - lsl::stream_outlet outlet_marker(marker_info); - + if(unsampledMarkers) { + lsl::stream_info marker_info("VAmp-" + boost::lexical_cast(deviceNumber) + "-Markers","Markers", 1, 0, lsl::cf_string,"VAmp_" + boost::lexical_cast(deviceNumber) + "_markers"); + marker_outlet = new lsl::stream_outlet(marker_info); + } + + lsl::stream_outlet *s_marker_outlet; + if(sampledMarkers) { + lsl::stream_info s_marker_info("VAmp-" + boost::lexical_cast(deviceNumber) + "-Sampled-Markers","sampledMarkers", 1, samplingRate, lsl::cf_string,"VAmp_" + boost::lexical_cast(deviceNumber) + "_sampled_markers"); + s_marker_outlet = new lsl::stream_outlet(s_marker_info); + // ditch the outlet if we don't need it (need to do it this way in order to trick C++ compiler into using this object conditionally) + } + int samples_read = 0; - USHORT prev_marker = 0x1FF; + USHORT prev_marker = 0; + USHORT prev_markerSampled = 0; + USHORT prev_markerEEG = 0; + float f_mrkr; + UINT nBlockCnt = 0; UINT nBlockSize = 19; // 16 eeg + 2 aux + 1 digital port - if(nDataMode != dmNormal) - nBlockSize = 4; // 4 eeg + 1 1 digital port - - bool useAUX = ui->useAUX->checkState()==Qt::Checked; - int eegChannelToSend = channelsToSendLSL; + if(nDataMode != dmNormal){ + if(useAUX) { + throw std::runtime_error("Cannot use aux channels in high speed mode"); + return; + } + nBlockSize = 5; // 4 eeg + 1 1 digital port + } + + int eegChannelToSend = channelCount; if(useAUX) eegChannelToSend -= 2; - + // enter transmission loop while (!stop_) { // (4) Serve data @@ -825,31 +457,29 @@ void MainWindow::read_thread(int deviceNumber, int channelsToSendLSL, int chunkS int nErrorSeverity = 0; HRESULT hResult = S_FALSE; hResult = l_pDevice->GetData(&sa, &nMaxChannels, &samples_read, &date, &nErrorSeverity); + + if(samples_read<=0 && samplingRate <= 2000){ + boost::this_thread::sleep(boost::posix_time::milliseconds(1)); + continue; + } + if (hResult == S_OK) { saData.Attach(sa); float *pBuffer = (float *)saData.m_psa->pvData; - // order of channel : - // VAmp 16: EEG: 0..15; AUX: 16, 17; Trigger: 18 - // firstAmp 8: EEG: 0..7; AUX: 8, 9; Trigger: 10 - /*printf("\r %10g s - %d: c1 = %g uV \t c2 = %g uV \t c3 = %g uV ", - float(((float) 0 - (float) 0) / 1000.f), - nBlockCnt++, - *(pBuffer ) * (float)pResolutions.GetAt(i), - *(pBuffer + 1) * (float)pResolutions.GetAt( 1), - *(pBuffer + 2) * (float)pResolutions.GetAt( 2)); */ - + int auxCount=0; double now = lsl::local_clock(); if (samples_read > 0) { int nBlock = 0; send_buffer.resize(samples_read); + marker_buffer.resize(samples_read); trig_buffer.resize(samples_read); - for (int s=0,e=samples_read;s(trig_buffer[s])); + prev_markerEEG = trig_buffer[s]; + send_buffer[s][eegChannelToSend + auxCount - 1] = f_mrkr; + } + + if (sampledMarkers) { + s_mrkr.clear(); + s_mrkr.push_back(trig_buffer[s] == prev_markerSampled ? "" : boost::lexical_cast(trig_buffer[s])); + marker_buffer.at(s) = s_mrkr; + //std::cout << "s: " << s << std::endl; + prev_markerSampled = trig_buffer[s]; + } + + nBlock += nBlockSize; } - } - // send EEG to LSL - data_outlet.push_chunk(send_buffer, now); - - // push markers into outlet - for (int s=0;s(mrk); - outlet_marker.push_sample(&mrk_string, now + (s + 1 - samples_read)/samplingRate); - prev_marker = mrk; + // send EEG to LSL + data_outlet.push_chunk(send_buffer, now); + + // send the sample markers (if we want to) + if(sampledMarkers) + s_marker_outlet->push_chunk(marker_buffer, now); + + // push markers into outlet + if(unsampledMarkers) { + for (int s=0;s(mrk); + marker_outlet->push_sample(&mrk_string, now + (s + 1 - samples_read)/samplingRate); + prev_marker = mrk; + } + } } } } - } - } + } // close if good sample condition + } // close while loop + // need to explicitly delete these objects + if(unsampledMarkers)delete(marker_outlet); + if(sampledMarkers)delete(s_marker_outlet); } catch(boost::thread_interrupted &) { // thread was interrupted: no error @@ -899,11 +553,11 @@ void MainWindow::read_thread(int deviceNumber, int channelsToSendLSL, int chunkS l_pDevice->Close(); l_pDevice = NULL; } + + } MainWindow::~MainWindow() { delete ui; } - - diff --git a/Apps/BrainProducts/VAmp/mainwindow.h b/Apps/BrainProducts/VAmp/mainwindow.h index 7e781584..4727fe10 100644 --- a/Apps/BrainProducts/VAmp/mainwindow.h +++ b/Apps/BrainProducts/VAmp/mainwindow.h @@ -10,9 +10,10 @@ #include #include + // LSL API #define LSL_DEBUG_BINDINGS -#include +#include "../../../LSL/liblsl/include/lsl_cpp.h" // BrainAmp API #define WIN32_LEAN_AND_MEAN @@ -36,15 +37,20 @@ private slots: // config file dialog ops (from main menu) void load_config_dialog(); void save_config_dialog(); + void update_channel_labels(int n); + void update_channels_for_sr(int n); - // start the VAmp connection + // start the ActiChamp connection void link(); // close event (potentially disabled) void closeEvent(QCloseEvent *ev); private: + + void update_channel_counter(); + // background data reader thread - void read_thread(int deviceNumber, int channelCount, int chunkSize, int samplingRate, bool useAUX, std::vector channelLabels); + void read_thread(int deviceNumber, int channelCount, int samplingRate, bool useAUX, bool unsampledMarkers, bool sampledMarkers, bool sampledMarkersEEG, std::vector channelLabels); // raw config file IO void load_config(const std::string &filename); @@ -54,8 +60,6 @@ private slots: boost::shared_ptr reader_thread_; // our reader thread Ui::MainWindow *ui; - - //void Transpose(const std::vector> &in, std::vector> &out); }; #endif // MAINWINDOW_H diff --git a/Apps/BrainProducts/VAmp/mainwindow.ui b/Apps/BrainProducts/VAmp/mainwindow.ui index 2a3b74ad..bbebfc7f 100644 --- a/Apps/BrainProducts/VAmp/mainwindow.ui +++ b/Apps/BrainProducts/VAmp/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 357 - 274 + 439 + 334 @@ -107,39 +107,13 @@
- - - Chunk Size (always 20ms) - - - - - - - The number of samples per chunk emitted by the driver -- a small value will lead to lower overall latency but causes more CPU load - - - 1 - - - 1024 - - - 20 - - - false - - - - Sampling Rate - + The sampling rate to use; higher sampling rates require more network bandwidth (and storage space if recording), particularly the very high rates of 10KHz+. The native rates are those that are natively supported by the hardware and the resampled rates are resampled in software (using a linear-phase sinc resampler that delays the output signal by 5 samples). @@ -182,21 +156,21 @@ 10000 high-speed (resampled) - - - 20000 high speed(native) - - + + + 20000 high speed(native) + + - + Use AUX Channels - + If this is checked then the EEG channels will hold the two AUX signals @@ -209,6 +183,69 @@ + + + + LSL Trigger Output Style + + + + + + Unsampled String Markers + + + + + + + <html><head/><body><p>For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.</p></body></html> + + + (check) + + + + + + + <html><head/><body><p>For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.</p></body></html> + + + (check) + + + + + + + Floating Point EEG Channel + + + + + + + <html><head/><body><p>For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.</p></body></html> + + + (check) + + + true + + + + + + + Sampled String Markers + + + + + + @@ -258,8 +295,8 @@ 0 0 - 357 - 18 + 439 + 21 diff --git a/Apps/BrainProducts/VAmp/ui_mainwindow.h b/Apps/BrainProducts/VAmp/ui_mainwindow.h index 937a53aa..433191df 100644 --- a/Apps/BrainProducts/VAmp/ui_mainwindow.h +++ b/Apps/BrainProducts/VAmp/ui_mainwindow.h @@ -1,8 +1,7 @@ /******************************************************************************** ** Form generated from reading UI file 'mainwindow.ui' ** -** Created: Fri 27. Jan 10:14:58 2017 -** by: Qt User Interface Compiler version 4.8.1 +** Created by: Qt User Interface Compiler version 4.8.6 ** ** WARNING! All changes made in this file will be lost when recompiling UI file! ********************************************************************************/ @@ -54,12 +53,18 @@ class Ui_MainWindow QSpinBox *deviceNumber; QLabel *label_2; QSpinBox *channelCount; - QLabel *label; - QSpinBox *chunkSize; QLabel *label_3; QComboBox *samplingRate; QLabel *label_7; QCheckBox *useAUX; + QGroupBox *groupBox_3; + QFormLayout *formLayout_2; + QLabel *label; + QCheckBox *unsampledMarkers; + QCheckBox *sampledMarkersEEG; + QLabel *label_6; + QCheckBox *sampledMarkers; + QLabel *label_5; QSpacerItem *verticalSpacer; QHBoxLayout *horizontalLayout; QSpacerItem *horizontalSpacer; @@ -72,7 +77,7 @@ class Ui_MainWindow { if (MainWindow->objectName().isEmpty()) MainWindow->setObjectName(QString::fromUtf8("MainWindow")); - MainWindow->resize(357, 274); + MainWindow->resize(439, 334); actionLoad_Configuration = new QAction(MainWindow); actionLoad_Configuration->setObjectName(QString::fromUtf8("actionLoad_Configuration")); actionSave_Configuration = new QAction(MainWindow); @@ -138,40 +143,66 @@ class Ui_MainWindow formLayout->setWidget(1, QFormLayout::FieldRole, channelCount); - label = new QLabel(groupBox); - label->setObjectName(QString::fromUtf8("label")); - - formLayout->setWidget(2, QFormLayout::LabelRole, label); - - chunkSize = new QSpinBox(groupBox); - chunkSize->setObjectName(QString::fromUtf8("chunkSize")); - chunkSize->setMinimum(1); - chunkSize->setMaximum(1024); - chunkSize->setValue(20); - chunkSize->setEnabled(false); - - formLayout->setWidget(2, QFormLayout::FieldRole, chunkSize); - label_3 = new QLabel(groupBox); label_3->setObjectName(QString::fromUtf8("label_3")); - formLayout->setWidget(3, QFormLayout::LabelRole, label_3); + formLayout->setWidget(2, QFormLayout::LabelRole, label_3); samplingRate = new QComboBox(groupBox); samplingRate->setObjectName(QString::fromUtf8("samplingRate")); - formLayout->setWidget(3, QFormLayout::FieldRole, samplingRate); + formLayout->setWidget(2, QFormLayout::FieldRole, samplingRate); label_7 = new QLabel(groupBox); label_7->setObjectName(QString::fromUtf8("label_7")); - formLayout->setWidget(4, QFormLayout::LabelRole, label_7); + formLayout->setWidget(3, QFormLayout::LabelRole, label_7); useAUX = new QCheckBox(groupBox); useAUX->setObjectName(QString::fromUtf8("useAUX")); useAUX->setChecked(false); - formLayout->setWidget(4, QFormLayout::FieldRole, useAUX); + formLayout->setWidget(3, QFormLayout::FieldRole, useAUX); + + groupBox_3 = new QGroupBox(groupBox); + groupBox_3->setObjectName(QString::fromUtf8("groupBox_3")); + formLayout_2 = new QFormLayout(groupBox_3); + formLayout_2->setSpacing(6); + formLayout_2->setContentsMargins(11, 11, 11, 11); + formLayout_2->setObjectName(QString::fromUtf8("formLayout_2")); + label = new QLabel(groupBox_3); + label->setObjectName(QString::fromUtf8("label")); + + formLayout_2->setWidget(0, QFormLayout::LabelRole, label); + + unsampledMarkers = new QCheckBox(groupBox_3); + unsampledMarkers->setObjectName(QString::fromUtf8("unsampledMarkers")); + + formLayout_2->setWidget(0, QFormLayout::FieldRole, unsampledMarkers); + + sampledMarkersEEG = new QCheckBox(groupBox_3); + sampledMarkersEEG->setObjectName(QString::fromUtf8("sampledMarkersEEG")); + + formLayout_2->setWidget(2, QFormLayout::FieldRole, sampledMarkersEEG); + + label_6 = new QLabel(groupBox_3); + label_6->setObjectName(QString::fromUtf8("label_6")); + + formLayout_2->setWidget(2, QFormLayout::LabelRole, label_6); + + sampledMarkers = new QCheckBox(groupBox_3); + sampledMarkers->setObjectName(QString::fromUtf8("sampledMarkers")); + sampledMarkers->setChecked(true); + + formLayout_2->setWidget(1, QFormLayout::FieldRole, sampledMarkers); + + label_5 = new QLabel(groupBox_3); + label_5->setObjectName(QString::fromUtf8("label_5")); + + formLayout_2->setWidget(1, QFormLayout::LabelRole, label_5); + + + formLayout->setWidget(4, QFormLayout::SpanningRole, groupBox_3); verticalLayout->addWidget(groupBox); @@ -201,7 +232,7 @@ class Ui_MainWindow MainWindow->setCentralWidget(centralWidget); menuBar = new QMenuBar(MainWindow); menuBar->setObjectName(QString::fromUtf8("menuBar")); - menuBar->setGeometry(QRect(0, 0, 357, 18)); + menuBar->setGeometry(QRect(0, 0, 439, 21)); menuFile = new QMenu(menuBar); menuFile->setObjectName(QString::fromUtf8("menuFile")); MainWindow->setMenuBar(menuBar); @@ -256,10 +287,6 @@ class Ui_MainWindow #ifndef QT_NO_TOOLTIP channelCount->setToolTip(QApplication::translate("MainWindow", "This must match the number of entries in the channel list", 0, QApplication::UnicodeUTF8)); #endif // QT_NO_TOOLTIP - label->setText(QApplication::translate("MainWindow", "Chunk Size (always 20ms)", 0, QApplication::UnicodeUTF8)); -#ifndef QT_NO_TOOLTIP - chunkSize->setToolTip(QApplication::translate("MainWindow", "The number of samples per chunk emitted by the driver -- a small value will lead to lower overall latency but causes more CPU load", 0, QApplication::UnicodeUTF8)); -#endif // QT_NO_TOOLTIP label_3->setText(QApplication::translate("MainWindow", "Sampling Rate", 0, QApplication::UnicodeUTF8)); samplingRate->clear(); samplingRate->insertItems(0, QStringList() @@ -280,6 +307,22 @@ class Ui_MainWindow useAUX->setToolTip(QApplication::translate("MainWindow", "If this is checked then the EEG channels will hold the two AUX signals", 0, QApplication::UnicodeUTF8)); #endif // QT_NO_TOOLTIP useAUX->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + groupBox_3->setTitle(QApplication::translate("MainWindow", "LSL Trigger Output Style", 0, QApplication::UnicodeUTF8)); + label->setText(QApplication::translate("MainWindow", "Unsampled String Markers", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + unsampledMarkers->setToolTip(QApplication::translate("MainWindow", "

For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.

", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + unsampledMarkers->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + sampledMarkersEEG->setToolTip(QApplication::translate("MainWindow", "

For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.

", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + sampledMarkersEEG->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + label_6->setText(QApplication::translate("MainWindow", "Floating Point EEG Channel", 0, QApplication::UnicodeUTF8)); +#ifndef QT_NO_TOOLTIP + sampledMarkers->setToolTip(QApplication::translate("MainWindow", "

For an explanation of trigger marker types please read 'explanation_of_trigger_marker_types.pdf'.

", 0, QApplication::UnicodeUTF8)); +#endif // QT_NO_TOOLTIP + sampledMarkers->setText(QApplication::translate("MainWindow", "(check)", 0, QApplication::UnicodeUTF8)); + label_5->setText(QApplication::translate("MainWindow", " Sampled String Markers", 0, QApplication::UnicodeUTF8)); linkButton->setText(QApplication::translate("MainWindow", "Link", 0, QApplication::UnicodeUTF8)); menuFile->setTitle(QApplication::translate("MainWindow", "File", 0, QApplication::UnicodeUTF8)); } // retranslateUi diff --git a/Apps/OpenVR/.gitignore b/Apps/OpenVR/.gitignore new file mode 100644 index 00000000..2db34067 --- /dev/null +++ b/Apps/OpenVR/.gitignore @@ -0,0 +1 @@ +deps/* diff --git a/Apps/OpenVR/CMakeLists.txt b/Apps/OpenVR/CMakeLists.txt new file mode 100644 index 00000000..775a7d9d --- /dev/null +++ b/Apps/OpenVR/CMakeLists.txt @@ -0,0 +1,109 @@ +cmake_minimum_required(VERSION 3.0) +project(LSLOpenVR VERSION 0.0.1) + +################## +# GENERAL CONFIG # +################## + +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") +include(ExternalProject) +# Set policies +cmake_policy(SET CMP0028 NEW) # ENABLE CMP0028: Double colon in target name means ALIAS or IMPORTED target. +cmake_policy(SET CMP0054 NEW) # ENABLE CMP0054: Only interpret if() arguments as variables or keywords when unquoted. +cmake_policy(SET CMP0042 NEW) # ENABLE CMP0042: MACOSX_RPATH is enabled by default. +cmake_policy(SET CMP0063 NEW) # ENABLE CMP0063: Honor visibility properties for all target types. +# Generate folders for IDE targets (e.g., VisualStudio solutions) +set_property(GLOBAL PROPERTY USE_FOLDERS ON) +set(IDE_FOLDER "") +# +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +# Set runtime path +set(CMAKE_SKIP_BUILD_RPATH FALSE) # Add absolute path to all dependencies for BUILD +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) # Use CMAKE_INSTALL_RPATH for INSTALL +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE) # Do NOT add path to dependencies for INSTALL +# Runtime should find binaries relative to itself +if(APPLE) + set(CMAKE_INSTALL_RPATH "@loader_path/../../../${INSTALL_LIB}") +else() + set(CMAKE_INSTALL_RPATH "$ORIGIN/${INSTALL_LIB}") +endif() + +######################### +# THIRD PARTY LIBRARIES # +######################### + +# LSL +set(LSL_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../../LSL/liblsl) +find_package(LSL REQUIRED) + +# Qt +FIND_PROGRAM(QT_QMAKE_EXECUTABLE_FINDQT + NAMES + qmake qmake5 qmake-qt5 + PATHS + "${QT_SEARCH_PATH}/bin" + "$ENV{QTDIR}/bin" +) +SET(QT_QMAKE_EXECUTABLE ${QT_QMAKE_EXECUTABLE_FINDQT} CACHE PATH "Qt qmake program.") +EXEC_PROGRAM(${QT_QMAKE_EXECUTABLE} ARGS "-query QT_INSTALL_PREFIX" OUTPUT_VARIABLE CMAKE_PREFIX_PATH) +find_package(Qt5Core REQUIRED) +find_package(Qt5Network) +find_package(Qt5Xml) +find_package(Qt5Gui) +find_package(Qt5Widgets) +set(CMAKE_INCLUDE_CURRENT_DIR ON) # Because the ui_mainwindow.h file. +# Enable automoc +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(AUTOMOC_MOC_OPTIONS PROPERTIES FOLDER CMakeAutomocTargets) +set_property(GLOBAL PROPERTY AUTOMOC_FOLDER CMakeAutomocTargets) +# ENABLE CMP0020: Automatically link Qt executables to qtmain target on Windows. +cmake_policy(SET CMP0020 NEW) + +# OpenVR +IF(NOT OpenVR_ROOT_DIR) + message(STATUS "OpenVR_ROOT_DIR not set.") + message("\tUse cmake [...] -DOpenVR_ROOT_DIR=") + message("\tAttempting Chad's default rel directory...") + SET(OpenVR_ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../../VR/openvr") +ENDIF() +find_package(OpenVR) +IF(NOT OpenVR_FOUND) + message(STATUS "OpenVR not found. It will be downloaded from GitHub during configure. This can be very slow.") + ExternalProject_Add(openvr + PREFIX ${CMAKE_CURRENT_LIST_DIR}/deps/openvr + DOWNLOAD_NO_PROGRESS 0 # Do not disable download progress. + GIT_REPOSITORY https://github.com/ValveSoftware/openvr.git + GIT_SHALLOW 1 + BUILD_COMMAND "" # Disable build step. + INSTALL_COMMAND "" # Disable install step. + ) + ExternalProject_Get_Property(openvr SOURCE_DIR) + # Cannot use find_package because the project is not downloaded yet. + # Instead, assume file locations. + set(SIZEOF_VOIDP ${CMAKE_SIZEOF_VOID_P}) + if((NOT APPLE) AND (CMAKE_SIZEOF_VOID_P EQUAL 8)) + set(PROCESSOR_ARCH "64") + else() + set(PROCESSOR_ARCH "32") + endif() + if(WIN32) + set(PLATFORM_NAME "win") + elseif(UNIX AND NOT APPLE) + if(CMAKE_SYSTEM_NAME MATCHES ".*Linux") + set(PLATFORM_NAME "linux") + endif() + elseif(APPLE) + if(CMAKE_SYSTEM_NAME MATCHES ".*Darwin.*" OR CMAKE_SYSTEM_NAME MATCHES ".*MacOS.*") + set(PLATFORM_NAME "osx") + endif() + endif() + SET(OpenVR_INCLUDE_DIRS ${SOURCE_DIR}/headers) + SET(OpenVR_BINARIES ${SOURCE_DIR}/bin/${PLATFORM_NAME}${PROCESSOR_ARCH}/${CMAKE_SHARED_LIBRARY_PREFIX}openvr_api${CMAKE_SHARED_LIBRARY_SUFFIX}) + SET(OpenVR_LIBRARIES ${SOURCE_DIR}/lib/${PLATFORM_NAME}${PROCESSOR_ARCH}/${CMAKE_SHARED_LIBRARY_PREFIX}openvr_api${CMAKE_IMPORT_LIBRARY_SUFFIX}) +ENDIF() + +############### +# APPLICATION # +############### +add_subdirectory(src) diff --git a/Apps/OpenVR/README.md b/Apps/OpenVR/README.md new file mode 100644 index 00000000..822172f3 --- /dev/null +++ b/Apps/OpenVR/README.md @@ -0,0 +1,62 @@ +# LabStreamingLayer application for OpenVR + +## Build Instructions + +Requirements: + +* [CMake](https://cmake.org/download/) +* [Qt5](https://www.qt.io/download-open-source/) +* [OpenVR](https://github.com/ValveSoftware/openvr) +* liblsl +* Build environment + * Tested with MSVC 2015, Xcode, and Qt Creator + +Instructions: + +1. Open a Command Prompt / Terminal and change into this directory. +1. Make a build subdirectory: `mkdir build && cd build` +1. Call cmake + * Windows: `cmake .. -G "Visual Studio 14 2015 Win64"` (other [generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html#visual-studio-generators)) + * Mac: `cmake .. -G Xcode` +1. You may need to specify additional cmake options. + * set OpenVR_ROOT_DIR to the directory where you downloaded openvr + * For Qt, you have a few options. + * Make sure qmake is on the path + * Set an environment variable to your Qt directory (e.g. `SET QTDIR=C:\Qt\5.8\msvc2015_64`) before calling cmake + * Pass a cmake argument for QT_SEARCH_PATH (e.g., `-DQT_SEARCH_PATH=C:\Qt\5.8\msvc2015_64`) + +## Usage Instructions + +### Running the app + +1. Launch the app +1. Click on "Scan Devices" to connect to OpenVR and scan for connected devices. +1. Enter the sampling rate you would like to sample your poses at. +1. Click on the individual device(s) you want to stream, or don't click to stream all. +1. Click on "Stream Devices" to start the LSL Outlets. + +### Data format + +* One stream will be started for all devices' pose data. Each device gets 12 channels, corresponding to the 12 cells in a 3x4 transformation matrix. + * The channel label prefix (#_) indicates the device ID. + * The channel label suffix (_#) indicates the transformation matrix cell location. + +``` +00 01 02 X +10 11 12 Y +20 21 22 Z +``` + +Where X,Y,Z refer to the 3-D position. + +Another stream will be started for each controller's button press events. +This is a single int32 channel with each value having the following format XXYYYYYZZ + +* XX : Device ID. Up to 16. +* YYYYY : [vr::EVREventType](https://github.com/ValveSoftware/openvr/blob/master/headers/openvr.h#L449-L585) +* ZZ : [vr::EVRButtonId](https://github.com/ValveSoftware/openvr/blob/master/headers/openvr.h#L600-L626) + +## Known Issues + +* Cannot build debug app in Windows because it tries to link against non-debug liblsl64.lib, even though liblsl64-debug.lib is correctly specified in the project settings. I'm confused. +* Sometimes an OpenVR controller will sleep/disconnect but it'll still be returned in the list of devices. Sleeping controllers will stream 0's for their pose data. diff --git a/Apps/OpenVR/cmake/FindLSL.cmake b/Apps/OpenVR/cmake/FindLSL.cmake new file mode 100644 index 00000000..c244ebd9 --- /dev/null +++ b/Apps/OpenVR/cmake/FindLSL.cmake @@ -0,0 +1,74 @@ +# - Try to find the labstreaminglayer library +# +# LSL_FOUND - system has lsl +# LSL_INCLUDE_DIRS - the lsl include directory +# LSL_LIBRARIES - Link these to use lsl +# LSL_BINARIES + +set(LSL_ROOT_DIR + "${LSL_ROOT_DIR}" + CACHE + PATH + "Directory to search for LabStreamingLayer API") + + +IF (LSL_INCLUDE_DIRS AND LSL_LIBRARIES) + + # in cache already + set(LSL_FOUND TRUE) + +ELSE (LSL_INCLUDE_DIRS AND LSL_LIBRARIES) + IF(NOT LSL_ROOT_DIR) + message(STATUS "LSL_ROOT_DIR not set. Use `cmake [...] -DLSL_ROOT_DIR=") + message("\tDefaulting to ${CMAKE_CURRENT_LIST_DIR}/../../../LSL/liblsl") + SET(LSL_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../LSL/liblsl CACHE PATH "Path to local liblsl") + ENDIF() + + FIND_PATH(LSL_INCLUDE_DIRS + NAMES + lsl_cpp.h + PATHS + ${LSL_ROOT_DIR}/include + ) + + IF (${CMAKE_C_SIZEOF_DATA_PTR} EQUAL 8) + SET(_arch 64) + ELSE() + SET(_arch 32) + ENDIF() + + # LSL prefixes all libs with 'lib'. + # This is expected in UNIX. + SET(MY_PREFIX "") + # But is unusual in Windows. + IF(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + SET(MY_PREFIX lib) + ENDIF() + + find_library(LSL_LIBRARY_RELEASE + NAMES ${MY_PREFIX}lsl${_arch} + PATHS ${LSL_ROOT_DIR}/bin + ) + + find_library(LSL_LIBRARY_DEBUG + NAMES ${MY_PREFIX}lsl${_arch}-debug + PATHS ${LSL_ROOT_DIR}/bin + ) + SET(LSL_LIBRARIES + debug ${LSL_LIBRARY_DEBUG} + optimized ${LSL_LIBRARY_RELEASE}) + + find_file(LSL_BINARY_RELEASE + NAMES liblsl${_arch}${CMAKE_SHARED_LIBRARY_SUFFIX} + PATHS ${LSL_ROOT_DIR}/bin + ) + find_file(LSL_BINARY_DEBUG + NAMES liblsl${_arch}-debug${CMAKE_SHARED_LIBRARY_SUFFIX} + PATHS ${LSL_ROOT_DIR}/bin + ) + + INCLUDE(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(LSL DEFAULT_MSG LSL_INCLUDE_DIRS LSL_LIBRARIES LSL_BINARY_DEBUG LSL_BINARY_RELEASE) + MARK_AS_ADVANCED(LSL_INCLUDE_DIRS LSL_LIBRARIES LSL_BINARY_DEBUG LSL_BINARY_RELEASE) + +ENDIF (LSL_INCLUDE_DIRS AND LSL_LIBRARIES) \ No newline at end of file diff --git a/Apps/OpenVR/cmake/FindOpenVR.cmake b/Apps/OpenVR/cmake/FindOpenVR.cmake new file mode 100644 index 00000000..0ffee698 --- /dev/null +++ b/Apps/OpenVR/cmake/FindOpenVR.cmake @@ -0,0 +1,88 @@ +# - try to find the OpenVR SDK - currently designed for the version on GitHub. +# +# Cache Variables: (probably not for direct use in your scripts) +# OPENVR_INCLUDE_DIR +# +# Non-cache variables you might use in your CMakeLists.txt: +# OpenVR_FOUND +# OpenVR_INCLUDE_DIRS +# OpenVR_LIBRARIES +# OpenVR_BINARIES +# +# Requires these CMake modules: +# FindPackageHandleStandardArgs (known included with CMake >=2.6.2) +# +# Original Author: +# 2015 Ryan A. Pavlik +# Modified by: +# 2017 Chadwick Boulay +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +set(OpenVR_ROOT_DIR + "${OpenVR_ROOT_DIR}" + CACHE + PATH + "Directory to search for OpenVR SDK") + +# Check if 32 or 64 bit system. +set(SIZEOF_VOIDP ${CMAKE_SIZEOF_VOID_P}) +if((NOT APPLE) AND (CMAKE_SIZEOF_VOID_P EQUAL 8)) + #64-bit not available in Mac. + set(PROCESSOR_ARCH "64") +else() + set(PROCESSOR_ARCH "32") +endif() + +# Get platform. +if(WIN32) + set(PLATFORM_NAME "win") +elseif(UNIX AND NOT APPLE) + if(CMAKE_SYSTEM_NAME MATCHES ".*Linux") + set(PLATFORM_NAME "linux") + endif() +elseif(APPLE) + if(CMAKE_SYSTEM_NAME MATCHES ".*Darwin.*" OR CMAKE_SYSTEM_NAME MATCHES ".*MacOS.*") + set(PLATFORM_NAME "osx") + endif() +endif() + +find_path(OPENVR_INCLUDE_DIR + NAMES + openvr_driver.h + PATHS + ${OpenVR_ROOT_DIR}/headers +) +list(APPEND OpenVR_INCLUDE_DIRS ${OPENVR_INCLUDE_DIR}) + +find_library(OpenVR_LIBRARIES + NAMES + openvr_api + PATHS + ${OpenVR_ROOT_DIR}/lib/${PLATFORM_NAME}${PROCESSOR_ARCH} +) + +find_file(OpenVR_BINARIES + NAMES + ${CMAKE_SHARED_LIBRARY_PREFIX}openvr_api${CMAKE_SHARED_LIBRARY_SUFFIX} + PATHS + ${OpenVR_ROOT_DIR}/bin/${PLATFORM_NAME}${PROCESSOR_ARCH} +) + +SET(OpenVR_FOUND TRUE) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(OpenVR + DEFAULT_MSG + OpenVR_FOUND + OpenVR_INCLUDE_DIRS + OpenVR_LIBRARIES + OpenVR_BINARIES) + +mark_as_advanced( + OpenVR_FOUND + OpenVR_INCLUDE_DIRS + OpenVR_LIBRARIES + OpenVR_BINARIES) \ No newline at end of file diff --git a/Apps/OpenVR/openvr_config.cfg b/Apps/OpenVR/openvr_config.cfg new file mode 100644 index 00000000..4af1653d --- /dev/null +++ b/Apps/OpenVR/openvr_config.cfg @@ -0,0 +1,5 @@ + + + 1000 + 0 + diff --git a/Apps/OpenVR/src/CMakeLists.txt b/Apps/OpenVR/src/CMakeLists.txt new file mode 100644 index 00000000..ff1d46ff --- /dev/null +++ b/Apps/OpenVR/src/CMakeLists.txt @@ -0,0 +1,84 @@ +# Collect sources, headers, and libs + +SET(LSLOpenVR_SRC) +SET(LSLOpenVR_INCL_DIRS) +SET(LSLOpenVR_REQ_LIBS) + +# Sources +LIST(APPEND LSLOpenVR_SRC + ${CMAKE_CURRENT_LIST_DIR}/main.cpp + ${CMAKE_CURRENT_LIST_DIR}/mainwindow.cpp + ${CMAKE_CURRENT_LIST_DIR}/mainwindow.h + ${CMAKE_CURRENT_LIST_DIR}/mainwindow.ui + ${CMAKE_CURRENT_LIST_DIR}/openvrthread.h + ${CMAKE_CURRENT_LIST_DIR}/openvrthread.cpp +) + +LIST(APPEND LSL_OPENVR_INCL_DIRS + ${LSL_INCLUDE_DIRS} + ${OpenVR_INCLUDE_DIRS} +) + +LIST(APPEND LSLOPENVR_REQ_LIBS + Qt5::Core + Qt5::Gui + Qt5::Widgets + Qt5::Xml + ${LSL_LIBRARIES} + ${OpenVR_LIBRARIES} +) + +########## +# TARGET # +########## + +SET(target LSLOpenVR) + +add_executable(${target} + MACOSX_BUNDLE + ${LSLOpenVR_SRC} +) + +target_include_directories(${target} + PRIVATE + ${LSL_OPENVR_INCL_DIRS}) + +target_link_libraries(${target} + PRIVATE + ${LSLOPENVR_REQ_LIBS} +) + +# Deployment +# +if(WIN32) + get_target_property(QT5_QMAKE_EXECUTABLE Qt5::qmake IMPORTED_LOCATION) + get_filename_component(QT5_WINDEPLOYQT_EXECUTABLE ${QT5_QMAKE_EXECUTABLE} PATH) + set(QT5_WINDEPLOYQT_EXECUTABLE "${QT5_WINDEPLOYQT_EXECUTABLE}/windeployqt.exe") + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${QT5_WINDEPLOYQT_EXECUTABLE} --qmldir + ${CMAKE_SOURCE_DIR} + $) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${LSL_BINARY_RELEASE} + $) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${LSL_BINARY_DEBUG} + $) +endif(WIN32) +add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${OpenVR_BINARIES} + $) +if(APPLE) + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_CURRENT_LIST_DIR}/../openvr_config.cfg" + ${PROJECT_BINARY_DIR}) +else() + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_CURRENT_LIST_DIR}/../openvr_config.cfg" + $) +endif() diff --git a/Apps/OpenVR/src/main.cpp b/Apps/OpenVR/src/main.cpp new file mode 100644 index 00000000..8797e1fa --- /dev/null +++ b/Apps/OpenVR/src/main.cpp @@ -0,0 +1,23 @@ +#include "mainwindow.h" +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + QCommandLineParser parser; + parser.setApplicationDescription("LabStreamingLayer interface for OpenVR."); + parser.addHelpOption(); + parser.addVersionOption(); + QCommandLineOption configFileOption(QStringList() << "c" << "config", + QCoreApplication::translate("main", "Load configuration from ."), + QCoreApplication::translate("main", "config"), + default_config_fname); + parser.addOption(configFileOption); + parser.process(a); + QString configFilename = parser.value(configFileOption); + MainWindow w(0, configFilename); + w.show(); + + return a.exec(); +} diff --git a/Apps/OpenVR/src/mainwindow.cpp b/Apps/OpenVR/src/mainwindow.cpp new file mode 100644 index 00000000..79765594 --- /dev/null +++ b/Apps/OpenVR/src/mainwindow.cpp @@ -0,0 +1,130 @@ +#include +#include +#include +#include "mainwindow.h" +#include "ui_mainwindow.h" + +MainWindow::MainWindow(QWidget *parent, const QString config_file) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + load_config(config_file); + connect(&m_thread, SIGNAL(openvrConnected(bool)), this, SLOT(update_connect_label(bool))); + connect(&m_thread, SIGNAL(deviceListUpdated(QStringList)), this, SLOT(update_list_devices(QStringList))); + connect(&m_thread, SIGNAL(outletsStarted(bool)), this, SLOT(update_stream_button(bool))); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::load_config(const QString filename) +{ + QFile* xmlFile = new QFile(filename); + if (!xmlFile->open(QIODevice::ReadOnly | QIODevice::Text)) { + qDebug() << "Could not load XML from file " << filename; + return; + } + QXmlStreamReader* xmlReader = new QXmlStreamReader(xmlFile); + while(!xmlReader->atEnd() && !xmlReader->hasError()) { + // Read next element + xmlReader->readNext(); + if(xmlReader->isStartElement() && xmlReader->name() != "settings") + { + QStringRef elname = xmlReader->name(); + if (elname == "sampling-rate") + ui->spinBox_sampling_rate->setValue(xmlReader->readElementText().toInt()); + if (elname == "origin-style") + { + ui->comboBox_origin->setCurrentIndex(xmlReader->readElementText().toInt()); + } + } + } + if(xmlReader->hasError()) { + qDebug() << "Config file parse error " + << xmlReader->error() + << ": " + << xmlReader->errorString(); + } + xmlReader->clear(); + xmlFile->close(); +} + +void MainWindow::save_config(const QString filename) +{ + qDebug() << "save_config(" << filename << "); TODO: Write form contents to XML file."; +} + +void MainWindow::on_actionLoad_Configuration_triggered() +{ + QString sel = QFileDialog::getOpenFileName(this, + "Load Configuration File", + "",//QDir::currentPath() + "Configuration Files (*.cfg)"); + if (!sel.isEmpty()) + { + load_config(sel); + } +} + +void MainWindow::on_actionSave_Configuration_triggered() +{ + QString sel = QFileDialog::getSaveFileName(this,"Save Configuration File","","Configuration Files (*.cfg)"); + if (!sel.isEmpty()) + { + save_config(sel); + } +} + +void MainWindow::update_connect_label(bool status) +{ + if (status) + { + ui->label_conn_status->setText("Connected to OpenVR"); + ui->pushButton_stream->setText("Start Streams"); + } + else + { + ui->label_conn_status->setText("Not Connected"); + ui->pushButton_stream->setText("No streams to start."); + } +} + +void MainWindow::update_list_devices(QStringList deviceList) +{ + ui->list_devices->clear(); + ui->list_devices->addItems(deviceList); +} + +void MainWindow::update_stream_button(bool status) +{ + if (status) + { + ui->pushButton_stream->setText("Stop Streams"); + } + else + { + ui->pushButton_stream->setText("Start Streams"); + } +} + +void MainWindow::on_pushButton_scan_clicked() +{ + m_thread.initOpenVR(ui->spinBox_sampling_rate->value()); + ui->pushButton_scan->setText("Scanning..."); + ui->pushButton_scan->setDisabled(true); +} + +void MainWindow::on_pushButton_stream_clicked() +{ + int originIndex = ui->comboBox_origin->currentIndex(); + QStringList devStringList; + QList lwi = ui->list_devices->selectedItems(); + for( int i=0; itext(); + } + m_thread.startStreams(devStringList, originIndex); +} diff --git a/Apps/OpenVR/src/mainwindow.h b/Apps/OpenVR/src/mainwindow.h new file mode 100644 index 00000000..0bb860e7 --- /dev/null +++ b/Apps/OpenVR/src/mainwindow.h @@ -0,0 +1,42 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include "openvrthread.h" + +const QString default_config_fname = "openvr_config.cfg"; + +namespace Ui { + class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0, + const QString config_file = default_config_fname); + ~MainWindow(); + +private slots: + + void on_actionLoad_Configuration_triggered(); + void on_actionSave_Configuration_triggered(); + void update_connect_label(bool status); + void update_list_devices(QStringList deviceList); + void update_stream_button(bool status); + + void on_pushButton_scan_clicked(); + + void on_pushButton_stream_clicked(); + +private: + void load_config(const QString filename); + void save_config(const QString filename); + + Ui::MainWindow *ui; + OpenVRThread m_thread; +}; + +#endif // MAINWINDOW_H diff --git a/Apps/OpenVR/src/mainwindow.ui b/Apps/OpenVR/src/mainwindow.ui new file mode 100644 index 00000000..4b436aa9 --- /dev/null +++ b/Apps/OpenVR/src/mainwindow.ui @@ -0,0 +1,169 @@ + + + MainWindow + + + + 0 + 0 + 400 + 302 + + + + LSL OpenVR + + + + + + + + + Sampling Rate (Hz) + + + + + + + 1 + + + 30000 + + + 1000 + + + + + + + + + + + + + + Connection Status + + + + + + + Origin Style + + + + + + + 0 + + + + Seated + + + + + Standing + + + + + Raw + + + + + + + + + + + + + + Scan for devices + + + + + + + Start Streams + + + + + + + + + + + + + + + + 0 + 0 + 400 + 21 + + + + + File + + + + + + + + + + + TopToolBarArea + + + false + + + + + + Load Configuration + + + Load configuration from cfg file... + + + + + Save Configuration + + + Save configuration to cfg file... + + + + + Quit + + + Disconnects from VR Sys.; Closes stream(s) + + + + + + + diff --git a/Apps/OpenVR/src/openvrthread.cpp b/Apps/OpenVR/src/openvrthread.cpp new file mode 100644 index 00000000..b3811169 --- /dev/null +++ b/Apps/OpenVR/src/openvrthread.cpp @@ -0,0 +1,425 @@ +#include "openvrthread.h" +#include +#include + +enum runPhase +{ + phase_startLink, + phase_scanForDevices, + phase_createOutlets, + phase_transferData, + phase_shutdown +}; + +QString GetTrackedDeviceString(vr::IVRSystem *pVrsys, vr::TrackedDeviceIndex_t deviceIx) +{ + QString devString = QString::number(deviceIx); + devString.append(":"); + + vr::ETrackedPropertyError *pError = nullptr; + char pchValue[vr::k_unMaxPropertyStringSize]; + uint32_t nBytes = + pVrsys->GetStringTrackedDeviceProperty(deviceIx, + vr::Prop_RenderModelName_String, + VR_OUT_STRING() pchValue, + vr::k_unMaxPropertyStringSize, + pError); + devString += pchValue; + devString.append(":"); + + nBytes = pVrsys->GetStringTrackedDeviceProperty( deviceIx, + vr::Prop_ModelNumber_String, + VR_OUT_STRING() pchValue, + vr::k_unMaxPropertyStringSize, + pError); + devString += pchValue; + + vr::ETrackedDeviceClass devClass = pVrsys->GetTrackedDeviceClass( deviceIx ); + if (devClass == vr::TrackedDeviceClass_HMD) + devString.append("(HMD)"); + else if (devClass == vr::TrackedDeviceClass_Controller) + { + vr::ETrackedControllerRole role = + pVrsys->GetControllerRoleForTrackedDeviceIndex( deviceIx ); + if (role == vr::TrackedControllerRole_LeftHand) + devString.append("(L)"); + else if (role == vr::TrackedControllerRole_RightHand) + devString.append("(R)"); + } + else if (devClass == vr::TrackedDeviceClass_GenericTracker) + devString.append("(generic)"); + + return devString; +} + +void +PrintPose(vr::HmdMatrix34_t pose_mat) +{ + double clk = lsl::local_clock(); + qDebug() + << std::fmod(1000.0*clk, 1000) + << ", " + << pose_mat.m[0][0] + << ", " + << pose_mat.m[0][1] + << ", " + << pose_mat.m[0][2] + << ", " + << pose_mat.m[0][3] + << ", " + << pose_mat.m[1][0] + << ", " + << pose_mat.m[1][1] + << ", " + << pose_mat.m[1][2] + << ", " + << pose_mat.m[1][3] + << ", " + << pose_mat.m[2][0] + << ", " + << pose_mat.m[2][1] + << ", " + << pose_mat.m[2][2] + << ", " + << pose_mat.m[2][3]; +} + + +OpenVRThread::OpenVRThread(QObject *parent) + : QThread(parent) + , abort(false) + , m_srate(lsl::IRREGULAR_RATE) + , m_bGoOutlets(false) + , m_pushCounter(0) + , m_originIndex(vr::TrackingUniverseSeated) +{ + // Any other initializations +} + +OpenVRThread::~OpenVRThread() +{ + mutex.lock(); + abort = true; // Tell run() loop to stop. + condition.wakeOne(); // In case thread is sleeping + mutex.unlock(); + + wait(); +} + +void OpenVRThread::initOpenVR(int srate) +{ + QMutexLocker locker(&mutex); + //Set member variables passed in as arguments. + this->m_srate = srate; + + if (!isRunning()) { + start(HighPriority); + } + else + { + qDebug() << "OpenVRThread is already running. Disconnecting..."; + this->abort = true; + } + +} + +void OpenVRThread::startStreams(QStringList streamDeviceList, int originIndex) +{ + std::vector newStreamDeviceIndices; + if (!this->m_bGoOutlets) + { + if (streamDeviceList.length() == 0) + { + // No devices were selected. Stream all devices. + newStreamDeviceIndices = m_deviceIndices; + } + else + { + for (QStringList::iterator it = streamDeviceList.begin(); + it != streamDeviceList.end(); ++it) + { + QStringList pieces = it->split(":"); + QString strIx = pieces.value(0); + newStreamDeviceIndices.push_back(strIx.toInt()); + } + } + } + this->mutex.lock(); + this->m_bGoOutlets = !this->m_bGoOutlets; + this->m_streamDeviceIndices = newStreamDeviceIndices; + this->m_originIndex = originIndex; + this->mutex.unlock(); +} + +bool OpenVRThread::connectToOpenVR() +{ + vr::EVRInitError eError = vr::VRInitError_None; + vr::IVRSystem *vrsys = vr::VR_Init(&eError, vr::VRApplication_Other); + if (eError != vr::VRInitError_None) + { + vrsys = NULL; + qDebug() << "Unable to init VR runtime:" << vr::VR_GetVRInitErrorAsEnglishDescription(eError); + return false; + } + + if (vrsys->IsInputFocusCapturedByAnotherProcess()) + { + qDebug() << "Input focus is captured by another process"; + return false; + } + + this->mutex.lock(); + this->m_vrsys = vrsys; + this->mutex.unlock(); + + return true; +} + +void OpenVRThread::refreshDeviceList() +{ + QStringList deviceList; + std::vector newDeviceIndices; + for (vr::TrackedDeviceIndex_t deviceIx=0; deviceIxGetTrackedDeviceClass( deviceIx ); + if (devClass == vr::TrackedDeviceClass_HMD + || devClass == vr::TrackedDeviceClass_Controller + || devClass == vr::TrackedDeviceClass_GenericTracker) + { + newDeviceIndices.push_back(deviceIx); + deviceList << GetTrackedDeviceString(m_vrsys, deviceIx); + } + } + if (newDeviceIndices.size() != m_deviceIndices.size() + || newDeviceIndices != m_deviceIndices) + { + m_deviceIndices = newDeviceIndices; + emit deviceListUpdated(deviceList); + } +} + +bool OpenVRThread::createOutlets() +{ + // Safely copy member variables to local variables. + this->mutex.lock(); + int desiredSRate = this->m_srate; + std::vector devInds = this->m_streamDeviceIndices; + this->mutex.unlock(); + + // Count channels + uint32_t nPoseChans = 0; + for(auto it=devInds.begin(); it < devInds.end(); it++ ) + { + // Every tracked device gets pose channels -> 12 for full transformation matrix. + nPoseChans += 12; + } + + if (nPoseChans <= 0) + { + return false; + } + + QStringList poseChanLabels; + poseChanLabels + << "00" << "01" << "02" << "X" + << "10" << "11" << "12" << "Y" + << "20" << "21" << "22" << "Z"; + + // Create lsl pose stream info + lsl::stream_info poseInfo( + "OpenVRPoses", "MoCap", + nPoseChans, desiredSRate, + lsl::cf_float32, "_openvrposes"); + // Append device meta-data + poseInfo.desc().append_child("acquisition") + .append_child_value("manufacturer", "Valve Software") + .append_child_value("model", "OpenVR"); + // Append channel info + lsl::xml_element poseInfoChannels = poseInfo.desc().append_child("channels"); + for(auto it=devInds.begin(); it < devInds.end(); it++ ) + { + QString devStr = QString::number(*it); + devStr += "_"; + for (int poseAx = 0; poseAx < poseChanLabels.size(); poseAx++) + { + QString chLabel = devStr; + chLabel.append(poseChanLabels[poseAx]); + poseInfoChannels.append_child("channel") + .append_child_value("label", chLabel.toStdString()) + .append_child_value("type", "Pose") + .append_child_value("unit", "m"); + } + } + + // Create lsl button stream info + // Up to 16 devices [0-15], non-contiguous vr::EVREventType [100-19999], up to 64 vr::EVRButtonId [0-64] + lsl::stream_info eventInfo("OpenVREvents", "Events", 1, + lsl::IRREGULAR_RATE, lsl::cf_int32, "_openvrevents"); + // Append device meta-data + eventInfo.desc().append_child("acquisition") + .append_child_value("manufacturer", "Valve Software") + .append_child_value("model", "OpenVR"); + // Append channel info + lsl::xml_element eventInfoChannel = eventInfo.desc().append_child("channels"); + eventInfoChannel.append_child("channel") + .append_child_value("label", "OpenVREvent") + .append_child_value("type", "EventCode") + .append_child_value("unit", "uint16_code"); + //TODO: MetaData to describe the event code structure. + + this->mutex.lock(); + this->m_poseOutlet = new lsl::stream_outlet(poseInfo); + this->m_buttonOutlet = new lsl::stream_outlet(eventInfo); + this->mutex.unlock(); + + return true; +} + +bool OpenVRThread::pollAndPush(vr::TrackedDevicePose_t *poseBuffers) +{ + bool pushedAny = false; + + this->mutex.lock(); +// int desiredSRate = this->m_srate; + std::vector devInds = this->m_streamDeviceIndices; + this->mutex.unlock(); + + // Events go first because we may be spinning waiting for poses later. + vr::VREvent_t event; + while (this->m_vrsys->PollNextEvent(&event, sizeof(event))) + { + if (std::find(m_streamDeviceIndices.begin(), m_streamDeviceIndices.end(), event.trackedDeviceIndex) != m_streamDeviceIndices.end()) + { + uint32_t evCode = event.trackedDeviceIndex * 10000000; + evCode += event.eventType * 100; + //vr::EVREventType evType = (vr::EVREventType) event.eventType; + + vr::ETrackedDeviceClass devClass = this->m_vrsys->GetTrackedDeviceClass(event.trackedDeviceIndex); + //TrackedDeviceClass_HMD, TrackedDeviceClass_Controller, TrackedDeviceClass_GenericTracker + if (devClass == vr::TrackedDeviceClass_Controller) + { + evCode += event.data.controller.button; + //vr::EVRButtonId buttonId = (vr::EVRButtonId) event.data.controller.button; + } + const int sample = evCode; + m_buttonOutlet->push_sample(&sample, lsl::local_clock() - event.eventAgeSeconds); + pushedAny = true; + } + } + + // Poses + double poseTime = lsl::local_clock(); + if ((poseTime - m_startTime) * m_srate > m_pushCounter) + { + std::vector poseSample(vr::k_unMaxTrackedDeviceCount * 3 * 4); + int deviceCountIx = 0; + this->m_vrsys->GetDeviceToAbsoluteTrackingPose((vr::ETrackingUniverseOrigin)m_originIndex, 0.0f, VR_ARRAY_COUNT(vr::k_unMaxTrackedDeviceCount) poseBuffers, vr::k_unMaxTrackedDeviceCount); + poseTime = lsl::local_clock(); // Update poseTime. + for (auto it = devInds.begin(); it < devInds.end(); it++, deviceCountIx++) + { + if (poseBuffers[*it].bPoseIsValid && poseBuffers[*it].bDeviceIsConnected) + { + vr::HmdMatrix34_t poseMat = poseBuffers[*it].mDeviceToAbsoluteTracking; + for (int row_ix = 0; row_ix < 3; row_ix++) + { + for (int col_ix = 0; col_ix < 4; col_ix++) + { + poseSample[(4*3*deviceCountIx) + 4*row_ix + col_ix] = + poseMat.m[row_ix][col_ix]; + } + } + } + } + poseSample.resize(devInds.size() * 3 * 4); + m_poseOutlet->push_sample(poseSample, poseTime); + pushedAny = true; + m_pushCounter++; + } + return pushedAny; +} + +void OpenVRThread::run() +{ + runPhase phase = phase_startLink; + vr::TrackedDevicePose_t rTrackedDevicePose[vr::k_unMaxTrackedDeviceCount]; + // Thread-safe copy member variables to local variables. + this->mutex.lock(); + this->mutex.unlock(); + + forever { + this->mutex.lock(); + if (this->abort) + phase = phase_shutdown; + this->mutex.unlock(); + + switch (phase) { + case phase_startLink: + if (connectToOpenVR()) + { + emit openvrConnected(true); + phase = phase_scanForDevices; + } + else + { + phase = phase_shutdown; + } + break; + case phase_scanForDevices: + if (this->m_bGoOutlets) + { + phase = phase_createOutlets; + } + else + { + refreshDeviceList(); + this->msleep(100); + } + break; + case phase_createOutlets: + if (createOutlets()) + { + emit outletsStarted(true); + m_startTime = lsl::local_clock(); + phase = phase_transferData; + } + else + { + phase = phase_shutdown; + } + break; + case phase_transferData: + if (!this->m_poseOutlet) + { + qDebug() << "Something went wrong. m_poseOutlet lost unexpectedly."; + phase = phase_shutdown; + break; + } + if (!this->m_bGoOutlets) + { + qDebug() << "Instructed to stop streaming."; + this->mutex.lock(); + this->m_poseOutlet = nullptr; + this->m_buttonOutlet = nullptr; + phase = phase_scanForDevices; + this->mutex.unlock(); + emit outletsStarted(false); + break; + } + if (!pollAndPush(rTrackedDevicePose)) + { + this->usleep(1); + } + break; + case phase_shutdown: + vr::VR_Shutdown(); + this->mutex.lock(); + this->m_poseOutlet = nullptr; + this->m_buttonOutlet = nullptr; + this->mutex.unlock(); + emit outletsStarted(false); + emit openvrConnected(false); + return; + break; + } + } +} diff --git a/Apps/OpenVR/src/openvrthread.h b/Apps/OpenVR/src/openvrthread.h new file mode 100644 index 00000000..57cad3c9 --- /dev/null +++ b/Apps/OpenVR/src/openvrthread.h @@ -0,0 +1,52 @@ +#ifndef CERELINKTHREAD_H +#define CERELINKTHREAD_H + +#include +#include +#include +#include "lsl_cpp.h" +#include "openvr.h" + +class OpenVRThread : public QThread +{ + Q_OBJECT + +public: + OpenVRThread(QObject *parent = 0); + ~OpenVRThread(); + + void initOpenVR(int srate); // Starts the thread. Passes parameters from GUI to OpenVRThread member variables. + void startStreams( + QStringList streamDeviceList = QStringList(), + int originIndex = vr::TrackingUniverseSeated); // Starts streams for indicated devices. Default is all devices. + +signals: + void openvrConnected(bool result); // Emitted after successful OpenVR initialization. + void deviceListUpdated(QStringList deviceList); // Emitted after a new device is detected. + void outletsStarted(bool result); // Emitted after LSL outlets are created. + +protected: + void run() override; + +private: + bool connectToOpenVR(); // Initialize OpenVR. If successful, device scanning will begin. + void refreshDeviceList(); // Scan for devices. + bool createOutlets(); // Create the outlets. + bool pollAndPush(vr::TrackedDevicePose_t *poseBuffers); + + QMutex mutex; + QWaitCondition condition; + bool abort; + vr::IVRSystem *m_vrsys; // The openvr subsysem. + int m_srate; // Desired pose sampling rate. + bool m_bGoOutlets; // Request to start streams has been made. + std::vector m_deviceIndices; // List of found devices indices. + std::vector m_streamDeviceIndices; // List of device indices for streams. + lsl::stream_outlet* m_poseOutlet; + lsl::stream_outlet* m_buttonOutlet; + uint64_t m_pushCounter; + double m_startTime; + int m_originIndex; +}; + +#endif // CERELINKTHREAD_H diff --git a/LSL/liblsl/src/api_config.cpp b/LSL/liblsl/src/api_config.cpp index 2525e4a2..e756ef9c 100644 --- a/LSL/liblsl/src/api_config.cpp +++ b/LSL/liblsl/src/api_config.cpp @@ -174,6 +174,7 @@ void api_config::load_from_file(const std::string &filename) { inlet_buffer_reserve_ms_ = pt.get("tuning.InletBufferReserveMs",5000); inlet_buffer_reserve_samples_ = pt.get("tuning.InletBufferReserveSamples",128); smoothing_halftime_ = pt.get("tuning.SmoothingHalftime",90.0f); + force_default_timestamps_ = pt.get("tuning.ForceDefaultTimestamps", false); } catch(std::exception &e) { std::cerr << "Error parsing config file " << filename << " (" << e.what() << "). Rolling back to defaults." << std::endl; diff --git a/LSL/liblsl/src/api_config.h b/LSL/liblsl/src/api_config.h index 8fa0a939..6ee45a42 100644 --- a/LSL/liblsl/src/api_config.h +++ b/LSL/liblsl/src/api_config.h @@ -167,7 +167,8 @@ namespace lsl { int inlet_buffer_reserve_samples() const { return inlet_buffer_reserve_samples_; } /// Default halftime of the time-stamp smoothing window (if enabled), in seconds. float smoothing_halftime() const { return smoothing_halftime_; } - + /// Override timestamps with lsl clock if True + bool force_default_timestamps() const { return force_default_timestamps_; } private: // Thread-safe initialization logic (boilerplate). @@ -220,6 +221,7 @@ namespace lsl { int inlet_buffer_reserve_ms_; int inlet_buffer_reserve_samples_; float smoothing_halftime_; + bool force_default_timestamps_; }; } diff --git a/LSL/liblsl/src/stream_outlet_impl.h b/LSL/liblsl/src/stream_outlet_impl.h index d9cb85c9..9a10fc01 100644 --- a/LSL/liblsl/src/stream_outlet_impl.h +++ b/LSL/liblsl/src/stream_outlet_impl.h @@ -83,7 +83,9 @@ namespace lsl { * @param pushthrough Whether to push the sample through to the receivers instead of buffering it into a chunk according to network speeds. */ void push_numeric_raw(void *data, double timestamp=0.0, bool pushthrough=true) { - sample_p smp(sample_factory_->new_sample(timestamp==0.0 ? lsl_clock() : timestamp, pushthrough)); + if (lsl::api_config::get_instance()->force_default_timestamps()) + timestamp = 0.0; + sample_p smp(sample_factory_->new_sample(timestamp == 0.0 ? lsl_clock() : timestamp, pushthrough)); smp->assign_untyped(data); send_buffer_->push_sample(smp); } @@ -172,7 +174,9 @@ namespace lsl { * Allocate and enqueue a new sample into the send buffer. */ template void enqueue(T* data, double timestamp, bool pushthrough) { - sample_p smp(sample_factory_->new_sample(timestamp==0.0 ? lsl_clock() : timestamp, pushthrough)); + if (lsl::api_config::get_instance()->force_default_timestamps()) + timestamp = 0.0; + sample_p smp(sample_factory_->new_sample(timestamp == 0.0 ? lsl_clock() : timestamp, pushthrough)); smp->assign_typed(data); send_buffer_->push_sample(smp); }