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

Namespaces

namespace  pipeline
 

Functions

void runPulsedFinalizer (radar::Receiver *receiver, const std::vector< std::unique_ptr< radar::Target > > *targets, std::shared_ptr< core::ProgressReporter > reporter, const std::string &output_dir, std::shared_ptr< core::OutputMetadataCollector > metadata_collector=nullptr)
 The main function for a dedicated pulsed-mode receiver finalizer thread.
 
void finalizeCwReceiver (radar::Receiver *receiver, pool::ThreadPool *pool, std::shared_ptr< core::ProgressReporter > reporter, const std::string &output_dir, std::shared_ptr< core::OutputMetadataCollector > metadata_collector=nullptr)
 The finalization task for a continuous-wave (CW) mode receiver.
 
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.
 
RealType quantizeAndScaleWindow (std::span< ComplexType > window)
 Simulates ADC quantization and scales a window of complex I/Q samples.
 

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

111 {
112 if (noiseTemperature == 0)
113 {
114 return;
115 }
116
117 const RealType b = params::rate() / (2.0 * params::oversampleRatio());
118 const RealType total_power = params::boltzmannK() * noiseTemperature * b;
119
120 // Split total power equally between the I and Q channels
121 const RealType per_channel_power = total_power / 2.0;
122 const RealType stddev = std::sqrt(per_channel_power);
123
124 noise::WgnGenerator generator(rngEngine, stddev);
125 for (auto& sample : window)
126 {
127 sample += ComplexType(generator.getSample(), generator.getSample());
128 }
129 }
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

References params::boltzmannK(), noise::WgnGenerator::getSample(), params::oversampleRatio(), and params::rate().

Referenced by finalizeCwReceiver(), and runPulsedFinalizer().

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

◆ finalizeCwReceiver()

void processing::finalizeCwReceiver ( radar::Receiver receiver,
pool::ThreadPool pool,
std::shared_ptr< core::ProgressReporter reporter,
const std::string &  output_dir,
std::shared_ptr< core::OutputMetadataCollector metadata_collector = nullptr 
)

The finalization task for a continuous-wave (CW) mode receiver.

This function is submitted to the main thread pool when a CW receiver finishes its operation. It processes the entire collected I/Q buffer, applies interference and noise, and writes the final data to a file.

Parameters
receiverA pointer to the CW-mode receiver to finalize.
poolA pointer to the main thread pool for parallelizing sub-tasks.
reporterShared pointer to the progress reporter for status updates.
output_dirOutput directory for the simulation files.

Definition at line 239 of file finalizer.cpp.

