feat(engine): HDF5-native galleries with embedded calibration; TensorRT backends; scene detection

Gallery format switches from JSON to HDF5 exclusively (JSON read-only kept for
back-compat): save_gallery always writes HDF5, and the fitted Platt-sigmoid
calibration (a, b, valid, hash) is now embedded directly in the gallery file
instead of a sidecar .calib_cache.json — identity_matcher reads it from the
loaded gallery and writes back only when the embeddings actually changed
(hash mismatch), skipping the O(n^2) refit otherwise.

Also includes: TensorRT inference backend support (ort_backend.cpp,
trt_backend.cpp), gemm_backend improvements, TransNetV2-based scene-boundary
detection wired through frame_source/face_tracker/main, and CMake build
target updates for the new sources.

Bumps the KPN submodule to feature/persistent-pipeline-reuse (push_blocking
backpressure, node_ptr/node_stats introspection, ObjectVariantNodeWrapper for
stateful functors) — needed by the optimizer's sae_kpn Python bindings.
This commit is contained in:
2026-07-19 19:04:03 +02:00
parent aca6147d69
commit 41a277bc19
19 changed files with 1151 additions and 216 deletions
+30 -45
View File
@@ -335,63 +335,48 @@ inline uint64_t hash_gallery_embeddings(
return h;
}
// Calibrates the gallery, caching the fitted (a, b, valid) result on disk
// keyed by a hash of the reference embeddings. The O(n^2) pairwise fit only
// re-runs when the gallery's embeddings/actor assignments actually change.
// Calibrates the gallery, reusing (cached_a, cached_b, cached_valid) if
// cached_hash matches a fresh hash of the current embeddings/actor
// assignments — the O(n^2) pairwise fit only re-runs when they actually
// change. Distinct from calibrate_gallery_cached's old sidecar-JSON-file
// design: the cache now lives in the gallery HDF5 itself (ActorGallery::
// calib_*, see gallery_store.hpp), so this takes the previous values
// in-memory rather than a file path. Sets `recomputed` so the caller (which
// holds the open gallery file/struct) knows whether it needs to persist the
// refreshed values back.
inline GalleryCalibration calibrate_gallery_cached(
const std::vector<Embedding>& flat_emb,
const std::vector<int>& flat_actor,
const std::string& cache_path)
float cached_a,
float cached_b,
bool cached_valid,
uint64_t cached_hash,
const std::string& curve_base_path,
bool& recomputed)
{
uint64_t hash = hash_gallery_embeddings(flat_emb, flat_actor);
recomputed = false;
std::string base_path = cache_path;
constexpr std::string_view kJsonExt = ".json";
if (base_path.size() >= kJsonExt.size() &&
base_path.compare(base_path.size() - kJsonExt.size(), kJsonExt.size(), kJsonExt) == 0)
base_path.resize(base_path.size() - kJsonExt.size());
std::ifstream in(cache_path);
if (in.is_open()) {
try {
nlohmann::json j;
in >> j;
if (j.at("hash").get<uint64_t>() == hash) {
GalleryCalibration cal;
cal.a = j.at("a").get<float>();
cal.b = j.at("b").get<float>();
cal.valid = j.at("valid").get<bool>();
std::cerr << "[calibration] using cached calibration from "
<< cache_path << " (a=" << cal.a << " b=" << cal.b
<< " valid=" << cal.valid << ")\n";
save_calibration_curve(cal, base_path);
return cal;
}
std::cerr << "[calibration] cache at " << cache_path
<< " is stale, recomputing\n";
} catch (const std::exception&) {
std::cerr << "[calibration] cache at " << cache_path
<< " is unreadable, recomputing\n";
}
if (cached_hash != 0 && cached_hash == hash) {
GalleryCalibration cal{cached_a, cached_b, cached_valid};
std::cerr << "[calibration] using cached calibration from gallery"
<< " (a=" << cal.a << " b=" << cal.b
<< " valid=" << cal.valid << ")\n";
if (!curve_base_path.empty()) save_calibration_curve(cal, curve_base_path);
return cal;
}
if (cached_hash != 0)
std::cerr << "[calibration] cached calibration is stale (embeddings changed), "
"recomputing\n";
auto t0 = std::chrono::steady_clock::now();
GalleryCalibration cal = calibrate_gallery(flat_emb, flat_actor);
auto t1 = std::chrono::steady_clock::now();
double secs = std::chrono::duration<double>(t1 - t0).count();
std::cerr << "[calibration] fit took " << secs << "s for "
std::cerr << "[calibration] fit took "
<< std::chrono::duration<double>(t1 - t0).count() << "s for "
<< flat_emb.size() << " embeddings\n";
nlohmann::json j;
j["hash"] = hash;
j["a"] = cal.a;
j["b"] = cal.b;
j["valid"] = cal.valid;
j["fit_secs"] = secs;
std::ofstream out(cache_path);
if (out.is_open()) out << j.dump(2) << "\n";
save_calibration_curve(cal, base_path);
if (!curve_base_path.empty()) save_calibration_curve(cal, curve_base_path);
recomputed = true;
return cal;
}