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

Namespaces

namespace  pipeline
 

Classes

struct  FixedFullscaleIqSample
 One complex Cartesian IQ sample scaled for VITA-style signed 16-bit transport. More...
 
struct  FixedFullscaleScalingResult
 Result of fixed-fullscale IQ scaling. More...
 

Functions

core::OutputFileMetadata buildStreamingOutputMetadata (const radar::Receiver *receiver, const std::string &output_path, std::size_t total_samples, const std::vector< core::ActiveStreamingSource > &streaming_sources, RealType output_sample_rate)
 Builds HDF5 file metadata for a streaming receiver result emitted through the output sink.
 
core::ReceiverStreamDescriptor buildReceiverStreamDescriptor (const radar::Receiver *receiver, RealType sample_rate, std::span< const core::ActiveStreamingSource > streaming_sources={})
 Builds the receiver stream descriptor used by output sinks.
 
core::ReceiverSampleBlock buildReceiverSampleBlock (const radar::Receiver *receiver, RealType first_sample_time, RealType sample_rate, std::span< const ComplexType > samples, std::uint64_t sample_start, std::shared_ptr< const core::OutputFileMetadata > file_metadata=nullptr)
 Builds a non-owning output sample block over contiguous processed complex samples.
 
core::ReceiverSampleBlock buildReceiverSampleBlock (const radar::Receiver *receiver, RealType first_sample_time, RealType sample_rate, std::span< const ComplexType > samples, std::uint64_t sample_start, std::span< const core::ActiveStreamingSource > streaming_sources, std::shared_ptr< const core::OutputFileMetadata > file_metadata)
 Builds a sample block with active streaming-source context for VITA Context packets.
 
void runPulsedFinalizer (radar::Receiver *receiver, const std::vector< std::unique_ptr< radar::Target > > *targets, const std::shared_ptr< core::ProgressReporter > &reporter, const std::string &output_dir, const std::shared_ptr< core::OutputMetadataCollector > &metadata_collector=nullptr, core::ReceiverOutputSink *output_sink=nullptr)
 The main function for a dedicated pulsed-mode receiver finalizer thread.
 
void renderWindow (std::vector< ComplexType > &window, RealType length, RealType start, RealType fracDelay, std::span< const std::unique_ptr< serial::Response > > responses)
 Renders a time-window of I/Q data from a collection of raw radar responses.
 
void applyThermalNoise (std::span< ComplexType > window, RealType noiseTemperature, std::mt19937 &rngEngine)
 Applies thermal (Johnson-Nyquist) noise to a window of I/Q samples.
 
void applyThermalNoiseAtSampleRate (std::span< ComplexType > window, RealType noiseTemperature, std::mt19937 &rngEngine, RealType sampleRateHz)
 Applies circular complex thermal noise using a caller-specified complex-baseband sample rate.
 
RealType quantizeAndScaleWindow (std::span< ComplexType > window)
 Simulates ADC quantization and scales a window of complex I/Q samples.
 
FixedFullscaleScalingResult scaleToInt16FixedFullscale (std::span< const ComplexType > samples, RealType fullscale)
 Scales complex samples against a fixed full-scale to signed int16 IQ.
 

Function Documentation

◆ applyThermalNoise()

void processing::applyThermalNoise ( std::span< ComplexType window,
RealType  noiseTemperature,
std::mt19937 &  rngEngine 
)

Applies thermal (Johnson-Nyquist) noise to a window of I/Q samples.

Simulates the addition of white Gaussian noise based on the receiver's noise temperature and the simulation bandwidth.

Parameters
windowA span of I/Q data to which noise will be added.
noiseTemperatureThe effective noise temperature in Kelvin.
rngEngineA random number generator engine to use for noise generation.

Definition at line 142 of file signal_processor.cpp.

