FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
finalizer.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2025-present FERS Contributors (see AUTHORS.md).
4//
5// See the GNU GPLv2 LICENSE file in the FERS project root for more information.
6
7#include "finalizer.h"
8
9#include <algorithm>
10#include <chrono>
11#include <cmath>
12#include <cstdint>
13#include <format>
14#include <optional>
15#include <span>
16#include <stdexcept>
17#include <utility>
18#include <vector>
19
20#include "core/logging.h"
22#include "core/parameters.h"
23#include "core/rendering_job.h"
24#include "core/sim_threading.h"
27#include "radar/receiver.h"
28#include "radar/transmitter.h"
29#include "signal/radar_signal.h"
30#include "timing/timing.h"
31
32namespace processing
33{
34 namespace
35 {
36 /// Converts a cached streaming source to reusable FMCW waveform metadata.
38 {
40 {
41 return core::FmcwMetadata{.waveform_shape = "file",
42 .sampled_duration = source.file_duration,
43 .sampled_count = source.file != nullptr
44 ? std::optional<std::uint64_t>(static_cast<std::uint64_t>(
45 std::llround(source.file_duration * params::rate())))
46 : std::nullopt};
47 }
49 {
50 return core::FmcwMetadata{
51 .waveform_shape = "triangle",
52 .chirp_bandwidth = source.triangle != nullptr ? source.triangle->getChirpBandwidth() : 0.0,
53 .chirp_duration = source.chirp_duration,
54 .chirp_rate = source.chirp_rate,
55 .start_frequency_offset = source.start_freq_off,
56 .triangle_period = source.triangle_period,
57 .triangle_count = source.triangle_count.has_value()
58 ? std::optional<std::uint64_t>(static_cast<std::uint64_t>(*source.triangle_count))
59 : std::nullopt};
60 }
61
62 return core::FmcwMetadata{
63 .waveform_shape = "linear",
64 .chirp_bandwidth = source.fmcw != nullptr ? source.fmcw->getChirpBandwidth() : 0.0,
65 .chirp_duration = source.chirp_duration,
66 .chirp_period = source.chirp_period,
67 .chirp_rate = source.chirp_rate,
68 .chirp_rate_signed = source.signed_chirp_rate,
69 .chirp_direction = source.fmcw != nullptr
71 : std::string("up"),
72 .start_frequency_offset = source.start_freq_off,
73 .chirp_count = source.chirp_count.has_value()
74 ? std::optional<std::uint64_t>(static_cast<std::uint64_t>(*source.chirp_count))
75 : std::nullopt};
76 }
77
79 {
80 if (source.sfcw == nullptr)
81 {
82 return {};
83 }
84 const RealType effective_bandwidth = source.sfcw->effectiveBandwidth();
85 const RealType step_size = std::abs(source.sfcw->getStepSize());
86 return core::SfcwMetadata{
88 .start_frequency_offset = source.sfcw->getStartFrequencyOffset(),
89 .step_size = source.sfcw->getStepSize(),
90 .step_count = static_cast<std::uint64_t>(source.sfcw->getStepCount()),
91 .dwell_time = source.sfcw->getDwellTime(),
92 .step_period = source.sfcw->getStepPeriod(),
93 .sweep_count = source.sfcw->getSweepCount().has_value()
94 ? std::optional<std::uint64_t>(static_cast<std::uint64_t>(*source.sfcw->getSweepCount()))
95 : std::nullopt,
96 .first_frequency = source.sfcw->firstFrequency(source.carrier_freq),
97 .last_frequency = source.sfcw->lastFrequency(source.carrier_freq),
98 .frequency_span = source.sfcw->frequencySpan(),
99 .effective_bandwidth = effective_bandwidth,
100 .range_resolution = effective_bandwidth > 0.0 ? params::c() / (2.0 * effective_bandwidth) : 0.0,
101 .unambiguous_range = step_size > 0.0 ? params::c() / (2.0 * step_size) : 0.0};
102 }
103
104 /// Builds one FMCW source schedule segment from an active source cache.
106 {
107 const RealType active_start = std::max(params::startTime(), source.segment_start);
108 const RealType active_end = std::min(params::endTime(), source.segment_end);
109 core::FmcwSourceSegmentMetadata segment{.start_time = source.segment_start, .end_time = source.segment_end};
111 {
112 return segment;
113 }
115 {
116 segment.first_triangle_start_time = core::firstFmcwTriangleStart(source, active_start, active_end);
117 segment.emitted_triangle_count = core::countFmcwTriangleStarts(source, active_start, active_end);
118 }
119 else
120 {
121 segment.first_chirp_start_time = core::firstFmcwChirpStart(source, active_start, active_end);
122 segment.emitted_chirp_count = core::countFmcwChirpStarts(source, active_start, active_end);
123 }
124 return segment;
125 }
126
127 /// Finds the first source metadata entry for a transmitter/waveform pair.
128 std::vector<core::FmcwSourceMetadata>::iterator findFmcwSource(std::vector<core::FmcwSourceMetadata>& sources,
129 const SimId transmitter_id,
130 const SimId waveform_id)
131 {
132 return std::ranges::find_if(
133 sources, [&](const core::FmcwSourceMetadata& source)
134 { return source.transmitter_id == transmitter_id && source.waveform_id == waveform_id; });
135 }
136
137 /// Builds explicit per-source FMCW metadata from active streaming transmitters.
138 std::vector<core::FmcwSourceMetadata>
139 buildFmcwSources(const std::vector<core::ActiveStreamingSource>& streaming_sources)
140 {
141 std::vector<core::FmcwSourceMetadata> fmcw_sources;
142 for (const auto& streaming_source : streaming_sources)
143 {
144 if (!streaming_source.is_fmcw || streaming_source.transmitter == nullptr)
145 {
146 continue;
147 }
148
149 const auto* signal = streaming_source.transmitter->getSignal();
150 if (signal == nullptr)
151 {
152 continue;
153 }
154
155 const auto transmitter_id = streaming_source.transmitter->getId();
156 const auto waveform_id = signal->getId();
157 auto existing = findFmcwSource(fmcw_sources, transmitter_id, waveform_id);
158 if (existing == fmcw_sources.end())
159 {
160 core::FmcwSourceMetadata source{.transmitter_id = transmitter_id,
161 .transmitter_name = streaming_source.transmitter->getName(),
162 .waveform_id = waveform_id,
163 .waveform_name = signal->getName(),
164 .carrier_frequency = signal->getCarrier(),
166 source.segments.push_back(buildFmcwSourceSegment(streaming_source));
167 fmcw_sources.push_back(std::move(source));
168 continue;
169 }
170
172 }
173 return fmcw_sources;
174 }
175
177 {
178 const RealType active_start = std::max(params::startTime(), source.segment_start);
179 const RealType active_end = std::min(params::endTime(), source.segment_end);
181 .start_time = source.segment_start,
182 .end_time = source.segment_end,
183 .first_step_start_time = core::firstSfcwStepStart(source, active_start, active_end),
184 .emitted_step_count = core::countSfcwStepStarts(source, active_start, active_end)};
185 }
186
187 std::vector<core::SfcwSourceMetadata>::iterator findSfcwSource(std::vector<core::SfcwSourceMetadata>& sources,
188 const SimId transmitter_id,
189 const SimId waveform_id)
190 {
191 return std::ranges::find_if(
192 sources, [&](const core::SfcwSourceMetadata& source)
193 { return source.transmitter_id == transmitter_id && source.waveform_id == waveform_id; });
194 }
195
196 std::vector<core::SfcwSourceMetadata>
197 buildSfcwSources(const std::vector<core::ActiveStreamingSource>& streaming_sources)
198 {
199 std::vector<core::SfcwSourceMetadata> sfcw_sources;
200 for (const auto& streaming_source : streaming_sources)
201 {
202 if (!streaming_source.is_sfcw || streaming_source.transmitter == nullptr)
203 {
204 continue;
205 }
206
207 const auto* signal = streaming_source.transmitter->getSignal();
208 if (signal == nullptr)
209 {
210 continue;
211 }
212
213 const auto transmitter_id = streaming_source.transmitter->getId();
214 const auto waveform_id = signal->getId();
215 auto existing = findSfcwSource(sfcw_sources, transmitter_id, waveform_id);
216 if (existing == sfcw_sources.end())
217 {
218 core::SfcwSourceMetadata source{.transmitter_id = transmitter_id,
219 .transmitter_name = streaming_source.transmitter->getName(),
220 .waveform_id = waveform_id,
221 .waveform_name = signal->getName(),
223 source.segments.push_back(buildSfcwSourceSegment(streaming_source));
224 sfcw_sources.push_back(std::move(source));
225 continue;
226 }
227
229 }
230 return sfcw_sources;
231 }
232
233 /// Adds scalar compatibility chirp metadata to receiver streaming segments for one FMCW source.
235 const core::ActiveStreamingSource& source)
236 {
237 for (auto& segment : metadata.streaming_segments)
238 {
239 const RealType active_start = std::max(segment.start_time, source.segment_start);
240 const RealType active_end = std::min(segment.end_time, source.segment_end);
242 {
244 const auto emitted = core::countFmcwTriangleStarts(source, active_start, active_end);
245 if (first_triangle.has_value() || emitted > 0)
246 {
247 segment.first_triangle_start_time = first_triangle;
248 segment.emitted_triangle_count = emitted;
249 }
250 }
251 else
252 {
254 const auto emitted = core::countFmcwChirpStarts(source, active_start, active_end);
255 if (first_chirp.has_value() || emitted > 0)
256 {
257 segment.first_chirp_start_time = first_chirp;
258 segment.emitted_chirp_count = emitted;
259 }
260 }
261 }
262 }
263
265 const core::ActiveStreamingSource& source)
266 {
267 for (auto& segment : metadata.streaming_segments)
268 {
269 const RealType active_start = std::max(segment.start_time, source.segment_start);
270 const RealType active_end = std::min(segment.end_time, source.segment_end);
272 const auto emitted = core::countSfcwStepStarts(source, active_start, active_end);
273 if (first_step.has_value() || emitted > 0)
274 {
275 segment.first_sfcw_step_start_time = first_step;
276 segment.emitted_sfcw_step_count = emitted;
277 }
278 }
279 }
280
281 /// Half-open time interval in simulation seconds.
282 using TimeSpan = std::pair<RealType, RealType>;
283
284 /// Merges overlapping or adjacent time spans.
285 void normalizeTimeSpans(std::vector<TimeSpan>& spans)
286 {
287 std::ranges::sort(spans, [](const TimeSpan& lhs, const TimeSpan& rhs) { return lhs.first < rhs.first; });
288 std::vector<TimeSpan> merged;
289 for (const auto& span : spans)
290 {
291 if (span.second <= span.first)
292 {
293 continue;
294 }
295 if (merged.empty() || span.first > merged.back().second)
296 {
297 merged.push_back(span);
298 continue;
299 }
300 merged.back().second = std::max(merged.back().second, span.second);
301 }
302 spans = std::move(merged);
303 }
304
305 /// Returns receiver active intervals clipped to simulation time.
306 std::vector<TimeSpan> receiverActiveTimeSpans(const radar::Receiver* receiver)
307 {
308 std::vector<TimeSpan> spans;
309 if (receiver->getSchedule().empty())
310 {
311 spans.emplace_back(params::startTime(), params::endTime());
312 return spans;
313 }
314
315 for (const auto& period : receiver->getSchedule())
316 {
317 const RealType start = std::max(params::startTime(), period.start);
318 const RealType end = std::min(params::endTime(), period.end);
319 if (start < end)
320 {
321 spans.emplace_back(start, end);
322 }
323 }
324 return spans;
325 }
326
327 /// Adds LO-active intervals for one source intersected with a receiver-active interval.
329 std::vector<TimeSpan>& output)
330 {
331 const RealType clipped_start = std::max({receiver_span.first, source.segment_start, params::startTime()});
332 const RealType clipped_end = std::min({receiver_span.second, source.segment_end, params::endTime()});
334 {
335 return;
336 }
337
339 {
340 if (source.chirp_period <= 0.0 || source.chirp_duration <= 0.0)
341 {
342 return;
343 }
345 ? std::size_t{0}
346 : static_cast<std::size_t>(
347 std::floor((clipped_start - source.segment_start) / source.chirp_period));
348 while (true)
349 {
350 if (source.chirp_count.has_value() && chirp_index >= *source.chirp_count)
351 {
352 return;
353 }
354 const RealType chirp_start =
355 source.segment_start + static_cast<RealType>(chirp_index) * source.chirp_period;
357 {
358 return;
359 }
360 const RealType chirp_end = std::min(chirp_start + source.chirp_duration, source.segment_end);
362 const RealType active_end = std::min(chirp_end, clipped_end);
364 {
365 output.emplace_back(active_start, active_end);
366 }
367 ++chirp_index;
368 }
369 }
370
371 output.emplace_back(clipped_start, clipped_end);
372 }
373
374 /// Returns exact LO-active time spans for a dechirped receiver.
375 std::vector<TimeSpan> dechirpActiveTimeSpans(const radar::Receiver* receiver)
376 {
377 std::vector<TimeSpan> spans;
379 for (const auto& receiver_span : receiver_spans)
380 {
381 for (const auto& source : receiver->getDechirpSources())
382 {
384 }
385 }
387 return spans;
388 }
389
390 void appendStreamingSegment(core::OutputFileMetadata& metadata, const std::size_t total_samples,
391 const RealType output_sample_rate, const RealType start_time,
392 const RealType end_time)
393 {
394 const auto start_sample = static_cast<std::uint64_t>(std::min<RealType>(
395 static_cast<RealType>(total_samples),
396 std::max<RealType>(0.0, std::ceil((start_time - params::startTime()) * output_sample_rate))));
397 const auto end_sample = static_cast<std::uint64_t>(std::min<RealType>(
398 static_cast<RealType>(total_samples),
399 std::max<RealType>(0.0, std::ceil((end_time - params::startTime()) * output_sample_rate))));
401 {
402 const core::StreamingSegmentMetadata segment{.start_time = start_time,
403 .end_time = end_time,
404 .sample_count = end_sample - start_sample,
405 .sample_start = start_sample,
406 .sample_end_exclusive = end_sample};
407 metadata.streaming_segments.push_back(segment);
408 }
409 }
410
412 const std::size_t total_samples, const RealType output_sample_rate)
413 {
414 const auto& schedule = receiver->getSchedule();
415 if (schedule.empty())
416 {
419 return;
420 }
421
422 for (const auto& period : schedule)
423 {
424 const RealType start = std::max(params::startTime(), period.start);
425 const RealType end = std::min(params::endTime(), period.end);
426 if (start < end)
427 {
428 appendStreamingSegment(metadata, total_samples, output_sample_rate, start, end);
429 }
430 }
431 }
432
434 const std::size_t total_samples, const RealType output_sample_rate,
435 const std::vector<TimeSpan>& dechirp_time_spans)
436 {
437 if (!receiver->isDechirpEnabled())
438 {
440 return;
441 }
442
443 for (const auto& span : dechirp_time_spans)
444 {
445 appendStreamingSegment(metadata, total_samples, output_sample_rate, span.first, span.second);
446 }
447 }
448
450 const std::vector<core::ActiveStreamingSource>& streaming_sources)
451 {
453 if (metadata.fmcw_sources.size() != 1)
454 {
455 return;
456 }
457
458 metadata.fmcw = metadata.fmcw_sources.front().waveform;
459 for (const auto& streaming_source : streaming_sources)
460 {
461 if (streaming_source.is_fmcw && streaming_source.transmitter != nullptr &&
462 streaming_source.transmitter->getId() == metadata.fmcw_sources.front().transmitter_id)
463 {
465 }
466 }
467 }
468
470 const std::vector<core::ActiveStreamingSource>& streaming_sources)
471 {
473 if (metadata.sfcw_sources.size() != 1)
474 {
475 return;
476 }
477
478 metadata.sfcw = metadata.sfcw_sources.front().waveform;
479 for (const auto& streaming_source : streaming_sources)
480 {
481 if (streaming_source.is_sfcw && streaming_source.transmitter != nullptr &&
482 streaming_source.transmitter->getId() == metadata.sfcw_sources.front().transmitter_id)
483 {
485 }
486 }
487 }
488
490 {
491 const auto& if_request = receiver->getFmcwIfChainRequest();
492 const auto& if_plan = receiver->getFmcwIfResamplerPlan();
493 metadata.fmcw_if_legacy_full_rate = !if_request.sample_rate_hz.has_value();
494 metadata.fmcw_if_decimation_enabled = if_plan.has_value();
495 if (if_request.sample_rate_hz.has_value())
496 {
497 metadata.fmcw_if_requested_sample_rate = if_request.sample_rate_hz;
498 }
499 if (!if_plan.has_value())
500 {
501 return;
502 }
503
504 metadata.fmcw_if_sample_rate = if_plan->actual_output_sample_rate_hz;
505 metadata.fmcw_if_input_sample_rate = if_plan->input_sample_rate_hz;
506 metadata.fmcw_if_resample_numerator = static_cast<unsigned>(if_plan->overall_ratio.numerator);
507 metadata.fmcw_if_resample_denominator = static_cast<unsigned>(if_plan->overall_ratio.denominator);
508 metadata.fmcw_if_decimation_factor = if_plan->actual_output_sample_rate_hz > 0.0
509 ? if_plan->input_sample_rate_hz / if_plan->actual_output_sample_rate_hz
510 : 0.0;
511 metadata.fmcw_if_filter_bandwidth = if_plan->filter_bandwidth_hz;
512 metadata.fmcw_if_filter_transition_width = if_plan->filter_transition_width_hz;
513 metadata.fmcw_if_filter_stopband = if_plan->stopband_attenuation_db;
514 metadata.fmcw_if_filter_group_delay_seconds = if_plan->group_delay_seconds;
515 metadata.fmcw_if_compensated_integer_delay_samples = if_plan->warmup_discard_samples;
516 metadata.fmcw_if_compensated_fractional_delay_samples = if_plan->fractional_output_delay_samples;
517 metadata.fmcw_if_warmup_discard_samples = if_plan->warmup_discard_samples;
518 metadata.fmcw_if_phase_refinement = static_cast<unsigned>(if_plan->phase_refinement);
519 metadata.fmcw_if_timing_error_seconds = if_plan->estimated_timing_error_seconds;
520 metadata.fmcw_if_phase_error_radians = if_plan->estimated_phase_error_radians;
521 metadata.fmcw_if_noise_variance =
522 params::boltzmannK() * receiver->getNoiseTemperature() * if_plan->actual_output_sample_rate_hz;
523 metadata.fmcw_if_group_delay_compensated = if_plan->group_delay_compensated;
524 }
525
527 {
528 const auto& reference = receiver->getDechirpReference();
532 {
533 metadata.fmcw_dechirp_reference_transmitter_id = reference.transmitter_id;
534 metadata.fmcw_dechirp_reference_transmitter_name = reference.transmitter_name;
535 }
537 {
538 metadata.fmcw_dechirp_reference_waveform_id = reference.waveform_id;
539 metadata.fmcw_dechirp_reference_waveform_name = reference.waveform_name;
540 if (!receiver->getDechirpSources().empty())
541 {
542 metadata.fmcw_dechirp_reference_waveform = buildFmcwMetadata(receiver->getDechirpSources().front());
543 }
544 }
545 }
546
547 /// Builds output metadata for a streaming receiver result file.
550 const std::size_t total_samples,
551 const std::vector<core::ActiveStreamingSource>& streaming_sources,
552 const RealType output_sample_rate, const std::vector<TimeSpan>& dechirp_time_spans = {})
553 {
555 .receiver_id = receiver->getId(),
556 .receiver_name = receiver->getName(),
557 .mode = receiver->getMode() == radar::OperationMode::FMCW_MODE
558 ? "fmcw"
559 : (receiver->getMode() == radar::OperationMode::SFCW_MODE ? "sfcw" : "cw"),
560 .path = hdf5_filename,
561 .sampling_rate = output_sample_rate,
562 .total_samples = static_cast<std::uint64_t>(total_samples),
563 .sample_start = 0,
564 .sample_end_exclusive = static_cast<std::uint64_t>(total_samples)};
565
569
570 metadata.fmcw_dechirp_mode = std::string(radar::dechirpModeToken(receiver->getDechirpMode()));
571 if (receiver->isDechirpEnabled())
572 {
575 }
576
577 return metadata;
578 }
579
580 /// Converts a receiver mode to the stable sink descriptor token.
582 {
583 switch (receiver->getMode())
584 {
586 return "pulsed";
588 return "fmcw";
590 return "sfcw";
592 return "cw";
593 }
594 return "unknown";
595 }
596
597 [[nodiscard]] std::string coordinateFrameToken(const params::CoordinateFrame frame)
598 {
599 switch (frame)
600 {
602 return "ENU";
604 return "UTM";
606 return "ECEF";
607 }
608 return "ENU";
609 }
610
612 {
615 .origin_latitude = params::originLatitude(),
616 .origin_longitude = params::originLongitude(),
617 .origin_altitude = params::originAltitude(),
618 .utm_zone = params::utmZone(),
619 .utm_north_hemisphere = params::utmNorthHemisphere()};
620 }
621
624 {
626 const auto* platform = receiver->getPlatform();
627 if (platform == nullptr)
628 {
629 return state;
630 }
631
632 const RealType t0 = params::startTime();
633 state.platform_id = platform->getId();
634 state.platform_name = platform->getName();
635 try
636 {
637 const auto position = platform->getPosition(t0);
638 state.position_x = position.x;
639 state.position_y = position.y;
640 state.position_z = position.z;
641 }
642 catch (...)
643 {
644 }
645 try
646 {
647 const auto velocity = platform->getMotionPath()->getVelocity(t0);
648 state.velocity_x = velocity.x;
649 state.velocity_y = velocity.y;
650 state.velocity_z = velocity.z;
651 }
652 catch (...)
653 {
654 }
655 try
656 {
657 const auto rotation = platform->getRotation(t0);
658 state.azimuth = rotation.azimuth;
659 state.elevation = rotation.elevation;
660 }
661 catch (...)
662 {
663 }
664 return state;
665 }
666
669 const std::span<const core::ActiveStreamingSource> streaming_sources)
670 {
671 if (receiver->isDechirpEnabled() && !receiver->getDechirpSources().empty())
672 {
673 return &receiver->getDechirpSources().front();
674 }
675 const auto found = std::ranges::find_if(streaming_sources, [](const core::ActiveStreamingSource& source)
676 { return source.is_fmcw; });
677 return found == streaming_sources.end() ? nullptr : &*found;
678 }
679
681 {
682 return receiver == nullptr ? nullptr : dynamic_cast<const radar::Transmitter*>(receiver->getAttached());
683 }
684
685 [[nodiscard]] bool isCwContextSource(const core::ActiveStreamingSource& source) noexcept
686 {
687 return source.transmitter != nullptr &&
689 }
690
691 /// Returns the sole logical CW source represented by the active segments.
692 /// Repeated schedule segments from the same transmitter/waveform remain one
693 /// source; multiple distinct CW sources are intentionally left unbound because
694 /// the scalar VITA CW context cannot describe their superposition faithfully.
696 uniqueCwContextTransmitter(const std::span<const core::ActiveStreamingSource> streaming_sources)
697 {
698 const radar::Transmitter* candidate = nullptr;
700 for (const auto& source : streaming_sources)
701 {
702 if (!isCwContextSource(source))
703 {
704 continue;
705 }
706 const auto* signal = source.transmitter->getSignal();
707 if (signal == nullptr)
708 {
709 continue;
710 }
711 if (candidate == nullptr)
712 {
713 candidate = source.transmitter;
715 continue;
716 }
717 if (candidate != source.transmitter || candidate_signal != signal)
718 {
719 return nullptr;
720 }
721 }
722 return candidate;
723 }
724
727 {
728 if (signal == nullptr)
729 {
730 return;
731 }
732 context.waveform_id = signal->getId();
733 context.waveform_name = signal->getName();
734 context.carrier_frequency = signal->getCarrier();
735 context.power = signal->getPower();
736 context.pulse_width = signal->getLength();
737 context.native_sample_rate = signal->getRate();
738 context.native_sample_count = signal->getSampleCount();
739 }
740
743 {
744 if (signal == nullptr)
745 {
746 return;
747 }
748 context.waveform_id = signal->getId();
749 context.waveform_name = signal->getName();
750 context.carrier_frequency = signal->getCarrier();
751 context.power = signal->getPower();
752 }
753
756 {
757 if (signal == nullptr)
758 {
759 return;
760 }
761 context.waveform_id = signal->getId();
762 context.waveform_name = signal->getName();
763 context.carrier_frequency = signal->getCarrier();
764 context.power = signal->getPower();
765 const auto* sfcw = signal->getSteppedFrequencySignal();
766 if (sfcw == nullptr)
767 {
768 return;
769 }
770 const RealType effective_bandwidth = sfcw->effectiveBandwidth();
771 const RealType step_size = std::abs(sfcw->getStepSize());
772 context.start_frequency_offset = sfcw->getStartFrequencyOffset();
773 context.step_size = sfcw->getStepSize();
774 context.step_count = static_cast<std::uint64_t>(sfcw->getStepCount());
775 context.dwell_time = sfcw->getDwellTime();
776 context.step_period = sfcw->getStepPeriod();
777 context.sweep_period = sfcw->getSweepPeriod();
778 context.sweep_count = sfcw->getSweepCount().has_value()
779 ? std::optional<std::uint64_t>(static_cast<std::uint64_t>(*sfcw->getSweepCount()))
780 : std::nullopt;
781 context.first_frequency = sfcw->firstFrequency(signal->getCarrier());
782 context.last_frequency = sfcw->lastFrequency(signal->getCarrier());
783 context.frequency_span = sfcw->frequencySpan();
784 context.effective_bandwidth = effective_bandwidth;
785 context.range_resolution = effective_bandwidth > 0.0 ? params::c() / (2.0 * effective_bandwidth) : 0.0;
786 context.unambiguous_range = step_size > 0.0 ? params::c() / (2.0 * step_size) : 0.0;
787 }
788
790 {
792 if (receiver == nullptr || receiver->getMode() != radar::OperationMode::PULSED_MODE)
793 {
794 return context;
795 }
796
797 context.present = true;
798 context.window_length = receiver->getWindowLength();
799 context.window_prf = receiver->getWindowPrf();
800 context.window_skip = receiver->getWindowSkip();
801 context.window_count = receiver->getWindowCount();
802 if (const auto* transmitter = attachedTransmitter(receiver); transmitter != nullptr)
803 {
804 populateWaveformIdentity(context, transmitter->getSignal());
805 }
806 if (context.carrier_frequency == 0.0)
807 {
808 if (const auto timing = receiver->getTiming(); timing)
809 {
810 context.carrier_frequency = timing->getFrequency();
811 }
812 }
813 return context;
814 }
815
818 const std::span<const core::ActiveStreamingSource> streaming_sources)
819 {
821 if (receiver == nullptr || receiver->getMode() != radar::OperationMode::CW_MODE)
822 {
823 return context;
824 }
825
826 context.present = true;
828 if (transmitter == nullptr || transmitter->getSignal() == nullptr)
829 {
831 }
832 if (transmitter != nullptr)
833 {
834 populateWaveformIdentity(context, transmitter->getSignal());
835 }
836 if (context.carrier_frequency == 0.0)
837 {
838 if (const auto timing = receiver->getTiming(); timing)
839 {
840 context.carrier_frequency = timing->getFrequency();
841 }
842 }
843 return context;
844 }
845
848 const std::span<const core::ActiveStreamingSource> streaming_sources)
849 {
851 if (receiver == nullptr || receiver->getMode() != radar::OperationMode::SFCW_MODE)
852 {
853 return context;
854 }
855
856 context.present = true;
857 if (const auto* transmitter = attachedTransmitter(receiver); transmitter != nullptr)
858 {
859 populateWaveformIdentity(context, transmitter->getSignal());
860 }
861 if (context.waveform_id == 0)
862 {
863 const auto found = std::ranges::find_if(streaming_sources, [](const core::ActiveStreamingSource& source)
864 { return source.is_sfcw && source.transmitter != nullptr; });
865 if (found != streaming_sources.end())
866 {
867 populateWaveformIdentity(context, found->transmitter->getSignal());
868 }
869 }
870 return context;
871 }
872
875 const std::span<const core::ActiveStreamingSource> streaming_sources)
876 {
878 if (receiver == nullptr || receiver->getMode() != radar::OperationMode::FMCW_MODE)
879 {
880 return context;
881 }
882
883 context.dechirp_mode = std::string(radar::dechirpModeToken(receiver->getDechirpMode()));
884 const auto& reference = receiver->getDechirpReference();
886 context.dechirp_reference_transmitter_id = reference.transmitter_id;
887 context.dechirp_reference_transmitter_name = reference.transmitter_name;
888 context.dechirp_reference_waveform_id = reference.waveform_id;
889 context.dechirp_reference_waveform_name = reference.waveform_name;
890
891 const auto* source = findFmcwContextSource(receiver, streaming_sources);
892 if (source == nullptr)
893 {
894 return context;
895 }
896
897 const auto waveform = buildFmcwMetadata(*source);
898 context.present = true;
899 context.waveform_shape = waveform.waveform_shape;
900 context.chirp_bandwidth = waveform.chirp_bandwidth;
901 context.chirp_duration = waveform.chirp_duration;
902 context.chirp_period = waveform.chirp_period;
903 context.chirp_rate = waveform.chirp_rate;
904 context.chirp_rate_signed = waveform.chirp_rate_signed;
905 context.sweep_direction =
906 source->kind == core::StreamingWaveformKind::FmcwTriangle ? "up_down" : waveform.chirp_direction;
907 context.start_frequency_offset = waveform.start_frequency_offset;
908 context.triangle_period = waveform.triangle_period;
909 context.chirp_count = waveform.chirp_count;
910 context.triangle_count = waveform.triangle_count;
911 return context;
912 }
913
914 /// Resolves the RF reference represented by the receiver stream. The receiver
915 /// timing source is a clock model, not normally an RF carrier, so it is only a
916 /// compatibility fallback when the mode-specific metadata has no bound source.
918 const core::ReceiverStreamDescriptor& descriptor,
919 const std::span<const core::ActiveStreamingSource> streaming_sources)
920 {
921 switch (receiver->getMode())
922 {
924 if (descriptor.pulsed.waveform_id != 0 || !descriptor.pulsed.waveform_name.empty())
925 {
926 return descriptor.pulsed.carrier_frequency;
927 }
928 break;
930 if (descriptor.cw.waveform_id != 0 || !descriptor.cw.waveform_name.empty())
931 {
932 return descriptor.cw.carrier_frequency;
933 }
934 break;
936 if (const auto* source = findFmcwContextSource(receiver, streaming_sources); source != nullptr)
937 {
938 return source->carrier_freq;
939 }
940 break;
942 if (descriptor.sfcw.waveform_id != 0 || !descriptor.sfcw.waveform_name.empty())
943 {
944 return descriptor.sfcw.carrier_frequency;
945 }
946 break;
947 }
948
949 if (const auto timing = receiver->getTiming(); timing)
950 {
951 return timing->getFrequency();
952 }
953 return 0.0;
954 }
955 }
956
958 const radar::Receiver* receiver, const std::string& output_path, const std::size_t total_samples,
959 const std::vector<core::ActiveStreamingSource>& streaming_sources, const RealType output_sample_rate)
960 {
961 const auto dechirp_time_spans =
962 receiver->isDechirpEnabled() ? dechirpActiveTimeSpans(receiver) : std::vector<TimeSpan>{};
965 }
966
969 const std::span<const core::ActiveStreamingSource> streaming_sources)
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 }
988
990 const RealType first_sample_time, const RealType sample_rate,
991 const std::span<const ComplexType> samples,
992 const std::uint64_t sample_start,
993 std::shared_ptr<const core::OutputFileMetadata> file_metadata)
994 {
995 return buildReceiverSampleBlock(receiver, first_sample_time, sample_rate, samples, sample_start,
996 std::span<const core::ActiveStreamingSource>{}, std::move(file_metadata));
997 }
998
1001 const RealType sample_rate, const std::span<const ComplexType> samples,
1002 const std::uint64_t sample_start,
1003 const std::span<const core::ActiveStreamingSource> streaming_sources,
1004 std::shared_ptr<const core::OutputFileMetadata> file_metadata)
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 }
1017
1018 void runPulsedFinalizer(radar::Receiver* receiver, const std::vector<std::unique_ptr<radar::Target>>* targets,
1019 const std::shared_ptr<core::ProgressReporter>& reporter, const std::string& output_dir,
1020 const std::shared_ptr<core::OutputMetadataCollector>& metadata_collector,
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 {
1070 std::ranges::generate(pnoise, [&] { return timing_model->getNextSample(); });
1072 job.ideal_start_time, pnoise[0], timing_model->getFrequency(), rate);
1073 }
1074
1075 std::vector<ComplexType> window_buffer(window_samples);
1076
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 {
1085 }
1086
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 }
1125
1126}
const Transmitter & transmitter
const Receiver & receiver
Vec3 position
RealType getChirpBandwidth() const noexcept
Gets the chirp bandwidth in hertz.
FmcwChirpDirection getDirection() const noexcept
Gets the FMCW sweep direction.
RealType getChirpBandwidth() const noexcept
Gets the chirp bandwidth in hertz.
Class representing a radar signal with associated properties.
RealType effectiveBandwidth() const noexcept
Gets DFT-convention effective bandwidth in hertz.
const std::optional< std::size_t > & getSweepCount() const noexcept
Gets optional finite sweep count.
RealType getStepSize() const noexcept
Gets the uniform frequency step in hertz.
RealType lastFrequency(RealType carrier_frequency) const noexcept
Gets final-step RF frequency in hertz.
RealType frequencySpan() const noexcept
Gets first-to-last absolute span in hertz.
RealType firstFrequency(RealType carrier_frequency) const noexcept
Gets first-step RF frequency in hertz.
RealType getStartFrequencyOffset() const noexcept
Gets the first-step offset from carrier in hertz.
std::size_t getStepCount() const noexcept
Gets the number of steps per sweep.
RealType getStepPeriod() const noexcept
Gets step repetition period in seconds.
RealType getDwellTime() const noexcept
Gets active dwell time per step in seconds.
RealType x
The x component of the vector.
RealType z
The z component of the vector.
RealType y
The y component of the vector.
Manages radar signal reception and response processing.
Definition receiver.h:47
@ Transmitter
Use a named transmitter.
@ Attached
Use the attached transmitter.
@ Custom
Use a named top-level waveform with the receiver schedule.
Represents a radar transmitter system.
Definition transmitter.h:34
fers_signal::RadarSignal * getSignal() const noexcept
Retrieves the radar signal currently being transmitted.
Definition transmitter.h:72
double RealType
Type for real numbers.
Definition config.h:27
Declares the functions for the asynchronous receiver finalization pipelines.
Declares focused, testable pipeline steps for receiver finalization.
Header file for the logging system.
#define LOG(level,...)
Definition logging.h:19
std::uint64_t countFmcwTriangleStarts(const ActiveStreamingSource &source, const RealType active_start, const RealType active_end)
Counts FMCW triangles that start inside the absolute interval.
std::uint64_t countSfcwStepStarts(const ActiveStreamingSource &source, const RealType active_start, const RealType active_end)
Counts SFCW active dwells that start inside the absolute interval.
std::uint64_t countFmcwChirpStarts(const ActiveStreamingSource &source, const RealType active_start, const RealType active_end)
Counts FMCW chirps that start inside the absolute interval.
std::optional< RealType > firstFmcwTriangleStart(const ActiveStreamingSource &source, const RealType active_start, const RealType active_end)
Returns the first FMCW triangle start inside the absolute interval, if one exists.
std::optional< RealType > firstSfcwStepStart(const ActiveStreamingSource &source, const RealType active_start, const RealType active_end)
Returns the first SFCW step start inside the absolute interval, if one exists.
std::optional< RealType > firstFmcwChirpStart(const ActiveStreamingSource &source, const RealType active_start, const RealType active_end)
Returns the first FMCW chirp start inside the absolute interval, if one exists.
std::string_view fmcwChirpDirectionToken(const FmcwChirpDirection direction) noexcept
Converts a chirp direction to the schema token.
@ FATAL
Fatal level for severe error events.
@ INFO
Info level for informational messages.
RealType endTime() noexcept
Get the end time for the simulation.
Definition parameters.h:109
RealType rate() noexcept
Get the rendering sample rate.
Definition parameters.h:121
RealType startTime() noexcept
Get the start time for the simulation.
Definition parameters.h:103
RealType boltzmannK() noexcept
Get the Boltzmann constant.
Definition parameters.h:97
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
Definition parameters.h:151
double originLongitude() noexcept
Gets the KML/geospatial export origin longitude.
Definition parameters.h:279
int utmZone() noexcept
Gets the configured KML UTM zone.
Definition parameters.h:333
CoordinateFrame
Defines the coordinate systems supported for KML/geospatial export.
Definition parameters.h:31
@ UTM
Universal Transverse Mercator.
@ ENU
East-North-Up local tangent plane (default)
@ ECEF
Earth-Centered, Earth-Fixed.
unsigned adcBits() noexcept
Get the ADC quantization bits.
Definition parameters.h:133
CoordinateFrame coordinateFrame() noexcept
Gets the KML/geospatial export coordinate frame.
Definition parameters.h:321
double originLatitude() noexcept
Gets the KML/geospatial export origin latitude.
Definition parameters.h:273
bool utmNorthHemisphere() noexcept
Gets the configured KML UTM hemisphere.
Definition parameters.h:339
RealType c() noexcept
Get the speed of light.
Definition parameters.h:91
double originAltitude() noexcept
Gets the KML/geospatial export origin altitude.
Definition parameters.h:285
void applyStreamingInterference(std::span< ComplexType > window, const RealType actual_start, const RealType dt, const radar::Receiver *receiver, const std::vector< core::ActiveStreamingSource > &streaming_sources, const std::vector< std::unique_ptr< radar::Target > > *targets, core::ReceiverTrackerCache &tracker_cache, const simulation::CwPhaseNoiseLookup *phase_noise_lookup)
Applies streaming interference to a time window.
void addPhaseNoiseToWindow(std::span< const RealType > noise, std::span< ComplexType > window)
Applies a pre-generated sequence of phase noise samples to an I/Q buffer.
void advanceTimingModel(timing::Timing *timing_model, const radar::Receiver *receiver, const RealType rate)
Advances the receiver's timing model to the start of the next processing window.
std::tuple< RealType, RealType > calculateJitteredStart(const RealType ideal_start, const RealType first_phase_noise, const RealType carrier_freq, const RealType rate)
Calculates the jittered start time and fractional delay from a phase noise sample.
void applyDownsampling(std::vector< ComplexType > &buffer)
Downsamples an IQ buffer to the configured output rate without quantization.
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.
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, core::ReceiverOutputSink *output_sink)
The main function for a dedicated pulsed-mode receiver finalizer thread.
core::OutputFileMetadata 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.
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.
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.
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::string_view dechirpReferenceSourceToken(const Receiver::DechirpReferenceSource source) noexcept
Converts a dechirp reference source to its scenario token.
Definition receiver.cpp:76
@ SFCW_MODE
The component operates in a stepped-frequency CW streaming mode.
@ PULSED_MODE
The component operates in a pulsed mode.
@ CW_MODE
The component operates in a continuous-wave mode.
@ FMCW_MODE
The component operates in an FMCW streaming mode.
std::string_view dechirpModeToken(const Receiver::DechirpMode mode) noexcept
Converts a dechirp mode to its scenario token.
Definition receiver.cpp:45
Defines the Parameters struct and provides methods for managing simulation parameters.
Classes for handling radar waveforms and signals.
Radar Receiver class for managing signal reception and response handling.
Defines the data packet for asynchronous receiver finalization.
Header for receiver-side signal processing and rendering.
uint64_t SimId
64-bit Unique Simulation ID.
Definition sim_id.h:18
math::Vec3 max
Header file for the main simulation runner.
Cached description of an active streaming transmitter segment.
RealType triangle_period
Cached full triangle period in seconds.
RealType carrier_freq
Cached carrier frequency in hertz.
const fers_signal::SteppedFrequencySignal * sfcw
Stable pointer to the stepped-frequency waveform, if any.
RealType segment_start
Segment start time in seconds.
RealType signed_chirp_rate
Cached signed FMCW chirp rate in hertz per second.
RealType file_duration
Duration of the finite file waveform in seconds.
const radar::Transmitter * transmitter
Transmitter active during this segment.
RealType chirp_duration
Cached FMCW chirp duration in seconds.
bool is_sfcw
Compatibility flag for any stepped-frequency source.
RealType chirp_period
Cached FMCW chirp period in seconds.
StreamingWaveformKind kind
Cached streaming waveform shape.
const fers_signal::FmcwChirpSignal * fmcw
Stable pointer to the linear FMCW waveform, if any.
const fers_signal::FileSignal * file
Stable pointer to the finite sampled waveform, if any.
bool is_fmcw
Compatibility flag for any FMCW source.
std::optional< std::size_t > chirp_count
Optional finite chirp count for the segment.
std::optional< std::size_t > triangle_count
Optional finite triangle count for the segment.
RealType segment_end
Segment end time in seconds.
const fers_signal::FmcwTriangleSignal * triangle
Stable pointer to the triangle waveform, if any.
RealType chirp_rate
Cached FMCW chirp rate in hertz per second.
RealType start_freq_off
Cached FMCW start frequency offset in hertz.
FMCW waveform metadata captured for a streaming output file.
std::string waveform_shape
FMCW waveform shape token: linear, triangle, or file.
Metadata for one FMCW illuminator represented in a streaming output file.
SimId transmitter_id
FMCW transmitter SimId.
SimId waveform_id
FMCW waveform SimId.
Metadata for one active FMCW transmitter schedule segment.
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::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::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.
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::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::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.
ReceiverStreamDescriptor stream
std::optional< std::uint64_t > chirp_count
std::optional< std::uint64_t > triangle_count
Per-receiver FMCW tracker state for direct and reflected streaming paths.
A data packet containing all information needed to process one receive window.
SFCW waveform metadata captured for a streaming output file.
RealType carrier_frequency
Waveform carrier frequency in hertz.
Metadata for one SFCW illuminator represented in a streaming output file.
SimId transmitter_id
SFCW transmitter SimId.
SimId waveform_id
SFCW waveform SimId.
Metadata for one active SFCW transmitter schedule segment.
RealType start_time
Transmitter segment start time in seconds.
Metadata for one contiguous streaming output segment.
Timing source for simulation objects.
Header file for the Transmitter class in the radar namespace.