FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
processing::pipeline Namespace Reference

Classes

struct  SampleSpan
 Half-open sample span used for dechirped LO-active finalization ranges. More...
 

Functions

void advanceTimingModel (timing::Timing *timing_model, const radar::Receiver *receiver, RealType rate)
 Advances the receiver's timing model to the start of the next processing window.
 
std::tuple< RealType, RealTypecalculateJitteredStart (RealType ideal_start, RealType first_phase_noise, RealType carrier_freq, RealType rate)
 Calculates the jittered start time and fractional delay from a phase noise sample.
 
void applyStreamingInterference (std::span< ComplexType > window, RealType actual_start, RealType dt, const radar::Receiver *receiver, const std::vector< core::ActiveStreamingSource > &streaming_sources, const std::vector< std::unique_ptr< radar::Target > > *targets, core::ReceiverTrackerCache &tracker_cache, const simulation::CwPhaseNoiseLookup *phase_noise_lookup=nullptr)
 Applies streaming interference to a time window.
 
void applyPulsedInterference (std::vector< ComplexType > &iq_buffer, const std::vector< std::unique_ptr< serial::Response > > &interference_log)
 Renders and applies pulsed interference to a streaming IQ buffer.
 
void applyPulsedInterference (std::vector< ComplexType > &iq_buffer, const std::vector< std::unique_ptr< serial::Response > > &interference_log, std::span< const SampleSpan > active_spans, RealType output_sample_rate)
 Renders and applies pulsed interference only inside active sample spans.
 
void addPhaseNoiseToWindow (std::span< const RealType > noise, std::span< ComplexType > window)
 Applies a pre-generated sequence of phase noise samples to an I/Q buffer.
 
RealType applyDownsamplingAndQuantization (std::vector< ComplexType > &buffer)
 Downsamples and quantizes an IQ buffer.
 
void applyDownsampling (std::vector< ComplexType > &buffer)
 Downsamples an IQ buffer to the configured output rate without quantization.
 
void exportStreamingToHdf5 (const std::string &filename, const std::vector< ComplexType > &iq_buffer, RealType fullscale, RealType ref_freq, const core::OutputFileMetadata *metadata=nullptr, RealType sample_rate=0.0)
 Exports a finalized streaming IQ buffer to an HDF5 file.
 

Function Documentation

◆ addPhaseNoiseToWindow()

void processing::pipeline::addPhaseNoiseToWindow ( std::span< const RealType noise,
std::span< ComplexType window 
)

Applies a pre-generated sequence of phase noise samples to an I/Q buffer.

This function performs the complex multiplication IQ_out = IQ_in * e^(j*phase_noise) for each sample in the window, effectively modulating the phase of the signal.

Parameters
noiseA span of phase noise samples in radians.
windowThe I/Q buffer to be modified.

Definition at line 230 of file finalizer_pipeline.cpp.

231 {
232 for (auto [n, w] : std::views::zip(noise, window))
233 {
234 w *= std::polar(1.0, n);
235 }
236 }
math::Vec3 max

References max.

Referenced by processing::runPulsedFinalizer().

+ Here is the caller graph for this function:

◆ advanceTimingModel()

void processing::pipeline::advanceTimingModel ( timing::Timing timing_model,
const radar::Receiver receiver,
RealType  rate 
)

Advances the receiver's timing model to the start of the next processing window.

This function handles the "dead time" between receive windows. For sync-on-pulse models, it resets the phase and skips to the window's start offset. For free-running models, it skips the number of samples corresponding to the inter-pulse period.

Parameters
timing_modelThe stateful timing model instance to advance.
receiverThe receiver whose properties (sync mode, PRF, etc.) determine how to advance the model.
rateThe oversampled simulation rate, used to calculate samples to skip.

Definition at line 67 of file finalizer_pipeline.cpp.