143 {
144 if (noiseTemperature == 0)
145 {
146 return;
147 }
148
149 const RealType b = params::rate() / (2.0 * params::oversampleRatio());
151
152 // Split total power equally between the I and Q channels
154 const RealType stddev = std::sqrt(per_channel_power);
155
157 for (auto& sample : window)
158 {
159 sample += ComplexType(generator.getSample(), generator.getSample());
160 }
161 }
Generates white Gaussian noise.
double RealType
Type for real numbers.
Definition config.h:27
std::complex< RealType > ComplexType
Type for complex numbers.
Definition config.h:35
RealType rate() noexcept
Get the rendering sample rate.
Definition parameters.h:121
RealType boltzmannK() noexcept
Get the Boltzmann constant.
Definition parameters.h:97
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
Definition parameters.h:151
math::Vec3 max
RealType b

References b, params::boltzmannK(), max, params::oversampleRatio(), and params::rate().

+ Here is the call graph for this function:

◆ applyThermalNoiseAtSampleRate()

void processing::applyThermalNoiseAtSampleRate ( std::span< ComplexType window,
const RealType  noiseTemperature,
std::mt19937 &  rngEngine,
const RealType  sampleRateHz 
)

Applies circular complex thermal noise using a caller-specified complex-baseband sample rate.

Definition at line 163 of file signal_processor.cpp.

165 {
166 if (noiseTemperature == 0 || sampleRateHz <= 0.0)
167 {
168 return;
169 }
170
173 const RealType stddev = std::sqrt(per_channel_power);
174
176 for (auto& sample : window)
177 {
178 sample += ComplexType(generator.getSample(), generator.getSample());
179 }
180 }

References params::boltzmannK(), and max.

Referenced by runPulsedFinalizer().

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

◆ buildReceiverSampleBlock() [1/2]

core::ReceiverSampleBlock processing::buildReceiverSampleBlock ( const radar::Receiver receiver,
const RealType  first_sample_time,
const RealType  sample_rate,
const std::span< const ComplexType samples,
const std::uint64_t  sample_start,
std::shared_ptr< const core::OutputFileMetadata file_metadata 
)

Builds a non-owning output sample block over contiguous processed complex samples.

Definition at line 702 of file finalizer.cpp.

707 {
708 return buildReceiverSampleBlock(receiver, first_sample_time, sample_rate, samples, sample_start,
709 std::span<const core::ActiveStreamingSource>{}, std::move(file_metadata));
710 }
const Receiver & receiver
core::ReceiverSampleBlock buildReceiverSampleBlock(const radar::Receiver *receiver, const RealType first_sample_time, const RealType sample_rate, const std::span< const ComplexType > samples, const std::uint64_t sample_start, std::shared_ptr< const core::OutputFileMetadata > file_metadata)
Builds a non-owning output sample block over contiguous processed complex samples.

References buildReceiverSampleBlock(), and receiver.

Referenced by buildReceiverSampleBlock(), and runPulsedFinalizer().

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

◆ buildReceiverSampleBlock() [2/2]

core::ReceiverSampleBlock processing::buildReceiverSampleBlock ( const radar::Receiver receiver,
const RealType  first_sample_time,
const RealType  sample_rate,
const std::span< const ComplexType samples,
const std::uint64_t  sample_start,
const std::span< const core::ActiveStreamingSource streaming_sources,
std::shared_ptr< const core::OutputFileMetadata file_metadata 
)

Builds a sample block with active streaming-source context for VITA Context packets.

Definition at line 713 of file finalizer.cpp.

718 {
721 .first_sample_time = first_sample_time,
722 .sample_rate = sample_rate,
723 .samples = samples,
724 .sample_start = sample_start,
725 .valid_data = true,
726 .calibrated_time = true,
727 .reference_lock = true,
728 .file_metadata = std::move(file_metadata)};
729 }
core::ReceiverStreamDescriptor buildReceiverStreamDescriptor(const radar::Receiver *receiver, const RealType sample_rate, const std::span< const core::ActiveStreamingSource > streaming_sources)
Builds the receiver stream descriptor used by output sinks.
ReceiverStreamDescriptor stream

References buildReceiverStreamDescriptor(), max, receiver, core::ReceiverStreamDescriptor::sample_rate, and core::ReceiverSampleBlock::stream.

