diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2022-05-11 20:42:25 +0200 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2022-05-11 20:45:05 +0200 |
commit | 2b19c6c532c37c2eeb2547cf92f719197957ab72 (patch) | |
tree | 26fb69b756d8ae49fb87a18956eca26dd2c55005 /tracker-trackhat | |
parent | be42a20f03e3ccb1eaa390c072ac72cbe2d9f13b (diff) |
tracker/trackhat: new tracker
Sponsored by: TrackHat
Diffstat (limited to 'tracker-trackhat')
-rw-r--r-- | tracker-trackhat/CMakeLists.txt | 16 | ||||
-rw-r--r-- | tracker-trackhat/camera.cpp | 126 | ||||
-rw-r--r-- | tracker-trackhat/dialog.cpp | 144 | ||||
-rw-r--r-- | tracker-trackhat/dialog.hpp | 36 | ||||
-rw-r--r-- | tracker-trackhat/dialog.ui | 522 | ||||
-rw-r--r-- | tracker-trackhat/extractor.cpp | 21 | ||||
-rw-r--r-- | tracker-trackhat/frame.cpp | 127 | ||||
-rw-r--r-- | tracker-trackhat/handle.cpp | 72 | ||||
-rw-r--r-- | tracker-trackhat/images/trackhat-64x64.png | bin | 0 -> 1430 bytes | |||
-rw-r--r-- | tracker-trackhat/images/trackhat.ico | bin | 0 -> 3758 bytes | |||
-rw-r--r-- | tracker-trackhat/images/trackhat.png | bin | 0 -> 14581 bytes | |||
-rw-r--r-- | tracker-trackhat/lang/nl_NL.ts | 134 | ||||
-rw-r--r-- | tracker-trackhat/lang/ru_RU.ts | 134 | ||||
-rw-r--r-- | tracker-trackhat/lang/stub.ts | 134 | ||||
-rw-r--r-- | tracker-trackhat/lang/zh_CN.ts | 134 | ||||
-rw-r--r-- | tracker-trackhat/metadata.cpp | 39 | ||||
-rw-r--r-- | tracker-trackhat/metadata.hpp | 21 | ||||
-rw-r--r-- | tracker-trackhat/settings.cpp | 147 | ||||
-rw-r--r-- | tracker-trackhat/tracker_trackhat.qrc | 5 | ||||
-rw-r--r-- | tracker-trackhat/trackhat-res.qrc | 5 | ||||
-rw-r--r-- | tracker-trackhat/trackhat.hpp | 180 |
21 files changed, 1997 insertions, 0 deletions
diff --git a/tracker-trackhat/CMakeLists.txt b/tracker-trackhat/CMakeLists.txt new file mode 100644 index 00000000..483bf4c9 --- /dev/null +++ b/tracker-trackhat/CMakeLists.txt @@ -0,0 +1,16 @@ +if(WIN32) + include(opentrack-opencv) + find_package(OpenCV QUIET) + if(OpenCV_FOUND) + foreach(k core imgproc) + otr_install_lib("opencv_${k}" "${opentrack-libexec}") + endforeach() + set(SDK_TRACKHAT_SENSOR CACHE PATH "") + if(SDK_TRACKHAT_SENSOR) + include_directories("${SDK_TRACKHAT_SENSOR}/include" ${OpenCV_INCLUDE_DIRS}) + link_directories("${SDK_TRACKHAT_SENSOR}/lib") + link_libraries(opencv_imgproc opencv_core opentrack-tracker-pt-base track-hat) + otr_module(tracker-trackhat) + endif() + endif() +endif() diff --git a/tracker-trackhat/camera.cpp b/tracker-trackhat/camera.cpp new file mode 100644 index 00000000..dd1f3d3a --- /dev/null +++ b/tracker-trackhat/camera.cpp @@ -0,0 +1,126 @@ +#include "trackhat.hpp" +#include "compat/sleep.hpp" +#include <cstdio> + +namespace trackhat_impl { + +TH_ErrorCode log_error(TH_ErrorCode error, const char* source, + const char* file, int line, const char* function) +{ + if (error == TH_ERROR_DEVICE_ALREADY_OPEN) + error = TH_SUCCESS; + if (error) + { + auto logger = QMessageLogger(file, line, function).warning(); + logger << "tracker/trackhat: error" << (void*)-error << "in" << source; + } + return error; +} + +} // ns trackhat_impl + +pt_camera::result trackhat_camera::get_info() const +{ + return {true, get_desired() }; +} + +pt_camera_info trackhat_camera::get_desired() const +{ + pt_camera_info ret = {}; + + ret.fov = sensor_fov; + ret.fps = 250; + ret.res_x = sensor_size; + ret.res_y = sensor_size; + + return ret; +} + +QString trackhat_camera::get_desired_name() const +{ + return QStringLiteral("TrackHat sensor"); +} + +QString trackhat_camera::get_active_name() const +{ + return get_desired_name(); +} + +void trackhat_camera::set_fov(pt_camera::f) {} +void trackhat_camera::show_camera_settings() {} + +trackhat_camera::trackhat_camera() +{ + s.set_raii_dtor_state(false); + t.set_raii_dtor_state(false); + + for (auto* slider : { &t.exposure, /*&t.threshold,*/ }) + { + QObject::connect(slider, options::value_::value_changed<options::slider_value>(), + &sig, &trackhat_impl::setting_receiver::settings_changed, + Qt::DirectConnection); + } +} + +trackhat_camera::~trackhat_camera() +{ + stop(); +} + +pt_camera::result trackhat_camera::get_frame(pt_frame& frame_) +{ + if (!device.ensure_connected()) + goto error; + + if (sig.test_and_clear() && !init_regs()) + goto error; + + set_pt_options(); + + { + trackHat_ExtendedPoints_t points; + if (!!th_check(trackHat_GetDetectedPointsExtended(&*device, &points))) + goto error; + auto& frame = *frame_.as<trackhat_frame>(); + frame.init_points(points, t.min_pt_size, t.max_pt_size); + } + + return {true, get_desired()}; + +error: + stop(); + return {false, {}}; +} + +static void log_handler(const char* file, int line, const char* function, char level, const char* str, size_t len) +{ + if (level != 'E') + return; + char file_[128]; + snprintf(file_, std::size(file_), "trackhat/%s", file); + auto logger = QMessageLogger(file_, line, function).debug(); + logger << "tracker/trackhat:"; + logger.noquote() << QLatin1String(str, (int)len); +} + +bool trackhat_camera::start(const pt_settings&) +{ + trackHat_SetDebugHandler(log_handler); + + if constexpr(debug_mode) + trackHat_EnableDebugMode(); + else + trackHat_DisableDebugMode(); + + if (!device.ensure_device_exists()) + return false; + + set_pt_options(); + + return true; +} + +void trackhat_camera::stop() +{ + device.disconnect(); +} diff --git a/tracker-trackhat/dialog.cpp b/tracker-trackhat/dialog.cpp new file mode 100644 index 00000000..cfff03ba --- /dev/null +++ b/tracker-trackhat/dialog.cpp @@ -0,0 +1,144 @@ +#include "dialog.hpp" + +using namespace options; + +trackhat_dialog::trackhat_dialog() +{ + ui.setupUi(this); + poll_tracker_info(); + poll_timer.setInterval(100); + + const std::tuple<QString, model_type> model_types[] = { + { tr("Cap"), model_cap }, + { tr("Clip (left)"), model_clip_left }, + { tr("Clip (right)"), model_clip_right }, + { tr("Mini Clip (left)"), model_mini_clip_left }, + { tr("Mini Clip (right)"), model_mini_clip_right }, + { tr("Custom"), model_mystery_meat }, + }; + + ui.model_type->clear(); + + for (const auto& [name, type] : model_types) + ui.model_type->addItem(QIcon{}, name, (QVariant)(int)type); + + // model + + tie_setting(t.model, ui.model_type); + tie_setting(t.min_pt_size, ui.min_point_size); + tie_setting(t.max_pt_size, ui.max_point_size); + tie_setting(t.point_filter_limit, ui.point_filter_limit); + + // exposure + + ui.exposure_slider->setMinimum((int)t.exposure->min()); + ui.exposure_slider->setMaximum((int)t.exposure->max()); + + tie_setting(t.exposure, ui.exposure_slider); + ui.exposure_label->setValue((int)*t.exposure); + ui.point_filter_limit_label->setValue(*t.point_filter_limit); + + connect(&t.exposure, value_::value_changed<slider_value>(), ui.exposure_label, + [this] { ui.exposure_label->setValue((int)*t.exposure); }, Qt::QueuedConnection); + connect(&t.point_filter_limit, value_::value_changed<slider_value>(), ui.point_filter_limit_label, + [this] { ui.point_filter_limit_label->setValue(*t.point_filter_limit); }, Qt::QueuedConnection); + + // threshold + +#if 0 + tie_setting(t.threshold, ui.threshold_slider); + + ui.threshold_label->setValue((int)*t.threshold); + + connect(&t.threshold, value_::value_changed<slider_value>(), ui.threshold_label, [=] { + ui.threshold_label->setValue((int)*t.threshold); + }, Qt::QueuedConnection); +#endif + + // point filter + + tie_setting(t.enable_point_filter, ui.enable_point_filter); + tie_setting(t.point_filter_coefficient, ui.point_filter_slider); + ui.point_filter_label->setValue(*t.point_filter_coefficient); + + connect(&t.point_filter_coefficient, value_::value_changed<slider_value>(), ui.point_filter_label, + [this] { ui.point_filter_label->setValue(*t.point_filter_coefficient); }, Qt::QueuedConnection); + + tie_setting(t.point_filter_deadzone, ui.point_filter_deadzone); + ui.point_filter_deadzone_label->setValue(*t.point_filter_deadzone); + + connect(&t.point_filter_deadzone, value_::value_changed<slider_value>(), ui.point_filter_deadzone_label, + [this] { ui.point_filter_deadzone_label->setValue(*t.point_filter_deadzone); }, Qt::QueuedConnection); + + // stuff + + connect(&poll_timer, &QTimer::timeout, this, &trackhat_dialog::poll_tracker_info); + connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &trackhat_dialog::doOK); + connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &trackhat_dialog::doCancel); +} + +void trackhat_dialog::register_tracker(ITracker* tracker_) +{ + tracker = static_cast<Tracker_PT*>(tracker_); + poll_tracker_info(); + poll_timer.start(); +} + +void trackhat_dialog::unregister_tracker() +{ + tracker = nullptr; + poll_tracker_info(); + poll_timer.stop(); + update_raw_data(); +} + +void trackhat_dialog::save() +{ + s.b->save(); + t.b->save(); +} + +void trackhat_dialog::reload() +{ + s.b->reload(); + t.b->reload(); +} + +void trackhat_dialog::doCancel() { reload(); close(); } +void trackhat_dialog::doOK() { save(); close(); } + +trackhat_dialog::~trackhat_dialog() +{ +} + +void trackhat_dialog::poll_tracker_info() +{ + if (!tracker) + ui.status_label->setText(tr("Status: Tracking stopped.")); + else if (tracker->get_n_points() == 3) + ui.status_label->setText(tr("Status: %1 points detected. Good!").arg(tracker->get_n_points())); + else + ui.status_label->setText(tr("Status: %1 points detected. BAD!").arg(tracker->get_n_points())); + update_raw_data(); +} + +void trackhat_dialog::set_buttons_visible(bool x) +{ + ui.buttonBox->setVisible(x); + adjustSize(); +} +void trackhat_dialog::update_raw_data() +{ + QLabel* labels[] = { ui.label_x, ui.label_y, ui.label_z, ui.label_yaw, ui.label_pitch, ui.label_roll }; + if (tracker) + { + QString str; str.reserve(16); + double data[6] {}; + tracker->data(data); + for (unsigned i = 0; i < std::size(labels); i++) + labels[i]->setText(str.sprintf("%.2f%s", data[i], i >= 3 ? "°" : " mm")); + } + else + for (QLabel* x : labels) + x->setText(QStringLiteral("-")); +} diff --git a/tracker-trackhat/dialog.hpp b/tracker-trackhat/dialog.hpp new file mode 100644 index 00000000..35ca866b --- /dev/null +++ b/tracker-trackhat/dialog.hpp @@ -0,0 +1,36 @@ +#pragma once +#include "trackhat.hpp" +#include "ui_dialog.h" +#include "tracker-pt/ftnoir_tracker_pt.h" +#include "api/plugin-api.hpp" +#include <QTimer> + +class trackhat_dialog final : public ITrackerDialog +{ + Q_OBJECT + +protected: + Ui_trackhat_dialog ui; + Tracker_PT* tracker = nullptr; + QTimer poll_timer{this}; + + pt_settings s{trackhat_metadata::module_name}; + trackhat_settings t; + + void set_buttons_visible(bool x) override; + void update_raw_data(); + +public: + trackhat_dialog(); + ~trackhat_dialog() override; + void register_tracker(ITracker *tracker) override; + void unregister_tracker() override; + bool embeddable() noexcept override { return true; } + void save() override; + void reload() override; + +public slots: + void doOK(); + void doCancel(); + void poll_tracker_info(); +}; diff --git a/tracker-trackhat/dialog.ui b/tracker-trackhat/dialog.ui new file mode 100644 index 00000000..30cf78db --- /dev/null +++ b/tracker-trackhat/dialog.ui @@ -0,0 +1,522 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>trackhat_dialog</class> + <widget class="QDialog" name="trackhat_dialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>365</width> + <height>429</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>365</width> + <height>0</height> + </size> + </property> + <property name="windowTitle"> + <string>TrackHat</string> + </property> + <property name="windowIcon"> + <iconset resource="trackhat-res.qrc"> + <normaloff>:/images/trackhat-64x64.png</normaloff>:/images/trackhat-64x64.png</iconset> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Camera</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="2"> + <widget class="QSpinBox" name="exposure_label"> + <property name="focusPolicy"> + <enum>Qt::NoFocus</enum> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::NoButtons</enum> + </property> + <property name="maximum"> + <number>999</number> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSlider" name="exposure_slider"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximum"> + <number>239</number> + </property> + <property name="pageStep"> + <number>10</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Exposure</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Model</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label_6"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>10</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Type</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="model_type"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>9</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Min point size</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Max point size</string> + </property> + </widget> + </item> + <item row="2" column="1" alignment="Qt::AlignRight"> + <widget class="QDoubleSpinBox" name="max_point_size"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="suffix"> + <string> px</string> + </property> + <property name="decimals"> + <number>1</number> + </property> + <property name="minimum"> + <double>1.000000000000000</double> + </property> + <property name="maximum"> + <double>999.000000000000000</double> + </property> + </widget> + </item> + <item row="1" column="1" alignment="Qt::AlignRight"> + <widget class="QDoubleSpinBox" name="min_point_size"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="suffix"> + <string> px</string> + </property> + <property name="decimals"> + <number>1</number> + </property> + <property name="minimum"> + <double>1.000000000000000</double> + </property> + <property name="maximum"> + <double>999.000000000000000</double> + </property> + <property name="value"> + <double>1.000000000000000</double> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Tracking</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QWidget" name="widget" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="0" column="2"> + <widget class="QDoubleSpinBox" name="point_filter_label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::NoFocus</enum> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::NoButtons</enum> + </property> + <property name="suffix"> + <string> px</string> + </property> + <property name="decimals"> + <number>2</number> + </property> + <property name="maximum"> + <double>300.000000000000000</double> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSlider" name="point_filter_limit"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximum"> + <number>99</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>Limit</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>Deadzone</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QCheckBox" name="enable_point_filter"> + <property name="text"> + <string>Point filter</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QDoubleSpinBox" name="point_filter_limit_label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::NoFocus</enum> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::NoButtons</enum> + </property> + <property name="prefix"> + <string/> + </property> + <property name="suffix"> + <string/> + </property> + <property name="decimals"> + <number>2</number> + </property> + <property name="maximum"> + <double>1.000000000000000</double> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSlider" name="point_filter_slider"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximum"> + <number>300</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSlider" name="point_filter_deadzone"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximum"> + <number>100</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QDoubleSpinBox" name="point_filter_deadzone_label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::NoFocus</enum> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + <property name="buttonSymbols"> + <enum>QAbstractSpinBox::NoButtons</enum> + </property> + <property name="suffix"> + <string> px</string> + </property> + <property name="decimals"> + <number>2</number> + </property> + <property name="maximum"> + <double>300.000000000000000</double> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="status_label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Status</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_4"> + <property name="title"> + <string>Raw data</string> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>X</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label_y"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_z"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="label_13"> + <property name="text"> + <string>Yaw</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string>Roll</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Y</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label_x"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_12"> + <property name="text"> + <string>Z</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="label_14"> + <property name="text"> + <string>Pitch</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLabel" name="label_yaw"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QLabel" name="label_pitch"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + <item row="2" column="3"> + <widget class="QLabel" name="label_roll"> + <property name="text"> + <string>TextLabel</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>10</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>exposure_slider</tabstop> + <tabstop>model_type</tabstop> + <tabstop>min_point_size</tabstop> + <tabstop>max_point_size</tabstop> + <tabstop>enable_point_filter</tabstop> + <tabstop>point_filter_slider</tabstop> + <tabstop>point_filter_limit</tabstop> + </tabstops> + <resources> + <include location="trackhat-res.qrc"/> + </resources> + <connections/> +</ui> diff --git a/tracker-trackhat/extractor.cpp b/tracker-trackhat/extractor.cpp new file mode 100644 index 00000000..58b9fd05 --- /dev/null +++ b/tracker-trackhat/extractor.cpp @@ -0,0 +1,21 @@ +#include "trackhat.hpp" +#include <algorithm> +#include <iterator> + +void trackhat_extractor::extract_points(const pt_frame& data, + pt_preview&, bool, + std::vector<vec2>& points) +{ + points.clear(); + points.reserve(trackhat_camera::point_count); + const auto& copy = data.as_const<trackhat_frame>()->points; + + for (const auto& pt : copy) + { + if (!pt.ok) + continue; + constexpr int sz = trackhat_camera::sensor_size; + auto [ x, y ] = to_screen_pos(pt.x, pt.y, sz, sz); + points.push_back({x, y}); + } +} diff --git a/tracker-trackhat/frame.cpp b/tracker-trackhat/frame.cpp new file mode 100644 index 00000000..d8fae002 --- /dev/null +++ b/tracker-trackhat/frame.cpp @@ -0,0 +1,127 @@ +#include "trackhat.hpp" +#include <opencv2/imgproc.hpp> +#include "compat/math.hpp" + +trackhat_preview::trackhat_preview(int w, int h) +{ + frame_bgr.create(h, w, CV_8UC3); + frame_bgra.create(h, w, CV_8UC4); +} + +void trackhat_preview::set_last_frame(const pt_frame& frame_) +{ + center = {-1, -1}; + points = frame_.as_const<trackhat_frame>()->points; +} + +void trackhat_preview::draw_head_center(pt_pixel_pos_mixin::f x, pt_pixel_pos_mixin::f y) +{ + center = {x, y}; +} + +QImage trackhat_preview::get_bitmap() +{ + frame_bgr.setTo({0}); + + draw_points(); + draw_center(); + + cv::cvtColor(frame_bgr, frame_bgra, cv::COLOR_BGR2BGRA); + + return QImage((const unsigned char*) frame_bgra.data, + frame_bgra.cols, frame_bgra.rows, + (int)frame_bgra.step.p[0], + QImage::Format_ARGB32); +} + +void trackhat_preview::draw_center() +{ + if (center == numeric_types::vec2(-1, -1)) + return; + + auto [px_, py_] = to_pixel_pos(center[0], center[1], frame_bgr.cols, frame_bgr.rows); + int px = iround(px_), py = iround(py_); + + const f dpi = (f)frame_bgr.cols / f(320); + constexpr int len_ = 9; + int len = iround(len_ * dpi); + + static const cv::Scalar color(0, 255, 255); + cv::line(frame_bgr, + cv::Point(px - len, py), + cv::Point(px + len, py), + color, 1); + cv::line(frame_bgr, + cv::Point(px, py - len), + cv::Point(px, py + len), + color, 1); +} + +void trackhat_preview::draw_points() +{ + for (const auto& pt : points) + { + if (pt.brightness == 0) + continue; + + constexpr int sz = trackhat_camera::sensor_size; + constexpr f scaling_factor = 10; + const int x = pt.x * frame_bgr.cols / sz, y = pt.y * frame_bgr.rows / sz; + const f dpi = (f)frame_bgr.cols / f(320); + const int W = std::max(1, iround(pt.W * frame_bgr.cols * scaling_factor / sz)), + H = std::max(1, iround(pt.H * frame_bgr.rows * scaling_factor / sz)); + const auto point_color = progn(double c = pt.brightness; return cv::Scalar{c, c, c};); + const auto outline_color = pt.ok + ? cv::Scalar{255, 255, 0} + : cv::Scalar{192, 192, 192}; + + cv::ellipse(frame_bgr, {x, y}, {W, H}, + 0, 0, 360, point_color, -1, cv::LINE_AA); + cv::ellipse(frame_bgr, {x, y}, {iround(W + 2*dpi), iround(H + 2*dpi)}, + 0, 0, 360, outline_color, iround(dpi), cv::LINE_AA); + + char buf[16]; + std::snprintf(buf, sizeof(buf), "%dpx", pt.area); + auto text_color = pt.ok + ? cv::Scalar(0, 0, 255) + : cv::Scalar(160, 160, 160); + const int offx = iround(W + 9*dpi), offy = H*3/2; + + cv::putText(frame_bgr, buf, {x+offx, y+offy}, + cv::FONT_HERSHEY_PLAIN, iround(dpi), text_color, + 1); + } +} + +void trackhat_frame::init_points(const trackHat_ExtendedPoints_t& points_, double min_size, double max_size) +{ + trackHat_ExtendedPoints_t copy = points_; + points = {}; + + std::sort(std::begin(copy.m_point), std::end(copy.m_point), + [](trackHat_ExtendedPoint_t p1, trackHat_ExtendedPoint_t p2) { + return p1.m_averageBrightness > p2.m_averageBrightness; + }); + + unsigned i = 0; + + for (const trackHat_ExtendedPoint_t& pt : copy.m_point) + { + if (pt.m_averageBrightness == 0) + continue; + + point p = {}; + + if (pt.m_area >= min_size && pt.m_area <= max_size) + p.ok = true; + + p.brightness = pt.m_averageBrightness; + p.area = pt.m_area; + p.W = std::max(1, pt.m_boundryRigth - pt.m_boundryLeft); + p.H = std::max(1, pt.m_boundryDown - pt.m_boundryUp); + p.x = trackhat_camera::sensor_size-1-pt.m_coordinateX; + p.y = pt.m_coordinateY; + + points[i++] = p; + } +} diff --git a/tracker-trackhat/handle.cpp b/tracker-trackhat/handle.cpp new file mode 100644 index 00000000..42902e76 --- /dev/null +++ b/tracker-trackhat/handle.cpp @@ -0,0 +1,72 @@ +#undef NDEBUG +#include "trackhat.hpp" +#include "compat/sleep.hpp" +#include "compat/timer.hpp" +#include <cassert> + +bool camera_handle::ensure_connected() +{ + if (state_ >= st_streaming) + return true; + else if (state_ == st_stopped) + return false; + + Timer t; + + constexpr int max_attempts = 5; + + if (!ensure_device_exists()) + goto error; + + for (int i = 0; i < max_attempts; i++) + { + if (!th_check(trackHat_Connect(&device_, TH_FRAME_EXTENDED))) + { + state_ = st_streaming; + if (int ms = (int)t.elapsed_ms(); ms > 1000) + qDebug() << "tracker/trackhat: connecting took" << ms << "ms"; + return true; + } + + auto dbg = qDebug(); + dbg << "tracker/trackhat: connect failed, retry"; + dbg.space(); dbg.nospace(); + dbg << (i+1) << "/" << max_attempts; + portable::sleep(50); + } + +error: + disconnect(); + return false; +} + +bool camera_handle::ensure_device_exists() +{ + switch (state_) + { + case st_streaming: + return true; + case st_detected: + disconnect(); + [[fallthrough]]; + case st_stopped: + assert(!th_check(trackHat_Initialize(&device_)) && device_.m_pInternal); + if (auto error = th_check(trackHat_DetectDevice(&device_)); error) + { + disconnect(); + return false; + } + state_ = st_detected; + return true; + } +} + +void camera_handle::disconnect() +{ + state_ = st_stopped; + if (device_.m_pInternal) + { + (void)!th_check(trackHat_Disconnect(&device_)); + trackHat_Deinitialize(&device_); + } +} diff --git a/tracker-trackhat/images/trackhat-64x64.png b/tracker-trackhat/images/trackhat-64x64.png Binary files differnew file mode 100644 index 00000000..9e856c23 --- /dev/null +++ b/tracker-trackhat/images/trackhat-64x64.png diff --git a/tracker-trackhat/images/trackhat.ico b/tracker-trackhat/images/trackhat.ico Binary files differnew file mode 100644 index 00000000..b5f34db3 --- /dev/null +++ b/tracker-trackhat/images/trackhat.ico diff --git a/tracker-trackhat/images/trackhat.png b/tracker-trackhat/images/trackhat.png Binary files differnew file mode 100644 index 00000000..4f17de81 --- /dev/null +++ b/tracker-trackhat/images/trackhat.png diff --git a/tracker-trackhat/lang/nl_NL.ts b/tracker-trackhat/lang/nl_NL.ts new file mode 100644 index 00000000..73e375fc --- /dev/null +++ b/tracker-trackhat/lang/nl_NL.ts @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="nl_NL"> +<context> + <name>trackhat_dialog</name> + <message> + <source>Cap</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip (left)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip (right)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Mini Clip (left)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Mini Clip (right)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Custom</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status: Tracking stopped.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status: %1 points detected. Good!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status: %1 points detected. BAD!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>TrackHat</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Camera</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Exposure</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Model</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Type</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Min point size</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Max point size</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> px</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracking</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Point filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Limit</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Raw data</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>X</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>TextLabel</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Yaw</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Roll</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Y</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Z</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Pitch</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Deadzone</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>trackhat_module</name> + <message> + <source>TrackHat Point Tracker</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/tracker-trackhat/lang/ru_RU.ts b/tracker-trackhat/lang/ru_RU.ts new file mode 100644 index 00000000..091c2101 --- /dev/null +++ b/tracker-trackhat/lang/ru_RU.ts @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="ru_RU"> +<context> + <name>trackhat_dialog</name> + <message> + <source>Cap</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip (left)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip (right)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Mini Clip (left)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Mini Clip (right)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Custom</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status: Tracking stopped.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status: %1 points detected. Good!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status: %1 points detected. BAD!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>TrackHat</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Camera</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Exposure</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Model</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Type</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Min point size</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Max point size</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> px</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracking</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Point filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Limit</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Raw data</source> + <translation>Исходные данные</translation> + </message> + <message> + <source>X</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>TextLabel</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Yaw</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Roll</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Y</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Z</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Pitch</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Deadzone</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>trackhat_module</name> + <message> + <source>TrackHat Point Tracker</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/tracker-trackhat/lang/stub.ts b/tracker-trackhat/lang/stub.ts new file mode 100644 index 00000000..548138a3 --- /dev/null +++ b/tracker-trackhat/lang/stub.ts @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="stub"> +<context> + <name>trackhat_dialog</name> + <message> + <source>TrackHat</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Camera</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Exposure</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Model</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Type</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Min point size</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Max point size</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> px</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracking</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Limit</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Deadzone</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Point filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Raw data</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>X</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>TextLabel</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Yaw</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Roll</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Y</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Z</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Pitch</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Cap</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip (left)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip (right)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Mini Clip (left)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Mini Clip (right)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Custom</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status: Tracking stopped.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status: %1 points detected. Good!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status: %1 points detected. BAD!</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>trackhat_module</name> + <message> + <source>TrackHat Point Tracker</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/tracker-trackhat/lang/zh_CN.ts b/tracker-trackhat/lang/zh_CN.ts new file mode 100644 index 00000000..cb881fa8 --- /dev/null +++ b/tracker-trackhat/lang/zh_CN.ts @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="zh_CN"> +<context> + <name>trackhat_dialog</name> + <message> + <source>Cap</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip (left)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Clip (right)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Mini Clip (left)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Mini Clip (right)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Custom</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status: Tracking stopped.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status: %1 points detected. Good!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status: %1 points detected. BAD!</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>TrackHat</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Camera</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Exposure</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Model</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Type</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Min point size</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Max point size</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> px</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracking</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Point filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Limit</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Status</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Raw data</source> + <translation>跟踪器原始数据</translation> + </message> + <message> + <source>X</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>TextLabel</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Yaw</source> + <translation>偏航</translation> + </message> + <message> + <source>Roll</source> + <translation>横滚</translation> + </message> + <message> + <source>Y</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Z</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Pitch</source> + <translation>仰俯</translation> + </message> + <message> + <source>Deadzone</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>trackhat_module</name> + <message> + <source>TrackHat Point Tracker</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/tracker-trackhat/metadata.cpp b/tracker-trackhat/metadata.cpp new file mode 100644 index 00000000..8c6f4de0 --- /dev/null +++ b/tracker-trackhat/metadata.cpp @@ -0,0 +1,39 @@ +#include "metadata.hpp" +#include "api/plugin-api.hpp" +#include "trackhat.hpp" +#include "dialog.hpp" + +// XXX TODO +const QString trackhat_metadata::module_name = QStringLiteral("tracker-trackhat/pt"); + +pt_runtime_traits::pointer<pt_camera> trackhat_metadata::make_camera() const +{ + return std::make_shared<trackhat_camera>(); +} + +pt_runtime_traits::pointer<pt_point_extractor> trackhat_metadata::make_point_extractor() const +{ + return std::make_shared<trackhat_extractor>(); +} + +pt_runtime_traits::pointer<pt_frame> trackhat_metadata::make_frame() const +{ + return std::make_shared<trackhat_frame>(); +} + +pt_runtime_traits::pointer<pt_preview> trackhat_metadata::make_preview(int w, int h) const +{ + return std::make_shared<trackhat_preview>(w, h); +} + +QString trackhat_metadata::get_module_name() const +{ + return trackhat_metadata::module_name; +} + +trackhat_pt::trackhat_pt() : + Tracker_PT(pt_runtime_traits::pointer<pt_runtime_traits>(new trackhat_metadata)) +{ +} + +OPENTRACK_DECLARE_TRACKER(trackhat_pt, trackhat_dialog, trackhat_module) diff --git a/tracker-trackhat/metadata.hpp b/tracker-trackhat/metadata.hpp new file mode 100644 index 00000000..c0f7567c --- /dev/null +++ b/tracker-trackhat/metadata.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "trackhat.hpp" +#include "../tracker-pt/ftnoir_tracker_pt.h" + +class trackhat_pt final : public Tracker_PT +{ + Q_OBJECT + +public: + trackhat_pt(); +}; + +class trackhat_module final : public Metadata +{ + Q_OBJECT + +public: + QString name() override { return tr("TrackHat Point Tracker"); } + QIcon icon() override { return QIcon(":/images/trackhat-64x64.png"); } + static const QString module_name; +}; diff --git a/tracker-trackhat/settings.cpp b/tracker-trackhat/settings.cpp new file mode 100644 index 00000000..ec46b1e9 --- /dev/null +++ b/tracker-trackhat/settings.cpp @@ -0,0 +1,147 @@ +#include "trackhat.hpp" +#include "compat/sleep.hpp" +#include "compat/timer.hpp" + +namespace trackhat_impl { + +trackhat_settings::trackhat_settings() : opts{"tracker-trackhat"} +{ +} +int trackhat_settings::effective_exposure() const +{ + return std::clamp((int)*exposure, min_exposure, max_exposure); +} +int trackhat_settings::effective_gain() const +{ + return min_gain + std::clamp((int)*exposure - max_exposure, 0, max_gain - min_gain); +} + +void setting_receiver::settings_changed() +{ + changed = true; +} + +bool setting_receiver::test_and_clear() +{ + bool x = true; + return changed.compare_exchange_strong(x, false); +} + +setting_receiver::setting_receiver(bool value) : changed{value} +{ +} + +} // ns trackhat_impl + +void trackhat_camera::set_pt_options() +{ + s.min_point_size = t.min_pt_size; + s.max_point_size = t.max_pt_size; + + switch (t.model) + { + default: + case model_cap: + s.t_MH_x = 0; s.t_MH_y = 0; s.t_MH_z = 0; + break; + case model_mystery_meat: + case model_clip_left: + case model_mini_clip_left: + s.t_MH_x = -135; s.t_MH_y = 0; s.t_MH_z = 0; + break; + case model_clip_right: + case model_mini_clip_right: + s.t_MH_x = 135; s.t_MH_y = 0; s.t_MH_z = 0; + break; + } + + switch (t.model) + { + default: + eval_once(qDebug() << "tracker/trackhat: unknown model"); + [[fallthrough]]; + case model_clip_left: + case model_clip_right: + s.clip_tz = 27; s.clip_ty = 43; s.clip_by = 62; s.clip_bz = 74; + break; + case model_mini_clip_left: + case model_mini_clip_right: + s.clip_tz = 13; s.clip_ty = 42; s.clip_by = 60; s.clip_bz = 38; + break; + case model_cap: + s.cap_x = 60; s.cap_y = 90; s.cap_z = 95; + break; + case model_mystery_meat: + break; + } + + s.dynamic_pose = t.model == model_cap; + s.init_phase_timeout = 500; + + s.camera_name = QStringLiteral("TrackHat Sensor (WIP)"); + + s.active_model_panel = t.model == model_cap ? 1 : 0; + s.enable_point_filter = t.enable_point_filter; + s.point_filter_coefficient = *t.point_filter_coefficient; + s.point_filter_limit = *t.point_filter_limit; + s.point_filter_deadzone = *t.point_filter_deadzone; +} + +bool trackhat_camera::init_regs() +{ + auto exp = (uint8_t)t.effective_exposure(); + auto exp2 = (uint8_t)(exp == 0xff ? 0xf0 : 0xff); + auto thres = (uint8_t)0xfe; + auto thres2 = (uint8_t)3; + + auto gain = (uint8_t)t.effective_gain(); + auto gain_c = (uint8_t)(gain/0x10); + gain %= 0x10; gain_c %= 4; + + trackHat_SetRegisterGroup_t regs = { + { + { 0x0c, 0x0f, exp2 }, // exposure lo + { 0x0c, 0x10, exp }, // exposure hi + { 0x00, 0x0b, 0xff }, // blob area max size + { 0x00, 0x0c, 0x03 }, // blob area min size + { 0x0c, 0x08, gain }, // gain + { 0x0c, 0x0c, gain_c }, // gain multiplier + { 0x0c, 0x47, thres }, // min brightness + { 0x00, 0x0f, thres2 }, // brightness margin, formula is `thres >= px > thres - fuzz' + { 0x00, 0x01, 0x01 }, // bank0 sync + { 0x01, 0x01, 0x01 }, // bank1 sync + }, + 10 + }; + + Timer t; + + constexpr int max = 5; + int i = 0; + for (i = 0; i < max; i++) + { + TH_ErrorCode status = TH_SUCCESS; + status = th_check(trackHat_SetRegisterGroupValue(&*device, ®s)); + if (status == TH_SUCCESS) + break; + else if (status != TH_FAILED_TO_SET_REGISTER && + status != TH_ERROR_DEVICE_COMMUNICATION_TIMEOUT) + return false; + else + { + auto dbg = qDebug(); + dbg << "tracker/trackhat: set register retry attempt"; + dbg.space(); dbg.nospace(); + dbg << i << "/" << max; + portable::sleep(50); + } + } + + if (i == max) + return false; + + if (int elapsed = (int)t.elapsed_ms(); elapsed > 100) + qDebug() << "tracker/trackhat: setting registers took" << elapsed << "ms"; + + return true; +} diff --git a/tracker-trackhat/tracker_trackhat.qrc b/tracker-trackhat/tracker_trackhat.qrc new file mode 100644 index 00000000..d54010a0 --- /dev/null +++ b/tracker-trackhat/tracker_trackhat.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>images/trackhat-64x64.png</file> + </qresource> +</RCC> diff --git a/tracker-trackhat/trackhat-res.qrc b/tracker-trackhat/trackhat-res.qrc new file mode 100644 index 00000000..9aeb8879 --- /dev/null +++ b/tracker-trackhat/trackhat-res.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource> + <file>images/trackhat-64x64.png</file> + </qresource> +</RCC> diff --git a/tracker-trackhat/trackhat.hpp b/tracker-trackhat/trackhat.hpp new file mode 100644 index 00000000..d94841d2 --- /dev/null +++ b/tracker-trackhat/trackhat.hpp @@ -0,0 +1,180 @@ +#pragma once + +#include "../tracker-pt/pt-api.hpp" +#include "compat/macros.hpp" +#include "options/options.hpp" + +#include <track_hat_driver.h> + +#include <array> +#include <atomic> +#include <opencv2/core/mat.hpp> + +enum model_type : int +{ + model_cap = 1, + model_clip_left, + model_clip_right, + model_mini_clip_left, + model_mini_clip_right, + model_mystery_meat, +}; + +namespace trackhat_impl +{ +using namespace options; + +TH_ErrorCode log_error(TH_ErrorCode error, const char* source, const char* file, int line, const char* function); +#define th_check_(expr, expr2) ::trackhat_impl::log_error((expr), expr2) +#define th_check(expr) ::trackhat_impl::log_error((expr), #expr, __FILE__, __LINE__, function_name) + +struct trackhat_settings : opts +{ + static constexpr int min_gain = 16, max_gain = 47, + min_exposure = 0x10, max_exposure = 0xff; + static constexpr int num_exposure_steps = max_gain + max_exposure - min_gain - min_exposure; + int effective_exposure() const; + int effective_gain() const; + trackhat_settings(); + value<slider_value> exposure{b, "exposure", {min_exposure, min_exposure, max_exposure + max_gain - min_gain}}; + //value<slider_value> threshold{b, "threshold", {0x97, 64, 0xfe}}; + value<model_type> model{b, "model", model_mini_clip_left}; + value<double> min_pt_size{b, "min-point-size", 10}; + value<double> max_pt_size{b, "max-point-size", 50}; + value<bool> enable_point_filter{b, "enable-point-filter", true }; + value<slider_value> point_filter_coefficient{b, "point-filter-coefficient", { 1.5, 1, 4 }}; + value<slider_value> point_filter_limit { b, "point-filter-limit", { 0.1, 0.01, 1 }}; + value<slider_value> point_filter_deadzone { b, "point-filter-deadzone", {0, 0, 1}}; +}; + +class setting_receiver : public QObject +{ + Q_OBJECT + +public: + explicit setting_receiver(bool value); + bool test_and_clear(); +public slots: + void settings_changed(); +private: + std::atomic<bool> changed{false}; +}; + +} // ns trackhat_impl + +using typename trackhat_impl::trackhat_settings; + +struct trackhat_metadata final : pt_runtime_traits +{ + pt_runtime_traits::pointer<pt_camera> make_camera() const override; + pt_runtime_traits::pointer<pt_point_extractor> make_point_extractor() const override; + pt_runtime_traits::pointer<pt_frame> make_frame() const override; + pt_runtime_traits::pointer<pt_preview> make_preview(int w, int h) const override; + QString get_module_name() const override; + + OTR_DISABLE_MOVE_COPY(trackhat_metadata); + + trackhat_metadata() = default; + ~trackhat_metadata() override = default; + + static const QString module_name; +}; + +struct point +{ + int brightness = 0, area, x, y, W, H; + bool ok = false; +}; + +struct camera_handle final +{ + OTR_DISABLE_MOVE_COPY(camera_handle); + trackHat_Device_t* operator->() { return &device_; } + trackHat_Device_t& operator*() { return device_; } + + camera_handle() = default; + ~camera_handle() = default; + + [[nodiscard]] bool ensure_connected(); + [[nodiscard]] bool ensure_device_exists(); + void disconnect(); +private: + trackHat_Device_t device_ = {}; + enum state { st_stopped, st_detected, st_streaming, }; + state state_ = st_stopped; +}; + +struct trackhat_camera final : pt_camera +{ + trackhat_camera(); + ~trackhat_camera() override; + + OTR_DISABLE_MOVE_COPY(trackhat_camera); + + bool start(const pt_settings& s) override; + void stop() override; + + pt_camera::result get_frame(pt_frame& frame) override; + pt_camera::result get_info() const override; + pt_camera_info get_desired() const override; + + QString get_desired_name() const override; + QString get_active_name() const override; + + void set_fov(f value) override; + void show_camera_settings() override; + + f deadzone_amount() const override { return 10; } + + static constexpr int sensor_size = 2940; + static constexpr int sensor_fov = 52; + static constexpr int point_count = TRACK_HAT_NUMBER_OF_POINTS; + static constexpr bool debug_mode = true; + +private: + trackhat_impl::setting_receiver sig{true}; + + [[nodiscard]] bool init_regs(); + void set_pt_options(); + + camera_handle device; + pt_settings s{trackhat_metadata::module_name}; + trackhat_settings t; +}; + +struct trackhat_frame final : pt_frame +{ + void init_points(const trackHat_ExtendedPoints_t& points, double min_size, double max_size); + trackhat_frame() = default; + ~trackhat_frame() override = default; + + std::array<point, trackhat_camera::point_count> points; +}; + +struct trackhat_preview final : pt_preview +{ + QImage get_bitmap() override; + void draw_head_center(f x, f y) override; + void set_last_frame(const pt_frame&) override; // NOLINT(misc-unconventional-assign-operator) + + trackhat_preview(int w, int h); + ~trackhat_preview() override = default; + void draw_points(); + void draw_center(); + + OTR_DISABLE_MOVE_COPY(trackhat_preview); + + cv::Mat frame_bgr, frame_bgra; + numeric_types::vec2 center{-1, -1}; + std::array<point, trackhat_camera::point_count> points; +}; + +struct trackhat_extractor final : pt_point_extractor +{ + void extract_points(const pt_frame& data, pt_preview&, bool, std::vector<vec2>& points) override; + + OTR_DISABLE_MOVE_COPY(trackhat_extractor); + + trackhat_extractor() = default; + ~trackhat_extractor() override = default; +}; |