Browse Source

First commit

Ioannis Agtzidis 5 năm trước cách đây
commit
a032c80fa1
49 tập tin đã thay đổi với 7018 bổ sung0 xóa
  1. 793 0
      GTA-VI/ArffWidgetBase.cpp
  2. 178 0
      GTA-VI/ArffWidgetBase.h
  3. 77 0
      GTA-VI/ArffWidgetCoord.cpp
  4. 39 0
      GTA-VI/ArffWidgetCoord.h
  5. 189 0
      GTA-VI/ArffWidgetSpeed.cpp
  6. 47 0
      GTA-VI/ArffWidgetSpeed.h
  7. 222 0
      GTA-VI/EquirectangularToFovBase.cpp
  8. 66 0
      GTA-VI/EquirectangularToFovBase.h
  9. 86 0
      GTA-VI/EquirectangularToFovGaze.cpp
  10. 28 0
      GTA-VI/EquirectangularToFovGaze.h
  11. 170 0
      GTA-VI/EquirectangularToFovSpeed.cpp
  12. 51 0
      GTA-VI/EquirectangularToFovSpeed.h
  13. 105 0
      GTA-VI/EquirectangularToFovVideo.cpp
  14. 210 0
      GTA-VI/EquirectangularToFovVideo.cu
  15. 60 0
      GTA-VI/EquirectangularToFovVideo.h
  16. 58 0
      GTA-VI/EquirectangularToHead.cpp
  17. 24 0
      GTA-VI/EquirectangularToHead.h
  18. 604 0
      GTA-VI/FlowWidget.cpp
  19. 140 0
      GTA-VI/FlowWidget.h
  20. 106 0
      GTA-VI/GTA-VI.cpp
  21. 143 0
      GTA-VI/GTA-VI.pro
  22. 96 0
      GTA-VI/GazeSpeed.cpp
  23. 30 0
      GTA-VI/GazeSpeed.h
  24. 542 0
      GTA-VI/MainWindow.cpp
  25. 149 0
      GTA-VI/MainWindow.h
  26. 182 0
      GTA-VI/MediaPlayer.cpp
  27. 84 0
      GTA-VI/MediaPlayer.h
  28. 148 0
      GTA-VI/PaintGaze.cpp
  29. 64 0
      GTA-VI/PaintGaze.h
  30. 36 0
      GTA-VI/Types.h
  31. 8 0
      GTA-VI/Unused.h
  32. 172 0
      GTA-VI/Util.cpp
  33. 33 0
      GTA-VI/Util.h
  34. 307 0
      GTA-VI/VideoExtractor.cpp
  35. 61 0
      GTA-VI/VideoExtractor.h
  36. 173 0
      GTA-VI/VideoWidget.cpp
  37. 84 0
      GTA-VI/VideoWidget.h
  38. 228 0
      README.md
  39. 202 0
      README_360.md
  40. 116 0
      arffHelper/Arff.cpp
  41. 64 0
      arffHelper/Arff.h
  42. 307 0
      arffHelper/ArffBase.cpp
  43. 91 0
      arffHelper/ArffBase.h
  44. 123 0
      arffHelper/ArffOps.cpp
  45. 26 0
      arffHelper/ArffOps.h
  46. 32 0
      arffHelper/ArffUtil.cpp
  47. 19 0
      arffHelper/ArffUtil.h
  48. 144 0
      arffHelper/AttributeTypes.cpp
  49. 101 0
      arffHelper/AttributeTypes.h

+ 793 - 0
GTA-VI/ArffWidgetBase.cpp

@@ -0,0 +1,793 @@
+// ArffWidgetBase.cpp
+
+#include "ArffWidgetBase.h"
+#include "../arffHelper/ArffUtil.h"
+#include "Unused.h"
+
+#include <QTime>
+#include <cassert>
+#include <iostream>
+
+#define MIN_WINDOW_DUR 100000
+#define MAX_WINDOW_DUR 90000000
+#define LOW_ALPHA 150
+#define DEFAULT_ALPHA 180
+#define HIGH_ALPHA 200
+
+using namespace std;
+using namespace std::chrono;
+
+// PUBLIC SLOTS:
+
+void ArffWidgetBase::HandleWindowDur(int dur_us, QObject *pSender)
+{
+    if (pSender == this)
+        return;
+
+    blockSignals(true);
+
+    SetWindowDuration(dur_us);
+
+    blockSignals(false);
+}
+
+void ArffWidgetBase::HandleTime(int curTime_us, QObject *pSender)
+{
+    if (pSender == this)
+        return;
+
+    blockSignals(true);
+
+    SetTime(curTime_us);
+
+    blockSignals(false);
+}
+
+void ArffWidgetBase::HandleUpdate()
+{
+    Update();
+}
+
+void ArffWidgetBase::HandleSelectedEyeMovement(int eyeMovement)
+{
+    m_selectedEyeMovement = eyeMovement;
+    if (m_IsPressedLeft)
+    {
+        ChangeInterval(m_selectedEyeMovement);
+        emit SendUpdate();
+    }
+}
+
+/*virtual*/ void ArffWidgetBase::HandleToggleView()
+{
+    if (m_pXYArff && m_pXYSecArff)
+    {
+        swap(m_pXYArff, m_pXYSecArff);
+        swap(m_windowMaxVal, m_windowSecMaxVal);
+            
+        int confInd;
+        bool res = ArffUtil::GetTXYCindex(m_pArff, m_timeInd, m_xInd, m_yInd, confInd);
+        UNUSED(res);
+
+        update();
+    }
+}
+
+
+// PUBLIC:
+
+ArffWidgetBase::ArffWidgetBase(QWidget *) : m_pArff(0), m_pXYArff(0), m_pXYSecArff(0), m_intervalAtt(-1), m_LeftPressPosition(-1), m_prevDragPosX(-1)
+{
+    InitializeLegend();
+    m_windowDuration = 10000000; // 10s duration
+    m_gridTickX = m_windowDuration/50; // 200ms tick
+    m_gridTickY = 100; // 100 points of whatever it is(pixels, pixels/s, ...)
+    m_windowMaxVal = 1.0;
+    m_winStartTime = 0;
+    m_pointerStart = 0;
+    m_pointerEnd = 0;
+    m_intervalLength = 0;
+    m_attToPaint = 0;
+    m_selectedEyeMovement = 0;
+    m_IsPressedLeft = false;
+
+    ClearEventVariables();
+    setFocusPolicy(Qt::ClickFocus); // set focus policy by clicking
+
+    setAttribute(Qt::WA_NoSystemBackground);
+}
+
+void ArffWidgetBase::SetGridInterval(int tickX, int tickY)
+{
+    m_gridTickX = tickX;
+    m_gridTickY = tickY;
+}
+
+void ArffWidgetBase::SetWindowDuration(int duration)
+{
+    // return for very small or big durations
+    if (duration < MIN_WINDOW_DUR || duration > MAX_WINDOW_DUR)
+        return;
+    m_windowDuration = duration;
+
+    Update();
+}
+
+void ArffWidgetBase::SetTime(int currentTime)
+{
+    m_currentTime = currentTime;
+    m_winStartTime = currentTime - m_windowDuration/2;
+
+    Update();
+}
+
+void ArffWidgetBase::SetData(Arff &arff, int attToPaint, double maxValue)
+{
+    m_pArff = &arff;
+    m_pXYArff = &arff;
+    int confInd;
+    bool res = ArffUtil::GetTXYCindex(m_pArff, m_timeInd, m_xInd, m_yInd, confInd);
+    if (res)
+        InitializeLegend(attToPaint);
+    
+    m_pointerStart = 0;
+    m_pointerEnd = 0;
+    m_intervalLength = 0;
+    m_windowMaxVal = maxValue;
+    if (maxValue < 0)
+        m_windowMaxVal = geometry().height();
+    m_attToPaint = attToPaint;
+
+    SetTime(0);
+}
+
+void ArffWidgetBase::SetFovData(Arff &arff, double maxValue)
+{
+    m_pXYSecArff = &arff;
+    m_windowSecMaxVal = maxValue;
+}
+
+void ArffWidgetBase::SetIntervalAtt(int intervalAtt)
+{
+    m_intervalAtt = intervalAtt;
+}
+
+QSize ArffWidgetBase::minimumSizeHint() const
+{
+    return QSize(160, 90);
+}
+
+void ArffWidgetBase::Update()
+{
+    m_gridTickX = m_windowDuration/50; 
+    // reset counters only when data have been set
+    m_pointerStart = 0;
+    m_pointerEnd = 0;
+    m_intervalLength = 1;
+    m_DoubleLeftPressPos = -1;
+
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+    if (rows == 0)
+        return;
+
+    m_pointerEnd = ArffUtil::FindPosition(m_pArff, m_timeInd, m_winStartTime + m_windowDuration);
+    m_pointerStart = ArffUtil::FindPosition(m_pArff, m_timeInd, m_winStartTime);
+    m_intervalLength = m_pointerEnd - m_pointerStart + 1;
+
+    // bring intervals up-to-date
+    CalculateIntervals();
+
+    update();
+}
+
+void ArffWidgetBase::MoveToStart()
+{
+    if (m_pArff == NULL)
+        return;
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+    if (rows == 0)
+        return;
+
+    int startingTime = (*m_pArff)[0][m_timeInd];
+
+    emit SendTime(startingTime);
+
+    SetTime(startingTime);
+}
+
+// PROTECTED:
+void ArffWidgetBase::paintEvent(QPaintEvent *)
+{   
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+    // if no data points exist 
+    if (rows == 0)
+        return;
+    
+    QPainter painter(this);
+    //painter.setRenderHint(QPainter::Antialiasing, true);
+    
+    PaintBase(&painter);
+    //PaintLine(&painter); 
+}
+
+void ArffWidgetBase::PaintBase(QPainter *painter)
+{
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+    // if no data points exist 
+    if (rows == 0)
+        return;
+
+    PaintAreas(painter);
+    PaintGrid(painter); 
+    PaintVideoTime(painter);
+    PaintText(painter);
+    PaintLine(painter);
+    PaintLegend(painter);
+}
+
+void ArffWidgetBase::PaintAreas(QPainter *painter)
+{
+    QRect geom = geometry();
+    int attValue;
+
+    // draw all intervals
+    for (int i=0; i<(int)m_vIntervals.size()-1; i++)
+    {
+        double start_width = (double)geom.width()*((*m_pArff)[m_pointerStart+m_vIntervals[i]][m_timeInd] - (double)m_winStartTime)/(double)(m_windowDuration);
+        double end_width = (double)geom.width()*((*m_pArff)[m_pointerStart+m_vIntervals[i+1]-1][m_timeInd] - (double)m_winStartTime)/(double)(m_windowDuration);
+        double length_width = end_width-start_width;
+
+        attValue = (*m_pArff)[m_pointerStart+m_vIntervals[i]][m_attToPaint];
+        attValue = attValue >= m_colorNum?0:attValue;
+        // check if clicked interval
+        if (m_IsPressedLeft && m_LeftPressPosition >= start_width && m_LeftPressPosition < end_width)
+        {
+            painter->setBrush(QBrush(QColor(m_vLegendColors[attValue][0],m_vLegendColors[attValue][1],m_vLegendColors[attValue][2],HIGH_ALPHA)));
+        }
+        else
+        {
+            painter->setBrush(QBrush(QColor(m_vLegendColors[attValue][0],m_vLegendColors[attValue][1],m_vLegendColors[attValue][2],LOW_ALPHA)));
+        }
+
+        // paint rectangle
+        painter->drawRect(QRect(start_width,0, length_width, geom.height()));
+    
+    }
+}
+
+void ArffWidgetBase::PaintLine(QPainter *painter)
+{
+    UNUSED(*painter);
+}
+
+void ArffWidgetBase::PaintVideoTime(QPainter *painter)
+{
+    QRect geom = geometry();
+    QPoint points[2];
+    painter->setPen(QPen(Qt::black, 8.0*geom.height()/1000.0));
+
+    double x = (double)geom.width()*(m_currentTime - m_winStartTime)/(double)m_windowDuration;
+    points[0].setX((int)x);
+    points[1].setX((int)x);
+
+    // draw upper part
+    points[0].setY(0);
+    points[1].setY(geom.height()/8);
+    painter->drawPolyline(points, 2);
+
+    // draw lower part
+    points[0].setY(geom.height());
+    points[1].setY(geom.height()*0.875);
+    painter->drawPolyline(points, 2);
+}
+
+void ArffWidgetBase::PaintGrid(QPainter *painter)
+{
+    QRect geom = geometry();
+    long int rest;
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+    if (rows > 0)
+        rest = (m_winStartTime-(int)(*m_pArff)[0][m_timeInd]) % m_gridTickX;
+    else
+        rest = 0;
+
+    long int gridTime = m_gridTickX - rest;
+    QPoint points[2];
+    painter->setPen(QPen(Qt::darkGray, 2.0*geom.height()/1000.0, Qt::DashLine));
+    while (gridTime < m_windowDuration)
+    {
+        // calculate points
+        points[0].setX((double)geom.width()*(double)gridTime/(double)m_windowDuration);
+        points[1].setX(points[0].x());
+
+        points[0].setY(0);
+        points[1].setY(geom.height());
+
+        // draw line
+        painter->drawPolyline(points, 2);
+        
+        // update time position
+        gridTime += m_gridTickX;
+    }
+}
+
+void ArffWidgetBase::PaintText(QPainter *painter)
+{
+    QRect geom = geometry();
+    // paint window start time
+    painter->setPen(QPen(Qt::black));
+    int precision;
+    if (m_winStartTime < 1000000)
+        precision=1;
+    else if (m_winStartTime < 10000000)
+        precision = 2;
+    else
+        precision=3;
+    painter->drawText(0,geom.height()-5, QString::number((double)m_winStartTime/(double)1000000, 'g', precision));
+
+    // paint current time
+    const QLocale & cLocale = QLocale::c();
+    QString sTime = cLocale.toString(m_winStartTime+m_windowDuration/2);
+    sTime.replace(cLocale.groupSeparator(), ",");
+    painter->drawText(geom.width()/2+30,geom.height()-5, sTime + QString(" us"));
+
+    // paint window end time
+    if (m_winStartTime+m_windowDuration < 1000000)
+        precision=1;
+    else if (m_winStartTime+m_windowDuration < 10000000)
+        precision = 2;
+    else
+        precision=3;
+    painter->drawText(geom.width() - 35 - 5*precision,geom.height()-5, QString::number((double)(m_winStartTime+m_windowDuration)/(double)1000000, 'g', precision));
+}
+
+void ArffWidgetBase::PaintLegend(QPainter *painter)
+{
+    QRect geom = geometry();
+    int width = geom.width();
+    int height = geom.height();
+
+    QWidget *pParent = nativeParentWidget();
+    if (pParent)
+    {
+        QRect parGeom = pParent->geometry();
+        width = parGeom.width();
+        height = parGeom.height();
+    }
+    
+    int legendHeight = height/60;
+    int legendWidth = width/20;
+
+    // scale font size
+    QFont font = painter->font();
+    font.setPointSize(legendHeight/1.5);
+    painter->setFont(font);
+
+    painter->setPen(QPen(Qt::black));
+    for (int i=0; i<m_colorNum; i++)
+    {
+        // paint white background
+        painter->setBrush(QBrush(QColor(255,255,255,255)));
+        painter->drawRect(QRect(0,i*legendHeight, legendWidth, legendHeight));
+        // paint attribute color
+        painter->setBrush(QBrush(QColor(m_vLegendColors[i][0],m_vLegendColors[i][1],m_vLegendColors[i][2],DEFAULT_ALPHA)));
+        painter->drawRect(QRect(0,i*legendHeight, legendWidth, legendHeight));
+
+        // paint text
+        QString label = QString::number(i) + QString(" ") + QString::fromStdString(m_vLegendCaption[i]);
+        painter->drawText(QRect(0,i*legendHeight, legendWidth, legendHeight), 0, label);
+    }
+}
+
+void ArffWidgetBase::mousePressEvent(QMouseEvent *event)
+{
+    if (event->button() == Qt::LeftButton)
+    {
+        m_IsPressedLeft = true;
+        m_LeftPressPosition = event->x();
+        //setCursor(Qt::SplitHCursor);
+
+        update();
+
+    }
+    else if (event->button() == Qt::RightButton)
+    {
+        setCursor(Qt::ClosedHandCursor);
+        m_RightPressPosition = event->x();
+    }
+    else // call default otherwise
+        QWidget::mousePressEvent(event);
+}
+
+void ArffWidgetBase::mouseDoubleClickEvent(QMouseEvent *event)
+{
+    if (event->button() == Qt::LeftButton)
+    {
+        m_DoubleLeftPressPos = event->x();
+        InsertInterval();
+    }
+    else
+        QWidget::mouseDoubleClickEvent(event);
+}
+
+void ArffWidgetBase::mouseReleaseEvent(QMouseEvent *event)
+{
+    if (event->button() == Qt::LeftButton)
+    {
+        setCursor(Qt::ArrowCursor);
+
+        m_IsPressedLeft = false;
+        m_prevDragPosX = -1;
+        update(); // only repaint
+    }
+    else if (event->button() == Qt::RightButton)
+    {
+        setCursor(Qt::ArrowCursor);
+        m_RightPressPosition = -1;
+    }
+    else // call default otherwise
+        QWidget::mousePressEvent(event);
+}
+
+void ArffWidgetBase::mouseMoveEvent(QMouseEvent *event)
+{
+    if (m_IsPressedLeft)
+    {
+        if (m_attToPaint < 0 || m_attToPaint > (int)(*m_pArff)[m_pointerStart].size()-1)
+            return;
+
+        if (m_prevDragPosX < 0)
+        {
+            m_prevDragPosX = event->x();
+            return;
+        }
+
+        // if any other element is assigned a different label
+        // we update the intevals and keep the interval selection active
+
+        // find selected interval limits
+        QRect geom = geometry();
+        double start_width = 0, end_width;
+        int intPointer=-1;
+        for (int i=0; i<(int)m_vIntervals.size()-1; i++)
+        {
+            end_width = (double)geom.width()*((*m_pArff)[m_pointerStart+m_vIntervals[i+1]-1][m_timeInd] - (double)m_winStartTime)/(double)(m_windowDuration);
+
+            if (m_LeftPressPosition>start_width && m_LeftPressPosition<end_width)
+            {
+                intPointer = i;
+                break;
+            }
+
+            start_width = end_width;
+        }
+
+        // not within interval, update left press position
+        if (intPointer == -1)
+        {
+            m_LeftPressPosition = event->x();
+            intPointer = m_vIntervals.size()-1;
+            return;
+        }
+
+        double distLeft = event->x() - start_width;
+        double distRight = end_width - event->x();
+        bool changeLeft = false;
+        double maxBorderDist = 0.05 * (double)geom.width();
+        if (distLeft > maxBorderDist && distRight > maxBorderDist)
+            return;
+        else if (distLeft < distRight)
+            changeLeft = true;
+
+        setCursor(Qt::SplitHCursor);
+
+        double dir = event->x() - m_prevDragPosX;
+        bool moveLeft = false;
+        if (dir < 0)
+            moveLeft = true;
+
+        if (changeLeft && moveLeft)
+        {
+            ExpandIntLeft(intPointer, event->x());
+        }
+        else if (changeLeft && !moveLeft)
+        {
+            ExpandIntRight(intPointer, event->x());
+        }
+        else if (!changeLeft && moveLeft)
+        {
+            intPointer++;
+            ExpandIntLeft(intPointer, event->x());
+        }
+        else if (!changeLeft && !moveLeft)
+        {
+            intPointer++;
+            ExpandIntRight(intPointer, event->x());
+        }
+
+        m_prevDragPosX = event->x();
+
+        emit SendUpdate();
+    }
+    else if (m_RightPressPosition != -1)
+    {
+        QRect geom = geometry();
+        double displacement = (double)(event->x()-m_RightPressPosition)/(double)(geom.width());
+        displacement *= (double)m_windowDuration;
+
+        m_RightPressPosition = event->x();
+
+        int currentTime = m_currentTime - displacement;
+        emit SendTime(currentTime);
+        
+        SetTime(currentTime);
+    }
+    else
+        QWidget::mouseMoveEvent(event);
+}
+
+void ArffWidgetBase::ExpandIntLeft(int intPointer, double newLimit)
+{
+    if (intPointer < 0 || intPointer >= (int)m_vIntervals.size())
+        return;
+
+    int attValue = (*m_pArff)[m_pointerStart+m_vIntervals[intPointer]][m_attToPaint];
+    QRect geom = geometry();
+
+    int endPoint = m_vIntervals[intPointer];
+    int startPoint = endPoint;
+    while (startPoint > 0 && newLimit < (double)geom.width()*((*m_pArff)[m_pointerStart + startPoint][m_timeInd] - (double)m_winStartTime)/(double)(m_windowDuration))
+        startPoint--;
+
+    int attValueLeft = (*m_pArff)[m_pointerStart+startPoint][m_attToPaint];
+    if (attValueLeft == attValue)
+        return;
+
+    for (int i=startPoint; i<endPoint; i++)
+        m_pArff->ChangeData(m_pointerStart + i, m_attToPaint, attValue);
+}
+
+void ArffWidgetBase::ExpandIntRight(int intPointer, double newLimit)
+{
+    if (intPointer < 0 || intPointer >= (int)m_vIntervals.size())
+        return;
+
+    int arffIndex = m_pointerStart+m_vIntervals[intPointer];
+    int attValue = (*m_pArff)[arffIndex][m_attToPaint];
+    QRect geom = geometry();
+
+    attValue = (*m_pArff)[m_pointerStart+m_vIntervals[intPointer-1]][m_attToPaint];
+
+    int startPoint = m_vIntervals[intPointer-1];
+    int endPoint = startPoint;
+    while (endPoint<m_intervalLength && newLimit>(double)geom.width()*((*m_pArff)[m_pointerStart+endPoint][m_timeInd] - (double)m_winStartTime)/(double)(m_windowDuration))
+        endPoint++;
+
+    int attValueRight = (*m_pArff)[m_pointerStart+endPoint-1][m_attToPaint];
+    if (attValue == attValueRight)
+        return;
+
+    for (int i=startPoint; i<endPoint; i++)
+        m_pArff->ChangeData(m_pointerStart + i, m_attToPaint, attValue);
+}
+
+void ArffWidgetBase::wheelEvent(QWheelEvent *event)
+{
+    QRect geom = geometry();
+    // find where the roll took place
+    double pointerTime = m_winStartTime + ((double)event->x()/(double)geom.width())*(double)m_windowDuration;
+
+    // set new window duration
+    if (event->delta() < 0)
+        SetWindowDuration(2*m_windowDuration);
+    else
+        SetWindowDuration(m_windowDuration/2);
+
+    emit SendWindowDur(m_windowDuration);
+
+    int winStartTime = pointerTime-((double)event->x()/(double)geom.width())*m_windowDuration;
+    int currentTime = winStartTime + m_windowDuration/2;
+
+    // set time for the new duration
+    SetTime(currentTime);
+    emit SendTime(currentTime);
+}
+
+void ArffWidgetBase::keyPressEvent(QKeyEvent *event)
+{
+    int key = event->key();
+
+    if (key == Qt::Key_Delete)
+        DeleteInterval();
+    else if (key == Qt::Key_Insert)
+        InsertInterval();
+    else
+        QWidget::keyPressEvent(event);
+}
+
+void ArffWidgetBase::DeleteInterval()
+{
+    ChangeInterval(0);
+}
+
+void ArffWidgetBase::InsertInterval()
+{
+    if (m_selectedEyeMovement >= m_colorNum)
+        return;
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+    if (rows == 0)
+        return;
+    
+    QRect geom = geometry();
+    double pressPos = 0;
+    if (m_DoubleLeftPressPos >= 0)
+        pressPos = m_DoubleLeftPressPos;
+    else
+        pressPos = m_LeftPressPosition;
+
+    long int leftPressTime = (long int)(m_windowDuration*pressPos/(double)geom.width()) + m_winStartTime;
+    if (leftPressTime < (*m_pArff)[0][m_timeInd] || leftPressTime > (*m_pArff)[rows-1][m_timeInd])
+        return;
+
+    int startIndex, endIndex;
+    if (m_DoubleLeftPressPos >= 0)
+    {
+        if (m_intervalAtt < 0)
+        {
+            Update();
+            return;
+        }
+
+        int index = (int) ArffUtil::FindPosition(m_pArff, m_timeInd, leftPressTime);
+        double labelOtherAtt = (*m_pArff)[index][m_intervalAtt];
+        double labelPaintAtt = (*m_pArff)[index][m_attToPaint];
+        startIndex = index;
+        endIndex = index;
+        while (startIndex >= 0 && (*m_pArff)[startIndex][m_intervalAtt] == labelOtherAtt && (*m_pArff)[startIndex][m_attToPaint] == labelPaintAtt)
+            startIndex--;
+        startIndex++;
+        while (endIndex < rows && (*m_pArff)[endIndex][m_intervalAtt] == labelOtherAtt && (*m_pArff)[endIndex][m_attToPaint] == labelPaintAtt)
+            endIndex++;
+        endIndex--;
+    }
+    else
+    {
+        int intDuration = 40000; // 40ms on each side
+        startIndex = (int) ArffUtil::FindPosition(m_pArff, m_timeInd, leftPressTime - intDuration);
+        endIndex = (int) ArffUtil::FindPosition(m_pArff, m_timeInd, leftPressTime + intDuration);
+    }
+
+    for (int i=startIndex; i<=endIndex; i++)
+        m_pArff->ChangeData(i, m_attToPaint, m_selectedEyeMovement);
+
+    emit SendUpdate();
+}
+
+void ArffWidgetBase::ChangeInterval(int newValue)
+{
+    if (m_selectedEyeMovement >= m_colorNum)
+        return;
+
+    QRect geom = geometry();
+    int activeInterval=-1;
+
+    // find the selected interval
+    for (int i=0; i<(int)m_vIntervals.size()-1; i++)
+    {
+        double start_width = (double)geom.width()*((*m_pArff)[m_pointerStart+m_vIntervals[i]][m_timeInd] - (double)m_winStartTime)/(double)(m_windowDuration);
+        double end_width = (double)geom.width()*((*m_pArff)[m_pointerStart+m_vIntervals[i+1]-1][m_timeInd] - (double)m_winStartTime)/(double)(m_windowDuration);
+
+        if (m_LeftPressPosition >= start_width && m_LeftPressPosition < end_width)
+        {
+            activeInterval = i;
+            break;
+        }
+    }
+
+    // click between intervals, nothing to change
+    if (activeInterval == -1)
+        return;
+
+    // set elements of selected interval to unassigned
+    for (int i=m_vIntervals[activeInterval]; i<m_vIntervals[activeInterval+1]; i++)
+        m_pArff->ChangeData(m_pointerStart+i, m_attToPaint, newValue);
+
+    emit SendUpdate();
+}
+
+void ArffWidgetBase::CalculateIntervals()
+{
+    // iterate through data points and use asked attribute
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+    if (rows == 0 || m_attToPaint < 0 || m_attToPaint > columns-1)
+        return;
+
+    m_vIntervals.clear();
+    m_vIntervals.push_back(0);
+
+    int attValue = (*m_pArff)[m_pointerStart][m_attToPaint];
+
+    for (int i=1; i<m_intervalLength; i++)
+    {
+        if (attValue != (*m_pArff)[m_pointerStart+i][m_attToPaint])
+        {
+            m_vIntervals.push_back(i); // add to intervals
+
+            // re-assign values
+            attValue = (*m_pArff)[m_pointerStart+i][m_attToPaint];
+        }
+    }
+
+    // last interval that is within the window
+    if (m_intervalLength > 1)
+        m_vIntervals.push_back(m_intervalLength-1); // add interval length to intervals
+}
+
+void ArffWidgetBase::ClearEventVariables()
+{
+    m_LeftPressPosition = -1;
+}
+
+void ArffWidgetBase::InitializeLegend()
+{
+    // Currently up to 10 eye movement are possible
+    m_vLegendColors.clear();
+    m_vLegendColors.push_back(vector<int> {255,255,255,255});
+    m_vLegendColors.push_back(vector<int> {255,0,0,0});
+    m_vLegendColors.push_back(vector<int> {0,255,0,0});
+    m_vLegendColors.push_back(vector<int> {0,0,255,0});
+    m_vLegendColors.push_back(vector<int> {100,100,100,0});
+    m_vLegendColors.push_back(vector<int> {0,180,180,0});
+    m_vLegendColors.push_back(vector<int> {255,0,255,0});
+    m_vLegendColors.push_back(vector<int> {10,150,70,0});
+    m_vLegendColors.push_back(vector<int> {150,30,70,0});
+    m_vLegendColors.push_back(vector<int> {150,70,150,0});
+    m_vLegendColors.push_back(vector<int> {170,130,100,0});
+
+    // Initialize default names
+    m_vLegendCaption.clear();
+    m_vLegendCaption.push_back("unassigned");
+    m_vLegendCaption.push_back("fixation");
+    m_vLegendCaption.push_back("saccade");
+    m_vLegendCaption.push_back("SP");
+    m_vLegendCaption.push_back("noise");
+    m_vLegendCaption.push_back("");
+    m_vLegendCaption.push_back("");
+    m_vLegendCaption.push_back("");
+    m_vLegendCaption.push_back("");
+    m_vLegendCaption.push_back("");
+    m_vLegendCaption.push_back("other");
+
+    assert(m_vLegendColors.size() == m_vLegendCaption.size());
+
+    m_colorNum = m_vLegendColors.size();
+}
+
+void ArffWidgetBase::InitializeLegend(int attInd)
+{
+    InitializeLegend();
+    vector<string> legendCaption;
+
+    if (m_pArff->GetAttMapping(attInd, legendCaption))
+    {
+        if (legendCaption.size() > m_vLegendCaption.size())
+        {
+            cout << "ERROR: cannot handle more than 9 colors for the provided input argument" << endl;
+            exit(-1);
+        }
+
+        m_vLegendCaption = legendCaption;
+        m_colorNum = m_vLegendCaption.size();
+    }
+    else
+    {
+        cout << "WARNING: using default legend colors" << endl;
+    }
+}

+ 178 - 0
GTA-VI/ArffWidgetBase.h