+ Here is the call graph for this function:

◆ buildReceiverStreamDescriptor()

core::ReceiverStreamDescriptor processing::buildReceiverStreamDescriptor ( const radar::Receiver receiver,
const RealType  sample_rate,
const std::span< const core::ActiveStreamingSource streaming_sources 
)

Builds the receiver stream descriptor used by output sinks.

Definition at line 679 of file finalizer.cpp.

681 {
683 .receiver_name = receiver->getName(),
685 .sample_rate = sample_rate,
686 .bandwidth = sample_rate > 0.0 ? sample_rate / 2.0 : 0.0,
687 .dechirped = receiver->isDechirpEnabled(),
688 .if_resampled = receiver->getFmcwIfResamplerPlan().has_value(),
689 .adc_bits = params::adcBits(),
690 .coordinate = buildCoordinateContext(),
691 .initial_platform_state = buildInitialPlatformState(receiver),
692 .pulsed = buildPulsedContext(receiver),
695 if (const auto timing = receiver->getTiming(); timing)
696 {
697 descriptor.reference_frequency = timing->getFrequency();
698 }
699 return descriptor;
700 }
unsigned adcBits() noexcept
Get the ADC quantization bits.
Definition parameters.h:133

References params::adcBits(), max, receiver, and core::ReceiverStreamDescriptor::receiver_id.

Referenced by buildReceiverSampleBlock(), and runPulsedFinalizer().

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

◆ buildStreamingOutputMetadata()

core::OutputFileMetadata processing::buildStreamingOutputMetadata ( const radar::Receiver receiver,
const std::string &  output_path,
const std::size_t  total_samples,
const std::vector< core::ActiveStreamingSource > &  streaming_sources,
const RealType  output_sample_rate 
)

Builds HDF5 file metadata for a streaming receiver result emitted through the output sink.

Definition at line 668 of file finalizer.cpp.

671 {
672 const auto dechirp_time_spans =
673 receiver->isDechirpEnabled() ? dechirpActiveTimeSpans(receiver) : std::vector<TimeSpan>{};
676 }

References max, and receiver.

◆ quantizeAndScaleWindow()

RealType processing::quantizeAndScaleWindow ( std::span< ComplexType window)

Simulates ADC quantization and scales a window of complex I/Q samples.

This function first finds the maximum absolute value in the I/Q data to determine the full-scale range. It then simulates the quantization process based on the configured number of ADC bits. If no quantization is specified (adc_bits=0), it normalizes the data to a maximum amplitude of 1.0.

Parameters
windowThe window of complex I/Q samples to quantize and scale.
Returns
The full-scale value used for quantization/normalization.

Definition at line 182 of file signal_processor.cpp.

183 {
185 for (const auto& sample : window)
186 {
187 const RealType real_abs = std::fabs(sample.real());
188 const RealType imag_abs = std::fabs(sample.imag());
189
190 max_value = std::max({max_value, real_abs, imag_abs});
191 }
192
193 if (const auto adc_bits = params::adcBits(); adc_bits > 0)
194 {
195 adcSimulate(window, adc_bits, max_value);
196 }
197 else if (max_value != 0)
198 {
199 for (auto& sample : window)
200 {
201 sample /= max_value;
202 }
203 }
204 return max_value;
205 }

References params::adcBits(), and max.

Referenced by processing::pipeline::applyDownsamplingAndQuantization(), serial::Hdf5OutputSink::Impl::closeStreamingStream(), and serial::Hdf5OutputSink::Impl::writePulsedBlock().

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

◆ renderWindow()

void processing::renderWindow ( std::vector< ComplexType > &  window,
RealType  length,
RealType  start,
RealType  fracDelay,
std::span< const std::unique_ptr< serial::Response > >  responses 
)

Renders a time-window of I/Q data from a collection of raw radar responses.

This function orchestrates the process of converting abstract Response objects into a concrete vector of complex I/Q samples for a specific time window. It handles the superposition of multiple signals arriving at the receiver during the window and can use a thread pool for parallel processing.