68 {
69
70 if ((timing_model == nullptr) || !timing_model->isEnabled())
71 {
72 return;
73 }
74
75 // Convert the duration-derived sample advance to a signed temporary first.
76 // The physical calculation can legitimately produce a negative or zero result
77 // (for example due to floor() or an inter-pulse gap that is not positive),
78 // but skipSamples() models advancing by a non-negative sample count only.
79 // After validating that the computed value is > 0, cast to std::size_t for
80 // the skipSamples() call chain:
81 // Timing::skipSamples -> ClockModelGenerator::skipSamples ->
82 // MultirateGenerator::skipSamples.
83 // This preserves the sign until validation and avoids passing invalid negative
84 // counts into the timing/noise generators.
85 if (timing_model->getSyncOnPulse())
86 {
87 timing_model->reset();
88 const auto skip_samples = static_cast<long long>(std::floor(rate * receiver->getWindowSkip()));
89 if (skip_samples > 0)
90 {
91 timing_model->skipSamples(static_cast<std::size_t>(skip_samples));
92 }
93 }
94 else
95 {
96 const RealType inter_pulse_skip_duration = 1.0 / receiver->getWindowPrf() - receiver->getWindowLength();
97 const auto samples_to_skip = static_cast<long long>(std::floor(rate * inter_pulse_skip_duration));
98 if (samples_to_skip > 0)
99 {
100 timing_model->skipSamples(static_cast<std::size_t>(samples_to_skip));
101 }
102 }
103 }
const Receiver & receiver
double RealType
Type for real numbers.
Definition config.h:27

References max, and receiver.

Referenced by processing::runPulsedFinalizer().

+ Here is the caller graph for this function:

◆ applyDownsampling()

void processing::pipeline::applyDownsampling ( std::vector< ComplexType > &  buffer)

Downsamples an IQ buffer to the configured output rate without quantization.

This is used by streaming output sinks that must apply their own fixed-scale quantization after all producer-side physics, interference, and noise are complete.

Parameters
bufferThe I/Q buffer to downsample in place when oversampling is configured.

Definition at line 244 of file finalizer_pipeline.cpp.

245 {
246 if (params::oversampleRatio() > 1)
247 {
249 }
250 }
std::vector< ComplexType > downsample(std::span< const ComplexType > in)
Low-pass filters and decimates an oversampled complex waveform back to base rate.
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
Definition parameters.h:151

References fers_signal::downsample(), max, and params::oversampleRatio().

Referenced by applyDownsamplingAndQuantization(), and processing::runPulsedFinalizer().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ applyDownsamplingAndQuantization()

RealType processing::pipeline::applyDownsamplingAndQuantization ( std::vector< ComplexType > &  buffer)

Downsamples and quantizes an IQ buffer.

This function performs the final processing steps. If oversampling is enabled, it first downsamples the buffer to the final output rate. It then simulates an ADC by quantizing and scaling the data.

Parameters
bufferThe I/Q buffer to be processed. This is an in-out parameter; it will be replaced by the downsampled buffer if applicable.
Returns
The full-scale value (RealType) calculated during quantization, which is needed for HDF5 metadata.

Definition at line 238 of file finalizer_pipeline.cpp.

239 {
242 }
void applyDownsampling(std::vector< ComplexType > &buffer)
Downsamples an IQ buffer to the configured output rate without quantization.
RealType quantizeAndScaleWindow(std::span< ComplexType > window)
Simulates ADC quantization and scales a window of complex I/Q samples.

References applyDownsampling(), max, and processing::quantizeAndScaleWindow().

+ Here is the call graph for this function:

◆ applyPulsedInterference() [1/2]

void processing::pipeline::applyPulsedInterference ( std::vector< ComplexType > &  iq_buffer,
const std::vector< std::unique_ptr< serial::Response > > &  interference_log 
)

Renders and applies pulsed interference to a streaming IQ buffer.

Processes a log of Response objects that represent pulsed signals received during a streaming receiver's operation. Each response is rendered into a temporary buffer and then added to the main streaming IQ buffer at the correct time offset.

Parameters
iq_bufferThe main, simulation-long IQ buffer for the streaming receiver.
interference_logA list of Response objects representing the pulsed interference.

Definition at line 173 of file finalizer_pipeline.cpp.