242 {
243 LOG(logging::Level::INFO, "Finalization task started for CW receiver '{}'.", receiver->getName());
244 if (reporter)
245 {
246 reporter->report(std::format("Finalizing CW Receiver {}", receiver->getName()), 0, 100);
247 }
248
249 auto& iq_buffer = receiver->getMutableCwData();
250 if (iq_buffer.empty())
251 {
252 LOG(logging::Level::INFO, "No CW data to finalize for receiver '{}'.", receiver->getName());
253 return;
254 }
255
256 if (reporter)
257 {
258 reporter->report(std::format("Rendering Interference for {}", receiver->getName()), 25, 100);
259 }
260 pipeline::applyPulsedInterference(iq_buffer, receiver->getPulsedInterferenceLog());
261
262 const auto timing_model = receiver->getTiming()->clone();
263 if (!timing_model)
264 {
265 LOG(logging::Level::FATAL, "Failed to clone timing model for CW receiver '{}'", receiver->getName());
266 return;
267 }
268
269 if (reporter)
270 {
271 reporter->report(std::format("Applying Noise for {}", receiver->getName()), 50, 100);
272 }
273 applyThermalNoise(iq_buffer, receiver->getNoiseTemperature(), receiver->getRngEngine());
274
275 if (timing_model->isEnabled())
276 {
277 std::vector pnoise(iq_buffer.size(), 0.0);
278 std::ranges::generate(pnoise, [&] { return timing_model->getNextSample(); });
279 pipeline::addPhaseNoiseToWindow(pnoise, iq_buffer);
280 }
281
282 const RealType fullscale = pipeline::applyDownsamplingAndQuantization(iq_buffer);
283
284 if (reporter)
285 {
286 reporter->report(std::format("Writing HDF5 for {}", receiver->getName()), 75, 100);
287 }
288
289 std::filesystem::path out_path(output_dir);
290 if (!std::filesystem::exists(out_path))
291 {
292 std::filesystem::create_directories(out_path);
293 }
294 const auto hdf5_filename = (out_path / std::format("{}_results.h5", receiver->getName())).string();
295 auto file_metadata = buildCwMetadata(receiver, hdf5_filename, iq_buffer.size());
296 pipeline::exportCwToHdf5(hdf5_filename, iq_buffer, fullscale, timing_model->getFrequency(), &file_metadata);
297 if (metadata_collector)
298 {
299 metadata_collector->addFile(std::move(file_metadata));
300 }
301
302 if (reporter)
303 {
304 reporter->report(std::format("Finalized {}", receiver->getName()), 100, 100);
305 }
306 }
const std::string & getName() const noexcept
Retrieves the name of the object.
Definition object.h:79
std::shared_ptr< timing::Timing > getTiming() const
Retrieves the timing source for the radar.
Definition radar_obj.cpp:66
std::mt19937 & getRngEngine() noexcept
Gets the receiver's internal random number generator engine.
Definition receiver.h:153
std::vector< ComplexType > & getMutableCwData()
Retrieves the collected CW IQ data for modification.
Definition receiver.h:256
const std::vector< std::unique_ptr< serial::Response > > & getPulsedInterferenceLog() const
Retrieves the log of pulsed interferences for CW mode.
Definition receiver.h:262
RealType getNoiseTemperature() const noexcept
Retrieves the noise temperature of the receiver.
Definition receiver.h:102
#define LOG(level,...)
Definition logging.h:19
@ FATAL
Fatal level for severe error events.
@ INFO
Info level for informational messages.
void applyThermalNoise(std::span< ComplexType > window, const RealType noiseTemperature, std::mt19937 &rngEngine)
Applies thermal (Johnson-Nyquist) noise to a window of I/Q samples.

References processing::pipeline::addPhaseNoiseToWindow(), processing::pipeline::applyDownsamplingAndQuantization(), processing::pipeline::applyPulsedInterference(), applyThermalNoise(), processing::pipeline::exportCwToHdf5(), logging::FATAL, radar::Receiver::getMutableCwData(), radar::Object::getName(), radar::Receiver::getNoiseTemperature(), radar::Receiver::getPulsedInterferenceLog(), radar::Receiver::getRngEngine(), radar::Radar::getTiming(), logging::INFO, and LOG.

+ Here is the call graph for this function:

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

132 {
133 RealType max_value = 0;
134 for (const auto& sample : window)
135 {
136 const RealType real_abs = std::fabs(sample.real());
137 const RealType imag_abs = std::fabs(sample.imag());
138
139 max_value = std::max({max_value, real_abs, imag_abs});
140 }
141
142 if (const auto adc_bits = params::adcBits(); adc_bits > 0)
143 {
144 adcSimulate(window, adc_bits, max_value);
145 }
146 else if (max_value != 0)
147 {
148 for (auto& sample : window)
149 {
150 sample /= max_value;
151 }
152 }
153 return max_value;
154 }
unsigned adcBits() noexcept
Get the ADC quantization bits.
Definition parameters.h:133

References params::adcBits().

Referenced by processing::pipeline::applyDownsamplingAndQuantization().

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

