#include #include #include #include #include #include "node.h" #include "utils/simple_json.h" #define private public #undef REGISTER_NODE #define REGISTER_NODE(NodeClass, NodeTypeStr) #include "../plugins/alarm/alarm_node.cpp" namespace rk3588 { namespace { FaceRecogItem MakeKnownFace(int track_id, int person_id, const std::string& name, float best_sim) { FaceRecogItem item; item.person_track_id = track_id; item.best_person_id = person_id; item.best_name = name; item.candidate_person_id = person_id; item.candidate_name = name; item.best_sim = best_sim; item.second_sim = 0.20f; item.state = FaceRecogState::Known; return item; } FaceRecogItem MakeUncertainFace(int track_id, int candidate_person_id, const std::string& candidate_name, float best_sim) { FaceRecogItem item; item.person_track_id = track_id; item.candidate_person_id = candidate_person_id; item.candidate_name = candidate_name; item.best_sim = best_sim; item.second_sim = best_sim - 0.02f; item.state = FaceRecogState::Uncertain; return item; } FramePtr MakeFaceFrame(uint64_t frame_id, const FaceRecogItem& item, int img_w = 100, int img_h = 100) { auto frame = std::make_shared(); frame->frame_id = frame_id; frame->width = img_w; frame->height = img_h; frame->face_recog = std::make_shared(); frame->face_recog->img_w = img_w; frame->face_recog->img_h = img_h; frame->face_recog->items.push_back(item); return frame; } SimpleJson ParseConfig(const std::string& text) { SimpleJson config; std::string err; const bool ok = ParseSimpleJson(text, config, err); EXPECT_TRUE(ok) << err; return config; } AlarmNode::FaceRule MakeKnownRule() { AlarmNode::FaceRule rule; rule.name = "known_person"; rule.kind = AlarmNode::FaceRule::Kind::Person; rule.persons = {"alice"}; rule.cooldown_ms = 0; return rule; } TEST(FaceTrackAlarmTest, IgnoresLowQualityUntrackedFace) { FaceTrackAggregationConfig cfg; FaceTrackState state; std::unordered_map known_identity_last_alarm_ms; FaceRecogItem item = MakeUncertainFace(-1, -1, "", 0.40f); const FaceTrackDecision decision = UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 1000); EXPECT_FALSE(decision.trigger_known); EXPECT_FALSE(decision.trigger_unknown); } TEST(FaceTrackAlarmTest, EmitsKnownAfterStableHitsOnSameTrack) { FaceTrackAggregationConfig cfg; cfg.known_min_hits = 3; cfg.known_hit_window_ms = 3000; FaceTrackState state; std::unordered_map known_identity_last_alarm_ms; const FaceRecogItem item = MakeKnownFace(5, 1, "reg_001", 0.62f); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 1000).trigger_known); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 1500).trigger_known); const FaceTrackDecision decision = UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 2000); EXPECT_TRUE(decision.trigger_known); EXPECT_FALSE(decision.trigger_unknown); } TEST(FaceTrackAlarmTest, SuppressesKnownReentryInsideConfiguredCooldown) { FaceTrackAggregationConfig cfg; cfg.known_min_hits = 2; cfg.known_hit_window_ms = 3000; cfg.known_reentry_cooldown_ms = 5000; std::unordered_map known_identity_last_alarm_ms; const FaceRecogItem initial_track = MakeKnownFace(8, 1, "alice", 0.66f); FaceTrackState first_track_state; EXPECT_FALSE(UpdateFaceTrackState(cfg, first_track_state, known_identity_last_alarm_ms, initial_track, 1000).trigger_known); EXPECT_TRUE(UpdateFaceTrackState(cfg, first_track_state, known_identity_last_alarm_ms, initial_track, 1500).trigger_known); FaceTrackState reentry_state; FaceRecogItem reentry_track = initial_track; reentry_track.person_track_id = 18; EXPECT_FALSE(UpdateFaceTrackState(cfg, reentry_state, known_identity_last_alarm_ms, reentry_track, 4000).trigger_known); const FaceTrackDecision suppressed = UpdateFaceTrackState( cfg, reentry_state, known_identity_last_alarm_ms, reentry_track, 4500); EXPECT_FALSE(suppressed.trigger_known); EXPECT_FALSE(reentry_state.reported_known); EXPECT_EQ(known_identity_last_alarm_ms.at(1), 1500u); const FaceTrackDecision after_cooldown = UpdateFaceTrackState( cfg, reentry_state, known_identity_last_alarm_ms, reentry_track, 7000); EXPECT_TRUE(after_cooldown.trigger_known); EXPECT_TRUE(reentry_state.reported_known); EXPECT_EQ(known_identity_last_alarm_ms.at(1), 7000u); } TEST(FaceTrackAlarmTest, DoesNotEmitUnknownForKnownPersonScoreWobble) { FaceTrackAggregationConfig cfg; cfg.known_min_hits = 3; cfg.known_hit_window_ms = 3000; cfg.unknown_min_track_age_ms = 1000; cfg.unknown_min_quality_hits = 1; FaceTrackState state; std::unordered_map known_identity_last_alarm_ms; const FaceRecogItem known = MakeKnownFace(9, 1, "reg_001", 0.63f); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, known, 1000).trigger_known); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, known, 1500).trigger_known); EXPECT_TRUE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, known, 2000).trigger_known); const FaceRecogItem wobble = MakeUncertainFace(9, 1, "reg_001", 0.42f); const FaceTrackDecision decision = UpdateFaceTrackState( cfg, state, known_identity_last_alarm_ms, wobble, 2600); EXPECT_FALSE(decision.trigger_known); EXPECT_FALSE(decision.trigger_unknown); } TEST(FaceTrackAlarmTest, DoesNotEmitUnknownWhileTrackIsKnownLeaning) { FaceTrackAggregationConfig cfg; cfg.known_min_hits = 3; cfg.known_hit_window_ms = 3000; cfg.unknown_min_track_age_ms = 1000; cfg.unknown_min_quality_hits = 1; FaceTrackState state; std::unordered_map known_identity_last_alarm_ms; const FaceRecogItem known = MakeKnownFace(10, 1, "alice", 0.63f); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, known, 1000).trigger_known); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, known, 1500).trigger_known); EXPECT_EQ(state.best_known_person_id, 1); EXPECT_EQ(state.best_known_name, "alice"); EXPECT_EQ(state.known_hit_times.size(), 2u); const FaceRecogItem wobble = MakeUncertainFace(10, 1, "alice", 0.42f); const FaceTrackDecision decision = UpdateFaceTrackState( cfg, state, known_identity_last_alarm_ms, wobble, 2600); EXPECT_FALSE(decision.trigger_known); EXPECT_FALSE(decision.trigger_unknown); } TEST(FaceTrackAlarmTest, ResetsKnownStateWhenIdentityChangesOnSameTrack) { FaceTrackAggregationConfig cfg; cfg.known_min_hits = 2; cfg.known_hit_window_ms = 3000; FaceTrackState state; std::unordered_map known_identity_last_alarm_ms; const FaceRecogItem alice = MakeKnownFace(7, 1, "alice", 0.80f); const FaceRecogItem bob = MakeKnownFace(7, 2, "bob", 0.82f); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, alice, 1000).trigger_known); EXPECT_TRUE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, alice, 1500).trigger_known); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, bob, 2000).trigger_known); EXPECT_TRUE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, bob, 2500).trigger_known); } TEST(FaceTrackAlarmTest, ExpiresStateAfterTrackInactivity) { FaceTrackAggregationConfig cfg; cfg.known_min_hits = 2; cfg.known_hit_window_ms = 3000; cfg.state_expire_ms = 1000; FaceTrackState state; std::unordered_map known_identity_last_alarm_ms; const FaceRecogItem item = MakeKnownFace(8, 1, "alice", 0.80f); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 1000).trigger_known); EXPECT_TRUE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 1500).trigger_known); EXPECT_FALSE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 4000).trigger_known); EXPECT_TRUE(UpdateFaceTrackState(cfg, state, known_identity_last_alarm_ms, item, 4500).trigger_known); } TEST(FaceTrackAlarmTest, DisqualifiedFramesDoNotAdvanceTrackAggregation) { AlarmNode node; node.track_agg_cfg_.known_min_hits = 2; node.track_agg_cfg_.known_hit_window_ms = 5000; auto rule = MakeKnownRule(); rule.min_face_area_ratio = 0.03f; node.face_rules_.push_back(rule); FaceRecogItem small = MakeKnownFace(11, 1, "alice", 0.85f); small.bbox = Rect{0.0f, 0.0f, 5.0f, 5.0f}; FaceRecogItem valid = MakeKnownFace(11, 1, "alice", 0.85f); valid.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f}; EXPECT_EQ(node.Process(MakeFaceFrame(1, small)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 0u); EXPECT_EQ(node.Process(MakeFaceFrame(2, valid)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 0u); EXPECT_EQ(node.Process(MakeFaceFrame(3, valid)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 1u); } TEST(FaceTrackAlarmTest, UntrackedQualifiedFaceDoesNotTriggerAlarm) { AlarmNode node; node.track_agg_cfg_.known_min_hits = 2; node.track_agg_cfg_.known_hit_window_ms = 5000; node.face_rules_.push_back(MakeKnownRule()); FaceRecogItem first = MakeKnownFace(-1, 5, "alice", 0.88f); first.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f}; FaceRecogItem second = first; EXPECT_EQ(node.Process(MakeFaceFrame(1, first)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 0u); EXPECT_EQ(node.Process(MakeFaceFrame(2, second)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 0u); } TEST(FaceTrackAlarmTest, UnknownRuleAcceptsLowKnownSimilarityCandidate) { AlarmNode::FaceRule rule; rule.name = "unknown_face"; rule.kind = AlarmNode::FaceRule::Kind::Unknown; rule.max_known_sim = 0.35f; FaceRecogItem item = MakeUncertainFace(101, 7, "reg_007", 0.20f); item.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f}; AlarmNode::FaceRuleEval eval; EXPECT_TRUE(AlarmNode::FaceItemMatchesRule(rule, item, 10000.0, &eval)); EXPECT_TRUE(eval.matched); } TEST(FaceTrackAlarmTest, UnknownRuleDiagnosticsExplainMaxKnownSimRejection) { AlarmNode::FaceRule rule; rule.name = "unknown_face"; rule.kind = AlarmNode::FaceRule::Kind::Unknown; rule.max_known_sim = 0.35f; FaceRecogItem item = MakeUncertainFace(101, 7, "reg_007", 0.42f); item.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f}; AlarmNode::FaceRuleEval eval; EXPECT_FALSE(AlarmNode::FaceItemMatchesRule(rule, item, 10000.0, &eval)); EXPECT_FALSE(eval.matched); EXPECT_EQ(eval.reject_reason, "max_known_sim"); EXPECT_NE(eval.detail.find("best_sim=0.420"), std::string::npos); EXPECT_NE(eval.detail.find("max=0.350"), std::string::npos); } TEST(FaceTrackAlarmTest, UnknownRuleDiagnosticsUseHighPrecisionAreaRatio) { AlarmNode::FaceRule rule; rule.name = "unknown_face"; rule.kind = AlarmNode::FaceRule::Kind::Unknown; rule.min_face_area_ratio = 0.001f; FaceRecogItem item = MakeUncertainFace(102, 7, "reg_007", 0.40f); item.bbox = Rect{0.0f, 0.0f, 28.0f, 40.0f}; AlarmNode::FaceRuleEval eval; EXPECT_FALSE(AlarmNode::FaceItemMatchesRule(rule, item, 1920.0 * 1080.0, &eval)); EXPECT_EQ(eval.reject_reason, "min_face_area_ratio"); EXPECT_NE(eval.detail.find("area_ratio=0.000540"), std::string::npos); EXPECT_NE(eval.detail.find("min=0.001000"), std::string::npos); } TEST(FaceTrackAlarmTest, ParsesFaceDebugUnknownCandidateLogging) { AlarmNode node; NodeContext ctx; ctx.input_queue = std::make_shared>(4, QueueDropStrategy::DropOldest); const SimpleJson config = ParseConfig(R"({ "id": "alarm", "face_debug": { "log_unknown_candidates": true, "unknown_candidate_interval_ms": 123 } })"); ASSERT_TRUE(node.Init(config, ctx)); EXPECT_TRUE(node.face_debug_log_unknown_candidates_); EXPECT_EQ(node.face_debug_unknown_candidate_interval_ms_, 123); } TEST(FaceTrackAlarmTest, KnownLeaningTrackDoesNotFireUnknownRule) { AlarmNode node; node.track_agg_cfg_.known_min_hits = 3; node.track_agg_cfg_.known_hit_window_ms = 5000; node.track_agg_cfg_.unknown_min_track_age_ms = 1000; node.track_agg_cfg_.unknown_min_quality_hits = 1; auto known_rule = MakeKnownRule(); AlarmNode::FaceRule unknown_rule; unknown_rule.name = "unknown_face"; unknown_rule.kind = AlarmNode::FaceRule::Kind::Unknown; unknown_rule.cooldown_ms = 0; node.face_rules_.push_back(known_rule); node.face_rules_.push_back(unknown_rule); FaceRecogItem known = MakeKnownFace(41, 5, "alice", 0.88f); known.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f}; FaceRecogItem wobble = MakeUncertainFace(41, 5, "alice", 0.42f); wobble.bbox = known.bbox; EXPECT_EQ(node.Process(MakeFaceFrame(1, known)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 0u); EXPECT_EQ(node.Process(MakeFaceFrame(2, known)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 0u); EXPECT_EQ(node.Process(MakeFaceFrame(3, wobble)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 0u); EXPECT_EQ(node.Process(MakeFaceFrame(4, known)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 1u); } TEST(FaceTrackAlarmTest, OverlappingRulesDoNotDoubleCountSingleFrameEvidence) { AlarmNode node; node.track_agg_cfg_.known_min_hits = 2; node.track_agg_cfg_.known_hit_window_ms = 5000; auto first_rule = MakeKnownRule(); first_rule.name = "known_person_primary"; auto second_rule = MakeKnownRule(); second_rule.name = "known_person_secondary"; node.face_rules_.push_back(first_rule); node.face_rules_.push_back(second_rule); FaceRecogItem item = MakeKnownFace(51, 5, "alice", 0.88f); item.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f}; EXPECT_EQ(node.Process(MakeFaceFrame(1, item)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 0u); const auto it_first = node.face_track_states_.find(51); ASSERT_NE(it_first, node.face_track_states_.end()); EXPECT_EQ(it_first->second.known_hit_times.size(), 1u); EXPECT_FALSE(it_first->second.reported_known); EXPECT_EQ(node.Process(MakeFaceFrame(2, item)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 2u); const auto it_second = node.face_track_states_.find(51); ASSERT_NE(it_second, node.face_track_states_.end()); EXPECT_EQ(it_second->second.known_hit_times.size(), 2u); EXPECT_TRUE(it_second->second.reported_known); } TEST(FaceTrackAlarmTest, KnownPersonDoesNotReAlarmWhileTrackStaysActive) { AlarmNode node; node.track_agg_cfg_.known_min_hits = 2; node.track_agg_cfg_.known_hit_window_ms = 5000; auto rule = MakeKnownRule(); rule.cooldown_ms = 0; node.face_rules_.push_back(rule); FaceRecogItem item = MakeKnownFace(61, 5, "alice", 0.88f); item.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f}; EXPECT_EQ(node.Process(MakeFaceFrame(1, item)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 0u); EXPECT_EQ(node.Process(MakeFaceFrame(2, item)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 1u); EXPECT_EQ(node.Process(MakeFaceFrame(3, item)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 1u); EXPECT_EQ(node.Process(MakeFaceFrame(4, item)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 1u); } TEST(FaceTrackAlarmTest, KnownPersonReEntryInsideCooldownOnNewTrackDoesNotReAlarm) { AlarmNode node; node.track_agg_cfg_.known_min_hits = 2; node.track_agg_cfg_.known_hit_window_ms = 5000; node.track_agg_cfg_.known_reentry_cooldown_ms = 1000; auto rule = MakeKnownRule(); rule.cooldown_ms = 0; node.face_rules_.push_back(rule); FaceRecogItem first_track = MakeKnownFace(71, 5, "alice", 0.88f); first_track.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f}; FaceRecogItem second_track = MakeKnownFace(72, 5, "alice", 0.88f); second_track.bbox = first_track.bbox; EXPECT_EQ(node.Process(MakeFaceFrame(1, first_track)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 0u); EXPECT_EQ(node.Process(MakeFaceFrame(2, first_track)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 1u); ASSERT_EQ(node.known_identity_last_alarm_ms_.size(), 1u); const auto first_alarm_it = node.known_identity_last_alarm_ms_.find(5); ASSERT_NE(first_alarm_it, node.known_identity_last_alarm_ms_.end()); EXPECT_EQ(node.Process(MakeFaceFrame(3, second_track)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 1u); EXPECT_EQ(node.Process(MakeFaceFrame(4, second_track)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 1u); EXPECT_EQ(node.known_identity_last_alarm_ms_.at(5), first_alarm_it->second); } TEST(FaceTrackAlarmTest, KnownPersonReEntryTrackCanAlarmAfterCooldownExpires) { AlarmNode node; node.track_agg_cfg_.known_min_hits = 2; node.track_agg_cfg_.known_hit_window_ms = 5000; node.track_agg_cfg_.known_reentry_cooldown_ms = 1000; auto rule = MakeKnownRule(); rule.cooldown_ms = 0; node.face_rules_.push_back(rule); FaceRecogItem first_track = MakeKnownFace(81, 5, "alice", 0.88f); first_track.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f}; FaceRecogItem second_track = MakeKnownFace(82, 5, "alice", 0.88f); second_track.bbox = first_track.bbox; EXPECT_EQ(node.Process(MakeFaceFrame(1, first_track)), NodeStatus::OK); EXPECT_EQ(node.Process(MakeFaceFrame(2, first_track)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 1u); EXPECT_EQ(node.Process(MakeFaceFrame(3, second_track)), NodeStatus::OK); EXPECT_EQ(node.Process(MakeFaceFrame(4, second_track)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 1u); EXPECT_FALSE(node.face_track_states_.at(82).reported_known); node.known_identity_last_alarm_ms_[5] = 0; EXPECT_EQ(node.Process(MakeFaceFrame(5, second_track)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 2u); EXPECT_TRUE(node.face_track_states_.at(82).reported_known); } TEST(FaceTrackAlarmTest, SuppressedKnownReentryTrackDoesNotAgeIntoUnknownAfterSuppressionWindowWobble) { FaceTrackAggregationConfig cfg; cfg.known_min_hits = 2; cfg.known_hit_window_ms = 5000; cfg.known_reentry_cooldown_ms = 5000; cfg.unknown_min_track_age_ms = 1000; cfg.unknown_min_quality_hits = 1; std::unordered_map known_identity_last_alarm_ms; const FaceRecogItem initial_track = MakeKnownFace(91, 5, "alice", 0.88f); FaceTrackState first_track_state; EXPECT_FALSE(UpdateFaceTrackState(cfg, first_track_state, known_identity_last_alarm_ms, initial_track, 1000).trigger_known); EXPECT_TRUE(UpdateFaceTrackState(cfg, first_track_state, known_identity_last_alarm_ms, initial_track, 1500).trigger_known); FaceTrackState reentry_state; FaceRecogItem reentry_track = initial_track; reentry_track.person_track_id = 92; EXPECT_FALSE(UpdateFaceTrackState(cfg, reentry_state, known_identity_last_alarm_ms, reentry_track, 4000).trigger_known); EXPECT_FALSE(UpdateFaceTrackState(cfg, reentry_state, known_identity_last_alarm_ms, reentry_track, 4500).trigger_known); FaceRecogItem wobble = MakeUncertainFace(92, 5, "alice", 0.42f); const FaceTrackDecision wobble_decision = UpdateFaceTrackState( cfg, reentry_state, known_identity_last_alarm_ms, wobble, 7000); EXPECT_FALSE(wobble_decision.trigger_unknown); EXPECT_FALSE(reentry_state.reported_unknown); } TEST(FaceTrackAlarmTest, IgnoresLegacyDisableFlagAndStillUsesTrackAggregation) { AlarmNode node; NodeContext ctx; ctx.input_queue = std::make_shared>(4, QueueDropStrategy::DropOldest); const SimpleJson config = ParseConfig(R"({ "id": "alarm", "face_rules": [ { "name": "known_person", "type": "person", "persons": ["alice"], "cooldown_ms": 0 } ], "face_track_aggregation": { "enable": false, "known": { "min_hits": 2, "hit_window_ms": 5000, "reentry_cooldown_ms": 60000 } } })"); ASSERT_TRUE(node.Init(config, ctx)); EXPECT_EQ(node.track_agg_cfg_.known_min_hits, 2); EXPECT_EQ(node.track_agg_cfg_.known_hit_window_ms, 5000); EXPECT_EQ(node.track_agg_cfg_.known_reentry_cooldown_ms, 60000); FaceRecogItem item = MakeKnownFace(31, 5, "alice", 0.88f); item.bbox = Rect{0.0f, 0.0f, 20.0f, 20.0f}; EXPECT_EQ(node.Process(MakeFaceFrame(1, item)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 0u); EXPECT_EQ(node.Process(MakeFaceFrame(2, item)), NodeStatus::OK); EXPECT_EQ(node.alarm_count_, 1u); } TEST(FaceTrackAlarmTest, ParsesTrackAggregationWithoutUnknownReentryCooldownContract) { AlarmNode node; NodeContext ctx; ctx.input_queue = std::make_shared>(4, QueueDropStrategy::DropOldest); const SimpleJson config = ParseConfig(R"({ "id": "alarm", "face_rules": [ { "name": "unknown_face", "type": "unknown", "cooldown_ms": 0, "max_known_sim": 0.35 } ], "face_track_aggregation": { "known": { "min_hits": 2, "hit_window_ms": 5000, "reentry_cooldown_ms": 60000 }, "unknown": { "min_track_age_ms": 1200, "min_quality_hits": 2 } } })"); ASSERT_TRUE(node.Init(config, ctx)); EXPECT_EQ(node.track_agg_cfg_.known_reentry_cooldown_ms, 60000); EXPECT_EQ(node.track_agg_cfg_.unknown_min_track_age_ms, 1200); EXPECT_EQ(node.track_agg_cfg_.unknown_min_quality_hits, 2); ASSERT_EQ(node.face_rules_.size(), 1u); EXPECT_FLOAT_EQ(node.face_rules_[0].max_known_sim, 0.35f); } } // namespace } // namespace rk3588