Parameters
windowThe output vector where the rendered I/Q samples will be added.
lengthThe duration of the time window in seconds.
startThe start time of the window in seconds.
fracDelayA fractional sample delay to apply for fine-grained timing.
responsesA span of unique pointers to the Response objects to be rendered.

Definition at line 110 of file signal_processor.cpp.

112 {
113 const RealType end = start + length;
114 std::queue<serial::Response*> work_list;
115
116 for (const auto& response : responses)
117 {
118 if (response->startTime() <= end && response->endTime() >= start)
119 {
120 work_list.push(response.get());
121 }
122 }
123
125 const auto local_window_size = static_cast<std::size_t>(std::ceil(length * rate));
126
128
129 while (!work_list.empty())
130 {
131 const auto* resp = work_list.front();
132 work_list.pop();
134 }
135
136 for (std::size_t i = 0; i < local_window_size; ++i)
137 {
138 window[i] += local_window[i];
139 }
140 }

References max, params::oversampleRatio(), and params::rate().

Referenced by runPulsedFinalizer().

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

◆ runPulsedFinalizer()

void processing::runPulsedFinalizer ( radar::Receiver receiver,
const std::vector< std::unique_ptr< radar::Target > > *  targets,
const std::shared_ptr< core::ProgressReporter > &  reporter,
const std::string &  output_dir,
const std::shared_ptr< core::OutputMetadataCollector > &  metadata_collector = nullptr,
core::ReceiverOutputSink output_sink = nullptr 
)

The main function for a dedicated pulsed-mode receiver finalizer thread.

This function runs in a loop, dequeuing and processing RenderingJobs for a specific receiver. It handles all expensive rendering, signal processing, and I/O for that receiver's data.

Parameters
receiverA pointer to the pulsed-mode receiver to process.
targetsA pointer to the world's list of targets for interference calculation.
reporterShared pointer to the progress reporter for status updates.
output_dirOutput directory for the simulation files.

Definition at line 731 of file finalizer.cpp.

