fix: a loaded recording is filtered into its buffer, not truncated
The loader pushed one slot per file row while the plan decimated by eight, so a 10 M-row file overran the ring and only its last 4.19 s survived -- a 20 s recording that read as a 4 s one. Each group of `time_stride` rows is now averaged into one slot at the group's mean stamp, as the packet path already does, and the load line reports rows and decimation so a filtered file cannot be mistaken for a truncated one. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
50a1ddca62
commit
345145c52b
7 files changed
+104
-11
No files matched your search
+46
-4
@@ -8,6 +8,7 @@
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "layout_store.hpp"
|
||||
@@ -283,18 +284,57 @@ bool load_waveform_into_model(ScopeModel& model, const std::string& path,
|
||||
if (error != nullptr) *error = "'" + path + "': the model built no channels for its columns";
|
||||
return false;
|
||||
}
|
||||
// The plan the enables above built may **decimate**, and then the file has to be filtered into the
|
||||
// ring exactly as the packet path filters the wire: one stored slot per `time_stride` rows,
|
||||
// carrying their mean. A recording longer than a few seconds at a high rate does not fit a buffer
|
||||
// sized in megabytes -- 20 s of 10 channels at 500 kS/s is 480 MB of rings -- so the planner
|
||||
// decimates, and pushing every row regardless overruns the ring and leaves the file as *its own
|
||||
// tail*: `stored=10000000` against a `published=2097152` ring read as "I loaded a 20 s file and
|
||||
// only the last 4 s are there". Averaging and not striding, for the reason
|
||||
// `invariants-data-path.md` gives: a stride aliases whatever is above the stored Nyquist.
|
||||
std::uint64_t decim = 1;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(model.mutex());
|
||||
if (model.partition().time_stride > 0) decim = model.partition().time_stride;
|
||||
}
|
||||
std::vector<float> values(reader.names().size(), 0.0f);
|
||||
std::vector<float> mean(reader.names().size(), 0.0f);
|
||||
std::vector<double> acc(reader.names().size(), 0.0);
|
||||
std::size_t pushed = 0;
|
||||
std::size_t rows_read = 0;
|
||||
std::uint64_t in_group = 0;
|
||||
double t_acc = 0.0;
|
||||
double abs_acc = 0.0;
|
||||
double stamp = 0.0;
|
||||
double abs_stamp = 0.0;
|
||||
double first = 0.0;
|
||||
double last = 0.0;
|
||||
while (reader.next(&stamp, &abs_stamp, values.data())) {
|
||||
if (pushed == 0) first = stamp;
|
||||
last = stamp;
|
||||
model.push_loaded_slot(values, stamp, abs_stamp);
|
||||
// The group's mean value at its mean stamp, which is where the ingest filter puts it too: a slot
|
||||
// stamped at the group's *first* sample would shift the whole trace half a group early.
|
||||
const auto flush_group = [&]() {
|
||||
const double n = static_cast<double>(in_group);
|
||||
for (std::size_t i = 0; i < acc.size(); ++i) {
|
||||
mean[i] = static_cast<float>(acc[i] / n);
|
||||
acc[i] = 0.0;
|
||||
}
|
||||
model.push_loaded_slot(mean, t_acc / n, abs_acc / n);
|
||||
++pushed;
|
||||
t_acc = 0.0;
|
||||
abs_acc = 0.0;
|
||||
in_group = 0;
|
||||
};
|
||||
while (reader.next(&stamp, &abs_stamp, values.data())) {
|
||||
if (rows_read == 0) first = stamp;
|
||||
last = stamp;
|
||||
++rows_read;
|
||||
for (std::size_t i = 0; i < acc.size(); ++i) acc[i] += values[i];
|
||||
t_acc += stamp;
|
||||
abs_acc += abs_stamp;
|
||||
if (++in_group >= decim) flush_group();
|
||||
}
|
||||
// The last group need not be whole, and dropping it would shorten the record by up to one slot --
|
||||
// which on a one-slot file (`decim` larger than the whole recording) is the entire file.
|
||||
if (in_group > 0) flush_group();
|
||||
reader.close();
|
||||
|
||||
model.set_epoch_if_unset(first);
|
||||
@@ -305,6 +345,8 @@ bool load_waveform_into_model(ScopeModel& model, const std::string& path,
|
||||
if (report != nullptr) {
|
||||
report->signals = channels;
|
||||
report->samples = pushed;
|
||||
report->rows = rows_read;
|
||||
report->decimation = decim;
|
||||
report->span_s = pushed > 0 ? std::abs(last - first) : 0.0;
|
||||
report->rate_hz = rate;
|
||||
report->first_time = first;
|
||||
|
||||
Reference in new issue
Block a user