OSVR Framework (Internal Development Docs)  0.6-1962-g59773924
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
MeasureTrackingCameraLatency.cpp
Go to the documentation of this file.
1 
11 // Copyright 2015 Sensics, Inc.
12 //
13 // Licensed under the Apache License, Version 2.0 (the "License");
14 // you may not use this file except in compliance with the License.
15 // You may obtain a copy of the License at
16 //
17 // http://www.apache.org/licenses/LICENSE-2.0
18 //
19 // Unless required by applicable law or agreed to in writing, software
20 // distributed under the License is distributed on an "AS IS" BASIS,
21 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 // See the License for the specific language governing permissions and
23 // limitations under the License.
24 #define OSVR_HAVE_BOOST
25 
26 // Internal Includes
30 
31 // Library/third-party includes
32 #include <boost/lexical_cast.hpp>
33 #include <opencv2/highgui/highgui.hpp>
34 #include <vrpn_SerialPort.h>
35 
36 // Standard includes
37 #include <chrono>
38 #include <cstddef> // for std::size_t
39 #include <cstdint>
40 #include <fstream>
41 #include <functional>
42 #include <iostream>
43 #include <memory>
44 #include <random>
45 #include <sstream>
46 #include <thread>
47 
48 using SerialBufType = const unsigned char;
49 
53 template <typename T, std::size_t N>
54 inline void writeStringWithoutNullTerminator(vrpn_SerialPort &port, T (&buf)[N],
55  std::size_t repetitions = 1) {
56  static_assert(sizeof(T) == 1, "Can only pass character/byte arrays");
57  std::size_t len = N;
58  if ('\0' == buf[len - 1]) {
59  len--;
60  }
61  for (std::size_t i = 0; i < repetitions; ++i) {
62  port.write(reinterpret_cast<SerialBufType *>(&(buf[0])), len);
63  }
64 }
65 
69 template <typename T, std::size_t N>
70 inline void writeCommand(vrpn_SerialPort &port, T (&buf)[N],
71  std::size_t repetitions = 1) {
72  for (std::size_t i = 0; i < repetitions; ++i) {
75  }
76 }
77 
80 template <std::size_t N>
81 inline void writeBinaryArray(vrpn_SerialPort &port, SerialBufType (&buf)[N],
82  std::size_t repetitions = 1) {
83  std::size_t len = N;
84  for (std::size_t i = 0; i < repetitions; ++i) {
85  port.write(reinterpret_cast<SerialBufType *>(&(buf[0])), N);
86  }
87 }
88 
90 class SerialIRLED {
91  public:
93  void trigger() { handle_trigger(getPort()); }
94  virtual ~SerialIRLED() {}
95 
96  protected:
97  SerialIRLED(vrpn_SerialPort &port) : port_(std::ref(port)) {}
98  virtual void handle_trigger(vrpn_SerialPort &port) = 0;
99 
100  vrpn_SerialPort &getPort() { return port_.get(); }
101 
102  private:
103  std::reference_wrapper<vrpn_SerialPort> port_;
104 };
105 using SerialIRLEDPtr = std::unique_ptr<SerialIRLED>;
106 using SerialLEDControllerFactory =
107  std::function<SerialIRLEDPtr(vrpn_SerialPort &)>;
108 
111 class ArduinoLED : public SerialIRLED {
112  public:
113  static SerialIRLEDPtr make(vrpn_SerialPort &port) {
114  SerialIRLEDPtr ret(new ArduinoLED(port));
115  return ret;
116  }
117 
118  private:
119  ArduinoLED(vrpn_SerialPort &port) : SerialIRLED(port) {}
120  void handle_trigger(vrpn_SerialPort &port) override {
121  // write any byte to trigger a pulse
123  port.drain_output_buffer();
124  }
125 };
126 
130 class BusPirateAuxLED : public SerialIRLED {
131  public:
132  static SerialIRLEDPtr make(vrpn_SerialPort &port) {
133  SerialIRLEDPtr ret;
134  try {
135  ret.reset(new BusPirateAuxLED(port));
136  } catch (std::exception &e) {
137  std::cerr << "Error setting up bus pirate: " << e.what()
138  << std::endl;
139  }
140  return ret;
141  }
142 
143  virtual ~BusPirateAuxLED() {
144  const std::uint8_t resetToTerminal[] = {RESET_TO_TERMINAL};
145  writeBinaryArray(getPort(), resetToTerminal);
146  getPort().drain_output_buffer();
147  }
148 
149  private:
150  BusPirateAuxLED(vrpn_SerialPort &port) : SerialIRLED(port) {
151  bool success = enterBitBangMode(port);
152  if (success) {
153  std::cout << "Bus Pirate successfully entered binary bit-bang mode!"
154  << std::endl;
155  } else {
156  std::cout << "Couldn't get bus pirate into binary bit-bang mode: "
157  "check that nothing else is using the port and try "
158  "resetting it."
159  << std::endl;
160  throw std::runtime_error("Could not transition bus pirate to "
161  "binary bit-bang mode: check that nothing "
162  "else is using the port and try resetting "
163  "it.");
164  }
165 
166  const std::uint8_t configPortModes[] = {PORT_MODE, ONLY_POWER_ON};
167  writeBinaryArray(port, configPortModes);
168  // wait until everything has been sent.
169  port.drain_output_buffer();
170  // throw away replies.
171  port.flush_input_buffer();
172  }
173  static const std::uint8_t ONE = 0x01;
174  static const std::uint8_t ZERO = 0x00;
175 
176  // 01001111: aka 0x4f configure everything except AUX as input.
177  static const std::uint8_t PORT_MODE = 0x4f;
178  // 11000000: aka 0xc0 turn on the power only
179  static const std::uint8_t ONLY_POWER_ON = 0xc0;
180  // 11010000: aka 0xd0 turn on the power and AUX
181  static const std::uint8_t POWER_AND_AUX_ON = 0xd0;
182  // 00001111: aka 0x0f reset bus pirate to terminal mode.
183  static const std::uint8_t RESET_TO_TERMINAL = 0x0f;
184  bool enterBitBangMode(vrpn_SerialPort &port) {
185 
186  SerialBufType enter[] = "\n";
187  // throw away anything we got to read.
188  port.flush_input_buffer();
189  // write at least 10 enters to get out of any menus.
190  writeStringWithoutNullTerminator(port, enter, 10);
191  // send reset command
192  writeCommand(port, "#");
193  // wait until everything has been sent.
194  port.drain_output_buffer();
195  std::cout << "Resetting bus pirate" << std::endl;
196  std::this_thread::sleep_for(std::chrono::seconds(1));
197  // throw away anything we got to read.
198  port.flush_input_buffer();
199 
200  bool success = false;
201  const std::uint8_t bitBangReset[] = {ZERO};
202  static const auto desiredReply = "BBIO1";
203  writeBinaryArray(port, bitBangReset, 10);
204  for (int i = 0; i < 40; ++i) {
205  writeBinaryArray(port, bitBangReset);
206  std::this_thread::sleep_for(std::chrono::milliseconds(5));
207  // try more times to get BBIO1
208  auto response = port.read_available_characters(5);
209  if (desiredReply == response) {
210  return true;
211  } else if (!response.empty()) {
212  std::cout << "When waiting for " << desiredReply
213  << " Bus Pirate sent us " << response << std::endl;
214  }
215  }
216  return false;
217  }
218  void handle_trigger(vrpn_SerialPort &port) override {
219 
220  const std::uint8_t turnOn[] = {POWER_AND_AUX_ON};
221 
222  writeBinaryArray(port, turnOn);
223  // wait until everything has been sent.
224  port.drain_output_buffer();
225  // throw away replies.
226  port.flush_input_buffer();
227 
228  std::this_thread::sleep_for(std::chrono::milliseconds(5));
229 
230  const std::uint8_t turnOff[] = {ONLY_POWER_ON};
231 
232  writeBinaryArray(port, turnOff);
233  // wait until everything has been sent.
234  port.drain_output_buffer();
235  // throw away replies.
236  port.flush_input_buffer();
237  }
238 };
239 
240 static const std::string windowNameAndInstructions(
241  "OSVR tracking camera preview | q or esc to quit");
242 static const auto BUS_PIRATE_ARGS = {"--bp", "--buspirate", "-b"};
243 static const auto ARDUINO_ARGS = {"--arduino", "-a"};
244 static const auto DEVICE_ARG = "--dev";
245 static const auto DEFAULT_DEVICE = "COM1";
246 static const auto RATE_ARG = "--rate";
247 static const auto DEFAULT_RATE = 115200;
248 static const auto MEASUREMENTS_ARG = "--measurements";
249 static const auto DEFAULT_MEASUREMENTS = 20;
250 
251 static const double CUTOFF_VALUE = 200.;
252 
253 struct Config {
254  SerialLEDControllerFactory controllerFactory;
255  std::string device = DEFAULT_DEVICE;
256  std::uint32_t rate = DEFAULT_RATE;
257  std::uint32_t measurements = DEFAULT_MEASUREMENTS;
258 } g_config;
259 
260 int main(int argc, char *argv[]) {
261  {
262  auto withUsageError = [] {
264  return 1;
265  };
266  using namespace osvr::util::args;
267  auto args = makeArgList(argc, argv);
268  if (handle_has_any_iswitch_of(args, BUS_PIRATE_ARGS)) {
269  std::cout << "Will use the Bus Pirate, IR LED on AUX, controller."
270  << std::endl;
271  g_config.controllerFactory = BusPirateAuxLED::make;
272  }
273  if (handle_has_any_iswitch_of(args, ARDUINO_ARGS)) {
274  if (g_config.controllerFactory) {
275  std::cerr << "Got an arduino arg, but already was specified to "
276  "use bus pirate!"
277  << std::endl;
278  return withUsageError();
279  }
280  g_config.controllerFactory = ArduinoLED::make;
281  }
282 
283  if (!g_config.controllerFactory) {
284  std::cerr << "No controller type specified! Assuming bus pirate."
285  << std::endl;
286  g_config.controllerFactory = BusPirateAuxLED::make;
287  }
288 
289  handle_value_arg(
290  args, [](std::string const &a) { return a == DEVICE_ARG; },
291  [&](std::string const &val) {
292  std::cout << "Setting serial device to " << val << std::endl;
293  g_config.device = val;
294  });
295 
296  handle_value_arg(
297  args, [](std::string const &a) { return a == RATE_ARG; },
298  [&](std::string const &val) {
299  g_config.rate = boost::lexical_cast<std::uint32_t>(val);
300  std::cout << "Setting serial rate to " << g_config.rate
301  << std::endl;
302  });
303 
304  handle_value_arg(
305  args, [](std::string const &a) { return a == MEASUREMENTS_ARG; },
306  [&](std::string const &val) {
307  g_config.measurements = boost::lexical_cast<std::uint32_t>(val);
308  std::cout << "Setting number of measurements to "
309  << g_config.measurements << std::endl;
310  });
311  }
312 #ifdef _WIN32
313  auto cam = osvr::vbtracker::openHDKCameraDirectShow();
314 #else
315  std::cerr << "Warning: Just using OpenCV to open Camera #0, which may not "
316  "be the tracker camera."
317  << std::endl;
318  auto cam = osvr::vbtracker::openOpenCVCamera(0);
319 #endif
320  if (!cam || !cam->ok()) {
321  std::cerr << "Couldn't find, open, or read from the OSVR HDK tracking "
322  "camera.\n"
323  << "Press enter to exit." << std::endl;
324  std::cin.ignore();
325  return -1;
326  }
327 
328  auto FRAME_DISPLAY_STRIDE = 3u;
329  cam->grab();
330 
331  auto frame = cv::Mat{};
332  auto grayFrame = cv::Mat{};
333 
334  vrpn_SerialPort comPort(g_config.device.c_str(), g_config.rate);
335  if (!comPort.is_open()) {
336  std::cerr << "Couldn't open com port!" << std::endl;
337  return -1;
338  }
339 
340  std::unique_ptr<SerialIRLED> LEDController(
341  g_config.controllerFactory(comPort));
342 
343  if (!LEDController) {
344  std::cerr << "Could not start LEDController, exiting." << std::endl;
345  return 1;
346  }
347 
348  enum class State { StartedTrigger, SawFlash, AwaitingNewTrigger };
349 
351  TimeValue triggerTime = {};
352  double peakVal = 0.;
353 
354  State s = State::AwaitingNewTrigger;
355 
356  std::random_device rd;
357  std::mt19937 mt(rd());
358  // distribution of how many frames we'll wait before triggering a new pulse.
359  std::uniform_int_distribution<int> dist(5, 50);
360  auto generateNewCountdown = [&mt, &dist] { return dist(mt); };
361  int countdownToTrigger = generateNewCountdown();
362 
363  std::uint32_t samples = 0;
364  std::ofstream os("latency.csv");
365  do {
366  TimeValue tv;
367  cam->retrieve(frame, grayFrame, tv);
368  switch (s) {
369  case State::AwaitingNewTrigger: {
370  if (countdownToTrigger == 0) {
371  // trigger timing
372  s = State::StartedTrigger;
373  peakVal = 0.;
374  LEDController->trigger();
375  triggerTime = osvr::util::time::getNow();
376  } else {
377  countdownToTrigger--;
378  }
379  break;
380  }
381  case State::StartedTrigger: {
382  auto now = osvr::util::time::getNow();
383  double minVal, maxVal;
384  cv::minMaxIdx(grayFrame, &minVal, &maxVal);
385  // if (maxVal < peakVal) {
386  if (maxVal > CUTOFF_VALUE) {
387  osvrTimeValueDifference(&now, &triggerTime);
388  osvrTimeValueDifference(&tv, &triggerTime);
389  if (tv.microseconds > 0) {
390  // we got it
391  s = State::SawFlash;
392  std::cout << " Current: " << maxVal << "\n";
393  std::cout << "Latency from trigger to sample time: "
394  << tv.microseconds << "us\n";
395  std::cout << "Latency from trigger to retrieval time: "
396  << now.microseconds << "us\n";
397  cv::imwrite("triggered.png", frame);
398  samples++;
399  os << tv.microseconds << std::endl;
400  } else {
401  std::cout << "Got a spurious flash, negative trigger to "
402  "sample duration"
403  << std::endl;
404  }
405  }
406  break;
407  }
408  case State::SawFlash: {
409  // wait for the flash to disappear
410  double minVal, maxVal;
411  cv::minMaxIdx(grayFrame, &minVal, &maxVal);
412  if (maxVal < CUTOFF_VALUE) {
413  // OK, ready to go for a new trigger at some point.
414  s = State::AwaitingNewTrigger;
415  countdownToTrigger = generateNewCountdown();
416  std::cout << "Will wait " << countdownToTrigger
417  << " frames before triggering again\n";
418  }
419  break;
420  }
421  }
422  } while (cam->grab() && samples < g_config.measurements);
423 
424  LEDController.reset();
425 
426  return 0;
427 }
void trigger()
Call to cause an IR LED pulse to be emitted.
Header containing some simple args-handling routines: nothing sophisticated, just enough to keep the ...
Abstract base class for ways of controlling an IR LED through a serial port.
void getNow(TimeValue &tv)
Set the given TimeValue to the current time.
Definition: TimeValue.h:51
void writeStringWithoutNullTerminator(vrpn_SerialPort &port, T(&buf)[N], std::size_t repetitions=1)
STL namespace.
void writeCommand(vrpn_SerialPort &port, T(&buf)[N], std::size_t repetitions=1)
::OSVR_TimeValue TimeValue
C++-friendly typedef for the OSVR_TimeValue structure.
Definition: TimeValue.h:48
void writeBinaryArray(vrpn_SerialPort &port, SerialBufType(&buf)[N], std::size_t repetitions=1)
void osvrTimeValueDifference(OSVR_TimeValue *tvA, const OSVR_TimeValue *tvB)
Computes the difference between two time values, replacing the first with the result.
Definition: TimeValueC.cpp:75
int main(int argc, char *argv[])