Skip to main content

Overview

The ITracker interface is the base class for all tracker plugins. Trackers are responsible for capturing head pose data from various sources including cameras, hardware devices, and network sources.

Interface Definition

struct ITracker
{
    ITracker();
    virtual ~ITracker();
    
    // Core methods (must implement)
    virtual module_status start_tracker(QFrame* frame) = 0;
    virtual void data(double *data) = 0;
    
    // Optional methods
    virtual bool center();
    
    // Helper methods
    static module_status status_ok();
    static module_status error(const QString& error);
    
    // Deleted methods (non-copyable)
    ITracker(const ITracker&) = delete;
    ITracker& operator=(const ITracker&) = delete;
};

Methods

start_tracker()

virtual module_status start_tracker(QFrame* frame) = 0;
Initializes the tracker and optionally sets up video preview display.
frame
QFrame*
Qt frame widget for displaying video preview. Can be nullptr if no preview needed.
return
module_status
Returns status_ok() on success, or error(message) on failure.
Description:
  • Called once when tracking is started
  • Initialize hardware, open cameras, start threads
  • Set up video preview widget if frame is provided
  • Return error status if initialization fails
Example implementation:
module_status Tracker::start_tracker(QFrame* video_frame)
{
    // Validate configuration
    if (iSolver != cv::SOLVEPNP_P3P && 
        iSolver != cv::SOLVEPNP_AP3P && 
        iModel.size() == 3)
    {
        return error("Solver not supported, use P3P or AP3P");
    }

    // Create camera
    camera = video::make_camera(iSettings.camera_name);
    if (!camera)
        return error(QStringLiteral("Can't open camera %1")
                     .arg(iSettings.camera_name));

    // Set up video preview widget
    widget = std::make_unique<video_widget>(video_frame);
    layout = std::make_unique<QHBoxLayout>(video_frame);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->addWidget(&*widget);
    video_frame->setLayout(&*layout);
    video_frame->show();

    // Start processing thread
    iTicker.setTimerType(Qt::PreciseTimer);
    SetFps(iSettings.cam_fps);
    iTicker.moveToThread(&iThread);
    connect(&iTicker, SIGNAL(timeout()), SLOT(Tick()), 
            Qt::DirectConnection);
    iTicker.connect(&iThread, SIGNAL(started()), SLOT(start()));
    
    iFpsTimer.start();
    iThread.setObjectName("EasyTrackerThread");
    iThread.setPriority(QThread::HighPriority);
    iThread.start();

    return status_ok();
}

data()

virtual void data(double *data) = 0;
Provides current head pose data to the tracking pipeline.
data
double*
Output array of 6 doubles: [TX, TY, TZ, Yaw, Pitch, Roll]. Must be filled by implementation.
This method is called approximately 250 times per second. Keep it fast and non-blocking.
Description:
  • Called at ~250Hz from the tracking pipeline thread
  • Fill the data array with current pose
  • Use mutex protection for thread-safe access
  • Don’t perform heavy computation here
  • Use a background thread for processing
Coordinate system:
  • TX (data[0]): X translation in centimeters (left: negative, right: positive)
  • TY (data[1]): Y translation in centimeters (down: negative, up: positive)
  • TZ (data[2]): Z translation in centimeters (backward: negative, forward: positive)
  • Yaw (data[3]): Rotation around Y axis in degrees (left: negative, right: positive)
  • Pitch (data[4]): Rotation around X axis in degrees (down: negative, up: positive)
  • Roll (data[5]): Rotation around Z axis in degrees (left: negative, right: positive)
Example implementation:
void Tracker::data(double* aData)
{
    if (ever_success.load(std::memory_order_relaxed))
    {
        // Thread-safe data access
        QMutexLocker l(&iDataLock);
        
        // Auto-center if no recent tracking
        if (iSettings.iAutoCenter && 
            iBestTime.elapsed_ms() > iSettings.iAutoCenterTimeout)
        {
            // Reset to center
            FeedData(aData, iCenterAngles, iCenterTranslation);
        }
        else
        {
            // Provide current tracking data
            FeedData(aData, iBestAngles, iBestTranslation);
        }
    }
}