@@ -0,0 +1,178 @@
+// WindowArffBase.h
+
+#ifndef __ARFFWIDGETBASE_H__
+#define __ARFFWIDGETBASE_H__
+
+#include <QtWidgets>
+#include <vector>
+
+#include "../arffHelper/Arff.h"
+
+using namespace std;
+
+class ArffWidgetBase : public QWidget
+{
+    Q_OBJECT
+signals:
+    void SendWindowDur(int dur_us);
+
+    void SendTime(int curTime_us);
+    ///< Sends the current time.
+
+    void SendUpdate();
+    ///< Singal to upadte connected widgets including sending object.
+
+public slots:
+    void HandleWindowDur(int dur_us, QObject *pSender);
+
+    void HandleTime(int curTime_us, QObject *pSender);
+
+    void HandleUpdate();
+
+    void HandleSelectedEyeMovement(int eyeMovement);
+
+    virtual void HandleToggleView();
+
+public:
+    ArffWidgetBase(QWidget *parent=0);
+
+    QSize minimumSizeHint() const Q_DECL_OVERRIDE;
+    ///< Returns the required mimimum size for the widget in order
+    ///< to render correctly. If no layout is present returns invalid size.
+
+    void SetGridInterval(int tickX, int tickY);
+    ///< Sets the tick of x,y axes. Otherwise uses default.
+
+    void SetWindowDuration(int duration);
+    ///< Sets the duration of the window in us.
+
+    void SetTime(int currentTime);
+    ///< Sets the current time of display. Initially 0.
+
+    virtual void SetData(Arff &arff, int attToPaint, double maxValue=-1.0);
+    ///< Sets the ARFF data and the index of the attribute to paint. \p maxValue 
+    ///< is the normlization on the plotted data.
+
+    virtual void SetFovData(Arff &arff, double maxValue);
+    ///< Sets Field Of View data created from te equirectangular data. This 
+    ///< function is called after data has been set.
+
+    void SetIntervalAtt(int intervalAtt);
+    ///< Sets an attribute that is going to be used as guide for interval
+    ///< insertion in the painted attribute. This primary useful for secondary
+    ///< labels.
+
+    void Update();
+    ///< Updates all displayed information and repaints the area.
+
+    void MoveToStart();
+    ///< Moves the time to the time of the first sample point and emits signal in order
+    ///< to sync all other widgets. If by the time of the call no data are present
+    ///< it does nothing.
+
+protected:
+    void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
+
+    void PaintBase(QPainter *painter);
+
+    void PaintAreas(QPainter *painter);
+    ///< Paints areas on the background with color.
+
+    void PaintVideoTime(QPainter *painter);
+    ///< Paints the position of the video on window.
+
+    virtual void PaintLine(QPainter *painter);
+    ///< Paints the line on the canvas.
+
+    void PaintGrid(QPainter *painter);
+    ///< Paints grid lines on the canvas.
+
+    void PaintText(QPainter *painter);
+    ///< Paints axes limits on the canvas.
+
+    void PaintLegend(QPainter *painter);
+    ///< Paints legend for colors and values.
+
+    void mousePressEvent(QMouseEvent *event);
+    ///< Handles the press of mouse buttons.
+
+    void mouseDoubleClickEvent(QMouseEvent *event);
+    ///< Handles left mouse double click. It inserts an interval based on the
+    ///< on the interval and paint attributes. It works with SetIntervalAtt(int)
+
+    void mouseReleaseEvent(QMouseEvent *event);
+    ///< Handles the realese of mouse buttons.
+
+    void mouseMoveEvent(QMouseEvent *event);
+    ///< Mouse tracking is on when a button is clicked.
+
+    void wheelEvent(QWheelEvent *event);
+    ///< Tracks mouse wheel events.
+
+    void keyPressEvent(QKeyEvent *event);
+    ///< Handles keyboard key presses.
+
+    void ExpandIntLeft(int intPointer, double newLimit);
+
+    void ExpandIntRight(int intPointer, double newLimit);
+
+    void DeleteInterval(void);
+    ///< Sets the selected interval to unassigned.
+
+    void InsertInterval(void);
+    ///< Insert new interval to the selected interval at the click point.
+    ///< The new interval has duration of 80ms.
+
+    void ChangeInterval(int newValue);
+    ///< If the mouse is clicked and a number is typed then the interval
+    ///< is changed to the new eye movement.
+
+    void CalculateIntervals(void);
+    ///< Calculates the intervals for the given time window and
+    ///< sets the interval pointer to the interval of the left click position
+    ///< if available.
+
+    void ClearEventVariables(void);
+    ///< Clears the state of all local variables.
+
+    void InitializeLegend();
+
+    void InitializeLegend(int attInd);
+
+    int                         m_gridTickY; // tick on y axis
+    int                         m_gridTickX; // tick on x axis
+    int                         m_windowDuration; // duration of window in us
+    int                         m_windowMaxVal; // max value to display
+    int                         m_windowSecMaxVal; // max value to display for secondary data
+    int                         m_winStartTime; // start time of the window
+    int                         m_currentTime;
+    Arff                        *m_pArff; // pointer to Arff
+    Arff                        *m_pXYArff; // pointer to Arff only for valid XY
+    int                         m_timeInd;
+    int                         m_xInd;
+    int                         m_yInd;
+    Arff                        *m_pXYSecArff; // Arff pointer for secondary data for valid XY
+
+    int                         m_pointerStart; // starting DataPoint for current time
+    int                         m_pointerEnd; // end point for end of window
+    int                         m_intervalLength; // amount of samples in between
+    int                         m_attToPaint; // pointer of the attribute to paint as background
+    int                         m_intervalAtt;
+
+    // event specific variables
+    bool                        m_IsPressedLeft; // indicates if the left button is pressed
+    double                      m_LeftPressPosition; // position of the left press relative to widget
+    double                      m_DoubleLeftPressPos; 
+    int                         m_RightPressPosition; // position of the left press relative to widget
+    vector<int>                 m_vIntervals; // each element represents the start of interval
+
+    vector<string>              m_vLegendCaption;
+    vector<vector<int>>         m_vLegendColors;
+    int                         m_colorNum;
+
+    int                         m_selectedEyeMovement; // when numbers are pressed a specific eye movement is selected
+
+    double                      m_prevDragPosX;
+};
+
+#endif /*__ARFFWIDGETBASE_H__*/

+ 77 - 0
GTA-VI/ArffWidgetCoord.cpp

@@ -0,0 +1,77 @@
+// ArffWidgetCoord.cpp
+
+#include "ArffWidgetCoord.h"
+#include "../arffHelper/ArffUtil.h"
+#include "Unused.h"
+
+#include <QTime>
+#include <cassert>
+
+using namespace std;
+
+// PUBLIC:
+
+ArffWidgetCoord::ArffWidgetCoord(QWidget *parent) : ArffWidgetBase(parent), m_plotAttInd(0)
+{
+}
+
+
+// PROTECTED:
+void ArffWidgetCoord::PaintLine(QPainter *painter)
+{
+    QPoint points[m_intervalLength];
+    QRect geom = geometry();
+
+    painter->setPen(QPen(Qt::black, 3.0*geom.height()/1000.0));
+    painter->translate(0, geom.height());
+    painter->scale(1,-1);
+    painter->setRenderHint(QPainter::Antialiasing, true);
+    for (int i=0; i<m_intervalLength; i++)
+    {
+        double x = (double)geom.width()*((*m_pXYArff)[m_pointerStart+i][m_timeInd] - (double)m_winStartTime)/(double)(m_windowDuration);
+        points[i].setX((int)x);
+        points[i].setY((double)(*m_pXYArff)[m_pointerStart+i][m_plotAttInd]*(double)geom.height()/m_windowMaxVal);
+        if (i>0)
+            painter->drawLine(points[i], points[i-1]);
+    }
+
+    painter->translate(0, geom.height());
+    painter->scale(1,-1);
+    painter->setRenderHint(QPainter::Antialiasing, false);
+}
+
+// CLASS ArffWidgetCoordX
+
+ArffWidgetCoordX::ArffWidgetCoordX(QWidget *parent) : ArffWidgetCoord(parent)
+{
+}
+
+void ArffWidgetCoordX::SetData(Arff &arff, int attToPaint, double maxValue)
+{
+    ArffWidgetCoord::SetData(arff, attToPaint, maxValue);
+    m_plotAttInd = m_xInd;
+}
+
+void ArffWidgetCoordX::SetFovData(Arff &arff, double maxValue)
+{
+    ArffWidgetCoord::SetFovData(arff, maxValue);
+    m_plotAttInd = m_xInd;
+}
+
+// CLASS ArffWidgetCoordY
+
+ArffWidgetCoordY::ArffWidgetCoordY(QWidget *parent) : ArffWidgetCoord(parent)
+{
+}
+
+void ArffWidgetCoordY::SetData(Arff &arff, int attToPaint, double maxValue)
+{
+    ArffWidgetCoord::SetData(arff, attToPaint, maxValue);
+    m_plotAttInd = m_yInd;
+}
+
+void ArffWidgetCoordY::SetFovData(Arff &arff, double maxValue)
+{
+    ArffWidgetCoord::SetFovData(arff, maxValue);
+    m_plotAttInd = m_yInd;
+}

+ 39 - 0
GTA-VI/ArffWidgetCoord.h

@@ -0,0 +1,39 @@
+// ArffWidgetCoord.h
+
+#ifndef __PAINTWIDGET_H__
+#define __PAINTWIDGET_H__
+
+#include "ArffWidgetBase.h"
+
+class ArffWidgetCoord : public ArffWidgetBase
+{
+public:
+    ArffWidgetCoord(QWidget *parent=0);
+
+protected:
+    virtual void PaintLine(QPainter *painter);
+    ///< Paints a coordinate line on the canvas.
+
+    int m_plotAttInd;
+};
+
+class ArffWidgetCoordX : public ArffWidgetCoord
+{
+public:
+    ArffWidgetCoordX(QWidget *parent=0);
+
+    virtual void SetData(Arff &arff, int attToPaint, double maxValue=-1.0);
+
+    virtual void SetFovData(Arff &arff, double maxValue);
+};
+
+class ArffWidgetCoordY : public ArffWidgetCoord
+{
+public:
+    ArffWidgetCoordY(QWidget *parent=0);
+
+    virtual void SetData(Arff &arff, int attToPaint, double maxValue=-1.0);
+
+    virtual void SetFovData(Arff &arff, double maxValue);
+};
+#endif /*__PAINTWIDGET_H__*/

+ 189 - 0
GTA-VI/ArffWidgetSpeed.cpp

@@ -0,0 +1,189 @@
+// ArffWidgetSpeed.cpp
+
+#include "ArffWidgetSpeed.h"
+#include "EquirectangularToFovSpeed.cpp"
+#include "GazeSpeed.h"
+
+#include <algorithm>
+#include <iostream>
+
+#define RELATION360 "GAZE_360"
+#define MAX_SPEED 200.0
+#define STEP 5
+
+using namespace std;
+
+// PUBLIC SLOTS:
+
+void ArffWidgetSpeed::HandleToggleView()
+{
+    ArffWidgetBase::HandleToggleView();
+    
+    if (m_vSpeed.size() != m_vSecondSpeed.size() || !m_FovDisplayed)
+        return;
+
+    if (m_pSpeed == &m_vSpeed)
+        m_pSpeed = &m_vSecondSpeed;
+    else
+        m_pSpeed = &m_vSpeed;
+
+    update();
+}
+
+// PUBLIC:
+ArffWidgetSpeed::ArffWidgetSpeed(QWidget *parent) : ArffWidgetBase(parent), m_pSpeed(&m_vSpeed), m_FovDisplayed(false)
+{
+}
+
+// PROTECTED:
+void ArffWidgetSpeed::SetData(Arff &arff, int attToPaint, double maxValue)
+{
+    ArffWidgetBase::SetData(arff, attToPaint, maxValue);
+
+    if (IsArff360())
+    {
+        EquirectangularToFovSpeed eqToSpeed(m_pArff, STEP);
+        // Always get head+eye speed because the provided ARFF is converted to 
+        // hold the data to display in x,y
+        m_vSpeed = eqToSpeed.GetHeadPlusEyeSpeed(); 
+        NormalizeSpeed(m_vSpeed);
+        m_vSecondSpeed = eqToSpeed.GetEyeFovSpeed();
+        NormalizeSpeed(m_vSecondSpeed);
+        m_vHeadSpeed = eqToSpeed.GetHeadSpeed();
+        NormalizeSpeed(m_vHeadSpeed);
+    }
+    else
+    {
+        GazeSpeed gazeSpeed(m_pArff, STEP);
+        m_vSpeed = gazeSpeed.GetSpeed();
+        NormalizeSpeed(m_vSpeed);
+    }
+}
+
+void ArffWidgetSpeed::SetFovData(Arff &arff, double maxValue)
+{
+    ArffWidgetBase::SetFovData(arff, maxValue);
+    DisplayFov();
+}
+
+void ArffWidgetSpeed::DisplayFov()
+{
+    m_FovDisplayed = true;
+}
+
+void ArffWidgetSpeed::DisplayHead()
+{
+    EquirectangularToFovSpeed eqToSpeed(m_pArff, STEP);
+    m_vSpeed = eqToSpeed.GetHeadSpeed();
+    NormalizeSpeed(m_vSpeed);
+    m_vHeadSpeed.clear();
+}
+
+// PROTECTED:
+
+void ArffWidgetSpeed::PaintLine(QPainter *painter)
+{
+    QRect geom = geometry();
+
+    painter->setPen(QPen(Qt::black, 4.0*geom.height()/1000.0));
+    PaintSpeed(*m_pSpeed, painter);
+    
+    painter->setPen(QPen(Qt::darkRed, 4.0*geom.height()/1000.0));
+    PaintSpeed(m_vHeadSpeed, painter);
+
+    PaintSpeedLines(painter);
+}
+
+// PRIVATE:
+
+void ArffWidgetSpeed::PaintSpeed(const vector<double> &vSpeed, QPainter *painter)
+{
+    if (vSpeed.empty())
+        return;
+
+    QRect geom = geometry();
+    QPoint points[m_intervalLength];
+    
+    for (int i=0; i<m_intervalLength; i++)
+    {
+        //double x = (double)geom.width()*((*m_pEqArff)[m_pointerStart+i][m_timeInd] - (double)m_winStartTime)/(double)(m_windowDuration);
+        double x = (double)geom.width()*((*m_pArff)[m_pointerStart+i][m_timeInd] - (double)m_winStartTime)/(double)(m_windowDuration);
+        points[i].setX((int)x);
+        points[i].setY((double)vSpeed[m_pointerStart+i]*(double)geom.height()/m_windowMaxVal);
+    }
+    
+    painter->translate(0, geom.height());
+    painter->scale(1,-1); 
+    painter->drawPolyline(points,m_intervalLength);
+    painter->translate(0, geom.height());
+    painter->scale(1,-1); 
+}
+
+void ArffWidgetSpeed::PaintSpeedLines(QPainter *painter)
+{
+    vector<double> percentage {0.025, 0.05, 0.1, 0.2, 0.4, 0.7, 1};
+
+    QRect geom = geometry();
+	double boxHeight = 0.02*geom.height();
+    QPoint points[2];
+    for (auto perc:percentage)
+    {
+        // draw speed line
+        painter->setPen(QPen(Qt::darkCyan, 2.0*geom.height()/1000.0));
+        points[0].setX(0);
+        points[1].setX(geom.width());
+    
+        points[0].setY((double)geom.height() * (1 - TransformPercentage(perc)));
+        points[1].setY(points[0].y());
+
+        painter->drawPolyline(points, 2);
+
+        // draw text
+        points[0].setX(geom.width() - 6 * boxHeight);
+        points[0].setY((double)geom.height() * (1 - TransformPercentage(perc)));
+        points[1].setX(geom.width());
+        points[1].setY((double)geom.height() * (1 - TransformPercentage(perc)) + boxHeight);
+        QRect rect(points[0], points[1]);
+        painter->setBrush(QBrush(QColor(255,255,255,255)));
+        painter->drawRect(rect);
+
+        QString label = QString::number(int(perc*MAX_SPEED)) + QString(" deg/sec");
+		// scale font size
+		QFont font = painter->font();
+		font.setPointSize(boxHeight/1.5);
+		painter->setFont(font);
+		painter->setPen(QPen(Qt::black));
+
+        painter->drawText(rect, Qt::AlignCenter, label);
+
+    }
+}
+
+bool ArffWidgetSpeed::IsArff360()
+{
+    string relation = m_pArff->GetRelation();
+    transform(relation.begin(), relation.end(), relation.begin(), ::toupper);
+
+    return relation == RELATION360;
+}
+
+void ArffWidgetSpeed::NormalizeSpeed(vector<double> &vSpeed)
+{
+    for (double &value:vSpeed)
+        value = value>MAX_SPEED?MAX_SPEED:value;
+    m_windowMaxVal = MAX_SPEED;
+    
+    for (double &value:vSpeed)
+        value = TransformPercentage(value / MAX_SPEED) * MAX_SPEED;
+}
+
+double ArffWidgetSpeed::TransformPercentage(double perc)
+{
+    // Use exponential decay to spread percentages at the lower end. The smaller
+    // the exponential multiplier the bigger the spread in the lower values
+    double expMult = -2.5;
+    double maxValMult = exp(expMult);
+    double newPerc = (1 - exp(expMult*perc)) / (1 - maxValMult);
+
+    return newPerc;
+}

+ 47 - 0
GTA-VI/ArffWidgetSpeed.h

@@ -0,0 +1,47 @@
+// ArffWidgetSpeed.h
+
+#include "ArffWidgetBase.h"
+
+class ArffWidgetSpeed : public ArffWidgetBase
+{
+public slots:
+    void HandleToggleView();
+
+public:
+    ArffWidgetSpeed(QWidget *parent=0);
+
+    virtual void SetData(Arff &arff, int attToPaint, double maxValue=-1.0);
+
+    virtual void SetFovData(Arff &arff, double maxValue=-1.0);
+
+    void DisplayFov();
+
+    void DisplayHead();
+
+protected:
+    virtual void PaintLine(QPainter *painter);
+
+private:
+    vector<double> m_vSpeed;
+    vector<double> m_vSecondSpeed;
+    vector<double> m_vHeadSpeed;
+    vector<double> *m_pSpeed; 
+    bool           m_FovDisplayed;
+
+    void PaintSpeed(const vector<double>& vSpeed, QPainter *painter);
+    // Paints speed from the provided vector to the painter.
+
+    void PaintSpeedLines(QPainter *painter);
+    // Paints the speed line. Speed is calculated differently depending on the type of
+    // of stimulus (normal or 360 video).
+
+    bool IsArff360();
+    // Checks the relation of the ARFF and returns true if the recording was in
+    // 360 degrees equirectangular.
+
+    void NormalizeSpeed(vector<double> &vSpeed);
+    // Removes extreme speed values and converts speed to a non linear representation.
+
+    double TransformPercentage(double perc);
+    // Expands the range for lower speed values to make them more visible.
+};

+ 222 - 0
GTA-VI/EquirectangularToFovBase.cpp

@@ -0,0 +1,222 @@
+// EquirectangularToFovBase.cpp
+
+#include "EquirectangularToFovBase.h"
+#include "Types.h"
+#include "Util.h"
+#include "../arffHelper/ArffUtil.h"
+
+#include <iostream>
+#include <cstdlib>
+#include <string>
+#include <algorithm>
+#include <cassert>
+
+#define PI 3.14159265
+#define EPSILON 0.001
+
+
+using namespace std;
+
+const char *c_horHeadAttName = "x_head";
+const char *c_verHeadAttName = "y_head";
+const char *c_tiltHeadAttName = "angle_deg_head";
+
+const char *c_fovWidthDegName = "fov_width_deg";
+const char *c_fovHeightDegName = "fov_height_deg";
+const char *c_fovWidthPxName = "fov_width_px";
+const char *c_fovHeightPxName = "fov_height_px";
+
+// PUBLIC:
+
+EquirectangularToFovBase::EquirectangularToFovBase(Arff* pArff)
+{
+    if (pArff == NULL)
+    {
+        cout << "ERROR: you provided NULL pointer. Could not initialize." << endl;
+        exit(-1);
+    }
+    m_pArff = pArff;
+    bool res = ArffUtil::GetTXYCindex(m_pArff, m_timeInd, m_xInd, m_yInd, m_confInd);
+    if (!res)
+    {
+        cout << "ERROR: could not find time or x or y or confidence in the provided ARFF file." << endl;
+        exit(-1);
+    }
+
+    // get experiment values
+    res = true;
+    res &= m_pArff->GetAttIndex(c_horHeadAttName, m_horHeadInd);
+    res &= m_pArff->GetAttIndex(c_verHeadAttName, m_verHeadInd);
+    res &= m_pArff->GetAttIndex(c_tiltHeadAttName, m_tiltHeadInd);
+
+    if (!res)
+    {
+        cout << "ERROR: could not find " << c_horHeadAttName << " or " << c_verHeadAttName <<  " or " << c_tiltHeadAttName << " in the provided ARFF file." << endl;
+        exit(-1);
+    }
+
+    string value;
+    if(!m_pArff->GetMetadata(c_fovWidthDegName, value))
+    {
+        cout << "ERROR: METADATA " << c_fovWidthDegName << " is missing" << endl;
+        exit(-1);
+    }
+    m_fovWidthDeg = stod(value);
+    if (!m_pArff->GetMetadata(c_fovHeightDegName, value))
+    {
+        cout << "ERROR: METADATA " << c_fovHeightDegName << " is missing" << endl;
+        exit(-1);
+    }
+    m_fovHeightDeg = stod(value);
+    if (!m_pArff->GetMetadata(c_fovWidthPxName, value))
+    {
+        cout << "ERROR: METADATA " << c_fovWidthPxName << " is missing" << endl;
+        exit(-1);
+    }
+    m_fovWidthPx = stoi(value);
+    if (!m_pArff->GetMetadata(c_fovHeightPxName, value))
+    {
+        cout << "ERROR: METADATA " << c_fovHeightPxName << " is missing" << endl;
+        exit(-1);
+    }
+    m_fovHeightPx = stoi(value);
+}
+
+/*virtual*/ EquirectangularToFovBase::~EquirectangularToFovBase()
+{
+}
+
+// PROTECTED:
+void EquirectangularToFovBase::EquirectangularToSpherical(unsigned int xEq, unsigned int yEq, unsigned int widthPx, unsigned int heightPx, double *horRads, double *verRads)
+{
+    *horRads = (xEq * 2.0 * PI) / widthPx;
+    *verRads = (yEq * PI) / heightPx;
+}
+
+void EquirectangularToFovBase::SphericalToEquirectangular(double horRads, double verRads, unsigned int widthPx, unsigned int heightPx, unsigned int *xEq, unsigned int *yEq)
+{
+    int x = (int)((horRads / (2.0 * PI)) * widthPx + 0.5); // round double to closer int
+    int y = (int)((verRads / PI) * heightPx + 0.5);
+
+    // make sure returned values are within the video
+    if (y < 0)
+    {
+        y = -y;
+        x += widthPx / 2;
+    }
+
+    if (y >= (int)heightPx)
+    {
+        y = 2 * heightPx - y - 1;
+        x += widthPx / 2;
+    }
+    *yEq = (unsigned int) y;
+
+    if (x < 0)
+        x = widthPx + x - 1;
+    else if (x >= (int)widthPx)
+        x -= widthPx;
+
+    *xEq = (unsigned int) x;
+}
+
+void EquirectangularToFovBase::SphericalToCartesian(double horRads, double verRads, Vec3 *cart)
+{
+    cart->x = sin(verRads)*cos(horRads);
+    cart->y = cos(verRads);
+    cart->z = sin(verRads)*sin(horRads);
+}
+
+void EquirectangularToFovBase::CartesianToSpherical(Vec3 cart, double *horRads, double *verRads)
+{
+    *horRads = atan2(cart.z, cart.x);
+    *verRads = acos(cart.y);
+}
+
+void EquirectangularToFovBase::GetHeadPos(long int time, double *x, double *y, double *tilt)
+{
+    unsigned int ind = ArffUtil::FindPosition(m_pArff, m_timeInd, time);
+    GetHeadPos(ind, x, y, tilt);
+}
+
+void EquirectangularToFovBase::GetHeadPos(unsigned int ind, double *x, double *y, double *tilt)
+{
+    *x = (*m_pArff)[ind][m_horHeadInd];
+    *y = (*m_pArff)[ind][m_verHeadInd];
+    *tilt = (*m_pArff)[ind][m_tiltHeadInd];
+}
+
+void EquirectangularToFovBase::GetEyePos(unsigned int ind, double *x, double *y)
+{
+    *x = (*m_pArff)[ind][m_xInd];
+    *y = (*m_pArff)[ind][m_yInd];
+}
+
+Matrix33 EquirectangularToFovBase::HeadToVideoRotation(Vec3 head, double headTiltRads, Vec3 video)
+{
+    Matrix33 headToRef = YZXrotation(head, -headTiltRads);
+    Matrix33 videoToRef = YZXrotation(video, 0);
+
+    Matrix33 ret = videoToRef*Transpose(headToRef);
+    return ret;
+}
+
+Matrix33 EquirectangularToFovBase::YZXrotation(Vec3 vec, double tiltRads)
+{
+    // we follow the Trait-Bryan angles with Y-Z'-X" intrinsic rotations
+    double theta = asin(vec.y);
+
+    double psi = 0;
+    if (abs(theta) < PI/2.0 - 0.01)
+        psi = atan2(vec.z, vec.x);
+
+    /*
+    Matrix33 Yrot;
+    Yrot.mat[0][0] = cos(psi);
+    Yrot.mat[0][1] = 0;
+    Yrot.mat[0][2] = sin(psi);
+    Yrot.mat[1][0] = 0;
+    Yrot.mat[1][1] = 1;
+    Yrot.mat[1][2] = 0;
+    Yrot.mat[2][0] = -sin(psi);
+    Yrot.mat[2][1] = 0;
+    Yrot.mat[2][2] = cos(psi);
+
+    Matrix33 Zrot;
+    Zrot.mat[0][0] = cos(theta);
+    Zrot.mat[0][1] = -sin(theta);
+    Zrot.mat[0][2] = 0;
+    Zrot.mat[1][0] = sin(theta);
+    Zrot.mat[1][1] = cos(theta);
+    Zrot.mat[1][2] = 0;
+    Zrot.mat[2][0] = 0;
+    Zrot.mat[2][1] = 0;
+    Zrot.mat[2][2] = 1;
+
+    Matrix33 Xrot;
+    Xrot.mat[0][0] = 1;
+    Xrot.mat[0][1] = 0;
+    Xrot.mat[0][2] = 0;
+    Xrot.mat[1][0] = 0;
+    Xrot.mat[1][1] = cos(tiltRads);
+    Xrot.mat[1][2] = -sin(tiltRads);
+    Xrot.mat[2][0] = 0;
+    Xrot.mat[2][1] = sin(tiltRads);
+    Xrot.mat[2][2] = cos(tiltRads);
+
+    Matrix33 ret = Xrot*Zrot*Yrot;
+    */
+
+    Matrix33 ret;
+    ret.mat[0][0] = cos(theta)*cos(psi);
+    ret.mat[0][1] = -sin(theta);
+    ret.mat[0][2] = cos(theta)*sin(psi);
+    ret.mat[1][0] = cos(tiltRads)*sin(theta)*cos(psi) + sin(tiltRads)*sin(psi);
+    ret.mat[1][1] = cos(tiltRads)*cos(theta);
+    ret.mat[1][2] = cos(tiltRads)*sin(theta)*sin(psi) - sin(tiltRads)*cos(psi);
+    ret.mat[2][0] = sin(tiltRads)*sin(theta)*cos(psi) - cos(tiltRads)*sin(psi);
+    ret.mat[2][1] = sin(tiltRads)*cos(theta);
+    ret.mat[2][2] = sin(tiltRads)*sin(theta)*sin(psi) + cos(tiltRads)*cos(psi);
+    return ret;
+
+}

+ 66 - 0
GTA-VI/EquirectangularToFovBase.h

@@ -0,0 +1,66 @@
+// EquirectangularToFov.h
+// equirectangular to field of view conversion
+
+#ifndef __EQUIRECTANGULARTOFOVBASE_H__
+#define __EQUIRECTANGULARTOFOVBASE_H__
+
+#include "../arffHelper/Arff.h"
+#include "Types.h"
+
+#include <vector>
+
+using namespace std;
+
+class EquirectangularToFovBase
+{
+    //Q_OBJECT
+
+public:
+    EquirectangularToFovBase(Arff* pArff);
+    virtual ~EquirectangularToFovBase() = 0;
+
+protected:
+    void EquirectangularToSpherical(unsigned int xEq, unsigned int yEq, unsigned int widthPx, unsigned int heightPx, double *horRads, double *verRads);
+    ///< Converts the provided equirectangular coordinates to angles i.e. spherical representation.
+
+    void SphericalToEquirectangular(double horRads, double verRads, unsigned int widthPx, unsigned int heightPx, unsigned int *xEq, unsigned int *yEq);
+    ///< Converts spherical coordinates to pixels in the equirectangular representation.
+
+    void SphericalToCartesian(double horRads, double verRads, Vec3 *cart);
+    ///< Converts a unit vector from spherical to cartesian coordinates.
+    ///< \p horRads is the angle in the between X axis and the vector in the XZ plane
+    ///< \p verRads is teh angle between the vector and the Y axis.
+
+    void CartesianToSpherical(Vec3 cart, double *horRads, double *verRads);
+    ///< Converts a cartesian vector to unit spherical vector.
+
+    void GetHeadPos(long int time, double *x, double *y, double *tilt);
+    ///< Retrieve the head position.
+
+    void GetHeadPos(unsigned int ind, double *x, double *y, double *tilt);
+
+    void GetEyePos(unsigned int ind, double *x, double *y);
+
+    Matrix33 HeadToVideoRotation(Vec3 head, double headTiltRads, Vec3 video);
+
+    Matrix33 YZXrotation(Vec3 vec, double tiltRads);
+
+    Arff        *m_pArff;
+    int         m_horHeadInd;
+    int         m_verHeadInd;
+    int         m_tiltHeadInd;
+    int         m_timeInd;
+    int         m_xInd;
+    int         m_yInd;
+    int         m_confInd;
+    int         m_fovWidthPx;
+    int         m_fovHeightPx;
+    double      m_fovWidthDeg;
+    double      m_fovHeightDeg;
+
+    vector<double> m_vHorSampling;
+    vector<double> m_vVerSampling;
+};
+
+
+#endif /*__EQUIRECTANGULARTOFOV_H__*/

+ 86 - 0
GTA-VI/EquirectangularToFovGaze.cpp