175 {
176 const std::array active_spans{SampleSpan{.start = 0, .end_exclusive = iq_buffer.size()}};
177 applyPulsedInterference(iq_buffer, interference_log, active_spans,
178 params::rate() * static_cast<RealType>(params::oversampleRatio()));
179 }
RealType rate() noexcept
Get the rendering sample rate.
Definition parameters.h:121
Half-open sample span used for dechirped LO-active finalization ranges.
std::size_t start
Inclusive first sample index in the span.

References applyPulsedInterference(), max, params::oversampleRatio(), params::rate(), and processing::pipeline::SampleSpan::start.

Referenced by applyPulsedInterference().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ applyPulsedInterference() [2/2]

void processing::pipeline::applyPulsedInterference ( std::vector< ComplexType > &  iq_buffer,
const std::vector< std::unique_ptr< serial::Response > > &  interference_log,
std::span< const SampleSpan active_spans,
RealType  output_sample_rate 
)

Renders and applies pulsed interference only inside active sample spans.

Parameters
iq_bufferThe main, simulation-long IQ buffer for the streaming receiver.
interference_logA list of Response objects representing the pulsed interference.
active_spansHalf-open sample spans where LO-active output should receive interference.
output_sample_rateThe sample rate used by iq_buffer, in hertz.

Definition at line 181 of file finalizer_pipeline.cpp.

184 {
185 for (const auto& response : interference_log)
186 {
187 unsigned psize = 0;
188 RealType prate = std::numeric_limits<RealType>::quiet_NaN();
189 const auto rendered_pulse = response->renderBinary(prate, psize, 0.0);
190 const RealType rate_tolerance = std::numeric_limits<RealType>::epsilon() *
191 std::max(std::abs(prate), std::abs(output_sample_rate)) * 16.0;
192 if (std::abs(prate - output_sample_rate) > rate_tolerance)
193 {
194 throw std::runtime_error(
195 "Pulsed interference sample rate must match the streaming output sample rate.");
196 }
197
198 const RealType pulse_end_time = response->startTime() + static_cast<RealType>(psize) / prate;
199 const auto pulse_start_index =
200 static_cast<long long>(std::floor((response->startTime() - params::startTime()) * output_sample_rate));
201 const auto pulse_end_index =
202 static_cast<long long>(std::ceil((pulse_end_time - params::startTime()) * output_sample_rate));
203 const auto buffer_end_index = static_cast<long long>(iq_buffer.size());
204
205 for (const auto& span : active_spans)
206 {
207 const auto span_start = static_cast<long long>(std::min(span.start, iq_buffer.size()));
208 const auto span_end = static_cast<long long>(std::min(span.end_exclusive, iq_buffer.size()));
209 const auto dest_begin = std::max({span_start, pulse_start_index, 0LL});
210 const auto dest_end = std::min({span_end, pulse_end_index, buffer_end_index});
211 if (dest_begin >= dest_end)
212 {
213 continue;
214 }
215
216 const auto copy_count = static_cast<std::size_t>(dest_end - dest_begin);
218 for (std::size_t i = 0; i < copy_count; ++i, ++source_index)
219 {
220 if (source_index >= 0 && source_index < static_cast<long long>(rendered_pulse.size()))
221 {
222 iq_buffer[static_cast<std::size_t>(dest_begin) + i] +=
223 rendered_pulse[static_cast<std::size_t>(source_index)];
224 }
225 }
226 }
227 }
228 }
RealType startTime() noexcept
Get the start time for the simulation.
Definition parameters.h:103

References max, and params::startTime().

+ Here is the call graph for this function:

◆ applyStreamingInterference()

void processing::pipeline::applyStreamingInterference ( std::span< ComplexType window,
RealType  actual_start,
RealType  dt,
const radar::Receiver receiver,
const std::vector< core::ActiveStreamingSource > &  streaming_sources,
const std::vector< std::unique_ptr< radar::Target > > *  targets,
core::ReceiverTrackerCache tracker_cache,
const simulation::CwPhaseNoiseLookup phase_noise_lookup = nullptr 
)

Applies streaming interference to a time window.

