#pragma once /// TRACES: VR-001 | PR-002 #include "types.hpp" #include "config.hpp" #include "gallery/embedder_stamp.hpp" #include #include #include #include #include // ── EmbeddingDumpFunc ───────────────────────────────────────────────────────── // KPN sink that taps the EmbeddedSceneFrame channel and writes the per-frame face // metadata + embeddings to one HDF5 file (schema: scripts/optimizer/SCHEMA.md). // The dump is the expensive, parameter-independent half of the pipeline // (decode→detect→align→embed); replaying it lets a threshold sweep re-run the cheap // downstream nodes thousands of times with no GPU. See sae_kpn / scripts/optimizer. // // Accumulates in flat/ragged arrays and writes once on EOF. struct EmbeddingDumpFunc { static constexpr std::string_view label() { return "embedding_dump"; } EmbeddingDumpFunc(const Config& cfg, std::atomic& done) : path_(cfg.dump_embeddings_path), movie_(cfg.movie_path), sample_fps_(cfg.sample_fps), done_(done) { /// TRACES: GR-004 | SR-001 // A dump is a bag of embeddings with no model attached, replayed against a // gallery hours or weeks later — the same silent cross-model hazard as the // gallery itself, so it carries the same stamp. stamp_ = make_embedder_stamp(cfg.arcface_model); std::cerr << "[embedding_dump] writing " << path_ << " embedder: " << stamp_.describe() << "\n"; } void operator()(EmbeddedSceneFrame ef) { if (ef.source.eof) { flush(); return; } const int32_t n = static_cast(ef.faces.size()); ts_.push_back(ef.source.timestamp_sec); fidx_.push_back(ef.source.frame_idx); is_cut_.push_back(ef.source.is_cut ? 1 : 0); is_bnd_.push_back(ef.source.is_scene_boundary ? 1 : 0); face_off_.push_back(static_cast(conf_.size())); face_cnt_.push_back(n); for (int i = 0; i < n; ++i) { const auto& f = ef.faces[i]; bbox_.insert(bbox_.end(), {f.bbox.x, f.bbox.y, f.bbox.width, f.bbox.height}); for (int k = 0; k < 5; ++k) { lmk_.push_back(f.landmarks[k].x); lmk_.push_back(f.landmarks[k].y); } conf_.push_back(f.confidence); const auto& e = ef.embeddings[i]; emb_.insert(emb_.end(), e.begin(), e.end()); } } void flush() { if (written_.exchange(true)) return; try { write_hdf5(); } catch (const H5::Exception& e) { std::cerr << "[embedding_dump] HDF5 error: " << e.getDetailMsg() << "\n"; } done_.store(true, std::memory_order_release); } private: static constexpr int kSchemaVersion = 1; static constexpr int kEmbedDim = 512; template void write_vec(H5::Group& g, const char* name, const std::vector& v, const H5::PredType& dtype, hsize_t cols = 0) { hsize_t rows = cols ? v.size() / cols : v.size(); std::vector dims = cols ? std::vector{rows, cols} : std::vector{rows}; H5::DataSpace space(static_cast(dims.size()), dims.data()); auto ds = g.createDataSet(name, dtype, space); if (!v.empty()) ds.write(v.data(), dtype); } void write_hdf5() { H5::H5File file(path_, H5F_ACC_TRUNC); // root attrs auto scalar = H5::DataSpace(H5S_SCALAR); auto ver = file.createAttribute("schema_version", H5::PredType::NATIVE_INT, scalar); int sv = kSchemaVersion; ver.write(H5::PredType::NATIVE_INT, &sv); auto ed = file.createAttribute("embed_dim", H5::PredType::NATIVE_INT, scalar); int dim = kEmbedDim; ed.write(H5::PredType::NATIVE_INT, &dim); auto fps = file.createAttribute("sample_fps", H5::PredType::NATIVE_FLOAT, scalar); fps.write(H5::PredType::NATIVE_FLOAT, &sample_fps_); H5::StrType str(H5::PredType::C_S1, H5T_VARIABLE); auto mv = file.createAttribute("movie", str, scalar); mv.write(str, movie_); /// TRACES: GR-004 | SR-001 file.createAttribute("embedder_model", str, scalar).write(str, stamp_.model_name); file.createAttribute("embedder_sha256", str, scalar).write(str, stamp_.model_sha256); H5::Group frames = file.createGroup("frames"); write_vec(frames, "timestamp_sec", ts_, H5::PredType::NATIVE_DOUBLE); write_vec(frames, "frame_idx", fidx_, H5::PredType::NATIVE_INT64); write_vec(frames, "is_cut", is_cut_, H5::PredType::NATIVE_UINT8); write_vec(frames, "is_scene_boundary", is_bnd_, H5::PredType::NATIVE_UINT8); write_vec(frames, "face_offset", face_off_, H5::PredType::NATIVE_INT64); write_vec(frames, "face_count", face_cnt_, H5::PredType::NATIVE_INT32); H5::Group faces = file.createGroup("faces"); write_vec(faces, "embedding", emb_, H5::PredType::NATIVE_FLOAT, kEmbedDim); write_vec(faces, "bbox", bbox_, H5::PredType::NATIVE_FLOAT, 4); write_vec(faces, "landmarks", lmk_, H5::PredType::NATIVE_FLOAT, 10); write_vec(faces, "confidence", conf_, H5::PredType::NATIVE_FLOAT); std::cerr << "[embedding_dump] wrote " << ts_.size() << " frames, " << conf_.size() << " faces → " << path_ << "\n"; } std::string path_, movie_; EmbedderStamp stamp_; float sample_fps_; std::atomic& done_; std::atomic written_{false}; std::vector ts_; std::vector fidx_; std::vector is_cut_, is_bnd_; std::vector face_off_; std::vector face_cnt_; std::vector emb_, bbox_, lmk_, conf_; };