80 {
81 const RealType end = start + length;
82 std::queue<serial::Response*> work_list;
83
84 for (const auto& response : responses)
85 {
86 if (response->startTime() <= end && response->endTime() >= start)
87 {
88 work_list.push(response.get());
89 }
90 }
91
93 const auto local_window_size = static_cast<std::size_t>(std::ceil(length * rate));
94
95 std::vector local_window(local_window_size, ComplexType{});
96
97 while (!work_list.empty())
98 {
99 const auto* resp = work_list.front();
100 work_list.pop();
101 processResponse(resp, local_window, rate, start, fracDelay, local_window_size);
102 }
103
104 for (std::size_t i = 0; i < local_window_size; ++i)
105 {
106 window[i] += local_window[i];
107 }
108 }

References 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,
std::shared_ptr< core::ProgressReporter reporter,
const std::string &  output_dir,
std::shared_ptr< core::OutputMetadataCollector metadata_collector = 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 111 of file finalizer.cpp.

114 {
115 const auto timing_model = receiver->getTiming()->clone();
116 if (!timing_model)
117 {
118 LOG(logging::Level::FATAL, "Failed to clone timing model for receiver '{}'", receiver->getName());
119 return;
120 }
121
122 std::filesystem::path out_path(output_dir);
123 if (!std::filesystem::exists(out_path))
124 {
125 std::filesystem::create_directories(out_path);
126 }
127 const auto hdf5_filename = (out_path / std::format("{}_results.h5", receiver->getName())).string();
128 core::OutputFileMetadata file_metadata{.receiver_id = receiver->getId(),
129 .receiver_name = receiver->getName(),
130 .mode = "pulsed",
131 .path = hdf5_filename};
132
133 std::unique_ptr<HighFive::File> h5_file;
134 {
135 std::scoped_lock lock(serial::hdf5_global_mutex);
136 h5_file = std::make_unique<HighFive::File>(hdf5_filename, HighFive::File::Truncate);
137 }
138
139 unsigned chunk_index = 0;
140
141 LOG(logging::Level::INFO, "Finalizer thread started for receiver '{}'. Outputting to '{}'.",
142 receiver->getName(), hdf5_filename);
143
144 auto last_report_time = std::chrono::steady_clock::now();
145 const auto report_interval = std::chrono::milliseconds(100);
147 const RealType dt = 1.0 / rate;
148
149 while (true)
150 {
152 if (!receiver->waitAndDequeueFinalizerJob(job))
153 {
154 break; // Shutdown signal received
155 }
156
157 const auto window_samples = static_cast<unsigned>(std::ceil(job.duration * rate));
158 std::vector pnoise(window_samples, 0.0);
159
160 RealType actual_start = job.ideal_start_time;
161 RealType frac_delay = 0.0;
162
163 if (timing_model->isEnabled())
164 {
165 pipeline::advanceTimingModel(timing_model.get(), receiver, rate);
166 std::ranges::generate(pnoise, [&] { return timing_model->getNextSample(); });
167 std::tie(actual_start, frac_delay) = pipeline::calculateJitteredStart(
168 job.ideal_start_time, pnoise[0], timing_model->getFrequency(), rate);
169 }
170
171 std::vector<ComplexType> window_buffer(window_samples);
172
173 applyThermalNoise(window_buffer, receiver->getNoiseTemperature(receiver->getRotation(actual_start)),
174 receiver->getRngEngine());
175
176 pipeline::applyCwInterference(window_buffer, actual_start, dt, receiver, job.active_cw_sources, targets);
177
178 renderWindow(window_buffer, job.duration, actual_start, frac_delay, job.responses);
179
180 if (timing_model->isEnabled())
181 {
182 pipeline::addPhaseNoiseToWindow(pnoise, window_buffer);
183 }
184
185 const RealType fullscale = pipeline::applyDownsamplingAndQuantization(window_buffer);
186
187 const auto current_chunk_index = chunk_index++;
188 const auto sample_start = file_metadata.total_samples;
189 core::PulseChunkMetadata chunk_metadata{.chunk_index = current_chunk_index,
190 .i_dataset = std::format("chunk_{:06}_I", current_chunk_index),
191 .q_dataset = std::format("chunk_{:06}_Q", current_chunk_index),
192 .start_time = actual_start,
193 .sample_count = static_cast<std::uint64_t>(window_buffer.size()),
194 .sample_start = sample_start,
195 .sample_end_exclusive = sample_start +
196 static_cast<std::uint64_t>(window_buffer.size())};
197
198 serial::addChunkToFile(*h5_file, window_buffer, actual_start, fullscale, current_chunk_index,
199 &chunk_metadata);
200 file_metadata.chunks.push_back(std::move(chunk_metadata));
201 file_metadata.total_samples = file_metadata.chunks.back().sample_end_exclusive;
202
203 if (reporter)
204 {
205 const auto now = std::chrono::steady_clock::now();
206 if ((now - last_report_time) >= report_interval)
207 {
208 reporter->report(std::format("Exporting {}: Chunk {}", receiver->getName(), chunk_index),
209 static_cast<int>(chunk_index), 0);
210 last_report_time = now;
211 }
212 }
213 }
214
215 finalizePulsedMetadata(file_metadata);
216 {
217 std::scoped_lock lock(serial::hdf5_global_mutex);
218 serial::writeOutputFileMetadataAttributes(*h5_file, file_metadata);
219 }
220
221 {
222 // Safe destruction of the HDF5 object inside a lock
223 std::scoped_lock lock(serial::hdf5_global_mutex);
224 h5_file.reset();
225 }
226
227 if (metadata_collector)
228 {
229 metadata_collector->addFile(std::move(file_metadata));
230 }
231
232 if (reporter)
233 {
234 reporter->report(std::format("Finished Exporting {}", receiver->getName()), 100, 100);
235 }
236 LOG(logging::Level::INFO, "Finalizer thread for receiver '{}' finished.", receiver->getName());
237 }
math::SVec3 getRotation(const RealType time) const
Retrieves the rotation of the object.
Definition object.h:58
SimId getId() const noexcept
Retrieves the unique ID of the receiver.
Definition receiver.h:95
bool waitAndDequeueFinalizerJob(core::RenderingJob &job)
Waits for and dequeues a RenderingJob from the finalizer queue.
Definition receiver.cpp:59
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.
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.
void addChunkToFile(HighFive::File &file, const std::vector< ComplexType > &data, const RealType time, const RealType fullscale, const unsigned count, const core::PulseChunkMetadata *metadata)
Adds a chunk of data to an HDF5 file.
A data packet containing all information needed to process one receive window.
RealType duration
The duration of the receive window in seconds.
std::vector< std::unique_ptr< serial::Response > > responses
A list of all Response objects that overlap with this window.
RealType ideal_start_time
The ideal, jitter-free start time of the receive window.
std::vector< radar::Transmitter * > active_cw_sources
A list of all CW transmitters that were active during this window.

References core::RenderingJob::active_cw_sources, serial::addChunkToFile(), processing::pipeline::addPhaseNoiseToWindow(), processing::pipeline::advanceTimingModel(), processing::pipeline::applyCwInterference(), processing::pipeline::applyDownsamplingAndQuantization(), applyThermalNoise(), processing::pipeline::calculateJitteredStart(), core::PulseChunkMetadata::chunk_index, core::RenderingJob::duration, logging::FATAL, radar::Receiver::getId(), radar::Object::getName(), radar::Receiver::getNoiseTemperature(), radar::Receiver::getRngEngine(), radar::Object::getRotation(), radar::Radar::getTiming(), serial::hdf5_global_mutex, core::RenderingJob::ideal_start_time, logging::INFO, LOG, params::oversampleRatio(), params::rate(), core::OutputFileMetadata::receiver_id, renderWindow(), core::RenderingJob::responses, radar::Receiver::waitAndDequeueFinalizerJob(), and serial::writeOutputFileMetadataAttributes().

+ Here is the call graph for this function: