mod plan;
use std::fmt;
use crate::cube::CubeTransformError;
pub use plan::PlanarCartesianProjectionPlan;
#[derive(Clone, Copy, Debug)]
pub struct PlanarCartesianProjectionConfig {
pub range_resolution_m: f32,
pub source_doppler_bins: usize,
pub source_velocity_start_mps: f32,
pub source_velocity_step_mps: f32,
pub target_doppler_bins: usize,
pub target_velocity_start_mps: f32,
pub target_velocity_step_mps: f32,
pub grid_shape_zyx: [usize; 3],
pub grid_origin_xyz_m: [f32; 3],
pub grid_voxel_size_xyz_m: [f32; 3],
pub azimuth_n_fft: usize,
pub elevation_n_fft: usize,
pub aperture_spacing_wavelengths: f32,
}
impl PlanarCartesianProjectionConfig {
pub fn validate(self) -> Result<(), CartesianProjectionError> {
for value in [
self.range_resolution_m,
self.source_velocity_step_mps,
self.target_velocity_step_mps,
self.aperture_spacing_wavelengths,
] {
if !value.is_finite() || value <= 0.0 {
return Err(CartesianProjectionError::InvalidPositiveResolution);
}
}
if self.source_doppler_bins <= 1
|| self.target_doppler_bins == 0
|| self.azimuth_n_fft <= 1
|| self.elevation_n_fft <= 1
{
return Err(CartesianProjectionError::InvalidAxisSize);
}
if !self.source_velocity_start_mps.is_finite()
|| !self.target_velocity_start_mps.is_finite()
{
return Err(CartesianProjectionError::InvalidVelocityOrigin);
}
if self.grid_shape_zyx.into_iter().any(|size| size == 0) {
return Err(CartesianProjectionError::InvalidGridShape);
}
if self
.grid_origin_xyz_m
.into_iter()
.any(|coordinate| !coordinate.is_finite())
{
return Err(CartesianProjectionError::InvalidGridOrigin);
}
if self
.grid_voxel_size_xyz_m
.into_iter()
.any(|size| !size.is_finite() || size <= 0.0)
{
return Err(CartesianProjectionError::InvalidGridVoxelSize);
}
Ok(())
}
}
#[derive(Debug)]
pub struct PlanarCartesianProjection {
pub magnitude_dzyx: Vec<f32>,
pub shape_dzyx: [usize; 4],
pub doppler_start: usize,
pub doppler_stop: usize,
pub range_start: usize,
pub range_stop: usize,
pub spatial_valid_count: usize,
pub doppler_valid_count: usize,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CartesianProjectionError {
Cube(CubeTransformError),
SourceRank { actual: usize },
SourceRequiresSingleFrame { actual: usize },
SourceDopplerMismatch { expected: usize, actual: usize },
SourceApertureMismatch { expected: usize, actual: usize },
SourceRangeMismatch { expected: usize, actual: usize },
InvalidPositiveResolution,
InvalidAxisSize,
InvalidVelocityOrigin,
InvalidGridShape,
InvalidGridOrigin,
InvalidGridVoxelSize,
NoSpatialSupport,
NoDopplerSupport,
EmptySourceSelection,
NonFiniteGridCoordinate,
NonFiniteDopplerCoordinate,
WorkerPanicked,
}
impl fmt::Display for CartesianProjectionError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Cube(error) => error.fmt(formatter),
Self::SourceRank { actual } => {
write!(
formatter,
"Planar Cartesian source rank must be 4; got {actual}."
)
}
Self::SourceRequiresSingleFrame { actual } => write!(
formatter,
"Planar Cartesian projection requires exactly one radar frame; got {actual}."
),
Self::SourceDopplerMismatch { expected, actual } => write!(
formatter,
"Planar Cartesian source Doppler size must be {expected}; got {actual}."
),
Self::SourceApertureMismatch { expected, actual } => write!(
formatter,
"Planar Cartesian source virtual-array size must be {expected}; got {actual}."
),
Self::SourceRangeMismatch { expected, actual } => write!(
formatter,
"Planar Cartesian source range size must be {expected}; got {actual}."
),
Self::InvalidPositiveResolution => write!(
formatter,
"Cartesian projection resolutions must be finite and positive."
),
Self::InvalidAxisSize => write!(
formatter,
"Cartesian projection FFT and Doppler sizes are invalid."
),
Self::InvalidVelocityOrigin => write!(
formatter,
"Cartesian projection velocity origins must be finite."
),
Self::InvalidGridShape => write!(
formatter,
"Cartesian projection grid_shape_zyx must be positive."
),
Self::InvalidGridOrigin => write!(
formatter,
"Cartesian projection grid origin must be finite."
),
Self::InvalidGridVoxelSize => write!(
formatter,
"Cartesian projection voxel sizes must be positive."
),
Self::NoSpatialSupport => write!(
formatter,
"Cartesian target grid has no support in the source radar field of view."
),
Self::NoDopplerSupport => write!(
formatter,
"Cartesian target Doppler axis has no support in the source radar axis."
),
Self::EmptySourceSelection => write!(
formatter,
"Cartesian interpolation source selection is empty."
),
Self::NonFiniteGridCoordinate => write!(
formatter,
"Cartesian projection grid coordinates must remain finite."
),
Self::NonFiniteDopplerCoordinate => write!(
formatter,
"Cartesian projection Doppler coordinates must remain finite."
),
Self::WorkerPanicked => write!(formatter, "Cartesian projection worker panicked."),
}
}
}
impl std::error::Error for CartesianProjectionError {}
impl From<CubeTransformError> for CartesianProjectionError {
fn from(error: CubeTransformError) -> Self {
Self::Cube(error)
}
}