radio plot cleanup
This commit is contained in:
+124
-49
@@ -43,12 +43,44 @@ function [f, fDist, R] = plotRadioLogs(resultsPath, G, tLim)
|
||||
|
||||
metricNames = ["SNR", "Power", "Quality", "PathLoss", "NoiseFloor", "FreqOffset"];
|
||||
yLabels = ["SNR (dB)", "Power (dB)", "Quality", "Path Loss (dB)", "Noise Floor (dB)", "Freq Offset (MHz)"];
|
||||
nMetrics = numel(metricNames);
|
||||
|
||||
% --- Time-based figure ---
|
||||
f = figure;
|
||||
tl = tiledlayout(numel(metricNames), 1, 'TileSpacing', 'compact', 'Padding', 'compact');
|
||||
tl = tiledlayout(nMetrics + 1, 1, 'TileSpacing', 'compact', 'Padding', 'compact');
|
||||
|
||||
for mi = 1:numel(metricNames)
|
||||
% Distance vs time tile (first)
|
||||
ax = nexttile(tl);
|
||||
hold(ax, 'on'); grid(ax, 'on');
|
||||
legendEntries = string.empty;
|
||||
ci = 1;
|
||||
if ~isempty(G)
|
||||
for rxIdx = 1:nUAV
|
||||
tbl = R{rxIdx};
|
||||
txIDs = unique(tbl.TxUAVID);
|
||||
for ti = 1:numel(txIDs)
|
||||
txID = txIDs(ti);
|
||||
rows = tbl(tbl.TxUAVID == txID, :);
|
||||
rows = rows(rows.Timestamp >= tLim(1) & rows.Timestamp <= tLim(2), :);
|
||||
if isempty(rows), continue; end
|
||||
[~, ia] = unique(rows.Timestamp);
|
||||
[radioPt, dist] = pairDist(rows(ia, :), G);
|
||||
if isempty(dist) || all(isnan(dist)), continue; end
|
||||
valid = ~isnan(dist);
|
||||
si = mod(ci - 1, numel(styles)) + 1;
|
||||
plot(ax, datetime(radioPt(valid), 'ConvertFrom', 'posixtime'), dist(valid), ...
|
||||
styles(si), 'Color', colors(ci, :), 'MarkerSize', 3, 'LineWidth', 0.5);
|
||||
legendEntries(end+1) = sprintf("TX %d → RX %d", txID, rows.RxUAVID(1)); %#ok<AGROW>
|
||||
ci = ci + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
ylabel(ax, 'Distance (m)');
|
||||
xlabel(ax, 'Time');
|
||||
legend(ax, legendEntries, 'Location', 'best');
|
||||
hold(ax, 'off');
|
||||
|
||||
for mi = 1:nMetrics
|
||||
ax = nexttile(tl);
|
||||
hold(ax, 'on');
|
||||
grid(ax, 'on');
|
||||
@@ -63,21 +95,30 @@ function [f, fDist, R] = plotRadioLogs(resultsPath, G, tLim)
|
||||
rows = tbl(tbl.TxUAVID == txID, :);
|
||||
rows = rows(rows.Timestamp >= tLim(1) & rows.Timestamp <= tLim(2), :);
|
||||
vals = rows.(metricNames(mi));
|
||||
valid = ~isnan(vals);
|
||||
rows = rows(valid, :);
|
||||
vals = vals(valid);
|
||||
|
||||
if isempty(rows) || all(isnan(vals))
|
||||
if isempty(rows)
|
||||
continue;
|
||||
end
|
||||
|
||||
si = mod(ci - 1, numel(styles)) + 1;
|
||||
plot(ax, rows.Timestamp, vals, styles(si), ...
|
||||
'Color', colors(ci, :), 'MarkerSize', 3, 'LineWidth', 1);
|
||||
'Color', colors(ci, :), 'MarkerSize', 3, 'LineWidth', 0.5);
|
||||
legendEntries(end+1) = sprintf("TX %d → RX %d", txID, tbl.RxUAVID(1)); %#ok<AGROW>
|
||||
|
||||
% Median per 1/3-second time bin
|
||||
[t_med, v_med] = timeBinMedian(posixtime(rows.Timestamp), vals, 1/3);
|
||||
plot(ax, datetime(t_med, 'ConvertFrom', 'posixtime'), v_med, '-', ...
|
||||
'Color', 'r', 'LineWidth', 2);
|
||||
legendEntries(end+1) = sprintf("TX %d → RX %d (median)", txID, tbl.RxUAVID(1)); %#ok<AGROW>
|
||||
ci = ci + 1;
|
||||
end
|
||||
end
|
||||
|
||||
ylabel(ax, yLabels(mi));
|
||||
if mi == numel(metricNames)
|
||||
if mi == nMetrics
|
||||
xlabel(ax, 'Time');
|
||||
end
|
||||
legend(ax, legendEntries, 'Location', 'best');
|
||||
@@ -93,9 +134,38 @@ function [f, fDist, R] = plotRadioLogs(resultsPath, G, tLim)
|
||||
return;
|
||||
end
|
||||
|
||||
tl2 = tiledlayout(numel(metricNames), 1, 'TileSpacing', 'compact', 'Padding', 'compact');
|
||||
tl2 = tiledlayout(nMetrics + 1, 1, 'TileSpacing', 'compact', 'Padding', 'compact');
|
||||
|
||||
for mi = 1:numel(metricNames)
|
||||
% Distance vs time tile (first)
|
||||
ax = nexttile(tl2);
|
||||
hold(ax, 'on'); grid(ax, 'on');
|
||||
legendEntries = string.empty;
|
||||
ci = 1;
|
||||
for rxIdx = 1:nUAV
|
||||
tbl = R{rxIdx};
|
||||
txIDs = unique(tbl.TxUAVID);
|
||||
for ti = 1:numel(txIDs)
|
||||
txID = txIDs(ti);
|
||||
rows = tbl(tbl.TxUAVID == txID, :);
|
||||
rows = rows(rows.Timestamp >= tLim(1) & rows.Timestamp <= tLim(2), :);
|
||||
if isempty(rows), continue; end
|
||||
[~, ia] = unique(rows.Timestamp);
|
||||
[radioPt, dist] = pairDist(rows(ia, :), G);
|
||||
if isempty(dist) || all(isnan(dist)), continue; end
|
||||
valid = ~isnan(dist);
|
||||
si = mod(ci - 1, numel(styles)) + 1;
|
||||
plot(ax, datetime(radioPt(valid), 'ConvertFrom', 'posixtime'), dist(valid), ...
|
||||
styles(si), 'Color', colors(ci, :), 'MarkerSize', 3, 'LineWidth', 0.5);
|
||||
legendEntries(end+1) = sprintf("TX %d → RX %d", txID, rows.RxUAVID(1)); %#ok<AGROW>
|
||||
ci = ci + 1;
|
||||
end
|
||||
end
|
||||
ylabel(ax, 'Distance (m)');
|
||||
xlabel(ax, 'Time');
|
||||
legend(ax, legendEntries, 'Location', 'best');
|
||||
hold(ax, 'off');
|
||||
|
||||
for mi = 1:nMetrics
|
||||
ax = nexttile(tl2);
|
||||
hold(ax, 'on');
|
||||
grid(ax, 'on');
|
||||
@@ -119,61 +189,39 @@ function [f, fDist, R] = plotRadioLogs(resultsPath, G, tLim)
|
||||
end
|
||||
|
||||
vals = rows.(metricNames(mi));
|
||||
if all(isnan(vals))
|
||||
valid = ~isnan(vals);
|
||||
rows = rows(valid, :);
|
||||
vals = vals(valid);
|
||||
|
||||
if isempty(rows)
|
||||
continue;
|
||||
end
|
||||
|
||||
% Map 0-based UAV IDs to 1-based GPS cell indices
|
||||
txGpsIdx = double(txID) + 1;
|
||||
rxGpsIdx = double(rows.RxUAVID(1)) + 1;
|
||||
[radioPt, dist] = pairDist(rows, G);
|
||||
if isempty(dist) || all(isnan(dist)), continue; end
|
||||
|
||||
if txGpsIdx > numel(G) || rxGpsIdx > numel(G)
|
||||
continue;
|
||||
end
|
||||
|
||||
Gtx = G{txGpsIdx};
|
||||
Grx = G{rxGpsIdx};
|
||||
|
||||
if ~ismember('East', Gtx.Properties.VariableNames) || ...
|
||||
~ismember('East', Grx.Properties.VariableNames)
|
||||
continue;
|
||||
end
|
||||
|
||||
% Strip timezone before posixtime so radio and GPS timestamps
|
||||
% are treated on the same scale (both are AERPAW wall-clock time)
|
||||
txTs = Gtx.Timestamp; txTs.TimeZone = '';
|
||||
rxTs = Grx.Timestamp; rxTs.TimeZone = '';
|
||||
txPt = posixtime(txTs);
|
||||
rxPt = posixtime(rxTs);
|
||||
radioPt = posixtime(rows.Timestamp);
|
||||
|
||||
% Interpolate GPS positions at radio measurement times.
|
||||
% Exclude NaN ENU entries (outside algorithm flight range).
|
||||
validTx = ~isnan(Gtx.East);
|
||||
validRx = ~isnan(Grx.East);
|
||||
|
||||
txE = interp1(txPt(validTx), Gtx.East(validTx), radioPt, 'linear', NaN);
|
||||
txN = interp1(txPt(validTx), Gtx.North(validTx), radioPt, 'linear', NaN);
|
||||
txU = interp1(txPt(validTx), Gtx.Up(validTx), radioPt, 'linear', NaN);
|
||||
rxE = interp1(rxPt(validRx), Grx.East(validRx), radioPt, 'linear', NaN);
|
||||
rxN = interp1(rxPt(validRx), Grx.North(validRx), radioPt, 'linear', NaN);
|
||||
rxU = interp1(rxPt(validRx), Grx.Up(validRx), radioPt, 'linear', NaN);
|
||||
|
||||
dist = vecnorm([txE - rxE, txN - rxN, txU - rxU], 2, 2);
|
||||
|
||||
if all(isnan(dist))
|
||||
continue;
|
||||
end
|
||||
% Drop points where GPS interpolation returned NaN
|
||||
validDist = ~isnan(dist);
|
||||
rowTs = radioPt(validDist);
|
||||
dist = dist(validDist);
|
||||
vals = vals(validDist);
|
||||
|
||||
si = mod(ci - 1, numel(styles)) + 1;
|
||||
scatter(ax, dist, vals, 9, colors(ci, :), strrep(styles(si), "-", ""), 'filled');
|
||||
legendEntries(end+1) = sprintf("TX %d → RX %d", txID, rows.RxUAVID(1)); %#ok<AGROW>
|
||||
|
||||
% Median per 1/3-second time bin, plotted against median distance
|
||||
[~, dv_med] = timeBinMedian(rowTs, [dist, vals], 1/3);
|
||||
[d_med, si_sort] = sort(dv_med(:, 1));
|
||||
v_med = dv_med(si_sort, 2);
|
||||
plot(ax, d_med, v_med, '-', 'Color', 'r', 'LineWidth', 2);
|
||||
legendEntries(end+1) = sprintf("TX %d → RX %d (median)", txID, rows.RxUAVID(1)); %#ok<AGROW>
|
||||
ci = ci + 1;
|
||||
end
|
||||
end
|
||||
|
||||
ylabel(ax, yLabels(mi));
|
||||
if mi == numel(metricNames)
|
||||
if mi == nMetrics
|
||||
xlabel(ax, 'Distance (m)');
|
||||
end
|
||||
legend(ax, legendEntries, 'Location', 'best');
|
||||
@@ -182,3 +230,30 @@ function [f, fDist, R] = plotRadioLogs(resultsPath, G, tLim)
|
||||
|
||||
title(tl2, 'Radio Channel Metrics vs Distance');
|
||||
end
|
||||
|
||||
|
||||
function [radioPt, dist] = pairDist(rows, G)
|
||||
% Interpolate GPS-based inter-UAV distance at each row's timestamp.
|
||||
radioPt = []; dist = [];
|
||||
txGpsIdx = double(rows.TxUAVID(1)) + 1;
|
||||
rxGpsIdx = double(rows.RxUAVID(1)) + 1;
|
||||
if txGpsIdx > numel(G) || rxGpsIdx > numel(G), return; end
|
||||
Gtx = G{txGpsIdx};
|
||||
Grx = G{rxGpsIdx};
|
||||
if ~ismember('East', Gtx.Properties.VariableNames) || ...
|
||||
~ismember('East', Grx.Properties.VariableNames), return; end
|
||||
txTs = Gtx.Timestamp; txTs.TimeZone = '';
|
||||
rxTs = Grx.Timestamp; rxTs.TimeZone = '';
|
||||
txPt = posixtime(txTs);
|
||||
rxPt = posixtime(rxTs);
|
||||
radioPt = posixtime(rows.Timestamp);
|
||||
validTx = ~isnan(Gtx.East);
|
||||
validRx = ~isnan(Grx.East);
|
||||
txE = interp1(txPt(validTx), Gtx.East(validTx), radioPt, 'linear', NaN);
|
||||
txN = interp1(txPt(validTx), Gtx.North(validTx), radioPt, 'linear', NaN);
|
||||
txU = interp1(txPt(validTx), Gtx.Up(validTx), radioPt, 'linear', NaN);
|
||||
rxE = interp1(rxPt(validRx), Grx.East(validRx), radioPt, 'linear', NaN);
|
||||
rxN = interp1(rxPt(validRx), Grx.North(validRx), radioPt, 'linear', NaN);
|
||||
rxU = interp1(rxPt(validRx), Grx.Up(validRx), radioPt, 'linear', NaN);
|
||||
dist = vecnorm([txE - rxE, txN - rxN, txU - rxU], 2, 2);
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user