Merge pull request 'feature/add_coverage' (#2) from feature/add_coverage into main

Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
2026-03-06 06:53:15 +09:00
5 changed files with 597 additions and 82 deletions
@@ -0,0 +1,118 @@
function [windowVector, metrics] = create_window_with_metrics(windowType, windowLength, varargin)
% CREATE_WINDOW_WITH_METRICS -
%
% :
% windowType - : 'none', 'hann', 'hamming', 'blackman', 'chebwin'
% windowLength - ( )
% varargin - (: chebwin의 sidelobe level)
%
% :
% windowVector - (1 x windowLength)
% metrics -
% .type :
% .length :
% .snr_loss_dB : SNR (dB)
% .scalloping_loss_dB: Scalloping (dB)
% .coherent_gain :
% .enbw : Equivalent Noise Bandwidth (bins)
%
if windowLength <= 0
error('Window length must be positive.');
end
%
switch lower(windowType)
case 'none'
windowVector = ones(1, windowLength);
case 'hann'
windowVector = hann(windowLength)';
case 'hamming'
windowVector = hamming(windowLength)';
case 'blackman'
windowVector = blackman(windowLength)';
case 'chebwin'
% Chebyshev sidelobe level (: 60dB)
if ~isempty(varargin)
sidelobe_dB = varargin{1};
else
sidelobe_dB = 60;
end
windowVector = chebwin(windowLength, sidelobe_dB)';
otherwise
warning('Unknown window type "%s". Using Hann window as default.', windowType);
windowVector = hann(windowLength)';
windowType = 'hann';
end
%
metrics = calculate_window_metrics(windowVector, windowType);
end
function metrics = calculate_window_metrics(windowVector, windowType)
% CALCULATE_WINDOW_METRICS -
%
% :
% 1. SNR Loss (dB) : SNR
% 2. Scalloping Loss (dB): FFT bin (0.5 bin offset)
% 3. Coherent Gain :
% 4. ENBW (bins) : Equivalent Noise Bandwidth
w = windowVector(:).'; % Row vector로
N = numel(w);
if N == 0
metrics = struct('type', windowType, 'length', 0, ...
'snr_loss_dB', NaN, 'scalloping_loss_dB', NaN, ...
'coherent_gain', NaN, 'enbw', NaN);
return;
end
% 1. Coherent Gain ( )
coherent_gain = mean(w);
% 2. Noise Power Gain ( )
noise_power_gain = mean(abs(w).^2);
% 3. SNR Loss (dB)
% SNR_loss = (Noise Power Gain) / (Coherent Gain)^2
%
if abs(coherent_gain) > eps
snr_loss_linear = noise_power_gain / (abs(coherent_gain)^2);
snr_loss_dB = 10 * log10(snr_loss_linear);
else
snr_loss_dB = NaN;
end
% 4. Equivalent Noise Bandwidth (ENBW)
% ENBW는 bin의
enbw = N * noise_power_gain / (sum(w)^2);
% 5. Scalloping Loss (dB)
% FFT bin (0.5 bin offset)
%
sample_index = 0:(N-1);
half_bin_response = abs(sum(w .* exp(-1j * 2 * pi * 0.5 * sample_index / N)));
dc_response = abs(sum(w));
if dc_response > eps
scalloping_loss_dB = -20 * log10(half_bin_response / dc_response);
else
scalloping_loss_dB = NaN;
end
%
metrics = struct(...
'type', lower(windowType), ...
'length', N, ...
'snr_loss_dB', snr_loss_dB, ...
'scalloping_loss_dB', scalloping_loss_dB, ...
'coherent_gain', coherent_gain, ...
'enbw', enbw);
end
+4 -13
View File
@@ -3,7 +3,6 @@ function [rd_map, doppler_axis] = process_doppler_fft(range_profile, RadarParams
% - range_profile: [NumRx, NumTx, NumChirps, NumRangeBins]
% - RadarParams:
NumChirps = RadarParams.Waveform.NumChirps;
window_type = RadarParams.SP.RDM.window_type_doppler;
[~, ~, ~, ~] = size(range_profile);
%
@@ -12,18 +11,10 @@ function [rd_map, doppler_axis] = process_doppler_fft(range_profile, RadarParams
% 1. (PRI, Pulse Repetition Interval)
T_pri = RadarParams.Waveform.PRI;
% 2. Doppler-FFT용 ( )
% (3 ) .
if strcmpi(window_type, 'none')
win_doppler = ones(1, NumChirps);
elseif strcmpi(window_type, 'hamming')
win_doppler = hamming(NumChirps)';
elseif strcmpi(window_type, 'blackman')
win_doppler = blackman(NumChirps)';
elseif strcmpi(window_type, 'hann')
win_doppler = hann(NumChirps)';
else % default: 'chebwin'
win_doppler = chebwin(NumChirps, 60)'; % 60dB
% 2. Main.m에서 Doppler-FFT용
win_doppler = RadarParams.SP.RDM.window_doppler;
if length(win_doppler) ~= NumChirps
error('Doppler window length mismatch: expected %d, got %d', NumChirps, length(win_doppler));
end
win_data = range_profile .* reshape(win_doppler, [1, 1, NumChirps, 1]);
+4 -12
View File
@@ -5,22 +5,14 @@ function [range_profile, range_axis] = process_range_fft_lpf(adc_raw_data, Radar
fs_adc = RadarParams.Waveform.fs_adc;
Slope = RadarParams.Waveform.Slope;
fc_lpf_Hz = RadarParams.Rxpath.fc_lpf;
window_type = RadarParams.SP.RDM.window_type_range;
[~, ~, ~, N_samples] = size(adc_raw_data);
c = RadarParams.Basic.c;
% 1.
if strcmpi(window_type, 'none')
win = ones(1, N_samples);
elseif strcmpi(window_type, 'hamming')
win = hamming(N_samples)';
elseif strcmpi(window_type, 'blackman')
win = blackman(N_samples)';
elseif strcmpi(window_type, 'chebwin')
win = chebwin(N_samples, 60)';
else % default: 'hann'
win = hann(N_samples)';
% 1. Main.m에서
win = RadarParams.SP.RDM.window_range;
if length(win) ~= N_samples
error('Window length mismatch: expected %d, got %d', N_samples, length(win));
end
win_data = adc_raw_data .* reshape(win, [1, 1, 1, N_samples]);
+379
View File
@@ -0,0 +1,379 @@
function [snr_coverage_2d, azimuth_deg, elevation_deg, coverage_info, fig] = analyze_coverage(RadarParams, target_range_m, target_rcs_dBsm)
% Coverage : 2D SNR
%
% :
% 1) Azimuth 0, Elevation 0 SNR
% 2) SNR ()
% 3) RCS
%
% :
% - RadarParams:
% - target_range_m: (m), 100
% - target_rcs_dBsm: RCS (dBsm), 0
%
% :
% - snr_coverage_2d: 2D SNR [elevation x azimuth]
% - azimuth_deg:
% - elevation_deg:
% - coverage_info:
% - fig: figure
%%
if nargin < 2
target_range_m = 100; % : 100m
end
if nargin < 3
target_rcs_dBsm = 0; % : 0 dBsm = 1
end
%%
lambda = RadarParams.Waveform.lambda_c;
fc = RadarParams.Waveform.fc;
kb = RadarParams.Basic.kb;
T0 = RadarParams.Basic.T0;
%
TxPattern = RadarParams.Antenna.TxPattern;
RxPattern = RadarParams.Antenna.RxPattern;
NumTx = RadarParams.Antenna.NumTx;
NumRx = RadarParams.Antenna.NumRx;
%
rxPathGain_dB = RadarParams.Rxpath.rxPathGain_dB;
system_NF_dB = RadarParams.Rxpath.system_NF_dB;
% RCS ()
rcs_target = 10^(target_rcs_dBsm / 10);
%% 2D (1D -> 2D )
% ( )
az_common = -90:1:90;
el_common = -90:1:90;
[AZ_common_grid, EL_common_grid] = meshgrid(az_common, el_common);
% TX 2D로
avg_tx_gain_2d = zeros(length(el_common), length(az_common));
for tx = 1:NumTx
if isfield(TxPattern(tx), 'gain_az_dBi') % 1D
az_angles = TxPattern(tx).az_angles;
el_angles = TxPattern(tx).el_angles;
gain_az = TxPattern(tx).gain_az_dBi;
gain_el = TxPattern(tx).gain_el_dBi;
%
max_gain_az = max(gain_az);
max_gain_el = max(gain_el);
max_gain_ref = max(max_gain_az, max_gain_el);
% Normalize ( = 0 dB)
gain_az_norm = gain_az - max_gain_az;
gain_el_norm = gain_el - max_gain_el;
%
gain_az_interp = interp1(az_angles, gain_az_norm, AZ_common_grid, 'linear', 'extrap');
gain_el_interp = interp1(el_angles, gain_el_norm, EL_common_grid, 'linear', 'extrap');
% 2D로
tx_gain_2d = gain_az_interp + gain_el_interp + max_gain_ref;
else % 2D
az_angles = TxPattern(tx).az_angles;
el_angles = TxPattern(tx).el_angles;
gain_dBi = TxPattern(tx).gain_dBi;
[AZ_grid, EL_grid] = ndgrid(az_angles, el_angles);
F = scatteredInterpolant(AZ_grid(:), EL_grid(:), gain_dBi(:), 'linear', 'nearest');
tx_gain_2d = F(AZ_common_grid, EL_common_grid);
end
avg_tx_gain_2d = avg_tx_gain_2d + tx_gain_2d;
end
avg_tx_gain_2d = avg_tx_gain_2d / NumTx;
% RX 2D로 ( )
avg_rx_gain_2d = zeros(length(el_common), length(az_common));
for rx = 1:NumRx
if isfield(RxPattern(rx), 'gain_az_dBi') % 1D
az_angles = RxPattern(rx).az_angles;
el_angles = RxPattern(rx).el_angles;
gain_az = RxPattern(rx).gain_az_dBi;
gain_el = RxPattern(rx).gain_el_dBi;
%
max_gain_az = max(gain_az);
max_gain_el = max(gain_el);
max_gain_ref = max(max_gain_az, max_gain_el);
% Normalize ( = 0 dB)
gain_az_norm = gain_az - max_gain_az;
gain_el_norm = gain_el - max_gain_el;
%
gain_az_interp = interp1(az_angles, gain_az_norm, AZ_common_grid, 'linear', 'extrap');
gain_el_interp = interp1(el_angles, gain_el_norm, EL_common_grid, 'linear', 'extrap');
% 2D로
rx_gain_2d = gain_az_interp + gain_el_interp + max_gain_ref;
else % 2D
az_angles = RxPattern(rx).az_angles;
el_angles = RxPattern(rx).el_angles;
gain_dBi = RxPattern(rx).gain_dBi;
[AZ_grid, EL_grid] = ndgrid(az_angles, el_angles);
F = scatteredInterpolant(AZ_grid(:), EL_grid(:), gain_dBi(:), 'linear', 'nearest');
rx_gain_2d = F(AZ_common_grid, EL_common_grid);
end
avg_rx_gain_2d = avg_rx_gain_2d + rx_gain_2d;
end
avg_rx_gain_2d = avg_rx_gain_2d / NumRx;
%% TX
% TX
pa_profile_freq = RadarParams.RFOutput.PA_Profile.freqs;
pa_profile_power_dbm = RadarParams.RFOutput.PA_Profile.power_dBm;
ptx_dbm = interp1(pa_profile_freq, pa_profile_power_dbm, fc, 'linear', 'extrap');
ptx_w = 10^((ptx_dbm - 30) / 10);
% ( )
path_loss_factor = (4 * pi * target_range_m)^2;
rxGain_linear = 10^(rxPathGain_dB / 10);
noise_power_w = kb * T0 * RadarParams.Waveform.fs_adc;
system_NF_linear = 10^(system_NF_dB / 10);
total_noise_power_w = noise_power_w * system_NF_linear * rxGain_linear; % RX
%% SNR (2D - )
% X축: Azimuth, Y축: Elevation, Z축: SNR
azimuth_deg = -90:1:90; % X축: -90 ~ 90, 1
elevation_deg = -90:1:90; % Y축: -90 ~ 90, 1
% az_common과 elevation_deg가
% (dB)
g_tx_db_grid = avg_tx_gain_2d; % [num_el x num_az]
g_rx_db_grid = avg_rx_gain_2d; % [num_el x num_az]
%
g_tx_linear_grid = 10.^(g_tx_db_grid / 10);
g_rx_linear_grid = 10.^(g_rx_db_grid / 10);
%
SP_gain_rngFFT = RadarParams.Waveform.Timing.AdcSampTime * RadarParams.Waveform.fs_adc;
SP_gain_dopFFT = RadarParams.Waveform.NumChirps;
SP_loss_rngwin = 10^(RadarParams.SP.RDM.window_metrics_range.snr_loss_dB / 10);
SP_loss_dopwin = 10^(RadarParams.SP.RDM.window_metrics_doppler.snr_loss_dB / 10);
SP_loss_rng_straddle = 10^(RadarParams.SP.RDM.window_metrics_range.scalloping_loss_dB / 10);
SP_loss_dop_straddle = 10^(RadarParams.SP.RDM.window_metrics_doppler.scalloping_loss_dB / 10);
SP_total = SP_gain_rngFFT * SP_gain_dopFFT / (SP_loss_rngwin * SP_loss_dopwin * SP_loss_rng_straddle * SP_loss_dop_straddle);
% Secondary Surface Loss
secondary_loss = 10^(RadarParams.Antenna.SecondarySurfaceLoss_dB / 10);
fprintf('SP gain rng FFT: %.2f, SP gain doppler FFT: %.2f\n', SP_gain_rngFFT, SP_gain_dopFFT);
fprintf('SP loss range window: %.2f dB, SP loss doppler window: %.2f dB\n', RadarParams.SP.RDM.window_metrics_range.snr_loss_dB, RadarParams.SP.RDM.window_metrics_doppler.snr_loss_dB);
fprintf('SP loss range straddle: %.2f dB, SP loss doppler straddle: %.2f dB\n', RadarParams.SP.RDM.window_metrics_range.scalloping_loss_dB, RadarParams.SP.RDM.window_metrics_doppler.scalloping_loss_dB);
fprintf('SP loss doppler window: %.2f dB\n', RadarParams.SP.RDM.window_metrics_doppler.snr_loss_dB);
fprintf('SP loss doppler straddle: %.2f dB\n', RadarParams.SP.RDM.window_metrics_doppler.scalloping_loss_dB);
fprintf('Secondary surface loss: %.2f dB\n', RadarParams.Antenna.SecondarySurfaceLoss_dB);
% SNR : ( × 2D )
p_rx_w = (ptx_w * (lambda^2) * rcs_target) / ((path_loss_factor^2) * (4 * pi)) * SP_total / secondary_loss;
p_rx_w = p_rx_w * g_tx_linear_grid .* g_rx_linear_grid; %
p_rx_after_rxgain_w = p_rx_w * rxGain_linear;
snr_linear = p_rx_after_rxgain_w / total_noise_power_w;
snr_coverage_2d = 10 * log10(max(snr_linear, eps));
%% 1D CUT (elevation=0 Azimuth cut, azimuth=0 Elevation cut)
[~, el_idx_zero] = min(abs(elevation_deg - 0));
snr_az_cut = snr_coverage_2d(el_idx_zero, :); % elevation=0
[~, az_idx_zero] = min(abs(azimuth_deg - 0));
snr_el_cut = snr_coverage_2d(:, az_idx_zero); % azimuth=0
%% Cartesian (Range, Azimuth, Elevation -> x, y, z)
[AZ_deg_grid, EL_deg_grid] = meshgrid(azimuth_deg, elevation_deg);
x_grid_m = target_range_m .* cosd(EL_deg_grid) .* cosd(AZ_deg_grid);
y_grid_m = target_range_m .* cosd(EL_deg_grid) .* sind(AZ_deg_grid);
z_grid_m = target_range_m .* sind(EL_deg_grid);
%% Coverage
snr_2d_vec = snr_coverage_2d(:);
coverage_info.azimuth_deg = azimuth_deg;
coverage_info.elevation_deg = elevation_deg;
coverage_info.snr_coverage_2d = snr_coverage_2d;
coverage_info.snr_az_cut = snr_az_cut;
coverage_info.snr_el_cut = snr_el_cut;
coverage_info.target_range_m = target_range_m;
coverage_info.target_rcs_dBsm = target_rcs_dBsm;
coverage_info.x_grid_m = x_grid_m;
coverage_info.y_grid_m = y_grid_m;
coverage_info.z_grid_m = z_grid_m;
coverage_info.mean_snr = mean(snr_2d_vec);
coverage_info.max_snr = max(snr_2d_vec);
coverage_info.min_snr = min(snr_2d_vec);
[~, max_idx_2d] = max(snr_2d_vec);
[max_el_idx_2d, max_az_idx_2d] = ind2sub(size(snr_coverage_2d), max_idx_2d);
coverage_info.max_snr_azimuth = azimuth_deg(max_az_idx_2d);
coverage_info.max_snr_elevation = elevation_deg(max_el_idx_2d);
coverage_info.max_snr_x_m = x_grid_m(max_el_idx_2d, max_az_idx_2d);
coverage_info.max_snr_y_m = y_grid_m(max_el_idx_2d, max_az_idx_2d);
coverage_info.max_snr_z_m = z_grid_m(max_el_idx_2d, max_az_idx_2d);
%% 3D Coverage
fig = figure('Name', 'Coverage Analysis - 3D');
set(fig, 'Position', [100, 100, 1600, 900]);
% 1D cut SNR
[~, el_idx_zero] = min(abs(elevation_deg - 0));
[~, az_idx_zero] = min(abs(azimuth_deg - 0));
snr_az_cut = snr_coverage_2d(el_idx_zero, :); % elevation=0 cut
snr_el_cut = snr_coverage_2d(:, az_idx_zero); % azimuth=0 cut
% Subplot 1: x-y top view (color = SNR at elevation=0 cut)
ax_topview = subplot(2, 2, 3);
x_top_m = target_range_m .* cosd(azimuth_deg);
y_top_m = target_range_m .* sind(azimuth_deg);
scatter(x_top_m, y_top_m, 45, snr_az_cut, 'filled');
axis equal;
grid on;
cb = colorbar;
ylabel(cb, 'SNR (dB)');
xlabel('x (m)');
ylabel('y (m)');
title('Top View (Azimuth)');
% Subplot 2: 3D Surface (geometry = x,y,z, color = SNR) - spans full width at top
ax_3d = subplot(2, 2, 1:2);
surf(x_grid_m, y_grid_m, z_grid_m, snr_coverage_2d, 'EdgeColor', 'none');
colormap(jet);
cb_3d = colorbar;
ylabel(cb_3d, 'SNR (dB)');
xlabel('x (m)');
ylabel('y (m)');
zlabel('z (m)');
title('3D Coverage in Cartesian Coordinates');
view(45, 30);
grid on;
hold on;
plot3(coverage_info.max_snr_x_m, coverage_info.max_snr_y_m, coverage_info.max_snr_z_m, 'r*', 'MarkerSize', 20, 'LineWidth', 2);
hold off;
% Subplot 3: x-z side view (azimuth=0, color=SNR at azimuth=0 cut)
ax_sideview = subplot(2, 2, 4);
x_side_m = target_range_m .* cosd(elevation_deg);
z_side_m = target_range_m .* sind(elevation_deg);
scatter(x_side_m, z_side_m, 45, snr_el_cut, 'filled');
grid on;
cb_side = colorbar;
ylabel(cb_side, 'SNR (dB)');
xlabel('x (m)');
ylabel('z (m)');
title('Side View (Elevation)');
%
sgtitle(sprintf('3D Coverage (x,y,z) | Max SNR: %.2f dB at (%.2f, %.2f, %.2f) m', ...
coverage_info.max_snr, coverage_info.max_snr_x_m, coverage_info.max_snr_y_m, coverage_info.max_snr_z_m), 'FontSize', 12);
% Data Cursor Mode ( )
dcm_obj = datacursormode(fig);
dcm_obj.Enable = 'on';
set(dcm_obj, 'UpdateFcn', {@unified_datatip_callback, ...
ax_topview, ax_3d, ax_sideview, ...
x_top_m, y_top_m, snr_az_cut, azimuth_deg, ...
x_grid_m, y_grid_m, z_grid_m, snr_coverage_2d, elevation_deg, ...
x_side_m, z_side_m, snr_el_cut});
dcm_obj.SnapToDataVertex = 'on';
% Help text
fprintf('\n>>> Data Cursor Mode ENABLED <<<\n');
fprintf('Instructions:\n');
fprintf(' 1. Left-click on any plot to inspect points\n');
fprintf(' 2. Top View shows: x, y, Azimuth, SNR\n');
fprintf(' 3. 3D Surface shows: x, y, z, Azimuth, Elevation, SNR\n');
fprintf(' 4. Side View shows: x, z, Elevation, SNR\n');
fprintf(' 5. Press Escape or click "Disable Data Cursor" in Figure Tools to deactivate\n\n');
%%
fprintf('\n===== 3D COVERAGE ANALYSIS REPORT (Cartesian x,y,z) =====\n');
fprintf('Target Configuration:\n');
fprintf(' - Range: %.1f m\n', target_range_m);
fprintf(' - RCS: %.1f dBsm\n', target_rcs_dBsm);
fprintf(' - Analysis Angles: Azimuth ±90 deg, Elevation ±90 deg\n');
fprintf('\nSNR Statistics (Full Grid):\n');
fprintf(' - Mean SNR: %.2f dB\n', coverage_info.mean_snr);
fprintf(' - Max SNR: %.2f dB @ (x,y,z)=(%.2f, %.2f, %.2f) m\n', coverage_info.max_snr, coverage_info.max_snr_x_m, coverage_info.max_snr_y_m, coverage_info.max_snr_z_m);
fprintf(' - Min SNR: %.2f dB\n', coverage_info.min_snr);
fprintf(' - SNR Range: %.2f dB\n', coverage_info.max_snr - coverage_info.min_snr);
fprintf('System Parameters:\n');
fprintf(' - TX Power: %.2f dBm\n', ptx_dbm);
fprintf(' - RX Gain: %.1f dB\n', rxPathGain_dB);
fprintf(' - System NF: %.1f dB\n', system_NF_dB);
fprintf('=============================================\n\n');
end
function output_txt = unified_datatip_callback(~, event, ...
ax_topview, ax_3d, ax_sideview, ...
x_top_m, y_top_m, snr_az_cut, azimuth_deg, ...
x_grid_m, y_grid_m, z_grid_m, snr_coverage_2d, elevation_deg, ...
x_side_m, z_side_m, snr_el_cut)
% Unified datatip callback for all subplots
try
pos = event.Position;
current_ax = event.Target.Parent; % Get current axis
% Determine which subplot was clicked
if isequal(current_ax, ax_3d)
% 3D Plot
x_val = pos(1);
y_val = pos(2);
z_val = pos(3);
dist2 = (x_grid_m - x_val).^2 + (y_grid_m - y_val).^2 + (z_grid_m - z_val).^2;
[~, nearest_idx] = min(dist2(:));
[nearest_el_idx, nearest_az_idx] = ind2sub(size(dist2), nearest_idx);
snr_val = snr_coverage_2d(nearest_el_idx, nearest_az_idx);
az_val = azimuth_deg(nearest_az_idx);
el_val = elevation_deg(nearest_el_idx);
output_txt = {
['x: ' num2str(x_val, '%.3f') ' m']
['y: ' num2str(y_val, '%.3f') ' m']
['z: ' num2str(z_val, '%.3f') ' m']
['Azimuth: ' num2str(az_val, '%.2f') ' deg']
['Elevation: ' num2str(el_val, '%.2f') ' deg']
['SNR[dB]: ' num2str(snr_val, '%.3f')]
};
elseif isequal(current_ax, ax_topview)
% Top View - x, y, Azimuth, SNR
x_val = pos(1);
y_val = pos(2);
[~, nearest_az_idx] = min(abs(x_top_m - x_val).^2 + abs(y_top_m - y_val).^2);
az_val = azimuth_deg(nearest_az_idx);
snr_val = snr_az_cut(nearest_az_idx);
output_txt = {
['x: ' num2str(x_val, '%.3f') ' m']
['y: ' num2str(y_val, '%.3f') ' m']
['Azimuth: ' num2str(az_val, '%.2f') ' deg']
['SNR[dB]: ' num2str(snr_val, '%.3f')]
};
elseif isequal(current_ax, ax_sideview)
% Side View - x, z, Elevation, SNR
x_val = pos(1);
z_val = pos(2);
[~, nearest_el_idx] = min(abs(x_side_m - x_val).^2 + abs(z_side_m - z_val).^2);
el_val = elevation_deg(nearest_el_idx);
snr_val = snr_el_cut(nearest_el_idx);
output_txt = {
['x: ' num2str(x_val, '%.3f') ' m']
['z: ' num2str(z_val, '%.3f') ' m']
['Elevation: ' num2str(el_val, '%.2f') ' deg']
['SNR[dB]: ' num2str(snr_val, '%.3f')]
};
else
output_txt = 'Unknown plot';
end
catch ME
output_txt = ['Error: ' ME.message];
end
end
+92 -57
View File
@@ -10,6 +10,17 @@ currentFilePath = mfilename('fullpath');
currentFolder = fileparts(currentFilePath);
addpath(genpath(currentFolder));
%% =================== ===================
% figure를 on/off .
PlotToggle.range_profile = false; % Step 8 Range Profile
PlotToggle.tx_single = false; % Figure 1 Single chirp waveform
PlotToggle.tx_multi = false; % Figure 2 Multi chirp waveform
PlotToggle.tx_antenna = false; % Figure 3 TX antenna pattern
PlotToggle.rx_antenna = false; % Figure 4 RX antenna pattern
PlotToggle.rd_map = false; % Figure 5 Range-Doppler map
PlotToggle.cfar = false; % Figure 6 CFAR detections
PlotToggle.coverage = true; % Figure 7 Coverage analysis
%% =================== ===================
% 1)
@@ -108,29 +119,13 @@ RadarParams.SP.RDM.window_type_doppler = 'chebwin'; % Doppler FFT용 윈도
num_samples_range = round(RadarParams.Waveform.fs_adc * RadarParams.Waveform.Timing.AdcSampTime);
num_chirps_doppler = RadarParams.Waveform.NumChirps;
if strcmpi(RadarParams.SP.RDM.window_type_range, 'none')
RadarParams.SP.RDM.window_range = ones(1, num_samples_range);
elseif strcmpi(RadarParams.SP.RDM.window_type_range, 'hamming')
RadarParams.SP.RDM.window_range = hamming(num_samples_range)';
elseif strcmpi(RadarParams.SP.RDM.window_type_range, 'blackman')
RadarParams.SP.RDM.window_range = blackman(num_samples_range)';
elseif strcmpi(RadarParams.SP.RDM.window_type_range, 'chebwin')
RadarParams.SP.RDM.window_range = chebwin(num_samples_range, 60)';
else
RadarParams.SP.RDM.window_range = hann(num_samples_range)';
end
% Range
[RadarParams.SP.RDM.window_range, RadarParams.SP.RDM.window_metrics_range] = ...
create_window_with_metrics(RadarParams.SP.RDM.window_type_range, num_samples_range, 60);
if strcmpi(RadarParams.SP.RDM.window_type_doppler, 'none')
RadarParams.SP.RDM.window_doppler = ones(1, num_chirps_doppler);
elseif strcmpi(RadarParams.SP.RDM.window_type_doppler, 'hamming')
RadarParams.SP.RDM.window_doppler = hamming(num_chirps_doppler)';
elseif strcmpi(RadarParams.SP.RDM.window_type_doppler, 'blackman')
RadarParams.SP.RDM.window_doppler = blackman(num_chirps_doppler)';
elseif strcmpi(RadarParams.SP.RDM.window_type_doppler, 'hann')
RadarParams.SP.RDM.window_doppler = hann(num_chirps_doppler)';
else
RadarParams.SP.RDM.window_doppler = chebwin(num_chirps_doppler, 60)';
end
% Doppler
[RadarParams.SP.RDM.window_doppler, RadarParams.SP.RDM.window_metrics_doppler] = ...
create_window_with_metrics(RadarParams.SP.RDM.window_type_doppler, num_chirps_doppler, 60);
RadarParams.SP.CFAR.method = 'OS'; % 'CA' 'OS'
RadarParams.SP.CFAR.dimension = '2D'; % '1D' '2D'
@@ -140,7 +135,14 @@ RadarParams.SP.CFAR.train = [8, 8]; % [doppler, range] training cell 수
RadarParams.SP.CFAR.guard = [2, 2]; % [doppler, range] guard cell (1D면 )
RadarParams.SP.CFAR.rank = 0.75; % OS-CFAR rank (0~1)
RadarParams.SP.CFAR.os_scale = 15.0; % OS-CFAR
% 8) Secondary surface loss ( )
RadarParams.Antenna.SecondarySurfaceLoss_dB = 3;
% 9)
RadarParams.Coverage.R_max = 10; % (m)
RadarParams.Coverage.RCS_dBsm = 0; % RCS (dBsm)
%% 2. (TX )
% step 0. TX
@@ -205,15 +207,17 @@ adc_digital_expanded = reshape(adc_digital, [size(adc_digital,1), 1, size(adc_di
[range_data, r_axis] = process_range_fft_lpf(adc_digital_expanded, RadarParams);
% step 8. Range Profile (1 , 1 )
figure('Name', 'Range Profile with Ideal LPF');
plot(r_axis, 20*log10(abs(squeeze(range_data(1,1,1,:)))));
grid on; hold on;
xlabel('Range (m)');
ylabel('Magnitude (dB)');
title(['Range Profile (LPF Cut-off: ', num2str(RadarParams.Rxpath.fc_lpf/1e6), ' MHz)']);
if PlotToggle.range_profile
figure('Name', 'Range Profile with Ideal LPF');
plot(r_axis, 20*log10(abs(squeeze(range_data(1,1,1,:)))));
grid on; hold on;
xlabel('Range (m)');
ylabel('Magnitude (dB)');
title(['Range Profile (LPF Cut-off: ', num2str(RadarParams.Rxpath.fc_lpf/1e6), ' MHz)']);
% LPF
xline((RadarParams.Rxpath.fc_lpf * RadarParams.Basic.c)/(2*RadarParams.Waveform.Slope), '--r', 'LPF Cut-off');
% LPF
xline((RadarParams.Rxpath.fc_lpf * RadarParams.Basic.c)/(2*RadarParams.Waveform.Slope), '--r', 'LPF Cut-off');
end
% step 9. Doppler-FFT
[rd_cube, v_axis] = process_doppler_fft(range_data, RadarParams);
@@ -232,40 +236,71 @@ idx_single = (t <= T_chirp);
t_single = t(idx_single);
tx_mask_single = tx_mask(idx_single);
fig_single = visualize_tx_waveform(t_single, RadarParams.Waveform.Timing, RadarParams.Waveform.fc, RadarParams.Waveform.f_start, RadarParams.Waveform.Slope, tx_mask_single, RadarParams.Waveform.nonideal.peak_phase_error, RadarParams.Waveform.nonideal.f_ripple);
fig_single = [];
if PlotToggle.tx_single
fig_single = visualize_tx_waveform(t_single, RadarParams.Waveform.Timing, RadarParams.Waveform.fc, RadarParams.Waveform.f_start, RadarParams.Waveform.Slope, tx_mask_single, RadarParams.Waveform.nonideal.peak_phase_error, RadarParams.Waveform.nonideal.f_ripple);
end
% [Figure 2] 퀀 MIMO
% (: mimoMode와 NumTx )
fig_multi = visualize_multi_tx_waveform(t, RadarParams.Waveform.Timing, RadarParams.Waveform.fc, RadarParams.Waveform.f_start, RadarParams.Waveform.Slope, tx_mask, NumChirps, RadarParams.Waveform.mimoMode, NumTx);
fig_multi = [];
if PlotToggle.tx_multi
fig_multi = visualize_multi_tx_waveform(t, RadarParams.Waveform.Timing, RadarParams.Waveform.fc, RadarParams.Waveform.f_start, RadarParams.Waveform.Slope, tx_mask, NumChirps, RadarParams.Waveform.mimoMode, NumTx);
end
% --- [ Figure 3, 4: ] ---
% 'TX' 'RX' .
fig_tx_ant = visualize_antenna_pattern(TxPattern, 'TX');
fig_rx_ant = visualize_antenna_pattern(RxPattern, 'RX');
% [Figure 5] Range-Doppler Map (NumRx NCI RDM)
fig_rd_map = visualize_rd_map_with_spurs(target_rd_map, r_axis, v_axis, Target, RadarParams.SpurParams, RadarParams, RadarParams.Waveform.Slope);
% [Figure 6] CFAR (2D MAP )
fig_cfar = figure('Name', 'CFAR Detections on NCI RDM');
imagesc(r_axis, v_axis, 20*log10(abs(target_rd_map)));
axis xy;
colormap(jet);
colorbar;
xlabel('Range (m)');
ylabel('Velocity (m/s)');
title('CFAR Detections (Circle Markers)');
hold on;
if ~isempty(cfar_detections)
det_d_idx = cfar_detections(:,1); % Doppler bin index
det_r_idx = cfar_detections(:,2); % Range bin index
det_r = r_axis(det_r_idx);
det_v = v_axis(det_d_idx);
plot(det_r, det_v, 'wo', 'MarkerSize', 7, 'LineWidth', 1.5);
fig_tx_ant = [];
if PlotToggle.tx_antenna
fig_tx_ant = visualize_antenna_pattern(TxPattern, 'TX');
end
hold off;
fig_rx_ant = [];
if PlotToggle.rx_antenna
fig_rx_ant = visualize_antenna_pattern(RxPattern, 'RX');
end
% [Figure 5] Range-Doppler Map (NumRx NCI RDM)
fig_rd_map = [];
if PlotToggle.rd_map
fig_rd_map = visualize_rd_map_with_spurs(target_rd_map, r_axis, v_axis, Target, RadarParams.SpurParams, RadarParams, RadarParams.Waveform.Slope);
end
% [Figure 6] CFAR (2D MAP )
fig_cfar = [];
if PlotToggle.cfar
fig_cfar = figure('Name', 'CFAR Detections on NCI RDM');
imagesc(r_axis, v_axis, 20*log10(abs(target_rd_map)));
axis xy;
colormap(jet);
colorbar;
xlabel('Range (m)');
ylabel('Velocity (m/s)');
title('CFAR Detections (Circle Markers)');
hold on;
if ~isempty(cfar_detections)
det_d_idx = cfar_detections(:,1); % Doppler bin index
det_r_idx = cfar_detections(:,2); % Range bin index
det_r = r_axis(det_r_idx);
det_v = v_axis(det_d_idx);
plot(det_r, det_v, 'wo', 'MarkerSize', 7, 'LineWidth', 1.5);
end
hold off;
end
% ================= Coverage Analysis (Performance Analysis) =================
% [Figure 7] Angular Coverage : 2D SNR
% 100m, RCS 0 dBsm SNR
snr_coverage_2d = [];
azimuth_deg = [];
elevation_deg = [];
coverage_info = [];
fig_coverage = [];
if PlotToggle.coverage
[snr_coverage_2d, azimuth_deg, elevation_deg, coverage_info, fig_coverage] = analyze_coverage(RadarParams, RadarParams.Coverage.R_max, RadarParams.Coverage.RCS_dBsm);
end