Iterates through each sample of a processing window, calculating the combined direct and reflected path contributions from all active streaming sources at that precise moment in time. The resulting complex sample is added to the window.

Parameters
windowThe I/Q buffer for the receive window to which interference will be added.
actual_startThe jittered, sample-aligned start time of the window.
dtThe time step between samples (1.0 / rate).
receiverThe receiver being interfered with.
streaming_sourcesA list of currently active streaming transmitters.
targetsThe list of all targets for calculating reflected paths.
tracker_cacheCaller-owned reusable tracker storage for FMCW path boundary state.

Definition at line 114 of file finalizer_pipeline.cpp.

120 {
121 const simulation::CwPhaseNoiseLookup* lookup = phase_noise_lookup;
122 std::optional<simulation::CwPhaseNoiseLookup> owned_lookup;
123 if (lookup == nullptr)
124 {
125 std::unordered_map<SimId, std::shared_ptr<timing::Timing>> unique_timings;
126 unique_timings.try_emplace(receiver->getTiming()->getId(), receiver->getTiming());
127 for (const auto& streaming_source : streaming_sources)
128 {
129 unique_timings.try_emplace(streaming_source.transmitter->getTiming()->getId(),
130 streaming_source.transmitter->getTiming());
131 }
132
133 std::vector<std::shared_ptr<timing::Timing>> timings;
134 timings.reserve(unique_timings.size());
135 for (const auto& entry : unique_timings)
136 {
137 timings.push_back(entry.second);
138 }
139
140 const RealType end_time =
141 actual_start + dt * static_cast<RealType>(window.empty() ? 0 : (window.size() - 1));
144 }
145
147
149 for (auto& window_sample : window)
150 {
152 for (std::size_t source_index = 0; source_index < streaming_sources.size(); ++source_index)
153 {
156 {
159 }
160 for (std::size_t target_index = 0; target_index < targets->size(); ++target_index)
161 {
162 const auto& target_ptr = (*targets)[target_index];
166 }
167 }
169 t_sample += dt;
170 }
171 }
@ FLAG_NODIRECT
Disable direct-path reception.
std::complex< RealType > ComplexType
Type for complex numbers.
Definition config.h:35
ComplexType calculateStreamingDirectPathContribution(const core::ActiveStreamingSource &source, const Receiver *recv, const RealType timeK, const CwPhaseNoiseLookup *const phase_noise_lookup, core::FmcwChirpBoundaryTracker *const chirp_tracker, const StreamingTimingPhaseMode timing_phase_mode)
Calculates a direct-path contribution from a cached streaming source.
ComplexType calculateStreamingReflectedPathContribution(const core::ActiveStreamingSource &source, const Receiver *recv, const Target *targ, const RealType timeK, const CwPhaseNoiseLookup *const phase_noise_lookup, core::FmcwChirpBoundaryTracker *const chirp_tracker, const StreamingTimingPhaseMode timing_phase_mode)
Calculates a reflected-path contribution from a cached streaming source.
Lookup table for CW phase noise across timing sources.
static CwPhaseNoiseLookup build(std::span< const std::shared_ptr< timing::Timing > > timings, RealType start_time, RealType end_time)
Builds a phase-noise lookup for the requested timing sources and time range.

References simulation::CwPhaseNoiseLookup::build(), simulation::calculateStreamingDirectPathContribution(), simulation::calculateStreamingReflectedPathContribution(), radar::Receiver::FLAG_NODIRECT, max, and receiver.

Referenced by processing::runPulsedFinalizer().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ calculateJitteredStart()

std::tuple< RealType, RealType > processing::pipeline::calculateJitteredStart ( RealType  ideal_start,
RealType  first_phase_noise,
RealType  carrier_freq,
RealType  rate 
)

Calculates the jittered start time and fractional delay from a phase noise sample.

Converts the first phase noise sample (in radians) of a window into a time jitter offset. It then decomposes the resulting "actual" start time into a component aligned with the sample clock and a fractional delay to be handled by the rendering engine's interpolation filter.