@@ -0,0 +1,86 @@
+// EquirectangularToFovGaze.cpp
+
+#include "EquirectangularToFovGaze.h"
+#include "Util.h"
+
+#define PI 3.14159265
+
+using namespace std;
+
+EquirectangularToFovGaze::EquirectangularToFovGaze(Arff *pArff) : 
+    EquirectangularToFovBase(pArff)
+{
+}
+
+/*virtual*/ EquirectangularToFovGaze::~EquirectangularToFovGaze()
+{
+}
+
+unique_ptr<Arff> EquirectangularToFovGaze::Convert()
+{
+    unique_ptr<Arff> convertedArff(new Arff());
+    convertedArff->SetWidthPx(m_fovWidthPx);
+    convertedArff->SetHeightPx(m_fovHeightPx);
+    convertedArff->AddColumn("time", "integer");
+    convertedArff->AddColumn("x", "numeric");
+    convertedArff->AddColumn("y", "numeric");
+    convertedArff->AddColumn("confidence", "numeric");
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+
+    //DataPoint iniPoint, convPoint;
+    for (unsigned int ind=0; ind < (unsigned int)rows; ind++)
+    {
+        double xHead, yHead, tiltHead;
+        GetHeadPos(ind, &xHead, &yHead, &tiltHead);
+        double horHeadRads, verHeadRads;
+        EquirectangularToSpherical(xHead, yHead, m_pArff->WidthPx(), m_pArff->HeightPx(), &horHeadRads, &verHeadRads);
+        Vec3 headVec(0,0,0);
+        SphericalToCartesian(horHeadRads, verHeadRads, &headVec);
+
+        Vec3 vidVec(-1,0,0); // middle of the video is the center
+        double tiltHeadRads = tiltHead * PI / 180;
+        Matrix33 rot = HeadToVideoRotation(headVec, tiltHeadRads, vidVec);
+        rot = Transpose(rot);
+
+        // Convert gaze to FOV coordinates
+        double xGaze, yGaze;
+        GetEyePos(ind, &xGaze, &yGaze);
+        double horGazeRads, verGazeRads;
+        EquirectangularToSpherical(xGaze, yGaze, m_pArff->WidthPx(), m_pArff->HeightPx(), &horGazeRads, &verGazeRads);
+        Vec3 GazeVec(0,0,0);
+        SphericalToCartesian(horGazeRads, verGazeRads, &GazeVec);
+
+        Vec3 rotGazeVec = RotatePoint(rot, GazeVec);
+        double rotHorRads, rotVerRads;
+        CartesianToSpherical(rotGazeVec, &rotHorRads, &rotVerRads);
+
+        double xFov, yFov;
+        SphericalToFov(rotHorRads, rotVerRads, &xFov, &yFov);
+        vector<double> row;
+        row.push_back((*m_pArff)[ind][m_timeInd]);
+        row.push_back(xFov);
+        row.push_back(yFov);
+        row.push_back((*m_pArff)[ind][m_confInd]);
+        convertedArff->AddRow(row);
+    }
+
+    return move(convertedArff);
+}
+
+// PRIVATE:
+void EquirectangularToFovGaze::SphericalToFov(double horRads, double verRads, double *x, double *y)
+{
+    // Change them to [0, 2*pi) and [0, pi) range
+    horRads = horRads < 0? 2*PI + horRads: horRads;
+    verRads = verRads < 0? PI + verRads: verRads;
+    // convert them from the middle of the video to the start of the coordinates
+    horRads -= PI;
+    verRads -= PI / 2.0;
+
+    double fovHorRads = m_fovWidthDeg * PI / 180.0;
+    double fovVerRads = m_fovHeightDeg * PI / 180.0;
+
+    *x = m_fovWidthPx * (horRads + fovHorRads / 2.0) / fovHorRads;
+    *y = m_fovHeightPx * (verRads + fovVerRads / 2.0) / fovVerRads;
+}

+ 28 - 0
GTA-VI/EquirectangularToFovGaze.h

@@ -0,0 +1,28 @@
+// EquirectangularToFovGaze.h
+// Conversion of gaze from equirectangular coordinates to FOV coordinates
+
+#ifndef __EQUIRECTANGULARTOFOVGAZE_H__
+#define __EQUIRECTANGULARTOFOVGAZE_H__
+
+#include "EquirectangularToFovBase.h"
+
+#include <memory>
+
+using namespace std;
+
+class EquirectangularToFovGaze : public EquirectangularToFovBase
+{
+public:
+    EquirectangularToFovGaze(Arff *pArff);
+    virtual ~EquirectangularToFovGaze();
+    
+    unique_ptr<Arff> Convert();
+    ///< Cnverts the gaze of the input ARFF object to the 
+    ///< field of view and stores it to a new ARFF object. A pointer to this
+    ///< object is returned.
+
+private:
+    void SphericalToFov(double horRads, double verRads, double *x, double *y);
+};
+
+#endif /*__EQUIRECTANGULARTOFOVGAZE_H__*/

+ 170 - 0
GTA-VI/EquirectangularToFovSpeed.cpp

@@ -0,0 +1,170 @@
+// EquirectangularToFovSpeed.cpp
+
+#include "EquirectangularToFovSpeed.h"
+#include "Util.h"
+
+#include <cassert>
+#include <cmath>
+
+#define USECS_TO_SECS 1000000.0
+#define PI 3.14159265
+#define DELTA 0.00001
+
+using namespace std;
+
+EquirectangularToFovSpeed::EquirectangularToFovSpeed(Arff *pArff, int step) :
+    EquirectangularToFovBase(pArff)
+{
+    m_step = step;
+    
+    FillVectors();
+}
+
+/*virtual*/ EquirectangularToFovSpeed::~EquirectangularToFovSpeed()
+{
+}
+
+void EquirectangularToFovSpeed::AddSpeed()
+{
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+    // add x,y within FOV
+    m_pArff->AddColumn("x_fov", "numeric");
+    for (int r=0; r<rows; r++)
+        (*m_pArff)[r][columns] = m_vXfov[r];
+    columns++;
+
+    m_pArff->AddColumn("y_fov", "numeric");
+    for (int r=0; r<rows; r++)
+        (*m_pArff)[r][columns] = m_vYfov[r];
+    columns++;
+
+    // add speed attributes
+    string eyeFovAttName = "eye_fov_speed_" + to_string(m_step);
+    m_pArff->AddColumn(eyeFovAttName, "numeric");
+    GetSpeed(m_vEyeFov, columns);
+    columns++;
+
+    string headAttName = "head_speed_" + to_string(m_step);
+    m_pArff->AddColumn(headAttName, "numeric");
+    GetSpeed(m_vHead, columns);
+    columns++;
+
+    string headEyeAttName = "head_plus_eye_speed_" + to_string(m_step);
+    m_pArff->AddColumn(headEyeAttName, "numeric");
+    GetSpeed(m_vHeadEye, columns);
+}
+
+vector<double> EquirectangularToFovSpeed::GetHeadSpeed()
+{
+    return GetSpeed(m_vHead);
+}
+
+vector<double> EquirectangularToFovSpeed::GetEyeFovSpeed()
+{
+    return GetSpeed(m_vEyeFov);
+}
+
+vector<double> EquirectangularToFovSpeed::GetHeadPlusEyeSpeed()
+{
+    return GetSpeed(m_vHeadEye);
+}
+
+// PRIVATE:
+
+void EquirectangularToFovSpeed::GetSpeed(const vector<Vec3> &inVec, const int attInd)
+{
+    vector<double> speed = GetSpeed(inVec);
+
+    for (unsigned int ind=0; ind<speed.size(); ind++)
+        (*m_pArff)[ind][attInd] = speed[ind];
+}
+
+vector<double> EquirectangularToFovSpeed::GetSpeed(const vector<Vec3> &inVec)
+{
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+
+    assert(inVec.size() == (unsigned int) rows);
+    vector<double> res;
+    res.resize(rows);
+
+    for (int ind=m_step; ind<rows; ind++)
+    {
+        double dotProd = DotProd(inVec[ind], inVec[ind-m_step]);
+        // even though vectors are nomalized rounding errors can push the dot product slightly above 1
+        dotProd = dotProd > 1.0 && dotProd < 1.0 + DELTA? 1.0: dotProd;
+        double rads = acos(dotProd);
+        double degs = rads * 180 / PI;
+        double time = (double)((*m_pArff)[ind][m_timeInd] - (*m_pArff)[ind-m_step][m_timeInd]) / USECS_TO_SECS;
+        double degsSec = degs/time;
+        assert(degsSec==degsSec); // check for NaN values
+
+        //res[ind] = degsSec;
+        res[ind - m_step/2] = degsSec;
+    }
+
+    return res;
+}
+
+void EquirectangularToFovSpeed::FillVectors()
+{
+    m_vHead.clear();
+    m_vHeadEye.clear();
+    m_vEyeFov.clear();
+
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+    for (unsigned int ind=0; ind < (unsigned int) rows; ind++)
+    {
+        double xHead, yHead, tiltHead;
+        GetHeadPos(ind, &xHead, &yHead, &tiltHead);
+        double horHeadRads, verHeadRads;
+        EquirectangularToSpherical(xHead, yHead, m_pArff->WidthPx(), m_pArff->HeightPx(), &horHeadRads, &verHeadRads);
+        Vec3 headVec(0,0,0);
+        SphericalToCartesian(horHeadRads, verHeadRads, &headVec);
+        m_vHead.push_back(headVec);
+
+		Vec3 vidVec(-1,0,0); // middle of the video is the center
+        double tiltHeadRads = tiltHead * PI / 180;
+        Matrix33 rot = HeadToVideoRotation(headVec, tiltHeadRads, vidVec);
+        rot = Transpose(rot);
+        
+		// Convert gaze to FOV coordinates
+        double xGaze, yGaze;
+        GetEyePos(ind, &xGaze, &yGaze);
+        double horGazeRads, verGazeRads;
+        EquirectangularToSpherical(xGaze, yGaze, m_pArff->WidthPx(), m_pArff->HeightPx(), &horGazeRads, &verGazeRads);
+        Vec3 GazeVec(0,0,0);
+        SphericalToCartesian(horGazeRads, verGazeRads, &GazeVec);
+		m_vHeadEye.push_back(GazeVec);
+
+        Vec3 GazeWithinHeadVec = RotatePoint(rot, GazeVec);
+		m_vEyeFov.push_back(GazeWithinHeadVec);
+		
+		double rotHorRads, rotVerRads;
+        CartesianToSpherical(GazeWithinHeadVec, &rotHorRads, &rotVerRads);
+
+        double xFov, yFov;
+        SphericalToFov(rotHorRads, rotVerRads, &xFov, &yFov);
+        m_vXfov.push_back(xFov);
+        m_vYfov.push_back(yFov);
+    }
+}
+
+// PRIVATE:
+void EquirectangularToFovSpeed::SphericalToFov(double horRads, double verRads, double *x, double *y)
+{
+    // Change them to [0, 2*pi) and [0, pi) range
+    horRads = horRads < 0? 2*PI + horRads: horRads;
+    verRads = verRads < 0? PI + verRads: verRads;
+    // convert them from the middle of the video to the start of the coordinates
+    horRads -= PI;
+    verRads -= PI / 2.0;
+
+    double fovHorRads = m_fovWidthDeg * PI / 180.0;
+    double fovVerRads = m_fovHeightDeg * PI / 180.0;
+
+    *x = m_fovWidthPx * (horRads + fovHorRads / 2.0) / fovHorRads;
+    *y = m_fovHeightPx * (verRads + fovVerRads / 2.0) / fovVerRads;
+}

+ 51 - 0
GTA-VI/EquirectangularToFovSpeed.h

@@ -0,0 +1,51 @@
+// EquirectangularToFovSpeed.h
+//
+// Calculation of eye within head, head and head+eye speeds. These are absolute
+// values in degrees per second
+
+#ifndef __EQUIRECTANGULARTOFOVSPEED_H__
+#define __EQUIRECTANGULARTOFOVSPEED_H__
+
+#include "EquirectangularToFovBase.h"
+
+using namespace std;
+
+class EquirectangularToFovSpeed : public EquirectangularToFovBase
+{
+public:
+    EquirectangularToFovSpeed(Arff *pArff, int step=1);
+    ///< \p step is the interval to use between samples for speed calculation. It acts
+    ///< as simple filtering.
+
+    virtual ~EquirectangularToFovSpeed();
+
+    void AddSpeed();
+    ///< Adds three new attributes to ARFF representing speed in deg/s for eye+head,
+    ///< eye within head (fov) and head only.
+
+    vector<double> GetHeadSpeed();
+
+    vector<double> GetEyeFovSpeed();
+
+    vector<double> GetHeadPlusEyeSpeed();
+
+
+private:
+    void GetSpeed(const vector<Vec3> &inVec, const int attInd);
+
+    vector<double> GetSpeed(const vector<Vec3> &inVec);
+
+    void FillVectors();
+
+    void SphericalToFov(double horRads, double verRads, double *x, double *y);
+
+    vector<Vec3>    m_vEyeFov;
+    vector<Vec3>    m_vHead;
+    vector<Vec3>    m_vHeadEye;
+    vector<double>  m_vXfov;
+    vector<double>  m_vYfov;
+
+    int             m_step;
+};
+
+#endif /*__EQUIRECTANGULARTOFOVSPEED_H__*/

+ 105 - 0
GTA-VI/EquirectangularToFovVideo.cpp

@@ -0,0 +1,105 @@
+// EquirectangularToFovVideo.cpp
+
+#include "EquirectangularToFovVideo.h"
+#include "Types.h"
+#include "Util.h"
+
+#include <iostream>
+#include <cstdlib>
+#include <algorithm>
+
+#define PI 3.14159265
+
+using namespace std;
+
+// PUBLIC:
+
+EquirectangularToFovVideo::EquirectangularToFovVideo(Arff* pArff) : EquirectangularToFovBase(pArff)
+{
+}
+
+/*virtual*/ EquirectangularToFovVideo::~EquirectangularToFovVideo()
+{
+}
+
+bool EquirectangularToFovVideo::Convert(const QImage *eqImage, long int time, QImage *fovImage)
+{
+    double xEqHead, yEqHead, tiltHead; 
+    GetHeadPos(time, &xEqHead, &yEqHead, &tiltHead);
+
+    double horHeadRads, verHeadRads;
+    EquirectangularToSpherical(xEqHead, yEqHead, m_pArff->WidthPx(), m_pArff->HeightPx(), &horHeadRads, &verHeadRads);
+
+    Vec3 headVec(0,0,0);
+    SphericalToCartesian(horHeadRads, verHeadRads, &headVec);
+    
+    Vec3 vidVec(-1,0,0); // pointing to the middle of equirectangular projection
+    double headTiltRads = tiltHead * PI / 180;
+
+    Matrix33 rot = HeadToVideoRotation(headVec, headTiltRads, vidVec);
+
+    double horRads, verRads;
+    double vidHorRads, vidVerRads;
+    unsigned int xEq, yEq;
+
+    const uchar *eqImageBits = eqImage->bits();
+    uchar *fovImageBits = fovImage->bits();
+
+    GenerateSampling(fovImage);
+    for (int y=0; y<fovImage->height(); y++)
+    {
+        for (int x=0; x<fovImage->width(); x++)
+        {
+            horRads = m_vHorSampling[x];
+            verRads = m_vVerSampling[y];
+            // make it point towards center of equirectangular projection
+            horRads += PI;
+            verRads += PI/2;
+            Vec3 pixelVec(0,0,0);
+            SphericalToCartesian(horRads, verRads, &pixelVec);
+
+            Vec3 vidPixelVec = RotatePoint(rot, pixelVec);
+            CartesianToSpherical(vidPixelVec, &vidHorRads, &vidVerRads);
+            SphericalToEquirectangular(vidHorRads, vidVerRads, eqImage->width(), eqImage->height(), &xEq, &yEq);
+
+            //fovImage->setPixel(x, y, eqImage->pixel(xEq, yEq));
+			int posEq = yEq*eqImage->bytesPerLine() + xEq*4;
+            int posFov = y*fovImage->bytesPerLine() + x*4;
+            *(fovImageBits + posFov) = *(eqImageBits + posEq);
+            *(fovImageBits + posFov + 1) = *(eqImageBits + posEq + 1);
+            *(fovImageBits + posFov + 2) = *(eqImageBits + posEq + 2);
+            *(fovImageBits + posFov + 3) = *(eqImageBits + posEq + 3);
+        }
+    }
+
+    // *** Placeholder
+    return true;
+}
+
+double EquirectangularToFovVideo::GetAspectRatio()
+{
+    return (double)m_fovWidthPx/m_fovHeightPx;
+}
+
+// PRIVATE:
+
+void EquirectangularToFovVideo::GenerateSampling(const QImage *image)
+{
+    if ((int)m_vHorSampling.size() == image->width() && (int)m_vVerSampling.size() == image->height())
+        return;
+
+    m_vHorSampling.resize(image->width());
+    m_vVerSampling.resize(image->height());
+
+    double fovWidthRads = (m_fovWidthDeg * PI / 180);
+    double fovHeightRads = (m_fovHeightDeg * PI / 180);
+
+    Generate1DSampling(fovWidthRads, &m_vHorSampling);
+    Generate1DSampling(fovHeightRads, &m_vVerSampling);
+}
+
+void EquirectangularToFovVideo::Generate1DSampling(double fovRads, vector<double> *samples)
+{
+    for (size_t i=0; i<samples->size(); i++)
+        (*samples)[i] = i * fovRads / samples->size() - fovRads / 2.0;
+}

+ 210 - 0
GTA-VI/EquirectangularToFovVideo.cu

@@ -0,0 +1,210 @@
+// EquirectangularToFovVideo.cu
+
+#include "EquirectangularToFovVideo.h"
+#include "Util.h"
+
+#include <iostream>
+#include <cstdlib>
+#include <algorithm>
+#include <cmath>
+
+#define PI 3.14159265
+
+using namespace std;
+
+// PUBLIC:
+
+EquirectangularToFovVideo::EquirectangularToFovVideo(Arff* pArff) : EquirectangularToFovBase(pArff), eq_d{nullptr}, fov_d{nullptr}, m_pInfo{new ConvertionInfo()}
+{
+}
+
+/*virtual*/ EquirectangularToFovVideo::~EquirectangularToFovVideo()
+{
+    if (eq_d != nullptr)
+        cudaFree(eq_d);
+    if (fov_d != nullptr)
+        cudaFree(fov_d);
+    delete m_pInfo;
+}
+
+// The following functions are the same as the ones provided from the base class
+// but specialized to run on the device (GPU)
+
+__device__ void EquirectangularToSpherical_d(unsigned int xEq, unsigned int yEq, unsigned int widthPx, unsigned int heightPx, double *horRads, double *verRads)
+{
+    *horRads = (xEq * 2.0 * PI) / widthPx;
+    *verRads = (yEq * PI) / heightPx;
+}
+
+__device__ void SphericalToEquirectangular_d(double horRads, double verRads, unsigned int widthPx, unsigned int heightPx, unsigned int *xEq, unsigned int *yEq)
+{
+    int x = (int)((horRads / (2.0 * PI)) * widthPx + 0.5); // round double to closer int
+    int y = (int)((verRads / PI) * heightPx + 0.5);
+
+    // make sure returned values are within the video
+    if (x < 0)
+        x = widthPx + x - 1;
+    else if (x >= (int)widthPx)
+        x -= widthPx;
+    *xEq = (unsigned int) x;
+
+    if (y < 0)
+        y = heightPx + y - 1;
+
+    if (y >= (int)heightPx)
+        y = 2 * heightPx - y - 1;
+
+    *yEq = (unsigned int) y;
+}
+
+__device__ void SphericalToCartesian_d(double horRads, double verRads, Vec3 *cart)
+{
+    cart->x = sin(verRads)*cos(horRads);
+    cart->y = cos(verRads);
+    cart->z = sin(verRads)*sin(horRads);
+}
+
+__device__ void CartesianToSpherical_d(Vec3 cart, double *horRads, double *verRads)
+{
+    *horRads = atan2(cart.z, cart.x);
+    *verRads = acos(cart.y);
+}
+
+__device__ Vec3 RotatePoint_d(Matrix33 rot, Vec3 v)
+{
+    //Vec3 res(0,0,0);
+    Vec3 res = v; // Avoid __device__ constructor by using copy constructor
+
+    res.x = rot.mat[0][0]*v.x + rot.mat[0][1]*v.y + rot.mat[0][2]*v.z;
+    res.y = rot.mat[1][0]*v.x + rot.mat[1][1]*v.y + rot.mat[1][2]*v.z;
+    res.z = rot.mat[2][0]*v.x + rot.mat[2][1]*v.y + rot.mat[2][2]*v.z;
+
+    return res;
+}
+
+__global__ void GPUCalculation(const uchar *eq_d, uchar *fov_d, ConvertionInfo *info)
+{
+    double fovWidth_rads = (info->fovWidth_deg * PI / 180);
+    double fovHeight_rads = (info->fovHeight_deg * PI / 180);
+
+    double horRads, verRads;
+    double vidHorRads, vidVerRads;
+    unsigned int xEq, yEq;
+
+    int idx = blockIdx.x*blockDim.x + threadIdx.x;
+    int idy = blockIdx.y*blockDim.y + threadIdx.y;
+
+    int x = idx;
+    int y = idy;
+
+    if (x >= info->fovWidth_px || y >= info->fovHeight_px)
+        return;
+
+    horRads =  x * fovWidth_rads / info->fovWidth_px - fovWidth_rads / 2.0;
+    verRads = y * fovHeight_rads / info->fovHeight_px - fovHeight_rads / 2.0;
+    // make it point towards center of equirectangular projection
+    horRads += PI;
+    verRads += PI/2;
+    //Vec3 pixelVec(0,0,0);
+    Vec3 pixelVec = info->tmpVec; // Avoid __device__ constructor by using copy constructor
+    SphericalToCartesian_d(horRads, verRads, &pixelVec);
+
+    Vec3 vidPixelVec = RotatePoint_d(info->rot, pixelVec);
+    CartesianToSpherical_d(vidPixelVec, &vidHorRads, &vidVerRads);
+    SphericalToEquirectangular_d(vidHorRads, vidVerRads, info->eqWidth_px, info->eqHeight_px, &xEq, &yEq);
+
+	int posEq = yEq*info->eqWidth_px*4 + xEq*4;
+    int posFov = y*info->fovWidth_px*4 + x*4;
+    *(fov_d + posFov) = *(eq_d + posEq);
+    *(fov_d + posFov + 1) = *(eq_d + posEq + 1);
+    *(fov_d + posFov + 2) = *(eq_d + posEq + 2);
+    *(fov_d + posFov + 3) = *(eq_d + posEq + 3);
+}
+
+bool EquirectangularToFovVideo::Convert(const QImage *eqImage, long int time, QImage *fovImage)
+{
+    double xEqHead, yEqHead, tiltHead; 
+    GetHeadPos(time, &xEqHead, &yEqHead, &tiltHead);
+
+    double horHeadRads, verHeadRads;
+    EquirectangularToSpherical(xEqHead, yEqHead, m_pArff->WidthPx(), m_pArff->HeightPx(), &horHeadRads, &verHeadRads);
+
+    Vec3 headVec(0,0,0);
+    SphericalToCartesian(horHeadRads, verHeadRads, &headVec);
+    
+    Vec3 vidVec(-1,0,0); // pointing to the middle of equirectangular projection
+    double headTiltRads = tiltHead * PI / 180;
+
+    Matrix33 rot = HeadToVideoRotation(headVec, headTiltRads, vidVec);
+
+    const uchar *eqImageBits = eqImage->bits();
+    uchar *fovImageBits = fovImage->bits();
+
+    GenerateSampling(fovImage);
+
+    // Set up GPU for calculation
+    ConvertionInfo *info = new ConvertionInfo();
+    ConvertionInfo *info_d;
+    m_pInfo->rot = rot;
+    m_pInfo->fovWidth_deg = m_fovWidthDeg;
+    m_pInfo->fovHeight_deg = m_fovHeightDeg;
+    m_pInfo->fovWidth_px = fovImage->width();
+    m_pInfo->fovHeight_px = fovImage->height();
+    m_pInfo->eqWidth_px = eqImage->width();
+    m_pInfo->eqHeight_px = eqImage->height();
+
+    if (eq_d == nullptr)
+        cudaMalloc((void**)&eq_d, eqImage->byteCount());
+    if (fov_d == nullptr)
+        cudaMalloc((void**)&fov_d, fovImage->byteCount());
+    cudaMalloc((void**)&info_d, sizeof(ConvertionInfo)); 
+    cudaMemcpy(eq_d, eqImageBits, eqImage->byteCount(), cudaMemcpyHostToDevice);
+    cudaMemcpy(fov_d, fovImageBits, fovImage->byteCount(), cudaMemcpyHostToDevice);
+    cudaMemcpy(info_d, m_pInfo, sizeof(ConvertionInfo), cudaMemcpyHostToDevice);
+
+    int device;
+    cudaGetDevice(&device);
+    cudaDeviceProp devProp;
+    cudaGetDeviceProperties(&devProp, device);
+    int maxThreads = devProp.maxThreadsPerBlock;
+    int maxThreadsDim = floor(sqrt(maxThreads));
+
+    dim3 dimBlock(maxThreadsDim, maxThreadsDim);
+    dim3 dimGrid(fovImage->width()/dimBlock.x + 1, fovImage->height()/dimBlock.y + 1);
+
+    GPUCalculation<<<dimGrid,dimBlock>>>(eq_d, fov_d, info_d);
+
+    cudaMemcpy(fovImageBits, fov_d, fovImage->byteCount(), cudaMemcpyDeviceToHost);
+    cudaFree(info_d);
+
+    // *** Placeholder
+    return true;
+}
+
+double EquirectangularToFovVideo::GetAspectRatio()
+{
+    return (double)m_fovWidthPx/m_fovHeightPx;
+}
+
+// PRIVATE:
+
+void EquirectangularToFovVideo::GenerateSampling(const QImage *image)
+{
+    if ((int)m_vHorSampling.size() == image->width() && (int)m_vVerSampling.size() == image->height())
+        return;
+
+    m_vHorSampling.resize(image->width());
+    m_vVerSampling.resize(image->height());
+
+    double fovWidthRads = (m_fovWidthDeg * PI / 180);
+    double fovHeightRads = (m_fovHeightDeg * PI / 180);
+
+    Generate1DSampling(fovWidthRads, &m_vHorSampling);
+    Generate1DSampling(fovHeightRads, &m_vVerSampling);
+}
+
+void EquirectangularToFovVideo::Generate1DSampling(double fovRads, vector<double> *samples)
+{
+    for (size_t i=0; i<samples->size(); i++)
+        (*samples)[i] = i * fovRads / samples->size() - fovRads / 2.0;
+}

+ 60 - 0
GTA-VI/EquirectangularToFovVideo.h

@@ -0,0 +1,60 @@
+// EquirectangularToFovVideo.h
+// equirectangular to field of view conversion for videos/images
+
+#ifndef __EQUIRECTANGULARTOFOVVIDEO_H__
+#define __EQUIRECTANGULARTOFOVVIDEO_H__
+
+#include "EquirectangularToFovBase.h"
+
+#include <QImage>
+
+using namespace std;
+
+#ifdef USE_CUDA
+struct ConvertionInfo
+{
+    double fovWidth_deg;
+    double fovHeight_deg;
+    int fovWidth_px;
+    int fovHeight_px;
+    int eqWidth_px;
+    int eqHeight_px;
+
+    Matrix33 rot;
+    Vec3 tmpVec;
+
+    ConvertionInfo(): tmpVec{Vec3(0,0,0)} {}
+};
+#endif
+
+class EquirectangularToFovVideo : public EquirectangularToFovBase
+{
+public:
+    EquirectangularToFovVideo(Arff* pArff);
+    virtual ~EquirectangularToFovVideo();
+
+    bool Convert(const QImage *eqImage, long int time, QImage *fovImage);
+
+    double GetAspectRatio();
+    ///< Return the aspect ratio of the FOV. Aspect ratio = width_fov/height_fov
+
+private:
+    void GenerateSampling(const QImage *image);
+    ///< This function generates the sampling on the sphere for the given image size
+
+    void Generate1DSampling(double fovRads, vector<double> *samples);
+    ///< For the fiven field of view (rads) populates the provided vector with
+    ///< the correct sampling angles. The samples are placed of the periphery 
+    ///< of the circle. This means that every sample represents the same angle
+
+    vector<double> m_vHorSampling;
+    vector<double> m_vVerSampling;
+
+#ifdef USE_CUDA
+    uchar *eq_d;
+    uchar *fov_d;
+    ConvertionInfo *m_pInfo;
+#endif
+};
+
+#endif /*__EQUIRECTANGULARTOFOVVIDEO_H__*/

+ 58 - 0
GTA-VI/EquirectangularToHead.cpp

@@ -0,0 +1,58 @@
+// EquirectangularToHead.cpp
+
+#include "EquirectangularToHead.h"
+#include "../arffHelper/ArffUtil.h"
+
+#include <iostream>
+#include <memory>
+
+using namespace std;
+
+EquirectangularToHead::EquirectangularToHead(Arff *arff) : m_pArff(arff) 
+{
+}
+
+EquirectangularToHead::~EquirectangularToHead()
+{
+}
+
+unique_ptr<Arff> EquirectangularToHead::Convert()
+{
+    const char *c_horHeadAttName = "x_head";
+    const char *c_verHeadAttName = "y_head";
+
+    unique_ptr<Arff> convertedArff(new Arff());
+    convertedArff->SetWidthPx(m_pArff->WidthPx());
+    convertedArff->SetHeightPx(m_pArff->HeightPx());
+    convertedArff->SetRelation(m_pArff->GetRelation());
+    convertedArff->AddColumn("time", "integer");
+    convertedArff->AddColumn("x", "numeric");
+    convertedArff->AddColumn("y", "numeric");
+    convertedArff->AddColumn("confidence", "numeric");
+
+    int xInd, yInd, timeInd, confInd;
+    bool res = ArffUtil::GetTXYCindex(m_pArff, timeInd, xInd, yInd, confInd);
+
+    int xHeadInd, yHeadInd;
+    res &= m_pArff->GetAttIndex(c_horHeadAttName, xHeadInd);
+    res &= m_pArff->GetAttIndex(c_verHeadAttName, yHeadInd);
+
+    if (!res)
+    {
+        cout << "ERROR: could not find time, or x, or y, or confidence, or " << c_horHeadAttName << ", or " << c_verHeadAttName << " in the provided ARFF file." << endl;
+        exit(-1);
+    }
+
+    for (auto entry : *m_pArff)
+    {
+        vector<double> row;
+        row.push_back(entry[timeInd]);
+        row.push_back(entry[xHeadInd]);
+        row.push_back(entry[yHeadInd]);
+        row.push_back(entry[confInd]);
+
+        convertedArff->AddRow(row);
+    }
+
+    return move(convertedArff);
+}

+ 24 - 0
GTA-VI/EquirectangularToHead.h

@@ -0,0 +1,24 @@
+// EquirectangularToHead.h
+// Transfers the x_head, y_head coordinates to the x,y
+
+#ifndef __EQUIRECTANGULARTOHEAD_H__
+#define __EQUIRECTANGULARTOHEAD_H__
+
+#include "../arffHelper/Arff.h"
+
+class EquirectangularToHead
+{
+public:
+    EquirectangularToHead(Arff *pArff);
+
+    ~EquirectangularToHead();
+
+    unique_ptr<Arff> Convert();
+    ///< Populates the x,y attributes of the returned ARFF with the x_head, 
+    ///< y_head of the input ARFF.
+
+private:
+    Arff    *m_pArff;
+};
+
+#endif /*__EQUIRECTANGULARTOHEAD_H__*/

+ 604 - 0
GTA-VI/FlowWidget.cpp

