Commit f459913b authored by Thiago C. Santini's avatar Thiago C. Santini

Adds global timestamp reference as well as keyboard and parallel port triggers

Note that for the moment the triggers are only saved to their respective
files and not appended to journal entries (thus, also not transmitted
through the network).
parent 85584487
...@@ -20,7 +20,10 @@ SOURCES += \ ...@@ -20,7 +20,10 @@ SOURCES += \
$${TOP}/src/Calibration.cpp \ $${TOP}/src/Calibration.cpp \
$${TOP}/src/settings.cpp \ $${TOP}/src/settings.cpp \
$${TOP}/src/utils.cpp \ $${TOP}/src/utils.cpp \
src/ThreadManager.cpp $${TOP}/src/ThreadManager.cpp \
$${TOP}/src/ParallelPort.cpp \
$${TOP}/src/Trigger.cpp \
$${TOP}/src/GlobalTimer.cpp
HEADERS += \ HEADERS += \
$${TOP}/src/ImageProcessing.h \ $${TOP}/src/ImageProcessing.h \
...@@ -32,6 +35,10 @@ HEADERS += \ ...@@ -32,6 +35,10 @@ HEADERS += \
$${TOP}/src/utils.h \ $${TOP}/src/utils.h \
$${TOP}/src/NetworkStream.h \ $${TOP}/src/NetworkStream.h \
$${TOP}/src/Gui.h \ $${TOP}/src/Gui.h \
$${TOP}/src/ThreadManager.h \
$${TOP}/src/ParallelPort.h \
$${TOP}/src/Trigger.h \
$${TOP}/src/GlobalTimer.h \
$${TOP}/excuse/algo.h \ $${TOP}/excuse/algo.h \
$${TOP}/excuse/bw_select.h \ $${TOP}/excuse/bw_select.h \
$${TOP}/excuse/canny_ml.h \ $${TOP}/excuse/canny_ml.h \
...@@ -51,8 +58,7 @@ HEADERS += \ ...@@ -51,8 +58,7 @@ HEADERS += \
$${TOP}/else/blob_gen.h \ $${TOP}/else/blob_gen.h \
$${TOP}/else/canny_impl.h \ $${TOP}/else/canny_impl.h \
$${TOP}/else/filter_edges.h \ $${TOP}/else/filter_edges.h \
$${TOP}/else/find_best_edge.h \ $${TOP}/else/find_best_edge.h
src/ThreadManager.h
FORMS += \ FORMS += \
$${TOP}/src/Gui.ui $${TOP}/src/Gui.ui
......
...@@ -144,7 +144,7 @@ void Calibration::reset() ...@@ -144,7 +144,7 @@ void Calibration::reset()
double Calibration::dumpCalibrationInfo() double Calibration::dumpCalibrationInfo()
{ {
std::ofstream info(getNamePrefix().append(QString("calibration-%1.txt").arg(idx)).toStdString()); std::ofstream info(getNamePrefix().append(QString("calibration-%1.txt").arg(gFileIndexStr)).toStdString());
if (calibrationMatrix.empty()) { if (calibrationMatrix.empty()) {
info << "Not calibrated." << std::endl; info << "Not calibrated." << std::endl;
......
...@@ -42,7 +42,6 @@ public: ...@@ -42,7 +42,6 @@ public:
bool calibrated; bool calibrated;
bool calibrating; bool calibrating;
bool recording; bool recording;
QString idx;
private slots: private slots:
void endCollection(); void endCollection();
......
#include "GlobalTimer.h"
GlobalTimer::GlobalTimer()
{
offset = 0;
timer.start();
}
GlobalTimer::~GlobalTimer()
{
}
void GlobalTimer::setOffset(qint64 val)
{
QMutexLocker(&this->mutex);
offset = val - timer.elapsed();
}
qint64 GlobalTimer::timestamp()
{
QMutexLocker(&this->mutex);
if (!offset) // not synchronized yet
return -1;
return timer.elapsed() + offset;
}
qint64 GlobalTimer::elapsed()
{
QMutexLocker(&this->mutex);
return timer.elapsed();
}
#ifndef GLOBALTIMER_H
#define GLOBALTIMER_H
#include <QElapsedTimer>
#include <QMutex>
class GlobalTimer
{
public:
GlobalTimer();
~GlobalTimer();
void setOffset(qint64 val);
qint64 timestamp();
qint64 elapsed();
private:
QElapsedTimer timer;
QMutex mutex;
qint64 offset;
};
#endif // GLOBALTIMER_H
...@@ -53,6 +53,7 @@ Gui::Gui(QWidget *parent) : ...@@ -53,6 +53,7 @@ Gui::Gui(QWidget *parent) :
newWidth, newWidth,
newHeight newHeight
); );
} }
Gui::~Gui() Gui::~Gui()
...@@ -124,6 +125,7 @@ void Gui::update(JournalEntry journalEntry) ...@@ -124,6 +125,7 @@ void Gui::update(JournalEntry journalEntry)
} }
} }
// Field drawings // Field drawings
cv::Mat fieldCanvas; cv::Mat fieldCanvas;
...@@ -205,6 +207,15 @@ void Gui::mousePressEvent(QMouseEvent *event) ...@@ -205,6 +207,15 @@ void Gui::mousePressEvent(QMouseEvent *event)
} }
void Gui::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
default:
if (keyboardTrigger.is_open())
keyboardTrigger << gTimer.timestamp() << "\t" << event->text().toStdString() << std::endl;
}
}
void Gui::on_discardButton_clicked() void Gui::on_discardButton_clicked()
{ {
if (calibration.calibrating) if (calibration.calibrating)
...@@ -260,6 +271,7 @@ void Gui::on_startRecording_clicked() ...@@ -260,6 +271,7 @@ void Gui::on_startRecording_clicked()
ui->startCalibration->setEnabled(true); ui->startCalibration->setEnabled(true);
ui->changeSubject->setEnabled(true); ui->changeSubject->setEnabled(true);
qDebug() << "Finished recording for " << gCurrentSubjectName;
} }
else { else {
if (gCurrentSubjectName.isEmpty()) { if (gCurrentSubjectName.isEmpty()) {
...@@ -285,6 +297,8 @@ void Gui::on_startRecording_clicked() ...@@ -285,6 +297,8 @@ void Gui::on_startRecording_clicked()
return; return;
} }
} }
qDebug() << "Starting recording for " << gCurrentSubjectName;
recording = true; recording = true;
ui->startRecording->setText("Stop"); ui->startRecording->setText("Stop");
...@@ -355,8 +369,15 @@ void Gui::on_swapViews_clicked() ...@@ -355,8 +369,15 @@ void Gui::on_swapViews_clicked()
swapViews = !swapViews; swapViews = !swapViews;
} }
void Gui::dumpCalibration(QString idxStr) void Gui::recordingStarted()
{ {
calibration.idx = idxStr;
calibration.dumpCalibrationInfo(); calibration.dumpCalibrationInfo();
if (settings.value("trigger/keyboard").toBool())
keyboardTrigger.open(getNamePrefix().append(QString("keyboard-%1.txt").arg(gFileIndexStr)).toStdString());
}
void Gui::recordingStopped()
{
if (keyboardTrigger.is_open())
keyboardTrigger.close();
} }
#ifndef GUI_H #ifndef GUI_H
#define GUI_H #define GUI_H
#include <fstream>
#include <QMainWindow> #include <QMainWindow>
#include <QPainter> #include <QPainter>
#include <QMouseEvent> #include <QMouseEvent>
...@@ -29,13 +31,15 @@ public: ...@@ -29,13 +31,15 @@ public:
public slots: public slots:
void update(JournalEntry journalEntry); void update(JournalEntry journalEntry);
void dumpCalibration(QString idxStr); void recordingStarted();
void recordingStopped();
signals: signals:
void setRecording(bool val); void setRecording(bool val);
private slots: private slots:
void mousePressEvent(QMouseEvent *event); void mousePressEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
void on_discardButton_clicked(); void on_discardButton_clicked();
...@@ -53,6 +57,7 @@ private: ...@@ -53,6 +57,7 @@ private:
QString outputRootPath; QString outputRootPath;
bool swapViews; bool swapViews;
unsigned int blinkerCounter; unsigned int blinkerCounter;
std::ofstream keyboardTrigger;
}; };
#endif // GUI_H #endif // GUI_H
...@@ -57,7 +57,8 @@ static void updateSlave(ImageAcquisition *imageAcquisition) ...@@ -57,7 +57,8 @@ static void updateSlave(ImageAcquisition *imageAcquisition)
void ImageAcquisition::onThreadStarted(){ void ImageAcquisition::onThreadStarted(){
running = true; running = true;
slaveThread = QtConcurrent::run(updateSlave, this); if (slave)
slaveThread = QtConcurrent::run(updateSlave, this);
} }
void ImageAcquisition::onThreadFinishing() void ImageAcquisition::onThreadFinishing()
...@@ -98,7 +99,7 @@ ImageAcquisition::ImageAcquisition(QObject *parent) : QObject(parent), ...@@ -98,7 +99,7 @@ ImageAcquisition::ImageAcquisition(QObject *parent) : QObject(parent),
// IMPORTANT: we assume the OpenCV enumeration is identic to VideoMan's // IMPORTANT: we assume the OpenCV enumeration is identic to VideoMan's
VideoCapture cap(i); VideoCapture cap(i);
if( ! cap.isOpened() ) if( ! cap.isOpened() )
continue; continue;
int width = cap.get(CAP_PROP_FRAME_WIDTH); int width = cap.get(CAP_PROP_FRAME_WIDTH);
int height = cap.get(CAP_PROP_FRAME_HEIGHT); int height = cap.get(CAP_PROP_FRAME_HEIGHT);
cap.release(); cap.release();
...@@ -125,29 +126,6 @@ ImageAcquisition::ImageAcquisition(QObject *parent) : QObject(parent), ...@@ -125,29 +126,6 @@ ImageAcquisition::ImageAcquisition(QObject *parent) : QObject(parent),
videoManControl.freeAvailableDevicesList( &devList, devCount ); videoManControl.freeAvailableDevicesList( &devList, devCount );
if (cameras.size() < 2 ) {
QString masterName = settings.value("eyeCam/name").toString();
if (masterName.isEmpty())
masterName = "Empty";
QString tmp(QString("Current master: %1\n").arg(masterName));
QString slaveName = settings.value("fieldCam/name").toString();
if (slaveName.isEmpty())
slaveName = "Empty";
tmp.append(QString("Current slave: %1\n").arg(slaveName));
tmp.append("\nThese are the cameras I found:\n\n");
for (unsigned int i=0; i<tmpList.size(); i++)
tmp.append(camToStr(tmpList[i])).append("\n");
tmp.append("\nPossible solutions:\n");
tmp.append("1) Check your config file.\n");
tmp.append("2) Make sure no other program is using the cameras.\n");
tmp.append("3) Turn off the cameras and replug.\n");
tmp.append("4) Reboot your computer.\n");
QMessageBox::critical(NULL, "Failed to open cameras.", tmp, QMessageBox::Ok, QMessageBox::NoButton);
return;
}
master = NULL; master = NULL;
slave = NULL; slave = NULL;
for (unsigned int i=0; i<cameras.size(); i++) { for (unsigned int i=0; i<cameras.size(); i++) {
...@@ -170,11 +148,38 @@ ImageAcquisition::ImageAcquisition(QObject *parent) : QObject(parent), ...@@ -170,11 +148,38 @@ ImageAcquisition::ImageAcquisition(QObject *parent) : QObject(parent),
slave = camera; slave = camera;
} }
if ( !master || (!slave && !isSlave("dummy")) ) {
QString masterName = settings.value("eyeCam/name").toString();
if (masterName.isEmpty())
masterName = "Empty";
QString tmp(QString("Current master: %1\n").arg(masterName));
QString slaveName = settings.value("fieldCam/name").toString();
if (slaveName.isEmpty())
slaveName = "Empty";
tmp.append(QString("Current slave: %1\n").arg(slaveName));
tmp.append("\nThese are the cameras I found:\n\n");
for (unsigned int i=0; i<tmpList.size(); i++)
tmp.append(camToStr(tmpList[i])).append("\n");
tmp.append("\nPossible solutions:\n");
tmp.append("1) Check your config file.\n");
tmp.append("2) Make sure no other program is using the cameras.\n");
tmp.append("3) Turn off the cameras and replug.\n");
tmp.append("4) Reboot your computer.\n");
QMessageBox::critical(NULL, "Failed to open cameras.", tmp, QMessageBox::Ok, QMessageBox::NoButton);
return;
}
if (slave) { if (slave) {
qDebug() << "Slave is " << camToStr(*slave); qDebug() << "Slave is " << camToStr(*slave);
setUndistortionMaps( Size( slave->format.width, slave->format.height) ); setUndistortionMaps( Size( slave->format.width, slave->format.height) );
latestSlaveFrame = Mat::zeros(slave->format.width, slave->format.height, CV_8UC3); latestSlaveFrame = Mat::zeros(slave->format.width, slave->format.height, CV_8UC3);
putText(latestSlaveFrame, "No valid frame so far...", Point(40,latestSlaveFrame.cols/2), FONT_HERSHEY_SIMPLEX, 1, Scalar(255,255,255), 2); putText(latestSlaveFrame, "No valid frame so far...", Point(40,latestSlaveFrame.cols/2), FONT_HERSHEY_SIMPLEX, 1, Scalar(255,255,255), 2);
} else {
qDebug() << "Using dummy slave";
latestSlaveFrame = Mat::zeros(settings.value("fieldCam/outResY").toInt(), settings.value("fieldCam/outResX").toInt(), CV_8UC3);
putText(latestSlaveFrame, "No valid frame so far...", Point(40,latestSlaveFrame.cols/2), FONT_HERSHEY_SIMPLEX, 1, Scalar(255,255,255), 2);
} }
if (master) { if (master) {
...@@ -226,8 +231,10 @@ void ImageAcquisition::frameCallback(char *pixelBuffer, size_t input, double tim ...@@ -226,8 +231,10 @@ void ImageAcquisition::frameCallback(char *pixelBuffer, size_t input, double tim
void ImageAcquisition::frameCallbackInternal(char *pixelBuffer, size_t input, double timestamp) void ImageAcquisition::frameCallbackInternal(char *pixelBuffer, size_t input, double timestamp)
{ {
if (!ready) if (!ready) {
recordingStarted = GetTickCount64(); recordingStarted = GetTickCount64();
gTimer.setOffset(recordingStarted+1e3*timestamp);
}
ready = true; ready = true;
if (running && pixelBuffer != NULL) { if (running && pixelBuffer != NULL) {
...@@ -247,13 +254,13 @@ void ImageAcquisition::frameCallbackInternal(char *pixelBuffer, size_t input, do ...@@ -247,13 +254,13 @@ void ImageAcquisition::frameCallbackInternal(char *pixelBuffer, size_t input, do
cvtColor(entry.leftEyeFrame, entry.leftEyeFrame, CV_RGB2GRAY); // Assumes the pupil detection uses a grayscale input, may have to be changed later on cvtColor(entry.leftEyeFrame, entry.leftEyeFrame, CV_RGB2GRAY); // Assumes the pupil detection uses a grayscale input, may have to be changed later on
flip(entry.leftEyeFrame, entry.leftEyeFrame, settings.value("eyeCam/flip").toInt()); flip(entry.leftEyeFrame, entry.leftEyeFrame, settings.value("eyeCam/flip").toInt());
if (slave) { // videoMan getFrame function does not respect the wait=false parameter, so we have to update the image on another thread (see updateSlave)
// videoMan getFrame function does not respect the wait=false parameter, so we have to update the image on another thread (see updateSlave) slaveMutex.lock();
slaveMutex.lock(); latestSlaveFrame.copyTo(entry.fieldFrame);
latestSlaveFrame.copyTo(entry.fieldFrame); slaveMutex.unlock();
slaveMutex.unlock();
}
Scalar m = mean(entry.leftEyeFrame);
gIntensity = m[0];
entry.incIdx(); entry.incIdx();
emit imageAcquisitionDone(entry); emit imageAcquisitionDone(entry);
} }
......
...@@ -156,6 +156,7 @@ void Journal::setOutput() ...@@ -156,6 +156,7 @@ void Journal::setOutput()
file.setFileName(getNamePrefix().append(QString("journal-%1.txt").arg(fileIndexStr))); file.setFileName(getNamePrefix().append(QString("journal-%1.txt").arg(fileIndexStr)));
out.setDevice(&file); out.setDevice(&file);
file.open(QIODevice::WriteOnly | QIODevice::Text); file.open(QIODevice::WriteOnly | QIODevice::Text);
gFileIndexStr = fileIndexStr;
} }
void Journal::store(JournalEntry journalEntry) void Journal::store(JournalEntry journalEntry)
...@@ -185,7 +186,7 @@ void Journal::setRecording(bool val) ...@@ -185,7 +186,7 @@ void Journal::setRecording(bool val)
out << journalHeader; out << journalHeader;
int codec = CV_FOURCC('D', 'I', 'V', 'X'); int codec = CV_FOURCC('D', 'I', 'V', 'X');
//int codec = -1; //codec = -1;
fieldVideoWriter.open(QString("field"), fieldVideoWriter.open(QString("field"),
codec, codec,
...@@ -203,12 +204,13 @@ void Journal::setRecording(bool val) ...@@ -203,12 +204,13 @@ void Journal::setRecording(bool val)
false, false,
fileIndexStr); fileIndexStr);
emit dumpCalibration(fileIndexStr); emit recordingStarted();
} else { } else {
out.flush(); out.flush();
file.close(); file.close();
fieldVideoWriter.release(); fieldVideoWriter.release();
leftEyeVideoWriter.release(); leftEyeVideoWriter.release();
emit recordingStopped();
} }
} }
...@@ -242,8 +244,7 @@ void VideoWriterParts::nextVideoPart() ...@@ -242,8 +244,7 @@ void VideoWriterParts::nextVideoPart()
partIndex++; partIndex++;
cv::VideoWriter::open(name.toStdString(), codec, fps, size, color); cv::VideoWriter::open(name.toStdString(), codec, fps, size, color);
if (!isOpened()) { if (!isOpened()) {
std::cerr << "Could not open " << name.toStdString() << std::endl; qFatal(QString("Could not open %1").arg(name).toStdString().c_str());
exit(1);
} }
fileInfo.setFile(name); fileInfo.setFile(name);
} }
......
...@@ -101,7 +101,8 @@ public slots: ...@@ -101,7 +101,8 @@ public slots:
void onThreadFinishing(); void onThreadFinishing();
signals: signals:
void dumpCalibration(QString idxStr); void recordingStarted();
void recordingStopped();
private: private:
QDateTime startingDateTime; QDateTime startingDateTime;
......
#include "ParallelPort.h"
ParallelPort::ParallelPort()
{
QMutexLocker locker(&mutex);
log = NULL;
if ( settings.value("trigger/parallelPort").toBool()) {
std::wstring tmp(gBinaryPath.toStdWString());
tmp.append( L"/inpout32.dll" ) ;
hInpOutDll = LoadLibrary ( tmp.c_str() ) ;
if ( hInpOutDll != NULL ) {
fpOut32 = (lpOut32)GetProcAddress(hInpOutDll, "Out32");
fpInp32 = (lpInp32)GetProcAddress(hInpOutDll, "Inp32");
fpIsInpOutDriverOpen = (lpIsInpOutDriverOpen)GetProcAddress(hInpOutDll, "IsInpOutDriverOpen");
} else {
qDebug() << "Failed to load inpout library.";
}
period = 1000/settings.value("trigger/parallelPortPollingFrequency").toDouble();
log = new std::ofstream();
}
}
ParallelPort::~ParallelPort()
{
if (hInpOutDll)
FreeLibrary ( hInpOutDll ) ;
if (log) {
closeLog();
delete log;
}
}
void ParallelPort::openLog()
{
QMutexLocker locker(&mutex);
log->open(getNamePrefix().append(QString("parallelPort-%1.txt").arg(gFileIndexStr)).toStdString(), std::fstream::app);
}
void ParallelPort::closeLog()
{
QMutexLocker locker(&mutex);
if (log->is_open())
log->close();
}
void ParallelPort::poll()
{
QMutexLocker locker(&mutex);
if (!fpIsInpOutDriverOpen)
return;
if (log->is_open()) {
WORD wData = fpInp32(888);
//*log << gTimer.timestamp() << "\t" << wData << std::endl;
*log << gTimer.timestamp() << "," << wData << "," << gIntensity << std::endl;
}
}
#ifndef PARALLELPORT_H
#define PARALLELPORT_H
#include "windows.h"
#include <fstream>
#include <QMutexLocker>
#include <QDebug>
#include "settings.h"
#include "utils.h"
typedef void (__stdcall *lpOut32)(short, short);
typedef short (__stdcall *lpInp32)(short);
typedef BOOL (__stdcall *lpIsInpOutDriverOpen)(void);
typedef BOOL (__stdcall *lpIsXP64Bit)(void);
class ParallelPort
{
public:
ParallelPort();
~ParallelPort();
void poll();
double period;
void openLog();
void closeLog();
std::atomic<bool> recording;
private:
lpOut32 fpOut32;
lpInp32 fpInp32;
lpIsInpOutDriverOpen fpIsInpOutDriverOpen;
HINSTANCE hInpOutDll ;
std::ofstream *log;
QMutex mutex;
};
#endif // PARALLELPORT_H
#include "ThreadManager.h"
ThreadManager::ThreadManager(QObject *parent) : QObject(parent)
{
}
ThreadManager::~ThreadManager()
{
}
void ThreadManager::newThread(QObject &object, QString name, QThread::Priority priority)
{
QThread *thread = new QThread();
thread->setObjectName(name);
object.moveToThread(thread);
QThread::connect(thread, SIGNAL(started()), &object, SLOT(onThreadStarted()));
QThread::connect(this, SIGNAL(finish()), &object, SLOT(onThreadFinishing()));
QThread::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
threads.push_back(thread);
thread->start(priority);
}
void ThreadManager::finishThreads()
{
qDebug() << "Manager finishing...";
emit finish();
for (unsigned int i=0; i<threads.size(); i++) {
qDebug() << "[Waiting] " << threads[i]->objectName();