Parameters
ideal_startThe perfect, jitter-free start time of the window.
first_phase_noiseThe first phase noise sample (radians) for this window.
carrier_freqThe carrier frequency, needed to convert phase to time.
rateThe sampling rate, used for sample clock alignment.
Returns
A tuple containing:
  1. The sample-aligned start time (RealType).
  2. The fractional sample delay (RealType).

Definition at line 105 of file finalizer_pipeline.cpp.

107 {
108 const RealType actual_start = ideal_start + first_phase_noise / (2.0 * PI * carrier_freq);
109 const RealType rounded_start = std::round(actual_start * rate) / rate;
110 const RealType fractional_delay = actual_start * rate - std::round(actual_start * rate);
112 }
constexpr RealType PI
Mathematical constant π (pi).
Definition config.h:43

References max, and PI.

Referenced by processing::runPulsedFinalizer().

+ Here is the caller graph for this function:

◆ exportStreamingToHdf5()

void processing::pipeline::exportStreamingToHdf5 ( const std::string &  filename,
const std::vector< ComplexType > &  iq_buffer,
RealType  fullscale,
RealType  ref_freq,
const core::OutputFileMetadata metadata = nullptr,
RealType  sample_rate = 0.0 
)

Exports a finalized streaming IQ buffer to an HDF5 file.

Creates an HDF5 file, splits the complex buffer into real (I) and imaginary (Q) components, writes them as separate datasets, and adds relevant simulation parameters (sample rate, start time, etc.) as file attributes.

Parameters
filenameThe path to the output HDF5 file.
iq_bufferThe final, processed I/Q data to write.
fullscaleThe full-scale value from quantization, saved as metadata.
ref_freqThe reference carrier frequency, saved as metadata.
metadataOptional structured output metadata to attach to the file.
sample_rateOutput sample rate to store in the file, or params::rate() when unset.

Definition at line 252 of file finalizer_pipeline.cpp.

255 {
256 std::scoped_lock const lock(serial::hdf5_global_mutex);
257 try
258 {
259 HighFive::File file(filename, HighFive::File::Truncate);
260
261 std::vector<RealType> i_data(iq_buffer.size());
262 std::vector<RealType> q_data(iq_buffer.size());
263 std::ranges::transform(iq_buffer, i_data.begin(), [](const auto& c) { return c.real(); });
264 std::ranges::transform(iq_buffer, q_data.begin(), [](const auto& c) { return c.imag(); });
265
266 HighFive::DataSet i_dataset = file.createDataSet<RealType>("I_data", HighFive::DataSpace::From(i_data));
267 i_dataset.write(i_data);
268 HighFive::DataSet q_dataset = file.createDataSet<RealType>("Q_data", HighFive::DataSpace::From(q_data));
269 q_dataset.write(q_data);
270
271 file.createAttribute("sampling_rate", sample_rate > 0.0 ? sample_rate : params::rate());
272 file.createAttribute("start_time", params::startTime());
273 file.createAttribute("fullscale", fullscale);
274 file.createAttribute("reference_carrier_frequency", ref_freq);
275 if (metadata != nullptr)
276 {
278 }
279
280 LOG(logging::Level::INFO, "Successfully exported streaming data to '{}'", filename);
281 }
282 catch (const HighFive::Exception& err)
283 {
284 LOG(logging::Level::FATAL, "Error writing streaming data to HDF5 file '{}': {}", filename, err.what());
285 }
286 }
#define LOG(level,...)
Definition logging.h:19
@ FATAL
Fatal level for severe error events.
@ INFO
Info level for informational messages.
std::mutex hdf5_global_mutex
Global mutex to protect all HDF5 C-library calls, which are not thread-safe.
void writeOutputFileMetadataAttributes(HighFive::File &file, const core::OutputFileMetadata &metadata)
Writes additive FERS output metadata attributes to an open HDF5 file.
RealType c

References c, logging::FATAL, serial::hdf5_global_mutex, logging::INFO, LOG, max, params::rate(), params::startTime(), and serial::writeOutputFileMetadataAttributes().

Referenced by serial::Hdf5OutputSink::Impl::closeStreamingStream().

+ Here is the call graph for this function:
+ Here is the caller graph for this function: