ClippingPredictorEvaluator: predictions only match future detections
To focus on the ability to predict clipping, the clipping predictor
evaluator doesn't increment the true positive count anymore when a
prediction is simultaneously observed with a detection.
Note that `WebRTC.Audio.Agc.ClippingPredictor.F1Score` is still used
to log the F1 score - i.e., the histogram hasn't been renamed.
Bug: webrtc:12774
Change-Id: Ia987e568a6df2a3ddba7fa1b5697d6feda22d20c
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/231233
Commit-Queue: Alessio Bazzica <alessiob@webrtc.org>
Reviewed-by: Hanna Silen <silen@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#34942}
diff --git a/modules/audio_processing/agc/agc_manager_direct.cc b/modules/audio_processing/agc/agc_manager_direct.cc
index 22a097e..bda1cae 100644
--- a/modules/audio_processing/agc/agc_manager_direct.cc
+++ b/modules/audio_processing/agc/agc_manager_direct.cc
@@ -629,7 +629,7 @@
frames_since_clipped_ = 0;
if (!!clipping_predictor_) {
clipping_predictor_->Reset();
- clipping_predictor_evaluator_.Reset();
+ clipping_predictor_evaluator_.RemoveExpectations();
}
}
AggregateChannelLevels();
diff --git a/modules/audio_processing/agc/clipping_predictor_evaluator.cc b/modules/audio_processing/agc/clipping_predictor_evaluator.cc
index f211d6e..ed7198d 100644
--- a/modules/audio_processing/agc/clipping_predictor_evaluator.cc
+++ b/modules/audio_processing/agc/clipping_predictor_evaluator.cc
@@ -50,10 +50,6 @@
RTC_DCHECK_LT(ring_buffer_tail_, ring_buffer_capacity_);
DecreaseTimesToLive();
- if (clipping_predicted) {
- // TODO(bugs.webrtc.org/12874): Use designated initializers one fixed.
- Push(/*expected_detection=*/{/*ttl=*/history_size_, /*detected=*/false});
- }
// Clipping is expected if there are expected detections regardless of
// whether all the expected detections have been previously matched - i.e.,
// `ExpectedDetection::detected` is true.
@@ -80,15 +76,29 @@
RTC_DCHECK(!clipping_expected && !clipping_detected);
counters_.true_negatives++;
}
+
+ if (clipping_predicted) {
+ // TODO(bugs.webrtc.org/12874): Use designated initializers one fixed.
+ Push(/*expected_detection=*/{/*ttl=*/history_size_, /*detected=*/false});
+ }
+
return prediction_interval;
}
-void ClippingPredictorEvaluator::Reset() {
+void ClippingPredictorEvaluator::RemoveExpectations() {
// Empty the ring buffer of expected detections.
ring_buffer_tail_ = 0;
ring_buffer_size_ = 0;
}
+void ClippingPredictorEvaluator::Reset() {
+ counters_.true_positives = 0;
+ counters_.true_negatives = 0;
+ counters_.false_positives = 0;
+ counters_.false_negatives = 0;
+ RemoveExpectations();
+}
+
// Cost: O(1).
void ClippingPredictorEvaluator::Push(ExpectedDetection value) {
ring_buffer_[ring_buffer_tail_] = value;
diff --git a/modules/audio_processing/agc/clipping_predictor_evaluator.h b/modules/audio_processing/agc/clipping_predictor_evaluator.h
index 3a4ebd5..348f753 100644
--- a/modules/audio_processing/agc/clipping_predictor_evaluator.h
+++ b/modules/audio_processing/agc/clipping_predictor_evaluator.h
@@ -57,6 +57,9 @@
// Removes any expectation recently set after a call to `Observe()` having
// `clipping_predicted` set to true. Counters won't be reset.
+ void RemoveExpectations();
+
+ // Resets counters and removes any expectation (see `RemoveExpectations()`).
void Reset();
ClippingPredictionCounters counters() const { return counters_; }
diff --git a/modules/audio_processing/agc/clipping_predictor_evaluator_unittest.cc b/modules/audio_processing/agc/clipping_predictor_evaluator_unittest.cc
index caebe8f..b2d2797 100644
--- a/modules/audio_processing/agc/clipping_predictor_evaluator_unittest.cc
+++ b/modules/audio_processing/agc/clipping_predictor_evaluator_unittest.cc
@@ -34,12 +34,14 @@
constexpr bool kPredicted = true;
constexpr bool kNotPredicted = false;
-int SumTrueFalsePositivesNegatives(
- const ClippingPredictorEvaluator& evaluator) {
- return evaluator.counters().true_positives +
- evaluator.counters().true_negatives +
- evaluator.counters().false_positives +
- evaluator.counters().false_negatives;
+ClippingPredictionCounters operator-(const ClippingPredictionCounters& lhs,
+ const ClippingPredictionCounters& rhs) {
+ return {
+ lhs.true_positives - rhs.true_positives,
+ lhs.true_negatives - rhs.true_negatives,
+ lhs.false_positives - rhs.false_positives,
+ lhs.false_negatives - rhs.false_negatives,
+ };
}
// Checks the metrics after init - i.e., no call to `Observe()`.
@@ -69,21 +71,19 @@
for (int i = 0; i < kNumCalls; ++i) {
SCOPED_TRACE(i);
// Read metrics before `Observe()` is called.
- const int last_tp = evaluator.counters().true_positives;
- const int last_tn = evaluator.counters().true_negatives;
- const int last_fp = evaluator.counters().false_positives;
- const int last_fn = evaluator.counters().false_negatives;
+ const auto pre = evaluator.counters();
// `Observe()` a random observation.
bool clipping_detected = random_generator.Rand<bool>();
bool clipping_predicted = random_generator.Rand<bool>();
evaluator.Observe(clipping_detected, clipping_predicted);
// Check that at most one metric has changed.
+ const auto post = evaluator.counters();
int num_changes = 0;
- num_changes += last_tp == evaluator.counters().true_positives ? 0 : 1;
- num_changes += last_tn == evaluator.counters().true_negatives ? 0 : 1;
- num_changes += last_fp == evaluator.counters().false_positives ? 0 : 1;
- num_changes += last_fn == evaluator.counters().false_negatives ? 0 : 1;
+ num_changes += pre.true_positives == post.true_positives ? 0 : 1;
+ num_changes += pre.true_negatives == post.true_negatives ? 0 : 1;
+ num_changes += pre.false_positives == post.false_positives ? 0 : 1;
+ num_changes += pre.false_negatives == post.false_negatives ? 0 : 1;
EXPECT_GE(num_changes, 0);
EXPECT_LE(num_changes, 1);
}
@@ -99,20 +99,18 @@
for (int i = 0; i < kNumCalls; ++i) {
SCOPED_TRACE(i);
// Read metrics before `Observe()` is called.
- const int last_tp = evaluator.counters().true_positives;
- const int last_tn = evaluator.counters().true_negatives;
- const int last_fp = evaluator.counters().false_positives;
- const int last_fn = evaluator.counters().false_negatives;
+ const auto pre = evaluator.counters();
// `Observe()` a random observation.
bool clipping_detected = random_generator.Rand<bool>();
bool clipping_predicted = random_generator.Rand<bool>();
evaluator.Observe(clipping_detected, clipping_predicted);
// Check that metrics are weakly monotonic.
- EXPECT_GE(evaluator.counters().true_positives, last_tp);
- EXPECT_GE(evaluator.counters().true_negatives, last_tn);
- EXPECT_GE(evaluator.counters().false_positives, last_fp);
- EXPECT_GE(evaluator.counters().false_negatives, last_fn);
+ const auto post = evaluator.counters();
+ EXPECT_GE(post.true_positives, pre.true_positives);
+ EXPECT_GE(post.true_negatives, pre.true_negatives);
+ EXPECT_GE(post.false_positives, pre.false_positives);
+ EXPECT_GE(post.false_negatives, pre.false_negatives);
}
}
@@ -126,23 +124,20 @@
for (int i = 0; i < kNumCalls; ++i) {
SCOPED_TRACE(i);
// Read metrics before `Observe()` is called.
- const int last_tp = evaluator.counters().true_positives;
- const int last_tn = evaluator.counters().true_negatives;
- const int last_fp = evaluator.counters().false_positives;
- const int last_fn = evaluator.counters().false_negatives;
+ const auto pre = evaluator.counters();
// `Observe()` a random observation.
bool clipping_detected = random_generator.Rand<bool>();
bool clipping_predicted = random_generator.Rand<bool>();
evaluator.Observe(clipping_detected, clipping_predicted);
+ const auto diff = evaluator.counters() - pre;
// Check that TPs grow by at most `history_size() + 1`. Such an upper bound
// is reached when multiple predictions are matched by a single detection.
- EXPECT_LE(evaluator.counters().true_positives - last_tp,
- history_size() + 1);
- // Check that TNs, FPs and FNs grow by at most one. `max_growth`.
- EXPECT_LE(evaluator.counters().true_negatives - last_tn, 1);
- EXPECT_LE(evaluator.counters().false_positives - last_fp, 1);
- EXPECT_LE(evaluator.counters().false_negatives - last_fn, 1);
+ EXPECT_LE(diff.true_positives, history_size() + 1);
+ // Check that TNs, FPs and FNs grow by at most one.
+ EXPECT_LE(diff.true_negatives, 1);
+ EXPECT_LE(diff.false_positives, 1);
+ EXPECT_LE(diff.false_negatives, 1);
}
}
@@ -180,90 +175,144 @@
::testing::Combine(::testing::Values(4, 8, 15, 16, 23, 42),
::testing::Values(1, 10, 21)));
-// Checks that, observing a detection and a prediction after init, produces a
-// true positive.
-TEST(ClippingPredictionEvalTest, OneTruePositiveAfterInit) {
+// Checks that after initialization, when no detection is expected,
+// observing no detection and no prediction produces a true negative.
+TEST(ClippingPredictionEvalTest, TrueNegativeWithNoDetectNoPredictAfterInit) {
ClippingPredictorEvaluator evaluator(/*history_size=*/3);
- evaluator.Observe(kDetected, kPredicted);
- EXPECT_EQ(evaluator.counters().true_positives, 1);
- EXPECT_EQ(evaluator.counters().true_negatives, 0);
- EXPECT_EQ(evaluator.counters().false_positives, 0);
- EXPECT_EQ(evaluator.counters().false_negatives, 0);
-}
-
-// Checks that, observing a detection but no prediction after init, produces a
-// false negative.
-TEST(ClippingPredictionEvalTest, OneFalseNegativeAfterInit) {
- ClippingPredictorEvaluator evaluator(/*history_size=*/3);
- evaluator.Observe(kDetected, kNotPredicted);
- EXPECT_EQ(evaluator.counters().false_negatives, 1);
-
+ evaluator.Observe(kNotDetected, kNotPredicted);
EXPECT_EQ(evaluator.counters().true_positives, 0);
- EXPECT_EQ(evaluator.counters().true_negatives, 0);
- EXPECT_EQ(evaluator.counters().false_positives, 0);
-}
-
-// Checks that, observing no detection but a prediction after init, produces a
-// false positive after expiration.
-TEST(ClippingPredictionEvalTest, OneFalsePositiveAfterInit) {
- ClippingPredictorEvaluator evaluator(/*history_size=*/3);
- evaluator.Observe(kNotDetected, kPredicted);
- EXPECT_EQ(evaluator.counters().false_positives, 0);
- evaluator.Observe(kNotDetected, kNotPredicted);
- evaluator.Observe(kNotDetected, kNotPredicted);
- evaluator.Observe(kNotDetected, kNotPredicted);
- EXPECT_EQ(evaluator.counters().false_positives, 1);
-
- EXPECT_EQ(evaluator.counters().true_positives, 0);
- EXPECT_EQ(evaluator.counters().true_negatives, 0);
- EXPECT_EQ(evaluator.counters().false_negatives, 0);
-}
-
-// Checks that, observing no detection and no prediction after init, produces a
-// true negative.
-TEST(ClippingPredictionEvalTest, OneTrueNegativeAfterInit) {
- ClippingPredictorEvaluator evaluator(/*history_size=*/3);
- evaluator.Observe(kNotDetected, kNotPredicted);
EXPECT_EQ(evaluator.counters().true_negatives, 1);
-
- EXPECT_EQ(evaluator.counters().true_positives, 0);
EXPECT_EQ(evaluator.counters().false_positives, 0);
EXPECT_EQ(evaluator.counters().false_negatives, 0);
}
+// Checks that after initialization, when no detection is expected,
+// observing no detection and prediction produces a true negative.
+TEST(ClippingPredictionEvalTest, TrueNegativeWithNoDetectPredictAfterInit) {
+ ClippingPredictorEvaluator evaluator(/*history_size=*/3);
+
+ evaluator.Observe(kNotDetected, kPredicted);
+ EXPECT_EQ(evaluator.counters().true_positives, 0);
+ EXPECT_EQ(evaluator.counters().true_negatives, 1);
+ EXPECT_EQ(evaluator.counters().false_positives, 0);
+ EXPECT_EQ(evaluator.counters().false_negatives, 0);
+}
+
+// Checks that after initialization, when no detection is expected,
+// observing a detection and no prediction produces a false negative.
+TEST(ClippingPredictionEvalTest, FalseNegativeWithDetectNoPredictAfterInit) {
+ ClippingPredictorEvaluator evaluator(/*history_size=*/3);
+
+ evaluator.Observe(kDetected, kNotPredicted);
+ EXPECT_EQ(evaluator.counters().true_positives, 0);
+ EXPECT_EQ(evaluator.counters().true_negatives, 0);
+ EXPECT_EQ(evaluator.counters().false_positives, 0);
+ EXPECT_EQ(evaluator.counters().false_negatives, 1);
+}
+
+// Checks that after initialization, when no detection is expected,
+// simultaneously observing a detection and a prediction produces a false
+// negative.
+TEST(ClippingPredictionEvalTest, FalseNegativeWithDetectPredictAfterInit) {
+ ClippingPredictorEvaluator evaluator(/*history_size=*/3);
+
+ evaluator.Observe(kDetected, kPredicted);
+ EXPECT_EQ(evaluator.counters().true_positives, 0);
+ EXPECT_EQ(evaluator.counters().true_negatives, 0);
+ EXPECT_EQ(evaluator.counters().false_positives, 0);
+ EXPECT_EQ(evaluator.counters().false_negatives, 1);
+}
+
+// Checks that, after removing existing expectations, observing no detection and
+// no prediction produces a true negative.
+TEST(ClippingPredictionEvalTest,
+ TrueNegativeWithNoDetectNoPredictAfterRemoveExpectations) {
+ ClippingPredictorEvaluator evaluator(/*history_size=*/3);
+
+ // Set an expectation, then remove it.
+ evaluator.Observe(kNotDetected, kPredicted);
+ evaluator.RemoveExpectations();
+ const auto pre = evaluator.counters();
+
+ evaluator.Observe(kNotDetected, kNotPredicted);
+ const auto diff = evaluator.counters() - pre;
+ EXPECT_EQ(diff.true_positives, 0);
+ EXPECT_EQ(diff.true_negatives, 1);
+ EXPECT_EQ(diff.false_positives, 0);
+ EXPECT_EQ(diff.false_negatives, 0);
+}
+
+// Checks that, after removing existing expectations, observing no detection and
+// a prediction produces a true negative.
+TEST(ClippingPredictionEvalTest,
+ TrueNegativeWithNoDetectPredictAfterRemoveExpectations) {
+ ClippingPredictorEvaluator evaluator(/*history_size=*/3);
+
+ // Set an expectation, then remove it.
+ evaluator.Observe(kNotDetected, kPredicted);
+ evaluator.RemoveExpectations();
+ const auto pre = evaluator.counters();
+
+ evaluator.Observe(kNotDetected, kPredicted);
+ const auto diff = evaluator.counters() - pre;
+ EXPECT_EQ(diff.true_positives, 0);
+ EXPECT_EQ(diff.true_negatives, 1);
+ EXPECT_EQ(diff.false_positives, 0);
+ EXPECT_EQ(diff.false_negatives, 0);
+}
+
+// Checks that, after removing existing expectations, observing a detection and
+// no prediction produces a false negative.
+TEST(ClippingPredictionEvalTest,
+ FalseNegativeWithDetectNoPredictAfterRemoveExpectations) {
+ ClippingPredictorEvaluator evaluator(/*history_size=*/3);
+
+ // Set an expectation, then remove it.
+ evaluator.Observe(kNotDetected, kPredicted);
+ evaluator.RemoveExpectations();
+ const auto pre = evaluator.counters();
+
+ evaluator.Observe(kDetected, kNotPredicted);
+ const auto diff = evaluator.counters() - pre;
+ EXPECT_EQ(diff.true_positives, 0);
+ EXPECT_EQ(diff.true_negatives, 0);
+ EXPECT_EQ(diff.false_positives, 0);
+ EXPECT_EQ(diff.false_negatives, 1);
+}
+
+// Checks that, after removing existing expectations, simultaneously observing a
+// detection and a prediction produces a false negative.
+TEST(ClippingPredictionEvalTest,
+ FalseNegativeWithDetectPredictAfterRemoveExpectations) {
+ ClippingPredictorEvaluator evaluator(/*history_size=*/3);
+
+ // Set an expectation, then remove it.
+ evaluator.Observe(kNotDetected, kPredicted);
+ evaluator.RemoveExpectations();
+ const auto pre = evaluator.counters();
+
+ evaluator.Observe(kDetected, kPredicted);
+ const auto diff = evaluator.counters() - pre;
+ EXPECT_EQ(diff.false_negatives, 1);
+ EXPECT_EQ(diff.true_positives, 0);
+ EXPECT_EQ(diff.true_negatives, 0);
+ EXPECT_EQ(diff.false_positives, 0);
+}
+
// Checks that the evaluator detects true negatives when clipping is neither
// predicted nor detected.
-TEST(ClippingPredictionEvalTest, NeverDetectedAndNotPredicted) {
+TEST(ClippingPredictionEvalTest, TrueNegativesWhenNeverDetectedOrPredicted) {
ClippingPredictorEvaluator evaluator(/*history_size=*/3);
evaluator.Observe(kNotDetected, kNotPredicted);
evaluator.Observe(kNotDetected, kNotPredicted);
evaluator.Observe(kNotDetected, kNotPredicted);
evaluator.Observe(kNotDetected, kNotPredicted);
EXPECT_EQ(evaluator.counters().true_negatives, 4);
-
- EXPECT_EQ(evaluator.counters().true_positives, 0);
- EXPECT_EQ(evaluator.counters().false_positives, 0);
- EXPECT_EQ(evaluator.counters().false_negatives, 0);
}
-// Checks that the evaluator detects a false negative when clipping is detected
-// but not predicted.
-TEST(ClippingPredictionEvalTest, DetectedButNotPredicted) {
- ClippingPredictorEvaluator evaluator(/*history_size=*/3);
- evaluator.Observe(kNotDetected, kNotPredicted);
- evaluator.Observe(kNotDetected, kNotPredicted);
- evaluator.Observe(kNotDetected, kNotPredicted);
- evaluator.Observe(kDetected, kNotPredicted);
- EXPECT_EQ(evaluator.counters().false_negatives, 1);
-
- EXPECT_EQ(evaluator.counters().true_positives, 0);
- EXPECT_EQ(evaluator.counters().true_negatives, 3);
- EXPECT_EQ(evaluator.counters().false_positives, 0);
-}
-
-// Checks that the evaluator does not detect a false positive when clipping is
-// predicted but not detected until the observation period expires.
+// Checks that, until the observation period expires, the evaluator does not
+// count a false positive when clipping is predicted and not detected.
TEST(ClippingPredictionEvalTest, PredictedOnceAndNeverDetectedBeforeDeadline) {
ClippingPredictorEvaluator evaluator(/*history_size=*/3);
evaluator.Observe(kNotDetected, kPredicted);
@@ -272,15 +321,12 @@
evaluator.Observe(kNotDetected, kNotPredicted);
EXPECT_EQ(evaluator.counters().false_positives, 0);
evaluator.Observe(kNotDetected, kNotPredicted);
- EXPECT_EQ(evaluator.counters().false_positives, 1);
-
EXPECT_EQ(evaluator.counters().true_positives, 0);
- EXPECT_EQ(evaluator.counters().true_negatives, 0);
- EXPECT_EQ(evaluator.counters().false_negatives, 0);
+ EXPECT_EQ(evaluator.counters().false_positives, 1);
}
-// Checks that the evaluator detects a false positive when clipping is predicted
-// but detected after the observation period expires.
+// Checks that, after the observation period expires, the evaluator detects a
+// false positive when clipping is predicted and detected.
TEST(ClippingPredictionEvalTest, PredictedOnceButDetectedAfterDeadline) {
ClippingPredictorEvaluator evaluator(/*history_size=*/3);
evaluator.Observe(kNotDetected, kPredicted);
@@ -288,24 +334,17 @@
evaluator.Observe(kNotDetected, kNotPredicted);
evaluator.Observe(kNotDetected, kNotPredicted);
evaluator.Observe(kDetected, kNotPredicted);
- EXPECT_EQ(evaluator.counters().false_positives, 1);
-
EXPECT_EQ(evaluator.counters().true_positives, 0);
- EXPECT_EQ(evaluator.counters().true_negatives, 0);
- EXPECT_EQ(evaluator.counters().false_negatives, 1);
+ EXPECT_EQ(evaluator.counters().false_positives, 1);
}
// Checks that a prediction followed by a detection counts as true positive.
TEST(ClippingPredictionEvalTest, PredictedOnceAndThenImmediatelyDetected) {
ClippingPredictorEvaluator evaluator(/*history_size=*/3);
evaluator.Observe(kNotDetected, kPredicted);
- EXPECT_EQ(evaluator.counters().false_positives, 0);
evaluator.Observe(kDetected, kNotPredicted);
EXPECT_EQ(evaluator.counters().true_positives, 1);
-
- EXPECT_EQ(evaluator.counters().true_negatives, 0);
EXPECT_EQ(evaluator.counters().false_positives, 0);
- EXPECT_EQ(evaluator.counters().false_negatives, 0);
}
// Checks that a prediction followed by a delayed detection counts as true
@@ -313,15 +352,10 @@
TEST(ClippingPredictionEvalTest, PredictedOnceAndDetectedBeforeDeadline) {
ClippingPredictorEvaluator evaluator(/*history_size=*/3);
evaluator.Observe(kNotDetected, kPredicted);
- EXPECT_EQ(evaluator.counters().false_positives, 0);
evaluator.Observe(kNotDetected, kNotPredicted);
- EXPECT_EQ(evaluator.counters().false_positives, 0);
evaluator.Observe(kDetected, kNotPredicted);
EXPECT_EQ(evaluator.counters().true_positives, 1);
-
- EXPECT_EQ(evaluator.counters().true_negatives, 0);
EXPECT_EQ(evaluator.counters().false_positives, 0);
- EXPECT_EQ(evaluator.counters().false_negatives, 0);
}
// Checks that a prediction followed by a delayed detection counts as true
@@ -329,17 +363,11 @@
TEST(ClippingPredictionEvalTest, PredictedOnceAndDetectedAtDeadline) {
ClippingPredictorEvaluator evaluator(/*history_size=*/3);
evaluator.Observe(kNotDetected, kPredicted);
- EXPECT_EQ(evaluator.counters().false_positives, 0);
evaluator.Observe(kNotDetected, kNotPredicted);
- EXPECT_EQ(evaluator.counters().false_positives, 0);
evaluator.Observe(kNotDetected, kNotPredicted);
- EXPECT_EQ(evaluator.counters().false_positives, 0);
evaluator.Observe(kDetected, kNotPredicted);
EXPECT_EQ(evaluator.counters().true_positives, 1);
-
- EXPECT_EQ(evaluator.counters().true_negatives, 0);
EXPECT_EQ(evaluator.counters().false_positives, 0);
- EXPECT_EQ(evaluator.counters().false_negatives, 0);
}
// Checks that a prediction followed by a multiple adjacent detections within
@@ -352,19 +380,22 @@
// Multiple detections.
evaluator.Observe(kDetected, kNotPredicted);
EXPECT_EQ(evaluator.counters().true_positives, 1);
+ EXPECT_EQ(evaluator.counters().false_negatives, 0);
+ EXPECT_EQ(evaluator.counters().false_positives, 0);
evaluator.Observe(kDetected, kNotPredicted);
EXPECT_EQ(evaluator.counters().true_positives, 1);
+ EXPECT_EQ(evaluator.counters().false_negatives, 0);
+ EXPECT_EQ(evaluator.counters().false_positives, 0);
// A detection outside of the observation period counts as false negative.
evaluator.Observe(kDetected, kNotPredicted);
+ EXPECT_EQ(evaluator.counters().true_positives, 1);
EXPECT_EQ(evaluator.counters().false_negatives, 1);
- EXPECT_EQ(SumTrueFalsePositivesNegatives(evaluator), 2);
-
- EXPECT_EQ(evaluator.counters().true_negatives, 0);
EXPECT_EQ(evaluator.counters().false_positives, 0);
}
-// Checks that a false positive is added when clipping is detected after a too
-// early prediction.
+// Checks that when clipping is predicted multiple times, a prediction that is
+// observed too early counts as a false positive, whereas the other predictions
+// that are matched to a detection count as true positives.
TEST(ClippingPredictionEvalTest,
PredictedMultipleTimesAndDetectedOnceAfterDeadline) {
ClippingPredictorEvaluator evaluator(/*history_size=*/3);
@@ -378,9 +409,8 @@
// The detection above does not match the first prediction because it happened
// after the deadline of the 1st prediction.
EXPECT_EQ(evaluator.counters().false_positives, 1);
-
+ // However, the detection matches all the other predictions.
EXPECT_EQ(evaluator.counters().true_positives, 3);
- EXPECT_EQ(evaluator.counters().true_negatives, 0);
EXPECT_EQ(evaluator.counters().false_negatives, 0);
}
@@ -401,7 +431,6 @@
evaluator.Observe(kNotDetected, kNotPredicted);
EXPECT_EQ(evaluator.counters().true_negatives, true_negatives);
- EXPECT_EQ(evaluator.counters().true_negatives, 0);
EXPECT_EQ(evaluator.counters().false_positives, 0);
EXPECT_EQ(evaluator.counters().false_negatives, 0);
}
@@ -424,7 +453,6 @@
evaluator.Observe(kNotDetected, kNotPredicted);
EXPECT_EQ(evaluator.counters().true_negatives, true_negatives);
- EXPECT_EQ(evaluator.counters().true_negatives, 0);
EXPECT_EQ(evaluator.counters().false_positives, 0);
EXPECT_EQ(evaluator.counters().false_negatives, 0);
}
@@ -440,7 +468,7 @@
evaluator.Observe(kDetected, kNotPredicted); // <-+ <-+
evaluator.Observe(kDetected, kNotPredicted); // <-+
EXPECT_EQ(evaluator.counters().true_positives, 3);
- EXPECT_EQ(evaluator.counters().true_negatives, 0);
+
EXPECT_EQ(evaluator.counters().false_positives, 0);
EXPECT_EQ(evaluator.counters().false_negatives, 0);
}
@@ -456,7 +484,7 @@
evaluator.Observe(kDetected, kNotPredicted); // <-+
evaluator.Observe(kDetected, kNotPredicted); // <-+
EXPECT_EQ(evaluator.counters().true_positives, 2);
- EXPECT_EQ(evaluator.counters().true_negatives, 0);
+
EXPECT_EQ(evaluator.counters().false_positives, 0);
EXPECT_EQ(evaluator.counters().false_negatives, 0);
}
@@ -469,16 +497,16 @@
};
// Checks that the minimum prediction interval is returned if clipping is
-// correctly predicted as soon as detected - i.e., no anticipation.
+// correctly predicted just before clipping is detected - i.e., smallest
+// anticipation.
TEST_P(ClippingPredictorEvaluatorPredictionIntervalParameterization,
MinimumPredictionInterval) {
ClippingPredictorEvaluator evaluator(history_size());
for (int i = 0; i < num_extra_observe_calls(); ++i) {
EXPECT_EQ(evaluator.Observe(kNotDetected, kNotPredicted), absl::nullopt);
}
- absl::optional<int> prediction_interval =
- evaluator.Observe(kDetected, kPredicted);
- EXPECT_THAT(prediction_interval, Optional(Eq(0)));
+ EXPECT_EQ(evaluator.Observe(kNotDetected, kPredicted), absl::nullopt);
+ EXPECT_THAT(evaluator.Observe(kDetected, kNotPredicted), Optional(Eq(1)));
}
// Checks that a prediction interval between the minimum and the maximum is
@@ -493,9 +521,7 @@
EXPECT_EQ(evaluator.Observe(kNotDetected, kPredicted), absl::nullopt);
EXPECT_EQ(evaluator.Observe(kNotDetected, kPredicted), absl::nullopt);
EXPECT_EQ(evaluator.Observe(kNotDetected, kPredicted), absl::nullopt);
- absl::optional<int> prediction_interval =
- evaluator.Observe(kDetected, kPredicted);
- EXPECT_THAT(prediction_interval, Optional(Eq(3)));
+ EXPECT_THAT(evaluator.Observe(kDetected, kNotPredicted), Optional(Eq(3)));
}
// Checks that the maximum prediction interval is returned if clipping is
@@ -509,9 +535,8 @@
for (int i = 0; i < history_size(); ++i) {
EXPECT_EQ(evaluator.Observe(kNotDetected, kPredicted), absl::nullopt);
}
- absl::optional<int> prediction_interval =
- evaluator.Observe(kDetected, kPredicted);
- EXPECT_THAT(prediction_interval, Optional(Eq(history_size())));
+ EXPECT_THAT(evaluator.Observe(kDetected, kNotPredicted),
+ Optional(Eq(history_size())));
}
// Checks that `Observe()` returns the prediction interval as soon as a true
@@ -527,7 +552,7 @@
}
// Observe a detection.
absl::optional<int> prediction_interval =
- evaluator.Observe(kDetected, kPredicted);
+ evaluator.Observe(kDetected, kNotPredicted);
EXPECT_TRUE(prediction_interval.has_value());
// `Observe()` does not return a prediction interval anymore during ongoing
// detections observed while a detection is still expected.
@@ -539,31 +564,36 @@
INSTANTIATE_TEST_SUITE_P(
ClippingPredictionEvalTest,
ClippingPredictorEvaluatorPredictionIntervalParameterization,
- ::testing::Combine(::testing::Values(0, 3, 5), ::testing::Values(7, 11)));
+ ::testing::Combine(::testing::Values(1, 3, 5), ::testing::Values(7, 11)));
-// Checks that, when a detection is expected, the expectation is removed if and
-// only if `Reset()` is called after a prediction is observed.
-TEST(ClippingPredictionEvalTest, NoFalsePositivesAfterReset) {
+// Checks that, when a detection is expected, the expectation is not removed
+// before the detection deadline expires unless `RemoveExpectations()` is
+// called.
+TEST(ClippingPredictionEvalTest, NoFalsePositivesAfterRemoveExpectations) {
constexpr int kHistorySize = 2;
- ClippingPredictorEvaluator with_reset(kHistorySize);
- with_reset.Observe(kNotDetected, kPredicted);
- with_reset.Reset();
- with_reset.Observe(kNotDetected, kNotPredicted);
- with_reset.Observe(kNotDetected, kNotPredicted);
- EXPECT_EQ(with_reset.counters().true_positives, 0);
- EXPECT_EQ(with_reset.counters().true_negatives, 2);
- EXPECT_EQ(with_reset.counters().false_positives, 0);
- EXPECT_EQ(with_reset.counters().false_negatives, 0);
+ // Case 1: `RemoveExpectations()` is NOT called.
+ ClippingPredictorEvaluator e1(kHistorySize);
+ e1.Observe(kNotDetected, kPredicted);
+ ASSERT_EQ(e1.counters().true_negatives, 1);
+ e1.Observe(kNotDetected, kNotPredicted);
+ e1.Observe(kNotDetected, kNotPredicted);
+ EXPECT_EQ(e1.counters().true_positives, 0);
+ EXPECT_EQ(e1.counters().true_negatives, 1);
+ EXPECT_EQ(e1.counters().false_positives, 1);
+ EXPECT_EQ(e1.counters().false_negatives, 0);
- ClippingPredictorEvaluator no_reset(kHistorySize);
- no_reset.Observe(kNotDetected, kPredicted);
- no_reset.Observe(kNotDetected, kNotPredicted);
- no_reset.Observe(kNotDetected, kNotPredicted);
- EXPECT_EQ(no_reset.counters().true_positives, 0);
- EXPECT_EQ(no_reset.counters().true_negatives, 0);
- EXPECT_EQ(no_reset.counters().false_positives, 1);
- EXPECT_EQ(no_reset.counters().false_negatives, 0);
+ // Case 2: `RemoveExpectations()` is called.
+ ClippingPredictorEvaluator e2(kHistorySize);
+ e2.Observe(kNotDetected, kPredicted);
+ ASSERT_EQ(e2.counters().true_negatives, 1);
+ e2.RemoveExpectations();
+ e2.Observe(kNotDetected, kNotPredicted);
+ e2.Observe(kNotDetected, kNotPredicted);
+ EXPECT_EQ(e2.counters().true_positives, 0);
+ EXPECT_EQ(e2.counters().true_negatives, 3);
+ EXPECT_EQ(e2.counters().false_positives, 0);
+ EXPECT_EQ(e2.counters().false_negatives, 0);
}
class ComputeClippingPredictionMetricsParameterization