FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
hdf5_handler.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2006-2008 Marc Brooker and Michael Inggs
4// Copyright (c) 2008-present FERS Contributors (see AUTHORS.md).
5//
6// See the GNU GPLv2 LICENSE file in the FERS project root for more information.
7
8/**
9 * @file hdf5_handler.cpp
10 * @brief Source file for HDF5 data export and import functions.
11 */
12
13#include "hdf5_handler.h"
14
15#include <algorithm>
16#include <complex>
17#include <filesystem>
18#include <format>
19#include <highfive/highfive.hpp>
20#include <stdexcept>
21#include <string>
22#include <vector>
23
24#include "core/logging.h"
25#include "core/parameters.h"
26
27using logging::Level;
28
29namespace serial
30{
32
33 namespace
34 {
35 template <typename T>
36 void createOptionalAttribute(HighFive::File& file, const std::string& name, const std::optional<T>& value)
37 {
38 if (value.has_value())
39 {
40 file.createAttribute(name, *value);
41 }
42 }
43
44 template <typename T>
45 void createOptionalUnsignedAttribute(HighFive::File& file, const std::string& name,
46 const std::optional<T>& value)
47 {
48 if (value.has_value())
49 {
50 file.createAttribute(name, static_cast<unsigned long long>(*value));
51 }
52 }
53
54 void writeBaseMetadataAttributes(HighFive::File& file, const core::OutputFileMetadata& metadata)
55 {
56 file.createAttribute("fers_metadata_schema_version", 1U);
57 file.createAttribute("fers_metadata_json", core::outputFileMetadataToJsonString(metadata));
58 file.createAttribute("receiver_id", static_cast<unsigned long long>(metadata.receiver_id));
59 file.createAttribute("receiver_name", metadata.receiver_name);
60 file.createAttribute("data_mode", metadata.mode);
61 if (metadata.sampling_rate > 0.0)
62 {
63 file.createAttribute("output_sampling_rate", metadata.sampling_rate);
64 }
65 file.createAttribute("total_samples", static_cast<unsigned long long>(metadata.total_samples));
66 file.createAttribute("sample_start", static_cast<unsigned long long>(metadata.sample_start));
67 file.createAttribute("sample_end_exclusive",
68 static_cast<unsigned long long>(metadata.sample_end_exclusive));
69 file.createAttribute("streaming_segment_count",
70 static_cast<unsigned long long>(metadata.streaming_segments.size()));
71 file.createAttribute("fmcw_source_count", static_cast<unsigned long long>(metadata.fmcw_sources.size()));
72 file.createAttribute("sfcw_source_count", static_cast<unsigned long long>(metadata.sfcw_sources.size()));
73 file.createAttribute("fmcw_dechirp_mode", metadata.fmcw_dechirp_mode);
74 file.createAttribute("fmcw_dechirp_reference_source", metadata.fmcw_dechirp_reference_source);
75 file.createAttribute("fmcw_if_decimation_enabled", metadata.fmcw_if_decimation_enabled);
76 file.createAttribute("fmcw_if_legacy_full_rate", metadata.fmcw_if_legacy_full_rate);
77 file.createAttribute("fmcw_if_group_delay_compensated", metadata.fmcw_if_group_delay_compensated);
78 }
79
80 void writeFmcwIfAttributes(HighFive::File& file, const core::OutputFileMetadata& metadata)
81 {
82 createOptionalAttribute(file, "fmcw_if_requested_sample_rate", metadata.fmcw_if_requested_sample_rate);
83 createOptionalAttribute(file, "fmcw_if_sample_rate", metadata.fmcw_if_sample_rate);
84 createOptionalAttribute(file, "fmcw_if_input_sample_rate", metadata.fmcw_if_input_sample_rate);
85 createOptionalUnsignedAttribute(file, "fmcw_if_resample_numerator", metadata.fmcw_if_resample_numerator);
86 createOptionalUnsignedAttribute(file, "fmcw_if_resample_denominator",
88 createOptionalAttribute(file, "fmcw_if_decimation_factor", metadata.fmcw_if_decimation_factor);
89 createOptionalAttribute(file, "fmcw_if_filter_bandwidth", metadata.fmcw_if_filter_bandwidth);
90 createOptionalAttribute(file, "fmcw_if_filter_transition_width", metadata.fmcw_if_filter_transition_width);
91 createOptionalAttribute(file, "fmcw_if_filter_stopband", metadata.fmcw_if_filter_stopband);
92 createOptionalAttribute(file, "fmcw_if_filter_group_delay_seconds",
94 createOptionalUnsignedAttribute(file, "fmcw_if_compensated_integer_delay_samples",
96 createOptionalAttribute(file, "fmcw_if_compensated_fractional_delay_samples",
98 createOptionalUnsignedAttribute(file, "fmcw_if_warmup_discard_samples",
100 createOptionalUnsignedAttribute(file, "fmcw_if_phase_refinement", metadata.fmcw_if_phase_refinement);
101 createOptionalAttribute(file, "fmcw_if_timing_error_seconds", metadata.fmcw_if_timing_error_seconds);
102 createOptionalAttribute(file, "fmcw_if_phase_error_radians", metadata.fmcw_if_phase_error_radians);
103 createOptionalAttribute(file, "fmcw_if_noise_variance", metadata.fmcw_if_noise_variance);
104 }
105
106 void writeFmcwWaveformAttributes(HighFive::File& file, const std::string& prefix,
107 const core::FmcwMetadata& waveform, const bool write_counts)
108 {
109 file.createAttribute(prefix + "waveform_shape", waveform.waveform_shape);
110 file.createAttribute(prefix + "chirp_bandwidth", waveform.chirp_bandwidth);
111 file.createAttribute(prefix + "chirp_duration", waveform.chirp_duration);
112 file.createAttribute(prefix + "chirp_rate", waveform.chirp_rate);
113 file.createAttribute(prefix + "start_frequency_offset", waveform.start_frequency_offset);
114 if (waveform.waveform_shape == "linear")
115 {
116 file.createAttribute(prefix + "chirp_period", waveform.chirp_period);
117 file.createAttribute(prefix + "chirp_direction", waveform.chirp_direction);
118 if (write_counts)
119 {
120 file.createAttribute(prefix + "chirp_rate_signed", waveform.chirp_rate_signed);
121 createOptionalUnsignedAttribute(file, prefix + "chirp_count", waveform.chirp_count);
122 }
123 }
124 else if (waveform.waveform_shape == "file")
125 {
126 createOptionalAttribute(file, prefix + "sampled_duration", waveform.sampled_duration);
127 createOptionalUnsignedAttribute(file, prefix + "sampled_count", waveform.sampled_count);
128 }
129 else if (waveform.waveform_shape == "triangle" && waveform.triangle_period.has_value())
130 {
131 file.createAttribute(prefix + "triangle_period", *waveform.triangle_period);
132 if (write_counts)
133 {
134 createOptionalUnsignedAttribute(file, prefix + "triangle_count", waveform.triangle_count);
135 }
136 }
137 }
138
139 void writeDechirpReferenceAttributes(HighFive::File& file, const core::OutputFileMetadata& metadata)
140 {
141 createOptionalUnsignedAttribute(file, "fmcw_dechirp_reference_transmitter_id",
143 createOptionalAttribute(file, "fmcw_dechirp_reference_transmitter_name",
145 createOptionalUnsignedAttribute(file, "fmcw_dechirp_reference_waveform_id",
147 createOptionalAttribute(file, "fmcw_dechirp_reference_waveform_name",
149 if (metadata.fmcw_dechirp_reference_waveform.has_value())
150 {
151 writeFmcwWaveformAttributes(file, "fmcw_dechirp_reference_", *metadata.fmcw_dechirp_reference_waveform,
152 false);
153 }
154 }
155
156 void writeStreamingSegmentFmcwAttributes(HighFive::File& file, const core::OutputFileMetadata& metadata)
157 {
158 std::vector<RealType> streaming_first_chirp_starts;
159 std::vector<unsigned long long> streaming_emitted_chirp_counts;
160 std::vector<RealType> streaming_first_triangle_starts;
161 std::vector<unsigned long long> streaming_emitted_triangle_counts;
162 for (const auto& segment : metadata.streaming_segments)
163 {
164 if (segment.first_chirp_start_time.has_value())
165 {
166 streaming_first_chirp_starts.push_back(*segment.first_chirp_start_time);
167 }
168 if (segment.emitted_chirp_count.has_value())
169 {
171 static_cast<unsigned long long>(*segment.emitted_chirp_count));
172 }
173 if (segment.first_triangle_start_time.has_value())
174 {
175 streaming_first_triangle_starts.push_back(*segment.first_triangle_start_time);
176 }
177 if (segment.emitted_triangle_count.has_value())
178 {
180 static_cast<unsigned long long>(*segment.emitted_triangle_count));
181 }
182 }
183 if (!streaming_first_chirp_starts.empty())
184 {
185 auto attr = file.createAttribute<RealType>("streaming_first_chirp_start_time",
186 HighFive::DataSpace::From(streaming_first_chirp_starts));
188 }
190 {
191 auto attr = file.createAttribute<unsigned long long>(
192 "streaming_emitted_chirp_count", HighFive::DataSpace::From(streaming_emitted_chirp_counts));
194 }
196 {
197 auto attr = file.createAttribute<RealType>("streaming_first_triangle_start_time",
198 HighFive::DataSpace::From(streaming_first_triangle_starts));
200 }
202 {
203 auto attr = file.createAttribute<unsigned long long>(
204 "streaming_emitted_triangle_count", HighFive::DataSpace::From(streaming_emitted_triangle_counts));
206 }
207 }
208
209 void writeFmcwAttributes(HighFive::File& file, const core::OutputFileMetadata& metadata)
210 {
211 if (!metadata.fmcw.has_value())
212 {
213 return;
214 }
215 writeFmcwWaveformAttributes(file, "fmcw_", *metadata.fmcw, true);
217 }
218
219 void writeSfcwAttributes(HighFive::File& file, const core::OutputFileMetadata& metadata)
220 {
221 if (!metadata.sfcw.has_value())
222 {
223 return;
224 }
225 const auto& sfcw = *metadata.sfcw;
226 file.createAttribute("sfcw_carrier_frequency", sfcw.carrier_frequency);
227 file.createAttribute("sfcw_start_frequency_offset", sfcw.start_frequency_offset);
228 file.createAttribute("sfcw_step_size", sfcw.step_size);
229 file.createAttribute("sfcw_step_count", static_cast<unsigned long long>(sfcw.step_count));
230 file.createAttribute("sfcw_dwell_time", sfcw.dwell_time);
231 file.createAttribute("sfcw_step_period", sfcw.step_period);
232 createOptionalUnsignedAttribute(file, "sfcw_sweep_count", sfcw.sweep_count);
233 file.createAttribute("sfcw_first_frequency", sfcw.first_frequency);
234 file.createAttribute("sfcw_last_frequency", sfcw.last_frequency);
235 file.createAttribute("sfcw_frequency_span", sfcw.frequency_span);
236 file.createAttribute("sfcw_effective_bandwidth", sfcw.effective_bandwidth);
237 file.createAttribute("sfcw_range_resolution", sfcw.range_resolution);
238 file.createAttribute("sfcw_unambiguous_range", sfcw.unambiguous_range);
239 file.createAttribute("sfcw_flat_attributes_are_summary", true);
240 }
241
242 void writeSfcwWaveformGroup(HighFive::Group& group, const core::SfcwMetadata& waveform)
243 {
244 group.createAttribute("carrier_frequency", waveform.carrier_frequency);
245 group.createAttribute("start_frequency_offset", waveform.start_frequency_offset);
246 group.createAttribute("step_size", waveform.step_size);
247 group.createAttribute("step_count", static_cast<unsigned long long>(waveform.step_count));
248 group.createAttribute("dwell_time", waveform.dwell_time);
249 group.createAttribute("step_period", waveform.step_period);
250 if (waveform.sweep_count.has_value())
251 {
252 group.createAttribute("sweep_count", static_cast<unsigned long long>(*waveform.sweep_count));
253 }
254 group.createAttribute("first_frequency", waveform.first_frequency);
255 group.createAttribute("last_frequency", waveform.last_frequency);
256 group.createAttribute("frequency_span", waveform.frequency_span);
257 group.createAttribute("effective_bandwidth", waveform.effective_bandwidth);
258 group.createAttribute("range_resolution", waveform.range_resolution);
259 group.createAttribute("unambiguous_range", waveform.unambiguous_range);
260 }
261
262 void writeNestedSfcwMetadata(HighFive::File& file, const core::OutputFileMetadata& metadata)
263 {
264 if (metadata.sfcw_sources.empty())
265 {
266 return;
267 }
268
269 auto metadata_group = file.createGroup("/metadata");
270 auto sfcw_group = metadata_group.createGroup("sfcw");
271 sfcw_group.createAttribute("schema_version", 1U);
272 sfcw_group.createAttribute("source_count", static_cast<unsigned long long>(metadata.sfcw_sources.size()));
273 auto sources_group = sfcw_group.createGroup("sources");
274 for (std::size_t source_index = 0; source_index < metadata.sfcw_sources.size(); ++source_index)
275 {
276 const auto& source = metadata.sfcw_sources[source_index];
277 auto source_group = sources_group.createGroup("source_" + std::to_string(source_index));
278 source_group.createAttribute("transmitter_id", static_cast<unsigned long long>(source.transmitter_id));
279 source_group.createAttribute("transmitter_name", source.transmitter_name);
280 source_group.createAttribute("waveform_id", static_cast<unsigned long long>(source.waveform_id));
281 source_group.createAttribute("waveform_name", source.waveform_name);
282
283 auto waveform_group = source_group.createGroup("waveform");
284 writeSfcwWaveformGroup(waveform_group, source.waveform);
285
286 auto segments_group = source_group.createGroup("segments");
287 segments_group.createAttribute("count", static_cast<unsigned long long>(source.segments.size()));
288 for (std::size_t segment_index = 0; segment_index < source.segments.size(); ++segment_index)
289 {
290 const auto& segment = source.segments[segment_index];
291 auto segment_group = segments_group.createGroup("segment_" + std::to_string(segment_index));
292 segment_group.createAttribute("start_time", segment.start_time);
293 segment_group.createAttribute("end_time", segment.end_time);
294 if (segment.first_step_start_time.has_value())
295 {
296 segment_group.createAttribute("first_step_start_time", *segment.first_step_start_time);
297 }
298 if (segment.emitted_step_count.has_value())
299 {
300 segment_group.createAttribute("emitted_step_count",
301 static_cast<unsigned long long>(*segment.emitted_step_count));
302 }
303 }
304 }
305 file.createAttribute("sfcw_metadata_path", std::string("/metadata/sfcw"));
306 }
307 }
308
309 void writeOutputFileMetadataAttributes(HighFive::File& file, const core::OutputFileMetadata& metadata)
310 {
311 writeBaseMetadataAttributes(file, metadata);
312 writeFmcwIfAttributes(file, metadata);
313 writeDechirpReferenceAttributes(file, metadata);
314 writeFmcwAttributes(file, metadata);
315 writeSfcwAttributes(file, metadata);
316 writeNestedSfcwMetadata(file, metadata);
317 }
318
319 void readPulseData(const std::string& name, std::vector<ComplexType>& data)
320 {
321 std::scoped_lock const lock(hdf5_global_mutex);
322
323 if (!std::filesystem::exists(name))
324 {
325 LOG(Level::FATAL, "File '{}' not found", name);
326 throw std::runtime_error("File " + name + " not found.");
327 }
328
329 LOG(Level::TRACE, "Opening file '{}'", name);
330 const HighFive::File file(name, HighFive::File::ReadOnly);
331
332 // Helper lambda to open group and read dataset
333 auto read_dataset = [&file](const std::string& groupName, std::vector<double>& buffer) -> size_t
334 {
335 const auto group = file.getGroup("/" + groupName);
336
337 const auto dataset = group.getDataSet("value");
338
339 const auto dimensions = dataset.getSpace().getDimensions();
340 const auto size = dimensions[0];
341
342 buffer.resize(size);
343 dataset.read(buffer);
344
345 return size;
346 };
347
348 LOG(Level::TRACE, "Reading dataset 'I' from file '{}'", name);
349 std::vector<double> buffer_i;
350 const auto size = read_dataset("I", buffer_i);
351
352 std::vector<double> buffer_q;
353 LOG(Level::TRACE, "Reading dataset 'Q' from file '{}'", name);
354 if (read_dataset("Q", buffer_q) != size)
355 {
356 LOG(Level::FATAL, "Dataset 'Q' is not the same size as dataset 'I' in file '{}'", name);
357 throw std::runtime_error(R"(Dataset "Q" is not the same size as dataset "I" in file )" + name);
358 }
359
360 data.resize(size);
361 for (size_t i = 0; i < size; ++i)
362 {
363 data[i] = ComplexType(buffer_i[i], buffer_q[i]);
364 }
365 LOG(Level::TRACE, "Read dataset successfully");
366 }
367
368 void addChunkToFile(HighFive::File& file, const std::vector<ComplexType>& data, const RealType time,
369 const RealType fullscale, const unsigned count, const core::PulseChunkMetadata* metadata)
370 {
371 std::scoped_lock const lock(hdf5_global_mutex);
372
373 const std::size_t size = data.size();
374
375 const std::string base_chunk_name = "chunk_" + std::format("{:06}", count);
376 const std::string i_chunk_name = base_chunk_name + "_I";
377 const std::string q_chunk_name = base_chunk_name + "_Q";
378
379 std::vector<RealType> i(size), q(size);
380 std::ranges::transform(data, i.begin(), [](const ComplexType& c) { return c.real(); });
381 std::ranges::transform(data, q.begin(), [](const ComplexType& c) { return c.imag(); });
382
383 auto write_chunk = [&](const std::string& chunkName, const std::vector<RealType>& chunkData)
384 {
385 try
386 {
387 HighFive::DataSet dataset =
388 file.createDataSet<RealType>(chunkName, HighFive::DataSpace::From(chunkData));
389 dataset.write(chunkData);
390 }
391 catch (const HighFive::Exception& err)
392 {
393 LOG(Level::FATAL, "Error while writing data to HDF5 file: {}", err.what());
394 throw std::runtime_error("Error while writing data to HDF5 file: " + chunkName + " - " + err.what());
395 }
396 };
397
398 auto set_chunk_attributes = [&](const std::string& chunkName)
399 {
400 try
401 {
402 HighFive::DataSet dataset = file.getDataSet(chunkName);
403 dataset.createAttribute("time", time);
404 dataset.createAttribute("rate", params::rate());
405 dataset.createAttribute("fullscale", fullscale);
406 if (metadata != nullptr)
407 {
408 dataset.createAttribute("chunk_index", metadata->chunk_index);
409 dataset.createAttribute("sample_count", static_cast<unsigned long long>(metadata->sample_count));
410 dataset.createAttribute("sample_start", static_cast<unsigned long long>(metadata->sample_start));
411 dataset.createAttribute("sample_end_exclusive",
412 static_cast<unsigned long long>(metadata->sample_end_exclusive));
413 }
414 }
415 catch (const HighFive::Exception& err)
416 {
417 LOG(Level::FATAL, "Error while setting attributes on chunk: {}", err.what());
418 throw std::runtime_error("Error while setting attributes on chunk: " + chunkName + " - " + err.what());
419 }
420 };
421
424
427 }
428
429 std::vector<std::vector<RealType>> readPattern(const std::string& name, const std::string& datasetName)
430 {
431 std::scoped_lock const lock(hdf5_global_mutex);
432 try
433 {
434 LOG(Level::TRACE, "Reading dataset '{}' from file '{}'", datasetName, name);
435 const HighFive::File file(name, HighFive::File::ReadOnly);
436
437 const auto dataset = file.getDataSet(datasetName);
438
439 const auto dataspace = dataset.getSpace();
440 const auto dims = dataspace.getDimensions();
441
442 if (dims.size() != 2)
443 {
444 LOG(Level::FATAL, "Invalid dataset dimensions for '{}' in file '{}'", datasetName, name);
445 throw std::runtime_error(
446 std::format(R"(Invalid dataset dimensions for "{}" in file "{}")", datasetName, name));
447 }
448
449 LOG(Level::TRACE, "Reading dataset with dimensions {}x{}", dims[0], dims[1]);
450
451 std::vector data(dims[0], std::vector<RealType>(dims[1]));
452 dataset.read(data);
453
454 LOG(Level::TRACE, "Read dataset successfully");
455
456 return data;
457 }
458 catch (const HighFive::Exception& err)
459 {
460 LOG(Level::FATAL, "Error handling HDF5 file: {}", err.what());
461 throw std::runtime_error("Error handling HDF5 file: " + std::string(err.what()));
462 }
463 }
464}
double RealType
Type for real numbers.
Definition config.h:27
std::complex< RealType > ComplexType
Type for complex numbers.
Definition config.h:35
Header file for HDF5 data export and import functions.
Header file for the logging system.
#define LOG(level,...)
Definition logging.h:19
std::string outputFileMetadataToJsonString(const OutputFileMetadata &metadata)
Serializes one output-file metadata entry to JSON.
RealType rate() noexcept
Get the rendering sample rate.
Definition parameters.h:121
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.
void readPulseData(const std::string &name, std::vector< ComplexType > &data)
Reads pulse data from an HDF5 file.
std::vector< std::vector< RealType > > readPattern(const std::string &name, const std::string &datasetName)
Reads a 2D antenna gain pattern from the named dataset.
Defines the Parameters struct and provides methods for managing simulation parameters.
math::Vec3 max
RealType c
FMCW waveform metadata captured for a streaming output file.
RealType chirp_period
Chirp repetition period in seconds.
std::optional< std::uint64_t > chirp_count
Optional finite chirp count.
RealType chirp_rate
Frequency sweep rate in hertz per second.
RealType chirp_duration
Active chirp duration in seconds.
RealType chirp_bandwidth
Chirp bandwidth in hertz.
std::optional< RealType > sampled_duration
Finite file-backed waveform duration in seconds.
std::string chirp_direction
Frequency sweep direction token.
std::string waveform_shape
FMCW waveform shape token: linear, triangle, or file.
std::optional< std::uint64_t > triangle_count
Optional finite triangle count.
RealType start_frequency_offset
Start frequency offset relative to carrier in hertz.
std::optional< RealType > triangle_period
Full triangle period in seconds.
std::optional< std::uint64_t > sampled_count
Native samples in a file-backed waveform.
RealType chirp_rate_signed
Signed frequency sweep rate in hertz per second.
Metadata for one receiver output file.
std::vector< FmcwSourceMetadata > fmcw_sources
FMCW illuminators represented in the output.
std::optional< RealType > fmcw_if_noise_variance
Post-resampling complex noise variance.
std::optional< RealType > fmcw_if_requested_sample_rate
Requested IF ADC rate in hertz.
std::uint64_t total_samples
Total sample count written to the file.
RealType sampling_rate
Sample rate for this output file in hertz.
std::optional< RealType > fmcw_if_timing_error_seconds
Estimated timing error.
std::optional< std::string > fmcw_dechirp_reference_transmitter_name
LO transmitter name.
SimId receiver_id
Receiver SimId that owns the output file.
std::uint64_t sample_start
Inclusive global sample index for the file start.
std::string fmcw_dechirp_mode
Receiver dechirp mode for FMCW streaming outputs.
std::vector< SfcwSourceMetadata > sfcw_sources
SFCW illuminators represented in the output.
std::optional< FmcwMetadata > fmcw
Optional FMCW metadata for streaming outputs.
std::uint64_t sample_end_exclusive
Exclusive global sample index for the file end.
bool fmcw_if_legacy_full_rate
True for legacy full-rate dechirped IF output.
std::optional< std::uint64_t > fmcw_if_compensated_integer_delay_samples
Integer output-delay compensation.
std::optional< RealType > fmcw_if_decimation_factor
Input/output sample-rate ratio.
std::optional< RealType > fmcw_if_sample_rate
Realized IF output sample rate in hertz.
std::optional< RealType > fmcw_if_compensated_fractional_delay_samples
Fractional output-delay compensation.
std::optional< FmcwMetadata > fmcw_dechirp_reference_waveform
Custom LO waveform parameters.
std::optional< RealType > fmcw_if_input_sample_rate
Input simulation sample rate in hertz.
std::string receiver_name
Receiver display name.
std::optional< SimId > fmcw_dechirp_reference_transmitter_id
Referenced LO transmitter ID.
std::optional< RealType > fmcw_if_filter_bandwidth
One-sided IF passband in hertz.
std::optional< SfcwMetadata > sfcw
Optional SFCW metadata for streaming outputs.
std::string mode
Output mode label, such as pulsed or streaming.
std::optional< RealType > fmcw_if_filter_stopband
IF stopband attenuation in dB.
std::optional< RealType > fmcw_if_phase_error_radians
Estimated IF edge phase error.
std::optional< unsigned > fmcw_if_resample_numerator
Reduced rational P.
std::optional< std::uint64_t > fmcw_if_warmup_discard_samples
Startup outputs discarded by the sink.
bool fmcw_if_decimation_enabled
True when IF-rate resampling is used.
bool fmcw_if_group_delay_compensated
True when IF output timestamps are aligned to t_start.
std::vector< StreamingSegmentMetadata > streaming_segments
Streaming segments written to the file.
std::optional< RealType > fmcw_if_filter_transition_width
IF transition width in hertz.
std::optional< RealType > fmcw_if_filter_group_delay_seconds
Total filter delay.
std::optional< std::string > fmcw_dechirp_reference_waveform_name
Custom LO waveform name.
std::string fmcw_dechirp_reference_source
Receiver dechirp reference source.
std::optional< unsigned > fmcw_if_phase_refinement
Polyphase refinement factor.
std::optional< unsigned > fmcw_if_resample_denominator
Reduced rational Q.
std::optional< SimId > fmcw_dechirp_reference_waveform_id
Custom LO waveform ID.
Metadata for one pulsed output chunk written to HDF5.
std::uint64_t sample_end_exclusive
Exclusive global sample index for the chunk end.
std::uint64_t sample_count
Number of samples in the chunk.
std::uint64_t sample_start
Inclusive global sample index for the chunk start.
unsigned chunk_index
Zero-based chunk index in the receiver output sequence.
SFCW waveform metadata captured for a streaming output file.
RealType unambiguous_range
Uniform-step unambiguous range in meters.
RealType effective_bandwidth
DFT-convention effective bandwidth in hertz.
RealType start_frequency_offset
First-step offset relative to carrier in hertz.
RealType carrier_frequency
Waveform carrier frequency in hertz.
RealType frequency_span
First-to-last absolute span in hertz.
std::uint64_t step_count
Steps per sweep.
RealType dwell_time
Active dwell time per step in seconds.
RealType last_frequency
Last RF step frequency in hertz.
RealType step_size
Uniform frequency step in hertz.
RealType range_resolution
Approximate range resolution in meters.
RealType first_frequency
First RF step frequency in hertz.
RealType step_period
Step period in seconds.
std::optional< std::uint64_t > sweep_count
Optional finite sweep count.