FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
waveform_factory.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 waveform_factory.cpp
10 * @brief Implementation for loading waveform data into RadarSignal objects.
11 */
12
13#include "waveform_factory.h"
14
15#include <cmath>
16#include <complex>
17#include <cstddef>
18#include <filesystem>
19#include <fstream>
20#include <limits>
21#include <span>
22#include <stdexcept>
23#include <string_view>
24#include <utility>
25#include <vector>
26
27#include "core/config.h"
28#include "core/parameters.h"
29#include "core/sim_id.h"
30#include "hdf5_handler.h"
31#include "signal/radar_signal.h"
32
36
37namespace
38{
39 /// Converts a sample count to unsigned after validating the supported range.
40 [[nodiscard]] unsigned checked_sample_count(const std::size_t sample_count, const std::string_view source)
41 {
42 if (sample_count == 0)
43 {
44 throw std::runtime_error(std::format("Waveform '{}' contains no samples.", source));
45 }
46 if (sample_count > static_cast<std::size_t>(std::numeric_limits<unsigned>::max()))
47 {
48 throw std::runtime_error(std::format("Waveform '{}' has too many samples to load into Signal.", source));
49 }
50 return static_cast<unsigned>(sample_count);
51 }
52
53 /// Parses and validates a CSV waveform sample count header.
54 [[nodiscard]] unsigned parse_csv_sample_count(const RealType sample_count, const std::filesystem::path& filepath)
55 {
56 if (!std::isfinite(sample_count) || sample_count < 0.0 || std::trunc(sample_count) != sample_count)
57 {
58 throw std::runtime_error("Waveform file '" + filepath.string() + "' has an invalid sample count header.");
59 }
60
61 if (sample_count > static_cast<RealType>(std::numeric_limits<unsigned>::max()))
62 {
63 throw std::runtime_error("Waveform file '" + filepath.string() +
64 "' declares more samples than Signal can represent.");
65 }
66
67 return static_cast<unsigned>(sample_count);
68 }
69
70 /**
71 * @brief Loads a radar waveform from an HDF5 file and returns a RadarSignal object.
72 *
73 * @param name The name of the radar signal.
74 * @param filepath The path to the HDF5 file containing the waveform data.
75 * @param power The power of the radar signal in the waveform.
76 * @param carrierFreq The carrier frequency of the radar signal.
77 * @return A unique pointer to a RadarSignal object loaded with the waveform data.
78 * @throws std::runtime_error If the file cannot be opened or the file format is unrecognized.
79 */
80 std::unique_ptr<RadarSignal> loadWaveformFromHdf5File(const std::string& name,
81 const std::filesystem::path& filepath, const RealType power,
82 const RealType carrierFreq, const SimId id,
83 const FileWaveformKind kind)
84 {
85 std::vector<ComplexType> data;
87 const unsigned sample_count = checked_sample_count(data.size(), filepath.string());
88
89 auto signal = std::make_unique<FileSignal>(kind);
90 signal->load(data, sample_count, params::rate());
91 return std::make_unique<RadarSignal>(
92 name, power, carrierFreq, static_cast<RealType>(sample_count) / params::rate(), std::move(signal), id);
93 }
94
95 /**
96 * @brief Loads a radar waveform from a CSV file and returns a RadarSignal object.
97 *
98 * @param name The name of the radar signal.
99 * @param filepath The path to the CSV file containing the waveform data.
100 * @param power The power of the radar signal in the waveform.
101 * @param carrierFreq The carrier frequency of the radar signal.
102 * @return A unique pointer to a RadarSignal object loaded with the waveform data.
103 * @throws std::runtime_error If the file cannot be opened or the file format is unrecognized.
104 */
105 std::unique_ptr<RadarSignal> loadWaveformFromCsvFile(const std::string& name, const std::filesystem::path& filepath,
106 const RealType power, const RealType carrierFreq,
107 const SimId id, const FileWaveformKind kind)
108 {
109 std::ifstream ifile(filepath);
110 if (!ifile)
111 {
112 LOG(logging::Level::FATAL, "Could not open file '{}' to read waveform", filepath.string());
113 throw std::runtime_error("Could not open file '" + filepath.string() + "' to read waveform");
114 }
115
116 RealType rlength = 0.0;
117 RealType rate = 0.0;
118 if (!(ifile >> rlength >> rate))
119 {
120 LOG(logging::Level::FATAL, "Could not read waveform header from file '{}'", filepath.string());
121 throw std::runtime_error("Could not read waveform header from file '" + filepath.string() + "'");
122 }
123 if (!std::isfinite(rate) || rate <= 0.0)
124 {
125 LOG(logging::Level::FATAL, "Waveform file '{}' has invalid sample rate {}", filepath.string(), rate);
126 throw std::runtime_error("Waveform file '" + filepath.string() + "' has an invalid sample rate");
127 }
128
129 const unsigned length = parse_csv_sample_count(rlength, filepath);
130 if (length == 0)
131 {
132 throw std::runtime_error("Waveform file '" + filepath.string() + "' contains no samples.");
133 }
134 std::vector<ComplexType> data(length);
135
136 // Read the file data
137 for (std::size_t done = 0; done < length && ifile >> data[done]; ++done)
138 {
139 }
140
141 if (ifile.fail() || data.size() != length)
142 {
143 LOG(logging::Level::FATAL, "Could not read full waveform from file '{}'", filepath.string());
144 throw std::runtime_error("Could not read full waveform from file '" + filepath.string() + "'");
145 }
146
147 auto signal = std::make_unique<FileSignal>(kind);
148 signal->load(data, length, rate);
149 return std::make_unique<RadarSignal>(name, power, carrierFreq, rlength / rate, std::move(signal), id);
150 }
151
152 /**
153 * @brief Checks if a filename has a specific extension.
154 *
155 * @param filename The filename to check.
156 * @param ext The extension to check for.
157 * @return True if the filename has the specified extension, false otherwise.
158 */
159 constexpr bool hasExtension(const std::string_view filename, const std::string_view ext) noexcept
160 {
161 return filename.ends_with(ext);
162 }
163}
164
165namespace serial
166{
167 std::unique_ptr<RadarSignal> loadWaveformFromFile(const std::string& name, const std::string& filename,
168 const RealType power, const RealType carrierFreq, const SimId id,
169 const FileWaveformKind kind)
170 {
171 const std::filesystem::path filepath = filename;
172 const auto extension = filepath.extension().string();
173 if (kind != FileWaveformKind::Pulsed && !hasExtension(extension, ".h5"))
174 {
175 throw std::runtime_error("File-backed CW and FMCW waveforms require the pulsed-compatible HDF5 (.h5) "
176 "format: " +
177 filename);
178 }
179
180 if (hasExtension(extension, ".csv"))
181 {
182 auto wave = loadWaveformFromCsvFile(name, filepath, power, carrierFreq, id, kind);
183 wave->setFilename(filename);
184 return wave;
185 }
186 if (hasExtension(extension, ".h5"))
187 {
188 auto wave = loadWaveformFromHdf5File(name, filepath, power, carrierFreq, id, kind);
189 wave->setFilename(filename);
190 return wave;
191 }
192
193 LOG(logging::Level::FATAL, "Unrecognized file extension '{}' for file: '{}'", extension, filename);
194 throw std::runtime_error("Unrecognized file extension '" + extension + "' for file: " + filename);
195 }
196}
File-backed sampled waveform with explicit radar-mode identity.
Class representing a radar signal with associated properties.
Global configuration file for the project.
double RealType
Type for real numbers.
Definition config.h:27
Header file for HDF5 data export and import functions.
#define LOG(level,...)
Definition logging.h:19
FileWaveformKind
Simulation mode assigned to samples loaded from a waveform file.
@ FATAL
Fatal level for severe error events.
RealType rate() noexcept
Get the rendering sample rate.
Definition parameters.h:121
std::unique_ptr< RadarSignal > loadWaveformFromFile(const std::string &name, const std::string &filename, const RealType power, const RealType carrierFreq, const SimId id, const FileWaveformKind kind)
void readPulseData(const std::string &name, std::vector< ComplexType > &data)
Reads pulse data from an HDF5 file.
Defines the Parameters struct and provides methods for managing simulation parameters.
Classes for handling radar waveforms and signals.
uint64_t SimId
64-bit Unique Simulation ID.
Definition sim_id.h:18
math::Vec3 max
Interface for loading waveform data into RadarSignal objects.