@@ -0,0 +1,604 @@
+// FlowWidget.cpp
+
+#include "FlowWidget.h"
+#include "../arffHelper/ArffUtil.h"
+#include "Unused.h"
+
+#include <iostream>
+#include <limits>
+#include <cmath>
+
+using namespace std;
+
+// PUBLIC SLOTS:
+
+void FlowWidget::HandleNewTime(int time)
+{
+    SetCurrentTime(time);
+}
+
+// PUBLIC:
+
+FlowWidget::FlowWidget(QWidget *parent) : QWidget(parent),
+    m_pStream(NULL), m_pArff(NULL), 
+    m_horVector(0), m_verVector(0), m_duration(0)
+{
+    SetCurrentTime(0);
+    SetInterval(100000); // 100ms
+}
+
+FlowWidget::~FlowWidget()
+{
+    if (m_pStream!=NULL)
+        fclose(m_pStream);
+}
+
+QSize FlowWidget::minimumSizeHint() const /*override*/
+{
+    return QSize(160,90);
+}
+
+void FlowWidget::SetCurrentTime(int currentTime)
+{
+    m_currentTime = currentTime;
+    CalculateMinMaxGaze();
+
+    // update area
+    update();
+}
+
+void FlowWidget::SetDuration(qint64 duration)
+{
+    m_duration = duration;
+}
+
+void FlowWidget::SetMedia(QString pathToFile)
+{
+    ReadFlow(pathToFile.toStdString().c_str());
+}
+
+void FlowWidget::SetData(Arff &arff)
+{
+    m_pArff = &arff;
+    int confInd;
+    bool res = ArffUtil::GetTXYCindex(m_pArff, m_timeInd, m_xInd, m_yInd, confInd);
+    UNUSED(confInd);
+    UNUSED(res);
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+
+
+    if (rows<2)
+        return;
+
+    m_meanHorGazeVel = 0;
+    m_meanVerGazeVel = 0;
+
+    for (int i=0; i<rows-1; i++){
+        m_meanHorGazeVel += abs((*m_pArff)[i][m_xInd]-(*m_pArff)[i+1][m_xInd]) / ((*m_pArff)[i+1][m_timeInd]-(*m_pArff)[i][m_timeInd]);
+        m_meanVerGazeVel += abs((*m_pArff)[i][m_yInd]-(*m_pArff)[i+1][m_yInd]) / ((*m_pArff)[i+1][m_timeInd]-(*m_pArff)[i][m_timeInd]);
+    }
+
+    m_meanHorGazeVel /= (double)(rows-1);
+    m_meanVerGazeVel /= (double)(rows-1);
+
+}
+
+void FlowWidget::SetInterval(qint64 interval)
+{
+    m_intervalDuration = interval;
+}
+
+
+// PROTECTED:
+
+void FlowWidget::paintEvent(QPaintEvent *) /*override*/
+{
+    QPainter painter(this);
+    painter.setRenderHint(QPainter::Antialiasing, true);
+
+    PaintFlow(&painter);
+    PaintGazeRect(&painter);
+    PaintVelocity(&painter);
+    PaintLegend(&painter);
+}
+
+void FlowWidget::resizeEvent(QResizeEvent *event) /*override*/
+{
+    QWidget::resizeEvent(event);
+    UpdateRectangle();
+}
+
+// PRIVATE:
+
+void FlowWidget::PaintFlow(QPainter *painter)
+{
+    if (CalculateFrameNum() != m_currentFrame){
+        m_currentFrame = CalculateFrameNum();
+        DrawImage();
+    }
+
+    painter->drawImage(m_targetRect, m_image, QRect(QPoint(0,0), m_image.size()));
+}
+
+void FlowWidget::PaintGazeRect(QPainter *painter)
+{
+    double width = (double)(m_maxGazeX-m_minGazeX)*(double)m_targetRect.width()/(double)m_width;
+    double height = (double)(m_maxGazeY-m_minGazeY)*(double)m_targetRect.height()/(double)m_height;
+    double startX = m_targetRect.left() + (double)(m_minGazeX)*(double)m_targetRect.width()/(double)m_width;
+    double startY = m_targetRect.top() + (double)(m_minGazeY)*(double)m_targetRect.height()/(double)m_height;
+
+    painter->setPen(QPen(Qt::black, 4.0*m_targetRect.width()/1000.0));
+    painter->drawRect(startX, startY,width, height);
+}
+
+void FlowWidget::PaintVelocity(QPainter *painter)
+{
+    if (m_pStream==NULL)
+        return;
+
+    // update mean velocity
+    CalculateMeanVelocity();
+
+    QPointF center(m_targetRect.left()+m_targetRect.height()/3.5, m_targetRect.top()+m_targetRect.height()/3.5);
+    double coorLength = m_targetRect.height()/5.0;
+
+    // paint coordinates
+    painter->setPen(QPen(Qt::black, 2.0*m_targetRect.width()/1000.0));
+    painter->setBrush(Qt::black);
+    QPointF points[2];
+
+    // x axis
+    points[0] = center - QPoint(coorLength,0);
+    points[1] = center + QPoint(coorLength,0);
+    PaintVector(points, painter);
+
+    // y axis
+    points[0] = center - QPoint(0,coorLength);
+    points[1] = center + QPoint(0,coorLength);
+    PaintVector(points, painter);
+
+    // paint horizontal velocity component
+    painter->setPen(QPen(Qt::blue, 2.5*m_targetRect.width()/1000.0));
+    painter->setBrush(Qt::blue);
+    points[0] = center;
+    if (m_horVector > 0)
+    {
+        // check limits because threshold for max is 0.7
+        if (m_horVector/m_normMaxU > 1)
+            points[1] = center + QPoint(coorLength,0);
+        else
+            points[1] = center + QPoint(coorLength*m_horVector/m_normMaxU,0);
+
+    }
+    else
+    {
+        if (m_horVector/m_normMinU > 1)
+            points[1] = center - QPoint(coorLength,0);
+        else
+            points[1] = center + QPoint(coorLength*(-1)*m_horVector/m_normMinU,0);
+    }
+    //PaintVector(points, painter);
+    // Store for later
+    QPointF horComp = points[1];
+
+    // paint vertical velocity component
+    painter->setPen(QPen(Qt::yellow, 2.5*m_targetRect.width()/1000.0));
+    painter->setBrush(Qt::yellow);
+    points[0] = center;
+    if (m_verVector > 0)
+    {
+        if (m_verVector/m_normMaxV > 1)
+            points[1] = center + QPoint(0, coorLength);
+        else
+            points[1] = center + QPoint(0, coorLength*m_verVector/m_normMaxV);
+    }
+    else
+    {
+        if (m_verVector/m_normMinV > 1)
+            points[1] = center - QPoint(0, coorLength);
+        else
+            points[1] = center + QPoint(0, coorLength*(-1)*m_verVector/m_normMinV);
+    }
+    //PaintVector(points, painter);
+
+    // paint velocity component
+    painter->setPen(QPen(Qt::red, 3*m_targetRect.width()/1000.0));
+    painter->setBrush(Qt::red);
+    points[1] += horComp;
+    points[1] -= center; // added twice
+    PaintVector(points, painter);
+
+    // paint gaze velocity
+    double normHorVel = m_horGazeVel/(1.0*m_meanHorGazeVel);
+    double normVerVel = m_verGazeVel/(1.0*m_meanVerGazeVel);
+
+    // keep aspect ratio if both are big
+    if (abs(normHorVel)>1 && abs(normVerVel)>1)
+    {
+        if (abs(normHorVel)>abs(normVerVel))
+            normVerVel /= abs(normHorVel);
+        else
+            normHorVel /= abs(normVerVel);
+    }
+    if (abs(normHorVel)>1)
+        normHorVel /= abs(normHorVel);
+    if (abs(normVerVel)>1)
+        normVerVel /= abs(normVerVel);
+
+    points[1] = center + QPoint(normHorVel*coorLength,normVerVel*coorLength);
+    painter->setPen(QPen(QColor(0,0,255,255), 2.5*m_targetRect.width()/1000.0));
+    painter->setBrush(QBrush(QColor(0,0,255,255)));
+    PaintVector(points, painter);
+
+
+}
+
+void FlowWidget::PaintVector(QPointF *points, QPainter *painter)
+{
+    QLineF line(points[0], points[1]);
+    painter->drawLine(line);
+
+    // Draw the arrows
+    double angle = ::acos(line.dx() / line.length());
+    double Pi = 3.14159265359;
+    double arrowSize = (double)m_targetRect.height()/30.0;
+    if (sqrt(pow(points[0].x()-points[1].x(),2) + pow(points[0].y()-points[1].y(),2)) < arrowSize)
+        arrowSize = sqrt(pow(points[0].x()-points[1].x(),2) + pow(points[0].y()-points[1].y(),2));
+
+    if (line.dy() >= 0)
+        angle = 2*Pi - angle;
+
+    QPointF destArrowP1 = points[1] + QPointF(sin(angle - Pi / 3) * arrowSize,
+                                              cos(angle - Pi / 3) * arrowSize);
+    QPointF destArrowP2 = points[1] + QPointF(sin(angle - Pi + Pi / 3) * arrowSize,
+                                              cos(angle - Pi + Pi / 3) * arrowSize);
+
+    painter->drawPolygon(QPolygonF() << line.p2() << destArrowP1 << destArrowP2);
+}
+
+void FlowWidget::PaintLegend(QPainter *painter)
+{
+    int legendHeight = m_targetRect.height()/30.0;
+    int legendWidth = m_targetRect.width()/10;
+    QFont font = painter->font();
+    font.setPointSize(legendHeight/1.5);
+    painter->setFont(font);
+
+    painter->setBrush(QBrush(QColor(255,0,0,160)));
+    painter->setPen(QPen(QColor(255,0,0,160)));
+    painter->drawRect(QRect(m_targetRect.left(),m_targetRect.top(), legendWidth, legendHeight));
+    painter->setPen(QPen(Qt::black));
+    painter->drawText(QRect(m_targetRect.left(),m_targetRect.top(), legendWidth, legendHeight), 0, "video flow");
+
+
+    painter->setBrush(QBrush(QColor(0,0,255,160)));
+    painter->setPen(QPen(QColor(0,0,255,160)));
+    painter->drawRect(QRect(m_targetRect.left(),m_targetRect.top()+legendHeight, legendWidth, legendHeight));
+    painter->setPen(QPen(Qt::black));
+    painter->drawText(QRect(m_targetRect.left(),m_targetRect.top()+legendHeight, legendWidth*1.5, legendHeight), 0, "gaze velocity");
+}
+
+void FlowWidget::ReadFlow(const char *filename, float normLevel /*=0.7*/)
+{
+    // close stream if open
+    if (m_pStream != NULL)
+        fclose(m_pStream);
+
+    // open new file
+    m_pStream = fopen(filename, "rb");
+    // return if not opened
+    if (m_pStream == NULL)
+        return;
+
+    // read data about flow
+    size_t res;
+    res = fread(&m_numOfFrames, sizeof(int), 1, m_pStream);
+    res = fread(&m_width, sizeof(int), 1, m_pStream);
+    res = fread(&m_height, sizeof(int), 1, m_pStream);
+    res = fread(&m_storedWidth, sizeof(int), 1, m_pStream);
+    res = fread(&m_storedHeight, sizeof(int), 1, m_pStream);
+
+    // allocate QImage
+    m_image = QImage(m_storedWidth, m_storedHeight, QImage::Format_RGB32);
+    // initialize to fixed color
+    unsigned char *pImageData = m_image.bits(); // BGRA format
+    for (int i=0; i<m_storedWidth*m_storedHeight*4; i++)
+        pImageData[i] = 220;
+
+    // update rectangle
+    UpdateRectangle();
+
+    // read max min for each frame
+    float minU, maxU, minV, maxV;
+    for (int i=0; i<m_numOfFrames; i++)
+    {
+        res = fread(&minU, sizeof(float), 1, m_pStream);
+        res = fread(&maxU, sizeof(float), 1, m_pStream);
+        res = fread(&minV, sizeof(float), 1, m_pStream);
+        res = fread(&maxV, sizeof(float), 1, m_pStream);
+
+        m_vMinU.push_back(minU);
+        m_vMaxU.push_back(maxU);
+        m_vMinV.push_back(minV);
+        m_vMaxV.push_back(maxV);
+
+        fseek(m_pStream, 2*m_storedWidth*m_storedHeight*sizeof(short int), SEEK_CUR);
+    }
+    UNUSED(res);
+
+    // reset seek to first frame
+    fseek(m_pStream, 5*sizeof(int), SEEK_SET);
+
+    m_currentFrame = 0;
+    m_ncols = 0;
+
+    // calculate normalization factor as 70% of min-max samples
+    vector<float> tmpVec;
+
+    // check limits
+    normLevel = normLevel <= 0.0 ? 0.01: normLevel;
+    normLevel = normLevel > 1.0? 1.0: normLevel;
+
+    int threshold = normLevel*(m_vMinU.size()-1);
+    tmpVec = m_vMinU;
+    sort(tmpVec.begin(), tmpVec.end(), greater<float>()); // sort negative in descending order
+    m_normMinU = tmpVec[threshold];
+    tmpVec = m_vMaxU;
+    sort(tmpVec.begin(), tmpVec.end());
+    m_normMaxU = tmpVec[threshold];
+    tmpVec = m_vMinV;
+    sort(tmpVec.begin(), tmpVec.end(), greater<float>()); // sort negative in descending order
+    m_normMinV = tmpVec[threshold];
+    tmpVec = m_vMaxV;
+    sort(tmpVec.begin(), tmpVec.end());
+    m_normMaxV = tmpVec[threshold];
+
+}
+
+int FlowWidget::CalculateFrameNum()
+{
+    int frameNum = (int)((double)m_numOfFrames*(double)m_currentTime/(double)m_duration);
+
+    if (frameNum < 0)
+        return 0;
+
+    if (frameNum > m_numOfFrames-1)
+        return m_numOfFrames-1;
+
+    return frameNum;
+}
+
+void FlowWidget::DrawImage()
+{
+    if (m_pStream==NULL)
+        return;
+
+    // move file pointer to correct position
+    long int frameOffset = 5*sizeof(int) + m_currentFrame*(4*sizeof(float) + 2*m_storedWidth*m_storedHeight*sizeof(short int));
+    fseek(m_pStream, frameOffset, SEEK_SET);
+    // skip max-min information
+    fseek(m_pStream, 4*sizeof(float), SEEK_CUR);
+
+    short int u,v;
+    float hor, ver;
+    int rgb[3];
+    int pointerImage = 0;
+    unsigned char *pImageData = m_image.bits(); // BGRA format
+    size_t res;
+    for (int y=0; y<m_storedHeight; y++)
+    {
+        for (int x=0; x<m_storedWidth; x++)
+        {
+            res = fread(&u, sizeof(short int), 1, m_pStream);
+            res = fread(&v, sizeof(short int), 1, m_pStream);
+
+            // without normalization
+            hor = (float)u*m_vMinU[m_currentFrame]/(float)numeric_limits<short int>::min();
+            ver = (float)v*m_vMinV[m_currentFrame]/(float)numeric_limits<short int>::min();
+
+            ComputeColor(hor, ver, rgb);
+           
+            pImageData[pointerImage+2] = rgb[0];
+            pImageData[pointerImage+1] = rgb[1];
+            pImageData[pointerImage+0] = rgb[2];
+
+            pointerImage += 4;
+        }
+    }
+    UNUSED(res);
+}
+
+
+// The code of the folowing 3 function was based on
+// http://vision.middlebury.edu/flow/data/flow-code.zip/colorcode.cpp
+void FlowWidget::SetCols(int r, int g, int b, int k)
+{
+    m_colorWheel[k][0] = r;
+    m_colorWheel[k][1] = g;
+    m_colorWheel[k][2] = b;
+}
+
+void FlowWidget::MakeColorWheel()
+{
+    // relative lengths of color transitions:
+    // these are chosen based on perceptual similarity
+    // (e.g. one can distinguish more shades between red and yellow 
+    //  than between yellow and green)
+    int RY = 15;
+    int YG = 6;
+    int GC = 4;
+    int CB = 11;
+    int BM = 13;
+    int MR = 6;
+    m_ncols = RY + YG + GC + CB + BM + MR;
+    if (m_ncols > MAXCOLS)
+    exit(1);
+    int i;
+    int k = 0;
+    for (i = 0; i < RY; i++) SetCols(255,      255*i/RY,     0,        k++);
+    for (i = 0; i < YG; i++) SetCols(255-255*i/YG, 255,      0,        k++);
+    for (i = 0; i < GC; i++) SetCols(0,        255,      255*i/GC,     k++);
+    for (i = 0; i < CB; i++) SetCols(0,        255-255*i/CB, 255,      k++);
+    for (i = 0; i < BM; i++) SetCols(255*i/BM,     0,        255,      k++);
+    for (i = 0; i < MR; i++) SetCols(255,      0,        255-255*i/MR, k++);
+}
+
+void FlowWidget::ComputeColor(float u, float v, int *rgb)
+{
+    if (m_ncols == 0)
+        MakeColorWheel();
+
+    float rad = sqrt(u * u + v * v);
+    float a = atan2(-v, -u) / M_PI;
+    float fk = (a + 1.0) / 2.0 * (m_ncols-1);
+    int k0 = (int)fk;
+    int k1 = (k0 + 1) % m_ncols;
+    float f = fk - k0;
+    //f = 0; // uncomment to see original color wheel
+    float col0, col;
+    for (int b = 0; b < 3; b++) {
+        col0 = m_colorWheel[k0][b] / 255.0;
+        col = m_colorWheel[k1][b] / 255.0;
+        col = (1 - f) * col0 + f * col;
+        if (rad <= 1)
+            col = 1 - rad * (1 - col); // increase saturation with radius
+        else
+            col *= .75; // out of range
+        rgb[b] = (int)(255.0 * col);
+    }
+}
+
+void FlowWidget::UpdateRectangle()
+{
+    QSize size = m_image.size();
+    size.scale(this->size(), Qt::KeepAspectRatio);
+    m_targetRect = QRect(QPoint(0,0), size);
+    m_targetRect.moveCenter(this->rect().center());
+}
+
+void FlowWidget::CalculateMinMaxGaze()
+{
+    if (m_pArff == NULL)
+        return;
+
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+    UNUSED(columns);
+    if (rows == 0)
+        return;
+
+    m_minGazeX = numeric_limits<int>::max();
+    m_minGazeY = numeric_limits<int>::max();
+    m_maxGazeX = numeric_limits<int>::min();
+    m_maxGazeY = numeric_limits<int>::min();
+
+    m_horGazeVel = 0.0;
+    m_verGazeVel = 0.0;
+    int counter = 0;
+
+    for (int i=0; i<rows-1; i++)
+    {
+        if ((*m_pArff)[i][m_timeInd]>m_currentTime-m_intervalDuration && (*m_pArff)[i][m_timeInd]<m_currentTime+m_intervalDuration)
+        {
+            if ((*m_pArff)[i][m_xInd] < m_minGazeX)
+                m_minGazeX = (*m_pArff)[i][m_xInd];
+            if ((*m_pArff)[i][m_xInd] > m_maxGazeX)
+                m_maxGazeX = (*m_pArff)[i][m_xInd];
+
+            if ((*m_pArff)[i][m_yInd] < m_minGazeY)
+                m_minGazeY = (*m_pArff)[i][m_yInd];
+            if ((*m_pArff)[i][m_yInd] > m_maxGazeY)
+                m_maxGazeY = (*m_pArff)[i][m_yInd];
+
+            // calculate velocity
+            m_horGazeVel += ((*m_pArff)[i+1][m_xInd]-(*m_pArff)[i][m_xInd]) / ((*m_pArff)[i+1][m_timeInd]-(*m_pArff)[i][m_timeInd]);
+            m_verGazeVel += ((*m_pArff)[i+1][m_yInd]-(*m_pArff)[i][m_yInd]) / ((*m_pArff)[i+1][m_timeInd]-(*m_pArff)[i][m_timeInd]);
+            counter++;
+        }
+    }
+
+    if (counter > 0)
+    {
+        m_horGazeVel /= (double)counter;
+        m_verGazeVel /= (double)counter;
+    }
+
+    if (m_minGazeX < 0)
+        m_minGazeX = 0;
+    if (m_minGazeX >= m_width)
+        m_minGazeX = m_width-1;
+
+    if (m_maxGazeX < 0)
+        m_maxGazeX = 0;
+    if (m_maxGazeX >= m_width)
+        m_maxGazeX = m_width-1;
+
+    if (m_minGazeY < 0)
+        m_minGazeY = 0;
+    if (m_minGazeY >= m_height)
+        m_minGazeY = m_height-1;
+
+    if (m_maxGazeY < 0)
+        m_maxGazeY = 0;
+    if (m_maxGazeY >= m_height)
+        m_maxGazeY = m_height-1;
+}
+
+void FlowWidget::CalculateMeanVelocity()
+{
+    if (m_pStream==NULL)
+        return;
+
+    long int frameOffset = 5*sizeof(int) + m_currentFrame*(4*sizeof(float) + 2*m_storedWidth*m_storedHeight*sizeof(short int));
+    fseek(m_pStream, frameOffset, SEEK_SET);
+    // skip max-min information
+    fseek(m_pStream, 4*sizeof(float), SEEK_CUR);
+
+    short int u,v;
+    int rgb[3];
+    int pointerImage = 0;
+    unsigned char *pImageData = m_image.bits(); // BGRA format
+    int yStart = m_minGazeY*(double)m_storedHeight/(double)m_height;
+    int yEnd = m_minGazeY*(double)m_storedHeight/(double)m_height;
+    int xStart = m_minGazeX*(double)m_storedWidth/(double)m_width;
+    int xEnd = m_maxGazeX*(double)m_storedWidth/(double)m_width;
+
+    m_horVector = 0;
+    m_verVector = 0;
+
+    // skip first lines of the image
+    fseek(m_pStream, 2*m_storedWidth*yStart*sizeof(short int), SEEK_CUR);
+
+    size_t res;
+    for (int y=0; y<yEnd-yStart+1; y++){ //** check +1 for corner cases
+        for (int x=0; x<m_storedWidth; x++){
+            res = fread(&u, sizeof(short int), 1, m_pStream);
+            res = fread(&v, sizeof(short int), 1, m_pStream);
+
+            // process data
+            if (x>=xStart && x<=xEnd)
+            {
+                if (u < 0)
+                    m_horVector += (double)u*m_vMinU[m_currentFrame]/(double)numeric_limits<short int>::min();
+                else
+                    m_horVector += (double)u*m_vMaxU[m_currentFrame]/(double)numeric_limits<short int>::max();
+
+                if (v < 0)
+                    m_verVector += (double)v*m_vMinV[m_currentFrame]/(double)numeric_limits<short int>::min();
+                else
+                    m_verVector += (double)v*m_vMaxV[m_currentFrame]/(double)numeric_limits<short int>::max();
+            }
+
+            pImageData[pointerImage+2] = rgb[0];
+            pImageData[pointerImage+1] = rgb[1];
+            pImageData[pointerImage+0] = rgb[2];
+
+            pointerImage += 4;
+        }
+    }
+    UNUSED(res);
+
+    m_horVector /= (yEnd-yStart+1)*(xEnd-xStart+1);
+    m_verVector /= (yEnd-yStart+1)*(xEnd-xStart+1);
+}

+ 140 - 0
GTA-VI/FlowWidget.h

@@ -0,0 +1,140 @@
+// FlowWidget.h
+
+#ifndef __FLOWWIDGET_H__
+#define __FLOWWIDGET_H__
+
+#include <QtWidgets>
+#include <cstdio>
+
+#include "../arffHelper/Arff.h"
+
+#define MAXCOLS 60
+
+class FlowWidget : public QWidget
+{
+    Q_OBJECT
+
+public slots:
+    void HandleNewTime(int time);
+    ///< handles signal of time change from video or by scrolling the area
+
+public:
+    FlowWidget(QWidget *parent=0);
+
+    ~FlowWidget(void);
+
+    QSize minimumSizeHint() const Q_DECL_OVERRIDE;
+
+    void SetCurrentTime(int currentTime);
+    ///< sets the time of the video in us
+
+    void SetDuration(qint64 duration);
+    ///< sets the duration of the video in us
+
+    void SetMedia(QString pathToFile);
+    ///< points to the flow file
+
+    void SetData(Arff &arff);
+    ///< sets gaze data
+
+    void SetInterval(qint64 interval);
+    ///< sets the time interval of window in which samples are counted
+
+protected:
+    void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
+    ///< called on update(), repaint(). Paints flow and bounding box with 
+    ///< mean velocity vector in it
+
+    void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
+    ///< handles painting box resize event
+
+private:
+    void PaintFlow(QPainter *painter);
+    ///< draw image and paints on canvas
+
+    void PaintGazeRect(QPainter *painter);
+    ///< draws the rectangle that contains the gaze samples
+
+    void PaintVelocity(QPainter *painter);
+    ///< paints the coordinates system and the velocity vectors
+
+    void PaintVector(QPointF *points, QPainter *painter);
+    ///< paints the given vector, with the already set up painter
+
+    void PaintLegend(QPainter *painter);
+    ///< paints legend for colored vectors
+
+    void ReadFlow(const char *filename, float normLevel=0.7);
+    ///< read data from the flow file
+
+    int CalculateFrameNum(void);
+    ///< calculates the frame number of the current time
+
+    void DrawImage(void);
+    ///< draws the current frame to the image
+
+    void SetCols(int r, int g, int b, int k);
+    ///< Creates a color wheel. Based on http://vision.middlebury.edu/flow/data/flow-code.zip/colorco    de.cpp
+
+    void MakeColorWheel(void);
+    ///< Created color wheel. For more info see \ref SetCols
+
+    void ComputeColor(float u, float v, int *rgb);
+    ///< Computes color for the given u,v components. For more info see \ref SetCols
+
+    void UpdateRectangle(void);
+    ///< updates painting rectangle
+
+    void CalculateMinMaxGaze(void);
+    ///< calculates min max gaze coordinates for the interval
+    ///< and the mean velocity of gaze samples
+
+    void CalculateMeanVelocity(void);
+
+    FILE                        *m_pStream; // flow stream
+    Arff                        *m_pArff; // pointer to Arff
+
+    int                         m_timeInd;
+    int                         m_xInd;
+    int                         m_yInd;
+
+    int                         m_numOfFrames;
+    int                         m_width;
+    int                         m_height;
+    int                         m_storedWidth;
+    int                         m_storedHeight;
+    vector<float>               m_vMinU;
+    vector<float>               m_vMaxU;
+    vector<float>               m_vMinV;
+    vector<float>               m_vMaxV;
+    float                       m_normMinU; // normalization factor based on percentage of minU
+    float                       m_normMaxU;
+    float                       m_normMinV;
+    float                       m_normMaxV;
+
+    int                         m_minGazeX; // max min gaze coordinates for current time within interval
+    int                         m_maxGazeX;
+    int                         m_minGazeY;
+    int                         m_maxGazeY;
+
+    double                      m_horVector; // flow vectors for rectangle
+    double                      m_verVector;
+
+    double                      m_horGazeVel; // gaze velocity for interval
+    double                      m_verGazeVel;
+    double                      m_meanHorGazeVel;
+    double                      m_meanVerGazeVel;
+
+    int                         m_colorWheel[MAXCOLS][3];
+    int                         m_ncols;
+
+    int                         m_currentFrame; // frame already drawn on QImage
+    qint64                      m_currentTime; // in us
+    qint64                      m_intervalDuration; // duration of intervals for before and after gaze sample
+    QImage                      m_image; // image holdinh flow in RGB
+    QRect                       m_targetRect; // rect to paint
+    qint64                      m_duration; // duration of the video
+
+};
+
+#endif /*__FLOWWIDGET_H__*/

+ 106 - 0
GTA-VI/GTA-VI.cpp

@@ -0,0 +1,106 @@
+
+#include "../arffHelper/Arff.h"
+#include "VideoWidget.h"
+#include "MainWindow.h"
+
+#include <QtGui>
+#include <iostream>
+
+using namespace std;
+ 
+int main(int argc, char *argv[]) 
+{
+    QApplication app(argc, argv);
+    QApplication::setApplicationName("GTA-VI (Ground Truth Annotation-Visualization Interface)");
+    QCoreApplication::setApplicationVersion("version 2.0");
+
+    // initialize parser
+    QCommandLineParser parser;
+    parser.addHelpOption();
+    parser.addVersionOption();
+
+    QCommandLineOption videoFileOption(QStringList() << "vf" << "video-file",
+            QCoreApplication::translate("main", "Video file."),
+            QCoreApplication::translate("main", "file"));
+    parser.addOption(videoFileOption);
+
+    QCommandLineOption arffFileOption(QStringList() << "af" << "arff-file",
+            QCoreApplication::translate("main", "ARFF file."),
+            QCoreApplication::translate("main", "file"));
+    parser.addOption(arffFileOption);
+
+    QCommandLineOption saveFileOption(QStringList() << "sf" << "save-file",
+            QCoreApplication::translate("main", "Save file."),
+            QCoreApplication::translate("main", "file"));
+    parser.addOption(saveFileOption);
+
+    QCommandLineOption primaryLabelOption(QStringList() << "pl" << "primary-label",
+            QCoreApplication::translate("main", "Primary hand labelling attribute name."),
+            QCoreApplication::translate("main", "name"));
+    parser.addOption(primaryLabelOption);
+
+    QCommandLineOption primaryLabelValueOption(QStringList() << "plv" << "primary-label-value",
+            QCoreApplication::translate("main", "(Optional) Create a nominal primary labelling attribute. Ex. \"{fix,sacc,sp}\"."),
+            QCoreApplication::translate("main", "name"),
+            "");
+    parser.addOption(primaryLabelValueOption);
+
+    QCommandLineOption secondaryLabelOption(QStringList() << "sl" << "secondary-label",
+            QCoreApplication::translate("main", "(Optional) Secondary hand labelling attribute name."),
+            QCoreApplication::translate("main", "name"));
+    parser.addOption(secondaryLabelOption);
+
+    QCommandLineOption secondaryLabelValueOption(QStringList() << "slv" << "secondary-label-value",
+            QCoreApplication::translate("main", "(Optional) Create a nominal secondary labelling attribute. Ex. \"{fix,sacc,sp}\"."),
+            QCoreApplication::translate("main", "name"),
+            "");
+    parser.addOption(secondaryLabelValueOption);
+
+    QCommandLineOption fullScreenOption(QStringList() << "f" << "full-screen",
+            QCoreApplication::translate("main", "Start window in full screen mode."));
+    parser.addOption(fullScreenOption);
+
+    QCommandLineOption fovFileOption(QStringList() << "fov" << "field-of-view",
+            QCoreApplication::translate("main", "Convert Equirectangular video to Field Of View"));
+    parser.addOption(fovFileOption);
+
+    QCommandLineOption headFileOption(QStringList() << "head" << "head-only-motion",
+            QCoreApplication::translate("main", "Display only head motion in the equirectangular video"));
+    parser.addOption(headFileOption);
+
+    // parse arguments
+    parser.process(app);
+
+    MainWindow *pMainWindow;
+    SetupValues setup;
+
+
+    setup.gazeType = GazeType::EYE_PLUS_HEAD;
+    if (parser.isSet("field-of-view"))
+        setup.gazeType = GazeType::FOV;
+    if (parser.isSet("head-only-motion"))
+        setup.gazeType = GazeType::HEAD;
+
+    if (parser.isSet("video-file") && parser.isSet("arff-file") && parser.isSet("save-file") && parser.isSet("primary-label"))
+    {
+        setup.arffFile = parser.value(arffFileOption);
+        setup.saveFile = parser.value(saveFileOption);
+        setup.videoFile = parser.value(videoFileOption);
+        setup.primaryLabel = parser.value(primaryLabelOption);
+        setup.primaryLabelValues = parser.value(primaryLabelValueOption);
+        setup.secondaryLabel = parser.value(secondaryLabelOption);
+        setup.secondaryLabelValues = parser.value(secondaryLabelValueOption);
+
+        pMainWindow = new MainWindow(setup);
+    }
+    else
+        pMainWindow = new MainWindow;
+
+    if (parser.isSet(fullScreenOption))
+        pMainWindow->showFullScreen();
+    else
+        pMainWindow->show();
+
+    return app.exec();
+}
+