735 {
736 (void)output_dir;
737 (void)metadata_collector;
738 if (output_sink == nullptr)
739 {
740 throw std::invalid_argument("runPulsedFinalizer requires a receiver output sink");
741 }
742
743 const auto timing_model = receiver->getTiming()->clone();
744 if (!timing_model)
745 {
746 LOG(logging::Level::FATAL, "Failed to clone timing model for receiver '{}'", receiver->getName());
747 return;
748 }
749
750 const auto sink_stream_id =
752 bool sink_stream_open = false;
753 std::uint64_t sink_sample_start = 0;
754
755 unsigned chunk_index = 0;
756
757 LOG(logging::Level::INFO, "Finalizer thread started for receiver '{}'. Routing to output sink.",
758 receiver->getName());
759
760 auto last_report_time = std::chrono::steady_clock::now();
761 const auto report_interval = std::chrono::milliseconds(100);
763 const RealType dt = 1.0 / rate;
765
766 while (true)
767 {
769 if (!receiver->waitAndDequeueFinalizerJob(job))
770 {
771 break; // Shutdown signal received
772 }
773
774 const auto window_samples = static_cast<unsigned>(std::ceil(job.duration * rate));
775 std::vector pnoise(window_samples, 0.0);
776
777 RealType actual_start = job.ideal_start_time;
778 RealType frac_delay = 0.0;
779
780 if (timing_model->isEnabled())
781 {
782 pipeline::advanceTimingModel(timing_model.get(), receiver, rate);
783 std::ranges::generate(pnoise, [&] { return timing_model->getNextSample(); });
784 std::tie(actual_start, frac_delay) = pipeline::calculateJitteredStart(
785 job.ideal_start_time, pnoise[0], timing_model->getFrequency(), rate);
786 }
787
788 std::vector<ComplexType> window_buffer(window_samples);
789
790 pipeline::applyStreamingInterference(window_buffer, actual_start, dt, receiver,
791 job.active_streaming_sources, targets, streaming_tracker_cache);
792
794
795 if (timing_model->isEnabled())
796 {
797 pipeline::addPhaseNoiseToWindow(pnoise, window_buffer);
798 }
799
800 pipeline::applyDownsampling(window_buffer);
802 receiver->getNoiseTemperature(receiver->getRotation(actual_start)),
803 receiver->getRngEngine(), params::rate());
804 if (!sink_stream_open)
805 {
807 sink_stream_open = true;
808 }
809 const auto block =
811 output_sink->submitBlock(block);
812 sink_sample_start += static_cast<std::uint64_t>(window_buffer.size());
813 ++chunk_index;
814
815 if (reporter)
816 {
817 const auto now = std::chrono::steady_clock::now();
819 {
820 reporter->report(std::format("Exporting {}: Chunk {}", receiver->getName(), chunk_index),
821 static_cast<int>(chunk_index), 0);
823 }
824 }
825 }
826
828 {
829 output_sink->closeStream(sink_stream_id);
830 }
831
832 if (reporter)
833 {
834 reporter->report(std::format("Finished Exporting {}", receiver->getName()), 100, 100);
835 }
836 LOG(logging::Level::INFO, "Finalizer thread for receiver '{}' finished.", receiver->getName());
837 }
#define LOG(level,...)
Definition logging.h:19
@ FATAL
Fatal level for severe error events.
@ INFO
Info level for informational messages.
void applyThermalNoiseAtSampleRate(std::span< ComplexType > window, const RealType noiseTemperature, std::mt19937 &rngEngine, const RealType sampleRateHz)
Applies circular complex thermal noise using a caller-specified complex-baseband sample rate.
void renderWindow(std::vector< ComplexType > &window, const RealType length, const RealType start, const RealType fracDelay, const std::span< const std::unique_ptr< serial::Response > > responses)
Renders a time-window of I/Q data from a collection of raw radar responses.
Per-receiver FMCW tracker state for direct and reflected streaming paths.
A data packet containing all information needed to process one receive window.

References processing::pipeline::addPhaseNoiseToWindow(), processing::pipeline::advanceTimingModel(), processing::pipeline::applyDownsampling(), processing::pipeline::applyStreamingInterference(), applyThermalNoiseAtSampleRate(), buildReceiverSampleBlock(), buildReceiverStreamDescriptor(), processing::pipeline::calculateJitteredStart(), logging::FATAL, logging::INFO, LOG, max, params::oversampleRatio(), params::rate(), receiver, and renderWindow().

+ Here is the call graph for this function:

◆ scaleToInt16FixedFullscale()

FixedFullscaleScalingResult processing::scaleToInt16FixedFullscale ( std::span< const ComplexType samples,
RealType  fullscale 
)

Scales complex samples against a fixed full-scale to signed int16 IQ.

This helper is intended for real-time receiver-output sinks, where scanning a complete future window to derive full-scale would change hardware-like behavior.

Parameters
samplesInput complex samples in physical units.
fullscalePositive fixed full-scale magnitude for both I and Q channels.
Returns
Scaled int16 IQ samples and a count of complex samples that clipped on either channel.
Exceptions
std::invalid_argumentwhen fullscale is not positive.

Definition at line 207 of file signal_processor.cpp.

209 {
210 if (fullscale <= 0.0 || !std::isfinite(fullscale))
211 {
212 throw std::invalid_argument("Fixed full-scale must be positive and finite.");
213 }
214
215 FixedFullscaleScalingResult result;
216 result.samples.reserve(samples.size());
217 for (const auto& sample : samples)
218 {
219 bool clipped = false;
220 const auto i = scaleComponentToInt16(sample.real(), fullscale, clipped);
221 const auto q = scaleComponentToInt16(sample.imag(), fullscale, clipped);
222 if (clipped)
223 {
224 ++result.clipped_sample_count;
225 }
226 result.samples.push_back(FixedFullscaleIqSample{.i = i, .q = q});
227 }
228 return result;
229 }

References processing::FixedFullscaleIqSample::i, and max.