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 += \
$${TOP}/src/Calibration.cpp \
$${TOP}/src/settings.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 += \
$${TOP}/src/ImageProcessing.h \
......@@ -32,6 +35,10 @@ HEADERS += \
$${TOP}/src/utils.h \
$${TOP}/src/NetworkStream.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/bw_select.h \
$${TOP}/excuse/canny_ml.h \
......@@ -51,8 +58,7 @@ HEADERS += \
$${TOP}/else/blob_gen.h \
$${TOP}/else/canny_impl.h \
$${TOP}/else/filter_edges.h \
$${TOP}/else/find_best_edge.h \
src/ThreadManager.h
$${TOP}/else/find_best_edge.h
FORMS += \
$${TOP}/src/Gui.ui
......
......@@ -144,7 +144,7 @@ void Calibration::reset()
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()) {
info << "Not calibrated." << std::endl;
......
......@@ -42,7 +42,6 @@ public:
bool calibrated;
bool calibrating;
bool recording;
QString idx;
private slots:
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) :
newWidth,
newHeight
);
}
Gui::~Gui()
......@@ -124,6 +125,7 @@ void Gui::update(JournalEntry journalEntry)
}
}
// Field drawings
cv::Mat fieldCanvas;
......@@ -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()
{
if (calibration.calibrating)
......@@ -260,6 +271,7 @@ void Gui::on_startRecording_clicked()
ui->startCalibration->setEnabled(true);
ui->changeSubject->setEnabled(true);
qDebug() << "Finished recording for " << gCurrentSubjectName;
}
else {
if (gCurrentSubjectName.isEmpty()) {
......@@ -285,6 +297,8 @@ void Gui::on_startRecording_clicked()
return;
}
}
qDebug() << "Starting recording for " << gCurrentSubjectName;
recording = true;
ui->startRecording->setText("Stop");
......@@ -355,8 +369,15 @@ void Gui::on_swapViews_clicked()
swapViews = !swapViews;
}
void Gui::dumpCalibration(QString idxStr)
void Gui::recordingStarted()
{
calibration.idx = idxStr;
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
#define GUI_H
#include <fstream>
#include <QMainWindow>
#include <QPainter>
#include <QMouseEvent>
......@@ -29,13 +31,15 @@ public:
public slots:
void update(JournalEntry journalEntry);
void dumpCalibration(QString idxStr);
void recordingStarted();
void recordingStopped();
signals:
void setRecording(bool val);
private slots:
void mousePressEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
void on_discardButton_clicked();
......@@ -53,6 +57,7 @@ private:
QString outputRootPath;
bool swapViews;
unsigned int blinkerCounter;
std::ofstream keyboardTrigger;
};
#endif // GUI_H
......@@ -57,7 +57,8 @@ static void updateSlave(ImageAcquisition *imageAcquisition)
void ImageAcquisition::onThreadStarted(){
running = true;
slaveThread = QtConcurrent::run(updateSlave, this);
if (slave)
slaveThread = QtConcurrent::run(updateSlave, this);
}
void ImageAcquisition::onThreadFinishing()
......@@ -98,7 +99,7 @@ ImageAcquisition::ImageAcquisition(QObject *parent) : QObject(parent),
// IMPORTANT: we assume the OpenCV enumeration is identic to VideoMan's
VideoCapture cap(i);
if( ! cap.isOpened() )
continue;
continue;
int width = cap.get(CAP_PROP_FRAME_WIDTH);
int height = cap.get(CAP_PROP_FRAME_HEIGHT);
cap.release();
......@@ -125,29 +126,6 @@ ImageAcquisition::ImageAcquisition(QObject *parent) : QObject(parent),
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;
slave = NULL;
for (unsigned int i=0; i<cameras.size(); i++) {
......@@ -170,11 +148,38 @@ ImageAcquisition::ImageAcquisition(QObject *parent) : QObject(parent),
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) {
qDebug() << "Slave is " << camToStr(*slave);
setUndistortionMaps( Size( slave->format.width, slave->format.height) );
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);
} 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) {
......@@ -226,8 +231,10 @@ void ImageAcquisition::frameCallback(char *pixelBuffer, size_t input, double tim
void ImageAcquisition::frameCallbackInternal(char *pixelBuffer, size_t input, double timestamp)
{
if (!ready)
if (!ready) {
recordingStarted = GetTickCount64();
gTimer.setOffset(recordingStarted+1e3*timestamp);
}
ready = true;
if (running && pixelBuffer != NULL) {
......@@ -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
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)
slaveMutex.lock();
latestSlaveFrame.copyTo(entry.fieldFrame);
slaveMutex.unlock();
}
// videoMan getFrame function does not respect the wait=false parameter, so we have to update the image on another thread (see updateSlave)
slaveMutex.lock();
latestSlaveFrame.copyTo(entry.fieldFrame);
slaveMutex.unlock();
Scalar m = mean(entry.leftEyeFrame);
gIntensity = m[0];
entry.incIdx();
emit imageAcquisitionDone(entry);
}
......
......@@ -156,6 +156,7 @@ void Journal::setOutput()
file.setFileName(getNamePrefix().append(QString("journal-%1.txt").arg(fileIndexStr)));
out.setDevice(&file);
file.open(QIODevice::WriteOnly | QIODevice::Text);
gFileIndexStr = fileIndexStr;
}
void Journal::store(JournalEntry journalEntry)
......@@ -185,7 +186,7 @@ void Journal::setRecording(bool val)
out << journalHeader;
int codec = CV_FOURCC('D', 'I', 'V', 'X');
//int codec = -1;
//codec = -1;
fieldVideoWriter.open(QString("field"),
codec,
......@@ -203,12 +204,13 @@ void Journal::setRecording(bool val)
false,
fileIndexStr);
emit dumpCalibration(fileIndexStr);
emit recordingStarted();
} else {
out.flush();
file.close();
fieldVideoWriter.release();
leftEyeVideoWriter.release();
emit recordingStopped();
}
}
......@@ -242,8 +244,7 @@ void VideoWriterParts::nextVideoPart()
partIndex++;
cv::VideoWriter::open(name.toStdString(), codec, fps, size, color);
if (!isOpened()) {
std::cerr << "Could not open " << name.toStdString() << std::endl;
exit(1);
qFatal(QString("Could not open %1").arg(name).toStdString().c_str());
}
fileInfo.setFile(name);
}
......
......@@ -101,7 +101,8 @@ public slots:
void onThreadFinishing();
signals:
void dumpCalibration(QString idxStr);
void recordingStarted();
void recordingStopped();
private:
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();
threads[i]->exit();
threads[i]->wait();
qDebug() << "[Finished] " << threads[i]->objectName();
}
}
#ifndef THREADMANAGER_H
#define THREADMANAGER_H
#include <vector>
#include <QObject>
#include <QThread>
#include <QDebug>
using namespace std;
class ThreadManager : public QObject
{
Q_OBJECT
public:
explicit ThreadManager(QObject *parent = 0);
~ThreadManager();
void newThread(QObject &object, QString name, QThread::Priority priority);
void finishThreads();
signals:
void finish();
public slots:
private:
vector<QThread*> threads;
};
#endif // THREADMANAGER_H
#include "Trigger.h"
static void pollParallelPort(Trigger* trigger)
{
QElapsedTimer counter;
while (trigger->running) {
counter.start();
trigger->parallelPort.poll();
double sleepPeriod = trigger->parallelPort.period - counter.elapsed();
if (sleepPeriod > 0)
QThread::msleep(sleepPeriod);
}
}
Trigger::Trigger(QObject *parent) : QObject(parent)
{
}
Trigger::~Trigger()
{
}
void Trigger::onThreadStarted()
{
running = true;
// This would be MUCH more elegant using QTimer.
// However, QTimer is not monotonic so....
if (settings.value("trigger/parallelPort").toBool())
parallelPortThread = QtConcurrent::run(pollParallelPort, this);
}
void Trigger::onThreadFinishing()
{
running = false;
if ( parallelPortThread.isRunning() )
parallelPortThread.waitForFinished();
qDebug() << QThread::currentThread()->objectName() << " done.";
}
void Trigger::recordingStarted()
{
parallelPort.closeLog();
parallelPort.openLog();
}
void Trigger::recordingStopped()
{
parallelPort.closeLog();
}
#ifndef TRIGGER_H
#define TRIGGER_H
#include <atomic>
#include <QObject>
#include <QThread>
#include <QElapsedTimer>
#include <QtConcurrent/QtConcurrentRun>
#include <QDebug>
#include "ParallelPort.h"
#include "settings.h"
#include "utils.h"
class Trigger : public QObject
{
Q_OBJECT
public:
explicit Trigger(QObject *parent = 0);
~Trigger();
std::atomic<bool> running;
std::atomic<bool> recording;
ParallelPort parallelPort;
signals:
public slots:
void onThreadStarted();
void onThreadFinishing();
void recordingStarted();
void recordingStopped();
private:
QFuture<void> parallelPortThread;
};
#endif // TRIGGER_H
#include <iostream>
#include <fstream>
#include <string>
#include <QtWidgets>
#include <QTime>