+ 143 - 0
GTA-VI/GTA-VI.pro

@@ -0,0 +1,143 @@
+######################################################################
+
+## Select to compilte for debug or release
+CONFIG += debug
+#CONFIG += release
+
+## Uncomment line below to use CUDA
+CONFIG += USE_CUDA
+
+## If you use CUDA you should find the compute capability of your GPU from the
+## following list:
+## https://developer.nvidia.com/cuda-gpus
+## The compute architecture is the same as the compute capability without the dot
+CUDA_COMPUTE_ARCH = 52
+## Path to cuda toolkit install
+CUDA_DIR = /usr/local/cuda-8.0
+
+QT+=widgets 
+TEMPLATE = app
+TARGET = GTA-VI
+INCLUDEPATH += .
+QMAKE_CXXFLAGS += -std=c++11
+QMAKE_LIBS += -lavutil -lavformat -lavcodec -lswscale
+
+## Enable profiling with gprof
+#QMAKE_CXXFLAGS_DEBUG *= -pg
+#QMAKE_LFLAGS_DEBUG *= -pg
+
+## Setup NVCC for compilation
+CONFIG (USE_CUDA) {
+    ## add macro for conditional compilation in code
+    DEFINES += USE_CUDA
+    CUDA_DEFINES += USE_CUDA
+
+    ## cuda source
+    CUDA_SOURCES += EquirectangularToFovVideo.cu
+
+    ## Path to cuda toolkit install
+    INCLUDEPATH += $$CUDA_DIR/include
+
+    ## 3rd party headers
+    SYSTEMINCLUDEPATH = /usr/include/x86_64-linux-gnu/qt5/QtGui
+    SYSTEMINCLUDEPATH += /usr/include/x86_64-linux-gnu/qt5/QtCore/
+    SYSTEMINCLUDEPATH += /usr/include/x86_64-linux-gnu/qt5
+    QMAKE_LIBDIR += $$CUDA_DIR/lib64
+
+    ## SYSTEM_TYPE - compiling for 32 or 64 bit architecture
+    SYSTEM_TYPE = 64
+
+    ## correctly formats CUDA_COMPUTE_ARCH to CUDA_ARCH with code gen flags
+    ## resulting format example: -gencode arch=compute_20,code=sm_20
+    for(_a, CUDA_COMPUTE_ARCH):{
+        formatted_arch =$$join(_a,'','-gencode arch=compute_',',code=sm_$$_a')
+        CUDA_ARCH += $$formatted_arch
+    }
+
+    ## correctly formats CUDA_DEFINES for nvcc
+    for(_defines, CUDA_DEFINES):{
+        formatted_defines += -D$$_defines
+    }
+    CUDA_DEFINES = $$formatted_defines
+
+    ## NVCC flags
+    NVCCFLAGS += --compiler-options -fno-strict-aliasing -use_fast_math --ptxas-options=-v
+
+    ## Path to libraries
+    LIBS += -lcudart -lcuda
+
+    ## join the includes in a line
+    CUDA_INC = $$join(INCLUDEPATH,' -I','-I',' ')
+    ## -isystem does not give warnings for vendor supplied headers
+    CUDA_INC += $$join(SYSTEMINCLUDEPATH,' -isystem ','-isystem ',' ')
+
+    ## NVCC_OPTIONS - any further options for the compiler
+    NVCC_OPTIONS += -Xcompiler -fPIC -std=c++11
+
+    CONFIG(debug, debug|release) {
+        ## Debug settings
+        cuda_d.input = CUDA_SOURCES
+        cuda_d.output = ${OBJECTS_DIR}${QMAKE_FILE_BASE}_cuda.o
+        cuda_d.commands = $$CUDA_DIR/bin/nvcc -D_DEBUG $$CUDA_DEFINES $$NVCC_OPTIONS $$CUDA_INC $$CUDA_LIBS --machine $$SYSTEM_TYPE $$CUDA_ARCH -c -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_NAME}
+        cuda_d.dependency_type = TYPE_C
+        QMAKE_EXTRA_COMPILERS += cuda_d
+    }
+    else {
+        ## Release settings
+        cuda.input = CUDA_SOURCES
+        cuda.output = ${OBJECTS_DIR}${QMAKE_FILE_BASE}_cuda.o
+        cuda.commands = $$CUDA_DIR/bin/nvcc $$CUDA_DEFINES $$NVCC_OPTIONS $$CUDA_INC $$CUDA_LIBS --machine $$SYSTEM_TYPE $$CUDA_ARCH -c -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_NAME}
+        cuda.dependency_type = TYPE_C
+        QMAKE_EXTRA_COMPILERS += cuda
+    }
+}
+else {
+    SOURCES += EquirectangularToFovVideo.cpp
+}
+
+
+## Input
+HEADERS += FlowWidget.h \
+           MainWindow.h \
+           PaintGaze.h \
+           ArffWidgetBase.h \
+           ArffWidgetCoord.h \
+           ArffWidgetSpeed.h \
+           VideoWidget.h \
+           MediaPlayer.h \
+           VideoExtractor.h \
+           EquirectangularToFovBase.h \
+           EquirectangularToFovVideo.h \
+           EquirectangularToFovGaze.h \
+           EquirectangularToFovSpeed.h \
+           EquirectangularToHead.h \
+           GazeSpeed.h \
+           Types.h \
+           Util.h \
+           Unused.h \
+           ../arffHelper/Arff.h \
+           ../arffHelper/ArffBase.h \
+           ../arffHelper/AttributeTypes.h \
+           ../arffHelper/ArffUtil.h \
+           ../arffHelper/ArffOps.h
+SOURCES += FlowWidget.cpp \
+           GTA-VI.cpp \
+           MainWindow.cpp \
+           PaintGaze.cpp \
+           ArffWidgetBase.cpp \
+           ArffWidgetCoord.cpp \
+           ArffWidgetSpeed.cpp \
+           VideoWidget.cpp \
+           MediaPlayer.cpp \
+           VideoExtractor.cpp \
+           EquirectangularToFovBase.cpp \
+           EquirectangularToFovGaze.cpp \
+           EquirectangularToFovSpeed.cpp \
+           EquirectangularToHead.cpp \
+           GazeSpeed.cpp \
+           Util.cpp \
+           ../arffHelper/Arff.cpp \
+           ../arffHelper/ArffBase.cpp \
+           ../arffHelper/AttributeTypes.cpp \
+           ../arffHelper/ArffUtil.cpp \
+           ../arffHelper/ArffOps.cpp

+ 96 - 0
GTA-VI/GazeSpeed.cpp

@@ -0,0 +1,96 @@
+// GazeSpeed.cpp
+
+#include "GazeSpeed.h"
+#include "../arffHelper/ArffUtil.h"
+
+#include <cmath>
+#include <iostream>
+
+#define USECS_TO_SECS 1000000.0
+#define PI 3.14159265
+
+const char *c_widthName = "width_mm";
+const char *c_heightName = "height_mm";
+const char *c_distanceName = "distance_mm";
+
+using namespace std;
+
+GazeSpeed::GazeSpeed(Arff *pArff, int step)
+{
+    m_pArff = pArff;
+    m_step = step;
+
+    FillVectors();
+}
+
+vector<double> GazeSpeed::GetSpeed()
+{
+    vector<double> speed;
+    speed.resize(m_vXdeg.size());
+    
+    for (int ind=m_step; ind<(int)speed.size(); ind++)
+    {
+        double xDisp = pow(m_vXdeg[ind] - m_vXdeg[ind-m_step], 2);
+        double yDisp = pow(m_vYdeg[ind] - m_vYdeg[ind-m_step], 2);
+
+        double disp = sqrt(xDisp + yDisp);
+        double time = ((*m_pArff)[ind][m_timeInd] - (*m_pArff)[ind-m_step][m_timeInd]) / USECS_TO_SECS;
+
+        speed[ind - m_step/2] = disp / time;
+    }
+
+    return speed;
+}
+
+void GazeSpeed::FillVectors()
+{
+    double width_px = m_pArff->WidthPx();
+    double height_px = m_pArff->HeightPx();
+
+    string value;
+	if(!m_pArff->GetMetadata(c_widthName, value))
+    {
+        cout << "ERROR: METADATA " << c_widthName << " is missing" << endl;
+        exit(-1);
+    }
+    double width_mm = stod(value);
+
+	if(!m_pArff->GetMetadata(c_heightName, value))
+    {
+        cout << "ERROR: METADATA " << c_heightName << " is missing" << endl;
+        exit(-1);
+    }
+    double height_mm = stod(value);
+
+	if(!m_pArff->GetMetadata(c_distanceName, value))
+    {
+        cout << "ERROR: METADATA " << c_distanceName << " is missing" << endl;
+        exit(-1);
+    }
+    double distance_mm = stod(value);
+
+    double thetaWidth = 2 * atan(width_mm/(2*distance_mm)) * 180/PI;
+    double ppdx = width_px/thetaWidth;
+
+    double thetaHeight = 2 * atan(height_mm/(2*distance_mm)) * 180/PI;
+    double ppdy = height_px/thetaHeight;
+
+    int xInd, yInd, confInd;
+	bool res = ArffUtil::GetTXYCindex(m_pArff, m_timeInd, xInd, yInd, confInd);
+    if (!res)
+    {
+        cout << "ERROR: could not find time or x or y or confidence in the provided ARFF file." << endl;
+        exit(-1);
+    }
+
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+    m_vXdeg.resize(rows);
+    m_vYdeg.resize(rows);
+
+    for (int ind=0; ind<rows; ind++)
+    {
+        m_vXdeg[ind] = (*m_pArff)[ind][xInd]/ppdx;
+        m_vYdeg[ind] = (*m_pArff)[ind][yInd]/ppdy;
+    }
+}

+ 30 - 0
GTA-VI/GazeSpeed.h

@@ -0,0 +1,30 @@
+// GazeSpeed.h
+//
+// This class calculates the speed for the provided ARFF file
+
+#ifndef __GAZESPEED_H__
+#define __GAZESPEED_H__
+
+#include "../arffHelper/Arff.h"
+
+#include <vector>
+
+class GazeSpeed
+{
+public:
+    GazeSpeed(Arff *pArff, int step=1);
+
+    vector<double> GetSpeed();
+
+private:
+    void FillVectors();
+
+    Arff *m_pArff;
+
+    vector<double>  m_vXdeg;
+    vector<double>  m_vYdeg;
+
+    int m_step;
+    int m_timeInd;
+};
+#endif /*__GAZESPEED_H__*/

+ 542 - 0
GTA-VI/MainWindow.cpp

@@ -0,0 +1,542 @@
+// MainWindow.cpp
+
+#include "MainWindow.h"
+#include "EquirectangularToFovGaze.h"
+#include "EquirectangularToHead.h"
+#include "../arffHelper/ArffOps.h"
+
+#include <iostream>
+#include <cassert>
+
+using namespace std;
+
+// PUBLIC:
+
+MainWindow::MainWindow() : m_pMainWidget(0), m_pVideoWidget(0), m_pArffWidgetCoordX(0), m_pArffWidgetCoordY(0), m_pArffWidgetSpeed(0), m_pPaintGaze(0), m_pArff(0), m_pFovArff(0), m_openAction(0), m_saveAction(0), m_undoAction(0), m_redoAction(0)
+{
+    InitializeVariables();
+    InitializeMainWidget();
+    InitializeMenu();
+    SetData(0);
+}
+
+MainWindow::MainWindow(SetupValues setup) : m_setup(setup), m_pMainWidget(0), m_pVideoWidget(0), m_pArffWidgetCoordX(0), m_pArffWidgetCoordY(0), m_pArffWidgetSpeed(0), m_pPaintGaze(0), m_pArff(0), m_pFovArff(0), m_openAction(0), m_saveAction(0), m_undoAction(0), m_redoAction(0)
+{
+    InitializeVariables();
+    InitializeMainWidget();
+    InitializeMenu();
+
+    // assign values
+    m_setup.arffFile = QFileInfo(m_setup.arffFile).absoluteFilePath();
+    m_setup.saveFile = QFileInfo(m_setup.saveFile).absoluteFilePath();
+    m_setup.videoFile = QFileInfo(m_setup.videoFile).absoluteFilePath();
+
+    // Load Files
+    if (!m_pVideoWidget->SetMedia(m_setup.videoFile))
+        exit(-1);
+
+    if (!m_pArff->Load(m_setup.arffFile.toStdString().c_str()))
+       exit(-1); 
+
+    int primAttPosition = InitializeAtt(m_setup.primaryLabel, m_setup.primaryLabelValues);
+    SetData(primAttPosition);
+    if (!m_setup.secondaryLabel.isEmpty())
+    {
+        int secAttPosition = InitializeAtt(m_setup.secondaryLabel, m_setup.secondaryLabelValues);
+        m_pArffSecondWidgetSpeed->SetData(*m_pArff, secAttPosition);
+        m_pArffSecondWidgetSpeed->SetIntervalAtt(primAttPosition);
+        m_pArffSecondWidgetY->SetData(*m_pArff, secAttPosition);
+        m_pArffSecondWidgetY->SetIntervalAtt(primAttPosition);
+    }
+
+    if (m_setup.gazeType == GazeType::FOV)
+    {
+        EquirectangularToFovGaze eqToFov(m_pArff.get());
+        m_pFovArff = eqToFov.Convert();
+        m_pPaintGaze->SetFovData(*m_pFovArff);
+        m_pVideoWidget->ConvertToFov(m_pArff.get());
+        m_pArffWidgetCoordX->SetFovData(*m_pFovArff, m_pFovArff->WidthPx());
+        m_pArffWidgetCoordY->SetFovData(*m_pFovArff, m_pFovArff->HeightPx());
+        m_pArffWidgetSpeed->DisplayFov();
+        emit SendToggleView();
+    }
+    else if (m_setup.gazeType == GazeType::HEAD)
+    {
+        EquirectangularToHead converter(m_pArff.get());
+        m_pFovArff = converter.Convert();
+        m_pPaintGaze->SetFovData(*m_pFovArff);
+        m_pArffWidgetCoordX->SetFovData(*m_pFovArff, m_pFovArff->WidthPx());
+        m_pArffWidgetCoordY->SetFovData(*m_pFovArff, m_pFovArff->HeightPx());
+        m_pArffWidgetSpeed->DisplayHead();
+        emit SendToggleView();
+    }
+}
+
+MainWindow::~MainWindow()
+{
+    delete m_pMainWidget;
+    delete m_pVideoWidget;
+    delete m_pArffWidgetCoordX;
+    delete m_pArffWidgetCoordY;
+    delete m_pArffWidgetSpeed;
+    delete m_pArffSecondWidgetSpeed;
+    delete m_pArffSecondWidgetY;
+    delete m_pPaintGaze;
+    delete m_openAction;
+    delete m_saveAction;
+    delete m_undoAction;
+    delete m_redoAction;
+}
+
+// PROTECTED:
+void MainWindow::closeEvent(QCloseEvent *event)
+{
+    if (m_pArff->IsDataChanged())
+    {
+        // display message box and ask for action
+        QMessageBox msgBox;
+        msgBox.setText("Gaze samples have been modified.");
+        msgBox.setInformativeText("Do you want to save your changes?");
+        msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
+        msgBox.setDefaultButton(QMessageBox::Cancel);
+
+        int ret = msgBox.exec();
+        switch (ret) {
+            case QMessageBox::Save:
+                SaveArff();
+                event->accept();
+                break;
+            case QMessageBox::Discard:
+                event->accept();
+                break;
+            case QMessageBox::Cancel:
+                event->ignore();
+                break;
+        }
+    }
+    else
+    {
+        event->accept();
+    }
+}
+
+// PRIVATE:
+
+void MainWindow::InitializeMainWidget()
+{
+    // load static file for beginning
+    m_pArff = make_shared<Arff>();
+
+    m_pVideoWidget = new VideoWidget;
+    m_pArffWidgetCoordX = new ArffWidgetCoordX;
+    m_pArffWidgetCoordY = new ArffWidgetCoordY;
+    m_pArffWidgetSpeed = new ArffWidgetSpeed;
+
+    m_pPaintGaze = new PaintGaze;
+
+    // connect time signals-slots
+    ConnectTimeSignals(m_pArffWidgetCoordX);
+    ConnectTimeSignals(m_pArffWidgetCoordY);
+    ConnectTimeSignals(m_pArffWidgetSpeed);
+    ConnectTimeSignals(m_pVideoWidget);
+    QObject::connect(this, SIGNAL(SendTime(int, QObject*)), m_pPaintGaze, SLOT(HandleTime(int, QObject*)));
+
+    ConnectWinDurSignals(m_pArffWidgetCoordX);
+    ConnectWinDurSignals(m_pArffWidgetCoordY);
+    ConnectWinDurSignals(m_pArffWidgetSpeed);
+    
+    ConnectUpdateSignals(m_pArffWidgetCoordX);
+    ConnectUpdateSignals(m_pArffWidgetCoordY);
+    ConnectUpdateSignals(m_pArffWidgetSpeed);
+    
+    ConnectEyeMovementSignals(m_pArffWidgetCoordX);
+    ConnectEyeMovementSignals(m_pArffWidgetCoordY);
+    ConnectEyeMovementSignals(m_pArffWidgetSpeed);
+
+    ConnectToggleViewSignals(m_pArffWidgetCoordX);
+    ConnectToggleViewSignals(m_pArffWidgetCoordY);
+    ConnectToggleViewSignals(m_pArffWidgetSpeed);
+    ConnectToggleViewSignals(m_pVideoWidget);
+    ConnectToggleViewSignals(m_pPaintGaze);
+    
+    QGridLayout *layout = new QGridLayout;
+    // add widgets to layout
+    layout->addWidget(m_pVideoWidget,0,0);
+    layout->addWidget(m_pArffWidgetCoordX,0,1);
+    layout->addWidget(m_pArffWidgetSpeed,1,0);
+    layout->addWidget(m_pArffWidgetCoordY,1,1);
+    layout->setColumnStretch(0,1);
+    layout->setColumnStretch(1,1);
+    layout->setRowStretch(0,4);
+    layout->setRowStretch(1,4);
+
+    // install event filter to all widgets
+    m_pVideoWidget->installEventFilter(this);
+    m_pArffWidgetCoordX->installEventFilter(this);
+    m_pArffWidgetCoordY->installEventFilter(this);
+    m_pArffWidgetSpeed->installEventFilter(this);
+
+    // add secondary label widgets
+    if (!m_setup.secondaryLabel.isEmpty())
+    {
+        m_pArffSecondWidgetSpeed = new ArffWidgetBase;
+        m_pArffSecondWidgetY = new ArffWidgetBase;
+
+        ConnectTimeSignals(m_pArffSecondWidgetSpeed);
+        ConnectTimeSignals(m_pArffSecondWidgetY);
+        ConnectWinDurSignals(m_pArffSecondWidgetSpeed);
+        ConnectWinDurSignals(m_pArffSecondWidgetY);
+        ConnectUpdateSignals(m_pArffSecondWidgetSpeed);
+        ConnectUpdateSignals(m_pArffSecondWidgetY);
+        ConnectEyeMovementSignals(m_pArffSecondWidgetSpeed);
+        ConnectEyeMovementSignals(m_pArffSecondWidgetY);
+
+        layout->addWidget(m_pArffSecondWidgetSpeed,2,0);
+        layout->addWidget(m_pArffSecondWidgetY,2,1);
+        layout->setRowStretch(2, 1);
+
+        m_pArffSecondWidgetSpeed->installEventFilter(this);
+        m_pArffSecondWidgetY->installEventFilter(this);
+    }
+
+    // create main widget
+    m_pMainWidget = new QWidget;
+    m_pMainWidget->setLayout(layout);
+    setCentralWidget(m_pMainWidget);
+}
+
+void MainWindow::ConnectTimeSignals(const QObject *pObject)
+{
+    QObject::connect(pObject, SIGNAL(SendTime(int)), this, SLOT(HandleTime(int)));
+    QObject::connect(this, SIGNAL(SendTime(int, QObject*)), pObject, SLOT(HandleTime(int, QObject*)));
+}
+
+void MainWindow::ConnectWinDurSignals(const QObject *pObject)
+{
+    QObject::connect(pObject, SIGNAL(SendWindowDur(int)), this, SLOT(HandleWindowDur(int)));
+    QObject::connect(this, SIGNAL(SendWindowDur(int, QObject*)), pObject, SLOT(HandleWindowDur(int, QObject*)));
+}
+
+void MainWindow::ConnectUpdateSignals(const QObject *pObject)
+{
+    QObject::connect(pObject, SIGNAL(SendUpdate()), this, SLOT(HandleUpdate()));
+    QObject::connect(this, SIGNAL(SendUpdate()), pObject, SLOT(HandleUpdate()));
+}
+
+void MainWindow::ConnectEyeMovementSignals(const QObject *pObject)
+{
+    QObject::connect(this, SIGNAL(SendSelectedEyeMovement(int)), pObject, SLOT(HandleSelectedEyeMovement(int)));
+}
+
+void MainWindow::ConnectToggleViewSignals(const QObject *pObject)
+{
+    QObject::connect(this, SIGNAL(SendToggleView()), pObject, SLOT(HandleToggleView()));
+}
+
+void MainWindow::InitializeVariables()
+{
+    path = QDir::current();
+}
+
+void MainWindow::InitializeMenu()
+{
+    // create new statusbar. Otherwise it is not visible
+    QStatusBar* status = new QStatusBar(this);
+    setStatusBar(status);
+
+    // making menu non native makes it visible in ubuntu
+    menuBar()->setNativeMenuBar(false);
+
+    // create actions
+    m_openAction = new QAction(tr("&Open"), this);
+    m_openAction->setShortcuts(QKeySequence::Open);
+    m_openAction->setStatusTip(tr("Open video and Arff files"));
+    connect(m_openAction, SIGNAL(triggered()), this, SLOT(OpenFiles()));
+
+    m_saveAction = new QAction(tr("&Save"), this);
+    m_saveAction->setShortcuts(QKeySequence::Save);
+    m_saveAction->setStatusTip(tr("Save Arff to file"));
+    connect(m_saveAction, &QAction::triggered, this, &MainWindow::SaveArff);
+
+    m_saveAsAction = new QAction(tr("Save&As"), this);
+    m_saveAsAction->setShortcuts(QKeySequence::SaveAs);
+    m_saveAsAction->setStatusTip(tr("Save Arff to new file"));
+    connect(m_saveAsAction, &QAction::triggered, this, &MainWindow::SaveAsArff);
+
+    m_undoAction = new QAction(tr("&Undo"), this);
+    m_undoAction->setShortcuts(QKeySequence::Undo);
+    m_undoAction->setStatusTip(tr("Undo last change"));
+    connect(m_undoAction, &QAction::triggered, this, &MainWindow::Undo);
+
+    m_redoAction = new QAction(tr("&Redo"), this);
+    m_redoAction->setShortcuts(QKeySequence::Redo);
+    m_redoAction->setStatusTip(tr("Redo last change"));
+    connect(m_redoAction, &QAction::triggered, this, &MainWindow::Redo);
+
+    m_fileMenu = menuBar()->addMenu(tr("&File"));
+    m_fileMenu->addAction(m_openAction);
+    m_fileMenu->addAction(m_saveAction);
+    m_fileMenu->addAction(m_saveAsAction);
+
+    m_editMenu = menuBar()->addMenu(tr("&Edit"));
+    m_editMenu->addAction(m_undoAction);
+    m_editMenu->addAction(m_redoAction);
+}
+
+int MainWindow::InitializeAtt(QString name, QString values)
+{
+    // check for hand labelling attribute use and prompt user for action
+    if (!UseAttributeDialog(name))
+        exit(-1);
+
+    int attPosition;
+    bool res = m_pArff->GetAttIndex(name.toStdString().c_str(), attPosition);
+    if (!res)
+    {
+        if (values.isEmpty())
+            // Get majority vote of attributes
+            ArffOps::MajorityVote(m_pArff.get(), name.toStdString().c_str()); // add mode att the last column
+        else
+            m_pArff->AddColumn(name.toStdString(), values.toStdString());
+
+        int rows, columns;
+        m_pArff->Size(rows, columns);
+        attPosition = columns-1;
+    }
+    else
+    {
+        cout << "WARNING: attribute '" << name.toStdString() << "' already in use." << endl;
+    }
+
+    return attPosition;
+}
+
+void MainWindow::SetData(int attIndex)
+{
+    // Set time to the beginning of both video and gaze
+    // Set max limits on each after reloading
+    m_pArffWidgetCoordX->SetData(*m_pArff, attIndex, m_pArff->WidthPx());
+    m_pArffWidgetCoordY->SetData(*m_pArff, attIndex, m_pArff->HeightPx());
+    m_pArffWidgetSpeed->SetData(*m_pArff, attIndex);
+    m_pVideoWidget->SetGazePaint(m_pPaintGaze);
+    m_pVideoWidget->SetCurrentTime(0);
+    m_pPaintGaze->SetData(*m_pArff);
+}
+
+void MainWindow::keyPressEvent(QKeyEvent *event)
+{
+    if (!ProcessKeyPress(event))
+        QMainWindow::keyPressEvent(event);
+
+}
+
+bool MainWindow::eventFilter(QObject *watched, QEvent *event)
+{
+    if (event->type() == QEvent::KeyPress)
+    {
+        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
+        if (ProcessKeyPress(keyEvent))
+            return true;
+    }
+
+    return QObject::eventFilter(watched, event);
+}
+
+
+bool MainWindow::ProcessKeyPress(QKeyEvent *event)
+{
+    statusBar()->showMessage(tr(""));
+
+	int key = event->key();
+    int key_0 = 0x30; // Qt::Key_0
+    int key_9 = 0x39; // Qt::Key_9
+    int mask = 0x0F; 
+    if (key >= key_0 && key <= key_9)
+    {
+        int eyeMovement = key & mask;
+        emit SendSelectedEyeMovement(eyeMovement);
+        return true;
+    }
+    else if (key == Qt::Key_Space)
+    {
+        m_pVideoWidget->TogglePlayer();
+        return true;
+    }
+    else if (key == Qt::Key_T)
+    {
+        emit SendToggleView();
+        return true;
+    }
+    else
+        return false;
+}
+
+bool MainWindow::UseAttributeDialog(QString attName)
+{
+    int tmpPos;
+    if (m_pArff->GetAttIndex(attName.toStdString().c_str(), tmpPos))
+    {
+        QMessageBox::StandardButton reply;
+        string message = "Do you still want to use attribute '" + attName.toStdString() + "'?";
+        reply = QMessageBox::question(this, "Attribute already exists", message.c_str(), QMessageBox::Yes|QMessageBox::No);
+
+        if (reply == QMessageBox::Yes){
+            return true;
+        }
+        else{
+            return false;
+        }
+    }
+    return true;
+}
+
+// PRIVATE SLOTS:
+
+void MainWindow::SaveArff()
+{
+    if (m_setup.saveFile.isEmpty())
+    {
+        m_setup.saveFile =  QFileDialog::getSaveFileName(this, tr("Save File"), 
+                path.path(), tr("Arff files (*.arff *.txt)"));
+        // update path
+        path = QFileInfo(m_setup.saveFile).dir();
+    }
+
+    // handle canceled window case
+    if (!m_setup.saveFile.isEmpty())
+    {
+        m_pArff->Save(m_setup.saveFile.toStdString().c_str());
+        statusBar()->showMessage(tr("Saved"));
+    }
+    else
+    {
+        statusBar()->showMessage(tr("Could not save Arff file"));
+    }
+
+}
+
+void MainWindow::SaveAsArff()
+{
+    m_setup.saveFile.clear();
+
+    SaveArff();
+}
+
+void MainWindow::OpenFiles()
+{
+    // first check for unsaved changes
+    if (m_pArff->IsDataChanged())
+    {
+        // display message box and ask for action
+        QMessageBox msgBox;
+        msgBox.setText("Gaze samples have been modified.");
+        msgBox.setInformativeText("Do you want to save your changes?");
+        msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
+        msgBox.setDefaultButton(QMessageBox::Cancel);
+
+        int ret = msgBox.exec();
+        switch (ret) 
+        {
+            case QMessageBox::Save:
+                SaveArff();
+                break;
+            case QMessageBox::Discard:
+                // do nothing
+                break;
+            case QMessageBox::Cancel:
+                return;
+                break;
+        }
+    }
+
+
+    m_setup.videoFile = QFileDialog::getOpenFileName(this, tr("Open Video File"),
+            path.path(), tr("Video files (*.m2t *.avi *.mp4 *.wmv)"));
+    // return if file is empty
+    if (m_setup.videoFile.isEmpty())
+        return;
+    // update path
+    path = QFileInfo(m_setup.videoFile).dir();
+    
+    m_setup.arffFile = QFileDialog::getOpenFileName(this, tr("Open Arff File"),
+            path.path(), tr("Arff files (*.arff *.txt)"));
+    if (m_setup.arffFile.isEmpty())
+        return;
+    // update path
+    path = QFileInfo(m_setup.arffFile).dir();
+
+    m_pVideoWidget->SetMedia(m_setup.videoFile);
+    m_pArff->Load(m_setup.arffFile.toStdString().c_str());
+
+    bool accepted=false;
+    do
+    {
+        // check if name for hand labelling attribute exists
+        if (m_setup.primaryLabel.isEmpty())
+        {
+            m_setup.primaryLabel = QInputDialog::getText(this, tr("Hande labeller name"), tr("Attribute name:"), QLineEdit::Normal, QDir::home().dirName(), &accepted);
+
+            // remove whitespace from input value
+            m_setup.primaryLabel = m_setup.primaryLabel.simplified();
+            m_setup.primaryLabel.replace(" ", "");
+            
+            // if canceled or empty string provided return
+            if (!accepted || m_setup.primaryLabel.isEmpty())
+                return;
+        }
+
+        // check for hand labelling attribute use
+        accepted = UseAttributeDialog(m_setup.primaryLabel);
+        if (!accepted)
+            m_setup.primaryLabel.clear();
+    }
+    while(!accepted);
+
+
+    int attPosition;
+    bool res = m_pArff->GetAttIndex(m_setup.primaryLabel.toStdString().c_str(), attPosition);
+    if (!res){
+        // Get majority vote for of attributes
+        ArffOps::MajorityVote(m_pArff.get(), m_setup.primaryLabel.toStdString().c_str()); // add mode att the last column
+        int rows, columns;
+        m_pArff->Size(rows, columns);
+        attPosition = columns-1;
+    }
+
+    SetData(attPosition);
+}
+
+void MainWindow::Undo()
+{
+    m_pArff->UndoLastChange();
+    emit SendUpdate();
+
+    statusBar()->showMessage(tr("Last change reverted"));
+}
+
+void MainWindow::Redo()
+{
+    m_pArff->RedoLastChange();
+    emit SendUpdate();
+
+    statusBar()->showMessage(tr("Last change remade"));
+}
+
+void MainWindow::HandleTime(int curTime_us)
+{
+    QObject *pSender = sender();
+    
+    emit SendTime(curTime_us, pSender);
+}
+
+void MainWindow::HandleWindowDur(int dur_us)
+{
+    QObject *pSender = sender();
+
+    emit SendWindowDur(dur_us, pSender);
+}
+
+void MainWindow::HandleUpdate()
+{
+    emit SendUpdate();
+}

