OSVR-Core
AssignMeasurementsToLeds.h
Go to the documentation of this file.
1 
11 // Copyright 2016 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 
25 #ifndef INCLUDED_AssignMeasurementsToLeds_h_GUID_F7146BCA_13BF_4C91_1EE6_27E9FF039AD7
26 #define INCLUDED_AssignMeasurementsToLeds_h_GUID_F7146BCA_13BF_4C91_1EE6_27E9FF039AD7
27 
28 // Internal Includes
29 #include "BeaconIdTypes.h"
30 #include "LED.h"
31 #include <LedMeasurement.h>
32 
33 // Library/third-party includes
34 #include <boost/assert.hpp>
35 #include <opencv2/core/core.hpp>
36 
37 // Standard includes
38 #include <algorithm>
39 #include <cstddef>
40 #include <iostream>
41 #include <iterator>
42 #include <stdexcept>
43 #include <tuple>
44 #include <utility>
45 #include <vector>
46 
47 namespace osvr {
48 namespace vbtracker {
49 
54  inline bool handleOutOfRangeIds(Led &led, const std::size_t numBeacons) {
55  if (led.identified() &&
56  makeZeroBased(led.getID()).value() >
57  static_cast<UnderlyingBeaconIdType>(numBeacons)) {
58  std::cerr << "Got a beacon claiming to be "
59  << led.getOneBasedID().value() << " when we only have "
60  << numBeacons << " beacons" << std::endl;
63  led.markMisidentified();
64  return true;
65  }
66  return false;
67  }
68 
70  inline float sqDist(cv::Point2f const &lhs, cv::Point2f const &rhs) {
71  auto diff = lhs - rhs;
72  return diff.dot(diff);
73  }
74 
76  static const char *getPrefix() { return "[AssignMeasurements] "; }
77 
78  public:
79  AssignMeasurementsToLeds(LedGroup &leds,
80  LedMeasurementVec const &measurements,
81  const std::size_t numBeacons,
82  float blobMoveThresh, bool verbose = false)
83  : leds_(leds), measurements_(measurements), ledsEnd_(end(leds_)),
84  numBeacons_(numBeacons), blobMoveThreshFactor_(blobMoveThresh),
85  maxMatches_(std::min(leds_.size(), measurements_.size())),
86  verbose_(verbose) {}
87 
88  using LedAndMeasurement = std::pair<Led &, LedMeasurement const &>;
89 
90  using LedMeasDistance = std::tuple<std::size_t, std::size_t, float>;
91  using HeapValueType = LedMeasDistance;
92  using HeapType = std::vector<HeapValueType>;
93  using size_type = HeapType::size_type;
94 
97  BOOST_ASSERT_MSG(!populated_,
98  "Can only call populateStructures() once.");
99  populated_ = true;
100  {
102  auto led = begin(leds_);
103  while (led != end(leds_)) {
104  led->resetUsed();
105  ledRefs_.push_back(led);
106  ++led;
107  }
108  }
109 
110  for (auto &meas : measurements_) {
112  measRefs_.push_back(&meas);
113  }
114 
117  auto nMeas = measRefs_.size();
118  auto nLed = ledRefs_.size();
119  for (size_type measIdx = 0; measIdx < nMeas; ++measIdx) {
120  auto distThreshSquared =
121  getDistanceThresholdSquared(*measRefs_[measIdx]);
122  for (size_type ledIdx = 0; ledIdx < nLed; ++ledIdx) {
125  possiblyPushLedMeasurement(ledIdx, measIdx,
126  distThreshSquared);
127  }
128  }
130 
139  makeHeap();
140  }
141 
145  size_type discardInvalidEntries(bool verbose = false) {
146  checkAndThrowNotPopulated("discardInvalidEntries()");
147  size_type discarded = 0;
148  if (empty()) {
149  return discarded;
150  }
151 
152  while (!empty()) {
153  if (verbose || verbose_) {
154  auto top = distanceHeap_.front();
155  std::cout << getPrefix() << "top: led index "
156  << ledIndex(top) << "\tmeas index "
157  << measIndex(top) << "\tsq dist "
158  << squaredDistance(top);
159  }
160  if (isTopValid()) {
161  if (verbose || verbose_) {
162  std::cout << " isTopValid() says keep!\n";
163  }
165  return discarded;
166  }
167  if (verbose || verbose_) {
168  std::cout << " isTopValid() says discard!\n";
169  }
170  popHeap();
171  discarded++;
172  }
173  return discarded;
174  }
175 
179  if (numMatches_ == 0) {
180  // can't have been consumed in the first place!
182  return false;
183  }
184 
185  auto it = std::find(begin(measurements_), end(measurements_), meas);
186  if (it == end(measurements_)) {
187  // sorry, can't help...
188  return false;
189  }
190 
191  auto idx = std::distance(begin(measurements_), it);
192  if (isMeasValid(idx)) {
193  std::cerr << "Trying to resubmit, but the measurement wasn't "
194  "marked as consumed!"
195  << std::endl;
196  return false;
197  }
199  numMatches_--;
201  measRefs_[idx] = &(*it);
202  return true;
203  }
204 
208  bool hasMoreMatches(bool verbose = false) {
209  checkAndThrowNotPopulated("hasMoreMatches()");
210  if (haveMadeMaxMatches()) {
213  if (verbose || verbose_) {
214  std::cout << getPrefix() << "hasMoreMatches: Already "
215  "reached our limit for "
216  "matches!\n";
217  }
218  return false;
219  }
220  auto discarded = discardInvalidEntries();
221  if (verbose || verbose_) {
222  std::cout << getPrefix() << "hasMoreMatches: Discarded "
223  << discarded << " invalid entries.\n";
224  }
225  if (empty()) {
226  return false;
227  }
228  return isTopValid();
229  }
230 
232  LedAndMeasurement getMatch(bool verbose = false) {
233  checkAndThrowNotPopulated("getMatch()");
234 
235  auto hasMatch = hasMoreMatches();
236 
237  if (!hasMatch) {
238  throw std::logic_error("Can't call getMatch() without first "
239  "getting success from hasMoreMatches()");
240  }
241 
242  if (verbose || verbose_) {
243  std::cout << getPrefix() << "Led Index "
244  << ledIndex(distanceHeap_.front()) << "\tMeas Index "
245  << measIndex(distanceHeap_.front()) << std::endl;
246  }
247  BOOST_ASSERT_MSG(isTopValid(), "Shouldn't be able to get here "
248  "without a valid top element on the "
249  "heap.");
250 
251  auto &topLed = *getTopLed();
252  auto &topMeas = *getTopMeasurement();
253 
255  markTopConsumed();
256 
259  BOOST_ASSERT(!isTopValid());
260 
262  popHeap();
263 
265  numMatches_++;
266 
268  return LedAndMeasurement(topLed, topMeas);
269  }
270 
273  bool haveMadeMaxMatches() const {
276  BOOST_ASSERT_MSG(populated_, "Should have called "
277  "populateStructures() before calling "
278  "haveMadeMaxMatches().");
279  return numMatches_ == maxMatches_;
280  }
281 
285  size_type maxMatches() const { return maxMatches_; }
286 
288  bool empty() const {
291  BOOST_ASSERT_MSG(populated_, "Should have called "
292  "populateStructures() before calling "
293  "empty().");
294  return distanceHeap_.empty();
295  }
296 
298  size_type size() const {
301  BOOST_ASSERT_MSG(populated_, "Should have called "
302  "populateStructures() before "
303  "calling size().");
304  return distanceHeap_.size();
305  }
306 
309  size_type theoreticalMaxSize() const {
310  return leds_.size() * measurements_.size();
311  }
312 
314  double heapSizeFraction() const {
317  BOOST_ASSERT_MSG(populated_, "Should have called "
318  "populateStructures() before calling "
319  "heapSizeFraction().");
320  return static_cast<double>(size()) /
321  static_cast<double>(theoreticalMaxSize());
322  }
323 
324  size_type numUnclaimedLedObjects() const {
325  return std::count_if(
326  begin(ledRefs_), end(ledRefs_),
327  [&](LedIter const &it) { return it != ledsEnd_; });
328  }
329 
330  void eraseUnclaimedLedObjects(bool verbose = false) {
331  for (auto &ledIter : ledRefs_) {
332  if (ledIter == ledsEnd_) {
334  continue;
335  }
336  if (verbose) {
337  if (ledIter->identified()) {
338  std::cout << "Erasing identified LED "
339  << ledIter->getOneBasedID().value()
340  << " because of a lack of updated data.\n";
341  } else {
342  std::cout << "Erasing unidentified LED at "
343  << ledIter->getLocation()
344  << " because of a lack of updated data.\n";
345  }
346  }
347  leds_.erase(ledIter);
348  }
349  }
350 
351  size_type numUnclaimedMeasurements() const {
352  return std::count_if(
353  begin(measRefs_), end(measRefs_),
354  [&](MeasPtr const &ptr) { return ptr != nullptr; });
355  }
356 
357  template <typename F> void forEachUnclaimedMeasurement(F &&op) {
358  for (auto &measRef : measRefs_) {
359  if (!measRef) {
361  continue;
362  }
364  std::forward<F>(op)(*measRef);
365  }
366  }
367 
368  size_type numCompletedMatches() const { return numMatches_; }
369 
370  private:
371  using LedIter = LedGroup::iterator;
372  using LedPtr = Led *;
373  using MeasPtr = LedMeasurement const *;
374  void checkAndThrowNotPopulated(const char *functionName) const {
375  if (!populated_) {
376  throw std::logic_error("Must have called "
377  "populateStructures() before "
378  "calling " +
379  std::string(functionName));
380  }
381  }
382 
385  static std::size_t ledIndex(LedMeasDistance const &val) {
386  return std::get<0>(val);
387  }
388  static std::size_t &ledIndex(LedMeasDistance &val) {
389  return std::get<0>(val);
390  }
391  static std::size_t measIndex(LedMeasDistance const &val) {
392  return std::get<1>(val);
393  }
394  static std::size_t &measIndex(LedMeasDistance &val) {
395  return std::get<1>(val);
396  }
397  static float squaredDistance(LedMeasDistance const &val) {
398  return std::get<2>(val);
399  }
401 
402  void possiblyPushLedMeasurement(std::size_t ledIdx, std::size_t measIdx,
403  float distThreshSquared) {
404  auto meas = measRefs_[measIdx];
405  auto led = ledRefs_[ledIdx];
406  auto squaredDist = sqDist(led->getLocation(), meas->loc);
407  if (squaredDist < distThreshSquared) {
408  // If we're within the threshold, let's push this candidate
409  // on the vector that will be turned into a heap.
410  distanceHeap_.emplace_back(ledIdx, measIdx, squaredDist);
411  }
412  }
413  LedIter getTopLed() const {
414  return ledRefs_[ledIndex(distanceHeap_.front())];
415  }
416 
417  MeasPtr getTopMeasurement() const {
418  return measRefs_[measIndex(distanceHeap_.front())];
419  }
420 
421  bool isLedValid(size_type idx) const {
422  return (ledRefs_[idx] != ledsEnd_);
423  }
424 
425  bool isLedValid(LedMeasDistance const &elt) const {
426  return (ledRefs_[ledIndex(elt)] != ledsEnd_);
427  }
428 
429  bool isMeasValid(size_type idx) const {
430  return (measRefs_[idx] != nullptr);
431  }
432 
433  bool isMeasValid(LedMeasDistance const &elt) const {
434  return (measRefs_[measIndex(elt)] != nullptr);
435  }
436 
437  bool isTopValid() const {
438  LedMeasDistance elt = distanceHeap_.front();
439  return isLedValid(elt) && isMeasValid(elt);
440  }
441 
442  void markTopConsumed() {
443  LedMeasDistance elt = distanceHeap_.front();
444  ledRefs_[ledIndex(elt)] = ledsEnd_;
445  measRefs_[measIndex(elt)] = nullptr;
447  BOOST_ASSERT(!isLedValid(elt));
448  BOOST_ASSERT(!isMeasValid(elt));
449  }
450 
456  float getDistanceThresholdSquared(LedMeasurement const &meas) const {
457  auto thresh = blobMoveThreshFactor_ * meas.diameter;
458  return thresh * thresh;
459  }
460 
463  class Comparator {
464  public:
465  bool operator()(HeapValueType const &lhs,
466  HeapValueType const &rhs) const {
467  return squaredDistance(lhs) > squaredDistance(rhs);
468  }
469  };
470 
471  void makeHeap() {
474  std::make_heap(begin(distanceHeap_), end(distanceHeap_),
475  Comparator());
476  }
477 
479  void popHeap() {
480  BOOST_ASSERT_MSG(!distanceHeap_.empty(),
481  "Cannot pop from an empty heap");
484  std::pop_heap(begin(distanceHeap_), end(distanceHeap_),
485  Comparator());
487  distanceHeap_.pop_back();
488  }
489 
490  bool populated_ = false;
491  std::vector<LedIter> ledRefs_;
492  std::vector<MeasPtr> measRefs_;
493  HeapType distanceHeap_;
494  size_type numMatches_ = 0;
495  LedGroup &leds_;
496  LedMeasurementVec const &measurements_;
497  const LedIter ledsEnd_;
498  const std::size_t numBeacons_;
499  const float blobMoveThreshFactor_;
500  const size_type maxMatches_;
501  const bool verbose_;
502  };
503 
504 } // namespace vbtracker
505 } // namespace osvr
506 
507 #endif // INCLUDED_AssignMeasurementsToLeds_h_GUID_F7146BCA_13BF_4C91_1EE6_27E9FF039AD7
Helper class to keep track of the state of a blob over time.
Definition: LED.h:47
ZeroBasedBeaconId makeZeroBased(OneBasedBeaconId id)
Overloaded conversion function to turn any beacon ID into zero-based, respecting the convention that ...
Definition: BeaconIdTypes.h:99
void eraseUnclaimedLedObjects(bool verbose=false)
Definition: AssignMeasurementsToLeds.h:330
The main namespace for all C++ elements of the framework, internal and external.
Definition: namespace_osvr.dox:3
bool empty() const
Is our heap of possibilities empty?
Definition: AssignMeasurementsToLeds.h:288
bool haveMadeMaxMatches() const
Have we made as many matches as we possibly can? (that is, the minimum of the number of LED objects a...
Definition: AssignMeasurementsToLeds.h:273
size_type maxMatches() const
The maximum number of matches theoretically achievable with this input: the minimum of the number of ...
Definition: AssignMeasurementsToLeds.h:285
size_type theoreticalMaxSize() const
This is the size it could have potentially been, had all LEDs been within the distance threshold...
Definition: AssignMeasurementsToLeds.h:309
ZeroBasedBeaconId getID() const
Tells which LED I am.
Definition: LED.h:89
Header file for class that tracks and identifies LEDs.
void markMisidentified()
Called from within pose estimation or elsewhere with model-based knowledge that can refute the identi...
Definition: LED.cpp:76
void populateStructures()
Must call first, and only once.
Definition: AssignMeasurementsToLeds.h:96
Definition: AssignMeasurementsToLeds.h:75
bool resumbitMeasurement(LedMeasurement const &meas)
In case a measurement update goes bad, we can try to "un-mark" a measurement as consumed.
Definition: AssignMeasurementsToLeds.h:178
float sqDist(cv::Point2f const &lhs, cv::Point2f const &rhs)
Get the squared distance between two OpenCV points.
Definition: AssignMeasurementsToLeds.h:70
OneBasedBeaconId getOneBasedID() const
Gets either the raw negative sentinel ID or a 1-based ID (for display purposes)
Definition: LED.h:93
bool identified() const
Do we have a positive identification as a known LED?
Definition: LED.h:96
float diameter
Blob diameter in pixels.
Definition: LedMeasurement.h:86
void forEachUnclaimedMeasurement(F &&op)
Definition: AssignMeasurementsToLeds.h:357
Definition: LedMeasurement.h:41
double heapSizeFraction() const
The fraction of the theoretical max that the size is.
Definition: AssignMeasurementsToLeds.h:314
bool hasMoreMatches(bool verbose=false)
Searches the heap, discarding now-invalid entries, until it finds an entry where both the LED and the...
Definition: AssignMeasurementsToLeds.h:208
bool handleOutOfRangeIds(Led &led, const std::size_t numBeacons)
In theory this shouldn&#39;t happen, but there are checks scattered all over the code.
Definition: AssignMeasurementsToLeds.h:54
LedAndMeasurement getMatch(bool verbose=false)
Requires that hasMoreMatches() has been run and returns true.
Definition: AssignMeasurementsToLeds.h:232
int UnderlyingBeaconIdType
All beacon IDs, whether 0 or 1 based, are ints on the inside.
Definition: BeaconIdTypes.h:49
size_type discardInvalidEntries(bool verbose=false)
Discards invalid entries (those where either the LED or the measurement, or both, have already been a...
Definition: AssignMeasurementsToLeds.h:145
size_type size() const
Entries in the heap of possibilities.
Definition: AssignMeasurementsToLeds.h:298