feat(presence): flood-fill presence mode
Add PresenceMode::flood alongside the default track_extent. In flood mode the result sink snaps each presence claim to the shot it sits in, so an actor seen once anywhere in a shot is reported for the whole shot [prev_boundary, next_boundary]. This trades precision for recall against X-Ray's per-scene cast granularity and is a toggleable knob for the optimizer to weigh rather than a default. Boundaries come from the frame stream, now carried through SceneAnnotation (is_cut and is_scene_boundary). Flood prefers TransNetV2 shot boundaries when a scene detector populated them, otherwise falls back to the always-on histogram cuts (camera_position_change_detector); with no boundaries it degrades to track_extent per claim. The is_scene_boundary path stays dormant so an out-of-process scene detector can be revived later without re-wiring. Selected with --presence-mode flood|track_extent (default track_extent), so existing output is byte-for-byte unchanged. The dump_embeddings header note records why TransNetV2 scene detection is not run in that process.
This commit is contained in:
@@ -184,6 +184,20 @@ private:
|
||||
aw.scenes.push_back({c.first_seen, c.last_seen, c.belief, c.route});
|
||||
}
|
||||
|
||||
// Flood-fill: snap each claim to the shot it sits in, so an actor seen
|
||||
// once in a scene is reported across the whole scene. Bounded by real
|
||||
// TransNetV2 boundaries — a window never crosses one — and a no-op when
|
||||
// scene detection found no boundaries (nothing to snap to).
|
||||
if (cfg_.presence_mode == PresenceMode::flood) {
|
||||
const std::vector<double> bounds = scene_boundaries();
|
||||
if (!bounds.empty())
|
||||
for (auto& [idx, aw] : by_actor)
|
||||
for (auto& w : aw.scenes) {
|
||||
w.start = boundary_at_or_before(bounds, w.start);
|
||||
w.end = boundary_after(bounds, w.end);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ActorWindow> result;
|
||||
for (auto& [idx, aw] : by_actor) {
|
||||
std::sort(aw.scenes.begin(), aw.scenes.end(),
|
||||
@@ -193,6 +207,45 @@ private:
|
||||
return result;
|
||||
}
|
||||
|
||||
// Sorted, de-duplicated boundary timestamps seen this run, framed by the
|
||||
// film's own extent so the first and last shots are closed intervals. Derived
|
||||
// from frames_ rather than a separate accumulator: the frames are already
|
||||
// retained and this runs once.
|
||||
//
|
||||
// Prefers TransNetV2 shot boundaries (is_scene_boundary) when a scene detector
|
||||
// populated them; otherwise falls back to the always-on histogram cuts
|
||||
// (is_cut, camera_position_change_detector). On this ROCm box the scene
|
||||
// detector cannot run in-process (see the dumper note), so is_cut is what
|
||||
// flood-fill actually snaps to — coarser than true shot boundaries (cuts also
|
||||
// fire on in-shot angle changes) but present with no extra pass.
|
||||
std::vector<double> scene_boundaries() const {
|
||||
bool have_scene = false;
|
||||
for (const auto& sa : frames_)
|
||||
if (sa.is_scene_boundary) { have_scene = true; break; }
|
||||
|
||||
std::vector<double> b;
|
||||
b.push_back(0.0);
|
||||
for (const auto& sa : frames_) {
|
||||
const bool boundary = have_scene ? sa.is_scene_boundary : sa.is_cut;
|
||||
if (boundary) b.push_back(sa.timestamp_sec);
|
||||
}
|
||||
b.push_back(last_ts_ + 1.0); // a right edge past the final sample
|
||||
std::sort(b.begin(), b.end());
|
||||
b.erase(std::unique(b.begin(), b.end()), b.end());
|
||||
return b;
|
||||
}
|
||||
|
||||
// The boundary opening the shot that contains t (largest boundary ≤ t).
|
||||
static double boundary_at_or_before(const std::vector<double>& b, double t) {
|
||||
auto it = std::upper_bound(b.begin(), b.end(), t);
|
||||
return (it == b.begin()) ? b.front() : *(it - 1);
|
||||
}
|
||||
// The boundary closing the shot that contains t (smallest boundary > t).
|
||||
static double boundary_after(const std::vector<double>& b, double t) {
|
||||
auto it = std::upper_bound(b.begin(), b.end(), t);
|
||||
return (it == b.end()) ? b.back() : *it;
|
||||
}
|
||||
|
||||
json build_epochs() {
|
||||
json actors = json::array();
|
||||
for (const auto& aw : build_actor_windows()) {
|
||||
|
||||
Reference in New Issue
Block a user