From 4a46db2fcfc4bb824a35f82c4b15054daf8b94b9 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 11 Nov 2018 10:34:15 +0100 Subject: [PATCH 001/235] set acquisition mode for PGR cams --- src/PGRSource.cpp | 77 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/src/PGRSource.cpp b/src/PGRSource.cpp index 2cfbbec..6664d35 100644 --- a/src/PGRSource.cpp +++ b/src/PGRSource.cpp @@ -10,6 +10,8 @@ #include "Logger.h" +#include "SpinGenApi/SpinnakerGenApi.h" + using namespace Spinnaker; using cv::Mat; @@ -35,7 +37,7 @@ PGRSource::PGRSource(int index) return; } else { - LOG_DBG("Found %d PGR cameras.", numCameras); + LOG_DBG("Found %d PGR cameras. Connecting to camera %d..", numCameras, index); } // Select camera @@ -44,6 +46,34 @@ PGRSource::PGRSource(int index) // Initialize camera _cam->Init(); + // set acquisition mode - needed? + { + // Retrieve GenICam nodemap + Spinnaker::GenApi::INodeMap& nodeMap = _cam->GetNodeMap(); + + // Retrieve enumeration node from nodemap + Spinnaker::GenApi::CEnumerationPtr ptrAcquisitionMode = nodeMap.GetNode("AcquisitionMode"); + if (!IsAvailable(ptrAcquisitionMode) || !IsWritable(ptrAcquisitionMode)) { + LOG_ERR("Unable to set acquisition mode to continuous (enum retrieval)!"); + return; + } + + // Retrieve entry node from enumeration node + Spinnaker::GenApi::CEnumEntryPtr ptrAcquisitionModeContinuous = ptrAcquisitionMode->GetEntryByName("Continuous"); + if (!IsAvailable(ptrAcquisitionModeContinuous) || !IsReadable(ptrAcquisitionModeContinuous)) { + LOG_ERR("Unable to set acquisition mode to continuous (entry retrieval)!"); + return; + } + + // Retrieve integer value from entry node + int64_t acquisitionModeContinuous = ptrAcquisitionModeContinuous->GetValue(); + + // Set integer value from entry node as new value of enumeration node + ptrAcquisitionMode->SetIntValue(acquisitionModeContinuous); + + LOG_DBG("Acquisition mode set to continuous."); + } + // Begin acquiring images _cam->BeginAcquisition(); @@ -65,6 +95,21 @@ PGRSource::PGRSource(int index) } } +PGRSource::~PGRSource() +{ + if (_open) { + _cam->EndAcquisition(); + _open = false; + } + _cam = NULL; + + // Clear camera list before releasing system + _camList.Clear(); + + // Release system + _system->ReleaseInstance(); +} + double PGRSource::getFPS() { // do nothing @@ -96,38 +141,30 @@ bool PGRSource::grab(cv::Mat& frame) pgr_image->Release(); return false; } + } + catch (Spinnaker::Exception& e) { + LOG_ERR("Error grabbing PGR frame! Error was: %s", e.what()); + pgr_image->Release(); + return false; + } + try { // Convert image ImagePtr bgr_image = pgr_image->Convert(PixelFormat_BGR8, NEAREST_NEIGHBOR); - // We have to release our original image to clear space on the buffer - pgr_image->Release(); - Mat tmp(_height, _width, CV_8UC3, bgr_image->GetData(), bgr_image->GetStride()); tmp.copyTo(frame); + // We have to release our original image to clear space on the buffer + pgr_image->Release(); + return true; } catch (Spinnaker::Exception& e) { - LOG_ERR("Error grabbing PGR frame! Error was: %s", e.what()); + LOG_ERR("Error converting PGR frame! Error was: %s", e.what()); pgr_image->Release(); return false; } } -PGRSource::~PGRSource() -{ - if( _open ) { - _cam->EndAcquisition(); - _open = false; - } - _cam = NULL; - - // Clear camera list before releasing system - _camList.Clear(); - - // Release system - _system->ReleaseInstance(); -} - #endif // PGR_USB3 From c85fccb6a82f0472dc80c83e50b43362c6f5cf64 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 11 Nov 2018 10:41:46 +0100 Subject: [PATCH 002/235] minor comments and printing; make_unique; delay frameGrabber instantiation until after trackball init --- src/ConfigGUI.cpp | 2 +- src/FrameGrabber.cpp | 8 ++++--- src/Trackball.cpp | 52 ++++++++++++++++++++++---------------------- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index 919632e..1d48d74 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -178,7 +178,7 @@ ConfigGui::ConfigGui(string config_fn) // then try loading as video file source = std::make_shared(input_fn); } -#else // PGR_USB3 +#else // !PGR_USB3 source = std::make_shared(input_fn); #endif // PGR_USB3 if (!source->isOpen()) { diff --git a/src/FrameGrabber.cpp b/src/FrameGrabber.cpp index 86c4eeb..11fcb6a 100644 --- a/src/FrameGrabber.cpp +++ b/src/FrameGrabber.cpp @@ -102,6 +102,8 @@ bool FrameGrabber::getFrameSet(Mat& frame, Mat& remap, double& timestamp, bool l // mutex unlocked in unique_lock dstr return false; } + + // must be frame_q.size() > 0 to get here (can be !_active) if ((n != _remap_q.size()) || (n != _ts_q.size())) { LOG_ERR("Error! Input processed frame queues are misaligned!"); @@ -338,10 +340,10 @@ void FrameGrabber::process() _remap_q.push_back(remap_grey); _ts_q.push_back(timestamp); _qCond.notify_all(); - - LOG_DBG("Processed frame added to input queue (l = %d).", _frame_q.size()); - + int q_size = _frame_q.size(); l.unlock(); + + LOG_DBG("Processed frame added to input queue (l = %d).", q_size); } LOG_DBG("Stopping frame grabbing loop!"); diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 617fb44..1f18c34 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -31,8 +31,8 @@ #include using std::string; -using std::unique_ptr; -using std::shared_ptr; +using std::make_unique; +using std::make_shared; using std::unique_lock; using std::lock_guard; using std::mutex; @@ -98,16 +98,16 @@ Trackball::Trackball(string cfg_fn) /// Open frame source and set fps. string src_fn = _cfg("src_fn"); - shared_ptr source; + std::shared_ptr source; #ifdef PGR_USB3 try { // first try reading input as camera id int id = std::stoi(src_fn); - source = std::make_shared(id); + source = make_shared(id); } catch (...) { // then try loading as video file - source = std::make_shared(src_fn); + source = make_shared(src_fn); } #else // PGR_USB3 source = std::make_shared(src_fn); @@ -186,7 +186,7 @@ Trackball::Trackball(string cfg_fn) _r_d_ratio = sin(_sphere_rad); /// Allow sphere region in mask. - shared_ptr> int_circ = projCircleInt(_src_model, _sphere_c, _sphere_rad); + auto int_circ = projCircleInt(_src_model, _sphere_c, _sphere_rad); cv::fillConvexPoly(src_mask, *int_circ, CV_RGB(255, 255, 255)); /// Mask out ignore regions. @@ -349,28 +349,19 @@ Trackball::Trackball(string cfg_fn) } /// Init optimisers. - _localOpt = unique_ptr(new Localiser( + _localOpt = make_unique( NLOPT_LN_BOBYQA, bound, tol, max_evals, _sphere_model, _sphere_map, - _roi_mask, _p1s_lut)); + _roi_mask, _p1s_lut); - _globalOpt = unique_ptr(new Localiser( + _globalOpt = make_unique( NLOPT_GN_CRS2_LM, CM_PI, tol, 1e5, _sphere_model, _sphere_map, - _roi_mask, _p1s_lut)); - - /// Frame source. - _frameGrabber = unique_ptr(new FrameGrabber( - source, - remapper, - _roi_mask, - thresh_ratio, - thresh_win_pc - )); + _roi_mask, _p1s_lut); /// Output. string data_fn = _base_fn + "-" + execTime() + ".dat"; - _log = unique_ptr(new Recorder(RecorderInterface::RecordType::FILE, data_fn)); + _log = make_unique(RecorderInterface::RecordType::FILE, data_fn); /// Display. _do_display = DO_DISPLAY_DEFAULT; @@ -415,6 +406,15 @@ Trackball::Trackball(string cfg_fn) } } + /// Frame source. + _frameGrabber = std::make_unique( + source, + remapper, + _roi_mask, + thresh_ratio, + thresh_win_pc + ); + /// Write all parameters back to config file. _cfg.write(); @@ -429,10 +429,10 @@ Trackball::Trackball(string cfg_fn) _active = true; if (_do_display) { - _drawThread = unique_ptr(new std::thread(&Trackball::processDrawQ, this)); + _drawThread = make_unique(&Trackball::processDrawQ, this); } // main processing thread - _thread = unique_ptr(new std::thread(&Trackball::process, this)); + _thread = make_unique(&Trackball::process, this); } /// @@ -550,7 +550,7 @@ void Trackball::process() } if (_do_display) { - shared_ptr data = shared_ptr(new DrawData()); + auto data = make_shared(); data->src_frame = _src_frame.clone(); data->roi_frame = _roi_frame.clone(); data->sphere_map = _sphere_map.clone(); @@ -1042,7 +1042,7 @@ void makeSphereRotMaps( /// /// /// -bool Trackball::updateCanvasAsync(shared_ptr data) +bool Trackball::updateCanvasAsync(std::shared_ptr data) { bool ret = false; lock_guard l(_drawMutex); @@ -1077,7 +1077,7 @@ void Trackball::processDrawQ() if (!_active) { break; } /// Retrieve data. - shared_ptr data = _drawQ.back(); + auto data = _drawQ.back(); /// Clear all other frames (only draw latest available). if (_drawQ.size() > 1) { @@ -1100,7 +1100,7 @@ void Trackball::processDrawQ() /// /// /// -void Trackball::drawCanvas(shared_ptr data) +void Trackball::drawCanvas(std::shared_ptr data) { static Mat canvas(3 * DRAW_CELL_DIM, 4 * DRAW_CELL_DIM, CV_8UC3); canvas.setTo(Scalar::all(0)); From 3b714dff666def0e5b0ea80c024b4393ad58bcc9 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 18 Nov 2018 15:19:14 +0100 Subject: [PATCH 003/235] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f80ec02..f79d018 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,16 @@ You might also be interested in the following links: * [Demo video](http://youtu.be/BeGYOEOdWjw) - Quick (30s) overview of what FicTrac does and how it works. * [FicTrac manual](http://link) - Detailed instructions, description of output data, parameters, recommendations, etc. * [Homepage](http://fictrac.rjdmoore.net) - Contact details for the main author/developer, links, and further info. -* [Forum](http://www.reddit.com/r/fictrac/) - Subreddit for FicTrac users to share issues and advice. +* [Forum](http://www.reddit.com/r/fictrac/) - Subreddit for faqs, support, advice, discussions, etc. * [Mailing list](http://fictrac.rjdmoore.net/mail.html) - Subscribe to receive important announcements and updates. Happy tracking! ## Getting started -If you're just setting up your lab, or wondering whether FicTrac is suitable for your setup (spoiler: yes, probably), check the [section below](#hardware-requirements) for the basic requirements. +If you're just setting up your lab, or wondering whether FicTrac is suitable for your setup (spoiler: yes, probably), check the [hardware requirements section below](#hardware-requirements) for the basic requirements. -If you already have an experimental enclosure with a camera, you can use FicTrac to either process those videos offline or to run live from the camera. Just follow the sections below to [install](#installation), [configure](#configuration), and [run FicTrac](#running-fictrac). +If you already have an experimental enclosure with a camera, you can use FicTrac to either process recorded videos offline or to run live from the camera. Skip ahead to [install](#installation), [configure](#configuration), and [run FicTrac](#running-fictrac). ### Hardware requirements @@ -66,7 +66,7 @@ cmake -G "Visual Studio 15 2017 Win64" -D OPENCV_DIR="C:\path\to\opencv-3.4.2\bu ``` 5. Finally, build and install FicTrac: ``` -cmake --build . --config Release --target ALL_BUILD +cmake --build . --config Release -j 4 ``` If everything went well, the executables for FicTrac and a configuration utility will be placed under the `bin` directory in the FicTrac project folder. @@ -90,7 +90,7 @@ cmake .. ``` 4. Finally, build and install FicTrac: ``` -make -j4 +cmake --build . --config Release -- -j 4 ``` If everything went well, the executables for FicTrac and a configuration utility will be placed under the `bin` directory in the FicTrac project folder. From 2170b2c34c4e83ed3ae3accaa3ea5d9239274e83 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 18 Nov 2018 16:26:25 +0100 Subject: [PATCH 004/235] Update README.md --- README.md | 54 +++++++++--------------------------------------------- 1 file changed, 9 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index f79d018..295065b 100644 --- a/README.md +++ b/README.md @@ -101,8 +101,8 @@ If you are using a USB3 camera and are receiving error messages when FicTrac tri ##### PGR (FLIR) Spinnaker SDK -1. Download and install the Spinnaker SDK from [PGR downloads page](https://www.ptgrey.com/support/downloads). First select your camera model and operating system, and then download and install the latest Spinnaker SDK release. -2. When preparing the build files for FicTrac using Cmake, you will need to specify to use Spinnaker using the switch `-D PGR_USB3=ON` and depending on where you installed the SDK, you may also need to provide the SDK directory path using the switch `-D PGR_DIR=...`. For example, for a [Windows installation](#windows-installation) you would replace step 4 with: +1. Download and install the Spinnaker SDK from [PGR downloads page](https://www.ptgrey.com/support/downloads). +2. When preparing the build files for FicTrac using Cmake, you will need to specify to use Spinnaker using the switch `-D PGR_USB3=ON` and depending on where you installed the SDK, you may also need to provide the SDK directory path using the switch `-D PGR_DIR=...`. For example, for a [Windows installation](#windows-installation) you would replace step 4 with (replace \path\to with your correct local path): ``` cmake -G "Visual Studio 15 2017 Win64" -D OPENCV_DIR="C:\path\to\opencv-3.4.2\build" -D NLOPT_DIR="C:\path\to\nlopt-2.4.2\" -D PGR_USB3=ON -D PGR_DIR="C:\path\to\Spinnaker" .. ``` @@ -113,60 +113,24 @@ Before running FicTrac, you may configure your camera (frame rate, resolution, e ### Configuration There are two neccessary steps to configure FicTrac prior to running the program: -1. You must provide a configuration text file that contains important parameters for your setup. At a minimum, this config file must define the parameters `src_fn` and `vfov`, which are the path to the image source (video file or camera) and vertical field of view (in degrees) of your camera/lens respectively. If you are running live from the camera, then `src_fn`is the camera index (e.g. 0). **The vertical field of view for your camera/lens must be specified accurately.** If `vfov` is incorrect, the surface map created by FicTrac will not wrap around the sphere correctly, and tracking will fail. An example config file is provided in the `sample` directory under the FicTrac project folder; you can use this file as a template to write your own config file. -2. You must run the interactive configuration program (configGui), passing the path to your config file (above) as an argument. This program will guide you through the configuration of the track ball region of interest within your input images and the transformation between the camera's and animal's frames of reference. **It is important that the camera is not moved after running the configuration utility. If the camera is moved, you must reconfigure.** After running the configuration utility, your config file will have some additional default parameters added automatically. +1. You must provide a configuration text file that contains important parameters for your setup. At a minimum, this config file must define the parameters `src_fn` and `vfov`, which are the path to the image source (video file or camera) and vertical field of view (in degrees) of your camera/lens respectively. If you are running live from the camera, then `src_fn`is the camera index (e.g. 0). You will find an example config file in the `sample` directory. +2. You must run the interactive configuration program (configGui). This program will guide you through the configuration of the track ball region of interest within your input images and the transformation between the camera's and animal's frames of reference. A more detailed guide for configuring FicTrac for your setup, as well as a complete list and explanation of parameters that can be specified, can be found in the [FicTrac manual](). -The commands for running the configuration utility under Windows and Ubuntu (Linux) are almost identical. Simply open a terminal in the FicTrac project folder and type: - -#### Windows -``` -.\bin\Release\configGui.exe path\to\config.txt -``` - -#### Ubuntu (Linux) -``` -./bin/configGui path/to/config.txt -``` - ### Running FicTrac -Once you have configured FicTrac for your setup, you may run FicTrac simply by opening a terminal in the FicTrac project folder and typing: - -#### Windows -``` -.\bin\Release\fictrac.exe path\to\config.txt -``` - -#### Ubuntu (Linux) -We execute as super user so that we can set the FicTrac process to higher priority than other system processes. -``` -sudo ./bin/fictrac path/to/config.txt -``` - -### Process sample data - -After you have build FicTrac, you can process the sample data set to make sure everything is running fine. Simply open a terminal in the FicTrac project folder and type: - -#### Windows -``` -cd sample -..\bin\Release\configGui.exe config.txt -``` -The sample config file `config.txt` is already configured for the sample data, but you can step through the configuration process to check that everything looks ok. Then, in the same terminal window, type: -``` -..\bin\Release\fictrac.exe config.txt -``` +To run FicTrac on the provided sample data, simply open a terminal in the FicTrac project folder and type: -#### Ubuntu (Linux) ``` cd sample -../bin/configGui config.txt +[Windows] ..\bin\Release\configGui.exe config.txt +[Linux] ../bin/configGui config.txt ``` The sample config file `config.txt` is already configured for the sample data, but you can step through the configuration process to check that everything looks ok. Then, in the same terminal window, type: ``` -sudo ../bin/Release/fictrac.exe config.txt +[Windows] ..\bin\Release\fictrac.exe config.txt +[Linux] sudo ../bin/fictrac config.txt ``` ## Research From cd9ad141bae2c1ef505d4b6ff4d079b3d4ced2bb Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 18 Nov 2018 16:30:08 +0100 Subject: [PATCH 005/235] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 295065b..9d3cbb7 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ If you are using a USB3 camera and are receiving error messages when FicTrac tri ##### PGR (FLIR) Spinnaker SDK 1. Download and install the Spinnaker SDK from [PGR downloads page](https://www.ptgrey.com/support/downloads). -2. When preparing the build files for FicTrac using Cmake, you will need to specify to use Spinnaker using the switch `-D PGR_USB3=ON` and depending on where you installed the SDK, you may also need to provide the SDK directory path using the switch `-D PGR_DIR=...`. For example, for a [Windows installation](#windows-installation) you would replace step 4 with (replace \path\to with your correct local path): +2. When preparing the build files for FicTrac using Cmake, you will need to specify to use Spinnaker using the switch `-D PGR_USB3=ON` and depending on where you installed the SDK, you may also need to provide the SDK directory path using the switch `-D PGR_DIR=...`. For example, for a [Windows installation](#windows-installation) you would replace step 4 with: ``` cmake -G "Visual Studio 15 2017 Win64" -D OPENCV_DIR="C:\path\to\opencv-3.4.2\build" -D NLOPT_DIR="C:\path\to\nlopt-2.4.2\" -D PGR_USB3=ON -D PGR_DIR="C:\path\to\Spinnaker" .. ``` @@ -112,7 +112,7 @@ Before running FicTrac, you may configure your camera (frame rate, resolution, e ### Configuration -There are two neccessary steps to configure FicTrac prior to running the program: +There are two necessary steps to configure FicTrac prior to running the program: 1. You must provide a configuration text file that contains important parameters for your setup. At a minimum, this config file must define the parameters `src_fn` and `vfov`, which are the path to the image source (video file or camera) and vertical field of view (in degrees) of your camera/lens respectively. If you are running live from the camera, then `src_fn`is the camera index (e.g. 0). You will find an example config file in the `sample` directory. 2. You must run the interactive configuration program (configGui). This program will guide you through the configuration of the track ball region of interest within your input images and the transformation between the camera's and animal's frames of reference. From 8c798879645543df6e0579183a724c7a76166258 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 18 Nov 2018 16:32:43 +0100 Subject: [PATCH 006/235] correct spinnaker paths for cmake configuration --- CMakeLists.txt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 51e3f05..5d61941 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ configure_file ( # dependency search dirs set(OPENCV_DIR "." CACHE PATH "Path to OpenCV folder containing OpenCVConfig.cmake") -set(NLOPT_DIR "." CACHE PATH "Path to NLOpt folder containing libnlopt-0.lib") +set(NLOPT_DIR "." CACHE PATH "Path to NLopt folder containing libnlopt-0.lib") # output dirs set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib) @@ -47,14 +47,19 @@ else() # gcc endif() get_filename_component(NLOPT_DIR ${NLOPT_LIB} DIRECTORY) if(NLOPT_LIB) - message(STATUS "Found NLOpt lib ${NLOPT_LIB}") + message(STATUS "Found NLopt lib ${NLOPT_LIB}") if(MSVC) message(STATUS "You might need to add ${NLOPT_DIR} to your PATH to be able to run the executable.") endif() else() - message(FATAL_ERROR "Error! Could not find NLOpt lib at ${NLOPT_DIR}!") + message(FATAL_ERROR "Error! Could not find NLopt lib at ${NLOPT_DIR}!") endif() if(PGR_USB3) + get_filename_component(PGR_DIR ${PGR_LIB} DIRECTORY) + get_filename_component(PGR_DIR ${PGR_DIR} DIRECTORY) # step up 1 level + if(MSVC) + get_filename_component(PGR_DIR ${PGR_DIR} DIRECTORY) # step up 1 level + endif() if(PGR_LIB) message(STATUS "Found PGR Spinnaker lib ${PGR_LIB}") else() @@ -65,8 +70,11 @@ endif() # add include dirs include_directories(${PROJECT_SOURCE_DIR}/include ${OpenCV_INCLUDE_DIRS} ${NLOPT_DIR}) if(PGR_USB3) - include_directories(${PGR_DIR}/include) - include_directories(${PGR_DIR}/include/spinnaker) + if(MSVC) + include_directories(${PGR_DIR}/include) + else() + include_directories(${PGR_DIR}/include/spinnaker) # for ubuntu default install dir + endif() endif() # find sources to build From 5ace4f7347ec0477073aa88fb2b55bdf13b6729e Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 18 Nov 2018 16:34:35 +0100 Subject: [PATCH 007/235] terminate on ctrl-c; minor debug formatting; no wait for input on exit --- exec/fictrac.cpp | 20 +++++++++++++++----- include/Trackball.h | 3 ++- src/Trackball.cpp | 24 +++++++++++------------- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/exec/fictrac.cpp b/exec/fictrac.cpp index 6671a34..603cc2a 100644 --- a/exec/fictrac.cpp +++ b/exec/fictrac.cpp @@ -11,9 +11,14 @@ #include "fictrac_version.h" #include +#include using std::string; +/// Ctrl-c handling +bool _active = true; +void ctrlcHandler(int /*signum*/) { _active = false; } + int main(int argc, char *argv[]) { @@ -47,8 +52,8 @@ int main(int argc, char *argv[]) /// Set logging level. Logger::setVerbosity(log_level); - //// Catch cntl-c - //signal(SIGINT, TERMINATE); + // Catch cntl-c + signal(SIGINT, ctrlcHandler); /// Set high priority (when run as SU). if (!SetProcessHighPriority()) { @@ -63,12 +68,17 @@ int main(int argc, char *argv[]) SetThreadNormalPriority(); // wait for tracking to finish - while (tracker.isActive()) { sleep(500); } + while (tracker.isActive()) { + if (!_active) { + tracker.terminate(); + } + sleep(250); + } //tracker.printState(); tracker.writeTemplate(); - PRINT("\n\nHit ENTER to exit.."); - getchar_clean(); + //PRINT("\n\nHit ENTER to exit.."); + //getchar_clean(); return 0; } diff --git a/include/Trackball.h b/include/Trackball.h index 891b4eb..e030f07 100644 --- a/include/Trackball.h +++ b/include/Trackball.h @@ -37,6 +37,7 @@ class Trackball ~Trackball(); bool isActive() { return _active; } + void terminate() { _kill = true; } void printState(); bool writeTemplate(std::string fn = ""); @@ -129,6 +130,6 @@ class Trackball std::unique_ptr _log; /// Thread stuff. - std::atomic_bool _active; + std::atomic_bool _active, _kill; std::unique_ptr _thread; }; diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 1f18c34..80877bb 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -87,7 +87,7 @@ bool intersectSphere(const double camVec[3], double sphereVec[3], const double r /// /// Trackball::Trackball(string cfg_fn) - : _init(false), _reset(true), _clean_map(true), _active(true) + : _init(false), _reset(true), _clean_map(true), _active(true), _kill(false) { /// Load and parse config file. if (_cfg.read(cfg_fn) <= 0) { @@ -109,7 +109,7 @@ Trackball::Trackball(string cfg_fn) // then try loading as video file source = make_shared(src_fn); } -#else // PGR_USB3 +#else // !PGR_USB3 source = std::make_shared(src_fn); #endif // PGR_USB3 if (!source->isOpen()) { @@ -511,7 +511,7 @@ void Trackball::process() double t1, t2, t3, t4, t5, t6; double t1avg = 0, t2avg = 0, t3avg = 0, t4avg = 0, t5avg = 0, t6avg = 0; double tfirst = -1, tlast = 0; - while (_active && _frameGrabber->getNextFrameSet(_src_frame, _roi_frame, _ts)) { + while (!_kill && _active && _frameGrabber->getNextFrameSet(_src_frame, _roi_frame, _ts)) { t1 = ts_ms(); PRINT(""); @@ -519,11 +519,8 @@ void Trackball::process() /// Localise current view of sphere. if (!doSearch(_global_search)) { - t2 = ts_ms(); - t3 = ts_ms(); - t4 = ts_ms(); - t5 = ts_ms(); - LOG_ERR("Error! Could not match current sphere orientation to within error threshold (%f).\nNo data will be output for this frame!", _error_thresh); + t2 = t3 = t4 = t5 = ts_ms(); + LOG_WRN("Warning! Could not match current sphere orientation to within error threshold (%f).\nNo data will be output for this frame!", _error_thresh); nbad++; } else { @@ -584,7 +581,7 @@ void Trackball::process() fps_avg += 0.25 * (fps_out - fps_avg); static double prev_ts = _ts; double fps_in = (_ts - prev_ts) > 0 ? 1000 / (_ts - prev_ts) : 0; - LOG("Average frame rate [curr input / output]: %.1f [%.1f / %.1f] fps", fps_avg, fps_in, fps_out); + LOG("Average frame rate [in/out]: %.1f [%.1f / %.1f] fps", fps_avg, fps_in, fps_out); prev_t6 = t6; prev_ts = _ts; @@ -601,15 +598,16 @@ void Trackball::process() _frameGrabber->terminate(); // make sure we've stopped grabbing frames as well if (_cnt > 1) { - PRINT(""); - LOG("Trackball timing"); + PRINT("\n----------------------------------------------------------------------------"); + LOG("Trackball timing:"); LOG("Average grab/opt/map/plot/log/disp time: %.1f / %.1f / %.1f / %.1f / %.1f / %.1f ms", t1avg / (_cnt - 1), t2avg / (_cnt - 1), t3avg / (_cnt - 1), t4avg / (_cnt - 1), t5avg / (_cnt - 1), t6avg / (_cnt - 1)); LOG("Average fps: %.2f", 1000. * (_cnt - 1) / (tlast - tfirst)); PRINT(""); - LOG("Optimiser test data."); + LOG("Optimiser test data:"); LOG("Average number evals / frame: %.1f", _evals_avg / (_cnt - 1)); + PRINT("----------------------------------------------------------------------------"); } _active = false; @@ -1291,7 +1289,7 @@ void Trackball::drawCanvas(std::shared_ptr data) uint16_t key = cv::waitKey(1); if (key == 0x1B) { // esc LOG("Exiting.."); - _active = false; + terminate(); } if (_save_raw) { From 80aaea73e6c2cd67cb5fe75207ba9b3d1e42ff29 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 18 Nov 2018 16:34:53 +0100 Subject: [PATCH 008/235] remove cvStartWindowThread() --- exec/configGui.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/exec/configGui.cpp b/exec/configGui.cpp index 91c5fcc..bc588f8 100644 --- a/exec/configGui.cpp +++ b/exec/configGui.cpp @@ -64,9 +64,6 @@ int main(int argc, char *argv[]) getchar_clean(); return -1; } - - /// Init OpenCV windows - only used by GTK backend? - cvStartWindowThread(); /// Run configuration GUI. bool ret = cfg.run(); From e205b7a124ec7cfae069d2a912b4de7558d50fec Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 18 Nov 2018 16:36:17 +0100 Subject: [PATCH 009/235] minor debug; remove extra line from generated config file; win line endings handled properly on linux --- src/ConfigGUI.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index 1d48d74..96b9cb1 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -422,7 +422,7 @@ bool ConfigGui::run() const char exit_key = 0x1b; #ifdef WIN32 const char enter_key = 0x0d; -#else // WIN32 +#else // !WIN32 const char enter_key = 0x0a; #endif // WIN32 const int click_rad = std::max(int(_w/150+0.5), 5); @@ -470,7 +470,7 @@ bool ConfigGui::run() /// Draw fitted circumference. if (r > 0) { drawCircle_camModel(disp_frame, _cam_model, c, r, Scalar(255,0,0), false); - + /// Display. cv::imshow("configGUI", disp_frame); cv::waitKey(100); //FIXME: why do we have to wait so long to make sure the frame is drawn? @@ -480,7 +480,7 @@ bool ConfigGui::run() // input loop while (true) { cv::waitKey(100); //FIXME: dirty hack - sometimes image doesn't draw, at least with this line we can just mash keys until it does - printf("\n Would you like to keep the existing spherer ROI configuration ([y]/n)? "); + printf("\n Would you like to keep the existing sphere ROI configuration ([y]/n)? "); in = getchar(); switch (in) { From fbafae7b4513585cce846ff893ace34b487f3edc Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 18 Nov 2018 16:36:37 +0100 Subject: [PATCH 010/235] minor debug; remove extra line from generated config file; win line endings handled properly on linux --- src/ConfigParser.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index f7902ea..1d122c0 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -71,20 +71,20 @@ int ConfigParser::read(string fn) } /// Tokenise - const string whitespace = ", \t\n\r"; + const string whitespace = ", \t\n"; std::size_t delim = line.find(":"); if (delim >= line.size()) { continue; } // skip blank lines string key = line.substr(0, line.find_last_not_of(whitespace, delim - 1) + 1), val = ""; try { val = line.substr(line.find_first_not_of(whitespace, delim + 1)); - //val.erase(std::remove(val.begin(), val.end(), '\r'), val.end()); // remove /r under linux + val.erase(std::remove(val.begin(), val.end(), '\r'), val.end()); // remove /r under linux } catch (...) {} // add blank values /// Add to map _data[key] = val; - LOG_DBG("Extracted key: %s val: %s", key.c_str(), val.c_str()); + LOG_DBG("Extracted key: |%s| val: |%s|", key.c_str(), val.c_str()); } /// Clean up @@ -124,9 +124,11 @@ int ConfigParser::write(string fn) } /// Write comments - f << std::endl; - for (auto c : _comments) { - f << c << std::endl; + if (_comments.size() > 0) { + f << std::endl; + for (auto c : _comments) { + f << c << std::endl; + } } /// Clean up From e9549f9e90abda38433714348a67852048c69da7 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 18 Nov 2018 16:37:09 +0100 Subject: [PATCH 011/235] get/set camera frame rate for PGR cameras --- src/CVSource.cpp | 13 +++++++------ src/PGRSource.cpp | 25 ++++++++++++++++++++----- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/CVSource.cpp b/src/CVSource.cpp index f2363b3..206d5b8 100644 --- a/src/CVSource.cpp +++ b/src/CVSource.cpp @@ -74,10 +74,11 @@ CVSource::~CVSource() /// double CVSource::getFPS() { + double fps = _fps; if (_open) { - _fps = _cap->get(cv::CAP_PROP_FPS); + fps = _cap->get(cv::CAP_PROP_FPS); } - return _fps; + return fps; } /// @@ -87,11 +88,11 @@ bool CVSource::setFPS(double fps) { bool ret = false; if (_open && (fps > 0)) { - _fps = fps; - if (!_cap->set(cv::CAP_PROP_FPS, _fps)) { - LOG_WRN("Warning! Failed to set device fps (attempted to set fps=%.2f).", _fps); + if (!_cap->set(cv::CAP_PROP_FPS, fps)) { + LOG_WRN("Warning! Failed to set device fps (attempted to set fps=%.2f).", fps); } - else { ret = true; } + _fps = getFPS(); + LOG("Device frame rate is now %.2f", _fps); } return ret; } diff --git a/src/PGRSource.cpp b/src/PGRSource.cpp index 6664d35..67b992a 100644 --- a/src/PGRSource.cpp +++ b/src/PGRSource.cpp @@ -9,6 +9,7 @@ #include "PGRSource.h" #include "Logger.h" +#include "timing.h" #include "SpinGenApi/SpinnakerGenApi.h" @@ -80,7 +81,7 @@ PGRSource::PGRSource(int index) // Get some params _width = _cam->Width(); _height = _cam->Height(); - _fps = _cam->AcquisitionFrameRate(); + _fps = getFPS(); LOG("PGR camera initialised (%dx%d @ %.3f fps)!", _width, _height, _fps); @@ -112,14 +113,24 @@ PGRSource::~PGRSource() double PGRSource::getFPS() { - // do nothing - return _fps; + double fps = _fps; + if (_open) { + fps = _cam->AcquisitionResultingFrameRate(); + } + return fps; } bool PGRSource::setFPS(double fps) { - _fps = fps; - return false; + bool ret = false; + if (_open && (fps > 0)) { + _cam->AcquisitionFrameRateEnable.SetValue(true); + _cam->AcquisitionFrameRate.SetValue(fps); + _fps = getFPS(); + LOG("Device frame rate is now %.2f", _fps); + ret = true; + } + return ret; } bool PGRSource::grab(cv::Mat& frame) @@ -132,7 +143,11 @@ bool PGRSource::grab(cv::Mat& frame) // Retrieve next received image long int timeout = _fps > 0 ? std::max(static_cast(1000), static_cast(1000. / _fps)) : 1000; // set capture timeout to at least 1000 ms pgr_image = _cam->GetNextImage(timeout); + double ts = static_cast(ts_ms()); // backup, in case the device timestamp is junk _timestamp = _cam->Timestamp(); + if (_timestamp <= 0) { + _timestamp = ts; + } // Ensure image completion if (pgr_image->IsIncomplete()) { From ec57aa12754a0450c9a4a84d7ef8132bc5a4291e Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 18 Nov 2018 16:37:32 +0100 Subject: [PATCH 012/235] file log always at dbg level --- src/Logger.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Logger.cpp b/src/Logger.cpp index 514a7e0..271268a 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -61,22 +61,21 @@ void Logger::mprintf(LogLevel lvl, string func, string format, ...) // not re-entrant lock_guard l1(log._pMutex); + // expand args + va_list args; + va_start(args, format); + vsnprintf(buf, buf_size, format.c_str(), args); + va_end(args); + // print and log if ((int)lvl >= (int)verbosity()) { - - // expand args - va_list args; - va_start(args, format); - vsnprintf(buf, buf_size, format.c_str(), args); - va_end(args); - // async printing to console log._cout->addMsg(string(buf) + "\n"); + } - // don't log display text to file - if (lvl != PRT) { - // async logging to file (with additional info) - log._log->addMsg(to_string(elapsed_secs()) + " " + func + " [" + log.LogLevelStrings[lvl] + "] " + buf + "\n"); - } + // don't log display text to file (but log everything else) + if (lvl != PRT) { + // async logging to file (with additional info) + log._log->addMsg(to_string(elapsed_secs()) + " " + func + " [" + log.LogLevelStrings[lvl] + "] " + buf + "\n"); } } From 25bafdc34dead711fa296e528a7b3953d9787faf Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 18 Nov 2018 16:54:26 +0100 Subject: [PATCH 013/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d3cbb7..153b4e5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

-**FicTrac** is an open source software library and set of executables for reconstructing the fictive path of an animal walking on a patterned sphere. The software is fast, flexible, and easy to use and simplifies the setup of closed-loop tracking experiments. +**FicTrac** is an open-source software library for reconstructing the fictive path of an animal walking on a patterned sphere. The software is fast, flexible, and easy to use and simplifies the setup of closed-loop tracking experiments. FicTrac was originally developed by researchers at the [Queensland Brain Institute](http://qbi.uq.edu.au/) at the University of Queensland, Australia for tracking honeybees and fruit flies during closed-loop tethered walking experiments, but it has since proved useful for tracking a wide range of animals with different movement speeds, track ball diameters and patterns, and stimuli. From 0dff25b8cfb2f3d5a1950f7e2eca6bad094b333a Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 18 Nov 2018 16:55:17 +0100 Subject: [PATCH 014/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 153b4e5..9395def 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

-**FicTrac** is an open-source software library for reconstructing the fictive path of an animal walking on a patterned sphere. The software is fast, flexible, and easy to use and simplifies the setup of closed-loop tracking experiments. +**FicTrac** is an open-source software library for reconstructing the fictive path of an animal walking on a patterned sphere. The software is fast, flexible, easy to use, and simplifies the setup of closed-loop tracking experiments. FicTrac was originally developed by researchers at the [Queensland Brain Institute](http://qbi.uq.edu.au/) at the University of Queensland, Australia for tracking honeybees and fruit flies during closed-loop tethered walking experiments, but it has since proved useful for tracking a wide range of animals with different movement speeds, track ball diameters and patterns, and stimuli. From 0b56887465ef129ac2ced6b6840cdb3b95382e6b Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 30 Nov 2018 18:10:02 +0100 Subject: [PATCH 015/235] clean up extra files --- TODO.txt | 20 --------- doc/data_header.txt | 82 +++++++++++++++++------------------ doc/ubuntu_setup.txt | 37 ---------------- docker/fictrac_dev.dockerfile | 29 ------------- 4 files changed, 41 insertions(+), 127 deletions(-) delete mode 100644 TODO.txt delete mode 100644 doc/ubuntu_setup.txt delete mode 100644 docker/fictrac_dev.dockerfile diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index 8e65b40..0000000 --- a/TODO.txt +++ /dev/null @@ -1,20 +0,0 @@ -== Development == -* write win/linux socket clients - - -== Testing == -* benchmark different ball patterns -* test with diverse recordings -* benchmark with noisy simulations -* compare performance with FicTrac v1 -* test sockets - - -== Misc == -* update website -* write new manual - - -== Research == -* SLAM-like map with elastic weights between pixels to enable distribution of error on loop closures? -* weight view matching to lessen impact of bad pixels near horizon? diff --git a/doc/data_header.txt b/doc/data_header.txt index 87d711a..053d897 100644 --- a/doc/data_header.txt +++ b/doc/data_header.txt @@ -1,41 +1,41 @@ - COL PARAMETER DESCRIPTION - 1 frame counter Corresponding video frame (starts at #1). - 2-4 delta rotation vector (cam) Change in orientation since last frame, - represented as rotation angle/axis (radians) - in camera coordinates (x right, y down, z - forward). - 5 delta rotation error score Error score associated with rotation - estimate. - 6-8 delta rotation vector (lab) Change in orientation since last frame, - represented as rotation angle/axis (radians) - in laboratory coordinates (see - *configImg.jpg). - 9-11 absolute rotation vector (cam) Absolute orientation of the sphere - represented as rotation angle/axis (radians) - in camera coordinates. - 12-14 absolute rotation vector (lab) Absolute orientation of the sphere - represented as rotation angle/axis (radians) - in laboratory coordinates. - 15-16 integrated x/y position (lab) Integrated x/y position (radians) in - laboratory coordinates. Scale by sphere - radius for true position (?). - 17 integrated animal heading (lab) Integrated heading orientation (radians) of - the animal in laboratory coordinates. This - is the direction the animal is facing. - 18 animal movement direction (lab) Instantaneous running direction (radians) of - the animal in laboratory coordinates. This is - the direction the animal is moving in the lab - frame (add to animal heading to get direction - in world). - 19 animal movement speed Instantaneous running speed (radians/frame) - of the animal. Scale by sphere radius for - true speed (?). - 20-21 integrated forward/side motion Integrated x/y position (radians) of the - sphere in laboratory coordinates neglecting - heading. Equivalent to the output from two - optic mice. - 22 timestamp Either position in video file (ms) or real - capture time for image frame. - 23 sequence counter Position in current frame sequence. Usually - corresponds directly to frame counter, but - can reset to 1 if tracking is reset. + COL PARAMETER DESCRIPTION + 1 frame counter Corresponding video frame (starts at #1). + 2-4 delta rotation vector (cam) Change in orientation since last frame, + represented as rotation angle/axis (radians) + in camera coordinates (x right, y down, z + forward). + 5 delta rotation error score Error score associated with rotation + estimate. + 6-8 delta rotation vector (lab) Change in orientation since last frame, + represented as rotation angle/axis (radians) + in laboratory coordinates (see + *configImg.jpg). + 9-11 absolute rotation vector (cam) Absolute orientation of the sphere + represented as rotation angle/axis (radians) + in camera coordinates. + 12-14 absolute rotation vector (lab) Absolute orientation of the sphere + represented as rotation angle/axis (radians) + in laboratory coordinates. + 15-16 integrated x/y position (lab) Integrated x/y position (radians) in + laboratory coordinates. Scale by sphere + radius for true position (?). + 17 integrated animal heading (lab) Integrated heading orientation (radians) of + the animal in laboratory coordinates. This + is the direction the animal is facing. + 18 animal movement direction (lab) Instantaneous running direction (radians) of + the animal in laboratory coordinates. This is + the direction the animal is moving in the lab + frame (add to animal heading to get direction + in world). + 19 animal movement speed Instantaneous running speed (radians/frame) + of the animal. Scale by sphere radius for + true speed (?). + 20-21 integrated forward/side motion Integrated x/y position (radians) of the + sphere in laboratory coordinates neglecting + heading. Equivalent to the output from two + optic mice. + 22 timestamp Either position in video file (ms) or real + capture time for image frame. + 23 sequence counter Position in current frame sequence. Usually + corresponds directly to frame counter, but + can reset to 1 if tracking is reset. diff --git a/doc/ubuntu_setup.txt b/doc/ubuntu_setup.txt deleted file mode 100644 index d3cf5f1..0000000 --- a/doc/ubuntu_setup.txt +++ /dev/null @@ -1,37 +0,0 @@ -Steps for setting up Ubuntu in virtual machine and building FicTrac from scratch. - -1. Download and install Oracle VM VirtualBox manager. -2. Download and install latest Lubuntu 64-bit operating system image (*.iso). - https://lubuntu.net/ -3. Open VirtualBox and create new machine - a. Adjust memory, processor, and USB3 settings - b. Allocate virtual hard drive at least 8 GB -4. Launch new virtual machine - a. Machine will fail to find operating system - b. Load Lubuntu OS image as optical drive (under device menu) -5. Install and launch Lubuntu - a. Select lightweight installation (if you don't want games and office apps) - b. Allow installation of 3rd party libraries (non-free) -6. Install guest additions in Lubuntu - a. Devices -> insert guest additions cd - b. Open terminal and navigate to mounted folder - c. sudo sh VBoxLinuxAdditions.run - d. Reboot Lubuntu -6. Open terminal and install prerequisites - a. sudo apt-get install build-essential cmake cmake-gui pkg-config gedit cairomm-1.0 libjpeg-dev libpng-dev libtiff-dev libavcodec-dev libavformat-dev libswscale-dev libv4l-dev libx264-dev libatlas-base-dev libboost-system-dev libnlopt-dev libgtk-3-dev libdc1394-22-dev -7. Install latest version of OpenCV - a. Download and extract latest source package from https://opencv.org/releases.html - b. in terminal navigate to opencv folder - c. mkdir build - d. cd build - e. cmake-gui .. - f. configure - g. set install prefix to /usr/local - h. disable building perf tests, building tests - i. disable all modules except for core, highgui, imgcodecs, imgproc, video, videoio - j. generate and close cmake-gui - k. make -j4 - l. sudo make install - m. sudo ldconfig - n. to check opencv has been installed run: pkg-config --modversion opencv -8. Download and build FicTrac source code \ No newline at end of file diff --git a/docker/fictrac_dev.dockerfile b/docker/fictrac_dev.dockerfile deleted file mode 100644 index 8db4704..0000000 --- a/docker/fictrac_dev.dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -## To build, execute from current directory: -# docker build -t fictrac_dev -f .\fictrac_dev.dockerfile . -## To enable volume sharing between host/container, execute in elevated powershell: -# Set-NetConnectionProfile -InterfaceAlias "vEthernet (DockerNAT)" -NetworkCategory Private -# (restart docker for windows) -## To launch container: -# docker run -v c:\Users\richardm:/home --name fictrac_dev -ti fictrac_dev /sbin/my_init -- bash -l -## To launch privileged container (e.g. for dmesg access) -# docker run --privileged [...] -## To exit/stop container: -# exit -## To relaunch container: -# docker start -i fictrac_dev - -FROM phusion/baseimage - -# Use baseimage-docker's init system. -CMD ["/sbin/my_init"] - -ENV DISPLAY=10.0.75.1:0 -#RUN rm -f /etc/service/sshd/down -#RUN sed -i 's/.*PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config - -# Install prerequisities -RUN apt-get update -RUN apt-get -y install make pkg-config libopencv-dev libboost-all-dev libnlopt-dev libcairomm-1.0 gedit git gitk git-gui - -# Clean up APT when done. -RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* From e856908a14353a7c58eb7ca85c162caf4f9acbdd Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 30 Nov 2018 20:30:36 +0100 Subject: [PATCH 016/235] Create params.md --- doc/params.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 doc/params.md diff --git a/doc/params.md b/doc/params.md new file mode 100644 index 0000000..89bf546 --- /dev/null +++ b/doc/params.md @@ -0,0 +1,35 @@ +This document provides a brief overview of FicTrac's configuration parameters. Many of these parameters are set automatically by the configuration utility. See the main documentation for instructions on how to [configure FicTrac](README.md#Configuration) before use. + +In the table below, the various possible parameters are listed. If nothing is listed under `Default value` then the parameter must be specified by the user. The valid range may use [interval notation](https://en.wikipedia.org/wiki/Interval_(mathematics)). The `Should I touch it?` column should give you some idea of which params to play around with. + +| Param name | Param type | Default value | Valid range | Should I touch it? | Description | +|------------|------------|---------------|-------------|--------------------|-------------| +| src_fn | string OR int | | int=[0,inf) | Yes, you have to | A string that specifies the path to the input video file, OR an integer that specifies which of several connected USB cameras to use. Paths can be absolute or relative to the working directory. | +| vfov | float | | (0,inf) | Yes, you have to | Vertical field of view of the input images in degrees. | +| | | | | | | +| do_display | bool | y | y/n | If you want to | Display debug screen during tracking. Slows execution very slightly. | +| save_debug | bool | n | y/n | If you want to | Record the debug screen to video file. Note that if the source frame rate is higher than FicTrac's processing frame rate, frames may be dropped from the video file. | +| save_raw | bool | n | y/n | If you want to | Record the input image stream to video file. Note that if the source frame rate is higher than FicTrac's processing frame rate, frames may be dropped from the video file. | +| | | | | | | +| q_factor | int | 6 | (0,inf) | Only if you need to | Adjusts the resolution of the tracking window. Smaller values correspond to coarser but quicker tracking and vice-versa. Normally in the range [3,10]. | +| src_fps | float | -1 | (0,inf) | Only if you need to | If set, FicTrac will attempt to set the frame rate for the image source (video file or camera). | +| max_bad_frames | int | -1 | (0,inf) | Only if you need to | If set, FicTrac will reset tracking after being unable to match this many frames in a row. Defaults to never resetting tracking. | +| opt_do_global | bool | n | y/n | Only if you need to | Perform a slow global search after max_bad_frames are reached. This may allow FicTrac to recover after a tracking fail, but should only be used when playing back from video file, as it is slow! | +| opt_max_err | float | -1 | [0,inf) | Only if you need to | If set, specifies the maximum allowable matching error before declaring a bad frame (i.e. tracking fail). Matching error is printed to screen during tracking (err=...), and also output in the [data file](doc/data_header.txt) (delta rotation error score). If unset, FicTrac will never detect bad matches (tracking will fail silently). | +| thr_ratio | float | 1.25 | (0,inf) | Only if you need to | Adjusts the adaptive thresholding of the input image. Values > 1 will favour foreground regions (more white in thresholded image) and values < 1 will favour background regions (more black in thresholded image). | +| thr_win_pc | float | 0.2 | [0,1] | Only if you need to | Adjusts the size of the neighbourhood window to use for adaptive thresholding of the input image, specified as a percentage of the width of the tracking window. Larger values avoid over-segmentation, whilst smaller values make segmentation more robust to illumination gradients on the trackball. | +| | | | | | | +| opt_max_evals | int | 50 | (0,inf) | Probably not | Specifies the maximum number of minimisation iterations to perform each frame. Smaller values may improve tracking frame rate at the risk of finding sub-optimal matches. Number of optimisation iterations is printed to screen during tracking (its=...). | +| opt_bound | float | 0.35 | (0,inf) | Probably not | Specifies the optimisation search range in radians. Larger values will facilitate more track ball rotation per frame, but result in slower tracking and also possibly lead to false matches. | +| opt_tol | float | 0.001 | (0,inf) | Probably not | Specifies the minimisation termination criteria for absolute change in input parameters (delta rotation vector). | +| | | | | | | +| c2a_cnrs_xy | vector\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XY axes. Set interactively in the ConfigGUI. | +| c2a_cnrs_yz | vector\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's YZ axes. Set interactively in the ConfigGUI. | +| c2a_cnrs_xz | vector\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XZ axes. Set interactively in the ConfigGUI. | +| c2a_src | string | | | Set by ConfigGui | Specifies which of the above corner sets is used to compute the camera-animal transform. Set interactively in the ConfigGUI. | +| c2a_r | vector\ | | | Set by ConfigGui | Rotational component of the camera-animal transform. Computed automatically by ConfigGUI. | +| c2a_t | vector\ | | | Set by ConfigGui | Translational component of the camera-animal transform. Computed automatically by ConfigGUI. | +| roi_circ | vector\ | | | Set by ConfigGui | Specifies points {X1,Y1,X2,Y2,...} around the circumference of the trackball in the input image. Set interactively in the ConfigGUI. | +| roi_c | vector\ | | | Set by ConfigGui | Camera-frame vector describing the centre point of the trackball in the input image. Computed automatically by ConfigGUI. | +| roi_r | float | | | Set by ConfigGui | Half-angle describing the radius of the trackball in the input image. Computed automatically by ConfigGUI. | +| roi_ignr | vector> | | | Set by ConfigGui | Specifies possibly several polygon regions {{X11,Y11,X12,Y12,...},{X21,Y21,X22,Y22,...},...} that should be ignored during matching (e.g. where the animal obscures the trackball). | From ca5362d8dd5087c2255ec3a03ee8566b6d68537c Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 30 Nov 2018 20:33:12 +0100 Subject: [PATCH 017/235] Update params.md --- doc/params.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/params.md b/doc/params.md index 89bf546..be41b9f 100644 --- a/doc/params.md +++ b/doc/params.md @@ -4,7 +4,7 @@ In the table below, the various possible parameters are listed. If nothing is li | Param name | Param type | Default value | Valid range | Should I touch it? | Description | |------------|------------|---------------|-------------|--------------------|-------------| -| src_fn | string OR int | | int=[0,inf) | Yes, you have to | A string that specifies the path to the input video file, OR an integer that specifies which of several connected USB cameras to use. Paths can be absolute or relative to the working directory. | +| src_fn | string OR int | | int=\[0,inf) | Yes, you have to | A string that specifies the path to the input video file, OR an integer that specifies which of several connected USB cameras to use. Paths can be absolute or relative to the working directory. | | vfov | float | | (0,inf) | Yes, you have to | Vertical field of view of the input images in degrees. | | | | | | | | | do_display | bool | y | y/n | If you want to | Display debug screen during tracking. Slows execution very slightly. | @@ -15,21 +15,21 @@ In the table below, the various possible parameters are listed. If nothing is li | src_fps | float | -1 | (0,inf) | Only if you need to | If set, FicTrac will attempt to set the frame rate for the image source (video file or camera). | | max_bad_frames | int | -1 | (0,inf) | Only if you need to | If set, FicTrac will reset tracking after being unable to match this many frames in a row. Defaults to never resetting tracking. | | opt_do_global | bool | n | y/n | Only if you need to | Perform a slow global search after max_bad_frames are reached. This may allow FicTrac to recover after a tracking fail, but should only be used when playing back from video file, as it is slow! | -| opt_max_err | float | -1 | [0,inf) | Only if you need to | If set, specifies the maximum allowable matching error before declaring a bad frame (i.e. tracking fail). Matching error is printed to screen during tracking (err=...), and also output in the [data file](doc/data_header.txt) (delta rotation error score). If unset, FicTrac will never detect bad matches (tracking will fail silently). | +| opt_max_err | float | -1 | \[0,inf) | Only if you need to | If set, specifies the maximum allowable matching error before declaring a bad frame (i.e. tracking fail). Matching error is printed to screen during tracking (err=...), and also output in the [data file](doc/data_header.txt) (delta rotation error score). If unset, FicTrac will never detect bad matches (tracking will fail silently). | | thr_ratio | float | 1.25 | (0,inf) | Only if you need to | Adjusts the adaptive thresholding of the input image. Values > 1 will favour foreground regions (more white in thresholded image) and values < 1 will favour background regions (more black in thresholded image). | -| thr_win_pc | float | 0.2 | [0,1] | Only if you need to | Adjusts the size of the neighbourhood window to use for adaptive thresholding of the input image, specified as a percentage of the width of the tracking window. Larger values avoid over-segmentation, whilst smaller values make segmentation more robust to illumination gradients on the trackball. | +| thr_win_pc | float | 0.2 | \[0,1] | Only if you need to | Adjusts the size of the neighbourhood window to use for adaptive thresholding of the input image, specified as a percentage of the width of the tracking window. Larger values avoid over-segmentation, whilst smaller values make segmentation more robust to illumination gradients on the trackball. | | | | | | | | | opt_max_evals | int | 50 | (0,inf) | Probably not | Specifies the maximum number of minimisation iterations to perform each frame. Smaller values may improve tracking frame rate at the risk of finding sub-optimal matches. Number of optimisation iterations is printed to screen during tracking (its=...). | | opt_bound | float | 0.35 | (0,inf) | Probably not | Specifies the optimisation search range in radians. Larger values will facilitate more track ball rotation per frame, but result in slower tracking and also possibly lead to false matches. | | opt_tol | float | 0.001 | (0,inf) | Probably not | Specifies the minimisation termination criteria for absolute change in input parameters (delta rotation vector). | | | | | | | | -| c2a_cnrs_xy | vector\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XY axes. Set interactively in the ConfigGUI. | -| c2a_cnrs_yz | vector\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's YZ axes. Set interactively in the ConfigGUI. | -| c2a_cnrs_xz | vector\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XZ axes. Set interactively in the ConfigGUI. | -| c2a_src | string | | | Set by ConfigGui | Specifies which of the above corner sets is used to compute the camera-animal transform. Set interactively in the ConfigGUI. | +| c2a_cnrs_xy | vector\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XY axes. Set interactively in ConfigGUI. | +| c2a_cnrs_yz | vector\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's YZ axes. Set interactively in ConfigGUI. | +| c2a_cnrs_xz | vector\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XZ axes. Set interactively in ConfigGUI. | +| c2a_src | string | | | Set by ConfigGui | Specifies which of the above corner sets is used to compute the camera-animal transform. Set interactively in ConfigGUI. | | c2a_r | vector\ | | | Set by ConfigGui | Rotational component of the camera-animal transform. Computed automatically by ConfigGUI. | | c2a_t | vector\ | | | Set by ConfigGui | Translational component of the camera-animal transform. Computed automatically by ConfigGUI. | -| roi_circ | vector\ | | | Set by ConfigGui | Specifies points {X1,Y1,X2,Y2,...} around the circumference of the trackball in the input image. Set interactively in the ConfigGUI. | +| roi_circ | vector\ | | | Set by ConfigGui | Specifies points {X1,Y1,X2,Y2,...} around the circumference of the trackball in the input image. Set interactively in ConfigGUI. | | roi_c | vector\ | | | Set by ConfigGui | Camera-frame vector describing the centre point of the trackball in the input image. Computed automatically by ConfigGUI. | | roi_r | float | | | Set by ConfigGui | Half-angle describing the radius of the trackball in the input image. Computed automatically by ConfigGUI. | -| roi_ignr | vector> | | | Set by ConfigGui | Specifies possibly several polygon regions {{X11,Y11,X12,Y12,...},{X21,Y21,X22,Y22,...},...} that should be ignored during matching (e.g. where the animal obscures the trackball). | +| roi_ignr | vector> | | | Set by ConfigGui | Specifies possibly several polygon regions {{X11,Y11,X12,Y12,...},{X21,Y21,X22,Y22,...},...} that should be ignored during matching (e.g. where the animal obscures the trackball). Set interactively in ConfigGUI. | From 23f249c572f2ff64795e23f312d61fb5bf2de2b8 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 30 Nov 2018 20:35:47 +0100 Subject: [PATCH 018/235] config param defaults --- sample/config.txt | 3 ++- src/FrameGrabber.cpp | 2 +- src/Trackball.cpp | 18 +++++++++--------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/sample/config.txt b/sample/config.txt index 175aa69..441163f 100644 --- a/sample/config.txt +++ b/sample/config.txt @@ -5,7 +5,7 @@ c2a_src : c2a_cnrs_xy c2a_t : { -0.674395, 0.389373, 2.889647 } do_display : y max_bad_frames : -1 -opt_bound : 0.25 +opt_bound : 0.35 opt_do_global : n opt_max_err : -1.000000 opt_max_evals : 50 @@ -16,6 +16,7 @@ roi_circ : { 63, 171, 81, 145, 106, 135, 150, 160 } roi_ignr : { { 96, 156, 113, 147, 106, 128, 82, 130, 81, 150 }, { 71, 213, 90, 219, 114, 218, 135, 211, 154, 196, 150, 217, 121, 228, 99, 234, 75, 225 } } roi_r : 0.124815 save_debug : n +save_raw : n src_fn : sample.mp4 src_fps : -1.000000 thr_ratio : 1.25 diff --git a/src/FrameGrabber.cpp b/src/FrameGrabber.cpp index 11fcb6a..50c57cb 100644 --- a/src/FrameGrabber.cpp +++ b/src/FrameGrabber.cpp @@ -43,7 +43,7 @@ FrameGrabber::FrameGrabber( shared_ptr source, _rh = _remapper->getDstH(); /// Thresholding. - if (thresh_ratio < 0) { + if (thresh_ratio <= 0) { LOG_WRN("Invalid thresh_ratio parameter (%f)! Defaulting to 1.0", thresh_ratio); thresh_ratio = 1.0; } diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 80877bb..2b4ca0e 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -50,7 +50,7 @@ const int DRAW_FICTIVE_PATH_LENGTH = 1000; const int Q_FACTOR_DEFAULT = 6; const double OPT_TOL_DEFAULT = 1e-3; -const double OPT_BOUND_DEFAULT = 0.25; +const double OPT_BOUND_DEFAULT = 0.35; const int OPT_MAX_EVAL_DEFAULT = 50; const bool OPT_GLOBAL_SEARCH_DEFAULT = false; const int OPT_MAX_BAD_FRAMES_DEFAULT = -1; @@ -118,7 +118,7 @@ Trackball::Trackball(string cfg_fn) return; } double src_fps = -1; - if (_cfg.getDbl("src_fps", src_fps) && (src_fps >= 0)) { + if (_cfg.getDbl("src_fps", src_fps) && (src_fps > 0)) { LOG("Setting source fps = %.2f..", src_fps); source->setFPS(src_fps); } @@ -138,7 +138,7 @@ Trackball::Trackball(string cfg_fn) /// Source camera model. double vfov = -1; - if (!_cfg.getDbl("vfov", vfov) || vfov <= 0) { + if (!_cfg.getDbl("vfov", vfov) || (vfov <= 0)) { LOG_ERR("Error! Camera vertical FoV parameter specified in the config file (vfov) is invalid!"); _active = false; return; @@ -148,11 +148,11 @@ Trackball::Trackball(string cfg_fn) /// Dimensions - quality defaults to 6 (remap_dim 60x60, sphere_dim 180x90). int q_factor = Q_FACTOR_DEFAULT; - if (!_cfg.getInt("q_factor", q_factor)) { + if (!_cfg.getInt("q_factor", q_factor) || (q_factor <= 0)) { LOG_WRN("Warning! Resolution parameter specified in the config file (q_factor) is invalid! Using default value (%d).", q_factor); _cfg.add("q_factor", q_factor); } - _roi_w = _roi_h = 10 * q_factor; + _roi_w = _roi_h = std::min(10 * q_factor,source->getWidth()); _map_h = static_cast(1.5 * _roi_h); _map_w = 2 * _map_h; @@ -308,17 +308,17 @@ Trackball::Trackball(string cfg_fn) /// Read config params. double tol = OPT_TOL_DEFAULT; - if (!_cfg.getDbl("opt_tol", tol)) { + if (!_cfg.getDbl("opt_tol", tol) || (tol <= 0)) { LOG_WRN("Warning! Using default value for opt_tol (%f).", tol); _cfg.add("opt_tol", tol); } double bound = OPT_BOUND_DEFAULT; - if (!_cfg.getDbl("opt_bound", bound)) { + if (!_cfg.getDbl("opt_bound", bound) || (bound <= 0)) { LOG_WRN("Warning! Using default value for opt_bound (%f).", bound); _cfg.add("opt_bound", bound); } int max_evals = OPT_MAX_EVAL_DEFAULT; - if (!_cfg.getInt("opt_max_evals", max_evals)) { + if (!_cfg.getInt("opt_max_evals", max_evals) || (max_evals <= 0)) { LOG_WRN("Warning! Using default value for opt_max_eval (%d).", max_evals); _cfg.add("opt_max_evals", max_evals); } @@ -333,7 +333,7 @@ Trackball::Trackball(string cfg_fn) _cfg.add("max_bad_frames", _max_bad_frames); } _error_thresh = -1; - if (!_cfg.getDbl("opt_max_err", _error_thresh) || _error_thresh < 0) { + if (!_cfg.getDbl("opt_max_err", _error_thresh) || (_error_thresh < 0)) { LOG_WRN("Warning! No optimisation error threshold specified in config file (opt_max_err) - poor matches will not be dropped!"); _cfg.add("opt_max_err", _error_thresh); } From 37855ec66c52faa90c2c72596dd3d96ce53df4fa Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 30 Nov 2018 20:39:30 +0100 Subject: [PATCH 019/235] Update params.md --- doc/params.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/params.md b/doc/params.md index be41b9f..8dba2e5 100644 --- a/doc/params.md +++ b/doc/params.md @@ -23,13 +23,13 @@ In the table below, the various possible parameters are listed. If nothing is li | opt_bound | float | 0.35 | (0,inf) | Probably not | Specifies the optimisation search range in radians. Larger values will facilitate more track ball rotation per frame, but result in slower tracking and also possibly lead to false matches. | | opt_tol | float | 0.001 | (0,inf) | Probably not | Specifies the minimisation termination criteria for absolute change in input parameters (delta rotation vector). | | | | | | | | -| c2a_cnrs_xy | vector\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XY axes. Set interactively in ConfigGUI. | -| c2a_cnrs_yz | vector\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's YZ axes. Set interactively in ConfigGUI. | -| c2a_cnrs_xz | vector\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XZ axes. Set interactively in ConfigGUI. | +| c2a_cnrs_xy | vec\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XY axes. Set interactively in ConfigGUI. | +| c2a_cnrs_yz | vec\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's YZ axes. Set interactively in ConfigGUI. | +| c2a_cnrs_xz | vec\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XZ axes. Set interactively in ConfigGUI. | | c2a_src | string | | | Set by ConfigGui | Specifies which of the above corner sets is used to compute the camera-animal transform. Set interactively in ConfigGUI. | -| c2a_r | vector\ | | | Set by ConfigGui | Rotational component of the camera-animal transform. Computed automatically by ConfigGUI. | -| c2a_t | vector\ | | | Set by ConfigGui | Translational component of the camera-animal transform. Computed automatically by ConfigGUI. | -| roi_circ | vector\ | | | Set by ConfigGui | Specifies points {X1,Y1,X2,Y2,...} around the circumference of the trackball in the input image. Set interactively in ConfigGUI. | -| roi_c | vector\ | | | Set by ConfigGui | Camera-frame vector describing the centre point of the trackball in the input image. Computed automatically by ConfigGUI. | +| c2a_r | vec\ | | | Set by ConfigGui | Rotational component of the camera-animal transform. Computed automatically by ConfigGUI. | +| c2a_t | vec\ | | | Set by ConfigGui | Translational component of the camera-animal transform. Computed automatically by ConfigGUI. | +| roi_circ | vec\ | | | Set by ConfigGui | Specifies points {X1,Y1,X2,Y2,...} around the circumference of the trackball in the input image. Set interactively in ConfigGUI. | +| roi_c | vec\ | | | Set by ConfigGui | Camera-frame vector describing the centre point of the trackball in the input image. Computed automatically by ConfigGUI. | | roi_r | float | | | Set by ConfigGui | Half-angle describing the radius of the trackball in the input image. Computed automatically by ConfigGUI. | -| roi_ignr | vector> | | | Set by ConfigGui | Specifies possibly several polygon regions {{X11,Y11,X12,Y12,...},{X21,Y21,X22,Y22,...},...} that should be ignored during matching (e.g. where the animal obscures the trackball). Set interactively in ConfigGUI. | +| roi_ignr | vec> | | | Set by ConfigGui | Specifies possibly several polygon regions {{X11,Y11,X12,Y12,...},{X21,Y21,X22,Y22,...},...} that should be ignored during matching (e.g. where the animal obscures the trackball). Set interactively in ConfigGUI. | From 665483395274acbe9869ae521de633f2ad14b889 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 30 Nov 2018 20:49:22 +0100 Subject: [PATCH 020/235] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9395def..c014408 100644 --- a/README.md +++ b/README.md @@ -113,10 +113,10 @@ Before running FicTrac, you may configure your camera (frame rate, resolution, e ### Configuration There are two necessary steps to configure FicTrac prior to running the program: -1. You must provide a configuration text file that contains important parameters for your setup. At a minimum, this config file must define the parameters `src_fn` and `vfov`, which are the path to the image source (video file or camera) and vertical field of view (in degrees) of your camera/lens respectively. If you are running live from the camera, then `src_fn`is the camera index (e.g. 0). You will find an example config file in the `sample` directory. +1. You must provide a text file that contains important [configuration parameters](doc/params.md) for your setup. At a minimum, this config file must define the parameters `src_fn` and `vfov`, which define the image source (path to video file or camera index) and vertical field of view (in degrees) of your camera respectively. You will find an example config file in the `sample` directory. 2. You must run the interactive configuration program (configGui). This program will guide you through the configuration of the track ball region of interest within your input images and the transformation between the camera's and animal's frames of reference. -A more detailed guide for configuring FicTrac for your setup, as well as a complete list and explanation of parameters that can be specified, can be found in the [FicTrac manual](). +A more [detailed guide](doc/requirements.md) on how to configure FicTrac for your setup and an explanation of all the [configuration parameters](doc/params.md) can be found in the `doc` directory. ### Running FicTrac From d6a010a4f9b8ab0b58e2d48c3405b67312042ce2 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 30 Nov 2018 20:50:17 +0100 Subject: [PATCH 021/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c014408..823975f 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ On this page you'll find information for: You might also be interested in the following links: * [Demo video](http://youtu.be/BeGYOEOdWjw) - Quick (30s) overview of what FicTrac does and how it works. -* [FicTrac manual](http://link) - Detailed instructions, description of output data, parameters, recommendations, etc. +* [FicTrac manual](doc/requirements.md) - Detailed instructions, description of output data, parameters, recommendations, etc. * [Homepage](http://fictrac.rjdmoore.net) - Contact details for the main author/developer, links, and further info. * [Forum](http://www.reddit.com/r/fictrac/) - Subreddit for faqs, support, advice, discussions, etc. * [Mailing list](http://fictrac.rjdmoore.net/mail.html) - Subscribe to receive important announcements and updates. From f85c8d04e246171b5ee19e99af3c114cdf876154 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 5 Dec 2018 21:12:46 +0100 Subject: [PATCH 022/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 823975f..bc1ac9b 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ The FicTrac source code can be built for both Windows and Ubuntu (Linux) operati 1. Download and install required dependencies: 1. [Cmake build system](https://cmake.org/download/) (binary distribution) - 2. [OpenCV computer vision library](https://opencv.org/releases.html) (latest release Win pack) + 2. [OpenCV computer vision library](https://opencv.org/releases.html) (version 3.4.2 Win pack) 3. [NLopt optimisation library](https://nlopt.readthedocs.io/en/latest/NLopt_on_Windows/) (precompiled DLL) 2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: ``` From 05679b4e88d1880b1ef9c1a3431501b2d07bab22 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 8 Jan 2019 20:24:11 +0100 Subject: [PATCH 023/235] make_unique --- src/FrameGrabber.cpp | 6 +++--- src/Logger.cpp | 4 ++-- src/Recorder.cpp | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/FrameGrabber.cpp b/src/FrameGrabber.cpp index 50c57cb..c655a3e 100644 --- a/src/FrameGrabber.cpp +++ b/src/FrameGrabber.cpp @@ -63,7 +63,7 @@ FrameGrabber::FrameGrabber( shared_ptr source, /// Thread stuff. _active = true; - _thread = std::unique_ptr(new std::thread(&FrameGrabber::process, this)); + _thread = std::make_unique(&FrameGrabber::process, this); } /// @@ -182,8 +182,8 @@ void FrameGrabber::process() Mat thresh_max(_rh, _rw, CV_8UC1); thresh_max.setTo(cv::Scalar::all(0)); - std::unique_ptr win_max_hist = std::unique_ptr(new uint8_t[_thresh_win]); - std::unique_ptr win_min_hist = std::unique_ptr(new uint8_t[_thresh_win]); + auto win_max_hist = std::make_unique(_thresh_win); + auto win_min_hist = std::make_unique(_thresh_win); /// Rewind to video start. _source->rewind(); diff --git a/src/Logger.cpp b/src/Logger.cpp index 271268a..fc92d27 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -18,8 +18,8 @@ Logger::Logger() { // create log writer string fn = string("fictrac-") + execTime() + ".log"; - _log = unique_ptr(new Recorder(RecorderInterface::RecordType::FILE, fn)); - _cout = unique_ptr(new Recorder(RecorderInterface::RecordType::TERM)); + _log = make_unique(RecorderInterface::RecordType::FILE, fn); + _cout = make_unique(RecorderInterface::RecordType::TERM); if (_log->is_active() && _cout->is_active()) { cout << "Initialised logging to " << fn << endl; } else { diff --git a/src/Recorder.cpp b/src/Recorder.cpp index 5425af6..5dea678 100644 --- a/src/Recorder.cpp +++ b/src/Recorder.cpp @@ -21,13 +21,13 @@ Recorder::Recorder(RecorderInterface::RecordType type, string fn) /// Set record type. switch (type) { case RecorderInterface::RecordType::TERM: - _record = unique_ptr(new TermRecorder()); + _record = make_unique(); break; case RecorderInterface::RecordType::FILE: - _record = unique_ptr(new FileRecorder()); + _record = make_unique(); break; case RecorderInterface::RecordType::SOCK: - _record = unique_ptr(new SocketRecorder()); + _record = make_unique(); default: break; } @@ -35,7 +35,7 @@ Recorder::Recorder(RecorderInterface::RecordType type, string fn) /// Open record and start async recording. if (_record && _record->openRecord(fn)) { _active = true; - _thread = unique_ptr(new thread(&Recorder::processMsgQ, this)); + _thread = make_unique(&Recorder::processMsgQ, this); } else { cerr << "Error initialising recorder!" << endl; From 6285fb1d771a016cf9c4ddc913832220d70668a1 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 8 Jan 2019 22:34:18 +0100 Subject: [PATCH 024/235] windows socket output --- include/Trackball.h | 3 +- sample/config.txt | 6 ++-- scripts/socket_client.py | 48 ++++++++++++++++++++++++++++++++ src/SocketRecorder_winsocket.src | 20 ++++++------- src/Trackball.cpp | 31 +++++++++++++++++++-- 5 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 scripts/socket_client.py diff --git a/include/Trackball.h b/include/Trackball.h index e030f07..828ae7c 100644 --- a/include/Trackball.h +++ b/include/Trackball.h @@ -127,7 +127,8 @@ class Trackball /// Data i/o. std::string _base_fn; std::unique_ptr _frameGrabber; - std::unique_ptr _log; + bool _do_sock_output; + std::unique_ptr _data_log, _data_sock; /// Thread stuff. std::atomic_bool _active, _kill; diff --git a/sample/config.txt b/sample/config.txt index 441163f..3fc14e4 100644 --- a/sample/config.txt +++ b/sample/config.txt @@ -1,15 +1,16 @@ -## FicTrac config file (build Oct 2 2018) +## FicTrac config file (build Jan 8 2019) c2a_cnrs_xy : { 191, 171, 128, 272, 20, 212, 99, 132 } c2a_r : { -0.722443, 0.131317, 0.460878 } c2a_src : c2a_cnrs_xy c2a_t : { -0.674395, 0.389373, 2.889647 } -do_display : y +do_display : n max_bad_frames : -1 opt_bound : 0.35 opt_do_global : n opt_max_err : -1.000000 opt_max_evals : 50 opt_tol : 0.001 +out_port : -1 q_factor : 6 roi_c : { -0.229390, 0.099969, 0.968187 } roi_circ : { 63, 171, 81, 145, 106, 135, 150, 160 } @@ -22,4 +23,3 @@ src_fps : -1.000000 thr_ratio : 1.25 thr_win_pc : 0.25 vfov : 45 - diff --git a/scripts/socket_client.py b/scripts/socket_client.py new file mode 100644 index 0000000..b65f8c0 --- /dev/null +++ b/scripts/socket_client.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +import socket + +HOST = '127.0.0.1' # The server's hostname or IP address +PORT = 508 # The port used by the server + +# Open the connection (FicTrac must be waiting for socket connection) +with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.connect((HOST, PORT)) + + # Keep receiving data until FicTrac closes + while True: + # Receive one data frame + data = sock.recv(1024) + if not data: + break + + # Decode received data + line = data.decode('UTF-8') + endline = line.find("\n") + line = line[:endline] + toks = line.split(", ") + + if ((len(toks) < 24) | (toks[0] != "FT")): + print('Bad read') + continue + + # Extract FicTrac variables + # (see https://github.com/rjdmoore/fictrac/blob/master/doc/data_header.txt for descriptions) + cnt = int(toks[1]) + dr_cam = [float(toks[2]), float(toks[3]), float(toks[4])] + err = float(toks[5]) + dr_lab = [float(toks[6]), float(toks[7]), float(toks[8])] + r_cam = [float(toks[9]), float(toks[10]), float(toks[11])] + r_lab = [float(toks[12]), float(toks[13]), float(toks[14])] + posx = float(toks[15]) + posy = float(toks[16]) + heading = float(toks[17]) + step_dir = float(toks[18]) + step_mag = float(toks[19]) + intx = float(toks[20]) + inty = float(toks[21]) + ts = float(toks[22]) + seq = int(toks[23]) + + # Do something ... + print(cnt) diff --git a/src/SocketRecorder_winsocket.src b/src/SocketRecorder_winsocket.src index 3cb2e07..c198c53 100644 --- a/src/SocketRecorder_winsocket.src +++ b/src/SocketRecorder_winsocket.src @@ -6,9 +6,10 @@ #include "SocketRecorder_winsocket.h" +#include "Logger.h" + #include #include -#include // cout/cerr /// /// @@ -35,7 +36,7 @@ bool SocketRecorder::openRecord(std::string port) // Initialize Winsock int iResult = WSAStartup(MAKEWORD(2, 2), &_wsaData); if (iResult != 0) { - std::cerr << "Error! Failed to initialise WinSock library (err = " << iResult << ")!" << std::endl; + LOG_ERR("Error! Failed to initialise WinSock library (err = %d)", iResult); return false; } @@ -50,7 +51,7 @@ bool SocketRecorder::openRecord(std::string port) // Resolve the local address and port to be used by the server iResult = getaddrinfo(NULL, port.c_str(), &hints, &result); if (iResult != 0) { - std::cerr << "Error! Failed to resolve local address (err = " << iResult << ")!" << std::endl; + LOG_ERR("Error! Failed to resolve local address (err = %d).", iResult); return false; } @@ -58,7 +59,7 @@ bool SocketRecorder::openRecord(std::string port) _listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); if (_listenSocket == INVALID_SOCKET) { //FIXME: include IP address in error msg. - std::cerr << "Error! Could not create valid socket on port " << port << " (err = " << WSAGetLastError() << ")!" << std::endl; + LOG_ERR("Error! Could not create valid socket on port %s (err = %d).", port.c_str(), WSAGetLastError()); freeaddrinfo(result); return false; } @@ -66,7 +67,7 @@ bool SocketRecorder::openRecord(std::string port) // Setup the TCP listening socket iResult = bind(_listenSocket, result->ai_addr, (int)result->ai_addrlen); if (iResult == SOCKET_ERROR) { - std::cerr << "Error! Failed to bind socket on port " << port << " (err = " << WSAGetLastError() << ")!" << std::endl; + LOG_ERR("Error! Failed to bind socket on port %s (err = %d)", port.c_str(), WSAGetLastError()); freeaddrinfo(result); return false; } @@ -76,17 +77,16 @@ bool SocketRecorder::openRecord(std::string port) // Listen on our socket. if (listen(_listenSocket, SOMAXCONN) == SOCKET_ERROR) { - std::cerr << "Error! Failed to listen to socket on port " << port << " (err = " << WSAGetLastError() << ")!" << std::endl; + LOG_ERR("Error! Failed to listen to socket on port %s (err = %d).", port.c_str(), WSAGetLastError()); return false; } // Wait for client connection... //FIXME: include IP:port info in this message - std::cout << "\nWaiting for client connection to socket: " << port << " ..." << std::endl; - std::fflush(stdout); + PRINT("\nWaiting for client connection to socket: %s ...\n", port.c_str()); _clientSocket = accept(_listenSocket, NULL, NULL); // blocking if (_clientSocket == INVALID_SOCKET) { - std::cerr << "Error! Failed to accept socket connection (err = " << WSAGetLastError() << ")!" << std::endl; + LOG_ERR("Error! Failed to accept socket connection (err = %d).", WSAGetLastError()); return false; } @@ -101,7 +101,7 @@ bool SocketRecorder::writeRecord(std::string s) if (_open) { int iSendResult = send(_clientSocket, s.c_str(), s.size(), 0); if (iSendResult == SOCKET_ERROR) { - std::cerr << "Error! Send failed (err = " << WSAGetLastError() << ")!" << std::endl; + LOG_ERR("Error! Send failed (err = %d).", WSAGetLastError()); _open = false; // should this be a terminal error? } } diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 2b4ca0e..53f2e87 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -60,6 +60,8 @@ const double THRESH_WIN_PC_DEFAULT = 0.25; const uint8_t SPHERE_MAP_FIRST_HIT_BONUS = 64; +const int OUT_PORT_DEFAULT = -1; + const bool DO_DISPLAY_DEFAULT = true; const bool SAVE_RAW_DEFAULT = false; const bool SAVE_DEBUG_DEFAULT = false; @@ -361,7 +363,27 @@ Trackball::Trackball(string cfg_fn) /// Output. string data_fn = _base_fn + "-" + execTime() + ".dat"; - _log = make_unique(RecorderInterface::RecordType::FILE, data_fn); + _data_log = make_unique(RecorderInterface::RecordType::FILE, data_fn); + if (!_data_log->is_active()) { + LOG_ERR("Error! Unable to open output data log file (%s).", data_fn.c_str()); + _active = false; + return; + } + + int out_port = OUT_PORT_DEFAULT; + _do_sock_output = false; + if (_cfg.getInt("out_port", out_port) && (out_port > 0)) { + _data_sock = make_unique(RecorderInterface::RecordType::SOCK, std::to_string(out_port)); + if (!_data_sock->is_active()) { + LOG_ERR("Error! Unable to open output data socket (%d).", out_port); + _active = false; + return; + } + _do_sock_output = true; + } else { + LOG_WRN("Warning! Using default value for out_port (%d).", out_port); + _cfg.add("out_port", out_port); + } /// Display. _do_display = DO_DISPLAY_DEFAULT; @@ -885,7 +907,12 @@ bool Trackball::logData() ss << _ts << ", " << _seq << std::endl; // async i/o - return _log->addMsg(ss.str()); + bool ret = true; + if (_do_sock_output) { + ret &= _data_sock->addMsg("FT, " + ss.str()); + } + ret &= _data_log->addMsg(ss.str()); + return ret; } /// From c1071118ad1ef7198c8e7fa835c3ab860b4f7d4e Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 8 Jan 2019 22:34:39 +0100 Subject: [PATCH 025/235] fix file playback fps --- src/CVSource.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/CVSource.cpp b/src/CVSource.cpp index 206d5b8..ab164d7 100644 --- a/src/CVSource.cpp +++ b/src/CVSource.cpp @@ -90,9 +90,13 @@ bool CVSource::setFPS(double fps) if (_open && (fps > 0)) { if (!_cap->set(cv::CAP_PROP_FPS, fps)) { LOG_WRN("Warning! Failed to set device fps (attempted to set fps=%.2f).", fps); + _fps = fps; // just set fps anyway for playback + LOG("Playback frame rate is now %.2f", _fps); + } + else { + _fps = getFPS(); + LOG("Device frame rate is now %.2f", _fps); } - _fps = getFPS(); - LOG("Device frame rate is now %.2f", _fps); } return ret; } @@ -153,9 +157,9 @@ bool CVSource::grab(cv::Mat& frame) /// Correct average frame rate when reading from file. if (!_live && (_fps > 0)) { - static double prev_ts = ts - 25; // initially 40 Hz - static double av_fps = 40; // initially 40 Hz - static double sleep_ms = 25; + static double prev_ts = ts - (1000/_fps); + static double av_fps = _fps; // initially 40 Hz + static double sleep_ms = 1000/_fps; av_fps = 0.15 * av_fps + 0.85 * (1000 / (ts - prev_ts)); sleep_ms *= 0.25 * (av_fps / _fps) + 0.75; sleep(static_cast(round(sleep_ms))); From 2a7d3150d43a10b01de2104cf17db57bf84ac401 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 8 Jan 2019 22:38:56 +0100 Subject: [PATCH 026/235] Update params.md --- doc/params.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/params.md b/doc/params.md index 8dba2e5..829909b 100644 --- a/doc/params.md +++ b/doc/params.md @@ -10,6 +10,7 @@ In the table below, the various possible parameters are listed. If nothing is li | do_display | bool | y | y/n | If you want to | Display debug screen during tracking. Slows execution very slightly. | | save_debug | bool | n | y/n | If you want to | Record the debug screen to video file. Note that if the source frame rate is higher than FicTrac's processing frame rate, frames may be dropped from the video file. | | save_raw | bool | n | y/n | If you want to | Record the input image stream to video file. Note that if the source frame rate is higher than FicTrac's processing frame rate, frames may be dropped from the video file. | +| out_port | int | -1 | (0,inf) | If you want to | Socket port over which to transmit FicTrac data. If unset or < 0, FicTrac will not transmit data over sockets. | | | | | | | | | q_factor | int | 6 | (0,inf) | Only if you need to | Adjusts the resolution of the tracking window. Smaller values correspond to coarser but quicker tracking and vice-versa. Normally in the range [3,10]. | | src_fps | float | -1 | (0,inf) | Only if you need to | If set, FicTrac will attempt to set the frame rate for the image source (video file or camera). | From 55f0cb5732f7b1a95bea656b0d46b4ce8e6230c3 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 8 Jan 2019 22:53:31 +0100 Subject: [PATCH 027/235] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bc1ac9b..090b18d 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,6 @@ A more [detailed guide](doc/requirements.md) on how to configure FicTrac for you ### Running FicTrac To run FicTrac on the provided sample data, simply open a terminal in the FicTrac project folder and type: - ``` cd sample [Windows] ..\bin\Release\configGui.exe config.txt @@ -133,6 +132,12 @@ The sample config file `config.txt` is already configured for the sample data, b [Linux] sudo ../bin/fictrac config.txt ``` +FicTrac will usually generate two output files: +1. Log file (*.log) - containing debugging information about FicTrac's execution. +2. Data file (*.dat) - containing output data. See [data_header](doc/data_header.txt) for information about output data. + +The output data file can be used for offline processing. To use FicTrac within a closed-loop setup (to provide real-time feedback for stimuli), you should configure FicTrac to output data via a socket (IP address/port) in real-time. To do this, just set `out_port` to a valid port number in the config file. There is an example Python script for receiving data via sockets in the `scripts` directory. + ## Research If you use FicTrac as part of your research, please cite the original FicTrac publication: From 491a53ecac11e66284e96af3e8716c3c1895c307 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 8 Jan 2019 22:56:18 +0100 Subject: [PATCH 028/235] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 090b18d..048cfbe 100644 --- a/README.md +++ b/README.md @@ -120,13 +120,15 @@ A more [detailed guide](doc/requirements.md) on how to configure FicTrac for you ### Running FicTrac -To run FicTrac on the provided sample data, simply open a terminal in the FicTrac project folder and type: +To configure FicTrac for the provided sample data, simply open a terminal in the FicTrac project folder and type: ``` cd sample [Windows] ..\bin\Release\configGui.exe config.txt [Linux] ../bin/configGui config.txt ``` -The sample config file `config.txt` is already configured for the sample data, but you can step through the configuration process to check that everything looks ok. Then, in the same terminal window, type: +The sample config file `config.txt` is already configured for the sample data, but you can step through the configuration process to check that everything looks ok. + +Then, to run FicTrac, type: ``` [Windows] ..\bin\Release\fictrac.exe config.txt [Linux] sudo ../bin/fictrac config.txt From 55eeb12c19d17bd81a0c805918fc1304b1393e57 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 8 Jan 2019 22:58:01 +0100 Subject: [PATCH 029/235] show debug display by default --- sample/config.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample/config.txt b/sample/config.txt index 3fc14e4..12b4e6b 100644 --- a/sample/config.txt +++ b/sample/config.txt @@ -3,7 +3,7 @@ c2a_cnrs_xy : { 191, 171, 128, 272, 20, 212, 99, 132 } c2a_r : { -0.722443, 0.131317, 0.460878 } c2a_src : c2a_cnrs_xy c2a_t : { -0.674395, 0.389373, 2.889647 } -do_display : n +do_display : y max_bad_frames : -1 opt_bound : 0.35 opt_do_global : n From f80e49c8bc4916132a58fe319b9185563c0746af Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 8 Jan 2019 23:30:33 +0100 Subject: [PATCH 030/235] sockets tested in linux --- sample/config.txt | 2 +- scripts/socket_client.py | 6 ++++-- src/SocketRecorder_linux.src | 15 +++++++++------ src/SocketRecorder_winsocket.src | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/sample/config.txt b/sample/config.txt index 12b4e6b..5f7b700 100644 --- a/sample/config.txt +++ b/sample/config.txt @@ -19,7 +19,7 @@ roi_r : 0.124815 save_debug : n save_raw : n src_fn : sample.mp4 -src_fps : -1.000000 +src_fps : -1 thr_ratio : 1.25 thr_win_pc : 0.25 vfov : 45 diff --git a/scripts/socket_client.py b/scripts/socket_client.py index b65f8c0..464529c 100644 --- a/scripts/socket_client.py +++ b/scripts/socket_client.py @@ -2,8 +2,8 @@ import socket -HOST = '127.0.0.1' # The server's hostname or IP address -PORT = 508 # The port used by the server +HOST = '127.0.0.1' # The server's hostname or IP address +PORT = ???? # The port used by the server # Open the connection (FicTrac must be waiting for socket connection) with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: @@ -22,6 +22,8 @@ line = line[:endline] toks = line.split(", ") + # Fixme: sometimes we read more than one line at a time, + # should handle that rather than just dropping extra data... if ((len(toks) < 24) | (toks[0] != "FT")): print('Bad read') continue diff --git a/src/SocketRecorder_linux.src b/src/SocketRecorder_linux.src index 6b0d972..26ef9ec 100644 --- a/src/SocketRecorder_linux.src +++ b/src/SocketRecorder_linux.src @@ -6,6 +6,8 @@ #include "SocketRecorder_linux.h" +#include "Logger.h" + #include #include #include @@ -44,7 +46,7 @@ bool SocketRecorder::openRecord(std::string port) // Create socket _listenSocket = socket(AF_INET, SOCK_STREAM, 0); if (_listenSocket < 0) { - std::cerr << "Error! Could not create valid socket on port " << port << "!" << std::endl; + LOG_ERR("Error! Could not create valid socket on port %s.", port.c_str()); return false; } @@ -55,28 +57,29 @@ bool SocketRecorder::openRecord(std::string port) portno = atoi(port.c_str()); } catch (...) { - std::cerr << "Error! Invalid socket port number (" << port << ")!" << std::endl; + LOG_ERR("Error! Invalid socket port number (%s).", port.c_str()); return false; } serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(portno); if (bind(_listenSocket, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { - std::cerr << "Error! Failed to bind socket on port " << port << "!" << std::endl; + LOG_ERR("Error! Failed to bind socket on port %s.", port.c_str()); return false; } // Listen on our socket if (listen(_listenSocket,5) < 0) { - std::cerr << "Error! Failed to listen to socket on port " << port << "!" << std::endl; + LOG_ERR("Error! Failed to listen to socket on port %s.", port.c_str()); return false; } // Wait for client connection... socklen_t clilen = sizeof(cli_addr); + PRINT("\nWaiting for client connection to socket: %s ...\n", port.c_str()); _clientSocket = accept(_listenSocket, (struct sockaddr *) &cli_addr, &clilen); // blocking if (_clientSocket < 0) { - std::cerr << "Error! Failed to accept socket connection on port " << port << "!" << std::endl; + LOG_ERR("Error! Failed to accept socket connection on port %s.", port.c_str()); return false; } @@ -91,7 +94,7 @@ bool SocketRecorder::writeRecord(std::string s) if (_open) { int n = write(_clientSocket,s.c_str(),s.size()); if (n < 0) { - std::cerr << "Error! Send failed!" << std::endl; + LOG_ERR("Error! Send failed."); _open = false; // should this be a terminal error? } } diff --git a/src/SocketRecorder_winsocket.src b/src/SocketRecorder_winsocket.src index c198c53..35f322e 100644 --- a/src/SocketRecorder_winsocket.src +++ b/src/SocketRecorder_winsocket.src @@ -67,7 +67,7 @@ bool SocketRecorder::openRecord(std::string port) // Setup the TCP listening socket iResult = bind(_listenSocket, result->ai_addr, (int)result->ai_addrlen); if (iResult == SOCKET_ERROR) { - LOG_ERR("Error! Failed to bind socket on port %s (err = %d)", port.c_str(), WSAGetLastError()); + LOG_ERR("Error! Failed to bind socket on port %s (err = %d).", port.c_str(), WSAGetLastError()); freeaddrinfo(result); return false; } From 7a21ddc10adab8578a2e5e13048bece48393b7b6 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 29 Jan 2019 08:56:33 +0100 Subject: [PATCH 031/235] extra debug for source fps; prevent opening output video with -ve fps --- src/CVSource.cpp | 8 ++++++++ src/Trackball.cpp | 12 +++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/CVSource.cpp b/src/CVSource.cpp index ab164d7..1ca0fb6 100644 --- a/src/CVSource.cpp +++ b/src/CVSource.cpp @@ -60,6 +60,14 @@ CVSource::CVSource(std::string input) if( _open ) { _width = static_cast(_cap->get(cv::CAP_PROP_FRAME_WIDTH)); _height = static_cast(_cap->get(cv::CAP_PROP_FRAME_HEIGHT)); + if (_live) { + _fps = getFPS(); // don't init fps for video files - we might want to play them back as fast as possible + + LOG("OpenCV camera initialised (%dx%d @ %.3f fps)!", _width, _height, _fps); + } + else { + LOG("OpenCV video initialised (%dx%d)!", _width, _height); + } } } diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 53f2e87..36c5e40 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -402,7 +402,7 @@ Trackball::Trackball(string cfg_fn) _cfg.add("save_debug", _save_debug); } if (_save_debug & !_do_display) { - LOG("Forcing do_display = true becase save_debug == true."); + LOG("Forcing do_display = true, becase save_debug == true."); _do_display = true; } if (_do_display) { @@ -411,7 +411,10 @@ Trackball::Trackball(string cfg_fn) } if (_save_raw) { string vid_fn = _base_fn + "-raw.mp4"; - _raw_vid.open(vid_fn, cv::VideoWriter::fourcc('H', '2', '6', '4'), source->getFPS(), cv::Size(source->getWidth(), source->getHeight())); + double fps = source->getFPS(); + if (fps <= 0) { fps = 25; } + LOG_DBG("Opening %s for video writing (%dx%d @ %f FPS)", vid_fn.c_str(), source->getWidth(), source->getHeight(), fps); + _raw_vid.open(vid_fn, cv::VideoWriter::fourcc('H', '2', '6', '4'), fps, cv::Size(source->getWidth(), source->getHeight())); if (!_raw_vid.isOpened()) { LOG_ERR("Error! Unable to open raw output video (%s).", vid_fn.c_str()); _active = false; @@ -420,7 +423,10 @@ Trackball::Trackball(string cfg_fn) } if (_save_debug) { string vid_fn = _base_fn + "-debug.mp4"; - _debug_vid.open(vid_fn, cv::VideoWriter::fourcc('H', '2', '6', '4'), source->getFPS(), cv::Size(4 * DRAW_CELL_DIM, 3 * DRAW_CELL_DIM)); + double fps = source->getFPS(); + if (fps <= 0) { fps = 25; } + LOG_DBG("Opening %s for video writing (%dx%d @ %f FPS)", vid_fn.c_str(), 4 * DRAW_CELL_DIM, 3 * DRAW_CELL_DIM, fps); + _debug_vid.open(vid_fn, cv::VideoWriter::fourcc('H', '2', '6', '4'), fps, cv::Size(4 * DRAW_CELL_DIM, 3 * DRAW_CELL_DIM)); if (!_debug_vid.isOpened()) { LOG_ERR("Error! Unable to open debug output video (%s).", vid_fn.c_str()); _active = false; From 154fcea41f191b8f33b1ffd7fd0e668052631279 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 19 Feb 2019 21:55:44 +0100 Subject: [PATCH 032/235] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 048cfbe..1379cb9 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ The FicTrac source code can be built for both Windows and Ubuntu (Linux) operati 1. Download and install required dependencies: 1. [Cmake build system](https://cmake.org/download/) (binary distribution) - 2. [OpenCV computer vision library](https://opencv.org/releases.html) (version 3.4.2 Win pack) + 2. [OpenCV computer vision library](https://opencv.org/releases.html) (version 4.0.1 Win pack) 3. [NLopt optimisation library](https://nlopt.readthedocs.io/en/latest/NLopt_on_Windows/) (precompiled DLL) 2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: ``` @@ -62,7 +62,7 @@ cd build 3. Next, we will configure and build the FicTrac project. FicTrac is written in C++, so you'll need a suitable compiler. In this example we will use MSVS Build Tools. If you don't already have Visual Studio, you will need to install the [build tools](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2017). 4. Run Cmake to prepare the necessary build files for FicTrac. Here we also need to provide the paths to where we installed OpenCV and NLopt (I have given example paths here, you will need to modify them for your installation): ``` -cmake -G "Visual Studio 15 2017 Win64" -D OPENCV_DIR="C:\path\to\opencv-3.4.2\build" -D NLOPT_DIR="C:\path\to\nlopt-2.4.2\" .. +cmake -G "Visual Studio 15 2017 Win64" -D OPENCV_DIR="C:\path\to\opencv-4.0.1\build" -D NLOPT_DIR="C:\path\to\nlopt-2.4.2\" .. ``` 5. Finally, build and install FicTrac: ``` @@ -71,8 +71,6 @@ cmake --build . --config Release -j 4 If everything went well, the executables for FicTrac and a configuration utility will be placed under the `bin` directory in the FicTrac project folder. -**Note:** To save video on Windows, you must also have the [H264 library](https://github.com/cisco/openh264/releases) in the system path. OpenCV 3.4.2 requires `openh264-1.7.0-win64.dll`, which should be downloaded and placed in the same directory as the generated `fictrac.exe`. If you have installed another version of OpenCV, and it requires another version of the H264 library, an error message should be printed to the terminal when you run FicTrac. You can obtain other H264 versions from the above link. - #### Ubuntu (Linux) installation 1. Install the required dependencies: @@ -140,6 +138,8 @@ FicTrac will usually generate two output files: The output data file can be used for offline processing. To use FicTrac within a closed-loop setup (to provide real-time feedback for stimuli), you should configure FicTrac to output data via a socket (IP address/port) in real-time. To do this, just set `out_port` to a valid port number in the config file. There is an example Python script for receiving data via sockets in the `scripts` directory. +**Note:** If you receive an error when running FicTrac about a missing [H264 library](https://github.com/cisco/openh264/releases), you can download the necessary library (i.e. OpenCV 3.4.2 requires `openh264-1.7.0-win64.dll`) from the above link and place it in the `dll` folder under the FicTrac main directory. You will then need to re-run the appropriate `cmake --build` command for your installation. + ## Research If you use FicTrac as part of your research, please cite the original FicTrac publication: From 66e30636e9e2180f6b6eae80f560c6c14002ef5e Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 19 Feb 2019 22:01:38 +0100 Subject: [PATCH 033/235] copy all dlls from dll folder --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d61941..868ac7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,7 +128,8 @@ if(MSVC) list(APPEND TO_COPY "${NLOPT_DIR}/libnlopt-0.dll") # copy h264 dll - list(APPEND TO_COPY "${PROJECT_SOURCE_DIR}/dll/openh264-1.7.0-win64.dll") + file(GLOB DLLS ${PROJECT_SOURCE_DIR}/dll/*.dll) + list(APPEND TO_COPY "${DLLS}") add_custom_command( TARGET fictrac POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different From 8eb4119979cba0eb157a16f140e807b4a283de27 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 19 Feb 2019 22:02:57 +0100 Subject: [PATCH 034/235] replace deprecated vars --- include/CmPoint.h | 7 +- include/Remapper.h | 1 - src/CVSource.cpp | 10 +-- src/ConfigGUI.cpp | 24 +++---- src/FrameGrabber.cpp | 2 +- src/Remapper.cpp | 156 ++++++++++++++++++------------------------- src/Trackball.cpp | 20 +++--- src/drawing.cpp | 44 ++++++------ 8 files changed, 119 insertions(+), 145 deletions(-) diff --git a/include/CmPoint.h b/include/CmPoint.h index 6aad8c3..e8ec4a9 100644 --- a/include/CmPoint.h +++ b/include/CmPoint.h @@ -22,9 +22,9 @@ class CmPointT { CmPointT() : x(0), y(0), z(0) {} CmPointT(T x_, T y_, T z_) : x(x_), y(y_), z(z_) {} - CmPointT(const CvPoint& p) : x(p.x), y(p.y), z(0) {} - CmPointT(const CvPoint2D32f& p) : x(p.x), y(p.y), z(0) {} - CmPointT(const CvPoint3D32f& p) : x(p.x), y(p.y), z(p.z) {} + CmPointT(const cv::Point& p) : x(p.x), y(p.y), z(0) {} + CmPointT(const cv::Point2f& p) : x(p.x), y(p.y), z(0) {} + CmPointT(const cv::Point3f& p) : x(p.x), y(p.y), z(p.z) {} CmPointT(const CmPoint32f& p) : x(p.x), y(p.y), z(p.z) {} CmPointT(const CmPoint64f& p) : x(p.x), y(p.y), z(p.z) {} CmPointT(T az, T el); @@ -32,7 +32,6 @@ class CmPointT { /// Allow implicit conversion of scalar to CmPointT for scaling CmPointT(T scale) : x(scale), y(scale), z(scale) {} - void copyTo(CvPoint3D32f& p) const { p = cvPoint3D32f(x,y,z); } void copyTo(cv::Point3f& p) const { p = cv::Point3f(static_cast(x), static_cast(y), static_cast(z)); } void copyTo(cv::Point3d& p) const { p = cv::Point3d(static_cast(x), static_cast(y), static_cast(z)); } void copyTo(float *p) const { p[0] = static_cast(x); p[1] = static_cast(y); p[2] = static_cast(z); } diff --git a/include/Remapper.h b/include/Remapper.h index 7f70437..b735a31 100644 --- a/include/Remapper.h +++ b/include/Remapper.h @@ -35,7 +35,6 @@ class Remapper int getDstH() { return _dstH; } virtual void apply(const cv::Mat& src, cv::Mat& dst); - void apply(const IplImage *src, IplImage *dst); void applyC1(const unsigned char *src, unsigned char *dst, int srcStep=0, int dstStep=0); diff --git a/src/CVSource.cpp b/src/CVSource.cpp index 1ca0fb6..fd1d4a6 100644 --- a/src/CVSource.cpp +++ b/src/CVSource.cpp @@ -143,20 +143,20 @@ bool CVSource::grab(cv::Mat& frame) if( _frame_cap.channels() == 1 ) { switch( _bayerType ) { case BAYER_BGGR: - cv::cvtColor(_frame_cap, frame, CV_BayerBG2BGR); + cv::cvtColor(_frame_cap, frame, cv::COLOR_BayerBG2BGR); break; case BAYER_GBRG: - cv::cvtColor(_frame_cap, frame, CV_BayerGB2BGR); + cv::cvtColor(_frame_cap, frame, cv::COLOR_BayerGB2BGR); break; case BAYER_GRBG: - cv::cvtColor(_frame_cap, frame, CV_BayerGR2BGR); + cv::cvtColor(_frame_cap, frame, cv::COLOR_BayerGR2BGR); break; case BAYER_RGGB: - cv::cvtColor(_frame_cap, frame, CV_BayerRG2BGR); + cv::cvtColor(_frame_cap, frame, cv::COLOR_BayerRG2BGR); break; case BAYER_NONE: default: - cv::cvtColor(_frame_cap, frame, CV_GRAY2BGR); + cv::cvtColor(_frame_cap, frame, cv::COLOR_GRAY2BGR); break; } } else { diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index 96b9cb1..4dafe35 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -225,7 +225,7 @@ bool ConfigGui::setFrame(Mat& frame) _frame = frame.clone(); } else if (frame.channels() == 1) { //_frame = frame.clone(); - cv::cvtColor(frame, _frame, CV_GRAY2BGR); + cv::cvtColor(frame, _frame, cv::COLOR_GRAY2BGR); } else { // uh oh, shouldn't get here LOG_ERR("Unexpected number of image channels (%d)!", frame.channels()); @@ -527,7 +527,7 @@ bool ConfigGui::run() /// Draw previous clicks. for (auto click : _input_data.circPts) { - cv::circle(disp_frame, click, click_rad, Scalar(255,255,0), 1, CV_AA); + cv::circle(disp_frame, click, click_rad, Scalar(255,255,0), 1, cv::LINE_AA); } /// Draw fitted circumference. @@ -604,9 +604,9 @@ bool ConfigGui::run() for (unsigned int i = 0; i < _input_data.ignrPts.size(); i++) { for (unsigned int j = 0; j < _input_data.ignrPts[i].size(); j++) { if (i == _input_data.ignrPts.size()-1) { - cv::circle(disp_frame, _input_data.ignrPts[i][j], click_rad, COLOURS[i%NCOLOURS], 1, CV_AA); + cv::circle(disp_frame, _input_data.ignrPts[i][j], click_rad, COLOURS[i%NCOLOURS], 1, cv::LINE_AA); } - cv::line(disp_frame, _input_data.ignrPts[i][j], _input_data.ignrPts[i][(j+1)%_input_data.ignrPts[i].size()], COLOURS[i%NCOLOURS], 1, CV_AA); + cv::line(disp_frame, _input_data.ignrPts[i][j], _input_data.ignrPts[i][(j+1)%_input_data.ignrPts[i].size()], COLOURS[i%NCOLOURS], 1, cv::LINE_AA); } } @@ -657,9 +657,9 @@ bool ConfigGui::run() for (unsigned int i = 0; i < _input_data.ignrPts.size(); i++) { for (unsigned int j = 0; j < _input_data.ignrPts[i].size(); j++) { if (i == _input_data.ignrPts.size()-1) { - cv::circle(disp_frame, _input_data.ignrPts[i][j], click_rad, COLOURS[i%NCOLOURS], 1, CV_AA); + cv::circle(disp_frame, _input_data.ignrPts[i][j], click_rad, COLOURS[i%NCOLOURS], 1, cv::LINE_AA); } - cv::line(disp_frame, _input_data.ignrPts[i][j], _input_data.ignrPts[i][(j+1)%_input_data.ignrPts[i].size()], COLOURS[i%NCOLOURS], 1, CV_AA); + cv::line(disp_frame, _input_data.ignrPts[i][j], _input_data.ignrPts[i][(j+1)%_input_data.ignrPts[i].size()], COLOURS[i%NCOLOURS], 1, cv::LINE_AA); } } @@ -759,7 +759,7 @@ bool ConfigGui::run() /// Draw previous clicks. for (auto click : _input_data.sqrPts) { - cv::circle(disp_frame, click, click_rad, Scalar(255, 255, 0), 1, CV_AA); + cv::circle(disp_frame, click, click_rad, Scalar(255, 255, 0), 1, cv::LINE_AA); } /// Draw reference corners. @@ -887,7 +887,7 @@ bool ConfigGui::run() /// Draw previous clicks. for (auto click : _input_data.sqrPts) { - cv::circle(disp_frame, click, click_rad, Scalar(255,255,0), 1, CV_AA); + cv::circle(disp_frame, click, click_rad, Scalar(255,255,0), 1, cv::LINE_AA); } /// Draw axes. @@ -937,7 +937,7 @@ bool ConfigGui::run() /// Draw previous clicks. for (auto click : _input_data.sqrPts) { - cv::circle(disp_frame, click, click_rad, Scalar(255,255,0), 1, CV_AA); + cv::circle(disp_frame, click, click_rad, Scalar(255,255,0), 1, cv::LINE_AA); } /// Draw axes. @@ -987,7 +987,7 @@ bool ConfigGui::run() /// Draw previous clicks. for (auto click : _input_data.sqrPts) { - cv::circle(disp_frame, click, click_rad, Scalar(255,255,0), 1, CV_AA); + cv::circle(disp_frame, click, click_rad, Scalar(255,255,0), 1, cv::LINE_AA); } /// Draw axes. @@ -1100,9 +1100,9 @@ bool ConfigGui::run() for (unsigned int i = 0; i < _input_data.ignrPts.size(); i++) { for (unsigned int j = 0; j < _input_data.ignrPts[i].size(); j++) { if (i == _input_data.ignrPts.size() - 1) { - cv::circle(disp_frame, _input_data.ignrPts[i][j], click_rad, COLOURS[i%NCOLOURS], 1, CV_AA); + cv::circle(disp_frame, _input_data.ignrPts[i][j], click_rad, COLOURS[i%NCOLOURS], 1, cv::LINE_AA); } - cv::line(disp_frame, _input_data.ignrPts[i][j], _input_data.ignrPts[i][(j + 1) % _input_data.ignrPts[i].size()], COLOURS[i%NCOLOURS], 1, CV_AA); + cv::line(disp_frame, _input_data.ignrPts[i][j], _input_data.ignrPts[i][(j + 1) % _input_data.ignrPts[i].size()], COLOURS[i%NCOLOURS], 1, cv::LINE_AA); } } diff --git a/src/FrameGrabber.cpp b/src/FrameGrabber.cpp index c655a3e..9740727 100644 --- a/src/FrameGrabber.cpp +++ b/src/FrameGrabber.cpp @@ -235,7 +235,7 @@ void FrameGrabber::process() memset(win_min_hist.get(), 0, _thresh_win); /// Create grey ROI frame. - cv::cvtColor(frame_bgr, frame_grey, CV_BGR2GRAY); + cv::cvtColor(frame_bgr, frame_grey, cv::COLOR_BGR2GRAY); _remapper->apply(frame_grey, remap_grey); /// Blur image before calculating region min/max values. diff --git a/src/Remapper.cpp b/src/Remapper.cpp index 2439e89..5473036 100644 --- a/src/Remapper.cpp +++ b/src/Remapper.cpp @@ -88,98 +88,74 @@ void Remapper::applyF4(const float *src, float *dst, int srcStep, int dstStep) _apply(REMAP_TYPE_F4, src, dst, srcStep, dstStep); } -void Remapper::apply(const IplImage *src, IplImage *dst) -{ - /// - /// Sanity check. - /// - if (src->width!=_srcW || src->height!=_srcH) { - LOG_ERR("Error applying remapping! Unexpected source image size (%dx%d)!", src->width, src->height); - return; - } - if (dst->width!=_dstW || dst->height!=_dstH) { - LOG_ERR("Error applying remapping! Unexpected destination image size (%dx%d)!", dst->width, dst->height); - return; - } - - /// - /// Note: not enforcing nChannels consistency directly, but a test is - /// performed in _apply() but only to avoid segfaults. This is - /// to allow the likes of BayerRemap to overload the _apply() - /// method and perform a remapping using a single channel source - /// image and a three channel destination image. - /// - if (src->depth != dst->depth) { - LOG_ERR("Error applying remapping! Image depth mismatch (%d != %d).", src->depth, dst->depth); - return; - } - - switch (src->depth) { - case IPL_DEPTH_8U: - case IPL_DEPTH_8S: - switch (src->nChannels) { - case 1: _apply(REMAP_TYPE_C1, - src->imageData, dst->imageData, - src->widthStep, dst->widthStep); break; - case 3: _apply(REMAP_TYPE_C3, - src->imageData, dst->imageData, - src->widthStep, dst->widthStep); break; - case 4: _apply(REMAP_TYPE_C4, - src->imageData, dst->imageData, - src->widthStep, dst->widthStep); break; - default: break; - } - break; - case IPL_DEPTH_16U: - case IPL_DEPTH_16S: - switch (src->nChannels) { - case 1: _apply(REMAP_TYPE_S1, - src->imageData, dst->imageData, - src->widthStep, dst->widthStep); break; - case 3: _apply(REMAP_TYPE_S3, - src->imageData, dst->imageData, - src->widthStep, dst->widthStep); break; - case 4: _apply(REMAP_TYPE_S4, - src->imageData, dst->imageData, - src->widthStep, dst->widthStep); break; - default: break; - } - break; - case IPL_DEPTH_32F: - switch (src->nChannels) { - case 1: _apply(REMAP_TYPE_F1, - src->imageData, dst->imageData, - src->widthStep, dst->widthStep); break; - case 3: _apply(REMAP_TYPE_F3, - src->imageData, dst->imageData, - src->widthStep, dst->widthStep); break; - case 4: _apply(REMAP_TYPE_F4, - src->imageData, dst->imageData, - src->widthStep, dst->widthStep); break; - default: break; - } - break; - default: - LOG_ERR("Error applying remapping! Invalid data type"); - return; - } -} - void Remapper::apply(const cv::Mat& src, cv::Mat& dst) { - /// - /// Ensure destination is same type as source. - /// - if (!dst.data || dst.cols!=_dstW || dst.rows!=_dstH - || dst.type()!=src.type()) - { - dst.create(_dstH, _dstW, src.type()); - dst.setTo(cv::Scalar::all(0)); - } - - IplImage iplSrc = src; - IplImage iplDst = dst; - apply(&iplSrc, &iplDst); + /// + /// Sanity check. + /// + if (src.cols != _srcW || src.rows != _srcH) { + LOG_ERR("Error applying remapping! Unexpected source image size (%dx%d)!", src.cols, src.rows); + return; + } + + /// + /// Ensure destination is same type as source. + /// + if (dst.cols != src.cols || dst.rows != src.rows || dst.type() != src.type()) { + // if logic unnecessary for create(), but we don't want to bother clearing mem unless we allocate + dst.create(_dstH, _dstW, src.type()); + dst.setTo(cv::Scalar::all(0)); + } + + switch (src.depth()) { + case CV_8U: + case CV_8S: + switch (src.channels()) { + case 1: _apply(REMAP_TYPE_C1, + src.data, dst.data, + src.step, dst.step); break; + case 3: _apply(REMAP_TYPE_C3, + src.data, dst.data, + src.step, dst.step); break; + case 4: _apply(REMAP_TYPE_C4, + src.data, dst.data, + src.step, dst.step); break; + default: break; + } + break; + case CV_16U: + case CV_16S: + switch (src.channels()) { + case 1: _apply(REMAP_TYPE_S1, + src.data, dst.data, + src.step, dst.step); break; + case 3: _apply(REMAP_TYPE_S3, + src.data, dst.data, + src.step, dst.step); break; + case 4: _apply(REMAP_TYPE_S4, + src.data, dst.data, + src.step, dst.step); break; + default: break; + } + break; + case CV_32F: + switch (src.channels()) { + case 1: _apply(REMAP_TYPE_F1, + src.data, dst.data, + src.step, dst.step); break; + case 3: _apply(REMAP_TYPE_F3, + src.data, dst.data, + src.step, dst.step); break; + case 4: _apply(REMAP_TYPE_F4, + src.data, dst.data, + src.step, dst.step); break; + default: break; + } + break; + default: + LOG_ERR("Error applying remapping! Invalid data type"); + return; + } } diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 36c5e40..53a569b 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -1178,25 +1178,25 @@ void Trackball::drawCanvas(std::shared_ptr data) static Mat resize_roi(DRAW_CELL_DIM, DRAW_CELL_DIM, CV_8UC1); cv::resize(roi_frame, resize_roi, resize_roi.size()); Mat draw_roi = canvas(Rect(2 * DRAW_CELL_DIM, 0, DRAW_CELL_DIM, DRAW_CELL_DIM)); - cv::cvtColor(resize_roi, draw_roi, CV_GRAY2BGR); + cv::cvtColor(resize_roi, draw_roi, cv::COLOR_GRAY2BGR); /// Draw warped diff ROI. static Mat resize_diff(DRAW_CELL_DIM, DRAW_CELL_DIM, CV_8UC1); cv::resize(diff_roi, resize_diff, resize_diff.size()); Mat draw_diff = canvas(Rect(3 * DRAW_CELL_DIM, 0, DRAW_CELL_DIM, DRAW_CELL_DIM)); - cv::cvtColor(resize_diff, draw_diff, CV_GRAY2BGR); + cv::cvtColor(resize_diff, draw_diff, cv::COLOR_GRAY2BGR); /// Draw current sphere view. static Mat resize_view(DRAW_CELL_DIM, 2 * DRAW_CELL_DIM, CV_8UC1); cv::resize(sphere_view, resize_view, resize_view.size()); Mat draw_view = canvas(Rect(2 * DRAW_CELL_DIM, 1 * DRAW_CELL_DIM, 2 * DRAW_CELL_DIM, DRAW_CELL_DIM)); - cv::cvtColor(resize_view, draw_view, CV_GRAY2BGR); + cv::cvtColor(resize_view, draw_view, cv::COLOR_GRAY2BGR); /// Draw current sphere map. static Mat resize_map(DRAW_CELL_DIM, 2 * DRAW_CELL_DIM, CV_8UC1); cv::resize(sphere_map, resize_map, resize_map.size()); Mat draw_map = canvas(Rect(2 * DRAW_CELL_DIM, 2 * DRAW_CELL_DIM, 2 * DRAW_CELL_DIM, DRAW_CELL_DIM)); - cv::cvtColor(resize_map, draw_map, CV_GRAY2BGR); + cv::cvtColor(resize_map, draw_map, cv::COLOR_GRAY2BGR); /// Draw fictive path. //FIXME: add heading arrow to fictive path @@ -1229,7 +1229,7 @@ void Trackball::drawCanvas(std::shared_ptr data) cv::line(draw_path, cv::Point(static_cast(round(ppx * 16)), static_cast(round(ppy * 16))), cv::Point(static_cast(round(px * 16)), static_cast(round(py * 16))), - CV_RGB(255, 255, 255), 1, CV_AA, 4); + CV_RGB(255, 255, 255), 1, cv::LINE_AA, 4); ppx = px; ppy = py; } @@ -1265,7 +1265,7 @@ void Trackball::drawCanvas(std::shared_ptr data) cv::line(draw_input, cv::Point(static_cast(round(px * 16)), static_cast(round(py * 16))), cv::Point(static_cast(round(ppx * 16)), static_cast(round(ppy * 16))), - CV_RGB(r, g, b), 1, CV_AA, 4); + CV_RGB(r, g, b), 1, cv::LINE_AA, 4); } } ppx = px; @@ -1277,19 +1277,19 @@ void Trackball::drawCanvas(std::shared_ptr data) cv::line(canvas, cv::Point(2 * DRAW_CELL_DIM, 0 * DRAW_CELL_DIM) * 16, cv::Point(2 * DRAW_CELL_DIM, 3 * DRAW_CELL_DIM) * 16, - CV_RGB(255, 255, 255), 2, CV_AA, 4); + CV_RGB(255, 255, 255), 2, cv::LINE_AA, 4); cv::line(canvas, cv::Point(0 * DRAW_CELL_DIM, 2 * DRAW_CELL_DIM) * 16, cv::Point(4 * DRAW_CELL_DIM, 2 * DRAW_CELL_DIM) * 16, - CV_RGB(255, 255, 255), 2, CV_AA, 4); + CV_RGB(255, 255, 255), 2, cv::LINE_AA, 4); cv::line(canvas, cv::Point(3 * DRAW_CELL_DIM, 0 * DRAW_CELL_DIM) * 16, cv::Point(3 * DRAW_CELL_DIM, 1 * DRAW_CELL_DIM) * 16, - CV_RGB(255, 255, 255), 2, CV_AA, 4); + CV_RGB(255, 255, 255), 2, cv::LINE_AA, 4); cv::line(canvas, cv::Point(2 * DRAW_CELL_DIM, 1 * DRAW_CELL_DIM) * 16, cv::Point(4 * DRAW_CELL_DIM, 1 * DRAW_CELL_DIM) * 16, - CV_RGB(255, 255, 255), 2, CV_AA, 4); + CV_RGB(255, 255, 255), 2, cv::LINE_AA, 4); /// Draw text (with shadow). shadowText(canvas, string("Processed ") + dateString(), diff --git a/src/drawing.cpp b/src/drawing.cpp index f6278c8..fa8d8b6 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -71,7 +71,7 @@ void drawCircle(cv::Mat& img, shared_ptr> circ_pts, const cv for (int i = 1; i < circ_pts->size(); i++) { /// Draw dashed/solid. p2 = (*circ_pts)[i]; - if (solid || (i % 2)) { cv::line(img, 4 * p1, 4 * p2, colour, 2, CV_AA, 2); } + if (solid || (i % 2)) { cv::line(img, 4 * p1, 4 * p2, colour, 2, cv::LINE_AA, 2); } p1 = p2; } } @@ -128,10 +128,10 @@ void drawCursor(Mat& rgb, const Point2d& pt, cv::Scalar colour) const int inner_rad = std::max(int(rgb.cols/500+0.5), 2); const int outer_rad = std::max(int(rgb.cols/150+0.5), 5); - cv::line(rgb, pt-Point2d(outer_rad,outer_rad), pt-Point2d(inner_rad,inner_rad), colour, 1, CV_AA); - cv::line(rgb, pt+Point2d(inner_rad,inner_rad), pt+Point2d(outer_rad,outer_rad), colour, 1, CV_AA); - cv::line(rgb, pt-Point2d(-outer_rad,outer_rad), pt-Point2d(-inner_rad,inner_rad), colour, 1, CV_AA); - cv::line(rgb, pt+Point2d(-inner_rad,inner_rad), pt+Point2d(-outer_rad,outer_rad), colour, 1, CV_AA); + cv::line(rgb, pt-Point2d(outer_rad,outer_rad), pt-Point2d(inner_rad,inner_rad), colour, 1, cv::LINE_AA); + cv::line(rgb, pt+Point2d(inner_rad,inner_rad), pt+Point2d(outer_rad,outer_rad), colour, 1, cv::LINE_AA); + cv::line(rgb, pt-Point2d(-outer_rad,outer_rad), pt-Point2d(-inner_rad,inner_rad), colour, 1, cv::LINE_AA); + cv::line(rgb, pt+Point2d(-inner_rad,inner_rad), pt+Point2d(-outer_rad,outer_rad), colour, 1, cv::LINE_AA); } /// @@ -159,15 +159,15 @@ void drawAxes(Mat& rgb, const CameraModelPtr cam_model, const Mat& R, const Mat& vec[2] = sx.at(2, 0); cam_model->vectorToPixel(vec, pt.x, pt.y); - cv::line(rgb, 4 * pt0, 4 * pt, colour, 2, CV_AA, 2); - cv::putText(rgb, "x", pt + Point2d(10, 0), cv::FONT_HERSHEY_SIMPLEX, 1.0, colour, 1, CV_AA); + cv::line(rgb, 4 * pt0, 4 * pt, colour, 2, cv::LINE_AA, 2); + cv::putText(rgb, "x", pt + Point2d(10, 0), cv::FONT_HERSHEY_SIMPLEX, 1.0, colour, 1, cv::LINE_AA); // indicate in to or out of the page if (vec[2] < t.at(2, 0)) { - cv::circle(rgb, 4 * pt, 4 * 4, colour, 2, CV_AA, 2); + cv::circle(rgb, 4 * pt, 4 * 4, colour, 2, cv::LINE_AA, 2); } else { - cv::line(rgb, 4 * (pt + Point2d(-4, -4)), 4 * (pt + Point2d(4, 4)), colour, 2, CV_AA, 2); - cv::line(rgb, 4 * (pt + Point2d(-4, 4)), 4 * (pt + Point2d(4, -4)), colour, 2, CV_AA, 2); + cv::line(rgb, 4 * (pt + Point2d(-4, -4)), 4 * (pt + Point2d(4, 4)), colour, 2, cv::LINE_AA, 2); + cv::line(rgb, 4 * (pt + Point2d(-4, 4)), 4 * (pt + Point2d(4, -4)), colour, 2, cv::LINE_AA, 2); } } @@ -178,15 +178,15 @@ void drawAxes(Mat& rgb, const CameraModelPtr cam_model, const Mat& R, const Mat& vec[2] = sy.at(2, 0); cam_model->vectorToPixel(vec, pt.x, pt.y); - cv::line(rgb, 4 * pt0, 4 * pt, colour, 2, CV_AA, 2); - cv::putText(rgb, "y", pt + Point2d(10, 0), cv::FONT_HERSHEY_SIMPLEX, 1.0, colour, 1, CV_AA); + cv::line(rgb, 4 * pt0, 4 * pt, colour, 2, cv::LINE_AA, 2); + cv::putText(rgb, "y", pt + Point2d(10, 0), cv::FONT_HERSHEY_SIMPLEX, 1.0, colour, 1, cv::LINE_AA); // indicate in to or out of the page if (vec[2] < t.at(2, 0)) { - cv::circle(rgb, 4 * pt, 4 * 4, colour, 2, CV_AA, 2); + cv::circle(rgb, 4 * pt, 4 * 4, colour, 2, cv::LINE_AA, 2); } else { - cv::line(rgb, 4 * (pt + Point2d(-4, -4)), 4 * (pt + Point2d(4, 4)), colour, 2, CV_AA, 2); - cv::line(rgb, 4 * (pt + Point2d(-4, 4)), 4 * (pt + Point2d(4, -4)), colour, 2, CV_AA, 2); + cv::line(rgb, 4 * (pt + Point2d(-4, -4)), 4 * (pt + Point2d(4, 4)), colour, 2, cv::LINE_AA, 2); + cv::line(rgb, 4 * (pt + Point2d(-4, 4)), 4 * (pt + Point2d(4, -4)), colour, 2, cv::LINE_AA, 2); } } @@ -197,15 +197,15 @@ void drawAxes(Mat& rgb, const CameraModelPtr cam_model, const Mat& R, const Mat& vec[2] = sz.at(2, 0); cam_model->vectorToPixel(vec, pt.x, pt.y); - cv::line(rgb, 4 * pt0, 4 * pt, colour, 2, CV_AA, 2); - cv::putText(rgb, "z", pt + Point2d(10, 0), cv::FONT_HERSHEY_SIMPLEX, 1.0, colour, 1, CV_AA); + cv::line(rgb, 4 * pt0, 4 * pt, colour, 2, cv::LINE_AA, 2); + cv::putText(rgb, "z", pt + Point2d(10, 0), cv::FONT_HERSHEY_SIMPLEX, 1.0, colour, 1, cv::LINE_AA); // indicate in to or out of the page if (vec[2] < t.at(2, 0)) { - cv::circle(rgb, 4 * pt, 4 * 4, colour, 2, CV_AA, 2); + cv::circle(rgb, 4 * pt, 4 * 4, colour, 2, cv::LINE_AA, 2); } else { - cv::line(rgb, 4 * (pt + Point2d(-4, -4)), 4 * (pt + Point2d(4, 4)), colour, 2, CV_AA, 2); - cv::line(rgb, 4 * (pt + Point2d(-4, 4)), 4 * (pt + Point2d(4, -4)), colour, 2, CV_AA, 2); + cv::line(rgb, 4 * (pt + Point2d(-4, -4)), 4 * (pt + Point2d(4, 4)), colour, 2, cv::LINE_AA, 2); + cv::line(rgb, 4 * (pt + Point2d(-4, 4)), 4 * (pt + Point2d(4, -4)), colour, 2, cv::LINE_AA, 2); } } } @@ -235,6 +235,6 @@ void drawRectCorners(Mat& rgb, const CameraModelPtr cam_model, Mat& cnrs, const /// void shadowText(Mat& img, std::string text, int px, int py, int r, int g, int b) { - cv::putText(img, text, cv::Point(px + 1, py + 1), CV_FONT_HERSHEY_SIMPLEX, 0.5, CV_RGB(0, 0, 0)); - cv::putText(img, text, cv::Point(px, py), CV_FONT_HERSHEY_SIMPLEX, 0.5, CV_RGB(r, g, b)); + cv::putText(img, text, cv::Point(px + 1, py + 1), cv::FONT_HERSHEY_SIMPLEX, 0.5, CV_RGB(0, 0, 0)); + cv::putText(img, text, cv::Point(px, py), cv::FONT_HERSHEY_SIMPLEX, 0.5, CV_RGB(r, g, b)); } From 8943dcdf455bc901a7d62cb2ab42cde78dfcf3bf Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 22 Feb 2019 10:38:08 +0100 Subject: [PATCH 035/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1379cb9..eada8d2 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ FicTrac will usually generate two output files: The output data file can be used for offline processing. To use FicTrac within a closed-loop setup (to provide real-time feedback for stimuli), you should configure FicTrac to output data via a socket (IP address/port) in real-time. To do this, just set `out_port` to a valid port number in the config file. There is an example Python script for receiving data via sockets in the `scripts` directory. -**Note:** If you receive an error when running FicTrac about a missing [H264 library](https://github.com/cisco/openh264/releases), you can download the necessary library (i.e. OpenCV 3.4.2 requires `openh264-1.7.0-win64.dll`) from the above link and place it in the `dll` folder under the FicTrac main directory. You will then need to re-run the appropriate `cmake --build` command for your installation. +**Note:** If you receive an error when running FicTrac about a missing [H264 library](https://github.com/cisco/openh264/releases), you can download the necessary library (i.e. OpenCV 4.0.1 requires `openh264-1.8.0-win64.dll`) from the above link and place it in the `dll` folder under the FicTrac main directory. You will then need to re-run the appropriate `cmake --build` command for your installation. ## Research From 956f8d0084f28ed414f6752c254eef3b2ed304f2 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 22 Feb 2019 10:41:33 +0100 Subject: [PATCH 036/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eada8d2..2a1759a 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ FicTrac will usually generate two output files: The output data file can be used for offline processing. To use FicTrac within a closed-loop setup (to provide real-time feedback for stimuli), you should configure FicTrac to output data via a socket (IP address/port) in real-time. To do this, just set `out_port` to a valid port number in the config file. There is an example Python script for receiving data via sockets in the `scripts` directory. -**Note:** If you receive an error when running FicTrac about a missing [H264 library](https://github.com/cisco/openh264/releases), you can download the necessary library (i.e. OpenCV 4.0.1 requires `openh264-1.8.0-win64.dll`) from the above link and place it in the `dll` folder under the FicTrac main directory. You will then need to re-run the appropriate `cmake --build` command for your installation. +**Note:** If you receive an error when running FicTrac about a missing [H264 library](https://github.com/cisco/openh264/releases), you can download the necessary library (i.e. OpenCV 4.0.1 requires `openh264-1.8.0-win64.dll`) from the above link and place it in the `dll` folder under the FicTrac main directory. You will then need to re-run the appropriate `cmake ..` and `cmake --build` commands for your installation. ## Research From f519b8a1addb413fdfda8bc8e41fc55ccc43da84 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 22 Feb 2019 13:01:01 +0100 Subject: [PATCH 037/235] Update params.md --- doc/params.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/params.md b/doc/params.md index 829909b..51b5d47 100644 --- a/doc/params.md +++ b/doc/params.md @@ -23,6 +23,7 @@ In the table below, the various possible parameters are listed. If nothing is li | opt_max_evals | int | 50 | (0,inf) | Probably not | Specifies the maximum number of minimisation iterations to perform each frame. Smaller values may improve tracking frame rate at the risk of finding sub-optimal matches. Number of optimisation iterations is printed to screen during tracking (its=...). | | opt_bound | float | 0.35 | (0,inf) | Probably not | Specifies the optimisation search range in radians. Larger values will facilitate more track ball rotation per frame, but result in slower tracking and also possibly lead to false matches. | | opt_tol | float | 0.001 | (0,inf) | Probably not | Specifies the minimisation termination criteria for absolute change in input parameters (delta rotation vector). | +| vid_codec | string | h264 | [h264,xvid,mpg4,mjpg,raw] | Probably not | Specifies the video codec to use when writing output videos (see `save_raw` and `save_debug`). | | | | | | | | | c2a_cnrs_xy | vec\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XY axes. Set interactively in ConfigGUI. | | c2a_cnrs_yz | vec\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's YZ axes. Set interactively in ConfigGUI. | From 2890e2c958a265504d9a1fca83ff11d64277f383 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 22 Feb 2019 13:05:44 +0100 Subject: [PATCH 038/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a1759a..d1145d9 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ FicTrac will usually generate two output files: The output data file can be used for offline processing. To use FicTrac within a closed-loop setup (to provide real-time feedback for stimuli), you should configure FicTrac to output data via a socket (IP address/port) in real-time. To do this, just set `out_port` to a valid port number in the config file. There is an example Python script for receiving data via sockets in the `scripts` directory. -**Note:** If you receive an error when running FicTrac about a missing [H264 library](https://github.com/cisco/openh264/releases), you can download the necessary library (i.e. OpenCV 4.0.1 requires `openh264-1.8.0-win64.dll`) from the above link and place it in the `dll` folder under the FicTrac main directory. You will then need to re-run the appropriate `cmake ..` and `cmake --build` commands for your installation. +**Note:** If you encounter issues trying to generate output videos (i.e. `save_raw` or `save_debug`), you might try changing the default video codec via `vid_codec` - see [config params](doc/params.md) for details. If you receive an error about a missing [H264 library](https://github.com/cisco/openh264/releases), you can download the necessary library (i.e. OpenCV 4.0.1 requires `openh264-1.8.0-win64.dll`) from the above link and place it in the `dll` folder under the FicTrac main directory. You will then need to re-run the appropriate `cmake ..` and `cmake --build` commands for your installation. ## Research From 714c08e20c4721b6401c58ddef714d170a3aadf1 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 22 Feb 2019 13:10:54 +0100 Subject: [PATCH 039/235] multi-choice video codec --- sample/config.txt | 2 +- src/Trackball.cpp | 80 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/sample/config.txt b/sample/config.txt index 11457ab..788299c 100644 --- a/sample/config.txt +++ b/sample/config.txt @@ -1,4 +1,4 @@ -## FicTrac config file (build Feb 19 2019) +## FicTrac config file (build Feb 22 2019) c2a_cnrs_xy : { 191, 171, 128, 272, 20, 212, 99, 132 } c2a_r : { -0.722443, 0.131317, 0.460878 } c2a_src : c2a_cnrs_xy diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 53a569b..fec4af6 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -43,6 +43,8 @@ using cv::Scalar; using cv::Rect; using cv::Point2d; using cv::Point2i; +using cv::VideoWriter; + const int DRAW_SPHERE_HIST_LENGTH = 250; const int DRAW_CELL_DIM = 160; @@ -66,6 +68,15 @@ const bool DO_DISPLAY_DEFAULT = true; const bool SAVE_RAW_DEFAULT = false; const bool SAVE_DEBUG_DEFAULT = false; +/// OpenCV codecs for video writing +const vector> CODECS = { + {"h264", "H264", "avi"}, + {"xvid", "XVID", "avi"}, + {"mpg4", "MP4V", "mp4"}, + {"mjpg", "MJPG", "avi"}, + {"raw", "", "avi"} +}; + /// /// /// @@ -409,28 +420,55 @@ Trackball::Trackball(string cfg_fn) _sphere_view.create(_map_h, _map_w, CV_8UC1); _sphere_view.setTo(Scalar::all(128)); } - if (_save_raw) { - string vid_fn = _base_fn + "-raw.mp4"; - double fps = source->getFPS(); - if (fps <= 0) { fps = 25; } - LOG_DBG("Opening %s for video writing (%dx%d @ %f FPS)", vid_fn.c_str(), source->getWidth(), source->getHeight(), fps); - _raw_vid.open(vid_fn, cv::VideoWriter::fourcc('H', '2', '6', '4'), fps, cv::Size(source->getWidth(), source->getHeight())); - if (!_raw_vid.isOpened()) { - LOG_ERR("Error! Unable to open raw output video (%s).", vid_fn.c_str()); - _active = false; - return; + + // do video stuff + if (_save_raw || _save_debug) { + // find codec + int fourcc = 0; + string cstr = _cfg("vid_codec"), fext; + for (auto codec : CODECS) { + if (cstr == codec[0]) { + fourcc = VideoWriter::fourcc(codec[1][0], codec[1][1], codec[1][2], codec[1][3]); + fext = codec[2]; + } } - } - if (_save_debug) { - string vid_fn = _base_fn + "-debug.mp4"; - double fps = source->getFPS(); - if (fps <= 0) { fps = 25; } - LOG_DBG("Opening %s for video writing (%dx%d @ %f FPS)", vid_fn.c_str(), 4 * DRAW_CELL_DIM, 3 * DRAW_CELL_DIM, fps); - _debug_vid.open(vid_fn, cv::VideoWriter::fourcc('H', '2', '6', '4'), fps, cv::Size(4 * DRAW_CELL_DIM, 3 * DRAW_CELL_DIM)); - if (!_debug_vid.isOpened()) { - LOG_ERR("Error! Unable to open debug output video (%s).", vid_fn.c_str()); - _active = false; - return; + if (fext.empty()) { + // codec not found - use default + auto codec = CODECS[0]; + cstr = codec[0]; + fourcc = VideoWriter::fourcc(codec[1][0], codec[1][1], codec[1][2], codec[1][3]); + fext = codec[2]; + LOG_WRN("Warning! Using default value for vid_codec (%s).", cstr.c_str()); + _cfg.add("vid_codec", cstr); + } + else + + // raw input video + if (_save_raw) { + string vid_fn = _base_fn + "-raw." + fext; + double fps = source->getFPS(); + if (fps <= 0) { fps = 25; } + LOG_DBG("Opening %s for video writing (%s %dx%d @ %f FPS)", vid_fn.c_str(), cstr.c_str(), source->getWidth(), source->getHeight(), fps); + _raw_vid.open(vid_fn, fourcc, fps, cv::Size(source->getWidth(), source->getHeight())); + if (!_raw_vid.isOpened()) { + LOG_ERR("Error! Unable to open raw output video (%s).", vid_fn.c_str()); + _active = false; + return; + } + } + + // debug output video + if (_save_debug) { + string vid_fn = _base_fn + "-debug." + fext; + double fps = source->getFPS(); + if (fps <= 0) { fps = 25; } + LOG_DBG("Opening %s for video writing (%s %dx%d @ %f FPS)", vid_fn.c_str(), cstr.c_str(), 4 * DRAW_CELL_DIM, 3 * DRAW_CELL_DIM, fps); + _debug_vid.open(vid_fn, fourcc, fps, cv::Size(4 * DRAW_CELL_DIM, 3 * DRAW_CELL_DIM)); + if (!_debug_vid.isOpened()) { + LOG_ERR("Error! Unable to open debug output video (%s).", vid_fn.c_str()); + _active = false; + return; + } } } From 812682bfdc0a3e872742bbadb1236c18fb75ff0e Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 22 Feb 2019 13:23:52 +0100 Subject: [PATCH 040/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d1145d9..2a51782 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ If you are using a USB3 camera and are receiving error messages when FicTrac tri ##### PGR (FLIR) Spinnaker SDK -1. Download and install the Spinnaker SDK from [PGR downloads page](https://www.ptgrey.com/support/downloads). +1. Download and install the latest Spinnaker (full) SDK from [PGR downloads page](https://www.ptgrey.com/support/downloads). 2. When preparing the build files for FicTrac using Cmake, you will need to specify to use Spinnaker using the switch `-D PGR_USB3=ON` and depending on where you installed the SDK, you may also need to provide the SDK directory path using the switch `-D PGR_DIR=...`. For example, for a [Windows installation](#windows-installation) you would replace step 4 with: ``` cmake -G "Visual Studio 15 2017 Win64" -D OPENCV_DIR="C:\path\to\opencv-3.4.2\build" -D NLOPT_DIR="C:\path\to\nlopt-2.4.2\" -D PGR_USB3=ON -D PGR_DIR="C:\path\to\Spinnaker" .. From 1698bbc8654efce63b0c499d4f85a69e50bc087a Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 25 Feb 2019 09:04:17 +0100 Subject: [PATCH 041/235] Update params.md --- doc/params.md | 52 +++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/doc/params.md b/doc/params.md index 51b5d47..d037427 100644 --- a/doc/params.md +++ b/doc/params.md @@ -2,36 +2,36 @@ This document provides a brief overview of FicTrac's configuration parameters. M In the table below, the various possible parameters are listed. If nothing is listed under `Default value` then the parameter must be specified by the user. The valid range may use [interval notation](https://en.wikipedia.org/wiki/Interval_(mathematics)). The `Should I touch it?` column should give you some idea of which params to play around with. -| Param name | Param type | Default value | Valid range | Should I touch it? | Description | -|------------|------------|---------------|-------------|--------------------|-------------| +| Param name | Param type | Default value | Valid range | Should I touch it? | Description | +|------------|------------|---------------|-------------|---------------------|-------------| | src_fn | string OR int | | int=\[0,inf) | Yes, you have to | A string that specifies the path to the input video file, OR an integer that specifies which of several connected USB cameras to use. Paths can be absolute or relative to the working directory. | -| vfov | float | | (0,inf) | Yes, you have to | Vertical field of view of the input images in degrees. | -| | | | | | | -| do_display | bool | y | y/n | If you want to | Display debug screen during tracking. Slows execution very slightly. | -| save_debug | bool | n | y/n | If you want to | Record the debug screen to video file. Note that if the source frame rate is higher than FicTrac's processing frame rate, frames may be dropped from the video file. | -| save_raw | bool | n | y/n | If you want to | Record the input image stream to video file. Note that if the source frame rate is higher than FicTrac's processing frame rate, frames may be dropped from the video file. | -| out_port | int | -1 | (0,inf) | If you want to | Socket port over which to transmit FicTrac data. If unset or < 0, FicTrac will not transmit data over sockets. | -| | | | | | | +| vfov | float | | (0,inf) | Yes, you have to | Vertical field of view of the input images in degrees. | +| | | | | | | +| do_display | bool | y | y/n | If you want to | Display debug screen during tracking. Slows execution very slightly. | +| save_debug | bool | n | y/n | If you want to | Record the debug screen to video file. Note that if the source frame rate is higher than FicTrac's processing frame rate, frames may be dropped from the video file. | +| save_raw | bool | n | y/n | If you want to | Record the input image stream to video file. Note that if the source frame rate is higher than FicTrac's processing frame rate, frames may be dropped from the video file. | +| out_port | int | -1 | (0,inf) | If you want to | Socket port over which to transmit FicTrac data. If unset or < 0, FicTrac will not transmit data over sockets. | +| | | | | | | | q_factor | int | 6 | (0,inf) | Only if you need to | Adjusts the resolution of the tracking window. Smaller values correspond to coarser but quicker tracking and vice-versa. Normally in the range [3,10]. | | src_fps | float | -1 | (0,inf) | Only if you need to | If set, FicTrac will attempt to set the frame rate for the image source (video file or camera). | | max_bad_frames | int | -1 | (0,inf) | Only if you need to | If set, FicTrac will reset tracking after being unable to match this many frames in a row. Defaults to never resetting tracking. | | opt_do_global | bool | n | y/n | Only if you need to | Perform a slow global search after max_bad_frames are reached. This may allow FicTrac to recover after a tracking fail, but should only be used when playing back from video file, as it is slow! | -| opt_max_err | float | -1 | \[0,inf) | Only if you need to | If set, specifies the maximum allowable matching error before declaring a bad frame (i.e. tracking fail). Matching error is printed to screen during tracking (err=...), and also output in the [data file](doc/data_header.txt) (delta rotation error score). If unset, FicTrac will never detect bad matches (tracking will fail silently). | +| opt_max_err | float | -1 | \[0,inf) | Only if you need to | If set, specifies the maximum allowable matching error before declaring a bad frame (i.e. tracking fail). Matching error is printed to screen during tracking (err=...), and also output in the [data file](doc/data_header.txt) (delta rotation error score). If unset, FicTrac will never detect bad matches (tracking will fail silently). | | thr_ratio | float | 1.25 | (0,inf) | Only if you need to | Adjusts the adaptive thresholding of the input image. Values > 1 will favour foreground regions (more white in thresholded image) and values < 1 will favour background regions (more black in thresholded image). | -| thr_win_pc | float | 0.2 | \[0,1] | Only if you need to | Adjusts the size of the neighbourhood window to use for adaptive thresholding of the input image, specified as a percentage of the width of the tracking window. Larger values avoid over-segmentation, whilst smaller values make segmentation more robust to illumination gradients on the trackball. | -| | | | | | | -| opt_max_evals | int | 50 | (0,inf) | Probably not | Specifies the maximum number of minimisation iterations to perform each frame. Smaller values may improve tracking frame rate at the risk of finding sub-optimal matches. Number of optimisation iterations is printed to screen during tracking (its=...). | -| opt_bound | float | 0.35 | (0,inf) | Probably not | Specifies the optimisation search range in radians. Larger values will facilitate more track ball rotation per frame, but result in slower tracking and also possibly lead to false matches. | -| opt_tol | float | 0.001 | (0,inf) | Probably not | Specifies the minimisation termination criteria for absolute change in input parameters (delta rotation vector). | +| thr_win_pc | float | 0.2 | \[0,1] | Only if you need to | Adjusts the size of the neighbourhood window to use for adaptive thresholding of the input image, specified as a percentage of the width of the tracking window. Larger values avoid over-segmentation, whilst smaller values make segmentation more robust to illumination gradients on the trackball. | +| | | | | | | +| opt_max_evals | int | 50 | (0,inf) | Probably not | Specifies the maximum number of minimisation iterations to perform each frame. Smaller values may improve tracking frame rate at the risk of finding sub-optimal matches. Number of optimisation iterations is printed to screen during tracking (its=...). | +| opt_bound | float | 0.35 | (0,inf) | Probably not | Specifies the optimisation search range in radians. Larger values will facilitate more track ball rotation per frame, but result in slower tracking and also possibly lead to false matches. | +| opt_tol | float | 0.001 | (0,inf) | Probably not | Specifies the minimisation termination criteria for absolute change in input parameters (delta rotation vector). | | vid_codec | string | h264 | [h264,xvid,mpg4,mjpg,raw] | Probably not | Specifies the video codec to use when writing output videos (see `save_raw` and `save_debug`). | -| | | | | | | -| c2a_cnrs_xy | vec\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XY axes. Set interactively in ConfigGUI. | -| c2a_cnrs_yz | vec\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's YZ axes. Set interactively in ConfigGUI. | -| c2a_cnrs_xz | vec\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XZ axes. Set interactively in ConfigGUI. | -| c2a_src | string | | | Set by ConfigGui | Specifies which of the above corner sets is used to compute the camera-animal transform. Set interactively in ConfigGUI. | -| c2a_r | vec\ | | | Set by ConfigGui | Rotational component of the camera-animal transform. Computed automatically by ConfigGUI. | -| c2a_t | vec\ | | | Set by ConfigGui | Translational component of the camera-animal transform. Computed automatically by ConfigGUI. | -| roi_circ | vec\ | | | Set by ConfigGui | Specifies points {X1,Y1,X2,Y2,...} around the circumference of the trackball in the input image. Set interactively in ConfigGUI. | -| roi_c | vec\ | | | Set by ConfigGui | Camera-frame vector describing the centre point of the trackball in the input image. Computed automatically by ConfigGUI. | -| roi_r | float | | | Set by ConfigGui | Half-angle describing the radius of the trackball in the input image. Computed automatically by ConfigGUI. | -| roi_ignr | vec> | | | Set by ConfigGui | Specifies possibly several polygon regions {{X11,Y11,X12,Y12,...},{X21,Y21,X22,Y22,...},...} that should be ignored during matching (e.g. where the animal obscures the trackball). Set interactively in ConfigGUI. | +| | | | | | | +| c2a_cnrs_xy | vec\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XY axes. Set interactively in ConfigGUI. | +| c2a_cnrs_yz | vec\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's YZ axes. Set interactively in ConfigGUI. | +| c2a_cnrs_xz | vec\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XZ axes. Set interactively in ConfigGUI. | +| c2a_src | string | | | Set by ConfigGui | Specifies which of the above corner sets is used to compute the camera-animal transform. Set interactively in ConfigGUI. | +| c2a_r | vec\ | | | Set by ConfigGui | Rotational component of the camera-animal transform. Computed automatically by ConfigGUI. | +| c2a_t | vec\ | | | Set by ConfigGui | Translational component of the camera-animal transform. Computed automatically by ConfigGUI. | +| roi_circ | vec\ | | | Set by ConfigGui | Specifies points {X1,Y1,X2,Y2,...} around the circumference of the trackball in the input image. Set interactively in ConfigGUI. | +| roi_c | vec\ | | | Set by ConfigGui | Camera-frame vector describing the centre point of the trackball in the input image. Computed automatically by ConfigGUI. | +| roi_r | float | | | Set by ConfigGui | Half-angle describing the radius of the trackball in the input image. Computed automatically by ConfigGUI. | +| roi_ignr | vec> | | | Set by ConfigGui | Specifies possibly several polygon regions {{X11,Y11,X12,Y12,...},{X21,Y21,X22,Y22,...},...} that should be ignored during matching (e.g. where the animal obscures the trackball). Set interactively in ConfigGUI. | From ff4e51e515af2f0009821485869dc66df8193406 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 25 Feb 2019 09:09:18 +0100 Subject: [PATCH 042/235] Update params.md --- doc/params.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/params.md b/doc/params.md index d037427..3587478 100644 --- a/doc/params.md +++ b/doc/params.md @@ -31,7 +31,7 @@ In the table below, the various possible parameters are listed. If nothing is li | c2a_src | string | | | Set by ConfigGui | Specifies which of the above corner sets is used to compute the camera-animal transform. Set interactively in ConfigGUI. | | c2a_r | vec\ | | | Set by ConfigGui | Rotational component of the camera-animal transform. Computed automatically by ConfigGUI. | | c2a_t | vec\ | | | Set by ConfigGui | Translational component of the camera-animal transform. Computed automatically by ConfigGUI. | -| roi_circ | vec\ | | | Set by ConfigGui | Specifies points {X1,Y1,X2,Y2,...} around the circumference of the trackball in the input image. Set interactively in ConfigGUI. | +| roi_circ | vec\ | | | Set by ConfigGui | Specifies points {X1,Y1,X2,Y2,...} around the circumference of the trackball in the input image. Set interactively in ConfigGUI. | | roi_c | vec\ | | | Set by ConfigGui | Camera-frame vector describing the centre point of the trackball in the input image. Computed automatically by ConfigGUI. | -| roi_r | float | | | Set by ConfigGui | Half-angle describing the radius of the trackball in the input image. Computed automatically by ConfigGUI. | +| roi_r | float | | | Set by ConfigGui | Half-angle describing the radius of the trackball in the input image. Computed automatically by ConfigGUI. | | roi_ignr | vec> | | | Set by ConfigGui | Specifies possibly several polygon regions {{X11,Y11,X12,Y12,...},{X21,Y21,X22,Y22,...},...} that should be ignored during matching (e.g. where the animal obscures the trackball). Set interactively in ConfigGUI. | From 7969ca4f32d2455051d4ff6a199bac5ecd06ad57 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 25 Feb 2019 09:12:15 +0100 Subject: [PATCH 043/235] Update params.md --- doc/params.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/params.md b/doc/params.md index 3587478..ba7148e 100644 --- a/doc/params.md +++ b/doc/params.md @@ -19,11 +19,11 @@ In the table below, the various possible parameters are listed. If nothing is li | opt_max_err | float | -1 | \[0,inf) | Only if you need to | If set, specifies the maximum allowable matching error before declaring a bad frame (i.e. tracking fail). Matching error is printed to screen during tracking (err=...), and also output in the [data file](doc/data_header.txt) (delta rotation error score). If unset, FicTrac will never detect bad matches (tracking will fail silently). | | thr_ratio | float | 1.25 | (0,inf) | Only if you need to | Adjusts the adaptive thresholding of the input image. Values > 1 will favour foreground regions (more white in thresholded image) and values < 1 will favour background regions (more black in thresholded image). | | thr_win_pc | float | 0.2 | \[0,1] | Only if you need to | Adjusts the size of the neighbourhood window to use for adaptive thresholding of the input image, specified as a percentage of the width of the tracking window. Larger values avoid over-segmentation, whilst smaller values make segmentation more robust to illumination gradients on the trackball. | +| vid_codec | string | h264 | [h264,xvid,mpg4,mjpg,raw] | Only if you need to | Specifies the video codec to use when writing output videos (see `save_raw` and `save_debug`). | | | | | | | | | opt_max_evals | int | 50 | (0,inf) | Probably not | Specifies the maximum number of minimisation iterations to perform each frame. Smaller values may improve tracking frame rate at the risk of finding sub-optimal matches. Number of optimisation iterations is printed to screen during tracking (its=...). | | opt_bound | float | 0.35 | (0,inf) | Probably not | Specifies the optimisation search range in radians. Larger values will facilitate more track ball rotation per frame, but result in slower tracking and also possibly lead to false matches. | | opt_tol | float | 0.001 | (0,inf) | Probably not | Specifies the minimisation termination criteria for absolute change in input parameters (delta rotation vector). | -| vid_codec | string | h264 | [h264,xvid,mpg4,mjpg,raw] | Probably not | Specifies the video codec to use when writing output videos (see `save_raw` and `save_debug`). | | | | | | | | | c2a_cnrs_xy | vec\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's XY axes. Set interactively in ConfigGUI. | | c2a_cnrs_yz | vec\ | | | Set by ConfigGui | Specifies the corners {X1,Y1,X2,Y2,...} of a square shape aligned with the animal's YZ axes. Set interactively in ConfigGUI. | From 4f53553e19840608275a375847f58a16a440a8f5 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 27 Feb 2019 09:08:54 +0100 Subject: [PATCH 044/235] default input frame queue max size set to 1 (was 10) --- include/FrameGrabber.h | 2 +- src/FrameGrabber.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/FrameGrabber.h b/include/FrameGrabber.h index 4fb55c7..9cac12f 100644 --- a/include/FrameGrabber.h +++ b/include/FrameGrabber.h @@ -30,7 +30,7 @@ class FrameGrabber const cv::Mat& remap_mask, double thresh_ratio, double thresh_win_pc, - int max_buf_len = 10, + int max_buf_len = 1, int max_frame_cnt = -1 ); ~FrameGrabber(); diff --git a/src/FrameGrabber.cpp b/src/FrameGrabber.cpp index 9740727..5c3141c 100644 --- a/src/FrameGrabber.cpp +++ b/src/FrameGrabber.cpp @@ -202,7 +202,7 @@ void FrameGrabber::process() while (_active) { /// Wait until we need to capture a new frame. unique_lock l(_qMutex); - while (_active && (_max_buf_len >= 0) && (_frame_q.size() >= _max_buf_len)) { + while (_active && (_max_buf_len > 0) && (_frame_q.size() >= _max_buf_len)) { _qCond.wait(l); } l.unlock(); From 75130461f3b6a5f91a1a0eef71d984b6548ee64a Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 27 Feb 2019 13:00:09 +0100 Subject: [PATCH 045/235] default video codec h264 --- sample/config.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sample/config.txt b/sample/config.txt index 788299c..fa736fa 100644 --- a/sample/config.txt +++ b/sample/config.txt @@ -1,4 +1,4 @@ -## FicTrac config file (build Feb 22 2019) +## FicTrac config file (build Feb 27 2019) c2a_cnrs_xy : { 191, 171, 128, 272, 20, 212, 99, 132 } c2a_r : { -0.722443, 0.131317, 0.460878 } c2a_src : c2a_cnrs_xy @@ -23,3 +23,4 @@ src_fps : -1 thr_ratio : 1.25 thr_win_pc : 0.25 vfov : 45 +vid_codec : h264 From 50175cb0ddad0fb2acf264c0ca7a757bb241930b Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 27 Feb 2019 13:02:25 +0100 Subject: [PATCH 046/235] add manual reset option to debug window (shift+R) --- include/Trackball.h | 2 +- src/Trackball.cpp | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/include/Trackball.h b/include/Trackball.h index 828ae7c..cf7a727 100644 --- a/include/Trackball.h +++ b/include/Trackball.h @@ -131,6 +131,6 @@ class Trackball std::unique_ptr _data_log, _data_sock; /// Thread stuff. - std::atomic_bool _active, _kill; + std::atomic_bool _active, _kill, _do_reset; std::unique_ptr _thread; }; diff --git a/src/Trackball.cpp b/src/Trackball.cpp index fec4af6..7765e5b 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -100,7 +100,7 @@ bool intersectSphere(const double camVec[3], double sphereVec[3], const double r /// /// Trackball::Trackball(string cfg_fn) - : _init(false), _reset(true), _clean_map(true), _active(true), _kill(false) + : _init(false), _reset(true), _clean_map(true), _active(true), _kill(false), _do_reset(false) { /// Load and parse config file. if (_cfg.read(cfg_fn) <= 0) { @@ -555,6 +555,8 @@ void Trackball::reset() _R_roi_hist.clear(); _pos_heading_hist.clear(); } + + _do_reset = false; } /// @@ -583,6 +585,12 @@ void Trackball::process() PRINT(""); LOG("Frame %d", _cnt); + /// Handle reset request + if (_do_reset) { + nbad = 0; + reset(); + } + /// Localise current view of sphere. if (!doSearch(_global_search)) { t2 = t3 = t4 = t5 = ts_ms(); @@ -605,7 +613,6 @@ void Trackball::process() /// Handle failed localisation. if ((_max_bad_frames >= 0) && (nbad > _max_bad_frames)) { - _seq = 0; nbad = 0; reset(); } else { @@ -1362,6 +1369,10 @@ void Trackball::drawCanvas(std::shared_ptr data) LOG("Exiting.."); terminate(); } + else if (key == 0x52) { // shift+R + LOG("Resetting map!"); + _do_reset = true; + } if (_save_raw) { _raw_vid.write(src_frame); From d8126431ff673d80268d57e3f7f3c0ab2e12f5a3 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 27 Feb 2019 15:59:15 +0100 Subject: [PATCH 047/235] string protection for codecs --- src/Trackball.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 7765e5b..2476cd0 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -69,7 +69,7 @@ const bool SAVE_RAW_DEFAULT = false; const bool SAVE_DEBUG_DEFAULT = false; /// OpenCV codecs for video writing -const vector> CODECS = { +const vector> CODECS = { {"h264", "H264", "avi"}, {"xvid", "XVID", "avi"}, {"mpg4", "MP4V", "mp4"}, @@ -427,8 +427,10 @@ Trackball::Trackball(string cfg_fn) int fourcc = 0; string cstr = _cfg("vid_codec"), fext; for (auto codec : CODECS) { - if (cstr == codec[0]) { - fourcc = VideoWriter::fourcc(codec[1][0], codec[1][1], codec[1][2], codec[1][3]); + if (cstr.compare(codec[0]) == 0) { // found the codec + if (cstr.compare("raw") != 0) { // codec isn't RAW + fourcc = VideoWriter::fourcc(codec[1][0], codec[1][1], codec[1][2], codec[1][3]); + } fext = codec[2]; } } @@ -436,12 +438,13 @@ Trackball::Trackball(string cfg_fn) // codec not found - use default auto codec = CODECS[0]; cstr = codec[0]; - fourcc = VideoWriter::fourcc(codec[1][0], codec[1][1], codec[1][2], codec[1][3]); + if (cstr.compare("raw") != 0) { // codec isn't RAW + fourcc = VideoWriter::fourcc(codec[1][0], codec[1][1], codec[1][2], codec[1][3]); + } fext = codec[2]; LOG_WRN("Warning! Using default value for vid_codec (%s).", cstr.c_str()); _cfg.add("vid_codec", cstr); } - else // raw input video if (_save_raw) { From cd4c9a4b5bfa95c010809b2a7d0c065943a918a8 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 27 Feb 2019 20:09:48 +0100 Subject: [PATCH 048/235] exception handling for PGR --- src/PGRSource.cpp | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/PGRSource.cpp b/src/PGRSource.cpp index 67b992a..7a64ede 100644 --- a/src/PGRSource.cpp +++ b/src/PGRSource.cpp @@ -99,7 +99,15 @@ PGRSource::PGRSource(int index) PGRSource::~PGRSource() { if (_open) { - _cam->EndAcquisition(); + try { + _cam->EndAcquisition(); + } + catch (Spinnaker::Exception& e) { + LOG_ERR("Error ending acquisition! Error was: %s", e.what()); + } + catch (...) { + LOG_ERR("Error ending acquisition!"); + } _open = false; } _cam = NULL; @@ -115,7 +123,15 @@ double PGRSource::getFPS() { double fps = _fps; if (_open) { - fps = _cam->AcquisitionResultingFrameRate(); + try { + fps = _cam->AcquisitionResultingFrameRate(); + } + catch (Spinnaker::Exception& e) { + LOG_ERR("Error retrieving camera frame rate! Error was: %s", e.what()); + } + catch (...) { + LOG_ERR("Error retrieving camera frame rate!"); + } } return fps; } @@ -124,8 +140,16 @@ bool PGRSource::setFPS(double fps) { bool ret = false; if (_open && (fps > 0)) { - _cam->AcquisitionFrameRateEnable.SetValue(true); - _cam->AcquisitionFrameRate.SetValue(fps); + try { + _cam->AcquisitionFrameRateEnable.SetValue(true); + _cam->AcquisitionFrameRate.SetValue(fps); + } + catch (Spinnaker::Exception& e) { + LOG_ERR("Error setting frame rate! Error was: %s", e.what()); + } + catch (...) { + LOG_ERR("Error setting frame rate!"); + } _fps = getFPS(); LOG("Device frame rate is now %.2f", _fps); ret = true; @@ -162,6 +186,11 @@ bool PGRSource::grab(cv::Mat& frame) pgr_image->Release(); return false; } + catch (...) { + LOG_ERR("Error grabbing PGR frame!"); + pgr_image->Release(); + return false; + } try { // Convert image @@ -180,6 +209,11 @@ bool PGRSource::grab(cv::Mat& frame) pgr_image->Release(); return false; } + catch (...) { + LOG_ERR("Error converting PGR frame!"); + pgr_image->Release(); + return false; + } } #endif // PGR_USB3 From 22e34234478ecc0952ba4e16bcadac14bc4d590d Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 28 Feb 2019 21:17:03 +0100 Subject: [PATCH 049/235] fix various bugs with flat path drawing --- src/Trackball.cpp | 60 ++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 2476cd0..4b72b2a 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -1250,36 +1250,38 @@ void Trackball::drawCanvas(std::shared_ptr data) //FIXME: add heading arrow to fictive path { int npts = pos_heading_hist.size(); - double minx = 0, maxx = 0, miny = 0, maxy = 0; - for (int i = 0; i < npts; i++) { - double x = pos_heading_hist[i].x, y = pos_heading_hist[i].y; - if (x < minx) - minx = x; - if (x > maxx) - maxx = x; - if (y < miny) - miny = y; - if (y > maxy) - maxy = y; - } - double scl = 1; - if (npts > 1) { - double sclx = double(DRAW_CELL_DIM - 8) / (2.0 * std::max(fabs(minx), fabs(maxx))); - double scly = double(DRAW_CELL_DIM - 4) / std::max(fabs(miny), fabs(maxy)); - scl = std::min(sclx, scly); - } + if (npts > 0) { + double minx = DBL_MAX, maxx = -DBL_MAX, miny = DBL_MAX, maxy = -DBL_MAX; + for (auto p : pos_heading_hist) { + double x = p.x, y = p.y; + if (x < minx) + minx = x; + if (x > maxx) + maxx = x; + if (y < miny) + miny = y; + if (y > maxy) + maxy = y; + } + double scl = 1; + if (npts > 1) { + double sclx = (minx != maxx) ? double(DRAW_CELL_DIM - 8) / (maxx - minx) : 1; + double scly = (miny != maxy) ? double(2 * DRAW_CELL_DIM - 4) / (maxy - miny) : 1; + scl = std::min(sclx, scly); + } - Mat draw_path = canvas(Rect(0, 2 * DRAW_CELL_DIM, 2 * DRAW_CELL_DIM, DRAW_CELL_DIM)); - double cx = DRAW_CELL_DIM, cy = 0.5 * DRAW_CELL_DIM; - double ppx = cx, ppy = cy; - for (int i = 0; i < npts; i++) { - double px = cx + scl * pos_heading_hist[i].y, py = cy - scl * pos_heading_hist[i].x; - cv::line(draw_path, - cv::Point(static_cast(round(ppx * 16)), static_cast(round(ppy * 16))), - cv::Point(static_cast(round(px * 16)), static_cast(round(py * 16))), - CV_RGB(255, 255, 255), 1, cv::LINE_AA, 4); - ppx = px; - ppy = py; + Mat draw_path = canvas(Rect(0, 2 * DRAW_CELL_DIM, 2 * DRAW_CELL_DIM, DRAW_CELL_DIM)); + double my = (2 * DRAW_CELL_DIM - scl * (maxy - miny)) / 2, mx = DRAW_CELL_DIM - (DRAW_CELL_DIM - scl * (maxx - minx)) / 2; // zeroth pixel for y/x data axes + double ppx = mx - scl * (pos_heading_hist[0].x - minx), ppy = my + scl * (pos_heading_hist[0].y - miny); + for (int i = 1; i < npts; i++) { + double px = mx - scl * (pos_heading_hist[i].x - minx), py = my + scl * (pos_heading_hist[i].y - miny); + cv::line(draw_path, + cv::Point(static_cast(round(ppy * 16)), static_cast(round(ppx * 16))), + cv::Point(static_cast(round(py * 16)), static_cast(round(px * 16))), + CV_RGB(255, 255, 255), 1, cv::LINE_AA, 4); + ppx = px; + ppy = py; + } } } From 75f5e2c2170a72a414295304565ad96061f2200f Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 6 Mar 2019 12:12:17 +0100 Subject: [PATCH 050/235] add reconfig param; recompute roi_c, roi_r, c2a_r, c2a_t transforms from pixel coords when reconfig flag specified --- include/ConfigParser.h | 5 +++++ include/geometry.h | 3 +++ src/ConfigGUI.cpp | 14 +++++++++----- src/Trackball.cpp | 35 ++++++++++++++++++++++++++++------- src/geometry.cpp | 18 ++++++++++++++++++ 5 files changed, 63 insertions(+), 12 deletions(-) diff --git a/include/ConfigParser.h b/include/ConfigParser.h index 18930c0..b8ddbc9 100644 --- a/include/ConfigParser.h +++ b/include/ConfigParser.h @@ -72,6 +72,11 @@ class ConfigParser str = str.substr(0, std::max(static_cast(str.size()-2),2)) + " }"; // drop last comma _data[key] = str; } + + // erase element/s + void erase(std::string key) { + _data.erase(key); + } /// Debugging void printAll(); diff --git a/include/geometry.h b/include/geometry.h index a43133f..16b0952 100644 --- a/include/geometry.h +++ b/include/geometry.h @@ -115,3 +115,6 @@ bool computeRtFromSquare_YZ(const CameraModelPtr cam_model, const std::vector& cnrs, cv::Mat& R, cv::Mat& t); + +/// Compute camera-animal R+t transform. +bool computeRtFromSquare(const CameraModelPtr cam_model, const std::string ref_str, const std::vector& cnrs, cv::Mat& R, cv::Mat& t); diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index 4dafe35..91815ec 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -407,6 +407,10 @@ bool ConfigGui::run() /// Interactive window. cv::namedWindow("configGUI", cv::WINDOW_AUTOSIZE); cv::setMouseCallback("configGUI", onMouseEvent, &_input_data); + + /// If reconfiguring, then delete pre-computed values. + bool reconfig = false; + _cfg.getBool("reconfig", reconfig); /// Display/input loop. Mat R, t; @@ -442,10 +446,10 @@ bool ConfigGui::run() // test read cfg_pts.clear(); - if (_cfg.getVecDbl("roi_c", cfg_vec) && _cfg.getDbl("roi_r", r)) { + if (!reconfig && _cfg.getVecDbl("roi_c", cfg_vec) && _cfg.getDbl("roi_r", r)) { c.copy(cfg_vec.data()); LOG_DBG("Found roi_c = [%f %f %f] and roi_r = %f rad.", c[0], c[1], c[2], r); - LOG_WRN("Warning! When roi_c and roi_r are specified in the config file, roi_circ will be ignored.\nTo re-compute roi_c and roi_r, please delete these values from the config file and reconfigure."); + LOG_WRN("Warning! When roi_c and roi_r are specified in the config file, roi_circ will be ignored.\nTo re-compute roi_c and roi_r, please delete these values or set reconfig : y in the config file and reconfigure."); } else if (_cfg.getVecInt("roi_circ", cfg_pts)) { @@ -722,7 +726,7 @@ bool ConfigGui::run() case R_INIT: /// Load R+t transform from config file. cfg_vec.clear(); - if (_cfg.getVecDbl("c2a_r", cfg_vec)) { + if (!reconfig && _cfg.getVecDbl("c2a_r", cfg_vec)) { LOG_DBG("Read c2a_r = [%f %f %f]", cfg_vec[0], cfg_vec[1], cfg_vec[2]); R = CmPoint64f::omegaToMatrix(CmPoint(cfg_vec[0], cfg_vec[1], cfg_vec[2])); } @@ -733,7 +737,7 @@ bool ConfigGui::run() } cfg_vec.clear(); - if (_cfg.getVecDbl("c2a_t", cfg_vec)) { + if (!reconfig && _cfg.getVecDbl("c2a_t", cfg_vec)) { LOG_DBG("Read c2a_t = [%f %f %f]", cfg_vec[0], cfg_vec[1], cfg_vec[2]); t = (cv::Mat_(3, 1) << cfg_vec[0], cfg_vec[1], cfg_vec[2]); } @@ -743,7 +747,7 @@ bool ConfigGui::run() break; } - LOG_WRN("Warning! When c2a_r and c2a_t are specified in the config file, c2a_src and associated corners points will be ignored.\nTo re-compute c2a_r and c2a_t, please delete these values from the config file and reconfigure."); + LOG_WRN("Warning! When c2a_r and c2a_t are specified in the config file, c2a_src and associated corners points will be ignored.\nTo re-compute c2a_r and c2a_t, please delete these values or set reconfig : y in the config file and reconfigure."); /// Check also if corners specified (unnecessary). if (_cfg.getStr("c2a_src", cfg_r_src)) { diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 4b72b2a..3ea2c33 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -170,6 +170,8 @@ Trackball::Trackball(string cfg_fn) _map_w = 2 * _map_h; /// Load sphere config and mask. + bool reconfig = false; + _cfg.getBool("reconfig", reconfig); // ignore saved roi_c, roi_r, c2a_r, and c2a_t values and recompute from pixel coords - dangerous!! Mat src_mask(source->getHeight(), source->getWidth(), CV_8UC1); src_mask.setTo(Scalar::all(0)); { @@ -177,9 +179,9 @@ Trackball::Trackball(string cfg_fn) _sphere_rad = -1; vector circ_pxs; vector sphere_c; - if (_cfg.getVecDbl("roi_c", sphere_c) && _cfg.getDbl("roi_r", _sphere_rad)) { + if (!reconfig && _cfg.getVecDbl("roi_c", sphere_c) && _cfg.getDbl("roi_r", _sphere_rad)) { _sphere_c.copy(sphere_c.data()); - LOG("Found sphere ROI centred at [%f %f %f], with radius %f rad.", _sphere_c[0], _sphere_c[1], _sphere_c[2], _sphere_rad); + LOG_DBG("Found sphere ROI centred at [%f %f %f], with radius %f rad.", _sphere_c[0], _sphere_c[1], _sphere_c[2], _sphere_rad); } else if (_cfg.getVecInt("roi_circ", circ_pxs)) { vector circ_pts; @@ -189,7 +191,7 @@ Trackball::Trackball(string cfg_fn) // fit circular fov if ((circ_pts.size() >= 3) && circleFit_camModel(circ_pts, _src_model, _sphere_c, _sphere_rad)) { - LOG("Computed sphere ROI centred at [%f %f %f], with radius %f rad from %d roi_circ points.", + LOG_WRN("Warning! Re-computed sphere ROI centred at [%f %f %f], with radius %f rad from %d roi_circ points.", _sphere_c[0], _sphere_c[1], _sphere_c[2], _sphere_rad, circ_pts.size()); } } @@ -241,7 +243,7 @@ Trackball::Trackball(string cfg_fn) */ /// Create coordinate frame transformation matrices. - CmPoint64f roi_to_cam_r, cam_to_lab_r; + CmPoint64f roi_to_cam_r; { // ROI to cam transformation from sphere centre ray. CmPoint64f z(0, 0, 1); // forward in camera coords @@ -252,11 +254,30 @@ Trackball::Trackball(string cfg_fn) // Cam to lab transformation from configuration. vector c2a_r; - if (_cfg.getVecDbl("c2a_r", c2a_r) && (c2a_r.size() == 3)) { - cam_to_lab_r = CmPoint64f(c2a_r[0], c2a_r[1], c2a_r[2]); + string c2a_src; + vector c2a_pts; + if (!reconfig && _cfg.getVecDbl("c2a_r", c2a_r) && (c2a_r.size() == 3)) { + CmPoint64f cam_to_lab_r = CmPoint64f(c2a_r[0], c2a_r[1], c2a_r[2]); _cam_to_lab_R = CmPoint64f::omegaToMatrix(cam_to_lab_r); + LOG_DBG("Found C2A rotational transform: [%f %f %f].", cam_to_lab_r[0], cam_to_lab_r[1], cam_to_lab_r[2]); } - else { + else if (_cfg.getStr("c2a_src", c2a_src) && _cfg.getVecInt(c2a_src, c2a_pts)) { + // c2a source and pixel coords present - recompute transform + vector cnrs; + for (unsigned int i = 1; i < c2a_pts.size(); i += 2) { + cnrs.push_back(cv::Point2d(c2a_pts[i - 1], c2a_pts[i])); + } + Mat t; + if (computeRtFromSquare(_src_model, c2a_src.substr(c2a_src.size() - 2), cnrs, _cam_to_lab_R, t)) { + CmPoint64f cam_to_lab_r = CmPoint64f::matrixToOmega(_cam_to_lab_R); + LOG_WRN("Warning! Re-computed C2A rotational transform [%f %f %f] using %s.", cam_to_lab_r[0], cam_to_lab_r[1], cam_to_lab_r[2], c2a_src.c_str()); + } + else { + LOG_ERR("Error! Camera-to-lab coordinate tranformation specified in config file (c2a_r) is invalid!"); + _active = false; + return; + } + } else { LOG_ERR("Error! Camera-to-lab coordinate tranformation specified in config file (c2a_r) is invalid!"); _active = false; return; diff --git a/src/geometry.cpp b/src/geometry.cpp index bf7b8a1..6fadd4b 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -178,3 +178,21 @@ bool computeRtFromSquare_XZ(const CameraModelPtr cam_model, const vector& cnrs, Mat& R, Mat& t) +{ + bool ret = false; + if (ref_str == "xy") { + ret = computeRtFromSquare_XY(cam_model, cnrs, R, t); + } + else if (ref_str == "yz") { + ret = computeRtFromSquare_YZ(cam_model, cnrs, R, t); + } + else if (ref_str == "xz") { + ret = computeRtFromSquare_XZ(cam_model, cnrs, R, t); + } + return ret; +} \ No newline at end of file From fd0925586f16cc29140928ce87d5aaed5f85aee2 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 18 Mar 2019 21:45:50 +0100 Subject: [PATCH 051/235] PGR USB2 support --- CMakeLists.txt | 29 +++++++++--- include/PGRSource.h | 16 ++++--- src/PGRSource.cpp | 105 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 132 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 868ac7e..72adce4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,8 +22,11 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin) # optional build config option(PGR_USB3 "Use Spinnaker SDK to capture from PGR USB3 cameras" OFF) # Disabled by default +option(PGR_USB2 "Use FlyCapture SDK to capture from PGR USB2 cameras" OFF) # Disabled by default if(PGR_USB3) set(PGR_DIR "." CACHE PATH "Path to PGR Spinnaker SDK folder") +elseif(PGR_USB2) + set(PGR_DIR "." CACHE PATH "Path to PGR FlyCapture SDK folder") endif() # find dependencies @@ -35,6 +38,9 @@ if(MSVC) if(PGR_USB3) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}/lib64/vs2015) find_library(PGR_LIB Spinnaker_v140.lib) + elseif(PGR_USB2) + set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}/lib64/vs2015) + find_library(PGR_LIB FlyCapture2_v140.lib) endif() else() # gcc find_package(OpenCV) @@ -43,6 +49,9 @@ else() # gcc if(PGR_USB3) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}) find_library(PGR_LIB libSpinnaker.so) + elseif(PGR_USB2) + set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}) + find_library(PGR_LIB libflycapture.so) endif() endif() get_filename_component(NLOPT_DIR ${NLOPT_LIB} DIRECTORY) @@ -54,26 +63,30 @@ if(NLOPT_LIB) else() message(FATAL_ERROR "Error! Could not find NLopt lib at ${NLOPT_DIR}!") endif() -if(PGR_USB3) +if(PGR_USB2 OR PGR_USB3) get_filename_component(PGR_DIR ${PGR_LIB} DIRECTORY) get_filename_component(PGR_DIR ${PGR_DIR} DIRECTORY) # step up 1 level if(MSVC) get_filename_component(PGR_DIR ${PGR_DIR} DIRECTORY) # step up 1 level endif() if(PGR_LIB) - message(STATUS "Found PGR Spinnaker lib ${PGR_LIB}") + message(STATUS "Found PGR FlyCapture/Spinnaker lib ${PGR_LIB}") else() - message(FATAL_ERROR "Error! Could not find PGR Spinnaker lib at ${PGR_DIR}!") + message(FATAL_ERROR "Error! Could not find PGR FlyCapture/Spinnaker lib at ${PGR_DIR}!") endif() endif() # add include dirs include_directories(${PROJECT_SOURCE_DIR}/include ${OpenCV_INCLUDE_DIRS} ${NLOPT_DIR}) -if(PGR_USB3) +if(PGR_USB2 OR PGR_USB3) if(MSVC) include_directories(${PGR_DIR}/include) else() - include_directories(${PGR_DIR}/include/spinnaker) # for ubuntu default install dir + if(PGR_USB2) + include_directories(${PGR_DIR}/include/flycapture) # for ubuntu default install dir + elseif(PGR_USB3) + include_directories(${PGR_DIR}/include/spinnaker) # for ubuntu default install dir + endif() endif() endif() @@ -88,7 +101,9 @@ add_executable(fictrac ${PROJECT_SOURCE_DIR}/exec/fictrac.cpp) # add preprocessor definitions # public means defs will be inherited by linked executables target_compile_definitions(libfictrac PUBLIC _CRT_SECURE_NO_WARNINGS NOMINMAX) -if(PGR_USB3) +if(PGR_USB2) + target_compile_definitions(libfictrac PUBLIC PGR_USB2) +elseif(PGR_USB3) target_compile_definitions(libfictrac PUBLIC PGR_USB3) endif() @@ -101,7 +116,7 @@ endif() # linking and post-build target_link_libraries(libfictrac PUBLIC ${OpenCV_LIBS} ${NLOPT_LIB}) -if(PGR_USB3) +if(PGR_USB2 OR PGR_USB3) target_link_libraries(libfictrac PUBLIC ${PGR_LIB}) endif() if(MSVC) diff --git a/include/PGRSource.h b/include/PGRSource.h index 2932839..774bbc7 100644 --- a/include/PGRSource.h +++ b/include/PGRSource.h @@ -1,16 +1,20 @@ /// FicTrac http://rjdmoore.net/fictrac/ /// \file PGRSource.h -/// \brief PGR USB3 sources (Spinnaker SDK). +/// \brief PGR USB2/3 sources (FlyCapture/Spinnaker SDK). /// \author Richard Moore /// \copyright CC BY-NC-SA 3.0 #pragma once -#ifdef PGR_USB3 - #include "FrameSource.h" +#if defined(PGR_USB3) #include +#elif defined(PGR_USB2) +#include +#include +#endif // PGR_USB2/3 + #include class PGRSource : public FrameSource { @@ -24,9 +28,11 @@ class PGRSource : public FrameSource { virtual bool grab(cv::Mat& frame); private: +#if defined(PGR_USB3) Spinnaker::SystemPtr _system; Spinnaker::CameraList _camList; Spinnaker::CameraPtr _cam; +#elif defined(PGR_USB2) + std::shared_ptr _cam; +#endif // PGR_USB2/3 }; - -#endif diff --git a/src/PGRSource.cpp b/src/PGRSource.cpp index 7a64ede..60278a9 100644 --- a/src/PGRSource.cpp +++ b/src/PGRSource.cpp @@ -1,24 +1,27 @@ /// FicTrac http://rjdmoore.net/fictrac/ /// \file PGRSource.cpp -/// \brief PGR USB3 sources (Spinnaker SDK). +/// \brief PGR USB2/3 sources (FlyCapture/Spinnaker SDK). /// \author Richard Moore /// \copyright CC BY-NC-SA 3.0 -#ifdef PGR_USB3 - #include "PGRSource.h" #include "Logger.h" #include "timing.h" +#if defined(PGR_USB3) #include "SpinGenApi/SpinnakerGenApi.h" - using namespace Spinnaker; +#elif defined(PGR_USB2) +using namespace FlyCapture2; +#endif // PGR_USB2/3 + using cv::Mat; PGRSource::PGRSource(int index) { try { +#if defined(PGR_USB3) // Retrieve singleton reference to system object _system = System::GetInstance(); @@ -82,15 +85,64 @@ PGRSource::PGRSource(int index) _width = _cam->Width(); _height = _cam->Height(); _fps = getFPS(); +#elif defined(PGR_USB2) + LOG_DBG("Looking for camera at index %d...", index); + + BusManager busMgr; + PGRGuid guid; + Error error = busMgr.GetCameraFromIndex(index, &guid); + if (error != PGRERROR_OK) { + LOG_ERR("Error reading camera GUID!"); + return; + } + + _cam = std::make_shared(); + error = _cam->Connect(&guid); + if (error != PGRERROR_OK) { + LOG_ERR("Error connecting to camera!"); + return; + } + + CameraInfo camInfo; + error = _cam->GetCameraInfo(&camInfo); + if (error != PGRERROR_OK) { + LOG_ERR("Error retrieving camera information!"); + return; + } + else { + LOG_DBG("Connected to PGR camera (%s/%s max res: %s)", camInfo.modelName, camInfo.sensorInfo, camInfo.sensorResolution); + } + + error = _cam->StartCapture(); + if (error != PGRERROR_OK) { + LOG_ERR("Error starting video capture!"); + return; + } + + Image::SetDefaultColorProcessing(ColorProcessingAlgorithm::NEAREST_NEIGHBOR); + + // capture test image + Image testImg; + error = _cam->RetrieveBuffer(&testImg); + if (error != PGRERROR_OK) { + LOG_ERR("Error capturing image!"); + return; + } + _width = testImg.GetCols(); + _height = testImg.GetRows(); + _fps = getFPS(); +#endif // PGR_USB2/3 LOG("PGR camera initialised (%dx%d @ %.3f fps)!", _width, _height, _fps); _open = true; _live = true; } +#if defined(PGR_USB3) catch (Spinnaker::Exception& e) { LOG_ERR("Error opening capture device! Error was: %s", e.what()); } +#endif // PGR_USB3 catch (...) { LOG_ERR("Error opening capture device!"); } @@ -100,29 +152,44 @@ PGRSource::~PGRSource() { if (_open) { try { +#if defined(PGR_USB3) _cam->EndAcquisition(); +#elif defined(PGR_USB2) + _cam->StopCapture(); +#endif // PGR_USB2/3 } +#if defined(PGR_USB3) catch (Spinnaker::Exception& e) { LOG_ERR("Error ending acquisition! Error was: %s", e.what()); } +#endif // PGR_USB3 catch (...) { LOG_ERR("Error ending acquisition!"); } _open = false; } + +#if defined(PGR_USB2) + _cam->Disconnect(); +#endif // PGR_USB2 + _cam = NULL; +#if defined(PGR_USB3) // Clear camera list before releasing system _camList.Clear(); // Release system _system->ReleaseInstance(); +#endif // PGR_USB3 + } double PGRSource::getFPS() { double fps = _fps; if (_open) { +#if defined(PGR_USB3) try { fps = _cam->AcquisitionResultingFrameRate(); } @@ -132,6 +199,7 @@ double PGRSource::getFPS() catch (...) { LOG_ERR("Error retrieving camera frame rate!"); } +#endif // PGR_USB3 } return fps; } @@ -140,6 +208,7 @@ bool PGRSource::setFPS(double fps) { bool ret = false; if (_open && (fps > 0)) { +#if defined(PGR_USB3) try { _cam->AcquisitionFrameRateEnable.SetValue(true); _cam->AcquisitionFrameRate.SetValue(fps); @@ -150,6 +219,7 @@ bool PGRSource::setFPS(double fps) catch (...) { LOG_ERR("Error setting frame rate!"); } +#endif // PGR_USB3 _fps = getFPS(); LOG("Device frame rate is now %.2f", _fps); ret = true; @@ -161,6 +231,7 @@ bool PGRSource::grab(cv::Mat& frame) { if( !_open ) { return NULL; } +#if defined(PGR_USB3) ImagePtr pgr_image = NULL; try { @@ -214,6 +285,28 @@ bool PGRSource::grab(cv::Mat& frame) pgr_image->Release(); return false; } -} +#elif defined(PGR_USB2) + Image frame_raw; + Error error = _cam->RetrieveBuffer(&frame_raw); + double ts = static_cast(ts_ms()); // backup, in case the device timestamp is junks + if (error != PGRERROR_OK) { + LOG_ERR("Error grabbing image frame!"); + return false; + } + auto timestamp = frame_raw.GetTimeStamp(); + _timestamp = timestamp.seconds * 1e3 + timestamp.microSeconds / (double)1e3; + if (_timestamp <= 0) { + _timestamp = ts; + } -#endif // PGR_USB3 + Image frame_bgr; + error = frame_raw.Convert(PIXEL_FORMAT_BGR, &frame_bgr); + if (error != PGRERROR_OK) { + LOG_ERR("Error converting image format!"); + return false; + } + Mat frame_cv(frame_bgr.GetRows(), frame_bgr.GetCols(), CV_8UC3, frame_bgr.GetData(), frame_bgr.GetStride()); + frame_cv.copyTo(frame); + return true; +#endif // PGR_USB2/3 +} From 04462500e07545f9ff2cf5cf32bd3da3520808de Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 18 Mar 2019 21:46:30 +0100 Subject: [PATCH 052/235] version to 2.1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 72adce4..a900d61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ project (FicTrac) # The version number. set (FICTRAC_VERSION_MAJOR 2) -set (FICTRAC_VERSION_MINOR 0) +set (FICTRAC_VERSION_MINOR 1) # output version info to be included by project configure_file ( From 6ddfd574f6cb704b7e338de91007edcdc1115834 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 19 Mar 2019 08:49:05 +0100 Subject: [PATCH 053/235] PGR_USB2 support for executables --- src/ConfigGUI.cpp | 10 +++++----- src/Trackball.cpp | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index 91815ec..338d201 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -18,9 +18,9 @@ #include "timing.h" #include "misc.h" #include "CVSource.h" -#ifdef PGR_USB3 +#if defined(PGR_USB2) || defined(PGR_USB3) #include "PGRSource.h" -#endif // PGR_USB3 +#endif // PGR_USB2/3 /// OpenCV individual includes required by gcc? #include @@ -168,7 +168,7 @@ ConfigGui::ConfigGui(string config_fn) Mat input_frame; std::shared_ptr source; if (_open) { -#ifdef PGR_USB3 +#if defined(PGR_USB2) || defined(PGR_USB3) try { // first try reading input as camera id int id = std::stoi(input_fn); @@ -178,9 +178,9 @@ ConfigGui::ConfigGui(string config_fn) // then try loading as video file source = std::make_shared(input_fn); } -#else // !PGR_USB3 +#else // !PGR_USB2/3 source = std::make_shared(input_fn); -#endif // PGR_USB3 +#endif // PGR_USB2/3 if (!source->isOpen()) { LOG_ERR("Error! Could not open input frame source (%s)!", input_fn.c_str()); _open = false; diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 3ea2c33..a642d70 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -16,9 +16,9 @@ #include "BasicRemapper.h" #include "misc.h" #include "CVSource.h" -#ifdef PGR_USB3 +#if defined(PGR_USB2) || defined(PGR_USB3) #include "PGRSource.h" -#endif // PGR_USB3 +#endif // PGR_USB2/3 /// OpenCV individual includes required by gcc? #include @@ -112,7 +112,7 @@ Trackball::Trackball(string cfg_fn) /// Open frame source and set fps. string src_fn = _cfg("src_fn"); std::shared_ptr source; -#ifdef PGR_USB3 +#if defined(PGR_USB2) || defined(PGR_USB3) try { // first try reading input as camera id int id = std::stoi(src_fn); @@ -122,9 +122,9 @@ Trackball::Trackball(string cfg_fn) // then try loading as video file source = make_shared(src_fn); } -#else // !PGR_USB3 +#else // !PGR_USB2/3 source = std::make_shared(src_fn); -#endif // PGR_USB3 +#endif // PGR_USB2/3 if (!source->isOpen()) { LOG_ERR("Error! Could not open input frame source (%s)!", src_fn.c_str()); _active = false; From f522a81b3b89c12072fe5e4a1692b6405227868b Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 21 Mar 2019 14:15:56 +0100 Subject: [PATCH 054/235] fix bug that treated input video filenames starting with numerical values as camera ids --- src/ConfigGUI.cpp | 1 + src/Trackball.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index 338d201..ff61c44 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -170,6 +170,7 @@ ConfigGui::ConfigGui(string config_fn) if (_open) { #if defined(PGR_USB2) || defined(PGR_USB3) try { + if (input_fn.size() > 2) { throw std::exception(); } // first try reading input as camera id int id = std::stoi(input_fn); source = std::make_shared(id); diff --git a/src/Trackball.cpp b/src/Trackball.cpp index a642d70..5d6f95d 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -114,6 +114,7 @@ Trackball::Trackball(string cfg_fn) std::shared_ptr source; #if defined(PGR_USB2) || defined(PGR_USB3) try { + if (src_fn.size() > 2) { throw std::exception(); } // first try reading input as camera id int id = std::stoi(src_fn); source = make_shared(id); From d9faea9f8ab3a13659908139e0e1b68b34cc28eb Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 21 Mar 2019 14:16:25 +0100 Subject: [PATCH 055/235] fix bug that treated input video filenames starting with numerical values as camera ids --- src/CVSource.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CVSource.cpp b/src/CVSource.cpp index fd1d4a6..99f4a7a 100644 --- a/src/CVSource.cpp +++ b/src/CVSource.cpp @@ -30,6 +30,7 @@ CVSource::CVSource(std::string input) try { // try reading input as camera id LOG_DBG("Trying source as camera id.."); + if (input.size() > 2) { throw std::exception(); } int id = std::stoi(input); _cap = std::shared_ptr(new cv::VideoCapture(id)); if (!_cap->isOpened()) { throw 0; } From 5613206de3d32c69c8a52d0c6a9e2b7f2ced3b40 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 21 Mar 2019 14:16:46 +0100 Subject: [PATCH 056/235] warning suppression --- src/ConfigParser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 1d122c0..13a08c0 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -177,7 +177,7 @@ bool ConfigParser::getStr(string key, string& val) { val = _data[key]; return true; } - LOG_WRN("Warning! Key (%s) not found.", key.c_str()); + LOG_DBG("Key (%s) not found.", key.c_str()); return false; } From 0bc74591d797aa32ba228a57787316b7b40d3937 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 21 Mar 2019 14:24:54 +0100 Subject: [PATCH 057/235] remove pre-computed roi_c and roi_r from sample config --- sample/config.txt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sample/config.txt b/sample/config.txt index fa736fa..097a5ed 100644 --- a/sample/config.txt +++ b/sample/config.txt @@ -1,21 +1,19 @@ -## FicTrac config file (build Feb 27 2019) +## FicTrac config file (build Mar 21 2019) c2a_cnrs_xy : { 191, 171, 128, 272, 20, 212, 99, 132 } -c2a_r : { -0.722443, 0.131317, 0.460878 } +c2a_r : { 0.722445, -0.131314, -0.460878 } c2a_src : c2a_cnrs_xy -c2a_t : { -0.674395, 0.389373, 2.889647 } +c2a_t : { -0.674396, 0.389373, 2.889648 } do_display : y max_bad_frames : -1 opt_bound : 0.35 opt_do_global : n -opt_max_err : -1.000000 +opt_max_err : -1 opt_max_evals : 50 opt_tol : 0.001 out_port : -1 q_factor : 6 -roi_c : { -0.229390, 0.099969, 0.968187 } roi_circ : { 63, 171, 81, 145, 106, 135, 150, 160 } roi_ignr : { { 96, 156, 113, 147, 106, 128, 82, 130, 81, 150 }, { 71, 213, 90, 219, 114, 218, 135, 211, 154, 196, 150, 217, 121, 228, 99, 234, 75, 225 } } -roi_r : 0.124815 save_debug : n save_raw : n src_fn : sample.mp4 From f397604059a09e036b6a9b6277129c4d3ec785d4 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 21 Mar 2019 14:28:48 +0100 Subject: [PATCH 058/235] fix camera-lab coordinate transform bug; remove roi-camera transform for plotting; draw animal axes during config --- include/ConfigGui.h | 6 +- include/drawing.h | 3 + src/ConfigGUI.cpp | 189 ++++++++++++++++++++++++-------------------- src/Trackball.cpp | 32 +++----- src/drawing.cpp | 40 +++++++++- src/geometry.cpp | 11 +-- 6 files changed, 166 insertions(+), 115 deletions(-) diff --git a/include/ConfigGui.h b/include/ConfigGui.h index eccee43..098c5b0 100644 --- a/include/ConfigGui.h +++ b/include/ConfigGui.h @@ -80,11 +80,11 @@ class ConfigGui private: bool setFrame(cv::Mat& frame); - bool updateC2ATransform(const cv::Mat& ref_cnrs, cv::Mat& R, cv::Mat& t); + bool updateRt(const std::string& ref_str, cv::Mat& R, cv::Mat& t); //void drawC2ATransform(cv::Mat& disp_frame, const cv::Mat& ref_cnrs, const cv::Mat& R, const cv::Mat& t, const double& r, const CmPoint& c); void drawC2AAxes(cv::Mat& disp_frame, const cv::Mat& R, const cv::Mat& t, const double& r, const CmPoint& c); - void drawC2ACorners(cv::Mat& disp_frame, const cv::Mat& ref_cnrs, const cv::Mat& R, const cv::Mat& t); - bool saveC2ATransform(const cv::Mat& R, const cv::Mat& t); + void drawC2ACorners(cv::Mat& disp_frame, const std::string& ref_str, const cv::Mat& R, const cv::Mat& t); + bool saveC2ATransform(const std::string& ref_str, const cv::Mat& R, const cv::Mat& t); void changeState(INPUT_MODE new_state); diff --git a/include/drawing.h b/include/drawing.h index e18fecd..64359b1 100644 --- a/include/drawing.h +++ b/include/drawing.h @@ -30,6 +30,9 @@ void drawCursor(cv::Mat& rgb, const cv::Point2d& pt, cv::Scalar colour); /// Draw transformed axes. void drawAxes(cv::Mat& rgb, const CameraModelPtr cam_model, const cv::Mat& R, const cv::Mat& t, const cv::Scalar colour); +/// Draw animal axis. +void drawAnimalAxis(cv::Mat& rgb, const CameraModelPtr cam_model, const cv::Mat& R, const cv::Mat& t, const double r, const cv::Scalar colour); + /// Draw rect corners. void drawRectCorners(cv::Mat& rgb, const CameraModelPtr cam_model, cv::Mat& cnrs, const cv::Scalar colour); diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index ff61c44..6dadc1c 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -257,25 +257,10 @@ bool ConfigGui::setFrame(Mat& frame) /// /// Write camera-animal transform to config file. +/// Warning: input R+t is animal to camera frame transform! /// -bool ConfigGui::saveC2ATransform(const Mat& R, const Mat& t) +bool ConfigGui::saveC2ATransform(const string& ref_str, const Mat& R, const Mat& t) { - string sqr_type = ""; - switch (_input_data.mode) { - case R_XY: - sqr_type = "c2a_cnrs_xy"; - break; - case R_YZ: - sqr_type = "c2a_cnrs_yz"; - break; - case R_XZ: - sqr_type = "c2a_cnrs_xz"; - break; - default: - LOG_ERR("Uh oh, something went wrong :-("); - return false; - } - // dump corner points to config file vector cfg_pts; for (auto p : _input_data.sqrPts) { @@ -284,13 +269,13 @@ bool ConfigGui::saveC2ATransform(const Mat& R, const Mat& t) } // write to config file - LOG("Adding c2a_src and %s to config file and writing to disk (%s) ..", sqr_type.c_str(), _config_fn.c_str()); - _cfg.add("c2a_src", sqr_type); - _cfg.add(sqr_type, cfg_pts); + LOG("Adding c2a_src and %s to config file and writing to disk (%s) ..", ref_str.c_str(), _config_fn.c_str()); + _cfg.add("c2a_src", ref_str); + _cfg.add(ref_str, cfg_pts); // dump R to config file vector cfg_r, cfg_t; - CmPoint angleAxis = CmPoint64f::matrixToOmega(R); + CmPoint angleAxis = CmPoint64f::matrixToOmega(R.t()); // transpose to get camera-animal transform for (int i = 0; i < 3; i++) { cfg_r.push_back(angleAxis[i]); cfg_t.push_back(t.at(i, 0)); @@ -321,21 +306,25 @@ bool ConfigGui::saveC2ATransform(const Mat& R, const Mat& t) /// /// Update animal coordinate frame estimate. /// -bool ConfigGui::updateC2ATransform(const Mat& ref_cnrs, Mat& R, Mat& t) +bool ConfigGui::updateRt(const string& ref_str, Mat& R, Mat& t) { - bool ret = false; - if (_input_data.newEvent) { - //FIXME: also support edge clicks! e.g.: - // double x1 = click[2 * i + 0].x; double y1 = click[2 * i + 0].y; - // double x2 = click[2 * i + 1].x; double y2 = click[2 * i + 1].y; - // double x3 = click[2 * i + 2].x; double y3 = click[2 * i + 2].y; - // double x4 = click[2 * i + 3].x; double y4 = click[2 * i + 3].y; - // double px = ((x1*y2 - y1 * x2)*(x3 - x4) - (x1 - x2)*(x3*y4 - y3 * x4)) / ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4)); - // double py = ((x1*y2 - y1 * x2)*(y3 - y4) - (y1 - y2)*(x3*y4 - y3 * x4)) / ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4)); - ret = computeRtFromSquare(_cam_model, ref_cnrs, _input_data.sqrPts, R, t); - _input_data.newEvent = false; - } - return ret; + //FIXME: also support edge clicks! e.g.: + // double x1 = click[2 * i + 0].x; double y1 = click[2 * i + 0].y; + // double x2 = click[2 * i + 1].x; double y2 = click[2 * i + 1].y; + // double x3 = click[2 * i + 2].x; double y3 = click[2 * i + 2].y; + // double x4 = click[2 * i + 3].x; double y4 = click[2 * i + 3].y; + // double px = ((x1*y2 - y1 * x2)*(x3 - x4) - (x1 - x2)*(x3*y4 - y3 * x4)) / ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4)); + // double py = ((x1*y2 - y1 * x2)*(y3 - y4) - (y1 - y2)*(x3*y4 - y3 * x4)) / ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4)); + + bool ret = false; + if (ref_str == "c2a_cnrs_xy") { + ret = computeRtFromSquare(_cam_model, XY_CNRS, _input_data.sqrPts, R, t); + } else if (ref_str == "c2a_cnrs_yz") { + ret = computeRtFromSquare(_cam_model, YZ_CNRS, _input_data.sqrPts, R, t); + } else if (ref_str == "c2a_cnrs_xz") { + ret = computeRtFromSquare(_cam_model, XZ_CNRS, _input_data.sqrPts, R, t); + } + return ret; } ///// @@ -364,12 +353,23 @@ bool ConfigGui::updateC2ATransform(const Mat& ref_cnrs, Mat& R, Mat& t) /// /// /// -void ConfigGui::drawC2ACorners(Mat& disp_frame, const Mat& ref_cnrs, const Mat& R, const Mat& t) +void ConfigGui::drawC2ACorners(Mat& disp_frame, const string& ref_str, const Mat& R, const Mat& t) { // make x4 mat for projecting corners Mat T(3, 4, CV_64F); for (int i = 0; i < 4; i++) { t.copyTo(T.col(i)); } + Mat ref_cnrs; + if (ref_str == "c2a_cnrs_xy") { + ref_cnrs = XY_CNRS; + } else if (ref_str == "c2a_cnrs_yz") { + ref_cnrs = YZ_CNRS; + } else if (ref_str == "c2a_cnrs_xz") { + ref_cnrs = XZ_CNRS; + } else { + return; + } + // project reference corners Mat p = R * ref_cnrs + T; @@ -387,6 +387,7 @@ void ConfigGui::drawC2AAxes(Mat& disp_frame, const Mat& R, const Mat& t, const d double scale = 1.0 / tan(r); Mat so = (cv::Mat_(3, 1) << c.x, c.y, c.z) * scale; drawAxes(disp_frame, _cam_model, R, so, Scalar(0, 0, 255)); + drawAnimalAxis(disp_frame, _cam_model, R, so, r, Scalar(255, 0, 0)); } } @@ -419,7 +420,7 @@ bool ConfigGui::run() double r = -1; char key = 0; string val; - string cfg_r_src; + string c2a_src; vector cfg_pts; vector cfg_vec; vector> cfg_polys; @@ -725,61 +726,70 @@ bool ConfigGui::run() /// Choose method for defining animal frame. case R_INIT: + /// Check if corners specified (optional). + _input_data.sqrPts.clear(); + if (_cfg.getStr("c2a_src", c2a_src)) { + LOG_DBG("Found c2a_src: %s", c2a_src.c_str()); + + /// Load square corners from config file. + cfg_pts.clear(); + if (_cfg.getVecInt(c2a_src, cfg_pts)) { + for (unsigned int i = 1; i < cfg_pts.size(); i += 2) { + _input_data.sqrPts.push_back(cv::Point2d(cfg_pts[i - 1], cfg_pts[i])); + } + } + } + /// Load R+t transform from config file. + R.release(); // clear mat cfg_vec.clear(); if (!reconfig && _cfg.getVecDbl("c2a_r", cfg_vec)) { LOG_DBG("Read c2a_r = [%f %f %f]", cfg_vec[0], cfg_vec[1], cfg_vec[2]); - R = CmPoint64f::omegaToMatrix(CmPoint(cfg_vec[0], cfg_vec[1], cfg_vec[2])); + R = CmPoint64f::omegaToMatrix(CmPoint(cfg_vec[0], cfg_vec[1], cfg_vec[2])).t(); // transpose to lab-camera transform } else { - LOG_DBG("Error reading c2a_r from config file! Re-running configuration .."); - changeState(R_SLCT); - break; + LOG_WRN("Warning! c2a_r missing from config file. Looking for corner points.."); } + t.release(); // clear mat cfg_vec.clear(); if (!reconfig && _cfg.getVecDbl("c2a_t", cfg_vec)) { LOG_DBG("Read c2a_t = [%f %f %f]", cfg_vec[0], cfg_vec[1], cfg_vec[2]); t = (cv::Mat_(3, 1) << cfg_vec[0], cfg_vec[1], cfg_vec[2]); } else { - LOG_DBG("Error reading c2a_t from config file! Re-running configuration .."); - changeState(R_SLCT); - break; + LOG_WRN("Warning! c2a_t missing from config file. Looking for corner points.."); } - LOG_WRN("Warning! When c2a_r and c2a_t are specified in the config file, c2a_src and associated corners points will be ignored.\nTo re-compute c2a_r and c2a_t, please delete these values or set reconfig : y in the config file and reconfigure."); + if (R.empty() || t.empty()) { + if (!_input_data.sqrPts.empty()) { + LOG_DBG("Recomputing R+t from specified corner points..."); - /// Check also if corners specified (unnecessary). - if (_cfg.getStr("c2a_src", cfg_r_src)) { - LOG_DBG("Found c2a_src: %s", cfg_r_src.c_str()); - - /// Load square corners from config file. - cfg_pts.clear(); - if (_cfg.getVecInt(cfg_r_src, cfg_pts)) { - _input_data.sqrPts.clear(); - for (unsigned int i = 1; i < cfg_pts.size(); i += 2) { - _input_data.sqrPts.push_back(cv::Point2d(cfg_pts[i - 1], cfg_pts[i])); + /// Recompute R+t + if (updateRt(c2a_src, R, t)) { + saveC2ATransform(c2a_src, R, t); } + } + } + else { + LOG_WRN("Warning! When c2a_r and c2a_t are specified in the config file, c2a_src and associated corners points will be ignored.\nTo re-compute c2a_r and c2a_t, please delete these values or set reconfig : y in the config file and reconfigure."); + } - /// Draw previous clicks. - for (auto click : _input_data.sqrPts) { - cv::circle(disp_frame, click, click_rad, Scalar(255, 255, 0), 1, cv::LINE_AA); - } + /// If c2a_r/t missing and couldn't re-compute from specified corners points. + if (R.empty() || t.empty()) { + LOG_ERR("Error! Could not read or compute c2a_r and/or c2a_t. Re-running configuration.."); + changeState(R_SLCT); + break; + } - /// Draw reference corners. - if (cfg_r_src == "c2a_cnrs_xy") { - drawC2ACorners(disp_frame, XY_CNRS, R, t); - } - else if (cfg_r_src == "c2a_cnrs_yz") { - drawC2ACorners(disp_frame, YZ_CNRS, R, t); - } - else if (cfg_r_src == "c2a_cnrs_xz") { - drawC2ACorners(disp_frame, XZ_CNRS, R, t); - } - } + /// Draw previous clicks. + for (auto click : _input_data.sqrPts) { + cv::circle(disp_frame, click, click_rad, Scalar(255, 255, 0), 1, cv::LINE_AA); } + /// Draw reference corners. + drawC2ACorners(disp_frame, c2a_src, R, t); + /// Draw axes. drawC2AAxes(disp_frame, R, t, r, c); @@ -851,18 +861,21 @@ bool ConfigGui::run() { case 1: printf("\n\n\n XY-square method.\n\n Please click on the four corners of a square shape that is aligned with the animal's X-Y axes. The corners must be clicked in the following order: (+X,-Y), (+X,+Y), (-X,+Y), (-X,-Y). If your camera is looking down on the animal from above, then the four corners are (in order): TL, TR, BR, BL from the camera's perspective. If your camera is below the animal, then the order is TR, TL, BL, BR.\n\n Make sure the displayed axis is the correct right-handed coordinate frame!!\n\n You can hold F to mirror the axis if the handedness is incorrect.\n\n Press ENTER when you are satisfied with the animal's axis, or press ESC to exit..\n\n"); + c2a_src = "c2a_cnrs_xy"; // advance state changeState(R_XY); break; case 2: printf("\n\n\n YZ-square method.\n\n Please click on the four corners of a square shape that is aligned with the animal's Y-Z axes. The corners must be clicked in the following order: (-Y,-Z), (+Y,-Z), (+Y,+Z), (-Y,+Z). If your camera is behind the animal, then the four corners are (in order): TL, TR, BR, BL from the camera's perspective. If your camera is in front of the animal, then the order is TR, TL, BL, BR.\n\n Make sure the displayed axis is the correct right-handed coordinate frame!!\n\n You can hold F to mirror the axis if the handedness is incorrect.\n\n Press ENTER when you are satisfied with the animal's axis, or press ESC to exit..\n\n"); + c2a_src = "c2a_cnrs_yz"; // advance state changeState(R_YZ); break; case 3: printf("\n\n\n XZ-square method.\n\n Please click on the four corners of a square shape that is aligned with the animal's X-Z axes. The corners must be clicked in the following order: (+X,-Z), (-X,-Z), (-X,+Z), (+X,+Z). If your camera is to the animal's left side, then the four corners are (in order): TL, TR, BR, BL from the camera's perspective. If your camera is to the animal's right side, then the order is TR, TL, BL, BR.\n\n Make sure the displayed axis is the correct right-handed coordinate frame!!\n\n You can hold F to mirror the axis if the handedness is incorrect.\n\n Press ENTER when you are satisfied with the animal's axis, or press ESC to exit..\n\n"); + c2a_src = "c2a_cnrs_xz"; // advance state changeState(R_XZ); break; @@ -874,6 +887,7 @@ bool ConfigGui::run() // break; case 5: + c2a_src = "ext"; // advance state changeState(R_EXT); break; @@ -897,8 +911,11 @@ bool ConfigGui::run() /// Draw axes. if (_input_data.sqrPts.size() == 4) { - updateC2ATransform(XY_CNRS, R, t); - drawC2ACorners(disp_frame, XY_CNRS, R, t); + if (_input_data.newEvent) { + updateRt(c2a_src, R, t); + _input_data.newEvent = false; + } + drawC2ACorners(disp_frame, c2a_src, R, t); drawC2AAxes(disp_frame, R, t, r, c); } @@ -917,7 +934,7 @@ bool ConfigGui::run() if (key == enter_key) { if ((_input_data.sqrPts.size() == 4) && !R.empty()) { // dump corner points to config file - if (!saveC2ATransform(R, t)) { + if (!saveC2ATransform(c2a_src, R, t)) { LOG_ERR("Error writing coordinate transform to config file!"); _open = false; // will cause exit } @@ -947,8 +964,11 @@ bool ConfigGui::run() /// Draw axes. if (_input_data.sqrPts.size() == 4) { - updateC2ATransform(YZ_CNRS, R, t); - drawC2ACorners(disp_frame, YZ_CNRS, R, t); + if (_input_data.newEvent) { + updateRt(c2a_src, R, t); + _input_data.newEvent = false; + } + drawC2ACorners(disp_frame, c2a_src, R, t); drawC2AAxes(disp_frame, R, t, r, c); } @@ -967,7 +987,7 @@ bool ConfigGui::run() if (key == enter_key) { if ((_input_data.sqrPts.size() == 4) && !R.empty()) { // dump corner points to config file - if (!saveC2ATransform(R, t)) { + if (!saveC2ATransform(c2a_src, R, t)) { LOG_ERR("Error writing coordinate transform to config file!"); _open = false; // will cause exit } @@ -997,8 +1017,11 @@ bool ConfigGui::run() /// Draw axes. if (_input_data.sqrPts.size() == 4) { - updateC2ATransform(XZ_CNRS, R, t); - drawC2ACorners(disp_frame, XZ_CNRS, R, t); + if (_input_data.newEvent) { + updateRt(c2a_src, R, t); + _input_data.newEvent = false; + } + drawC2ACorners(disp_frame, c2a_src, R, t); drawC2AAxes(disp_frame, R, t, r, c); } @@ -1017,7 +1040,7 @@ bool ConfigGui::run() if (key == enter_key) { if ((_input_data.sqrPts.size() == 4) && !R.empty()) { // dump corner points to config file - if (!saveC2ATransform(R, t)) { + if (!saveC2ATransform(c2a_src, R, t)) { LOG_ERR("Error writing coordinate transform to config file!"); _open = false; // will cause exit } @@ -1113,13 +1136,7 @@ bool ConfigGui::run() // draw animal coordinate frame if (_input_data.sqrPts.size() == 4) { - if (cfg_r_src == "c2a_cnrs_xy") { - drawC2ACorners(disp_frame, XY_CNRS, R, t); - } else if (cfg_r_src == "c2a_cnrs_yz") { - drawC2ACorners(disp_frame, YZ_CNRS, R, t); - } else if (cfg_r_src == "c2a_cnrs_xz") { - drawC2ACorners(disp_frame, XZ_CNRS, R, t); - } + drawC2ACorners(disp_frame, c2a_src, R, t); } drawC2AAxes(disp_frame, R, t, r, c); diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 5d6f95d..a18f6d2 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -221,7 +221,7 @@ Trackball::Trackball(string cfg_fn) cv::fillPoly(src_mask, ignr_polys_pts, CV_RGB(0, 0, 0)); } else { - LOG_WRN("Warning! No valid mask ignore regions specified in config file (roi_ignr)!"); + LOG_DBG("No valid mask ignore regions specified in config file (roi_ignr)!"); } /// Sphere config read successfully. @@ -234,22 +234,13 @@ Trackball::Trackball(string cfg_fn) } } - /* - * The model sphere has it's own coordinate system, because we arbitrarily set - * the view vector corresponding to the centre of the projection of the tracking - * ball to be the principal axis of the virtual camera (cam_model). - * - * All incoming and outgoing vectors and matrices must thus be transposed to/from - * this coordinate system: lab <= (_cam_to_lab_R) => cam <= (_roi_to_cam_R) => roi. - */ - /// Create coordinate frame transformation matrices. CmPoint64f roi_to_cam_r; { // ROI to cam transformation from sphere centre ray. CmPoint64f z(0, 0, 1); // forward in camera coords roi_to_cam_r = _sphere_c.getRotationTo(z); // find axis-angle to rotate sphere centre to camera centre. - _roi_to_cam_R = CmPoint64f::omegaToMatrix(roi_to_cam_r); + /*_roi_to_cam_R = CmPoint64f::omegaToMatrix(roi_to_cam_r);*/ LOG_DBG("roi_to_cam_r: %.4f %.4f %.4f", roi_to_cam_r[0], roi_to_cam_r[1], roi_to_cam_r[2]); @@ -270,6 +261,7 @@ Trackball::Trackball(string cfg_fn) } Mat t; if (computeRtFromSquare(_src_model, c2a_src.substr(c2a_src.size() - 2), cnrs, _cam_to_lab_R, t)) { + _cam_to_lab_R = _cam_to_lab_R.t(); // transpose to convert to camera-lab transform CmPoint64f cam_to_lab_r = CmPoint64f::matrixToOmega(_cam_to_lab_R); LOG_WRN("Warning! Re-computed C2A rotational transform [%f %f %f] using %s.", cam_to_lab_r[0], cam_to_lab_r[1], cam_to_lab_r[2], c2a_src.c_str()); } @@ -360,7 +352,7 @@ Trackball::Trackball(string cfg_fn) _global_search = OPT_GLOBAL_SEARCH_DEFAULT; if (!_cfg.getBool("opt_do_global", _global_search)) { LOG_WRN("Warning! Using default value for opt_do_global (%d).", _global_search); - _cfg.add("opt_do_global", _global_search); + _cfg.add("opt_do_global", _global_search ? "y" : "n"); } _max_bad_frames = OPT_MAX_BAD_FRAMES_DEFAULT; if (!_cfg.getInt("max_bad_frames", _max_bad_frames)) { @@ -422,17 +414,17 @@ Trackball::Trackball(string cfg_fn) _do_display = DO_DISPLAY_DEFAULT; if (!_cfg.getBool("do_display", _do_display)) { LOG_WRN("Warning! Using default value for do_display (%d).", _do_display); - _cfg.add("do_display", _do_display); + _cfg.add("do_display", _do_display ? "y" : "n"); } _save_raw = SAVE_RAW_DEFAULT; if (!source->isLive() || !_cfg.getBool("save_raw", _save_raw)) { LOG_WRN("Warning! Using default value for save_raw (%d).", _save_raw); - _cfg.add("save_raw", _save_raw); + _cfg.add("save_raw", _save_raw ? "y" : "n"); } _save_debug = SAVE_DEBUG_DEFAULT; if (!_cfg.getBool("save_debug", _save_debug)) { LOG_WRN("Warning! Using default value for save_debug (%d).", _save_debug); - _cfg.add("save_debug", _save_debug); + _cfg.add("save_debug", _save_debug ? "y" : "n"); } if (_save_debug & !_do_display) { LOG("Forcing do_display = true, becase save_debug == true."); @@ -848,10 +840,10 @@ void Trackball::updatePath() _r_roi = CmPoint64f::matrixToOmega(_R_roi); // rel vec cam - _dr_cam = _dr_roi.getTransformed(_roi_to_cam_R); + _dr_cam = _dr_roi/*.getTransformed(_roi_to_cam_R)*/; // abs mat cam - _R_cam = _roi_to_cam_R * _R_roi; + _R_cam = /*_roi_to_cam_R * */_R_roi; // abs vec cam _r_cam = CmPoint64f::matrixToOmega(_R_cam); @@ -1310,14 +1302,14 @@ void Trackball::drawCanvas(std::shared_ptr data) /// Draw sphere orientation history (animal position history on sphere). { static const CmPoint up(0, 0, -1.0); - static CmPoint up_roi = up.getTransformed(_roi_to_cam_R.t() * _cam_to_lab_R.t()).getNormalised() * _r_d_ratio; + CmPoint up_roi = up.getTransformed(/*_roi_to_cam_R.t() * */_cam_to_lab_R.t()).getNormalised() * _r_d_ratio; double ppx = -1, ppy = -1; - draw_camera->vectorToPixelIndex(up_roi, ppx, ppy); + draw_camera->vectorToPixelIndex(up_roi, ppx, ppy); // don't need to correct for roi2cam R because origin is implicitly centre of draw_camera image anyway for (int i = R_roi_hist.size() - 1; i >= 0; i--) { // multiply by transpose - see Localiser::testRotation() CmPoint vec = up_roi.getTransformed(R_roi * R_roi_hist[i].t()).getNormalised() * _r_d_ratio; - + // sphere is centred at (0,0,1) cam coords, with r double px = -1, py = -1; diff --git a/src/drawing.cpp b/src/drawing.cpp index fa8d8b6..8799fcf 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -144,7 +144,7 @@ void drawAxes(Mat& rgb, const CameraModelPtr cam_model, const Mat& R, const Mat& Mat sy = R * (cv::Mat_(3,1) << 0,1,0) + t; Mat sz = R * (cv::Mat_(3,1) << 0,0,1) + t; - /// Draw transformed axes. + /// Find axes origin. double vec[3]; Point2d pt, pt0; vec[0] = t.at(0,0); @@ -210,6 +210,44 @@ void drawAxes(Mat& rgb, const CameraModelPtr cam_model, const Mat& R, const Mat& } } +/// +/// Draw animal axis. +/// +void drawAnimalAxis(Mat& rgb, const CameraModelPtr cam_model, const Mat& R, const Mat& t, const double r, const cv::Scalar colour) +{ + /// Transformed axes. + Mat sx = R * 0.5 * (cv::Mat_(3, 1) << 1, 0, 0); + Mat sx1 = R * 0.45 * (cv::Mat_(3, 1) << 1, 0, 0); + Mat sx2 = R * 0.45 * (cv::Mat_(3, 1) << 1, 0, 0); + Mat sz = R * -1.0 * (cv::Mat_(3, 1) << 0, 0, 1) + t; + + ///// Find axes origin. + //double vec[3]; + //vec[0] = t.at(0, 0); + //vec[1] = t.at(1, 0); + //vec[2] = t.at(2, 0); + //Point2d o; + //cam_model->vectorToPixel(vec, o.x, o.y); + + // up + double vec[3]; + vec[0] = sz.at(0, 0); + vec[1] = sz.at(1, 0); + vec[2] = sz.at(2, 0); + Point2d up; + cam_model->vectorToPixel(vec, up.x, up.y); + + // fwd + vec[0] = sx.at(0, 0) + sz.at(0, 0); + vec[1] = sx.at(1, 0) + sz.at(1, 0); + vec[2] = sx.at(2, 0) + sz.at(2, 0); + Point2d fwd; + cam_model->vectorToPixel(vec, fwd.x, fwd.y); + + cv::line(rgb, 4 * up, 4 * fwd, colour, 2, cv::LINE_AA, 2); + cv::circle(rgb, 4 * up, 4 * 2, colour, 2, cv::LINE_AA, 2); +} + /// /// Draw rectangle corners. /// diff --git a/src/geometry.cpp b/src/geometry.cpp index 6fadd4b..9e68f1c 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -102,7 +102,8 @@ bool circleFit_camModel(const vector& pix2d, const CameraModelPtr cam_m } /// -/// Compute camera-animal R+t transform from supplied square corners. +/// Compute animal-camera R+t transform from supplied square corners. +/// R is animal frame to camera frame transform. /// bool computeRtFromSquare(const CameraModelPtr cam_model, const Mat& ref_cnrs, const vector& cnrs, Mat& R, Mat& t) { @@ -153,7 +154,7 @@ bool computeRtFromSquare(const CameraModelPtr cam_model, const Mat& ref_cnrs, co } /// -/// Wrapper for computing camera-animal R+t transform from XY square. +/// Wrapper for computing animal-camera R+t transform from XY square. /// Square normal = animal Z axis. Corner ordering is TL (+X,-Y), TR (+X,+Y), BR (-X,+Y), BL (-X,-Y). /// bool computeRtFromSquare_XY(const CameraModelPtr cam_model, const vector& cnrs, Mat& R, Mat& t) @@ -162,7 +163,7 @@ bool computeRtFromSquare_XY(const CameraModelPtr cam_model, const vector& cnrs, Mat& R, Mat& t) @@ -171,7 +172,7 @@ bool computeRtFromSquare_YZ(const CameraModelPtr cam_model, const vector& cnrs, Mat& R, Mat& t) @@ -180,7 +181,7 @@ bool computeRtFromSquare_XZ(const CameraModelPtr cam_model, const vector& cnrs, Mat& R, Mat& t) { From e71529b8b30eb47122e73049711aa0d4098f8901 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 21 Mar 2019 14:29:15 +0100 Subject: [PATCH 059/235] fix drawing bug for path on sphere --- src/Trackball.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Trackball.cpp b/src/Trackball.cpp index a18f6d2..46d2b19 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -1319,8 +1319,8 @@ void Trackball::drawCanvas(std::shared_ptr data) draw_camera->vectorToPixelIndex(vec, px, py); // draw link - if ((ppx >= 0) && (ppy >= 0) && (px >= 0) && (py >= 0) && (ppx < draw_input.cols) && (ppy > draw_input.rows) && (px < draw_input.cols) && (py < draw_input.rows)) { - float mix = (i + 0.5f) / static_cast(R_roi_hist.size()); + if ((ppx >= 0) && (ppy >= 0) && (px >= 0) && (py >= 0) && (ppx < draw_input.cols) && (ppy < draw_input.rows) && (px < draw_input.cols) && (py < draw_input.rows)) { + float mix = 0.33f + 0.67f * (i + 0.5f) / static_cast(R_roi_hist.size()); cv::Vec3b rgb = draw_input.at(static_cast((ppy + py) / 2.f), static_cast((ppx + px) / 2.f)); // px/py are pixel index values int b = static_cast((1 - mix) * rgb[0] + mix * 255.f + 0.5f); int g = static_cast((1 - mix) * rgb[1] + mix * 255.f + 0.5f); From 8bb3d3c294ff063f9236fb39bd688a4f5b4c4a7b Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 29 Mar 2019 09:27:16 +0100 Subject: [PATCH 060/235] fix build error when not building PGR_USB2/3 --- include/PGRSource.h | 4 ++++ src/PGRSource.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/include/PGRSource.h b/include/PGRSource.h index 774bbc7..d76311a 100644 --- a/include/PGRSource.h +++ b/include/PGRSource.h @@ -4,6 +4,8 @@ /// \author Richard Moore /// \copyright CC BY-NC-SA 3.0 +#if defined(PGR_USB2) || defined(PGR_USB3) + #pragma once #include "FrameSource.h" @@ -36,3 +38,5 @@ class PGRSource : public FrameSource { std::shared_ptr _cam; #endif // PGR_USB2/3 }; + +#endif // PGR_USB2/3 diff --git a/src/PGRSource.cpp b/src/PGRSource.cpp index 60278a9..52f97ff 100644 --- a/src/PGRSource.cpp +++ b/src/PGRSource.cpp @@ -4,6 +4,8 @@ /// \author Richard Moore /// \copyright CC BY-NC-SA 3.0 +#if defined(PGR_USB2) || defined(PGR_USB3) + #include "PGRSource.h" #include "Logger.h" @@ -310,3 +312,5 @@ bool PGRSource::grab(cv::Mat& frame) return true; #endif // PGR_USB2/3 } + +#endif // PGR_USB2/3 From c39235d997ed02565c6684d3e4da510700bb4baa Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 29 Mar 2019 09:52:23 +0100 Subject: [PATCH 061/235] dynamic linking for nlopt on windows --- CMakeLists.txt | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a900d61..88cea37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ configure_file ( # dependency search dirs set(OPENCV_DIR "." CACHE PATH "Path to OpenCV folder containing OpenCVConfig.cmake") -set(NLOPT_DIR "." CACHE PATH "Path to NLopt folder containing libnlopt-0.lib") +set(NLOPT_DIR "." CACHE PATH "Path to NLopt folder containing libnlopt-0.dll") # output dirs set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib) @@ -33,7 +33,8 @@ endif() set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${OPENCV_DIR} ${NLOPT_DIR}) if(MSVC) find_package(opencv) - find_library(NLOPT_LIB libnlopt-0.lib) + # find_library(NLOPT_LIB libnlopt-0.lib) + find_file(NLOPT_LIB libnlopt-0.dll) if(PGR_USB3) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}/lib64/vs2015) @@ -110,19 +111,25 @@ endif() # add compile options if(MSVC) target_compile_options(libfictrac PUBLIC $<$:/MP /GS /GL /W3 /WX- /Gy /Zc:wchar_t /O2 /Oi /Zc:inline /fp:precise /MD /EHsc>) + # set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") else() # gcc target_compile_options(libfictrac PUBLIC -Ofast -Wall -c -fmessage-length=0 -std=c++14 -Wno-unused-function -march=native -MMD) endif() -# linking and post-build -target_link_libraries(libfictrac PUBLIC ${OpenCV_LIBS} ${NLOPT_LIB}) +# linking +target_link_libraries(libfictrac PUBLIC ${OpenCV_LIBS}) +if(MSVC) + target_link_libraries(libfictrac PUBLIC Ws2_32) +else() # gcc + target_link_libraries(libfictrac PUBLIC ${NLOPT_LIB} pthread) +endif() if(PGR_USB2 OR PGR_USB3) target_link_libraries(libfictrac PUBLIC ${PGR_LIB}) endif() + + +# post-build if(MSVC) - # win-specific libraries - target_link_libraries(libfictrac PUBLIC Ws2_32) - # copy all opencv dlls set(OPENCV_VER_STRING ${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}) foreach(lib ${OpenCV_LIBS}) @@ -151,8 +158,7 @@ if(MSVC) ${TO_COPY} "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<$:Release>$<$:Debug>/") else() # gcc - # socket libs, pthread - target_link_libraries(libfictrac PUBLIC pthread) + # nothing here... endif() target_link_libraries(configGui libfictrac) From aafd2baab85b2922c8b018b68829d47e2965cfee Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 29 Mar 2019 20:55:20 +0100 Subject: [PATCH 062/235] revert dynamic linking for nlopt on windows --- CMakeLists.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 88cea37..64d41b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,8 +33,8 @@ endif() set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${OPENCV_DIR} ${NLOPT_DIR}) if(MSVC) find_package(opencv) - # find_library(NLOPT_LIB libnlopt-0.lib) - find_file(NLOPT_LIB libnlopt-0.dll) + find_library(NLOPT_LIB nlopt.lib) + # find_file(NLOPT_LIB libnlopt-0.dll) if(PGR_USB3) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}/lib64/vs2015) @@ -117,17 +117,16 @@ else() # gcc endif() # linking -target_link_libraries(libfictrac PUBLIC ${OpenCV_LIBS}) +target_link_libraries(libfictrac PUBLIC ${OpenCV_LIBS} ${NLOPT_LIB}) if(MSVC) target_link_libraries(libfictrac PUBLIC Ws2_32) else() # gcc - target_link_libraries(libfictrac PUBLIC ${NLOPT_LIB} pthread) + target_link_libraries(libfictrac PUBLIC pthread) endif() if(PGR_USB2 OR PGR_USB3) target_link_libraries(libfictrac PUBLIC ${PGR_LIB}) endif() - # post-build if(MSVC) # copy all opencv dlls From 76ae2507cf59c4aaef3e222474f8ae8deb925203 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 29 Mar 2019 21:30:13 +0100 Subject: [PATCH 063/235] revert dynamic linking for nlopt on windows --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 64d41b7..0916f7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ endif() set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${OPENCV_DIR} ${NLOPT_DIR}) if(MSVC) find_package(opencv) - find_library(NLOPT_LIB nlopt.lib) + find_library(NLOPT_LIB libnlopt-0.lib) # find_file(NLOPT_LIB libnlopt-0.dll) if(PGR_USB3) From 3f58771758d19325cfdab691396e4bd2f9c22d88 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 29 Mar 2019 22:30:21 +0100 Subject: [PATCH 064/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a51782..29f7e80 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ The FicTrac source code can be built for both Windows and Ubuntu (Linux) operati 1. Download and install required dependencies: 1. [Cmake build system](https://cmake.org/download/) (binary distribution) 2. [OpenCV computer vision library](https://opencv.org/releases.html) (version 4.0.1 Win pack) - 3. [NLopt optimisation library](https://nlopt.readthedocs.io/en/latest/NLopt_on_Windows/) (precompiled DLL) + 3. [NLopt optimisation library](https://www.dropbox.com/s/z0rjd7ksbf17fjd/nlopt-2.4.2-x64.zip?dl=0) (Just extract the zip folder somewhere. If you need the 32-bit version you can download the precompiled DLL from [here](https://nlopt.readthedocs.io/en/latest/NLopt_on_Windows/) and follow directions to create the LIB) 2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: ``` mkdir build From 3b246ebb6d5818d8e06d3326edebb475fd9145c6 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 29 Mar 2019 22:31:32 +0100 Subject: [PATCH 065/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 29f7e80..409d34c 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ The FicTrac source code can be built for both Windows and Ubuntu (Linux) operati 1. Download and install required dependencies: 1. [Cmake build system](https://cmake.org/download/) (binary distribution) 2. [OpenCV computer vision library](https://opencv.org/releases.html) (version 4.0.1 Win pack) - 3. [NLopt optimisation library](https://www.dropbox.com/s/z0rjd7ksbf17fjd/nlopt-2.4.2-x64.zip?dl=0) (Just extract the zip folder somewhere. If you need the 32-bit version you can download the precompiled DLL from [here](https://nlopt.readthedocs.io/en/latest/NLopt_on_Windows/) and follow directions to create the LIB) + 3. [NLopt optimisation library](https://www.dropbox.com/s/z0rjd7ksbf17fjd/nlopt-2.4.2-x64.zip?dl=1) (Just extract the zip folder somewhere. If you need the 32-bit version you can download the precompiled DLL from [here](https://nlopt.readthedocs.io/en/latest/NLopt_on_Windows/) and follow directions to create the LIB) 2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: ``` mkdir build From abc07da160b64ead4a2919608e676698ea2b94eb Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 29 Mar 2019 23:34:30 +0100 Subject: [PATCH 066/235] more useful error msg for cmake --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0916f7b..7967818 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,6 @@ set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${OPENCV_DIR} ${NLOPT_DIR}) if(MSVC) find_package(opencv) find_library(NLOPT_LIB libnlopt-0.lib) - # find_file(NLOPT_LIB libnlopt-0.dll) if(PGR_USB3) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}/lib64/vs2015) @@ -62,7 +61,7 @@ if(NLOPT_LIB) message(STATUS "You might need to add ${NLOPT_DIR} to your PATH to be able to run the executable.") endif() else() - message(FATAL_ERROR "Error! Could not find NLopt lib at ${NLOPT_DIR}!") + message(FATAL_ERROR "Error! Could not find NLopt lib at ${NLOPT_LIB}!") endif() if(PGR_USB2 OR PGR_USB3) get_filename_component(PGR_DIR ${PGR_LIB} DIRECTORY) From 1d9716449a5564f59ffbc28b4ce0b8d39f542579 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 29 Mar 2019 23:48:56 +0100 Subject: [PATCH 067/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 409d34c..e537367 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ The FicTrac source code can be built for both Windows and Ubuntu (Linux) operati 1. Download and install required dependencies: 1. [Cmake build system](https://cmake.org/download/) (binary distribution) 2. [OpenCV computer vision library](https://opencv.org/releases.html) (version 4.0.1 Win pack) - 3. [NLopt optimisation library](https://www.dropbox.com/s/z0rjd7ksbf17fjd/nlopt-2.4.2-x64.zip?dl=1) (Just extract the zip folder somewhere. If you need the 32-bit version you can download the precompiled DLL from [here](https://nlopt.readthedocs.io/en/latest/NLopt_on_Windows/) and follow directions to create the LIB) + 3. [NLopt optimisation library](https://www.dropbox.com/s/z0rjd7ksbf17fjd/nlopt-2.4.2-x64.zip?dl=1) (Just extract the zip folder somewhere. If you need the 32-bit version you can download the precompiled DLL from [here](https://nlopt.readthedocs.io/en/latest/NLopt_on_Windows/) and follow directions to create the import library) 2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: ``` mkdir build From 76693ab68e7e921b82c0393698131e9a31613665 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sat, 30 Mar 2019 00:04:21 +0100 Subject: [PATCH 068/235] comment --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7967818..6e2a468 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ configure_file ( # dependency search dirs set(OPENCV_DIR "." CACHE PATH "Path to OpenCV folder containing OpenCVConfig.cmake") -set(NLOPT_DIR "." CACHE PATH "Path to NLopt folder containing libnlopt-0.dll") +set(NLOPT_DIR "." CACHE PATH "Path to NLopt folder containing libnlopt-0.lib") # output dirs set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib) From fc1bda3e690c7990e26f3ca0a5f1e2e1e50d7225 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 8 Apr 2019 16:02:05 +0200 Subject: [PATCH 069/235] possible fix for to_string template instantiation issue --- include/ConfigParser.h | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/include/ConfigParser.h b/include/ConfigParser.h index b8ddbc9..052ecba 100644 --- a/include/ConfigParser.h +++ b/include/ConfigParser.h @@ -41,15 +41,19 @@ class ConfigParser bool getVVecInt(std::string key, std::vector>& val); /// Write access - template - void add(std::string key, T& val) { _data[key] = std::to_string(val); } + template< + typename T, + typename = typename std::enable_if::value, T>::type + > void add(std::string key, T& val) { _data[key] = std::to_string(val); } // special case: string void add(std::string key, std::string val) { _data[key] = val; } // special case: vector - template - void add(std::string key, std::vector& val) { + template< + typename T, + typename = typename std::enable_if::value, T>::type + > void add(std::string key, std::vector& val) { std::string str = "{ "; for (auto v : val) { str += std::to_string(v) + ", "; @@ -59,8 +63,10 @@ class ConfigParser } // super special case: vector of vectors - template - void add(std::string key, std::vector>& val) { + template< + typename T, + typename = typename std::enable_if::value, T>::type + > void add(std::string key, std::vector>& val) { std::string str = "{ "; for (auto v : val) { str += "{ "; From c523c6975f4150a1884dd0ed8f7c7c2248d12b93 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 8 Apr 2019 16:02:28 +0200 Subject: [PATCH 070/235] minor source cleanup --- src/Trackball.cpp | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 46d2b19..ff8766b 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -30,21 +30,8 @@ #include #include -using std::string; -using std::make_unique; -using std::make_shared; -using std::unique_lock; -using std::lock_guard; -using std::mutex; -using std::vector; -using std::deque; -using cv::Mat; -using cv::Scalar; -using cv::Rect; -using cv::Point2d; -using cv::Point2i; -using cv::VideoWriter; - +using namespace cv; +using namespace std; const int DRAW_SPHERE_HIST_LENGTH = 250; const int DRAW_CELL_DIM = 160; @@ -111,7 +98,7 @@ Trackball::Trackball(string cfg_fn) /// Open frame source and set fps. string src_fn = _cfg("src_fn"); - std::shared_ptr source; + shared_ptr source; #if defined(PGR_USB2) || defined(PGR_USB3) try { if (src_fn.size() > 2) { throw std::exception(); } @@ -124,7 +111,7 @@ Trackball::Trackball(string cfg_fn) source = make_shared(src_fn); } #else // !PGR_USB2/3 - source = std::make_shared(src_fn); + source = make_shared(src_fn); #endif // PGR_USB2/3 if (!source->isOpen()) { LOG_ERR("Error! Could not open input frame source (%s)!", src_fn.c_str()); @@ -317,7 +304,7 @@ Trackball::Trackball(string cfg_fn) } /// Pre-calc view rays. - _p1s_lut = std::shared_ptr(new double[_roi_w * _roi_h * 3]); + _p1s_lut = shared_ptr(new double[_roi_w * _roi_h * 3]); memset(_p1s_lut.get(), 0, _roi_w * _roi_h * 3 * sizeof(double)); for (int i = 0; i < _roi_h; i++) { uint8_t* pmask = _roi_mask.ptr(i); @@ -490,7 +477,7 @@ Trackball::Trackball(string cfg_fn) } /// Frame source. - _frameGrabber = std::make_unique( + _frameGrabber = make_unique( source, remapper, _roi_mask, @@ -1135,7 +1122,7 @@ void makeSphereRotMaps( /// /// /// -bool Trackball::updateCanvasAsync(std::shared_ptr data) +bool Trackball::updateCanvasAsync(shared_ptr data) { bool ret = false; lock_guard l(_drawMutex); @@ -1193,7 +1180,7 @@ void Trackball::processDrawQ() /// /// /// -void Trackball::drawCanvas(std::shared_ptr data) +void Trackball::drawCanvas(shared_ptr data) { static Mat canvas(3 * DRAW_CELL_DIM, 4 * DRAW_CELL_DIM, CV_8UC3); canvas.setTo(Scalar::all(0)); @@ -1401,9 +1388,9 @@ void Trackball::drawCanvas(std::shared_ptr data) } } -void Trackball::printState() +void Trackball::dumpState() { - PRINT(""); + PRINT("\n----------------------------------------------------------------------"); PRINT("Trackball state"); PRINT("Sphere orientation (cam): %f %f %f", _r_cam[0], _r_cam[1], _r_cam[2]); PRINT("Total heading rotation: %f deg", _ang_dist * CM_R2D); @@ -1412,6 +1399,7 @@ void Trackball::printState() PRINT("Distance travelled: %f rad (%f * 2pi)", _dist, _dist / (2 * CM_PI)); PRINT("Integrated X/Y position: (%.3e, %.3e) rad (%f / %f %% total path length)", _posx, _posy, _posx * 100. / _dist, _posy * 100. / _dist); PRINT("Average/stdev rotation: %.3e / %.3e rad/frame", _step_avg, sqrt(_step_var / _cnt)); // population variance + PRINT("\n----------------------------------------------------------------------"); } bool Trackball::writeTemplate(std::string fn) From e6d24258ae9ab99dd09f0511161e05d404a29133 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 8 Apr 2019 16:03:02 +0200 Subject: [PATCH 071/235] printState -> dumpState --- include/Trackball.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/Trackball.h b/include/Trackball.h index cf7a727..2d2fac0 100644 --- a/include/Trackball.h +++ b/include/Trackball.h @@ -38,7 +38,7 @@ class Trackball bool isActive() { return _active; } void terminate() { _kill = true; } - void printState(); + void dumpState(); bool writeTemplate(std::string fn = ""); private: From 8b3f653aed6ffeac845f4644868a5e5eb5233348 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 8 Apr 2019 16:03:27 +0200 Subject: [PATCH 072/235] enable test mode --- exec/fictrac.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/exec/fictrac.cpp b/exec/fictrac.cpp index 603cc2a..895bebc 100644 --- a/exec/fictrac.cpp +++ b/exec/fictrac.cpp @@ -34,6 +34,7 @@ int main(int argc, char *argv[]) /// Parse args. string log_level = "info"; string config_fn = "config.txt"; + bool do_test = false; for (int i = 1; i < argc; ++i) { if ((string(argv[i]) == "--verbosity") || (string(argv[i]) == "-v")) { if (++i < argc) { @@ -43,8 +44,11 @@ int main(int argc, char *argv[]) LOG_ERR("-v/--verbosity requires one argument (debug < info (default) < warn < error)!"); return -1; } - } - else { + } + else if ((string(argv[i]) == "--test") || (string(argv[i]) == "-t")) { + do_test = true; + } + else { config_fn = argv[i]; } } @@ -75,9 +79,12 @@ int main(int argc, char *argv[]) sleep(250); } - //tracker.printState(); tracker.writeTemplate(); + if (do_test) { + tracker.dumpState(); + } + //PRINT("\n\nHit ENTER to exit.."); //getchar_clean(); return 0; From 31b4d036af29d7c567e272e7d52a64291334287c Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 10 Apr 2019 22:28:06 +0200 Subject: [PATCH 073/235] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e537367..e427718 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,8 @@ cmake --build . --config Release -j 4 If everything went well, the executables for FicTrac and a configuration utility will be placed under the `bin` directory in the FicTrac project folder. +Remember to update and re-build FicTrac occasionally, as the program is still under development and fixes and improvements are being made continuously. + #### Ubuntu (Linux) installation 1. Install the required dependencies: @@ -93,6 +95,8 @@ cmake --build . --config Release -- -j 4 If everything went well, the executables for FicTrac and a configuration utility will be placed under the `bin` directory in the FicTrac project folder. +Remember to update and re-build FicTrac occasionally, as the program is still under development and fixes and improvements are being made continuously. + #### USB3 camera installation If you are using a USB3 camera and are receiving error messages when FicTrac tries to connect to your camera, you may need to tell FicTrac to use the SDK provided with your camera, rather than the generic OpenCV interface. The instructions for switching to the camera's SDK are different for each manufacturer. Currently there is support for PGR (FLIR) USB3 cameras via the Spinnaker SDK. From cbf9bdd397458ed9fec808ceb4f94020aea2ecd2 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 28 Apr 2019 13:55:45 +0200 Subject: [PATCH 074/235] extra debugging for frame grabbing --- src/CVSource.cpp | 3 ++- src/PGRSource.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/CVSource.cpp b/src/CVSource.cpp index 99f4a7a..c5755e0 100644 --- a/src/CVSource.cpp +++ b/src/CVSource.cpp @@ -135,8 +135,9 @@ bool CVSource::grab(cv::Mat& frame) LOG_ERR("Error grabbing image frame!"); return false; } - double ts = static_cast(ts_ms()); // backup, in case the device timestamp is junk + double ts = ts_ms(); // backup, in case the device timestamp is junk _timestamp = _cap->get(cv::CAP_PROP_POS_MSEC); + LOG_DBG("Frame captured %dx%d%d @ %f (%f)", _frame_cap.cols, _frame_cap.rows, _frame_cap.channels(), _timestamp, ts); if (_timestamp <= 0) { _timestamp = ts; } diff --git a/src/PGRSource.cpp b/src/PGRSource.cpp index 52f97ff..07f5875 100644 --- a/src/PGRSource.cpp +++ b/src/PGRSource.cpp @@ -240,8 +240,9 @@ bool PGRSource::grab(cv::Mat& frame) // Retrieve next received image long int timeout = _fps > 0 ? std::max(static_cast(1000), static_cast(1000. / _fps)) : 1000; // set capture timeout to at least 1000 ms pgr_image = _cam->GetNextImage(timeout); - double ts = static_cast(ts_ms()); // backup, in case the device timestamp is junk + double ts = ts_ms(); // backup, in case the device timestamp is junk _timestamp = _cam->Timestamp(); + LOG_DBG("Frame captured %dx%d%d @ %f (%f)", pgr_image->GetWidth(), pgr_image->GetHeight(), pgr_image->GetNumChannels(), _timestamp, ts); if (_timestamp <= 0) { _timestamp = ts; } @@ -290,7 +291,8 @@ bool PGRSource::grab(cv::Mat& frame) #elif defined(PGR_USB2) Image frame_raw; Error error = _cam->RetrieveBuffer(&frame_raw); - double ts = static_cast(ts_ms()); // backup, in case the device timestamp is junks + double ts = ts_ms(); // backup, in case the device timestamp is junk + //LOG_DBG("Frame captured %dx%d%d @ %f (%f)", pgr_image->GetWidth(), pgr_image->GetHeight(), pgr_image->GetNumChannels(), _timestamp, ts); if (error != PGRERROR_OK) { LOG_ERR("Error grabbing image frame!"); return false; From dcda219bb32264b568cda7457d42cc312810fe37 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 2 May 2019 21:38:44 +0200 Subject: [PATCH 075/235] don't crash cmake for missing opencv defines --- CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e2a468..7aee5a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,10 +137,11 @@ if(MSVC) endforeach() set(FFMPEG_LIB_BASE opencv_ffmpeg${OPENCV_VER_STRING}) - if(${OpenCV_ARCH} STREQUAL x64) - set(FFMPEG_LIB ${FFMPEG_LIB_BASE}_64.dll) - else() + if("${OpenCV_ARCH}" STREQUAL x86) set(FFMPEG_LIB ${FFMPEG_LIB_BASE}.dll) + else() + # default to 64-bit + set(FFMPEG_LIB ${FFMPEG_LIB_BASE}_64.dll) endif() list(APPEND TO_COPY "${_OpenCV_LIB_PATH}/${FFMPEG_LIB}") From b5b2119df15d41b36e0a9bbf7f8ef47b906fe799 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 20:15:16 +0200 Subject: [PATCH 076/235] fix error C2676: binary '[': 'std::shared_ptr' --- src/Localiser.cpp | 2 +- src/Trackball.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Localiser.cpp b/src/Localiser.cpp index 36ccd57..cd86044 100644 --- a/src/Localiser.cpp +++ b/src/Localiser.cpp @@ -96,7 +96,7 @@ double Localiser::testRotation(const double x[3]) for (int i = 0; i < _roi_h; i++) { const uint8_t* pmask = _roi_mask.ptr(i); const uint8_t* proi = _roi_frame.ptr(i); - const double* v = &(_p1s_lut[i * _roi_w * 3]); + const double* v = &(_p1s_lut.get()[i * _roi_w * 3]); for (int j = 0; j < _roi_w; j++) { if (pmask[j] < 255) { continue; } cnt++; diff --git a/src/Trackball.cpp b/src/Trackball.cpp index ff8766b..5cdfbb7 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -315,7 +315,7 @@ Trackball::Trackball(string cfg_fn) _roi_model->pixelIndexToVector(j, i, l); vec3normalise(l); - double* s = &_p1s_lut[(i * _roi_w + j) * 3]; + double* s = &_p1s_lut.get()[(i * _roi_w + j) * 3]; if (!intersectSphere(l, s, _r_d_ratio)) { pmask[j] = 128; } } } @@ -772,7 +772,7 @@ void Trackball::updateSphere() cnt++; // rotate point about rotation axis (sphere coords) - double* v = &_p1s_lut[(i * _roi_w + j) * 3]; + double* v = &_p1s_lut.get()[(i * _roi_w + j) * 3]; //p2s[0] = m[0] * v[0] + m[1] * v[1] + m[2] * v[2]; //p2s[1] = m[3] * v[0] + m[4] * v[1] + m[5] * v[2]; //p2s[2] = m[6] * v[0] + m[7] * v[1] + m[8] * v[2]; @@ -1013,7 +1013,7 @@ double Trackball::testRotation(const double x[3]) for (int i = 0; i < _roi_h; i++) { uint8_t* pmask = _roi_mask.ptr(i); uint8_t* proi = _roi_frame.ptr(i); - double* v = &_p1s_lut[i * _roi_w * 3]; + double* v = &_p1s_lut.get()[i * _roi_w * 3]; for (int j = 0; j < _roi_w; j++) { if (pmask[j] < 255) { continue; } cnt++; From 5541ce2ef0559a58c34c2200854eccf6f0ef89c3 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 20:25:59 +0200 Subject: [PATCH 077/235] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..aa91291 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,19 @@ +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + +trigger: +- master + +pool: + vmImage: 'ubuntu-latest' + +steps: +- script: echo Hello, world! + displayName: 'Run a one-line script' + +- script: | + echo Add other tasks to build, test, and deploy your project. + echo See https://aka.ms/yaml + displayName: 'Run a multi-line script' From d792f903f7219d4ec6adf5dea89b3e30467cd89a Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 20:50:54 +0200 Subject: [PATCH 078/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index aa91291..d6a2cd3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,19 +1,38 @@ -# Starter pipeline -# Start with a minimal pipeline that you can customize to build and deploy your code. -# Add steps that build, run tests, deploy, and more: +# Azure CI/CD pipeline config file # https://aka.ms/yaml +# Trigger builds on master branch +# https://docs.microsoft.com/en-us/azure/devops/pipelines/build/triggers?view=azure-devops&tabs=yaml + trigger: - master -pool: - vmImage: 'ubuntu-latest' +# We can run multiple jobs in parallel. +# see https://docs.microsoft.com/en-us/azure/devops/pipelines/process/phases +jobs: -steps: -- script: echo Hello, world! - displayName: 'Run a one-line script' +# Provide a name for the job +- job: Linux + # The VM image to use for the hosted agent. For a list of possible agents + # see https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted + # You can see the software installed on each agent at the same link. + pool: + vmImage: 'ubuntu-latest' + # The steps to run to execute the build. + steps: + - task: CMake@1 + inputs: + cmakeArgs: -- script: | - echo Add other tasks to build, test, and deploy your project. - echo See https://aka.ms/yaml - displayName: 'Run a multi-line script' +# Provide a name for the job +- job: Windows + # The VM image to use for the hosted agent. For a list of possible agents + # see https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted + # You can see the software installed on each agent at the same link. + pool: + vmImage: 'vs2017-win2016' + # The steps to run to execute the build. + steps: + - task: CMake@1 + inputs: + cmakeArgs: '-G "Visual Studio 15 2017 Win64"' From 6617f45475521830aeaf5f4cc8e7510c0b45887e Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 20:57:07 +0200 Subject: [PATCH 079/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d6a2cd3..16e5f1a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,7 +22,8 @@ jobs: steps: - task: CMake@1 inputs: - cmakeArgs: + workingDirectory: build + cmakeArgs: .. # Provide a name for the job - job: Windows From ac0899ce2d82caa765ffa210299ce1b6225f0f18 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 21:20:48 +0200 Subject: [PATCH 080/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 16e5f1a..310da6c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,6 +20,10 @@ jobs: vmImage: 'ubuntu-latest' # The steps to run to execute the build. steps: + - script: .\vcpkg\bootstrap-vcpkg.bat + displayName: Bootstrap vcpkg + - script: .\vcpkg\vcpkg.exe install opencv:x64-linux nlopt:x64-linux --vcpkg-root .\vcpkg + displayName: vcpkg install dependencies - task: CMake@1 inputs: workingDirectory: build From 6b3872b7efbf874f11216d6b21848ddcb0a435ad Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 21:22:25 +0200 Subject: [PATCH 081/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 310da6c..e428293 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,9 +20,9 @@ jobs: vmImage: 'ubuntu-latest' # The steps to run to execute the build. steps: - - script: .\vcpkg\bootstrap-vcpkg.bat + - script: ./vcpkg/bootstrap-vcpkg.bat displayName: Bootstrap vcpkg - - script: .\vcpkg\vcpkg.exe install opencv:x64-linux nlopt:x64-linux --vcpkg-root .\vcpkg + - script: ./vcpkg/vcpkg.exe install opencv:x64-linux nlopt:x64-linux --vcpkg-root ./vcpkg displayName: vcpkg install dependencies - task: CMake@1 inputs: From af88658e991d314ae41712ac735a39f0c608f45a Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 21:27:23 +0200 Subject: [PATCH 082/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e428293..a4bb8d2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,9 +20,9 @@ jobs: vmImage: 'ubuntu-latest' # The steps to run to execute the build. steps: - - script: ./vcpkg/bootstrap-vcpkg.bat + - script: bootstrap-vcpkg.sh displayName: Bootstrap vcpkg - - script: ./vcpkg/vcpkg.exe install opencv:x64-linux nlopt:x64-linux --vcpkg-root ./vcpkg + - script: vcpkg install opencv:x64-linux nlopt:x64-linux displayName: vcpkg install dependencies - task: CMake@1 inputs: From c762436cb5eccb2e9342cf32d001e6e74e090b40 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 21:41:17 +0200 Subject: [PATCH 083/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a4bb8d2..456a2e5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,8 +20,6 @@ jobs: vmImage: 'ubuntu-latest' # The steps to run to execute the build. steps: - - script: bootstrap-vcpkg.sh - displayName: Bootstrap vcpkg - script: vcpkg install opencv:x64-linux nlopt:x64-linux displayName: vcpkg install dependencies - task: CMake@1 From cb2dcf3630dd8088b7740f60d1f46473ff08a253 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 22:00:06 +0200 Subject: [PATCH 084/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 456a2e5..b4d643a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,8 +20,9 @@ jobs: vmImage: 'ubuntu-latest' # The steps to run to execute the build. steps: - - script: vcpkg install opencv:x64-linux nlopt:x64-linux - displayName: vcpkg install dependencies + - script: locate vcpkg + #- script: vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux + # displayName: vcpkg install dependencies - task: CMake@1 inputs: workingDirectory: build From 476a4b7434d99ff6974a6d6a36b71b7551457458 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 22:03:20 +0200 Subject: [PATCH 085/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b4d643a..3467571 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,7 +20,7 @@ jobs: vmImage: 'ubuntu-latest' # The steps to run to execute the build. steps: - - script: locate vcpkg + - script: echo $VCPKG_INSTALLATION_ROOT #- script: vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux # displayName: vcpkg install dependencies - task: CMake@1 From b6b38b01688bc04a3a24173b8e3ffab5f0dd74e1 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 22:11:32 +0200 Subject: [PATCH 086/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3467571..4f77c58 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,7 +20,9 @@ jobs: vmImage: 'ubuntu-latest' # The steps to run to execute the build. steps: - - script: echo $VCPKG_INSTALLATION_ROOT + - script: | + echo "set(VCPKG_BUILD_TYPE release)" >> $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake + echo less $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake #- script: vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux # displayName: vcpkg install dependencies - task: CMake@1 From 5d2097bd87e10f96a3c89a9df2fd8ad8700ed6b2 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 22:13:51 +0200 Subject: [PATCH 087/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4f77c58..a3c25b6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,9 +22,10 @@ jobs: steps: - script: | echo "set(VCPKG_BUILD_TYPE release)" >> $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake - echo less $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake - #- script: vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux - # displayName: vcpkg install dependencies + less $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake + displayName: vcpkg set build type + - script: vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux + displayName: vcpkg install dependencies - task: CMake@1 inputs: workingDirectory: build From 0cadbeca2b2f63d01a1d0c78eaa1869f5eac0304 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 22:18:54 +0200 Subject: [PATCH 088/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a3c25b6..3879144 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,14 +22,13 @@ jobs: steps: - script: | echo "set(VCPKG_BUILD_TYPE release)" >> $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake - less $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake displayName: vcpkg set build type - script: vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux displayName: vcpkg install dependencies - task: CMake@1 inputs: workingDirectory: build - cmakeArgs: .. + cmakeArgs: -D OPENCV_DIR=$VCPKG_INSTALLATION_ROOT/installed/x64-linux/share/opencv .. # Provide a name for the job - job: Windows From ad2349168b63a282074ff0b9f6ee9408cb1a7caa Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 22:23:00 +0200 Subject: [PATCH 089/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3879144..e2db3a9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,9 +20,8 @@ jobs: vmImage: 'ubuntu-latest' # The steps to run to execute the build. steps: - - script: | - echo "set(VCPKG_BUILD_TYPE release)" >> $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake - displayName: vcpkg set build type + #- script: echo "set(VCPKG_BUILD_TYPE release)" >> $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake + # displayName: vcpkg set build type - script: vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux displayName: vcpkg install dependencies - task: CMake@1 From 13af71da8292bd6c83a6ccc5af5cc32bf280baa2 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 22:42:10 +0200 Subject: [PATCH 090/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e2db3a9..d225a5b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -27,7 +27,7 @@ jobs: - task: CMake@1 inputs: workingDirectory: build - cmakeArgs: -D OPENCV_DIR=$VCPKG_INSTALLATION_ROOT/installed/x64-linux/share/opencv .. + cmakeArgs: -D CMAKE_TOOLCHAIN_FILE=$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake .. # Provide a name for the job - job: Windows @@ -38,6 +38,9 @@ jobs: vmImage: 'vs2017-win2016' # The steps to run to execute the build. steps: + - script: vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows + displayName: vcpkg install dependencies - task: CMake@1 inputs: - cmakeArgs: '-G "Visual Studio 15 2017 Win64"' + workingDirectory: build + cmakeArgs: -G "Visual Studio 15 2017 Win64" -D OPENCV_DIR=$VCPKG_INSTALLATION_ROOT/installed/x64-windows/share/opencv -D NLOPT_DIR=$VCPKG_INSTALLATION_ROOT/installed/x64-windows/share/nlopt .. From 5d139d4092a5d7ca55e1aa0383c2e52a14d18c74 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 23:02:18 +0200 Subject: [PATCH 091/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d225a5b..305f803 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -27,7 +27,7 @@ jobs: - task: CMake@1 inputs: workingDirectory: build - cmakeArgs: -D CMAKE_TOOLCHAIN_FILE=$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake .. + cmakeArgs: -D CMAKE_TOOLCHAIN_FILE=/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake .. # Provide a name for the job - job: Windows From 867e7224bb680e36c74f37dc7dd90d282a0b807e Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 23:16:59 +0200 Subject: [PATCH 092/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 305f803..5200187 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -43,4 +43,4 @@ jobs: - task: CMake@1 inputs: workingDirectory: build - cmakeArgs: -G "Visual Studio 15 2017 Win64" -D OPENCV_DIR=$VCPKG_INSTALLATION_ROOT/installed/x64-windows/share/opencv -D NLOPT_DIR=$VCPKG_INSTALLATION_ROOT/installed/x64-windows/share/nlopt .. + cmakeArgs: -D CMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -G "Visual Studio 15 2017 Win64" .. From dddaffac038a9322fba51df1ee1ae032a1aafc4a Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 23:19:35 +0200 Subject: [PATCH 093/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5200187..a0793d2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -28,6 +28,10 @@ jobs: inputs: workingDirectory: build cmakeArgs: -D CMAKE_TOOLCHAIN_FILE=/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake .. + - task: CMake@1 + inputs: + workingDirectory: build + cmakeArgs: --build . --config Release # Provide a name for the job - job: Windows @@ -44,3 +48,7 @@ jobs: inputs: workingDirectory: build cmakeArgs: -D CMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -G "Visual Studio 15 2017 Win64" .. + - task: CMake@1 + inputs: + workingDirectory: build + cmakeArgs: --build . --config Release \ No newline at end of file From d8eb3add476729c12b6ddd35c75ffacd3118e39b Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 23:43:39 +0200 Subject: [PATCH 094/235] revert error C2676 fix --- src/Localiser.cpp | 2 +- src/Trackball.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Localiser.cpp b/src/Localiser.cpp index cd86044..36ccd57 100644 --- a/src/Localiser.cpp +++ b/src/Localiser.cpp @@ -96,7 +96,7 @@ double Localiser::testRotation(const double x[3]) for (int i = 0; i < _roi_h; i++) { const uint8_t* pmask = _roi_mask.ptr(i); const uint8_t* proi = _roi_frame.ptr(i); - const double* v = &(_p1s_lut.get()[i * _roi_w * 3]); + const double* v = &(_p1s_lut[i * _roi_w * 3]); for (int j = 0; j < _roi_w; j++) { if (pmask[j] < 255) { continue; } cnt++; diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 5cdfbb7..ff8766b 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -315,7 +315,7 @@ Trackball::Trackball(string cfg_fn) _roi_model->pixelIndexToVector(j, i, l); vec3normalise(l); - double* s = &_p1s_lut.get()[(i * _roi_w + j) * 3]; + double* s = &_p1s_lut[(i * _roi_w + j) * 3]; if (!intersectSphere(l, s, _r_d_ratio)) { pmask[j] = 128; } } } @@ -772,7 +772,7 @@ void Trackball::updateSphere() cnt++; // rotate point about rotation axis (sphere coords) - double* v = &_p1s_lut.get()[(i * _roi_w + j) * 3]; + double* v = &_p1s_lut[(i * _roi_w + j) * 3]; //p2s[0] = m[0] * v[0] + m[1] * v[1] + m[2] * v[2]; //p2s[1] = m[3] * v[0] + m[4] * v[1] + m[5] * v[2]; //p2s[2] = m[6] * v[0] + m[7] * v[1] + m[8] * v[2]; @@ -1013,7 +1013,7 @@ double Trackball::testRotation(const double x[3]) for (int i = 0; i < _roi_h; i++) { uint8_t* pmask = _roi_mask.ptr(i); uint8_t* proi = _roi_frame.ptr(i); - double* v = &_p1s_lut.get()[i * _roi_w * 3]; + double* v = &_p1s_lut[i * _roi_w * 3]; for (int j = 0; j < _roi_w; j++) { if (pmask[j] < 255) { continue; } cnt++; From 37b85fad8d1772c306ecdff4c72e94d92d75efa9 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 3 May 2019 23:51:26 +0200 Subject: [PATCH 095/235] std=c++17 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7aee5a0..5cc0815 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,10 +109,10 @@ endif() # add compile options if(MSVC) - target_compile_options(libfictrac PUBLIC $<$:/MP /GS /GL /W3 /WX- /Gy /Zc:wchar_t /O2 /Oi /Zc:inline /fp:precise /MD /EHsc>) + target_compile_options(libfictrac PUBLIC $<$:/MP /GS /GL /W3 /WX- /Gy /Zc:wchar_t /O2 /Oi /Zc:inline /fp:precise /MD /EHsc /std:c++17>) # set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") else() # gcc - target_compile_options(libfictrac PUBLIC -Ofast -Wall -c -fmessage-length=0 -std=c++14 -Wno-unused-function -march=native -MMD) + target_compile_options(libfictrac PUBLIC -Ofast -Wall -c -fmessage-length=0 -std=c++17 -Wno-unused-function -march=native -MMD) endif() # linking From 74c6726bb6632855e2e78d14c45f10788c92028c Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sat, 4 May 2019 00:37:00 +0200 Subject: [PATCH 096/235] avoid error C2676 --- include/Localiser.h | 5 +++-- include/Trackball.h | 2 +- src/Localiser.cpp | 6 +++--- src/Trackball.cpp | 9 ++++----- src/drawing.cpp | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/Localiser.h b/include/Localiser.h index aedd0e8..b1f7f73 100644 --- a/include/Localiser.h +++ b/include/Localiser.h @@ -13,6 +13,7 @@ #include #include // shared_ptr +#include /// /// @@ -22,7 +23,7 @@ class Localiser : public NLoptFunc public: Localiser(nlopt_algorithm alg, double bound, double tol, int max_evals, CameraModelPtr sphere_model, const cv::Mat& sphere_map, - const cv::Mat& roi_mask, std::shared_ptr p1s_lut); + const cv::Mat& roi_mask, std::shared_ptr> p1s_lut); ~Localiser() {}; double search(cv::Mat& roi_frame, cv::Mat& R_roi, CmPoint64f& vx); @@ -36,7 +37,7 @@ class Localiser : public NLoptFunc const double* _R_roi; CameraModelPtr _sphere_model; const cv::Mat _sphere_map, _roi_mask; - std::shared_ptr _p1s_lut; + std::shared_ptr> _p1s_lut; cv::Mat _roi_frame; int _roi_w, _roi_h; }; diff --git a/include/Trackball.h b/include/Trackball.h index 2d2fac0..07dd30b 100644 --- a/include/Trackball.h +++ b/include/Trackball.h @@ -87,7 +87,7 @@ class Trackball CameraModelPtr _src_model, _roi_model, _sphere_model; RemapTransformPtr _cam_to_roi; cv::Mat _roi_to_cam_R, _cam_to_lab_R; - std::shared_ptr _p1s_lut; + std::shared_ptr> _p1s_lut; /// Arrays. int _map_w, _map_h; diff --git a/src/Localiser.cpp b/src/Localiser.cpp index 36ccd57..7c9e116 100644 --- a/src/Localiser.cpp +++ b/src/Localiser.cpp @@ -9,14 +9,14 @@ #include "Logger.h" using cv::Mat; - +using namespace std; /// /// /// Localiser::Localiser(nlopt_algorithm alg, double bound, double tol, int max_evals, CameraModelPtr sphere_model, const Mat& sphere_map, - const Mat& roi_mask, std::shared_ptr p1s_lut) + const Mat& roi_mask, shared_ptr> p1s_lut) : _bound(bound), _sphere_model(sphere_model), _sphere_map(sphere_map), _roi_mask(roi_mask), _p1s_lut(p1s_lut) { init(alg, 3); @@ -96,7 +96,7 @@ double Localiser::testRotation(const double x[3]) for (int i = 0; i < _roi_h; i++) { const uint8_t* pmask = _roi_mask.ptr(i); const uint8_t* proi = _roi_frame.ptr(i); - const double* v = &(_p1s_lut[i * _roi_w * 3]); + const double* v = &(*_p1s_lut)[i * _roi_w * 3]; for (int j = 0; j < _roi_w; j++) { if (pmask[j] < 255) { continue; } cnt++; diff --git a/src/Trackball.cpp b/src/Trackball.cpp index ff8766b..f47e500 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -304,8 +304,7 @@ Trackball::Trackball(string cfg_fn) } /// Pre-calc view rays. - _p1s_lut = shared_ptr(new double[_roi_w * _roi_h * 3]); - memset(_p1s_lut.get(), 0, _roi_w * _roi_h * 3 * sizeof(double)); + _p1s_lut = make_shared>(_roi_w * _roi_h * 3, 0); for (int i = 0; i < _roi_h; i++) { uint8_t* pmask = _roi_mask.ptr(i); for (int j = 0; j < _roi_w; j++) { @@ -315,7 +314,7 @@ Trackball::Trackball(string cfg_fn) _roi_model->pixelIndexToVector(j, i, l); vec3normalise(l); - double* s = &_p1s_lut[(i * _roi_w + j) * 3]; + double* s = &(*_p1s_lut)[(i * _roi_w + j) * 3]; if (!intersectSphere(l, s, _r_d_ratio)) { pmask[j] = 128; } } } @@ -772,7 +771,7 @@ void Trackball::updateSphere() cnt++; // rotate point about rotation axis (sphere coords) - double* v = &_p1s_lut[(i * _roi_w + j) * 3]; + double* v = &(*_p1s_lut)[(i * _roi_w + j) * 3]; //p2s[0] = m[0] * v[0] + m[1] * v[1] + m[2] * v[2]; //p2s[1] = m[3] * v[0] + m[4] * v[1] + m[5] * v[2]; //p2s[2] = m[6] * v[0] + m[7] * v[1] + m[8] * v[2]; @@ -1013,7 +1012,7 @@ double Trackball::testRotation(const double x[3]) for (int i = 0; i < _roi_h; i++) { uint8_t* pmask = _roi_mask.ptr(i); uint8_t* proi = _roi_frame.ptr(i); - double* v = &_p1s_lut[i * _roi_w * 3]; + double* v = &(*_p1s_lut)[i * _roi_w * 3]; for (int j = 0; j < _roi_w; j++) { if (pmask[j] < 255) { continue; } cnt++; diff --git a/src/drawing.cpp b/src/drawing.cpp index 8799fcf..6d98eac 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -68,7 +68,7 @@ void drawCircle(cv::Mat& img, shared_ptr> circ_pts, const cv /// Draw lines between circumference points. Point2d p1 = circ_pts->front(), p2; - for (int i = 1; i < circ_pts->size(); i++) { + for (unsigned int i = 1; i < circ_pts->size(); i++) { /// Draw dashed/solid. p2 = (*circ_pts)[i]; if (solid || (i % 2)) { cv::line(img, 4 * p1, 4 * p2, colour, 2, cv::LINE_AA, 2); } From 2ada777f35be2194dbd8813a2e35546c18b1b0ea Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sat, 4 May 2019 02:30:25 +0200 Subject: [PATCH 097/235] Update README.md --- README.md | 54 ++++++++++++++++-------------------------------------- 1 file changed, 16 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index e427718..c3834e8 100644 --- a/README.md +++ b/README.md @@ -44,53 +44,31 @@ FicTrac imposes no requirements on the *italicised* items; how you design these ### Installation -The FicTrac source code can be built for both Windows and Ubuntu (Linux) operating systems. You can even build and run FicTrac from within a [virtual machine](https://www.virtualbox.org/) on any operating system. - -**Note:** If you plan on using a USB3 camera, FicTrac may have issues using the OpenCV capture interface. The work around is to tell FicTrac to use the SDK provided with your camera instead of OpenCV to do the frame grabbing. See [USB3 camera installation](#usb3-camera-installation). - -#### Windows installation +The FicTrac source code can be built for both Windows and Ubuntu (Linux) operating systems, or you can build and run FicTrac from within a [virtual machine](https://www.virtualbox.org/) on any operating system. 1. Download and install required dependencies: 1. [Cmake build system](https://cmake.org/download/) (binary distribution) - 2. [OpenCV computer vision library](https://opencv.org/releases.html) (version 4.0.1 Win pack) - 3. [NLopt optimisation library](https://www.dropbox.com/s/z0rjd7ksbf17fjd/nlopt-2.4.2-x64.zip?dl=1) (Just extract the zip folder somewhere. If you need the 32-bit version you can download the precompiled DLL from [here](https://nlopt.readthedocs.io/en/latest/NLopt_on_Windows/) and follow directions to create the import library) -2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: -``` -mkdir build -cd build -``` -3. Next, we will configure and build the FicTrac project. FicTrac is written in C++, so you'll need a suitable compiler. In this example we will use MSVS Build Tools. If you don't already have Visual Studio, you will need to install the [build tools](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2017). -4. Run Cmake to prepare the necessary build files for FicTrac. Here we also need to provide the paths to where we installed OpenCV and NLopt (I have given example paths here, you will need to modify them for your installation): -``` -cmake -G "Visual Studio 15 2017 Win64" -D OPENCV_DIR="C:\path\to\opencv-4.0.1\build" -D NLOPT_DIR="C:\path\to\nlopt-2.4.2\" .. -``` -5. Finally, build and install FicTrac: + 2. For Windows installations, if you don't already have Visual Studio (C++ workflow) installed, you will need to install the [build tools](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2017). + 3. Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). + 4. Using Vcpkg, install OpenCV and NLopt software packages: ``` -cmake --build . --config Release -j 4 +[Windows] .\vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows +[Linux] ./vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux ``` - -If everything went well, the executables for FicTrac and a configuration utility will be placed under the `bin` directory in the FicTrac project folder. - -Remember to update and re-build FicTrac occasionally, as the program is still under development and fixes and improvements are being made continuously. - -#### Ubuntu (Linux) installation - -1. Install the required dependencies: -``` -sudo apt-get install gcc cmake libavcodec-dev libavformat-dev libswscale-dev libv4l-dev libgtk-3-dev libdc1394-22-dev libopencv-dev libnlopt-dev -``` -2. Clone or download the FicTrac repository, then navigate to that folder and create a build directory: +2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: ``` mkdir build cd build ``` -3. Run Cmake to prepare the necessary build files for FicTrac (if OpenCV and NLopt are not installed in the default location, you can help Cmake find them by defining OPENCV_DIR and NLOPT_DIR - see [Windows installation](#windows-installation) for an example): +3. Run Cmake to prepare the necessary build files for FicTrac. Here, we will need to provide the path to the Cmake toolchain file that was installed by Vcpkg (this path is printed to terminal when you run the Vcpkg system-wide integration step). ``` -cmake .. +[Windows] cmake -G "Visual Studio 15 2017 Win64" -D CMAKE_TOOLCHAIN_FILE=C:\path\to\vcpkg\scripts\buildsystems\vcpkg.cmake .. +[Linux] cmake -D CMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake .. ``` -4. Finally, build and install FicTrac: +5. Finally, build and install FicTrac: ``` -cmake --build . --config Release -- -j 4 +[Windows] cmake --build . --config Release -j 4 +[Linux] cmake --build . --config Release -- -j 4 ``` If everything went well, the executables for FicTrac and a configuration utility will be placed under the `bin` directory in the FicTrac project folder. @@ -104,11 +82,11 @@ If you are using a USB3 camera and are receiving error messages when FicTrac tri ##### PGR (FLIR) Spinnaker SDK 1. Download and install the latest Spinnaker (full) SDK from [PGR downloads page](https://www.ptgrey.com/support/downloads). -2. When preparing the build files for FicTrac using Cmake, you will need to specify to use Spinnaker using the switch `-D PGR_USB3=ON` and depending on where you installed the SDK, you may also need to provide the SDK directory path using the switch `-D PGR_DIR=...`. For example, for a [Windows installation](#windows-installation) you would replace step 4 with: +2. When preparing the build files for FicTrac using Cmake, you will need to specify to use Spinnaker using the switch `-D PGR_USB3=ON` and depending on where you installed the SDK, you may also need to provide the SDK directory path using the switch `-D PGR_DIR=...`. For example, for a Windows installation you would replace step 3 above with: ``` -cmake -G "Visual Studio 15 2017 Win64" -D OPENCV_DIR="C:\path\to\opencv-3.4.2\build" -D NLOPT_DIR="C:\path\to\nlopt-2.4.2\" -D PGR_USB3=ON -D PGR_DIR="C:\path\to\Spinnaker" .. +cmake -G "Visual Studio 15 2017 Win64" -D CMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -D PGR_USB3=ON -D PGR_DIR="C:\path\to\Spinnaker" .. ``` -3. Follow the other build steps for either [Windows](#windows-installation) or [Ubuntu (Linux)](#ubuntu-linux-installation) as normal. +3. Follow the other build steps as normal. Before running FicTrac, you may configure your camera (frame rate, resolution, etc) as desired using the SDK utilities. From 5f628edbb28135723e9e1a52bc708a1533689323 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sat, 4 May 2019 02:33:43 +0200 Subject: [PATCH 098/235] vcpkg package management --- CMakeLists.txt | 67 +++++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cc0815..20769f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ configure_file ( # dependency search dirs set(OPENCV_DIR "." CACHE PATH "Path to OpenCV folder containing OpenCVConfig.cmake") -set(NLOPT_DIR "." CACHE PATH "Path to NLopt folder containing libnlopt-0.lib") +set(NLOPT_DIR "." CACHE PATH "Path to NLopt folder containing NLoptConfig.cmake") # output dirs set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib) @@ -33,7 +33,14 @@ endif() set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${OPENCV_DIR} ${NLOPT_DIR}) if(MSVC) find_package(opencv) - find_library(NLOPT_LIB libnlopt-0.lib) + find_package(nlopt) + if(nlopt_FOUND) + set(NLopt_INCLUDE_DIRS ${NLOPT_INCLUDE_DIRS}/include) + # set(NLopt_LIBS ${NLOPT_LIBRARY_DIRS}/lib/nlopt.lib) + message(STATUS "Found NLopt: ${NLOPT_LIBRARY_DIRS}") + else() + message(FATAL_ERROR "Error! Could not find NLopt lib at ${NLOPT_LIB}!") + endif() if(PGR_USB3) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}/lib64/vs2015) @@ -44,7 +51,14 @@ if(MSVC) endif() else() # gcc find_package(OpenCV) - find_library(NLOPT_LIB libnlopt.a) + find_package(NLopt) + if(NLopt_FOUND) + set(NLopt_INCLUDE_DIRS ${NLOPT_INCLUDE_DIRS}/include) + # set(NLopt_LIBS ${NLOPT_LIBRARY_DIRS}/lib/libnlopt.a) + message(STATUS "Found NLopt: ${NLOPT_LIBRARY_DIRS}") + else() + message(FATAL_ERROR "Error! Could not find NLopt lib at ${NLOPT_LIB}!") + endif() if(PGR_USB3) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}) @@ -54,15 +68,6 @@ else() # gcc find_library(PGR_LIB libflycapture.so) endif() endif() -get_filename_component(NLOPT_DIR ${NLOPT_LIB} DIRECTORY) -if(NLOPT_LIB) - message(STATUS "Found NLopt lib ${NLOPT_LIB}") - if(MSVC) - message(STATUS "You might need to add ${NLOPT_DIR} to your PATH to be able to run the executable.") - endif() -else() - message(FATAL_ERROR "Error! Could not find NLopt lib at ${NLOPT_LIB}!") -endif() if(PGR_USB2 OR PGR_USB3) get_filename_component(PGR_DIR ${PGR_LIB} DIRECTORY) get_filename_component(PGR_DIR ${PGR_DIR} DIRECTORY) # step up 1 level @@ -77,7 +82,7 @@ if(PGR_USB2 OR PGR_USB3) endif() # add include dirs -include_directories(${PROJECT_SOURCE_DIR}/include ${OpenCV_INCLUDE_DIRS} ${NLOPT_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/include ${OpenCV_INCLUDE_DIRS} ${NLopt_INCLUDE_DIRS}) if(PGR_USB2 OR PGR_USB3) if(MSVC) include_directories(${PGR_DIR}/include) @@ -94,36 +99,36 @@ endif() file(GLOB LIBFICTRAC_SRCS ${PROJECT_SOURCE_DIR}/src/*.cpp) # add targets -add_library(libfictrac STATIC ${LIBFICTRAC_SRCS}) +add_library(fictrac_core STATIC ${LIBFICTRAC_SRCS}) add_executable(configGui ${PROJECT_SOURCE_DIR}/exec/configGui.cpp) add_executable(fictrac ${PROJECT_SOURCE_DIR}/exec/fictrac.cpp) # add preprocessor definitions # public means defs will be inherited by linked executables -target_compile_definitions(libfictrac PUBLIC _CRT_SECURE_NO_WARNINGS NOMINMAX) +target_compile_definitions(fictrac_core PUBLIC _CRT_SECURE_NO_WARNINGS NOMINMAX) if(PGR_USB2) - target_compile_definitions(libfictrac PUBLIC PGR_USB2) + target_compile_definitions(fictrac_core PUBLIC PGR_USB2) elseif(PGR_USB3) - target_compile_definitions(libfictrac PUBLIC PGR_USB3) + target_compile_definitions(fictrac_core PUBLIC PGR_USB3) endif() # add compile options if(MSVC) - target_compile_options(libfictrac PUBLIC $<$:/MP /GS /GL /W3 /WX- /Gy /Zc:wchar_t /O2 /Oi /Zc:inline /fp:precise /MD /EHsc /std:c++17>) + target_compile_options(fictrac_core PUBLIC $<$:/MP /GS /GL /W3 /WX- /Gy /Zc:wchar_t /O2 /Oi /Zc:inline /fp:precise /MD /EHsc /std:c++17>) # set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") else() # gcc - target_compile_options(libfictrac PUBLIC -Ofast -Wall -c -fmessage-length=0 -std=c++17 -Wno-unused-function -march=native -MMD) + target_compile_options(fictrac_core PUBLIC -Ofast -Wall -c -fmessage-length=0 -std=c++17 -Wno-unused-function -march=native -MMD) endif() # linking -target_link_libraries(libfictrac PUBLIC ${OpenCV_LIBS} ${NLOPT_LIB}) +target_link_libraries(fictrac_core PUBLIC ${OpenCV_LIBS} ${NLOPT_LIBRARIES}) if(MSVC) - target_link_libraries(libfictrac PUBLIC Ws2_32) + target_link_libraries(fictrac_core PUBLIC Ws2_32) else() # gcc - target_link_libraries(libfictrac PUBLIC pthread) + target_link_libraries(fictrac_core PUBLIC pthread) endif() if(PGR_USB2 OR PGR_USB3) - target_link_libraries(libfictrac PUBLIC ${PGR_LIB}) + target_link_libraries(fictrac_core PUBLIC ${PGR_LIB}) endif() # post-build @@ -131,8 +136,8 @@ if(MSVC) # copy all opencv dlls set(OPENCV_VER_STRING ${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}) foreach(lib ${OpenCV_LIBS}) - if(EXISTS "${_OpenCV_LIB_PATH}/${lib}${OPENCV_VER_STRING}.dll") - list(APPEND TO_COPY "${_OpenCV_LIB_PATH}/${lib}${OPENCV_VER_STRING}.dll" "${_OpenCV_LIB_PATH}/${lib}${OPENCV_VER_STRING}d.dll") + if(EXISTS "${OpenCV_INSTALL_PATH}/bin/${lib}${OPENCV_VER_STRING}.dll") + list(APPEND TO_COPY "${OpenCV_INSTALL_PATH}/bin/${lib}${OPENCV_VER_STRING}.dll") endif() endforeach() @@ -143,10 +148,10 @@ if(MSVC) # default to 64-bit set(FFMPEG_LIB ${FFMPEG_LIB_BASE}_64.dll) endif() - list(APPEND TO_COPY "${_OpenCV_LIB_PATH}/${FFMPEG_LIB}") + list(APPEND TO_COPY "${OpenCV_INSTALL_PATH}/bin/${FFMPEG_LIB}") # copy nlopt dll - list(APPEND TO_COPY "${NLOPT_DIR}/libnlopt-0.dll") + list(APPEND TO_COPY "${NLOPT_LIBRARY_DIRS}/bin/nlopt.dll") # copy h264 dll file(GLOB DLLS ${PROJECT_SOURCE_DIR}/dll/*.dll) @@ -160,7 +165,7 @@ else() # gcc # nothing here... endif() -target_link_libraries(configGui libfictrac) -add_dependencies(configGui libfictrac) -target_link_libraries(fictrac libfictrac) -add_dependencies(fictrac libfictrac) +target_link_libraries(configGui fictrac_core) +add_dependencies(configGui fictrac_core) +target_link_libraries(fictrac fictrac_core) +add_dependencies(fictrac fictrac_core) From 1255fe52b92f8d44dfe89ec84bf3244df802dd8b Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 5 May 2019 22:04:20 +0200 Subject: [PATCH 099/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a0793d2..3a65536 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,6 +6,7 @@ trigger: - master +- develop # We can run multiple jobs in parallel. # see https://docs.microsoft.com/en-us/azure/devops/pipelines/process/phases From 7391697ae714faf7c4e9c63d50660de7c12f41d5 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 5 May 2019 22:11:06 +0200 Subject: [PATCH 100/235] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c3834e8..6da6133 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,8 @@ FicTrac imposes no requirements on the *italicised* items; how you design these ### Installation +[![Build Status](https://dev.azure.com/rjdmoore/FicTrac/_apis/build/status/rjdmoore.fictrac?branchName=master)](https://dev.azure.com/rjdmoore/FicTrac/_build/latest?definitionId=1&branchName=master) + The FicTrac source code can be built for both Windows and Ubuntu (Linux) operating systems, or you can build and run FicTrac from within a [virtual machine](https://www.virtualbox.org/) on any operating system. 1. Download and install required dependencies: From 74de298fb1bce1e1cba3a921219a5bddccb64784 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 5 May 2019 22:29:24 +0200 Subject: [PATCH 101/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3a65536..4d8c915 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -33,6 +33,10 @@ jobs: inputs: workingDirectory: build cmakeArgs: --build . --config Release + - task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: 'bin' + artifactName: drop # Provide a name for the job - job: Windows @@ -52,4 +56,8 @@ jobs: - task: CMake@1 inputs: workingDirectory: build - cmakeArgs: --build . --config Release \ No newline at end of file + cmakeArgs: --build . --config Release + - task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: 'bin' + artifactName: drop From 60892bc1e2b812081fc4d1998f98569122ca0217 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 5 May 2019 22:51:35 +0200 Subject: [PATCH 102/235] common dependency search --- CMakeLists.txt | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 20769f1..8a946d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,17 +31,16 @@ endif() # find dependencies set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${OPENCV_DIR} ${NLOPT_DIR}) +find_package(OpenCV REQUIRED) +find_package(NLopt CONFIG REQUIRED) +if(NLopt_FOUND) + set(NLopt_INCLUDE_DIRS ${NLOPT_INCLUDE_DIRS}/include) + # set(NLopt_LIBS ${NLOPT_LIBRARY_DIRS}/lib/nlopt.lib) + message(STATUS "Found NLopt: ${NLOPT_LIBRARY_DIRS}") +else() + message(FATAL_ERROR "Error! Could not find NLopt lib at ${NLOPT_LIB}!") +endif() if(MSVC) - find_package(opencv) - find_package(nlopt) - if(nlopt_FOUND) - set(NLopt_INCLUDE_DIRS ${NLOPT_INCLUDE_DIRS}/include) - # set(NLopt_LIBS ${NLOPT_LIBRARY_DIRS}/lib/nlopt.lib) - message(STATUS "Found NLopt: ${NLOPT_LIBRARY_DIRS}") - else() - message(FATAL_ERROR "Error! Could not find NLopt lib at ${NLOPT_LIB}!") - endif() - if(PGR_USB3) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}/lib64/vs2015) find_library(PGR_LIB Spinnaker_v140.lib) @@ -50,16 +49,6 @@ if(MSVC) find_library(PGR_LIB FlyCapture2_v140.lib) endif() else() # gcc - find_package(OpenCV) - find_package(NLopt) - if(NLopt_FOUND) - set(NLopt_INCLUDE_DIRS ${NLOPT_INCLUDE_DIRS}/include) - # set(NLopt_LIBS ${NLOPT_LIBRARY_DIRS}/lib/libnlopt.a) - message(STATUS "Found NLopt: ${NLOPT_LIBRARY_DIRS}") - else() - message(FATAL_ERROR "Error! Could not find NLopt lib at ${NLOPT_LIB}!") - endif() - if(PGR_USB3) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}) find_library(PGR_LIB libSpinnaker.so) From 9c68d0d76bc522c9f61156c996cce679f64d3c0d Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 5 May 2019 22:56:20 +0200 Subject: [PATCH 103/235] initial serial output support --- include/RecorderInterface.h | 3 +- include/SerialRecorder.h | 13 +++ include/SerialRecorder_linux.h | 25 ++++ include/SerialRecorder_win.h | 27 +++++ include/Serial_win.h | 66 +++++++++++ include/SocketRecorder.h | 2 +- ...order_winsocket.h => SocketRecorder_win.h} | 2 +- include/Trackball.h | 4 +- src/Recorder.cpp | 3 + src/SerialRecorder.cpp | 11 ++ src/SerialRecorder_linux.src | 49 ++++++++ src/SerialRecorder_win.src | 106 +++++++++++++++++ src/Serial_win.src | 109 ++++++++++++++++++ src/SocketRecorder.cpp | 2 +- ...r_winsocket.src => SocketRecorder_win.src} | 4 +- src/Trackball.cpp | 40 +++++-- 16 files changed, 451 insertions(+), 15 deletions(-) create mode 100644 include/SerialRecorder.h create mode 100644 include/SerialRecorder_linux.h create mode 100644 include/SerialRecorder_win.h create mode 100644 include/Serial_win.h rename include/{SocketRecorder_winsocket.h => SocketRecorder_win.h} (93%) create mode 100644 src/SerialRecorder.cpp create mode 100644 src/SerialRecorder_linux.src create mode 100644 src/SerialRecorder_win.src create mode 100644 src/Serial_win.src rename src/{SocketRecorder_winsocket.src => SocketRecorder_win.src} (97%) diff --git a/include/RecorderInterface.h b/include/RecorderInterface.h index 0f66c98..84c0fcf 100644 --- a/include/RecorderInterface.h +++ b/include/RecorderInterface.h @@ -15,7 +15,8 @@ class RecorderInterface CLOSED, TERM, FILE, - SOCK + SOCK, + COM }; RecorderInterface() : _open(false), _type(CLOSED) {} diff --git a/include/SerialRecorder.h b/include/SerialRecorder.h new file mode 100644 index 0000000..b6e311d --- /dev/null +++ b/include/SerialRecorder.h @@ -0,0 +1,13 @@ +/// FicTrac http://rjdmoore.net/fictrac/ +/// \file SerialRecorder.h +/// \brief Implementation of serial recorder. +/// \author Richard Moore +/// \copyright CC BY-NC-SA 3.0 + +#pragma once + +#ifdef __linux__ +#include "SerialRecorder_linux.h" +#elif _WIN32 +#include "SerialRecorder_win.h" +#endif diff --git a/include/SerialRecorder_linux.h b/include/SerialRecorder_linux.h new file mode 100644 index 0000000..4bae07d --- /dev/null +++ b/include/SerialRecorder_linux.h @@ -0,0 +1,25 @@ +/// FicTrac http://rjdmoore.net/fictrac/ +/// \file SerialRecorder_linux.h +/// \brief Linux implementation of serial recorder. +/// \author Richard Moore +/// \copyright CC BY-NC-SA 3.0 + +#pragma once + +#include "RecorderInterface.h" + +#include + +class SerialRecorder : public RecorderInterface +{ +public: + SerialRecorder(); + ~SerialRecorder(); + + /// Interface to be overridden by implementations. + bool openRecord(std::string port_baud); + bool writeRecord(std::string s); + void closeRecord(); + +private: +}; diff --git a/include/SerialRecorder_win.h b/include/SerialRecorder_win.h new file mode 100644 index 0000000..d77f92c --- /dev/null +++ b/include/SerialRecorder_win.h @@ -0,0 +1,27 @@ +/// FicTrac http://rjdmoore.net/fictrac/ +/// \file SerialRecorder_win.h +/// \brief Windows implementation of serial recorder. +/// \author Richard Moore +/// \copyright CC BY-NC-SA 3.0 + +#pragma once + +#include "RecorderInterface.h" + +#include +#include + +class SerialRecorder : public RecorderInterface +{ +public: + SerialRecorder(); + ~SerialRecorder(); + + /// Interface to be overridden by implementations. + bool openRecord(std::string port_baud); + bool writeRecord(std::string s); + void closeRecord(); + +private: + HANDLE _commHandle; +}; diff --git a/include/Serial_win.h b/include/Serial_win.h new file mode 100644 index 0000000..8a7a13e --- /dev/null +++ b/include/Serial_win.h @@ -0,0 +1,66 @@ +/** Serial.h + * + * A very simple serial port control class that does NOT require MFC/AFX. + * + * License: This source code can be used and/or modified without restrictions. + * It is provided as is and the author disclaims all warranties, expressed + * or implied, including, without limitation, the warranties of + * merchantability and of fitness for any purpose. The user must assume the + * entire risk of using the Software. + * + * @author Hans de Ruiter + * + * @version 0.1 -- 28 October 2008 + */ + +#ifndef __SERIAL_H__ +#define __SERIAL_H__ + +#include +#include + +typedef std::basic_string tstring; + +class Serial +{ +private: + HANDLE commHandle; + +public: + Serial(tstring &commPortName, int bitRate = 115200); + + virtual ~Serial(); + + /** Writes a NULL terminated string. + * + * @param buffer the string to send + * + * @return int the number of characters written + */ + int write(const char buffer[]); + + /** Writes a string of bytes to the serial port. + * + * @param buffer pointer to the buffer containing the bytes + * @param buffLen the number of bytes in the buffer + * + * @return int the number of bytes written + */ + int write(const char *buffer, int buffLen); + + /** Reads a string of bytes from the serial port. + * + * @param buffer pointer to the buffer to be written to + * @param buffLen the size of the buffer + * @param nullTerminate if set to true it will null terminate the string + * + * @return int the number of bytes read + */ + int read(char *buffer, int buffLen, bool nullTerminate = true); + + /** Flushes everything from the serial port's read buffer + */ + void flush(); +}; + +#endif diff --git a/include/SocketRecorder.h b/include/SocketRecorder.h index e28ac08..1f1c85c 100644 --- a/include/SocketRecorder.h +++ b/include/SocketRecorder.h @@ -9,5 +9,5 @@ #ifdef __linux__ #include "SocketRecorder_linux.h" #elif _WIN32 -#include "SocketRecorder_winsocket.h" +#include "SocketRecorder_win.h" #endif diff --git a/include/SocketRecorder_winsocket.h b/include/SocketRecorder_win.h similarity index 93% rename from include/SocketRecorder_winsocket.h rename to include/SocketRecorder_win.h index b307319..fcda3c2 100644 --- a/include/SocketRecorder_winsocket.h +++ b/include/SocketRecorder_win.h @@ -1,5 +1,5 @@ /// FicTrac http://rjdmoore.net/fictrac/ -/// \file SocketRecorder_winsocket.h +/// \file SocketRecorder_win.h /// \brief Windows implementation of socket recorder. /// \author Richard Moore /// \copyright CC BY-NC-SA 3.0 diff --git a/include/Trackball.h b/include/Trackball.h index 07dd30b..6399e90 100644 --- a/include/Trackball.h +++ b/include/Trackball.h @@ -127,8 +127,8 @@ class Trackball /// Data i/o. std::string _base_fn; std::unique_ptr _frameGrabber; - bool _do_sock_output; - std::unique_ptr _data_log, _data_sock; + bool _do_sock_output, _do_com_output; + std::unique_ptr _data_log, _data_sock, _data_com; /// Thread stuff. std::atomic_bool _active, _kill, _do_reset; diff --git a/src/Recorder.cpp b/src/Recorder.cpp index 5dea678..5924b32 100644 --- a/src/Recorder.cpp +++ b/src/Recorder.cpp @@ -9,6 +9,7 @@ #include "TermRecorder.h" #include "FileRecorder.h" #include "SocketRecorder.h" +#include "SerialRecorder.h" #include "misc.h" // thread priority #include // cout/cerr @@ -28,6 +29,8 @@ Recorder::Recorder(RecorderInterface::RecordType type, string fn) break; case RecorderInterface::RecordType::SOCK: _record = make_unique(); + case RecorderInterface::RecordType::COM: + _record = make_unique(); default: break; } diff --git a/src/SerialRecorder.cpp b/src/SerialRecorder.cpp new file mode 100644 index 0000000..c75d0e6 --- /dev/null +++ b/src/SerialRecorder.cpp @@ -0,0 +1,11 @@ +/// FicTrac http://rjdmoore.net/fictrac/ +/// \file SerialRecorder.cpp +/// \brief Implementation of serial recorder. +/// \author Richard Moore +/// \copyright CC BY-NC-SA 3.0 + +#ifdef __linux__ +#include "SerialRecorder_linux.src" +#elif _WIN32 +#include "SerialRecorder_win.src" +#endif diff --git a/src/SerialRecorder_linux.src b/src/SerialRecorder_linux.src new file mode 100644 index 0000000..b075c75 --- /dev/null +++ b/src/SerialRecorder_linux.src @@ -0,0 +1,49 @@ +/// FicTrac http://rjdmoore.net/fictrac/ +/// \file SerialRecorder_linux.cpp +/// \brief Linux implementation of serial recorder. +/// \author Richard Moore +/// \copyright CC BY-NC-SA 3.0 + +#include "SerialRecorder_linux.h" + +#include "Logger.h" + + +/// +/// +/// +SerialRecorder::SerialRecorder() +{ + _type = COM; +} + +/// +/// +/// +SerialRecorder::~SerialRecorder() +{ + closeRecord(); +} + +/// +/// +/// +bool SerialRecorder::openRecord(std::string port_baud) +{ + return false; +} + +/// +/// +/// +bool SerialRecorder::writeRecord(std::string s) +{ + return false; +} + +/// +/// +/// +void SerialRecorder::closeRecord() +{ +} diff --git a/src/SerialRecorder_win.src b/src/SerialRecorder_win.src new file mode 100644 index 0000000..3b9125e --- /dev/null +++ b/src/SerialRecorder_win.src @@ -0,0 +1,106 @@ +/// FicTrac http://rjdmoore.net/fictrac/ +/// \file SerialRecorder_win.cpp +/// \brief Windows implementation of serial recorder. +/// \author Richard Moore +/// \copyright CC BY-NC-SA 3.0 + +#include "SerialRecorder_win.h" + +#include "Logger.h" + +#include + +using namespace std; + +typedef std::basic_string tstring; + +/// +/// +/// +SerialRecorder::SerialRecorder() +{ + _type = COM; +} + +/// +/// +/// +SerialRecorder::~SerialRecorder() +{ + closeRecord(); +} + +/// +/// +/// +bool SerialRecorder::openRecord(std::string port_baud) +{ + // extract port no + size_t pos = port_baud.find_first_of('/'); + if (pos == string::npos) { + LOG_ERR("Error! Malformed port:baud string."); + return false; + } + + string port = port_baud.substr(0, pos); + int baud = stoi(port_baud.substr(pos + 1)); + + _commHandle = CreateFile(port.c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + if(_commHandle == INVALID_HANDLE_VALUE) { + LOG_ERR("Error! Could not open com port (%s).", port.c_str()); + return false; + } + else { + // set timeouts + COMMTIMEOUTS cto = { MAXDWORD, 0, 0, 0, 0 }; + DCB dcb; + if(!SetCommTimeouts(_commHandle,&cto)) { + LOG_ERR("Error! Could not set com port time-outs."); + return false; + } + + // set DCB + memset(&dcb,0,sizeof(dcb)); + dcb.DCBlength = sizeof(dcb); + dcb.BaudRate = baud; + dcb.fBinary = 1; + dcb.fDtrControl = DTR_CONTROL_ENABLE; + dcb.fRtsControl = RTS_CONTROL_ENABLE; + + dcb.Parity = NOPARITY; + dcb.StopBits = ONESTOPBIT; + dcb.ByteSize = 8; + + if(!SetCommState(_commHandle,&dcb)) { + LOG_ERR("Error! Could not set com port parameters."); + return false; + } + } + + return (_open = true); +} + +/// +/// +/// +bool SerialRecorder::writeRecord(std::string s) +{ + if (_open) { + DWORD numWritten; + WriteFile(_commHandle, s.c_str(), s.size(), &numWritten, NULL); + if (numWritten <= 0) { + LOG_ERR("Error! No bytes written to com port."); + return false; + } + } + return _open; +} + +/// +/// +/// +void SerialRecorder::closeRecord() +{ + _open = false; + CloseHandle(_commHandle); +} diff --git a/src/Serial_win.src b/src/Serial_win.src new file mode 100644 index 0000000..f81972f --- /dev/null +++ b/src/Serial_win.src @@ -0,0 +1,109 @@ +/** Serial.cpp + * + * A very simple serial port control class that does NOT require MFC/AFX. + * + * @author Hans de Ruiter + * + * @version 0.1 -- 28 October 2008 + */ + +#include +using namespace std; + +#include "Serial.h" + +Serial::Serial(tstring &commPortName, int bitRate) +{ + commHandle = CreateFile(commPortName.c_str(), GENERIC_READ|GENERIC_WRITE, 0,NULL, OPEN_EXISTING, + 0, NULL); + + if(commHandle == INVALID_HANDLE_VALUE) + { + throw("ERROR: Could not open com port"); + } + else + { + // set timeouts + COMMTIMEOUTS cto = { MAXDWORD, 0, 0, 0, 0}; + DCB dcb; + if(!SetCommTimeouts(commHandle,&cto)) + { + Serial::~Serial(); + throw("ERROR: Could not set com port time-outs"); + } + + // set DCB + memset(&dcb,0,sizeof(dcb)); + dcb.DCBlength = sizeof(dcb); + dcb.BaudRate = bitRate; + dcb.fBinary = 1; + dcb.fDtrControl = DTR_CONTROL_ENABLE; + dcb.fRtsControl = RTS_CONTROL_ENABLE; + + dcb.Parity = NOPARITY; + dcb.StopBits = ONESTOPBIT; + dcb.ByteSize = 8; + + if(!SetCommState(commHandle,&dcb)) + { + Serial::~Serial(); + throw("ERROR: Could not set com port parameters"); + } + } +} + +Serial::~Serial() +{ + CloseHandle(commHandle); +} + +int Serial::write(const char *buffer) +{ + DWORD numWritten; + WriteFile(commHandle, buffer, strlen(buffer), &numWritten, NULL); + + return numWritten; +} + +int Serial::write(const char *buffer, int buffLen) +{ + DWORD numWritten; + WriteFile(commHandle, buffer, buffLen, &numWritten, NULL); + + return numWritten; +} + +int Serial::read(char *buffer, int buffLen, bool nullTerminate) +{ + DWORD numRead; + if(nullTerminate) + { + --buffLen; + } + + BOOL ret = ReadFile(commHandle, buffer, buffLen, &numRead, NULL); + + if(!ret) + { + return 0; + } + + if(nullTerminate) + { + buffer[numRead] = '\0'; + } + + return numRead; +} + +#define FLUSH_BUFFSIZE 10 + +void Serial::flush() +{ + char buffer[FLUSH_BUFFSIZE]; + int numBytes = read(buffer, FLUSH_BUFFSIZE, false); + while(numBytes != 0) + { + numBytes = read(buffer, FLUSH_BUFFSIZE, false); + } +} diff --git a/src/SocketRecorder.cpp b/src/SocketRecorder.cpp index 51a8bf9..c7fe566 100644 --- a/src/SocketRecorder.cpp +++ b/src/SocketRecorder.cpp @@ -7,5 +7,5 @@ #ifdef __linux__ #include "SocketRecorder_linux.src" #elif _WIN32 -#include "SocketRecorder_winsocket.src" +#include "SocketRecorder_win.src" #endif diff --git a/src/SocketRecorder_winsocket.src b/src/SocketRecorder_win.src similarity index 97% rename from src/SocketRecorder_winsocket.src rename to src/SocketRecorder_win.src index 35f322e..243d1e9 100644 --- a/src/SocketRecorder_winsocket.src +++ b/src/SocketRecorder_win.src @@ -1,10 +1,10 @@ /// FicTrac http://rjdmoore.net/fictrac/ -/// \file SocketRecorder_winsocket.cpp +/// \file SocketRecorder_win.cpp /// \brief Windows implementation of socket recorder. /// \author Richard Moore /// \copyright CC BY-NC-SA 3.0 -#include "SocketRecorder_winsocket.h" +#include "SocketRecorder_win.h" #include "Logger.h" diff --git a/src/Trackball.cpp b/src/Trackball.cpp index f47e500..c629d4a 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -49,7 +49,9 @@ const double THRESH_WIN_PC_DEFAULT = 0.25; const uint8_t SPHERE_MAP_FIRST_HIT_BONUS = 64; -const int OUT_PORT_DEFAULT = -1; +const int SOCK_PORT_DEFAULT = -1; + +const int COM_BAUD_DEFAULT = 115200; const bool DO_DISPLAY_DEFAULT = true; const bool SAVE_RAW_DEFAULT = false; @@ -381,19 +383,40 @@ Trackball::Trackball(string cfg_fn) return; } - int out_port = OUT_PORT_DEFAULT; + int sock_port = SOCK_PORT_DEFAULT; _do_sock_output = false; - if (_cfg.getInt("out_port", out_port) && (out_port > 0)) { - _data_sock = make_unique(RecorderInterface::RecordType::SOCK, std::to_string(out_port)); + if (_cfg.getInt("sock_port", sock_port) && (sock_port > 0)) { + _data_sock = make_unique(RecorderInterface::RecordType::SOCK, std::to_string(sock_port)); if (!_data_sock->is_active()) { - LOG_ERR("Error! Unable to open output data socket (%d).", out_port); + LOG_ERR("Error! Unable to open output data socket (%d).", sock_port); _active = false; return; } _do_sock_output = true; } else { - LOG_WRN("Warning! Using default value for out_port (%d).", out_port); - _cfg.add("out_port", out_port); + LOG_WRN("Warning! Using default value for sock_port (%d).", sock_port); + _cfg.add("sock_port", sock_port); + } + + string com_port = _cfg("com_port"); + _do_com_output = false; + if (com_port.length() > 0) { + int com_baud = COM_BAUD_DEFAULT; + if (!_cfg.getInt("com_baud", com_baud)) { + LOG_WRN("Warning! Using default value for com_baud (%d).", com_baud); + _cfg.add("com_baud", com_baud); + } + + _data_com = make_unique(RecorderInterface::RecordType::COM, com_port + "/" + std::to_string(com_baud)); + if (!_data_com->is_active()) { + LOG_ERR("Error! Unable to open output data com port (%s/%d).", com_port.c_str(), com_baud); + _active = false; + return; + } + _do_com_output = true; + } + else { + _cfg.add("com_port", com_port); } /// Display. @@ -965,6 +988,9 @@ bool Trackball::logData() if (_do_sock_output) { ret &= _data_sock->addMsg("FT, " + ss.str()); } + if (_do_com_output) { + ret &= _data_com->addMsg("FT, " + ss.str()); + } ret &= _data_log->addMsg(ss.str()); return ret; } From 042aa5da8325f862aa1cb453f249ca8cc90c588a Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 5 May 2019 22:57:14 +0200 Subject: [PATCH 104/235] bundle trackball state variables --- include/Trackball.h | 77 ++++++++++++---- src/Trackball.cpp | 213 ++++++++++++++++++++++++-------------------- 2 files changed, 176 insertions(+), 114 deletions(-) diff --git a/include/Trackball.h b/include/Trackball.h index 6399e90..5787626 100644 --- a/include/Trackball.h +++ b/include/Trackball.h @@ -32,12 +32,71 @@ /// class Trackball { +public: + /// Data. + struct DATA { + // trackball state + unsigned int cnt, seq; + CmPoint64f dr_roi, r_roi; + cv::Mat R_roi; + CmPoint64f dr_cam, r_cam; + cv::Mat R_cam; + CmPoint64f dr_lab, r_lab; + cv::Mat R_lab; + double ts; + + double velx, vely, step_mag, step_dir, intx, inty, heading, posx, posy; + + // testing + double dist, ang_dist, step_avg, step_var, evals_avg; + + // constructors + DATA() + : cnt(0), seq(0), + dr_roi(CmPoint64f(0, 0, 0)), r_roi(CmPoint64f(0, 0, 0)), + dr_cam(CmPoint64f(0, 0, 0)), r_cam(CmPoint64f(0, 0, 0)), + dr_lab(CmPoint64f(0, 0, 0)), r_lab(CmPoint64f(0, 0, 0)), + ts(-1), + velx(0), vely(0), + step_mag(0), step_dir(0), + intx(0), inty(0), + heading(0), posx(0), posy(0), + dist(0), ang_dist(0), + step_avg(0), step_var(0), + evals_avg(0) + { + R_roi = cv::Mat::eye(3, 3, CV_64F); + R_cam = cv::Mat::eye(3, 3, CV_64F); + R_lab = cv::Mat::eye(3, 3, CV_64F); + } + + DATA(const DATA &d) + : cnt(d.cnt), seq(d.seq), + dr_roi(d.dr_roi), r_roi(d.r_roi), + dr_cam(d.dr_cam), r_cam(d.r_cam), + dr_lab(d.dr_lab), r_lab(d.r_lab), + ts(d.ts), + velx(d.velx), vely(d.vely), + step_mag(d.step_mag), step_dir(d.step_dir), + intx(d.intx), inty(d.inty), + heading(d.heading), posx(d.posx), posy(d.posy), + dist(d.dist), ang_dist(d.ang_dist), + step_avg(d.step_avg), step_var(d.step_var), + evals_avg(d.evals_avg) + { + R_roi = d.R_roi.clone(); + R_cam = d.R_cam.clone(); + R_lab = d.R_lab.clone(); + } + }; + public: Trackball(std::string cfg_fn); ~Trackball(); bool isActive() { return _active; } void terminate() { _kill = true; } + std::shared_ptr getState(); void dumpState(); bool writeTemplate(std::string fn = ""); @@ -45,6 +104,7 @@ class Trackball /// Worker function. void process(); + void resetData(); void reset(); double testRotation(const double x[3]); virtual double objective(unsigned n, const double* x, double* grad) { return testRotation(x); } @@ -80,7 +140,6 @@ class Trackball std::unique_ptr _drawThread; private: - ConfigParser _cfg; /// Camera models and remapping. @@ -109,20 +168,8 @@ class Trackball /// Program. bool _init, _reset, _clean_map; - /// Data. - unsigned int _cnt, _seq; - CmPoint64f _dr_roi, _r_roi; - cv::Mat _R_roi; - CmPoint64f _dr_cam, _r_cam; - cv::Mat _R_cam; - CmPoint64f _dr_lab, _r_lab; - cv::Mat _R_lab; - double _ts; - - double _velx, _vely, _step_mag, _step_dir, _intx, _inty, _heading, _posx, _posy; - - // test data - double _dist, _ang_dist, _step_avg, _step_var, _evals_avg; + /// Data + DATA _data; /// Data i/o. std::string _base_fn; diff --git a/src/Trackball.cpp b/src/Trackball.cpp index c629d4a..c531ace 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -511,11 +511,13 @@ Trackball::Trackball(string cfg_fn) _cfg.write(); /// Data. - _cnt = 0; - _err = 0; - _intx = _inty = 0; reset(); + // not reset in resetData because they are not affected by heading reset + _data.cnt = 0; + _data.intx = _data.inty = 0; + _err = 0; + /// Thread stuff. _init = true; _active = true; @@ -546,6 +548,19 @@ Trackball::~Trackball() } } +/// +/// +/// +void Trackball::resetData() +{ + DATA new_data; + new_data.cnt = _data.cnt; // preserve cnt across resets (but reset seq) + new_data.intx = _data.intx; // can preserve intx/y because they're not affected by heading reset + new_data.inty = _data.inty; + + _data = new_data; +} + /// /// /// @@ -560,21 +575,7 @@ void Trackball::reset() _clean_map = true; } - /// Reset sphere. - _R_roi = Mat::eye(3, 3, CV_64F); - - /// Reset data. - _seq = 0; // indicates new sequence started - _posx = 0; // reset because heading was lost - _posy = 0; - _heading = 0; - - // test data - _dist = 0; - _ang_dist = 0; - _step_avg = 0; - _step_var = 0; - _evals_avg = 0; + resetData(); /// Drawing. if (_do_display) { @@ -605,11 +606,11 @@ void Trackball::process() double t1, t2, t3, t4, t5, t6; double t1avg = 0, t2avg = 0, t3avg = 0, t4avg = 0, t5avg = 0, t6avg = 0; double tfirst = -1, tlast = 0; - while (!_kill && _active && _frameGrabber->getNextFrameSet(_src_frame, _roi_frame, _ts)) { + while (!_kill && _active && _frameGrabber->getNextFrameSet(_src_frame, _roi_frame, _data.ts)) { t1 = ts_ms(); PRINT(""); - LOG("Frame %d", _cnt); + LOG("Frame %d", _data.cnt); /// Handle reset request if (_do_reset) { @@ -642,7 +643,7 @@ void Trackball::process() nbad = 0; reset(); } else { - _seq++; + _data.seq++; } if (_do_display) { @@ -651,8 +652,8 @@ void Trackball::process() data->roi_frame = _roi_frame.clone(); data->sphere_map = _sphere_map.clone(); data->sphere_view = _sphere_view.clone(); - data->dr_roi = _dr_roi; - data->R_roi = _R_roi.clone(); + data->dr_roi = _data.dr_roi; + data->R_roi = _data.R_roi.clone(); data->R_roi_hist = _R_roi_hist; data->pos_heading_hist = _pos_heading_hist; @@ -661,7 +662,7 @@ void Trackball::process() t6 = ts_ms(); /// Timing. - if (_cnt > 0) { // skip first frame (often global search...) + if (_data.cnt > 0) { // skip first frame (often global search...) t1avg += t1 - t0; t2avg += t2 - t1; t3avg += t3 - t2; @@ -670,7 +671,7 @@ void Trackball::process() t6avg += t6 - t5; // opt evals - _evals_avg += _nevals; + _data.evals_avg += _nevals; } LOG("Timing grab/opt/map/plot/log/disp: %.1f / %.1f / %.1f / %.1f / %.1f / %.1f ms", t1 - t0, t2 - t1, t3 - t2, t4 - t3, t5 - t4, t6 - t5); @@ -678,14 +679,14 @@ void Trackball::process() double fps_out = (t6 - prev_t6) > 0 ? 1000 / (t6 - prev_t6) : 0; static double fps_avg = fps_out; fps_avg += 0.25 * (fps_out - fps_avg); - static double prev_ts = _ts; - double fps_in = (_ts - prev_ts) > 0 ? 1000 / (_ts - prev_ts) : 0; + static double prev_ts = _data.ts; + double fps_in = (_data.ts - prev_ts) > 0 ? 1000 / (_data.ts - prev_ts) : 0; LOG("Average frame rate [in/out]: %.1f [%.1f / %.1f] fps", fps_avg, fps_in, fps_out); prev_t6 = t6; - prev_ts = _ts; + prev_ts = _data.ts; /// Always increment frame counter. - _cnt++; + _data.cnt++; t0 = ts_ms(); if (tfirst < 0) { tfirst = t0; } @@ -696,16 +697,16 @@ void Trackball::process() _frameGrabber->terminate(); // make sure we've stopped grabbing frames as well - if (_cnt > 1) { + if (_data.cnt > 1) { PRINT("\n----------------------------------------------------------------------------"); LOG("Trackball timing:"); LOG("Average grab/opt/map/plot/log/disp time: %.1f / %.1f / %.1f / %.1f / %.1f / %.1f ms", - t1avg / (_cnt - 1), t2avg / (_cnt - 1), t3avg / (_cnt - 1), t4avg / (_cnt - 1), t5avg / (_cnt - 1), t6avg / (_cnt - 1)); - LOG("Average fps: %.2f", 1000. * (_cnt - 1) / (tlast - tfirst)); + t1avg / (_data.cnt - 1), t2avg / (_data.cnt - 1), t3avg / (_data.cnt - 1), t4avg / (_data.cnt - 1), t5avg / (_data.cnt - 1), t6avg / (_data.cnt - 1)); + LOG("Average fps: %.2f", 1000. * (_data.cnt - 1) / (tlast - tfirst)); PRINT(""); LOG("Optimiser test data:"); - LOG("Average number evals / frame: %.1f", _evals_avg / (_cnt - 1)); + LOG("Average number evals / frame: %.1f", _data.evals_avg / (_data.cnt - 1)); PRINT("----------------------------------------------------------------------------"); } @@ -724,12 +725,12 @@ bool Trackball::doSearch(bool allow_global = false) /// Run optimisation and save result. _nevals = 0; if (!_reset) { - _dr_roi = guess; - _err = _localOpt->search(_roi_frame, _R_roi, _dr_roi); // _dr_roi contains optimal rotation + _data.dr_roi = guess; + _err = _localOpt->search(_roi_frame, _data.R_roi, _data.dr_roi); // _dr_roi contains optimal rotation _nevals = _localOpt->getNumEval(); } else { - _dr_roi = CmPoint64f(0, 0, 0); + _data.dr_roi = CmPoint64f(0, 0, 0); _err = 0; } @@ -740,31 +741,31 @@ bool Trackball::doSearch(bool allow_global = false) LOG("Doing global search.."); // do global search - _err = _globalOpt->search(_roi_frame, _R_roi, _r_roi); // use last know orientation, _r_roi, as guess and update with result + _err = _globalOpt->search(_roi_frame, _data.R_roi, _data.r_roi); // use last know orientation, _r_roi, as guess and update with result _nevals = _globalOpt->getNumEval(); bad_frame = _error_thresh >= 0 ? (_err > _error_thresh) : false; // if global search failed as well, just reset global orientation too if (bad_frame) { - _r_roi = CmPoint64f(0, 0, 0); // zero absolute orientation + _data.r_roi = CmPoint64f(0, 0, 0); // zero absolute orientation } // reset sphere to found orientation with zero motion - _dr_roi = CmPoint64f(0, 0, 0); // zero relative rotation - _R_roi = CmPoint64f::omegaToMatrix(_r_roi); + _data.dr_roi = CmPoint64f(0, 0, 0); // zero relative rotation + _data.R_roi = CmPoint64f::omegaToMatrix(_data.r_roi); } else { /// Accumulate sphere orientation. - Mat tmpR = CmPoint64f::omegaToMatrix(_dr_roi); // relative rotation (angle-axis) in ROI frame - _R_roi = tmpR * _R_roi; // pre-multiply to accumulate orientation matrix - _r_roi = CmPoint64f::matrixToOmega(_R_roi); + Mat tmpR = CmPoint64f::omegaToMatrix(_data.dr_roi); // relative rotation (angle-axis) in ROI frame + _data.R_roi = tmpR * _data.R_roi; // pre-multiply to accumulate orientation matrix + _data.r_roi = CmPoint64f::matrixToOmega(_data.R_roi); } - LOG("optimum sphere rotation:\t%.3f %.3f %.3f (err=%.3e/its=%d)", _dr_roi[0], _dr_roi[1], _dr_roi[2], _err, _nevals); - LOG_DBG("Current sphere orientation:\t%.3f %.3f %.3f", _r_roi[0], _r_roi[1], _r_roi[2]); + LOG("optimum sphere rotation:\t%.3f %.3f %.3f (err=%.3e/its=%d)", _data.dr_roi[0], _data.dr_roi[1], _data.dr_roi[2], _err, _nevals); + LOG_DBG("Current sphere orientation:\t%.3f %.3f %.3f", _data.r_roi[0], _data.r_roi[1], _data.r_roi[2]); if (!bad_frame) { - guess = 0.9 * _dr_roi + 0.1 * guess; + guess = 0.9 * _data.dr_roi + 0.1 * guess; } else { guess = CmPoint64f(0,0,0); } @@ -777,7 +778,7 @@ bool Trackball::doSearch(bool allow_global = false) /// void Trackball::updateSphere() { - double* m = reinterpret_cast(_R_roi.data); // absolute orientation (3d mat) in ROI frame + double* m = reinterpret_cast(_data.R_roi.data); // absolute orientation (3d mat) in ROI frame if (_do_display) { _sphere_view.setTo(Scalar::all(128)); @@ -846,25 +847,25 @@ void Trackball::updatePath() // _R_roi // abs vec roi - _r_roi = CmPoint64f::matrixToOmega(_R_roi); + _data.r_roi = CmPoint64f::matrixToOmega(_data.R_roi); // rel vec cam - _dr_cam = _dr_roi/*.getTransformed(_roi_to_cam_R)*/; + _data.dr_cam = _data.dr_roi/*.getTransformed(_roi_to_cam_R)*/; // abs mat cam - _R_cam = /*_roi_to_cam_R * */_R_roi; + _data.R_cam = /*_roi_to_cam_R * */_data.R_roi; // abs vec cam - _r_cam = CmPoint64f::matrixToOmega(_R_cam); + _data.r_cam = CmPoint64f::matrixToOmega(_data.R_cam); // rel vec world - _dr_lab = _dr_cam.getTransformed(_cam_to_lab_R); + _data.dr_lab = _data.dr_cam.getTransformed(_cam_to_lab_R); // abs mat world - _R_lab = _cam_to_lab_R * _R_cam; + _data.R_lab = _cam_to_lab_R * _data.R_cam; // abs vec world - _r_lab = CmPoint64f::matrixToOmega(_R_lab); + _data.r_lab = CmPoint64f::matrixToOmega(_data.R_lab); //// store initial rotation from template (if any) @@ -892,64 +893,64 @@ void Trackball::updatePath() // running speed, radians/frame (-ve rotation around x-axis causes y-axis translation & vice-versa!!) - _velx = _dr_lab[1]; - _vely = -_dr_lab[0]; - _step_mag = sqrt(_velx * _velx + _vely * _vely); // magnitude (radians) of ball rotation excluding turning (change in heading) + _data.velx = _data.dr_lab[1]; + _data.vely = -_data.dr_lab[0]; + _data.step_mag = sqrt(_data.velx * _data.velx + _data.vely * _data.vely); // magnitude (radians) of ball rotation excluding turning (change in heading) // test data - if (_cnt > 0) { - _dist += _step_mag; - double v = _dr_lab.len(); - double delta = v - _step_avg; - _step_avg += delta / static_cast(_cnt); // running average - double delta2 = v - _step_avg; - _step_var += delta * delta2; // running variance (Welford's alg) + if (_data.cnt > 0) { + _data.dist += _data.step_mag; + double v = _data.dr_lab.len(); + double delta = v - _data.step_avg; + _data.step_avg += delta / static_cast(_data.cnt); // running average + double delta2 = v - _data.step_avg; + _data.step_var += delta * delta2; // running variance (Welford's alg) } // running direction - _step_dir = atan2(_vely, _velx); - if (_step_dir < 0) { _step_dir += 360 * CM_D2R; } + _data.step_dir = atan2(_data.vely, _data.velx); + if (_data.step_dir < 0) { _data.step_dir += 360 * CM_D2R; } // integrated x/y pos (optical mouse style) - _intx += _velx; - _inty += _vely; + _data.intx += _data.velx; + _data.inty += _data.vely; // integrate bee heading - _heading -= _dr_lab[2]; - while (_heading < 0) { _heading += 360 * CM_D2R; } - while (_heading >= 360 * CM_D2R) { _heading -= 360 * CM_D2R; } - _ang_dist += abs(_dr_lab[2]); + _data.heading -= _data.dr_lab[2]; + while (_data.heading < 0) { _data.heading += 360 * CM_D2R; } + while (_data.heading >= 360 * CM_D2R) { _data.heading -= 360 * CM_D2R; } + _data.ang_dist += abs(_data.dr_lab[2]); // integrate 2d position { const int steps = 4; // increasing this doesn't help much - double step = _step_mag / steps; + double step = _data.step_mag / steps; static double prev_heading = 0; if (_reset) { prev_heading = 0; } - double heading_step = (_heading - prev_heading); + double heading_step = (_data.heading - prev_heading); while (heading_step >= 180 * CM_D2R) { heading_step -= 360 * CM_D2R; } while (heading_step < -180 * CM_D2R) { heading_step += 360 * CM_D2R; } heading_step /= steps; // do after wrapping above // super-res integration - CmPoint64f dir(_velx, _vely, 0); + CmPoint64f dir(_data.velx, _data.vely, 0); dir.normalise(); dir.rotateAboutNorm(CmPoint(0, 0, 1), prev_heading + heading_step / 2.0); for (int i = 0; i < steps; i++) { - _posx += step * dir[0]; - _posy += step * dir[1]; + _data.posx += step * dir[0]; + _data.posy += step * dir[1]; dir.rotateAboutNorm(CmPoint(0, 0, 1), heading_step); } - prev_heading = _heading; + prev_heading = _data.heading; } if (_do_display) { // update pos hist (in ROI-space!) - _R_roi_hist.push_back(_R_roi.clone()); + _R_roi_hist.push_back(_data.R_roi.clone()); while (_R_roi_hist.size() > DRAW_SPHERE_HIST_LENGTH) { _R_roi_hist.pop_front(); } - _pos_heading_hist.push_back(CmPoint(_posx, _posy, _heading)); + _pos_heading_hist.push_back(CmPoint(_data.posx, _data.posy, _data.heading)); while (_pos_heading_hist.size() > DRAW_FICTIVE_PATH_LENGTH) { _pos_heading_hist.pop_front(); } @@ -965,23 +966,23 @@ bool Trackball::logData() ss.precision(14); // frame_count - ss << _cnt << ", "; + ss << _data.cnt << ", "; // rel_vec_cam[3] | error - ss << _dr_cam[0] << ", " << _dr_cam[1] << ", " << _dr_cam[2] << ", " << _err << ", "; + ss << _data.dr_cam[0] << ", " << _data.dr_cam[1] << ", " << _data.dr_cam[2] << ", " << _err << ", "; // rel_vec_world[3] - ss << _dr_lab[0] << ", " << _dr_lab[1] << ", " << _dr_lab[2] << ", "; + ss << _data.dr_lab[0] << ", " << _data.dr_lab[1] << ", " << _data.dr_lab[2] << ", "; // abs_vec_cam[3] - ss << _r_cam[0] << ", " << _r_cam[1] << ", " << _r_cam[2] << ", "; + ss << _data.r_cam[0] << ", " << _data.r_cam[1] << ", " << _data.r_cam[2] << ", "; // abs_vec_world[3] - ss << _r_lab[0] << ", " << _r_lab[1] << ", " << _r_lab[2] << ", "; + ss << _data.r_lab[0] << ", " << _data.r_lab[1] << ", " << _data.r_lab[2] << ", "; // integrated xpos | integrated ypos | integrated heading - ss << _posx << ", " << _posy << ", " << _heading << ", "; + ss << _data.posx << ", " << _data.posy << ", " << _data.heading << ", "; // direction (radians) | speed (radians/frame) - ss << _step_dir << ", " << _step_mag << ", "; + ss << _data.step_dir << ", " << _data.step_mag << ", "; // integrated x movement | integrated y movement (mouse output equivalent) - ss << _intx << ", " << _inty << ", "; + ss << _data.intx << ", " << _data.inty << ", "; // timestamp | sequence number - ss << _ts << ", " << _seq << std::endl; + ss << _data.ts << ", " << _data.seq << std::endl; // async i/o bool ret = true; @@ -1002,9 +1003,9 @@ double Trackball::testRotation(const double x[3]) { static double lmat[9]; CmPoint64f tmp(x[0], x[1], x[2]); - tmp.omegaToMatrix(lmat); // relative rotation in camera frame - double* rmat = (double*)_R_roi.data; // pre-multiply to orientation matrix - static double m[9]; // absolute orientation in camera frame + tmp.omegaToMatrix(lmat); // relative rotation in camera frame + double* rmat = (double*)_data.R_roi.data; // pre-multiply to orientation matrix + static double m[9]; // absolute orientation in camera frame m[0] = lmat[0] * rmat[0] + lmat[1] * rmat[3] + lmat[2] * rmat[6]; m[1] = lmat[0] * rmat[1] + lmat[1] * rmat[4] + lmat[2] * rmat[7]; @@ -1413,20 +1414,34 @@ void Trackball::drawCanvas(shared_ptr data) } } +/// +/// +/// +shared_ptr Trackball::getState() +{ + return make_shared(_data); +} + +/// +/// +/// void Trackball::dumpState() { PRINT("\n----------------------------------------------------------------------"); PRINT("Trackball state"); - PRINT("Sphere orientation (cam): %f %f %f", _r_cam[0], _r_cam[1], _r_cam[2]); - PRINT("Total heading rotation: %f deg", _ang_dist * CM_R2D); - PRINT("Heading direction: %f deg (%f %% total heading rotation)", _heading * CM_R2D, _heading * 100. / _ang_dist); - PRINT("Accumulated X/Y motion: %f / %f rad (%f / %f * 2pi)", _intx, _inty, _intx / (2 * CM_PI), _inty / (2 * CM_PI)); - PRINT("Distance travelled: %f rad (%f * 2pi)", _dist, _dist / (2 * CM_PI)); - PRINT("Integrated X/Y position: (%.3e, %.3e) rad (%f / %f %% total path length)", _posx, _posy, _posx * 100. / _dist, _posy * 100. / _dist); - PRINT("Average/stdev rotation: %.3e / %.3e rad/frame", _step_avg, sqrt(_step_var / _cnt)); // population variance + PRINT("Sphere orientation (cam): %f %f %f", _data.r_cam[0], _data.r_cam[1], _data.r_cam[2]); + PRINT("Total heading rotation: %f deg", _data.ang_dist * CM_R2D); + PRINT("Heading direction: %f deg (%f %% total heading rotation)", _data.heading * CM_R2D, _data.heading * 100. / _data.ang_dist); + PRINT("Accumulated X/Y motion: %f / %f rad (%f / %f * 2pi)", _data.intx, _data.inty, _data.intx / (2 * CM_PI), _data.inty / (2 * CM_PI)); + PRINT("Distance travelled: %f rad (%f * 2pi)", _data.dist, _data.dist / (2 * CM_PI)); + PRINT("Integrated X/Y position: (%.3e, %.3e) rad (%f / %f %% total path length)", _data.posx, _data.posy, _data.posx * 100. / _data.dist, _data.posy * 100. / _data.dist); + PRINT("Average/stdev rotation: %.3e / %.3e rad/frame", _data.step_avg, sqrt(_data.step_var / _data.cnt)); // population variance PRINT("\n----------------------------------------------------------------------"); } +/// +/// +/// bool Trackball::writeTemplate(std::string fn) { if (!_init) { return false; } From 5b8d022ab81b498889a136edad0f4d028c80fd7f Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 5 May 2019 23:15:49 +0200 Subject: [PATCH 105/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a0793d2..3a65536 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,6 +6,7 @@ trigger: - master +- develop # We can run multiple jobs in parallel. # see https://docs.microsoft.com/en-us/azure/devops/pipelines/process/phases From 3a8bd3c3ff0609722ff521c1b6ad258d3a6bf7dc Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 5 May 2019 23:16:29 +0200 Subject: [PATCH 106/235] possible fix for IntegerNode error when getting camera timestamp for PGR cameras --- src/PGRSource.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PGRSource.cpp b/src/PGRSource.cpp index 07f5875..f3eaf27 100644 --- a/src/PGRSource.cpp +++ b/src/PGRSource.cpp @@ -241,7 +241,7 @@ bool PGRSource::grab(cv::Mat& frame) long int timeout = _fps > 0 ? std::max(static_cast(1000), static_cast(1000. / _fps)) : 1000; // set capture timeout to at least 1000 ms pgr_image = _cam->GetNextImage(timeout); double ts = ts_ms(); // backup, in case the device timestamp is junk - _timestamp = _cam->Timestamp(); + pgr_image->GetTimeStamp(); LOG_DBG("Frame captured %dx%d%d @ %f (%f)", pgr_image->GetWidth(), pgr_image->GetHeight(), pgr_image->GetNumChannels(), _timestamp, ts); if (_timestamp <= 0) { _timestamp = ts; From 016d92cefc2d0676d1e239f24b3dfe5d0610b2e5 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 5 May 2019 23:18:16 +0200 Subject: [PATCH 107/235] possible fix for IntegerNode error when getting camera timestamp for PGR cameras --- src/PGRSource.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PGRSource.cpp b/src/PGRSource.cpp index f3eaf27..e69c50d 100644 --- a/src/PGRSource.cpp +++ b/src/PGRSource.cpp @@ -241,7 +241,7 @@ bool PGRSource::grab(cv::Mat& frame) long int timeout = _fps > 0 ? std::max(static_cast(1000), static_cast(1000. / _fps)) : 1000; // set capture timeout to at least 1000 ms pgr_image = _cam->GetNextImage(timeout); double ts = ts_ms(); // backup, in case the device timestamp is junk - pgr_image->GetTimeStamp(); + _timestamp = pgr_image->GetTimeStamp(); LOG_DBG("Frame captured %dx%d%d @ %f (%f)", pgr_image->GetWidth(), pgr_image->GetHeight(), pgr_image->GetNumChannels(), _timestamp, ts); if (_timestamp <= 0) { _timestamp = ts; From 2cd5b6ed794a68b4efa55443ddf77f9df9058b33 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 6 May 2019 08:55:51 +0200 Subject: [PATCH 108/235] Update requirements.md --- doc/requirements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.md b/doc/requirements.md index 003c8ef..6a645a2 100644 --- a/doc/requirements.md +++ b/doc/requirements.md @@ -53,7 +53,7 @@ FicTrac can be built for both Windows and Ubuntu (Linux) operating systems. Ther For best performance, the processor should be reasonably fast (>2 GHz) and should be multi-core (ideally 4+). FicTrac uses <1 GB RAM. -On a ~3.2 GHz quadcore processor processor, and with default configuration settings (`q_factor : 6`), FicTrac runs at ~220 FPS. At a quality setting, (`q_factor : 4`), on the same machine, FicTrac runs at ~450 FPS. +On a ~3.2 GHz quadcore processor processor, and with default configuration settings (`q_factor : 6`), FicTrac runs at ~220 FPS. At a quality setting `q_factor : 4` on the same machine, FicTrac runs at ~450 FPS. ### Lighting From 31fc5846df67a48ff0533f287b93a9d9232464cc Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 7 May 2019 08:42:48 +0200 Subject: [PATCH 109/235] accept both windows and linux style return key press in config --- src/ConfigGUI.cpp | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index 6dadc1c..9d96332 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -425,16 +425,10 @@ bool ConfigGui::run() vector cfg_vec; vector> cfg_polys; changeState(CIRC_INIT); - const char exit_key = 0x1b; -#ifdef WIN32 - const char enter_key = 0x0d; -#else // !WIN32 - const char enter_key = 0x0a; -#endif // WIN32 const int click_rad = std::max(int(_w/150+0.5), 5); Mat disp_frame, zoom_frame(ZOOM_DIM, ZOOM_DIM, CV_8UC3); const int scaled_zoom_dim = static_cast(ZOOM_DIM * ZOOM_SCL + 0.5); - while (_open && (key != exit_key)) { + while (_open && (key != 0x1b)) { // esc /// Create frame for drawing. //cv::cvtColor(_frame, disp_frame, CV_GRAY2RGB); disp_frame = _frame.clone(); @@ -551,7 +545,7 @@ bool ConfigGui::run() key = cv::waitKey(5); /// State machine logic. - if (key == enter_key) { + if ((key == 0x0d) || (key == 0x0a)) { // return if (_input_data.circPts.size() >= 3) { // dump circumference points, c, and r to config file cfg_pts.clear(); @@ -684,7 +678,7 @@ bool ConfigGui::run() key = cv::waitKey(5); /// State machine logic. - if (key == enter_key) { + if ((key == 0x0d) || (key == 0x0a)) { // return // if current poly is empty, assume we've finished if (_input_data.ignrPts.empty() || _input_data.ignrPts.back().empty()) { if (!_input_data.ignrPts.empty()) { _input_data.ignrPts.pop_back(); } @@ -931,7 +925,7 @@ bool ConfigGui::run() key = cv::waitKey(5); /// State machine logic. - if (key == enter_key) { + if ((key == 0x0d) || (key == 0x0a)) { // return if ((_input_data.sqrPts.size() == 4) && !R.empty()) { // dump corner points to config file if (!saveC2ATransform(c2a_src, R, t)) { @@ -984,7 +978,7 @@ bool ConfigGui::run() key = cv::waitKey(5); /// State machine logic. - if (key == enter_key) { + if ((key == 0x0d) || (key == 0x0a)) { // return if ((_input_data.sqrPts.size() == 4) && !R.empty()) { // dump corner points to config file if (!saveC2ATransform(c2a_src, R, t)) { @@ -1037,7 +1031,7 @@ bool ConfigGui::run() key = cv::waitKey(5); /// State machine logic. - if (key == enter_key) { + if ((key == 0x0d) || (key == 0x0a)) { // return if ((_input_data.sqrPts.size() == 4) && !R.empty()) { // dump corner points to config file if (!saveC2ATransform(c2a_src, R, t)) { @@ -1108,7 +1102,7 @@ bool ConfigGui::run() /// Exit config. case EXIT: - key = exit_key; + key = 0x1b; // esc break; } } From 3a6f710f0bc327fe3262cc50c985f91c24c0d3ba Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 7 May 2019 20:44:07 +0200 Subject: [PATCH 110/235] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6da6133..73b660a 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,6 @@ FicTrac imposes no requirements on the *italicised* items; how you design these ### Installation -[![Build Status](https://dev.azure.com/rjdmoore/FicTrac/_apis/build/status/rjdmoore.fictrac?branchName=master)](https://dev.azure.com/rjdmoore/FicTrac/_build/latest?definitionId=1&branchName=master) - The FicTrac source code can be built for both Windows and Ubuntu (Linux) operating systems, or you can build and run FicTrac from within a [virtual machine](https://www.virtualbox.org/) on any operating system. 1. Download and install required dependencies: @@ -77,6 +75,10 @@ If everything went well, the executables for FicTrac and a configuration utility Remember to update and re-build FicTrac occasionally, as the program is still under development and fixes and improvements are being made continuously. +| | | | | | +| --- | --- | --- | --- | --- | +| Build status | Windows | [![Build Status](https://dev.azure.com/rjdmoore/FicTrac/_apis/build/status/rjdmoore.fictrac?branchName=master&jobName=Windows)](https://dev.azure.com/rjdmoore/FicTrac/_build/latest?definitionId=1&branchName=master) | Linux | [![Build Status](https://dev.azure.com/rjdmoore/FicTrac/_apis/build/status/rjdmoore.fictrac?branchName=master&jobName=Linux)](https://dev.azure.com/rjdmoore/FicTrac/_build/latest?definitionId=1&branchName=master) | + #### USB3 camera installation If you are using a USB3 camera and are receiving error messages when FicTrac tries to connect to your camera, you may need to tell FicTrac to use the SDK provided with your camera, rather than the generic OpenCV interface. The instructions for switching to the camera's SDK are different for each manufacturer. Currently there is support for PGR (FLIR) USB3 cameras via the Spinnaker SDK. From 861e5b05cdcc7f0e90c5cf6f5b3eaf0220f3685c Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 8 May 2019 22:26:53 +0200 Subject: [PATCH 111/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4d8c915..c002514 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,7 +23,7 @@ jobs: steps: #- script: echo "set(VCPKG_BUILD_TYPE release)" >> $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake # displayName: vcpkg set build type - - script: vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux + - script: vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux displayName: vcpkg install dependencies - task: CMake@1 inputs: @@ -33,10 +33,6 @@ jobs: inputs: workingDirectory: build cmakeArgs: --build . --config Release - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: 'bin' - artifactName: drop # Provide a name for the job - job: Windows @@ -47,7 +43,7 @@ jobs: vmImage: 'vs2017-win2016' # The steps to run to execute the build. steps: - - script: vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows + - script: vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows displayName: vcpkg install dependencies - task: CMake@1 inputs: @@ -57,7 +53,3 @@ jobs: inputs: workingDirectory: build cmakeArgs: --build . --config Release - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: 'bin' - artifactName: drop From 4e1d6a3f1a4684703b1ab34230b1be7a88e63e4d Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 8 May 2019 22:27:31 +0200 Subject: [PATCH 112/235] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3a65536..c002514 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,7 +23,7 @@ jobs: steps: #- script: echo "set(VCPKG_BUILD_TYPE release)" >> $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake # displayName: vcpkg set build type - - script: vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux + - script: vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux displayName: vcpkg install dependencies - task: CMake@1 inputs: @@ -43,7 +43,7 @@ jobs: vmImage: 'vs2017-win2016' # The steps to run to execute the build. steps: - - script: vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows + - script: vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows displayName: vcpkg install dependencies - task: CMake@1 inputs: @@ -52,4 +52,4 @@ jobs: - task: CMake@1 inputs: workingDirectory: build - cmakeArgs: --build . --config Release \ No newline at end of file + cmakeArgs: --build . --config Release From 03cf222cb02c9caa51279c20530d4645bcdfb975 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 8 May 2019 22:29:38 +0200 Subject: [PATCH 113/235] try to force clean destruction of Trackball objects prior to close --- exec/fictrac.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/exec/fictrac.cpp b/exec/fictrac.cpp index 895bebc..f7a05db 100644 --- a/exec/fictrac.cpp +++ b/exec/fictrac.cpp @@ -12,8 +12,9 @@ #include #include +#include -using std::string; +using namespace std; /// Ctrl-c handling bool _active = true; @@ -66,25 +67,33 @@ int main(int argc, char *argv[]) LOG("Set process priority to HIGH!"); } - Trackball tracker(config_fn); + unique_ptr tracker = make_unique(config_fn); /// Now Trackball has spawned our worker threads, we set this thread to low priority. SetThreadNormalPriority(); - // wait for tracking to finish - while (tracker.isActive()) { + /// Wait for tracking to finish. + while (tracker->isActive()) { if (!_active) { - tracker.terminate(); + tracker->terminate(); } sleep(250); } - tracker.writeTemplate(); + /// Save the eventual template to disk. + tracker->writeTemplate(); + /// If we're running in test mode, print some stats. if (do_test) { - tracker.dumpState(); + tracker->dumpState(); } + /// Try to force release of all objects. + tracker.reset(); + + /// Wait a bit before exiting... + sleep(250); + //PRINT("\n\nHit ENTER to exit.."); //getchar_clean(); return 0; From 09ba4f0ce90c4eb3ab69aa423415e3e948901535 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 8 May 2019 22:32:25 +0200 Subject: [PATCH 114/235] add platform independent data output via serial with boost::asio --- include/SerialRecorder.h | 28 +++++++-- include/SerialRecorder_linux.h | 25 -------- include/SerialRecorder_win.h | 27 --------- scripts/serial_client.py | 49 +++++++++++++++ scripts/socket_client.py | 17 ++++-- src/Recorder.cpp | 2 + src/SerialRecorder.cpp | 100 +++++++++++++++++++++++++++++-- src/SerialRecorder_linux.src | 49 --------------- src/SerialRecorder_win.src | 106 --------------------------------- src/Trackball.cpp | 24 +++----- 10 files changed, 189 insertions(+), 238 deletions(-) delete mode 100644 include/SerialRecorder_linux.h delete mode 100644 include/SerialRecorder_win.h create mode 100644 scripts/serial_client.py delete mode 100644 src/SerialRecorder_linux.src delete mode 100644 src/SerialRecorder_win.src diff --git a/include/SerialRecorder.h b/include/SerialRecorder.h index b6e311d..ba48ff2 100644 --- a/include/SerialRecorder.h +++ b/include/SerialRecorder.h @@ -6,8 +6,26 @@ #pragma once -#ifdef __linux__ -#include "SerialRecorder_linux.h" -#elif _WIN32 -#include "SerialRecorder_win.h" -#endif +#include "RecorderInterface.h" + +#include +#include + +#include +#include + +class SerialRecorder : public RecorderInterface +{ +public: + SerialRecorder(); + ~SerialRecorder(); + + /// Interface to be overridden by implementations. + bool openRecord(std::string port_baud); + bool writeRecord(std::string s); + void closeRecord(); + +private: + std::string _port_name; + std::shared_ptr _port; +}; diff --git a/include/SerialRecorder_linux.h b/include/SerialRecorder_linux.h deleted file mode 100644 index 4bae07d..0000000 --- a/include/SerialRecorder_linux.h +++ /dev/null @@ -1,25 +0,0 @@ -/// FicTrac http://rjdmoore.net/fictrac/ -/// \file SerialRecorder_linux.h -/// \brief Linux implementation of serial recorder. -/// \author Richard Moore -/// \copyright CC BY-NC-SA 3.0 - -#pragma once - -#include "RecorderInterface.h" - -#include - -class SerialRecorder : public RecorderInterface -{ -public: - SerialRecorder(); - ~SerialRecorder(); - - /// Interface to be overridden by implementations. - bool openRecord(std::string port_baud); - bool writeRecord(std::string s); - void closeRecord(); - -private: -}; diff --git a/include/SerialRecorder_win.h b/include/SerialRecorder_win.h deleted file mode 100644 index d77f92c..0000000 --- a/include/SerialRecorder_win.h +++ /dev/null @@ -1,27 +0,0 @@ -/// FicTrac http://rjdmoore.net/fictrac/ -/// \file SerialRecorder_win.h -/// \brief Windows implementation of serial recorder. -/// \author Richard Moore -/// \copyright CC BY-NC-SA 3.0 - -#pragma once - -#include "RecorderInterface.h" - -#include -#include - -class SerialRecorder : public RecorderInterface -{ -public: - SerialRecorder(); - ~SerialRecorder(); - - /// Interface to be overridden by implementations. - bool openRecord(std::string port_baud); - bool writeRecord(std::string s); - void closeRecord(); - -private: - HANDLE _commHandle; -}; diff --git a/scripts/serial_client.py b/scripts/serial_client.py new file mode 100644 index 0000000..0ee23a0 --- /dev/null +++ b/scripts/serial_client.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 + +import serial + +PORT = 'COM?' # The com port to receive data +BAUD = 115200 # Baud rate used by the com port +TIMEOUT_S = 1 + +# Open the connection +with serial.Serial(PORT, BAUD, timeout=TIMEOUT_S) as com: + + # Keep receiving data until FicTrac closes + while com.is_open: + # Receive one data frame + data = com.readline() + if (not data): + break + + line = data.decode('UTF-8') + + # Tokenise + toks = line.split(", ") + + # Fixme: sometimes we read more than one line at a time, + # should handle that rather than just dropping extra data... + if ((len(toks) < 24) | (toks[0] != "FT")): + print('Bad read') + continue + + # Extract FicTrac variables + # (see https://github.com/rjdmoore/fictrac/blob/master/doc/data_header.txt for descriptions) + cnt = int(toks[1]) + dr_cam = [float(toks[2]), float(toks[3]), float(toks[4])] + err = float(toks[5]) + dr_lab = [float(toks[6]), float(toks[7]), float(toks[8])] + r_cam = [float(toks[9]), float(toks[10]), float(toks[11])] + r_lab = [float(toks[12]), float(toks[13]), float(toks[14])] + posx = float(toks[15]) + posy = float(toks[16]) + heading = float(toks[17]) + step_dir = float(toks[18]) + step_mag = float(toks[19]) + intx = float(toks[20]) + inty = float(toks[21]) + ts = float(toks[22]) + seq = int(toks[23]) + + # Do something ... + print(cnt) diff --git a/scripts/socket_client.py b/scripts/socket_client.py index 464529c..f82b83f 100644 --- a/scripts/socket_client.py +++ b/scripts/socket_client.py @@ -9,17 +9,24 @@ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.connect((HOST, PORT)) + data = "" + # Keep receiving data until FicTrac closes while True: # Receive one data frame - data = sock.recv(1024) - if not data: + new_data = sock.recv(1024) + if not new_data: break # Decode received data - line = data.decode('UTF-8') - endline = line.find("\n") - line = line[:endline] + data += new_data.decode('UTF-8') + + # Find the first frame of data + endline = data.find("\n") + line = data[:endline] # copy first frame + data = data[endline+1:] # delete first frame + + # Tokenise toks = line.split(", ") # Fixme: sometimes we read more than one line at a time, diff --git a/src/Recorder.cpp b/src/Recorder.cpp index 5924b32..c060fab 100644 --- a/src/Recorder.cpp +++ b/src/Recorder.cpp @@ -29,8 +29,10 @@ Recorder::Recorder(RecorderInterface::RecordType type, string fn) break; case RecorderInterface::RecordType::SOCK: _record = make_unique(); + break; case RecorderInterface::RecordType::COM: _record = make_unique(); + break; default: break; } diff --git a/src/SerialRecorder.cpp b/src/SerialRecorder.cpp index c75d0e6..4c41fe7 100644 --- a/src/SerialRecorder.cpp +++ b/src/SerialRecorder.cpp @@ -4,8 +4,98 @@ /// \author Richard Moore /// \copyright CC BY-NC-SA 3.0 -#ifdef __linux__ -#include "SerialRecorder_linux.src" -#elif _WIN32 -#include "SerialRecorder_win.src" -#endif +#include "SerialRecorder.h" + +#include "Logger.h" + +#include +#include + +#include + +using namespace std; +using namespace boost; + +/// +/// +/// +SerialRecorder::SerialRecorder() +{ + _type = COM; +} + +/// +/// +/// +SerialRecorder::~SerialRecorder() +{ + closeRecord(); +} + +/// +/// +/// +bool SerialRecorder::openRecord(std::string port_baud) +{ + // extract port no and baud + size_t pos = port_baud.find_first_of('@'); + if (pos == string::npos) { + LOG_ERR("Error! Malformed port:baud string."); + return false; + } + _port_name = port_baud.substr(0, pos); + int baud = stoi(port_baud.substr(pos + 1)); + + LOG("Opening serial port %s with baud rate %d", _port_name.c_str(), baud); + + // open serial port + try { + asio::io_service io; + _port = make_shared(io); + _port->open(_port_name); + //_port->set_option(asio::serial_port_base::baud_rate(baud)); + _open = _port->is_open(); + if (!_open) { throw; } + } + catch (const boost::system::system_error &e) { + LOG_ERR("Error! Could not open serial port %s @ baud rate %d. Error was %s", _port_name.c_str(), baud, boost::diagnostic_information(e).c_str()); + _open = false; + } + catch (const boost::exception &e) { + LOG_ERR("Error! Could not open serial port %s @ baud rate %d. Error was %s", _port_name.c_str(), baud, boost::diagnostic_information(e).c_str()); + _open = false; + } + catch (...) { + LOG_ERR("Error! Could not open serial port %s @ baud rate %d.", _port_name.c_str(), baud); + _open = false; + } + return _open; +} + +/// +/// +/// +bool SerialRecorder::writeRecord(string s) +{ + if (_open) { + try { + _port->write_some(asio::buffer(s)); + } + catch (const boost::exception &e) { + LOG_ERR("Error writing to serial port (%s)! Error was %s", _port_name.c_str(), boost::diagnostic_information(e).c_str()); + return false; + } + } + return _open; +} + +/// +/// +/// +void SerialRecorder::closeRecord() +{ + LOG("Closing serial port %s", _port_name.c_str()); + + _open = false; + _port->close(); +} diff --git a/src/SerialRecorder_linux.src b/src/SerialRecorder_linux.src deleted file mode 100644 index b075c75..0000000 --- a/src/SerialRecorder_linux.src +++ /dev/null @@ -1,49 +0,0 @@ -/// FicTrac http://rjdmoore.net/fictrac/ -/// \file SerialRecorder_linux.cpp -/// \brief Linux implementation of serial recorder. -/// \author Richard Moore -/// \copyright CC BY-NC-SA 3.0 - -#include "SerialRecorder_linux.h" - -#include "Logger.h" - - -/// -/// -/// -SerialRecorder::SerialRecorder() -{ - _type = COM; -} - -/// -/// -/// -SerialRecorder::~SerialRecorder() -{ - closeRecord(); -} - -/// -/// -/// -bool SerialRecorder::openRecord(std::string port_baud) -{ - return false; -} - -/// -/// -/// -bool SerialRecorder::writeRecord(std::string s) -{ - return false; -} - -/// -/// -/// -void SerialRecorder::closeRecord() -{ -} diff --git a/src/SerialRecorder_win.src b/src/SerialRecorder_win.src deleted file mode 100644 index 3b9125e..0000000 --- a/src/SerialRecorder_win.src +++ /dev/null @@ -1,106 +0,0 @@ -/// FicTrac http://rjdmoore.net/fictrac/ -/// \file SerialRecorder_win.cpp -/// \brief Windows implementation of serial recorder. -/// \author Richard Moore -/// \copyright CC BY-NC-SA 3.0 - -#include "SerialRecorder_win.h" - -#include "Logger.h" - -#include - -using namespace std; - -typedef std::basic_string tstring; - -/// -/// -/// -SerialRecorder::SerialRecorder() -{ - _type = COM; -} - -/// -/// -/// -SerialRecorder::~SerialRecorder() -{ - closeRecord(); -} - -/// -/// -/// -bool SerialRecorder::openRecord(std::string port_baud) -{ - // extract port no - size_t pos = port_baud.find_first_of('/'); - if (pos == string::npos) { - LOG_ERR("Error! Malformed port:baud string."); - return false; - } - - string port = port_baud.substr(0, pos); - int baud = stoi(port_baud.substr(pos + 1)); - - _commHandle = CreateFile(port.c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); - if(_commHandle == INVALID_HANDLE_VALUE) { - LOG_ERR("Error! Could not open com port (%s).", port.c_str()); - return false; - } - else { - // set timeouts - COMMTIMEOUTS cto = { MAXDWORD, 0, 0, 0, 0 }; - DCB dcb; - if(!SetCommTimeouts(_commHandle,&cto)) { - LOG_ERR("Error! Could not set com port time-outs."); - return false; - } - - // set DCB - memset(&dcb,0,sizeof(dcb)); - dcb.DCBlength = sizeof(dcb); - dcb.BaudRate = baud; - dcb.fBinary = 1; - dcb.fDtrControl = DTR_CONTROL_ENABLE; - dcb.fRtsControl = RTS_CONTROL_ENABLE; - - dcb.Parity = NOPARITY; - dcb.StopBits = ONESTOPBIT; - dcb.ByteSize = 8; - - if(!SetCommState(_commHandle,&dcb)) { - LOG_ERR("Error! Could not set com port parameters."); - return false; - } - } - - return (_open = true); -} - -/// -/// -/// -bool SerialRecorder::writeRecord(std::string s) -{ - if (_open) { - DWORD numWritten; - WriteFile(_commHandle, s.c_str(), s.size(), &numWritten, NULL); - if (numWritten <= 0) { - LOG_ERR("Error! No bytes written to com port."); - return false; - } - } - return _open; -} - -/// -/// -/// -void SerialRecorder::closeRecord() -{ - _open = false; - CloseHandle(_commHandle); -} diff --git a/src/Trackball.cpp b/src/Trackball.cpp index c531ace..554c5b8 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -49,8 +49,6 @@ const double THRESH_WIN_PC_DEFAULT = 0.25; const uint8_t SPHERE_MAP_FIRST_HIT_BONUS = 64; -const int SOCK_PORT_DEFAULT = -1; - const int COM_BAUD_DEFAULT = 115200; const bool DO_DISPLAY_DEFAULT = true; @@ -122,7 +120,7 @@ Trackball::Trackball(string cfg_fn) } double src_fps = -1; if (_cfg.getDbl("src_fps", src_fps) && (src_fps > 0)) { - LOG("Setting source fps = %.2f..", src_fps); + LOG("Attempting to set source fps to %.2f", src_fps); source->setFPS(src_fps); } else { @@ -383,7 +381,7 @@ Trackball::Trackball(string cfg_fn) return; } - int sock_port = SOCK_PORT_DEFAULT; + int sock_port = 0; _do_sock_output = false; if (_cfg.getInt("sock_port", sock_port) && (sock_port > 0)) { _data_sock = make_unique(RecorderInterface::RecordType::SOCK, std::to_string(sock_port)); @@ -393,9 +391,6 @@ Trackball::Trackball(string cfg_fn) return; } _do_sock_output = true; - } else { - LOG_WRN("Warning! Using default value for sock_port (%d).", sock_port); - _cfg.add("sock_port", sock_port); } string com_port = _cfg("com_port"); @@ -407,17 +402,14 @@ Trackball::Trackball(string cfg_fn) _cfg.add("com_baud", com_baud); } - _data_com = make_unique(RecorderInterface::RecordType::COM, com_port + "/" + std::to_string(com_baud)); + _data_com = make_unique(RecorderInterface::RecordType::COM, com_port + "@" + std::to_string(com_baud)); if (!_data_com->is_active()) { - LOG_ERR("Error! Unable to open output data com port (%s/%d).", com_port.c_str(), com_baud); + LOG_ERR("Error! Unable to open output data com port (%s@%d).", com_port.c_str(), com_baud); _active = false; return; } _do_com_output = true; } - else { - _cfg.add("com_port", com_port); - } /// Display. _do_display = DO_DISPLAY_DEFAULT; @@ -534,7 +526,7 @@ Trackball::Trackball(string cfg_fn) /// Trackball::~Trackball() { - LOG("Closing sphere tracker.."); + LOG("Closing sphere tracker"); _init = false; _active = false; @@ -597,7 +589,7 @@ void Trackball::process() if (!SetThreadHighPriority()) { LOG_ERR("Error! Unable to set thread priority!"); } else { - LOG("Set processing thread priority to HIGH!"); + LOG_DBG("Set processing thread priority to HIGH!"); } /// Sphere tracking loop. @@ -738,7 +730,7 @@ bool Trackball::doSearch(bool allow_global = false) bool bad_frame = _error_thresh >= 0 ? (_err > _error_thresh) : false; if (allow_global && (bad_frame || (_reset && !_clean_map))) { - LOG("Doing global search.."); + LOG("Doing global search"); // do global search _err = _globalOpt->search(_roi_frame, _data.R_roi, _data.r_roi); // use last know orientation, _r_roi, as guess and update with result @@ -1398,7 +1390,7 @@ void Trackball::drawCanvas(shared_ptr data) cv::imshow("FicTrac-debug", canvas); uint16_t key = cv::waitKey(1); if (key == 0x1B) { // esc - LOG("Exiting.."); + LOG("Exiting"); terminate(); } else if (key == 0x52) { // shift+R From 09ca3fcd1cc7c34909c2eee08a415c8d85150abc Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 8 May 2019 22:32:59 +0200 Subject: [PATCH 115/235] playback from file uses system timestamp instead of video timestamps --- src/CVSource.cpp | 6 +++--- src/FrameGrabber.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/CVSource.cpp b/src/CVSource.cpp index c5755e0..7de14ee 100644 --- a/src/CVSource.cpp +++ b/src/CVSource.cpp @@ -29,7 +29,7 @@ CVSource::CVSource(std::string input) Mat test_frame; try { // try reading input as camera id - LOG_DBG("Trying source as camera id.."); + LOG_DBG("Trying source as camera id..."); if (input.size() > 2) { throw std::exception(); } int id = std::stoi(input); _cap = std::shared_ptr(new cv::VideoCapture(id)); @@ -43,7 +43,7 @@ CVSource::CVSource(std::string input) catch (...) { try { // then try loading as video file - LOG_DBG("Trying source as video file.."); + LOG_DBG("Trying source as video file..."); _cap = std::shared_ptr(new cv::VideoCapture(input)); if (!_cap->isOpened()) { throw 0; } *_cap >> test_frame; @@ -138,7 +138,7 @@ bool CVSource::grab(cv::Mat& frame) double ts = ts_ms(); // backup, in case the device timestamp is junk _timestamp = _cap->get(cv::CAP_PROP_POS_MSEC); LOG_DBG("Frame captured %dx%d%d @ %f (%f)", _frame_cap.cols, _frame_cap.rows, _frame_cap.channels(), _timestamp, ts); - if (_timestamp <= 0) { + if ((_timestamp <= 0) || (!_live)) { // also use system time when playing back from file _timestamp = ts; } diff --git a/src/FrameGrabber.cpp b/src/FrameGrabber.cpp index 5c3141c..8f9e78c 100644 --- a/src/FrameGrabber.cpp +++ b/src/FrameGrabber.cpp @@ -71,7 +71,7 @@ FrameGrabber::FrameGrabber( shared_ptr source, /// FrameGrabber::~FrameGrabber() { - LOG("Closing input stream.."); + LOG("Closing input stream"); unique_lock l(_qMutex); _active = false; @@ -194,7 +194,7 @@ void FrameGrabber::process() if (!SetThreadVeryHighPriority()) { LOG_ERR("Error! Unable to set thread priority!"); } else { - LOG("Set frame grabbing thread priority to HIGH!"); + LOG_DBG("Set frame grabbing thread priority to HIGH!"); } /// Frame grab loop. From 87168640baf53e57cfbf8c8a8d037709796893c1 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 8 May 2019 23:20:47 +0200 Subject: [PATCH 116/235] fix library include path --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a946d8..1843800 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,9 +34,9 @@ set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${OPENCV_DIR} ${NLOPT_DIR}) find_package(OpenCV REQUIRED) find_package(NLopt CONFIG REQUIRED) if(NLopt_FOUND) - set(NLopt_INCLUDE_DIRS ${NLOPT_INCLUDE_DIRS}/include) + get_filename_component(NLopt_INCLUDE_DIRS "${NLOPT_CONFIG_FILE}/../../../include" REALPATH) # set(NLopt_LIBS ${NLOPT_LIBRARY_DIRS}/lib/nlopt.lib) - message(STATUS "Found NLopt: ${NLOPT_LIBRARY_DIRS}") + message(STATUS "Found NLopt: ${NLOPT_CONFIG_FILE}") else() message(FATAL_ERROR "Error! Could not find NLopt lib at ${NLOPT_LIB}!") endif() From 7690ae0d27e7cb655ba8f2e2d7304089dc4691ff Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 8 May 2019 23:21:38 +0200 Subject: [PATCH 117/235] sdkddkver fix --- include/SerialRecorder.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/SerialRecorder.h b/include/SerialRecorder.h index ba48ff2..9f27fb2 100644 --- a/include/SerialRecorder.h +++ b/include/SerialRecorder.h @@ -8,7 +8,9 @@ #include "RecorderInterface.h" +#ifdef _WIN32 #include +#endif #include #include From 76bccfdb67be4a976bce6bd253214452c18fe986 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 8 May 2019 23:24:53 +0200 Subject: [PATCH 118/235] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 73b660a..f37ad44 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,10 @@ The FicTrac source code can be built for both Windows and Ubuntu (Linux) operati 1. [Cmake build system](https://cmake.org/download/) (binary distribution) 2. For Windows installations, if you don't already have Visual Studio (C++ workflow) installed, you will need to install the [build tools](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2017). 3. Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). - 4. Using Vcpkg, install OpenCV and NLopt software packages: + 4. Using Vcpkg, install OpenCV, NLopt, and Boost::asio software packages (this may take 10-30 mins): ``` -[Windows] .\vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows -[Linux] ./vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux +[Windows] .\vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows +[Linux] ./vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux ``` 2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: ``` From 6d6a1a04ae4da5cf963ff857675eb08ff7eabed4 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 9 May 2019 00:07:46 +0200 Subject: [PATCH 119/235] Update params.md --- doc/params.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/params.md b/doc/params.md index ba7148e..791a182 100644 --- a/doc/params.md +++ b/doc/params.md @@ -10,7 +10,9 @@ In the table below, the various possible parameters are listed. If nothing is li | do_display | bool | y | y/n | If you want to | Display debug screen during tracking. Slows execution very slightly. | | save_debug | bool | n | y/n | If you want to | Record the debug screen to video file. Note that if the source frame rate is higher than FicTrac's processing frame rate, frames may be dropped from the video file. | | save_raw | bool | n | y/n | If you want to | Record the input image stream to video file. Note that if the source frame rate is higher than FicTrac's processing frame rate, frames may be dropped from the video file. | -| out_port | int | -1 | (0,inf) | If you want to | Socket port over which to transmit FicTrac data. If unset or < 0, FicTrac will not transmit data over sockets. | +| sock_port | int | -1 | (0,inf) | If you want to | Socket port over which to transmit FicTrac data. If unset or < 0, FicTrac will not transmit data over sockets. | +| com_port | string | | | If you want to | Serial port over which to transmit FicTrac data. If unset, FicTrac will not transmit data over serial. | +| com_baud | int | 115200 | | If you want to | Baud rate to use for COM port. Unused if no com_port set. | | | | | | | | | q_factor | int | 6 | (0,inf) | Only if you need to | Adjusts the resolution of the tracking window. Smaller values correspond to coarser but quicker tracking and vice-versa. Normally in the range [3,10]. | | src_fps | float | -1 | (0,inf) | Only if you need to | If set, FicTrac will attempt to set the frame rate for the image source (video file or camera). | From 92a36ad87eb318b3d9327bb1091a9861fc5fe4de Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 9 May 2019 00:12:06 +0200 Subject: [PATCH 120/235] version to 2.02 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1843800..124a45e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ project (FicTrac) # The version number. set (FICTRAC_VERSION_MAJOR 2) -set (FICTRAC_VERSION_MINOR 1) +set (FICTRAC_VERSION_MINOR 2) # output version info to be included by project configure_file ( From 90f82eb179ef49ec2d369dfde627f523d94d80e0 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 9 May 2019 22:08:35 +0200 Subject: [PATCH 121/235] add exec time/date to output video filenames --- src/Trackball.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 554c5b8..22f2999 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -463,7 +463,7 @@ Trackball::Trackball(string cfg_fn) // raw input video if (_save_raw) { - string vid_fn = _base_fn + "-raw." + fext; + string vid_fn = _base_fn + "-raw-" + execTime() + "." + fext; double fps = source->getFPS(); if (fps <= 0) { fps = 25; } LOG_DBG("Opening %s for video writing (%s %dx%d @ %f FPS)", vid_fn.c_str(), cstr.c_str(), source->getWidth(), source->getHeight(), fps); @@ -477,7 +477,7 @@ Trackball::Trackball(string cfg_fn) // debug output video if (_save_debug) { - string vid_fn = _base_fn + "-debug." + fext; + string vid_fn = _base_fn + "-dbg-" + execTime() + "." + fext; double fps = source->getFPS(); if (fps <= 0) { fps = 25; } LOG_DBG("Opening %s for video writing (%s %dx%d @ %f FPS)", vid_fn.c_str(), cstr.c_str(), 4 * DRAW_CELL_DIM, 3 * DRAW_CELL_DIM, fps); From 736c404d767f9be436db968b931b58cab13f4e61 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 15 May 2019 23:55:05 +0200 Subject: [PATCH 122/235] [configGui] fix crash on exit due to missing R or t --- src/drawing.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/drawing.cpp b/src/drawing.cpp index 6d98eac..e7f2137 100644 --- a/src/drawing.cpp +++ b/src/drawing.cpp @@ -138,7 +138,9 @@ void drawCursor(Mat& rgb, const Point2d& pt, cv::Scalar colour) /// Draw transformed axes. /// void drawAxes(Mat& rgb, const CameraModelPtr cam_model, const Mat& R, const Mat& t, const cv::Scalar colour) -{ +{ + if (R.empty() || t.empty()) { return; } + /// Transformed axes. Mat sx = R * (cv::Mat_(3,1) << 1,0,0) + t; Mat sy = R * (cv::Mat_(3,1) << 0,1,0) + t; @@ -215,6 +217,8 @@ void drawAxes(Mat& rgb, const CameraModelPtr cam_model, const Mat& R, const Mat& /// void drawAnimalAxis(Mat& rgb, const CameraModelPtr cam_model, const Mat& R, const Mat& t, const double r, const cv::Scalar colour) { + if (R.empty() || t.empty()) { return; } + /// Transformed axes. Mat sx = R * 0.5 * (cv::Mat_(3, 1) << 1, 0, 0); Mat sx1 = R * 0.45 * (cv::Mat_(3, 1) << 1, 0, 0); From af44040c776d8e97cb7a7a3c59c2be78ac5fdab7 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 16 May 2019 00:07:48 +0200 Subject: [PATCH 123/235] [configGui] contrast stretching for interactive window --- src/ConfigGUI.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index 9d96332..31f16ab 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -432,6 +432,13 @@ bool ConfigGui::run() /// Create frame for drawing. //cv::cvtColor(_frame, disp_frame, CV_GRAY2RGB); disp_frame = _frame.clone(); + + // normalise zoom window + { + double min, max; + cv::minMaxLoc(disp_frame, &min, &max); + disp_frame = (disp_frame - min) * 255 / (max - min); + } int in; string str; From 17d22d3aca5f58fce36d7c2b8aa8ffa2e67211a6 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 22 May 2019 22:44:40 +0200 Subject: [PATCH 124/235] [configGui] fix zoom window upper edge bug --- src/ConfigGUI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index 31f16ab..fb841d0 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -141,7 +141,7 @@ void createZoomROI(Mat& zoom_roi, const Mat& frame, const Point2d& pt, int orig_ int x = frame.cols/2; if (pt.x >= 0) { x = clamp(int(pt.x - orig_dim/2 + 0.5), int(orig_dim/2), frame.cols - 1 - orig_dim); } int y = frame.rows/2; - if (pt.y >= 0) { y = clamp(int(pt.y - orig_dim/2 + 0.5), int(orig_dim/2), frame.rows - 1 - orig_dim); } + if (pt.y >= 0) { y = clamp(int(pt.y - orig_dim/2 + 0.5), 0, frame.rows - 1 - orig_dim); } Mat crop_rect = frame(cv::Rect(x, y, orig_dim, orig_dim)); cv::resize(crop_rect, zoom_roi, zoom_roi.size()); } From 1d3d7718713f711ca251442db848d24881ea4536 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 22 May 2019 22:46:15 +0200 Subject: [PATCH 125/235] [configGui] save recomputed roi_c and roi_r values to config --- src/ConfigGUI.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index fb841d0..3dbd5de 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -464,9 +464,25 @@ bool ConfigGui::run() /// Fit circular FoV to sphere. if (_input_data.circPts.size() >= 3) { - circleFit_camModel(_input_data.circPts, _cam_model, c, r); - - LOG_DBG("Computed roi_c = [%f %f %f] and roi_r = %f rad from %d roi_circ points.", c[0], c[1], c[2], r, _input_data.circPts.size()); + if (circleFit_camModel(_input_data.circPts, _cam_model, c, r)) { + + LOG_DBG("Computed roi_c = [%f %f %f] and roi_r = %f rad from %d roi_circ points.", c[0], c[1], c[2], r, _input_data.circPts.size()); + + // save re-computed values + cfg_vec.clear(); + cfg_vec.push_back(c[0]); + cfg_vec.push_back(c[1]); + cfg_vec.push_back(c[2]); + + // write to config file + LOG("Adding roi_c and roi_r to config file and writing to disk (%s) ..", _config_fn.c_str()); + _cfg.add("roi_c", cfg_vec); + _cfg.add("roi_r", r); + if (_cfg.write() <= 0) { + LOG_ERR("Error writing to config file (%s)!", _config_fn.c_str()); + _open = false; // will cause exit + } + } } } else { From 6d9c1a87b6fe80f661b1c98467d35568687c93fc Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 22 May 2019 22:47:11 +0200 Subject: [PATCH 126/235] [fictrac] ignore reconfig param --- src/Trackball.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 554c5b8..a940a16 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -159,7 +159,7 @@ Trackball::Trackball(string cfg_fn) /// Load sphere config and mask. bool reconfig = false; - _cfg.getBool("reconfig", reconfig); // ignore saved roi_c, roi_r, c2a_r, and c2a_t values and recompute from pixel coords - dangerous!! + //_cfg.getBool("reconfig", reconfig); // ignore saved roi_c, roi_r, c2a_r, and c2a_t values and recompute from pixel coords - dangerous!! Mat src_mask(source->getHeight(), source->getWidth(), CV_8UC1); src_mask.setTo(Scalar::all(0)); { From 64544fa5d0791911d1387f33cc1732145e758127 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 22 May 2019 22:49:30 +0200 Subject: [PATCH 127/235] [configGui] square minimisation initial guess further from camera to try to avoid negative z solutions --- src/SquareRT.cpp | 4 ++-- src/geometry.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SquareRT.cpp b/src/SquareRT.cpp index cf8fe1f..fe7830b 100644 --- a/src/SquareRT.cpp +++ b/src/SquareRT.cpp @@ -34,8 +34,8 @@ SquareRT::SquareRT(const vector& cnrs, const Mat& ref_cnrs) : _corners(cnrs), _ref_corners(ref_cnrs) { // tx ty tz r_az r_el r_mag - double lb[6] = {-CM_PI, -CM_PI, -CM_PI, -1e3, -1e3, 0}; - double ub[6] = {CM_PI, CM_PI, CM_PI, 1e3, 1e3, 1e3}; + double lb[6] = {-CM_PI, -CM_PI, -CM_PI, -1e4, -1e4, 0}; + double ub[6] = {CM_PI, CM_PI, CM_PI, 1e4, 1e4, 1e4}; init(NLOPT_GN_CRS2_LM, 6); setLowerBounds(lb); setUpperBounds(ub); diff --git a/src/geometry.cpp b/src/geometry.cpp index 9e68f1c..08acb11 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -126,7 +126,7 @@ bool computeRtFromSquare(const CameraModelPtr cam_model, const Mat& ref_cnrs, co /// Minimise transform from reference corners. SquareRT square(cnr_vecs, ref_cnrs); - double guess[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 1.0}; + double guess[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 100.0}; if (!R.empty() && !t.empty()) { // init guess if ((R.depth() != CV_64F) || (t.depth() != CV_64F)) { From 8ddf65474da0658a59b68faa27e4dcfe11e2a59a Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 23 May 2019 16:36:59 +0200 Subject: [PATCH 128/235] [fictrac] add support for fisheye lenses --- src/Trackball.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Trackball.cpp b/src/Trackball.cpp index a940a16..f1fdf78 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -144,8 +144,14 @@ Trackball::Trackball(string cfg_fn) _active = false; return; } - //FIXME: support other camera models - _src_model = CameraModel::createRectilinear(source->getWidth(), source->getHeight(), vfov * CM_D2R); + bool fisheye = false; + if (_cfg.getBool("fisheye", fisheye) && fisheye) { + _src_model = CameraModel::createFisheye(source->getWidth(), source->getHeight(), vfov * CM_D2R / (double)source->getHeight(), 360 * CM_D2R); + } + else { + // default to rectilinear + _src_model = CameraModel::createRectilinear(source->getWidth(), source->getHeight(), vfov * CM_D2R); + } /// Dimensions - quality defaults to 6 (remap_dim 60x60, sphere_dim 180x90). int q_factor = Q_FACTOR_DEFAULT; From 6fd1d63b1c7ff322e7ba674a34300c9514d7857b Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 23 May 2019 16:45:49 +0200 Subject: [PATCH 129/235] Update params.md --- doc/params.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/params.md b/doc/params.md index 791a182..eca7844 100644 --- a/doc/params.md +++ b/doc/params.md @@ -14,6 +14,7 @@ In the table below, the various possible parameters are listed. If nothing is li | com_port | string | | | If you want to | Serial port over which to transmit FicTrac data. If unset, FicTrac will not transmit data over serial. | | com_baud | int | 115200 | | If you want to | Baud rate to use for COM port. Unused if no com_port set. | | | | | | | | +| fisheye | bool | n | y/n | Only if you need to | If set, FicTrac will assume the imaging system has a fisheye lens, otherwise a rectilinear lens is assumed. | | q_factor | int | 6 | (0,inf) | Only if you need to | Adjusts the resolution of the tracking window. Smaller values correspond to coarser but quicker tracking and vice-versa. Normally in the range [3,10]. | | src_fps | float | -1 | (0,inf) | Only if you need to | If set, FicTrac will attempt to set the frame rate for the image source (video file or camera). | | max_bad_frames | int | -1 | (0,inf) | Only if you need to | If set, FicTrac will reset tracking after being unable to match this many frames in a row. Defaults to never resetting tracking. | From a90fc3a4dd12647b60c8494d35ace0a8fe96b71c Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 24 May 2019 19:39:37 +0200 Subject: [PATCH 130/235] Update params.md --- doc/params.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/params.md b/doc/params.md index eca7844..a69dafa 100644 --- a/doc/params.md +++ b/doc/params.md @@ -23,6 +23,7 @@ In the table below, the various possible parameters are listed. If nothing is li | thr_ratio | float | 1.25 | (0,inf) | Only if you need to | Adjusts the adaptive thresholding of the input image. Values > 1 will favour foreground regions (more white in thresholded image) and values < 1 will favour background regions (more black in thresholded image). | | thr_win_pc | float | 0.2 | \[0,1] | Only if you need to | Adjusts the size of the neighbourhood window to use for adaptive thresholding of the input image, specified as a percentage of the width of the tracking window. Larger values avoid over-segmentation, whilst smaller values make segmentation more robust to illumination gradients on the trackball. | | vid_codec | string | h264 | [h264,xvid,mpg4,mjpg,raw] | Only if you need to | Specifies the video codec to use when writing output videos (see `save_raw` and `save_debug`). | +| sphere_map_fn | string | | | Only if you need to | If specified, FicTrac will attempt to load a previously generated sphere surface map from this filename. | | | | | | | | | opt_max_evals | int | 50 | (0,inf) | Probably not | Specifies the maximum number of minimisation iterations to perform each frame. Smaller values may improve tracking frame rate at the risk of finding sub-optimal matches. Number of optimisation iterations is printed to screen during tracking (its=...). | | opt_bound | float | 0.35 | (0,inf) | Probably not | Specifies the optimisation search range in radians. Larger values will facilitate more track ball rotation per frame, but result in slower tracking and also possibly lead to false matches. | From 70ae10a565c702c339cf1ee6b2dc6146bc580072 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 24 May 2019 20:24:55 +0200 Subject: [PATCH 131/235] [fictrac] clearer variable naming for global search --- include/Trackball.h | 2 +- src/Trackball.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/Trackball.h b/include/Trackball.h index 5787626..97e173f 100644 --- a/include/Trackball.h +++ b/include/Trackball.h @@ -161,7 +161,7 @@ class Trackball /// Optimisation. std::unique_ptr _localOpt, _globalOpt; double _error_thresh, _err; - bool _global_search; + bool _do_global_search; int _max_bad_frames; int _nevals; diff --git a/src/Trackball.cpp b/src/Trackball.cpp index f1fdf78..e90ad25 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -341,10 +341,10 @@ Trackball::Trackball(string cfg_fn) LOG_WRN("Warning! Using default value for opt_max_eval (%d).", max_evals); _cfg.add("opt_max_evals", max_evals); } - _global_search = OPT_GLOBAL_SEARCH_DEFAULT; - if (!_cfg.getBool("opt_do_global", _global_search)) { - LOG_WRN("Warning! Using default value for opt_do_global (%d).", _global_search); - _cfg.add("opt_do_global", _global_search ? "y" : "n"); + _do_global_search = OPT_GLOBAL_SEARCH_DEFAULT; + if (!_cfg.getBool("opt_do_global", _do_global_search)) { + LOG_WRN("Warning! Using default value for opt_do_global (%d).", _do_global_search); + _cfg.add("opt_do_global", _do_global_search ? "y" : "n"); } _max_bad_frames = OPT_MAX_BAD_FRAMES_DEFAULT; if (!_cfg.getInt("max_bad_frames", _max_bad_frames)) { @@ -567,7 +567,7 @@ void Trackball::reset() _reset = true; /// Clear maps if we can't search the entire sphere to relocalise. - if (!_global_search) { + if (!_do_global_search) { //FIXME: possible for users to specify sphere_template without enabling global search.. _sphere_template.copyTo(_sphere_map); _clean_map = true; @@ -617,7 +617,7 @@ void Trackball::process() } /// Localise current view of sphere. - if (!doSearch(_global_search)) { + if (!doSearch(_do_global_search)) { t2 = t3 = t4 = t5 = ts_ms(); LOG_WRN("Warning! Could not match current sphere orientation to within error threshold (%f).\nNo data will be output for this frame!", _error_thresh); nbad++; From 3872e9284b3bb849129986e5f4eaf8f80f0b425d Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 26 May 2019 23:43:25 +0200 Subject: [PATCH 132/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f37ad44..de48b3f 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ FicTrac will usually generate two output files: The output data file can be used for offline processing. To use FicTrac within a closed-loop setup (to provide real-time feedback for stimuli), you should configure FicTrac to output data via a socket (IP address/port) in real-time. To do this, just set `out_port` to a valid port number in the config file. There is an example Python script for receiving data via sockets in the `scripts` directory. -**Note:** If you encounter issues trying to generate output videos (i.e. `save_raw` or `save_debug`), you might try changing the default video codec via `vid_codec` - see [config params](doc/params.md) for details. If you receive an error about a missing [H264 library](https://github.com/cisco/openh264/releases), you can download the necessary library (i.e. OpenCV 4.0.1 requires `openh264-1.8.0-win64.dll`) from the above link and place it in the `dll` folder under the FicTrac main directory. You will then need to re-run the appropriate `cmake ..` and `cmake --build` commands for your installation. +**Note:** If you encounter issues trying to generate output videos (i.e. `save_raw` or `save_debug`), you might try changing the default video codec via `vid_codec` - see [config params](doc/params.md) for details. If you receive an error about a missing [H264 library](https://github.com/cisco/openh264/releases), you can download the necessary library (i.e. OpenCV 3.4.3 requires `openh264-1.7.0-win64.dll`) from the above link and place it in the `dll` folder under the FicTrac main directory. You will then need to re-run the appropriate `cmake ..` and `cmake --build` commands for your installation. ## Research From f24aceabfbf9bc2be7e9d794231e0df4a1c4bb94 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 28 May 2019 23:11:39 +0200 Subject: [PATCH 133/235] [fictrac][configGui] templated accessor for configParser --- include/ConfigParser.h | 10 ++++++++-- src/ConfigParser.cpp | 29 +++++++---------------------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/include/ConfigParser.h b/include/ConfigParser.h index 052ecba..57c7d86 100644 --- a/include/ConfigParser.h +++ b/include/ConfigParser.h @@ -9,6 +9,7 @@ #include #include #include +#include /// /// Config file parser. @@ -26,10 +27,15 @@ class ConfigParser int write() { return write(_fn); } /// Quick accessor functions - std::string operator()(std::string key); + std::string operator()(std::string key) const; template - T get(std::string key); + T get(std::string key) const { + std::stringstream ss(operator()(key)); + T val; + ss >> val; + return val; + }; /// Accessor functions bool getStr(std::string key, std::string& val); diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 13a08c0..08ac665 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include // try, catch #include // erase, remove @@ -143,29 +142,15 @@ int ConfigParser::write(string fn) /// /// /// -string ConfigParser::operator()(string key) +string ConfigParser::operator()(string key) const { - string s = ""; - getStr(key, s); - return s; -} - -/// -/// -/// -template -T ConfigParser::get(string key) -{ - T val; - string s; - if (getStr(key, s)) { - std::stringstream ss(s); - try { ss >> val; } - catch (std::exception& e) { - LOG_ERR("Error parsing config file value (%s : %s)! Error was: %s", key.c_str(), ss.str().c_str(), e.what()); - } + try { + return _data.at(key); + } + catch (...) { + LOG_DBG("Key (%s) not found.", key.c_str()); } - return val; + return ""; } /// From 537dee63bb82bb5419409196c16279640e61b387 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 28 May 2019 23:14:49 +0200 Subject: [PATCH 134/235] [configGui] optional config image enhancement (enhance_cfg) --- src/ConfigGUI.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index 3dbd5de..5a6b399 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -193,6 +193,28 @@ ConfigGui::ConfigGui(string config_fn) } } + /// Optionally enhance frame for config + bool do_enhance = false; + _cfg.getBool("enhance_cfg", do_enhance); + if (_open && do_enhance) { + LOG("Enhancing config image .."); + Mat maximg = input_frame.clone(); + Mat minimg = input_frame.clone(); + while (source->grab(input_frame)) { + for (int i = 0; i < input_frame.rows; i++) { + uint8_t* pmin = minimg.ptr(i); + uint8_t* pmax = maximg.ptr(i); + const uint8_t* pimg = input_frame.ptr(i); + for (int j = 0; j < input_frame.cols * input_frame.channels(); j++) { + uint8_t p = pimg[j]; + if (p > pmax[j]) { pmax[j] = p; } + if (p < pmin[j]) { pmin[j] = p; } + } + } + } + input_frame = maximg - minimg; + } + /// Create base file name for output files. _base_fn = _cfg("output_fn"); if (_base_fn.empty()) { From 6c95e39a5dcad0613035c7eb4f919fd68ca4a269 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 28 May 2019 23:15:34 +0200 Subject: [PATCH 135/235] [configGui] allow image source types --- include/CVSource.h | 2 ++ src/CVSource.cpp | 43 ++++++++++++++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/include/CVSource.h b/include/CVSource.h index 77580ed..6f1f90e 100644 --- a/include/CVSource.h +++ b/include/CVSource.h @@ -28,4 +28,6 @@ class CVSource : public FrameSource { private: std::shared_ptr _cap; cv::Mat _frame_cap; + + bool _is_image; }; diff --git a/src/CVSource.cpp b/src/CVSource.cpp index 7de14ee..e537815 100644 --- a/src/CVSource.cpp +++ b/src/CVSource.cpp @@ -24,6 +24,7 @@ using cv::Mat; /// Constructor. /// CVSource::CVSource(std::string input) + : _is_image(false) { LOG_DBG("Source is: %s", input.c_str()); Mat test_frame; @@ -53,21 +54,41 @@ CVSource::CVSource(std::string input) _live = false; } catch (...) { - LOG_ERR("Could not interpret source type (%s)!", input.c_str()); - _open = false; + try { + // then try loading as an image file + LOG_DBG("Trying source as image file..."); + _frame_cap = cv::imread(input); + if (_frame_cap.empty()) { throw 0; } + LOG("Using source type: image file."); + _open = true; + _live = false; + _is_image = true; + } + catch (...) { + LOG_ERR("Could not interpret source type (%s)!", input.c_str()); + _open = false; + } } } if( _open ) { - _width = static_cast(_cap->get(cv::CAP_PROP_FRAME_WIDTH)); - _height = static_cast(_cap->get(cv::CAP_PROP_FRAME_HEIGHT)); + if (_is_image) { + _width = _frame_cap.cols; + _height = _frame_cap.rows; + } + else { + _width = static_cast(_cap->get(cv::CAP_PROP_FRAME_WIDTH)); + _height = static_cast(_cap->get(cv::CAP_PROP_FRAME_HEIGHT)); + } if (_live) { _fps = getFPS(); // don't init fps for video files - we might want to play them back as fast as possible - LOG("OpenCV camera initialised (%dx%d @ %.3f fps)!", _width, _height, _fps); + LOG("OpenCV camera source initialised (%dx%d @ %.3f fps)!", _width, _height, _fps); } - else { - LOG("OpenCV video initialised (%dx%d)!", _width, _height); + else if (_is_image) { + LOG("OpenCV image source initialised (%dx%d)!", _width, _height); + } else { + LOG("OpenCV video source initialised (%dx%d)!", _width, _height); } } } @@ -84,7 +105,7 @@ CVSource::~CVSource() double CVSource::getFPS() { double fps = _fps; - if (_open) { + if (_open && _cap) { fps = _cap->get(cv::CAP_PROP_FPS); } return fps; @@ -96,7 +117,7 @@ double CVSource::getFPS() bool CVSource::setFPS(double fps) { bool ret = false; - if (_open && (fps > 0)) { + if (_open && _cap && (fps > 0)) { if (!_cap->set(cv::CAP_PROP_FPS, fps)) { LOG_WRN("Warning! Failed to set device fps (attempted to set fps=%.2f).", fps); _fps = fps; // just set fps anyway for playback @@ -117,7 +138,7 @@ bool CVSource::setFPS(double fps) bool CVSource::rewind() { bool ret = false; - if (_open) { + if (_open && _cap) { if (!_cap->set(cv::CAP_PROP_POS_FRAMES, 0)) { LOG_WRN("Warning! Failed to rewind source."); } else { ret = true; } @@ -131,7 +152,7 @@ bool CVSource::rewind() bool CVSource::grab(cv::Mat& frame) { if( !_open ) { return false; } - if( !_cap->read(_frame_cap) ) { + if( !_is_image && !_cap->read(_frame_cap) ) { LOG_ERR("Error grabbing image frame!"); return false; } From bd6273385b25a914d445724c8d65c68fea1e576a Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 29 May 2019 13:27:41 +0200 Subject: [PATCH 136/235] [fictrac] flip drawing of surface pattern; draw longer path history --- src/Trackball.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Trackball.cpp b/src/Trackball.cpp index e90ad25..e76b6da 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -33,7 +33,7 @@ using namespace cv; using namespace std; -const int DRAW_SPHERE_HIST_LENGTH = 250; +const int DRAW_SPHERE_HIST_LENGTH = 1024; const int DRAW_CELL_DIM = 160; const int DRAW_FICTIVE_PATH_LENGTH = 1000; @@ -283,7 +283,7 @@ Trackball::Trackball(string cfg_fn) erode(_roi_mask, _roi_mask, Mat(), cv::Point(-1, -1), 1, cv::BORDER_CONSTANT, 0); // remove edge effects /// Surface mapping. - _sphere_model = CameraModel::createEquiArea(_map_w, _map_h); + _sphere_model = CameraModel::createEquiArea(_map_w, _map_h, CM_PI_2, -CM_PI, CM_PI, -2 * CM_PI); /// Buffers. _sphere_map.create(_map_h, _map_w, CV_8UC1); From 9c6fd47b40982c8829837993fa09fdbb2b52facb Mon Sep 17 00:00:00 2001 From: young24 Date: Sat, 1 Jun 2019 18:58:08 +0800 Subject: [PATCH 137/235] add BaslerSource, not tested --- include/BaslerSource.h | 31 +++++++++ src/BaslerSource.cpp | 142 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 include/BaslerSource.h create mode 100644 src/BaslerSource.cpp diff --git a/include/BaslerSource.h b/include/BaslerSource.h new file mode 100644 index 0000000..158a277 --- /dev/null +++ b/include/BaslerSource.h @@ -0,0 +1,31 @@ +#pragma once + +#include "FrameSource.h" + +// Include Basler Pylon libraries +#include +#include +#include +#ifdef PYLON_WIN_BUILD +# include +#endif + + +class BaslerSource : public FrameSource { + +public: + BaslerSource(int index=0); + virtual ~PGRSource(); + + virtual double getFPS(); + virtual bool setFPS(); + + virtual bool rewind() {return false; }; + virtual bool grab(cv::Mat& frame); + +private: + Pylon::CPylonImage pylonImg; + Pylon::CGrabResultPtr ptrGrabResult; + Pylon::CInstantCamera cam; + +}; diff --git a/src/BaslerSource.cpp b/src/BaslerSource.cpp new file mode 100644 index 0000000..565ed76 --- /dev/null +++ b/src/BaslerSource.cpp @@ -0,0 +1,142 @@ +class BaslerSource : public FrameSource { + +public: + BaslerSource(int index=0); + virtual ~PGRSource(); + + virtual double getFPS(); + virtual bool setFPS(); + + virtual bool rewind() {return false; }; + virtual bool grab(cv::Mat& frame); + +private: + Pylon::CPylonImage pylonImg; + Pylon::CGrabResultPtr ptrGrabResult; + Pylon::CInstantCamera cam; + +}; + +---- + +#include "BaslerSource.h" + +#include "Logger.h" +#include "timing.h" + +using namespace cv::Mat; +using namespace Pylon; + +BaslerSource::BaslerSource(int index) +{ + try { + PylonInitialize(); + // create an instant camera object + cam.Attach(CTlFactory::GetInstance().CreateFirstDevice()); + + LOG("Opening Basler camera device: %s", cam.GetDeviceInfo().GetModelName()); + + // Allow all the names in the namespace GenApi to be used without qualification. + using namespace GenApi; + + // Get the camera control object. + INodeMap &control = cam.GetNodeMap(); + + // Get some params + const CIntegerPtr camWidth = control.GetNode("Width"); + const CIntegerPtr camHeight = control.GetNode("Height"); + const CIntegerPtr camFrameRate = control.GetNode("FrameRate");// TODO: test this line + // TODO: assign int values to _width, _height, and _fps + + // Start acquisition + cam.StartGrabbing(); + + LOG("Basler camera initialised (%dx%d @ %.3f fps)!", _width, _height, _fps); + + _open = true; + _live = true; + + } + catch (const GenericException &e) { + LOG_ERR("Error opening capture device! Error was: %s", e.GetDescription()); + } +} + + +BaslerSource::~BaslerSource() +{ + if (_open) { + try { + cam.endAcquisition(); // TODO: find the correct method + } + catch (const GenericException &e) { + LOG_ERR("Error opening capture device! Error was: %s", e.GetDescription()); + } + _open = false; + } +} + +double BaslerSource::getFPS() +{ + return _fps; +} + +bool setFPS(double fps) +{ + using namespace GenApi; + + // Get the camera control object. + INodeMap &control = cam.GetNodeMap(); + const CIntegerPtr camFrameRate = control.GetNode("FrameRate");// TODO: test this line + if (IsWritable(camFrameRate)) + { + camFrameRate->SetValue(frameRate); + return true; + } + else { + return false; + } + +} + + +bool BaslerSource::grab(cv::mat& frame) +{ + if (!_open) {return NULL;} + + // Set grab timeout + long int timeout = _fps > 0 ? std::max(static_cast(1000), static_cast(1000. / _fps)) : 1000; // set capture timeout to at least 1000 ms + + try { + cam.RetrieveResult(timeout, ptrGrabResult, TimeoutHandling_ThrowException); + if (!ptrGrabResult->GrabSucceeded()) + cout << "Error: " << ptrGrabResult->GetErrorCode() << + " " << ptrGrabResult->GetErrorDescription() << endl; + else // TODO: find the correct method + LOG_DBG("Frame captured! %dx%d%d", _width, _height, ptrGrabResult->GetNumChannels()); + } + catch (const GenericException &e) { + cerr << "An exception occurred." << endl + << e.GetDescription() << endl; + } + + + try { + // Convert image + Pylon::CImageFormatConverter formatConverter; + formatConverter.Convert(pylonImg, ptrGrabResult); + + Mat tmp(_height, _width, CV_8UC3, (uint8_t*) ptrGrabResult); // TODO: test this initialization + tmp.copyTo(frame); + + // TODO: release the original image + ptrGrabResult->release(); // TODO: find the correct method + } + catch (const GenericException &e) { + LOG_ERR("Error converting PGR frame! Error was: %s", e.GetDescription()); + return false; + } + + + +} From febf2286b7070691a8861acb7e36cafeb77330bc Mon Sep 17 00:00:00 2001 From: young24 Date: Sun, 2 Jun 2019 17:48:54 +0800 Subject: [PATCH 138/235] update implementations, x64 VS2017 compilation passed --- include/BaslerSource.h | 4 ++-- include/timing.h | 1 + src/BaslerSource.cpp | 46 ++++++++++++++---------------------------- src/ConfigParser.cpp | 2 ++ 4 files changed, 20 insertions(+), 33 deletions(-) diff --git a/include/BaslerSource.h b/include/BaslerSource.h index 158a277..293b601 100644 --- a/include/BaslerSource.h +++ b/include/BaslerSource.h @@ -15,10 +15,10 @@ class BaslerSource : public FrameSource { public: BaslerSource(int index=0); - virtual ~PGRSource(); + virtual ~BaslerSource(); virtual double getFPS(); - virtual bool setFPS(); + virtual bool setFPS(double); virtual bool rewind() {return false; }; virtual bool grab(cv::Mat& frame); diff --git a/include/timing.h b/include/timing.h index afada2f..2965dd6 100644 --- a/include/timing.h +++ b/include/timing.h @@ -5,6 +5,7 @@ /// \copyright CC BY-NC-SA 3.0 #pragma once +#pragma warning(disable : 4996) #include #include diff --git a/src/BaslerSource.cpp b/src/BaslerSource.cpp index 565ed76..9eaf069 100644 --- a/src/BaslerSource.cpp +++ b/src/BaslerSource.cpp @@ -1,30 +1,14 @@ -class BaslerSource : public FrameSource { - -public: - BaslerSource(int index=0); - virtual ~PGRSource(); - - virtual double getFPS(); - virtual bool setFPS(); - - virtual bool rewind() {return false; }; - virtual bool grab(cv::Mat& frame); - -private: - Pylon::CPylonImage pylonImg; - Pylon::CGrabResultPtr ptrGrabResult; - Pylon::CInstantCamera cam; - -}; - ----- #include "BaslerSource.h" #include "Logger.h" #include "timing.h" -using namespace cv::Mat; +#include + + +using namespace std; +using namespace cv; using namespace Pylon; BaslerSource::BaslerSource(int index) @@ -67,7 +51,7 @@ BaslerSource::~BaslerSource() { if (_open) { try { - cam.endAcquisition(); // TODO: find the correct method + cam.DetachDevice(); } catch (const GenericException &e) { LOG_ERR("Error opening capture device! Error was: %s", e.GetDescription()); @@ -81,7 +65,7 @@ double BaslerSource::getFPS() return _fps; } -bool setFPS(double fps) +bool BaslerSource::setFPS(double fps) { using namespace GenApi; @@ -90,7 +74,7 @@ bool setFPS(double fps) const CIntegerPtr camFrameRate = control.GetNode("FrameRate");// TODO: test this line if (IsWritable(camFrameRate)) { - camFrameRate->SetValue(frameRate); + camFrameRate->SetValue(fps); return true; } else { @@ -100,20 +84,20 @@ bool setFPS(double fps) } -bool BaslerSource::grab(cv::mat& frame) +bool BaslerSource::grab(cv::Mat& frame) { if (!_open) {return NULL;} // Set grab timeout - long int timeout = _fps > 0 ? std::max(static_cast(1000), static_cast(1000. / _fps)) : 1000; // set capture timeout to at least 1000 ms + long int timeout = _fps > 0 ? max(static_cast(1000), static_cast(1000. / _fps)) : 1000; // set capture timeout to at least 1000 ms try { cam.RetrieveResult(timeout, ptrGrabResult, TimeoutHandling_ThrowException); if (!ptrGrabResult->GrabSucceeded()) cout << "Error: " << ptrGrabResult->GetErrorCode() << " " << ptrGrabResult->GetErrorDescription() << endl; - else // TODO: find the correct method - LOG_DBG("Frame captured! %dx%d%d", _width, _height, ptrGrabResult->GetNumChannels()); + else // TODO: no getNumChannels method found, use GetPixelType() instead. + LOG_DBG("Frame captured! %dx%d%s", _width, _height, ptrGrabResult->GetPixelType()); } catch (const GenericException &e) { cerr << "An exception occurred." << endl @@ -126,11 +110,11 @@ bool BaslerSource::grab(cv::mat& frame) Pylon::CImageFormatConverter formatConverter; formatConverter.Convert(pylonImg, ptrGrabResult); - Mat tmp(_height, _width, CV_8UC3, (uint8_t*) ptrGrabResult); // TODO: test this initialization + Mat tmp(_height, _width, CV_8UC3, (uint8_t*)pylonImg.GetBuffer()); tmp.copyTo(frame); - // TODO: release the original image - ptrGrabResult->release(); // TODO: find the correct method + // release the original image pointer + ptrGrabResult.Release(); } catch (const GenericException &e) { LOG_ERR("Error converting PGR frame! Error was: %s", e.GetDescription()); diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 13a08c0..ae34d7b 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -3,6 +3,8 @@ /// \brief Read/write simple key/value pair config files. /// \author Richard Moore /// \copyright CC BY-NC-SA 3.0 +#pragma warning(disable : 4996) + #include "ConfigParser.h" From 478bd431426d15e5901e9ac1bdb9ae3493c60c24 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 3 Jun 2019 22:29:13 +0200 Subject: [PATCH 139/235] [configGui] add fisheye support --- src/ConfigGUI.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index 5a6b399..8ac4584 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -270,9 +270,15 @@ bool ConfigGui::setFrame(Mat& frame) } LOG("Using vfov: %f deg", vfov); - - //FIXME: support also fisheye models! - _cam_model = CameraModel::createRectilinear(static_cast(_w), static_cast(_h), vfov * CM_D2R); + + bool fisheye = false; + if (_cfg.getBool("fisheye", fisheye) && fisheye) { + _cam_model = CameraModel::createFisheye(_w, _h, vfov * CM_D2R / (double)_h, 360 * CM_D2R); + } + else { + // default to rectilinear + _cam_model = CameraModel::createRectilinear(_w, _h, vfov * CM_D2R); + } return true; } From 4f825a47ceb28dbe2598f9d98392575104f63b84 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 3 Jun 2019 23:12:12 +0200 Subject: [PATCH 140/235] Update README.md --- README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index de48b3f..d73a6a8 100644 --- a/README.md +++ b/README.md @@ -44,17 +44,22 @@ FicTrac imposes no requirements on the *italicised* items; how you design these ### Installation -The FicTrac source code can be built for both Windows and Ubuntu (Linux) operating systems, or you can build and run FicTrac from within a [virtual machine](https://www.virtualbox.org/) on any operating system. - -1. Download and install required dependencies: - 1. [Cmake build system](https://cmake.org/download/) (binary distribution) - 2. For Windows installations, if you don't already have Visual Studio (C++ workflow) installed, you will need to install the [build tools](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2017). - 3. Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). +The FicTrac source code can be built for both Windows and Linux (e.g. Ubuntu) operating systems, or you can build and run FicTrac from within a [virtual machine](https://www.virtualbox.org/) on any operating system. The following instructions are for a 64-bit machine, if you are using a 32-bit machine you will need to replace x64 with x86 in the instructions below. + +1. Download and install required build tools and dependencies: + 1. Windows only: + 1. [Cmake build system](https://cmake.org/download/) (Windows win64-x64 Installer) + 2. If you don't already have Visual Studio (C++ workflow) installed, you will need to install the [Build Tools for Visual Studio](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2017). + 2. Linux (Ubuntu) only: + 1. Run the following from terminal: ```[Linux] sudo apt-get install git cmake curl unzip tar``` + 3. (Windows and Linux) Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). 4. Using Vcpkg, install OpenCV, NLopt, and Boost::asio software packages (this may take 10-30 mins): + ``` [Windows] .\vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows [Linux] ./vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux ``` + 2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: ``` mkdir build From 5a280d41487e2680f5c7eb327cd758aaba6a9b8f Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 4 Jun 2019 11:22:02 +0200 Subject: [PATCH 141/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d73a6a8..8f5561a 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The FicTrac source code can be built for both Windows and Linux (e.g. Ubuntu) op 1. [Cmake build system](https://cmake.org/download/) (Windows win64-x64 Installer) 2. If you don't already have Visual Studio (C++ workflow) installed, you will need to install the [Build Tools for Visual Studio](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2017). 2. Linux (Ubuntu) only: - 1. Run the following from terminal: ```[Linux] sudo apt-get install git cmake curl unzip tar``` + 1. Run the following from terminal to install necessary build tools and dependencies: ```[Linux] sudo apt-get install git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev``` 3. (Windows and Linux) Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). 4. Using Vcpkg, install OpenCV, NLopt, and Boost::asio software packages (this may take 10-30 mins): From e16d6e0b9410e54152fcb1e8b6ecacf6f809cbf1 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 4 Jun 2019 12:03:21 +0200 Subject: [PATCH 142/235] Update README.md --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8f5561a..66f3ecc 100644 --- a/README.md +++ b/README.md @@ -84,13 +84,24 @@ Remember to update and re-build FicTrac occasionally, as the program is still un | --- | --- | --- | --- | --- | | Build status | Windows | [![Build Status](https://dev.azure.com/rjdmoore/FicTrac/_apis/build/status/rjdmoore.fictrac?branchName=master&jobName=Windows)](https://dev.azure.com/rjdmoore/FicTrac/_build/latest?definitionId=1&branchName=master) | Linux | [![Build Status](https://dev.azure.com/rjdmoore/FicTrac/_apis/build/status/rjdmoore.fictrac?branchName=master&jobName=Linux)](https://dev.azure.com/rjdmoore/FicTrac/_build/latest?definitionId=1&branchName=master) | -#### USB3 camera installation +#### USB2/3 camera installation -If you are using a USB3 camera and are receiving error messages when FicTrac tries to connect to your camera, you may need to tell FicTrac to use the SDK provided with your camera, rather than the generic OpenCV interface. The instructions for switching to the camera's SDK are different for each manufacturer. Currently there is support for PGR (FLIR) USB3 cameras via the Spinnaker SDK. +If you are using an industrial USB2/3 camera and are receiving error messages when FicTrac tries to connect to your camera, you may need to tell FicTrac to use the SDK provided with your camera, rather than the generic OpenCV interface. The instructions for switching to the camera's SDK are different for each manufacturer. Currently there is support for PGR (FLIR) USB2/3 cameras via the Flycapture/Spinnaker SDK. + +##### PGR (FLIR) Flycapture SDK + +1. Download and install the latest [Flycapture SDK](https://www.flir.com/products/flycapture-sdk/). +2. When preparing the build files for FicTrac using Cmake, you will need to specify to use Flycapture using the switch `-D PGR_USB2=ON` and depending on where you installed the SDK, you may also need to provide the SDK directory path using the switch `-D PGR_DIR=...`. For example, for a Windows installation you would replace step 3 above with: +``` +cmake -G "Visual Studio 15 2017 Win64" -D CMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -D PGR_USB2=ON -D PGR_DIR="C:\path\to\Flycapture" .. +``` +3. Follow the other build steps as normal. + +Before running FicTrac, you may configure your camera (frame rate, resolution, etc) as desired using the SDK utilities. ##### PGR (FLIR) Spinnaker SDK -1. Download and install the latest Spinnaker (full) SDK from [PGR downloads page](https://www.ptgrey.com/support/downloads). +1. Download and install the latest [Spinnaker (full) SDK](https://www.flir.com/products/spinnaker-sdk/). 2. When preparing the build files for FicTrac using Cmake, you will need to specify to use Spinnaker using the switch `-D PGR_USB3=ON` and depending on where you installed the SDK, you may also need to provide the SDK directory path using the switch `-D PGR_DIR=...`. For example, for a Windows installation you would replace step 3 above with: ``` cmake -G "Visual Studio 15 2017 Win64" -D CMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -D PGR_USB3=ON -D PGR_DIR="C:\path\to\Spinnaker" .. From dde288fe2248f03c7276707bd7a02eae25c6fdd4 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 4 Jun 2019 12:06:03 +0200 Subject: [PATCH 143/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 66f3ecc..ae8efc4 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ Before running FicTrac, you may configure your camera (frame rate, resolution, e ##### PGR (FLIR) Spinnaker SDK -1. Download and install the latest [Spinnaker (full) SDK](https://www.flir.com/products/spinnaker-sdk/). +1. Download and install the latest [Spinnaker SDK](https://www.flir.com/products/spinnaker-sdk/). 2. When preparing the build files for FicTrac using Cmake, you will need to specify to use Spinnaker using the switch `-D PGR_USB3=ON` and depending on where you installed the SDK, you may also need to provide the SDK directory path using the switch `-D PGR_DIR=...`. For example, for a Windows installation you would replace step 3 above with: ``` cmake -G "Visual Studio 15 2017 Win64" -D CMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -D PGR_USB3=ON -D PGR_DIR="C:\path\to\Spinnaker" .. From ceb5e93fb709821f073bc6c652d6ede9fa39b894 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 4 Jun 2019 16:10:20 +0200 Subject: [PATCH 144/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae8efc4..d09527e 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The FicTrac source code can be built for both Windows and Linux (e.g. Ubuntu) op 1. [Cmake build system](https://cmake.org/download/) (Windows win64-x64 Installer) 2. If you don't already have Visual Studio (C++ workflow) installed, you will need to install the [Build Tools for Visual Studio](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2017). 2. Linux (Ubuntu) only: - 1. Run the following from terminal to install necessary build tools and dependencies: ```[Linux] sudo apt-get install git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev``` + 1. Run the following from terminal to install necessary build tools and dependencies: ```[Linux] sudo apt-get install gcc git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev``` 3. (Windows and Linux) Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). 4. Using Vcpkg, install OpenCV, NLopt, and Boost::asio software packages (this may take 10-30 mins): From 5e7eb44c5ed12986e1d1756c998ee369f89b4782 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 12 Jun 2019 22:02:00 +0200 Subject: [PATCH 145/235] [configGui] more descriptive name for enh_cfg_disp --- src/ConfigGUI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index 8ac4584..dafeeea 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -195,7 +195,7 @@ ConfigGui::ConfigGui(string config_fn) /// Optionally enhance frame for config bool do_enhance = false; - _cfg.getBool("enhance_cfg", do_enhance); + _cfg.getBool("enh_cfg_disp", do_enhance); if (_open && do_enhance) { LOG("Enhancing config image .."); Mat maximg = input_frame.clone(); From 7d5c9a6bac120199a6b1992a5be02b4f64628fbc Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Thu, 13 Jun 2019 00:39:44 +0200 Subject: [PATCH 146/235] revert disable 4996 --- include/timing.h | 1 - src/ConfigParser.cpp | 2 -- 2 files changed, 3 deletions(-) diff --git a/include/timing.h b/include/timing.h index 2965dd6..afada2f 100644 --- a/include/timing.h +++ b/include/timing.h @@ -5,7 +5,6 @@ /// \copyright CC BY-NC-SA 3.0 #pragma once -#pragma warning(disable : 4996) #include #include diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index ae34d7b..13a08c0 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -3,8 +3,6 @@ /// \brief Read/write simple key/value pair config files. /// \author Richard Moore /// \copyright CC BY-NC-SA 3.0 -#pragma warning(disable : 4996) - #include "ConfigParser.h" From a65283d8ac8c17ee3129fa06ce438eeaa5e31a4a Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Thu, 13 Jun 2019 00:40:51 +0200 Subject: [PATCH 147/235] fix grab() return NULL --- src/BaslerSource.cpp | 2 +- src/PGRSource.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/BaslerSource.cpp b/src/BaslerSource.cpp index 9eaf069..ad5cf0a 100644 --- a/src/BaslerSource.cpp +++ b/src/BaslerSource.cpp @@ -86,7 +86,7 @@ bool BaslerSource::setFPS(double fps) bool BaslerSource::grab(cv::Mat& frame) { - if (!_open) {return NULL;} + if (!_open) { return false; } // Set grab timeout diff --git a/src/PGRSource.cpp b/src/PGRSource.cpp index e69c50d..b3fba93 100644 --- a/src/PGRSource.cpp +++ b/src/PGRSource.cpp @@ -231,7 +231,7 @@ bool PGRSource::setFPS(double fps) bool PGRSource::grab(cv::Mat& frame) { - if( !_open ) { return NULL; } + if( !_open ) { return false; } #if defined(PGR_USB3) ImagePtr pgr_image = NULL; @@ -256,12 +256,12 @@ bool PGRSource::grab(cv::Mat& frame) } } catch (Spinnaker::Exception& e) { - LOG_ERR("Error grabbing PGR frame! Error was: %s", e.what()); + LOG_ERR("Error grabbing frame! Error was: %s", e.what()); pgr_image->Release(); return false; } catch (...) { - LOG_ERR("Error grabbing PGR frame!"); + LOG_ERR("Error grabbing frame!"); pgr_image->Release(); return false; } @@ -279,12 +279,12 @@ bool PGRSource::grab(cv::Mat& frame) return true; } catch (Spinnaker::Exception& e) { - LOG_ERR("Error converting PGR frame! Error was: %s", e.what()); + LOG_ERR("Error converting frame! Error was: %s", e.what()); pgr_image->Release(); return false; } catch (...) { - LOG_ERR("Error converting PGR frame!"); + LOG_ERR("Error converting frame!"); pgr_image->Release(); return false; } From 6b461db69f58bcc8f65391d6833a74917050fe62 Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Thu, 13 Jun 2019 00:41:48 +0200 Subject: [PATCH 148/235] formatting; additional logging; get fps from ResultingFrameRateAbs (?); --- include/BaslerSource.h | 36 ++++---- src/BaslerSource.cpp | 184 +++++++++++++++++++++++------------------ 2 files changed, 124 insertions(+), 96 deletions(-) diff --git a/include/BaslerSource.h b/include/BaslerSource.h index 293b601..d9ff747 100644 --- a/include/BaslerSource.h +++ b/include/BaslerSource.h @@ -1,3 +1,11 @@ +/// FicTrac http://rjdmoore.net/fictrac/ +/// \file BaslerSource.h +/// \brief Basler USB3 sources (Pylon SDK). +/// \author Wenbin Yang +/// \copyright CC BY-NC-SA 3.0 + +#if defined(BASLER_USB3) + #pragma once #include "FrameSource.h" @@ -6,26 +14,22 @@ #include #include #include -#ifdef PYLON_WIN_BUILD -# include -#endif - class BaslerSource : public FrameSource { - public: - BaslerSource(int index=0); - virtual ~BaslerSource(); - - virtual double getFPS(); - virtual bool setFPS(double); + BaslerSource(int index=0); + ~BaslerSource(); - virtual bool rewind() {return false; }; - virtual bool grab(cv::Mat& frame); + double getFPS(); + bool setFPS(double); -private: - Pylon::CPylonImage pylonImg; - Pylon::CGrabResultPtr ptrGrabResult; - Pylon::CInstantCamera cam; + bool rewind() { return false; }; + bool grab(cv::Mat& frame); + private: + Pylon::CPylonImage _pylonImg; + Pylon::CGrabResultPtr _ptrGrabResult; + Pylon::CInstantCamera _cam; }; + +#endif // BASLER_USB3 diff --git a/src/BaslerSource.cpp b/src/BaslerSource.cpp index ad5cf0a..c253643 100644 --- a/src/BaslerSource.cpp +++ b/src/BaslerSource.cpp @@ -1,3 +1,10 @@ +/// FicTrac http://rjdmoore.net/fictrac/ +/// \file BaslerSource.cpp +/// \brief Basler USB3 sources (Pylon SDK). +/// \author Wenbin Yang, Richard Moore +/// \copyright CC BY-NC-SA 3.0 + +#if defined(BASLER_USB3) #include "BaslerSource.h" @@ -6,121 +13,138 @@ #include - using namespace std; using namespace cv; using namespace Pylon; BaslerSource::BaslerSource(int index) { - try { - PylonInitialize(); - // create an instant camera object - cam.Attach(CTlFactory::GetInstance().CreateFirstDevice()); + try { + PylonInitialize(); + // create an instant camera object + _cam.Attach(CTlFactory::GetInstance().CreateFirstDevice()); - LOG("Opening Basler camera device: %s", cam.GetDeviceInfo().GetModelName()); + LOG("Opening Basler camera device: %s", _cam.GetDeviceInfo().GetModelName()); - // Allow all the names in the namespace GenApi to be used without qualification. - using namespace GenApi; + // Allow all the names in the namespace GenApi to be used without qualification. + using namespace GenApi; - // Get the camera control object. - INodeMap &control = cam.GetNodeMap(); + // Get the camera control object. + INodeMap &control = _cam.GetNodeMap(); - // Get some params - const CIntegerPtr camWidth = control.GetNode("Width"); - const CIntegerPtr camHeight = control.GetNode("Height"); - const CIntegerPtr camFrameRate = control.GetNode("FrameRate");// TODO: test this line - // TODO: assign int values to _width, _height, and _fps + // Start acquisition + _cam.StartGrabbing(); - // Start acquisition - cam.StartGrabbing(); + // Get some params + const CIntegerPtr camWidth = control.GetNode("Width"); + const CIntegerPtr camHeight = control.GetNode("Height"); - LOG("Basler camera initialised (%dx%d @ %.3f fps)!", _width, _height, _fps); + _width = camWidth->GetValue(); + _height = camHeight->GetValue(); + _fps = getFPS(); - _open = true; - _live = true; + LOG("Basler camera initialised (%dx%d @ %.3f fps)!", _width, _height, _fps); - } - catch (const GenericException &e) { - LOG_ERR("Error opening capture device! Error was: %s", e.GetDescription()); - } -} + _open = true; + _live = true; + + } + catch (const GenericException &e) { + LOG_ERR("Error opening capture device! Error was: %s", e.GetDescription()); + } +} BaslerSource::~BaslerSource() { - if (_open) { - try { - cam.DetachDevice(); - } - catch (const GenericException &e) { - LOG_ERR("Error opening capture device! Error was: %s", e.GetDescription()); + if (_open) { + try { + _cam.DetachDevice(); + } + catch (const GenericException &e) { + LOG_ERR("Error opening capture device! Error was: %s", e.GetDescription()); + } + _open = false; } - _open = false; - } } double BaslerSource::getFPS() { - return _fps; + const GenApi::CFloatPtr camFrameRate = _cam.GetNodeMap().GetNode("ResultingFrameRateAbs");// TODO: test this line + return camFrameRate->GetValue(); } bool BaslerSource::setFPS(double fps) { - using namespace GenApi; - - // Get the camera control object. - INodeMap &control = cam.GetNodeMap(); - const CIntegerPtr camFrameRate = control.GetNode("FrameRate");// TODO: test this line - if (IsWritable(camFrameRate)) - { - camFrameRate->SetValue(fps); - return true; - } - else { - return false; - } - + using namespace GenApi; + + bool ret = false; + if (_open && (fps > 0)) { + // Get the camera control object. + INodeMap &control = _cam.GetNodeMap(); + const GenApi::CFloatPtr camFrameRate = _cam.GetNodeMap().GetNode("ResultingFrameRateAbs");// TODO: test this line + if (IsWritable(camFrameRate)) + { + camFrameRate->SetValue(fps); + ret = true; + } + else { + LOG_ERR("Error setting frame rate!"); + } + _fps = getFPS(); + LOG("Device frame rate is now %.2f", _fps); + } } - bool BaslerSource::grab(cv::Mat& frame) { if (!_open) { return false; } - // Set grab timeout - - long int timeout = _fps > 0 ? max(static_cast(1000), static_cast(1000. / _fps)) : 1000; // set capture timeout to at least 1000 ms - try { - cam.RetrieveResult(timeout, ptrGrabResult, TimeoutHandling_ThrowException); - if (!ptrGrabResult->GrabSucceeded()) - cout << "Error: " << ptrGrabResult->GetErrorCode() << - " " << ptrGrabResult->GetErrorDescription() << endl; - else // TODO: no getNumChannels method found, use GetPixelType() instead. - LOG_DBG("Frame captured! %dx%d%s", _width, _height, ptrGrabResult->GetPixelType()); - } - catch (const GenericException &e) { - cerr << "An exception occurred." << endl - << e.GetDescription() << endl; - } - - - try { - // Convert image - Pylon::CImageFormatConverter formatConverter; - formatConverter.Convert(pylonImg, ptrGrabResult); - - Mat tmp(_height, _width, CV_8UC3, (uint8_t*)pylonImg.GetBuffer()); - tmp.copyTo(frame); + // Set grab timeout + long int timeout = _fps > 0 ? max(static_cast(1000), static_cast(1000. / _fps)) : 1000; // set capture timeout to at least 1000 ms + try { + _cam.RetrieveResult(timeout, _ptrGrabResult, TimeoutHandling_ThrowException); + double ts = ts_ms(); // backup, in case the device timestamp is junk + //_timestamp = pgr_image->GetTimeStamp(); // TODO: extract timestamp + if (!_ptrGrabResult->GrabSucceeded()) { + LOG_ERR("Error! Image capture failed (%d: %s).", _ptrGrabResult->GetErrorCode(), _ptrGrabResult->GetErrorDescription().c_str()); + // release the original image pointer + _ptrGrabResult.Release(); + return false; + } + else { // TODO: no getNumChannels method found, use GetPixelType() instead. + LOG_DBG("Frame captured %dx%dx%d @ %f (%f)", _ptrGrabResult->GetWidth(), _ptrGrabResult->GetHeight(), _ptrGrabResult->GetPixelType(), _timestamp, ts); + } + if (_timestamp <= 0) { + _timestamp = ts; + } + } + catch (const GenericException &e) { + LOG_ERR("Error grabbing frame! Error was: %s", e.GetDescription()); + // release the original image pointer + _ptrGrabResult.Release(); + return false; + } - // release the original image pointer - ptrGrabResult.Release(); - } - catch (const GenericException &e) { - LOG_ERR("Error converting PGR frame! Error was: %s", e.GetDescription()); - return false; - } + try { + // Convert image + Pylon::CImageFormatConverter formatConverter; + formatConverter.Convert(_pylonImg, _ptrGrabResult); + Mat tmp(_height, _width, CV_8UC3, (uint8_t*)_pylonImg.GetBuffer()); + tmp.copyTo(frame); + // release the original image pointer + _ptrGrabResult.Release(); + } + catch (const GenericException &e) { + LOG_ERR("Error converting frame! Error was: %s", e.GetDescription()); + // release the original image pointer + _ptrGrabResult.Release(); + return false; + } + return true; } + +#endif // BASLER_USB3 From 40008858b5b7fcde3399c2135a3b1776742bb858 Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Thu, 13 Jun 2019 00:42:01 +0200 Subject: [PATCH 149/235] add cmake rules for Basler --- CMakeLists.txt | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 124a45e..84b9020 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,10 +23,13 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin) # optional build config option(PGR_USB3 "Use Spinnaker SDK to capture from PGR USB3 cameras" OFF) # Disabled by default option(PGR_USB2 "Use FlyCapture SDK to capture from PGR USB2 cameras" OFF) # Disabled by default +option(BASLER_USB3 "Use Pylon SDK to capture from Basler USB3 cameras" OFF) # Disabled by default if(PGR_USB3) set(PGR_DIR "." CACHE PATH "Path to PGR Spinnaker SDK folder") elseif(PGR_USB2) set(PGR_DIR "." CACHE PATH "Path to PGR FlyCapture SDK folder") +elseif(BASLER_USB3) + set(BASLER_DIR "." CACHE PATH "Path to Basler Pylon SDK folder") endif() # find dependencies @@ -40,6 +43,7 @@ if(NLopt_FOUND) else() message(FATAL_ERROR "Error! Could not find NLopt lib at ${NLOPT_LIB}!") endif() + if(MSVC) if(PGR_USB3) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}/lib64/vs2015) @@ -47,7 +51,11 @@ if(MSVC) elseif(PGR_USB2) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}/lib64/vs2015) find_library(PGR_LIB FlyCapture2_v140.lib) + elseif(BASLER_USB3) + set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${BASLER_DIR}/Development/lib/x64) + find_library(BASLER_LIB PylonBase_MD_VC120_v5_0.lib) endif() + else() # gcc if(PGR_USB3) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}) @@ -55,8 +63,13 @@ else() # gcc elseif(PGR_USB2) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${PGR_DIR}) find_library(PGR_LIB libflycapture.so) + elseif(BASLER_USB3) + set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${BASLER_DIR}) + #find_library(PGR_LIB libflycapture.so) + # FIXME! endif() endif() + if(PGR_USB2 OR PGR_USB3) get_filename_component(PGR_DIR ${PGR_LIB} DIRECTORY) get_filename_component(PGR_DIR ${PGR_DIR} DIRECTORY) # step up 1 level @@ -68,6 +81,18 @@ if(PGR_USB2 OR PGR_USB3) else() message(FATAL_ERROR "Error! Could not find PGR FlyCapture/Spinnaker lib at ${PGR_DIR}!") endif() +elseif(BASLER_USB3) + # FIXME! Linux + get_filename_component(BASLER_DIR ${BASLER_LIB} DIRECTORY) + get_filename_component(BASLER_DIR ${BASLER_DIR} DIRECTORY) # step up 1 level + if(MSVC) + get_filename_component(BASLER_DIR ${BASLER_DIR} DIRECTORY) # step up 1 level + endif() + if(BASLER_LIB) + message(STATUS "Found Basler Pylon lib ${BASLER_LIB}") + else() + message(FATAL_ERROR "Error! Could not find Basler Pylon lib at ${BASLER_DIR}!") + endif() endif() # add include dirs @@ -82,6 +107,13 @@ if(PGR_USB2 OR PGR_USB3) include_directories(${PGR_DIR}/include/spinnaker) # for ubuntu default install dir endif() endif() +elseif(BASLER_USB3) + if(MSVC) + include_directories(${BASLER_DIR}/include) + else() + # FIXME! + #include_directories(${PGR_DIR}/include/spinnaker) # for ubuntu default install dir + endif() endif() # find sources to build @@ -93,12 +125,14 @@ add_executable(configGui ${PROJECT_SOURCE_DIR}/exec/configGui.cpp) add_executable(fictrac ${PROJECT_SOURCE_DIR}/exec/fictrac.cpp) # add preprocessor definitions -# public means defs will be inherited by linked executables +# 'public' means defs will be inherited by linked executables target_compile_definitions(fictrac_core PUBLIC _CRT_SECURE_NO_WARNINGS NOMINMAX) if(PGR_USB2) target_compile_definitions(fictrac_core PUBLIC PGR_USB2) elseif(PGR_USB3) target_compile_definitions(fictrac_core PUBLIC PGR_USB3) +elseif(BASLER_USB3) + target_compile_definitions(fictrac_core PUBLIC BASLER_USB3) endif() # add compile options @@ -118,6 +152,8 @@ else() # gcc endif() if(PGR_USB2 OR PGR_USB3) target_link_libraries(fictrac_core PUBLIC ${PGR_LIB}) +elseif(BASLER_USB3) + target_link_libraries(fictrac_core PUBLIC ${BASLER_LIB}) endif() # post-build @@ -146,6 +182,8 @@ if(MSVC) file(GLOB DLLS ${PROJECT_SOURCE_DIR}/dll/*.dll) list(APPEND TO_COPY "${DLLS}") + # PGR/Basler?? + add_custom_command( TARGET fictrac POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${TO_COPY} From 820e8670ef8e1cd2179cd0b5f50330a1e4b4d620 Mon Sep 17 00:00:00 2001 From: Richard Moore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 14 Jun 2019 13:28:40 +0200 Subject: [PATCH 150/235] fix missing BaslerSource::setFPS() return statement --- src/BaslerSource.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BaslerSource.cpp b/src/BaslerSource.cpp index c253643..b64582f 100644 --- a/src/BaslerSource.cpp +++ b/src/BaslerSource.cpp @@ -94,6 +94,7 @@ bool BaslerSource::setFPS(double fps) _fps = getFPS(); LOG("Device frame rate is now %.2f", _fps); } + return ret; } bool BaslerSource::grab(cv::Mat& frame) From f5218c894d44ab8c4a476c55d720600ace0c4b0c Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 4 Jul 2019 10:13:45 +0200 Subject: [PATCH 151/235] [fictrac] write frame log file when saving raw or debug videos --- include/Trackball.h | 3 ++- src/Trackball.cpp | 23 ++++++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/include/Trackball.h b/include/Trackball.h index 97e173f..0489c5d 100644 --- a/include/Trackball.h +++ b/include/Trackball.h @@ -116,6 +116,7 @@ class Trackball private: /// Drawing struct DrawData { + unsigned int log_frame; cv::Mat src_frame, roi_frame, sphere_view, sphere_map; CmPoint64f dr_roi; cv::Mat R_roi; @@ -175,7 +176,7 @@ class Trackball std::string _base_fn; std::unique_ptr _frameGrabber; bool _do_sock_output, _do_com_output; - std::unique_ptr _data_log, _data_sock, _data_com; + std::unique_ptr _data_log, _data_sock, _data_com, _vid_frames; /// Thread stuff. std::atomic_bool _active, _kill, _do_reset; diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 3531f1d..d4c0054 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -89,6 +89,9 @@ bool intersectSphere(const double camVec[3], double sphereVec[3], const double r Trackball::Trackball(string cfg_fn) : _init(false), _reset(true), _clean_map(true), _active(true), _kill(false), _do_reset(false) { + /// Save execTime for outptut file naming. + string exec_time = execTime(); + /// Load and parse config file. if (_cfg.read(cfg_fn) <= 0) { LOG_ERR("Error parsing config file (%s)!", cfg_fn.c_str()); @@ -379,7 +382,7 @@ Trackball::Trackball(string cfg_fn) _roi_mask, _p1s_lut); /// Output. - string data_fn = _base_fn + "-" + execTime() + ".dat"; + string data_fn = _base_fn + "-" + exec_time + ".dat"; _data_log = make_unique(RecorderInterface::RecordType::FILE, data_fn); if (!_data_log->is_active()) { LOG_ERR("Error! Unable to open output data log file (%s).", data_fn.c_str()); @@ -469,7 +472,7 @@ Trackball::Trackball(string cfg_fn) // raw input video if (_save_raw) { - string vid_fn = _base_fn + "-raw-" + execTime() + "." + fext; + string vid_fn = _base_fn + "-raw-" + exec_time + "." + fext; double fps = source->getFPS(); if (fps <= 0) { fps = 25; } LOG_DBG("Opening %s for video writing (%s %dx%d @ %f FPS)", vid_fn.c_str(), cstr.c_str(), source->getWidth(), source->getHeight(), fps); @@ -483,7 +486,7 @@ Trackball::Trackball(string cfg_fn) // debug output video if (_save_debug) { - string vid_fn = _base_fn + "-dbg-" + execTime() + "." + fext; + string vid_fn = _base_fn + "-dbg-" + exec_time + "." + fext; double fps = source->getFPS(); if (fps <= 0) { fps = 25; } LOG_DBG("Opening %s for video writing (%s %dx%d @ %f FPS)", vid_fn.c_str(), cstr.c_str(), 4 * DRAW_CELL_DIM, 3 * DRAW_CELL_DIM, fps); @@ -494,6 +497,15 @@ Trackball::Trackball(string cfg_fn) return; } } + + // create output file containing log lines corresponding to video frames, for synching video output + string fn = _base_fn + "-vidLogFrames-" + exec_time + ".txt"; + _vid_frames = make_unique(RecorderInterface::RecordType::FILE, fn); + if (!_vid_frames->is_active()) { + LOG_ERR("Error! Unable to open output video frame number log file (%s).", fn.c_str()); + _active = false; + return; + } } /// Frame source. @@ -646,6 +658,7 @@ void Trackball::process() if (_do_display) { auto data = make_shared(); + data->log_frame = _data.cnt; data->src_frame = _src_frame.clone(); data->roi_frame = _roi_frame.clone(); data->sphere_map = _sphere_map.clone(); @@ -1218,6 +1231,7 @@ void Trackball::drawCanvas(shared_ptr data) Mat& sphere_map = data->sphere_map; deque& R_roi_hist = data->R_roi_hist; deque& pos_heading_hist = data->pos_heading_hist; + unsigned int log_frame = data->log_frame; /// Draw source image. double radPerPix = _sphere_rad * 3.0 / (2 * DRAW_CELL_DIM); @@ -1410,6 +1424,9 @@ void Trackball::drawCanvas(shared_ptr data) if (_save_debug) { _debug_vid.write(canvas); } + if (_save_raw || _save_debug) { + _vid_frames->addMsg(to_string(log_frame) + "\n"); + } } /// From 41ab862cda14d4acf2b387d6d2d42459444fb48b Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 9 Jul 2019 22:19:27 +0200 Subject: [PATCH 152/235] [fictrac] data timestamp now set to ms since midnight in local timezone; add high resolution delta ts to output data --- include/FrameGrabber.h | 12 ++++++------ include/FrameSource.h | 3 ++- include/Trackball.h | 2 +- include/timing.h | 42 +++++++++++++++++++----------------------- src/CVSource.cpp | 5 +++-- src/FrameGrabber.cpp | 15 +++++++++++---- src/PGRSource.cpp | 3 ++- src/Trackball.cpp | 10 +++++++--- 8 files changed, 51 insertions(+), 41 deletions(-) diff --git a/include/FrameGrabber.h b/include/FrameGrabber.h index 9cac12f..f5d80a4 100644 --- a/include/FrameGrabber.h +++ b/include/FrameGrabber.h @@ -37,12 +37,12 @@ class FrameGrabber void terminate(); - bool getFrameSet(cv::Mat& frame, cv::Mat& remap, double& timestamp, bool latest); - bool getLatestFrameSet(cv::Mat& frame, cv::Mat& remap, double& timestamp) { - return getFrameSet(frame, remap, timestamp, true); + bool getFrameSet(cv::Mat& frame, cv::Mat& remap, double& timestamp, double& ms_since_midnight, bool latest = true); + bool getLatestFrameSet(cv::Mat& frame, cv::Mat& remap, double& timestamp, double& ms_since_midnight) { + return getFrameSet(frame, remap, timestamp, ms_since_midnight, true); } - bool getNextFrameSet(cv::Mat& frame, cv::Mat& remap, double& timestamp) { - return getFrameSet(frame, remap, timestamp, false); + bool getNextFrameSet(cv::Mat& frame, cv::Mat& remap, double& timestamp, double& ms_since_midnight) { + return getFrameSet(frame, remap, timestamp, ms_since_midnight, false); } private: @@ -69,5 +69,5 @@ class FrameGrabber /// Output queues. std::deque _frame_q, _remap_q; - std::deque _ts_q; + std::deque _ts_q, _ms_q; }; diff --git a/include/FrameSource.h b/include/FrameSource.h index 1970251..aed92b3 100644 --- a/include/FrameSource.h +++ b/include/FrameSource.h @@ -27,6 +27,7 @@ class FrameSource { int getWidth() { return _width; } int getHeight() { return _height; } double getTimestamp() { return _timestamp; } + double getMsSinceMidnight() { return _ms_since_midnight; } void setBayerType(BAYER_TYPE bayer_type) { _bayerType = bayer_type; } bool isLive() { return _live; } @@ -34,6 +35,6 @@ class FrameSource { bool _open; BAYER_TYPE _bayerType; int _width, _height; - double _timestamp, _fps; + double _timestamp, _fps, _ms_since_midnight; bool _live; }; diff --git a/include/Trackball.h b/include/Trackball.h index 0489c5d..215a914 100644 --- a/include/Trackball.h +++ b/include/Trackball.h @@ -43,7 +43,7 @@ class Trackball cv::Mat R_cam; CmPoint64f dr_lab, r_lab; cv::Mat R_lab; - double ts; + double ts, ms; double velx, vely, step_mag, step_dir, intx, inty, heading, posx, posy; diff --git a/include/timing.h b/include/timing.h index afada2f..929c7a9 100644 --- a/include/timing.h +++ b/include/timing.h @@ -32,6 +32,25 @@ static double ts_ms() { return duration_cast(t1.time_since_epoch()).count() / 1000.; } +/// +/// Return ms since midnight +/// +static double ms_since_midnight() { + + static std::chrono::system_clock::time_point tmidnight; + + if (tmidnight.time_since_epoch().count() == 0) { + auto texec = std::chrono::system_clock::to_time_t(_tExec); + tm* tdate = std::localtime(&texec); + tdate->tm_hour = 0; + tdate->tm_min = 0; + tdate->tm_sec = 0; + tmidnight = std::chrono::system_clock::from_time_t(std::mktime(tdate)); + } + + return std::chrono::duration_cast(std::chrono::system_clock::now() - tmidnight).count() / 1000.; +} + /// /// Return formatted date/time string for program launch. /// @@ -57,29 +76,6 @@ static std::string execTime() return s; } -/// -/// Return formatted date/time string. -/// -static std::string dateTimeString() -{ - time_t rawtime; - struct tm* timeinfo; - - time(&rawtime); - timeinfo = localtime(&rawtime); - - char tmps[16]; - sprintf(tmps, "%4d%02d%02d_%02d%02d%02d", - timeinfo->tm_year + 1900, - timeinfo->tm_mon+1, - timeinfo->tm_mday, - timeinfo->tm_hour, - timeinfo->tm_min, - timeinfo->tm_sec); - - return std::string(tmps); -} - /// /// Return formatted date string. /// diff --git a/src/CVSource.cpp b/src/CVSource.cpp index e537815..46e99db 100644 --- a/src/CVSource.cpp +++ b/src/CVSource.cpp @@ -157,9 +157,10 @@ bool CVSource::grab(cv::Mat& frame) return false; } double ts = ts_ms(); // backup, in case the device timestamp is junk + _ms_since_midnight = ms_since_midnight(); _timestamp = _cap->get(cv::CAP_PROP_POS_MSEC); - LOG_DBG("Frame captured %dx%d%d @ %f (%f)", _frame_cap.cols, _frame_cap.rows, _frame_cap.channels(), _timestamp, ts); - if ((_timestamp <= 0) || (!_live)) { // also use system time when playing back from file + LOG_DBG("Frame captured %dx%d%d @ %f (t_sys: %f ms, t_day: %f ms)", _frame_cap.cols, _frame_cap.rows, _frame_cap.channels(), _timestamp, ts, _ms_since_midnight); + if (_timestamp <= 0) { _timestamp = ts; } diff --git a/src/FrameGrabber.cpp b/src/FrameGrabber.cpp index 8f9e78c..ad476d4 100644 --- a/src/FrameGrabber.cpp +++ b/src/FrameGrabber.cpp @@ -86,7 +86,7 @@ FrameGrabber::~FrameGrabber() /// /// /// -bool FrameGrabber::getFrameSet(Mat& frame, Mat& remap, double& timestamp, bool latest=true) +bool FrameGrabber::getFrameSet(Mat& frame, Mat& remap, double& timestamp, double& ms_since_midnight, bool latest) { unique_lock l(_qMutex); while (_active && (_frame_q.size() == 0)) { @@ -105,13 +105,14 @@ bool FrameGrabber::getFrameSet(Mat& frame, Mat& remap, double& timestamp, bool l // must be frame_q.size() > 0 to get here (can be !_active) - if ((n != _remap_q.size()) || (n != _ts_q.size())) { + if ((n != _remap_q.size()) || (n != _ts_q.size()) || (n != _ms_q.size())) { LOG_ERR("Error! Input processed frame queues are misaligned!"); // drop all frames _frame_q.clear(); _remap_q.clear(); _ts_q.clear(); + _ms_q.clear(); // wake processing thread _qCond.notify_all(); @@ -124,6 +125,7 @@ bool FrameGrabber::getFrameSet(Mat& frame, Mat& remap, double& timestamp, bool l frame = _frame_q.back(); remap = _remap_q.back(); timestamp = _ts_q.back(); + ms_since_midnight = _ms_q.back(); if (n > 1) { LOG_WRN("Warning! Dropping %d frame/s from input processed frame queues!", n - 1); @@ -133,15 +135,18 @@ bool FrameGrabber::getFrameSet(Mat& frame, Mat& remap, double& timestamp, bool l _frame_q.clear(); _remap_q.clear(); _ts_q.clear(); + _ms_q.clear(); } else { frame = _frame_q.front(); remap = _remap_q.front(); timestamp = _ts_q.front(); + ms_since_midnight = _ms_q.front(); _frame_q.pop_front(); _remap_q.pop_front(); _ts_q.pop_front(); + _ms_q.pop_front(); if (n > 1) { LOG_DBG("%d frames remaining in processed frame queue.", _frame_q.size()); @@ -224,6 +229,7 @@ void FrameGrabber::process() break; } double timestamp = _source->getTimestamp(); + double ms_since_midnight = _source->getMsSinceMidnight(); /// Create output remap image in the loop. Mat remap_grey(_rh, _rw, CV_8UC1); @@ -339,11 +345,12 @@ void FrameGrabber::process() _frame_q.push_back(frame_bgr); _remap_q.push_back(remap_grey); _ts_q.push_back(timestamp); + _ms_q.push_back(ms_since_midnight); _qCond.notify_all(); - int q_size = _frame_q.size(); + size_t q_size = _frame_q.size(); l.unlock(); - LOG_DBG("Processed frame added to input queue (l = %d).", q_size); + LOG_DBG("Processed frame added to input queue (l = %zd).", q_size); } LOG_DBG("Stopping frame grabbing loop!"); diff --git a/src/PGRSource.cpp b/src/PGRSource.cpp index e69c50d..40c0ec5 100644 --- a/src/PGRSource.cpp +++ b/src/PGRSource.cpp @@ -241,8 +241,9 @@ bool PGRSource::grab(cv::Mat& frame) long int timeout = _fps > 0 ? std::max(static_cast(1000), static_cast(1000. / _fps)) : 1000; // set capture timeout to at least 1000 ms pgr_image = _cam->GetNextImage(timeout); double ts = ts_ms(); // backup, in case the device timestamp is junk + _ms_since_midnight = ms_since_midnight(); _timestamp = pgr_image->GetTimeStamp(); - LOG_DBG("Frame captured %dx%d%d @ %f (%f)", pgr_image->GetWidth(), pgr_image->GetHeight(), pgr_image->GetNumChannels(), _timestamp, ts); + LOG_DBG("Frame captured %dx%d%d @ %f (t_sys: %f ms, t_day: %f ms)", pgr_image->GetWidth(), pgr_image->GetHeight(), pgr_image->GetNumChannels(), _timestamp, ts, _ms_since_midnight); if (_timestamp <= 0) { _timestamp = ts; } diff --git a/src/Trackball.cpp b/src/Trackball.cpp index d4c0054..a991101 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -616,7 +616,7 @@ void Trackball::process() double t1, t2, t3, t4, t5, t6; double t1avg = 0, t2avg = 0, t3avg = 0, t4avg = 0, t5avg = 0, t6avg = 0; double tfirst = -1, tlast = 0; - while (!_kill && _active && _frameGrabber->getNextFrameSet(_src_frame, _roi_frame, _data.ts)) { + while (!_kill && _active && _frameGrabber->getNextFrameSet(_src_frame, _roi_frame, _data.ts, _data.ms)) { t1 = ts_ms(); PRINT(""); @@ -976,6 +976,8 @@ bool Trackball::logData() std::stringstream ss; ss.precision(14); + static double prev_ts = _data.ts; + // frame_count ss << _data.cnt << ", "; // rel_vec_cam[3] | error @@ -992,8 +994,10 @@ bool Trackball::logData() ss << _data.step_dir << ", " << _data.step_mag << ", "; // integrated x movement | integrated y movement (mouse output equivalent) ss << _data.intx << ", " << _data.inty << ", "; - // timestamp | sequence number - ss << _data.ts << ", " << _data.seq << std::endl; + // timestamp (ms since midnight) | sequence number | delta ts (ms since last frame) + ss << _data.ms << ", " << _data.seq << ", " << (_data.ts - prev_ts) << std::endl; + + prev_ts = _data.ts; // caution - be sure that this time delta corresponds to deltas for step size, rotation rate, etc!! // async i/o bool ret = true; From 94372ad71716ee232e4c0bfb23830c508e5c04bd Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 9 Jul 2019 22:23:41 +0200 Subject: [PATCH 153/235] [fictrac] add thr_rgb_tfrm config option; fix thresholding bug (win_it); minor change to mask roi growing (reduce circ rad rather than eroding mask) --- include/FrameGrabber.h | 7 ++++++ src/FrameGrabber.cpp | 57 +++++++++++++++++++++++++++++++++--------- src/Trackball.cpp | 6 ++--- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/include/FrameGrabber.h b/include/FrameGrabber.h index f5d80a4..c2059e5 100644 --- a/include/FrameGrabber.h +++ b/include/FrameGrabber.h @@ -30,6 +30,7 @@ class FrameGrabber const cv::Mat& remap_mask, double thresh_ratio, double thresh_win_pc, + std::string thresh_rgb_transform = "grey", int max_buf_len = 1, int max_frame_cnt = -1 ); @@ -58,6 +59,12 @@ class FrameGrabber double _thresh_ratio; int _thresh_win, _thresh_rad; + enum { + GREY, + RED, + GREEN, + BLUE + } _thresh_rgb_transform; int _max_buf_len, _max_frame_cnt; diff --git a/src/FrameGrabber.cpp b/src/FrameGrabber.cpp index ad476d4..e4671e1 100644 --- a/src/FrameGrabber.cpp +++ b/src/FrameGrabber.cpp @@ -18,11 +18,10 @@ #include #include // round +#include using cv::Mat; -using std::shared_ptr; -using std::unique_lock; -using std::mutex; +using namespace std; /// /// @@ -32,6 +31,7 @@ FrameGrabber::FrameGrabber( shared_ptr source, const Mat& remap_mask, double thresh_ratio, double thresh_win_pc, + string thresh_rgb_transform, int max_buf_len, int max_frame_cnt ) : _source(source), _remapper(remapper), _remap_mask(remap_mask), _active(false) @@ -49,6 +49,16 @@ FrameGrabber::FrameGrabber( shared_ptr source, } _thresh_ratio = thresh_ratio; + if ((thresh_rgb_transform == "red") || (thresh_rgb_transform == "r")) { + _thresh_rgb_transform = FrameGrabber::RED; + } else if ((thresh_rgb_transform == "green") || (thresh_rgb_transform == "g")) { + _thresh_rgb_transform = FrameGrabber::GREEN; + } else if ((thresh_rgb_transform == "blue") || (thresh_rgb_transform == "b")) { + _thresh_rgb_transform = FrameGrabber::BLUE; + } else { + _thresh_rgb_transform = FrameGrabber::GREY; + } + if ((thresh_win_pc < 0) || (thresh_win_pc > 1.0)) { LOG_WRN("Invalid thresh_win parameter (%f)! Defaulting to 0.2", thresh_win_pc); thresh_win_pc = 0.2; @@ -241,7 +251,28 @@ void FrameGrabber::process() memset(win_min_hist.get(), 0, _thresh_win); /// Create grey ROI frame. - cv::cvtColor(frame_bgr, frame_grey, cv::COLOR_BGR2GRAY); + int from_to[2] = { 0, 0 }; + switch (_thresh_rgb_transform) { + case RED: + from_to[0] = 2; from_to[1] = 0; + cv::mixChannels(&frame_bgr, 1, &frame_grey, 1, from_to, 1); + break; + + case GREEN: + from_to[0] = 1; from_to[1] = 0; + cv::mixChannels(&frame_bgr, 1, &frame_grey, 1, from_to, 1); + break; + + case BLUE: + from_to[0] = 0; from_to[1] = 0; + cv::mixChannels(&frame_bgr, 1, &frame_grey, 1, from_to, 1); + break; + + case GREY: + default: + cv::cvtColor(frame_bgr, frame_grey, cv::COLOR_BGR2GRAY); + break; + } _remapper->apply(frame_grey, remap_grey); /// Blur image before calculating region min/max values. @@ -259,12 +290,13 @@ void FrameGrabber::process() uint8_t* pgrey = remap_blur.ptr(i); for (int j = 0; j <= _thresh_rad; j++) { if (pmask[j] < 255) { continue; } - uint8_t g = pgrey[j]; + const uint8_t& g = pgrey[j]; if ((g > max) && (g < 255)) { max = g; } // ignore overexposed regions if (g < min) { min = g; } } - win_max_hist[win_it++] = max; - win_min_hist[win_it++] = min; + win_max_hist[win_it] = max; + win_min_hist[win_it] = min; + win_it++; } // compute window min/max @@ -272,16 +304,17 @@ void FrameGrabber::process() uint8_t* pthrmin = thresh_min.data; for (int j = 0; j < _rw; j++) { for (int i = 0; i < _rh; i++) { + // add row max = 0; min = 255; if ((i + _thresh_rad) < _rh) { const uint8_t* pmask = _remap_mask.ptr(i + _thresh_rad); - uint8_t* pgrey = remap_blur.ptr(i + _thresh_rad); + const uint8_t* pgrey = remap_blur.ptr(i + _thresh_rad); for (int s = -_thresh_rad; s <= _thresh_rad; s++) { - int js = j + s; + const int js = j + s; if ((js < 0) || (js >= _rw)) { continue; } if (pmask[js] < 255) { continue; } - uint8_t g = pgrey[js]; + const uint8_t& g = pgrey[js]; if ((g > max) && (g < 255)) { max = g; } // ignore overexposed regions if (g < min) { min = g; } } @@ -291,10 +324,10 @@ void FrameGrabber::process() const uint8_t* pmask = _remap_mask.ptr(i + _thresh_rad - _rh); uint8_t* pgrey = remap_blur.ptr(i + _thresh_rad - _rh); for (int s = -_thresh_rad; s <= _thresh_rad; s++) { - int js = j + s + 1; + const int js = j + s + 1; if ((js < 0) || (js >= _rw)) { continue; } if (pmask[js] < 255) { continue; } - uint8_t g = pgrey[js]; + const uint8_t& g = pgrey[js]; if ((g > max) && (g < 255)) { max = g; } // ignore overexposed regions if (g < min) { min = g; } } diff --git a/src/Trackball.cpp b/src/Trackball.cpp index a991101..06ec8b3 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -198,7 +198,7 @@ Trackball::Trackball(string cfg_fn) _r_d_ratio = sin(_sphere_rad); /// Allow sphere region in mask. - auto int_circ = projCircleInt(_src_model, _sphere_c, _sphere_rad); + auto int_circ = projCircleInt(_src_model, _sphere_c, _sphere_rad * 0.975f); // crop a bit of the circle to avoid circumference thresholding issues cv::fillConvexPoly(src_mask, *int_circ, CV_RGB(255, 255, 255)); /// Mask out ignore regions. @@ -283,7 +283,6 @@ Trackball::Trackball(string cfg_fn) _roi_mask.create(_roi_h, _roi_w, CV_8UC1); _roi_mask.setTo(cv::Scalar::all(255)); remapper->apply(src_mask, _roi_mask); - erode(_roi_mask, _roi_mask, Mat(), cv::Point(-1, -1), 1, cv::BORDER_CONSTANT, 0); // remove edge effects /// Surface mapping. _sphere_model = CameraModel::createEquiArea(_map_w, _map_h, CM_PI_2, -CM_PI, CM_PI, -2 * CM_PI); @@ -514,7 +513,8 @@ Trackball::Trackball(string cfg_fn) remapper, _roi_mask, thresh_ratio, - thresh_win_pc + thresh_win_pc, + _cfg("thr_rgb_tfrm") ); /// Write all parameters back to config file. From 562bc342151b5aa1bc3389f810ad777c911ed4da Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 16 Jul 2019 22:06:37 +0200 Subject: [PATCH 154/235] Update data_header.txt --- doc/data_header.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/data_header.txt b/doc/data_header.txt index 053d897..1211d8d 100644 --- a/doc/data_header.txt +++ b/doc/data_header.txt @@ -18,7 +18,7 @@ in laboratory coordinates. 15-16 integrated x/y position (lab) Integrated x/y position (radians) in laboratory coordinates. Scale by sphere - radius for true position (?). + radius for true position. 17 integrated animal heading (lab) Integrated heading orientation (radians) of the animal in laboratory coordinates. This is the direction the animal is facing. @@ -29,7 +29,7 @@ in world). 19 animal movement speed Instantaneous running speed (radians/frame) of the animal. Scale by sphere radius for - true speed (?). + true speed. 20-21 integrated forward/side motion Integrated x/y position (radians) of the sphere in laboratory coordinates neglecting heading. Equivalent to the output from two From 0d7b08dac70462381aeb80761215e1a543cfcbcd Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 21 Aug 2019 17:08:08 +0200 Subject: [PATCH 155/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d09527e..8c2de78 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ A typical FicTrac experimental setup might include at least the following equipm * *Animal tether/harness* - for keeping the animal stationary on the track ball * *Stimulus* - sensory feedback for the animal * *Track ball support* - structure to hold track ball in place whilst allowing free rotation -* [Track ball](doc/requirements.md#track-ball) - lightweight sphere that is rotated by the animal's walking/turning motions. Surface pattern should ideally be high-contrast, non-repeating, non-reflective (not glossy), and contain around 10~50 variously sized blobby shapes. +* [Track ball](doc/requirements.md#track-ball) - lightweight sphere that is rotated by the animal's walking/turning motions. Surface pattern should ideally be high-contrast, non-repeating, non-reflective (not glossy), and contain around 10~50 variously sized shapes with a mixture of texture scales (i.e. some small sharp features, some blobs, etc). * [Video camera](doc/requirements.md#video-camera) - for monitoring the track ball (and animal). Resolution is not important, and for vertebrates a USB webcam is likely sufficient. For faster moving insects, the camera should support frame rates >100 Hz. In all cases, the frame rate, exposure, and lens should be chosen to ensure the track ball pattern is well-exposed under all lighting/stimulus conditions and that there is no motion blur during the fastest expected movements. At least one half of one hemisphere of the track ball surface should be visible by the camera. * [PC/laptop](doc/requirements.md#pclaptop) - for running FicTrac software (and generating closed-loop stimuli). Processor should be somewhat recent (>2 GHz, multi-core). * [Lighting](doc/requirements.md#lighting) - ambient lighting should be diffuse (no specular reflections from track ball surface) and bright enough to give good track ball surface exposure at chosen frame rate. From 85035932d135ec376f21ed5f9bb63c5f294d1462 Mon Sep 17 00:00:00 2001 From: Tianxing Jiang Date: Wed, 28 Aug 2019 16:01:52 -0400 Subject: [PATCH 156/235] Update link to config documentation --- doc/params.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/params.md b/doc/params.md index a69dafa..aa798a4 100644 --- a/doc/params.md +++ b/doc/params.md @@ -1,4 +1,4 @@ -This document provides a brief overview of FicTrac's configuration parameters. Many of these parameters are set automatically by the configuration utility. See the main documentation for instructions on how to [configure FicTrac](README.md#Configuration) before use. +This document provides a brief overview of FicTrac's configuration parameters. Many of these parameters are set automatically by the configuration utility. See the main documentation for instructions on how to [configure FicTrac](../README.md#Configuration) before use. In the table below, the various possible parameters are listed. If nothing is listed under `Default value` then the parameter must be specified by the user. The valid range may use [interval notation](https://en.wikipedia.org/wiki/Interval_(mathematics)). The `Should I touch it?` column should give you some idea of which params to play around with. From e7e2e2ad47e9fdaa49f3a1716cb8f4c52269876d Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 5 Dec 2019 23:16:53 +0100 Subject: [PATCH 157/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c2de78..edd6f09 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ cd build ``` 3. Run Cmake to prepare the necessary build files for FicTrac. Here, we will need to provide the path to the Cmake toolchain file that was installed by Vcpkg (this path is printed to terminal when you run the Vcpkg system-wide integration step). ``` -[Windows] cmake -G "Visual Studio 15 2017 Win64" -D CMAKE_TOOLCHAIN_FILE=C:\path\to\vcpkg\scripts\buildsystems\vcpkg.cmake .. +[Windows] cmake -A x64 -D CMAKE_TOOLCHAIN_FILE=C:\path\to\vcpkg\scripts\buildsystems\vcpkg.cmake .. [Linux] cmake -D CMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake .. ``` 5. Finally, build and install FicTrac: From b27fb22a8db6fb2378846b6efbc1caaa0cd70151 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 5 Dec 2019 23:20:21 +0100 Subject: [PATCH 158/235] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index edd6f09..d66bdb2 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,8 @@ The FicTrac source code can be built for both Windows and Linux (e.g. Ubuntu) op 1. Download and install required build tools and dependencies: 1. Windows only: - 1. [Cmake build system](https://cmake.org/download/) (Windows win64-x64 Installer) - 2. If you don't already have Visual Studio (C++ workflow) installed, you will need to install the [Build Tools for Visual Studio](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2017). + 1. [Cmake build system (3.16.0)](https://cmake.org/download/) (Windows win64-x64 Installer) + 2. If you don't already have Visual Studio (C++ workflow) installed, you will need to install the [Build Tools for Visual Studio](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019). 2. Linux (Ubuntu) only: 1. Run the following from terminal to install necessary build tools and dependencies: ```[Linux] sudo apt-get install gcc git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev``` 3. (Windows and Linux) Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). From 29491d82aebebd8bc98a2ca094a651f1732c8042 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 6 Dec 2019 00:33:39 +0100 Subject: [PATCH 159/235] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d66bdb2..ebe58c3 100644 --- a/README.md +++ b/README.md @@ -53,11 +53,11 @@ The FicTrac source code can be built for both Windows and Linux (e.g. Ubuntu) op 2. Linux (Ubuntu) only: 1. Run the following from terminal to install necessary build tools and dependencies: ```[Linux] sudo apt-get install gcc git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev``` 3. (Windows and Linux) Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). - 4. Using Vcpkg, install OpenCV, NLopt, and Boost::asio software packages (this may take 10-30 mins): + 4. Using Vcpkg, install OpenCV, NLopt, Boost::asio, and FFmpeg software packages (this may take more than 30 mins in some cases!): ``` -[Windows] .\vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows -[Linux] ./vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux +[Windows] .\vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[*]:x64-windows +[Linux] ./vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux ffmpeg[*]:x64-linux ``` 2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: From 301d695df87f022b42e644f1debc558cb0d7ad7b Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 6 Dec 2019 00:41:07 +0100 Subject: [PATCH 160/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ebe58c3..9cb72ee 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ The FicTrac source code can be built for both Windows and Linux (e.g. Ubuntu) op ``` [Windows] .\vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[*]:x64-windows -[Linux] ./vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux ffmpeg[*]:x64-linux +[Linux] ./vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux ``` 2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: From 575d2ff06371eb858e61cdacb0864568e0a623a8 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 6 Dec 2019 00:44:51 +0100 Subject: [PATCH 161/235] [build] warning suppression; remove post-build --- CMakeLists.txt | 40 +++------------------------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 124a45e..d49aaa8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,10 +103,10 @@ endif() # add compile options if(MSVC) - target_compile_options(fictrac_core PUBLIC $<$:/MP /GS /GL /W3 /WX- /Gy /Zc:wchar_t /O2 /Oi /Zc:inline /fp:precise /MD /EHsc /std:c++17>) - # set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") + target_compile_options(fictrac_core PUBLIC $<$:/MP /GS /GL /w /WX- /Gy /Zc:wchar_t /O2 /Oi /Zc:inline /fp:precise /MD /EHsc /std:c++17>) + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") else() # gcc - target_compile_options(fictrac_core PUBLIC -Ofast -Wall -c -fmessage-length=0 -std=c++17 -Wno-unused-function -march=native -MMD) + target_compile_options(fictrac_core PUBLIC -Ofast -w -c -fmessage-length=0 -std=c++17 -Wno-unused-function -march=native -MMD) endif() # linking @@ -120,40 +120,6 @@ if(PGR_USB2 OR PGR_USB3) target_link_libraries(fictrac_core PUBLIC ${PGR_LIB}) endif() -# post-build -if(MSVC) - # copy all opencv dlls - set(OPENCV_VER_STRING ${OpenCV_VERSION_MAJOR}${OpenCV_VERSION_MINOR}${OpenCV_VERSION_PATCH}) - foreach(lib ${OpenCV_LIBS}) - if(EXISTS "${OpenCV_INSTALL_PATH}/bin/${lib}${OPENCV_VER_STRING}.dll") - list(APPEND TO_COPY "${OpenCV_INSTALL_PATH}/bin/${lib}${OPENCV_VER_STRING}.dll") - endif() - endforeach() - - set(FFMPEG_LIB_BASE opencv_ffmpeg${OPENCV_VER_STRING}) - if("${OpenCV_ARCH}" STREQUAL x86) - set(FFMPEG_LIB ${FFMPEG_LIB_BASE}.dll) - else() - # default to 64-bit - set(FFMPEG_LIB ${FFMPEG_LIB_BASE}_64.dll) - endif() - list(APPEND TO_COPY "${OpenCV_INSTALL_PATH}/bin/${FFMPEG_LIB}") - - # copy nlopt dll - list(APPEND TO_COPY "${NLOPT_LIBRARY_DIRS}/bin/nlopt.dll") - - # copy h264 dll - file(GLOB DLLS ${PROJECT_SOURCE_DIR}/dll/*.dll) - list(APPEND TO_COPY "${DLLS}") - - add_custom_command( TARGET fictrac POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${TO_COPY} - "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<$:Release>$<$:Debug>/") -else() # gcc - # nothing here... -endif() - target_link_libraries(configGui fictrac_core) add_dependencies(configGui fictrac_core) target_link_libraries(fictrac fictrac_core) From f6b81e414058b7da0068aa70218edd1f6679881f Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 6 Dec 2019 00:50:23 +0100 Subject: [PATCH 162/235] [configGUI] optional enhancement; refactoring --- include/ConfigGui.h | 10 +- src/ConfigGUI.cpp | 267 ++++++++++++++++++++++++-------------------- 2 files changed, 154 insertions(+), 123 deletions(-) diff --git a/include/ConfigGui.h b/include/ConfigGui.h index 098c5b0..664875b 100644 --- a/include/ConfigGui.h +++ b/include/ConfigGui.h @@ -8,6 +8,7 @@ #include "typesvars.h" #include "ConfigParser.h" +#include "FrameSource.h" #include "SharedPointers.h" SHARED_PTR(CameraModel); @@ -23,10 +24,9 @@ class ConfigGui ConfigGui(std::string config_fn); ~ConfigGui(); + bool is_open(); bool run(); - bool is_open() { return _open; } - enum INPUT_MODE { CIRC_INIT, CIRC_PTS, @@ -89,11 +89,11 @@ class ConfigGui void changeState(INPUT_MODE new_state); private: - bool _open; std::string _config_fn, _base_fn; ConfigParser _cfg; - cv::Mat _frame; - size_t _w, _h; + int _w, _h; CameraModelPtr _cam_model; INPUT_DATA _input_data; + + std::shared_ptr _source; }; diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index dafeeea..9b71612 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -150,124 +150,52 @@ void createZoomROI(Mat& zoom_roi, const Mat& frame, const Point2d& pt, int orig_ /// Constructor. /// ConfigGui::ConfigGui(string config_fn) -: _open(false), _config_fn(config_fn) +: _config_fn(config_fn) { /// Load and parse config file. - _open = (_cfg.read(_config_fn) > 0); - - /// Read source file name and load image. - string input_fn; - if (_open) { - input_fn = _cfg("src_fn"); - if (input_fn.empty()) { - _open = false; - } + if (_cfg.read(_config_fn) <= 0) { + LOG_ERR("Error! Could not read from config file (%s).", _config_fn.c_str()); + return; } - /// Load an image to use for annotation. - Mat input_frame; - std::shared_ptr source; - if (_open) { -#if defined(PGR_USB2) || defined(PGR_USB3) - try { - if (input_fn.size() > 2) { throw std::exception(); } - // first try reading input as camera id - int id = std::stoi(input_fn); - source = std::make_shared(id); - } - catch (...) { - // then try loading as video file - source = std::make_shared(input_fn); - } -#else // !PGR_USB2/3 - source = std::make_shared(input_fn); -#endif // PGR_USB2/3 - if (!source->isOpen()) { - LOG_ERR("Error! Could not open input frame source (%s)!", input_fn.c_str()); - _open = false; - } else if (!source->grab(input_frame)) { - LOG_ERR("Could not read frame from input (%s)!", input_fn.c_str()); - _open = false; - } else if (input_frame.empty()) { - _open = false; - } + /// Read source file name. + string input_fn = _cfg("src_fn"); + if (input_fn.empty()) { + LOG_ERR("Error! No src_fn defined in config file."); + return; } - /// Optionally enhance frame for config - bool do_enhance = false; - _cfg.getBool("enh_cfg_disp", do_enhance); - if (_open && do_enhance) { - LOG("Enhancing config image .."); - Mat maximg = input_frame.clone(); - Mat minimg = input_frame.clone(); - while (source->grab(input_frame)) { - for (int i = 0; i < input_frame.rows; i++) { - uint8_t* pmin = minimg.ptr(i); - uint8_t* pmax = maximg.ptr(i); - const uint8_t* pimg = input_frame.ptr(i); - for (int j = 0; j < input_frame.cols * input_frame.channels(); j++) { - uint8_t p = pimg[j]; - if (p > pmax[j]) { pmax[j] = p; } - if (p < pmin[j]) { pmin[j] = p; } - } - } - } - input_frame = maximg - minimg; + /// Open the image source. +#if defined(PGR_USB2) || defined(PGR_USB3) + try { + if (input_fn.size() > 2) { throw std::exception(); } + // first try reading input as camera id + int id = std::stoi(input_fn); + _source = std::make_shared(id); } - - /// Create base file name for output files. - _base_fn = _cfg("output_fn"); - if (_base_fn.empty()) { - if (_open && !source->isLive()) { - _base_fn = input_fn.substr(0, input_fn.length() - 4); - } else { - _base_fn = "fictrac"; - } + catch (...) { + // then try loading as video file + _source = std::make_shared(input_fn); } - - /// Setup. - if (_open) { - _open = setFrame(input_frame); +#else // !PGR_USB2/3 + _source = std::make_shared(input_fn); +#endif // PGR_USB2/3 + if (!_source || !_source->isOpen()) { + LOG_ERR("Error! Could not open input frame source (%s)!", input_fn.c_str()); + return; } -} -/// -/// Destructor. -/// -ConfigGui::~ConfigGui() -{} + /// Load the source camera model. + _w = _source->getWidth(); + _h = _source->getHeight(); -/// -/// Prepare input image for user input. -/// -bool ConfigGui::setFrame(Mat& frame) -{ - /// Copy input frame. - if (frame.channels() == 3) { - //cv::cvtColor(frame, _frame, CV_BGR2GRAY); - _frame = frame.clone(); - } else if (frame.channels() == 1) { - //_frame = frame.clone(); - cv::cvtColor(frame, _frame, cv::COLOR_GRAY2BGR); - } else { - // uh oh, shouldn't get here - LOG_ERR("Unexpected number of image channels (%d)!", frame.channels()); - return false; - } - - /// Stretch contrast for display - //histStretch(_frame); - _w = _frame.cols; - _h = _frame.rows; - - /// Load camera model. double vfov = 0; _cfg.getDbl("vfov", vfov); - if (vfov <= 0) { - LOG_ERR("vfov parameter must be > 0 (%f)!", vfov); - return false; - } + if (vfov <= 0) { + LOG_ERR("Error! vfov parameter must be > 0 (%f)", vfov); + return; + } LOG("Using vfov: %f deg", vfov); @@ -279,10 +207,24 @@ bool ConfigGui::setFrame(Mat& frame) // default to rectilinear _cam_model = CameraModel::createRectilinear(_w, _h, vfov * CM_D2R); } - - return true; + + /// Create base file name for output files. + _base_fn = _cfg("output_fn"); + if (_base_fn.empty()) { + if (_source->isLive()) { + _base_fn = "fictrac"; + } else { + _base_fn = input_fn.substr(0, input_fn.length() - 4); + } + } } +/// +/// Destructor. +/// +ConfigGui::~ConfigGui() +{} + /// /// Write camera-animal transform to config file. /// Warning: input R+t is animal to camera frame transform! @@ -429,11 +371,21 @@ void ConfigGui::changeState(INPUT_MODE new_state) _input_data.mode = new_state; } +/// +/// +/// +bool ConfigGui::is_open() +{ + return _source && _source->isOpen(); +} + /// /// Run user input program or configuration. /// bool ConfigGui::run() { + if (!is_open()) { return false; } + /// Interactive window. cv::namedWindow("configGUI", cv::WINDOW_AUTOSIZE); cv::setMouseCallback("configGUI", onMouseEvent, &_input_data); @@ -441,6 +393,49 @@ bool ConfigGui::run() /// If reconfiguring, then delete pre-computed values. bool reconfig = false; _cfg.getBool("reconfig", reconfig); + + /// Get a frame. + Mat frame; + if (!_source->grab(frame)) { + LOG_ERR("Error! Could not grab input frame."); + return false; + } + if ((frame.cols != _w) || (frame.rows != _h)) { + LOG_ERR("Error! Unexpected image size (%dx%d).", frame.cols, frame.rows); + return false; + } + + // convert to RGB + if (frame.channels() == 1) { + cv::cvtColor(frame, frame, cv::COLOR_GRAY2BGR); + } + + /// Optionally enhance frame for config + bool do_enhance = false; + _cfg.getBool("enh_cfg_disp", do_enhance); + if (do_enhance) { + LOG("Enhancing config image .."); + Mat maximg = frame.clone(); + Mat minimg = frame.clone(); + auto t0 = elapsed_secs(); + while (_source->grab(frame)) { + for (int i = 0; i < _h; i++) { + uint8_t* pmin = minimg.ptr(i); + uint8_t* pmax = maximg.ptr(i); + const uint8_t* pimg = frame.ptr(i); + for (int j = 0; j < _w * 3; j++) { + uint8_t p = pimg[j]; + if (p > pmax[j]) { pmax[j] = p; } + if (p < pmin[j]) { pmin[j] = p; } + } + } + + // Drop out after max 30s (avoid infinite loop when running live) + auto t1 = elapsed_secs(); + if ((t1 - t0) > 30) { break; } + } + frame = maximg - minimg; + } /// Display/input loop. Mat R, t; @@ -456,10 +451,11 @@ bool ConfigGui::run() const int click_rad = std::max(int(_w/150+0.5), 5); Mat disp_frame, zoom_frame(ZOOM_DIM, ZOOM_DIM, CV_8UC3); const int scaled_zoom_dim = static_cast(ZOOM_DIM * ZOOM_SCL + 0.5); - while (_open && (key != 0x1b)) { // esc + bool open = true; + while (open && (key != 0x1b)) { // esc /// Create frame for drawing. //cv::cvtColor(_frame, disp_frame, CV_GRAY2RGB); - disp_frame = _frame.clone(); + disp_frame = frame.clone(); // normalise zoom window { @@ -508,7 +504,7 @@ bool ConfigGui::run() _cfg.add("roi_r", r); if (_cfg.write() <= 0) { LOG_ERR("Error writing to config file (%s)!", _config_fn.c_str()); - _open = false; // will cause exit + open = false; // will cause exit } } } @@ -617,7 +613,7 @@ bool ConfigGui::run() _cfg.add("roi_r", r); if (_cfg.write() <= 0) { LOG_ERR("Error writing to config file (%s)!", _config_fn.c_str()); - _open = false; // will cause exit + open = false; // will cause exit } //// test read @@ -749,7 +745,7 @@ bool ConfigGui::run() _cfg.add("roi_ignr", cfg_polys); if (_cfg.write() <= 0) { LOG_ERR("Error writing to config file (%s)!", _config_fn.c_str()); - _open = false; // will cause exit + open = false; // will cause exit } //// test read @@ -981,7 +977,7 @@ bool ConfigGui::run() // dump corner points to config file if (!saveC2ATransform(c2a_src, R, t)) { LOG_ERR("Error writing coordinate transform to config file!"); - _open = false; // will cause exit + open = false; // will cause exit } // advance state @@ -1034,7 +1030,7 @@ bool ConfigGui::run() // dump corner points to config file if (!saveC2ATransform(c2a_src, R, t)) { LOG_ERR("Error writing coordinate transform to config file!"); - _open = false; // will cause exit + open = false; // will cause exit } // advance state @@ -1087,7 +1083,7 @@ bool ConfigGui::run() // dump corner points to config file if (!saveC2ATransform(c2a_src, R, t)) { LOG_ERR("Error writing coordinate transform to config file!"); - _open = false; // will cause exit + open = false; // will cause exit } // advance state @@ -1139,7 +1135,7 @@ bool ConfigGui::run() if (_cfg.write() <= 0) { LOG_ERR("Error writing to config file (%s)!", _config_fn.c_str()); - _open = false; // will cause exit + open = false; // will cause exit } // advance state @@ -1162,7 +1158,7 @@ bool ConfigGui::run() /// Save config image //cv::cvtColor(_frame, disp_frame, CV_GRAY2RGB); - disp_frame = _frame.clone(); + disp_frame = frame.clone(); // draw fitted circumference if (r > 0) { @@ -1192,12 +1188,47 @@ bool ConfigGui::run() LOG_ERR("Error writing config image to disk!"); } - if (_open) { + //// compute thresholding priors + //auto thr_mode = static_cast(_cfg.get("thr_mode")); // 0 = default (adapt); 1 = norm w/ priors + //if (thr_mode == NORM_PRIORS) { + + // LOG("Computing ROI thresholding priors .."); + + //// rewind source + //_source->rewind(); + + //vector> hist; + + //Mat maximg = frame.clone(); + //Mat minimg = frame.clone(); + //auto t0 = elapsed_secs(); + //while (_source->grab(frame)) { + // for (int i = 0; i < _h; i++) { + // uint8_t* pmin = minimg.ptr(i); + // uint8_t* pmax = maximg.ptr(i); + // const uint8_t* pimg = frame.ptr(i); + // for (int j = 0; j < _w * 3; j++) { + // uint8_t p = pimg[j]; + // if (p > pmax[j]) { pmax[j] = p; } + // if (p < pmin[j]) { pmin[j] = p; } + // } + // } + + // // Drop out after max 30s (avoid infinite loop when running live) + // auto t1 = elapsed_secs(); + // if ((t1 - t0) > 30) { break; } + // } + // frame = maximg - minimg; + //} + + + + if (open) { LOG("Configuration complete!"); } else { LOG_WRN("\n\nWarning! There were errors and the configuration file may not have been properly updated. Please run configuration again."); } LOG("Exiting configuration!"); - return _open; + return open; } From 22b64c9cc1fb55a80f89412a66c3dd853904bb7d Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 6 Dec 2019 00:52:13 +0100 Subject: [PATCH 163/235] [build] remove LTCG flag --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d49aaa8..f91ae85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,7 +104,7 @@ endif() # add compile options if(MSVC) target_compile_options(fictrac_core PUBLIC $<$:/MP /GS /GL /w /WX- /Gy /Zc:wchar_t /O2 /Oi /Zc:inline /fp:precise /MD /EHsc /std:c++17>) - set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") + # set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") else() # gcc target_compile_options(fictrac_core PUBLIC -Ofast -w -c -fmessage-length=0 -std=c++17 -Wno-unused-function -march=native -MMD) endif() From d6dde4eb3ad571535e7b943074fdb28713a8b88f Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 6 Dec 2019 01:04:25 +0100 Subject: [PATCH 164/235] [fictrac] map never freezes --- include/typesvars.h | 5 +++++ src/Trackball.cpp | 7 ++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/include/typesvars.h b/include/typesvars.h index 9a01dee..9119f1c 100644 --- a/include/typesvars.h +++ b/include/typesvars.h @@ -15,3 +15,8 @@ const CmReal CM_PI = 3.14159265358979323846; const CmReal CM_PI_2 = 1.57079632679489661923; const CmReal CM_R2D = 180.0 / CM_PI; const CmReal CM_D2R = CM_PI / 180.0; + +//enum THR_MODE { +// ADAPT, +// NORM_PRIORS +//}; diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 06ec8b3..734aa5d 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -821,15 +821,12 @@ void Trackball::updateSphere() uint8_t& map = _sphere_map.data[py * _sphere_map.step + px]; // update map tile - if ((map == 0) || (map == 255)) { - // map tile frozen - good++; - } else if (map == 128) { + if (map == 128) { // map tile previously unseen map = (proi[j] == 255) ? (128 + SPHERE_MAP_FIRST_HIT_BONUS) : (128 - SPHERE_MAP_FIRST_HIT_BONUS); } else { good++; - map = (proi[j] == 255) ? (map + 1) : (map - 1); + map = min(max((proi[j] == 255) ? (int(map) + 1) : (int(map) - 1), 0), 255); } // display From 7894ebba6e362f6c9665c6a79655f1d222508748 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 6 Dec 2019 01:19:06 +0100 Subject: [PATCH 165/235] [fictrac] revert original timestamp format; add additional timestamp output data --- doc/data_header.txt | 6 ++++-- src/Trackball.cpp | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/data_header.txt b/doc/data_header.txt index 1211d8d..4615c72 100644 --- a/doc/data_header.txt +++ b/doc/data_header.txt @@ -34,8 +34,10 @@ sphere in laboratory coordinates neglecting heading. Equivalent to the output from two optic mice. - 22 timestamp Either position in video file (ms) or real - capture time for image frame. + 22 timestamp Either position in video file (ms) or frame + capture time (ms since epoch). 23 sequence counter Position in current frame sequence. Usually corresponds directly to frame counter, but can reset to 1 if tracking is reset. + 24 delta timestamp Time (ms) since last frame. + 25 alt. timestamp Frame capture time (ms since midnight). diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 06ec8b3..d555243 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -994,8 +994,8 @@ bool Trackball::logData() ss << _data.step_dir << ", " << _data.step_mag << ", "; // integrated x movement | integrated y movement (mouse output equivalent) ss << _data.intx << ", " << _data.inty << ", "; - // timestamp (ms since midnight) | sequence number | delta ts (ms since last frame) - ss << _data.ms << ", " << _data.seq << ", " << (_data.ts - prev_ts) << std::endl; + // timestamp (ms since epoch) | sequence number | delta ts (ms since last frame) | timestamp (ms since midnight) + ss << _data.ts << ", " << _data.seq << ", " << (_data.ts - prev_ts) << ", " << _data.ms << std::endl; prev_ts = _data.ts; // caution - be sure that this time delta corresponds to deltas for step size, rotation rate, etc!! From f60cae42ec747ad8e050f46079b49df2f7698749 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Fri, 6 Dec 2019 01:20:41 +0100 Subject: [PATCH 166/235] [build;fictrac;configGUI] increment minor version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f91ae85..af17cdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ project (FicTrac) # The version number. set (FICTRAC_VERSION_MAJOR 2) -set (FICTRAC_VERSION_MINOR 2) +set (FICTRAC_VERSION_MINOR 3) # output version info to be included by project configure_file ( From e717421744ad726daace1c0ca3e6a33a29af7993 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 26 Jan 2020 22:49:04 +0100 Subject: [PATCH 167/235] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9cb72ee..1d64d86 100644 --- a/README.md +++ b/README.md @@ -80,9 +80,11 @@ If everything went well, the executables for FicTrac and a configuration utility Remember to update and re-build FicTrac occasionally, as the program is still under development and fixes and improvements are being made continuously. + #### USB2/3 camera installation From db86c7116da5219d013a8812287f7724dabf78fe Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Sun, 26 Jan 2020 22:54:17 +0100 Subject: [PATCH 168/235] [ConfigGUI] allow clamping display window size --- include/ConfigGui.h | 3 +++ src/ConfigGUI.cpp | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/include/ConfigGui.h b/include/ConfigGui.h index 664875b..8b8c9b5 100644 --- a/include/ConfigGui.h +++ b/include/ConfigGui.h @@ -63,12 +63,14 @@ class ConfigGui std::vector > ignrPts; std::vector sqrPts; INPUT_MODE mode; + float ptScl; INPUT_DATA() { newEvent = false; mode = CIRC_INIT; cursorPt.x = -1; cursorPt.y = -1; + ptScl = -1; addPoly(); } @@ -92,6 +94,7 @@ class ConfigGui std::string _config_fn, _base_fn; ConfigParser _cfg; int _w, _h; + float _disp_scl; CameraModelPtr _cam_model; INPUT_DATA _input_data; diff --git a/src/ConfigGUI.cpp b/src/ConfigGUI.cpp index 9b71612..e1d20a6 100644 --- a/src/ConfigGUI.cpp +++ b/src/ConfigGUI.cpp @@ -4,8 +4,6 @@ /// \author Richard Moore /// \copyright CC BY-NC-SA 3.0 -//TODO: check config.is_open() -//TODO: Add support for fisheye camera model. //TODO: Add support for edge clicks rather than square corner clicks. #include "ConfigGui.h" @@ -43,6 +41,7 @@ using std::string; /// const int ZOOM_DIM = 600; const double ZOOM_SCL = 1.0 / 10.0; +const int MAX_DISP_DIM = -1; const int NCOLOURS = 6; cv::Scalar COLOURS[NCOLOURS] = { @@ -60,6 +59,10 @@ cv::Scalar COLOURS[NCOLOURS] = { void onMouseEvent(int event, int x, int y, int f, void* ptr) { ConfigGui::INPUT_DATA* pdata = static_cast(ptr); + if (pdata->ptScl > 0) { + x = round(x * pdata->ptScl); + y = round(y * pdata->ptScl); + } switch(event) { case cv::EVENT_LBUTTONDOWN: @@ -188,6 +191,11 @@ ConfigGui::ConfigGui(string config_fn) /// Load the source camera model. _w = _source->getWidth(); _h = _source->getHeight(); + _disp_scl = -1; + if ((MAX_DISP_DIM > 0) && (std::max(_w,_h) > MAX_DISP_DIM)) { + _disp_scl = MAX_DISP_DIM / static_cast(std::max(_w,_h)); + _input_data.ptScl = 1.0 / _disp_scl; + } double vfov = 0; _cfg.getDbl("vfov", vfov); @@ -457,7 +465,7 @@ bool ConfigGui::run() //cv::cvtColor(_frame, disp_frame, CV_GRAY2RGB); disp_frame = frame.clone(); - // normalise zoom window + // normalise displayed image { double min, max; cv::minMaxLoc(disp_frame, &min, &max); @@ -519,6 +527,9 @@ bool ConfigGui::run() drawCircle_camModel(disp_frame, _cam_model, c, r, Scalar(255,0,0), false); /// Display. + if (_disp_scl > 0) { + cv::resize(disp_frame, disp_frame, cv::Size(), _disp_scl, _disp_scl); + } cv::imshow("configGUI", disp_frame); cv::waitKey(100); //FIXME: why do we have to wait so long to make sure the frame is drawn? @@ -588,6 +599,9 @@ bool ConfigGui::run() /// Display. cv::imshow("zoomROI", zoom_frame); + if (_disp_scl > 0) { + cv::resize(disp_frame, disp_frame, cv::Size(), _disp_scl, _disp_scl); + } cv::imshow("configGUI", disp_frame); key = cv::waitKey(5); @@ -658,6 +672,9 @@ bool ConfigGui::run() } /// Display. + if (_disp_scl > 0) { + cv::resize(disp_frame, disp_frame, cv::Size(), _disp_scl, _disp_scl); + } cv::imshow("configGUI", disp_frame); cv::waitKey(100); //FIXME: why do we have to wait so long to make sure the frame is drawn? @@ -721,6 +738,9 @@ bool ConfigGui::run() /// Display. cv::imshow("zoomROI", zoom_frame); + if (_disp_scl > 0) { + cv::resize(disp_frame, disp_frame, cv::Size(), _disp_scl, _disp_scl); + } cv::imshow("configGUI", disp_frame); key = cv::waitKey(5); @@ -835,6 +855,9 @@ bool ConfigGui::run() drawC2AAxes(disp_frame, R, t, r, c); /// Display. + if (_disp_scl > 0) { + cv::resize(disp_frame, disp_frame, cv::Size(), _disp_scl, _disp_scl); + } cv::imshow("configGUI", disp_frame); cv::waitKey(100); //FIXME: why do we have to wait so long to make sure the frame is drawn? @@ -968,6 +991,9 @@ bool ConfigGui::run() /// Display. cv::imshow("zoomROI", zoom_frame); + if (_disp_scl > 0) { + cv::resize(disp_frame, disp_frame, cv::Size(), _disp_scl, _disp_scl); + } cv::imshow("configGUI", disp_frame); key = cv::waitKey(5); @@ -1021,6 +1047,9 @@ bool ConfigGui::run() /// Display. cv::imshow("zoomROI", zoom_frame); + if (_disp_scl > 0) { + cv::resize(disp_frame, disp_frame, cv::Size(), _disp_scl, _disp_scl); + } cv::imshow("configGUI", disp_frame); key = cv::waitKey(5); @@ -1074,6 +1103,9 @@ bool ConfigGui::run() /// Display. cv::imshow("zoomROI", zoom_frame); + if (_disp_scl > 0) { + cv::resize(disp_frame, disp_frame, cv::Size(), _disp_scl, _disp_scl); + } cv::imshow("configGUI", disp_frame); key = cv::waitKey(5); From aee3ff2958c88238b3b7c197b1f3a5bd0dcd0366 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 10 Mar 2020 21:19:56 +0100 Subject: [PATCH 169/235] [fictrac] remove old param out_port --- sample/config.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sample/config.txt b/sample/config.txt index 097a5ed..cffdf24 100644 --- a/sample/config.txt +++ b/sample/config.txt @@ -1,4 +1,4 @@ -## FicTrac config file (build Mar 21 2019) +## FicTrac config file (build Mar 10 2020) c2a_cnrs_xy : { 191, 171, 128, 272, 20, 212, 99, 132 } c2a_r : { 0.722445, -0.131314, -0.460878 } c2a_src : c2a_cnrs_xy @@ -10,7 +10,6 @@ opt_do_global : n opt_max_err : -1 opt_max_evals : 50 opt_tol : 0.001 -out_port : -1 q_factor : 6 roi_circ : { 63, 171, 81, 145, 106, 135, 150, 160 } roi_ignr : { { 96, 156, 113, 147, 106, 128, 82, 130, 81, 150 }, { 71, 213, 90, 219, 114, 218, 135, 211, 154, 196, 150, 217, 121, 228, 99, 234, 75, 225 } } @@ -21,4 +20,3 @@ src_fps : -1 thr_ratio : 1.25 thr_win_pc : 0.25 vfov : 45 -vid_codec : h264 From 3387e8e881b9c8f45925545d37d4adecc8287aa4 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 11 Mar 2020 00:15:41 +0100 Subject: [PATCH 170/235] [fictrac] dumpStats --- exec/fictrac.cpp | 2 +- include/Trackball.h | 2 +- src/Trackball.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exec/fictrac.cpp b/exec/fictrac.cpp index f7a05db..0bda4ed 100644 --- a/exec/fictrac.cpp +++ b/exec/fictrac.cpp @@ -46,7 +46,7 @@ int main(int argc, char *argv[]) return -1; } } - else if ((string(argv[i]) == "--test") || (string(argv[i]) == "-t")) { + else if (string(argv[i]) == "--stats") { do_test = true; } else { diff --git a/include/Trackball.h b/include/Trackball.h index 215a914..bce0d5e 100644 --- a/include/Trackball.h +++ b/include/Trackball.h @@ -97,7 +97,7 @@ class Trackball bool isActive() { return _active; } void terminate() { _kill = true; } std::shared_ptr getState(); - void dumpState(); + void dumpStats(); bool writeTemplate(std::string fn = ""); private: diff --git a/src/Trackball.cpp b/src/Trackball.cpp index d2f04ea..d46de5f 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -1441,7 +1441,7 @@ shared_ptr Trackball::getState() /// /// /// -void Trackball::dumpState() +void Trackball::dumpStats() { PRINT("\n----------------------------------------------------------------------"); PRINT("Trackball state"); From 2d5172c944688cb899def045138af96b9e87e8ca Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 11 Mar 2020 00:23:16 +0100 Subject: [PATCH 171/235] [test] add 3axis0 test --- test/3axis0.avi | Bin 0 -> 739930 bytes test/3axis0.cfg | 22 ++++++++++ test/3axis0_gt.csv | 100 +++++++++++++++++++++++++++++++++++++++++++++ test/test.py | 64 +++++++++++++++++++++++++++++ test/test_3axis0.m | 46 +++++++++++++++++++++ 5 files changed, 232 insertions(+) create mode 100644 test/3axis0.avi create mode 100644 test/3axis0.cfg create mode 100644 test/3axis0_gt.csv create mode 100644 test/test.py create mode 100644 test/test_3axis0.m diff --git a/test/3axis0.avi b/test/3axis0.avi new file mode 100644 index 0000000000000000000000000000000000000000..6345e6cbba5c854c9fec1ec9687feedc1cba4f8d GIT binary patch literal 739930 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

2$#8!L$7Z=o^oRKF_`!u=cyP}jN!b*_bPSRT z7i5q@APyOS`)114>I3-}ZpKnkgt-iJ)@F zRhW+U-p~#73K-$$?@mlj5I&o11N;klI$s(~RPh{agE=5$#o`8mos`?mwjm$X%QQEy z%nTJGC_0cq^+t-zx}5s!?V8yhKiMW=Z|~*{fy&ksMX*AGMmjv(o1#fq4LSJH6j=5O z>U2!Oqa6ZvMd-Qrcb85pcLS%ru+Q94X@$?s4&P>3zpW>}jenvG2$ARtk$L9A5)I-@ zmLm6ag;G=B(5iih=-ue{#|8UOr5;YRYUf?jK>>bLG zpxtqP_Bx*DSP|L?ZWOp5KGke2(RGa1*9Taen-`u22hk_6WuFE$*d=e}3V@~M3$rO% z*FgvM;e+b@m^%M)89huH*6p|c3ILyA)Lp7k9@M_xA_H5l!MxBF@-`ln6=vMT4A-tP z{_uUSoTSS4G#)gEt+PnOerdrLLRLNApRifQ2rKy`^i$j?T-I>tIklQlt~%#vO4rgR z2+T&amF(x`*5xTYEl74>Zw462Z>Ee@`UuG`n%fo)e zoLUFM6anL#Vc^_sZ?1%S!9ad_NBbmZuYaIS5$LIb(JvuLF`gA6X|VRWi_zAxqsMPA z3;pCZf+BSCv-QO`4(7bo1YYIG!EV%)1^4KM-gt+T$=kO+yCqɐ&wobxXod*dkN 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Ⓢ;az{x9fk>Ce8 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

;!?9Yt{r@Q|aOhvGt-2zb0cUB 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≪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%0DTWM$33$)@Zl3Zm^bR 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

eKi8Cc&(o*TDo#!*F2m*CxcBmR}y+i94y1ugHf| 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�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

S6ygBHJb^3-*4#*f% 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 Dp_0QN literal 0 HcmV?d00001 diff --git a/test/3axis0.cfg b/test/3axis0.cfg new file mode 100644 index 0000000..dc2953b --- /dev/null +++ b/test/3axis0.cfg @@ -0,0 +1,22 @@ +## FicTrac config file (build Mar 10 2020) +c2a_cnrs_xy : { 0, 0, 640, 0, 640, 640, 0, 640 } +c2a_r : { -0.000000, 0.000000, 1.570796 } +c2a_src : c2a_cnrs_xy +c2a_t : { -0.000000, -0.000000, 0.288675 } +do_display : y +max_bad_frames : -1 +opt_bound : 0.350000 +opt_do_global : n +opt_max_err : -1.000000 +opt_max_evals : 50 +opt_tol : 0.001000 +q_factor : 6 +roi_circ : { 250, 318, 268, 274, 301, 253, 357, 262, 387, 304, 378, 355, 309, 387 } +roi_ignr : { } +save_debug : n +save_raw : n +src_fn : 3axis0.avi +src_fps : -1.000000 +thr_ratio : 1.250000 +thr_win_pc : 0.250000 +vfov : 120 diff --git a/test/3axis0_gt.csv b/test/3axis0_gt.csv new file mode 100644 index 0000000..98cda58 --- /dev/null +++ b/test/3axis0_gt.csv @@ -0,0 +1,100 @@ +0,-0,0 +-0.051106,0.19408,-0.12047 +-0.076408,0.39394,-0.22629 +-0.074381,0.59669,-0.31494 +-0.043754,0.79869,-0.38395 +0.016309,0.99551,-0.43098 +0.10599,1.1819,-0.45397 +0.22456,1.352,-0.45129 +0.37012,1.4992,-0.42193 +0.53939,1.6165,-0.36574 +0.7275,1.6974,-0.28363 +0.92799,1.7356,-0.17777 +1.133,1.7264,-0.051613 +1.3335,1.6669,0.090127 +1.5202,1.5566,0.24176 +1.6839,1.3977,0.39702 +1.8163,1.1948,0.54954 +1.911,0.9548,0.69344 +1.9633,0.68624,0.82374 +1.9708,0.39836,0.93671 +1.9331,0.10051,1.03 +1.8516,-0.1985,1.1028 +1.7288,-0.49077,1.1552 +1.5683,-0.76959,1.1888 +1.3742,-1.0294,1.2058 +1.1508,-1.2659,1.2089 +0.90248,-1.4757,1.2014 +0.63344,-1.6562,1.187 +0.3479,-1.8054,1.1695 +0.049999,-1.922,1.1529 +-0.25613,-2.0047,1.1412 +-0.56619,-2.0527,1.1388 +-0.87553,-2.0653,1.1499 +-1.179,-2.042,1.179 +-1.4705,-1.9826,1.2301 +-1.7433,-1.8872,1.3072 +-1.989,-1.7569,1.4136 +-2.1983,-1.5934,1.5513 +2.2179,1.3156,-1.6166 +2.171,1.0421,-1.6904 +2.084,0.79183,-1.7846 +1.9622,0.56789,-1.8965 +1.8101,0.37261,-2.0233 +1.6314,0.20788,-2.1624 +1.4295,0.075339,-2.3108 +1.2068,-0.023423,-2.4655 +0.96562,-0.086745,-2.6234 +0.70777,-0.11277,-2.7807 +0.43478,-0.099389,-2.9332 +0.14807,-0.044155,-3.0758 +0.14483,-0.053407,3.0728 +0.40491,-0.17846,2.9063 +0.62427,-0.32131,2.7073 +0.80366,-0.47505,2.4824 +0.94557,-0.63394,2.2374 +1.0535,-0.79341,1.9769 +1.1316,-0.94994,1.7048 +1.1841,-1.1009,1.424 +1.2152,-1.2443,1.137 +1.2291,-1.3788,0.84584 +1.2298,-1.5036,0.55219 +1.2212,-1.6183,0.25754 +1.2073,-1.7225,-0.036713 +1.192,-1.8166,-0.32917 +1.1793,-1.9007,-0.61829 +1.1734,-1.9756,-0.90229 +1.179,-2.042,-1.179 +1.201,-2.1009,-1.4454 +1.2451,-2.1536,-1.6979 +-1.2604,2.106,1.8474 +-1.1987,1.889,1.7981 +-1.1648,1.692,1.7067 +-1.1555,1.5171,1.5814 +-1.1678,1.3655,1.4291 +-1.1989,1.2375,1.2554 +-1.2457,1.1332,1.0648 +-1.3059,1.0524,0.86078 +-1.3769,0.99481,0.64645 +-1.4565,0.9601,0.42426 +-1.5427,0.94795,0.1963 +-1.6335,0.95804,-0.035632 +-1.7271,0.9901,-0.26996 +-1.8216,1.0439,-0.50529 +-1.9154,1.1193,-0.7403 +-2.0067,1.216,-0.97379 +-2.0937,1.334,-1.2046 +-2.1747,1.4732,-1.4315 +2.1204,-1.5407,1.5596 +1.859,-1.4593,1.5032 +1.6105,-1.3742,1.4155 +1.3758,-1.2829,1.3043 +1.1555,-1.1835,1.1757 +0.95014,-1.0747,1.0347 +0.76026,-0.95536,0.88559 +0.58662,-0.82468,0.73195 +0.43011,-0.6823,0.577 +0.29183,-0.52814,0.42365 +0.17308,-0.3625,0.27462 +0.075274,-0.18608,0.13255 +-3.9288e-15,-1.019e-13,-7.8575e-15 diff --git a/test/test.py b/test/test.py new file mode 100644 index 0000000..f0010da --- /dev/null +++ b/test/test.py @@ -0,0 +1,64 @@ +import sys +import csv +import numpy as np +import matplotlib.pyplot as plt +import glob +import subprocess +import os + +if (len(sys.argv) < 2): + print('Needs at least test name!') + sys.exit() + +test_name = sys.argv[1] + +print('Processing test {}'.format(test_name)) + +cfg_fn = test_name+'.cfg' + +print('Using cfg: {}'.format(cfg_fn)) + +gt_csv = test_name+'_gt.csv' + +print('Using gt: {}'.format(gt_csv)) + +if os.name == 'nt': + cmd = "..\\bin\\Release\\fictrac.exe" +else: + cmd = "..\\bin\\Release\\fictrac" + +subprocess.run([cmd, cfg_fn]) + +dat_list = glob.glob(test_name+'-*.dat') + +print('Found {} dat files matching pattern {}'.format(len(dat_list), test_name)) + +dat_fn = dat_list[-1] + +print('Using dat: {}'.format(dat_fn)) + +with open(gt_csv, 'r') as f: + tmp = list(csv.reader(f)) + gt = np.array([[float(i) for i in x] for x in tmp]) + +with open(dat_fn, 'r') as f: + tmp = list(csv.reader(f)) + dat = np.array([[float(i) for i in x] for x in tmp]) + dat = dat[:,8:11] + +err = np.linalg.norm(gt - dat, axis=1) +max_err = np.max(err) +end_err = err[-1] + +print('max_err: {} end_err: {}'.format(max_err, end_err)) + +plt.figure(1) +plt.plot(gt) +plt.plot(dat) +plt.grid() + +plt.figure(2) +plt.plot(err) +plt.grid() + +plt.show() diff --git a/test/test_3axis0.m b/test/test_3axis0.m new file mode 100644 index 0000000..a35f5bb --- /dev/null +++ b/test/test_3axis0.m @@ -0,0 +1,46 @@ +function [max_err, end_err] = test_3axis0(dat_fn) + +dat = load(dat_fn); +test = dat(:,9:11) + +rotx = @(t) [1 0 0; 0 cos(t) -sin(t) ; 0 sin(t) cos(t)] ; +roty = @(t) [cos(t) 0 sin(t) ; 0 1 0 ; -sin(t) 0 cos(t)] ; +rotz = @(t) [cos(t) -sin(t) 0 ; sin(t) cos(t) 0 ; 0 0 1] ; + +% rotate <360,1080,720>*clock +R = deg2rad([360,1080,720]); +t = linspace(0,100,100); + +rlist = zeros(100,3); +for i = 1:100 + dR = t(i) .* R; + + A = eye(3); + A = rotx(dR(1)) * A; + A = roty(dR(2)) * A; + A = rotz(dR(3)) * A; + + r = rotationMatrixToVector(A); + rlist(i,:) = r; +end + +% hmm.. +rlist(:,2) = -rlist(:,2); + +err = vecnorm(rlist - test, 2, 2); +max_err = max(err); +end_err = err(end); + +figure(1); +hold off; +plot(rlist); +hold on; +plot(test, 'x-'); +grid on; + +figure(2); +hold off; +plot(err); +grid on; + +csvwrite('3axis0_gt.csv', rlist); From fcefd8d64a92df70e9eb4684122d6d626f8bc738 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 11 Mar 2020 13:26:16 +0100 Subject: [PATCH 172/235] [fictrac] dumpStats --- exec/fictrac.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/exec/fictrac.cpp b/exec/fictrac.cpp index 0bda4ed..37562cb 100644 --- a/exec/fictrac.cpp +++ b/exec/fictrac.cpp @@ -35,7 +35,7 @@ int main(int argc, char *argv[]) /// Parse args. string log_level = "info"; string config_fn = "config.txt"; - bool do_test = false; + bool do_stats = false; for (int i = 1; i < argc; ++i) { if ((string(argv[i]) == "--verbosity") || (string(argv[i]) == "-v")) { if (++i < argc) { @@ -47,7 +47,7 @@ int main(int argc, char *argv[]) } } else if (string(argv[i]) == "--stats") { - do_test = true; + do_stats = true; } else { config_fn = argv[i]; @@ -84,8 +84,8 @@ int main(int argc, char *argv[]) tracker->writeTemplate(); /// If we're running in test mode, print some stats. - if (do_test) { - tracker->dumpState(); + if (do_stats) { + tracker->dumpStats(); } /// Try to force release of all objects. From 7ff8d34b14db70da5485a4425ac1a318175ef55a Mon Sep 17 00:00:00 2001 From: Raghav Prasad Date: Sun, 5 Jul 2020 10:05:00 +0530 Subject: [PATCH 173/235] Modify code to allow building from source on macOS Code was not checking for __APPLE__, only for __linux__ and _WIN32. This commit fixes that. Additionally, fixes the problem of non-trivial object types being passed to LOG functions. --- include/SocketRecorder.h | 2 +- src/ConfigParser.cpp | 6 +++--- src/SocketRecorder.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/SocketRecorder.h b/include/SocketRecorder.h index 1f1c85c..bf174a3 100644 --- a/include/SocketRecorder.h +++ b/include/SocketRecorder.h @@ -6,7 +6,7 @@ #pragma once -#ifdef __linux__ +#ifdef __APPLE__ || __linux__ #include "SocketRecorder_linux.h" #elif _WIN32 #include "SocketRecorder_win.h" diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 08ac665..071022c 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -102,7 +102,7 @@ int ConfigParser::write(string fn) /// Open output file std::ofstream f(fn); if (!f.is_open()) { - LOG_ERR("Could not open config file %s for writing!", fn); + LOG_ERR("Could not open config file %s for writing!", fn.c_str()); return -1; } @@ -115,7 +115,7 @@ int ConfigParser::write(string fn) // warning: super long str vals will cause overwrite error! try { sprintf(tmps, "%-16s : %s\n", it.first.c_str(), it.second.c_str()); } catch (std::exception& e) { - LOG_ERR("Error writing key/value pair (%s : %s)! Error was: %s", it.first, it.second, e.what()); + LOG_ERR("Error writing key/value pair (%s : %s)! Error was: %s", it.first.c_str(), it.second.c_str(), e.what()); f.close(); return -1; } @@ -331,5 +331,5 @@ void ConfigParser::printAll() for (auto& it : _data) { s << "\t" << it.first << "\t: " << it.second << std::endl; } - LOG_DBG("%s", s.str()); + LOG_DBG("%s", s.str().c_str()); } diff --git a/src/SocketRecorder.cpp b/src/SocketRecorder.cpp index c7fe566..15c10ca 100644 --- a/src/SocketRecorder.cpp +++ b/src/SocketRecorder.cpp @@ -4,7 +4,7 @@ /// \author Richard Moore /// \copyright CC BY-NC-SA 3.0 -#ifdef __linux__ +#ifdef __APPLE__ || __linux__ #include "SocketRecorder_linux.src" #elif _WIN32 #include "SocketRecorder_win.src" From 3d324d60508bf2b21017bfe7117ea7aed9d54940 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 17 Sep 2020 00:20:49 +0200 Subject: [PATCH 174/235] [FicTrac] minor refactor --- src/Trackball.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Trackball.cpp b/src/Trackball.cpp index d46de5f..642f436 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -67,7 +67,7 @@ const vector> CODECS = { /// /// /// -bool intersectSphere(const double camVec[3], double sphereVec[3], const double r) +bool intersectSphere(const double r, const double camVec[3], double sphereVec[3]) { double q = camVec[2] * camVec[2] + r * r - 1; if (q < 0) { return false; } @@ -318,12 +318,12 @@ Trackball::Trackball(string cfg_fn) for (int j = 0; j < _roi_w; j++) { if (pmask[j] < 255) { continue; } - double l[3] = { 0 }; + double l[3] = { 0, 0, 0 }; _roi_model->pixelIndexToVector(j, i, l); vec3normalise(l); double* s = &(*_p1s_lut)[(i * _roi_w + j) * 3]; - if (!intersectSphere(l, s, _r_d_ratio)) { pmask[j] = 128; } + if (!intersectSphere(_r_d_ratio, l, s)) { pmask[j] = 128; } } } From 160cb43e857442857d180a42492fbd08fd12dfd8 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 17 Sep 2020 00:25:10 +0200 Subject: [PATCH 175/235] [fictrac] replace TCP socket output with boost::asio UDP datagrams --- include/SocketRecorder.h | 30 +++++++-- scripts/socket_client.py | 10 +-- src/SocketRecorder.cpp | 98 ++++++++++++++++++++++++++-- src/SocketRecorder_linux.src | 112 -------------------------------- src/SocketRecorder_win.src | 120 ----------------------------------- src/Trackball.cpp | 15 ++++- 6 files changed, 133 insertions(+), 252 deletions(-) delete mode 100644 src/SocketRecorder_linux.src delete mode 100644 src/SocketRecorder_win.src diff --git a/include/SocketRecorder.h b/include/SocketRecorder.h index 1f1c85c..cf7ae44 100644 --- a/include/SocketRecorder.h +++ b/include/SocketRecorder.h @@ -1,13 +1,31 @@ /// FicTrac http://rjdmoore.net/fictrac/ /// \file SocketRecorder.h -/// \brief Implementation of socket recorder. +/// \brief Implementation of socket recorder based on boost::asio UDP datagrams. /// \author Richard Moore /// \copyright CC BY-NC-SA 3.0 #pragma once -#ifdef __linux__ -#include "SocketRecorder_linux.h" -#elif _WIN32 -#include "SocketRecorder_win.h" -#endif +#include "RecorderInterface.h" + +#include + +class SocketRecorder : public RecorderInterface +{ +public: + SocketRecorder(); + ~SocketRecorder(); + + /// Interface to be overridden by implementations. + bool openRecord(std::string host_port); + bool writeRecord(std::string s); + void closeRecord(); + +private: + std::string _host; + int _port; + + boost::asio::io_service _io_service; + boost::asio::ip::udp::socket _socket; + boost::asio::ip::udp::endpoint _endpoint; +}; diff --git a/scripts/socket_client.py b/scripts/socket_client.py index f82b83f..794785e 100644 --- a/scripts/socket_client.py +++ b/scripts/socket_client.py @@ -2,12 +2,12 @@ import socket -HOST = '127.0.0.1' # The server's hostname or IP address -PORT = ???? # The port used by the server +HOST = '127.0.0.1' # The (receiving) host IP address (sock_host) +PORT = ???? # The (receiving) host port (sock_port) -# Open the connection (FicTrac must be waiting for socket connection) -with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - sock.connect((HOST, PORT)) +# Open the connection (ctrl-c / ctrl-break to quit) +with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.bind((HOST, PORT)) data = "" diff --git a/src/SocketRecorder.cpp b/src/SocketRecorder.cpp index c7fe566..340c0df 100644 --- a/src/SocketRecorder.cpp +++ b/src/SocketRecorder.cpp @@ -1,11 +1,97 @@ /// FicTrac http://rjdmoore.net/fictrac/ /// \file SocketRecorder.cpp -/// \brief Implementation of socket recorder. +/// \brief Implementation of socket recorder based on boost::asio UDP datagrams. /// \author Richard Moore /// \copyright CC BY-NC-SA 3.0 -#ifdef __linux__ -#include "SocketRecorder_linux.src" -#elif _WIN32 -#include "SocketRecorder_win.src" -#endif +#include "SocketRecorder.h" + +#include "Logger.h" + +#include + +#include + +using namespace std; +using boost::asio::ip::udp; + +/// +/// +/// +SocketRecorder::SocketRecorder() + : _socket(_io_service) +{ + _type = SOCK; +} + +/// +/// +/// +SocketRecorder::~SocketRecorder() +{ + closeRecord(); +} + +/// +/// +/// +bool SocketRecorder::openRecord(std::string host_port) +{ + // extract host name and port + size_t pos = host_port.find_first_of(':'); + if (pos == string::npos) { + LOG_ERR("Error! Malformed host:port string."); + return false; + } + _host = host_port.substr(0, pos); + _port = stoi(host_port.substr(pos + 1)); + + _endpoint = udp::endpoint(boost::asio::ip::address::from_string(_host), _port); + + LOG("Opening UDP connection to %s:%d", _host.c_str(), _port); + + // open socket + try { + _socket.open(udp::v4()); + + _open = _socket.is_open(); + if (!_open) { throw; } + } + catch (const boost::system::system_error& e) { + LOG_ERR("Error! Could not open UDP connection to %s:%d due to %s", _host.c_str(), _port, e.what()); + _open = false; + } + catch (...) { + LOG_ERR("Error! Could not open UDP connection to %s:%d.", _host.c_str(), _port); + _open = false; + } + return _open; +} + +/// +/// +/// +bool SocketRecorder::writeRecord(string s) +{ + if (_open) { + try { + _socket.send_to(boost::asio::buffer(s), _endpoint); + } + catch (const boost::system::system_error& e) { + LOG_ERR("Error writing to socket (%s:%d)! Error was %s", _host.c_str(), _port, e.what()); + return false; + } + } + return _open; +} + +/// +/// +/// +void SocketRecorder::closeRecord() +{ + LOG("Closing UDP connection..."); + + _open = false; + _socket.close(); +} diff --git a/src/SocketRecorder_linux.src b/src/SocketRecorder_linux.src deleted file mode 100644 index 26ef9ec..0000000 --- a/src/SocketRecorder_linux.src +++ /dev/null @@ -1,112 +0,0 @@ -/// FicTrac http://rjdmoore.net/fictrac/ -/// \file SocketRecorder_linux.cpp -/// \brief Linux implementation of socket recorder. -/// \author Richard Moore -/// \copyright CC BY-NC-SA 3.0 - -#include "SocketRecorder_linux.h" - -#include "Logger.h" - -#include -#include -#include -#include -#include -#include -#include - -#include // try, catch -#include // cout/cerr - -/// -/// -/// -SocketRecorder::SocketRecorder() - : _listenSocket(-1), _clientSocket(-1) -{ - _type = SOCK; -} - -/// -/// -/// -SocketRecorder::~SocketRecorder() -{ - closeRecord(); -} - -/// -/// -/// -bool SocketRecorder::openRecord(std::string port) -{ - struct sockaddr_in serv_addr, cli_addr; - - // Create socket - _listenSocket = socket(AF_INET, SOCK_STREAM, 0); - if (_listenSocket < 0) { - LOG_ERR("Error! Could not create valid socket on port %s.", port.c_str()); - return false; - } - - // Bind socket - bzero((char *) &serv_addr, sizeof(serv_addr)); - int portno = -1; - try { - portno = atoi(port.c_str()); - } - catch (...) { - LOG_ERR("Error! Invalid socket port number (%s).", port.c_str()); - return false; - } - serv_addr.sin_family = AF_INET; - serv_addr.sin_addr.s_addr = INADDR_ANY; - serv_addr.sin_port = htons(portno); - if (bind(_listenSocket, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { - LOG_ERR("Error! Failed to bind socket on port %s.", port.c_str()); - return false; - } - - // Listen on our socket - if (listen(_listenSocket,5) < 0) { - LOG_ERR("Error! Failed to listen to socket on port %s.", port.c_str()); - return false; - } - - // Wait for client connection... - socklen_t clilen = sizeof(cli_addr); - PRINT("\nWaiting for client connection to socket: %s ...\n", port.c_str()); - _clientSocket = accept(_listenSocket, (struct sockaddr *) &cli_addr, &clilen); // blocking - if (_clientSocket < 0) { - LOG_ERR("Error! Failed to accept socket connection on port %s.", port.c_str()); - return false; - } - - return (_open = true); -} - -/// -/// -/// -bool SocketRecorder::writeRecord(std::string s) -{ - if (_open) { - int n = write(_clientSocket,s.c_str(),s.size()); - if (n < 0) { - LOG_ERR("Error! Send failed."); - _open = false; // should this be a terminal error? - } - } - return _open; -} - -/// -/// -/// -void SocketRecorder::closeRecord() -{ - _open = false; - close(_clientSocket); - close(_listenSocket); -} diff --git a/src/SocketRecorder_win.src b/src/SocketRecorder_win.src deleted file mode 100644 index 243d1e9..0000000 --- a/src/SocketRecorder_win.src +++ /dev/null @@ -1,120 +0,0 @@ -/// FicTrac http://rjdmoore.net/fictrac/ -/// \file SocketRecorder_win.cpp -/// \brief Windows implementation of socket recorder. -/// \author Richard Moore -/// \copyright CC BY-NC-SA 3.0 - -#include "SocketRecorder_win.h" - -#include "Logger.h" - -#include -#include - -/// -/// -/// -SocketRecorder::SocketRecorder() - : _listenSocket(INVALID_SOCKET), _clientSocket(INVALID_SOCKET) -{ - _type = SOCK; -} - -/// -/// -/// -SocketRecorder::~SocketRecorder() -{ - closeRecord(); -} - -/// -/// -/// -bool SocketRecorder::openRecord(std::string port) -{ - // Initialize Winsock - int iResult = WSAStartup(MAKEWORD(2, 2), &_wsaData); - if (iResult != 0) { - LOG_ERR("Error! Failed to initialise WinSock library (err = %d)", iResult); - return false; - } - - struct addrinfo *result = nullptr, hints; - - ZeroMemory(&hints, sizeof(hints)); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - hints.ai_flags = AI_PASSIVE; - - // Resolve the local address and port to be used by the server - iResult = getaddrinfo(NULL, port.c_str(), &hints, &result); - if (iResult != 0) { - LOG_ERR("Error! Failed to resolve local address (err = %d).", iResult); - return false; - } - - // Create a SOCKET for the server to listen for client connections - _listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); - if (_listenSocket == INVALID_SOCKET) { - //FIXME: include IP address in error msg. - LOG_ERR("Error! Could not create valid socket on port %s (err = %d).", port.c_str(), WSAGetLastError()); - freeaddrinfo(result); - return false; - } - - // Setup the TCP listening socket - iResult = bind(_listenSocket, result->ai_addr, (int)result->ai_addrlen); - if (iResult == SOCKET_ERROR) { - LOG_ERR("Error! Failed to bind socket on port %s (err = %d).", port.c_str(), WSAGetLastError()); - freeaddrinfo(result); - return false; - } - - // After successful bind(), we no longer need address info. - freeaddrinfo(result); - - // Listen on our socket. - if (listen(_listenSocket, SOMAXCONN) == SOCKET_ERROR) { - LOG_ERR("Error! Failed to listen to socket on port %s (err = %d).", port.c_str(), WSAGetLastError()); - return false; - } - - // Wait for client connection... - //FIXME: include IP:port info in this message - PRINT("\nWaiting for client connection to socket: %s ...\n", port.c_str()); - _clientSocket = accept(_listenSocket, NULL, NULL); // blocking - if (_clientSocket == INVALID_SOCKET) { - LOG_ERR("Error! Failed to accept socket connection (err = %d).", WSAGetLastError()); - return false; - } - - return (_open = true); -} - -/// -/// -/// -bool SocketRecorder::writeRecord(std::string s) -{ - if (_open) { - int iSendResult = send(_clientSocket, s.c_str(), s.size(), 0); - if (iSendResult == SOCKET_ERROR) { - LOG_ERR("Error! Send failed (err = %d).", WSAGetLastError()); - _open = false; // should this be a terminal error? - } - } - return _open; -} - -/// -/// -/// -void SocketRecorder::closeRecord() -{ - _open = false; - closesocket(_clientSocket); - closesocket(_listenSocket); - WSACleanup(); -} diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 642f436..464daca 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -49,6 +49,9 @@ const double THRESH_WIN_PC_DEFAULT = 0.25; const uint8_t SPHERE_MAP_FIRST_HIT_BONUS = 64; +const string SOCK_HOST_DEFAULT = "127.0.0.1"; +const int SOCK_PORT_DEFAULT = 0; + const int COM_BAUD_DEFAULT = 115200; const bool DO_DISPLAY_DEFAULT = true; @@ -389,12 +392,18 @@ Trackball::Trackball(string cfg_fn) return; } - int sock_port = 0; + int sock_port = SOCK_PORT_DEFAULT; _do_sock_output = false; if (_cfg.getInt("sock_port", sock_port) && (sock_port > 0)) { - _data_sock = make_unique(RecorderInterface::RecordType::SOCK, std::to_string(sock_port)); + string sock_host = SOCK_HOST_DEFAULT; + if (!_cfg.getStr("sock_host", sock_host)) { + LOG_WRN("Warning! Using default value for sock_host (%s).", sock_host.c_str()); + _cfg.add("sock_host", sock_host); + } + + _data_sock = make_unique(RecorderInterface::RecordType::SOCK, sock_host + ":" + std::to_string(sock_port)); if (!_data_sock->is_active()) { - LOG_ERR("Error! Unable to open output data socket (%d).", sock_port); + LOG_ERR("Error! Unable to open output data socket (%s:%d).", sock_host.c_str() ,sock_port); _active = false; return; } From 37401d93d678c813f19d94214a3baa001e603920 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 17 Sep 2020 00:26:03 +0200 Subject: [PATCH 176/235] [fictrac] minor logging and comments --- src/SerialRecorder.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/SerialRecorder.cpp b/src/SerialRecorder.cpp index 4c41fe7..e94d66a 100644 --- a/src/SerialRecorder.cpp +++ b/src/SerialRecorder.cpp @@ -40,7 +40,7 @@ bool SerialRecorder::openRecord(std::string port_baud) // extract port no and baud size_t pos = port_baud.find_first_of('@'); if (pos == string::npos) { - LOG_ERR("Error! Malformed port:baud string."); + LOG_ERR("Error! Malformed port@baud string."); return false; } _port_name = port_baud.substr(0, pos); @@ -54,6 +54,10 @@ bool SerialRecorder::openRecord(std::string port_baud) _port = make_shared(io); _port->open(_port_name); //_port->set_option(asio::serial_port_base::baud_rate(baud)); + //_port->set_option(asio::serial_port_base::character_size(8)); + //_port->set_option(asio::serial_port_base::flow_control(serial_port_base::flow_control::none)); + //_port->set_option(asio::serial_port_base::parity(serial_port_base::parity::none)); + //_port->set_option(asio::serial_port_base::stop_bits(serial_port_base::stop_bits::one)); _open = _port->is_open(); if (!_open) { throw; } } From fcab6acb8cad41bac50da70da0ae0817b98221c5 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 17 Sep 2020 00:42:30 +0200 Subject: [PATCH 177/235] [fictrac] change sock_port default to -1 --- src/Trackball.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 464daca..188c7ea 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -50,7 +50,7 @@ const double THRESH_WIN_PC_DEFAULT = 0.25; const uint8_t SPHERE_MAP_FIRST_HIT_BONUS = 64; const string SOCK_HOST_DEFAULT = "127.0.0.1"; -const int SOCK_PORT_DEFAULT = 0; +const int SOCK_PORT_DEFAULT = -1; const int COM_BAUD_DEFAULT = 115200; From 5964bc1fcb052f02bc67c6ddfac5d43b632a7797 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 17 Sep 2020 00:58:44 +0200 Subject: [PATCH 178/235] Update params.md --- doc/params.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/params.md b/doc/params.md index aa798a4..6173d25 100644 --- a/doc/params.md +++ b/doc/params.md @@ -10,12 +10,13 @@ In the table below, the various possible parameters are listed. If nothing is li | do_display | bool | y | y/n | If you want to | Display debug screen during tracking. Slows execution very slightly. | | save_debug | bool | n | y/n | If you want to | Record the debug screen to video file. Note that if the source frame rate is higher than FicTrac's processing frame rate, frames may be dropped from the video file. | | save_raw | bool | n | y/n | If you want to | Record the input image stream to video file. Note that if the source frame rate is higher than FicTrac's processing frame rate, frames may be dropped from the video file. | -| sock_port | int | -1 | (0,inf) | If you want to | Socket port over which to transmit FicTrac data. If unset or < 0, FicTrac will not transmit data over sockets. | +| sock_host | string | 127.0.0.1 | | If you want to | Destination IP address for socket data output. Unused if sock_port is not set. | +| sock_port | int | -1 | \[0,65535\] | If you want to | Destination socket port for socket data output. If unset or <= 0, FicTrac will not transmit data over sockets. Note that a number of ports are reserved and some might be in use. To avoid conflicts, you should check which UDP ports are available on your machine prior to launching FicTrac (try something like 1111). | | com_port | string | | | If you want to | Serial port over which to transmit FicTrac data. If unset, FicTrac will not transmit data over serial. | | com_baud | int | 115200 | | If you want to | Baud rate to use for COM port. Unused if no com_port set. | | | | | | | | | fisheye | bool | n | y/n | Only if you need to | If set, FicTrac will assume the imaging system has a fisheye lens, otherwise a rectilinear lens is assumed. | -| q_factor | int | 6 | (0,inf) | Only if you need to | Adjusts the resolution of the tracking window. Smaller values correspond to coarser but quicker tracking and vice-versa. Normally in the range [3,10]. | +| q_factor | int | 6 | (0,inf) | Only if you need to | Adjusts the resolution of the tracking window. Smaller values correspond to coarser but quicker tracking and vice-versa. Normally in the range \[3,10\]. | | src_fps | float | -1 | (0,inf) | Only if you need to | If set, FicTrac will attempt to set the frame rate for the image source (video file or camera). | | max_bad_frames | int | -1 | (0,inf) | Only if you need to | If set, FicTrac will reset tracking after being unable to match this many frames in a row. Defaults to never resetting tracking. | | opt_do_global | bool | n | y/n | Only if you need to | Perform a slow global search after max_bad_frames are reached. This may allow FicTrac to recover after a tracking fail, but should only be used when playing back from video file, as it is slow! | From 2c78ae30da7bf67aa7e88cedc94fd55e7dbf14ee Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Thu, 17 Sep 2020 22:41:06 +0200 Subject: [PATCH 179/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d64d86..3605b5c 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ FicTrac will usually generate two output files: 1. Log file (*.log) - containing debugging information about FicTrac's execution. 2. Data file (*.dat) - containing output data. See [data_header](doc/data_header.txt) for information about output data. -The output data file can be used for offline processing. To use FicTrac within a closed-loop setup (to provide real-time feedback for stimuli), you should configure FicTrac to output data via a socket (IP address/port) in real-time. To do this, just set `out_port` to a valid port number in the config file. There is an example Python script for receiving data via sockets in the `scripts` directory. +The output data file can be used for offline processing. To use FicTrac within a closed-loop setup (to provide real-time feedback for stimuli), you should configure FicTrac to output data via a socket (IP address/port) in real-time. To do this, just set `sock_port` to a valid port number in the config file. There is an example Python script for receiving data via sockets in the `scripts` directory. **Note:** If you encounter issues trying to generate output videos (i.e. `save_raw` or `save_debug`), you might try changing the default video codec via `vid_codec` - see [config params](doc/params.md) for details. If you receive an error about a missing [H264 library](https://github.com/cisco/openh264/releases), you can download the necessary library (i.e. OpenCV 3.4.3 requires `openh264-1.7.0-win64.dll`) from the above link and place it in the `dll` folder under the FicTrac main directory. You will then need to re-run the appropriate `cmake ..` and `cmake --build` commands for your installation. From 6ce1a1b2a2e6acaf78012fd31ec610f7daa1e85d Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 21 Sep 2020 23:49:40 +0200 Subject: [PATCH 180/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3605b5c..b9e5ae0 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ The FicTrac source code can be built for both Windows and Linux (e.g. Ubuntu) op 4. Using Vcpkg, install OpenCV, NLopt, Boost::asio, and FFmpeg software packages (this may take more than 30 mins in some cases!): ``` -[Windows] .\vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[*]:x64-windows +[Windows] .\vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[x264]:x64-windows [Linux] ./vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux ``` From eb79aeca5cdab3ad5440b0e976e44d2181ffcaa4 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 22 Sep 2020 00:28:38 +0200 Subject: [PATCH 181/235] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b9e5ae0..9f2a280 100644 --- a/README.md +++ b/README.md @@ -51,13 +51,13 @@ The FicTrac source code can be built for both Windows and Linux (e.g. Ubuntu) op 1. [Cmake build system (3.16.0)](https://cmake.org/download/) (Windows win64-x64 Installer) 2. If you don't already have Visual Studio (C++ workflow) installed, you will need to install the [Build Tools for Visual Studio](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019). 2. Linux (Ubuntu) only: - 1. Run the following from terminal to install necessary build tools and dependencies: ```[Linux] sudo apt-get install gcc git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev``` + 1. Run the following from terminal to install necessary build tools and dependencies: ```[Linux] sudo apt-get install gcc git cmake curl unzip tar yasm pkg-config libgtk2.0-dev``` 3. (Windows and Linux) Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). 4. Using Vcpkg, install OpenCV, NLopt, Boost::asio, and FFmpeg software packages (this may take more than 30 mins in some cases!): ``` [Windows] .\vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[x264]:x64-windows -[Linux] ./vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux +[Linux] ./vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux ffmpeg[x264]:x64-linux ``` 2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: From 6435474d69f4efb91cbb302cae34a74ef806b045 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 22 Sep 2020 00:44:47 +0200 Subject: [PATCH 182/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f2a280..2a8abf0 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The FicTrac source code can be built for both Windows and Linux (e.g. Ubuntu) op 1. [Cmake build system (3.16.0)](https://cmake.org/download/) (Windows win64-x64 Installer) 2. If you don't already have Visual Studio (C++ workflow) installed, you will need to install the [Build Tools for Visual Studio](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019). 2. Linux (Ubuntu) only: - 1. Run the following from terminal to install necessary build tools and dependencies: ```[Linux] sudo apt-get install gcc git cmake curl unzip tar yasm pkg-config libgtk2.0-dev``` + 1. Run the following from terminal to install necessary build tools and dependencies: ```[Linux] sudo apt-get install gcc g++ git cmake curl unzip tar yasm pkg-config libgtk2.0-dev``` 3. (Windows and Linux) Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). 4. Using Vcpkg, install OpenCV, NLopt, Boost::asio, and FFmpeg software packages (this may take more than 30 mins in some cases!): From 5aa4e056f3eb71d025e8633400cc0a79f70f8a50 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 22 Sep 2020 02:40:24 +0200 Subject: [PATCH 183/235] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2a8abf0..aee6d2e 100644 --- a/README.md +++ b/README.md @@ -51,13 +51,13 @@ The FicTrac source code can be built for both Windows and Linux (e.g. Ubuntu) op 1. [Cmake build system (3.16.0)](https://cmake.org/download/) (Windows win64-x64 Installer) 2. If you don't already have Visual Studio (C++ workflow) installed, you will need to install the [Build Tools for Visual Studio](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019). 2. Linux (Ubuntu) only: - 1. Run the following from terminal to install necessary build tools and dependencies: ```[Linux] sudo apt-get install gcc g++ git cmake curl unzip tar yasm pkg-config libgtk2.0-dev``` + 1. Run the following from terminal to install necessary build tools and dependencies: ```[Linux] sudo apt-get install gcc g++ git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev``` 3. (Windows and Linux) Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). 4. Using Vcpkg, install OpenCV, NLopt, Boost::asio, and FFmpeg software packages (this may take more than 30 mins in some cases!): ``` [Windows] .\vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[x264]:x64-windows -[Linux] ./vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux ffmpeg[x264]:x64-linux +[Linux] ./vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux ``` 2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: From 41a08921d49b2e8808667883a9f0e2de89cd76d8 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 22 Sep 2020 03:17:09 +0200 Subject: [PATCH 184/235] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aee6d2e..3d22399 100644 --- a/README.md +++ b/README.md @@ -51,13 +51,13 @@ The FicTrac source code can be built for both Windows and Linux (e.g. Ubuntu) op 1. [Cmake build system (3.16.0)](https://cmake.org/download/) (Windows win64-x64 Installer) 2. If you don't already have Visual Studio (C++ workflow) installed, you will need to install the [Build Tools for Visual Studio](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019). 2. Linux (Ubuntu) only: - 1. Run the following from terminal to install necessary build tools and dependencies: ```[Linux] sudo apt-get install gcc g++ git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev``` + 1. Run the following from terminal to install necessary build tools and dependencies: ```[Linux] sudo apt-get install gcc g++ git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev libopencv-dev``` 3. (Windows and Linux) Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). 4. Using Vcpkg, install OpenCV, NLopt, Boost::asio, and FFmpeg software packages (this may take more than 30 mins in some cases!): ``` [Windows] .\vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[x264]:x64-windows -[Linux] ./vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux +[Linux] ./vcpkg install nlopt:x64-linux boost-asio:x64-linux ``` 2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: From 915db3114cc9d466db597a07fab6506e7c18b976 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 22 Sep 2020 03:17:53 +0200 Subject: [PATCH 185/235] Update requirements.md --- doc/requirements.md | 72 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/doc/requirements.md b/doc/requirements.md index 6a645a2..e08e887 100644 --- a/doc/requirements.md +++ b/doc/requirements.md @@ -59,6 +59,78 @@ On a ~3.2 GHz quadcore processor processor, and with default configuration setti Ambient lighting should ideally be diffuse (no specular reflections from the track ball surface) and bright enough to give good track ball surface exposure at a fast frame rate. +## Installing FicTrac + +The install process for FicTrac is a little complicated because FicTrac is released as source code, which you need to build on your local machine in order to generate a program that you can execute. There are two main reasons behind this decision: +1. As an open source project, users can contribute fixes and improvements - speeding up development. +2. Building locally allows users to choose software versions that suit their needs - giving more flexibility. + +The [main installation guide](../README.md#Installation) list the steps required to build and install FicTrac and its dependencies. To help with issues you may face during the install process, here is a complete step-by-step guide for installing FicTrac in Lubuntu 20.04 running in a virtual machine. There is nothing particularly special about this choice of setup other than Lubuntu is fairly lightweight and running in virtual machine simplifies testing. Other flavours of Debian linux (e.g. Ubuntu, Mint, etc) will be very similar, if not identical. If you are running from boot (rather than in virtual machine) you can ignore the first steps. + +### Installing FicTrac in Lubuntu 20.04 in VirtualBox VM + +1. Install [Oracle VirtualBox VM](https://www.virtualbox.org/wiki/Downloads) +2. Install Lubuntu 20.04 + 1. Download [Desktop 64-bit](https://lubuntu.me/downloads/) + 2. Launch VirtualBox and create new Linux 64-bit VM (make virtual disk at least 15 GB) + 3. Load Lubuntu iso into optical drive and launch + 4. Install Lubuntu via shortcut on live desktop + 5. Restart VM +3. Prepare Lubuntu + 1. Launch Lubuntu + 2. Update/upgrade all packages (in terminal): +``` +sudo apt-get update +sudo apt-get upgrade +``` + 3. Install VirtualBox Guest Additions + 1. Menu -> Devices -> Insert Guest Additions CD + 2. Execute in terminal: +``` +sudo apt-get install gcc make perl +cd /media/User/VBox_GAs_6.1.12 +sudo ./VBoxLinuxAdditions.run +``` + 3. Restart + 4. Make Development folder: +``` +cd ~ +mkdir Development +``` +4. Install prerequisites and dependencies + 1. Install built-in packages: +``` +sudo apt-get install gcc git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev +``` + 2. Install Vcpkg following instructions at https://github.com/Microsoft/vcpkg: +``` +cd ~/Development +git clone https://github.com/microsoft/vcpkg +./vcpkg/bootstrap-vcpkg.sh +``` + 3. Integrate vcpkg and record the path to the CMAKE_TOOLCHAIN_FILE (we will use it later): +``` +./vcpkg integrate install +``` + 4. Install vcpkg packages + 1. To speed up building, we can ask vcpkg to only build release packages. Edit ./vcpkg/triplets/x64-linux.cmake and add the following line: +``` +set(VCPKG_BUILD_TYPE release) +``` + 2. Execute the followinig in terminal to install dependencies (may take 30 min or more!): +``` +./vcpkg/vcpkg install opencv[ffmpeg]:x64-linux nlopt:x64-linux boost-asio:x64-linux Warning: the above command may take 30 min to complete +``` +5. Install fictrac + 1. Open terminal and type the following commands: + cd ~/Development + git clone https://github.com/rjdmoore/fictrac.git + cd fictrac + mkdir build + cd build + cmake -D CMAKE_TOOLCHAIN_FILE=~/Development/vcpkg/scripts/buildsystems/vcpkg.cake + + ## Configuring FicTrac ## Input parameters From a16d11189eb0496c0f69aeaed433ba4f2773abcc Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 23 Sep 2020 11:49:44 +0200 Subject: [PATCH 186/235] Update README.md --- README.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3d22399..19f04cb 100644 --- a/README.md +++ b/README.md @@ -48,18 +48,19 @@ The FicTrac source code can be built for both Windows and Linux (e.g. Ubuntu) op 1. Download and install required build tools and dependencies: 1. Windows only: - 1. [Cmake build system (3.16.0)](https://cmake.org/download/) (Windows win64-x64 Installer) + 1. [Cmake build system](https://cmake.org/download/) (Windows win64-x64 Installer) 2. If you don't already have Visual Studio (C++ workflow) installed, you will need to install the [Build Tools for Visual Studio](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019). - 2. Linux (Ubuntu) only: - 1. Run the following from terminal to install necessary build tools and dependencies: ```[Linux] sudo apt-get install gcc g++ git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev libopencv-dev``` + 2. Linux only: + 1. Run the following from terminal to install necessary build tools and dependencies: + ``` + [Linux] sudo apt-get install gcc g++ git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev libopencv-dev + ``` 3. (Windows and Linux) Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). - 4. Using Vcpkg, install OpenCV, NLopt, Boost::asio, and FFmpeg software packages (this may take more than 30 mins in some cases!): - + 4. Using Vcpkg, install remaining dependencies: ``` [Windows] .\vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[x264]:x64-windows [Linux] ./vcpkg install nlopt:x64-linux boost-asio:x64-linux ``` - 2. Clone or download the FicTrac repository, then navigate to that folder, open a terminal, and create a build directory: ``` mkdir build @@ -78,6 +79,8 @@ cd build If everything went well, the executables for FicTrac and a configuration utility will be placed under the `bin` directory in the FicTrac project folder. +If you encounter issues during the build process, try simply re-executing the step that failed. If you still encounter the same problem, check the [FicTrac forum](http://www.reddit.com/r/fictrac/) to see if anyone else has encountered (and hopefully solved!) the same issue. + Remember to update and re-build FicTrac occasionally, as the program is still under development and fixes and improvements are being made continuously. #### USB2/3 camera installation From 44ff99f8716b665116b1e3de5a8a48b6a77841cf Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 6 Oct 2020 20:28:48 +0200 Subject: [PATCH 219/235] azure debugging --- azure-pipelines.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4f24d97..a8ceea8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -5,8 +5,8 @@ # https://docs.microsoft.com/en-us/azure/devops/pipelines/build/triggers?view=azure-devops&tabs=yaml trigger: -- master -#- develop +#- master +- develop # We can run multiple jobs in parallel. # see https://docs.microsoft.com/en-us/azure/devops/pipelines/process/phases @@ -25,6 +25,7 @@ jobs: displayName: apt-get install prereqs #- script: echo "set(VCPKG_BUILD_TYPE release)" >> $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake # displayName: vcpkg set build type + - script: vcpkg version - script: vcpkg install nlopt:x64-linux boost-asio:x64-linux displayName: vcpkg install dependencies - task: CMake@1 @@ -45,6 +46,7 @@ jobs: vmImage: 'windows-latest' # The steps to run to execute the build. steps: + - script: vcpkg version - script: vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[x264]:x64-windows displayName: vcpkg install dependencies - task: CMake@1 From 3782003ce5f726f4984ea426012a539c0242bc32 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 6 Oct 2020 20:34:41 +0200 Subject: [PATCH 220/235] azure debugging --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a8ceea8..afe0151 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,7 +25,7 @@ jobs: displayName: apt-get install prereqs #- script: echo "set(VCPKG_BUILD_TYPE release)" >> $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake # displayName: vcpkg set build type - - script: vcpkg version + - script: pwd - script: vcpkg install nlopt:x64-linux boost-asio:x64-linux displayName: vcpkg install dependencies - task: CMake@1 @@ -46,7 +46,7 @@ jobs: vmImage: 'windows-latest' # The steps to run to execute the build. steps: - - script: vcpkg version + - script: pwd - script: vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[x264]:x64-windows displayName: vcpkg install dependencies - task: CMake@1 From 691e1a0c50d83a8e3230adedb16300c0a7dfecc6 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 6 Oct 2020 20:57:27 +0200 Subject: [PATCH 221/235] azure debugging --- azure-pipelines.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index afe0151..52ac1e0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,7 +25,6 @@ jobs: displayName: apt-get install prereqs #- script: echo "set(VCPKG_BUILD_TYPE release)" >> $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake # displayName: vcpkg set build type - - script: pwd - script: vcpkg install nlopt:x64-linux boost-asio:x64-linux displayName: vcpkg install dependencies - task: CMake@1 @@ -46,7 +45,6 @@ jobs: vmImage: 'windows-latest' # The steps to run to execute the build. steps: - - script: pwd - script: vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[x264]:x64-windows displayName: vcpkg install dependencies - task: CMake@1 From 72ba70534666fbe3098df8317f23ceeb52d7d853 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 6 Oct 2020 21:19:53 +0200 Subject: [PATCH 222/235] azure debugging --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 52ac1e0..16d8aa3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -45,6 +45,7 @@ jobs: vmImage: 'windows-latest' # The steps to run to execute the build. steps: + - script: git -C "C:/vcpkg" status - script: vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[x264]:x64-windows displayName: vcpkg install dependencies - task: CMake@1 From ed3af10651683ccd439ef8e3df8b8c82f8e1897c Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 6 Oct 2020 21:23:09 +0200 Subject: [PATCH 223/235] azure debugging --- azure-pipelines.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 16d8aa3..697907f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -45,7 +45,8 @@ jobs: vmImage: 'windows-latest' # The steps to run to execute the build. steps: - - script: git -C "C:/vcpkg" status + - script: git -C "C:/vcpkg" pull + - script: vcpkg update - script: vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[x264]:x64-windows displayName: vcpkg install dependencies - task: CMake@1 From 57422606319a81d8a4b13b97cea9405ce21df2cc Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 6 Oct 2020 21:31:16 +0200 Subject: [PATCH 224/235] azure debugging --- azure-pipelines.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 697907f..016a36a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,6 +25,8 @@ jobs: displayName: apt-get install prereqs #- script: echo "set(VCPKG_BUILD_TYPE release)" >> $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake # displayName: vcpkg set build type + - script: git -C "/usr/local/share/vcpkg" pull + displayName: vcpkg pull latest - script: vcpkg install nlopt:x64-linux boost-asio:x64-linux displayName: vcpkg install dependencies - task: CMake@1 @@ -46,7 +48,7 @@ jobs: # The steps to run to execute the build. steps: - script: git -C "C:/vcpkg" pull - - script: vcpkg update + displayName: vcpkg pull latest - script: vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[x264]:x64-windows displayName: vcpkg install dependencies - task: CMake@1 From 7cb798398a1b6b5171934079b903a4114ed15932 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 7 Oct 2020 08:09:53 +0200 Subject: [PATCH 225/235] azure debugging --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 016a36a..2f0ed40 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,7 +25,7 @@ jobs: displayName: apt-get install prereqs #- script: echo "set(VCPKG_BUILD_TYPE release)" >> $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake # displayName: vcpkg set build type - - script: git -C "/usr/local/share/vcpkg" pull + - script: sudo git -C "/usr/local/share/vcpkg" pull displayName: vcpkg pull latest - script: vcpkg install nlopt:x64-linux boost-asio:x64-linux displayName: vcpkg install dependencies From b07ccde854bb1f3972c82722a36ed1347aeea216 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 7 Oct 2020 08:52:05 +0200 Subject: [PATCH 226/235] azure debugging --- azure-pipelines.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2f0ed40..7b1b2f4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,8 +25,12 @@ jobs: displayName: apt-get install prereqs #- script: echo "set(VCPKG_BUILD_TYPE release)" >> $VCPKG_INSTALLATION_ROOT/triplets/x64-linux.cmake # displayName: vcpkg set build type - - script: sudo git -C "/usr/local/share/vcpkg" pull + - script: git -C "/usr/local/share/vcpkg" status + - script: git -C "/usr/local/share/vcpkg" reset --hard + - script: git -C "/usr/local/share/vcpkg" status + - script: git -C "/usr/local/share/vcpkg" pull displayName: vcpkg pull latest + - script: sudo sh /usr/local/share/vcpkg/bootstrap-vcpkg.sh - script: vcpkg install nlopt:x64-linux boost-asio:x64-linux displayName: vcpkg install dependencies - task: CMake@1 @@ -47,8 +51,12 @@ jobs: vmImage: 'windows-latest' # The steps to run to execute the build. steps: + - script: git -C "C:/vcpkg" status + - script: git -C "C:/vcpkg" reset --hard + - script: git -C "C:/vcpkg" status - script: git -C "C:/vcpkg" pull displayName: vcpkg pull latest + - script: C:/vcpkg/bootstrap-vcpkg.bat - script: vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[x264]:x64-windows displayName: vcpkg install dependencies - task: CMake@1 From 05feb8604b002308cf7c0a7b6b3278ccb20372e6 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Wed, 7 Oct 2020 09:13:10 +0200 Subject: [PATCH 227/235] azure debugging --- azure-pipelines.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7b1b2f4..f6e44f0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,6 +14,7 @@ jobs: # Provide a name for the job - job: Linux + timeoutInMinutes: 180 # how long to run the job before automatically cancelling # The VM image to use for the hosted agent. For a list of possible agents # see https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted # You can see the software installed on each agent at the same link. @@ -27,10 +28,12 @@ jobs: # displayName: vcpkg set build type - script: git -C "/usr/local/share/vcpkg" status - script: git -C "/usr/local/share/vcpkg" reset --hard + displayName: reset vcpkg to master - script: git -C "/usr/local/share/vcpkg" status - script: git -C "/usr/local/share/vcpkg" pull displayName: vcpkg pull latest - script: sudo sh /usr/local/share/vcpkg/bootstrap-vcpkg.sh + displayName: rebuild vcpkg - script: vcpkg install nlopt:x64-linux boost-asio:x64-linux displayName: vcpkg install dependencies - task: CMake@1 @@ -44,6 +47,7 @@ jobs: # Provide a name for the job - job: Windows + timeoutInMinutes: 180 # how long to run the job before automatically cancelling # The VM image to use for the hosted agent. For a list of possible agents # see https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted # You can see the software installed on each agent at the same link. @@ -53,10 +57,12 @@ jobs: steps: - script: git -C "C:/vcpkg" status - script: git -C "C:/vcpkg" reset --hard + displayName: reset vcpkg to master - script: git -C "C:/vcpkg" status - script: git -C "C:/vcpkg" pull displayName: vcpkg pull latest - script: C:/vcpkg/bootstrap-vcpkg.bat + displayName: rebuild vcpkg - script: vcpkg install opencv[ffmpeg]:x64-windows nlopt:x64-windows boost-asio:x64-windows ffmpeg[x264]:x64-windows displayName: vcpkg install dependencies - task: CMake@1 From 14e59b2c515669b2b7b4a1232c008c6022fdb0db Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 12 Oct 2020 21:05:53 +0200 Subject: [PATCH 228/235] [fictrac] non-blocking receive call in example python script --- scripts/socket_client.py | 109 ++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 46 deletions(-) diff --git a/scripts/socket_client.py b/scripts/socket_client.py index 8a62b51..e7e8799 100644 --- a/scripts/socket_client.py +++ b/scripts/socket_client.py @@ -1,58 +1,75 @@ #!/usr/bin/env python3 import socket +import select HOST = '127.0.0.1' # The (receiving) host IP address (sock_host) PORT = ???? # The (receiving) host port (sock_port) +# TCP # Open the connection (ctrl-c / ctrl-break to quit) -with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: # UDP -#with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: # TCP - sock.bind((HOST, PORT)) # UDP -# sock.connect((HOST, PORT)) # TCP - - data = "" +#with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: +# sock.connect((HOST, PORT)) + +# UDP +# Open the connection (ctrl-c / ctrl-break to quit) +with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + sock.bind((HOST, PORT)) + sock.setblocking(0) # Keep receiving data until FicTrac closes + data = "" + timeout_in_seconds = 1 while True: - # Receive one data frame - new_data = sock.recv(1024) - if not new_data: - break - - # Decode received data - data += new_data.decode('UTF-8') - - # Find the first frame of data - endline = data.find("\n") - line = data[:endline] # copy first frame - data = data[endline+1:] # delete first frame - - # Tokenise - toks = line.split(", ") - - # Check that we have sensible tokens - if ((len(toks) < 24) | (toks[0] != "FT")): - print('Bad read') - continue - - # Extract FicTrac variables - # (see https://github.com/rjdmoore/fictrac/blob/master/doc/data_header.txt for descriptions) - cnt = int(toks[1]) - dr_cam = [float(toks[2]), float(toks[3]), float(toks[4])] - err = float(toks[5]) - dr_lab = [float(toks[6]), float(toks[7]), float(toks[8])] - r_cam = [float(toks[9]), float(toks[10]), float(toks[11])] - r_lab = [float(toks[12]), float(toks[13]), float(toks[14])] - posx = float(toks[15]) - posy = float(toks[16]) - heading = float(toks[17]) - step_dir = float(toks[18]) - step_mag = float(toks[19]) - intx = float(toks[20]) - inty = float(toks[21]) - ts = float(toks[22]) - seq = int(toks[23]) + # Check to see whether there is data waiting + ready = select.select([sock], [], [], timeout_in_seconds) + + # Only try to receive data if there is data waiting + if ready[0]: + # Receive one data frame + new_data = sock.recv(1024) + + # Uh oh? + if not new_data: + break + + # Decode received data + data += new_data.decode('UTF-8') + + # Find the first frame of data + endline = data.find("\n") + line = data[:endline] # copy first frame + data = data[endline+1:] # delete first frame + + # Tokenise + toks = line.split(", ") + + # Check that we have sensible tokens + if ((len(toks) < 24) | (toks[0] != "FT")): + print('Bad read') + continue + + # Extract FicTrac variables + # (see https://github.com/rjdmoore/fictrac/blob/master/doc/data_header.txt for descriptions) + cnt = int(toks[1]) + dr_cam = [float(toks[2]), float(toks[3]), float(toks[4])] + err = float(toks[5]) + dr_lab = [float(toks[6]), float(toks[7]), float(toks[8])] + r_cam = [float(toks[9]), float(toks[10]), float(toks[11])] + r_lab = [float(toks[12]), float(toks[13]), float(toks[14])] + posx = float(toks[15]) + posy = float(toks[16]) + heading = float(toks[17]) + step_dir = float(toks[18]) + step_mag = float(toks[19]) + intx = float(toks[20]) + inty = float(toks[21]) + ts = float(toks[22]) + seq = int(toks[23]) + + # Do something ... + print(cnt) - # Do something ... - print(cnt) + else: + # Didn't find any data - try again + print('retrying...') From 5aa9836f6010c8b317840cdbf499316ebffd9a55 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 4 Jan 2021 14:20:58 +0100 Subject: [PATCH 229/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e63ad30..8506683 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ The FicTrac source code can be built for both Windows and Linux (e.g. Ubuntu) op 2. Linux only: 1. Run the following from terminal to install necessary build tools and dependencies: ``` - [Linux] sudo apt-get install gcc g++ git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev libopencv-dev + sudo apt-get install gcc g++ git cmake curl unzip tar yasm pkg-config libgtk2.0-dev libavformat-dev libavcodec-dev libavresample-dev libswscale-dev libopencv-dev ``` 3. (Windows and Linux) Clone or download the [Vcpkg](https://github.com/Microsoft/vcpkg) repository and then follow the guide to install (make sure to perform the bootstrap and integration steps). 4. Using Vcpkg, install remaining dependencies: From 35da3250ac5800c2eb159195bc195a70bf803525 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 4 Jan 2021 14:25:04 +0100 Subject: [PATCH 230/235] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8506683..18be659 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Before running FicTrac, you may configure your camera (frame rate, resolution, e Basler Pylon SDK 1. Download and install the latest [Pylon SDK](https://www.baslerweb.com/en/products/software/basler-pylon-camera-software-suite/). -2. When preparing the build files for FicTrac using Cmake, you will need to specify to use Pylon using the switch `-D PGR_USB3=ON` and depending on where you installed the SDK, you may also need to provide the SDK directory path using the switch `-D BASLER_DIR=...`. For example, for a Windows installation you would replace step 3 above with (replacing with the path to your vcpkg root directory): +2. When preparing the build files for FicTrac using Cmake, you will need to specify to use Pylon using the switch `-D BASLER_USB3=ON` and depending on where you installed the SDK, you may also need to provide the SDK directory path using the switch `-D BASLER_DIR=...`. For example, for a Windows installation you would replace step 3 above with (replacing with the path to your vcpkg root directory): ``` cmake -A x64 -D CMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -D BASLER_USB3=ON -D BASLER_DIR="C:\path\to\Pylon" .. ``` From b87085468b9fbf36ed0f01e0c6f291e9f1858c63 Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Mon, 4 Jan 2021 14:33:15 +0100 Subject: [PATCH 231/235] Update params.md --- doc/params.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/params.md b/doc/params.md index 6173d25..9876b6c 100644 --- a/doc/params.md +++ b/doc/params.md @@ -24,7 +24,7 @@ In the table below, the various possible parameters are listed. If nothing is li | thr_ratio | float | 1.25 | (0,inf) | Only if you need to | Adjusts the adaptive thresholding of the input image. Values > 1 will favour foreground regions (more white in thresholded image) and values < 1 will favour background regions (more black in thresholded image). | | thr_win_pc | float | 0.2 | \[0,1] | Only if you need to | Adjusts the size of the neighbourhood window to use for adaptive thresholding of the input image, specified as a percentage of the width of the tracking window. Larger values avoid over-segmentation, whilst smaller values make segmentation more robust to illumination gradients on the trackball. | | vid_codec | string | h264 | [h264,xvid,mpg4,mjpg,raw] | Only if you need to | Specifies the video codec to use when writing output videos (see `save_raw` and `save_debug`). | -| sphere_map_fn | string | | | Only if you need to | If specified, FicTrac will attempt to load a previously generated sphere surface map from this filename. | +| sphere_map_fn | string | | | Only if you need to | If specified, FicTrac will attempt to load a previously generated sphere surface map from this filename. Note that if you set this option, you should probably also set `opt_do_global` otherwise FicTrac may not find the initial sphere attitude. | | | | | | | | | opt_max_evals | int | 50 | (0,inf) | Probably not | Specifies the maximum number of minimisation iterations to perform each frame. Smaller values may improve tracking frame rate at the risk of finding sub-optimal matches. Number of optimisation iterations is printed to screen during tracking (its=...). | | opt_bound | float | 0.35 | (0,inf) | Probably not | Specifies the optimisation search range in radians. Larger values will facilitate more track ball rotation per frame, but result in slower tracking and also possibly lead to false matches. | From 9ac055e52d89f49f492a8eb4e1f7c5b8cd6df40a Mon Sep 17 00:00:00 2001 From: rjdmoore <3844483+rjdmoore@users.noreply.github.com> Date: Tue, 9 Mar 2021 10:46:23 +0100 Subject: [PATCH 232/235] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 18be659..fc13821 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -
-

-
+![FicTrac: The webcam-based method for tracking spherical motion and generating fictive animal paths](https://user-images.githubusercontent.com/3844483/110451048-176e9300-80c4-11eb-8e1e-e96545d7d2ed.jpg) **FicTrac** is an open-source software library for reconstructing the fictive path of an animal walking on a patterned sphere. The software is fast, flexible, easy to use, and simplifies the setup of closed-loop tracking experiments. From 49b90b1698e76192fb057f60678e4b2e9848d1d5 Mon Sep 17 00:00:00 2001 From: David Turner Date: Tue, 25 May 2021 16:26:47 -0400 Subject: [PATCH 233/235] shared memory ouput added, not tested --- include/Trackball.h | 2 +- src/SharedMemTransferData.h | 77 +++++++++++++++++++++++++++++++++++++ src/Trackball.cpp | 29 ++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/SharedMemTransferData.h diff --git a/include/Trackball.h b/include/Trackball.h index bce0d5e..dcf1ed9 100644 --- a/include/Trackball.h +++ b/include/Trackball.h @@ -175,7 +175,7 @@ class Trackball /// Data i/o. std::string _base_fn; std::unique_ptr _frameGrabber; - bool _do_sock_output, _do_com_output; + bool _do_sock_output, _do_com_output, _do_shmem_output; std::unique_ptr _data_log, _data_sock, _data_com, _vid_frames; /// Thread stuff. diff --git a/src/SharedMemTransferData.h b/src/SharedMemTransferData.h new file mode 100644 index 0000000..73e6b12 --- /dev/null +++ b/src/SharedMemTransferData.h @@ -0,0 +1,77 @@ +#ifndef TRANSFER_DATA_H +#define TRANSFER_DATA_H + +#include +#include + +#include +#include +#include + +using namespace boost::interprocess; + +struct SHMEMTransferData +{ + int frame_cnt; + double del_rot_cam_vec[3]; + double del_rot_error; + double del_rot_lab_vec[3]; + double abs_ori_cam_vec[3]; + double abs_ori_lab_vec[3]; + double posx; + double posy; + double heading; + double direction; + double speed; + double intx; + double inty; + double timestamp; + int seq_num; +}; + +struct SHMEMSignalData +{ + int close; +}; + +template +T * setupSHMEMRegion(string name) { + + windows_shared_memory shmem(open_or_create, name, read_write, sizeof(SHMEMTransferData)); + //shared_memory_object shmem(open_or_create, "FicTracStateSHMEM", read_write); + //shmem.truncate(sizeof(SHMEMTransferData)); + mapped_region region(shmem, read_write); + std::memset(region.get_address(), 0, sizeof(T)); + T* shared_memory_data = reinterpret_cast(region.get_address()); + + return shared_memory_data +}; + + +#define COPY_VEC3(x) data->x[0] = x[0]; data->x[1] = x[1]; data->x[2] = x[2]; +void fillSHMEMData(SHMEMTransferData* data, int frame_cnt, + CmPoint64f & del_rot_cam_vec, double del_rot_error, + CmPoint64f & del_rot_lab_vec, CmPoint64f & abs_ori_cam_vec, + CmPoint64f & abs_ori_lab_vec, + double posx, double posy, double heading, double direction, + double speed, double intx, double inty, double timestamp, + int seq_num) +{ + COPY_VEC3(del_rot_cam_vec); + COPY_VEC3(del_rot_lab_vec); + COPY_VEC3(abs_ori_cam_vec); + COPY_VEC3(abs_ori_lab_vec); + data->frame_cnt = frame_cnt; + data->del_rot_error = del_rot_error; + data->posx = posx; + data->posy = posy; + data->heading = heading; + data->direction = direction; + data->speed = speed; + data->intx = intx; + data->inty = inty; + data->timestamp = timestamp; + data->seq_num = seq_num; +} + +#endif // TRANSFER_DATA_H diff --git a/src/Trackball.cpp b/src/Trackball.cpp index d3f8fb5..2b987b6 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -28,6 +28,9 @@ #include #include +// Stuff for shared memory output and control +#include "SharedMemTransferData.h" + #include #include #include @@ -435,6 +438,19 @@ Trackball::Trackball(string cfg_fn) _do_com_output = true; } + // Shared memory output\control - yes or no + _do_shmem_output = false; + if (!_cfg.getBool("do_shmem", _do_shmem_output)) + _cfg.add("do_shmem", _do_shmem_output ? "y" : "n"); + + if (_do_shmem_output) { + auto shared_memory_data = setupSHMEMRegion("FicTracStateSHMEM"); + + // Also setup a shared memory variable that allows signalling to close the program. + auto shared_memory_signal = setupSHMEMRegion("FicTracStateSHMEM_SIGNALS"); + } + + /// Display. _do_display = DO_DISPLAY_DEFAULT; if (!_cfg.getBool("do_display", _do_display)) { @@ -1024,6 +1040,19 @@ bool Trackball::logData() if (_do_com_output) { ret &= _data_com->addMsg("FT, " + ss.str()); } + if (_do_shmem_output) { + + // Copy the tracking data to shared memory for other processes that want it + if (shared_memory_data != NULL) + fillSHMEMData(shared_memory_data, _data.cnt, _data.dr_cam, _err, _data.dr_lab, _data.r_cam, _data.r_lab, _data.posx, _data.posy, _data.heading, _data.step_dir, _data.step_mag, _data.intx, _data.inty, _data.ts, _data.seq); + + // While were at it, check if the program has received close signal from having a special shared memory value set + if (shared_memory_signal->close == 1) { + printf("Received close signal from special shared memory variable. Shutting down.\n"); + ACTIVE = false; + } + } + ret &= _data_log->addMsg(ss.str()); return ret; } From 7062321700caad0a98d0e5a3cafbf4a28603abd0 Mon Sep 17 00:00:00 2001 From: David Turner Date: Fri, 28 May 2021 19:44:04 -0400 Subject: [PATCH 234/235] Support for shared memory access of fictrac state. - Very windows specific for right now. 1) Using Boost's windows_shared_memory class. 2) Using named system wide windows OS semaphores for synchronization. - The addition of the semaphore has the added benefit to allow a consuming process to hold FicTrac up until it has processed a frame. --- CMakeLists.txt | 2 + include/SharedMemTransferData.h | 175 ++++++++++++++++++++++++++++++++ include/Trackball.h | 7 ++ src/SharedMemTransferData.cpp | 37 +++++++ src/SharedMemTransferData.h | 77 -------------- src/Trackball.cpp | 129 +++++++++++------------ 6 files changed, 286 insertions(+), 141 deletions(-) create mode 100644 include/SharedMemTransferData.h create mode 100644 src/SharedMemTransferData.cpp delete mode 100644 src/SharedMemTransferData.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 740115d..3828039 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required (VERSION 3.0) project (FicTrac) +set (CMAKE_CXX_STANDARD 11) + # The version number. set(FICTRAC_VERSION_MAJOR 2) set(FICTRAC_VERSION_MIDDLE 1) diff --git a/include/SharedMemTransferData.h b/include/SharedMemTransferData.h new file mode 100644 index 0000000..b9962f8 --- /dev/null +++ b/include/SharedMemTransferData.h @@ -0,0 +1,175 @@ +#pragma once + +#include "CmPoint.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +using namespace boost::interprocess; + + +struct SHMEMTransferData +{ + int frame_cnt; + double del_rot_cam_vec[3]; + double del_rot_error; + double del_rot_lab_vec[3]; + double abs_ori_cam_vec[3]; + double abs_ori_lab_vec[3]; + double posx; + double posy; + double heading; + double direction; + double speed; + double intx; + double inty; + double timestamp; + int seq_num; +}; + +struct SHMEMSignalData +{ + int close; +}; + + +template +struct SHMEMRegion { + // Simple class to manage a shared memory region + + std::shared_ptr shmem; + std::shared_ptr region; + T* data; + HANDLE semaphore; + + SHMEMRegion(std::string name) + { + + shmem = std::make_shared(open_or_create, name.c_str(), read_write, sizeof(T)); + region = std::make_shared(*shmem, read_write); + + std::memset(region->get_address(), 0, sizeof(T)); + + data = reinterpret_cast(region->get_address()); + + // Create the name s semaphore to synchronize access to this structure + createSemaphore((name + "_SEMPH").c_str(), 1, 1); + + } + + ~SHMEMRegion() { + closeSemaphore(); + } + + + // Code for handling windows named semaphores. + void handleLastError() { + USES_CONVERSION; + LPTSTR lpMsgBuf; + DWORD lastError = GetLastError(); + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + lastError, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&lpMsgBuf, + 0, NULL); + std::wcout << T2W(lpMsgBuf) << L"\n"; + LocalFree(lpMsgBuf); + } + + HANDLE createSemaphore(LPCSTR name, int initCount, int maxCount) { + std::cout << "Creating semaphore " << name << " with counts " << initCount + << " of " << maxCount << "...\n"; + HANDLE h = CreateSemaphoreA(NULL, initCount, maxCount, name); + if (h == NULL) { + std::cout << "CreateSemaphoreA Error: "; + handleLastError(); + exit(1); + } + std::cout << "Created\n"; + + semaphore = h; + + return h; + } + + HANDLE openSemaphore(LPCSTR name) { + std::cout << "Opening semaphore " << name << "...\n"; + HANDLE h = OpenSemaphoreA(SEMAPHORE_ALL_ACCESS, false, name); + if (h == NULL) { + std::cout << "OpenSemaphoreA Error: "; + handleLastError(); + exit(1); + } + std::cout << "Opened\n"; + + semaphore = h; + + return h; + } + + void acquireSemaphore(DWORD timeout_ms) { + std::cout << "Acquiring...\n"; + DWORD res = WaitForSingleObject(semaphore, timeout_ms); + if (res == WAIT_OBJECT_0) { + std::cout << "Acquired\n"; + } + else if (res == WAIT_TIMEOUT) { + std::cout << "WaitForSingleObject Timeout\n"; + } + else if (res == WAIT_FAILED) { + std::cout << "WaitForSingleObject Error: "; + handleLastError(); + exit(1); + } + else { + std::cout << "Wait error:" << res << "\n"; + exit(1); + } + } + + void releaseSemaphore() { + std::cout << "Releasing...\n"; + BOOL res = ReleaseSemaphore(semaphore, 1, NULL); + if (!res) { + std::cout << "ReleaseSemaphore Error: "; + handleLastError(); + exit(1); + } + } + + int closeSemaphore() { + std::cout << "Closing...\n"; + BOOL closed = CloseHandle(semaphore); + if (!closed) { + std::cout << "CloseHandle Error: "; + handleLastError(); + exit(1); + } + std::cout << "Closed\n"; + return 0; + } + +}; + + +void fillSHMEMData(SHMEMRegion& shmem_region, int frame_cnt, + CmPoint64f& del_rot_cam_vec, double del_rot_error, + CmPoint64f& del_rot_lab_vec, CmPoint64f& abs_ori_cam_vec, + CmPoint64f& abs_ori_lab_vec, + double posx, double posy, double heading, double direction, + double speed, double intx, double inty, double timestamp, + int seq_num); + diff --git a/include/Trackball.h b/include/Trackball.h index dcf1ed9..0c86e5e 100644 --- a/include/Trackball.h +++ b/include/Trackball.h @@ -13,6 +13,9 @@ #include "FrameGrabber.h" #include "ConfigParser.h" +// Stuff for shared memory output and control +#include "SharedMemTransferData.h" + /// OpenCV individual includes required by gcc? #include #include @@ -181,4 +184,8 @@ class Trackball /// Thread stuff. std::atomic_bool _active, _kill, _do_reset; std::unique_ptr _thread; + + + std::shared_ptr> _shmem_data; + std::shared_ptr> _shmem_signal; }; diff --git a/src/SharedMemTransferData.cpp b/src/SharedMemTransferData.cpp new file mode 100644 index 0000000..1a368be --- /dev/null +++ b/src/SharedMemTransferData.cpp @@ -0,0 +1,37 @@ + +#include "SharedMemTransferData.h" + + + +#define COPY_VEC3(x) data->x[0] = x[0]; data->x[1] = x[1]; data->x[2] = x[2]; +void fillSHMEMData(SHMEMRegion & shmem_region, int frame_cnt, + CmPoint64f& del_rot_cam_vec, double del_rot_error, + CmPoint64f& del_rot_lab_vec, CmPoint64f& abs_ori_cam_vec, + CmPoint64f& abs_ori_lab_vec, + double posx, double posy, double heading, double direction, + double speed, double intx, double inty, double timestamp, + int seq_num) +{ + + shmem_region.acquireSemaphore(1000); + + SHMEMTransferData* data = shmem_region.data; + data->frame_cnt = frame_cnt; + data->del_rot_error = del_rot_error; + data->posx = posx; + data->posy = posy; + data->heading = heading; + data->direction = direction; + data->speed = speed; + data->intx = intx; + data->inty = inty; + data->timestamp = timestamp; + data->seq_num = seq_num; + COPY_VEC3(del_rot_cam_vec); + COPY_VEC3(del_rot_lab_vec); + COPY_VEC3(abs_ori_cam_vec); + COPY_VEC3(abs_ori_lab_vec); + + shmem_region.releaseSemaphore(); + +} diff --git a/src/SharedMemTransferData.h b/src/SharedMemTransferData.h deleted file mode 100644 index 73e6b12..0000000 --- a/src/SharedMemTransferData.h +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef TRANSFER_DATA_H -#define TRANSFER_DATA_H - -#include -#include - -#include -#include -#include - -using namespace boost::interprocess; - -struct SHMEMTransferData -{ - int frame_cnt; - double del_rot_cam_vec[3]; - double del_rot_error; - double del_rot_lab_vec[3]; - double abs_ori_cam_vec[3]; - double abs_ori_lab_vec[3]; - double posx; - double posy; - double heading; - double direction; - double speed; - double intx; - double inty; - double timestamp; - int seq_num; -}; - -struct SHMEMSignalData -{ - int close; -}; - -template -T * setupSHMEMRegion(string name) { - - windows_shared_memory shmem(open_or_create, name, read_write, sizeof(SHMEMTransferData)); - //shared_memory_object shmem(open_or_create, "FicTracStateSHMEM", read_write); - //shmem.truncate(sizeof(SHMEMTransferData)); - mapped_region region(shmem, read_write); - std::memset(region.get_address(), 0, sizeof(T)); - T* shared_memory_data = reinterpret_cast(region.get_address()); - - return shared_memory_data -}; - - -#define COPY_VEC3(x) data->x[0] = x[0]; data->x[1] = x[1]; data->x[2] = x[2]; -void fillSHMEMData(SHMEMTransferData* data, int frame_cnt, - CmPoint64f & del_rot_cam_vec, double del_rot_error, - CmPoint64f & del_rot_lab_vec, CmPoint64f & abs_ori_cam_vec, - CmPoint64f & abs_ori_lab_vec, - double posx, double posy, double heading, double direction, - double speed, double intx, double inty, double timestamp, - int seq_num) -{ - COPY_VEC3(del_rot_cam_vec); - COPY_VEC3(del_rot_lab_vec); - COPY_VEC3(abs_ori_cam_vec); - COPY_VEC3(abs_ori_lab_vec); - data->frame_cnt = frame_cnt; - data->del_rot_error = del_rot_error; - data->posx = posx; - data->posy = posy; - data->heading = heading; - data->direction = direction; - data->speed = speed; - data->intx = intx; - data->inty = inty; - data->timestamp = timestamp; - data->seq_num = seq_num; -} - -#endif // TRANSFER_DATA_H diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 2b987b6..4cb2ebf 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -28,15 +28,12 @@ #include #include -// Stuff for shared memory output and control -#include "SharedMemTransferData.h" - +#include #include #include #include using namespace cv; -using namespace std; const int DRAW_SPHERE_HIST_LENGTH = 1024; const int DRAW_CELL_DIM = 160; @@ -54,7 +51,7 @@ const double THRESH_WIN_PC_DEFAULT = 0.25; const uint8_t SPHERE_MAP_FIRST_HIT_BONUS = 64; -const string SOCK_HOST_DEFAULT = "127.0.0.1"; +const std::string SOCK_HOST_DEFAULT = "127.0.0.1"; const int SOCK_PORT_DEFAULT = -1; const int COM_BAUD_DEFAULT = 115200; @@ -64,7 +61,7 @@ const bool SAVE_RAW_DEFAULT = false; const bool SAVE_DEBUG_DEFAULT = false; /// OpenCV codecs for video writing -const vector> CODECS = { +const std::vector> CODECS = { {"h264", "H264", "avi"}, {"xvid", "XVID", "avi"}, {"mpg4", "MP4V", "mp4"}, @@ -94,11 +91,11 @@ bool intersectSphere(const double r, const double camVec[3], double sphereVec[3] /// /// /// -Trackball::Trackball(string cfg_fn) +Trackball::Trackball(std::string cfg_fn) : _init(false), _reset(true), _clean_map(true), _active(true), _kill(false), _do_reset(false) { /// Save execTime for outptut file naming. - string exec_time = execTime(); + std::string exec_time = execTime(); /// Load and parse config file. if (_cfg.read(cfg_fn) <= 0) { @@ -108,8 +105,8 @@ Trackball::Trackball(string cfg_fn) } /// Open frame source and set fps. - string src_fn = _cfg("src_fn"); - shared_ptr source; + std::string src_fn = _cfg("src_fn"); + std::shared_ptr source; // try specific camera sdk first if available #if defined(PGR_USB2) || defined(PGR_USB3) || defined(BASLER_USB3) try { @@ -117,17 +114,17 @@ Trackball::Trackball(string cfg_fn) // first try reading input as camera id int id = std::stoi(src_fn); #if defined(PGR_USB2) || defined(PGR_USB3) - source = make_shared(id); + source = std::make_shared(id); #elif defined(BASLER_USB3) - source = make_shared(id); + source = std::make_shared(id); #endif // PGR/BASLER } catch (...) { // fall back to OpenCV - source = make_shared(src_fn); + source = std::make_shared(src_fn); } #else // !PGR/BASLER - source = make_shared(src_fn); + source = std::make_shared(src_fn); #endif // PGR/BASLER if (!source->isOpen()) { LOG_ERR("Error! Could not open input frame source (%s)!", src_fn.c_str()); @@ -187,14 +184,14 @@ Trackball::Trackball(string cfg_fn) { // read pts from config file _sphere_rad = -1; - vector circ_pxs; - vector sphere_c; + std::vector circ_pxs; + std::vector sphere_c; if (!reconfig && _cfg.getVecDbl("roi_c", sphere_c) && _cfg.getDbl("roi_r", _sphere_rad)) { _sphere_c.copy(sphere_c.data()); LOG_DBG("Found sphere ROI centred at [%f %f %f], with radius %f rad.", _sphere_c[0], _sphere_c[1], _sphere_c[2], _sphere_rad); } else if (_cfg.getVecInt("roi_circ", circ_pxs)) { - vector circ_pts; + std::vector circ_pts; for (unsigned int i = 1; i < circ_pxs.size(); i += 2) { circ_pts.push_back(Point2d(circ_pxs[i - 1], circ_pxs[i])); } @@ -215,12 +212,12 @@ Trackball::Trackball(string cfg_fn) cv::fillConvexPoly(src_mask, *int_circ, CV_RGB(255, 255, 255)); /// Mask out ignore regions. - vector> ignr_polys; + std::vector> ignr_polys; if (_cfg.getVVecInt("roi_ignr", ignr_polys) && (ignr_polys.size() > 0)) { /// Load ignore polys from config file. - vector> ignr_polys_pts; + std::vector> ignr_polys_pts; for (auto poly : ignr_polys) { - ignr_polys_pts.push_back(vector()); + ignr_polys_pts.push_back(std::vector()); for (unsigned int i = 1; i < poly.size(); i += 2) { ignr_polys_pts.back().push_back(Point2i(poly[i - 1], poly[i])); } @@ -254,9 +251,9 @@ Trackball::Trackball(string cfg_fn) LOG_DBG("roi_to_cam_r: %.4f %.4f %.4f", roi_to_cam_r[0], roi_to_cam_r[1], roi_to_cam_r[2]); // Cam to lab transformation from configuration. - vector c2a_r; - string c2a_src; - vector c2a_pts; + std::vector c2a_r; + std::string c2a_src; + std::vector c2a_pts; if (!reconfig && _cfg.getVecDbl("c2a_r", c2a_r) && (c2a_r.size() == 3)) { CmPoint64f cam_to_lab_r = CmPoint64f(c2a_r[0], c2a_r[1], c2a_r[2]); _cam_to_lab_R = CmPoint64f::omegaToMatrix(cam_to_lab_r); @@ -264,7 +261,7 @@ Trackball::Trackball(string cfg_fn) } else if (_cfg.getStr("c2a_src", c2a_src) && _cfg.getVecInt(c2a_src, c2a_pts)) { // c2a source and pixel coords present - recompute transform - vector cnrs; + std::vector cnrs; for (unsigned int i = 1; i < c2a_pts.size(); i += 2) { cnrs.push_back(cv::Point2d(c2a_pts[i - 1], c2a_pts[i])); } @@ -307,7 +304,7 @@ Trackball::Trackball(string cfg_fn) /// Surface map template. _sphere_template = _sphere_map.clone(); { - string sphere_template_fn; + std::string sphere_template_fn; if (_cfg.getStr("sphere_map_fn", sphere_template_fn)) { _sphere_template = cv::imread(sphere_template_fn, 0); if ((_sphere_template.cols != _map_w) || (_sphere_template.rows != _map_h)) { @@ -325,7 +322,7 @@ Trackball::Trackball(string cfg_fn) } /// Pre-calc view rays. - _p1s_lut = make_shared>(_roi_w * _roi_h * 3, 0); + _p1s_lut = std::make_shared>(_roi_w * _roi_h * 3, 0); for (int i = 0; i < _roi_h; i++) { uint8_t* pmask = _roi_mask.ptr(i); for (int j = 0; j < _roi_w; j++) { @@ -383,19 +380,19 @@ Trackball::Trackball(string cfg_fn) } /// Init optimisers. - _localOpt = make_unique( + _localOpt = std::make_unique( NLOPT_LN_BOBYQA, bound, tol, max_evals, _sphere_model, _sphere_map, _roi_mask, _p1s_lut); - _globalOpt = make_unique( + _globalOpt = std::make_unique( NLOPT_GN_CRS2_LM, CM_PI, tol, 1e5, _sphere_model, _sphere_map, _roi_mask, _p1s_lut); /// Output. - string data_fn = _base_fn + "-" + exec_time + ".dat"; - _data_log = make_unique(RecorderInterface::RecordType::FILE, data_fn); + std::string data_fn = _base_fn + "-" + exec_time + ".dat"; + _data_log = std::make_unique(RecorderInterface::RecordType::FILE, data_fn); if (!_data_log->is_active()) { LOG_ERR("Error! Unable to open output data log file (%s).", data_fn.c_str()); _active = false; @@ -405,13 +402,13 @@ Trackball::Trackball(string cfg_fn) int sock_port = SOCK_PORT_DEFAULT; _do_sock_output = false; if (_cfg.getInt("sock_port", sock_port) && (sock_port > 0)) { - string sock_host = SOCK_HOST_DEFAULT; + std::string sock_host = SOCK_HOST_DEFAULT; if (!_cfg.getStr("sock_host", sock_host)) { LOG_WRN("Warning! Using default value for sock_host (%s).", sock_host.c_str()); _cfg.add("sock_host", sock_host); } - _data_sock = make_unique(RecorderInterface::RecordType::SOCK, sock_host + ":" + std::to_string(sock_port)); + _data_sock = std::make_unique(RecorderInterface::RecordType::SOCK, sock_host + ":" + std::to_string(sock_port)); if (!_data_sock->is_active()) { LOG_ERR("Error! Unable to open output data socket (%s:%d).", sock_host.c_str() ,sock_port); _active = false; @@ -420,7 +417,7 @@ Trackball::Trackball(string cfg_fn) _do_sock_output = true; } - string com_port = _cfg("com_port"); + std::string com_port = _cfg("com_port"); _do_com_output = false; if (com_port.length() > 0) { int com_baud = COM_BAUD_DEFAULT; @@ -429,7 +426,7 @@ Trackball::Trackball(string cfg_fn) _cfg.add("com_baud", com_baud); } - _data_com = make_unique(RecorderInterface::RecordType::COM, com_port + "@" + std::to_string(com_baud)); + _data_com = std::make_unique(RecorderInterface::RecordType::COM, com_port + "@" + std::to_string(com_baud)); if (!_data_com->is_active()) { LOG_ERR("Error! Unable to open output data com port (%s@%d).", com_port.c_str(), com_baud); _active = false; @@ -444,10 +441,13 @@ Trackball::Trackball(string cfg_fn) _cfg.add("do_shmem", _do_shmem_output ? "y" : "n"); if (_do_shmem_output) { - auto shared_memory_data = setupSHMEMRegion("FicTracStateSHMEM"); + // Setup a shared memory region for outputing fictrac state + LOG("Setting up a shared memory region (FicTracStateSHMEM) for state output"); + _shmem_data = std::make_shared>("FicTracStateSHMEM"); // Also setup a shared memory variable that allows signalling to close the program. - auto shared_memory_signal = setupSHMEMRegion("FicTracStateSHMEM_SIGNALS"); + LOG("Setting up a shared memory region (FicTracStateSHMEM_SIGNALS) for control"); + _shmem_signal = std::make_shared>("FicTracStateSHMEM_SIGNALS"); } @@ -480,7 +480,7 @@ Trackball::Trackball(string cfg_fn) if (_save_raw || _save_debug) { // find codec int fourcc = 0; - string cstr = _cfg("vid_codec"), fext; + std::string cstr = _cfg("vid_codec"), fext; for (auto codec : CODECS) { if (cstr.compare(codec[0]) == 0) { // found the codec if (cstr.compare("raw") != 0) { // codec isn't RAW @@ -503,7 +503,7 @@ Trackball::Trackball(string cfg_fn) // raw input video if (_save_raw) { - string vid_fn = _base_fn + "-raw-" + exec_time + "." + fext; + std::string vid_fn = _base_fn + "-raw-" + exec_time + "." + fext; double fps = source->getFPS(); if (fps <= 0) { fps = (src_fps > 0) ? src_fps : 25; // if we can't get fps from source, then use fps from config or - if not specified - default to 25 fps. @@ -519,7 +519,7 @@ Trackball::Trackball(string cfg_fn) // debug output video if (_save_debug) { - string vid_fn = _base_fn + "-dbg-" + exec_time + "." + fext; + std::string vid_fn = _base_fn + "-dbg-" + exec_time + "." + fext; double fps = source->getFPS(); if (fps <= 0) { fps = (src_fps > 0) ? src_fps : 25; // if we can't get fps from source, then use fps from config or - if not specified - default to 25 fps. @@ -534,8 +534,8 @@ Trackball::Trackball(string cfg_fn) } // create output file containing log lines corresponding to video frames, for synching video output - string fn = _base_fn + "-vidLogFrames-" + exec_time + ".txt"; - _vid_frames = make_unique(RecorderInterface::RecordType::FILE, fn); + std::string fn = _base_fn + "-vidLogFrames-" + exec_time + ".txt"; + _vid_frames = std::make_unique(RecorderInterface::RecordType::FILE, fn); if (!_vid_frames->is_active()) { LOG_ERR("Error! Unable to open output video frame number log file (%s).", fn.c_str()); _active = false; @@ -544,7 +544,7 @@ Trackball::Trackball(string cfg_fn) } /// Frame source. - _frameGrabber = make_unique( + _frameGrabber = std::make_unique( source, remapper, _roi_mask, @@ -569,10 +569,10 @@ Trackball::Trackball(string cfg_fn) _active = true; if (_do_display) { - _drawThread = make_unique(&Trackball::processDrawQ, this); + _drawThread = std::make_unique(&Trackball::processDrawQ, this); } // main processing thread - _thread = make_unique(&Trackball::process, this); + _thread = std::make_unique(&Trackball::process, this); } /// @@ -693,7 +693,7 @@ void Trackball::process() } if (_do_display) { - auto data = make_shared(); + auto data = std::make_shared(); data->log_frame = _data.cnt; data->src_frame = _src_frame.clone(); data->roi_frame = _roi_frame.clone(); @@ -737,6 +737,13 @@ void Trackball::process() t0 = ts_ms(); if (tfirst < 0) { tfirst = t0; } + + // Check if the program has received close signal from having a special shared memory value set + if (_shmem_signal->data->close == 1) { + LOG("Received close signal from special shared memory variable. Shutting down.\n"); + _active = false; + } + } tlast = t0; @@ -1043,14 +1050,8 @@ bool Trackball::logData() if (_do_shmem_output) { // Copy the tracking data to shared memory for other processes that want it - if (shared_memory_data != NULL) - fillSHMEMData(shared_memory_data, _data.cnt, _data.dr_cam, _err, _data.dr_lab, _data.r_cam, _data.r_lab, _data.posx, _data.posy, _data.heading, _data.step_dir, _data.step_mag, _data.intx, _data.inty, _data.ts, _data.seq); + fillSHMEMData(*_shmem_data.get(), _data.cnt, _data.dr_cam, _err, _data.dr_lab, _data.r_cam, _data.r_lab, _data.posx, _data.posy, _data.heading, _data.step_dir, _data.step_mag, _data.intx, _data.inty, _data.ts, _data.seq); - // While were at it, check if the program has received close signal from having a special shared memory value set - if (shared_memory_signal->close == 1) { - printf("Received close signal from special shared memory variable. Shutting down.\n"); - ACTIVE = false; - } } ret &= _data_log->addMsg(ss.str()); @@ -1209,10 +1210,10 @@ void makeSphereRotMaps( /// /// /// -bool Trackball::updateCanvasAsync(shared_ptr data) +bool Trackball::updateCanvasAsync(std::shared_ptr data) { bool ret = false; - lock_guard l(_drawMutex); + std::lock_guard l(_drawMutex); if (_active) { _drawQ.push_back(data); _drawCond.notify_all(); @@ -1232,7 +1233,7 @@ void Trackball::processDrawQ() } /// Get a un/lockable lock. - unique_lock l(_drawMutex); + std::unique_lock l(_drawMutex); /// Process drawing queue. while (_active) { @@ -1267,7 +1268,7 @@ void Trackball::processDrawQ() /// /// /// -void Trackball::drawCanvas(shared_ptr data) +void Trackball::drawCanvas(std::shared_ptr data) { static Mat canvas(3 * DRAW_CELL_DIM, 4 * DRAW_CELL_DIM, CV_8UC3); canvas.setTo(Scalar::all(0)); @@ -1279,8 +1280,8 @@ void Trackball::drawCanvas(shared_ptr data) Mat& R_roi = data->R_roi; Mat& sphere_view = data->sphere_view; Mat& sphere_map = data->sphere_map; - deque& R_roi_hist = data->R_roi_hist; - deque& pos_heading_hist = data->pos_heading_hist; + std::deque& R_roi_hist = data->R_roi_hist; + std::deque& pos_heading_hist = data->pos_heading_hist; unsigned int log_frame = data->log_frame; /// Draw source image. @@ -1431,10 +1432,10 @@ void Trackball::drawCanvas(shared_ptr data) CV_RGB(255, 255, 255), 2, cv::LINE_AA, 4); /// Draw text (with shadow). - shadowText(canvas, string("Processed ") + dateString(), + shadowText(canvas, std::string("Processed ") + dateString(), 2, 15, 255, 255, 0); - shadowText(canvas, string("FicTrac (") + string(__DATE__) + string(")"), + shadowText(canvas, std::string("FicTrac (") + std::string(__DATE__) + std::string(")"), canvas.cols - 184, 15, 255, 255, 0); shadowText(canvas, "input image", @@ -1475,16 +1476,16 @@ void Trackball::drawCanvas(shared_ptr data) _debug_vid.write(canvas); } if (_save_raw || _save_debug) { - _vid_frames->addMsg(to_string(log_frame) + "\n"); + _vid_frames->addMsg(std::to_string(log_frame) + "\n"); } } /// /// /// -shared_ptr Trackball::getState() +std::shared_ptr Trackball::getState() { - return make_shared(_data); + return std::make_shared(_data); } /// @@ -1511,7 +1512,7 @@ bool Trackball::writeTemplate(std::string fn) { if (!_init) { return false; } - string template_fn = _base_fn + "-template.png"; + std::string template_fn = _base_fn + "-template.png"; bool ret = cv::imwrite(template_fn, _sphere_map); if (!ret) { From 2b009c1f44c674d9cb3b71438b79e4edf2dc4aa9 Mon Sep 17 00:00:00 2001 From: David Turner Date: Mon, 28 Jun 2021 12:22:48 -0400 Subject: [PATCH 235/235] fix: crash caused when shared memory mode disabled. Was trying to access shared memory region when shared memory mode is disabled. This lead to an access violation. Proper guard is in place now. --- src/Trackball.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Trackball.cpp b/src/Trackball.cpp index 4cb2ebf..33a0fa9 100644 --- a/src/Trackball.cpp +++ b/src/Trackball.cpp @@ -739,7 +739,7 @@ void Trackball::process() if (tfirst < 0) { tfirst = t0; } // Check if the program has received close signal from having a special shared memory value set - if (_shmem_signal->data->close == 1) { + if (_do_shmem_output && _shmem_signal->data->close == 1) { LOG("Received close signal from special shared memory variable. Shutting down.\n"); _active = false; }