Ocean
Loading...
Searching...
No Matches
LineEvaluator.h
Go to the documentation of this file.
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8#ifndef META_OCEAN_CV_DETECTOR_LINE_EVALUATOR_H
9#define META_OCEAN_CV_DETECTOR_LINE_EVALUATOR_H
10
12
13#include "ocean/base/Median.h"
15
17
18#include <unordered_map>
19#include <unordered_set>
20
21namespace Ocean
22{
23
24namespace CV
25{
26
27namespace Detector
28{
29
30/**
31 * This class implements an evalutator for line segments.
32 * @ingroup cvdetector
33 */
35{
36 public:
37
38 /**
39 * Definition of an id identifying e.g., a specific line.
40 */
41 typedef unsigned int Id;
42
43 /**
44 * Definition of an unordered set of ids.
45 */
46 typedef std::unordered_set<Id> IdSet;
47
48 /**
49 * Definition of an unordered map mapping ids to sets of ids.
50 */
51 typedef std::unordered_map<Id, IdSet> IdToIdSetMap;
52
53 /**
54 * This class is the base class for all line matches.
55 * Each match is composed of at least a match type and an id of the source line.
56 */
58 {
59 public:
60
61 /**
62 * Definition of individual match types.
63 */
65 {
66 /// An invalid type.
68 /// A perfect match, @see PerfectLineMath.
70 /// A partial match, @see PartialLineMatch.
72 /// A complex match, @see ComplexLineMatch.
74 };
75
76 public:
77
78 /**
79 * Destructs a match object.
80 */
81 virtual ~LineMatch();
82
83 /**
84 * Returns the type of the match.
85 * @return The match's type
86 */
87 virtual MatchType matchType() const = 0;
88
89 /**
90 * Returns the id of the source line.
91 * @return The match's source line id
92 */
93 inline Id sourceId() const;
94
95 protected:
96
97 /**
98 * Creates a new match object.
99 * @param sourceId The id of the source line, must be valid
100 */
101 explicit inline LineMatch(const Id sourceId);
102
103 protected:
104
105 /// The id of the source line.
107 };
108
109 /**
110 * Definition of a shared pointer for a LineMatch object.
111 * @see LineMatch.
112 */
113 typedef std::shared_ptr<LineMatch> LineMatchRef;
114
115 /**
116 * Definition of an unordered multi map mapping ids to match objects.
117 */
118 typedef std::unordered_multimap<Id, LineMatchRef> LineMatchMap;
119
120 /**
121 * This class implements a perfect match between a source line and a target line.
122 * A perfect match is given whenever two lines are almost equal (end points and direction).<br>
123 * A source line may have several perfect matches to several individual target lines.<br>
124 * Below, the scheme of a perfect match is depicted:
125 * <pre>
126 * ground truth lines: ++++++++++++++
127 * evaluation lines: --------------
128 * </pre>
129 * @see PartialLineMatch, ComplexLineMatch.
130 */
132 {
133 public:
134
135 /**
136 * Creates a new match object.
137 * @param sourceId The id of the source line, must be valid
138 * @param targetId The id of the target line, must be valid
139 * @param angle The (absolute) angle between source and target lines in radian, with range [0, PI/2]
140 * @param maximalDistance The maximal distance between infinite source line and target line, with range [0, infinity)
141 */
142 inline PerfectLineMatch(const Id sourceId, const Id targetId, const double angle, const double maximalDistance);
143
144 /**
145 * Returns the type of the match.
146 * @return The match's type
147 */
148 MatchType matchType() const override;
149
150 /**
151 * Returns the id of the target line.
152 * @return The target line's id
153 */
154 inline Id targetId() const;
155
156 /**
157 * Returns the angle between the source and the target line.
158 * @return The (absolute) angle between both lines in radian, with range [0, PI/2]
159 */
160 inline double angle() const;
161
162 /**
163 * Returns maximal distance between infinite source line and target line.
164 * @return The maximal distance, with range [0, infinity)
165 */
166 inline double maximalDistance() const;
167
168 protected:
169
170 /// The id of the target line.
171 unsigned int targetId_;
172
173 /// The absolute angle between both lines, with range [0, PI/2]
174 double angle_;
175
176 /// The maximal distance between infinite source line and target line, with range [0, infinity)
178 };
179
180 /**
181 * This class implements a partial match between one source line and several target lines.
182 * A partial match is given whenever several target lines can be combined to cover a source line.<br>
183 * Below, the scheme of a partial match is depicted:
184 * <pre>
185 * ground truth lines: ++++++++++++++++++++++
186 * evaluation lines: -------- -------- ----
187 * </pre>
188 * @see PerfectLineMatch, ComplexLineMatch.
189 */
191 {
192 public:
193
194 /**
195 * Creates a new partial match object.
196 * @param sourceId The id of the soruce line, must be valid
197 * @param targetIds The ids of the target line matching with the source line, at least one valid id
198 * @param coverage The coverage of the source line (the amout the target lines cover the source line), with range (0, 1 + borderEps]
199 * @param medianAngle The median angle of between the source line and all target lines in radian, with range [0, PI/2]
200 * @param medianDistance The median distance from all target lines to the source lines, with range [0, infinity)
201 */
202 inline PartialLineMatch(const Id sourceId, const IdSet& targetIds, const double coverage, const double medianAngle, const double medianDistance);
203
204 /**
205 * Creates a new partial match object.
206 * @param sourceId The id of the soruce line, must be valid
207 * @param targetIds The ids of the target line matching with the source line, at least one valid id, will be moved
208 * @param coverage The coverage of the source line (the amout the target lines cover the source line), with range (0, 1 + borderEps]
209 * @param medianAngle The median angle of between the source line and all target lines in radian, with range [0, PI/2]
210 * @param medianDistance The median distance from all target lines to the source lines, with range [0, infinity)
211 */
212 inline PartialLineMatch(const Id sourceId, IdSet&& targetIds, const double coverage, const double medianAngle, const double medianDistance);
213
214 /**
215 * Returns the type of the match.
216 * @return The match's type
217 */
218 MatchType matchType() const override;
219
220 /**
221 * Returns the ids of all target lines belonging to the partial match.
222 * @return The target ids, at least one
223 */
224 inline const IdSet& targetIds() const;
225
226 /**
227 * The amout the target lines cover the source line.
228 * @return The coverage amout, with range (0, 1 + borderEps]
229 */
230 inline double coverage() const;
231
232 /**
233 * Returns the median angle between the source line and all target lines.
234 * @return The median angle in radian, with range [0, PI/2]
235 */
236 inline double medianAngle() const;
237
238 /**
239 * Returns the median distance between the source line and all target lines.
240 * @return The median distance, with range [0, infinity)
241 */
242 inline double medianDistance() const;
243
244 protected:
245
246 /// The ids of all target lines.
248
249 /// The coverage of the source line (the amout the target lines cover the source line), with range (0, 1 + borderEps]
250 double coverage_;
251
252 /// The median angle between the source line and all target lines, in radian, with range [0, PI/2]
254
255 /// The median distance between the source line and all target lines, with range [0, infinity)
257 };
258
259 /**
260 * This class implements a complex match between one source line and several target lines.
261 * A complex match is given whenever several source lines match to portions of several target lines.<br>
262 * The complex match is still defined for one source line, in combination with all portions of target lines.<br>
263 * Below, the scheme of a complex match is depicted:
264 * <pre>
265 * ground truth lines: ++++++++++++++++++++++ +++++++++++++ ++++++++++
266 * evaluation lines: -------- ----------------------- -------- -----
267 * </pre>
268 * @see PerfectLineMatch.
269 */
271 {
272 public:
273
274 /**
275 * Creates a new match object.
276 * @param sourceId The id of the soruce line, must be valid
277 * @param targetIds The ids of the target line matching with the source line, at least one valid id
278 * @param coverage The coverage of the source line (the amout the target lines cover the source line), with range (0, 1]
279 * @param medianAngle The median angle of between the source line and all target lines in radian, with range [0, PI/2]
280 * @param medianDistance The median distance from all target lines to the source lines, with range [0, infinity)
281 * @param connectedSourceIds The ids of all sibling/connected source lines which have been investigated during the match creation
282 * @param connectedTargetIds The ids of all sibling/connected target lines which have been investigated during the match creation, at least one
283 */
284 inline ComplexLineMatch(const Id sourceId, const IdSet& targetIds, const double coverage, const double medianAngle, const double medianDistance, const IdSet& connectedSourceIds, const IdSet& connectedTargetIds);
285
286 /**
287 * Returns the type of the match.
288 * @return The match's type
289 */
290 MatchType matchType() const override;
291
292 /**
293 * Returns the ids of all sibling/connected source lines which have been investigated during the match creation.
294 * @return The source lines' ids
295 */
296 inline const IdSet& connectedSourceIds() const;
297
298 /**
299 * Returns the ids of all sibling/connected target lines which have been investigated during the match creation.
300 * @return The target lines' ids
301 */
302 inline const IdSet& connectedTargetIds() const;
303
304 protected:
305
306 /// The ids of all sibling/connected source lines which have been investigated during the match creation.
308
309 /// The ids of all sibling/connected target lines which have been investigated during the match creation.
311 };
312
313 /**
314 * Definition of individual strategies to determine the distance between two line segments.
315 */
317 {
318 /**
319 * The end points of the evaluation line are projected onto the infinite ground truth line,
320 * and the maximal distance between end points and projected points is determined.<br>
321 * This measure has the property that very long evaluation lines and very short ground truth lines
322 * may result in a large distance although the overlapping area seems to be quite close.
323 */
325
326 /**
327 * The end points of the ground truth lines are projected onto the infinite evaluation line,
328 * and the maximal distance between end points and projected points is determined.<br>
329 * This measure has the property that very long ground truth lines and very short evaluation lines
330 * may result in a large distance although the overlapping area seems to be quite close.
331 */
333
334 /**
335 * This measure combines 'DM_PROJECTED_ONTO_GROUND_TRUTH' and 'DM_PROJECTED_ONTO_EVALUATION_LINE'.
336 * The distance is the minimal distance of both measures.<br>
337 * Thus, this measure has the property that combinations of long and small lines end up with smaller distances.
338 */
340 };
341
342 protected:
343
344 /**
345 * Definition of a set holding a pair of ids.
346 */
347 typedef std::unordered_set<unsigned long long> Id64Set;
348
349 public:
350
351 /**
352 * Checks whether two given lines are overlapping up to some extend and determines some overlapping metrics.
353 * This function can determine out-of-border distances for the projected end points of the evaluation line, see FiniteLineT2::nearestPointOnInfiniteLine() for more details.
354 * The paris of resulting distance values are sorted so that the following holds: outOfBorderDistance0 <= outOfBorderDistance1, locationOnLine0 <= locationOnLine1.
355 * @param lineGroundTruth The ground truth line (actually one of both lines) while all optional resulting location values are determined in relation to this line, must be valid
356 * @param lineEvaluation The evaluation line (actually the second line), must be valid
357 * @param angleThresholdCos The angle between two lines so that both lines count as similar, given in the cosine value of the angle: angleThresholdCos = cos(angleThreshold), with range [0, 1]
358 * @param distanceThresholdPixels The maixmal distance between two lines so that both lines count as similar, depending on the defined distance measure 'distanceMeasure', with range [0, infinity)
359 * @param distanceMeasure The distance measure to be applied when comparing with the provided distance theshold 'distanceThresholdPixels'
360 * @param projectedLength Optional resulting length of the projected evaluation line on the ground truth line, with range [0, infinity)
361 * @param outOfBorderDistance0 Optional resulting distance between the projected point (of the first end point of the evaluation line) on the infinite ground truth line and the closest end point of the (finite) ground truth line, with range (-infinity, 0]
362 * @param outOfBorderDistance1 Optional resulting distance between the projected point (of the second end point of the evaluation line) on the infinite ground truth line and the closest end point of the (finite) ground truth line, with range [0, infinity)
363 * @param locationOnLine0 Optional resulting location of the projected point (of the first end point of the evaluation line) on the infinite ground truth line in relation to the finite ground truth line so that the following holds lineGroundTruth.point0() + lineGroundTruth.direction() * locationOnLine0, with range (-infinity, infinity)
364 * @param locationOnLine1 Optional resulting location of the projected point (of the second end point of the evaluation line) on the infinite ground truth line in relation to the finite ground truth line so that the following holds lineGroundTruth.point1() + lineGroundTruth.direction() * locationOnLine1, with range (-infinity, infinity)
365 * @return True, if so
366 * @tparam T The data type of a scalar, either 'float' or 'double'
367 * @see determineOverlappingAmount(), FiniteLineT2::nearestPointOnInfiniteLine().
368 */
369 template <typename T>
370 static bool areLinesOverlapping(const FiniteLineT2<T>& lineGroundTruth, const FiniteLineT2<T>& lineEvaluation, const T angleThresholdCos, const T distanceThresholdPixels, const DistanceMeasure distanceMeasure, T* projectedLength = nullptr, T* outOfBorderDistance0 = nullptr, T* outOfBorderDistance1 = nullptr, T* locationOnLine0 = nullptr, T* locationOnLine1 = nullptr);
371
372 /**
373 * Determines the overlapping metrics between two lines that are known to overlapping (and to be similar) already.
374 * This function can determine out-of-border distances for the projected end points of the evaluation line, see FiniteLineT2::nearestPointOnInfiniteLine() for more details.
375 * The paris of resulting distance values are sorted so that the following holds: outOfBorderDistance0 <= outOfBorderDistance1, locationOnLine0 <= locationOnLine1.
376 * @param lineGroundTruth The ground truth line (actually one of both lines) while all optional resulting location values are determined in relation to this line, must be valid
377 * @param lineEvaluation The evaluation line (actually the second line), must be valid
378 * @param projectedLength Optional resulting length of the projected evaluation line on the ground truth line, with range [0, infinity)
379 * @param outOfBorderDistance0 Optional resulting distance between the projected point (of the first end point of the evaluation line) on the infinite ground truth line and the closest end point of the (finite) ground truth line, with range (-infinity, 0]
380 * @param outOfBorderDistance1 Optional resulting distance between the projected point (of the second end point of the evaluation line) on the infinite ground truth line and the closest end point of the (finite) ground truth line, with range [0, infinity)
381 * @param locationOnLine0 Optional resulting location of the projected point (of the first end point of the evaluation line) on the infinite ground truth line in relation to the finite ground truth line so that the following holds lineGroundTruth.point0() + lineGroundTruth.direction() * locationOnLine0, with range (-infinity, infinity)
382 * @param locationOnLine1 Optional resulting location of the projected point (of the second end point of the evaluation line) on the infinite ground truth line in relation to the finite ground truth line so that the following holds lineGroundTruth.point1() + lineGroundTruth.direction() * locationOnLine1, with range (-infinity, infinity)
383 * @tparam T The data type of a scalar, either 'float' or 'double'
384 * @see areLinesOverlapping(), FiniteLineT2::nearestPointOnInfiniteLine().
385 */
386 template <typename T>
387 static void determineOverlappingAmount(const FiniteLineT2<T>& lineGroundTruth, const FiniteLineT2<T>& lineEvaluation, T* projectedLength = nullptr, T* outOfBorderDistance0 = nullptr, T* outOfBorderDistance1 = nullptr, T* locationOnLine0 = nullptr, T* locationOnLine1 = nullptr);
388
389 /**
390 * Determines the similarity between two lines known to be overlapping.
391 * @param lineGroundTruth The ground truth line (actually one of both lines) while all optional resulting location values are determined in relation to this line, must be valid
392 * @param lineEvaluation The evaluation line (actually the second line), must be valid
393 * @param distanceMeasure The distance measure to be used to determine the distance between both lines
394 * @param angle The resulting angle between both lines in radian, with range [0, infinity)
395 * @param distance The distance between both lines based on the specified distance measure
396 * @tparam T The data type of a scalar, either 'float' or 'double'
397 * @see areLinesOverlapping().
398 */
399 template <typename T>
400 static void determineSimilarity(const FiniteLineT2<T>& lineGroundTruth, const FiniteLineT2<T>& lineEvaluation, const DistanceMeasure distanceMeasure, T& angle, T& distance);
401
402 /**
403 * Evaluates two sets of finite lines.
404 * The given ground truth lines should be accurate and should contain lines that e.g., can be detected by a line detector
405 * The lines to evaluate are match to the set of ground truth lines.
406 * @param linesGroundTruth The ground truth lines, at least one
407 * @param linesEvaluation The lines to be evaluated, at least one
408 * @param perfectMatchAngleThreshold The maximal angle between lines have a perfect match, in radian, with range [0, PI/2]
409 * @param perfectMatchPixelThreshold The maximal distance between corresponding end points of a perfect match, in pixels (defined in the domain of the line coordinates), with range [0, infinity)
410 * @param matchAngleThreshold The maximal angle between lines having a general match, in radian, with range [0, PI/2]
411 * @param matchCloseToLinePixelThreshold The maximal distance between two lines having a general match, in pixels (defined in the domain of the line coordinates), with range [0, infinity)
412 * @param partialMatchNonOverlappingPixelThreshold The maximal amount an evaluation line may exceed a ground truth line in a partial match, in pixels (defined in the domain of the line coordinates), with range [0, infinity)
413 * @param complexMatchMaximalGapPixelThreshold The maximal gap between valid evaluation lines for a complex match, in pixels (defined in the domain of the line coordinates), with range [0, infinity)
414 * @return The resulting set of matches
415 * @tparam T The data type of a scalar, either 'float' or 'double'
416 */
417 template <typename T>
418 static LineMatchMap evaluateLineSegments(const std::unordered_map<Id, FiniteLineT2<T>>& linesGroundTruth, const std::unordered_map<Id, FiniteLineT2<T>>& linesEvaluation, const T perfectMatchAngleThreshold = NumericT<T>::deg2rad(2), const T perfectMatchPixelThreshold = T(2), const T matchAngleThreshold = NumericT<T>::deg2rad(5), const T matchCloseToLinePixelThreshold = T(3), const T partialMatchNonOverlappingPixelThreshold = T(25), const T complexMatchMaximalGapPixelThreshold = T(15));
419
420 /**
421 * Evaluates the overall quality of line matches.
422 * @param linesGroundTruth The ground truth lines, at least one
423 * @param linesEvaluation The lines to be evaluated, at least one
424 * @param lineMatches The line matches that are found between the ground truth lines and evaluation lines
425 * @param coverage The resulting overall match coverage for all ground truth lines, with range [0, 1]
426 * @param medianAngle The resulting median angle of all matches, in radian, with range [0, PI/2]
427 * @param medianDistance The resulting median distance of all matches, with range [0, infinity)
428 * @param countPerfectMatches The resulting number of provided perfect matches, with range [0, lineMatches.size()]
429 * @param countPartialMatches The resulting number of provided partial matches, with range [0, lineMatches.size()]
430 * @param countComplexMatches The resulting number of provided complex matches, with range [0, lineMatches.size()]
431 * @param notCoveredGroundTruthLines The resulting number of ground truth lines not part of the matches, with range [0, linesGroundTruth.size() - 1]
432 * @param notCoveredEvaluationLines The resulting number of evaluation lines not part of the matches, with range [0, linesEvaluation.size() - 1]
433 * @param notCoveredGroundTruthLineIds Optional resulting ids of all ground truth lines not covered by a corresponding evaluation line
434 * @param notCoveredEvaluationLineIds Optional resulting ids of all evaluation lines not covered by a corresponding ground truth line
435 * @return True, if succeeded
436 * @tparam T The data type of a scalar, either 'float' or 'double'
437 */
438 template <typename T>
439 static bool evaluateLineMatches(const std::unordered_map<Id, FiniteLineT2<T>>& linesGroundTruth, const std::unordered_map<Id, FiniteLineT2<T>>& linesEvaluation, const LineMatchMap& lineMatches, double& coverage, double& medianAngle, double& medianDistance, size_t& countPerfectMatches, size_t& countPartialMatches, size_t& countComplexMatches, size_t& notCoveredGroundTruthLines, size_t& notCoveredEvaluationLines, IdSet* notCoveredGroundTruthLineIds = nullptr, IdSet* notCoveredEvaluationLineIds = nullptr);
440
441 protected:
442
443 /**
444 * Determines the union of segments resulting from projecting several finite lines onto a unique finite line of interest.
445 * The segments are specified by the location of the projected points on the unique finite line of interest.
446 * @param lineOfInterest The line of interest on which the segments will be determined, must be valid
447 * @param lineIdsToProject The ids of all lines for which the projected segments will be determined
448 * @param lines The map mapping the ids given in 'lineIdsToProject' to finite lines
449 * @return The resulting union of segments, may be an empty union
450 */
451 template <typename T>
452 static SegmentUnion<T> determineProjectedSegmentUnion(const FiniteLineT2<T>& lineOfInterest, const IdSet& lineIdsToProject, const std::unordered_map<Id, FiniteLineT2<T>>& lines);
453
454 /**
455 * Determines a complex match for a given ground truth line.
456 * @param linesGroundTruth The ground truth lines, at least one
457 * @param linesEvaluation The lines to be evaluated, at least one
458 * @param groundTruthToEvaluationMap The map mapping ids of ground truth lines to similar/overlapping evaluation lines
459 * @param evaluationToGroundTruthMap The map mapping ids of evaluation lines to to similar/overlapping ground truth lines, actually the reverse mapping of 'groundTruthToEvaluationMap'
460 * @param groundTruthId The id of the ground truth line for which the complex match will be determined
461 * @param groundTruthEvaluationSet The set of pairs of line ids for which a mapping in 'groundTruthToEvaluationMap' exists, this set is used to speed up the process
462 * @param complexMatchMaximalGapPixelThreshold The maximal gap between valid evaluation lines, in pixels (defined in the domain of the line coordinates), with range [0, infinity)
463 */
464 template <typename T>
465 static LineMatchRef determineComplexMatch(const std::unordered_map<Id, FiniteLineT2<T>>& linesGroundTruth, const std::unordered_map<Id, FiniteLineT2<T>>& linesEvaluation, const IdToIdSetMap& groundTruthToEvaluationMap, const IdToIdSetMap evaluationToGroundTruthMap, const Id groundTruthId, const Id64Set& groundTruthEvaluationSet, const T complexMatchMaximalGapPixelThreshold);
466
467 /**
468 * Combines two (32 bit) ids to one 64 bit value.
469 * @param firstId The first id to combine
470 * @param secondId The second id to combine
471 * @return The resulting 64 bit value
472 */
473 static inline unsigned long long combineIds(const Id& firstId, const Id& secondId);
474};
475
477 sourceId_(sourceId)
478{
479 // nothing to do here
480}
481
483{
484 // nothing to do here
485}
486
488{
489 return sourceId_;
490}
491
492inline LineEvaluator::PerfectLineMatch::PerfectLineMatch(const Id sourceId, const Id targetId, const double angle, const double maximalDistance) :
493 LineMatch(sourceId),
494 targetId_(targetId),
495 angle_(angle),
496 maximalDistance_(maximalDistance)
497{
498 ocean_assert(NumericD::isInsideRange(0, angle, NumericD::pi_2()));
499}
500
505
507{
508 return targetId_;
509}
510
512{
513 return angle_;
514}
515
517{
518 return maximalDistance_;
519}
520
521inline LineEvaluator::PartialLineMatch::PartialLineMatch(const Id sourceId, const IdSet& targetIds, const double coverage, const double medianAngle, const double medianDistance) :
522 LineMatch(sourceId),
523 targetIds_(targetIds),
524 coverage_(coverage),
525 medianAngle_(medianAngle),
526 medianDistance_(medianDistance)
527{
528 ocean_assert(coverage >= 0.0);
529 ocean_assert(medianAngle >= 0.0 && medianAngle <= NumericD::pi_2());
530 ocean_assert(medianDistance >= 0.0);
531}
532
533inline LineEvaluator::PartialLineMatch::PartialLineMatch(const Id sourceId, IdSet&& targetIds, const double coverage, const double medianAngle, const double medianDistance) :
534 LineMatch(sourceId),
535 targetIds_(std::move(targetIds)),
536 coverage_(coverage),
537 medianAngle_(medianAngle),
538 medianDistance_(medianDistance)
539{
540 ocean_assert(coverage >= 0.0);
541 ocean_assert(medianAngle >= 0.0 && medianAngle <= NumericD::pi_2());
542 ocean_assert(medianDistance >= 0.0);
543}
544
549
551{
552 return targetIds_;
553}
554
556{
557 return coverage_;
558}
559
561{
562 return medianAngle_;
563}
564
566{
567 return medianDistance_;
568}
569
570inline LineEvaluator::ComplexLineMatch::ComplexLineMatch(const Id sourceId, const IdSet& targetIds, const double coverage, const double medianAngle, const double medianDistance, const IdSet& connectedSourceIds, const IdSet& connectedTargetIds) :
571 PartialLineMatch(sourceId, targetIds, coverage, medianAngle, medianDistance),
572 connectedSourceIds_(connectedSourceIds),
573 connectedTargetIds_(connectedTargetIds)
574{
575 // nothing to do here
576}
577
582
584{
585 return connectedSourceIds_;
586}
587
589{
590 return connectedTargetIds_;
591}
592
593template <typename T>
594bool LineEvaluator::areLinesOverlapping(const FiniteLineT2<T>& lineGroundTruth, const FiniteLineT2<T>& lineEvaluation, const T angleThresholdCos, const T distanceThresholdPixels, DistanceMeasure distanceMeasure, T* projectedLength, T* outOfBorderDistance0, T* outOfBorderDistance1, T* locationOnLine0, T* locationOnLine1)
595{
596 ocean_assert(lineGroundTruth.isValid() && lineEvaluation.isValid());
597
598 ocean_assert(angleThresholdCos >= T(0) && angleThresholdCos <= T(1)); // angleThresholdCos = cos(angleThreshold)
599 ocean_assert(distanceThresholdPixels >= T(0));
600
601 // let's check whether both lines are almost parallel
602
603 const T absCosineValue = NumericT<T>::abs(lineGroundTruth.direction() * lineEvaluation.direction());
604
605 if (absCosineValue < angleThresholdCos)
606 {
607 return false;
608 }
609
610 const T sqrDistanceThresholdPixels = distanceThresholdPixels * distanceThresholdPixels;
611
612 T internalSqrDistance = NumericT<T>::maxValue();
613
614 if (distanceMeasure == DM_PROJECTED_ONTO_EVALUATION_LINE || distanceMeasure == DM_PROJECTED_ONTO_EACH_OTHER)
615 {
616 const VectorT2<T> pointOnInfiniteEvaluation0 = lineEvaluation.nearestPointOnInfiniteLine(lineGroundTruth.point0());
617 const VectorT2<T> pointOnInfiniteEvaluation1 = lineEvaluation.nearestPointOnInfiniteLine(lineGroundTruth.point1());
618
619 // the maximum of the distance between end points and projected end points
620 internalSqrDistance = std::max(pointOnInfiniteEvaluation0.sqrDistance(lineGroundTruth.point0()), pointOnInfiniteEvaluation1.sqrDistance(lineGroundTruth.point1()));
621
622 if (distanceMeasure == DM_PROJECTED_ONTO_EVALUATION_LINE)
623 {
624 // we may stop already if the distance is outside the threshold
625
626 if (internalSqrDistance > sqrDistanceThresholdPixels)
627 {
628 return false;
629 }
630 }
631 }
632
633 T internalOutOfBoundaryDistanceOnGroundTruth0, internalLocationOnGroundTruth0;
634 const VectorT2<T> pointOnInfiniteGroundTruth0 = lineGroundTruth.nearestPointOnInfiniteLine(lineEvaluation.point0(), &internalOutOfBoundaryDistanceOnGroundTruth0, &internalLocationOnGroundTruth0);
635
636 T internalOutOfBoundaryDistanceOnGroundTruth1, internalLocationOnGroundTruth1;
637 const VectorT2<T> pointOnInfiniteGroundTruth1 = lineGroundTruth.nearestPointOnInfiniteLine(lineEvaluation.point1(), &internalOutOfBoundaryDistanceOnGroundTruth1, &internalLocationOnGroundTruth1);
638
639 if (distanceMeasure != DM_PROJECTED_ONTO_EVALUATION_LINE)
640 {
641 ocean_assert(distanceMeasure == DM_PROJECTED_ONTO_GROUND_TRUTH || distanceMeasure == DM_PROJECTED_ONTO_EACH_OTHER);
642
643 const T sqrDistanceOnEvaluationLine = std::max(pointOnInfiniteGroundTruth0.sqrDistance(lineEvaluation.point0()), pointOnInfiniteGroundTruth1.sqrDistance(lineEvaluation.point1()));
644
645 if (distanceMeasure == DM_PROJECTED_ONTO_GROUND_TRUTH)
646 {
647 // we may stop already if the distance is outside the threshold
648
649 if (sqrDistanceOnEvaluationLine > sqrDistanceThresholdPixels)
650 {
651 return false;
652 }
653
654 internalSqrDistance = sqrDistanceOnEvaluationLine;
655 }
656 else
657 {
658 // we take the smallest of both distances
659
660 if (sqrDistanceOnEvaluationLine < internalSqrDistance)
661 {
662 internalSqrDistance = sqrDistanceOnEvaluationLine;
663 }
664
665 // we may stop already if the distance is outside the threshold
666
667 if (internalSqrDistance > sqrDistanceThresholdPixels)
668 {
669 return false;
670 }
671 }
672 }
673
674 ocean_assert(internalSqrDistance != NumericT<T>::maxValue());
675
676 // both lines are close to each other, further the angle is almost similar
677 // now let's find out whether the evaluation line is within the boundaries of the finite ground truth line
678
679 Utilities::sortLowestToFront2(internalOutOfBoundaryDistanceOnGroundTruth0, internalOutOfBoundaryDistanceOnGroundTruth1);
680 ocean_assert(internalOutOfBoundaryDistanceOnGroundTruth0 <= internalOutOfBoundaryDistanceOnGroundTruth1);
681
682 if (internalOutOfBoundaryDistanceOnGroundTruth1 < 0 || internalOutOfBoundaryDistanceOnGroundTruth0 > 0)
683 {
684 // the line is completely outside of the boundary of the ground truth line
685 return false;
686 }
687
688 Utilities::sortLowestToFront2(internalLocationOnGroundTruth0, internalLocationOnGroundTruth1);
689 ocean_assert(internalLocationOnGroundTruth0 <= internalLocationOnGroundTruth1);
690
691 if (projectedLength)
692 {
693 *projectedLength = internalLocationOnGroundTruth1 - internalLocationOnGroundTruth0;
694
695 ocean_assert(NumericT<T>::isWeakEqual(pointOnInfiniteGroundTruth0.distance(pointOnInfiniteGroundTruth1), *projectedLength));
696 }
697
698 if (outOfBorderDistance0)
699 {
700 *outOfBorderDistance0 = internalOutOfBoundaryDistanceOnGroundTruth0;
701 }
702
703 if (outOfBorderDistance1)
704 {
705 *outOfBorderDistance1 = internalOutOfBoundaryDistanceOnGroundTruth1;
706 }
707
708 if (locationOnLine0)
709 {
710 *locationOnLine0 = internalLocationOnGroundTruth0;
711 }
712
713 if (locationOnLine1)
714 {
715 *locationOnLine1 = internalLocationOnGroundTruth1;
716 }
717
718 return true;
719}
720
721template <typename T>
722void LineEvaluator::determineOverlappingAmount(const FiniteLineT2<T>& lineGroundTruth, const FiniteLineT2<T>& lineEvaluation, T* projectedLength, T* outOfBorderDistance0, T* outOfBorderDistance1, T* locationOnLine0, T* locationOnLine1)
723{
724 ocean_assert(lineGroundTruth.isValid() && lineEvaluation.isValid());
725
726 T internalOutOfBoundaryDistanceOnGroundTruth0, internalLocationOnGroundTruth0;
727 lineGroundTruth.nearestPointOnInfiniteLine(lineEvaluation.point0(), &internalOutOfBoundaryDistanceOnGroundTruth0, &internalLocationOnGroundTruth0);
728
729 T internalOutOfBoundaryDistanceOnGroundTruth1, internalLocationOnGroundTruth1;
730 lineGroundTruth.nearestPointOnInfiniteLine(lineEvaluation.point1(), &internalOutOfBoundaryDistanceOnGroundTruth1, &internalLocationOnGroundTruth1);
731
732 Utilities::sortLowestToFront2(internalOutOfBoundaryDistanceOnGroundTruth0, internalOutOfBoundaryDistanceOnGroundTruth1);
733 Utilities::sortLowestToFront2(internalLocationOnGroundTruth0, internalLocationOnGroundTruth1);
734
735 if (projectedLength)
736 {
737 *projectedLength = internalLocationOnGroundTruth1 - internalLocationOnGroundTruth0;
738 }
739
740 if (outOfBorderDistance0)
741 {
742 *outOfBorderDistance0 = internalOutOfBoundaryDistanceOnGroundTruth0;
743 }
744
745 if (outOfBorderDistance1)
746 {
747 *outOfBorderDistance1 = internalOutOfBoundaryDistanceOnGroundTruth1;
748 }
749
750 if (locationOnLine0)
751 {
752 *locationOnLine0 = internalLocationOnGroundTruth0;
753 }
754
755 if (locationOnLine1)
756 {
757 *locationOnLine1 = internalLocationOnGroundTruth1;
758 }
759}
760
761template <typename T>
762void LineEvaluator::determineSimilarity(const FiniteLineT2<T>& lineGroundTruth, const FiniteLineT2<T>& lineEvaluation, const DistanceMeasure distanceMeasure, T& angle, T& distance)
763{
764 ocean_assert(lineGroundTruth.isValid() && lineEvaluation.isValid());
765
766 const T absCosineValue = NumericT<T>::abs(lineGroundTruth.direction() * lineEvaluation.direction());
767 angle = NumericT<T>::acos(absCosineValue);
768
770
771 if (distanceMeasure == DM_PROJECTED_ONTO_EVALUATION_LINE || distanceMeasure == DM_PROJECTED_ONTO_EACH_OTHER)
772 {
773 const VectorT2<T> pointOnInfiniteEvaluation0 = lineEvaluation.nearestPointOnInfiniteLine(lineGroundTruth.point0());
774 const VectorT2<T> pointOnInfiniteEvaluation1 = lineEvaluation.nearestPointOnInfiniteLine(lineGroundTruth.point1());
775
776 // the maximum of the distance between end points and projected end points
777 sqrDistance = std::max(pointOnInfiniteEvaluation0.sqrDistance(lineGroundTruth.point0()), pointOnInfiniteEvaluation1.sqrDistance(lineGroundTruth.point1()));
778
779 if (distanceMeasure == DM_PROJECTED_ONTO_EVALUATION_LINE)
780 {
781 distance = NumericT<T>::sqrt(sqrDistance);
782 return;
783 }
784 }
785
786 ocean_assert(distanceMeasure == DM_PROJECTED_ONTO_GROUND_TRUTH || distanceMeasure == DM_PROJECTED_ONTO_EACH_OTHER);
787
788 const VectorT2<T> pointOnInfiniteGroundTruth0 = lineGroundTruth.nearestPointOnInfiniteLine(lineEvaluation.point0());
789 const VectorT2<T> pointOnInfiniteGroundTruth1 = lineGroundTruth.nearestPointOnInfiniteLine(lineEvaluation.point1());
790
791 const T sqrDistanceOnEvaluationLine = std::max(pointOnInfiniteGroundTruth0.sqrDistance(lineEvaluation.point0()), pointOnInfiniteGroundTruth1.sqrDistance(lineEvaluation.point1()));
792
793 if (sqrDistanceOnEvaluationLine < sqrDistance)
794 {
795 sqrDistance = sqrDistanceOnEvaluationLine;
796 }
797
798 distance = NumericT<T>::sqrt(sqrDistance);
799}
800
801template <typename T>
802LineEvaluator::LineMatchMap LineEvaluator::evaluateLineSegments(const std::unordered_map<Id, FiniteLineT2<T>>& linesGroundTruth, const std::unordered_map<Id, FiniteLineT2<T>>& linesEvaluation, const T perfectMatchAngleThreshold, const T perfectMatchPixelThreshold, const T matchAngleThreshold, const T matchCloseToLinePixelThreshold, const T partialMatchNonOverlappingPixelThreshold, const T complexMatchMaximalGapPixelThreshold)
803{
804 ocean_assert(!linesGroundTruth.empty() && !linesEvaluation.empty());
805
806 ocean_assert(perfectMatchAngleThreshold >= 0 && perfectMatchAngleThreshold <= NumericT<T>::pi_2());
807 ocean_assert(perfectMatchPixelThreshold >= 0);
808
809 ocean_assert(matchAngleThreshold >= 0 && matchAngleThreshold <= NumericT<T>::pi_2());
810 ocean_assert(matchCloseToLinePixelThreshold >= 0);
811
812 ocean_assert(partialMatchNonOverlappingPixelThreshold >= 0);
813 ocean_assert(complexMatchMaximalGapPixelThreshold >= 0);
814
815 typedef std::unordered_map<Id, FiniteLineT2<T>> LineMap;
816
817 // first, we determine a mapping from ground truth lines to connected evaluation lines (all lines which are almost similar and partially overlapping)
818 // we can use this map to check for valid matching candidates
819
820 IdToIdSetMap groundTruthToEvaluationMap;
821 IdToIdSetMap evaluationToGroundTruthMap;
822 Id64Set groundTruthEvaluationSet;
823
824 const T perfectMatchAngleThresholdCos = NumericT<T>::cos(perfectMatchAngleThreshold);
825 const T matchAngleThresholdCos = NumericT<T>::cos(matchAngleThreshold);
826
827 for (typename LineMap::const_iterator iG = linesGroundTruth.cbegin(); iG != linesGroundTruth.cend(); ++iG)
828 {
829 const Id& groundTruthId = iG->first;
830 const FiniteLineT2<T>& lineGroundTruth = iG->second;
831 ocean_assert(lineGroundTruth.isValid());
832
833 for (typename LineMap::const_iterator iE = linesEvaluation.cbegin(); iE != linesEvaluation.cend(); ++iE)
834 {
835 const Id& evaluationId = iE->first;
836 const FiniteLineT2<T>& lineEvaluation = iE->second;
837 ocean_assert(lineEvaluation.isValid());
838
839 if (areLinesOverlapping(lineGroundTruth, lineEvaluation, matchAngleThresholdCos, matchCloseToLinePixelThreshold, DM_PROJECTED_ONTO_EACH_OTHER))
840 {
841 // for each ground truth line we store the corresponding overlapping/similar evaluation line
842
843 groundTruthToEvaluationMap[groundTruthId].insert(evaluationId);
844
845 // just a set storing that the ground truth line is connected with the evaluation line
846 groundTruthEvaluationSet.insert(combineIds(groundTruthId, evaluationId));
847
848 // also, we store the reverse mapping
849 evaluationToGroundTruthMap[evaluationId].insert(groundTruthId);
850 }
851 }
852 }
853
854 // now we try to find a corresponding match for each ground truth line
855
856 LineMatchMap lineMatches;
857
858 for (typename LineMap::const_iterator iG = linesGroundTruth.cbegin(); iG != linesGroundTruth.cend(); ++iG)
859 {
860 const Id& groundTruthId = iG->first;
861 const FiniteLineT2<T>& lineGroundTruth = iG->second;
862
863 // whenever we have a valid complex match, we do not need to investage any remaining evaluation line, as they are covered within the complex match already
864 LineMatchRef complexMatch;
865
866 SegmentUnion<T> partialMatchUnion;
867 IdSet targetIds;
868
869 for (typename LineMap::const_iterator iE = linesEvaluation.cbegin(); !complexMatch && iE != linesEvaluation.cend(); ++iE)
870 {
871 const Id& evaluationId = iE->first;
872 const FiniteLineT2<T>& lineEvaluation = iE->second;
873
874 if (groundTruthEvaluationSet.find(combineIds(groundTruthId, evaluationId)) != groundTruthEvaluationSet.cend())
875 {
876 T projectedLengthEvaluationLine;
877 T outOfBoundaryDistance0, outOfBoundaryDistance1;
878 T locationOnLine0, locationOnLine1;
879
880 ocean_assert(areLinesOverlapping(lineGroundTruth, lineEvaluation, matchAngleThresholdCos, matchCloseToLinePixelThreshold, DM_PROJECTED_ONTO_EACH_OTHER));
881 determineOverlappingAmount(lineGroundTruth, lineEvaluation, &projectedLengthEvaluationLine, &outOfBoundaryDistance0, &outOfBoundaryDistance1, &locationOnLine0, &locationOnLine1);
882
883 const T projectedNonOverlappingLength = NumericT<T>::abs(outOfBoundaryDistance0) + NumericT<T>::abs(outOfBoundaryDistance1);
884
885 if (projectedNonOverlappingLength <= partialMatchNonOverlappingPixelThreshold)
886 {
887 const T projectedOverlappingLength = projectedLengthEvaluationLine - projectedNonOverlappingLength;
888
889 // the projected overlapping length cannot be longer than the length of the ground truth line
890 ocean_assert_and_suppress_unused(projectedOverlappingLength <= lineGroundTruth.length() + NumericT<T>::weakEps(), projectedOverlappingLength);
891
892 ocean_assert(locationOnLine0 <= locationOnLine1);
893 partialMatchUnion.addSegment(locationOnLine0, locationOnLine1);
894
895 targetIds.insert(evaluationId);
896 }
897 else
898 {
899 // the line segment is similar to the ground truth line but extends the ground truth line significantly
900 // thus, we need to check whether other ground truth lines support the line segment
901
902 complexMatch = determineComplexMatch(linesGroundTruth, linesEvaluation, groundTruthToEvaluationMap, evaluationToGroundTruthMap, groundTruthId, groundTruthEvaluationSet, complexMatchMaximalGapPixelThreshold);
903
904 // the for loop will stop immediately
905 }
906 }
907 }
908
909 if (complexMatch)
910 {
911 // **TODO** a complex match may also be a perfect match or a partial match
912
913 lineMatches.insert(std::make_pair(groundTruthId, complexMatch));
914 }
915 else
916 {
917 if (partialMatchUnion)
918 {
919 ocean_assert(!targetIds.empty());
920
921 LineMatchRef lineMatch;
922
923 if (targetIds.size() == 1)
924 {
925 // we may have found a perfect match
926
927 const Id evaluationId = *targetIds.begin();
928 const FiniteLineT2<T>& lineEvaluation = linesEvaluation.find(evaluationId)->second;
929
930 // let's check whether both lines are almost parallel
931 const T absCosineValue = NumericT<T>::abs(lineGroundTruth.direction() * lineEvaluation.direction());
932
933 if (absCosineValue >= perfectMatchAngleThresholdCos)
934 {
935 if (lineGroundTruth.isEqual(lineEvaluation, perfectMatchPixelThreshold))
936 {
937 const T projectedDistancePoint0 = lineGroundTruth.nearestPointOnInfiniteLine(lineEvaluation.point0()).distance(lineEvaluation.point0());
938 const T projectedDistancePoint1 = lineGroundTruth.nearestPointOnInfiniteLine(lineEvaluation.point1()).distance(lineEvaluation.point1());
939
940 const T maximalProjectedDistance = std::max(projectedDistancePoint0, projectedDistancePoint1);
941
942 lineMatch = std::make_shared<PerfectLineMatch>(groundTruthId, evaluationId, NumericT<T>::acos(absCosineValue), maximalProjectedDistance);
943 }
944 }
945 }
946
947 if (!lineMatch)
948 {
949 // we do not have a perfect match, so just a partial match
950
951 const T lengthGroundTruthLine = lineGroundTruth.length();
952 const T lengthMatch = partialMatchUnion.unionSize();
953
954 ocean_assert(NumericT<T>::isNotEqualEps(lengthGroundTruthLine) && NumericT<T>::isNotEqualEps(lengthMatch));
955
956 const T matchCoverage = lengthMatch / lengthGroundTruthLine;
957
958 std::vector<T> angles(targetIds.size());
959 std::vector<T> distances(targetIds.size());
960
961 const FiniteLineT2<T>& groundTruthLine = linesGroundTruth.find(groundTruthId)->second;
962
963 size_t n = 0;
964 for (const Id& targetId : targetIds)
965 {
966 const FiniteLineT2<T>& evaluationLine = linesEvaluation.find(targetId)->second;
967
968 determineSimilarity(groundTruthLine, evaluationLine, DM_PROJECTED_ONTO_EACH_OTHER, angles[n], distances[n]);
969 ++n;
970 }
971
972 const double medianAngle = double(Median::median<T>(angles.data(), angles.size()));
973 const double medianDistance = double(Median::median<T>(distances.data(), distances.size()));
974
975 lineMatch = std::make_shared<PartialLineMatch>(groundTruthId, std::move(targetIds), matchCoverage, medianAngle, medianDistance);
976 }
977
978 lineMatches.insert(std::make_pair(groundTruthId, lineMatch));
979 }
980 else
981 {
982 ocean_assert(targetIds.empty());
983 }
984 }
985 }
986
987 ocean_assert(lineMatches.size() <= linesGroundTruth.size());
988
989 return lineMatches;
990}
991
992template <typename T>
993bool LineEvaluator::evaluateLineMatches(const std::unordered_map<Id, FiniteLineT2<T>>& linesGroundTruth, const std::unordered_map<Id, FiniteLineT2<T>>& linesEvaluation, const LineMatchMap& lineMatches, double& coverage, double& medianAngle, double& medianDistance, size_t& countPerfectMatches, size_t& countPartialMatches, size_t& countComplexMatches, size_t& notCoveredGroundTruthLines, size_t& notCoveredEvaluationLines, IdSet* notCoveredGroundTruthLineIds, IdSet* notCoveredEvaluationLineIds)
994{
995 if (lineMatches.empty())
996 {
997 return false;
998 }
999
1000 ocean_assert(lineMatches.size() <= linesGroundTruth.size());
1001 if (lineMatches.size() > linesGroundTruth.size())
1002 {
1003 return false;
1004 }
1005
1006 double sumLengthGroundTruth = 0.0;
1007
1008 for (typename std::unordered_map<Id, FiniteLineT2<T>>::const_iterator iG = linesGroundTruth.cbegin(); iG != linesGroundTruth.cend(); ++iG)
1009 {
1010 sumLengthGroundTruth += double(iG->second.length());
1011 }
1012
1013 ocean_assert(sumLengthGroundTruth > 0.0);
1014 if (sumLengthGroundTruth <= 0.0)
1015 {
1016 return false;
1017 }
1018
1019 countPerfectMatches = 0;
1020 countPartialMatches = 0;
1021 countComplexMatches = 0;
1022
1023 double sumLengthMatches = 0.0;
1024
1025 std::vector<double> angles;
1026 angles.reserve(lineMatches.size());
1027
1028 std::vector<double> distances;
1029 distances.reserve(lineMatches.size());
1030
1031 IdSet coveredEvaluationLineIds;
1032
1033 for (typename LineMatchMap::const_iterator iMatch = lineMatches.cbegin(); iMatch != lineMatches.cend(); ++iMatch)
1034 {
1035 const LineMatchRef& match = iMatch->second;
1036 ocean_assert(match);
1037
1038 const typename std::unordered_map<Id, FiniteLineT2<T>>::const_iterator iG = linesGroundTruth.find(match->sourceId());
1039
1040 ocean_assert(iG != linesGroundTruth.cend());
1041 if (iG == linesGroundTruth.cend())
1042 {
1043 return false;
1044 }
1045
1046 const FiniteLineT2<T>& groundTruthLine = iG->second;
1047 ocean_assert(groundTruthLine.isValid());
1048
1049 const double lengthGroundTruth = double(groundTruthLine.length());
1050
1051 switch (match->matchType())
1052 {
1054 {
1055 const PerfectLineMatch& perfectMatch = dynamic_cast<const PerfectLineMatch&>(*match);
1056
1057 sumLengthMatches += double(lengthGroundTruth);
1058 angles.push_back(perfectMatch.angle());
1059 distances.push_back(perfectMatch.maximalDistance());
1060
1061 coveredEvaluationLineIds.insert(perfectMatch.targetId());
1062
1063 countPerfectMatches++;
1064
1065 break;
1066 }
1067
1069 {
1070 const PartialLineMatch& partialMatch = dynamic_cast<const PartialLineMatch&>(*match);
1071
1072 sumLengthMatches += partialMatch.coverage() * lengthGroundTruth;
1073 angles.push_back(partialMatch.medianAngle());
1074 distances.push_back(partialMatch.medianDistance());
1075
1076 coveredEvaluationLineIds.insert(partialMatch.targetIds().cbegin(), partialMatch.targetIds().cend());
1077
1078 countPartialMatches++;
1079
1080 break;
1081 }
1082
1084 {
1085 const ComplexLineMatch& complexMatch = dynamic_cast<const ComplexLineMatch&>(*match);
1086
1087 sumLengthMatches += complexMatch.coverage() * lengthGroundTruth;
1088 angles.push_back(complexMatch.medianAngle());
1089 distances.push_back(complexMatch.medianDistance());
1090
1091 coveredEvaluationLineIds.insert(complexMatch.targetIds().cbegin(), complexMatch.targetIds().cend());
1092
1093 countComplexMatches++;
1094
1095 break;
1096 }
1097
1098 default:
1099 ocean_assert(false && "Invalid type!");
1100 return false;
1101 }
1102 }
1103
1104 ocean_assert(NumericT<T>::isNotEqualEps(sumLengthGroundTruth));
1105 coverage = sumLengthMatches / sumLengthGroundTruth;
1106
1107 medianAngle = Median::median(angles.data(), angles.size());
1108 medianDistance = Median::median(distances.data(), distances.size());
1109
1110 ocean_assert(coveredEvaluationLineIds.size() <= linesEvaluation.size());
1111 if (coveredEvaluationLineIds.size() > linesEvaluation.size())
1112 {
1113 return false;
1114 }
1115
1116 notCoveredGroundTruthLines = linesGroundTruth.size() - lineMatches.size();
1117
1118 notCoveredEvaluationLines = linesEvaluation.size() - coveredEvaluationLineIds.size();
1119
1120 if (notCoveredGroundTruthLineIds)
1121 {
1122 notCoveredGroundTruthLineIds->clear();
1123
1124 for (typename std::unordered_map<Id, FiniteLineT2<T>>::const_iterator iG = linesGroundTruth.cbegin(); iG != linesGroundTruth.cend(); ++iG)
1125 {
1126 if (lineMatches.find(iG->first) == lineMatches.cend())
1127 {
1128 notCoveredGroundTruthLineIds->insert(iG->first);
1129 }
1130 }
1131 }
1132
1133 if (notCoveredEvaluationLineIds)
1134 {
1135 notCoveredEvaluationLineIds->clear();
1136
1137 for (typename std::unordered_map<Id, FiniteLineT2<T>>::const_iterator iE = linesEvaluation.cbegin(); iE != linesEvaluation.cend(); ++iE)
1138 {
1139 if (coveredEvaluationLineIds.find(iE->first) == coveredEvaluationLineIds.cend())
1140 {
1141 notCoveredEvaluationLineIds->insert(iE->first);
1142 }
1143 }
1144 }
1145
1146 return true;
1147}
1148
1149template <typename T>
1150SegmentUnion<T> LineEvaluator::determineProjectedSegmentUnion(const FiniteLineT2<T>& lineOfInterest, const IdSet& lineIdsToProject, const std::unordered_map<Id, FiniteLineT2<T>>& lines)
1151{
1152 ocean_assert(lineOfInterest.isValid());
1153 ocean_assert(!lineIdsToProject.empty());
1154
1155 SegmentUnion<T> segmentUnion;
1156
1157 for (const Id& lineIdToProject : lineIdsToProject)
1158 {
1159 ocean_assert(lines.find(lineIdToProject) != lines.cend());
1160
1161 const FiniteLineT2<T>& lineToProject = lines.find(lineIdToProject)->second;
1162 ocean_assert(lineToProject.isValid());
1163
1164 T locationOnLineOfInterest0;
1165 lineOfInterest.nearestPointOnInfiniteLine(lineToProject.point0(), nullptr, &locationOnLineOfInterest0);
1166
1167 T locationOnLineOfInterest1;
1168 lineOfInterest.nearestPointOnInfiniteLine(lineToProject.point1(), nullptr, &locationOnLineOfInterest1);
1169
1170 Utilities::sortLowestToFront2(locationOnLineOfInterest0, locationOnLineOfInterest1);
1171
1172 segmentUnion.addSegment(locationOnLineOfInterest0, locationOnLineOfInterest1);
1173 }
1174
1175 return segmentUnion;
1176}
1177
1178template <typename T>
1179LineEvaluator::LineMatchRef LineEvaluator::determineComplexMatch(const std::unordered_map<Id, FiniteLineT2<T>>& linesGroundTruth, const std::unordered_map<Id, FiniteLineT2<T>>& linesEvaluation, const IdToIdSetMap& groundTruthToEvaluationMap, const IdToIdSetMap evaluationToGroundTruthMap, const Id groundTruthId, const Id64Set& groundTruthEvaluationSet, const T complexMatchMaximalGapPixelThreshold)
1180{
1181 ocean_assert(complexMatchMaximalGapPixelThreshold >= 0);
1182
1183 /*
1184 * A complex match between lines is given whenever we do not have one or several evaluation line(s) for a ground truth line:
1185 *
1186 * A valid complex match:
1187 * ground truth lines: +++++++++++ ++++++++++++++++++++++++++++++ ++++++++++++
1188 * evaluation lines: ------------------------ -----------------------------
1189 *
1190 * An invalid complex match
1191 * ground truth lines: +++++++++++ ++++++++++++++++++++++++++++++ ++++++++++++
1192 * evaluation lines: ------------------------------ -----------------------------
1193 * ^^^^^^^^^
1194 * (out-of-boundary to large)
1195 *
1196 * Thus, we have to determine all sibling ground truth lines (connected via evaluation lines).
1197 * Afterwards, we can determine the coverage of ground truth line (based on individual evaluation lines)
1198 */
1199
1200 // first we gather all ground truth lines and all evaluation lines which are connected (almost similar and partially overlapping)
1201 // we start at the ground truth line of interest, determine all connected evaluation lines
1202 // for each evaluation line, we determine connected ground truth line and restart the process for (new) ground truth lines
1203
1204 IdSet connectedGroundTruthIds;
1205 IdSet connectedEvaluationIds;
1206
1207 std::vector<Id> groundTruthIdStack(1, groundTruthId);
1208
1209 while (!groundTruthIdStack.empty())
1210 {
1211 const Id currentGroundTruthId = groundTruthIdStack.back();
1212 groundTruthIdStack.pop_back();
1213
1214 connectedGroundTruthIds.insert(currentGroundTruthId);
1215
1216 const IdToIdSetMap::const_iterator iMappingsG2E = groundTruthToEvaluationMap.find(currentGroundTruthId);
1217
1218 if (iMappingsG2E != groundTruthToEvaluationMap.cend())
1219 {
1220 for (const Id& connectedEvaluationId : iMappingsG2E->second)
1221 {
1222 const IdSet::const_iterator iConnectedEvaluation = connectedEvaluationIds.find(connectedEvaluationId);
1223
1224 if (iConnectedEvaluation == connectedEvaluationIds.cend())
1225 {
1226 connectedEvaluationIds.insert(connectedEvaluationId);
1227
1228 const IdToIdSetMap::const_iterator iMappingsE2G = evaluationToGroundTruthMap.find(connectedEvaluationId);
1229
1230 if (iMappingsE2G != evaluationToGroundTruthMap.cend())
1231 {
1232 for (const Id& connectedGroundTruthId : iMappingsE2G->second)
1233 {
1234 if (connectedGroundTruthIds.find(connectedGroundTruthId) == connectedGroundTruthIds.cend())
1235 {
1236 groundTruthIdStack.push_back(connectedGroundTruthId);
1237 }
1238 }
1239 }
1240 }
1241 }
1242 }
1243 }
1244
1245 // no we have all lines that are connected with each other (sibling ground truth lines, and sibling evaluation lines)
1246 // we need to determine all evaluation lines which are invalid: evaluation lines not fully covered by a corresponding ground truth line
1247
1248 IdSet validEvaluationIds;
1249
1250 for (const Id& connectedEvaluationId : connectedEvaluationIds)
1251 {
1252 const FiniteLineT2<T>& connectedEvaluationLine = linesEvaluation.find(connectedEvaluationId)->second;
1253
1254 const SegmentUnion<T> connectedEvaluationUnion = determineProjectedSegmentUnion<T>(connectedEvaluationLine, connectedGroundTruthIds, linesGroundTruth);
1255
1256 const T lengthConnectedEvaluationLine = connectedEvaluationLine.length();
1257 const SegmentUnion<T> clampedConnectedEvaluationUnion = connectedEvaluationUnion.intersection(0, lengthConnectedEvaluationLine);
1258
1259 if (clampedConnectedEvaluationUnion)
1260 {
1261 const T frontGap = clampedConnectedEvaluationUnion.segments().begin()->first;
1262 ocean_assert(frontGap >= T(0));
1263
1264 const T backGap = lengthConnectedEvaluationLine - clampedConnectedEvaluationUnion.segments().rbegin()->second;
1265 ocean_assert(backGap >= T(0));
1266
1267 const T maximalGap = std::max(frontGap, std::max(backGap, clampedConnectedEvaluationUnion.maximalGap()));
1268
1269 if (maximalGap < complexMatchMaximalGapPixelThreshold)
1270 {
1271 ocean_assert(validEvaluationIds.find(connectedEvaluationId) == validEvaluationIds.end());
1272 validEvaluationIds.insert(connectedEvaluationId);
1273 }
1274 }
1275 }
1276
1277 // now we can handle our given ground truth line
1278 // we simply determine the coverage based on all valid evaluation ids
1279
1280 // NOTE: we do not use the gathered information for other ground truth lines; however, this could improve performance
1281
1282 const Id& connectedGroundTruthId = groundTruthId;
1283
1284 const FiniteLineT2<T>& connectedGroundTruthLine = linesGroundTruth.find(connectedGroundTruthId)->second;
1285 const T lengthConnectedGroundTruthLine = connectedGroundTruthLine.length();
1286
1287 SegmentUnion<T> connectedPartialMatch;
1288 IdSet connectedTargetIds;
1289
1290 std::vector<T> angles;
1291 angles.reserve(validEvaluationIds.size());
1292
1293 std::vector<T> distances;
1294 distances.reserve(validEvaluationIds.size());
1295
1296 for (const Id& validEvaluationId : validEvaluationIds)
1297 {
1298 const FiniteLineT2<T>& validEvaluationLine = linesEvaluation.find(validEvaluationId)->second;
1299
1300 if (groundTruthEvaluationSet.find(combineIds(connectedGroundTruthId, validEvaluationId)) != groundTruthEvaluationSet.cend())
1301 {
1302 // both lines are connected, so we need to determine the coverage
1303
1304 T locationOnLine0;
1305 connectedGroundTruthLine.nearestPointOnInfiniteLine(validEvaluationLine.point0(), nullptr, &locationOnLine0);
1306
1307 T locationOnLine1;
1308 connectedGroundTruthLine.nearestPointOnInfiniteLine(validEvaluationLine.point1(), nullptr, &locationOnLine1);
1309
1310 Utilities::sortLowestToFront2(locationOnLine0, locationOnLine1);
1311
1312 // as we have a complex matching, we do not consider any coverage outside the ground truth line
1313
1314 locationOnLine0 = minmax(T(0), locationOnLine0, lengthConnectedGroundTruthLine);
1315 locationOnLine1 = minmax(T(0), locationOnLine1, lengthConnectedGroundTruthLine);
1316
1317 ocean_assert(locationOnLine0 <= locationOnLine1);
1318 if (locationOnLine0 < locationOnLine1)
1319 {
1320 connectedPartialMatch.addSegment(locationOnLine0, locationOnLine1);
1321 connectedTargetIds.insert(validEvaluationId);
1322
1323 T angle, distance;
1324 determineSimilarity(connectedGroundTruthLine, validEvaluationLine, DM_PROJECTED_ONTO_EACH_OTHER, angle, distance);
1325
1326 angles.push_back(angle);
1327 distances.push_back(distance);
1328 }
1329 }
1330 }
1331
1332 if (connectedPartialMatch)
1333 {
1334 ocean_assert(!connectedTargetIds.empty());
1335
1336 const T lengthMatch = connectedPartialMatch.unionSize();
1337
1338 ocean_assert(NumericT<T>::isNotEqualEps(lengthConnectedGroundTruthLine) && NumericT<T>::isNotEqualEps(lengthMatch));
1339
1340 const T matchCoverage = lengthMatch / lengthConnectedGroundTruthLine;
1341
1342 const double medianAngle = double(Median::median<T>(angles.data(), angles.size()));
1343 const double medianDistance = double(Median::median<T>(distances.data(), distances.size()));
1344
1345 return std::make_shared<ComplexLineMatch>(connectedGroundTruthId, connectedTargetIds, matchCoverage, medianAngle, medianDistance, connectedGroundTruthIds, validEvaluationIds);
1346 }
1347
1348 return LineMatchRef();
1349}
1350
1351inline unsigned long long LineEvaluator::combineIds(const Id& firstId, const Id& secondId)
1352{
1353 static_assert(sizeof(Id) * 2 == sizeof(unsigned long long), "Invalid data type!");
1354
1355 return (unsigned long long)firstId | ((unsigned long long)secondId << 32ull);
1356}
1357
1358}
1359
1360}
1361
1362}
1363
1364#endif // META_OCEAN_CV_DETECTOR_LINE_EVALUATOR_H
This class implements a complex match between one source line and several target lines.
Definition LineEvaluator.h:271
const IdSet & connectedTargetIds() const
Returns the ids of all sibling/connected target lines which have been investigated during the match c...
Definition LineEvaluator.h:588
const IdSet & connectedSourceIds() const
Returns the ids of all sibling/connected source lines which have been investigated during the match c...
Definition LineEvaluator.h:583
IdSet connectedTargetIds_
The ids of all sibling/connected target lines which have been investigated during the match creation.
Definition LineEvaluator.h:310
ComplexLineMatch(const Id sourceId, const IdSet &targetIds, const double coverage, const double medianAngle, const double medianDistance, const IdSet &connectedSourceIds, const IdSet &connectedTargetIds)
Creates a new match object.
Definition LineEvaluator.h:570
IdSet connectedSourceIds_
The ids of all sibling/connected source lines which have been investigated during the match creation.
Definition LineEvaluator.h:307
MatchType matchType() const override
Returns the type of the match.
Definition LineEvaluator.h:578
This class is the base class for all line matches.
Definition LineEvaluator.h:58
MatchType
Definition of individual match types.
Definition LineEvaluator.h:65
@ MT_INVALID
An invalid type.
Definition LineEvaluator.h:67
@ MT_PARTIAL
A partial match,.
Definition LineEvaluator.h:71
@ MT_PERFECT
A perfect match,.
Definition LineEvaluator.h:69
@ MT_COMPLEX
A complex match,.
Definition LineEvaluator.h:73
Id sourceId_
The id of the source line.
Definition LineEvaluator.h:106
virtual MatchType matchType() const =0
Returns the type of the match.
LineMatch(const Id sourceId)
Creates a new match object.
Definition LineEvaluator.h:476
Id sourceId() const
Returns the id of the source line.
Definition LineEvaluator.h:487
virtual ~LineMatch()
Destructs a match object.
Definition LineEvaluator.h:482
This class implements a partial match between one source line and several target lines.
Definition LineEvaluator.h:191
double medianAngle_
The median angle between the source line and all target lines, in radian, with range [0,...
Definition LineEvaluator.h:253
double coverage() const
The amout the target lines cover the source line.
Definition LineEvaluator.h:555
double medianDistance() const
Returns the median distance between the source line and all target lines.
Definition LineEvaluator.h:565
const IdSet & targetIds() const
Returns the ids of all target lines belonging to the partial match.
Definition LineEvaluator.h:550
PartialLineMatch(const Id sourceId, const IdSet &targetIds, const double coverage, const double medianAngle, const double medianDistance)
Creates a new partial match object.
Definition LineEvaluator.h:521
double coverage_
The coverage of the source line (the amout the target lines cover the source line),...
Definition LineEvaluator.h:250
MatchType matchType() const override
Returns the type of the match.
Definition LineEvaluator.h:545
double medianDistance_
The median distance between the source line and all target lines, with range [0, infinity)
Definition LineEvaluator.h:256
IdSet targetIds_
The ids of all target lines.
Definition LineEvaluator.h:247
double medianAngle() const
Returns the median angle between the source line and all target lines.
Definition LineEvaluator.h:560
This class implements a perfect match between a source line and a target line.
Definition LineEvaluator.h:132
Id targetId() const
Returns the id of the target line.
Definition LineEvaluator.h:506
PerfectLineMatch(const Id sourceId, const Id targetId, const double angle, const double maximalDistance)
Creates a new match object.
Definition LineEvaluator.h:492
unsigned int targetId_
The id of the target line.
Definition LineEvaluator.h:171
double angle_
The absolute angle between both lines, with range [0, PI/2].
Definition LineEvaluator.h:174
double maximalDistance_
The maximal distance between infinite source line and target line, with range [0, infinity)
Definition LineEvaluator.h:177
double angle() const
Returns the angle between the source and the target line.
Definition LineEvaluator.h:511
MatchType matchType() const override
Returns the type of the match.
Definition LineEvaluator.h:501
double maximalDistance() const
Returns maximal distance between infinite source line and target line.
Definition LineEvaluator.h:516
This class implements an evalutator for line segments.
Definition LineEvaluator.h:35
static LineMatchRef determineComplexMatch(const std::unordered_map< Id, FiniteLineT2< T > > &linesGroundTruth, const std::unordered_map< Id, FiniteLineT2< T > > &linesEvaluation, const IdToIdSetMap &groundTruthToEvaluationMap, const IdToIdSetMap evaluationToGroundTruthMap, const Id groundTruthId, const Id64Set &groundTruthEvaluationSet, const T complexMatchMaximalGapPixelThreshold)
Determines a complex match for a given ground truth line.
Definition LineEvaluator.h:1179
std::unordered_map< Id, IdSet > IdToIdSetMap
Definition of an unordered map mapping ids to sets of ids.
Definition LineEvaluator.h:51
DistanceMeasure
Definition of individual strategies to determine the distance between two line segments.
Definition LineEvaluator.h:317
@ DM_PROJECTED_ONTO_GROUND_TRUTH
The end points of the evaluation line are projected onto the infinite ground truth line,...
Definition LineEvaluator.h:324
@ DM_PROJECTED_ONTO_EVALUATION_LINE
The end points of the ground truth lines are projected onto the infinite evaluation line,...
Definition LineEvaluator.h:332
@ DM_PROJECTED_ONTO_EACH_OTHER
This measure combines 'DM_PROJECTED_ONTO_GROUND_TRUTH' and 'DM_PROJECTED_ONTO_EVALUATION_LINE'.
Definition LineEvaluator.h:339
std::unordered_set< Id > IdSet
Definition of an unordered set of ids.
Definition LineEvaluator.h:46
static SegmentUnion< T > determineProjectedSegmentUnion(const FiniteLineT2< T > &lineOfInterest, const IdSet &lineIdsToProject, const std::unordered_map< Id, FiniteLineT2< T > > &lines)
Determines the union of segments resulting from projecting several finite lines onto a unique finite ...
Definition LineEvaluator.h:1150
unsigned int Id
Definition of an id identifying e.g., a specific line.
Definition LineEvaluator.h:41
static unsigned long long combineIds(const Id &firstId, const Id &secondId)
Combines two (32 bit) ids to one 64 bit value.
Definition LineEvaluator.h:1351
std::shared_ptr< LineMatch > LineMatchRef
Definition of a shared pointer for a LineMatch object.
Definition LineEvaluator.h:113
static void determineSimilarity(const FiniteLineT2< T > &lineGroundTruth, const FiniteLineT2< T > &lineEvaluation, const DistanceMeasure distanceMeasure, T &angle, T &distance)
Determines the similarity between two lines known to be overlapping.
Definition LineEvaluator.h:762
static void determineOverlappingAmount(const FiniteLineT2< T > &lineGroundTruth, const FiniteLineT2< T > &lineEvaluation, T *projectedLength=nullptr, T *outOfBorderDistance0=nullptr, T *outOfBorderDistance1=nullptr, T *locationOnLine0=nullptr, T *locationOnLine1=nullptr)
Determines the overlapping metrics between two lines that are known to overlapping (and to be similar...
Definition LineEvaluator.h:722
std::unordered_multimap< Id, LineMatchRef > LineMatchMap
Definition of an unordered multi map mapping ids to match objects.
Definition LineEvaluator.h:118
static bool evaluateLineMatches(const std::unordered_map< Id, FiniteLineT2< T > > &linesGroundTruth, const std::unordered_map< Id, FiniteLineT2< T > > &linesEvaluation, const LineMatchMap &lineMatches, double &coverage, double &medianAngle, double &medianDistance, size_t &countPerfectMatches, size_t &countPartialMatches, size_t &countComplexMatches, size_t &notCoveredGroundTruthLines, size_t &notCoveredEvaluationLines, IdSet *notCoveredGroundTruthLineIds=nullptr, IdSet *notCoveredEvaluationLineIds=nullptr)
Evaluates the overall quality of line matches.
Definition LineEvaluator.h:993
static bool areLinesOverlapping(const FiniteLineT2< T > &lineGroundTruth, const FiniteLineT2< T > &lineEvaluation, const T angleThresholdCos, const T distanceThresholdPixels, const DistanceMeasure distanceMeasure, T *projectedLength=nullptr, T *outOfBorderDistance0=nullptr, T *outOfBorderDistance1=nullptr, T *locationOnLine0=nullptr, T *locationOnLine1=nullptr)
Checks whether two given lines are overlapping up to some extend and determines some overlapping metr...
Definition LineEvaluator.h:594
std::unordered_set< unsigned long long > Id64Set
Definition of a set holding a pair of ids.
Definition LineEvaluator.h:347
static LineMatchMap evaluateLineSegments(const std::unordered_map< Id, FiniteLineT2< T > > &linesGroundTruth, const std::unordered_map< Id, FiniteLineT2< T > > &linesEvaluation, const T perfectMatchAngleThreshold=NumericT< T >::deg2rad(2), const T perfectMatchPixelThreshold=T(2), const T matchAngleThreshold=NumericT< T >::deg2rad(5), const T matchCloseToLinePixelThreshold=T(3), const T partialMatchNonOverlappingPixelThreshold=T(25), const T complexMatchMaximalGapPixelThreshold=T(15))
Evaluates two sets of finite lines.
Definition LineEvaluator.h:802
This class implements an finite line in 2D space.
Definition FiniteLine2.h:82
bool isValid() const
Returns whether this line has valid parameters.
Definition FiniteLine2.h:622
bool isEqual(const FiniteLineT2< T > &line, const T &epsilon) const
Returns whether two lines are equal up to a specified epsilon.
Definition FiniteLine2.h:628
T length() const
Returns the length of the finite line.
Definition FiniteLine2.h:403
const VectorT2< T > & direction() const
Returns the direction of the line: normalized(point1() - point0())
Definition FiniteLine2.h:381
const VectorT2< T > & point0() const
Returns the first end point of the line.
Definition FiniteLine2.h:348
VectorT2< T > nearestPointOnInfiniteLine(const VectorT2< T > &point, T *outOfBoundaryDistance=nullptr, T *finiteLineLocation=nullptr) const
Returns the point on the infinite line (defined by this finite line) to an arbitrary given point.
Definition FiniteLine2.h:475
const VectorT2< T > & point1() const
Returns the second end point of the line.
Definition FiniteLine2.h:354
static T median(T *values, const size_t number)
Returns the median value in a given data array.
Definition Median.h:1144
This class provides basic numeric functionalities.
Definition Numeric.h:57
static constexpr bool isInsideRange(const T lower, const T value, const T upper, const T epsilon=NumericT< T >::eps())
Returns whether a value lies between a given range up to a provided epsilon border.
Definition Numeric.h:2872
static constexpr T pi_2()
Returns PI/2 which is equivalent to 90 degree.
Definition Numeric.h:938
static T abs(const T value)
Returns the absolute value of a given value.
Definition Numeric.h:1220
static T sqrt(const T value)
Returns the square root of a given value.
Definition Numeric.h:1533
static T cos(const T value)
Returns the cosine of a given value.
Definition Numeric.h:1584
static T acos(const T value)
Returns the arccosine of a given value.
Definition Numeric.h:2907
static constexpr T maxValue()
Returns the max scalar value.
Definition Numeric.h:3244
This class implements a functionality to determine the union of individual segments.
Definition SegmentUnion.h:39
const SegmentMap & segments() const
Returns the segments of this object.
Definition SegmentUnion.h:570
T maximalGap() const
Returns the maximal gap between all successive segments.
Definition SegmentUnion.h:537
SegmentUnion< T > intersection(const T &startPosition, const T &stopPosition) const
Returns the intersection of this union with a given range (an additional segment).
Definition SegmentUnion.h:379
void addSegment(const T &startPosition, const T &stopPosition)
Adds a new segment to this union object.
Definition SegmentUnion.h:109
T unionSize() const
Returns the sum of all segment sizes.
Definition SegmentUnion.h:578
static void sortLowestToFront2(T &value0, T &value1)
Sorts two values so that the lowest value will finally be the first value.
Definition base/Utilities.h:619
This class implements a vector with two elements.
Definition Vector2.h:96
T distance(const VectorT2< T > &right) const
Returns the distance between this 2D position and a second 2D position.
Definition Vector2.h:639
T sqrDistance(const VectorT2< T > &right) const
Returns the square distance between this 2D position and a second 2D position.
Definition Vector2.h:645
T minmax(const T &lowerBoundary, const T &value, const T &upperBoundary)
This function fits a given parameter into a specified value range.
Definition base/Utilities.h:903
unsigned int sqrDistance(const char first, const char second)
Returns the square distance between two values.
Definition base/Utilities.h:1089
The namespace covering the entire Ocean framework.
Definition Accessor.h:15