+ 149 - 0
GTA-VI/MainWindow.h

@@ -0,0 +1,149 @@
+// MainWindow.h
+
+#ifndef __MAINWINDOW_H__
+#define __MAINWINDOW_H__
+
+#include "VideoWidget.h"
+#include "ArffWidgetCoord.h"
+#include "ArffWidgetSpeed.h"
+#include "PaintGaze.h"
+#include "../arffHelper/Arff.h"
+
+#include <QMainWindow>
+#include <memory>
+
+enum class GazeType
+{
+    EYE_PLUS_HEAD,
+    FOV,
+    HEAD
+};
+
+struct SetupValues
+{
+    QString arffFile;
+    QString saveFile;
+    QString videoFile;
+    QString primaryLabel;
+    QString primaryLabelValues;
+    QString secondaryLabel;
+    QString secondaryLabelValues;
+    GazeType gazeType;
+};
+
+class MainWindow : public QMainWindow
+{
+
+    Q_OBJECT
+
+public:
+    MainWindow();
+
+    MainWindow(SetupValues setup);
+    // Constructor for command line use
+
+    ~MainWindow();
+
+protected:
+    void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
+
+private slots:
+    void SaveArff();
+    ///< Saves Arff to m_saveFilename. Gets value if it is empty.
+
+    void SaveAsArff();
+    ///< proimpts user to provide a name
+
+    void OpenFiles();
+    ///< opens video and arff file
+
+    void Undo();
+    ///< undoes last change
+
+    void Redo();
+    ///< redoes change
+
+    void HandleTime(int curTime_us);
+
+    void HandleWindowDur(int dur_us);
+
+    void HandleUpdate();
+
+signals:
+    void SendTime(int curTime_us, QObject *pSender);
+
+    void SendWindowDur(int dur_us, QObject *pSender);
+
+    void SendUpdate();
+
+    void SendSelectedEyeMovement(int eyeMovement);
+
+    void SendToggleView();
+
+private:
+    void InitializeMainWidget();
+    ///< initializes object and connects signals
+
+    void InitializeVariables();
+    ///< initializes all needed variables
+
+    void InitializeMenu();
+    ///< initializes the menu and te actions
+
+    int InitializeAtt(QString name, QString values);
+
+    void ConnectTimeSignals(const QObject *pObject);
+
+    void ConnectWinDurSignals(const QObject *pObject);
+
+    void ConnectUpdateSignals(const QObject *pObject);
+
+    void ConnectEyeMovementSignals(const QObject *pObject);
+
+    void ConnectToggleViewSignals(const QObject *pObject);
+
+    void SetData(int attIndex);
+
+    void keyPressEvent(QKeyEvent *event);
+    ///< overwrites key press events when it has focus
+
+    bool eventFilter(QObject *watched, QEvent *event);
+    ///< overwrites the event filter in order to provide keyboard short cuts
+
+    bool ProcessKeyPress(QKeyEvent *event);
+    ///< processes key events. Returns true if the event was handled. False otherwise
+
+    bool UseAttributeDialog(QString attName);
+    ///< shows a dialog if attribute already exists and returns true if the user selected 
+    ///< to change the existing one or the attribute wans't present
+
+
+
+
+    SetupValues     m_setup;
+    QWidget         *m_pMainWidget;
+    QDir            path; // path to last used directory
+
+    VideoWidget     *m_pVideoWidget;
+    ArffWidgetCoordX     *m_pArffWidgetCoordX;
+    ArffWidgetCoordY     *m_pArffWidgetCoordY;
+    ArffWidgetSpeed      *m_pArffWidgetSpeed;
+    ArffWidgetBase       *m_pArffSecondWidgetSpeed;
+    ArffWidgetBase       *m_pArffSecondWidgetY;
+    PaintGaze       *m_pPaintGaze;
+
+    shared_ptr<Arff>         m_pArff;
+    shared_ptr<Arff>         m_pFovArff; // arff for FOV conversion from equirectangular
+
+    // menu windows and actions
+    QMenu           *m_fileMenu;
+    QMenu           *m_editMenu;
+
+    QAction         *m_openAction;
+    QAction         *m_saveAction;
+    QAction         *m_saveAsAction;
+    QAction         *m_undoAction;
+    QAction         *m_redoAction;
+};
+
+#endif /*__MAINWINDOW_H__*/

+ 182 - 0
GTA-VI/MediaPlayer.cpp

@@ -0,0 +1,182 @@
+// MediaPlayer.cpp
+
+#include "MediaPlayer.h"
+#include "Unused.h"
+
+#include <string>
+#include <QTime>
+#include <fstream>
+#include <iostream>
+
+#define FOV_IMAGE_HEIGHT 600
+
+using namespace std;
+
+// PRIVATE SLOTS:
+
+void MediaPlayer::HandleTimerTick()
+{
+    if (m_state==StoppedState || m_state==PausedState)
+        return;
+
+    // if the timer is fire due to main thread blocking, skip the frames of the delay
+    if (m_startPlayMs < 0) // player starts after pause
+    {
+        m_startPlayMs =m_time.elapsed();
+        m_startPlayFrame = m_curFrame+1;
+        LoadFrame(++m_startPlayFrame);
+    }
+    else // it means the player was already playing
+    {
+        int curTime =m_time.elapsed();
+        int framesElapsed = (int)(((curTime-m_startPlayMs)*m_videoParams.GetFrameRate())/1000.0 + 0.5); // round to pcloser frame
+        framesElapsed = framesElapsed<1? 1: framesElapsed;
+        LoadFrame(m_startPlayFrame+framesElapsed);
+    }
+
+
+    qint64 newTime = (qint64)(1000.0*m_curFrame/m_videoParams.GetFrameRate());
+
+    // notify that frame has changed
+    emit positionChanged(newTime);
+}
+
+// PUBLIC:
+
+MediaPlayer::MediaPlayer(QObject *parent) : QObject(parent), m_startPlayFrame(0), m_startPlayMs(-1), m_state(StoppedState), m_curFrame(0), m_bVideoAv(false), m_pEqToFov(NULL)
+{
+    m_pFpsTimer = new QTimer(this); // timer hasn't started yet
+    connect(m_pFpsTimer, SIGNAL(timeout()), this, SLOT(HandleTimerTick()));
+    m_time.start();
+}
+
+MediaPlayer::~MediaPlayer()
+{
+    delete m_pFpsTimer;
+}
+
+void MediaPlayer::setPosition(qint64 position)
+{
+    int frameNum = (int)((double)position*m_videoParams.GetFrameRate()/1000.0);
+
+    LoadFrame(frameNum);
+}
+
+bool MediaPlayer::setMedia(QUrl videoFile)
+{
+    string sVideoFile = videoFile.toString().toStdString();
+    if (!VideoExtractor::IsVideoExtracted(sVideoFile.c_str()))
+    {
+        if (!VideoExtractor::ExtractFrames(sVideoFile))
+        {
+            cerr << "Could not open file: " << sVideoFile << endl;
+            return false;
+        }
+    }
+
+    string paramsFile = VideoExtractor::GetParamsname();
+
+    bool res = VideoExtractor::LoadVideoParams(paramsFile, m_videoParams);
+    UNUSED(res);
+
+    // after knowing video is extracted, load first frame
+    LoadFrame(0);
+    m_bVideoAv = true;
+
+    // and start timer with correct tick interval
+    int interval = (int)(1000.0/m_videoParams.GetFrameRate());
+    m_pFpsTimer->start(interval);
+
+    return true;
+}
+
+void MediaPlayer::play()
+{
+    m_state = PlayingState;
+}
+
+void MediaPlayer::pause()
+{
+    m_state = PausedState;
+    m_startPlayMs = -1;
+}
+
+MediaPlayer::State MediaPlayer::state() const
+{
+    return m_state;
+}
+
+qint64 MediaPlayer::duration()
+{
+    qint64 duration = (qint64)(m_videoParams.numOfFrames*1000.0/m_videoParams.GetFrameRate());
+
+    return duration;
+}
+
+bool MediaPlayer::isVideoAvailable() const
+{
+    return m_bVideoAv;
+}
+
+void MediaPlayer::Paint(QPainter *painter, QSize size)
+{
+    QRect sourceRect = QRect(QPoint(0,0), m_image.size());
+    double videoAspectRatio = (double)m_image.width()/(double)m_image.height();
+    double areaAspectRatio = (double)size.width()/(double)size.height();
+    int width, height, xDisp, yDisp;
+    if (videoAspectRatio > areaAspectRatio){
+        width = size.width();
+        xDisp = 0;
+        height = width/videoAspectRatio;
+        yDisp = (size.height()-height)/2;
+    }
+    else{
+        height = size.height();
+        yDisp = 0;
+        width = height*videoAspectRatio;
+        xDisp = (size.width()-width)/2;
+    }
+
+    QRect targetRect = QRect(xDisp, yDisp, width, height);
+
+    painter->drawImage(targetRect, m_image, sourceRect);
+}
+
+void MediaPlayer::SetConverter(EquirectangularToFovVideo *pEqToFov)
+{
+    m_pEqToFov = pEqToFov;
+    LoadFrame(m_curFrame);
+}
+
+// PRIVATE:
+
+void MediaPlayer::LoadFrame(int frameNum)
+{
+    // check if frame number is within the video
+    frameNum = frameNum<0? 0: frameNum;
+    frameNum = frameNum>=m_videoParams.numOfFrames? m_videoParams.numOfFrames-1: frameNum;
+    long int frameTime = (long int)(1000000.0*frameNum/m_videoParams.GetFrameRate());
+
+    string framePath = VideoExtractor::GetFramename(frameNum);
+
+    if (m_pEqToFov)
+    {
+        bool res = m_tmpImage.load(framePath.c_str());
+        UNUSED(res);
+        if (m_image.height() != FOV_IMAGE_HEIGHT)
+        {
+            int height = FOV_IMAGE_HEIGHT;
+            double aspectRatio = m_pEqToFov->GetAspectRatio();
+            int width = height * aspectRatio;
+            m_image = QImage(width, height, m_tmpImage.format());
+        }
+        m_pEqToFov->Convert(&m_tmpImage, frameTime, &m_image);
+    }
+    else
+    {
+        bool res = m_image.load(framePath.c_str()); 
+        UNUSED(res);
+    }
+
+    m_curFrame = frameNum;
+}

+ 84 - 0
GTA-VI/MediaPlayer.h

@@ -0,0 +1,84 @@
+/// MediaPlayer.h
+
+#ifndef __MEDIAPLAYER_H__
+#define __MEDIAPLAYER_H__
+
+#include <QtWidgets>
+#include <QTimer>
+#include <QTime>
+#include <QPainter>
+#include <QThread>
+
+#include "VideoExtractor.h"
+#include "EquirectangularToFovVideo.h"
+
+/// The following class tries to imitate the QMediaPlayer, but it comes with
+/// some differentitaions. The functions that start in lower case imitate the 
+/// Qt ones/ The functions that start with upper case are unique toi this 
+/// implementation
+
+class MediaPlayer : public QObject
+{
+    Q_OBJECT
+
+signals:
+    void positionChanged(qint64);
+    ///< emits the current time of player in ms
+
+private slots:
+    void HandleTimerTick();
+    ///< handle for timer tick
+
+public:
+    MediaPlayer(QObject *parent=0);
+
+    ~MediaPlayer();
+
+    enum State {PausedState, StoppedState, PlayingState};
+    ///< Enumerator holding the player state.
+
+    void setPosition(qint64 position);
+    ///< Sets the position of the player in ms.
+
+    bool setMedia(QUrl videoFile);
+    ///< Sets the input file.
+
+    void play();
+    ///< Starts player.
+
+    void pause();
+    ///< Pauses player.
+
+    State state() const;
+    ///< Returns the current state of the player.
+
+    qint64 duration();
+    ///< Returns duration fo the video in ms.
+
+    bool isVideoAvailable() const;
+    ///< Returns true if video is available.
+
+    void Paint(QPainter *painter, QSize size);
+    ///< Paints the current image to the provided painter.
+
+    void SetConverter(EquirectangularToFovVideo *pEqToFov);
+
+private:
+    QTimer          *m_pFpsTimer; // timer emulating the fps of the initial video
+    QImage          m_image;
+    QImage          m_tmpImage;
+    QTime           m_time; // hold time since start of player
+    int             m_startPlayFrame; // frame since we started playing video
+    int             m_startPlayMs; // presentaion time of first frame since started playing video
+    VideoParams     m_videoParams;
+    double          m_frameDur; // duration of frame in ms
+    State           m_state; // state of the player
+    int             m_curFrame; // currently loaded frame
+    bool            m_bVideoAv;
+    EquirectangularToFovVideo *m_pEqToFov; // convert from equirectangular to field of view
+
+    void LoadFrame(int frameNum);
+    ///< Loads te provided frame and updates current frame state.
+};
+
+#endif /*__MEDIAPLAYER_H__*/

+ 148 - 0
GTA-VI/PaintGaze.cpp

@@ -0,0 +1,148 @@
+// PaintGaze.cpp
+
+#include "PaintGaze.h"
+#include "../arffHelper/ArffUtil.h"
+#include "Unused.h"
+
+#include <iostream>
+
+#define INTERVAL_DURATION 100000 // 100ms
+
+using namespace std;
+
+// PUBLIC SLOTS:
+void PaintGaze::HandleTime(int time, QObject *pSender)
+{
+    if (pSender == this)
+        return;
+
+    SetCurrentTime(time);
+}
+
+void PaintGaze::HandleToggleView()
+{
+    if (m_pArff && m_pSecArff)
+    {
+        swap(m_pArff, m_pSecArff);
+        GetSetup();
+    }
+}
+
+// PUBLIC:
+
+PaintGaze::PaintGaze() : m_pArff(NULL), m_pSecArff(NULL) 
+{
+    SetCurrentTime(0);
+    SetInterval(INTERVAL_DURATION); 
+}
+
+void PaintGaze::SetData(Arff &arff)
+{
+    m_pArff = &arff;
+    GetSetup();
+}
+
+void PaintGaze::SetFovData(Arff &arff)
+{
+    m_pSecArff = &arff;
+}
+
+void PaintGaze::SetCurrentTime(long int currentTime)
+{
+    m_currentTime = currentTime;
+    CalculateIntervals();
+}
+
+void PaintGaze::SetInterval(long int intervalDuration)
+{
+    m_intervalDuration = intervalDuration;
+    CalculateIntervals();
+}
+
+void PaintGaze::Paint(QPainter *painter, QSize size)
+{
+    if (m_pArff == NULL)
+        return;
+
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+    if (rows > 0)
+    {
+        painter->setPen(QPen(Qt::gray, 1.3*size.width()/1000.0));
+        int circleSize = size.width()/60;
+        int halfCircleSize = circleSize/2;
+        int xPos, yPos; // x,y position on the drawing area
+        double videoAspectRatio = (double)m_videoWidth/(double)m_videoHeight;
+        double areaAspectRatio = (double)size.width()/(double)size.height();
+        int width, height, xDisp, yDisp;
+        if (videoAspectRatio > areaAspectRatio){
+            width = size.width();
+            xDisp = 0;
+            height = width/videoAspectRatio;
+            yDisp = (size.height()-height)/2;
+        }
+        else{
+            height = size.height();
+            yDisp = 0;
+            width = height*videoAspectRatio;
+            xDisp = (size.width()-width)/2;
+        }
+
+
+
+        bool changedColor = false;
+        int position=-1;
+        for(int i=m_endPoint; i>=m_startPoint; i--)
+        {
+            if ((*m_pArff)[i][m_timeInd]<m_currentTime && !changedColor){
+                painter->setPen(QPen(Qt::red, 3.5*size.height()/1000.0));
+                position = i;
+                changedColor = true;
+            }
+            
+            xPos = (int)(xDisp+((double)width/m_videoWidth)*(*m_pArff)[i][m_xInd]);
+            yPos = (int)(yDisp+((double)height/m_videoHeight)*(*m_pArff)[i][m_yInd]);
+
+            painter->drawEllipse(xPos-halfCircleSize, yPos-halfCircleSize, circleSize, circleSize);
+                    
+        }
+
+        if (position != -1){
+            painter->setPen(QPen(Qt::green, 3.5*size.height()/1000.0));
+            xPos = (int)(xDisp+((double)width/m_videoWidth)*(*m_pArff)[position][m_xInd]);
+            yPos = (int)(yDisp+((double)height/m_videoHeight)*(*m_pArff)[position][m_yInd]);
+
+            painter->drawEllipse(xPos-halfCircleSize, yPos-halfCircleSize, circleSize, circleSize);
+        }
+    }
+}
+
+// PRIVATE:
+
+void PaintGaze::CalculateIntervals()
+{
+    m_endPoint = 0;
+    m_startPoint = 0;
+
+    if (m_pArff == NULL)
+        return;
+
+    int rows, columns;
+    m_pArff->Size(rows, columns);
+    if (rows == 0)
+        return;
+
+    m_endPoint = ArffUtil::FindPosition(m_pArff, m_timeInd, m_currentTime + m_intervalDuration);
+    m_startPoint = ArffUtil::FindPosition(m_pArff, m_timeInd, m_currentTime - m_intervalDuration);
+}
+
+void PaintGaze::GetSetup()
+{
+    int confInd;
+    bool res = ArffUtil::GetTXYCindex(m_pArff, m_timeInd, m_xInd, m_yInd, confInd);
+    UNUSED(res);
+    m_videoWidth = m_pArff->WidthPx();
+    m_videoHeight = m_pArff->HeightPx();
+
+    CalculateIntervals();
+}

+ 64 - 0
GTA-VI/PaintGaze.h

@@ -0,0 +1,64 @@
+/// PaintGaze.h
+
+#ifndef __PAINTGAZE_H__
+#define __PAINTGAZE_H__
+
+#include <QtWidgets>
+#include <vector>
+
+#include "../arffHelper/Arff.h"
+
+using namespace std;
+
+class PaintGaze : public QObject
+{
+    Q_OBJECT
+public slots:
+    void HandleTime(int time, QObject *pSender);
+    ///< Handles new position of video.
+
+    void HandleToggleView();
+    ///< Toggles the diplayed gaze between the primary and secondary ARFF files
+
+public:
+    PaintGaze();
+
+    void SetData(Arff &arff);
+    ///< Sets the data to read that contain time,x,y information.
+
+    void SetFovData(Arff &arff);
+    ///< Sets the data to Field Of View ARFF converted from equirectangualr file.
+
+    void SetCurrentTime(long int currentTime);
+    ///< Sets the current time.
+
+    void SetInterval(long int intervalDuration);
+    ///< Sets the interval duration.
+
+    void Paint(QPainter *painter, QSize size);
+    ///< Paints the gaze points with painter and normalizes 
+    ///< to the provided size.
+
+private:
+    void CalculateIntervals();
+    ///< Calculates the intervals for before and after the current time.
+
+    void GetSetup();
+    ///< It populates the variables needed to correclt display the gaze
+
+    Arff                        *m_pArff; // pointer to ARFF data container
+    Arff                        *m_pSecArff; // pointer to sendary ARFF
+    int                         m_timeInd;
+    int                         m_xInd;
+    int                         m_yInd;
+    long int                    m_currentTime; // current time in us
+    long int                    m_intervalDuration; // duration of intervals for before and after
+    int                         m_startPoint; // point of m_vpDataPoins for the interval in the past
+    int                         m_endPoint; // point in the future
+    double                      m_videoWidth; // width of the video
+    double                      m_videoHeight; // height of the video
+
+};
+
+
+#endif /*__PAINTGAZE_H__*/

+ 36 - 0
GTA-VI/Types.h

@@ -0,0 +1,36 @@
+// Types.h
+
+#ifndef __TYPES_H__
+#define __TYPES_H__
+
+#include <ostream>
+
+using namespace std;
+
+struct Matrix33
+{
+    double mat[3][3] = {};
+
+    friend ostream& operator<<(ostream& os, Matrix33 const & m) 
+    {
+        return os << m.mat[0][0] << ", " << m.mat[0][1] << ", " << m.mat[0][2] << '\n'
+                  << m.mat[1][0] << ", " << m.mat[1][1] << ", " << m.mat[1][2] << '\n'
+                  << m.mat[2][0] << ", " << m.mat[2][1] << ", " << m.mat[2][2];
+    }
+};
+
+struct Vec3
+{
+    double x=0;
+    double y=0;
+    double z=0;
+  
+    Vec3(double ix, double iy, double iz): x(ix), y(iy), z(iz) {}
+
+    friend ostream& operator<<(ostream& os, Vec3 const & v) 
+    {
+        return os << v.x << ", " << v.y << ", " << v.z;
+    }
+};
+
+#endif /*__TYPES_H__*/

+ 8 - 0
GTA-VI/Unused.h

@@ -0,0 +1,8 @@
+// Unused.h
+
+#ifndef __UNUSED_H__
+#define __UNUSED_H__
+
+#define UNUSED(x) ((void)(x))
+
+#endif /*__UNUSED_H__*/

+ 172 - 0
GTA-VI/Util.cpp

@@ -0,0 +1,172 @@
+// Util.cpp
+
+#include "Util.h"
+#include <cmath>
+#include <cstdlib>
+
+#define PI 3.14159265
+
+using namespace std;
+
+Vec3 operator/(Vec3 v, double scalar)
+{
+    return Vec3(v.x/scalar, v.y/scalar, v.z/scalar);
+}
+
+Vec3 operator+(Vec3 v1, Vec3 v2)
+{
+    return Vec3(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
+}
+
+bool operator==(Vec3 v1, Vec3 v2)
+{
+    return v1.x == v2.x && v1.y == v2.y && v1.z == v2.z;
+}
+
+double Magnitude(Vec3 v)
+{
+    return sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
+}
+
+Vec3 Normalize(Vec3 v)
+{
+    return v/Magnitude(v);
+}
+
+Vec3 Perpendicular(Vec3 v)
+{
+    int range = 100;
+    v = Normalize(v);
+    if (v.x == 0)
+        return Vec3(1,0,0);
+    if (v.y == 0)
+        return Vec3(0,1,0);
+    if (v.z == 0)
+        return Vec3(0,0,1);
+
+    Vec3 res(0,0,0);
+    res.x = (double)(rand() % (2*range) - range)/range;
+    res.y = (double)(rand() % (2*range) - range)/range;
+    res.z = -(v.x*res.x + v.y*res.y)/v.z;
+
+    return res;
+}
+
+double DotProd(Vec3 v1, Vec3 v2)
+{
+    double res = 0.0;
+    res += v1.x*v2.x;
+    res += v1.y*v2.y;
+    res += v1.z*v2.z;
+
+    return res;
+}
+
+Vec3 CrossProd(Vec3 v1, Vec3 v2)
+{
+    Vec3 res{0, 0, 0};
+
+    res.x = v1.y*v2.z - v2.y*v1.z;
+    res.y = -v1.x*v2.z + v2.x*v1.z;
+    res.z = v1.x*v2.y - v2.x*v1.y;
+
+    return res;
+}
+
+Matrix33 operator*(Matrix33 left, Matrix33 right)
+{
+    Matrix33 res;
+    res.mat[0][0] = left.mat[0][0]*right.mat[0][0] + left.mat[0][1]*right.mat[1][0] + left.mat[0][2]*right.mat[2][0];
+    res.mat[0][1] = left.mat[0][0]*right.mat[0][1] + left.mat[0][1]*right.mat[1][1] + left.mat[0][2]*right.mat[2][1];
+    res.mat[0][2] = left.mat[0][0]*right.mat[0][2] + left.mat[0][1]*right.mat[1][2] + left.mat[0][2]*right.mat[2][2];
+
+    res.mat[1][0] = left.mat[1][0]*right.mat[0][0] + left.mat[1][1]*right.mat[1][0] + left.mat[1][2]*right.mat[2][0];
+    res.mat[1][1] = left.mat[1][0]*right.mat[0][1] + left.mat[1][1]*right.mat[1][1] + left.mat[1][2]*right.mat[2][1];
+    res.mat[1][2] = left.mat[1][0]*right.mat[0][2] + left.mat[1][1]*right.mat[1][2] + left.mat[1][2]*right.mat[2][2];
+
+    res.mat[2][0] = left.mat[2][0]*right.mat[0][0] + left.mat[2][1]*right.mat[1][0] + left.mat[2][2]*right.mat[2][0];
+    res.mat[2][1] = left.mat[2][0]*right.mat[0][1] + left.mat[2][1]*right.mat[1][1] + left.mat[2][2]*right.mat[2][1];
+    res.mat[2][2] = left.mat[2][0]*right.mat[0][2] + left.mat[2][1]*right.mat[1][2] + left.mat[2][2]*right.mat[2][2];
+
+    return res;
+}
+
+Matrix33 Transpose(Matrix33 in)
+{
+    Matrix33 res;
+    res.mat[0][0] = in.mat[0][0];
+    res.mat[0][1] = in.mat[1][0];
+    res.mat[0][2] = in.mat[2][0];
+
+    res.mat[1][0] = in.mat[0][1];
+    res.mat[1][1] = in.mat[1][1];
+    res.mat[1][2] = in.mat[2][1];
+
+    res.mat[2][0] = in.mat[0][2];
+    res.mat[2][1] = in.mat[1][2];
+    res.mat[2][2] = in.mat[2][2];
+
+    return res;
+}
+
+Matrix33 Rodrigues(Vec3 v, double angle)
+{
+    Matrix33 rot;
+    rot.mat[0][0] = (1-cos(angle))*v.x*v.x + cos(angle);
+    rot.mat[0][1] = (1-cos(angle))*v.x*v.y - sin(angle)*v.z;
+    rot.mat[0][2] = (1-cos(angle))*v.x*v.z + sin(angle)*v.y;
+    rot.mat[1][0] = (1-cos(angle))*v.y*v.x + sin(angle)*v.z;
+    rot.mat[1][1] = (1-cos(angle))*v.y*v.y + cos(angle);
+    rot.mat[1][2] = (1-cos(angle))*v.y*v.z - sin(angle)*v.x;
+    rot.mat[2][0] = (1-cos(angle))*v.z*v.x - sin(angle)*v.y;
+    rot.mat[2][1] = (1-cos(angle))*v.z*v.y + sin(angle)*v.x;
+    rot.mat[2][2] = (1-cos(angle))*v.z*v.z + cos(angle);
+
+    return rot;
+}
+
+Matrix33 RotationVecVec(Vec3 v1, Vec3 v2)
+{
+    // This function finds the rotation from one vector to the other. The process
+    // is the following:
+    // a. Normalize vectors
+    // b. Middle direction
+    // c. Normalize middle vector
+    // d. Use middle vector in Rodrigues' formula with rotation of pi
+
+    v1 = Normalize(v1);
+    v2 = Normalize(v2);
+
+    Vec3 midVec(0,0,0);
+    if (v1+v2 == Vec3(0,0,0)) // vectors are opposite
+        midVec = Perpendicular(v1);
+    else
+        midVec = v1+v2;
+
+    midVec = Normalize(midVec);
+
+    // for pi rotation cos(pi) = -1, sin(pi) = 0
+    Matrix33 rot = Rodrigues(midVec, PI);
+    //rot.mat[0][0] = 2*midVec.x*midVec.x - 1;
+    //rot.mat[0][1] = 2*midVec.x*midVec.y;
+    //rot.mat[0][2] = 2*midVec.x*midVec.z;
+    //rot.mat[1][0] = 2*midVec.y*midVec.x;
+    //rot.mat[1][1] = 2*midVec.y*midVec.y - 1;
+    //rot.mat[1][2] = 2*midVec.y*midVec.z;
+    //rot.mat[2][0] = 2*midVec.z*midVec.x;
+    //rot.mat[2][1] = 2*midVec.z*midVec.y;
+    //rot.mat[2][2] = 2*midVec.z*midVec.z - 1;
+
+    return rot;
+}
+
+Vec3 RotatePoint(Matrix33 rot, Vec3 v)
+{
+    Vec3 res(0,0,0);
+
+    res.x = rot.mat[0][0]*v.x + rot.mat[0][1]*v.y + rot.mat[0][2]*v.z;
+    res.y = rot.mat[1][0]*v.x + rot.mat[1][1]*v.y + rot.mat[1][2]*v.z;
+    res.z = rot.mat[2][0]*v.x + rot.mat[2][1]*v.y + rot.mat[2][2]*v.z;
+
+    return res;
+}

+ 33 - 0
GTA-VI/Util.h

@@ -0,0 +1,33 @@
+// Util.h
+
+#ifndef __UTIL_H__
+#define __UTIL_H__
+
+#include "Types.h"
+
+Vec3 operator/(Vec3 v, double scalar);
+Vec3 operator+(Vec3 v1, Vec3 v2);
+bool operator==(Vec3 v1, Vec3 v2);
+
+double Magnitude(Vec3 v);
+Vec3 Normalize(Vec3 v);
+Vec3 Perpendicular(Vec3 v);
+///< returns a perpendicular vector (out of infinite) to the provided one
+
+Vec3 CrossProd(Vec3 v1, Vec3 v2);
+
+double DotProd(Vec3 v1, Vec3 v2);
+
+Matrix33 operator*(Matrix33 left, Matrix33 right);
+
+Matrix33 Transpose(Matrix33 in);
+
+Matrix33 Rodrigues(Vec3 v, double angle);
+///< Rodrigues formula for rotation around vector 
+
+Matrix33 RotationVecVec(Vec3 v1, Vec3 v2);
+///< Returns the rotation matrix in order to rotate one vector to the other
+
+Vec3 RotatePoint(Matrix33 rot, Vec3 v);
+
+#endif /*__UTIL_H__*/

+ 307 - 0
GTA-VI/VideoExtractor.cpp

@@ -0,0 +1,307 @@
+// VideoExtractor.cpp
+
+#include "VideoExtractor.h"
+
+extern "C"
+{
+    #include <libavcodec/avcodec.h>
+    #include <libavformat/avformat.h>
+    #include <libswscale/swscale.h>
+}
+
+#include <stdio.h>
+#include <fstream>
+#include <sys/stat.h>
+#include <cstdlib>
+
+using namespace std;
+
+#define MAX_FRAME_WIDTH 1920
+#define MAX_CHARS_PER_LINE 512
+#define TOKENS_PER_LINE 2
+// compatibility with newer API
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
+#define av_frame_alloc avcodec_alloc_frame
+#define av_frame_free avcodec_free_frame
+#endif
+
+// constants for saving frames and video data
+const string basePath = "/tmp/gta-vi/";
+const string frameBasename = "frame";
+const string frameExt = ".ppm";
+const string paramsFile = "video_parameters.txt";
+
+
+// PUBLIC
+
+/*static*/ bool VideoExtractor::IsVideoExtracted(string videoname)
+{
+    VideoParams videoParams;
+    string paramsname = GetParamsname();
+    if (!LoadVideoParams(paramsname, videoParams))
+        return false;
+
+    if (videoParams.videoname.compare(videoname) != 0)
+        return false;
+
+    return true;
+}
+
+/*static*/ bool VideoExtractor::ExtractFrames(string videoname)
+{
+  // Initalizing these to NULL prevents segfaults!
+  AVFormatContext   *pFormatCtx = NULL;
+  size_t            i;
+  int               videoStream;
+  AVCodecContext    *pCodecCtxOrig = NULL;
+  AVCodecContext    *pCodecCtx = NULL;
+  AVCodec           *pCodec = NULL;
+  AVFrame           *pFrame = NULL;
+  AVFrame           *pFrameRGB = NULL;
+  AVPacket          packet;
+  int               frameFinished;
+  int               numBytes;
+  uint8_t           *buffer = NULL;
+  struct SwsContext *sws_ctx = NULL;
+  VideoParams       videoParams; // parameters of the stored video
+  videoParams.videoname = videoname;
+
+  // Register all formats and codecs
+  av_register_all();
+  
+  // Open video file
+  if(avformat_open_input(&pFormatCtx, videoname.c_str(), NULL, NULL)!=0)
+    return false; // Couldn't open file
+  
+  // Retrieve stream information
+  if(avformat_find_stream_info(pFormatCtx, NULL)<0)
+    return false; // Couldn't find stream information
+  
+  // Dump information about file onto standard error
+  av_dump_format(pFormatCtx, 0, videoname.c_str(), 0);
+  
+  // Find the first video stream
+  videoStream=-1;
+  for(i=0; i<pFormatCtx->nb_streams; i++)
+    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
+      videoStream=i;
+      break;
+    }
+  if(videoStream==-1)
+    return false; // Didn't find a video stream
+  
+  // Get a pointer to the codec context for the video stream
+  pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec;
+  // Get frame rate
+  videoParams.fps_num = pFormatCtx->streams[videoStream]->avg_frame_rate.num;
+  videoParams.fps_den = pFormatCtx->streams[videoStream]->avg_frame_rate.den;  
+  // Find the decoder for the video stream
+  pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id);
+  if(pCodec==NULL) {
+    fprintf(stderr, "Unsupported codec!\n");
+    return false; // Codec not found
+  }
+  // Copy context
+  pCodecCtx = avcodec_alloc_context3(pCodec);
+  if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
+    fprintf(stderr, "Couldn't copy codec context");
+    return false; // Error copying codec context
+  }
+
+  // Open codec
+  if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
+    return false; // Could not open codec
+  
+  // Allocate video frame
+  pFrame=av_frame_alloc();
+  
+  // Allocate an AVFrame structure
+  pFrameRGB=av_frame_alloc();
+  if(pFrameRGB==NULL)
+    return false;
+
+  // Determine required buffer size and allocate buffer
+  numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
+			      pCodecCtx->height);
+  buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
+  
+  int out_width = pCodecCtx->width;
+  int out_height = pCodecCtx->height;
+  while (out_width > MAX_FRAME_WIDTH)
+  {
+      out_width /= 2; // halving size makes extraction faster
+      out_height = (int)((double)out_width*pCodecCtx->height/pCodecCtx->width);
+  }
+  videoParams.width = out_width;
+  videoParams.height = out_height;
+  //int out_height = 300;
+  // Assign appropriate parts of buffer to image planes in pFrameRGB
+  // Note that pFrameRGB is an AVFrame, but AVFrame is a superset
+  // of AVPicture
+  avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
+		 out_width, out_height);
+  
+  // initialize SWS context for software scaling
+  sws_ctx = sws_getContext(pCodecCtx->width,
+			   pCodecCtx->height,
+			   pCodecCtx->pix_fmt,
+			   out_width,
+			   out_height,
+			   PIX_FMT_RGB24,
+			   SWS_BILINEAR,
+			   NULL,
+			   NULL,
+			   NULL
+			   );
+
+  // make sure the output directory exists
+  if (!CreateBaseDir())
+      return false;
+
+  // Read frames and save first five frames to disk
+  i=0;
+  while(av_read_frame(pFormatCtx, &packet)>=0) {
+    // Is this a packet from the video stream?
+    if(packet.stream_index==videoStream) {
+      // Decode video frame
+      avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
+      
+      // Did we get a video frame?
+      if(frameFinished) {
+	    // Convert the image from its native format to RGB
+	    sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
+		    pFrame->linesize, 0, pCodecCtx->height,
+		    pFrameRGB->data, pFrameRGB->linesize);
+	
+    	// Save the frame to disk
+    	SaveFrame(pFrameRGB, out_width, out_height,i++);
+      }
+    }
+    
+    // Free the packet that was allocated by av_read_frame
+    av_free_packet(&packet);
+  }
+
+  videoParams.numOfFrames = i;
+  string paramsFile = GetParamsname();
+  SaveVideoParams(videoParams, paramsFile);
+  
+  // Free the RGB image
+  av_free(buffer);
+  av_frame_free(&pFrameRGB);
+  
+  // Free the YUV frame
+  av_frame_free(&pFrame);
+  
+  // Close the codecs
+  avcodec_close(pCodecCtx);
+  avcodec_close(pCodecCtxOrig);
+
+  // Close the video file
+  avformat_close_input(&pFormatCtx);
+  
+  return true;
+}
+
+// PRIVATE
+
+
+/*static*/ string VideoExtractor::GetFramename(int frameNum)
+{
+    string frameName = basePath + frameBasename + to_string(frameNum) + frameExt;
+    return frameName;
+}
+
+/*static*/ string VideoExtractor::GetParamsname()
+{
+    return basePath+paramsFile;
+}
+
+/*static*/ void VideoExtractor::SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
+  FILE *pFile;
+  int  y;
+  
+  // Open file
+  //sprintf(szFilename, "frame%d.ppm", iFrame);
+  //pFile=fopen(szFilename, "wb");
+  string framename = GetFramename(iFrame);
+  pFile=fopen(framename.c_str(), "wb");
+  if(pFile==NULL)
+    return;
+  
+  // Write header
+  fprintf(pFile, "P6\n%d %d\n255\n", width, height);
+  
+  // Write pixel data
+  for(y=0; y<height; y++)
+    fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
+  
+  // Close file
+  fclose(pFile);
+}
+
+// functions for storing and loading video parameters
+/*static*/ void VideoExtractor::SaveVideoParams(VideoParams videoParams, string filename)
+{
+    ofstream ofile;
+    ofile.open(filename.c_str());
+
+    ofile << "videoname: " << videoParams.videoname << "\n";
+    ofile << "fps_num: " << videoParams.fps_num << "\n";
+    ofile << "fps_den: " << videoParams.fps_den << "\n";
+    ofile << "width: " << videoParams.width << "\n";
+    ofile << "height: " << videoParams.height << "\n";
+    ofile << "numOfFrames: " << videoParams.numOfFrames << "\n";
+
+    ofile.close();
+}
+
+/*static*/ bool VideoExtractor::LoadVideoParams(string filename, VideoParams &rVideoParams)
+{
+    ifstream ifile;
+    ifile.open(filename.c_str());
+    if (!ifile.is_open())
+        return false;
+
+    char buf[MAX_CHARS_PER_LINE];
+    char *token[TOKENS_PER_LINE];
+
+    while(!ifile.getline(buf, MAX_CHARS_PER_LINE).eof())
+    {
+        token[0] = strtok(buf, " ");
+        token[1] = strtok(0, " ");
+        if (string(token[0]).compare("videoname:") == 0)
+            rVideoParams.videoname = token[1];
+        else if (string(token[0]).compare("fps_num:") == 0)
+            rVideoParams.fps_num = atoi(token[1]);
+        else if (string(token[0]).compare("fps_den:") == 0)
+            rVideoParams.fps_den = atoi(token[1]);
+        else if (string(token[0]).compare("width:") == 0)
+            rVideoParams.width = atoi(token[1]);
+        else if (string(token[0]).compare("height:") == 0)
+            rVideoParams.height = atoi(token[1]);
+        else if (string(token[0]).compare("numOfFrames:") == 0)
+            rVideoParams.numOfFrames = atoi(token[1]);
+
+    }
+
+    ifile.close();
+    return true;
+}
+
+/*static*/ bool VideoExtractor::CreateBaseDir()
+{
+    struct stat info;
+    // directory already exists
+    if( stat(basePath.c_str(), &info) == 0 )
+        return true;
+
+    // otherwise create it
+    string command = "mkdir -p " + basePath;
+    //const int dir_err = mkdir(basePath.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); // this command cannot create last level directory if all the precious directories do not exist
+    const int dir_err = system(command.c_str());
+    if (dir_err == 0)
+        return true;
+
+    return false;
+}

