fix graph and white background added

This commit is contained in:
Ayzen
2026-10-15 14:46:06 +03:00
parent 5102ed751c
commit 9a9b8ce5c7
35 changed files with 730 additions and 463 deletions
@@ -38,8 +38,11 @@ class DataProcessor {
const ProcessingLiveConfig& live_config
) -> ipc::ResultCollection;
[[nodiscard]] auto requested_mode(const ProcessingLiveConfig& live_config) const -> const std::string&;
[[nodiscard]] auto resolve_processor(const ProcessingLiveConfig& live_config) -> ProcessorInterface&;
[[nodiscard]] auto should_replay_entire_history(const ProcessingLiveConfig& live_config) const -> bool;
// Only gpr and sar3d use the speed, so only they recompute when a locator client changes it.
[[nodiscard]] auto uses_socket_speed(const ProcessingLiveConfig& live_config) const -> bool;
// Speed streamed by a locator client (`vlc`), which then replaces
// `gpr_speed_m_s`; nullopt when `ignore_socket_speed` is set or no fresh
@@ -76,9 +76,8 @@ void DataProcessor::run(const std::atomic<bool>& stop_requested) {
std::uint64_t last_replayed_revision = live_config_loader_.revision();
runtime::CollectionErrors errors;
std::uint64_t oversized_results = 0;
// track the socket-fed speed used for the last reprocess so a change
// arriving without a live-config revision bump still triggers a reprocess of
// the current result (gated below by reprocess_current_result).
// The socket-fed speed used by the last reprocess: in the modes that use the speed, a
// change without a live-config revision still recomputes the current result.
std::optional<double> last_reprocessed_socket_speed = std::nullopt;
while (!stop_requested.load(std::memory_order_relaxed)) {
@@ -97,7 +96,8 @@ void DataProcessor::run(const std::atomic<bool>& stop_requested) {
}
auto& processor = resolve_processor(live_config);
const auto history_limit = processing_history_limit(config_, live_config);
const bool socket_speed_dirty = current_socket_speed != last_reprocessed_socket_speed;
const bool socket_speed_dirty =
uses_socket_speed(live_config) && current_socket_speed != last_reprocessed_socket_speed;
const auto was_removed = [&](const ipc::PreprocessedCollection& collection) {
if (live_config.history_clear_before_ns &&
@@ -111,39 +111,36 @@ void DataProcessor::run(const std::atomic<bool>& stop_requested) {
});
};
if (live_revision != last_replayed_revision || socket_speed_dirty) {
// Settled before recomputing: a sweep that fails is reported once rather than
// retried every tick, which would stop all new input.
last_replayed_revision = live_revision;
last_reprocessed_socket_speed = current_socket_speed;
std::erase_if(preprocessed_history, was_removed);
if (preprocessed_history.size() > history_limit) {
preprocessed_history.erase(preprocessed_history.begin(), preprocessed_history.end() - history_limit);
}
if (!live_config.reprocess_current_result) {
// Socket-fed speed updates should affect only future preprocessed collections,
// not replay the current history entry.
} else if (should_replay_entire_history(live_config)) {
for (std::size_t index = 0; index < preprocessed_history.size(); ++index) {
const auto replay_result = process_collection(
preprocessed_history[index],
std::span<const ipc::PreprocessedCollection>(preprocessed_history.data(), index),
processor,
live_config
);
publish_result_collection(replay_result, results_ring_, oversized_results);
publish_locator(replay_result);
// The GUI turns this off while it re-feeds the sweeps of a stopped B-scan itself.
if (live_config.reprocess_current_result && !preprocessed_history.empty()) {
const auto newest = preprocessed_history.size() - 1U;
const auto first = should_replay_entire_history(live_config) ? std::size_t{0} : newest;
for (auto index = first; index <= newest; ++index) {
try {
const auto replay_result = process_collection(
preprocessed_history[index],
std::span<const ipc::PreprocessedCollection>(preprocessed_history.data(), index),
processor,
live_config
);
publish_result_collection(replay_result, results_ring_, oversized_results);
if (index == newest) {
publish_locator(replay_result); // The locator reports the current scene.
}
} catch (const std::exception& exc) {
errors.report("data_processor", exc);
}
}
} else if (!preprocessed_history.empty()) {
const auto replay_result = process_collection(
preprocessed_history.back(),
std::span<const ipc::PreprocessedCollection>(preprocessed_history.data(), preprocessed_history.size() - 1U),
processor,
live_config
);
publish_result_collection(replay_result, results_ring_, oversized_results);
publish_locator(replay_result);
}
last_replayed_revision = live_revision;
//record the speed we just reprocessed with so an
// unchanged socket value does not retrigger every iteration.
last_reprocessed_socket_speed = current_socket_speed;
}
if (preprocessed_ring_.pop(bytes)) {
@@ -219,11 +216,12 @@ auto DataProcessor::process_collection(
return result;
}
auto DataProcessor::resolve_processor(const ProcessingLiveConfig& live_config) -> ProcessorInterface& {
const std::string requested_mode =
live_config.processor_mode.empty() ? default_processor_mode_ : live_config.processor_mode;
auto DataProcessor::requested_mode(const ProcessingLiveConfig& live_config) const -> const std::string& {
return live_config.processor_mode.empty() ? default_processor_mode_ : live_config.processor_mode;
}
if (auto found = processors_.find(requested_mode); found != processors_.end()) {
auto DataProcessor::resolve_processor(const ProcessingLiveConfig& live_config) -> ProcessorInterface& {
if (auto found = processors_.find(requested_mode(live_config)); found != processors_.end()) {
return *(found->second);
}
if (auto fallback = processors_.find(default_processor_mode_); fallback != processors_.end()) {
@@ -233,9 +231,12 @@ auto DataProcessor::resolve_processor(const ProcessingLiveConfig& live_config) -
}
auto DataProcessor::should_replay_entire_history(const ProcessingLiveConfig& live_config) const -> bool {
const std::string requested_mode =
live_config.processor_mode.empty() ? default_processor_mode_ : live_config.processor_mode;
return requested_mode == "bscan";
return requested_mode(live_config) == "bscan";
}
auto DataProcessor::uses_socket_speed(const ProcessingLiveConfig& live_config) const -> bool {
const auto& mode = requested_mode(live_config);
return mode == "gpr" || mode == "sar3d";
}
auto DataProcessor::socket_speed(const ProcessingLiveConfig& live_config) const -> std::optional<double> {
@@ -93,24 +93,9 @@ void fft_inplace(std::vector<std::complex<double>>& values, bool inverse) {
return std::abs(sample);
}
[[nodiscard]] auto fallback_profile(
const ipc::SweepTraceBlock& trace,
std::span<const ipc::Complex32> selected_samples
) -> BScanProfile {
const std::size_t point_count = std::min(trace.frequency_hz.size(), selected_samples.size());
BScanProfile fallback{};
fallback.depth_m.reserve(point_count);
fallback.response.reserve(point_count);
const float denominator = point_count > 1U ? static_cast<float>(point_count - 1U) : 1.0F;
for (std::size_t index = 0U; index < point_count; ++index) {
const auto& sample = selected_samples[index];
fallback.depth_m.push_back(static_cast<float>(static_cast<float>(index) / denominator));
fallback.response.push_back(std::sqrt(sample.re * sample.re + sample.im * sample.im));
}
return fallback;
}
// A range profile needs at least two in-band points on an increasing frequency grid.
// Otherwise the profile is empty and the display leaves the column out: drawing |S21|
// against a made-up depth axis would look like a valid B-scan.
[[nodiscard]] auto compute_bscan_profile(
const ipc::SweepTraceBlock& trace,
const ProcessingLiveConfig& live_config
@@ -118,7 +103,7 @@ void fft_inplace(std::vector<std::complex<double>>& values, bool inverse) {
const auto selected_samples = std::span<const ipc::Complex32>(trace.s21);
const std::size_t point_count = std::min(trace.frequency_hz.size(), selected_samples.size());
if (point_count < 2U) {
return fallback_profile(trace, selected_samples);
return {};
}
const double configured_start_hz = static_cast<double>(live_config.bscan_start_freq_mhz) * 1'000'000.0;
@@ -142,35 +127,35 @@ void fft_inplace(std::vector<std::complex<double>>& values, bool inverse) {
}
if (filtered_freq_hz.size() < 2U) {
return fallback_profile(trace, selected_samples);
return {};
}
const std::size_t filtered_count = filtered_freq_hz.size();
const double df = (filtered_freq_hz.back() - filtered_freq_hz.front()) / static_cast<double>(filtered_count - 1U);
if (df <= 0.0) {
return fallback_profile(trace, selected_samples);
return {};
}
const auto start_bin = static_cast<std::int64_t>(std::llround(filtered_freq_hz.front() / df));
if (start_bin < 0) {
return fallback_profile(trace, selected_samples);
return {};
}
const auto start_index = static_cast<std::size_t>(start_bin);
if (start_index > (std::numeric_limits<std::size_t>::max() / 2U)) {
return fallback_profile(trace, selected_samples);
return {};
}
if (start_index > (std::numeric_limits<std::size_t>::max() - filtered_count + 1U)) {
return fallback_profile(trace, selected_samples);
return {};
}
const std::size_t min_fft_len = 2U * (start_index + filtered_count - 1U);
const std::size_t fft_len = next_power_of_two(min_fft_len);
if (fft_len < min_fft_len || (fft_len & (fft_len - 1U)) != 0U) {
return fallback_profile(trace, selected_samples);
return {};
}
if (start_index > fft_len || filtered_count > (fft_len - start_index)) {
return fallback_profile(trace, selected_samples);
return {};
}
std::vector<std::complex<double>> spectrum(fft_len, std::complex<double>(0.0, 0.0));