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 new file mode 100644 index 0000000..670df3e --- /dev/null +++ b/05. Performance Analysis/analyze_coverage.m @@ -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 diff --git a/Main.m b/Main.m index 4c21b9c..89f6ed1 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) 기본 물리 파라미터 @@ -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