176 lines
7.0 KiB
Matlab
176 lines
7.0 KiB
Matlab
function fig = visualize_rd_map_with_spurs(target_rd_map, r_axis, v_axis, Target, SpurParams, RadarParams, Slope)
|
|
% 2D Range-Doppler 맵을 시각화하고 스퍼 및 타겟 위치를 표시합니다.
|
|
%
|
|
% 입력:
|
|
% - target_rd_map: 2D RD 맵 데이터 [Doppler x Range]
|
|
% - r_axis: 거리 축 벡터
|
|
% - v_axis: 속도 축 벡터
|
|
% - Target: 타겟 정보 구조체 (R, v, NumTargets 필드 포함)
|
|
% - SpurParams: 스퍼 파라미터 구조체 (lo, adc, mixer, pulse, lo_leak, clip 등)
|
|
% - RadarParams: 레이더 파라미터 구조체
|
|
% - Slope: 스윕 속도 (Hz/s)
|
|
%
|
|
% 출력:
|
|
% - fig: 생성된 figure 핸들
|
|
|
|
if ~isfield(RadarParams, 'SP') || ~isfield(RadarParams.SP, 'RDM') || ...
|
|
~isfield(RadarParams.SP.RDM, 'window_range') || ~isfield(RadarParams.SP.RDM, 'window_doppler')
|
|
error('RadarParams.SP.RDM.window_range / window_doppler 정보가 필요합니다.');
|
|
end
|
|
|
|
win_r = RadarParams.SP.RDM.window_range;
|
|
win_d = RadarParams.SP.RDM.window_doppler;
|
|
|
|
normal_factor = RadarParams.Rxpath.fc_lpf * RadarParams.Waveform.PRF * sum(win_r.^2) * sum(win_d.^2);
|
|
|
|
if ~isfield(RadarParams, 'Rxpath') || ~isfield(RadarParams.Rxpath, 'load_impedance_ohm')
|
|
error('RadarParams.Rxpath.load_impedance_ohm 값이 필요합니다.');
|
|
end
|
|
load_impedance_ohm = RadarParams.Rxpath.load_impedance_ohm;
|
|
|
|
psd2d_w_hz2 = (abs(target_rd_map).^2) / (load_impedance_ohm * max(normal_factor, eps));
|
|
rbw_range_hz = RadarParams.Waveform.fs_adc * sum(win_r.^2) / max(eps, (sum(win_r)^2));
|
|
rbw_doppler_hz = RadarParams.Waveform.PRF * sum(win_d.^2) / max(eps, (sum(win_d)^2));
|
|
rd_map_dbm_bin = 10*log10(max(psd2d_w_hz2, realmin) / 1e-3) + 10*log10(max(rbw_range_hz * rbw_doppler_hz, eps));
|
|
|
|
fig = figure('Name', '2D Range-Doppler Map');
|
|
imagesc(r_axis, v_axis, rd_map_dbm_bin);
|
|
axis xy; % y축 방향(속도)을 위로 정렬
|
|
cb = colorbar;
|
|
cb.Label.String = 'Power (dBm/bin)';
|
|
xlabel('Range (m)');
|
|
ylabel('Velocity (m/s)');
|
|
title('Range-Doppler Map (dBm/bin)');
|
|
colormap(jet);
|
|
|
|
% 범례용 핸들 및 레이블 수집
|
|
hLines = gobjects(0);
|
|
hLabels = {};
|
|
|
|
% --- 1. 실제 타겟 위치 표시 ---
|
|
hold on;
|
|
hTarget = gobjects(0);
|
|
if exist('Target','var') && isstruct(Target)
|
|
for k = 1:Target.NumTargets
|
|
% 범위/속도 축 내에 있는지 확인
|
|
if Target.R(k) < min(r_axis) || Target.R(k) > max(r_axis) || ...
|
|
Target.v(k) < min(v_axis) || Target.v(k) > max(v_axis)
|
|
continue; % 지도 밖이면 생략
|
|
end
|
|
[~, ix] = min(abs(r_axis - Target.R(k)));
|
|
[~, iy] = min(abs(v_axis - Target.v(k)));
|
|
hTarget(end+1) = plot(r_axis(ix), v_axis(iy), 'ro', 'MarkerSize',8, 'LineWidth',1.5);
|
|
text(r_axis(ix), v_axis(iy), sprintf(' T%d', k), 'Color','r','FontSize',8);
|
|
end
|
|
end
|
|
hold off;
|
|
|
|
% 범례에 타겟 추가
|
|
if exist('hTarget','var') && ~isempty(hTarget)
|
|
hLines(end+1) = hTarget(1);
|
|
hLabels{end+1} = 'actual target';
|
|
end
|
|
|
|
% --- nonideal/spur 오버레이 표시 여부 판단 ---
|
|
show_nonideal_overlay = true;
|
|
if exist('SpurParams','var') && isstruct(SpurParams) && isfield(SpurParams,'enabled')
|
|
show_nonideal_overlay = logical(SpurParams.enabled);
|
|
end
|
|
|
|
if exist('RadarParams','var') && isstruct(RadarParams) && ...
|
|
isfield(RadarParams,'Waveform') && isfield(RadarParams.Waveform,'nonideal')
|
|
ni = RadarParams.Waveform.nonideal;
|
|
enable_phase_noise = false;
|
|
enable_nonlinearity = false;
|
|
|
|
if isfield(ni,'enable_phase_noise')
|
|
enable_phase_noise = logical(ni.enable_phase_noise);
|
|
end
|
|
if isfield(ni,'enable_nonlinearity')
|
|
enable_nonlinearity = logical(ni.enable_nonlinearity);
|
|
end
|
|
show_nonideal_overlay = show_nonideal_overlay && ...
|
|
(enable_phase_noise || enable_nonlinearity);
|
|
end
|
|
|
|
% --- 2. LO/ADC 톤 스퍼 표시 ---
|
|
if show_nonideal_overlay && exist('SpurParams','var') && isstruct(SpurParams)
|
|
spur_freqs = [];
|
|
spur_names = {};
|
|
|
|
if isfield(SpurParams,'lo') && ~isempty(SpurParams.lo)
|
|
spur_freqs(end+1) = SpurParams.lo.freq;
|
|
spur_names{end+1} = 'LO spur';
|
|
end
|
|
if isfield(SpurParams,'adc') && ~isempty(SpurParams.adc)
|
|
spur_freqs(end+1) = SpurParams.adc.freq;
|
|
spur_names{end+1} = 'ADC spur';
|
|
end
|
|
|
|
hold on;
|
|
for idx = 1:length(spur_freqs)
|
|
f = spur_freqs(idx);
|
|
name = sprintf('%s @ %.2f MHz', spur_names{idx}, f/1e6);
|
|
r_spur = (RadarParams.Basic.c * f) / (2 * Slope);
|
|
hLines(end+1) = xline(r_spur, '--m', name);
|
|
hLabels{end+1} = name;
|
|
end
|
|
|
|
% --- 3. Pulse comb 스퍼 표시 ---
|
|
if isfield(SpurParams,'pulse') && ~isempty(SpurParams.pulse)
|
|
p = SpurParams.pulse;
|
|
num_harm = 5;
|
|
for n = 1:num_harm
|
|
f_n = n * p.freq;
|
|
r_n = (RadarParams.Basic.c * f_n) / (2 * Slope);
|
|
if r_n <= max(r_axis)
|
|
lbl = sprintf('pulse comb %d', n);
|
|
hLines(end+1) = xline(r_n, '--g', lbl);
|
|
hLabels{end+1} = lbl;
|
|
end
|
|
end
|
|
end
|
|
|
|
% --- 4. Mixer 비선형 스퍼 표시 ---
|
|
if isfield(SpurParams,'mixer') && ~isempty(SpurParams.mixer) && exist('Target','var')
|
|
base_ranges = Target.R;
|
|
harmonics = [2,3];
|
|
colors = {'--c','--y'};
|
|
for hi = 1:length(harmonics)
|
|
mul = harmonics(hi);
|
|
for r0 = base_ranges
|
|
r_spur2 = r0 * mul;
|
|
if r_spur2 <= max(r_axis)
|
|
lbl = sprintf('mixer x%d', mul);
|
|
hLines(end+1) = xline(r_spur2, colors{hi}, lbl);
|
|
hLabels{end+1} = lbl;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
% --- 5. DC offset / LO leakage 표시 ---
|
|
if isfield(SpurParams,'lo_leak') && ~isempty(SpurParams.lo_leak)
|
|
hLines(end+1) = line([min(r_axis), max(r_axis)], [0, 0], 'Color','r','LineStyle',':', 'LineWidth',1.5);
|
|
hLabels{end+1} = 'DC offset/LO leak';
|
|
end
|
|
|
|
% --- 6. Clipping spur 표시 ---
|
|
if isfield(SpurParams,'clip') && ~isempty(SpurParams.clip)
|
|
r_clip = SpurParams.clip.range;
|
|
if r_clip >= min(r_axis) && r_clip <= max(r_axis)
|
|
hLines(end+1) = line([r_clip, r_clip], [min(v_axis), max(v_axis)], 'Color', 'r', 'LineStyle', ':', 'LineWidth', 1.2);
|
|
hLabels{end+1} = sprintf('clipping @ %.1f m', r_clip);
|
|
end
|
|
end
|
|
|
|
hold off;
|
|
end
|
|
|
|
% --- 7. 범례 추가 ---
|
|
if ~isempty(hLines)
|
|
legend(hLines, hLabels, 'Location', 'northeastoutside');
|
|
end
|
|
|
|
end
|