+ 61 - 0
GTA-VI/VideoExtractor.h

@@ -0,0 +1,61 @@
+/// VideoExtractor.h
+/// This class provides functionality for extracting the frames of a video.
+/// Curently it extracts at a fixed directory within /tmp.
+
+#ifndef __VIDEOEXTRACTOR_H__
+#define __VIDEOEXTRACTOR_H__
+
+#include <string>
+
+extern "C"
+{
+    #include <libavformat/avformat.h>
+}
+
+using namespace std;
+
+// structure holding video parameters
+struct VideoParams
+{
+    string videoname;
+    int         fps_num;
+    int         fps_den;
+    int         width;
+    int         height;
+    int         numOfFrames;
+
+    double GetFrameRate()
+    {
+        if (fps_den <=0)
+            return -1.0;
+
+        return (double)fps_num/fps_den;
+    }
+};
+
+class VideoExtractor
+{
+public:
+    static bool IsVideoExtracted(string videoname);
+
+    static bool ExtractFrames(string videoname);
+
+    static string GetFramename(int frameNum);
+
+    static string GetParamsname();
+
+    static bool LoadVideoParams(string filename, VideoParams &rVideoParams);
+
+private:
+    VideoExtractor();
+    VideoExtractor(const VideoExtractor &f);
+    VideoExtractor &operator=(const VideoExtractor &f);
+
+    static void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame);
+
+    static void SaveVideoParams(VideoParams videoParams, string filename);
+
+    static bool CreateBaseDir();
+};
+
+#endif /* __VIDEOEXTRACTOR_H__ */

+ 173 - 0
GTA-VI/VideoWidget.cpp

@@ -0,0 +1,173 @@
+// VideoWidget.cpp
+
+#include "VideoWidget.h"
+
+#include <iostream>
+#include <QThread>
+using namespace std;
+
+// PUBLIC SLOTS:
+
+void VideoWidget::HandleTime(int time, QObject *pSender)
+{
+    if (pSender == this)
+        return;
+    else
+        Pause();
+
+    // block signals
+    blockSignals(true);
+
+    // Pause on user input
+    //Pause();
+    SetCurrentTime(time);
+
+    // unblock singals
+    blockSignals(false);
+}
+
+void VideoWidget::HandleToggleView()
+{
+    if (!m_pEqToFov)
+        return;
+
+    if (m_toggled)
+    {
+        m_mediaPlayer.SetConverter(nullptr);
+        m_toggled = false;
+    }
+    else
+    {
+        m_mediaPlayer.SetConverter(m_pEqToFov);
+        m_toggled = true;
+    }
+
+    update();
+}
+
+// PRIVATE SLOTS:
+
+void VideoWidget::HandleTimeChanged(qint64 time)
+{
+    const QSignalBlocker blocker(m_mediaPlayer);
+    // convert time from ms to us
+    if (!m_isPaused)
+    {
+        m_previousTime = time*1000;
+        // emit signal
+        emit SendTime(m_previousTime);
+        update();
+    }
+}
+
+// PUBLIC:
+
+VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent), m_pPaintGaze(0), m_pEqToFov(0), m_isPaused(true), m_toggled(false)
+{
+    SetCurrentTime(0);
+
+    // set the following attributes to get some speed up
+    setAttribute(Qt::WA_NoSystemBackground, true);
+
+    // connect objects signal with private slot
+    QObject::connect(&m_mediaPlayer, SIGNAL(positionChanged(qint64)), this, SLOT(HandleTimeChanged(qint64)));
+}
+
+VideoWidget::~VideoWidget()
+{
+    if (!m_pEqToFov)
+        delete m_pEqToFov;
+}
+
+QSize VideoWidget::minimumSizeHint() const
+{
+    return QSize(160, 90);
+}
+
+void VideoWidget::SetCurrentTime(int currentTime)
+{
+    if(!m_mutex.tryLock())
+        return;
+    //const QSignalBlocker blocker(m_mediaPlayer);
+    m_previousTime = currentTime;
+    int msTime = currentTime/1000; // convert to ms
+    m_mediaPlayer.setPosition(msTime);
+
+    update();
+    m_mutex.unlock();
+}
+
+bool VideoWidget::SetMedia(QString pathToFile)
+{
+    return m_mediaPlayer.setMedia(QUrl::fromLocalFile(pathToFile));
+}
+
+void VideoWidget::SetGazePaint(PaintGaze *pPaintGaze)
+{
+    m_pPaintGaze = pPaintGaze;
+}
+
+void VideoWidget::Play()
+{
+    if (m_mediaPlayer.isVideoAvailable())
+    {
+        m_isPaused = false;
+        //m_mediaPlayer.blockSignals(false);
+        m_mediaPlayer.play();
+        repaint();
+    }
+}
+
+void VideoWidget::Pause()
+{
+    if (m_mediaPlayer.isVideoAvailable())
+    {
+        m_isPaused = true;
+        //m_mediaPlayer.blockSignals(true);
+        m_mediaPlayer.pause();
+    }
+}
+
+void VideoWidget::TogglePlayer()
+{
+    if (m_mediaPlayer.state()==MediaPlayer::PausedState || m_mediaPlayer.state()==MediaPlayer::StoppedState)
+        Play();
+    else
+        Pause();
+}
+
+qint64 VideoWidget::Duration()
+{
+    qint64 duration = m_mediaPlayer.duration();
+
+    if (duration < 0)
+        return -1;
+    else
+        return 1000*duration;
+}
+
+void VideoWidget::ConvertToFov(Arff *pArff)
+{
+    m_pEqToFov = new EquirectangularToFovVideo(pArff);
+}
+
+
+// PROTECTED:
+
+void VideoWidget::paintEvent(QPaintEvent *) /*override*/
+{
+    QPainter painter(this);
+    // paint video
+    QRect geom = geometry();
+    m_mediaPlayer.Paint(&painter, geom.size());
+    // paint gaze
+    if (m_pPaintGaze != NULL)
+    {
+        m_pPaintGaze->Paint(&painter, geom.size());
+    }
+}
+
+void VideoWidget::resizeEvent(QResizeEvent *event) /*override*/
+{
+    QWidget::resizeEvent(event);
+}

+ 84 - 0
GTA-VI/VideoWidget.h

@@ -0,0 +1,84 @@
+/// VideoWidget.h
+
+#ifndef __VIDEOWIDGET_H__
+#define __VIDEOWIDGET_H__
+
+#include <QtWidgets>
+#include <QMutex>
+#include "PaintGaze.h"
+#include "MediaPlayer.h"
+#include "EquirectangularToFovVideo.h"
+
+// Forward declare
+class Arff;
+
+class VideoWidget : public QWidget
+{
+    Q_OBJECT
+
+signals:
+    void SendTime(int time);
+    ///< emits the time shift from m_qMediaplayer in us.
+
+public slots:
+    void HandleTime(int time, QObject *pSender);
+    ///< Handles the signal for changing the position on the video.
+
+    void HandleToggleView();
+    ///< It toggles the displayed video if FOV data are available
+
+private slots:
+    void HandleTimeChanged(qint64 time);
+    ///< Takes care of hadling the signal from m_qMediaPlayer and transmits it
+    ///< as being its own.
+
+public:
+    VideoWidget(QWidget *parent=0);
+
+    ~VideoWidget();
+
+    QSize minimumSizeHint() const Q_DECL_OVERRIDE;
+    
+    void SetCurrentTime(int currentTime);
+    ///< Sets the time of the video in us.
+
+    bool SetMedia(QString pathToFile);
+    ///< Sets media to the provided local path.
+
+    void SetGazePaint(PaintGaze *pPaintGaze);
+    
+    void Play();
+    ///< Starts playing video.
+
+    void Pause();
+    ///< Pauses video.
+
+    void TogglePlayer();
+    ///< Toggles the state of the video from stopped to playing.
+
+    qint64 Duration();
+    ///< Returns the video duration in us.
+    ///< \b Available only after starting playing the video.
+
+    void ConvertToFov(Arff *pArff);
+    ///< By calling this function we signal that the video should be converted
+    ///< from 360 degrees equirectangular to field of view.
+
+protected:
+    void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
+    ///< Called on update(), repaint() and then passes painter to surface in order to paint.
+
+    void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
+    ///< Update area on resize.
+
+private:
+    MediaPlayer             m_mediaPlayer;
+    PaintGaze               *m_pPaintGaze; // pointer to gaze painter
+    EquirectangularToFovVideo    *m_pEqToFov;
+    qint64                  m_previousTime;
+    bool                    m_isPaused; // used for correcting time offset when pausing
+    QMutex                  m_mutex; // mutex for updating QMediaPlayer time
+    bool                    m_toggled; // toggle the diplayed frame
+};
+
+#endif /*__MEDIAPLAYER_H__*/

+ 228 - 0
README.md

@@ -0,0 +1,228 @@
+
+# Ground Truth Annotation-Visualization Interface for Eye Movements
+
+**For the 360 video extension documentation click [here](README_360.md)**
+
+## 1. Introduction
+
+In eye movement research with dynamic natural scenes there is a great need for
+ground thruth since the stimuli are not artificially created and their
+properties are not known. Even though many automatic algorithms exist for
+annotating samples from streams to fixation, saccades, smooth pursuit etc., hand
+labelling is still considered the best for those tasks especially when we are
+talking about complex eye movements such as smooth pursuits. This interface
+combines the results from many automatic algorithms, placed in columns of an
+ARFF file (explained below), and allows the user to fine tune the intervals.
+
+## 2. ARFF files
+
+The ARFF format is widely used in the pattern recognition community and
+documentation is available at
+`https://waikato.github.io/weka-wiki/arff_stable/`. A brief overview with some
+tool-specific variations is given below:
+
+- All the data keywords are case insensitive
+
+- Lines starting with "%" are comments
+
+- Lines starting with "%" and followed by "@METADATA" are considered special
+comments "%@METADATA" followed by the metadata name followed by the value. The
+available metadata names are *width_px, height_px, distance_mm, width_mm, 
+height_mm*.
+
+- Lines starting with "@" followed by a word, without space, are considered
+keywords. The available keywords are the following: 
+    - "@ATTRIBUTE" followed by the attribute name followed by the type of data.
+    The tool curently support *INTEGER*, *NUMERIC* and *NOMINAL* attributes. The
+    nominal attributes are handled internally as enumerations. Thus when you
+    acces the loaded you will see integer values in their place. 
+    - "@DATA" denotes that all the lines from next line onwards are data. The 
+    data should follow the order that the attributes were presented.
+
+- Each automatic algorithm is assigned to an attribute
+
+### 2.1. ARFF example
+
+Part of example ARFF file with added explanation.
+
+```
+@RELATION gaze_labels <-data included in arff
+
+%@METADATA width_px 1280 <-recording metadata
+%@METADATA height_px 720
+%@METADATA distance_mm 450
+%@METADATA width_mm 400
+%@METADATA height_mm 230
+
+@ATTRIBUTE time NUMERIC
+@ATTRIBUTE x NUMERIC
+@ATTRIBUTE y NUMERIC
+@ATTRIBUTE dbscan NUMERIC <-smooth pursuit detection algorithm
+@ATTRIBUTE i-vvt NUMERIC <-saccade detection algorithm
+@ATTRIBUTE i-vdt NUMERIC <-fixation detection algorithm
+@ATTRIBUTE i-dt NUMERIC <-fixation detection algorithm
+@ATTRIBUTE expert1 NUMERIC <-hand-tuned output
+
+
+@DATA <- after this point the actual data start
+2000,620.80,289.20,0,0,0,0,0
+6000,622.70,291.30,0,0,0,1,1 
+10000,622.90,292.10,0,0,0,1,1
+14000,623.00,290.90,0,0,1,1,1
+18000,623.00,289.70,0,0,1,1,1
+```
+
+### 2.2. ARFF conversion from .coord
+
+See the coord2arff subdirectory for a MATLAB/Octave tool to convert
+the GazeCom .coord gaze file format to ARFF.
+
+## 3. License
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see `http://www.gnu.org/licenses/`
+license - GPL.
+
+## 4. Citing GTA-VI
+
+We would ask you to cite the following paper if you use GTA-VI
+
+Ioannis Agtzidis, Mikhail Startsev and Michael Dorr,
+In the pursuit of (ground) truth: A hand-labelling tool for eye movements
+recorded during dynamic scene viewing, ETVIS 2016
+
+> \@article{Agtzidis2016InTP,<br/>
+> title={In the pursuit of (ground) truth: a hand-labelling tool for eye movements recorded during dynamic scene viewing},<br/> 
+> author={Ioannis Agtzidis and Mikhail Startsev and Michael Dorr},<br/>
+> journal={2016 IEEE Second Workshop on Eye Tracking and Visualization (ETVIS)},<br/>
+> year={2016},<br/>
+> pages={65-68}<br/>
+> }  
+
+## 5. Installation
+
+The code of this interface was tested with Ubuntu 16.04 LTS and it should work
+with other Linux environments. Before you start, make sure the following
+packages are installed:
+
+```
+apt-get install qt5-default
+apt-get install libavutil-dev
+apt-get install libavformat-dev
+apt-get install libavcodec-dev
+apt-get install libswscale-dev
+```
+
+If all of the above were installed successfully and the Qt version is 5.3 or
+above, move to the root directory within the repository that you cloned and run
+the following:
+
+```
+cd GTA-VI
+qmake
+make
+```
+
+Good luck! ;-)
+
+## 6. Examples
+
+If the above installation steps exited without error, you are ready to use the
+interface.
+Move to the GTA-VI directory and run ./GTA-VI -h for help. Three example
+commands are given below (samples from [1] are provided in the samples directory):
+
+- Start interface and load data with mouse
+```
+./GTA-VI
+```  
+
+- Start interface already preloaded with labelled data and video
+```
+./GTA-VI --vf ../samples/breite_strasse.m2t --af ../samples/MKB_breite_strasse_1_20s_1.arff --sf test.arff --pl myname
+```
+
+- Start interface as above which custom nominal labels
+```
+./GTA-VI --vf ../samples/breite_strasse.m2t --af ../samples/MKB_breite_strasse_1_20s_1.arff --sf test.arff --pl myname --plv {unassigned, fixation, saccade, noise}
+```
+
+| Short Option | Long Option | Explanation |
+| ------ | ------ | ------ |
+|-h| \-\-help                            | Displays this help.                  |   
+|-v| \-\-version                         | Displays version information.            |
+|\-\-vf| \-\-video-file \<file\>             | Video file.                            |
+|\-\-af| \-\-arff-file \<file\>              | ARFF file.                             |
+|\-\-sf| \-\-save-file \<file\>              | Save file.                             |
+|\-\-pl| \-\-primary-label \<name\>          | Primary hand labelling attribute name. |
+|\-\-plv| \-\-primary-label-value \<name\>   | (Optional) Create a nominal primary labelling attribute. Ex.     "{fix,sacc,sp}".|
+|\-\-sl| \-\-secondary-label \<name\>        | (Optional) Secondary hand labelling attribute name.|
+|\-\-slv| \-\-secondary-label-value \<name\> | (Optional) Create a nominal secondary labelling attribute. E    x. "{fix,sacc,sp}".|                     
+|-f| \-\-full-screen                     | Start window in full screen mode.|   
+|\-\-fov| \-\-field-of-view                | Convert Equirectangular video to Field Of View|
+|\-\-head| \-\-head-only-motion            | Display only head motion in the equirectangular video|
+
+## 7. GTA-VI functionality
+
+Most of the user interaction is achieved through the interactive panels 
+of the interface and keyboard shortcuts. If a secondary label is used a a third
+color coded row appears at the bottom of the interface representing the eye
+movements of the secondary label. The X,Y and speed panels change the primary
+label and the two optional bottom panels the secondary label. A list of actions is
+presented below:
+
+- Right-clicking and dragging moves the current position in time (backwards or
+forwards according to the direction of the mouse movement)
+
+- Scrolling the mouse wheel changes the temporal scale, i.e.  increases or
+decreases (according to the scroll direction) the temporal window represented by
+the plots on the right-side panels
+
+- Left-clicking and dragging moves the closest border
+
+- Holding the left-click on an interval and pressing a number on the keyboard
+changes the label of the interval. The legend provides information on the
+correspondence between numbers and the assigned labels
+
+- The sequence of a left-click and  pressing the
+Insert key inserts a new interval of the selected type spanning a temporal
+window of +/- 40 ms around the current time; this interval can then be
+adjusted as above. The type of inserted interval is selected through the
+keyboard number press
+
+- Double left click on the secondary label panels adds an interval that matches
+the borders of the primary interval above it. It does not overwrite secondary
+labels if they exist. The type of inserted interval is selected through the 
+keyboard number press
+
+- The sequence of a left-click and pressing the Delete key unassigns the label
+of the selected interval
+
+- Pressing the Space key starts playing or pauses the video
+
+- Pressing Ctrl-Z reverts the last change
+
+- Pressing Ctrl-Shift-Z acts as “redo” (reapplies the last cancelled change)
+
+## 8. References
+
+[1] Michael Dorr, Thomas Martinetz, Karl Gegenfurtner, and Erhardt Barth.
+Variability of eye movements when viewing dynamic natural scenes. Journal of
+Vision, 10(10):1-17, 2010    
+
+http://jov.arvojournals.org/article.aspx?articleid=2121333
+
+## 9. Contact
+
+For feedback please contact Ioannis Agtzidis at ioannis.agtzidis@tum.de
+

+ 202 - 0
README_360.md

@@ -0,0 +1,202 @@
+
+# Ground Truth Annotation-Visualization Interface for Eye Movements in 360 degree Videos
+
+**For the documentation of the tool refer to the _Main Page_**
+
+## 1. Introduction
+
+Here we explain the specifics of the 360 degree extension to the original GTA-VI tool. This
+extension allows the visualization and annotation of data gathered during 360
+degrees experiments. The experiment stimuli used so far is monocular 360 degree
+equirectangular videos. The eye tracking recordings are stored in the previously
+explained ARFF format but it is extended in order to be able to represent the
+state of the experiment.
+
+## 2. ARFF files
+
+Here the "@relation" is set to gaze_360 to distinguish the recordings from plain
+gaze recordings. We also make use of the "%@METADATA" special comments which
+describe the field of view of the used headset. The extra metadata names are
+*fov_width_px, fov_width_deg, fov_height_px, fov_height_deg*. Although none of
+the extra metadata are strictly needed for gaze visualization in the FOV
+representation, they bring the representation closer to what was displayed
+during the experiment.
+
+
+### 2.1. ARFF example
+
+```
+@RELATION gaze_360
+
+%@METADATA distance_mm 0.00
+%@METADATA height_mm 0.00
+%@METADATA height_px 1080
+%@METADATA width_mm 0.00
+%@METADATA width_px 1920
+
+%@METADATA fov_height_deg 100.00
+%@METADATA fov_height_px 1440
+%@METADATA fov_width_deg 100.00
+%@METADATA fov_width_px 1280
+
+@ATTRIBUTE time INTEGER
+@ATTRIBUTE x NUMERIC
+@ATTRIBUTE y NUMERIC
+@ATTRIBUTE confidence NUMERIC
+@ATTRIBUTE x_head NUMERIC
+@ATTRIBUTE y_head NUMERIC
+@ATTRIBUTE angle_deg_head NUMERIC
+@ATTRIBUTE labeller_1 {unassigned,fixation,saccade,SP,noise,VOR,OKN}
+
+
+@DATA
+0,960.00,540.00,1.00,960.00,540.00,1.22,fixation
+5000,959.00,539.00,1.00,959.00,539.00,1.23,fixation
+13000,959.00,539.00,1.00,959.00,539.00,1.23,fixation
+18000,959.00,539.00,1.00,959.00,539.00,1.23,fixation
+29000,959.00,539.00,1.00,959.00,539.00,1.24,fixation
+34000,959.00,539.00,1.00,959.00,539.00,1.24,fixation
+45000,959.00,539.00,1.00,959.00,539.00,1.24,fixation
+49000,959.00,539.00,1.00,959.00,539.00,1.24,fixation
+61000,959.00,539.00,1.00,959.00,539.00,1.24,fixation
+66000,959.00,539.00,1.00,959.00,539.00,1.24,fixation
+77000,959.00,539.00,1.00,959.00,540.00,1.24,fixation
+82000,959.00,539.00,1.00,959.00,540.00,1.24,fixation
+94000,959.00,539.00,1.00,960.00,540.00,1.24,fixation
+99000,959.00,539.00,1.00,960.00,540.00,1.24,fixation
+110000,959.00,539.00,1.00,960.00,540.00,1.25,fixation
+114000,959.00,539.00,1.00,960.00,540.00,1.25,fixation
+125000,958.00,538.00,1.00,960.00,540.00,1.26,saccade
+129000,956.00,537.00,1.00,960.00,540.00,1.27,saccade
+141000,948.00,530.00,1.00,960.00,540.00,1.28,saccade
+```
+
+## 3. License
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+license - GPL
+
+## 4. Citing GTA-VI
+
+We would ask you to cite the following paper if you use GTA-VI
+
+Ioannis Agtzidis, Mikhail Startsev and Michael Dorr,
+In the pursuit of (ground) truth: A hand-labelling tool for eye movements
+recorded during dynamic scene viewing, ETVIS 2016
+
+> \@article{Agtzidis2016InTP,<br/>
+> title={In the pursuit of (ground) truth: a hand-labelling tool for eye movements recorded during dynamic scene viewing},<br/>
+> author={Ioannis Agtzidis and Mikhail Startsev and Michael Dorr},<br/>
+> journal={2016 IEEE Second Workshop on Eye Tracking and Visualization (ETVIS)},<br/>
+> year={2016},<br/>
+> pages={65-68}<br/>
+> }
+
+## 5. Citing GTA-VI 360 degrees extension
+
+We would ask you to cite the following paper if you use GTA-VI tool along with
+its 360 degrees extension
+
+%TODO add citation of the SWAET/JEMR abstract
+
+## 6. Use with CUDA
+
+The extension supports CUDA accelerated conversion from equirectangular to field of view
+frames. In order to use this functionality you will have to change the *GTA-VI.pro* file.
+
+First you have to find the compute architecture of you GPU card. The compute architecture
+can be found in <https://developer.nvidia.com/cuda-gpus>. The compute architecture is the
+derived from the compute capability by removing the dot between the 2 numbers.
+
+Second in the *GTA-VI.pro* file uncomment the *CONFIG += USE_CUDA*, place the number from
+the previous step in the *CUDA_COMPUTE_ARCH* variable and finally provide the installation path
+of CUDA in the *CUDA_DIR* variable.
+
+Now you are ready to recompile your project.
+
+```
+qmake
+make clean
+make -j4
+```
+
+If things do not compile or work as expected you can take a look at *QMAKE_LIBDIR* and 
+*INCLUDEPATH* in the *GTA-VI.pro* and fix possible errors.
+
+If you still cannot make it work you can use the CPU implementation, by commenting 
+the "#CONFIG += USE_CUDA" line, which does the same but slower.
+
+## 7. Examples
+
+If you have installed the tool succesfully you can try to run the following
+examples.
+
+The 360 degrees extension of the tool is only available through the command line
+interface.
+
+Move to the GTA-VI directory and run ./GTA-VI -h for help. Some example
+commands are given below:
+
+```
+./GTA-VI --vf 360_video.mp4 --af recoding1.arff --pl ioannis_pl --plv "{unassigned, fix, sacc, sp, noise}" --sl ioannis_sl --slv "{unassigned, okn, vor, noise}" --sf test1.arff --fov -f
+```
+
+```
+./GTA-VI --vf 360_video.mp4 --af recoding1.arff --pl ioannis_pl --plv "{unassigned, fix, sacc, sp, noise}" --sf test1.arff -f
+```
+
+The explanation of the command line a arguments as return from the tool is given below:
+
+| Short Option | Long Option | Explanation |
+| ------ | ------ | ------ |
+|-h| \-\-help                            | Displays this help.					|
+|-v| \-\-version                         | Displays version information.			|
+|\-\-vf| \-\-video-file <file>             | Video file.							|
+|\-\-af| \-\-arff-file <file>              | ARFF file.								|
+|\-\-sf| \-\-save-file <file>              | Save file.								|
+|\-\-pl| \-\-primary-label <name>          | Primary hand labelling attribute name.	|
+|\-\-plv| \-\-primary-label-value <name>   | (Optional) Create a nominal primary labelling attribute. Ex. "{fix,sacc,sp}".|
+|\-\-sl| \-\-secondary-label <name>        | (Optional) Secondary hand labelling attribute name.|
+|\-\-slv| \-\-secondary-label-value <name> | (Optional) Create a nominal secondary labelling attribute. Ex. "{fix,sacc,sp}".|
+|-f| \-\-full-screen                     | Start window in full screen mode.|
+|\-\-fov| \-\-field-of-view                | Convert Equirectangular video to Field Of View|
+|\-\-head| \-\-head-only-motion            | Display only head motion in the equirectangular video|
+
+## 8. GTA-VI 360 degrees functionality
+
+The current extension displays gaze/head coordinates along with their speed.
+Without any argument the head+eye gaze trace is displayed on all 4 panels along
+with its overlay on the equirectangular. 
+
+By passing the \-\-fov argument the gaze within head (FOV) is diplayed, which
+is equivalent to the movement of the eye within the eye socket. Also with the
+\-\-fov option the video panel displays only the part of the video that was
+displayed during the experiment in the head mounted display. With this option 
+you can toggle to the head+eye view and back by pressing the "t" key.
+
+With the \-\-head argument only the head coordinates in the equirectangular
+video is displayed. This last option is useful mostly for visualization of head
+movement and to a lesser extend for eye movement classification.
+
+## 9. References
+
+[1] Agtzidis, Ioannis, Mikhail Startsev, and Michael Dorr. "In the pursuit of
+(ground) truth: A hand-labelling tool for eye movements recorded during dynamic
+scene viewing." Eye Tracking and Visualization (ETVIS), IEEE Second Workshop on.
+IEEE, 2016.
+
+## 10. Contact
+
+For feedback please contact Ioannis Agtzidis at ioannis.agtzidis@tum.de
+