// Helper function to populate data array
void FeedData(double* aData, const cv::Vec3d& aAngles, 
              const cv::Vec3d& aTranslation)
{
    aData[Yaw] = aAngles[1];
    aData[Pitch] = aAngles[0];
    aData[Roll] = aAngles[2];
    aData[TX] = aTranslation[0];
    aData[TY] = aTranslation[1];
    aData[TZ] = aTranslation[2];
}

center()

virtual bool center();
Called when the user requests to center/reset tracking.
return
bool
  • true: Use current pose as center (identity transform)
  • false: Use default center behavior (current pose as offset)
Description:
  • Called from UI thread when user presses center hotkey
  • Store current pose as center reference
  • Return false to use default centering (recommended)
  • Return true to make identity the center pose
Example implementation:
bool Tracker::center()
{
    QMutexLocker l(&iDataLock);
    
    // Store current pose as center offset
    iCenterTranslation = iBestTranslation;
    iCenterAngles = iBestAngles;
    
    // Use default center behavior
    return false;
}

status_ok() and error()

static module_status status_ok();
static module_status error(const QString& error);
Helper methods to create module status objects.
error
const QString&
Error message to display to user
Example:
if (!device->open())
    return error("Failed to open device");

return status_ok();

Dialog Interface

struct ITrackerDialog : public BaseDialog
{
    virtual void register_tracker(ITracker *tracker);
    virtual void unregister_tracker();
};

register_tracker()

virtual void register_tracker(ITracker *tracker);
Receives a pointer to the active tracker instance.
tracker
ITracker*
Pointer to the running tracker instance. Can be nullptr.
Description:
  • Called from UI thread when tracker starts
  • Store pointer for runtime interaction (if needed)
  • Can query tracker state or update preview
  • Default implementation does nothing
Example:
void Dialog::register_tracker(ITracker *tracker)
{
    this->tracker = static_cast<Tracker*>(tracker);
    // Can now interact with tracker if needed
}

unregister_tracker()

virtual void unregister_tracker();
Called when tracker is about to be destroyed. Description:
  • Clear any stored tracker pointers
  • Stop accessing tracker data
  • Default implementation does nothing
Example:
void Dialog::unregister_tracker()
{
    tracker = nullptr;
}

Complete Example

#pragma once
#include "api/plugin-api.hpp"
#include <QMutex>
#include <QThread>

class MyTracker : public QObject, ITracker
{
    Q_OBJECT
public:
    MyTracker();
    ~MyTracker() override;
    
    module_status start_tracker(QFrame* frame) override;
    void data(double* data) override;
    bool center() override;
    
private slots:
    void process_frame();
    
private:
    QThread worker_thread;
    QMutex data_mutex;
    
    double current_pose[6] = {};
    double center_pose[6] = {};
    bool tracking_active = false;
};

class MyTrackerDialog : public ITrackerDialog
{
    Q_OBJECT
public:
    MyTrackerDialog();
    void register_tracker(ITracker* t) override;
    void unregister_tracker() override;
};

class MyTrackerMetadata : public Metadata
{
    Q_OBJECT
    QString name() override { return tr("My Tracker"); }
    QIcon icon() override { return QIcon(":/images/icon.png"); }
};

Threading Best Practices

class Tracker : public QObject, ITracker
{
    QThread processing_thread;
    QTimer frame_timer;
    
    void start_tracker(QFrame* frame) override
    {
        frame_timer.moveToThread(&processing_thread);
        connect(&frame_timer, SIGNAL(timeout()), 
                SLOT(process()), Qt::DirectConnection);
        processing_thread.start();
    }
};
class Tracker : ITracker
{
    QMutex pose_mutex;
    double pose_data[6];
    
    void data(double* data) override
    {
        QMutexLocker lock(&pose_mutex);
        memcpy(data, pose_data, sizeof(pose_data));
    }
    
    void update_pose(const double* new_pose)
    {
        QMutexLocker lock(&pose_mutex);
        memcpy(pose_data, new_pose, sizeof(pose_data));
    }
};
class Tracker : ITracker
{
    std::atomic<bool> tracking_successful{false};
    
    void processing_thread()
    {
        if (compute_pose_succeeded)
            tracking_successful.store(true, 
                std::memory_order_relaxed);
    }
    
    void data(double* data) override
    {
        if (tracking_successful.load(std::memory_order_relaxed))
        {
            // Provide pose data
        }
    }
};

See Also