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 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

◆ 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 142 of file signal_processor.cpp.

144 {
145 if (noiseTemperature == 0 || sampleRateHz <= 0.0)
146 {
147 return;
148 }
149
152 const RealType stddev = std::sqrt(per_channel_power);
153
155 for (auto& sample : window)
156 {
157 sample += ComplexType(generator.getSample(), generator.getSample());
158 }
159 }
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 boltzmannK() noexcept
Get the Boltzmann constant.
Definition parameters.h:97
math::Vec3 max

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 989 of file finalizer.cpp.

994 {
995 return buildReceiverSampleBlock(receiver, first_sample_time, sample_rate, samples, sample_start,
996 std::span<const core::ActiveStreamingSource>{}, std::move(file_metadata));
997 }
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 1000 of file finalizer.cpp.

1005 {
1008 .first_sample_time = first_sample_time,
1009 .sample_rate = sample_rate,
1010 .samples = samples,
1011 .sample_start = sample_start,
1012 .valid_data = true,
1013 .calibrated_time = true,
1014 .reference_lock = true,
1015 .file_metadata = std::move(file_metadata)};
1016 }
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 968 of file finalizer.cpp.

970 {
972 .receiver_name = receiver->getName(),
974 .sample_rate = sample_rate,
975 .bandwidth = sample_rate > 0.0 ? sample_rate / 2.0 : 0.0,
976 .dechirped = receiver->isDechirpEnabled(),
977 .if_resampled = receiver->getFmcwIfResamplerPlan().has_value(),
978 .adc_bits = params::adcBits(),
979 .coordinate = buildCoordinateContext(),
980 .initial_platform_state = buildInitialPlatformState(receiver),
981 .pulsed = buildPulsedContext(receiver),
985 descriptor.reference_frequency = referenceFrequency(receiver, descriptor, streaming_sources);
986 return descriptor;
987 }
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 957 of file finalizer.cpp.

960 {
961 const auto dechirp_time_spans =
962 receiver->isDechirpEnabled() ? dechirpActiveTimeSpans(receiver) : std::vector<TimeSpan>{};
965 }

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 161 of file signal_processor.cpp.

162 {
164 for (const auto& sample : window)
165 {
166 const RealType real_abs = std::fabs(sample.real());
167 const RealType imag_abs = std::fabs(sample.imag());
168
169 max_value = std::max({max_value, real_abs, imag_abs});
170 }
171
172 if (const auto adc_bits = params::adcBits(); adc_bits > 0)
173 {
174 adcSimulate(window, adc_bits, max_value);
175 }
176 else if (max_value != 0)
177 {
178 for (auto& sample : window)
179 {
180 sample /= max_value;
181 }
182 }
183 return max_value;
184 }

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 }
RealType rate() noexcept
Get the rendering sample rate.
Definition parameters.h:121
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
Definition parameters.h:151

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 1018 of file finalizer.cpp.

1022 {
1023 (void)output_dir;
1024 (void)metadata_collector;
1025 if (output_sink == nullptr)
1026 {
1027 throw std::invalid_argument("runPulsedFinalizer requires a receiver output sink");
1028 }
1029
1030 const auto timing_model = receiver->getTiming()->clone();
1031 if (!timing_model)
1032 {
1033 LOG(logging::Level::FATAL, "Failed to clone timing model for receiver '{}'", receiver->getName());
1034 return;
1035 }
1036
1037 const auto sink_stream_id =
1039 bool sink_stream_open = false;
1040 std::uint64_t sink_sample_start = 0;
1041
1042 unsigned chunk_index = 0;
1043
1044 LOG(logging::Level::INFO, "Finalizer thread started for receiver '{}'. Routing to output sink.",
1045 receiver->getName());
1046
1047 auto last_report_time = std::chrono::steady_clock::now();
1048 const auto report_interval = std::chrono::milliseconds(100);
1050 const RealType dt = 1.0 / rate;
1052
1053 while (true)
1054 {
1056 if (!receiver->waitAndDequeueFinalizerJob(job))
1057 {
1058 break; // Shutdown signal received
1059 }
1060
1061 const auto window_samples = static_cast<unsigned>(std::ceil(job.duration * rate));
1062 std::vector pnoise(window_samples, 0.0);
1063
1064 RealType actual_start = job.ideal_start_time;
1065 RealType frac_delay = 0.0;
1066
1067 if (timing_model->isEnabled())
1068 {
1069 pipeline::advanceTimingModel(timing_model.get(), receiver, rate);
1070 std::ranges::generate(pnoise, [&] { return timing_model->getNextSample(); });
1071 std::tie(actual_start, frac_delay) = pipeline::calculateJitteredStart(
1072 job.ideal_start_time, pnoise[0], timing_model->getFrequency(), rate);
1073 }
1074
1075 std::vector<ComplexType> window_buffer(window_samples);
1076
1077 pipeline::applyStreamingInterference(window_buffer, actual_start, dt, receiver,
1078 job.active_streaming_sources, targets, streaming_tracker_cache);
1079
1080 renderWindow(window_buffer, job.duration, actual_start, frac_delay, job.responses);
1081
1082 if (timing_model->isEnabled())
1083 {
1084 pipeline::addPhaseNoiseToWindow(pnoise, window_buffer);
1085 }
1086
1087 pipeline::applyDownsampling(window_buffer);
1089 receiver->getNoiseTemperature(receiver->getRotation(actual_start)),
1090 receiver->getRngEngine(), params::rate());
1091 if (!sink_stream_open)
1092 {
1094 sink_stream_open = true;
1095 }
1096 const auto block =
1098 output_sink->submitBlock(block);
1099 sink_sample_start += static_cast<std::uint64_t>(window_buffer.size());
1100 ++chunk_index;
1101
1102 if (reporter)
1103 {
1104 const auto now = std::chrono::steady_clock::now();
1106 {
1107 reporter->report(std::format("Exporting {}: Chunk {}", receiver->getName(), chunk_index),
1108 static_cast<int>(chunk_index), 0);
1110 }
1111 }
1112 }
1113
1114 if (sink_stream_open)
1115 {
1116 output_sink->closeStream(sink_stream_id);
1117 }
1118
1119 if (reporter)
1120 {
1121 reporter->report(std::format("Finished Exporting {}", receiver->getName()), 100, 100);
1122 }
1123 LOG(logging::Level::INFO, "Finalizer thread for receiver '{}' finished.", receiver->getName());
1124 }
#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 186 of file signal_processor.cpp.

188 {
189 if (fullscale <= 0.0 || !std::isfinite(fullscale))
190 {
191 throw std::invalid_argument("Fixed full-scale must be positive and finite.");
192 }
193
194 FixedFullscaleScalingResult result;
195 result.samples.reserve(samples.size());
196 for (const auto& sample : samples)
197 {
198 bool clipped = false;
199 const auto i = scaleComponentToInt16(sample.real(), fullscale, clipped);
200 const auto q = scaleComponentToInt16(sample.imag(), fullscale, clipped);
201 if (clipped)
202 {
203 ++result.clipped_sample_count;
204 }
205 result.samples.push_back(FixedFullscaleIqSample{.i = i, .q = q});
206 }
207 return result;
208 }

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