From c4a98fc313006ce62b01cf419713d0ee11f61d98 Mon Sep 17 00:00:00 2001 From: yeokwanggoo-stack Date: Thu, 5 Mar 2026 20:01:31 +0900 Subject: [PATCH 1/3] =?UTF-8?q?1)=20Figure=20=EC=B0=BD=20on/off=20?= =?UTF-8?q?=ED=86=A0=EA=B8=80=20=EC=B6=94=EA=B0=80=202)=20Coverage=20?= =?UTF-8?q?=EB=B6=84=EC=84=9D=20=EB=8F=84=EA=B5=AC=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?-=20=EC=B6=94=EA=B0=80=20=EA=B2=80=EC=A6=9D=20=ED=95=84?= =?UTF-8?q?=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 05. Performance Analysis/analyze_coverage.m | 324 ++++++++++++++++++++ Main.m | 112 +++++-- 2 files changed, 402 insertions(+), 34 deletions(-) create mode 100644 05. Performance Analysis/analyze_coverage.m diff --git a/05. Performance Analysis/analyze_coverage.m b/05. Performance Analysis/analyze_coverage.m new file mode 100644 index 0000000..b111860 --- /dev/null +++ b/05. Performance Analysis/analyze_coverage.m @@ -0,0 +1,324 @@ +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); + + % SNR 계산: 벡터화 연산 (스칼라 항 × 2D 배열) + p_rx_w = (ptx_w * g_tx_linear_grid .* g_rx_linear_grid * (lambda^2) * rcs_target) / ((path_loss_factor^2) * (4 * pi)); + 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 + 1D Cut 시각화 + fig = figure('Name', 'Coverage Analysis - 3D + 1D Cut'); + set(fig, 'Position', [100, 100, 1600, 900]); + + % Subplot 1: x-SNR Cut (elevation=0) + subplot(2, 3, 1); + x_cut_m = target_range_m .* cosd(azimuth_deg); + plot(x_cut_m, snr_az_cut, 'b-', 'LineWidth', 2); + hold on; + [~, max_idx_az] = max(snr_az_cut); + plot(x_cut_m(max_idx_az), snr_az_cut(max_idx_az), 'r*', 'MarkerSize', 15, 'LineWidth', 2); + grid on; + xlabel('x (m)'); + ylabel('SNR (dB)'); + title('1D CUT: elevation = 0 deg'); + hold off; + + % Subplot 2: z-SNR Cut (azimuth=0) + subplot(2, 3, 2); + z_cut_m = target_range_m .* sind(elevation_deg); + plot(z_cut_m, snr_el_cut, 'g-', 'LineWidth', 2); + hold on; + [~, max_idx_el] = max(snr_el_cut); + plot(z_cut_m(max_idx_el), snr_el_cut(max_idx_el), 'r*', 'MarkerSize', 15, 'LineWidth', 2); + grid on; + xlabel('z (m)'); + ylabel('SNR (dB)'); + title('1D CUT: azimuth = 0 deg'); + hold off; + + % Subplot 3: x-y top view (color = SNR at elevation=0) + subplot(2, 3, 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 (elevation = 0 deg)'); + + % Subplot 4-5: 3D Surface (geometry = x,y,z, color = SNR) + subplot(2, 3, 4:5, 'replace'); + 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 (Click to inspect)'); + 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; + + % Datacursor mode 활성화 (클릭 기능) + dcm_obj = datacursormode(fig); + dcm_obj.Enable = 'on'; + set(dcm_obj, 'UpdateFcn', @datatip_update_callback); + set(dcm_obj, 'SnapToDataVertex', 'on'); + + % Help text + fprintf('\n>>> Data Cursor Mode ENABLED <<<\n'); + fprintf('Instructions:\n'); + fprintf(' 1. Left-click on the 3D surface to inspect points\n'); + fprintf(' 2. Tooltip shows x, y, z (m) and SNR[dB]\n'); + fprintf(' 3. Press Escape or click "Disable Data Cursor" in Figure Tools to deactivate\n\n'); + + %% Nested function for datacursor callback + function output_txt = datatip_update_callback(~, event) + pos = event.Position; + 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); + + output_txt = { + ['x: ' num2str(x_val, '%.3f') ' m'] + ['y: ' num2str(y_val, '%.3f') ' m'] + ['z: ' num2str(z_val, '%.3f') ' m'] + ['SNR[dB]: ' num2str(snr_val, '%.3f')] + }; + end + + % Subplot 6: x-z side view (azimuth=0, color=SNR) + subplot(2, 3, 6); + 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 (azimuth = 0 deg)'); + + % 전체 타이틀 + 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); + + %% 통계 출력 + 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('\n1D CUT Statistics:\n'); + fprintf(' - elevation=0 cut: Max=%.2f dB, Min=%.2f dB\n', max(snr_az_cut), min(snr_az_cut)); + fprintf(' - azimuth=0 cut: Max=%.2f dB, Min=%.2f dB\n', max(snr_el_cut), min(snr_el_cut)); + 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 diff --git a/Main.m b/Main.m index 4c21b9c..6b44b0a 100644 --- a/Main.m +++ b/Main.m @@ -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) 기본 물리 파라미터 @@ -205,15 +216,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 +245,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, 10, 0); +end From e002d08580fb4b1b35ed2f3b17aef88164e712ae Mon Sep 17 00:00:00 2001 From: yeokwanggoo-stack Date: Thu, 5 Mar 2026 21:22:21 +0900 Subject: [PATCH 2/3] git ssh test. --- Main.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Main.m b/Main.m index 6b44b0a..eb3a5a7 100644 --- a/Main.m +++ b/Main.m @@ -308,7 +308,7 @@ elevation_deg = []; coverage_info = []; fig_coverage = []; if PlotToggle.coverage - [snr_coverage_2d, azimuth_deg, elevation_deg, coverage_info, fig_coverage] = analyze_coverage(RadarParams, 10, 0); + [snr_coverage_2d, azimuth_deg, elevation_deg, coverage_info, fig_coverage] = analyze_coverage(RadarParams, 20, 0); end From 3c05d2be0905a797f3db3aa0ad34b11d6bbe6769 Mon Sep 17 00:00:00 2001 From: yeokwanggoo-stack Date: Fri, 6 Mar 2026 06:48:07 +0900 Subject: [PATCH 3/3] =?UTF-8?q?1.=20SNR=20=EC=8B=A0=ED=98=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC,=20Secondary=20surface=20=EC=98=81=ED=96=A5=20?= =?UTF-8?q?=EA=B3=A0=EB=A0=A4=ED=95=98=EC=97=AC=20SNR=20=EC=9E=AC=EA=B3=84?= =?UTF-8?q?=EC=82=B0=202.=20Coverage=20Figure=20=EA=B0=9C=EC=84=A0=203.=20?= =?UTF-8?q?=EC=9C=88=EB=8F=84=EC=9A=B0=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20?= =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EA=B3=84=EC=82=B0=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../create_window_with_metrics.m | 118 ++++++++++ 04. Signal Processing/process_doppler_fft.m | 17 +- 04. Signal Processing/process_range_fft_lpf.m | 16 +- 05. Performance Analysis/analyze_coverage.m | 203 +++++++++++------- Main.m | 39 ++-- 5 files changed, 270 insertions(+), 123 deletions(-) create mode 100644 04. Signal Processing/create_window_with_metrics.m diff --git a/04. Signal Processing/create_window_with_metrics.m b/04. Signal Processing/create_window_with_metrics.m new file mode 100644 index 0000000..5577ad7 --- /dev/null +++ b/04. Signal Processing/create_window_with_metrics.m @@ -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 diff --git a/04. Signal Processing/process_doppler_fft.m b/04. Signal Processing/process_doppler_fft.m index da810ca..8f51503 100644 --- a/04. Signal Processing/process_doppler_fft.m +++ b/04. Signal Processing/process_doppler_fft.m @@ -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]); diff --git a/04. Signal Processing/process_range_fft_lpf.m b/04. Signal Processing/process_range_fft_lpf.m index 8176a86..8b05606 100644 --- a/04. Signal Processing/process_range_fft_lpf.m +++ b/04. Signal Processing/process_range_fft_lpf.m @@ -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]); diff --git a/05. Performance Analysis/analyze_coverage.m b/05. Performance Analysis/analyze_coverage.m index b111860..670df3e 100644 --- a/05. Performance Analysis/analyze_coverage.m +++ b/05. Performance Analysis/analyze_coverage.m @@ -150,9 +150,29 @@ function [snr_coverage_2d, azimuth_deg, elevation_deg, coverage_info, fig] = ana % 이득을 선형으로 변환 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 * g_tx_linear_grid .* g_rx_linear_grid * (lambda^2) * rcs_target) / ((path_loss_factor^2) * (4 * pi)); + 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)); @@ -194,38 +214,18 @@ function [snr_coverage_2d, azimuth_deg, elevation_deg, coverage_info, fig] = ana 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 + 1D Cut 시각화 - fig = figure('Name', 'Coverage Analysis - 3D + 1D Cut'); + %% 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-SNR Cut (elevation=0) - subplot(2, 3, 1); - x_cut_m = target_range_m .* cosd(azimuth_deg); - plot(x_cut_m, snr_az_cut, 'b-', 'LineWidth', 2); - hold on; - [~, max_idx_az] = max(snr_az_cut); - plot(x_cut_m(max_idx_az), snr_az_cut(max_idx_az), 'r*', 'MarkerSize', 15, 'LineWidth', 2); - grid on; - xlabel('x (m)'); - ylabel('SNR (dB)'); - title('1D CUT: elevation = 0 deg'); - hold off; - - % Subplot 2: z-SNR Cut (azimuth=0) - subplot(2, 3, 2); - z_cut_m = target_range_m .* sind(elevation_deg); - plot(z_cut_m, snr_el_cut, 'g-', 'LineWidth', 2); - hold on; - [~, max_idx_el] = max(snr_el_cut); - plot(z_cut_m(max_idx_el), snr_el_cut(max_idx_el), 'r*', 'MarkerSize', 15, 'LineWidth', 2); - grid on; - xlabel('z (m)'); - ylabel('SNR (dB)'); - title('1D CUT: azimuth = 0 deg'); - hold off; - - % Subplot 3: x-y top view (color = SNR at elevation=0) - subplot(2, 3, 3); + % 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'); @@ -235,10 +235,10 @@ function [snr_coverage_2d, azimuth_deg, elevation_deg, coverage_info, fig] = ana ylabel(cb, 'SNR (dB)'); xlabel('x (m)'); ylabel('y (m)'); - title('Top View (elevation = 0 deg)'); + title('Top View (Azimuth)'); - % Subplot 4-5: 3D Surface (geometry = x,y,z, color = SNR) - subplot(2, 3, 4:5, 'replace'); + % 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; @@ -246,48 +246,15 @@ function [snr_coverage_2d, azimuth_deg, elevation_deg, coverage_info, fig] = ana xlabel('x (m)'); ylabel('y (m)'); zlabel('z (m)'); - title('3D Coverage in Cartesian Coordinates (Click to inspect)'); + 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; - % Datacursor mode 활성화 (클릭 기능) - dcm_obj = datacursormode(fig); - dcm_obj.Enable = 'on'; - set(dcm_obj, 'UpdateFcn', @datatip_update_callback); - set(dcm_obj, 'SnapToDataVertex', 'on'); - - % Help text - fprintf('\n>>> Data Cursor Mode ENABLED <<<\n'); - fprintf('Instructions:\n'); - fprintf(' 1. Left-click on the 3D surface to inspect points\n'); - fprintf(' 2. Tooltip shows x, y, z (m) and SNR[dB]\n'); - fprintf(' 3. Press Escape or click "Disable Data Cursor" in Figure Tools to deactivate\n\n'); - - %% Nested function for datacursor callback - function output_txt = datatip_update_callback(~, event) - pos = event.Position; - 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); - - output_txt = { - ['x: ' num2str(x_val, '%.3f') ' m'] - ['y: ' num2str(y_val, '%.3f') ' m'] - ['z: ' num2str(z_val, '%.3f') ' m'] - ['SNR[dB]: ' num2str(snr_val, '%.3f')] - }; - end - - % Subplot 6: x-z side view (azimuth=0, color=SNR) - subplot(2, 3, 6); + % 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'); @@ -296,12 +263,31 @@ function [snr_coverage_2d, azimuth_deg, elevation_deg, coverage_info, fig] = ana ylabel(cb_side, 'SNR (dB)'); xlabel('x (m)'); ylabel('z (m)'); - title('Side View (azimuth = 0 deg)'); + 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'); @@ -313,12 +299,81 @@ function [snr_coverage_2d, azimuth_deg, elevation_deg, coverage_info, fig] = ana 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('\n1D CUT Statistics:\n'); - fprintf(' - elevation=0 cut: Max=%.2f dB, Min=%.2f dB\n', max(snr_az_cut), min(snr_az_cut)); - fprintf(' - azimuth=0 cut: Max=%.2f dB, Min=%.2f dB\n', max(snr_el_cut), min(snr_el_cut)); 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 diff --git a/Main.m b/Main.m index eb3a5a7..89f6ed1 100644 --- a/Main.m +++ b/Main.m @@ -119,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' @@ -151,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 마스크 생성 @@ -308,7 +299,7 @@ elevation_deg = []; coverage_info = []; fig_coverage = []; if PlotToggle.coverage - [snr_coverage_2d, azimuth_deg, elevation_deg, coverage_info, fig_coverage] = analyze_coverage(RadarParams, 20, 0); + [snr_coverage_2d, azimuth_deg, elevation_deg, coverage_info, fig_coverage] = analyze_coverage(RadarParams, RadarParams.Coverage.R_max, RadarParams.Coverage.RCS_dBsm); end