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.
Qt frame widget for displaying video preview. Can be nullptr if no preview needed.
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:
tracker-easy/tracker-easy.cpp
protocol-ft/ftnoir_protocol_ft.cpp
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.
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:
tracker-easy/tracker-easy.cpp
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()
Called when the user requests to center/reset tracking.
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 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.
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
my-tracker.h
my-tracker.cpp
#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
Use Background Threads for Processing
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 ();
}
};
Protect Shared Data with Mutexes
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));
}
};
Use Atomic Flags for Simple State
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