summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorStéphane Lenclud <github@lenclud.com>2019-03-27 21:18:39 +0100
committerStéphane Lenclud <github@lenclud.com>2019-04-24 18:46:12 +0200
commitb0e22e955d566b2a981cb43ae477f2646c0c978d (patch)
treec895caf17f673ae8c45ce9984400bbb0e712ec0c
parent4017bc6534c1c52ad950589d758a3ff30d78f280 (diff)
Create Points Tracker based on original Point Tracker.
-rw-r--r--tracker-points/CMakeLists.txt8
-rw-r--r--tracker-points/FTNoIR_PT_Controls.ui1430
-rw-r--r--tracker-points/Resources/Logo_IR.pngbin0 -> 10386 bytes
-rw-r--r--tracker-points/Resources/cap_front.pngbin0 -> 1164 bytes
-rw-r--r--tracker-points/Resources/cap_side.pngbin0 -> 1733 bytes
-rw-r--r--tracker-points/Resources/clip_front.pngbin0 -> 571 bytes
-rw-r--r--tracker-points/Resources/clip_side.pngbin0 -> 2677 bytes
-rw-r--r--tracker-points/doc/index.htm262
-rw-r--r--tracker-points/doc/logo.pngbin0 -> 10386 bytes
-rw-r--r--tracker-points/doc/ptrack.icobin0 -> 4286 bytes
-rw-r--r--tracker-points/doc/settings1.pngbin0 -> 25013 bytes
-rw-r--r--tracker-points/doc/settings2.pngbin0 -> 26841 bytes
-rw-r--r--tracker-points/doc/settings3.pngbin0 -> 29547 bytes
-rw-r--r--tracker-points/doc/style.css131
-rw-r--r--tracker-points/ftnoir_tracker_pt.cpp229
-rw-r--r--tracker-points/ftnoir_tracker_pt.h81
-rw-r--r--tracker-points/ftnoir_tracker_pt_dialog.cpp282
-rw-r--r--tracker-points/ftnoir_tracker_pt_dialog.h55
-rw-r--r--tracker-points/lang/nl_NL.ts290
-rw-r--r--tracker-points/lang/ru_RU.ts295
-rw-r--r--tracker-points/lang/stub.ts290
-rw-r--r--tracker-points/lang/zh_CN.ts290
-rw-r--r--tracker-points/module/CMakeLists.txt6
-rw-r--r--tracker-points/module/Resources/Logo_IR.pngbin0 -> 10386 bytes
-rw-r--r--tracker-points/module/camera.cpp152
-rw-r--r--tracker-points/module/camera.h55
-rw-r--r--tracker-points/module/export.hpp11
-rw-r--r--tracker-points/module/frame.cpp80
-rw-r--r--tracker-points/module/frame.hpp44
-rw-r--r--tracker-points/module/lang/nl_NL.ts11
-rw-r--r--tracker-points/module/lang/ru_RU.ts11
-rw-r--r--tracker-points/module/lang/stub.ts11
-rw-r--r--tracker-points/module/lang/zh_CN.ts11
-rw-r--r--tracker-points/module/module.cpp72
-rw-r--r--tracker-points/module/module.hpp20
-rw-r--r--tracker-points/module/point_extractor.cpp387
-rw-r--r--tracker-points/module/point_extractor.h58
-rw-r--r--tracker-points/module/tracker_pt.qrc5
-rw-r--r--tracker-points/point_tracker.cpp364
-rw-r--r--tracker-points/point_tracker.h88
-rw-r--r--tracker-points/pt-api.cpp54
-rw-r--r--tracker-points/pt-api.hpp123
-rw-r--r--tracker-points/pt-settings.hpp73
-rw-r--r--tracker-points/tracker_pt_base.qrc8
44 files changed, 5287 insertions, 0 deletions
diff --git a/tracker-points/CMakeLists.txt b/tracker-points/CMakeLists.txt
new file mode 100644
index 00000000..b9fcca9e
--- /dev/null
+++ b/tracker-points/CMakeLists.txt
@@ -0,0 +1,8 @@
+find_package(OpenCV QUIET)
+if(OpenCV_FOUND)
+ otr_module(tracker-points-base STATIC)
+ target_include_directories(${self} SYSTEM PUBLIC ${OpenCV_INCLUDE_DIRS})
+ target_link_libraries(${self} opencv_imgproc opentrack-cv opencv_core opentrack-video)
+ #set_property(TARGET ${self} PROPERTY OUTPUT_NAME "points-base")
+endif()
+add_subdirectory(module)
diff --git a/tracker-points/FTNoIR_PT_Controls.ui b/tracker-points/FTNoIR_PT_Controls.ui
new file mode 100644
index 00000000..061f5351
--- /dev/null
+++ b/tracker-points/FTNoIR_PT_Controls.ui
@@ -0,0 +1,1430 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UICPTClientControls</class>
+ <widget class="QWidget" name="UICPTClientControls">
+ <property name="windowModality">
+ <enum>Qt::NonModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>418</width>
+ <height>724</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>PointTracker Settings</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="module/tracker_pt.qrc">
+ <normaloff>:/Resources/Logo_IR.png</normaloff>:/Resources/Logo_IR.png</iconset>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="autoFillBackground">
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_9">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetFixedSize</enum>
+ </property>
+ <item row="1" column="0" alignment="Qt::AlignVCenter">
+ <widget class="QGroupBox" name="groupBox_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Status</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_10">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Extracted Points:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_38">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Camera Info:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="pointinfo_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="caminfo_label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="locale">
+ <locale language="English" country="UnitedStates"/>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>Camera</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="camera_settings_groupbox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Camera settings</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="1">
+ <widget class="QComboBox" name="camdevice_combo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumContentsLength">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QSpinBox" name="fov">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="suffix">
+ <string>°</string>
+ </property>
+ <property name="prefix">
+ <string/>
+ </property>
+ <property name="minimum">
+ <number>10</number>
+ </property>
+ <property name="maximum">
+ <number>90</number>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Diagonal field of view</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_36">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Width</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_37">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>FPS</string>
+ </property>
+ <property name="buddy">
+ <cstring>fps_spin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSpinBox" name="res_y_spin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Desired capture height</string>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ <property name="maximum">
+ <number>2000</number>
+ </property>
+ <property name="singleStep">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="label_6">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Dynamic pose timeout</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QSpinBox" name="fps_spin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Desired capture framerate</string>
+ </property>
+ <property name="suffix">
+ <string> Hz</string>
+ </property>
+ <property name="maximum">
+ <number>2000</number>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QCheckBox" name="dynamic_pose">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="res_x_spin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Desired capture width</string>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ <property name="maximum">
+ <number>2000</number>
+ </property>
+ <property name="singleStep">
+ <number>10</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_41">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Height</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QSpinBox" name="init_phase_timeout">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="suffix">
+ <string> ms</string>
+ </property>
+ <property name="minimum">
+ <number>50</number>
+ </property>
+ <property name="maximum">
+ <number>5000</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Device</string>
+ </property>
+ <property name="buddy">
+ <cstring>camdevice_combo</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="1">
+ <widget class="QPushButton" name="camera_settings">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Open</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Camera settings (when available)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="0">
+ <widget class="QLabel" name="label_12">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Color channels used</string>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="1">
+ <widget class="QComboBox" name="blob_color">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>Average</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Natural</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Red only</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Green only</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Blue only</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Dynamic pose (for caps only, never clips)</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Point extraction</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_7">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Threshold</string>
+ </property>
+ <property name="buddy">
+ <cstring>threshold_slider</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Min size</string>
+ </property>
+ <property name="buddy">
+ <cstring>mindiam_spin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Max size</string>
+ </property>
+ <property name="buddy">
+ <cstring>maxdiam_spin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" colspan="2">
+ <widget class="QSlider" name="threshold_slider">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Intensity threshold for point extraction</string>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>127</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksBothSides</enum>
+ </property>
+ <property name="tickInterval">
+ <number>25</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QCheckBox" name="auto_threshold">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Enable, slider sets point size</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_11">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Automatic threshold</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QDoubleSpinBox" name="maxdiam_spin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Maximum point diameter</string>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ <property name="decimals">
+ <number>1</number>
+ </property>
+ <property name="singleStep">
+ <double>0.100000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QDoubleSpinBox" name="mindiam_spin">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Minimum point diameter</string>
+ </property>
+ <property name="suffix">
+ <string> px</string>
+ </property>
+ <property name="decimals">
+ <number>1</number>
+ </property>
+ <property name="singleStep">
+ <double>0.100000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="threshold_value_display">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_14">
+ <property name="text">
+ <string>Value</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_4">
+ <attribute name="title">
+ <string>Model</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QTabWidget" name="model_tabs">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="tabShape">
+ <enum>QTabWidget::Rounded</enum>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <property name="usesScrollButtons">
+ <bool>false</bool>
+ </property>
+ <property name="documentMode">
+ <bool>false</bool>
+ </property>
+ <property name="tabsClosable">
+ <bool>false</bool>
+ </property>
+ <widget class="QWidget" name="tab_5">
+ <attribute name="title">
+ <string>Clip</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_6">
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="groupBox_8">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>331</width>
+ <height>208</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Model Dimensions</string>
+ </property>
+ <widget class="QSpinBox" name="clip_tlength_spin">
+ <property name="geometry">
+ <rect>
+ <x>70</x>
+ <y>35</y>
+ <width>100</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ <widget class="QSpinBox" name="clip_bheight_spin">
+ <property name="geometry">
+ <rect>
+ <x>150</x>
+ <y>130</y>
+ <width>100</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_44">
+ <property name="geometry">
+ <rect>
+ <x>65</x>
+ <y>55</y>
+ <width>71</width>
+ <height>111</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="tracker_pt_base.qrc">:/Resources/clip_side.png</pixmap>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_50">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>40</y>
+ <width>46</width>
+ <height>13</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Side</string>
+ </property>
+ </widget>
+ <widget class="QSpinBox" name="clip_blength_spin">
+ <property name="geometry">
+ <rect>
+ <x>50</x>
+ <y>160</y>
+ <width>100</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ <widget class="QSpinBox" name="clip_theight_spin">
+ <property name="geometry">
+ <rect>
+ <x>150</x>
+ <y>70</y>
+ <width>100</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_51">
+ <property name="geometry">
+ <rect>
+ <x>290</x>
+ <y>40</y>
+ <width>46</width>
+ <height>13</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Front</string>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_45">
+ <property name="geometry">
+ <rect>
+ <x>300</x>
+ <y>70</y>
+ <width>21</width>
+ <height>111</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="tracker_pt_base.qrc">:/Resources/clip_front.png</pixmap>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_6">
+ <attribute name="title">
+ <string>Cap</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_14">
+ <item>
+ <widget class="QGroupBox" name="groupBox_9">
+ <property name="minimumSize">
+ <size>
+ <width>331</width>
+ <height>208</height>
+ </size>
+ </property>
+ <property name="title">
+ <string>Model Dimensions</string>
+ </property>
+ <widget class="QLabel" name="label_46">
+ <property name="geometry">
+ <rect>
+ <x>100</x>
+ <y>60</y>
+ <width>111</width>
+ <height>81</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="tracker_pt_base.qrc">:/Resources/cap_side.png</pixmap>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_48">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>40</y>
+ <width>46</width>
+ <height>13</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Side</string>
+ </property>
+ </widget>
+ <widget class="QSpinBox" name="cap_length_spin">
+ <property name="geometry">
+ <rect>
+ <x>90</x>
+ <y>40</y>
+ <width>101</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_47">
+ <property name="geometry">
+ <rect>
+ <x>220</x>
+ <y>100</y>
+ <width>81</width>
+ <height>81</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="tracker_pt_base.qrc">:/Resources/cap_front.png</pixmap>
+ </property>
+ </widget>
+ <widget class="QSpinBox" name="cap_width_spin">
+ <property name="geometry">
+ <rect>
+ <x>240</x>
+ <y>70</y>
+ <width>81</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label_49">
+ <property name="geometry">
+ <rect>
+ <x>240</x>
+ <y>40</y>
+ <width>46</width>
+ <height>13</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Front</string>
+ </property>
+ </widget>
+ <widget class="QSpinBox" name="cap_height_spin">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>90</y>
+ <width>81</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_7">
+ <attribute name="title">
+ <string>Custom</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="groupBox_7">
+ <property name="title">
+ <string>Model Dimensions</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="3" column="1">
+ <widget class="QLabel" name="label_57">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>z:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="5">
+ <widget class="QSpinBox" name="m2y_spin">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="label_63">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>x:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QSpinBox" name="m1x_spin">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QSpinBox" name="m1y_spin">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="5">
+ <widget class="QSpinBox" name="m2z_spin">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QSpinBox" name="m2x_spin">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="6">
+ <widget class="QLabel" name="label_56">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Location of the two remaining model points&lt;br/&gt;with respect to the reference point in default pose&lt;/p&gt;&lt;p&gt;Use any units you want, not necessarily centimeters.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QSpinBox" name="m1z_spin">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65535</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="4">
+ <widget class="QLabel" name="label_70">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>y:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QLabel" name="label_67">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>x:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QLabel" name="label_64">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;3&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_60">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;2&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="4">
+ <widget class="QLabel" name="label_69">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>z:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="label_58">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>y:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QGroupBox" name="groupBox_10">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Model position</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="0">
+ <widget class="QFrame" name="frame_2">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_11">
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="ty_spin">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65536</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_66">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>z:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSpinBox" name="tz_spin">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65536</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_61">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>x:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="tx_spin">
+ <property name="suffix">
+ <string> mm</string>
+ </property>
+ <property name="minimum">
+ <number>-65535</number>
+ </property>
+ <property name="maximum">
+ <number>65536</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_62">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>y:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QFrame" name="frame">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_59">
+ <property name="text">
+ <string>Use only yaw and pitch while calibrating.
+Don't roll or change position.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="sample_count_display">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="tcalib_button">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Start calibration</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_3">
+ <attribute name="title">
+ <string>About</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_8">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_10">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;FTNoIR PointTracker Plugin&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;by Patrick Ruoff&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://ftnoirpt.sourceforge.net/&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#0000ff;&quot;&gt;Manual (external)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label_35">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="module/tracker_pt.qrc">:/Resources/Logo_IR.png</pixmap>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>tabWidget</tabstop>
+ <tabstop>camdevice_combo</tabstop>
+ <tabstop>res_x_spin</tabstop>
+ <tabstop>res_y_spin</tabstop>
+ <tabstop>fps_spin</tabstop>
+ <tabstop>fov</tabstop>
+ <tabstop>dynamic_pose</tabstop>
+ <tabstop>init_phase_timeout</tabstop>
+ <tabstop>camera_settings</tabstop>
+ <tabstop>blob_color</tabstop>
+ <tabstop>auto_threshold</tabstop>
+ <tabstop>threshold_slider</tabstop>
+ <tabstop>mindiam_spin</tabstop>
+ <tabstop>maxdiam_spin</tabstop>
+ <tabstop>model_tabs</tabstop>
+ <tabstop>clip_tlength_spin</tabstop>
+ <tabstop>clip_theight_spin</tabstop>
+ <tabstop>clip_bheight_spin</tabstop>
+ <tabstop>clip_blength_spin</tabstop>
+ <tabstop>cap_length_spin</tabstop>
+ <tabstop>cap_height_spin</tabstop>
+ <tabstop>cap_width_spin</tabstop>
+ <tabstop>m1x_spin</tabstop>
+ <tabstop>m1y_spin</tabstop>
+ <tabstop>m1z_spin</tabstop>
+ <tabstop>m2x_spin</tabstop>
+ <tabstop>m2y_spin</tabstop>
+ <tabstop>m2z_spin</tabstop>
+ <tabstop>tx_spin</tabstop>
+ <tabstop>ty_spin</tabstop>
+ <tabstop>tz_spin</tabstop>
+ <tabstop>tcalib_button</tabstop>
+ </tabstops>
+ <resources>
+ <include location="module/tracker_pt.qrc"/>
+ <include location="module/tracker_pt.qrc"/>
+ <include location="module/tracker_pt.qrc"/>
+ <include location="module/tracker_pt.qrc"/>
+ <include location="module/tracker_pt.qrc"/>
+ <include location="tracker_pt_base.qrc"/>
+ <include location="tracker_pt_base.qrc"/>
+ <include location="tracker_pt_base.qrc"/>
+ <include location="tracker_pt_base.qrc"/>
+ <include location="tracker_pt_base.qrc"/>
+ </resources>
+ <connections/>
+ <slots>
+ <slot>startEngineClicked()</slot>
+ <slot>stopEngineClicked()</slot>
+ <slot>cameraSettingsClicked()</slot>
+ </slots>
+</ui>
diff --git a/tracker-points/Resources/Logo_IR.png b/tracker-points/Resources/Logo_IR.png
new file mode 100644
index 00000000..95032a25
--- /dev/null
+++ b/tracker-points/Resources/Logo_IR.png
Binary files differ
diff --git a/tracker-points/Resources/cap_front.png b/tracker-points/Resources/cap_front.png
new file mode 100644
index 00000000..14207a67
--- /dev/null
+++ b/tracker-points/Resources/cap_front.png
Binary files differ
diff --git a/tracker-points/Resources/cap_side.png b/tracker-points/Resources/cap_side.png
new file mode 100644
index 00000000..5ad4ee65
--- /dev/null
+++ b/tracker-points/Resources/cap_side.png
Binary files differ
diff --git a/tracker-points/Resources/clip_front.png b/tracker-points/Resources/clip_front.png
new file mode 100644
index 00000000..04880138
--- /dev/null
+++ b/tracker-points/Resources/clip_front.png
Binary files differ
diff --git a/tracker-points/Resources/clip_side.png b/tracker-points/Resources/clip_side.png
new file mode 100644
index 00000000..72667ac7
--- /dev/null
+++ b/tracker-points/Resources/clip_side.png
Binary files differ
diff --git a/tracker-points/doc/index.htm b/tracker-points/doc/index.htm
new file mode 100644
index 00000000..87b7356f
--- /dev/null
+++ b/tracker-points/doc/index.htm
@@ -0,0 +1,262 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+ <title>FTNoIR PointTracker Help</title>
+
+ <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+
+ <meta name="author"
+ content="Patrick Ruoff (C14)"/>
+ <meta name="keywords"
+ content="facetracknoir infrared point model tracker plugin"/>
+ <meta name="description"
+ content="Pointtracker plugin for FaceTrackNoIR"/>
+
+ <link rel="shortcut icon" href="ptrack.ico" type="image/vnd.microsoft.icon" />
+ <link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+<div id="navbar">
+<ul class="navbar">
+<li class="navbar"><a class="navbar" href="#about">About</a></li>
+<li class="navbar"><a class="navbar" href="#settings">Settings</a></li>
+<li class="navbar"><a class="navbar" href="#setup">Filter Setup</a></li>
+<li class="navbar"><a class="navbar" href="#support">Support</a></li>
+<li class="navbar"><a class="navbar" href="#changelog">ChangeLog</a></li>
+<li class="navbar"><a class="navbar" href="#build_instructions">Build Instructions</a></li>
+</ul>
+</div>
+
+<div id="content">
+<div style="text-align:center"><h1>FaceTrackNoIR PointTracker Plugin</h1><img src="logo.png" alt="PointTracker Plugin Logo" /></div>
+
+<a class="nav" id="about"></a>
+<h2>About</h2>
+<div class="indent">
+<p>
+PointTracker is a plugin for the free head tracking software <a href="http://facetracknoir.sourceforge.net">FaceTrackNoIR</a>
+which introduces the capability to track a (typically IR-) point model comprising 3 bright points to FaceTrackNoIR,
+much like the popular free tracking software <a href="http://www.free-track.net/">Freetrack</a> does.<br/>
+It was created as a stable modular alternative to Freetrack, which has some stability issues with newer systems and seems to be no longer actively developped.
+</p>
+</div>
+
+<a class="nav" id="settings"></a>
+<h2>Settings</h2>
+<div class="indent">
+<p>
+This section desribes the various settings of the PointTracker plugin in detail.
+</p>
+
+<img src="settings1.png" alt="Settings Pane 1"/>
+<dl>
+<dt>Show VideoWidget</dt><dd>Whether the video widget is updated or not. It may save some performance to turn this off when not needed</dd>
+<dt>Sleep time</dt><dd>Time the tracking thread sleeps after each processed image. It's inverse should be below the framefrate you want to achieve.
+(check the framerate in the status region when tracker is active, in case the sleep time is too high, the framerate will decrease).
+Low values will result in more CPU-load.</dd>
+<dt>Dynamic Pose Resolution</dt><dd>Whether the point correspondence and pose ambiquity is resolved using a more sophisticated dynamic algorithm (constant velocity prediction) or a simple static resolution.
+Dynamic pose resolution can capture more extreme poses but may occasionally get stuck in a wrong pose estimates so that a reset of the internal state becomes neccessary.</dd>
+<dt>Auto-reset time</dt><dd>If no valid tracking result can be found when using dynamic pose resolution, the tracker will automatically reset its internal state (used for resolving the pose ambiguity and point correspondence)
+and return to a fail-safe initialization phase that assumes a neutral pose after this time.
+Decrease this time, if you get stuck in a wrong pose too often.</dd>
+<dt>Reset</dt><dd>Manually reset the trackers internal state used for dynamic pose resolution and return to a fail-safe initialization phase that assumes a neutral pose.
+You may use this in case you get stuck in a wrong pose.</dd>
+<dt>Enable Axis ...</dt><dd>Which axis to use for FTNoIR.</dd>
+</dl>
+
+<img src="settings2.png" alt="Settings Pane 2"/>
+<dl>
+<dt>Device</dt><dd>The camera used for tracking.</dd>
+<dt>Resolution</dt><dd>The desired capture resolution. If your camera does not support the entered resolution the true output resolution may be different or even invalid.
+You may check the true capture resolution in the status area while the tracker is running. A higher resolution results in more accurate point positions and will increase the
+stability of the tracking result, as long as the signal/noise ratio is sufficiently high.</dd>
+<dt>FPS</dt><dd>The desired capture framerate. Again, if your camera does not support the entered framerate, the true caputre framerate may be different or invalid.
+You may check the true processing framerate in the status area while the tracker is running.</dd>
+<dt>F/W</dt><dd>The focal length of the camera divided by the sensor width (of course in the same units).
+In case you don't have access to your camera's specifications, you can measure this yourself by placing a plane object of known width (for example a piece of cardboard) in front of the camera until it fills the whole image width.
+Then measure the distance between the object and the camera and divide by the object width.</dd>
+<dt>VideoWidget</dt><dd>Shows a resizable stand-alone video widget that shows the same content as the integrated video widget in FTNoIR.
+Update rate is only 10 fps and may lag behind a bit. Mainly useful during calibration of the point extraction. Same as for the integrated wiget, to save resources, this widget should only be shown when needed.</dd>
+<dt>Roll Pitch Yaw...</dt><dd>The orientation of the camera relative to the reference frame.
+If these angles are setup properly, the direction of translations may not be correct.
+Roll is treated in a special way since it is implemented as a frame rotation by +/- 90 deg that is transparent to the rest of the processing pipeline.
+</dd>
+<dt>Threshold</dt><dd>The threshold for point recognition. Areas above the threshold are shown in blue in the VideoWidget.
+Since point accuracy is best if the points are as big as possible in pixels, the theshold should be chosen as low as possible (stop before the contour of the points becomes "noisy").
+If small reflections are being falsely classified as points, increasing the minimum point diameter (see below) may help.</dd>
+<dt>Min Diameter</dt><dd>Minimum diameter of blobs to be classified as a pointmodel-point.</dd>
+<dt>Max Diameter</dt><dd>Maximum diameter of blobs to be classified as a pointmodel-point.</dd>
+<dt>Status</dt><dd>The tracker's status is shown in this area while the tracker is running.
+The FPS shown here correspond to the framerate of the whole tracker processing chain and may be lower than what your camera is able to provide, when<br/>
+1. The processing gets not enough CPU time<br/>
+2. The sleep time of the tracking thread is set too high<br/></dd>
+</dl>
+
+<img src="settings3.png" alt="Settings Pane 3"/>
+<dl>
+<dt>Model Selection and Dimensions ...</dt><dd>
+First select your model type (point, clip, custom), then enter the dimensions of your model in milimeters here.<br/>
+For the custom setting, the coordinates of the two remaining model points have to be entered (reference point M0 is at (0,0,0)) in a pose where the model roughly faces the camera.
+For orientation, the coordinates for the standard Freetrack clip are (0,40,-30), (0,-70,-80) and the ones for the cap (40,-60,-100), (-40,-60,-100).<br/>
+When using a custom point-model configuration, the following restrictions should be observed:<br/>
+The plane in which the 3 points lie should never be parallel to the image plane, M0-M1 and M0-M2 should be roughly perpendicular.</dd>
+
+<dt>Model Position</dt><dd>The vector from the model to the center of the head in the model frame. Can be calibrated automatically.</dd>
+<dt>Calibrate</dt><dd>In order to automatically calibrate the model-head offset, do the following:<br/>Press the Calibrate button, then look around while not moving your shoulder. (i.e. only rotation, no translation).
+Do not stay in one pose for too long. The current translation estimate will be updated in real time. As soon as the values stabilized sufficiently, press the Calibrate button again to stop the calibration process.</dd>
+</dl>
+</div>
+
+<a class="nav" id="setup"></a>
+<h2>Filter Setup</h2>
+<div class="indent">
+<p>
+This section desribes how the FTNoIR filter work and what the recommended settings for PointTracker are.
+</p>
+<p>
+Filtering is always a tradeoff between stability, accuracy and responsiveness.
+</p>
+<p>
+The <q>Smoothing</q> filter in FTNoIR is just a simple average over the last n samples.
+Since this filter produces input lag no matter how fast the head-movements are, it is recommended to turn it off by setting samples to 1.
+</p>
+<p>
+In the filter tab, it is recommended to select <q>Accela Filter Mk2</q>.
+Accela is a non-linear filter that works as follows:<br/>
+It looks at the difference between the new raw values <i>new_val</i> from the tracker and the last filtered value <i>old_val</i>
+and maps this difference via the customizable response function <i>f</i> via:<br/>
+</p>
+<p style="text-align: center">
+<i>new_val = old_val + f(new_val - old_val) / reduction_factor</i>
+</p>
+<p>
+So by setting <i>f(x) = reduction_factor * x</i>, one will get no filtering at all.<br/>
+If you set lower values for small x, small deviations (usually noise) will get dampened.
+This results in a dynamic dead-zone around the current position.
+</p>
+<p>
+The last two points are used by accela to extrapolate for large deviations.
+So in order to get a fast unfiltered response for large deviations, the line connecting the last two points should have a slope >= <i>reduction_factor</i>.
+</p>
+<p>
+More aggressive accela settings than the default FTNoIR accela settings are recommended in order to decrease the filtering lag and fully use the potential of point tracking.<br/>
+My current settings are:
+</p>
+<pre class="indent"><code>
+[Accela]
+Reduction=20
+
+[Curves-Accela-Scaling-Rotation]
+point-count=4
+point-0-x=0.1
+point-0-y=0
+point-1-x=1.43
+point-1-y=2.45
+point-2-x=2.0
+point-2-y=5.44
+point-3-x=2.06
+point-3-y=6
+</code></pre>
+<p>
+The curve is not too different from the standard one (except that I like a small dynamic dead zone for steady aiming, that's why the curve has a slope of 0 at the beginning).<br/>
+However, the reduction factor is decreased to a value of 20 (compared to the standard value of 100). This implies that each value of the curve is effectively 5 times higher than in standard FTNoIR (see formula above), which means higher responsiveness but can also lead to jitter/shaking.<br/>
+Keep in mind that there are no <q>best filter settings</q>. Since filtering is always a compromise it's a matter of personal taste and
+playing around with the filter settings is highly recommended.
+</p>
+</div>
+
+<a class="nav" id="support"></a>
+<h2>Support</h2>
+<div class="indent">
+<p>
+For questions/feedback about the plugin, post to the <a href="https://sourceforge.net/projects/facetracknoir/forums">FTNoIR-Forum</a>.<br/>
+In case you like this plugin and would like to support the author, you may consider making a donation.
+</p>
+<div style="text-align:center">
+<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
+<fieldset class="blind">
+<input type="hidden" name="cmd" value="_s-xclick"/>
+<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHJwYJKoZIhvcNAQcEoIIHGDCCBxQCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYCa+2zPZ+6vFPqveJsBIjFLpy54m7tl0AdojRr/K5qa3QJDyRBhGwGAP2jRihkmZFE2oKlfLpkz7nrwOQY/wFEPkggO+cABxUfjcQVpIupHEtwdV0hMklLs0RmACJy802yfi1yTiCpJ4hvWN+VfUI3gOiZ9uRZ3L4iGXES7xtqJbDELMAkGBSsOAwIaBQAwgaQGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIeopHzcJ8XBOAgYCYJFyTejSplEOwF21aQ01qQOads9Z+RUVI+hlvM/pHTjimaZPKSis3poAeqv6wKn40DpLNxDnmcT+Y9KXhrV+Gy4GZCPaeNzq2vquQ2ZVN0fTr84QVmKqPkjMBGmJAHSLCcZswUddemJgoD1uyvS0kNbchvxw7gDXJnJeBRNyXXKCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTEyMDkyMzA5NTcwOFowIwYJKoZIhvcNAQkEMRYEFG/qW7uo4R4m5uFYegcZaZsTPAcUMA0GCSqGSIb3DQEBAQUABIGAGygLfrR6IQbG2xZY2OrwKkfmRwiwtnXpLBnSbnWb7XxUOMhvM6962RiKBQBGP0+XYw0S9yu8ZHx7tqz/3bcMfGjtz7PwixYx6Rm8Z29ja78aUy5FmU7fc9yAWFxLHptSliK1dJBPxdQa9J2YSDvPQPAj+AdB9sJvqJoMoxTFGM4=-----END PKCS7-----
+"/>
+<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif" name="submit" alt="PayPal - The safer, easier way to pay online!"/>
+<img alt="" src="https://www.paypalobjects.com/de_DE/i/scr/pixel.gif" width="1" height="1"/>
+</fieldset>
+</form>
+</div>
+</div>
+
+<a class="nav" id="changelog"></a>
+<h2>ChangeLog</h2>
+<div class="indent">
+<h3>1.1</h3>
+<ul>
+<li>Added camera yaw and roll correction (intended for vertically mounted cameras)</li>
+<li>Improved point extraction algorithm, thanks to Michael Welter</li>
+<li>UI improvements: Select camera by device name, different VideoWidget architecture</li>
+<li>Bugfixes: Removed 99 FPS limitation</li>
+</ul>
+
+<h3>1.0</h3>
+<ul>
+<li>Added camera pitch correction</li>
+<li>Better communication with FTNoIR: output axis configuration, status report</li>
+</ul>
+
+<h3>1.0 beta</h3>
+<ul>
+<li>Switchted to videoInput library for capture. Desired capture resolution and fps can now be customized</li>
+<li>Introduced dynamic point-correspondence and POSIT-ambiguity resolution, which allows for the reconstruction of more extreme poses</li>
+<li>More convenient freetrack-like model dimension GUI</li>
+<li>Bugfixes: VideoWidget skipping frames, Timer resolution too low for accurate FPS measurement</li>
+</ul>
+</div>
+
+<a class="nav" id="build_instructions"></a>
+<h2>Build Instructions</h2>
+<div class="indent">
+<p>
+This section describes what you need to do in order to build PointTracker yourself.<br/>
+You can find the sources at the <a href="https://sourceforge.net/projects/ftnoirpt/">project site</a>
+or as part of the <a href="https://sourceforge.net/projects/facetracknoir/">FTNoIR sources</a>.
+</p>
+<p> The project was created with Visual Studio. </p>
+
+<h3>Dependencies</h3>
+<ul>
+<li>Qt 4.8.2 library</li>
+<li>Qt plugin for Visual studio</li>
+<li>OpenCV 2.4 prebuilt for Windows</li>
+<li>Boost 1.47</li>
+</ul>
+
+<h3>Details</h3>
+<div class="indent">
+<h4>Common</h4>
+<ul>
+<li>setup environment variable "QTDIR" (example value "D:\Devel\Libs\Qt\4.8.2")</li>
+<li>add "%QTDIR%\bin" to PATH</li>
+<li>setup environment variable "BOOST_DIR" (example value "D:\Devel\Libs\boost_1_47_0")</li>
+<li>setup environment variable "OPENCV_DIR" (example value "D:\Devel\Libs\opencv\build")</li>
+</ul>
+<h4>Debug</h4>
+<p>opencv linked dynamically:</p>
+<ul>
+<li>add "%OPENCV_DIR%\x86\vc9\bin" to PATH</li>
+</ul>
+<p>(in case of different Visual studio, change PATH and linker dependencies accordingly)</p>
+<h4>Release</h4>
+<p>opencv linked statically:</p>
+<ul>
+<li>custom build a statically linked version of opencv with the buil-option BUILD_WITH_STATIC_CRT set to OFF!</li>
+<li>copy resulting libaries to "%OPENCV_DIR%\x86\vc9\static_lib"</li>
+</ul>
+<p>(in case of different Visual studio, change PATH and linker dependencies accordingly)</p>
+</div>
+</div>
+
+</div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/tracker-points/doc/logo.png b/tracker-points/doc/logo.png
new file mode 100644
index 00000000..95032a25
--- /dev/null
+++ b/tracker-points/doc/logo.png
Binary files differ
diff --git a/tracker-points/doc/ptrack.ico b/tracker-points/doc/ptrack.ico
new file mode 100644
index 00000000..c4b2aedc
--- /dev/null
+++ b/tracker-points/doc/ptrack.ico
Binary files differ
diff --git a/tracker-points/doc/settings1.png b/tracker-points/doc/settings1.png
new file mode 100644
index 00000000..35b84c5c
--- /dev/null
+++ b/tracker-points/doc/settings1.png
Binary files differ
diff --git a/tracker-points/doc/settings2.png b/tracker-points/doc/settings2.png
new file mode 100644
index 00000000..c6cfd1f3
--- /dev/null
+++ b/tracker-points/doc/settings2.png
Binary files differ
diff --git a/tracker-points/doc/settings3.png b/tracker-points/doc/settings3.png
new file mode 100644
index 00000000..5922403d
--- /dev/null
+++ b/tracker-points/doc/settings3.png
Binary files differ
diff --git a/tracker-points/doc/style.css b/tracker-points/doc/style.css
new file mode 100644
index 00000000..0c3d29a6
--- /dev/null
+++ b/tracker-points/doc/style.css
@@ -0,0 +1,131 @@
+body {
+ width: 1000px;
+ font-size: 13px;
+ color: #000000;
+ padding: 0;
+ margin: 0 auto;
+ background: #444444;
+ font-family: verdana,arial;
+}
+
+table {
+ border-width: 3px;
+ border-color: #0000FF;
+ border-style: ridge;
+ margin-top: 5px;
+ background-color: #E0E0FF;
+}
+
+table.blind {
+ border: none;
+ background-color: #E6E6E6;
+}
+
+fieldset.blind {
+ border: none;
+}
+
+h1 { font-size: 160%; }
+h2 { font-size: 140%; }
+h3 { font-size: 115%; }
+
+.indent {
+ margin-left: 25px;
+}
+
+p
+{
+ margin-left: 10px;
+}
+
+li
+{
+ margin: 10px;
+}
+
+
+dl
+{
+ /*width: 80%;*/
+ border-bottom: 1px solid #999;
+}
+
+dt
+{
+ padding-top: 5px;
+ font-weight: bold;
+ border-top: 1px solid #999;
+}
+
+dd
+{
+ padding: 5px;
+}
+
+
+hr {
+ color: #688938;
+}
+
+a:link, a:visited {
+ color: #0000BF;
+}
+a:hover {
+ color: #0000FF;
+}
+
+a.nav {
+ position: relative;
+ top: -30px;
+ display: block;
+ visibility: hidden;
+}
+
+#navbar {
+ width: 1000px;
+ height: 30px;
+ background-color:#1a1a1b;
+ position: fixed;
+ margin: 0 auto;
+ padding: 0;
+}
+
+#navbar ul
+{
+ list-style-type: none;
+ margin: 0 auto;
+ padding: 0;
+ overflow: hidden;
+}
+
+#navbar li
+{
+ margin: 0 auto;
+ padding: 5px;
+ float:left;
+}
+
+#navbar a:link,a:visited
+{
+ display:block;
+ width:150px;
+ font-weight:bold;
+ color:#e85d02;
+ text-align:center;
+ /*padding:4px;*/
+ text-decoration:none;
+ /*text-transform:uppercase;*/
+}
+
+#navbar a:hover,a:active
+{
+ color:#ffffff;
+}
+
+#content {
+ background-color:#ffffff;
+ padding: 15px;
+ padding-top: 40px;
+ padding-right: 40px;
+ margin: 0 auto;
+}
diff --git a/tracker-points/ftnoir_tracker_pt.cpp b/tracker-points/ftnoir_tracker_pt.cpp
new file mode 100644
index 00000000..ca228a24
--- /dev/null
+++ b/tracker-points/ftnoir_tracker_pt.cpp
@@ -0,0 +1,229 @@
+/* Copyright (c) 2012 Patrick Ruoff
+ * Copyright (c) 2014-2016 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#include "ftnoir_tracker_pt.h"
+#include "video/video-widget.hpp"
+#include "compat/math-imports.hpp"
+#include "compat/check-visible.hpp"
+
+#include "pt-api.hpp"
+
+#include <QHBoxLayout>
+#include <QDebug>
+#include <QFile>
+#include <QCoreApplication>
+
+using namespace options;
+
+namespace pt_impl {
+
+Tracker_PT::Tracker_PT(pointer<pt_runtime_traits> const& traits) :
+ traits { traits },
+ s { traits->get_module_name() },
+ point_extractor { traits->make_point_extractor() },
+ camera { traits->make_camera() },
+ frame { traits->make_frame() },
+ preview_frame { traits->make_preview(preview_width, preview_height) }
+{
+ cv::setBreakOnError(true);
+ cv::setNumThreads(1);
+
+ connect(s.b.get(), &bundle_::saving, this, &Tracker_PT::maybe_reopen_camera, Qt::DirectConnection);
+ connect(s.b.get(), &bundle_::reloading, this, &Tracker_PT::maybe_reopen_camera, Qt::DirectConnection);
+
+ connect(&s.fov, value_::value_changed<int>(), this, &Tracker_PT::set_fov, Qt::DirectConnection);
+ set_fov(s.fov);
+}
+
+Tracker_PT::~Tracker_PT()
+{
+ requestInterruption();
+ wait();
+
+ QMutexLocker l(&camera_mtx);
+ camera->stop();
+}
+
+void Tracker_PT::run()
+{
+ maybe_reopen_camera();
+
+ while(!isInterruptionRequested())
+ {
+ pt_camera_info info;
+ bool new_frame = false;
+
+ {
+ QMutexLocker l(&camera_mtx);
+
+ if (camera)
+ std::tie(new_frame, info) = camera->get_frame(*frame);
+ }
+
+ if (new_frame)
+ {
+ const bool preview_visible = check_is_visible();
+
+ if (preview_visible)
+ *preview_frame = *frame;
+
+ point_extractor->extract_points(*frame, *preview_frame, points);
+ point_count.store(points.size(), std::memory_order_relaxed);
+
+ const bool success = points.size() >= PointModel::N_POINTS;
+
+ Affine X_CM;
+
+ {
+ QMutexLocker l(&center_lock);
+
+ if (success)
+ {
+ int dynamic_pose_ms = s.dynamic_pose && s.active_model_panel != PointModel::Clip
+ ? s.init_phase_timeout
+ : 0;
+
+ point_tracker.track(points,
+ PointModel(s),
+ info,
+ dynamic_pose_ms);
+ ever_success.store(true, std::memory_order_relaxed);
+ }
+
+ QMutexLocker l2(&data_lock);
+ X_CM = point_tracker.pose();
+ }
+
+ if (preview_visible)
+ {
+ const f fx = pt_camera_info::get_focal_length(info.fov, info.res_x, info.res_y);
+ Affine X_MH(mat33::eye(), vec3(s.t_MH_x, s.t_MH_y, s.t_MH_z));
+ Affine X_GH = X_CM * X_MH;
+ vec3 p = X_GH.t; // head (center?) position in global space
+
+ if (p[2] > f(.1))
+ preview_frame->draw_head_center((p[0] * fx) / p[2], (p[1] * fx) / p[2]);
+
+ widget->update_image(preview_frame->get_bitmap());
+
+ auto [ w, h ] = widget->preview_size();
+ if (w != preview_width || h != preview_height)
+ {
+ preview_width = w; preview_height = h;
+ preview_frame = traits->make_preview(w, h);
+ }
+ }
+ }
+ }
+}
+
+bool Tracker_PT::maybe_reopen_camera()
+{
+ QMutexLocker l(&camera_mtx);
+
+ return camera->start(s.camera_name,
+ s.cam_fps, s.cam_res_x, s.cam_res_y);
+}
+
+void Tracker_PT::set_fov(int value)
+{
+ QMutexLocker l(&camera_mtx);
+ camera->set_fov(value);
+}
+
+module_status Tracker_PT::start_tracker(QFrame* video_frame)
+{
+ //video_frame->setAttribute(Qt::WA_NativeWindow);
+
+ widget = std::make_unique<video_widget>(video_frame);
+ layout = std::make_unique<QHBoxLayout>(video_frame);
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->addWidget(widget.get());
+ video_frame->setLayout(layout.get());
+ //video_widget->resize(video_frame->width(), video_frame->height());
+ video_frame->show();
+
+ start(QThread::HighPriority);
+
+ return {};
+}
+
+void Tracker_PT::data(double *data)
+{
+ if (ever_success.load(std::memory_order_relaxed))
+ {
+ Affine X_CM;
+ {
+ QMutexLocker l(&data_lock);
+ X_CM = point_tracker.pose();
+ }
+
+ Affine X_MH(mat33::eye(), vec3(s.t_MH_x, s.t_MH_y, s.t_MH_z));
+ Affine X_GH(X_CM * X_MH);
+
+ // translate rotation matrix from opengl (G) to roll-pitch-yaw (E) frame
+ // -z -> x, y -> z, x -> -y
+ mat33 R_EG(0, 0,-1,
+ -1, 0, 0,
+ 0, 1, 0);
+ mat33 R(R_EG * X_GH.R * R_EG.t());
+
+ // get translation(s)
+ const vec3& t = X_GH.t;
+
+ // extract rotation angles
+ auto r00 = (double)R(0, 0);
+ auto r10 = (double)R(1,0), r20 = (double)R(2,0);
+ auto r21 = (double)R(2,1), r22 = (double)R(2,2);
+
+ double beta = atan2(-r20, sqrt(r21*r21 + r22*r22));
+ double alpha = atan2(r10, r00);
+ double gamma = atan2(r21, r22);
+
+ constexpr double rad2deg = 180/M_PI;
+
+ data[Yaw] = rad2deg * alpha;
+ data[Pitch] = -rad2deg * beta;
+ data[Roll] = rad2deg * gamma;
+
+ // convert to cm
+ data[TX] = (double)t[0] / 10;
+ data[TY] = (double)t[1] / 10;
+ data[TZ] = (double)t[2] / 10;
+ }
+}
+
+bool Tracker_PT::center()
+{
+ QMutexLocker l(&center_lock);
+
+ point_tracker.reset_state();
+ return false;
+}
+
+int Tracker_PT::get_n_points()
+{
+ return (int)point_count.load(std::memory_order_relaxed);
+}
+
+bool Tracker_PT::get_cam_info(pt_camera_info& info)
+{
+ QMutexLocker l(&camera_mtx);
+ bool ret;
+
+ std::tie(ret, info) = camera->get_info();
+ return ret;
+}
+
+Affine Tracker_PT::pose() const
+{
+ QMutexLocker l(&data_lock);
+ return point_tracker.pose();
+}
+
+} // ns pt_impl
diff --git a/tracker-points/ftnoir_tracker_pt.h b/tracker-points/ftnoir_tracker_pt.h
new file mode 100644
index 00000000..210c6a01
--- /dev/null
+++ b/tracker-points/ftnoir_tracker_pt.h
@@ -0,0 +1,81 @@
+/* Copyright (c) 2012 Patrick Ruoff
+ * Copyright (c) 2014-2016 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#pragma once
+
+#include "api/plugin-api.hpp"
+#include "pt-api.hpp"
+#include "point_tracker.h"
+#include "cv/numeric.hpp"
+#include "video/video-widget.hpp"
+
+#include <atomic>
+#include <memory>
+#include <vector>
+
+#include <opencv2/core.hpp>
+
+#include <QThread>
+#include <QMutex>
+#include <QLayout>
+
+namespace pt_impl {
+
+class TrackerDialog_PT;
+
+using namespace numeric_types;
+
+struct Tracker_PT : QThread, ITracker
+{
+ friend class TrackerDialog_PT;
+
+ template<typename t> using pointer = pt_pointer<t>;
+
+ explicit Tracker_PT(pointer<pt_runtime_traits> const& pt_runtime_traits);
+ ~Tracker_PT() override;
+ module_status start_tracker(QFrame* parent_window) override;
+ void data(double* data) override;
+ bool center() override;
+
+ int get_n_points();
+ [[nodiscard]] bool get_cam_info(pt_camera_info& info);
+ Affine pose() const;
+
+private:
+ void run() override;
+
+ bool maybe_reopen_camera();
+ void set_fov(int value);
+
+ pointer<pt_runtime_traits> traits;
+
+ QMutex camera_mtx;
+
+ PointTracker point_tracker;
+
+ pt_settings s;
+
+ std::unique_ptr<QLayout> layout;
+ std::vector<vec2> points;
+
+ int preview_width = 320, preview_height = 240;
+
+ pointer<pt_point_extractor> point_extractor;
+ pointer<pt_camera> camera;
+ pointer<video_widget> widget;
+ pointer<pt_frame> frame;
+ pointer<pt_preview> preview_frame;
+
+ std::atomic<unsigned> point_count { 0 };
+ std::atomic<bool> ever_success = false;
+ mutable QMutex center_lock, data_lock;
+};
+
+} // ns pt_impl
+
+using Tracker_PT = pt_impl::Tracker_PT;
diff --git a/tracker-points/ftnoir_tracker_pt_dialog.cpp b/tracker-points/ftnoir_tracker_pt_dialog.cpp
new file mode 100644
index 00000000..edf689a9
--- /dev/null
+++ b/tracker-points/ftnoir_tracker_pt_dialog.cpp
@@ -0,0 +1,282 @@
+/* Copyright (c) 2012 Patrick Ruoff
+ * Copyright (c) 2014-2015 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#include "ftnoir_tracker_pt_dialog.h"
+#include "compat/math.hpp"
+#include "video/camera.hpp"
+
+#include <opencv2/core.hpp>
+
+#include <QString>
+#include <QtGlobal>
+#include <QDebug>
+
+using namespace options;
+
+static void init_resources() { Q_INIT_RESOURCE(tracker_pt_base); }
+
+namespace pt_impl {
+
+TrackerDialog_PT::TrackerDialog_PT(const QString& module_name) :
+ s(module_name),
+ tracker(nullptr),
+ timer(this),
+ trans_calib(1, 2)
+{
+ init_resources();
+
+ ui.setupUi(this);
+
+ for (const QString& str : video::camera_names())
+ ui.camdevice_combo->addItem(str);
+
+ tie_setting(s.camera_name, ui.camdevice_combo);
+ tie_setting(s.cam_res_x, ui.res_x_spin);
+ tie_setting(s.cam_res_y, ui.res_y_spin);
+ tie_setting(s.cam_fps, ui.fps_spin);
+
+ tie_setting(s.threshold_slider, ui.threshold_slider);
+
+ tie_setting(s.min_point_size, ui.mindiam_spin);
+ tie_setting(s.max_point_size, ui.maxdiam_spin);
+
+ tie_setting(s.clip_by, ui.clip_bheight_spin);
+ tie_setting(s.clip_bz, ui.clip_blength_spin);
+ tie_setting(s.clip_ty, ui.clip_theight_spin);
+ tie_setting(s.clip_tz, ui.clip_tlength_spin);
+
+ tie_setting(s.cap_x, ui.cap_width_spin);
+ tie_setting(s.cap_y, ui.cap_height_spin);
+ tie_setting(s.cap_z, ui.cap_length_spin);
+
+ tie_setting(s.m01_x, ui.m1x_spin);
+ tie_setting(s.m01_y, ui.m1y_spin);
+ tie_setting(s.m01_z, ui.m1z_spin);
+
+ tie_setting(s.m02_x, ui.m2x_spin);
+ tie_setting(s.m02_y, ui.m2y_spin);
+ tie_setting(s.m02_z, ui.m2z_spin);
+
+ tie_setting(s.t_MH_x, ui.tx_spin);
+ tie_setting(s.t_MH_y, ui.ty_spin);
+ tie_setting(s.t_MH_z, ui.tz_spin);
+
+ tie_setting(s.fov, ui.fov);
+
+ tie_setting(s.active_model_panel, ui.model_tabs);
+
+ tie_setting(s.dynamic_pose, ui.dynamic_pose);
+ tie_setting(s.init_phase_timeout, ui.init_phase_timeout);
+
+ tie_setting(s.auto_threshold, ui.auto_threshold);
+
+ connect(ui.tcalib_button,SIGNAL(toggled(bool)), this, SLOT(startstop_trans_calib(bool)));
+
+ connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(doOK()));
+ connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(doCancel()));
+
+ connect(ui.camdevice_combo, &QComboBox::currentTextChanged, this, &TrackerDialog_PT::set_camera_settings_available);
+ set_camera_settings_available(ui.camdevice_combo->currentText());
+ connect(ui.camera_settings, &QPushButton::clicked, this, &TrackerDialog_PT::show_camera_settings);
+
+ connect(&timer, &QTimer::timeout, this, &TrackerDialog_PT::poll_tracker_info_impl);
+ timer.setInterval(250);
+
+ connect(&calib_timer, &QTimer::timeout, this, &TrackerDialog_PT::trans_calib_step);
+ calib_timer.setInterval(35);
+
+ poll_tracker_info_impl();
+
+ connect(this, &TrackerDialog_PT::poll_tracker_info, this, &TrackerDialog_PT::poll_tracker_info_impl, Qt::DirectConnection);
+
+ constexpr pt_color_type color_types[] = {
+ pt_color_average,
+ pt_color_natural,
+ pt_color_red_only,
+ pt_color_green_only,
+ pt_color_blue_only,
+ };
+
+ for (unsigned k = 0; k < std::size(color_types); k++)
+ ui.blob_color->setItemData(k, int(color_types[k]));
+
+ tie_setting(s.blob_color, ui.blob_color);
+
+ tie_setting(s.threshold_slider, ui.threshold_value_display, [this](const slider_value& val) {
+ return threshold_display_text(int(val));
+ });
+
+ // refresh threshold display on auto-threshold checkbox state change
+ tie_setting(s.auto_threshold,
+ this,
+ [this](bool) { s.threshold_slider.notify(); });
+}
+
+QString TrackerDialog_PT::threshold_display_text(int threshold_value)
+{
+ if (!s.auto_threshold)
+ return tr("Brightness %1/255").arg(threshold_value);
+ else
+ {
+ pt_camera_info info;
+ int w = s.cam_res_x, h = s.cam_res_y;
+
+ if (w * h <= 0)
+ {
+ w = 640;
+ h = 480;
+ }
+
+ if (tracker && tracker->get_cam_info(info) && info.res_x * info.res_y != 0)
+ {
+ w = info.res_x;
+ h = info.res_y;
+ }
+
+ double value = (double)pt_point_extractor::threshold_radius_value(w, h, threshold_value);
+
+ return tr("LED radius %1 pixels").arg(value, 0, 'f', 2);
+ }
+}
+
+void TrackerDialog_PT::startstop_trans_calib(bool start)
+{
+ QMutexLocker l(&calibrator_mutex);
+
+ if (start)
+ {
+ qDebug() << "pt: starting translation calibration";
+ calib_timer.start();
+ trans_calib.reset();
+ s.t_MH_x = 0;
+ s.t_MH_y = 0;
+ s.t_MH_z = 0;
+
+ ui.sample_count_display->setText(QString());
+ }
+ else
+ {
+ calib_timer.stop();
+ qDebug() << "pt: stopping translation calibration";
+ {
+ auto [tmp, nsamples] = trans_calib.get_estimate();
+ s.t_MH_x = int(tmp[0]);
+ s.t_MH_y = int(tmp[1]);
+ s.t_MH_z = int(tmp[2]);
+
+ constexpr int min_yaw_samples = 15;
+ constexpr int min_pitch_samples = 15;
+ constexpr int min_samples = min_yaw_samples+min_pitch_samples;
+
+ // Don't bother counting roll samples. Roll calibration is hard enough
+ // that it's a hidden unsupported feature anyway.
+
+ QString sample_feedback;
+ if (nsamples[0] < min_yaw_samples)
+ sample_feedback = tr("%1 yaw samples. Yaw more to %2 samples for stable calibration.").arg(nsamples[0]).arg(min_yaw_samples);
+ else if (nsamples[1] < min_pitch_samples)
+ sample_feedback = tr("%1 pitch samples. Pitch more to %2 samples for stable calibration.").arg(nsamples[1]).arg(min_pitch_samples);
+ else
+ {
+ const int nsamples_total = nsamples[0] + nsamples[1];
+ sample_feedback = tr("%1 samples. Over %2, good!").arg(nsamples_total).arg(min_samples);
+ }
+
+ ui.sample_count_display->setText(sample_feedback);
+ }
+ }
+ ui.tx_spin->setEnabled(!start);
+ ui.ty_spin->setEnabled(!start);
+ ui.tz_spin->setEnabled(!start);
+
+ if (start)
+ ui.tcalib_button->setText(tr("Stop calibration"));
+ else
+ ui.tcalib_button->setText(tr("Start calibration"));
+}
+
+void TrackerDialog_PT::poll_tracker_info_impl()
+{
+ pt_camera_info info;
+ if (tracker && tracker->get_cam_info(info))
+ {
+ ui.caminfo_label->setText(tr("%1x%2 @ %3 FPS").arg(info.res_x).arg(info.res_y).arg(iround(info.fps)));
+
+ // display point info
+ const int n_points = tracker->get_n_points();
+ ui.pointinfo_label->setText((n_points == 3 ? tr("%1 OK!") : tr("%1 BAD!")).arg(n_points));
+ }
+ else
+ {
+ ui.caminfo_label->setText(tr("Tracker offline"));
+ ui.pointinfo_label->setText(QString());
+ }
+}
+
+void TrackerDialog_PT::set_camera_settings_available(const QString& /* camera_name */)
+{
+ ui.camera_settings->setEnabled(true);
+}
+
+void TrackerDialog_PT::show_camera_settings()
+{
+ if (tracker)
+ {
+ QMutexLocker l(&tracker->camera_mtx);
+ tracker->camera->show_camera_settings();
+ }
+ else
+ (void)video::show_dialog(s.camera_name);
+}
+
+void TrackerDialog_PT::trans_calib_step()
+{
+ QMutexLocker l(&calibrator_mutex);
+
+ if (tracker)
+ {
+ Affine X_CM = tracker->pose();
+ trans_calib.update(X_CM.R, X_CM.t);
+ }
+ else
+ startstop_trans_calib(false);
+}
+
+void TrackerDialog_PT::save()
+{
+ s.b->save();
+}
+
+void TrackerDialog_PT::doOK()
+{
+ save();
+ close();
+}
+
+void TrackerDialog_PT::doCancel()
+{
+ close();
+}
+
+void TrackerDialog_PT::register_tracker(ITracker *t)
+{
+ tracker = static_cast<Tracker_PT*>(t);
+ ui.tcalib_button->setEnabled(true);
+ poll_tracker_info();
+ timer.start();
+}
+
+void TrackerDialog_PT::unregister_tracker()
+{
+ tracker = nullptr;
+ ui.tcalib_button->setEnabled(false);
+ poll_tracker_info();
+ timer.stop();
+}
+
+} // ns pt_impl
diff --git a/tracker-points/ftnoir_tracker_pt_dialog.h b/tracker-points/ftnoir_tracker_pt_dialog.h
new file mode 100644
index 00000000..66981ee6
--- /dev/null
+++ b/tracker-points/ftnoir_tracker_pt_dialog.h
@@ -0,0 +1,55 @@
+/* Copyright (c) 2012 Patrick Ruoff
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#pragma once
+
+#include "pt-api.hpp"
+
+#include "ftnoir_tracker_pt.h"
+#include "tracker-pt/ui_FTNoIR_PT_Controls.h"
+#include "cv/translation-calibrator.hpp"
+#include "video/video-widget.hpp"
+
+#include <QTimer>
+#include <QMutex>
+
+namespace pt_impl {
+
+class TrackerDialog_PT : public ITrackerDialog
+{
+ Q_OBJECT
+public:
+ TrackerDialog_PT(const QString& module_name);
+ void register_tracker(ITracker *tracker) override;
+ void unregister_tracker() override;
+ void save();
+public slots:
+ void doOK();
+ void doCancel();
+
+ void startstop_trans_calib(bool start);
+ void trans_calib_step();
+ void poll_tracker_info_impl();
+ void set_camera_settings_available(const QString& camera_name);
+ void show_camera_settings();
+signals:
+ void poll_tracker_info();
+protected:
+ QString threshold_display_text(int threshold_value);
+
+ pt_settings s;
+ Tracker_PT* tracker;
+ QTimer timer, calib_timer;
+ TranslationCalibrator trans_calib;
+ QMutex calibrator_mutex;
+
+ Ui::UICPTClientControls ui;
+};
+
+} // ns pt_impl
+
+using TrackerDialog_PT = pt_impl::TrackerDialog_PT;
diff --git a/tracker-points/lang/nl_NL.ts b/tracker-points/lang/nl_NL.ts
new file mode 100644
index 00000000..5897a731
--- /dev/null
+++ b/tracker-points/lang/nl_NL.ts
@@ -0,0 +1,290 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>UICPTClientControls</name>
+ <message>
+ <source>PointTracker Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>°</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Diagonal field of view</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Width</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FPS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Desired capture height</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> px</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Dynamic pose timeout</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Desired capture framerate</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> Hz</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Desired capture width</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Height</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> ms</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Open</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera settings (when available)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Point extraction</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Threshold</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Min size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Intensity threshold for point extraction</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Automatic threshold</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Enable, slider sets point size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Color channels used</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Average</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Natural</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Red only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Blue only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Dynamic pose (for caps only, never clips)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Maximum point diameter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Minimum point diameter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Value</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model Dimensions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Side</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Front</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Cap</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Custom</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>z:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>x:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Location of the two remaining model points&lt;br/&gt;with respect to the reference point in default pose&lt;/p&gt;&lt;p&gt;Use any units you want, not necessarily centimeters.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>y:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;3&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;2&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model position</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use only yaw and pitch while calibrating.
+Don&apos;t roll or change position.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>About</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;FTNoIR PointTracker Plugin&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;by Patrick Ruoff&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://ftnoirpt.sourceforge.net/&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#0000ff;&quot;&gt;Manual (external)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Extracted Points:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera Info:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Green only</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>pt_impl::TrackerDialog_PT</name>
+ <message>
+ <source>Brightness %1/255</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>LED radius %1 pixels</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 samples. Over %2, good!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Stop calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1x%2 @ %3 FPS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 OK!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 BAD!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker offline</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>Points Tracker 0.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-points/lang/ru_RU.ts b/tracker-points/lang/ru_RU.ts
new file mode 100644
index 00000000..8a06ebac
--- /dev/null
+++ b/tracker-points/lang/ru_RU.ts
@@ -0,0 +1,295 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>UICPTClientControls</name>
+ <message>
+ <source>PointTracker Settings</source>
+ <translation>Настройки PointTracker</translation>
+ </message>
+ <message>
+ <source>Camera</source>
+ <translation>Камера</translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation>Настройка камеры</translation>
+ </message>
+ <message>
+ <source>°</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Diagonal field of view</source>
+ <translation>Угол обзора камеры</translation>
+ </message>
+ <message>
+ <source>Width</source>
+ <translation>Ширина</translation>
+ </message>
+ <message>
+ <source>FPS</source>
+ <translation>FPS (Кадров в секунду)</translation>
+ </message>
+ <message>
+ <source>Desired capture height</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source> px</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Dynamic pose timeout</source>
+ <translation>Динамическая поза (время ожидания)</translation>
+ </message>
+ <message>
+ <source>Desired capture framerate</source>
+ <translation>Желаемая частота кадров</translation>
+ </message>
+ <message>
+ <source> Hz</source>
+ <translation> Гц</translation>
+ </message>
+ <message>
+ <source>Desired capture width</source>
+ <translation>Желаемая ширина захвата</translation>
+ </message>
+ <message>
+ <source>Height</source>
+ <translation>Высота</translation>
+ </message>
+ <message>
+ <source> ms</source>
+ <translation> мс</translation>
+ </message>
+ <message>
+ <source>Dynamic pose (for caps only, never clips)</source>
+ <translation>Динамическая поза (Только для модели &quot;Кепка&quot;)</translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation>Устройство</translation>
+ </message>
+ <message>
+ <source>Open</source>
+ <translation>Открыть</translation>
+ </message>
+ <message>
+ <source>Camera settings (when available)</source>
+ <translation>Параметры камеры (если доступно)</translation>
+ </message>
+ <message>
+ <source>Point extraction</source>
+ <translation>Извлечение точек</translation>
+ </message>
+ <message>
+ <source>Max size</source>
+ <translation>Макс.размер</translation>
+ </message>
+ <message>
+ <source>Threshold</source>
+ <translation>Порог</translation>
+ </message>
+ <message>
+ <source>Min size</source>
+ <translation>Мин.размер</translation>
+ </message>
+ <message>
+ <source>Intensity threshold for point extraction</source>
+ <translation>Порог интенсивности для извлечения точки</translation>
+ </message>
+ <message>
+ <source>Automatic threshold</source>
+ <translation>Автоматич. порог</translation>
+ </message>
+ <message>
+ <source>Enable, slider sets point size</source>
+ <translation>Полузнок устанавливает размер точек</translation>
+ </message>
+ <message>
+ <source>Color channels used</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Average</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Natural</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Red only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Blue only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Maximum point diameter</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Minimum point diameter</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Value</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model</source>
+ <translation>Модель</translation>
+ </message>
+ <message>
+ <source>Clip</source>
+ <translation>Клипса</translation>
+ </message>
+ <message>
+ <source>Model Dimensions</source>
+ <translation>Размеры модели</translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation> мм</translation>
+ </message>
+ <message>
+ <source>Side</source>
+ <translation>Сбоку</translation>
+ </message>
+ <message>
+ <source>Front</source>
+ <translation>Спереди</translation>
+ </message>
+ <message>
+ <source>Cap</source>
+ <translation>Кепка</translation>
+ </message>
+ <message>
+ <source>Custom</source>
+ <translation>Свой</translation>
+ </message>
+ <message>
+ <source>z:</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>x:</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Location of the two remaining model points&lt;br/&gt;with respect to the reference point in default pose&lt;/p&gt;&lt;p&gt;Use any units you want, not necessarily centimeters.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translatorcomment>Расположение двух оставшихся точек модели относительно опорной точки в стандартной позе. Возможно исп-ть любые единицы измерения, не обязательно сантиметры.</translatorcomment>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt; Расположение двух оставшихся точек модели&lt;br/&gt;относительно опорной точки в стандартной позе. &lt;/p&gt;&lt;p&gt;Возможно использовать любые единицы измерения.&lt;/p&gt;&lt;/body&gt;&lt;/html</translation>
+ </message>
+ <message>
+ <source>y:</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;3&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;2&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation></translation>
+ </message>
+ <message>
+ <source>Model position</source>
+ <translation>Положение модели</translation>
+ </message>
+ <message>
+ <source>Use only yaw and pitch while calibrating.
+Don&apos;t roll or change position.</source>
+ <translation>Во время калибровки
+используйте только оси
+YAW и PITCH.
+Не используйте оси
+ROLL или X/Y-смещения.</translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation>Начать калибровку</translation>
+ </message>
+ <message>
+ <source>About</source>
+ <translation>О программе</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;FTNoIR PointTracker Plugin&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;by Patrick Ruoff&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://ftnoirpt.sourceforge.net/&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#0000ff;&quot;&gt;Manual (external)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;FTNoIR PointTracker Plugin&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;by Patrick Ruoff&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://ftnoirpt.sourceforge.net/&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#0000ff;&quot;&gt;Руководство (PointTracker)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>Status</source>
+ <translation>Статус</translation>
+ </message>
+ <message>
+ <source>Extracted Points:</source>
+ <translation>Извлечено точек:</translation>
+ </message>
+ <message>
+ <source>Camera Info:</source>
+ <translation>Параметры камеры:</translation>
+ </message>
+ <message>
+ <source>Green only</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>pt_impl::TrackerDialog_PT</name>
+ <message>
+ <source>Brightness %1/255</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>LED radius %1 pixels</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
+ <translation type="unfinished">По оси YAW выполнено: %1 замер(а/ов). Для стабильного результата необходимо не меньше %2</translation>
+ </message>
+ <message>
+ <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
+ <translation type="unfinished">По оси Pitch выполнено: %1 замер(а/ов). Для стабильного результата необходимо не меньше %2</translation>
+ </message>
+ <message>
+ <source>%1 samples. Over %2, good!</source>
+ <translation type="unfinished">Получено %1 образца(-ов). Больше %2, отлично!!</translation>
+ </message>
+ <message>
+ <source>Stop calibration</source>
+ <translation type="unfinished">Остановить калибровку</translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation type="unfinished">Начать калибровку</translation>
+ </message>
+ <message>
+ <source>%1x%2 @ %3 FPS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 OK!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 BAD!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker offline</source>
+ <translation type="unfinished">Отслеживание отключено</translation>
+ </message>
+</context>
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>Points Tracker 0.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-points/lang/stub.ts b/tracker-points/lang/stub.ts
new file mode 100644
index 00000000..6c493c14
--- /dev/null
+++ b/tracker-points/lang/stub.ts
@@ -0,0 +1,290 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>UICPTClientControls</name>
+ <message>
+ <source>PointTracker Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>°</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Diagonal field of view</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Width</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>FPS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Desired capture height</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> px</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Dynamic pose timeout</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Desired capture framerate</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> Hz</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Desired capture width</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Height</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> ms</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Open</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera settings (when available)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Point extraction</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Max size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Threshold</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Min size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Intensity threshold for point extraction</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Automatic threshold</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Enable, slider sets point size</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Color channels used</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Average</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Natural</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Red only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Blue only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Dynamic pose (for caps only, never clips)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Maximum point diameter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Minimum point diameter</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Value</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Clip</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model Dimensions</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Side</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Front</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Cap</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Custom</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>z:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>x:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Location of the two remaining model points&lt;br/&gt;with respect to the reference point in default pose&lt;/p&gt;&lt;p&gt;Use any units you want, not necessarily centimeters.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>y:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;3&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;2&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Model position</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use only yaw and pitch while calibrating.
+Don&apos;t roll or change position.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>About</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;FTNoIR PointTracker Plugin&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;by Patrick Ruoff&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://ftnoirpt.sourceforge.net/&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#0000ff;&quot;&gt;Manual (external)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Status</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Extracted Points:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Camera Info:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Green only</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>pt_impl::TrackerDialog_PT</name>
+ <message>
+ <source>Brightness %1/255</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>LED radius %1 pixels</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 samples. Over %2, good!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Stop calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1x%2 @ %3 FPS</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 OK!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 BAD!</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Tracker offline</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>Points Tracker 0.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-points/lang/zh_CN.ts b/tracker-points/lang/zh_CN.ts
new file mode 100644
index 00000000..8212de68
--- /dev/null
+++ b/tracker-points/lang/zh_CN.ts
@@ -0,0 +1,290 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="zh_CN">
+<context>
+ <name>UICPTClientControls</name>
+ <message>
+ <source>PointTracker Settings</source>
+ <translation>PointTracker设置</translation>
+ </message>
+ <message>
+ <source>Camera</source>
+ <translation>摄像头</translation>
+ </message>
+ <message>
+ <source>Camera settings</source>
+ <translation>摄像头设置</translation>
+ </message>
+ <message>
+ <source>°</source>
+ <translation>度</translation>
+ </message>
+ <message>
+ <source>Diagonal field of view</source>
+ <translation>对角线</translation>
+ </message>
+ <message>
+ <source>Width</source>
+ <translation>宽度</translation>
+ </message>
+ <message>
+ <source>FPS</source>
+ <translation>帧数</translation>
+ </message>
+ <message>
+ <source>Desired capture height</source>
+ <translation>期望高度</translation>
+ </message>
+ <message>
+ <source> px</source>
+ <translation> 像素点</translation>
+ </message>
+ <message>
+ <source>Dynamic pose timeout</source>
+ <translation>动态姿态超时时间</translation>
+ </message>
+ <message>
+ <source>Desired capture framerate</source>
+ <translation>期望帧数</translation>
+ </message>
+ <message>
+ <source> Hz</source>
+ <translation> 赫兹</translation>
+ </message>
+ <message>
+ <source>Desired capture width</source>
+ <translation>期望宽度</translation>
+ </message>
+ <message>
+ <source>Height</source>
+ <translation>高度</translation>
+ </message>
+ <message>
+ <source> ms</source>
+ <translation> 毫秒</translation>
+ </message>
+ <message>
+ <source>Device</source>
+ <translation>设备名称</translation>
+ </message>
+ <message>
+ <source>Open</source>
+ <translation>打开</translation>
+ </message>
+ <message>
+ <source>Camera settings (when available)</source>
+ <translation>摄像头设置 (连接时)</translation>
+ </message>
+ <message>
+ <source>Point extraction</source>
+ <translation>跟踪点解析</translation>
+ </message>
+ <message>
+ <source>Max size</source>
+ <translation>最大</translation>
+ </message>
+ <message>
+ <source>Threshold</source>
+ <translation>大小门限值</translation>
+ </message>
+ <message>
+ <source>Min size</source>
+ <translation>最小</translation>
+ </message>
+ <message>
+ <source>Intensity threshold for point extraction</source>
+ <translation>点密度</translation>
+ </message>
+ <message>
+ <source>Automatic threshold</source>
+ <translation>自动门限值</translation>
+ </message>
+ <message>
+ <source>Enable, slider sets point size</source>
+ <translation>激活,滑动,设置跟踪点大小</translation>
+ </message>
+ <message>
+ <source>Maximum point diameter</source>
+ <translation>最大点直径</translation>
+ </message>
+ <message>
+ <source>Minimum point diameter</source>
+ <translation>最小点直径</translation>
+ </message>
+ <message>
+ <source>Model</source>
+ <translation>点模式</translation>
+ </message>
+ <message>
+ <source>Clip</source>
+ <translation>夹子式</translation>
+ </message>
+ <message>
+ <source>Model Dimensions</source>
+ <translation>尺寸</translation>
+ </message>
+ <message>
+ <source> mm</source>
+ <translation> 毫米</translation>
+ </message>
+ <message>
+ <source>Side</source>
+ <translation>侧面</translation>
+ </message>
+ <message>
+ <source>Front</source>
+ <translation>正面</translation>
+ </message>
+ <message>
+ <source>Cap</source>
+ <translation>帽子式</translation>
+ </message>
+ <message>
+ <source>Custom</source>
+ <translation>自定义模式</translation>
+ </message>
+ <message>
+ <source>z:</source>
+ <translation>Z:</translation>
+ </message>
+ <message>
+ <source>x:</source>
+ <translation>X:</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Location of the two remaining model points&lt;br/&gt;with respect to the reference point in default pose&lt;/p&gt;&lt;p&gt;Use any units you want, not necessarily centimeters.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;三点中的两点位置是相对第一个点的&lt;/p&gt;&lt;p&gt;单位不一定要用厘米&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>y:</source>
+ <translation>Y:</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;3&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;3&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;2&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt;&quot;&gt;P&lt;/span&gt;&lt;span style=&quot; font-size:16pt; vertical-align:sub;&quot;&gt;2&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>Model position</source>
+ <translation>姿态空间位置</translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation>开始校准</translation>
+ </message>
+ <message>
+ <source>About</source>
+ <translation>关于</translation>
+ </message>
+ <message>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;FTNoIR PointTracker Plugin&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;by Patrick Ruoff&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://ftnoirpt.sourceforge.net/&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#0000ff;&quot;&gt;Manual (external)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;FTNoIR PointTracker Plugin&lt;br/&gt;Version 1.1&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Patrick Ruoff&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;http://ftnoirpt.sourceforge.net/&quot;&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline; color:#0000ff;&quot;&gt;参考手册 (外部链接)&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <source>Status</source>
+ <translation>状态</translation>
+ </message>
+ <message>
+ <source>Extracted Points:</source>
+ <translation>解析出的点:</translation>
+ </message>
+ <message>
+ <source>Camera Info:</source>
+ <translation>设备信息:</translation>
+ </message>
+ <message>
+ <source>Color channels used</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Average</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Natural</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Red only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Blue only</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Dynamic pose (for caps only, never clips)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Value</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>Use only yaw and pitch while calibrating.
+Don&apos;t roll or change position.</source>
+ <translation>用pitch和yaw校准。不要roll或者变换位置</translation>
+ </message>
+ <message>
+ <source>Green only</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>pt_impl::TrackerDialog_PT</name>
+ <message>
+ <source>Brightness %1/255</source>
+ <translation type="unfinished">亮度 %1/255</translation>
+ </message>
+ <message>
+ <source>LED radius %1 pixels</source>
+ <translation type="unfinished">光源半径 %1 像素</translation>
+ </message>
+ <message>
+ <source>%1 yaw samples. Yaw more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 pitch samples. Pitch more to %2 samples for stable calibration.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <source>%1 samples. Over %2, good!</source>
+ <translation type="unfinished">%1 样本。%2 正常</translation>
+ </message>
+ <message>
+ <source>Stop calibration</source>
+ <translation type="unfinished">停止校准</translation>
+ </message>
+ <message>
+ <source>Start calibration</source>
+ <translation type="unfinished">开始校准</translation>
+ </message>
+ <message>
+ <source>%1x%2 @ %3 FPS</source>
+ <translation type="unfinished">%1x%2 @ %3 帧</translation>
+ </message>
+ <message>
+ <source>%1 OK!</source>
+ <translation type="unfinished">%1 正常</translation>
+ </message>
+ <message>
+ <source>%1 BAD!</source>
+ <translation type="unfinished">%1 异常</translation>
+ </message>
+ <message>
+ <source>Tracker offline</source>
+ <translation type="unfinished">跟踪器脱机</translation>
+ </message>
+</context>
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>Points Tracker 0.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-points/module/CMakeLists.txt b/tracker-points/module/CMakeLists.txt
new file mode 100644
index 00000000..1eec9616
--- /dev/null
+++ b/tracker-points/module/CMakeLists.txt
@@ -0,0 +1,6 @@
+find_package(OpenCV QUIET)
+if(OpenCV_FOUND)
+ otr_module(tracker-points)
+ target_link_libraries(${self} opentrack-tracker-points-base)
+ target_include_directories(${self} PUBLIC "${CMAKE_SOURCE_DIR}/tracker-pt")
+endif()
diff --git a/tracker-points/module/Resources/Logo_IR.png b/tracker-points/module/Resources/Logo_IR.png
new file mode 100644
index 00000000..95032a25
--- /dev/null
+++ b/tracker-points/module/Resources/Logo_IR.png
Binary files differ
diff --git a/tracker-points/module/camera.cpp b/tracker-points/module/camera.cpp
new file mode 100644
index 00000000..a70698de
--- /dev/null
+++ b/tracker-points/module/camera.cpp
@@ -0,0 +1,152 @@
+/* Copyright (c) 2012 Patrick Ruoff
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#include "camera.h"
+#include "frame.hpp"
+
+#include "compat/math-imports.hpp"
+
+#include <opencv2/core.hpp>
+
+namespace pt_module {
+
+Camera::Camera(const QString& module_name) : s { module_name }
+{
+}
+
+QString Camera::get_desired_name() const
+{
+ return cam_desired.name;
+}
+
+QString Camera::get_active_name() const
+{
+ return cam_info.name;
+}
+
+void Camera::show_camera_settings()
+{
+ if (cap)
+ (void)cap->show_dialog();
+}
+
+Camera::result Camera::get_info() const
+{
+ if (cam_info.res_x == 0 || cam_info.res_y == 0)
+ return { false, pt_camera_info() };
+ else
+ return { true, cam_info };
+}
+
+Camera::result Camera::get_frame(pt_frame& frame_)
+{
+ cv::Mat& frame = frame_.as<Frame>()->mat;
+
+ const bool new_frame = get_frame_(frame);
+
+ if (new_frame)
+ {
+ const f dt = (f)t.elapsed_seconds();
+ t.start();
+
+ // measure fps of valid frames
+ constexpr f RC = f{1}/10; // seconds
+ const f alpha = dt/(dt + RC);
+
+ if (dt_mean < dt_eps)
+ dt_mean = dt;
+ else
+ dt_mean = (1-alpha) * dt_mean + alpha * dt;
+
+ cam_info.fps = dt_mean > dt_eps ? 1 / dt_mean : 0;
+ cam_info.res_x = frame.cols;
+ cam_info.res_y = frame.rows;
+ cam_info.fov = fov;
+
+ return { true, cam_info };
+ }
+ else
+ return { false, {} };
+}
+
+bool Camera::start(const QString& name, int fps, int res_x, int res_y)
+{
+ if (fps >= 0 && res_x >= 0 && res_y >= 0)
+ {
+ if (cam_desired.name != name ||
+ (int)cam_desired.fps != fps ||
+ cam_desired.res_x != res_x ||
+ cam_desired.res_y != res_y ||
+ !cap || !cap->is_open())
+ {
+ stop();
+
+ cam_desired.name = name;
+ cam_desired.fps = fps;
+ cam_desired.res_x = res_x;
+ cam_desired.res_y = res_y;
+ cam_desired.fov = fov;
+
+ cap = video::make_camera(name);
+
+ if (!cap)
+ goto fail;
+
+ camera::info info {};
+ info.fps = fps;
+ info.width = res_x;
+ info.height = res_y;
+
+ if (!cap->start(info))
+ goto fail;
+
+ cam_info = pt_camera_info();
+ cam_info.name = name;
+ dt_mean = 0;
+
+ cv::Mat tmp;
+
+ if (!get_frame_(tmp))
+ goto fail;
+
+ t.start();
+ }
+ }
+
+ return true;
+
+fail:
+ stop();
+ return false;
+}
+
+void Camera::stop()
+{
+ cap = nullptr;
+ cam_info = {};
+ cam_desired = {};
+}
+
+bool Camera::get_frame_(cv::Mat& img)
+{
+ if (cap && cap->is_open())
+ {
+ auto [ frame, ret ] = cap->get_frame();
+ if (ret)
+ {
+ int stride = frame.stride;
+ if (stride == 0)
+ stride = cv::Mat::AUTO_STEP;
+ img = cv::Mat(frame.height, frame.width, CV_8UC(frame.channels), (void*)frame.data, stride);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // ns pt_module
diff --git a/tracker-points/module/camera.h b/tracker-points/module/camera.h
new file mode 100644
index 00000000..02e2fe4d
--- /dev/null
+++ b/tracker-points/module/camera.h
@@ -0,0 +1,55 @@
+/* Copyright (c) 2012 Patrick Ruoff
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#pragma once
+
+#include "pt-api.hpp"
+#include "compat/timer.hpp"
+#include "video/camera.hpp"
+
+#include <memory>
+
+#include <opencv2/core.hpp>
+
+#include <QString>
+
+namespace pt_module {
+
+struct Camera final : pt_camera
+{
+ Camera(const QString& module_name);
+
+ bool start(const QString& name, int fps, int res_x, int res_y) override;
+ void stop() override;
+
+ result get_frame(pt_frame& Frame) override;
+ result get_info() const override;
+
+ pt_camera_info get_desired() const override { return cam_desired; }
+ QString get_desired_name() const override;
+ QString get_active_name() const override;
+
+ void set_fov(f value) override { fov = value; }
+ void show_camera_settings() override;
+
+private:
+ using camera = typename video::impl::camera;
+
+ [[nodiscard]] bool get_frame_(cv::Mat& frame);
+
+ f dt_mean = 0, fov = 30;
+ Timer t;
+ pt_camera_info cam_info;
+ pt_camera_info cam_desired;
+
+ std::unique_ptr<camera> cap;
+ pt_settings s;
+
+ static constexpr f dt_eps = f{1}/256;
+};
+
+} // ns pt_module
diff --git a/tracker-points/module/export.hpp b/tracker-points/module/export.hpp
new file mode 100644
index 00000000..a733c9fe
--- /dev/null
+++ b/tracker-points/module/export.hpp
@@ -0,0 +1,11 @@
+// generates export.hpp for each module from compat/linkage.hpp
+
+#pragma once
+
+#include "compat/linkage-macros.hpp"
+
+#ifdef BUILD_TRACKER_PT
+# define OTR_PT_EXPORT OTR_GENERIC_EXPORT
+#else
+# define OTR_PT_EXPORT OTR_GENERIC_IMPORT
+#endif
diff --git a/tracker-points/module/frame.cpp b/tracker-points/module/frame.cpp
new file mode 100644
index 00000000..a045b783
--- /dev/null
+++ b/tracker-points/module/frame.cpp
@@ -0,0 +1,80 @@
+#include "frame.hpp"
+
+#include "compat/math.hpp"
+
+#include <opencv2/imgproc.hpp>
+
+namespace pt_module {
+
+Preview& Preview::operator=(const pt_frame& frame_)
+{
+ const cv::Mat& frame = frame_.as_const<const Frame>()->mat;
+
+ if (frame.channels() != 3)
+ {
+ eval_once(qDebug() << "tracker/pt: camera frame depth: 3 !=" << frame.channels());
+ return *this;
+ }
+
+ const bool need_resize = frame.cols != frame_out.cols || frame.rows != frame_out.rows;
+ if (need_resize)
+ cv::resize(frame, frame_copy, cv::Size(frame_out.cols, frame_out.rows), 0, 0, cv::INTER_NEAREST);
+ else
+ frame.copyTo(frame_copy);
+
+ return *this;
+}
+
+Preview::Preview(int w, int h)
+{
+ ensure_size(frame_out, w, h, CV_8UC4);
+ ensure_size(frame_copy, w, h, CV_8UC3);
+
+ frame_copy.setTo(cv::Scalar(0, 0, 0));
+}
+
+QImage Preview::get_bitmap()
+{
+ int stride = frame_out.step.p[0];
+
+ if (stride < 64 || stride < frame_out.cols * 4)
+ {
+ eval_once(qDebug() << "bad stride" << stride
+ << "for bitmap size" << frame_copy.cols << frame_copy.rows);
+ return QImage();
+ }
+
+ cv::cvtColor(frame_copy, frame_out, cv::COLOR_BGR2BGRA);
+
+ return QImage((const unsigned char*) frame_out.data,
+ frame_out.cols, frame_out.rows,
+ stride,
+ QImage::Format_ARGB32);
+}
+
+void Preview::draw_head_center(f x, f y)
+{
+ auto [px_, py_] = to_pixel_pos(x, y, frame_copy.cols, frame_copy.rows);
+
+ int px = iround(px_), py = iround(py_);
+
+ constexpr int len = 9;
+
+ static const cv::Scalar color(0, 255, 255);
+ cv::line(frame_copy,
+ cv::Point(px - len, py),
+ cv::Point(px + len, py),
+ color, 1);
+ cv::line(frame_copy,
+ cv::Point(px, py - len),
+ cv::Point(px, py + len),
+ color, 1);
+}
+
+void Preview::ensure_size(cv::Mat& frame, int w, int h, int type)
+{
+ if (frame.cols != w || frame.rows != h)
+ frame = cv::Mat(h, w, type);
+}
+
+} // ns pt_module
diff --git a/tracker-points/module/frame.hpp b/tracker-points/module/frame.hpp
new file mode 100644
index 00000000..89334599
--- /dev/null
+++ b/tracker-points/module/frame.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "pt-api.hpp"
+
+#include <opencv2/core.hpp>
+#include <QImage>
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wweak-vtables"
+#endif
+
+namespace pt_module {
+
+struct Frame final : pt_frame
+{
+ cv::Mat mat;
+
+ operator const cv::Mat&() const& { return mat; }
+ operator cv::Mat&() & { return mat; }
+};
+
+struct Preview final : pt_preview
+{
+ Preview(int w, int h);
+
+ Preview& operator=(const pt_frame& frame) override;
+ QImage get_bitmap() override;
+ void draw_head_center(f x, f y) override;
+
+ operator cv::Mat&() { return frame_copy; }
+ operator cv::Mat const&() const { return frame_copy; }
+
+private:
+ static void ensure_size(cv::Mat& frame, int w, int h, int type);
+
+ cv::Mat frame_copy, frame_out;
+};
+
+} // ns pt_module
+
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
diff --git a/tracker-points/module/lang/nl_NL.ts b/tracker-points/module/lang/nl_NL.ts
new file mode 100644
index 00000000..aaaead85
--- /dev/null
+++ b/tracker-points/module/lang/nl_NL.ts
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>Points Tracker 0.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-points/module/lang/ru_RU.ts b/tracker-points/module/lang/ru_RU.ts
new file mode 100644
index 00000000..b71de82c
--- /dev/null
+++ b/tracker-points/module/lang/ru_RU.ts
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>Points Tracker 0.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-points/module/lang/stub.ts b/tracker-points/module/lang/stub.ts
new file mode 100644
index 00000000..9a27c78c
--- /dev/null
+++ b/tracker-points/module/lang/stub.ts
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>Points Tracker 0.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-points/module/lang/zh_CN.ts b/tracker-points/module/lang/zh_CN.ts
new file mode 100644
index 00000000..9a27c78c
--- /dev/null
+++ b/tracker-points/module/lang/zh_CN.ts
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+ <name>pt_module::metadata_pt</name>
+ <message>
+ <source>Points Tracker 0.1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/tracker-points/module/module.cpp b/tracker-points/module/module.cpp
new file mode 100644
index 00000000..06cd003b
--- /dev/null
+++ b/tracker-points/module/module.cpp
@@ -0,0 +1,72 @@
+#include "ftnoir_tracker_pt.h"
+
+#include "module.hpp"
+#include "camera.h"
+#include "frame.hpp"
+#include "point_extractor.h"
+#include "ftnoir_tracker_pt_dialog.h"
+
+#include "pt-api.hpp"
+
+#include <memory>
+
+static const QString module_name = "tracker-pt";
+
+#ifdef __clang__
+# pragma clang diagnostic ignored "-Wweak-vtables"
+#endif
+
+namespace pt_module {
+
+struct pt_module_traits final : pt_runtime_traits
+{
+ pointer<pt_camera> make_camera() const override
+ {
+ return pointer<pt_camera>(new Camera(module_name));
+ }
+
+ pointer<pt_point_extractor> make_point_extractor() const override
+ {
+ return pointer<pt_point_extractor>(new PointExtractor(module_name));
+ }
+
+ QString get_module_name() const override
+ {
+ return module_name;
+ }
+
+ pointer<pt_frame> make_frame() const override
+ {
+ return pointer<pt_frame>(new Frame);
+ }
+
+ pointer<pt_preview> make_preview(int w, int h) const override
+ {
+ return pointer<pt_preview>(new Preview(w, h));
+ }
+};
+
+struct tracker_pt : Tracker_PT
+{
+ tracker_pt() : Tracker_PT(pointer<pt_runtime_traits>(new pt_module_traits))
+ {
+ }
+};
+
+struct dialog_pt : TrackerDialog_PT
+{
+ dialog_pt();
+};
+
+dialog_pt::dialog_pt() : TrackerDialog_PT(module_name) {}
+
+QString metadata_pt::name() { return tr("Points Tracker 0.1"); }
+QIcon metadata_pt::icon() { return QIcon(":/Resources/Logo_IR.png"); }
+
+}
+
+// ns pt_module
+
+using namespace pt_module;
+
+OPENTRACK_DECLARE_TRACKER(tracker_pt, dialog_pt, metadata_pt)
diff --git a/tracker-points/module/module.hpp b/tracker-points/module/module.hpp
new file mode 100644
index 00000000..0b3f12cf
--- /dev/null
+++ b/tracker-points/module/module.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "api/plugin-api.hpp"
+#include <QIcon>
+#include <QString>
+
+#include "compat/linkage-macros.hpp"
+
+namespace pt_module
+{
+
+class OTR_GENERIC_EXPORT metadata_pt : public Metadata
+{
+ Q_OBJECT
+
+ QString name() override;
+ QIcon icon() override;
+};
+
+} // ns pt_module
diff --git a/tracker-points/module/point_extractor.cpp b/tracker-points/module/point_extractor.cpp
new file mode 100644
index 00000000..1a75a3e3
--- /dev/null
+++ b/tracker-points/module/point_extractor.cpp
@@ -0,0 +1,387 @@
+/* Copyright (c) 2012 Patrick Ruoff
+ * Copyright (c) 2015-2017 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#include "point_extractor.h"
+#include "point_tracker.h"
+#include "frame.hpp"
+
+#include "cv/numeric.hpp"
+#include "compat/math.hpp"
+
+#undef PREVIEW
+//#define PREVIEW
+
+#if defined PREVIEW
+# include <opencv2/highgui.hpp>
+#endif
+
+#include <cmath>
+#include <algorithm>
+#include <cinttypes>
+#include <memory>
+
+#include <QDebug>
+
+using namespace numeric_types;
+
+// meanshift code written by Michael Welter
+
+/*
+http://en.wikipedia.org/wiki/Mean-shift
+In this application the idea, is to eliminate any bias of the point estimate
+which is introduced by the rather arbitrary thresholded area. One must recognize
+that the thresholded area can only move in one pixel increments since it is
+binary. Thus, its center of mass might make "jumps" as pixels are added/removed
+from the thresholded area.
+With mean-shift, a moving "window" or kernel is multiplied with the gray-scale
+image, and the COM is calculated of the result. This is iterated where the
+kernel center is set the previously computed COM. Thus, peaks in the image intensity
+distribution "pull" the kernel towards themselves. Eventually it stops moving, i.e.
+then the computed COM coincides with the kernel center. We hope that the
+corresponding location is a good candidate for the extracted point.
+The idea similar to the window scaling suggested in Berglund et al. "Fast, bias-free
+algorithm for tracking single particles with variable size and shape." (2008).
+*/
+static vec2 MeanShiftIteration(const cv::Mat1b &frame_gray, const vec2 &current_center, f filter_width)
+{
+ const f s = 1 / filter_width;
+
+ f m = 0;
+ vec2 com { 0, 0 };
+ for (int i = 0; i < frame_gray.rows; i++)
+ {
+ uint8_t const* const __restrict frame_ptr = frame_gray.ptr(i);
+ for (int j = 0; j < frame_gray.cols; j++)
+ {
+ f val = frame_ptr[j];
+ val = val * val; // taking the square weighs brighter parts of the image stronger.
+ f dx = (j - current_center[0])*s;
+ f dy = (i - current_center[1])*s;
+ f max = std::fmax(f(0), 1 - dx*dx - dy*dy);
+ val *= max;
+ m += val;
+ com[0] += j * val;
+ com[1] += i * val;
+ }
+ }
+ if (m > f(.1))
+ {
+ com *= 1 / m;
+ return com;
+ }
+ else
+ return current_center;
+}
+
+namespace pt_module {
+
+PointExtractor::PointExtractor(const QString& module_name) : s(module_name)
+{
+ blobs.reserve(max_blobs);
+}
+
+void PointExtractor::ensure_channel_buffers(const cv::Mat& orig_frame)
+{
+ if (ch[0].rows != orig_frame.rows || ch[0].cols != orig_frame.cols)
+ for (cv::Mat1b& x : ch)
+ x = cv::Mat1b(orig_frame.rows, orig_frame.cols);
+}
+
+void PointExtractor::ensure_buffers(const cv::Mat& frame)
+{
+ const int W = frame.cols, H = frame.rows;
+
+ if (frame_gray.rows != W || frame_gray.cols != H)
+ {
+ frame_gray = cv::Mat1b(H, W);
+ frame_bin = cv::Mat1b(H, W);
+ frame_gray_unmasked = cv::Mat1b(H, W);
+ }
+}
+
+void PointExtractor::extract_single_channel(const cv::Mat& orig_frame, int idx, cv::Mat1b& dest)
+{
+ ensure_channel_buffers(orig_frame);
+
+ const int from_to[] = {
+ idx, 0,
+ };
+
+ cv::mixChannels(&orig_frame, 1, &dest, 1, from_to, 1);
+}
+
+void PointExtractor::color_to_grayscale(const cv::Mat& frame, cv::Mat1b& output)
+{
+ switch (s.blob_color)
+ {
+ case pt_color_green_only:
+ {
+ extract_single_channel(frame, 1, output);
+ break;
+ }
+ case pt_color_blue_only:
+ {
+ extract_single_channel(frame, 0, output);
+ break;
+ }
+ case pt_color_red_only:
+ {
+ extract_single_channel(frame, 2, output);
+ break;
+ }
+ case pt_color_average:
+ {
+ const int W = frame.cols, H = frame.rows, sz = W*H;
+ cv::reduce(frame.reshape(1, sz),
+ output.reshape(1, sz),
+ 1, cv::REDUCE_AVG);
+ break;
+ }
+ default:
+ eval_once(qDebug() << "wrong pt_color_type enum value" << int(s.blob_color));
+ [[fallthrough]];
+ case pt_color_natural:
+ cv::cvtColor(frame, output, cv::COLOR_BGR2GRAY);
+ break;
+ }
+}
+
+void PointExtractor::threshold_image(const cv::Mat& frame_gray, cv::Mat1b& output)
+{
+ const int threshold_slider_value = s.threshold_slider.to<int>();
+
+ if (!s.auto_threshold)
+ {
+ cv::threshold(frame_gray, output, threshold_slider_value, 255, cv::THRESH_BINARY);
+ }
+ else
+ {
+ const int hist_size = 256;
+ const float ranges_[] = { 0, 256 };
+ float const* ranges = (const float*) ranges_;
+
+ cv::calcHist(&frame_gray,
+ 1,
+ nullptr,
+ cv::noArray(),
+ hist,
+ 1,
+ &hist_size,
+ &ranges);
+
+ const f radius = threshold_radius_value(frame_gray.cols, frame_gray.rows, threshold_slider_value);
+
+ float const* const __restrict ptr = hist.ptr<float>(0);
+ const unsigned area = uround(3 * pi * radius*radius);
+ const unsigned sz = unsigned(hist.cols * hist.rows);
+ constexpr unsigned min_thres = 64;
+ unsigned thres = min_thres;
+ for (unsigned i = sz-1, cnt = 0; i > 32; i--)
+ {
+ cnt += (unsigned)ptr[i];
+ if (cnt >= area)
+ break;
+ thres = i;
+ }
+
+ cv::threshold(frame_gray, output, thres, 255, cv::THRESH_BINARY);
+ }
+}
+
+static void draw_blobs(cv::Mat& preview_frame, const blob* blobs, unsigned nblobs, const cv::Size& size)
+{
+ for (unsigned k = 0; k < nblobs; k++)
+ {
+ const blob& b = blobs[k];
+
+ if (b.radius < 0)
+ continue;
+
+ const f dpi = preview_frame.cols / f(320);
+ const f offx = 10 * dpi, offy = f(7.5) * dpi;
+
+ const f cx = preview_frame.cols / f(size.width),
+ cy = preview_frame.rows / f(size.height),
+ c = std::fmax(f(1), cx+cy)/2;
+
+ constexpr unsigned fract_bits = 8;
+ constexpr int c_fract(1 << fract_bits);
+
+ cv::Point p(iround(b.pos[0] * cx * c_fract), iround(b.pos[1] * cy * c_fract));
+
+ auto circle_color = k >= PointModel::N_POINTS
+ ? cv::Scalar(192, 192, 192)
+ : cv::Scalar(255, 255, 0);
+
+ const int overlay_size = iround(dpi);
+
+ cv::circle(preview_frame, p, iround((b.radius + f(3.3) * c) * c_fract),
+ circle_color, overlay_size,
+ cv::LINE_AA, fract_bits);
+
+ char buf[16];
+ buf[sizeof(buf)-1] = '\0';
+ std::snprintf(buf, sizeof(buf) - 1, "%.2fpx", (double)b.radius);
+
+ auto text_color = k >= PointModel::N_POINTS
+ ? cv::Scalar(160, 160, 160)
+ : cv::Scalar(0, 0, 255);
+
+ cv::Point pos(iround(b.pos[0]*cx+offx), iround(b.pos[1]*cy+offy));
+ cv::putText(preview_frame, buf, pos,
+ cv::FONT_HERSHEY_PLAIN, overlay_size, text_color,
+ 1);
+ }
+}
+
+void PointExtractor::extract_points(const pt_frame& frame_, pt_preview& preview_frame_, std::vector<vec2>& points)
+{
+ const cv::Mat& frame = frame_.as_const<Frame>()->mat;
+
+ ensure_buffers(frame);
+ color_to_grayscale(frame, frame_gray_unmasked);
+
+#if defined PREVIEW
+ cv::imshow("capture", frame_gray);
+ cv::waitKey(1);
+#endif
+
+ threshold_image(frame_gray_unmasked, frame_bin);
+ frame_gray_unmasked.copyTo(frame_gray, frame_bin);
+
+ const f region_size_min = (f)s.min_point_size;
+ const f region_size_max = (f)s.max_point_size;
+
+ unsigned idx = 0;
+
+ blobs.clear();
+
+ for (int y=0; y < frame_bin.rows; y++)
+ {
+ const unsigned char* __restrict ptr_bin = frame_bin.ptr(y);
+ for (int x=0; x < frame_bin.cols; x++)
+ {
+ if (ptr_bin[x] != 255)
+ continue;
+ idx = blobs.size() + 1;
+
+ cv::Rect rect;
+ cv::floodFill(frame_bin,
+ cv::Point(x,y),
+ cv::Scalar(idx),
+ &rect,
+ cv::Scalar(0),
+ cv::Scalar(0),
+ 4 | cv::FLOODFILL_FIXED_RANGE);
+
+ unsigned cnt = 0;
+ unsigned norm = 0;
+
+ const int ymax = rect.y+rect.height,
+ xmax = rect.x+rect.width;
+
+ for (int i=rect.y; i < ymax; i++)
+ {
+ unsigned char const* const __restrict ptr_blobs = frame_bin.ptr(i);
+ unsigned char const* const __restrict ptr_gray = frame_gray.ptr(i);
+ for (int j=rect.x; j < xmax; j++)
+ {
+ if (ptr_blobs[j] != idx)
+ continue;
+
+ //ptr_blobs[j] = 0;
+ norm += ptr_gray[j];
+ cnt++;
+ }
+ }
+
+ const f radius = std::sqrt(cnt / pi);
+ if (radius > region_size_max || radius < region_size_min)
+ continue;
+
+ blobs.emplace_back(radius,
+ vec2(rect.width/f(2), rect.height/f(2)),
+ std::pow(f(norm), f(1.1))/cnt,
+ rect);
+
+ if (idx >= max_blobs)
+ goto end;
+
+ // XXX we could go to the next scanline unless the points are really small.
+ // i'd expect each point being present on at least one unique scanline
+ // but it turns out some people are using 2px points -sh 20180110
+ //break;
+ }
+ }
+end:
+
+ const int W = frame_gray.cols;
+ const int H = frame_gray.rows;
+
+ const unsigned sz = blobs.size();
+
+ std::sort(blobs.begin(), blobs.end(), [](const blob& b1, const blob& b2) { return b2.brightness < b1.brightness; });
+
+ for (idx = 0; idx < sz; ++idx)
+ {
+ blob& b = blobs[idx];
+ cv::Rect rect = b.rect;
+
+ rect.x -= rect.width / 2;
+ rect.y -= rect.height / 2;
+ rect.width *= 2;
+ rect.height *= 2;
+ rect &= cv::Rect(0, 0, W, H); // crop at frame boundaries
+
+ cv::Mat frame_roi = frame_gray(rect);
+
+ // smaller values mean more changes. 1 makes too many changes while 1.5 makes about .1
+ static constexpr f radius_c = f(1.75);
+
+ const f kernel_radius = b.radius * radius_c;
+ vec2 pos(rect.width/f(2), rect.height/f(2)); // position relative to ROI.
+
+ for (int iter = 0; iter < 10; ++iter)
+ {
+ vec2 com_new = MeanShiftIteration(frame_roi, pos, kernel_radius);
+ vec2 delta = com_new - pos;
+ pos = com_new;
+ if (delta.dot(delta) < f(1e-3))
+ break;
+ }
+
+ b.pos[0] = pos[0] + rect.x;
+ b.pos[1] = pos[1] + rect.y;
+ }
+
+ draw_blobs(preview_frame_.as<Frame>()->mat,
+ blobs.data(), blobs.size(),
+ frame_gray.size());
+
+
+ // End of mean shift code. At this point, blob positions are updated with hopefully less noisy less biased values.
+ points.reserve(max_blobs);
+ points.clear();
+
+ for (const auto& b : blobs)
+ {
+ // note: H/W is equal to fx/fy
+
+ vec2 p;
+ std::tie(p[0], p[1]) = to_screen_pos(b.pos[0], b.pos[1], W, H);
+ points.push_back(p);
+ }
+}
+
+blob::blob(f radius, const vec2& pos, f brightness, const cv::Rect& rect) :
+ radius(radius), brightness(brightness), pos(pos), rect(rect)
+{
+ //qDebug() << "radius" << radius << "pos" << pos[0] << pos[1];
+}
+
+} // ns pt_module
diff --git a/tracker-points/module/point_extractor.h b/tracker-points/module/point_extractor.h
new file mode 100644
index 00000000..a6103667
--- /dev/null
+++ b/tracker-points/module/point_extractor.h
@@ -0,0 +1,58 @@
+/* Copyright (c) 2012 Patrick Ruoff
+ * Copyright (c) 2015-2016 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#pragma once
+
+#include "pt-api.hpp"
+
+#include <vector>
+
+#include <opencv2/core.hpp>
+#include <opencv2/imgproc.hpp>
+
+namespace pt_module {
+
+using namespace numeric_types;
+
+struct blob final
+{
+ f radius, brightness;
+ vec2 pos;
+ cv::Rect rect;
+
+ blob(f radius, const vec2& pos, f brightness, const cv::Rect& rect);
+};
+
+class PointExtractor final : public pt_point_extractor
+{
+public:
+ // extracts points from frame and draws some processing info into frame, if draw_output is set
+ // dt: time since last call in seconds
+ void extract_points(const pt_frame& frame, pt_preview& preview_frame, std::vector<vec2>& points) override;
+ PointExtractor(const QString& module_name);
+private:
+ static constexpr int max_blobs = 16;
+
+ pt_settings s;
+
+ cv::Mat1b frame_gray_unmasked, frame_bin, frame_gray;
+ cv::Mat1f hist;
+ std::vector<blob> blobs;
+ cv::Mat1b ch[3];
+
+ void ensure_channel_buffers(const cv::Mat& orig_frame);
+ void ensure_buffers(const cv::Mat& frame);
+
+ void extract_single_channel(const cv::Mat& orig_frame, int idx, cv::Mat1b& dest);
+
+ void color_to_grayscale(const cv::Mat& frame, cv::Mat1b& output);
+ void threshold_image(const cv::Mat& frame_gray, cv::Mat1b& output);
+};
+
+} // ns impl
+
diff --git a/tracker-points/module/tracker_pt.qrc b/tracker-points/module/tracker_pt.qrc
new file mode 100644
index 00000000..dfeb7369
--- /dev/null
+++ b/tracker-points/module/tracker_pt.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>Resources/Logo_IR.png</file>
+ </qresource>
+</RCC>
diff --git a/tracker-points/point_tracker.cpp b/tracker-points/point_tracker.cpp
new file mode 100644
index 00000000..e209938f
--- /dev/null
+++ b/tracker-points/point_tracker.cpp
@@ -0,0 +1,364 @@
+/* Copyright (c) 2012 Patrick Ruoff
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#include "point_tracker.h"
+#include "compat/math-imports.hpp"
+
+#include <vector>
+#include <algorithm>
+#include <cmath>
+
+#include <QDebug>
+
+namespace pt_impl {
+
+using namespace numeric_types;
+
+static void get_row(const mat33& m, int i, vec3& v)
+{
+ v[0] = m(i,0);
+ v[1] = m(i,1);
+ v[2] = m(i,2);
+}
+
+static void set_row(mat33& m, int i, const vec3& v)
+{
+ m(i,0) = v[0];
+ m(i,1) = v[1];
+ m(i,2) = v[2];
+}
+
+PointModel::PointModel(const pt_settings& s)
+{
+ set_model(s);
+ // calculate u
+ u = M01.cross(M02);
+ u = cv::normalize(u);
+
+ // calculate projection matrix on M01,M02 plane
+ f s11 = M01.dot(M01);
+ f s12 = M01.dot(M02);
+ f s22 = M02.dot(M02);
+ P = 1/(s11*s22-s12*s12) * mat22(s22, -s12, -s12, s11);
+}
+
+void PointModel::set_model(const pt_settings& s)
+{
+ switch (s.active_model_panel)
+ {
+ default:
+ eval_once(qDebug() << "pt: wrong model type selected");
+ [[fallthrough]];
+ case Clip:
+ M01 = vec3(0, s.clip_ty, -s.clip_tz);
+ M02 = vec3(0, -s.clip_by, -s.clip_bz);
+ break;
+ case Cap:
+ M01 = vec3(-s.cap_x, -s.cap_y, -s.cap_z);
+ M02 = vec3(s.cap_x, -s.cap_y, -s.cap_z);
+ break;
+ case Custom:
+ M01 = vec3(s.m01_x, s.m01_y, s.m01_z);
+ M02 = vec3(s.m02_x, s.m02_y, s.m02_z);
+ break;
+ }
+}
+
+void PointModel::get_d_order(const vec2* points, unsigned* d_order, const vec2& d) const
+{
+ constexpr unsigned cnt = PointModel::N_POINTS;
+ // fit line to orthographically projected points
+ using t = std::pair<f,unsigned>;
+ t d_vals[cnt];
+ // get sort indices with respect to d scalar product
+ for (unsigned i = 0; i < cnt; ++i)
+ d_vals[i] = t(d.dot(points[i]), i);
+
+ std::sort(d_vals,
+ d_vals + 3,
+ [](const t& a, const t& b) { return a.first < b.first; });
+
+ for (unsigned i = 0; i < cnt; ++i)
+ d_order[i] = d_vals[i].second;
+}
+
+
+PointTracker::PointTracker() = default;
+
+PointTracker::PointOrder PointTracker::find_correspondences_previous(const vec2* points,
+ const PointModel& model,
+ const pt_camera_info& info)
+{
+ const f fx = pt_camera_info::get_focal_length(info.fov, info.res_x, info.res_y);
+ PointTracker::PointOrder p;
+ p[0] = project(vec3(0,0,0), fx);
+ p[1] = project(model.M01, fx);
+ p[2] = project(model.M02, fx);
+
+ constexpr unsigned sz = PointModel::N_POINTS;
+
+ // set correspondences by minimum distance to projected model point
+ bool point_taken[sz] {};
+
+ for (unsigned i=0; i < sz; ++i)
+ {
+ f min_sdist = 0;
+ unsigned min_idx = 0;
+ // find closest point to projected model point i
+ for (unsigned j=0; j < sz; ++j)
+ {
+ vec2 d = p[i]-points[j];
+ f sdist = d.dot(d);
+ if (sdist < min_sdist || j == 0)
+ {
+ min_idx = j;
+ min_sdist = sdist;
+ }
+ }
+
+ // if one point is closest to more than one model point, fallback
+ if (point_taken[min_idx])
+ {
+ reset_state();
+ return find_correspondences(points, model);
+ }
+ point_taken[min_idx] = true;
+ p[i] = points[min_idx];
+ }
+
+ return p;
+}
+
+void PointTracker::track(const std::vector<vec2>& points,
+ const PointModel& model,
+ const pt_camera_info& info,
+ int init_phase_timeout)
+{
+ const f fx = pt_camera_info::get_focal_length(info.fov, info.res_x, info.res_y);
+ PointOrder order;
+
+ if (init_phase || init_phase_timeout <= 0 || t.elapsed_ms() > init_phase_timeout)
+ {
+ reset_state();
+ order = find_correspondences(points.data(), model);
+ }
+ else
+ order = find_correspondences_previous(points.data(), model, info);
+
+ if (POSIT(model, order, fx) != -1)
+ {
+ init_phase = false;
+ t.start();
+ }
+ else
+ reset_state();
+}
+
+PointTracker::PointOrder PointTracker::find_correspondences(const vec2* points, const PointModel& model)
+{
+ constexpr unsigned cnt = PointModel::N_POINTS;
+ // We do a simple freetrack-like sorting in the init phase...
+ unsigned point_d_order[cnt];
+ unsigned model_d_order[cnt];
+ // calculate d and d_order for simple freetrack-like point correspondence
+ vec2 d(model.M01[0]-model.M02[0], model.M01[1]-model.M02[1]);
+ // sort points
+ model.get_d_order(points, point_d_order, d);
+ vec2 pts[cnt] {
+ { 0, 0 },
+ { model.M01[0], model.M01[1] },
+ { model.M02[0], model.M02[1] },
+ };
+ model.get_d_order(pts, model_d_order, d);
+
+ // set correspondences
+ PointOrder p;
+ for (unsigned i = 0; i < cnt; ++i)
+ p[model_d_order[i]] = points[point_d_order[i]];
+
+ return p;
+}
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wfloat-equal"
+#endif
+
+int PointTracker::POSIT(const PointModel& model, const PointOrder& order, f focal_length)
+{
+ // POSIT algorithm for coplanar points as presented in
+ // [Denis Oberkampf, Daniel F. DeMenthon, Larry S. Davis: "Iterative Pose Estimation Using Coplanar Feature Points"]
+ // we use the same notation as in the paper here
+
+ // The expected rotation used for resolving the ambiguity in POSIT:
+ // In every iteration step the rotation closer to R_expected is taken
+ const mat33& R_expected{X_CM_expected.R};
+
+ // initial pose = last (predicted) pose
+ vec3 k;
+ get_row(R_expected, 2, k);
+ f Z0 = X_CM.t[2] < f(1e-4) ? f(1000) : X_CM.t[2];
+
+ f old_epsilon_1 = 0;
+ f old_epsilon_2 = 0;
+ f epsilon_1, epsilon_2;
+
+ vec3 I0, J0;
+ vec2 I0_coeff, J0_coeff;
+
+ vec3 I_1, J_1, I_2, J_2;
+ mat33 R_1, R_2;
+ mat33* R_current = &R_1;
+
+ constexpr int max_iter = 100;
+
+ int i;
+ for (i = 1; i < max_iter; ++i)
+ {
+ epsilon_1 = k.dot(model.M01)/Z0;
+ epsilon_2 = k.dot(model.M02)/Z0;
+
+ // vector of scalar products <I0, M0i> and <J0, M0i>
+ vec2 I0_M0i(order[1][0]*(1 + epsilon_1) - order[0][0],
+ order[2][0]*(1 + epsilon_2) - order[0][0]);
+ vec2 J0_M0i(order[1][1]*(1 + epsilon_1) - order[0][1],
+ order[2][1]*(1 + epsilon_2) - order[0][1]);
+
+ // construct projection of I, J onto M0i plane: I0 and J0
+ I0_coeff = model.P * I0_M0i;
+ J0_coeff = model.P * J0_M0i;
+ I0 = I0_coeff[0]*model.M01 + I0_coeff[1]*model.M02;
+ J0 = J0_coeff[0]*model.M01 + J0_coeff[1]*model.M02;
+
+ // calculate u component of I, J
+ f II0 = I0.dot(I0);
+ f IJ0 = I0.dot(J0);
+ f JJ0 = J0.dot(J0);
+ f rho, theta;
+ // CAVEAT don't change to comparison with an epsilon -sh 20160423
+ if (JJ0 == II0) {
+ rho = sqrt(fabs(2*IJ0));
+ theta = -pi/4;
+ if (IJ0<0) theta *= -1;
+ }
+ else {
+ rho = sqrt(sqrt( (JJ0-II0)*(JJ0-II0) + 4*IJ0*IJ0 ));
+ theta = atan( -2*IJ0 / (JJ0-II0) );
+ // avoid branch misprediction
+ theta += (JJ0 - II0 < 0) * pi;
+ theta *= f(.5);
+ }
+
+ // construct the two solutions
+ I_1 = I0 + rho*cos(theta)*model.u;
+ I_2 = I0 - rho*cos(theta)*model.u;
+
+ J_1 = J0 + rho*sin(theta)*model.u;
+ J_2 = J0 - rho*sin(theta)*model.u;
+
+ f norm_const = (f)(1/cv::norm(I_1)); // all have the same norm
+
+ // create rotation matrices
+ I_1 *= norm_const; J_1 *= norm_const;
+ I_2 *= norm_const; J_2 *= norm_const;
+
+ set_row(R_1, 0, I_1);
+ set_row(R_1, 1, J_1);
+ set_row(R_1, 2, I_1.cross(J_1));
+
+ set_row(R_2, 0, I_2);
+ set_row(R_2, 1, J_2);
+ set_row(R_2, 2, I_2.cross(J_2));
+
+ // the single translation solution
+ Z0 = norm_const * focal_length;
+
+ // pick the rotation solution closer to the expected one
+ // in simple metric d(A,B) = || I - A * B^T ||
+ f R_1_deviation = (f)(cv::norm(mat33::eye() - R_expected * R_1.t()));
+ f R_2_deviation = (f)(cv::norm(mat33::eye() - R_expected * R_2.t()));
+
+ if (R_1_deviation < R_2_deviation)
+ R_current = &R_1;
+ else
+ R_current = &R_2;
+
+ get_row(*R_current, 2, k);
+
+ // check for convergence condition
+ const f delta = fabs(epsilon_1 - old_epsilon_1) + fabs(epsilon_2 - old_epsilon_2);
+
+ if (delta < eps)
+ break;
+
+ old_epsilon_1 = epsilon_1;
+ old_epsilon_2 = epsilon_2;
+ }
+
+ const f t[3] = {
+ order[0][0] * Z0/focal_length,
+ order[0][1] * Z0/focal_length,
+ Z0
+ };
+ const mat33& r = *R_current;
+
+ for (int i = 0; i < 3; i++)
+ for (int j = 0; j < 3; j++)
+ {
+ int ret = std::fpclassify(r(i, j));
+ if (ret == FP_NAN || ret == FP_INFINITE)
+ {
+ qDebug() << "posit nan R";
+ return -1;
+ }
+ }
+
+ for (unsigned i = 0; i < 3; i++) // NOLINT(modernize-loop-convert)
+ {
+ int ret = std::fpclassify(t[i]);
+ if (ret == FP_NAN || ret == FP_INFINITE)
+ {
+ qDebug() << "posit nan T";
+ return -1;
+ }
+ }
+
+ // apply results
+ X_CM.R = r;
+ X_CM.t[0] = t[0];
+ X_CM.t[1] = t[1];
+ X_CM.t[2] = t[2];
+
+ X_CM_expected = X_CM;
+
+ //qDebug() << "iter:" << i;
+
+ return i;
+}
+
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
+
+vec2 PointTracker::project(const vec3& v_M, f focal_length)
+{
+ return project(v_M, focal_length, X_CM);
+}
+
+vec2 PointTracker::project(const vec3& v_M, f focal_length, const Affine& X_CM)
+{
+ vec3 v_C = X_CM * v_M;
+ return vec2(focal_length*v_C[0]/v_C[2], focal_length*v_C[1]/v_C[2]);
+}
+
+void PointTracker::reset_state()
+{
+ init_phase = true;
+ X_CM_expected = {};
+}
+
+} // ns pt_impl
diff --git a/tracker-points/point_tracker.h b/tracker-points/point_tracker.h
new file mode 100644
index 00000000..70c7a9fc
--- /dev/null
+++ b/tracker-points/point_tracker.h
@@ -0,0 +1,88 @@
+/* Copyright (c) 2012 Patrick Ruoff
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#pragma once
+
+#include "compat/timer.hpp"
+#include "cv/affine.hpp"
+#include "cv/numeric.hpp"
+#include "pt-api.hpp"
+
+#include <cstddef>
+#include <memory>
+#include <vector>
+#include <array>
+
+#include <opencv2/core.hpp>
+
+#include <QObject>
+
+namespace pt_impl {
+
+// ----------------------------------------------------------------------------
+// Describes a 3-point model
+// nomenclature as in
+// [Denis Oberkampf, Daniel F. DeMenthon, Larry S. Davis: "Iterative Pose Estimation Using Coplanar Feature Points"]
+
+using namespace numeric_types;
+
+struct PointModel final
+{
+ static constexpr unsigned N_POINTS = 3;
+
+ vec3 M01; // M01 in model frame
+ vec3 M02; // M02 in model frame
+
+ vec3 u; // unit vector perpendicular to M01,M02-plane
+
+ mat22 P;
+
+ enum Model { Clip, Cap, Custom };
+
+ explicit PointModel(const pt_settings& s);
+ void set_model(const pt_settings& s);
+
+ void get_d_order(const vec2* points, unsigned* d_order, const vec2& d) const;
+};
+
+// ----------------------------------------------------------------------------
+// Tracks a 3-point model
+// implementing the POSIT algorithm for coplanar points as presented in
+// [Denis Oberkampf, Daniel F. DeMenthon, Larry S. Davis: "Iterative Pose Estimation Using Coplanar Feature Points"]
+class PointTracker final
+{
+public:
+ PointTracker();
+ // track the pose using the set of normalized point coordinates (x pos in range -0.5:0.5)
+ // f : (focal length)/(sensor width)
+ // dt : time since last call
+ void track(const std::vector<vec2>& projected_points, const PointModel& model, const pt_camera_info& info, int init_phase_timeout);
+ Affine pose() const { return X_CM; }
+ vec2 project(const vec3& v_M, f focal_length);
+ vec2 project(const vec3& v_M, f focal_length, const Affine& X_CM);
+ void reset_state();
+
+private:
+ // the points in model order
+ using PointOrder = std::array<vec2, 3>;
+
+ PointOrder find_correspondences(const vec2* projected_points, const PointModel &model);
+ PointOrder find_correspondences_previous(const vec2* points, const PointModel &model, const pt_camera_info& info);
+ // The POSIT algorithm, returns the number of iterations
+ int POSIT(const PointModel& point_model, const PointOrder& order, f focal_length);
+
+ Affine X_CM; // transform from model to camera
+ Affine X_CM_expected;
+ PointOrder prev_positions;
+ Timer t;
+ bool init_phase = true;
+};
+
+} // ns pt_impl
+
+using PointTracker = pt_impl::PointTracker;
+using PointModel = pt_impl::PointModel;
diff --git a/tracker-points/pt-api.cpp b/tracker-points/pt-api.cpp
new file mode 100644
index 00000000..f64d5c9a
--- /dev/null
+++ b/tracker-points/pt-api.cpp
@@ -0,0 +1,54 @@
+#include "pt-api.hpp"
+#include "cv/numeric.hpp"
+
+using namespace numeric_types;
+
+pt_camera_info::pt_camera_info() = default;
+
+f pt_camera_info::get_focal_length(f fov, int res_x, int res_y)
+{
+ const f diag_len = std::sqrt(f(res_x*res_x + res_y*res_y));
+ const f aspect_x = res_x / diag_len;
+ //const double aspect_y = res_y / diag_len;
+ const f diag_fov = fov * pi/180;
+ const f fov_x = 2*std::atan(std::tan(diag_fov*f{.5}) * aspect_x);
+ //const double fov_y = 2*atan(tan(diag_fov*.5) * aspect_y);
+ const f fx = f{.5} / std::tan(fov_x * f{.5});
+ return fx;
+ //fy = .5 / tan(fov_y * .5);
+ //static bool once = false; if (!once) { once = true; qDebug() << "f" << ret << "fov" << (fov * 180/M_PI); }
+}
+
+pt_camera::pt_camera() = default;
+pt_camera::~pt_camera() = default;
+pt_runtime_traits::pt_runtime_traits() = default;
+pt_runtime_traits::~pt_runtime_traits() = default;
+pt_point_extractor::pt_point_extractor() = default;
+pt_point_extractor::~pt_point_extractor() = default;
+
+f pt_point_extractor::threshold_radius_value(int w, int h, int threshold)
+{
+ f cx = w / f{640}, cy = h / f{480};
+
+ const f min_radius = f{1.75} * cx;
+ const f max_radius = f{15} * cy;
+
+ const f radius = std::fmax(f{0}, (max_radius-min_radius) * threshold / f(255) + min_radius);
+
+ return radius;
+}
+
+std::tuple<f, f> pt_pixel_pos_mixin::to_pixel_pos(f x, f y, int w, int h)
+{
+ return std::make_tuple(w*(x+f{.5}), f{.5}*(h - 2*y*w));
+}
+
+std::tuple<f, f> pt_pixel_pos_mixin::to_screen_pos(f px, f py, int w, int h)
+{
+ px *= w/(w-f{1}); py *= h/(h-f{1});
+ return std::make_tuple((px - w/f{2})/w, -(py - h/f{2})/w);
+}
+
+pt_frame::pt_frame() = default;
+
+pt_frame::~pt_frame() = default;
diff --git a/tracker-points/pt-api.hpp b/tracker-points/pt-api.hpp
new file mode 100644
index 00000000..741576a1
--- /dev/null
+++ b/tracker-points/pt-api.hpp
@@ -0,0 +1,123 @@
+#pragma once
+
+#include "pt-settings.hpp"
+
+#include "cv/numeric.hpp"
+#include "options/options.hpp"
+
+#include <tuple>
+#include <type_traits>
+#include <memory>
+
+#include <opencv2/core.hpp>
+
+#include <QImage>
+#include <QString>
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wweak-vtables"
+#endif
+
+struct pt_camera_info final
+{
+ using f = numeric_types::f;
+
+ pt_camera_info();
+ static f get_focal_length(f fov, int res_x, int res_y);
+
+ f fov = 0;
+ f fps = 0;
+
+ int res_x = 0;
+ int res_y = 0;
+ QString name;
+};
+
+struct pt_pixel_pos_mixin
+{
+ using f = numeric_types::f;
+
+ static std::tuple<f, f> to_pixel_pos(f x, f y, int w, int h);
+ static std::tuple<f, f> to_screen_pos(f px, f py, int w, int h);
+};
+
+struct pt_frame : pt_pixel_pos_mixin
+{
+ pt_frame();
+ virtual ~pt_frame();
+
+ template<typename t>
+ t* as() &
+ {
+ return static_cast<t*>(this);
+ }
+
+ template<typename t>
+ t const* as_const() const&
+ {
+ return static_cast<t const*>(this);
+ }
+};
+
+struct pt_preview : pt_frame
+{
+ virtual pt_preview& operator=(const pt_frame&) = 0;
+ virtual QImage get_bitmap() = 0;
+ virtual void draw_head_center(f x, f y) = 0;
+};
+
+struct pt_camera
+{
+ using result = std::tuple<bool, pt_camera_info>;
+ using f = numeric_types::f;
+
+ pt_camera();
+ virtual ~pt_camera();
+
+ [[nodiscard]] virtual bool start(const QString& name, int fps, int res_x, int res_y) = 0;
+ virtual void stop() = 0;
+
+ virtual result get_frame(pt_frame& frame) = 0;
+ virtual result get_info() const = 0;
+ virtual pt_camera_info get_desired() const = 0;
+
+ virtual QString get_desired_name() const = 0;
+ virtual QString get_active_name() const = 0;
+
+ virtual void set_fov(f value) = 0;
+ virtual void show_camera_settings() = 0;
+};
+
+struct pt_point_extractor : pt_pixel_pos_mixin
+{
+ using vec2 = numeric_types::vec2;
+ using f = numeric_types::f;
+
+ pt_point_extractor();
+ virtual ~pt_point_extractor();
+ virtual void extract_points(const pt_frame& image, pt_preview& preview_frame, std::vector<vec2>& points) = 0;
+
+ static f threshold_radius_value(int w, int h, int threshold);
+};
+
+struct pt_runtime_traits
+{
+ template<typename t> using pointer = std::shared_ptr<t>;
+
+ pt_runtime_traits();
+ virtual ~pt_runtime_traits();
+
+ virtual pointer<pt_camera> make_camera() const = 0;
+ virtual pointer<pt_point_extractor> make_point_extractor() const = 0;
+ virtual pointer<pt_frame> make_frame() const = 0;
+ virtual pointer<pt_preview> make_preview(int w, int h) const = 0;
+ virtual QString get_module_name() const = 0;
+};
+
+template<typename t>
+using pt_pointer = typename pt_runtime_traits::pointer<t>;
+
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
diff --git a/tracker-points/pt-settings.hpp b/tracker-points/pt-settings.hpp
new file mode 100644
index 00000000..723ee08d
--- /dev/null
+++ b/tracker-points/pt-settings.hpp
@@ -0,0 +1,73 @@
+#pragma once
+
+#include "options/options.hpp"
+
+#include <QString>
+
+enum pt_color_type
+{
+ // explicit values, gotta preserve the numbering in .ini
+ // don't reuse when removing some of the modes
+ pt_color_natural = 2,
+ pt_color_red_only = 3,
+ pt_color_average = 5,
+ pt_color_blue_only = 6,
+ pt_color_green_only = 7,
+};
+
+namespace pt_impl {
+
+using namespace options;
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wweak-vtables"
+#endif
+
+struct pt_settings final : options::opts
+{
+ using slider_value = options::slider_value;
+
+ value<QString> camera_name { b, "camera-name", "" };
+ value<int> cam_res_x { b, "camera-res-width", 640 },
+ cam_res_y { b, "camera-res-height", 480 },
+ cam_fps { b, "camera-fps", 30 };
+ value<double> min_point_size { b, "min-point-size", 2.5 },
+ max_point_size { b, "max-point-size", 50 };
+
+ value<int> m01_x { b, "m_01-x", 0 }, m01_y { b, "m_01-y", 0 }, m01_z { b, "m_01-z", 0 };
+ value<int> m02_x { b, "m_02-x", 0 }, m02_y { b, "m_02-y", 0 }, m02_z { b, "m_02-z", 0 };
+
+ value<int> t_MH_x { b, "model-centroid-x", 0 },
+ t_MH_y { b, "model-centroid-y", 0 },
+ t_MH_z { b, "model-centroid-z", 0 };
+
+ value<int> clip_ty { b, "clip-ty", 40 },
+ clip_tz { b, "clip-tz", 30 },
+ clip_by { b, "clip-by", 70 },
+ clip_bz { b, "clip-bz", 80 };
+
+ value<int> active_model_panel { b, "active-model-panel", 0 },
+ cap_x { b, "cap-x", 40 },
+ cap_y { b, "cap-y", 60 },
+ cap_z { b, "cap-z", 100 };
+
+ value<int> fov { b, "camera-fov", 56 };
+
+ value<bool> dynamic_pose { b, "dynamic-pose-resolution", false };
+ value<int> init_phase_timeout { b, "init-phase-timeout", 250 };
+ value<bool> auto_threshold { b, "automatic-threshold", true };
+ value<pt_color_type> blob_color { b, "blob-color", pt_color_natural };
+
+ value<slider_value> threshold_slider { b, "threshold-slider", { 128, 0, 255 } };
+
+ explicit pt_settings(const QString& name) : opts(name) {}
+};
+
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
+
+} // ns pt_impl
+
+using pt_settings = pt_impl::pt_settings;
diff --git a/tracker-points/tracker_pt_base.qrc b/tracker-points/tracker_pt_base.qrc
new file mode 100644
index 00000000..8c270540
--- /dev/null
+++ b/tracker-points/tracker_pt_base.qrc
@@ -0,0 +1,8 @@
+<RCC>
+ <qresource prefix="/">
+ <file>Resources/cap_front.png</file>
+ <file>Resources/cap_side.png</file>
+ <file>Resources/clip_front.png</file>
+ <file>Resources/clip_side.png</file>
+ </qresource>
+</RCC>