+ 116 - 0
arffHelper/Arff.cpp

@@ -0,0 +1,116 @@
+// Arff.cpp
+
+#include "Arff.h"
+
+using namespace std;
+
+#define MAX_TIME_GAP_MS 200
+
+Arff::Arff() : ArffBase(), m_isChanged(false)
+{
+}
+
+Arff::Arff(const char* filename) : ArffBase(filename), m_isChanged(false)
+{
+}
+
+void Arff::Save(const char *filename) 
+{
+	m_isChanged = false;
+
+    ArffBase::Save(filename);
+}
+
+void Arff::ChangeData(int row, int column, double value)
+{
+    // store change
+    Change change;
+    change.row = row;
+    change.column = column;
+    change.value = (*this)[row][column];
+    m_changes.push_back(change);
+
+    // apply change
+    (*this)[row][column] = value;
+
+    // clear prevously applied changes
+    m_redoChanges.clear();
+
+    // set state
+    m_isChanged = true;
+}
+
+int Arff::WidthPx()
+{
+    string value;
+    if (GetMetadata("width_px", value))
+        return stoi(value);
+    else
+        return -1;
+}
+
+void Arff::SetWidthPx(int width)
+{
+    SetMetadata("width_px", to_string(width));
+}
+
+int Arff::HeightPx()
+{
+    string value;
+    if (GetMetadata("height_px", value))
+        return stoi(value);
+    else
+        return -1;
+}
+
+void Arff::SetHeightPx(int height)
+{
+    SetMetadata("height_px", to_string(height));
+}
+
+void Arff::UndoLastChange()
+{
+    MoveChanges(m_changes, m_redoChanges);
+    m_isChanged = true;
+}
+
+void Arff::RedoLastChange()
+{
+    MoveChanges(m_redoChanges, m_changes);
+    m_isChanged = true;
+}
+
+bool Arff::IsDataChanged()
+{
+    return m_isChanged;
+}
+
+// PRIVATE:
+
+void Arff::MoveChanges(vector<Change> &from, vector<Change> &to)
+{
+    if (from.empty())
+        return;
+
+    chrono::system_clock::time_point prevTime = from.back().time;
+    chrono::system_clock::time_point curTime;
+    unsigned int timeDiffMs;
+    Change change;
+
+    while(!from.empty())
+    {
+        change = from.back();
+        curTime = change.time;
+        timeDiffMs = abs(chrono::duration_cast<chrono::milliseconds>(curTime - prevTime).count());
+
+        if (timeDiffMs > MAX_TIME_GAP_MS)
+            break;
+
+        double tmpValue = (*this)[change.row][change.column];
+        (*this)[change.row][change.column] = change.value;
+        change.value = tmpValue;
+
+        to.push_back(change);
+        from.pop_back();
+    }
+}

+ 64 - 0
arffHelper/Arff.h

@@ -0,0 +1,64 @@
+// Arff.h
+
+#ifndef __ARFF_H__
+#define __ARFF_H__
+
+#include "ArffBase.h"
+
+#include <chrono>
+
+using namespace std;
+
+// struct that holds the information about the change in one point
+struct Change
+{   
+    Change() {row=0; column=0; value=0;
+        time=chrono::system_clock::now();
+    }
+    Change(int r, int c, int val){row=r; column=c;
+        value=val; time=chrono::system_clock::now();
+    }
+    int row;
+    int column;
+    double value;
+    chrono::system_clock::time_point time;
+};
+
+
+class Arff : public ArffBase
+{
+public:
+    Arff();
+
+    Arff(const char* filename);
+
+    void Save(const char *filename);
+
+    void ChangeData(int row, int column, double value);
+    ///< This function is called instead of the oveloaded [] operator in 
+    ///< case that we require a reversible change.
+
+    int WidthPx() const;
+
+    void SetWidthPx(int width);
+
+    int HeightPx() const;
+
+    void SetHeightPx(int height);
+
+    void UndoLastChange();
+
+    void RedoLastChange();
+
+    bool IsDataChanged();
+
+private:
+	vector<Change> m_changes;
+    vector<Change> m_redoChanges;
+
+    bool m_isChanged;
+
+    void MoveChanges(vector<Change> &from, vector<Change> &to);
+};
+
+#endif /*__ARFF_H__*/

+ 307 - 0
arffHelper/ArffBase.cpp

@@ -0,0 +1,307 @@
+// ArffBase.cpp
+
+#include "ArffBase.h"
+
+#include <fstream>
+#include <iterator>
+#include <sstream>
+#include <algorithm>
+#include <cassert>
+#include <iostream>
+
+using namespace std;
+
+ArffBase::ArffBase() : m_dataReached(false)
+{
+}
+
+ArffBase::ArffBase(const char* filename) : m_dataReached(false)
+{
+    Load(filename);
+}
+
+ArffBase::~ArffBase()
+{
+}
+
+bool ArffBase::Load(const char* filename)
+{
+    ifstream ifs;
+
+    ifs.open(filename, ifstream::in);
+
+    if (ifs.fail())
+    {
+        cerr << "Could not open file: " << filename << endl;
+        return false;
+    }
+
+    string line;
+    while (ifs)
+    {
+        if(!getline(ifs, line))
+            break;
+        ProcessArffLine(line);
+    }
+
+    ifs.close();
+    return true;
+}
+
+void ArffBase::Save(const char* filename)
+{
+    ofstream ofs;
+    ofs.open(filename, ofstream::out);
+
+    ofs << "@RELATION " << m_relation << endl << endl;
+
+    for (auto p: m_mMetadata)
+        ofs << "\%@METADATA " << p.first << " " << p.second << endl;
+    ofs << endl;
+
+    for (unsigned int i=0; i<m_vpAttributes.size(); i++)
+        (*m_vpAttributes[i]).PrintDescription(ofs);
+    ofs << endl;
+
+    for (auto p:m_vComments)
+        ofs << p << endl;
+    ofs << endl;
+
+    ofs << "@DATA" << endl;
+    for (auto lineEntry:m_data)
+    {
+        unsigned int i;
+        for (i=0; i<lineEntry.size()-1; i++)
+        {
+            (*m_vpAttributes.at(i)).Print(ofs, lineEntry[i]);
+            ofs << ",";
+        }
+        (*m_vpAttributes.at(i)).Print(ofs, lineEntry[i]);
+        ofs << endl;
+    }
+
+
+    ofs.close();
+}
+
+void ArffBase::AddRow(vector<double> row)
+{
+    assert(row.size() == m_vpAttributes.size());
+    m_data.push_back(row);
+}
+
+void ArffBase::AddColumn(string attName, string type)
+{
+    // remove spaces from type
+    type.erase(remove(type.begin(), type.end(), ' '), type.end());
+
+    m_vpAttributes.push_back(AttributeFactory(attName, type));
+
+    for (auto &row:m_data)
+        row.push_back(0.0);
+}
+
+vector<double>& ArffBase::operator[](const int index)
+{
+    // range checked indexing
+    return m_data.at(index);
+}
+
+void ArffBase::SetRelation(string relation)
+{
+    m_relation = relation;
+}
+
+string ArffBase::GetRelation()
+{
+    return m_relation;
+}
+
+bool ArffBase::GetMetadata(const string& key, string& value)
+{
+    map<string,string>::iterator it = m_mMetadata.find(key);
+    if (it != m_mMetadata.end())
+    {
+        value = it->second;
+        return true;
+    }
+
+    value = "";
+    return false;
+}
+
+bool ArffBase::SetMetadata(const string &key, const string &value)
+{
+    m_mMetadata[key] = value;
+
+    return true;
+}
+
+bool ArffBase::GetAttMapping(const int &attIndex, vector<string>& values)
+{
+    values = m_vpAttributes.at(attIndex)->GetMapping();
+    if (values.size() == 0)
+        return false;
+
+    return true;
+}
+
+bool ArffBase::GetAttMapping(const string &attName, vector<string>& values)
+{
+    int index = 0;
+
+    if (!GetAttIndex(attName, index))
+        return false;
+
+    return GetAttMapping(index, values);
+}
+
+bool ArffBase::GetAttIndex(const string &attName, int &index) const
+{
+    for (index=0; index<(int)m_vpAttributes.size(); index++)
+    {
+        string curAttName = m_vpAttributes[index]->GetName();
+        if (CompareStrings(attName, curAttName))
+            break;
+    }
+
+    if (index == (int)m_vpAttributes.size())
+        return false;
+
+    return true;
+}
+
+void ArffBase::Size(int &rows, int &columns) const
+{
+    rows = m_data.size();
+    columns = m_vpAttributes.size();
+}
+
+arffData::const_iterator ArffBase::cbegin() const
+{
+    return m_data.cbegin();
+}
+
+arffData::const_iterator ArffBase::cend() const
+{
+    return m_data.cend();
+}
+
+arffData::iterator ArffBase::begin()
+{
+    return m_data.begin();
+}
+
+arffData::iterator ArffBase::end()
+{
+    return m_data.end();
+}
+
+// PRIVATE:
+
+void ArffBase::ProcessArffLine(string& line)
+{
+    if (line.size() == 0)
+        return;
+
+    // break string on white space
+    stringstream ss(line);
+    istream_iterator<string> begin(ss);
+    istream_iterator<string> end;
+    vector<string> lineWords(begin, end);
+
+    // find what type of line it is
+    if (m_dataReached)
+        ProcessData(line);
+    else if (lineWords.size() > 0 && CompareStrings(lineWords[0], "@ATTRIBUTE"))
+        ProcessAttribute(lineWords);
+    else if (lineWords.size() > 0 && CompareStrings(lineWords[0], "\%@METADATA"))
+        ProcessMetadata(lineWords);
+    else if (lineWords.size() > 1 && CompareStrings(lineWords[0], "@RELATION"))
+        m_relation = lineWords[1];
+    else if (line[0] == '%')
+        ProcessComment(line);
+    else if (lineWords.size() > 0 && CompareStrings(lineWords[0], "@DATA"))
+        m_dataReached = true;
+
+}
+
+void ArffBase::ProcessMetadata(vector<string>& metaLine)
+{
+    m_mMetadata[metaLine[1]] = metaLine[2];
+}
+
+void ArffBase::ProcessAttribute(vector<string>& attLine)
+{
+    // Attribute with fewer than 3 string parts is not possible
+    if (attLine.size() < 3)
+        return;
+
+    string attName = attLine[1];
+
+    string type;
+    if (attLine[2].find('{') != string::npos)
+    {
+        for (auto s:attLine)
+            type += s;
+    }
+    else
+        type = attLine[2];
+
+    m_vpAttributes.push_back(AttributeFactory(attName, type));
+}
+
+unique_ptr<AttributeType> ArffBase::AttributeFactory(string attName, string type)
+{
+    unique_ptr<AttributeType> res;
+
+    if (CompareStrings(type, "INTEGER"))
+        res.reset(new AttributeTypeInt(attName));
+    else if (CompareStrings(type, "NUMERIC"))
+        res.reset(new AttributeTypeNum(attName));
+    else
+        res.reset(new AttributeTypeNom(attName, type));
+
+    return move(res);
+}
+
+void ArffBase::ProcessComment(string& comLine)
+{
+    m_vComments.push_back(comLine);
+}
+
+void ArffBase::ProcessData(string& dataLine)
+{
+    // remove comments
+    size_t pos = dataLine.find("\%");
+    if (pos != string::npos)
+        dataLine = dataLine.substr(0,pos);
+
+    stringstream ss(dataLine);
+    int id = 0;
+    vector<double> dataEntry;
+    while (ss)
+    {
+        string value;
+        if (!getline(ss, value, ','))
+            break;
+
+        // Convert value based on the attribute
+        double singleEntry = (*m_vpAttributes.at(id))(value);
+        dataEntry.push_back(singleEntry);
+        id++;
+    }
+    if (id == 0)
+        return;
+
+    assert(dataEntry.size() == m_vpAttributes.size()); 
+    m_data.push_back(dataEntry);
+}
+
+bool ArffBase::CompareStrings(string first, string second) const
+{
+    transform(first.begin(), first.end(), first.begin(), ::toupper);
+    transform(second.begin(), second.end(), second.begin(), ::toupper);
+
+    return first == second;
+}

+ 91 - 0
arffHelper/ArffBase.h

@@ -0,0 +1,91 @@
+// ArffBase.h
+
+#ifndef __ARFFBASE_H__
+#define __ARFFBASE_H__
+
+#include "AttributeTypes.h"
+
+#include <memory>
+#include <vector>
+#include <iterator>
+
+using namespace std;
+
+typedef vector<vector<double>> arffData;
+
+class ArffBase
+{
+public:
+    ArffBase();
+
+    ArffBase(const char* filename);
+
+    ~ArffBase();
+
+    bool Load(const char *filename);
+
+    void Save(const char *filename);
+
+    void AddRow(vector<double> row);
+    ///< Adds the provided row at the end of the data
+
+    void AddColumn(string attName, string type);
+    ///< Adds a new column at the end and initializes the new column data to 0. The string type
+    ///< is either "integer", "numeric" or nominal. For nominal attributes provide
+    ///< the possible values within curly braces (ex. "{val1,val2}").
+
+    vector<double>& operator[](const int index);
+    ///< Returns an entry (a row) of the ARFF data.
+
+    void SetRelation(string relation);
+
+    string GetRelation();
+
+    bool GetMetadata(const string& key, string& value);
+
+    bool SetMetadata(const string &key, const string &value);
+    ///< This function returns always true.
+
+    bool GetAttMapping(const int &attIndex, vector<string>& values);
+
+    bool GetAttMapping(const string &attName, vector<string>& values);
+    ///< The values are the strings of the mapping to the doubles
+    ///< in the data.
+
+    bool GetAttIndex(const string &attName, int &index) const;
+
+    void Size(int &rows, int &columns) const;
+    ///< \p rows represents number of data rows in the ARFF file and
+    ///< \p columns represents the number of attributes.
+
+    arffData::const_iterator cbegin() const;
+
+    arffData::const_iterator cend() const;
+
+    arffData::iterator begin();
+
+    arffData::iterator end();
+private:
+    void ProcessArffLine(string& line);
+    void ProcessMetadata(vector<string>& metaLine);
+    void ProcessAttribute(vector<string>& attLine);
+    void ProcessComment(string& comLine);
+    void ProcessData(string& dataLine);
+
+    unique_ptr<AttributeType> AttributeFactory(string attName, string type);
+
+    bool CompareStrings(string first, string second) const;
+    ///< Case insensitive string comparison.
+
+
+    vector<unique_ptr<AttributeType>>   m_vpAttributes;
+    map<string, string>                 m_mMetadata;
+    vector<string>                      m_vComments;
+
+    arffData                            m_data;
+    string                              m_relation;
+
+    bool                                m_dataReached;
+};
+
+#endif /*__ARFFBASE_H__*/

+ 123 - 0
arffHelper/ArffOps.cpp

@@ -0,0 +1,123 @@
+// ArffOps.cpp
+
+#include "ArffOps.h"
+#include "ArffUtil.h"
+
+#include <vector>
+#include <algorithm>
+#include <cassert>
+
+#define UNUSED(x) ((void)(x))
+#define ATT_TYPE "INTEGER"
+#define CONF_THRESHOLD 0.5
+
+using namespace std;
+
+/*static*/ void ArffOps::MajorityVote(Arff *pArff, const char *attributeName, vector<unsigned int> attIds)
+{
+    int rows, columns;
+    pArff->Size(rows, columns);
+
+    // if empty or no attributes provided return
+    if (rows == 0)
+        return;
+
+    // check if attribute ids are within limits
+    for (size_t i=0; i<attIds.size(); i++)
+        assert(attIds[i] < (unsigned int)columns);
+
+    // if no attribute before majority vote, add attribute and return
+    pArff->AddColumn(attributeName, ATT_TYPE);
+
+    // vector to hold the attributes
+    vector<double> attributes;
+
+    int maxLabel, maxCount;
+    int curLabel, curCount;
+
+    int confIndex;
+    bool res = pArff->GetAttIndex("confidence", confIndex);
+    UNUSED(res);
+
+    for (int i=0; i<rows; i++)
+    {
+        attributes.clear();
+        for (size_t j=0; j<attIds.size(); j++)
+            attributes.push_back((*pArff)[i][attIds[j]]);
+        
+        sort(attributes.begin(), attributes.end());
+        // push back 0 in order to get an attribute change
+        attributes.push_back(0);
+
+        maxLabel = 0;
+        maxCount = -1;
+        curLabel = attributes[0];
+        curCount = 0;
+
+        for (size_t j=0; j<attributes.size(); j++)
+        {
+            // attribute = curLabel increase counter
+            if (attributes[j] == curLabel)
+            {
+                curCount++;
+            }
+            else
+            {
+                if (curCount > maxCount && curLabel >= 0){
+                    maxLabel = curLabel;
+                    maxCount = curCount;
+                }
+                curLabel = attributes[j];
+                curCount = 1;
+            }
+        }
+
+        //check if confidence for current sample is above threshold
+        double confThreshold = CONF_THRESHOLD;
+        if ((*pArff)[i][confIndex] > confThreshold)
+        {
+            // set label of newly added column to the mode of attributes if greater than 0
+            pArff->ChangeData(i, columns, maxLabel);
+        }
+        else
+        {
+            // set label to noise -> 4
+            pArff->ChangeData(i, columns, 4);
+        }
+    }
+}
+
+/*static*/ void ArffOps::MajorityVote(Arff *pArff, const char *attributeName)
+{
+    int rows, columns;
+    pArff->Size(rows, columns);
+
+    // if empty return
+    if (rows == 0)
+        return;
+
+    int t,x,y,c;
+    bool res = ArffUtil::GetTXYCindex(pArff, t, x, y, c);
+    UNUSED(res);
+    vector<int> ind = {t,x,y,c}; 
+
+    vector<unsigned int> attIds;
+    for (unsigned int i=0; i<(unsigned int)columns; i++)
+    {
+        vector<string> values;
+        pArff->GetAttMapping(i, values);
+        if (find(ind.begin(), ind.end(), i) == ind.end() && !values.empty())
+            attIds.push_back(i);
+    }
+
+    ArffOps::MajorityVote(pArff, attributeName, attIds);
+}
+
+/*static*/ void ArffOps::MajorityVote(Arff *pArff, const char *attributeName, unsigned int startId, unsigned int endId)
+{
+    vector<unsigned int> attIds;
+    for (size_t i=startId; i<=endId; i++)
+        attIds.push_back(i);
+
+    ArffOps::MajorityVote(pArff, attributeName, attIds);
+}

+ 26 - 0
arffHelper/ArffOps.h

@@ -0,0 +1,26 @@
+// ArffOps.h
+
+#ifndef __ARFFOPS_H__
+#define __ARFFOPS_H__
+
+#include "Arff.h"
+
+#include <vector>
+
+class ArffOps
+{
+public:
+    static void MajorityVote(Arff *pArff, const char *attributeName, vector<unsigned int> attIds);
+    ///< Add a columns at the end with the provided name and populates it
+    ///< based on the majority vote of the provided columns (time, x, y excluded)
+    ///< if they exist. Attribute values of 0 do not contribute in the vote.
+
+    static void MajorityVote(Arff *pArff, const char *attributeName);
+    ///< Overload function. Returns vote considering all columns.
+
+    static void MajorityVote(Arff *pArff, const char *attributeName, unsigned int startId, unsigned int endId);
+    ///< Overload function. Returns vote for given range.
+
+};
+
+#endif /*__ARFFOPS_H__*/

+ 32 - 0
arffHelper/ArffUtil.cpp

@@ -0,0 +1,32 @@
+// ArffUtil.cpp
+
+#include "ArffUtil.h"
+
+#include <algorithm>
+
+/*static*/ unsigned long int ArffUtil::FindPosition(const Arff *pArff, const int &attIndex, const double &value)
+{
+    arffData::const_iterator pos = lower_bound(pArff->cbegin(), pArff->cend(), value, [&attIndex](const vector<double>& a, const long double& b) { return a.at(attIndex) < b;});
+
+    // if the provided time is higher than all times in the data, pos points to
+    // the first element after the vector. Thus outside of its range. It is
+    // changed to last valid point
+    unsigned int res = pos - pArff->cbegin();
+    
+    if (pos == pArff->cend())
+        return res-1;
+    else
+        return res;
+}
+
+/*static*/ bool ArffUtil::GetTXYCindex(const Arff *pArff, int &timeInd, int &xInd, int &yInd, int &confInd)
+{
+    bool res = true;
+
+    res &= pArff->GetAttIndex("time", timeInd);
+    res &= pArff->GetAttIndex("x", xInd);
+    res &= pArff->GetAttIndex("y", yInd);
+    res &= pArff->GetAttIndex("confidence", confInd);
+
+    return res;
+}

+ 19 - 0
arffHelper/ArffUtil.h

@@ -0,0 +1,19 @@
+// ArffUtil.h
+
+#ifndef __ARFFUTIL_H__
+#define __ARFFUTIL_H__
+
+#include "Arff.h"
+
+class ArffUtil
+{
+public:
+    static unsigned long int FindPosition(const Arff *pArff, const int &attIndex, const double &value);
+    ///< This function searches in a sorted column of the Arff class and returns
+    ///< the position of the first element that is bigger than the provided value.
+
+    static bool GetTXYCindex(const Arff *pArff, int &timeInd, int &xInd, int &yInd, int &confInd);
+    ///< This is a helper function to get time, x, y, confidence indices in Arff data.
+};
+
+#endif /*__ARFFUTIL_H__*/

+ 144 - 0
arffHelper/AttributeTypes.cpp

@@ -0,0 +1,144 @@
+// AttributeTypes.cpp
+
+#include "AttributeTypes.h"
+
+#include <iomanip>
+#include <sstream>
+#include <algorithm>
+#include <clocale>
+
+using namespace std;
+
+// CLASS ATTRIBUTE_TYPE
+AttributeType::~AttributeType()
+{
+}
+
+void AttributeType::PrintDescription(ofstream &ofs)
+{
+    ofs << "@ATTRIBUTE " << m_attName << " ";
+}
+
+vector<string> AttributeType::GetMapping()
+{
+    return vector<string>(0);
+}
+
+string AttributeType::GetName()
+{
+    return m_attName;
+}
+
+// CLASS ATTRIBUTE_TYPE_INT
+AttributeTypeInt::AttributeTypeInt(string attName) : AttributeType(attName)
+{
+}
+
+AttributeTypeInt::~AttributeTypeInt()
+{
+}
+
+double AttributeTypeInt::operator()(string attValue)
+{
+    return stoi(attValue);
+}
+
+void AttributeTypeInt::Print(ofstream& ofs, double const &attValue)
+{
+    ofs << (int) attValue;
+}
+
+void AttributeTypeInt::PrintDescription(ofstream& ofs)
+{
+    AttributeType::PrintDescription(ofs);
+    ofs << "INTEGER\n";
+}
+
+// CLASS ATTRIBUTE_TYPE_NUM
+AttributeTypeNum::AttributeTypeNum(string attName, int precision) : AttributeType(attName), m_precision(precision)
+{
+    // *** Use the following locale in order to get "." as decimal-point separator
+    std::setlocale(LC_NUMERIC, "C");
+}
+
+AttributeTypeNum::~AttributeTypeNum()
+{
+}
+
+double AttributeTypeNum::operator()(string attValue)
+{
+    return stod(attValue);
+}
+
+void AttributeTypeNum::Print(ofstream& ofs, double const &attValue)
+{
+    ofs << fixed << setprecision(m_precision) << attValue;
+}
+
+void AttributeTypeNum::PrintDescription(ofstream& ofs)
+{
+    AttributeType::PrintDescription(ofs);
+    ofs << "NUMERIC\n";
+}
+
+// CLASS ATTRIBUTE_TYPE_NOM
+AttributeTypeNom::AttributeTypeNom(string attName, string attDescription) : AttributeType(attName)
+{
+    // find content between curly braces
+    size_t start = attDescription.find("{");
+    size_t end = attDescription.find("}");
+
+    if (start == string::npos || end == string::npos)
+        throw range_error("Could not find one or both curly braces");
+
+    attDescription = attDescription.substr(start+1, end-start-1);
+    
+    stringstream ss(attDescription);
+    int id = 0;
+    while (ss)
+    {
+        string value;
+        if (!getline(ss, value, ','))
+            break;
+        m_nominalMap[value] = id;
+        id++;
+    }
+
+    //create inverse map
+    for (auto p: m_nominalMap)
+        m_inversedMap[p.second] = p.first;
+}
+
+AttributeTypeNom::~AttributeTypeNom()
+{
+}
+
+double AttributeTypeNom::operator()(string attValue)
+{
+    return m_nominalMap[attValue];
+}
+
+void AttributeTypeNom::Print(ofstream& ofs, double const &attValue)
+{
+    ofs << m_inversedMap[attValue];
+}
+
+void AttributeTypeNom::PrintDescription(ofstream& ofs)
+{
+    AttributeType::PrintDescription(ofs);
+    ofs << "{";
+    std::map<string,double>::iterator it;
+    unsigned int i;
+    for (i=0; i<m_inversedMap.size()-1; i++)
+        ofs << m_inversedMap[i] << ',';
+    ofs << m_inversedMap[i] <<  "}" << endl;
+}
+
+vector<string> AttributeTypeNom::GetMapping()
+{
+    vector<string> res;
+    for (unsigned int i=0; i<m_inversedMap.size(); i++)
+        res.push_back(m_inversedMap[i]);
+
+    return res;
+}

+ 101 - 0
arffHelper/AttributeTypes.h

@@ -0,0 +1,101 @@
+// AttributeTypes.h
+
+#ifndef __ATTRIBUTETYPES_H__
+#define __ATTRIBUTETYPES_H__
+
+#include <string>
+#include <ostream>
+#include <fstream>
+#include <map>
+#include <vector>
+
+using namespace std;
+
+class AttributeType
+{
+public:
+    AttributeType(string attName) : m_attName(attName) {}
+    
+    virtual ~AttributeType() = 0;
+
+    virtual double operator()(string attValue) = 0;
+    ///< Functor to convert the string values of the attribute in the file
+    ///< to double representation in the data.
+
+    virtual void Print(ofstream& ofs, double const &attValue) = 0;
+    ///< Writes data correctly formatted to the provided output file stream.
+
+    virtual void PrintDescription(ofstream &ofs);
+    ///< Writes the description of the attributes as it should be in the ARFF file.
+
+    virtual vector<string> GetMapping();
+    ///< Returns the mapping of the attribute values to the one
+    ///< provided in the definition of the attribute.
+
+    string GetName();
+
+private:
+    string m_attName;
+};
+
+// Class representing Integer attribute in ARFF
+class AttributeTypeInt : public AttributeType
+{
+public:
+    AttributeTypeInt(string attName);
+    
+    ~AttributeTypeInt();
+
+    double operator()(string attValue);
+    ///< The returned double can correctly represent an integer up the
+    ///< range of its fraction term with 0 exponent.
+
+    void Print(ofstream& ofs, double const &attValue);
+    
+    void PrintDescription(ofstream &ofs);
+};
+
+// Class representing Numeric attribute in ARFF
+class AttributeTypeNum : public AttributeType
+{
+public:
+    AttributeTypeNum(string attName, int precision = 2);
+    
+    ~AttributeTypeNum();
+
+    double operator()(string attValue);
+    ///< Just return a double.
+
+    void Print(ofstream& ofs, double const &attValue);
+
+    void PrintDescription(ofstream &ofs);
+private:
+    int m_precision;
+};
+
+// Class representing Nominal attribute in ARFF
+class AttributeTypeNom : public AttributeType
+{
+public:
+    AttributeTypeNom(string attName, string attDescription);
+    ///< The input string is the line describing the attribute. It processes 
+    ///< the content within the curly braces.
+    
+    ~AttributeTypeNom();
+
+    double operator()(string attValue);
+    ///< Returns the position of the string the list of nominal values of teh attribute.
+    ///< It works quivalently to an enumeration.
+
+    void Print(ofstream& ofs, double const &attValue);
+
+    void PrintDescription(ofstream &ofs);
+
+    vector<string> GetMapping();
+    ///< Returns the mapping of nominal values to double.
+private:
+    map<string,double> m_nominalMap;
+    map<double,string> m_inversedMap;
+};
+
+#endif /*__ATTRIBUTETYPES_H__*/